mirror of
https://github.com/appwrite/console.git
synced 2026-06-06 19:27:48 +00:00
Merge branch 'main' of https://github.com/appwrite/appwrite-console-poc into feat-trim-text-tooltip
This commit is contained in:
Generated
+301
-997
File diff suppressed because it is too large
Load Diff
+2
-2
@@ -28,8 +28,8 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@playwright/test": "^1.26.1",
|
||||
"@sveltejs/adapter-auto": "1.0.0-next.80",
|
||||
"@sveltejs/kit": "1.0.0-next.509",
|
||||
"@sveltejs/adapter-static": "1.0.0-next.44",
|
||||
"@sveltejs/kit": "1.0.0-next.512",
|
||||
"@sveltejs/vite-plugin-svelte": "^1.0.8",
|
||||
"@testing-library/dom": "^8.18.1",
|
||||
"@testing-library/jest-dom": "^5.16.5",
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
<article class="card common-section">
|
||||
<script lang="ts">
|
||||
export let isTile = false;
|
||||
</script>
|
||||
|
||||
<article class="card" class:common-section={!isTile}>
|
||||
<slot />
|
||||
</article>
|
||||
|
||||
@@ -34,3 +34,5 @@ export { default as Json } from './json.svelte';
|
||||
export { default as CustomId } from './customId.svelte';
|
||||
export { default as Secret } from './secret.svelte';
|
||||
export { default as Trim } from './trim.svelte';
|
||||
export { default as Tabs } from './tabs.svelte';
|
||||
export { default as Tab } from './tab.svelte';
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
<script lang="ts">
|
||||
export let selected = false;
|
||||
export let href: string = null;
|
||||
</script>
|
||||
|
||||
<li class="tabs-item">
|
||||
{#if href}
|
||||
<a class="tabs-button" {href} class:is-selected={selected}>
|
||||
<span class="text"><slot /></span>
|
||||
</a>
|
||||
{:else}
|
||||
<button class="tabs-button" on:click|preventDefault class:is-selected={selected}>
|
||||
<span class="text"><slot /></span>
|
||||
</button>
|
||||
{/if}
|
||||
</li>
|
||||
@@ -0,0 +1,70 @@
|
||||
<script lang="ts">
|
||||
let showLeft = false;
|
||||
let showRight = false;
|
||||
let tabsList: HTMLUListElement;
|
||||
|
||||
const slide = (direction: 'left' | 'right') => {
|
||||
let scrollCompleted = 0;
|
||||
let slideVar = setInterval(() => {
|
||||
if (direction == 'left') {
|
||||
tabsList.scrollLeft -= 10;
|
||||
} else {
|
||||
tabsList.scrollLeft += 10;
|
||||
}
|
||||
scrollCompleted += 10;
|
||||
if (scrollCompleted >= 100) {
|
||||
clearInterval(slideVar);
|
||||
}
|
||||
}, 10);
|
||||
};
|
||||
|
||||
//TODO: implement this directly into onScroll
|
||||
const throttle = (fn: () => void, delay: number) => {
|
||||
let timeout = false;
|
||||
return () => {
|
||||
if (!timeout) {
|
||||
timeout = true;
|
||||
fn.apply(this);
|
||||
setTimeout(() => {
|
||||
timeout = false;
|
||||
}, delay);
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
const onScroll = () => {
|
||||
if (!tabsList) {
|
||||
return;
|
||||
}
|
||||
const { offsetWidth, scrollLeft, scrollWidth } = tabsList;
|
||||
showLeft = scrollLeft > 10;
|
||||
showRight = scrollLeft < scrollWidth - offsetWidth - 10;
|
||||
};
|
||||
</script>
|
||||
|
||||
<svelte:window on:resize={throttle(onScroll, 25)} />
|
||||
|
||||
<div class="tabs">
|
||||
{#if showLeft}
|
||||
<button
|
||||
class="tabs-button-scroll is-start"
|
||||
aria-label="Show items in start side"
|
||||
on:click={() => slide('left')}>
|
||||
<span class="icon-cheveron-left" aria-hidden="true" />
|
||||
</button>
|
||||
{/if}
|
||||
{#if showRight}
|
||||
<button
|
||||
class="tabs-button-scroll is-end"
|
||||
aria-label="Show items in end side"
|
||||
on:click={() => slide('right')}>
|
||||
<span class="icon-cheveron-right" aria-hidden="true" />
|
||||
</button>
|
||||
{/if}
|
||||
<ul
|
||||
class="tabs-list scroll-shadow-horizontal"
|
||||
bind:this={tabsList}
|
||||
on:scroll={throttle(onScroll, 25)}>
|
||||
<slot />
|
||||
</ul>
|
||||
</div>
|
||||
@@ -1,3 +1,3 @@
|
||||
<ul class="grid-box common-section">
|
||||
<ul class="grid-box common-section" style="--grid-item-size:25rem;">
|
||||
<slot />
|
||||
</ul>
|
||||
|
||||
@@ -10,3 +10,4 @@ export { default as Wizard } from './wizard.svelte';
|
||||
export { default as WizardStep } from './wizardStep.svelte';
|
||||
export { default as Breadcrumbs } from './breadcrumbs.svelte';
|
||||
export { default as Unauthenticated } from './unauthenticated.svelte';
|
||||
export { default as Usage, type UsagePeriods } from './usage.svelte';
|
||||
|
||||
+17
-75
@@ -2,7 +2,15 @@
|
||||
import { navigating, page } from '$app/stores';
|
||||
import { tabs, title, backButton, copyData, titleDropdown } from '$lib/stores/layout';
|
||||
import { Cover } from '.';
|
||||
import { Copy, DropList, DropListItem, DropListLink, AvatarGroup } from '$lib/components';
|
||||
import {
|
||||
Copy,
|
||||
DropList,
|
||||
DropListItem,
|
||||
DropListLink,
|
||||
AvatarGroup,
|
||||
Tabs,
|
||||
Tab
|
||||
} from '$lib/components';
|
||||
import { Pill } from '$lib/elements';
|
||||
import { Button } from '$lib/elements/forms';
|
||||
import {
|
||||
@@ -22,29 +30,12 @@
|
||||
export let showSideNavigation = false;
|
||||
|
||||
let y: number;
|
||||
let tabsList: HTMLUListElement;
|
||||
let showLeft = false;
|
||||
let showRight = false;
|
||||
let showDropdown = false;
|
||||
|
||||
navigating.subscribe(() => {
|
||||
if (isOpen) isOpen = false;
|
||||
});
|
||||
|
||||
const slide = (direction: 'left' | 'right') => {
|
||||
let scrollCompleted = 0;
|
||||
let slideVar = setInterval(() => {
|
||||
if (direction == 'left') {
|
||||
tabsList.scrollLeft -= 10;
|
||||
} else {
|
||||
tabsList.scrollLeft += 10;
|
||||
}
|
||||
scrollCompleted += 10;
|
||||
if (scrollCompleted >= 100) {
|
||||
clearInterval(slideVar);
|
||||
}
|
||||
}, 10);
|
||||
};
|
||||
let avatars = [];
|
||||
let avatarsTotal = 0;
|
||||
|
||||
@@ -65,29 +56,6 @@
|
||||
await goto(`${base}/login`);
|
||||
};
|
||||
|
||||
const onScroll = () => {
|
||||
if (!tabsList) {
|
||||
return;
|
||||
}
|
||||
const { offsetWidth, scrollLeft, scrollWidth } = tabsList;
|
||||
showLeft = scrollLeft > 10;
|
||||
showRight = scrollLeft < scrollWidth - offsetWidth - 10;
|
||||
};
|
||||
|
||||
//TODO: implement this directly into onScroll
|
||||
const throttle = (fn: () => void, delay: number) => {
|
||||
let timeout = false;
|
||||
return () => {
|
||||
if (!timeout) {
|
||||
timeout = true;
|
||||
fn.apply(this);
|
||||
setTimeout(() => {
|
||||
timeout = false;
|
||||
}, delay);
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
const toggleMenu = () => {
|
||||
y = 0;
|
||||
isOpen = !isOpen;
|
||||
@@ -101,7 +69,7 @@
|
||||
};
|
||||
</script>
|
||||
|
||||
<svelte:window bind:scrollY={y} on:resize={throttle(onScroll, 25)} />
|
||||
<svelte:window bind:scrollY={y} />
|
||||
|
||||
<main
|
||||
class:grid-with-side={showSideNavigation}
|
||||
@@ -200,39 +168,13 @@
|
||||
</svelte:fragment>
|
||||
|
||||
{#if $tabs.length}
|
||||
<div class="tabs">
|
||||
{#if showLeft}
|
||||
<button
|
||||
class="tabs-button-scroll is-start"
|
||||
aria-label="Show items in start side"
|
||||
on:click={() => slide('left')}>
|
||||
<span class="icon-cheveron-left" aria-hidden="true" />
|
||||
</button>
|
||||
{/if}
|
||||
{#if showRight}
|
||||
<button
|
||||
class="tabs-button-scroll is-end"
|
||||
aria-label="Show items in end side"
|
||||
on:click={() => slide('right')}>
|
||||
<span class="icon-cheveron-right" aria-hidden="true" />
|
||||
</button>
|
||||
{/if}
|
||||
<ul
|
||||
class="tabs-list scroll-shadow-horizontal"
|
||||
bind:this={tabsList}
|
||||
on:scroll={throttle(onScroll, 25)}>
|
||||
{#each $tabs as tab}
|
||||
<li class="tabs-item">
|
||||
<a
|
||||
class="tabs-button"
|
||||
href={tab.href}
|
||||
class:is-selected={$page.url.pathname === tab.href}>
|
||||
<span class="text">{tab.title}</span>
|
||||
</a>
|
||||
</li>
|
||||
{/each}
|
||||
</ul>
|
||||
</div>
|
||||
<Tabs>
|
||||
{#each $tabs as tab}
|
||||
<Tab href={tab.href} selected={$page.url.pathname === tab.href}>
|
||||
{tab.title}
|
||||
</Tab>
|
||||
{/each}
|
||||
</Tabs>
|
||||
{/if}
|
||||
</Cover>
|
||||
<slot />
|
||||
|
||||
@@ -0,0 +1,126 @@
|
||||
<script context="module" lang="ts">
|
||||
export type UsagePeriods = '24h' | '30d' | '90d';
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
import { Container } from '$lib/layout';
|
||||
import { BarChart, LineChart } from '$lib/charts';
|
||||
import { Card, DropTabs, DropTabsItem, Tiles } from '$lib/components';
|
||||
import type { Models } from '@aw-labs/appwrite-console';
|
||||
|
||||
type MetricMetadata = {
|
||||
title: string;
|
||||
legend: string;
|
||||
};
|
||||
|
||||
export let count: Models.Metric[];
|
||||
export let created: Models.Metric[];
|
||||
export let read: Models.Metric[];
|
||||
export let updated: Models.Metric[];
|
||||
export let deleted: Models.Metric[];
|
||||
|
||||
export let countMetadata: MetricMetadata;
|
||||
export let createdMetadata: MetricMetadata;
|
||||
export let readMetadata: MetricMetadata;
|
||||
export let updatedMetadata: MetricMetadata;
|
||||
export let deletedMetadata: MetricMetadata;
|
||||
|
||||
export let range: UsagePeriods;
|
||||
|
||||
function last(set: Models.Metric[]): Models.Metric | null {
|
||||
return set.slice(-1)[0] ?? null;
|
||||
}
|
||||
|
||||
function total(set: Models.Metric[]): number {
|
||||
return set.reduce((prev, curr) => prev + curr.value, 0);
|
||||
}
|
||||
</script>
|
||||
|
||||
<Container>
|
||||
<div class="u-flex u-main-space-between common-section">
|
||||
<h2 class="heading-level-5">Users</h2>
|
||||
<DropTabs>
|
||||
<DropTabsItem on:click={() => (range = '24h')} disabled={range === '24h'}>
|
||||
24h
|
||||
</DropTabsItem>
|
||||
<DropTabsItem on:click={() => (range = '30d')} disabled={range === '30d'}>
|
||||
30d
|
||||
</DropTabsItem>
|
||||
<DropTabsItem on:click={() => (range = '90d')} disabled={range === '90d'}>
|
||||
90d
|
||||
</DropTabsItem>
|
||||
</DropTabs>
|
||||
</div>
|
||||
<Card>
|
||||
{#if count}
|
||||
<h6 class="heading-level-6">{last(count).value}</h6>
|
||||
<p>{countMetadata.title}</p>
|
||||
<div class="u-margin-block-start-16" />
|
||||
<BarChart
|
||||
series={[
|
||||
{
|
||||
name: countMetadata.legend,
|
||||
data: [...count.map((e) => [e.date, e.value])]
|
||||
}
|
||||
]} />
|
||||
{/if}
|
||||
</Card>
|
||||
<Tiles>
|
||||
<Card isTile>
|
||||
{#if created}
|
||||
<h6 class="heading-level-6">{total(created)}</h6>
|
||||
<p>{createdMetadata.title}</p>
|
||||
<div class="u-margin-block-start-16" />
|
||||
<LineChart
|
||||
series={[
|
||||
{
|
||||
name: createdMetadata.legend,
|
||||
data: [...created.map((e) => [e.date, e.value])]
|
||||
}
|
||||
]} />
|
||||
{/if}
|
||||
</Card>
|
||||
<Card isTile>
|
||||
{#if read}
|
||||
<h6 class="heading-level-6">{total(read)}</h6>
|
||||
<p>{createdMetadata.title}</p>
|
||||
<div class="u-margin-block-start-16" />
|
||||
<LineChart
|
||||
series={[
|
||||
{
|
||||
name: readMetadata.legend,
|
||||
data: [...read.map((e) => [e.date, e.value])]
|
||||
}
|
||||
]} />
|
||||
{/if}
|
||||
</Card>
|
||||
<Card isTile>
|
||||
{#if updated}
|
||||
<h6 class="heading-level-6">{total(updated)}</h6>
|
||||
<p>{updatedMetadata.title}</p>
|
||||
<div class="u-margin-block-start-16" />
|
||||
<LineChart
|
||||
series={[
|
||||
{
|
||||
name: updatedMetadata.legend,
|
||||
data: [...updated.map((e) => [e.date, e.value])]
|
||||
}
|
||||
]} />
|
||||
{/if}
|
||||
</Card>
|
||||
<Card isTile>
|
||||
{#if deleted}
|
||||
<h6 class="heading-level-6">{total(deleted)}</h6>
|
||||
<p>{deletedMetadata.title}</p>
|
||||
<div class="u-margin-block-start-16" />
|
||||
<LineChart
|
||||
series={[
|
||||
{
|
||||
name: deletedMetadata.legend,
|
||||
data: [...deleted.map((e) => [e.date, e.value])]
|
||||
}
|
||||
]} />
|
||||
{/if}
|
||||
</Card>
|
||||
</Tiles>
|
||||
</Container>
|
||||
@@ -0,0 +1,13 @@
|
||||
<script>
|
||||
import { Tabs, Tab } from '../components';
|
||||
|
||||
export let tabs = [];
|
||||
</script>
|
||||
|
||||
<Tabs>
|
||||
{#each tabs as tab}
|
||||
<Tab href={tab.href}>
|
||||
{tab.title}
|
||||
</Tab>
|
||||
{/each}
|
||||
</Tabs>
|
||||
@@ -1,101 +1,45 @@
|
||||
<script>
|
||||
import { Container } from '$lib/layout';
|
||||
import { BarChart } from '$lib/charts';
|
||||
import { Card } from '$lib/components';
|
||||
<script lang="ts">
|
||||
import { Usage } from '$lib/layout';
|
||||
import { usersUsage } from '../store';
|
||||
import type { Models } from '@aw-labs/appwrite-console';
|
||||
import type { UsagePeriods } from '$lib/layout/usage.svelte';
|
||||
|
||||
let currentRange = '30d';
|
||||
let range: UsagePeriods = '30d';
|
||||
|
||||
$: usersUsage.load(currentRange);
|
||||
$: data = $usersUsage;
|
||||
$: usersUsage.load(range);
|
||||
|
||||
//TODO: add types once they are fixed
|
||||
// TODO: metric type is wrong
|
||||
$: count = $usersUsage?.usersCount as unknown as Models.Metric[];
|
||||
$: created = $usersUsage?.usersCreate as unknown as Models.Metric[];
|
||||
$: read = $usersUsage?.usersRead as unknown as Models.Metric[];
|
||||
$: updated = $usersUsage?.usersUpdate as unknown as Models.Metric[];
|
||||
$: deleted = $usersUsage?.usersDelete as unknown as Models.Metric[];
|
||||
</script>
|
||||
|
||||
<Container>
|
||||
{#if data}
|
||||
<div class="u-flex u-main-end common-section">
|
||||
<ul class="drop-tabs">
|
||||
<li class="drop-tabs-item">
|
||||
<button
|
||||
class="drop-tabs-button"
|
||||
on:click={() => (currentRange = '24h')}
|
||||
disabled={currentRange === '24h'}>
|
||||
<span class="text">24h</span>
|
||||
</button>
|
||||
</li>
|
||||
<li class="drop-tabs-item">
|
||||
<button
|
||||
class="drop-tabs-button"
|
||||
on:click={() => (currentRange = '30d')}
|
||||
disabled={currentRange === '30d'}>
|
||||
<span class="text">30d</span>
|
||||
</button>
|
||||
</li>
|
||||
<li class="drop-tabs-item">
|
||||
<button
|
||||
class="drop-tabs-button"
|
||||
on:click={() => (currentRange = '90d')}
|
||||
disabled={currentRange === '90d'}>
|
||||
<span class="text">90d</span>
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<Card>
|
||||
<h6 class="heading-level-6">Users</h6>
|
||||
<p>Count of users over time</p>
|
||||
<BarChart
|
||||
series={[
|
||||
{
|
||||
name: 'User',
|
||||
data: [...data.usersCount.map((e) => [e.date, e.value])]
|
||||
}
|
||||
]} />
|
||||
</Card>
|
||||
<Card>
|
||||
<h6 class="heading-level-6">Operations</h6>
|
||||
<p>Count of users create, read, update and delete operations over time</p>
|
||||
<BarChart
|
||||
series={[
|
||||
{
|
||||
name: 'Create',
|
||||
data: [...data.usersCreate.map((e) => [e.date, e.value])]
|
||||
}
|
||||
]} />
|
||||
</Card>
|
||||
<Card>
|
||||
<h6 class="heading-level-6">Operations</h6>
|
||||
<p>Count of users create, read, update and delete operations over time</p>
|
||||
<BarChart
|
||||
series={[
|
||||
{
|
||||
name: 'Read',
|
||||
data: [...data.usersRead.map((e) => [e.date, e.value])]
|
||||
}
|
||||
]} />
|
||||
</Card>
|
||||
<Card>
|
||||
<h6 class="heading-level-6">Operations</h6>
|
||||
<p>Count of users create, read, update and delete operations over time</p>
|
||||
<BarChart
|
||||
series={[
|
||||
{
|
||||
name: 'Update',
|
||||
data: [...data.usersUpdate.map((e) => [e.date, e.value])]
|
||||
}
|
||||
]} />
|
||||
</Card>
|
||||
<Card>
|
||||
<h6 class="heading-level-6">Operations</h6>
|
||||
<p>Count of users create, read, update and delete operations over time</p>
|
||||
<BarChart
|
||||
series={[
|
||||
{
|
||||
name: 'Delete',
|
||||
data: [...data.usersDelete.map((e) => [e.date, e.value])]
|
||||
}
|
||||
]} />
|
||||
</Card>
|
||||
{/if}
|
||||
</Container>
|
||||
<Usage
|
||||
bind:range
|
||||
{count}
|
||||
{created}
|
||||
{read}
|
||||
{updated}
|
||||
{deleted}
|
||||
countMetadata={{
|
||||
legend: 'Users',
|
||||
title: 'Registered users'
|
||||
}}
|
||||
createdMetadata={{
|
||||
legend: 'Create',
|
||||
title: 'Users created'
|
||||
}}
|
||||
readMetadata={{
|
||||
legend: 'Read',
|
||||
title: 'Users read'
|
||||
}}
|
||||
updatedMetadata={{
|
||||
legend: 'Update',
|
||||
title: 'Users updated'
|
||||
}}
|
||||
deletedMetadata={{
|
||||
legend: 'Delete',
|
||||
title: 'Users deleted'
|
||||
}} />
|
||||
|
||||
+4
@@ -46,6 +46,10 @@
|
||||
href: `${path}/indexes`,
|
||||
title: 'Indexes'
|
||||
},
|
||||
{
|
||||
href: `${path}/usage`,
|
||||
title: 'Usage'
|
||||
},
|
||||
{
|
||||
href: `${path}/settings`,
|
||||
title: 'Settings'
|
||||
|
||||
+14
@@ -116,3 +116,17 @@ export const attributeList = cachedStore<
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
export const collectionUsage = cachedStore<
|
||||
Models.UsageCollection,
|
||||
{
|
||||
load: (databaseId: string, collectionId: string, range: string) => Promise<void>;
|
||||
}
|
||||
>('collectionUsage', function ({ set }) {
|
||||
return {
|
||||
load: async (databaseId, range) => {
|
||||
const usages = await sdkForProject.databases.getCollectionUsage(databaseId, range);
|
||||
set(usages);
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
+48
@@ -0,0 +1,48 @@
|
||||
<script lang="ts">
|
||||
import { page } from '$app/stores';
|
||||
import { Usage, type UsagePeriods } from '$lib/layout';
|
||||
import { collectionUsage } from '../store';
|
||||
import type { Models } from '@aw-labs/appwrite-console';
|
||||
|
||||
let range: UsagePeriods = '30d';
|
||||
|
||||
const databaseId = $page.params.database;
|
||||
const collectionId = $page.params.collection;
|
||||
|
||||
$: collectionUsage.load(databaseId, collectionId, range);
|
||||
|
||||
// TODO: metric type is wrong
|
||||
$: count = $collectionUsage?.documentsCount as unknown as Models.Metric[];
|
||||
$: created = $collectionUsage?.documentsCreate as unknown as Models.Metric[];
|
||||
$: read = $collectionUsage?.documentsRead as unknown as Models.Metric[];
|
||||
$: updated = $collectionUsage?.documentsUpdate as unknown as Models.Metric[];
|
||||
$: deleted = $collectionUsage?.documentsDelete as unknown as Models.Metric[];
|
||||
</script>
|
||||
|
||||
<Usage
|
||||
bind:range
|
||||
{count}
|
||||
{created}
|
||||
{read}
|
||||
{updated}
|
||||
{deleted}
|
||||
countMetadata={{
|
||||
legend: 'Documents',
|
||||
title: 'Total documents'
|
||||
}}
|
||||
createdMetadata={{
|
||||
legend: 'Create',
|
||||
title: 'Documents created'
|
||||
}}
|
||||
readMetadata={{
|
||||
legend: 'Read',
|
||||
title: 'Documents read'
|
||||
}}
|
||||
updatedMetadata={{
|
||||
legend: 'Update',
|
||||
title: 'Documents updated'
|
||||
}}
|
||||
deletedMetadata={{
|
||||
legend: 'Delete',
|
||||
title: 'Documents deleted'
|
||||
}} />
|
||||
@@ -38,3 +38,17 @@ export const database = cachedStore<
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
export const databaseUsage = cachedStore<
|
||||
Models.UsageDatabase,
|
||||
{
|
||||
load: (databaseId: string, range: string) => Promise<void>;
|
||||
}
|
||||
>('databaseUsage', function ({ set }) {
|
||||
return {
|
||||
load: async (databaseId, range) => {
|
||||
const usages = await sdkForProject.databases.getDatabaseUsage(databaseId, range);
|
||||
set(usages);
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
+45
-7
@@ -1,9 +1,47 @@
|
||||
<script>
|
||||
import { Card } from '$lib/components';
|
||||
import { Container } from '$lib/layout';
|
||||
<script lang="ts">
|
||||
import { page } from '$app/stores';
|
||||
import { Usage, type UsagePeriods } from '$lib/layout';
|
||||
import { databaseUsage } from '../store';
|
||||
import type { Models } from '@aw-labs/appwrite-console';
|
||||
|
||||
let range: UsagePeriods = '30d';
|
||||
|
||||
const databaseId = $page.params.database;
|
||||
|
||||
$: databaseUsage.load(databaseId, range);
|
||||
|
||||
// TODO: metric type is wrong
|
||||
$: count = $databaseUsage?.collectionsCount as unknown as Models.Metric[];
|
||||
$: created = $databaseUsage?.collectionsCreate as unknown as Models.Metric[];
|
||||
$: read = $databaseUsage?.collectionsRead as unknown as Models.Metric[];
|
||||
$: updated = $databaseUsage?.collectionsUpdate as unknown as Models.Metric[];
|
||||
$: deleted = $databaseUsage?.collectionsDelete as unknown as Models.Metric[];
|
||||
</script>
|
||||
|
||||
<Container>
|
||||
<h1>Usage</h1>
|
||||
<Card />
|
||||
</Container>
|
||||
<Usage
|
||||
bind:range
|
||||
{count}
|
||||
{created}
|
||||
{read}
|
||||
{updated}
|
||||
{deleted}
|
||||
countMetadata={{
|
||||
legend: 'Collections',
|
||||
title: 'Total collections'
|
||||
}}
|
||||
createdMetadata={{
|
||||
legend: 'Create',
|
||||
title: 'Collections created'
|
||||
}}
|
||||
readMetadata={{
|
||||
legend: 'Read',
|
||||
title: 'Collections read'
|
||||
}}
|
||||
updatedMetadata={{
|
||||
legend: 'Update',
|
||||
title: 'Collections updated'
|
||||
}}
|
||||
deletedMetadata={{
|
||||
legend: 'Delete',
|
||||
title: 'Collections deleted'
|
||||
}} />
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { sdkForProject } from '$lib/stores/sdk';
|
||||
import type { Models } from '@aw-labs/appwrite-console';
|
||||
import { cachedStore } from '$lib/helpers/cache';
|
||||
import type { Models } from '@aw-labs/appwrite-console';
|
||||
|
||||
export const databaseList = cachedStore<
|
||||
Models.DatabaseList,
|
||||
@@ -15,3 +15,17 @@ export const databaseList = cachedStore<
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
export const databasesUsage = cachedStore<
|
||||
Models.UsageDatabases,
|
||||
{
|
||||
load: (range: string) => Promise<void>;
|
||||
}
|
||||
>('databasesUsage', function ({ set }) {
|
||||
return {
|
||||
load: async (range) => {
|
||||
const usages = await sdkForProject.databases.getUsage(range);
|
||||
set(usages);
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
@@ -1,10 +1,44 @@
|
||||
<script>
|
||||
import { Card } from '$lib/components';
|
||||
import { Container } from '$lib/layout';
|
||||
<script lang="ts">
|
||||
import { Usage, type UsagePeriods } from '$lib/layout';
|
||||
import { databasesUsage } from '../store';
|
||||
import type { Models } from '@aw-labs/appwrite-console';
|
||||
|
||||
let range: UsagePeriods = '30d';
|
||||
|
||||
$: databasesUsage.load(range);
|
||||
|
||||
// TODO: metric type is wrong
|
||||
$: count = $databasesUsage?.databasesCount as unknown as Models.Metric[];
|
||||
$: created = $databasesUsage?.databasesCreate as unknown as Models.Metric[];
|
||||
$: read = $databasesUsage?.databasesRead as unknown as Models.Metric[];
|
||||
$: updated = $databasesUsage?.databasesUpdate as unknown as Models.Metric[];
|
||||
$: deleted = $databasesUsage?.databasesDelete as unknown as Models.Metric[];
|
||||
</script>
|
||||
|
||||
<Container>
|
||||
<h1>Usage</h1>
|
||||
|
||||
<Card />
|
||||
</Container>
|
||||
<Usage
|
||||
bind:range
|
||||
{count}
|
||||
{created}
|
||||
{read}
|
||||
{updated}
|
||||
{deleted}
|
||||
countMetadata={{
|
||||
legend: 'Databases',
|
||||
title: 'Total databases'
|
||||
}}
|
||||
createdMetadata={{
|
||||
legend: 'Create',
|
||||
title: 'Databases created'
|
||||
}}
|
||||
readMetadata={{
|
||||
legend: 'Read',
|
||||
title: 'Databases read'
|
||||
}}
|
||||
updatedMetadata={{
|
||||
legend: 'Update',
|
||||
title: 'Databases updated'
|
||||
}}
|
||||
deletedMetadata={{
|
||||
legend: 'Delete',
|
||||
title: 'Databases deleted'
|
||||
}} />
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
<script lang="ts">
|
||||
import type { Models } from '@aw-labs/appwrite-console';
|
||||
import { Container } from '$lib/layout';
|
||||
import { Container, type UsagePeriods } from '$lib/layout';
|
||||
import { page } from '$app/stores';
|
||||
import { browser } from '$app/environment';
|
||||
import { tabs, title, backButton, copyData } from '$lib/stores/layout';
|
||||
import { sdkForConsole } from '$lib/stores/sdk';
|
||||
import { project } from '../store';
|
||||
import { usage, type UsagePeriods } from './store';
|
||||
import { usage } from './store';
|
||||
import { onMount } from 'svelte';
|
||||
import { afterNavigate } from '$app/navigation';
|
||||
import { DropList, DropListItem } from '$lib/components';
|
||||
|
||||
@@ -2,11 +2,10 @@ import { sdkForConsole } from '$lib/stores/sdk';
|
||||
import { cachedStore } from '$lib/helpers/cache';
|
||||
import type { Models } from '@aw-labs/appwrite-console';
|
||||
|
||||
export type UsagePeriods = '24h' | '30d' | '90d';
|
||||
export const usage = cachedStore<
|
||||
Models.UsageProject,
|
||||
{
|
||||
load: (projectId: string, range: UsagePeriods) => Promise<void>;
|
||||
load: (projectId: string, range: string) => Promise<void>;
|
||||
}
|
||||
>('projectUsage', function ({ set }) {
|
||||
return {
|
||||
|
||||
@@ -1,42 +1,45 @@
|
||||
import { sdkForProject } from '$lib/stores/sdk';
|
||||
import type { Models } from '@aw-labs/appwrite-console';
|
||||
import { writable } from 'svelte/store';
|
||||
import { browser } from '$app/environment';
|
||||
|
||||
function createBucketStore() {
|
||||
const { subscribe, set } = writable<Models.Bucket>(
|
||||
browser ? JSON.parse(sessionStorage.getItem('bucket')) : null
|
||||
);
|
||||
import { cachedStore } from '$lib/helpers/cache';
|
||||
|
||||
export const bucket = cachedStore<
|
||||
Models.Bucket,
|
||||
{
|
||||
load: (bucketId: string) => Promise<void>;
|
||||
}
|
||||
>('bucket', function ({ set }) {
|
||||
return {
|
||||
subscribe,
|
||||
set,
|
||||
load: async (bucketId: string) => {
|
||||
load: async (bucketId) => {
|
||||
const response = await sdkForProject.storage.getBucket(bucketId);
|
||||
set(response);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function createFilesStore() {
|
||||
const { subscribe, set } = writable<Models.FileList>(
|
||||
browser ? JSON.parse(sessionStorage.getItem('files')) : null
|
||||
);
|
||||
});
|
||||
|
||||
export const files = cachedStore<
|
||||
Models.FileList,
|
||||
{
|
||||
load: (bucketId: string, queries: string[], search: string) => Promise<void>;
|
||||
}
|
||||
>('files', function ({ set }) {
|
||||
return {
|
||||
subscribe,
|
||||
set,
|
||||
load: async (bucketId: string, queries?: string[], search?: string) => {
|
||||
load: async (bucketId, queries, search) => {
|
||||
const response = await sdkForProject.storage.listFiles(bucketId, queries, search);
|
||||
set(response);
|
||||
}
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
export const bucket = createBucketStore();
|
||||
export const files = createFilesStore();
|
||||
|
||||
if (browser) {
|
||||
bucket.subscribe((n) => sessionStorage?.setItem('bucket', JSON.stringify(n ?? '')));
|
||||
files.subscribe((n) => sessionStorage?.setItem('files', JSON.stringify(n ?? '')));
|
||||
}
|
||||
export const bucketUsage = cachedStore<
|
||||
Models.UsageBuckets,
|
||||
{
|
||||
load: (bucketId: string, range: string) => Promise<void>;
|
||||
}
|
||||
>('bucketsUsage', function ({ set }) {
|
||||
return {
|
||||
load: async (bucketId, range) => {
|
||||
const response = await sdkForProject.storage.getBucketUsage(bucketId, range);
|
||||
set(response);
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
@@ -1,10 +1,47 @@
|
||||
<script>
|
||||
import { Card } from '$lib/components';
|
||||
import { Container } from '$lib/layout';
|
||||
<script lang="ts">
|
||||
import { page } from '$app/stores';
|
||||
import { Usage, type UsagePeriods } from '$lib/layout';
|
||||
import { bucketUsage } from '../store';
|
||||
import type { Models } from '@aw-labs/appwrite-console';
|
||||
|
||||
let range: UsagePeriods = '30d';
|
||||
|
||||
const bucketId = $page.params.bucket;
|
||||
|
||||
$: bucketUsage.load(bucketId, range);
|
||||
|
||||
// TODO: metric type is wrong
|
||||
$: count = $bucketUsage?.filesCount as unknown as Models.Metric[];
|
||||
$: created = $bucketUsage?.filesCreate as unknown as Models.Metric[];
|
||||
$: read = $bucketUsage?.filesRead as unknown as Models.Metric[];
|
||||
$: updated = $bucketUsage?.filesUpdate as unknown as Models.Metric[];
|
||||
$: deleted = $bucketUsage?.filesDelete as unknown as Models.Metric[];
|
||||
</script>
|
||||
|
||||
<Container>
|
||||
<h1>Usage</h1>
|
||||
|
||||
<Card />
|
||||
</Container>
|
||||
<Usage
|
||||
bind:range
|
||||
{count}
|
||||
{created}
|
||||
{read}
|
||||
{updated}
|
||||
{deleted}
|
||||
countMetadata={{
|
||||
legend: 'Files',
|
||||
title: 'Total files'
|
||||
}}
|
||||
createdMetadata={{
|
||||
legend: 'Create',
|
||||
title: 'Files created'
|
||||
}}
|
||||
readMetadata={{
|
||||
legend: 'Read',
|
||||
title: 'Files read'
|
||||
}}
|
||||
updatedMetadata={{
|
||||
legend: 'Update',
|
||||
title: 'Files updated'
|
||||
}}
|
||||
deletedMetadata={{
|
||||
legend: 'Delete',
|
||||
title: 'Files deleted'
|
||||
}} />
|
||||
|
||||
@@ -15,3 +15,17 @@ export const bucketList = cachedStore<
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
export const storageUsage = cachedStore<
|
||||
Models.UsageStorage,
|
||||
{
|
||||
load: (range: string) => Promise<void>;
|
||||
}
|
||||
>('storageUsage', function ({ set }) {
|
||||
return {
|
||||
load: async (range) => {
|
||||
const response = await sdkForProject.storage.getUsage(range);
|
||||
set(response);
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
@@ -1,10 +1,44 @@
|
||||
<script>
|
||||
import { Card } from '$lib/components';
|
||||
import { Container } from '$lib/layout';
|
||||
<script lang="ts">
|
||||
import { Usage, type UsagePeriods } from '$lib/layout';
|
||||
import { storageUsage } from '../store';
|
||||
import type { Models } from '@aw-labs/appwrite-console';
|
||||
|
||||
let range: UsagePeriods = '30d';
|
||||
|
||||
$: storageUsage.load(range);
|
||||
|
||||
// TODO: metric type is wrong
|
||||
$: count = $storageUsage?.bucketsCount as unknown as Models.Metric[];
|
||||
$: created = $storageUsage?.bucketsCreate as unknown as Models.Metric[];
|
||||
$: read = $storageUsage?.bucketsRead as unknown as Models.Metric[];
|
||||
$: updated = $storageUsage?.bucketsUpdate as unknown as Models.Metric[];
|
||||
$: deleted = $storageUsage?.bucketsDelete as unknown as Models.Metric[];
|
||||
</script>
|
||||
|
||||
<Container>
|
||||
<h1>Usage</h1>
|
||||
|
||||
<Card />
|
||||
</Container>
|
||||
<Usage
|
||||
bind:range
|
||||
{count}
|
||||
{created}
|
||||
{read}
|
||||
{updated}
|
||||
{deleted}
|
||||
countMetadata={{
|
||||
legend: 'Buckets',
|
||||
title: 'Total buckets'
|
||||
}}
|
||||
createdMetadata={{
|
||||
legend: 'Create',
|
||||
title: 'Buckets created'
|
||||
}}
|
||||
readMetadata={{
|
||||
legend: 'Read',
|
||||
title: 'Buckets read'
|
||||
}}
|
||||
updatedMetadata={{
|
||||
legend: 'Update',
|
||||
title: 'Buckets updated'
|
||||
}}
|
||||
deletedMetadata={{
|
||||
legend: 'Delete',
|
||||
title: 'Buckets deleted'
|
||||
}} />
|
||||
|
||||
+4
-2
@@ -1,4 +1,4 @@
|
||||
import adapter from '@sveltejs/adapter-auto';
|
||||
import adapter from '@sveltejs/adapter-static';
|
||||
import preprocess from 'svelte-preprocess';
|
||||
|
||||
/** @type {import('@sveltejs/kit').Config} */
|
||||
@@ -10,7 +10,9 @@ const config = {
|
||||
accessors: process.env.VITEST
|
||||
},
|
||||
kit: {
|
||||
adapter: adapter(),
|
||||
adapter: adapter({
|
||||
fallback: 'index.html'
|
||||
}),
|
||||
paths: {
|
||||
base: ''
|
||||
}
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
import '@testing-library/jest-dom';
|
||||
import { vi } from 'vitest';
|
||||
import { render, fireEvent } from '@testing-library/svelte';
|
||||
import { Tab } from '../../../src/lib/components';
|
||||
|
||||
test('shows tab', () => {
|
||||
const { getByRole } = render(Tab);
|
||||
|
||||
expect(getByRole('button')).toBeInTheDocument();
|
||||
});
|
||||
test('shows tab - is selected', () => {
|
||||
const { getByRole } = render(Tab, { selected: true });
|
||||
|
||||
expect(getByRole('button')).toHaveClass('is-selected');
|
||||
});
|
||||
|
||||
test('shows tab - is link', () => {
|
||||
render(Tab, { href: 'https://appwrite.io' });
|
||||
const link = document.querySelector('a');
|
||||
|
||||
expect(link).toHaveAttribute('href', 'https://appwrite.io');
|
||||
});
|
||||
|
||||
test('shows tab - on:click', async () => {
|
||||
const { getByRole, component } = render(Tab);
|
||||
const tab = getByRole('button');
|
||||
const callback = vi.fn();
|
||||
component.$on('click', callback);
|
||||
|
||||
await fireEvent.click(tab);
|
||||
expect(callback).toHaveBeenCalled();
|
||||
});
|
||||
@@ -0,0 +1,27 @@
|
||||
import '@testing-library/jest-dom';
|
||||
import { render } from '@testing-library/svelte';
|
||||
import Tabs from '../../../src/lib/mock/tabs.test.svelte';
|
||||
|
||||
const list = [
|
||||
{
|
||||
title: 'Tab 1',
|
||||
href: '#tab-1'
|
||||
},
|
||||
{
|
||||
title: 'Tab 2',
|
||||
href: '#tab-2'
|
||||
},
|
||||
{
|
||||
title: 'Tab 3',
|
||||
href: '#tab-3'
|
||||
}
|
||||
];
|
||||
|
||||
test('shows Tabs component', () => {
|
||||
const { container } = render(Tabs, { tabs: list });
|
||||
const tabs = container.querySelector('div.tabs');
|
||||
const children = tabs.childNodes;
|
||||
|
||||
expect(tabs).toBeInTheDocument();
|
||||
expect(children.length).toBe(list.length);
|
||||
});
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"rewrites": [
|
||||
{
|
||||
"source": "/(.*)",
|
||||
"destination": "/"
|
||||
}
|
||||
]
|
||||
}
|
||||
+2
-2
@@ -4,10 +4,10 @@ import { sveltekit } from '@sveltejs/kit/vite';
|
||||
const config = {
|
||||
plugins: [sveltekit()],
|
||||
optimizeDeps: {
|
||||
include: ['echarts']
|
||||
include: ['echarts', 'prismjs']
|
||||
},
|
||||
ssr: {
|
||||
noExternal: ['echarts']
|
||||
noExternal: ['echarts', 'prismjs']
|
||||
},
|
||||
define: {
|
||||
'import.meta.env.VERCEL_ANALYTICS_ID': JSON.stringify(process.env.VERCEL_ANALYTICS_ID)
|
||||
|
||||
Reference in New Issue
Block a user