Merge remote-tracking branch 'origin/main' into feat-string-types

This commit is contained in:
Jake Barnby
2026-02-06 04:36:11 +13:00
25 changed files with 381 additions and 100 deletions
@@ -36,6 +36,7 @@
</Layout.Stack>
<Table.Root
class="responsive-table"
columns={[
{ id: 'type', width: { min: 150 } },
{ id: 'name', width: { min: 80 } },
@@ -102,6 +102,7 @@
</Layout.Stack>
<Table.Root
class="responsive-table"
columns={[
{ id: 'type', width: { min: 150 } },
{ id: 'name', width: { min: 80 } },
+235 -20
View File
@@ -1,35 +1,246 @@
<script lang="ts">
import { Icon, Layout, Tag, Tooltip } from '@appwrite.io/pink-svelte';
import { queries, tagFormat, tags } from './store';
import {
Icon,
Layout,
Tooltip,
CompoundTagRoot,
CompoundTagChild,
Typography,
ActionMenu,
Selector
} from '@appwrite.io/pink-svelte';
import { capitalize } from '$lib/helpers/string';
import { queries, tags } from './store';
import { IconX } from '@appwrite.io/pink-icons-svelte';
import { parsedTags } from './setFilters';
import { parsedTags, type ParsedTag } from './setFilters';
import { Button } from '$lib/elements/forms';
import type { Column } from '$lib/helpers/types';
import { writable, type Writable } from 'svelte/store';
import Menu from '$lib/components/menu/menu.svelte';
import { addFilterAndApply, buildFilterCol, type FilterData } from './quickFilters';
import QuickFilters from '$lib/components/filters/quickFilters.svelte';
import { isSmallViewport } from '$lib/stores/viewport';
let {
columns = writable([]),
analyticsSource = ''
}: { columns?: Writable<Column[]>; analyticsSource?: string } = $props();
function parseTagParts(tagString: string): { text: string; operator: boolean }[] {
return tagString
.split(/\*\*(.*?)\*\*/)
.map((part, index) => {
// Even indices are outside bold (operators), odd indices are inside bold (values)
if (index % 2 === 0) {
return part
.split(/\s+/)
.filter(Boolean)
.map((t) => ({ text: t, operator: true }));
} else {
return [{ text: part, operator: false }];
}
})
.flat()
.filter((p) => Boolean(p.text));
}
function getFilterFor(title: string): FilterData | null {
if (!columns) return null;
const col = ($columns as unknown as Column[]).find((c) => c.title === title);
if (!col) return null;
const filter = buildFilterCol(col);
return filter ?? null;
}
// Build available filter definitions from provided columns
let availableFilters = $derived(
($columns as unknown as Column[] | undefined)?.length
? (($columns as unknown as Column[])
.map((c) => (c.filter !== false ? buildFilterCol(c) : null))
.filter((f) => f && f.options) as FilterData[])
: []
);
// QuickFilters uses the same filters list
let filterCols = $derived(availableFilters);
// Always-show placeholders are derived from available filters (no hardcoding)
// Use reactive array so runes can track changes
let hiddenPlaceholders: string[] = $state([]);
let activeTitles = $derived(
($parsedTags || []).map((t) => (t as ParsedTag).title).filter(Boolean) as string[]
);
// Compute current placeholders (major filters not already active or dismissed)
let placeholders = $derived(
availableFilters
.filter((f) => !activeTitles.includes(f.title))
.filter((f) => !hiddenPlaceholders.includes(f.title))
);
</script>
{#if $parsedTags?.length}
<Layout.Stack direction="row" gap="s" wrap="wrap" alignItems="center" inline>
<Layout.Stack direction="row" gap="s" wrap="wrap" alignItems="center" inline>
{#if $parsedTags?.length}
{#each $parsedTags as tag (tag.tag)}
<span>
<Tooltip
disabled={Array.isArray(tag.value) ? tag.value?.length < 3 : true}
maxWidth="600px">
<Tag
size="s"
on:click={() => {
const t = $tags.filter((t) => t.tag.includes(tag.tag.split(' ')[0]));
t.forEach((t) => (t ? queries.removeFilter(t) : null));
queries.apply();
parsedTags.update((tags) => tags.filter((t) => t.tag !== tag.tag));
}}>
{#key tag.tag}
<span use:tagFormat>{tag.tag}</span>
{/key}
<Icon icon={IconX} size="s" slot="end" />
</Tag>
<CompoundTagRoot size="s">
{@const parts = parseTagParts(tag.tag)}
{@const property = (tag as ParsedTag).title}
{#each parts as part}
<CompoundTagChild>
<Menu>
<span>
{#if part.operator}
<Typography.Text color="--fgcolor-neutral-secondary"
>{part.text}</Typography.Text>
{:else}
<Typography.Text
variant="m-500"
color="--fgcolor-neutral-secondary"
>{part.text
.split(' or ')
.map((t) => capitalize(t))
.join(' or ')}</Typography.Text>
{/if}
</span>
<svelte:fragment slot="menu">
{#if property}
{@const filter = getFilterFor(property)}
{#if filter}
{@const isArray = filter?.array}
{@const selectedArray = Array.isArray(tag.value)
? tag.value
: []}
{#each filter.options as option (filter.title + option.value + option.label)}
<ActionMenu.Root>
<ActionMenu.Item.Button
on:click={() => {
if (isArray) {
const exists =
selectedArray.includes(
option.value
);
const next = exists
? selectedArray.filter(
(v) =>
v !== option.value
)
: [
...selectedArray,
option.value
];
addFilterAndApply(
filter.id,
filter.title,
filter.operator,
null,
next,
$columns,
analyticsSource
);
} else {
addFilterAndApply(
filter.id,
filter.title,
filter.operator,
option.value,
[],
$columns,
analyticsSource
);
}
}}>
<Layout.Stack direction="row" gap="s">
{#if isArray}
<Selector.Checkbox
checked={selectedArray.includes(
option.value
)}
size="s" />
{/if}
{capitalize(option.label)}
</Layout.Stack>
</ActionMenu.Item.Button>
</ActionMenu.Root>
{/each}
{/if}
{/if}
</svelte:fragment>
</Menu>
</CompoundTagChild>
{/each}
<CompoundTagChild
dismiss
on:click={() => {
const t = $tags.filter((t) =>
t.tag.includes((tag as ParsedTag).title)
);
t.forEach((t) => (t ? queries.removeFilter(t) : null));
queries.apply();
parsedTags.update((tags) => tags.filter((t) => t.tag !== tag.tag));
}}>
<Icon icon={IconX} size="s" />
</CompoundTagChild>
</CompoundTagRoot>
<span slot="tooltip">{tag?.value?.toString()}</span>
</Tooltip>
</span>
{/each}
{/if}
<!-- Always render remaining placeholder tags alongside active tags -->
{#if placeholders?.length}
{#each placeholders as filter (filter.title + filter.id)}
<span>
<Menu>
<CompoundTagRoot size="s">
<CompoundTagChild>
<span>{capitalize(filter.title)}</span>
</CompoundTagChild>
<CompoundTagChild
dismiss
on:click={(e) => {
e.stopPropagation();
if (!hiddenPlaceholders.includes(filter.title)) {
hiddenPlaceholders = [...hiddenPlaceholders, filter.title];
}
}}>
<Icon icon={IconX} size="s" />
</CompoundTagChild>
</CompoundTagRoot>
<svelte:fragment slot="menu">
{#if filter.options}
{#each filter.options as option (filter.title + option.value + option.label)}
<ActionMenu.Root>
<ActionMenu.Item.Button
on:click={() => {
addFilterAndApply(
filter.id,
filter.title,
filter.operator,
filter?.array ? null : option.value,
filter?.array ? [option.value] : [],
$columns,
analyticsSource
);
}}>
{capitalize(option.label)}
</ActionMenu.Item.Button>
</ActionMenu.Root>
{/each}
{/if}
</svelte:fragment>
</Menu>
</span>
{/each}
{/if}
{#if $parsedTags?.length}
<Button
size="s"
text
@@ -38,5 +249,9 @@
queries.apply();
parsedTags.set([]);
}}>Clear all</Button>
</Layout.Stack>
{/if}
{/if}
{#if filterCols?.length && !$isSmallViewport}
<QuickFilters {columns} {analyticsSource} {filterCols} />
{/if}
</Layout.Stack>
@@ -22,9 +22,12 @@
</script>
<Menu>
<Button secondary badge={$parsedTags?.length ? `${$parsedTags.length}` : undefined}>
<Icon icon={IconFilterLine} slot="start" size="s" />
Filters
<Button
ariaLabel="Filters"
text
icon
badge={$parsedTags?.length ? `${$parsedTags.length}` : undefined}>
<Icon icon={IconFilterLine} size="s" />
</Button>
<svelte:fragment slot="menu">
{#each filterCols.filter((f) => f?.options) as filter (filter.title + filter.id)}
+20 -11
View File
@@ -3,7 +3,11 @@ import { get, writable } from 'svelte/store';
import { type FilterData } from './quickFilters';
import { tags, type TagValue } from './store';
export const parsedTags = writable<TagValue[]>([]);
export type ParsedTag = TagValue & {
title: string;
};
export const parsedTags = writable<ParsedTag[]>([]);
export function setFilters(localTags: TagValue[], filterCols: FilterData[], $columns: Column[]) {
if (!localTags?.length) {
@@ -47,9 +51,10 @@ export function setFilterData(filter: FilterData) {
});
}
cleanOldTags(filter?.title);
const newTag = {
const newTag: ParsedTag = {
tag: tagData.tag.replace(',', ' or '),
value: tagData.value
value: tagData.value,
title: filter.title
};
parsedTags.update((tags) => {
@@ -69,9 +74,10 @@ export function setTimeFilter(filter: FilterData, columns: Column[]) {
const ranges = col.elements as { value: string; label: string }[];
const timeRange = ranges.find((range) => range.value === timeTag.value);
if (timeRange) {
const newTag = {
const newTag: ParsedTag = {
tag: `**${filter.title}** is **${timeRange.label}**`,
value: timeRange.value
value: timeRange.value,
title: filter.title
};
cleanOldTags(filter?.title);
@@ -102,9 +108,10 @@ export function setSizeFilter(filter: FilterData, columns: Column[]) {
if (sizeRange) {
cleanOldTags(filter?.title);
const newTag = {
const newTag: ParsedTag = {
tag: `**${filter.title}** is **${sizeRange.label}**`,
value: sizeTag.value
value: sizeTag.value,
title: filter.title
};
parsedTags.update((tags) => {
tags.push(newTag);
@@ -126,9 +133,10 @@ export function setStatusCodeFilter(filter: FilterData, columns: Column[]) {
const codeRange = ranges.find((c) => c?.value && c.value === statusCodeTag.value);
if (codeRange) {
cleanOldTags(filter?.title);
const newTag = {
const newTag: ParsedTag = {
tag: `**${filter.title}** is **${codeRange.label}**`,
value: statusCodeTag.value
value: statusCodeTag.value,
title: filter.title
};
parsedTags.update((tags) => {
tags.push(newTag);
@@ -156,9 +164,10 @@ export function setDateFilter(filter: FilterData, columns: Column[]) {
});
if (dateRange) {
cleanOldTags(filter?.title);
const newTag = {
const newTag: ParsedTag = {
tag: `**${filter.title}** is **${dateRange.label}**`,
value: dateTag.value
value: dateTag.value,
title: filter.title
};
parsedTags.update((tags) => {
tags.push(newTag);
+1 -1
View File
@@ -49,7 +49,7 @@
expanded={databasesScreen && !insideSideSheet}
slotSpacing={databasesScreen && !insideSideSheet}>
{#if logs.total}
<Table.Root {columns} let:root>
<Table.Root class="responsive-table" {columns} let:root>
<svelte:fragment slot="header" let:root>
<Table.Header.Cell column="user" {root}>User</Table.Header.Cell>
<Table.Header.Cell column="event" {root}>Event</Table.Header.Cell>
+28 -21
View File
@@ -1,12 +1,7 @@
<script lang="ts">
import { SearchQuery, ViewSelector } from '$lib/components';
import {
FiltersBottomSheet,
ParsedTagList,
queryParamToMap,
Filters
} from '$lib/components/filters';
import QuickFilters from '$lib/components/filters/quickFilters.svelte';
import { FiltersBottomSheet, ParsedTagList, queryParamToMap } from '$lib/components/filters';
import Button from '$lib/elements/forms/button.svelte';
import { View } from '$lib/helpers/load';
import type { Column } from '$lib/helpers/types';
@@ -28,7 +23,6 @@
hasSearch = false,
searchPlaceholder = 'Search by ID',
hasFilters = false,
hasCustomFiltersOnly = false,
analyticsSource = '',
children
}: {
@@ -39,7 +33,6 @@
hasSearch?: boolean;
searchPlaceholder?: string;
hasFilters?: boolean;
hasCustomFiltersOnly?: boolean;
analyticsSource?: string;
children?: Snippet;
} = $props();
@@ -106,22 +99,37 @@
{#if showSearch && hasSearch}
<SearchQuery placeholder={searchPlaceholder} />
{/if}
<div style="overflow-x: auto;">
<ParsedTagList {columns} {analyticsSource} />
</div>
</Layout.Stack>
{:else}
<Layout.Stack direction="row" justifyContent="space-between">
<Layout.Stack direction="row" alignItems="center">
<Layout.Stack direction="row" justifyContent="space-between" alignItems="flex-start">
<Layout.Stack
direction="row"
alignItems="center"
gap="m"
style="min-width: 0; flex: 1 1 auto;">
{#if hasSearch}
<SearchQuery placeholder={searchPlaceholder} />
{/if}
</Layout.Stack>
<Layout.Stack direction="row" alignItems="center" justifyContent="flex-end">
{#if hasFilters && $columns?.length}
{#if hasCustomFiltersOnly}
<Filters query="[]" {columns} {analyticsSource} />
{:else}
<QuickFilters {columns} {analyticsSource} {filterCols} />
{/if}
{#if hasFilters}
<!-- Tags with Filters button (rendered inside ParsedTagList) -->
<Layout.Stack
direction="row"
alignItems="center"
gap="s"
wrap="wrap"
style="min-width: 0;">
<ParsedTagList {columns} {analyticsSource} />
</Layout.Stack>
{/if}
</Layout.Stack>
<Layout.Stack
direction="row"
alignItems="center"
justifyContent="flex-end"
style="align-self: flex-start; white-space: nowrap;">
{#if hasDisplaySettings}
<ViewSelector ui="new" {view} {columns} {hideView} {hideColumns} />
{/if}
@@ -131,7 +139,6 @@
</Layout.Stack>
</Layout.Stack>
{/if}
<ParsedTagList />
</Layout.Stack>
</header>
@@ -171,7 +178,7 @@
{/snippet}
{#snippet filtersButton(icon = false)}
<Button ariaLabel="Filters" on:click={() => (showFilters = !showFilters)} secondary {icon}>
<Button ariaLabel="Filters" on:click={() => (showFilters = !showFilters)} text {icon}>
<Icon icon={IconFilterLine} />
</Button>
{/snippet}
+9 -5
View File
@@ -5,8 +5,9 @@
import { activeHeaderAlert } from '$routes/(console)/store';
import { onMount, setContext } from 'svelte';
import { writable } from 'svelte/store';
import { showSubNavigation } from '$lib/stores/layout';
import { showSubNavigation, showOnboardingAnimation } from '$lib/stores/layout';
import { organization, organizationList } from '$lib/stores/organization';
import { sdk } from '$lib/stores/sdk';
import { user } from '$lib/stores/user';
import { isCloud } from '$lib/system';
@@ -176,7 +177,8 @@
$: subNavigation = $page.data.subNavigation;
$: shouldRenderSidebar = !$isNewWizardStatusOpen && showSideNavigation;
$: shouldRenderSidebar =
!$isNewWizardStatusOpen && showSideNavigation && !$showOnboardingAnimation;
$: hasSidebarSpace = shouldRenderSidebar && !$isTabletViewport && !!selectedProject;
$: {
@@ -203,9 +205,9 @@
class:is-open={$showSubNavigation}
class:u-hide={$wizard.show || $wizard.cover}
class:is-fixed-layout={$activeHeaderAlert?.show}
class:no-header={!showHeader}
class:no-header={!showHeader || $showOnboardingAnimation}
style:--p-side-size={sideSize}>
{#if showHeader}
{#if showHeader && !$showOnboardingAnimation}
<Navbar {...navbarProps} bind:sideBarIsOpen={$isSidebarOpen} bind:showAccountMenu />
{/if}
@@ -220,7 +222,9 @@
bind:state />
{/if}
<SideNavigation bind:subNavigation />
{#if !$showOnboardingAnimation}
<SideNavigation bind:subNavigation />
{/if}
<div
class="content"
+1
View File
@@ -27,3 +27,4 @@ export function updateLayout(args: updateLayoutArguments) {
}
export const showSubNavigation = writable(false);
export const showOnboardingAnimation = writable(false);
+4 -1
View File
@@ -3,6 +3,7 @@ import Breadcrumbs from './breadcrumbs.svelte';
import type { LayoutLoad } from './$types';
import { sdk } from '$lib/stores/sdk';
import { Dependencies } from '$lib/constants';
import { Platform, Query } from '@appwrite.io/console';
export const load: LayoutLoad = async ({ depends }) => {
depends(Dependencies.FACTORS);
@@ -10,7 +11,9 @@ export const load: LayoutLoad = async ({ depends }) => {
const [factors, identities] = await Promise.all([
sdk.forConsole.account.listMFAFactors(),
sdk.forConsole.account.listIdentities()
sdk.forConsole.account.listIdentities({
queries: [Query.notContains('provider', Platform.Imagine)]
})
]);
return {
@@ -97,6 +97,7 @@
</Layout.Stack>
<Table.Root
class="responsive-table"
let:root
columns={[
{ id: 'client', width: { min: 450 } },
@@ -8,12 +8,13 @@
import { Dependencies } from '$lib/constants';
import { Submit, trackError, trackEvent } from '$lib/actions/analytics';
import { goto, invalidate } from '$app/navigation';
import { base } from '$app/paths';
import { base, resolve } from '$app/paths';
import { addNotification } from '$lib/stores/notifications';
import CreateProject from '$lib/layout/createProject.svelte';
import { loadAvailableRegions } from '$routes/(console)/regions';
import { regions as regionsStore } from '$lib/stores/organization';
import { user } from '$lib/stores/user';
import { showOnboardingAnimation } from '$lib/stores/layout';
let isLoading = false;
let startAnimation = false;
@@ -57,10 +58,17 @@
});
startAnimation = true;
showOnboardingAnimation.set(true);
setTimeout(async () => {
await invalidate(Dependencies.ACCOUNT);
goto(`${base}/project-${project.region ?? 'default'}-${project.$id}`);
await goto(
resolve('/(console)/project-[region]-[project]', {
region: project.region ?? 'default',
project: project.$id
})
);
showOnboardingAnimation.set(false);
}, 3000);
} catch (e) {
trackError(e, Submit.ProjectCreate);
@@ -23,7 +23,7 @@
$: if (prefs) {
const currentNormalized = normalizePrefs(prefs);
const originalNormalized = normalizePrefs(
Object.entries($team.prefs as Record<string, string>)
Object.entries(($team?.prefs ?? {}) as Record<string, string>)
);
arePrefsDisabled = deepEqual(currentNormalized, originalNormalized);
@@ -33,7 +33,7 @@
let arePrefsDisabled = true;
onMount(async () => {
const entries = Object.entries($team.prefs as Record<string, string>);
const entries = Object.entries(($team?.prefs ?? {}) as Record<string, string>);
prefs =
entries.length > 0
? entries.map(([key, value]) => createPrefRow(key, value))
@@ -81,8 +81,9 @@
<Layout.Stack direction="row" alignItems="flex-end">
<InputText
id={`key-${index}`}
bind:value={pref.key}
on:input={() => {
value={pref.key}
on:input={(e) => {
pref.key = (e.currentTarget as HTMLInputElement).value;
prefs = [...prefs];
}}
placeholder="Enter key"
@@ -91,8 +92,9 @@
<Layout.Stack direction="row" alignItems="flex-end" gap="xs">
<InputText
id={`value-${index}`}
bind:value={pref.value}
on:input={() => {
value={pref.value}
on:input={(e) => {
pref.value = (e.currentTarget as HTMLInputElement).value;
prefs = [...prefs];
}}
placeholder="Enter value"
@@ -7,5 +7,8 @@ export const load: PageLoad = async ({ params }) => {
const period = isValueOfStringEnum(UsageRange, params.period)
? params.period
: UsageRange.ThirtyDays;
return sdk.forProject(params.region, params.project).users.getUsage({ range: period });
return {
...(await sdk.forProject(params.region, params.project).users.getUsage({ range: period }))
};
};
@@ -22,7 +22,7 @@
$: if (prefs) {
const currentNormalized = normalizePrefs(prefs);
const originalNormalized = normalizePrefs(Object.entries($user.prefs));
const originalNormalized = normalizePrefs(Object.entries($user?.prefs ?? {}));
arePrefsDisabled = deepEqual(currentNormalized, originalNormalized);
}
@@ -31,7 +31,7 @@
let arePrefsDisabled = true;
onMount(async () => {
const entries = Object.entries($user.prefs);
const entries = Object.entries($user?.prefs ?? {});
prefs =
entries.length > 0
? entries.map(([key, value]) => createPrefRow(key, value))
@@ -78,8 +78,9 @@
<Layout.Stack direction="row" alignItems="flex-end">
<InputText
id={`key-${index}`}
bind:value={pref.key}
on:input={() => {
value={pref.key}
on:input={(e) => {
pref.key = (e.currentTarget as HTMLInputElement).value;
prefs = [...prefs];
}}
placeholder="Enter key"
@@ -88,8 +89,9 @@
<Layout.Stack direction="row" alignItems="flex-end" gap="xs">
<InputText
id={`value-${index}`}
bind:value={pref.value}
on:input={() => {
value={pref.value}
on:input={(e) => {
pref.value = (e.currentTarget as HTMLInputElement).value;
prefs = [...prefs];
}}
placeholder="Enter value"
@@ -8,9 +8,11 @@ export const load: PageLoad = async ({ params }) => {
? params.period
: UsageRange.ThirtyDays;
return sdk.forProject(params.region, params.project).tablesDB.getTableUsage({
databaseId: params.database,
tableId: params.table,
range: period
});
return {
...(await sdk.forProject(params.region, params.project).tablesDB.getTableUsage({
databaseId: params.database,
tableId: params.table,
range: period
}))
};
};
@@ -7,8 +7,11 @@ export const load: PageLoad = async ({ params }) => {
const period = isValueOfStringEnum(UsageRange, params.period)
? params.period
: UsageRange.ThirtyDays;
return sdk.forProject(params.region, params.project).tablesDB.getUsage({
databaseId: params.database,
range: period
});
return {
...(await sdk.forProject(params.region, params.project).tablesDB.getUsage({
databaseId: params.database,
range: period
}))
};
};
@@ -8,5 +8,9 @@ export const load: PageLoad = async ({ params }) => {
? params.period
: UsageRange.ThirtyDays;
return sdk.forProject(params.region, params.project).tablesDB.listUsage({ range: period });
return {
...(await sdk
.forProject(params.region, params.project)
.tablesDB.listUsage({ range: period }))
};
};
@@ -8,8 +8,10 @@ export const load: PageLoad = async ({ params }) => {
? params.period
: UsageRange.ThirtyDays;
return sdk.forProject(params.region, params.project).functions.getUsage({
functionId: params.function,
range: period
});
return {
...(await sdk.forProject(params.region, params.project).functions.getUsage({
functionId: params.function,
range: period
}))
};
};
@@ -8,5 +8,9 @@ export const load: PageLoad = async ({ params }) => {
? params.period
: UsageRange.ThirtyDays;
return sdk.forProject(params.region, params.project).functions.listUsage({ range: period });
return {
...(await sdk
.forProject(params.region, params.project)
.functions.listUsage({ range: period }))
};
};
@@ -23,7 +23,6 @@
view={View.Table}
hideView
hasFilters
hasCustomFiltersOnly
hasSearch
analyticsSource="messaging_providers"
searchPlaceholder="Search by name or ID">
@@ -70,7 +70,6 @@
view={View.Table}
hideView
hasFilters
hasCustomFiltersOnly
hasSearch
analyticsSource="messaging_topics_filter"
searchPlaceholder="Search by name or ID">
@@ -8,8 +8,10 @@ export const load: PageLoad = async ({ params }) => {
? params.period
: UsageRange.ThirtyDays;
return sdk.forProject(params.region, params.project).sites.getUsage({
siteId: params.site,
range: period
});
return {
...(await sdk.forProject(params.region, params.project).sites.getUsage({
siteId: params.site,
range: period
}))
};
};
@@ -7,5 +7,8 @@ export const load: PageLoad = async ({ params }) => {
const period = isValueOfStringEnum(UsageRange, params.period)
? params.period
: UsageRange.ThirtyDays;
return sdk.forProject(params.region, params.project).sites.listUsage({ range: period });
return {
...(await sdk.forProject(params.region, params.project).sites.listUsage({ range: period }))
};
};
@@ -7,5 +7,8 @@ export const load: PageLoad = async ({ params }) => {
const period = isValueOfStringEnum(UsageRange, params.period)
? params.period
: UsageRange.ThirtyDays;
return sdk.forProject(params.region, params.project).storage.getUsage({ range: period });
return {
...(await sdk.forProject(params.region, params.project).storage.getUsage({ range: period }))
};
};