mirror of
https://github.com/appwrite/console.git
synced 2026-06-06 19:27:48 +00:00
Merge remote-tracking branch 'origin/main' into feat-string-types
This commit is contained in:
@@ -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 } },
|
||||
|
||||
@@ -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)}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -27,3 +27,4 @@ export function updateLayout(args: updateLayoutArguments) {
|
||||
}
|
||||
|
||||
export const showSubNavigation = writable(false);
|
||||
export const showOnboardingAnimation = writable(false);
|
||||
|
||||
@@ -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);
|
||||
|
||||
+8
-6
@@ -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"
|
||||
|
||||
+7
-5
@@ -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
-4
@@ -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 }))
|
||||
};
|
||||
};
|
||||
|
||||
+6
-4
@@ -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">
|
||||
|
||||
+6
-4
@@ -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 }))
|
||||
};
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user