fix: migrate tables

This commit is contained in:
Torsten Dittmann
2025-03-18 18:38:47 +01:00
parent 08b3d96abe
commit 76d20ed522
106 changed files with 1989 additions and 2195 deletions
+2 -2
View File
@@ -21,9 +21,9 @@
"dependencies": {
"@appwrite.io/console": "https://pkg.pr.new/appwrite/appwrite/@appwrite.io/console@74f9464",
"@appwrite.io/pink-icons": "0.25.0",
"@appwrite.io/pink-icons-svelte": "https://pkg.pr.new/appwrite/pink/@appwrite.io/pink-icons-svelte@bcb8ad2b",
"@appwrite.io/pink-icons-svelte": "https://pkg.pr.new/appwrite/pink/@appwrite.io/pink-icons-svelte@64948b6 ",
"@appwrite.io/pink-legacy": "^1.0.3",
"@appwrite.io/pink-svelte": "https://pkg.pr.new/appwrite/pink/@appwrite.io/pink-svelte@5a02879",
"@appwrite.io/pink-svelte": "https://pkg.pr.new/appwrite/pink/@appwrite.io/pink-svelte@64948b6 ",
"@popperjs/core": "^2.11.8",
"@sentry/sveltekit": "^8.38.0",
"@stripe/stripe-js": "^3.5.0",
+14 -14
View File
@@ -15,14 +15,14 @@ importers:
specifier: 0.25.0
version: 0.25.0
'@appwrite.io/pink-icons-svelte':
specifier: https://pkg.pr.new/appwrite/pink/@appwrite.io/pink-icons-svelte@bcb8ad2b
version: https://pkg.pr.new/appwrite/pink/@appwrite.io/pink-icons-svelte@bcb8ad2b(svelte@4.2.19)
specifier: 'https://pkg.pr.new/appwrite/pink/@appwrite.io/pink-icons-svelte@64948b6 '
version: https://pkg.pr.new/appwrite/pink/@appwrite.io/pink-icons-svelte@64948b6(svelte@4.2.19)
'@appwrite.io/pink-legacy':
specifier: ^1.0.3
version: 1.0.3
'@appwrite.io/pink-svelte':
specifier: https://pkg.pr.new/appwrite/pink/@appwrite.io/pink-svelte@5a02879
version: https://pkg.pr.new/appwrite/pink/@appwrite.io/pink-svelte@5a02879(react-dom@18.3.1(react@18.3.1))(svelte@4.2.19)
specifier: 'https://pkg.pr.new/appwrite/pink/@appwrite.io/pink-svelte@64948b6 '
version: https://pkg.pr.new/appwrite/pink/@appwrite.io/pink-svelte@64948b6(react-dom@18.3.1(react@18.3.1))(svelte@4.2.19)
'@popperjs/core':
specifier: ^2.11.8
version: 2.11.8
@@ -215,14 +215,14 @@ packages:
resolution: {tarball: https://pkg.pr.new/appwrite/appwrite/@appwrite.io/console@74f9464}
version: 1.2.1
'@appwrite.io/pink-icons-svelte@https://pkg.pr.new/appwrite/pink/@appwrite.io/pink-icons-svelte@5a02879249b98cec27dcc3cfa27bd8b20a50b47a':
resolution: {tarball: https://pkg.pr.new/appwrite/pink/@appwrite.io/pink-icons-svelte@5a02879249b98cec27dcc3cfa27bd8b20a50b47a}
'@appwrite.io/pink-icons-svelte@https://pkg.pr.new/appwrite/pink/@appwrite.io/pink-icons-svelte@64948b6':
resolution: {tarball: https://pkg.pr.new/appwrite/pink/@appwrite.io/pink-icons-svelte@64948b6}
version: 1.0.0-next.7
peerDependencies:
svelte: ^4.0.0
'@appwrite.io/pink-icons-svelte@https://pkg.pr.new/appwrite/pink/@appwrite.io/pink-icons-svelte@bcb8ad2b':
resolution: {tarball: https://pkg.pr.new/appwrite/pink/@appwrite.io/pink-icons-svelte@bcb8ad2b}
'@appwrite.io/pink-icons-svelte@https://pkg.pr.new/appwrite/pink/@appwrite.io/pink-icons-svelte@64948b6b72fb39c9bac60b63a603942291765189':
resolution: {tarball: https://pkg.pr.new/appwrite/pink/@appwrite.io/pink-icons-svelte@64948b6b72fb39c9bac60b63a603942291765189}
version: 1.0.0-next.7
peerDependencies:
svelte: ^4.0.0
@@ -236,8 +236,8 @@ packages:
'@appwrite.io/pink-legacy@1.0.3':
resolution: {integrity: sha512-GGde5fmPhs+s6/3aFeMPc/kKADG/gTFkYQSy6oBN8pK0y0XNCLrZZgBv+EBbdhwdtqVEWXa0X85Mv9w7jcIlwQ==}
'@appwrite.io/pink-svelte@https://pkg.pr.new/appwrite/pink/@appwrite.io/pink-svelte@5a02879':
resolution: {tarball: https://pkg.pr.new/appwrite/pink/@appwrite.io/pink-svelte@5a02879}
'@appwrite.io/pink-svelte@https://pkg.pr.new/appwrite/pink/@appwrite.io/pink-svelte@64948b6':
resolution: {tarball: https://pkg.pr.new/appwrite/pink/@appwrite.io/pink-svelte@64948b6}
version: 1.0.0-next.85
peerDependencies:
react-dom: ^18.0.0
@@ -4099,11 +4099,11 @@ snapshots:
'@appwrite.io/console@https://pkg.pr.new/appwrite/appwrite/@appwrite.io/console@74f9464': {}
'@appwrite.io/pink-icons-svelte@https://pkg.pr.new/appwrite/pink/@appwrite.io/pink-icons-svelte@5a02879249b98cec27dcc3cfa27bd8b20a50b47a(svelte@4.2.19)':
'@appwrite.io/pink-icons-svelte@https://pkg.pr.new/appwrite/pink/@appwrite.io/pink-icons-svelte@64948b6(svelte@4.2.19)':
dependencies:
svelte: 4.2.19
'@appwrite.io/pink-icons-svelte@https://pkg.pr.new/appwrite/pink/@appwrite.io/pink-icons-svelte@bcb8ad2b(svelte@4.2.19)':
'@appwrite.io/pink-icons-svelte@https://pkg.pr.new/appwrite/pink/@appwrite.io/pink-icons-svelte@64948b6b72fb39c9bac60b63a603942291765189(svelte@4.2.19)':
dependencies:
svelte: 4.2.19
@@ -4116,9 +4116,9 @@ snapshots:
'@appwrite.io/pink-icons': 1.0.0
the-new-css-reset: 1.11.3
'@appwrite.io/pink-svelte@https://pkg.pr.new/appwrite/pink/@appwrite.io/pink-svelte@5a02879(react-dom@18.3.1(react@18.3.1))(svelte@4.2.19)':
'@appwrite.io/pink-svelte@https://pkg.pr.new/appwrite/pink/@appwrite.io/pink-svelte@64948b6(react-dom@18.3.1(react@18.3.1))(svelte@4.2.19)':
dependencies:
'@appwrite.io/pink-icons-svelte': https://pkg.pr.new/appwrite/pink/@appwrite.io/pink-icons-svelte@5a02879249b98cec27dcc3cfa27bd8b20a50b47a(svelte@4.2.19)
'@appwrite.io/pink-icons-svelte': https://pkg.pr.new/appwrite/pink/@appwrite.io/pink-icons-svelte@64948b6b72fb39c9bac60b63a603942291765189(svelte@4.2.19)
'@floating-ui/dom': 1.6.13
'@melt-ui/pp': 0.3.2(@melt-ui/svelte@0.86.3(svelte@4.2.19))(svelte@4.2.19)
'@melt-ui/svelte': 0.86.3(svelte@4.2.19)
+25 -25
View File
@@ -60,11 +60,11 @@
</svelte:fragment>
</Alert.Inline>
<Table.Root>
<svelte:fragment slot="header">
<Table.Header.Cell>Resource</Table.Header.Cell>
<Table.Header.Cell>Free limit</Table.Header.Cell>
<Table.Header.Cell>
<Table.Root columns={3} let:root>
<svelte:fragment slot="header" let:root>
<Table.Header.Cell {root}>Resource</Table.Header.Cell>
<Table.Header.Cell {root}>Free limit</Table.Header.Cell>
<Table.Header.Cell {root}>
Excess usage <Tooltip
><Icon icon={IconInfo} />
<span slot="tooltip">Metrics are estimates updated every 24 hours</span>
@@ -72,37 +72,37 @@
</Table.Header.Cell>
</svelte:fragment>
{#if excess?.members}
<Table.Row>
<Table.Cell>Organization members</Table.Cell>
<Table.Cell>{plan.members} members</Table.Cell>
<Table.Cell>
<Table.Row.Base {root}>
<Table.Cell {root}>Organization members</Table.Cell>
<Table.Cell {root}>{plan.members} members</Table.Cell>
<Table.Cell {root}>
<p class="u-color-text-danger u-flex u-cross-center u-gap-4">
<span class="icon-arrow-up" />
{excess?.members} members
</p>
</Table.Cell>
</Table.Row>
</Table.Row.Base>
{/if}
{#if excess?.storage}
<Table.Row>
<Table.Cell>Storage</Table.Cell>
<Table.Cell>{plan.storage} GB</Table.Cell>
<Table.Cell>
<Table.Row.Base {root}>
<Table.Cell {root}>Storage</Table.Cell>
<Table.Cell {root}>{plan.storage} GB</Table.Cell>
<Table.Cell {root}>
<p class="u-color-text-danger">
<span class="icon-arrow-up" />
{humanFileSize(excess?.storage).value}
{humanFileSize(excess?.storage).unit}
</p>
</Table.Cell>
</Table.Row>
</Table.Row.Base>
{/if}
{#if excess?.executions}
<Table.Row>
<Table.Cell>Function executions</Table.Cell>
<Table.Cell>
<Table.Row.Base {root}>
<Table.Cell {root}>Function executions</Table.Cell>
<Table.Cell {root}>
{abbreviateNumber(plan.executions)} executions
</Table.Cell>
<Table.Cell>
<Table.Cell {root}>
<p class="u-color-text-danger">
<span class="icon-arrow-up" />
<span
@@ -113,15 +113,15 @@
</span>
</p>
</Table.Cell>
</Table.Row>
</Table.Row.Base>
{/if}
{#if excess?.users}
<Table.Row>
<Table.Cell>Users</Table.Cell>
<Table.Cell>
<Table.Row.Base {root}>
<Table.Cell {root}>Users</Table.Cell>
<Table.Cell {root}>
{abbreviateNumber(plan.users)} users
</Table.Cell>
<Table.Cell>
<Table.Cell column="usage" {root}>
<p class="u-color-text-danger">
<span class="icon-arrow-up" />
<span title={excess?.users ? excess.users.toString() : 'users'}>
@@ -129,7 +129,7 @@
</span>
</p>
</Table.Cell>
</Table.Row>
</Table.Row.Base>
{/if}
</Table.Root>
{/if}
+19 -21
View File
@@ -62,42 +62,40 @@
following rates. Next billing period: {toLocaleDate(nextDate)}.
</Typography.Text>
{/if}
<Table.Root>
<svelte:fragment slot="header">
<Table.Header.Cell>Resource</Table.Header.Cell>
<Table.Header.Cell>Limit</Table.Header.Cell>
{#if !isFree}
<Table.Header.Cell>Rate</Table.Header.Cell>
{/if}
<Table.Root
columns={[{ id: 'resource' }, { id: 'limit' }, { id: 'rate', hide: isFree }]}
let:root>
<svelte:fragment slot="header" let:root>
<Table.Header.Cell column="resource" {root}>Resource</Table.Header.Cell>
<Table.Header.Cell column="limit" {root}>Limit</Table.Header.Cell>
<Table.Header.Cell column="rate" {root}>Rate</Table.Header.Cell>
</svelte:fragment>
{#each planData as usage}
{#if usage['id'] === 'members'}
<Table.Row>
<Table.Cell>{usage.resource}</Table.Cell>
<Table.Cell>
<Table.Row.Base {root}>
<Table.Cell column="resource" {root}>{usage.resource}</Table.Cell>
<Table.Cell column="limit" {root}>
{plan[usage.id] || 'Unlimited'}
</Table.Cell>
{#if !isFree}
<Table.Cell>
{formatCurrency(plan.addons?.member?.price)}/{usage?.unit}
</Table.Cell>
{/if}
</Table.Row>
<Table.Cell column="rate" {root}>
{formatCurrency(plan.addons?.member?.price)}/{usage?.unit}
</Table.Cell>
</Table.Row.Base>
{:else}
{@const addon = plan.addons[usage.id]}
<Table.Row>
<Table.Cell>{usage.resource}</Table.Cell>
<Table.Cell>
<Table.Row.Base {root}>
<Table.Cell column="resource" {root}>{usage.resource}</Table.Cell>
<Table.Cell column="limit" {root}>
{abbreviateNumber(plan[usage.id])}{usage?.unit}
</Table.Cell>
{#if !isFree}
<Table.Cell>
<Table.Cell column="rate" {root}>
{formatCurrency(addon?.price)}/{['MB', 'GB', 'TB'].includes(addon?.unit)
? addon?.value
: abbreviateNumber(addon?.value, 0)}{usage?.unit}
</Table.Cell>
{/if}
</Table.Row>
</Table.Row.Base>
{/if}
{/each}
</Table.Root>
+7 -4
View File
@@ -2,12 +2,14 @@
import type { PaymentMethodData } from '$lib/sdk/billing';
import { Badge, Layout, Link, Popover, Table } from '@appwrite.io/pink-svelte';
import CreditCardBrandImage from './creditCardBrandImage.svelte';
import type { RootProp } from '@appwrite.io/pink-svelte/dist/table';
export let root: RootProp;
export let paymentMethod: PaymentMethodData;
export let isBackup: boolean = false;
</script>
<Table.Cell>
<Table.Cell column="cc" {root}>
<Layout.Stack direction="row" alignItems="center" gap="s">
<CreditCardBrandImage brand={paymentMethod?.brand} />
<span>ending in {paymentMethod?.last4}</span>
@@ -16,9 +18,10 @@
{/if}
</Layout.Stack>
</Table.Cell>
<Table.Cell>{paymentMethod?.name}</Table.Cell>
<Table.Cell>{paymentMethod?.expiryMonth}/{paymentMethod?.expiryYear}</Table.Cell>
<Table.Cell>
<Table.Cell column="name" {root}>{paymentMethod?.name}</Table.Cell>
<Table.Cell column="expiry" {root}
>{paymentMethod?.expiryMonth}/{paymentMethod?.expiryYear}</Table.Cell>
<Table.Cell column="status" {root}>
{#if paymentMethod?.lastError || paymentMethod?.expired}
<Popover let:toggle>
<Layout.Stack gap="xs" direction="row">
+27 -16
View File
@@ -1,5 +1,5 @@
<script lang="ts">
import { Id, Trim } from '.';
import { Id } from '.';
import { Button } from '$lib/elements/forms';
import { sdk } from '$lib/stores/sdk';
import { ID, Query, Permission, Role } from '@appwrite.io/console';
@@ -9,7 +9,6 @@
import InputSearch from '$lib/elements/forms/inputSearch.svelte';
import { writable } from 'svelte/store';
import { onMount } from 'svelte';
import { clickOnEnter } from '$lib/helpers/a11y';
import { base } from '$app/paths';
import { page } from '$app/stores';
import {
@@ -256,25 +255,37 @@
</Layout.Grid>
{/if}
{#if view === 'list'}
<Table.Root>
<svelte:fragment slot="header">
<Table.Header.Cell>Filename</Table.Header.Cell>
<Table.Header.Cell width="140px">
<Table.Root
let:root
columns={[
{ id: 'filename', width: { min: 140 } },
{ id: 'id', width: { min: 100 } },
{ id: 'type', width: { min: 100 } },
{ id: 'size', width: { min: 100 } },
{ id: 'created', width: { min: 120 } }
]}>
<svelte:fragment slot="header" let:root>
<Table.Header.Cell column="filename" {root}>
Filename
</Table.Header.Cell>
<Table.Header.Cell column="id" {root}>
ID
</Table.Header.Cell>
<Table.Header.Cell width="100px">
<Table.Header.Cell column="type" {root}>
Type
</Table.Header.Cell>
<Table.Header.Cell width="100px">
<Table.Header.Cell column="size" {root}>
Size
</Table.Header.Cell>
<Table.Header.Cell width="120px">
<Table.Header.Cell column="created" {root}>
Created
</Table.Header.Cell>
</svelte:fragment>
{#each response?.files as file}
<Table.Button on:click={() => selectFile(file)}>
<Table.Cell>
<Table.Row.Button
{root}
on:click={() => selectFile(file)}>
<Table.Cell column="filename" {root}>
<div
class="u-inline-flex u-cross-center u-gap-12">
<Selector.Radio
@@ -297,19 +308,19 @@
</Typography.Text>
</div>
</Table.Cell>
<Table.Cell>
<Table.Cell column="id" {root}>
<Id value={file.$id}>{file.$id}</Id>
</Table.Cell>
<Table.Cell>
<Table.Cell column="type" {root}>
{file.mimeType}
</Table.Cell>
<Table.Cell>
<Table.Cell column="size" {root}>
{calculateSize(file.sizeOriginal)}
</Table.Cell>
<Table.Cell>
<Table.Cell column="created" {root}>
{toLocaleDate(file.$createdAt)}
</Table.Cell>
</Table.Button>
</Table.Row.Button>
{/each}
</Table.Root>
{/if}
+8 -8
View File
@@ -136,10 +136,10 @@
</Layout.Stack>
{#if selectedInstallation}
{#await loadRepositories(selectedInstallation, search)}
<Table.Root>
<Table.Root columns={1} let:root>
{#each Array(4) as _}
<Table.Row>
<Table.Cell>
<Table.Row.Base {root}>
<Table.Cell {root}>
<Layout.Stack direction="row" alignItems="center">
<Skeleton variant="circle" width={24} />
@@ -149,7 +149,7 @@
<Skeleton variant="line" width={76} height={32} />
</Layout.Stack>
</Table.Cell>
</Table.Row>
</Table.Row.Base>
{/each}
</Table.Root>
{:then response}
@@ -159,10 +159,10 @@
let:paginatedItems
hideFooter={response?.length <= 6}
limit={6}>
<Table.Root>
<Table.Root columns={1} let:root>
{#each paginatedItems as repo}
<Table.Row>
<Table.Cell>
<Table.Row.Base {root}>
<Table.Cell {root}>
<Layout.Stack direction="row" alignItems="center" gap="s">
{#if action === 'select'}
<input
@@ -229,7 +229,7 @@
{/if}
</Layout.Stack>
</Table.Cell>
</Table.Row>
</Table.Row.Base>
{/each}
</Table.Root>
</Paginator>
@@ -18,6 +18,7 @@
import Row from './row.svelte';
import { Icon, Selector, Table } from '@appwrite.io/pink-svelte';
import { IconPlus, IconX } from '@appwrite.io/pink-icons-svelte';
import type { Column } from '@appwrite.io/pink-svelte/dist/table';
export let withCreate = false;
export let permissions: string[] = [];
@@ -124,59 +125,65 @@
return a.localeCompare(b);
}
const columns: Column[] = [
{ id: 'role', width: { min: 100 } },
{ id: 'create', width: { min: 100 }, hide: !withCreate },
{ id: 'read', width: { min: 100 } },
{ id: 'update', width: { min: 100 } },
{ id: 'delete', width: { min: 100 } },
{ id: 'action', width: 40 }
];
</script>
{#if [...$groups]?.length}
<div class="table-wrapper">
<Table.Root>
<svelte:fragment slot="header">
<Table.Header.Cell>Role</Table.Header.Cell>
<Table.Root {columns} let:root>
<svelte:fragment slot="header" let:root>
<Table.Header.Cell column="role" {root}>Role</Table.Header.Cell>
{#if withCreate}
<Table.Header.Cell width="90px">Create</Table.Header.Cell>
<Table.Header.Cell column="create" {root}>Create</Table.Header.Cell>
{/if}
<Table.Header.Cell width="90px">Read</Table.Header.Cell>
<Table.Header.Cell width="90px">Update</Table.Header.Cell>
<Table.Header.Cell width="90px">Delete</Table.Header.Cell>
<Table.Header.Cell width="40px" />
<Table.Header.Cell column="read" {root}>Read</Table.Header.Cell>
<Table.Header.Cell column="update" {root}>Update</Table.Header.Cell>
<Table.Header.Cell column="delete" {root}>Delete</Table.Header.Cell>
<Table.Header.Cell column="action" {root} />
</svelte:fragment>
{#each [...$groups].sort(sortRoles) as [role, permission] (role)}
<Table.Row>
<Table.Cell>
<Table.Row.Base {root}>
<Table.Cell column="role" {root}>
<Row {role} />
</Table.Cell>
{#if withCreate}
<Table.Cell>
<Selector.Checkbox
checked={permission.create}
on:change={() => togglePermission(role, 'create')} />
</Table.Cell>
{/if}
<Table.Cell>
<Table.Cell column="create" {root}>
<Selector.Checkbox
checked={permission.create}
on:change={() => togglePermission(role, 'create')} />
</Table.Cell>
<Table.Cell column="read" {root}>
<Selector.Checkbox
size="s"
checked={permission.read}
on:change={() => togglePermission(role, 'read')} />
</Table.Cell>
<Table.Cell>
<Table.Cell column="update" {root}>
<Selector.Checkbox
size="s"
checked={permission.update}
on:change={() => togglePermission(role, 'update')} />
</Table.Cell>
<Table.Cell>
<Table.Cell column="delete" {root}>
<Selector.Checkbox
size="s"
checked={permission.delete}
on:change={() => togglePermission(role, 'delete')} />
</Table.Cell>
<Table.Cell>
<Table.Cell column="action" {root}>
<Button compact icon ariaLabel="delete" on:click={() => deleteRole(role)}>
<Icon icon={IconX} size="s" />
</Button>
</Table.Cell>
</Table.Row>
</Table.Row.Base>
{/each}
</Table.Root>
</div>
+8 -8
View File
@@ -78,24 +78,24 @@
{#if [...$groups.keys()]?.length}
<Layout.Stack>
<Table.Root>
<svelte:fragment slot="header">
<Table.Header.Cell>Role</Table.Header.Cell>
<Table.Header.Cell width="40px" />
<Table.Root columns={[{ id: 'role' }, { id: 'action', width: 40 }]} let:root>
<svelte:fragment slot="header" let:root>
<Table.Header.Cell column="role" {root}>Role</Table.Header.Cell>
<Table.Header.Cell column="action" {root} />
</svelte:fragment>
{#each [...$groups.keys()].sort(sortRoles) as role}
<Table.Row>
<Table.Cell>
<Table.Row.Base {root}>
<Table.Cell column="role" {root}>
<Row {role} />
</Table.Cell>
<Table.Cell>
<Table.Cell column="action" {root}>
<Layout.Stack justifyContent="flex-end">
<Button compact icon on:click={() => deleteRole(role)}>
<Icon icon={IconX} size="s" />
</Button>
</Layout.Stack>
</Table.Cell>
</Table.Row>
</Table.Row.Base>
{/each}
</Table.Root>
<Actions
+5 -5
View File
@@ -78,18 +78,18 @@
>.</Typography.Text>
<InputSearch autofocus placeholder="Search by name or ID" bind:value={search} />
{#if results?.teams?.length}
<Table.Root>
<Table.Root columns={[{ id: 'checkbox', width: 40 }, { id: 'team' }]} let:root>
{#each results.teams as team (team.$id)}
{@const role = `team:${team.$id}`}
{@const exists = $groups.has(role)}
<Table.Button on:click={() => onSelection(role)} disabled={exists}>
<Table.Cell width="40px">
<Table.Row.Button {root} on:click={() => onSelection(role)} disabled={exists}>
<Table.Cell column="checkbox" {root}>
<Selector.Checkbox
id={team.$id}
checked={exists || selected.has(role)}
disabled={exists} />
</Table.Cell>
<Table.Cell>
<Table.Cell column="team" {root}>
<Layout.Stack direction="row" alignItems="center" gap="s">
<AvatarInitials size="xs" name={team.name} />
<Layout.Stack gap="none">
@@ -102,7 +102,7 @@
</Layout.Stack>
</Layout.Stack>
</Table.Cell>
</Table.Button>
</Table.Row.Button>
{/each}
</Table.Root>
<div class="u-flex u-margin-block-start-32 u-main-space-between">
+5 -5
View File
@@ -77,19 +77,19 @@
<Typography.Text>Grant access to any authenticated or anonymous user.</Typography.Text>
<InputSearch autofocus placeholder="Search by name, email, phone or ID" bind:value={search} />
{#if results?.users?.length}
<Table.Root>
<Table.Root columns={[{ id: 'checkbox', width: 40 }, { id: 'user' }]} let:root>
{#each results.users as user (user.$id)}
{@const role = `user:${user.$id}`}
{@const exists = $groups.has(role)}
<Table.Button on:click={() => onSelection(role)} disabled={exists}>
<Table.Cell width="40px">
<Table.Row.Button {root} on:click={() => onSelection(role)} disabled={exists}>
<Table.Cell column="checkbox" {root}>
<Selector.Checkbox
size="s"
id={user.$id}
checked={exists || selected.has(role)}
disabled={exists} />
</Table.Cell>
<Table.Cell>
<Table.Cell column="user" {root}>
<Layout.Stack direction="row" alignItems="center" gap="s">
{#if user.email || user.phone}
{#if user.name}
@@ -135,7 +135,7 @@
{/if}
</Layout.Stack>
</Table.Cell>
</Table.Button>
</Table.Row.Button>
{/each}
</Table.Root>
+12 -14
View File
@@ -29,7 +29,7 @@
const prefs = preferences.getCustomCollectionColumns($page.params.collection);
columns.set(
$columns.map((column) => {
column.show = prefs?.includes(column.id) ?? true;
column.hide = prefs?.includes(column.id) ?? false;
return column;
})
);
@@ -40,7 +40,7 @@
if (prefs?.columns) {
columns.set(
$columns.map((column) => {
column.show = prefs.columns?.includes(column.id) ?? true;
column.hide = prefs.columns?.includes(column.id) ?? false;
return column;
})
);
@@ -48,7 +48,7 @@
}
columns.subscribe((ctx) => {
const columns = ctx.filter((n) => n.show).map((n) => n.id);
const columns = ctx.filter((n) => n.hide === true).map((n) => n.id);
if (isCustomCollection) {
preferences.setCustomCollectionColumns(columns);
@@ -75,14 +75,11 @@
});
}
$: selectedColumnsNumber = $columns
.filter((c) => !c.hide)
.reduce((acc, column) => {
if (column.show) {
acc++;
}
return acc;
}, 0);
$: selectedColumnsNumber = $columns.reduce((acc, column) => {
if (column.hide === true) return acc;
return ++acc;
}, 0);
</script>
{#if !hideColumns && view === View.Table}
@@ -98,14 +95,15 @@
<svelte:fragment slot="tooltip">
<ActionMenu.Root>
<Layout.Stack>
{#each $columns.filter((c) => !c.hide) as column}
{#each $columns as column}
<InputCheckbox
id={column.id}
label={column.title}
bind:checked={column.show}
on:change={() => (column.hide = !column.hide)}
checked={!column.hide}
disabled={allowNoColumns
? false
: selectedColumnsNumber <= 1 && column.show} />
: selectedColumnsNumber <= 1 && column.hide !== true} />
{/each}
</Layout.Stack>
</ActionMenu.Root>
+2 -14
View File
@@ -1,3 +1,4 @@
import type { Column as PinkColumn } from '@appwrite.io/pink-svelte/dist/table';
import type { Writable } from 'svelte/store';
export type WritableValue<T> = T extends Writable<infer U> ? U : never;
@@ -23,26 +24,13 @@ const columnTypes = [
'enum'
] as const;
export type ColumnType = (typeof columnTypes)[number];
export type Column = {
id: string;
export type Column = PinkColumn & {
title: string;
type: ColumnType;
/**
* Set to false to hide by default
*/
show: boolean;
width?: number;
/**
* Set to false to disable filtering for this column
*/
filter?: boolean;
array?: boolean;
format?: string;
elements?: string[] | { value: string | number; label: string }[];
/**
* Set to true to hide this column by default
*/
hide?: boolean;
};
export function isValueOfStringEnum<T extends Record<string, string>>(
+24 -15
View File
@@ -3,27 +3,36 @@
import { Container } from '$lib/layout';
import { toLocaleDateTime } from '$lib/helpers/date';
import type { Models } from '@appwrite.io/console';
import { Layout, Table, Card, Empty, Typography } from '@appwrite.io/pink-svelte';
import { Layout, Table, Card, Empty } from '@appwrite.io/pink-svelte';
import Button from '$lib/elements/forms/button.svelte';
import type { Column } from '@appwrite.io/pink-svelte/dist/table';
export let logs: Models.LogList;
export let offset = 0;
export let limit = 0;
const columns: Column[] = [
{ id: 'user' },
{ id: 'event' },
{ id: 'location' },
{ id: 'ip' },
{ id: 'date' }
];
</script>
<Container>
{#if logs.total}
<Table.Root>
<svelte:fragment slot="header">
<Table.Header.Cell>User</Table.Header.Cell>
<Table.Header.Cell>Event</Table.Header.Cell>
<Table.Header.Cell>Location</Table.Header.Cell>
<Table.Header.Cell>IP</Table.Header.Cell>
<Table.Header.Cell>Date</Table.Header.Cell>
<Table.Root {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>
<Table.Header.Cell column="location" {root}>Location</Table.Header.Cell>
<Table.Header.Cell column="ip" {root}>IP</Table.Header.Cell>
<Table.Header.Cell column="date" {root}>Date</Table.Header.Cell>
</svelte:fragment>
{#each logs.logs as log}
<Table.Row>
<Table.Cell>
<Table.Row.Base {root}>
<Table.Cell column="user" {root}>
<Layout.Stack direction="row" alignItems="center">
{#if log.userEmail}
{#if log.userName}
@@ -41,23 +50,23 @@
{/if}
</Layout.Stack>
</Table.Cell>
<Table.Cell>
<Table.Cell column="event" {root}>
{log.event}
</Table.Cell>
<Table.Cell>
<Table.Cell column="location" {root}>
{#if log.countryCode !== '--'}
{log.countryName}
{:else}
Unknown
{/if}
</Table.Cell>
<Table.Cell>
<Table.Cell column="ip" {root}>
{log.ip}
</Table.Cell>
<Table.Cell>
<Table.Cell column="date" {root}>
{toLocaleDateTime(log.time)}
</Table.Cell>
</Table.Row>
</Table.Row.Base>
{/each}
</Table.Root>
+26 -26
View File
@@ -215,20 +215,20 @@
</div>
{#if selectedRequest === 'parameters'}
{#if parameters?.length}
<Table.Root>
<svelte:fragment slot="header">
<Table.Header.Cell>Name</Table.Header.Cell>
<Table.Header.Cell>Value</Table.Header.Cell>
<Table.Root columns={2} let:root>
<svelte:fragment slot="header" let:root>
<Table.Header.Cell {root}>Name</Table.Header.Cell>
<Table.Header.Cell {root}>Value</Table.Header.Cell>
</svelte:fragment>
{#each parameters as param}
<Table.Row>
<Table.Cell>
<Table.Row.Base {root}>
<Table.Cell {root}>
{param.key}
</Table.Cell>
<Table.Cell>
<Table.Cell {root}>
{param.value}
</Table.Cell>
</Table.Row>
</Table.Row.Base>
{/each}
</Table.Root>
{/if}
@@ -249,20 +249,20 @@
</p>
{:else if selectedRequest === 'headers'}
{#if execution.requestHeaders.length}
<Table.Root>
<svelte:fragment slot="header">
<Table.Header.Cell>Name</Table.Header.Cell>
<Table.Header.Cell>Value</Table.Header.Cell>
<Table.Root columns={2} let:root>
<svelte:fragment slot="header" let:root>
<Table.Header.Cell {root}>Name</Table.Header.Cell>
<Table.Header.Cell {root}>Value</Table.Header.Cell>
</svelte:fragment>
{#each execution.requestHeaders as header}
<Table.Row>
<Table.Cell>
{header.key}
<Table.Row.Base {root}>
<Table.Cell {root}>
{header.name}
</Table.Cell>
<Table.Cell>
<Table.Cell {root}>
{header.value}
</Table.Cell>
</Table.Row>
</Table.Row.Base>
{/each}
</Table.Root>
{/if}
@@ -363,20 +363,20 @@
{/if}
{:else if selectedResponse === 'headers'}
{#if execution.responseHeaders.length}
<Table.Root>
<svelte:fragment slot="header">
<Table.Header.Cell>Name</Table.Header.Cell>
<Table.Header.Cell>Value</Table.Header.Cell>
<Table.Root columns={2} let:root>
<svelte:fragment slot="header" let:root>
<Table.Header.Cell {root}>Name</Table.Header.Cell>
<Table.Header.Cell {root}>Value</Table.Header.Cell>
</svelte:fragment>
{#each execution.responseHeaders as header}
<Table.Row>
<Table.Cell>
{header.key}
<Table.Row.Base {root}>
<Table.Cell {root}>
{header.name}
</Table.Cell>
<Table.Cell>
<Table.Cell {root}>
{header.value}
</Table.Cell>
</Table.Row>
</Table.Row.Base>
{/each}
</Table.Root>
{/if}
+42 -39
View File
@@ -72,25 +72,30 @@
</Layout.Stack>
{/if}
{#if rules.total}
<Table.Root>
<svelte:fragment slot="header">
<Table.Header.Cell>Name</Table.Header.Cell>
<Table.Header.Cell>Verification status</Table.Header.Cell>
<Table.Header.Cell>Certificate status</Table.Header.Cell>
{#if $canWriteRules}
<Table.Header.Cell width="20px" />
{/if}
<Table.Root
let:root
columns={[
{ id: 'name' },
{ id: 'verification' },
{ id: 'certificate' },
{ id: 'action', width: 20, hide: !$canWriteRules }
]}>
<svelte:fragment slot="header" let:root>
<Table.Header.Cell column="name" {root}>Name</Table.Header.Cell>
<Table.Header.Cell column="verification" {root}>Verification status</Table.Header.Cell>
<Table.Header.Cell column="certificate" {root}>Certificate status</Table.Header.Cell>
<Table.Header.Cell column="action" {root} />
</svelte:fragment>
{#each rules.rules as domain}
<Table.Row>
<Table.Cell>
<Table.Row.Base {root}>
<Table.Cell column="name" {root}>
<Layout.Stack direction="row" alignItems="center">
<Link.Anchor href={`https://${domain.domain}`} target="_blank">
{domain.domain}
</Link.Anchor>
</Layout.Stack>
</Table.Cell>
<Table.Cell>
<Table.Cell column="verification" {root}>
{#if domain.status === 'created'}
<Layout.Stack direction="row" alignItems="center">
<Badge variant="secondary" content="failed" type="error">
@@ -106,7 +111,7 @@
</Badge>
{/if}
</Table.Cell>
<Table.Cell>
<Table.Cell column="certificate" {root}>
{#if domain.status === 'unverified'}
<Layout.Stack direction="row" alignItems="center">
<Badge variant="secondary" content="failed" type="error">
@@ -133,35 +138,33 @@
</Badge>
{/if}
</Table.Cell>
{#if $canWriteRules}
<Table.Cell>
<Popover let:toggle placement="bottom-start" padding="none">
<Button text icon ariaLabel="more options" on:click={toggle}>
<Icon icon={IconDotsHorizontal} size="s" />
</Button>
<ActionMenu.Root slot="tooltip">
{#if domain.status !== 'verified'}
<ActionMenu.Item.Button
leadingIcon={IconRefresh}
on:click={() => openRetry(domain)}>
{domain.status === 'unverified'
? 'Retry generation'
: 'Retry verification'}
</ActionMenu.Item.Button>
{/if}
<Table.Cell column="action" {root}>
<Popover let:toggle placement="bottom-start" padding="none">
<Button text icon ariaLabel="more options" on:click={toggle}>
<Icon icon={IconDotsHorizontal} size="s" />
</Button>
<ActionMenu.Root slot="tooltip">
{#if domain.status !== 'verified'}
<ActionMenu.Item.Button
leadingIcon={IconTrash}
on:click={() => {
selectedDomain = domain;
showDelete = true;
}}>
Delete
leadingIcon={IconRefresh}
on:click={() => openRetry(domain)}>
{domain.status === 'unverified'
? 'Retry generation'
: 'Retry verification'}
</ActionMenu.Item.Button>
</ActionMenu.Root>
</Popover>
</Table.Cell>
{/if}
</Table.Row>
{/if}
<ActionMenu.Item.Button
leadingIcon={IconTrash}
on:click={() => {
selectedDomain = domain;
showDelete = true;
}}>
Delete
</ActionMenu.Item.Button>
</ActionMenu.Root>
</Popover>
</Table.Cell>
</Table.Row.Base>
{/each}
</Table.Root>
{:else}
+14 -12
View File
@@ -12,25 +12,27 @@
$: cnameValue = domain.domain.replace('.' + registerable, '');
</script>
<Table.Root>
<svelte:fragment slot="header">
<Table.Header.Cell>Type</Table.Header.Cell>
<Table.Header.Cell>Name</Table.Header.Cell>
<Table.Header.Cell>Value</Table.Header.Cell>
<Table.Header.Cell width="40px" />
<Table.Root
let:root
columns={[{ id: 'type' }, { id: 'name' }, { id: 'value' }, { id: 'action', width: 40 }]}>
<svelte:fragment slot="header" let:root>
<Table.Header.Cell column="type" {root}>Type</Table.Header.Cell>
<Table.Header.Cell column="name" {root}>Name</Table.Header.Cell>
<Table.Header.Cell column="value" {root}>Value</Table.Header.Cell>
<Table.Header.Cell column="action" {root} />
</svelte:fragment>
<Table.Row>
<Table.Cell>CNAME</Table.Cell>
<Table.Cell>{cnameValue}</Table.Cell>
<Table.Cell>
<Table.Row.Base {root}>
<Table.Cell column="type" {root}>CNAME</Table.Cell>
<Table.Cell column="name" {root}>{cnameValue}</Table.Cell>
<Table.Cell column="value" {root}>
{target}
</Table.Cell>
<Table.Cell>
<Table.Cell column="action" {root}>
<Button text>
<Copy value={target}>
<span class="icon-duplicate" aria-hidden="true" />
</Copy>
</Button>
</Table.Cell>
</Table.Row>
</Table.Row.Base>
</Table.Root>
+23 -18
View File
@@ -45,18 +45,26 @@
it here." />
</Card.Base>
{:else}
<Table.Root>
<svelte:fragment slot="header">
<Table.Header.Cell>Provider</Table.Header.Cell>
<Table.Header.Cell>Email</Table.Header.Cell>
<Table.Header.Cell>Created At</Table.Header.Cell>
<Table.Header.Cell>Expiry Date</Table.Header.Cell>
<Table.Header.Cell width="40px" />
<Table.Root
let:root
columns={[
{ id: 'provider' },
{ id: 'email' },
{ id: 'createdAt' },
{ id: 'expiryDate' },
{ id: 'actions', width: 40 }
]}>
<svelte:fragment slot="header" let:root>
<Table.Header.Cell column="provider" {root}>Provider</Table.Header.Cell>
<Table.Header.Cell column="email" {root}>Email</Table.Header.Cell>
<Table.Header.Cell column="createdAt" {root}>Created At</Table.Header.Cell>
<Table.Header.Cell column="expiryDate" {root}>Expiry Date</Table.Header.Cell>
<Table.Header.Cell column="actions" {root} />
</svelte:fragment>
{#each $identities as identity (identity.$id)}
{@const provider = oAuthProviders[identity.provider]}
<Table.Row>
<Table.Cell>
<Table.Row.Base {root}>
<Table.Cell column="provider" {root}>
<Layout.Stack direction="row" alignItems="center">
<div class="avatar is-size-small">
<span
@@ -66,24 +74,21 @@
{provider.name}
</Layout.Stack>
</Table.Cell>
<Table.Cell>
<Table.Cell column="email" {root}>
{identity.providerEmail}
</Table.Cell>
<Table.Cell>
<Table.Cell column="createdAt" {root}>
{toLocaleDateTime(identity.$createdAt)}
</Table.Cell>
<Table.Cell>
<Table.Cell column="expiryDate" {root}>
{toLocaleDateTime(identity.providerAccessTokenExpiry)}
</Table.Cell>
<Table.Cell>
<Button
text
class="is-only-icon"
on:click={() => deleteIdentity(identity.$id)}>
<Table.Cell column="actions" {root}>
<Button text on:click={() => deleteIdentity(identity.$id)}>
<Icon icon={IconTrash} size="s" />
</Button>
</Table.Cell>
</Table.Row>
</Table.Row.Base>
{/each}
</Table.Root>
{/if}
@@ -37,18 +37,18 @@
</Button>
</Copy>
</Layout.Stack>
<Table.Root>
<Table.Root columns={[{ id: 'code' }, { id: 'action', width: 24 }]} let:root>
{#each codes.recoveryCodes as code}
<Table.Row>
<Table.Cell>
<Table.Row.Base {root}>
<Table.Cell column="code" {root}>
<Output value={code} hideCopyIcon>{code}</Output>
</Table.Cell>
<Table.Cell width="24px">
<Table.Cell column="action" {root}>
<Copy value={code}>
<Icon icon={IconDuplicate} />
</Copy>
</Table.Cell>
</Table.Row>
</Table.Row.Base>
{/each}
</Table.Root>
{/if}
@@ -48,7 +48,9 @@
View or update your billing address. This address will be included in your invoices from Appwrite.
<svelte:fragment slot="aside">
{#if $addressList.total && countryList?.total}
<Table.Root>
<Table.Root
let:root
columns={[{ id: 'address' }, { id: 'links' }, { id: 'actions', width: 40 }]}>
{#each $addressList.billingAddresses as address}
{@const country = countryList?.countries?.find(
(c) => c.code === address.country
@@ -57,8 +59,8 @@
(org) => address.$id === org.billingAddressId
)}
<Table.Row>
<Table.Cell>
<Table.Row.Base {root}>
<Table.Cell column="address" {root}>
{address.streetAddress},
{#if address?.addressLine2}
{address.addressLine2},
@@ -72,7 +74,7 @@
{/if}
{country ? country.name : address.country}
</Table.Cell>
<Table.Cell>
<Table.Cell column="links" {root}>
{#if linkedOrgs?.length > 0}
<Popover let:toggle>
<Tag on:click={toggle} size="s">
@@ -98,7 +100,7 @@
</Popover>
{/if}
</Table.Cell>
<Table.Cell width="40px">
<Table.Cell column="actions" {root}>
<Popover let:toggle placement="bottom-end" padding="none">
<Button icon text ariaLabel="More options" on:click={toggle}>
<Icon icon={IconDotsHorizontal} size="s" />
@@ -124,7 +126,7 @@
</ActionMenu.Root>
</Popover>
</Table.Cell>
</Table.Row>
</Table.Row.Base>
{/each}
</Table.Root>
@@ -49,14 +49,22 @@
View or update your payment methods. These can be applied to any organizations you have created.
<svelte:fragment slot="aside">
{#if $paymentMethods?.total && filteredMethods?.length > 0}
<Table.Root>
<svelte:fragment slot="header">
<Table.Header.Cell>Credit card</Table.Header.Cell>
<Table.Header.Cell>Name</Table.Header.Cell>
<Table.Header.Cell>Expiration date</Table.Header.Cell>
<Table.Header.Cell />
<Table.Header.Cell />
<Table.Header.Cell width="40px" />
<Table.Root
let:root
columns={[
{ id: 'cc' },
{ id: 'name' },
{ id: 'expiry' },
{ id: 'status' },
{ id: 'links' },
{ id: 'actions', width: 40 }
]}>
<svelte:fragment slot="header" let:root>
<Table.Header.Cell column="cc" {root}>Credit card</Table.Header.Cell>
<Table.Header.Cell column="name" {root}>Name</Table.Header.Cell>
<Table.Header.Cell column="expiration" {root}
>Expiration date</Table.Header.Cell>
<Table.Header.Cell column="actions" {root} />
</svelte:fragment>
{#each filteredMethods as paymentMethod, i}
{@const linkedOrgs = orgList?.filter(
@@ -65,9 +73,9 @@
paymentMethod.$id === org.backupPaymentMethodId
)}
<Table.Row>
<CreditCardInfo {paymentMethod} />
<Table.Cell>
<Table.Row.Base {root}>
<CreditCardInfo {root} {paymentMethod} />
<Table.Cell column="links" {root}>
{#if linkedOrgs?.length > 0}
<Popover let:toggle>
<Tag on:click={toggle} size="s">
@@ -93,7 +101,7 @@
</Popover>
{/if}
</Table.Cell>
<Table.Cell>
<Table.Cell column="actions" {root}>
<Popover let:toggle placement="bottom-start" padding="none">
<Button icon text on:click={toggle}>
<Icon icon={IconDotsHorizontal} size="s" />
@@ -124,7 +132,7 @@
</svelte:fragment>
</Popover>
</Table.Cell>
</Table.Row>
</Table.Row.Base>
{/each}
</Table.Root>
<div>
@@ -85,17 +85,24 @@
<Button secondary on:click={logoutAll}>Sign out all sessions</Button>
</Layout.Stack>
<Table.Root>
<svelte:fragment slot="header">
<Table.Header.Cell>Client</Table.Header.Cell>
<Table.Header.Cell>Location</Table.Header.Cell>
<Table.Header.Cell>IP</Table.Header.Cell>
<Table.Header.Cell width="68px" />
<Table.Root
let:root
columns={[
{ id: 'client' },
{ id: 'location' },
{ id: 'ip' },
{ id: 'actions', width: 40 }
]}>
<svelte:fragment slot="header" let:root>
<Table.Header.Cell column="client" {root}>Client</Table.Header.Cell>
<Table.Header.Cell column="location" {root}>Location</Table.Header.Cell>
<Table.Header.Cell column="ip" {root}>IP</Table.Header.Cell>
<Table.Header.Cell column="actions" {root} />
</svelte:fragment>
{#each data.sessions.sessions as session}
{@const browser = getBrowser(session.clientCode)}
<Table.Row>
<Table.Cell>
<Table.Row.Base {root}>
<Table.Cell column="client" {root}>
<Layout.Stack direction="row" alignItems="center">
{#if session.clientName}
<div class="avatar is-size-small">
@@ -131,18 +138,20 @@
{/if}
</Layout.Stack>
</Table.Cell>
<Table.Cell>
<Table.Cell column="location" {root}>
{#if session.countryCode !== '--'}
{session.countryName}
{:else}
Unknown
{/if}
</Table.Cell>
<Table.Cell>{session.ip}</Table.Cell>
<Table.Cell>
<Table.Cell column="ip" {root}>
{session.ip}
</Table.Cell>
<Table.Cell column="actions" {root}>
<Button size="xs" secondary on:click={() => logout(session)}>Sign out</Button>
</Table.Cell>
</Table.Row>
</Table.Row.Base>
{/each}
</Table.Root>
</Container>
@@ -114,22 +114,22 @@
{/if}
</div>
{#if creditList?.total}
<Table.Root>
<svelte:fragment slot="header">
<Table.Header.Cell>Code</Table.Header.Cell>
<Table.Header.Cell>Total</Table.Header.Cell>
<Table.Header.Cell>Remaining</Table.Header.Cell>
<Table.Header.Cell>Expires at</Table.Header.Cell>
<Table.Root columns={4} let:root>
<svelte:fragment slot="header" let:root>
<Table.Header.Cell {root}>Code</Table.Header.Cell>
<Table.Header.Cell {root}>Total</Table.Header.Cell>
<Table.Header.Cell {root}>Remaining</Table.Header.Cell>
<Table.Header.Cell {root}>Expires at</Table.Header.Cell>
</svelte:fragment>
{#each creditList.credits as credit}
<Table.Row>
<Table.Cell>
<Table.Row.Base {root}>
<Table.Cell {root}>
{credit?.couponId ?? '-'}
</Table.Cell>
<Table.Cell>
<Table.Cell {root}>
{formatCurrency(credit.total)}
</Table.Cell>
<Table.Cell>
<Table.Cell {root}>
{#if credit.status === 'expired'}
<span
style="text-decoration: line-through;"
@@ -139,7 +139,7 @@
{formatCurrency(credit.credits)}
{/if}
</Table.Cell>
<Table.Cell>
<Table.Cell {root}>
{#if credit.status === 'expired'}
<Tooltip>
<span>
@@ -152,7 +152,7 @@
{toLocaleDate(credit.expiration)}
{/if}
</Table.Cell>
</Table.Row>
</Table.Row.Base>
{/each}
</Table.Root>
{#if creditList?.total > limit}
@@ -118,13 +118,13 @@
{/if}
{#if alerts.length}
<Table.Root>
<Table.Root columns={[{ id: 'percentage' }, { id: 'action', width: 40 }]} let:root>
{#each alerts.sort() as alert}
<Table.Row>
<Table.Cell>
<Table.Row.Base {root}>
<Table.Cell column="percentage" {root}>
{alert}%
</Table.Cell>
<Table.Cell width="40px">
<Table.Cell column="action" {root}>
<Button
text
icon
@@ -133,7 +133,7 @@
<Icon icon={IconTrash} size="s" />
</Button>
</Table.Cell>
</Table.Row>
</Table.Row.Base>
{/each}
</Table.Root>
{/if}
@@ -69,20 +69,27 @@
Transaction history for this organization. Download invoices for more details about your payments.
<svelte:fragment slot="aside">
{#if invoiceList.total > 0}
<Table.Root>
<svelte:fragment slot="header">
<Table.Header.Cell>Due date</Table.Header.Cell>
<Table.Header.Cell width="200px">Status</Table.Header.Cell>
<Table.Header.Cell width="120px">Amount due</Table.Header.Cell>
<Table.Header.Cell width="40px" />
<Table.Root
let:root
columns={[
{ id: 'dueDate' },
{ id: 'status', width: { min: 200 } },
{ id: 'amount', width: { min: 120 } },
{ id: 'action', width: 40 }
]}>
<svelte:fragment slot="header" let:root>
<Table.Header.Cell column="dueDate" {root}>Due date</Table.Header.Cell>
<Table.Header.Cell column="status" {root}>Status</Table.Header.Cell>
<Table.Header.Cell column="amount" {root}>Amount due</Table.Header.Cell>
<Table.Header.Cell column="action" {root} />
</svelte:fragment>
{#each invoiceList?.invoices as invoice}
{@const status = invoice.status}
<Table.Row>
<Table.Cell>
<Table.Row.Base {root}>
<Table.Cell column="dueDate" {root}>
{toLocaleDate(invoice.dueAt)}
</Table.Cell>
<Table.Cell>
<Table.Cell column="status" {root}>
{@const isDanger =
status === 'overdue' ||
status === 'failed' ||
@@ -115,10 +122,10 @@
{/if}
</Layout.Stack>
</Table.Cell>
<Table.Cell>
<Table.Cell column="amount" {root}>
{formatCurrency(invoice.grossAmount)}
</Table.Cell>
<Table.Cell>
<Table.Cell column="status" {root}>
<Popover let:toggle placement="bottom-start" padding="none">
<Button text icon ariaLabel="more options" on:click={toggle}>
<Icon icon={IconDotsHorizontal} size="s" />
@@ -152,7 +159,7 @@
</ActionMenu.Root>
</Popover>
</Table.Cell>
</Table.Row>
</Table.Row.Base>
{/each}
</Table.Root>
{#if invoiceList.total > limit}
@@ -106,18 +106,26 @@
View or update your organization payment methods here.
<svelte:fragment slot="aside">
{#if $organization?.paymentMethodId}
<Table.Root>
<svelte:fragment slot="header">
<Table.Header.Cell>Credit card</Table.Header.Cell>
<Table.Header.Cell>Name</Table.Header.Cell>
<Table.Header.Cell>Expiration date</Table.Header.Cell>
<Table.Header.Cell />
<Table.Header.Cell width="40px" />
<Table.Root
let:root
columns={[
{ id: 'cc' },
{ id: 'name' },
{ id: 'expiry' },
{ id: 'status' },
{ id: 'actions', width: 40 }
]}>
<svelte:fragment slot="header" let:root>
<Table.Header.Cell column="cc" {root}>Credit card</Table.Header.Cell>
<Table.Header.Cell column="name" {root}>Name</Table.Header.Cell>
<Table.Header.Cell column="expiry" {root}>Expiration date</Table.Header.Cell>
<Table.Header.Cell column="status" {root} />
<Table.Header.Cell column="actions" {root} />
</svelte:fragment>
<Table.Row>
<CreditCardInfo paymentMethod={defaultPaymentMethod} />
<Table.Cell>
<Table.Row.Base {root}>
<CreditCardInfo {root} paymentMethod={defaultPaymentMethod} />
<Table.Cell column="actions" {root}>
<Popover let:toggle placement="bottom-start" padding="none">
<Button text icon ariaLabel="more options" on:click={toggle}>
<Icon icon={IconDotsHorizontal} size="s" />
@@ -152,11 +160,11 @@
</ActionMenu.Root>
</Popover>
</Table.Cell>
</Table.Row>
</Table.Row.Base>
{#if $organization?.backupPaymentMethodId}
<Table.Row>
<CreditCardInfo isBackup paymentMethod={backupPaymentMethod} />
<Table.Cell width="40px">
<Table.Row.Base {root}>
<CreditCardInfo {root} isBackup paymentMethod={backupPaymentMethod} />
<Table.Cell column="actions" {root}>
<Popover let:toggle placement="bottom-start" padding="none">
<Button text icon ariaLabel="more options" on:click={toggle}>
<Icon icon={IconDotsHorizontal} size="s" />
@@ -191,7 +199,7 @@
</ActionMenu.Root>
</Popover>
</Table.Cell>
</Table.Row>
</Table.Row.Base>
{/if}
</Table.Root>
{#if !$organization?.backupPaymentMethodId}
@@ -36,8 +36,6 @@
let showDelete = false;
let showRetry = false;
let selectedDomain: Domain = null;
$: console.log(data.domains);
</script>
<Container>
@@ -56,33 +54,47 @@
</Layout.Stack>
{#if data.domains.total}
<Table.Root>
<svelte:fragment slot="header">
<Table.Header.Cell>Domain</Table.Header.Cell>
<Table.Header.Cell>Registrar</Table.Header.Cell>
<Table.Header.Cell>Nameservers</Table.Header.Cell>
<Table.Header.Cell>Expiry date</Table.Header.Cell>
<Table.Header.Cell>Renewal</Table.Header.Cell>
<Table.Header.Cell>Auto renewal</Table.Header.Cell>
<Table.Header.Cell />
<Table.Root
let:root
columns={[
{ id: 'domain' },
{ id: 'registrar' },
{ id: 'nameservers' },
{ id: 'expiry_date' },
{ id: 'renewal' },
{ id: 'auto_renewal' },
{ id: 'actions', width: 40 }
]}>
<svelte:fragment slot="header" let:root>
<Table.Header.Cell column="domain" {root}>Domain</Table.Header.Cell>
<Table.Header.Cell column="registrar" {root}>Registrar</Table.Header.Cell>
<Table.Header.Cell column="nameservers" {root}>Nameservers</Table.Header.Cell>
<Table.Header.Cell column="expiry_date" {root}>Expiry date</Table.Header.Cell>
<Table.Header.Cell column="renewal" {root}>Renewal</Table.Header.Cell>
<Table.Header.Cell column="auto_renewal" {root}>Auto renewal</Table.Header.Cell>
<Table.Header.Cell column="actions" {root} />
</svelte:fragment>
{#each data.domains.domains as domain}
<Table.Link
<Table.Row.Link
{root}
href={`${base}/organization-${$page.params.organization}/domains/domain-${domain.$id}`}>
<Table.Cell>
<Table.Cell column="domain" {root}>
<Link external icon href={`${$protocol}${domain.domain}`} variant="quiet">
{domain.domain}
</Link>
</Table.Cell>
<Table.Cell>{domain?.registrar || '-'}</Table.Cell>
<Table.Cell>{domain?.nameservers || '-'}</Table.Cell>
<Table.Cell>
{domain?.expiry ? toLocaleDateTime(domain.expiry) : '-'}</Table.Cell>
<Table.Cell>
<Table.Cell column="registrar" {root}>{domain?.registrar || '-'}</Table.Cell>
<Table.Cell column="nameservers" {root}
>{domain?.nameservers || '-'}</Table.Cell>
<Table.Cell column="expiry_date" {root}>
{domain?.expiry ? toLocaleDateTime(domain.expiry) : '-'}
</Table.Cell>
<Table.Cell column="renewal" {root}>
{domain.renewal ? toLocaleDateTime(domain.renewal) : '-'}
</Table.Cell>
<Table.Cell>{domain.autoRenewal ? 'On' : 'Off'}</Table.Cell>
<Table.Cell>
<Table.Cell column="auto_renewal" {root}
>{domain.autoRenewal ? 'On' : 'Off'}</Table.Cell>
<Table.Cell column="actions" {root}>
<Layout.Stack direction="row" justifyContent="flex-end">
<Popover let:toggle placement="bottom-end" padding="none">
<Button
@@ -131,7 +143,7 @@
</Popover>
</Layout.Stack>
</Table.Cell>
</Table.Link>
</Table.Row.Link>
{/each}
</Table.Root>
@@ -36,20 +36,27 @@
</Layout.Stack>
{#if data.certificates.total}
<Table.Root>
<svelte:fragment slot="header">
<Table.Header.Cell>ID</Table.Header.Cell>
<Table.Header.Cell>Renewal</Table.Header.Cell>
<Table.Header.Cell>Expiry date</Table.Header.Cell>
<Table.Header.Cell />
<Table.Root
let:root
columns={[
{ id: 'id' },
{ id: 'renewal' },
{ id: 'expiry' },
{ id: 'actions', width: 40 }
]}>
<svelte:fragment slot="header" let:root>
<Table.Header.Cell column="id" {root}>ID</Table.Header.Cell>
<Table.Header.Cell column="renewal" {root}>Renewal</Table.Header.Cell>
<Table.Header.Cell column="expiry" {root}>Expiry date</Table.Header.Cell>
<Table.Header.Cell column="actions" {root} />
</svelte:fragment>
{#each data.certificates.certificates as certificate}
{@const isExpired = new Date(certificate.expiresAt) < now}
<Table.Row>
<Table.Cell>
<Table.Row.Base {root}>
<Table.Cell column="id" {root}>
<Id value={certificate.$id}>{certificate.$id}</Id>
</Table.Cell>
<Table.Cell>
<Table.Cell column="renewal" {root}>
{#if certificate?.renewAt && !isExpired}
<Layout.Stack direction="row" gap="xs">
{certificate.autoRenewal ? 'Auto' : ''}
@@ -63,7 +70,7 @@
-
{/if}
</Table.Cell>
<Table.Cell>
<Table.Cell column="expiry" {root}>
{#if isExpired}
<Tooltip>
<Badge
@@ -80,7 +87,7 @@
{/if}
</Table.Cell>
<Table.Cell>
<Table.Cell column="actions" {root}>
<Layout.Stack direction="row" justifyContent="flex-end">
<Popover let:toggle placement="bottom-start" padding="none">
<Button
@@ -121,7 +128,7 @@
</Popover>
</Layout.Stack>
</Table.Cell>
</Table.Row>
</Table.Row.Base>
{/each}
</Table.Root>
@@ -50,34 +50,34 @@
Forward emails to your domain using the following {capitalize(selectedPreset)} DNS records.
</span>
<Table.Root>
<svelte:fragment slot="header">
<Table.Header.Cell>Type</Table.Header.Cell>
<Table.Header.Cell>Name</Table.Header.Cell>
<Table.Header.Cell>Value</Table.Header.Cell>
<Table.Header.Cell>TTL</Table.Header.Cell>
<Table.Header.Cell>Priority</Table.Header.Cell>
<Table.Root columns={5} let:root>
<svelte:fragment slot="header" let:root>
<Table.Header.Cell {root}>Type</Table.Header.Cell>
<Table.Header.Cell {root}>Name</Table.Header.Cell>
<Table.Header.Cell {root}>Value</Table.Header.Cell>
<Table.Header.Cell {root}>TTL</Table.Header.Cell>
<Table.Header.Cell {root}>Priority</Table.Header.Cell>
</svelte:fragment>
{#await fetchPreset()}
{#each Array(2) as _}
<Table.Row>
<Table.Row.Base {root}>
{#each Array(5) as _}
<Table.Cell>
<Table.Cell {root}>
<Skeleton variant="line" width={70} height={15} />
</Table.Cell>
{/each}
</Table.Row>
</Table.Row.Base>
{/each}
{:then presetData}
{JSON.stringify(presetData)}
{#each presetData as record}
<Table.Row>
<Table.Cell>{record.type}</Table.Cell>
<Table.Cell>{record.name}</Table.Cell>
<Table.Cell>{record.value}</Table.Cell>
<Table.Cell>{record.ttl}</Table.Cell>
<Table.Cell>{record.priority}</Table.Cell>
</Table.Row>
<Table.Row.Base {root}>
<Table.Cell {root}>{record.type}</Table.Cell>
<Table.Cell {root}>{record.name}</Table.Cell>
<Table.Cell {root}>{record.value}</Table.Cell>
<Table.Cell {root}>{record.ttl}</Table.Cell>
<Table.Cell {root}>{record.priority}</Table.Cell>
</Table.Row.Base>
{/each}
{/await}
</Table.Root>
@@ -5,13 +5,13 @@ import { derived, writable } from 'svelte/store';
export const domain = derived(page, ($page) => $page.data.domain as Domain);
export const columns = writable<Column[]>([
{ id: 'name', title: 'Name', type: 'string', show: true },
{ id: 'type', title: 'Type', type: 'string', show: true },
{ id: 'value', title: 'Value', type: 'string', show: true },
{ id: 'ttl', title: 'TTL', type: 'integer', show: true },
{ id: 'priority', title: 'Priority', type: 'integer', show: false },
{ id: 'comment', title: 'Comment', type: 'string', show: false },
{ id: '$createdAt', title: 'Created', type: 'datetime', show: true }
{ id: 'name', title: 'Name', type: 'string' },
{ id: 'type', title: 'Type', type: 'string' },
{ id: 'value', title: 'Value', type: 'string' },
{ id: 'ttl', title: 'TTL', type: 'integer' },
{ id: 'priority', title: 'Priority', type: 'integer', hide: true },
{ id: 'comment', title: 'Comment', type: 'string', hide: true },
{ id: '$createdAt', title: 'Created', type: 'datetime' }
]);
export const recordTypes = [
@@ -26,66 +26,50 @@
</script>
<Layout.Stack>
<Table.Root>
<svelte:fragment slot="header">
{#each $columns as column}
{#if column.show}
<Table.Header.Cell width={column?.width?.toString() ?? ''}>
{column.title}
</Table.Header.Cell>
{/if}
<Table.Root columns={[...$columns, { id: 'actions', width: 40 }]} let:root>
<svelte:fragment slot="header" let:root>
{#each $columns as { id, title } (id)}
<Table.Header.Cell column={id} {root}>
{title}
</Table.Header.Cell>
{/each}
<Table.Header.Cell width="40" />
<Table.Header.Cell column="actions" {root} />
</svelte:fragment>
{#each data.recordList.dnsRecords as record}
<Table.Row>
<Table.Row.Base {root}>
{#each $columns as column}
{#if column.show}
<Table.Cell column={column.id} {root}>
{#if column.id === 'name'}
<Table.Cell>
<Typography.Code>
{record.name}
</Typography.Code>
</Table.Cell>
<Typography.Code>
{record.name}
</Typography.Code>
{:else if column.id === 'type'}
<Table.Cell>
<Typography.Text>
{record.type}
</Typography.Text>
</Table.Cell>
<Typography.Text>
{record.type}
</Typography.Text>
{:else if column.id === 'value'}
<Table.Cell>
<InteractiveText variant="copy" text={record.value} isVisible />
</Table.Cell>
<InteractiveText variant="copy" text={record.value} isVisible />
{:else if column.id === 'ttl'}
<Table.Cell>
<Typography.Text>
{record.ttl}
</Typography.Text>
</Table.Cell>
<Typography.Text>
{record.ttl}
</Typography.Text>
{:else if column.id === 'priority'}
<Table.Cell>
<Typography.Text>
{record?.priority ?? '-'}
</Typography.Text>
</Table.Cell>
<Typography.Text>
{record?.priority ?? '-'}
</Typography.Text>
{:else if column.id === 'comment'}
<Table.Cell>
<Typography.Text>
{record?.comment ?? '-'}
</Typography.Text>
</Table.Cell>
<Typography.Text>
{record?.comment ?? '-'}
</Typography.Text>
{:else if column.id === '$createdAt'}
<Table.Cell>
<Typography.Text>
{capitalize(timeFromNow(record.$createdAt))}
</Typography.Text>
</Table.Cell>
<Typography.Text>
{capitalize(timeFromNow(record.$createdAt))}
</Typography.Text>
{/if}
{/if}
</Table.Cell>
{/each}
<Table.Cell>
<Table.Cell column="actions" {root}>
<Layout.Stack direction="row" justifyContent="flex-end">
<Popover let:toggle placement="bottom-start" padding="none">
<Button.Button
@@ -127,7 +111,7 @@
</Popover>
</Layout.Stack>
</Table.Cell>
</Table.Row>
</Table.Row.Base>
{/each}
</Table.Root>
@@ -73,24 +73,26 @@
</Typography.Text>
</Layout.Stack>
<Table.Root>
<svelte:fragment slot="header">
<Table.Header.Cell>Type</Table.Header.Cell>
<Table.Header.Cell>Value</Table.Header.Cell>
<Table.Header.Cell />
<Table.Root
columns={[{ id: 'type' }, { id: 'value' }, { id: 'actions', width: 40 }]}
let:root>
<svelte:fragment slot="header" let:root>
<Table.Header.Cell column="type" {root}>Type</Table.Header.Cell>
<Table.Header.Cell column="value" {root}>Value</Table.Header.Cell>
<Table.Header.Cell column="actions" {root} />
</svelte:fragment>
{#each nameservers as nameserver}
<Table.Row>
<Table.Cell>NS</Table.Cell>
<Table.Cell>{nameserver}</Table.Cell>
<Table.Cell>
<Table.Row.Base {root}>
<Table.Cell column="type" {root}>NS</Table.Cell>
<Table.Cell column="value" {root}>{nameserver}</Table.Cell>
<Table.Cell column="actions" {root}>
<Layout.Stack alignItems="flex-end">
<Copy value={nameserver}>
<Icon icon={IconDuplicate} />
</Copy>
</Layout.Stack>
</Table.Cell>
</Table.Row>
</Table.Row.Base>
{/each}
</Table.Root>
<Layout.Stack gap="s" direction="row" alignItems="center">
@@ -76,11 +76,19 @@
</ConsoleButton>
</Layout.Stack>
<Table.Root>
<svelte:fragment slot="header">
<Table.Header.Cell>Name</Table.Header.Cell>
<Table.Header.Cell>Email</Table.Header.Cell>
<Table.Header.Cell>
<Table.Root
let:root
columns={[
{ id: 'name' },
{ id: 'email' },
{ id: 'roles' },
{ id: 'mfa' },
{ id: 'actions', hide: !$isOwner, width: 40 }
]}>
<svelte:fragment slot="header" let:root>
<Table.Header.Cell column="name" {root}>Name</Table.Header.Cell>
<Table.Header.Cell column="email" {root}>Email</Table.Header.Cell>
<Table.Header.Cell column="roles" {root}>
<Layout.Stack direction="row" gap="xxxs" alignItems="center">
Roles
<Popover let:toggle>
@@ -91,14 +99,12 @@
</Popover>
</Layout.Stack>
</Table.Header.Cell>
<Table.Header.Cell>2FA</Table.Header.Cell>
{#if $isOwner}
<Table.Header.Cell width="40px" />
{/if}
<Table.Header.Cell column="mfa" {root}>2FA</Table.Header.Cell>
<Table.Header.Cell column="actions" {root} />
</svelte:fragment>
{#each data.organizationMembers.memberships as member, index}
<Table.Row>
<Table.Cell width="40%">
<Table.Row.Base {root}>
<Table.Cell column="name" {root}>
<Layout.Stack direction="row" alignItems="center" gap="s">
<AvatarInitials size="xs" name={member.userName} />
<span class="u-trim">
@@ -109,59 +115,57 @@
{/if}
</Layout.Stack>
</Table.Cell>
<Table.Cell width="30%">
<Table.Cell column="email" {root}>
{member.userEmail}
</Table.Cell>
<Table.Cell width="20%">
<Table.Cell column="roles" {root}>
{member.roles.map((role) => getRoleLabel(role)).join(', ')}
</Table.Cell>
<Table.Cell width="10%">
<Table.Cell column="mfa" {root}>
<Badge
size="xs"
type={member.mfa ? 'success' : undefined}
variant="secondary"
content={member.mfa ? 'Enabled' : 'Disabled'} />
</Table.Cell>
{#if $isOwner}
<Table.Cell>
<Popover let:toggle padding="none" placement="bottom-end">
<Button.Button icon variant="ghost" size="s" on:click={toggle}>
<Icon size="s" icon={IconDotsHorizontal} />
</Button.Button>
<div style:min-width="232px" slot="tooltip">
<ActionMenu.Root>
<Table.Cell column="actions" {root}>
<Popover let:toggle padding="none" placement="bottom-end">
<Button.Button icon variant="ghost" size="s" on:click={toggle}>
<Icon size="s" icon={IconDotsHorizontal} />
</Button.Button>
<div style:min-width="232px" slot="tooltip">
<ActionMenu.Root>
<ActionMenu.Item.Button
trailingIcon={IconPencil}
on:click={() => {
selectedMember = member;
showEdit = true;
showDropdown[index] = false;
}}>
Edit role
</ActionMenu.Item.Button>
{#if member.invited && !member.joined}
<ActionMenu.Item.Button
trailingIcon={IconPencil}
trailingIcon={IconRefresh}
on:click={() => {
selectedMember = member;
showEdit = true;
showDropdown[index] = false;
resend(member);
}}>
Edit role
Resend
</ActionMenu.Item.Button>
{#if member.invited && !member.joined}
<ActionMenu.Item.Button
trailingIcon={IconRefresh}
on:click={() => {
resend(member);
}}>
Resend
</ActionMenu.Item.Button>
{/if}
<ActionMenu.Item.Button
trailingIcon={IconTrash}
on:click={() => {
selectedMember = member;
showDelete = true;
}}>
Remove
</ActionMenu.Item.Button>
</ActionMenu.Root>
</div>
</Popover>
</Table.Cell>
{/if}
</Table.Row>
{/if}
<ActionMenu.Item.Button
trailingIcon={IconTrash}
on:click={() => {
selectedMember = member;
showDelete = true;
}}>
Remove
</ActionMenu.Item.Button>
</ActionMenu.Root>
</div>
</Popover>
</Table.Cell>
</Table.Row.Base>
{/each}
</Table.Root>
@@ -23,10 +23,6 @@
let selectedTab = 'projects';
let organizationName: string = null;
/* enable overflow-x */
const columnWidth = 120;
const columnWidthSmall = columnWidth / 4;
async function deleteOrg() {
try {
if (isCloud) {
@@ -72,21 +68,6 @@
}
];
$: tabData =
selectedTab === 'projects'
? {
headers: ['Project', '', 'Last Updated'],
rows: $projects.projects.map((project) => ({
cells: [project.name, '', toLocaleDate(project.$updatedAt)]
}))
}
: {
headers: ['Member', '', 'Last Activity'],
rows: $members.memberships.map((member) => ({
cells: [member.userName, '', toLocaleDate(member.$updatedAt)]
}))
};
$: upcomingInvoice = invoices?.invoices.find((i) => i.status === 'upcoming' && i.amount > 0);
$: if (!showDelete) {
@@ -127,51 +108,35 @@
</Tabs.Item.Button>
{/each}
</Tabs.Root>
<Table.Root>
<svelte:fragment slot="header">
{#each tabData.headers as header}
<Table.Header.Cell width={columnWidth + 'px'}>{header}</Table.Header.Cell>
{/each}
</svelte:fragment>
{#each tabData.rows as row}
<Table.Row>
{#each row.cells as cell}
<Table.Cell>{cell}</Table.Cell>
{/each}
</Table.Row>
{/each}
</Table.Root>
<div class="box is-not-desktop">
<SecondaryTabs large stretch class="u-sep-block-end u-padding-8">
{#each tabs as { name, label, total }}
<SecondaryTabsItem
center
fullWidth
disabled={selectedTab === name}
on:click={() => (selectedTab = name)}>
{label.mobile} ({total})
</SecondaryTabsItem>
{/each}
</SecondaryTabs>
<Table.Root>
<svelte:fragment slot="header">
{#each tabData.headers as header, index}
<Table.Header.Cell
width={(index === 1 ? columnWidthSmall : columnWidth) + 'px'}
>{header}</Table.Header.Cell>
{/each}
{#if selectedTab === 'projects'}
<Table.Root columns={[{ id: 'projects' }, { id: 'lastUpdated' }]} let:root>
<svelte:fragment slot="header" let:root>
<Table.Header.Cell column="projects" {root}>Projects</Table.Header.Cell>
<Table.Header.Cell column="lastUpdated" {root}>Last updated</Table.Header.Cell>
</svelte:fragment>
{#each tabData.rows as row}
<Table.Row>
{#each row.cells as cell}
<Table.Cell>{cell}</Table.Cell>
{/each}
</Table.Row>
{#each $projects.projects as project}
<Table.Row.Base {root}>
<Table.Cell column="projects" {root}>{project.name}</Table.Cell>
<Table.Cell column="lastUpdated" {root}
>{toLocaleDate(project.$updatedAt)}</Table.Cell>
</Table.Row.Base>
{/each}
</Table.Root>
</div>
{:else}
<Table.Root columns={[{ id: 'member' }, { id: 'lastUpdated' }]} let:root>
<svelte:fragment slot="header" let:root>
<Table.Header.Cell column="member" {root}>Members</Table.Header.Cell>
<Table.Header.Cell column="lastUpdated" {root}>Last updated</Table.Header.Cell>
</svelte:fragment>
{#each $members.memberships as membership}
<Table.Row.Base {root}>
<Table.Cell column="member" {root}>{membership.userName}</Table.Cell>
<Table.Cell column="lastUpdated" {root}
>{toLocaleDate(membership.$updatedAt)}</Table.Cell>
</Table.Row.Base>
{/each}
</Table.Root>
{/if}
{/if}
<InputText
label={`Confirm the organization name to continue`}
@@ -104,17 +104,22 @@
{#if projects.some((project) => project[metric]) || projects.some( (project) => databaseOperationMetric?.some((metric) => project[metric]) )}
<Accordion title="Project breakdown" hideDivider>
<Table.Root>
<svelte:fragment slot="header">
<Table.Header.Cell>Project</Table.Header.Cell>
{#if databaseOperationMetric}
<Table.Header.Cell>Reads</Table.Header.Cell>
<Table.Header.Cell>Writes</Table.Header.Cell>
{:else}
<Table.Header.Cell>{getMetricTitle(metric)}</Table.Header.Cell>
{/if}
<Table.Header.Cell>Estimated cost</Table.Header.Cell>
<Table.Root
columns={[
{ id: 'project' },
{ id: 'reads', hide: !databaseOperationMetric },
{ id: 'writes', hide: !databaseOperationMetric },
{ id: 'metric', hide: !!databaseOperationMetric },
{ id: 'costs' }
]}
let:root>
<svelte:fragment slot="header" let:root>
<Table.Header.Cell column="project" {root}>Project</Table.Header.Cell>
<Table.Header.Cell column="reads" {root}>Reads</Table.Header.Cell>
<Table.Header.Cell column="writes" {root}>Writes</Table.Header.Cell>
<Table.Header.Cell column="metric" {root}
>{getMetricTitle(metric)}</Table.Header.Cell>
<Table.Header.Cell column="costs" {root}>Estimated cost</Table.Header.Cell>
</svelte:fragment>
{#each groupByProject(metric, estimate, databaseOperationMetric).sort((a, b) => {
const aValue = a.usage ?? a.databasesReads ?? 0;
@@ -122,49 +127,41 @@
return bValue - aValue;
}) as project}
{#if !$canSeeProjects}
<Table.Row>
<Table.Cell>
<Table.Row.Base {root}>
<Table.Cell column="project" {root}>
{data.projectNames[project.projectId]?.name ?? 'Unknown'}
</Table.Cell>
{#if databaseOperationMetric}
<Table.Cell>
{format(project.databasesReads ?? 0)}
</Table.Cell>
<Table.Cell>
{format(project.databasesWrites ?? 0)}
</Table.Cell>
{:else}
<Table.Cell>
{format(project.usage)}
</Table.Cell>
{/if}
<Table.Cell>
<Table.Cell column="reads" {root}>
{format(project.databasesReads ?? 0)}
</Table.Cell>
<Table.Cell column="writes" {root}>
{format(project.databasesWrites ?? 0)}
</Table.Cell>
<Table.Cell column="metric" {root}>
{format(project.usage)}
</Table.Cell>
<Table.Cell column="costs" {root}>
{formatCurrency(project.estimate ?? 0)}
</Table.Cell>
</Table.Row>
</Table.Row.Base>
{:else}
<Table.Link href={getProjectUsageLink(project.projectId)}>
<Table.Cell>
<Table.Row.Link href={getProjectUsageLink(project.projectId)} {root}>
<Table.Cell column="project" {root}>
{data.projectNames[project.projectId]?.name ?? 'Unknown'}
</Table.Cell>
{#if databaseOperationMetric}
<Table.Cell>
{format(project.databasesReads ?? 0)}
</Table.Cell>
<Table.Cell>
{format(project.databasesWrites ?? 0)}
</Table.Cell>
{:else}
<Table.Cell>
{format(project.usage)}
</Table.Cell>
{/if}
<Table.Cell>
<Table.Cell column="reads" {root}>
{format(project.databasesReads ?? 0)}
</Table.Cell>
<Table.Cell column="writes" {root}>
{format(project.databasesWrites ?? 0)}
</Table.Cell>
<Table.Cell column="metric" {root}>
{format(project.usage)}
</Table.Cell>
<Table.Cell column="costs" {root}>
{formatCurrency(project.estimate ?? 0)}
</Table.Cell>
</Table.Link>
</Table.Row.Link>
{/if}
{/each}
</Table.Root>
@@ -56,14 +56,14 @@
items={members.memberships}
let:paginatedItems
hideFooter={members?.total <= 5}>
<Table.Root>
<svelte:fragment slot="header">
<Table.Header.Cell>Members</Table.Header.Cell>
<Table.Header.Cell>Joined</Table.Header.Cell>
<Table.Root columns={2} let:root>
<svelte:fragment slot="header" let:root>
<Table.Header.Cell {root}>Members</Table.Header.Cell>
<Table.Header.Cell {root}>Joined</Table.Header.Cell>
</svelte:fragment>
{#each paginatedItems as member}
<Table.Row>
<Table.Cell>
<Table.Row.Base {root}>
<Table.Cell {root}>
<Layout.Stack direction="row" alignItems="center" gap="s">
<AvatarInitials
size="xs"
@@ -77,12 +77,12 @@
</Layout.Stack>
</Layout.Stack>
</Table.Cell>
<Table.Cell>
<Table.Cell {root}>
{member.joined
? toLocaleDate(member.joined)
: toLocaleDate(member.$createdAt)}
</Table.Cell>
</Table.Row>
</Table.Row.Base>
{/each}
</Table.Root>
</Paginator>
@@ -33,13 +33,19 @@
const projectId = $page.params.project;
const columns = writable<Column[]>([
{ id: '$id', title: 'User ID', type: 'string', show: true, width: 140 },
{ id: 'name', title: 'Name', type: 'string', show: true, width: 260 },
{ id: 'identifiers', title: 'Identifiers', type: 'string', show: true, width: 140 },
{ id: 'status', title: 'Status', type: 'string', show: true, width: 140 },
{ id: 'labels', title: 'Labels', type: 'string', show: false, width: 140 },
{ id: 'joined', title: 'Joined', type: 'string', show: true, width: 140 },
{ id: 'lastActivity', title: 'Last activity', type: 'string', show: false, width: 140 }
{ id: '$id', title: 'User ID', type: 'string', width: { min: 140 } },
{ id: 'name', title: 'Name', type: 'string', width: { min: 260 } },
{ id: 'identifiers', title: 'Identifiers', type: 'string', width: { min: 140 } },
{ id: 'status', title: 'Status', type: 'string', width: { min: 140 } },
{ id: 'labels', title: 'Labels', type: 'string', hide: true, width: { min: 140 } },
{ id: 'joined', title: 'Joined', type: 'string', width: { min: 140 } },
{
id: 'lastActivity',
title: 'Last activity',
type: 'string',
hide: true,
width: { min: 140 }
}
]);
async function userCreated(event: CustomEvent<Models.User<Record<string, unknown>>>) {
@@ -62,102 +68,81 @@
</Layout.Stack>
{#if data.users.total}
<Table.Root>
<svelte:fragment slot="header">
{#each $columns as column (column.id)}
{#if column.show}
<Table.Header.Cell>{column.title}</Table.Header.Cell>
{/if}
<Table.Root columns={$columns} let:root>
<svelte:fragment slot="header" let:root>
{#each $columns as { id, title } (id)}
<Table.Header.Cell column={id} {root}>{title}</Table.Header.Cell>
{/each}
</svelte:fragment>
{#each data.users.users as user}
<Table.Link href={`${base}/project-${projectId}/auth/user-${user.$id}`}>
{#each $columns as column (column.id)}
{#if column.show}
{#if column.id === '$id'}
<Table.Cell width={column.width + 'px'}>
<Copy value={user.$id} event="user">
<Tag size="xs" variant="code">
<Icon size="s" icon={IconDuplicate} slot="start" />
{user.$id}
</Tag>
</Copy>
</Table.Cell>
{:else if column.id === 'name'}
<Table.Cell>
<Layout.Stack direction="row" alignItems="center" gap="s">
{#if user.email || user.phone}
{#if user.name}
<AvatarInitials size="xs" name={user.name} />
<span>
{user.name}
</span>
{:else}
<div class="avatar is-size-small">
<span
class="icon-minus-sm"
aria-hidden="true" />
</div>
{/if}
<Table.Row.Link href={`${base}/project-${projectId}/auth/user-${user.$id}`} {root}>
{#each $columns as { id } (id)}
<Table.Cell column={id} {root}>
{#if id === '$id'}
<Copy value={user.$id} event="user">
<Tag size="xs" variant="code">
<Icon size="s" icon={IconDuplicate} slot="start" />
{user.$id}
</Tag>
</Copy>
{:else if id === 'name'}
<Layout.Stack direction="row" alignItems="center" gap="s">
{#if user.email || user.phone}
{#if user.name}
<AvatarInitials size="xs" name={user.name} />
<span>
{user.name}
</span>
{:else}
<div class="avatar is-size-small">
<span class="icon-anonymous" aria-hidden="true" />
<span class="icon-minus-sm" aria-hidden="true" />
</div>
<span class="text u-trim">{user.name}</span>
{/if}
</Layout.Stack>
</Table.Cell>
{:else if column.id === 'identifiers'}
<Table.Cell>
{user.email && user.phone
? [user.email, user.phone].join(',')
: user.email || user.phone}
</Table.Cell>
{:else if column.id === 'status'}
<Table.Cell>
{#if user.status}
{@const success =
user.emailVerification || user.phoneVerification}
<Badge
size="xs"
variant="secondary"
type={success ? 'success' : undefined}
content={user.emailVerification &&
user.phoneVerification
? 'Verified'
: user.emailVerification
? 'Verified email'
: user.phoneVerification
? 'Verified phone'
: 'Unverified'} />
{:else}
<Badge
size="xs"
variant="secondary"
type="error"
content="blocked" />
<div class="avatar is-size-small">
<span class="icon-anonymous" aria-hidden="true" />
</div>
<span class="text u-trim">{user.name}</span>
{/if}
</Table.Cell>
{:else if column.id === 'labels'}
<Table.Cell>
{user.labels.join(', ')}
</Table.Cell>
{:else if column.id === 'joined'}
<Table.Cell>
{toLocaleDateTime(user.registration)}
</Table.Cell>
{:else if column.id === 'lastActivity'}
<Table.Cell>
{user.accessedAt ? toLocaleDate(user.accessedAt) : 'never'}
</Table.Cell>
</Layout.Stack>
{:else if id === 'identifiers'}
{user.email && user.phone
? [user.email, user.phone].join(',')
: user.email || user.phone}
{:else if id === 'status'}
{#if user.status}
{@const success =
user.emailVerification || user.phoneVerification}
<Badge
size="xs"
variant="secondary"
type={success ? 'success' : undefined}
content={user.emailVerification && user.phoneVerification
? 'Verified'
: user.emailVerification
? 'Verified email'
: user.phoneVerification
? 'Verified phone'
: 'Unverified'} />
{:else}
<Badge
size="xs"
variant="secondary"
type="error"
content="blocked" />
{/if}
{:else if id === 'labels'}
{user.labels.join(', ')}
{:else if id === 'joined'}
{toLocaleDateTime(user.registration)}
{:else if id === 'lastActivity'}
{user.accessedAt ? toLocaleDate(user.accessedAt) : 'never'}
{:else}
<Table.Cell width={column.width + 'px'}>
{user[column.id]}
</Table.Cell>
{user[id]}
{/if}
{/if}
</Table.Cell>
{/each}
</Table.Link>
</Table.Row.Link>
{/each}
</Table.Root>
@@ -1,15 +1,7 @@
<script lang="ts">
import { Submit, trackEvent, trackError } from '$lib/actions/analytics';
import { Modal, CustomId } from '$lib/components';
import { Pill } from '$lib/elements/';
import {
Button,
InputPassword,
InputEmail,
InputText,
InputPhone,
FormList
} from '$lib/elements/forms';
import { Button, InputPassword, InputEmail, InputText, InputPhone } from '$lib/elements/forms';
import { addNotification } from '$lib/stores/notifications';
import { sdk } from '$lib/stores/sdk';
import { ID } from '@appwrite.io/console';
@@ -61,32 +53,21 @@
</script>
<Modal title="Create user" {error} bind:show={showCreate} onSubmit={create}>
<FormList>
<InputText
id="name"
label="Name"
placeholder="Enter name"
autofocus={true}
bind:value={name} />
<InputEmail id="email" label="Email" placeholder="Enter email" bind:value={mail} />
<InputPhone id="phone" label="Phone" placeholder="Enter phone" bind:value={phone} />
<InputPassword
id="password"
label="Password"
placeholder="Enter password"
bind:value={pass} />
<InputText id="name" label="Name" placeholder="Enter name" autofocus={true} bind:value={name} />
<InputEmail id="email" label="Email" placeholder="Enter email" bind:value={mail} />
<InputPhone id="phone" label="Phone" placeholder="Enter phone" bind:value={phone} />
<InputPassword id="password" label="Password" placeholder="Enter password" bind:value={pass} />
{#if !showDropdown}
<div>
<Tag
size="s"
on:click={() => {
showDropdown = true;
}}><Icon icon={IconPencil} /> User ID</Tag>
</div>
{/if}
<CustomId bind:show={showDropdown} name="User" bind:id fullWidth={true} />
</FormList>
{#if !showDropdown}
<div>
<Tag
size="s"
on:click={() => {
showDropdown = true;
}}><Icon icon={IconPencil} /> User ID</Tag>
</div>
{/if}
<CustomId bind:show={showDropdown} name="User" bind:id fullWidth={true} />
<svelte:fragment slot="footer">
<Button text on:mousedown={() => (showCreate = false)}>Cancel</Button>
<Button submit>Create</Button>
@@ -46,27 +46,29 @@
</Layout.Stack>
{#if data.teams.total}
<Table.Root>
<svelte:fragment slot="header">
<Table.Header.Cell>Name</Table.Header.Cell>
<Table.Header.Cell>Members</Table.Header.Cell>
<Table.Header.Cell>Created</Table.Header.Cell>
<Table.Root columns={3} let:root>
<svelte:fragment slot="header" let:root>
<Table.Header.Cell {root}>Name</Table.Header.Cell>
<Table.Header.Cell {root}>Members</Table.Header.Cell>
<Table.Header.Cell {root}>Created</Table.Header.Cell>
</svelte:fragment>
{#each data.teams.teams as team}
<Table.Link href={`${base}/project-${project}/auth/teams/team-${team.$id}`}>
<Table.Cell>
<Table.Row.Link
{root}
href={`${base}/project-${project}/auth/teams/team-${team.$id}`}>
<Table.Cell {root}>
<Layout.Stack direction="row" alignItems="center">
<AvatarInitials size="xs" name={team.name} />
<span class="u-trim">{team.name}</span>
</Layout.Stack>
</Table.Cell>
<Table.Cell>
<Table.Cell {root}>
{team.total} members
</Table.Cell>
<Table.Cell>
<Table.Cell {root}>
{toLocaleDateTime(team.$createdAt)}
</Table.Cell>
</Table.Link>
</Table.Row.Link>
{/each}
</Table.Root>
@@ -37,29 +37,38 @@
</Layout.Stack>
{#if data.memberships.total}
<Table.Root>
<svelte:fragment slot="header">
<Table.Header.Cell>Name</Table.Header.Cell>
<Table.Header.Cell>Roles</Table.Header.Cell>
<Table.Header.Cell>Joined</Table.Header.Cell>
<Table.Header.Cell width="40px" />
<Table.Root
let:root
columns={[
{ id: 'name' },
{ id: 'roles' },
{ id: 'joined' },
{ id: 'actions', width: 40 }
]}>
<svelte:fragment slot="header" let:root>
<Table.Header.Cell column="name" {root}>Name</Table.Header.Cell>
<Table.Header.Cell column="roles" {root}>Roles</Table.Header.Cell>
<Table.Header.Cell column="joined" {root}>Joined</Table.Header.Cell>
<Table.Header.Cell column="actions" {root} />
</svelte:fragment>
{#each data.memberships.memberships as membership}
{@const username = membership.userName ? membership.userName : '-'}
<Table.Link href={`${base}/project-${project}/auth/user-${membership.userId}`}>
<Table.Cell>
<Table.Row.Link
{root}
href={`${base}/project-${project}/auth/user-${membership.userId}`}>
<Table.Cell column="name" {root}>
<Layout.Stack direction="row" alignItems="center">
<AvatarInitials size="xs" name={username} />
<span>{username}</span>
</Layout.Stack>
</Table.Cell>
<Table.Cell>
<Table.Cell column="roles" {root}>
{membership.roles}
</Table.Cell>
<Table.Cell>
<Table.Cell column="joined" {root}>
{toLocaleDateTime(membership.joined)}
</Table.Cell>
<Table.Cell>
<Table.Cell column="actions" {root}>
<button
class="button is-only-icon is-text"
aria-label="Delete item"
@@ -71,7 +80,7 @@
<span class="icon-trash" aria-hidden="true" />
</button>
</Table.Cell>
</Table.Link>
</Table.Row.Link>
{/each}
</Table.Root>
@@ -14,19 +14,18 @@
export let data: PageData;
const columns = writable<Column[]>([
{ id: '$id', title: 'Identity ID', type: 'string', show: true, width: 140 },
{ id: 'provider', title: 'Provider', type: 'string', show: true, width: 80 },
{ id: 'providerUid', title: 'Provider UID', type: 'string', show: false, width: 80 },
{ id: 'providerEmail', title: 'Email', type: 'string', show: true, width: 80 },
{ id: '$id', title: 'Identity ID', type: 'string', width: 140 },
{ id: 'provider', title: 'Provider', type: 'string', width: 80 },
{ id: 'providerUid', title: 'Provider UID', type: 'string', hide: true, width: 80 },
{ id: 'providerEmail', title: 'Email', type: 'string', width: 80 },
{
id: 'providerAccessTokenExpiry',
title: 'Expiry Date',
type: 'datetime',
show: true,
width: 60
},
{ id: '$createdAt', title: 'Created', type: 'datetime', show: true, width: 80 },
{ id: '$updatedAt', title: 'Updated', type: 'datetime', show: false, width: 80 }
{ id: '$createdAt', title: 'Created', type: 'datetime', width: 80 },
{ id: '$updatedAt', title: 'Updated', type: 'datetime', hide: true, width: 80 }
]);
</script>
@@ -50,62 +50,47 @@
}
</script>
<Table.Root>
<svelte:fragment slot="header">
<Table.Header.Selector width="40px" />
{#each columns as column}
{#if column.show}
<Table.Header.Cell width={column.width + 'px'}>{column.title}</Table.Header.Cell>
{/if}
<Table.Root {columns} allowSelection let:root>
<svelte:fragment slot="header" let:root>
{#each columns as { id, title }}
<Table.Header.Cell column={id} {root}>{title}</Table.Header.Cell>
{/each}
</svelte:fragment>
{#each data.identities.identities as identity (identity.$id)}
<Table.Row>
<Table.Cell>
<Selector.Checkbox size="s" />
</Table.Cell>
<Table.Row.Base {root}>
{#each columns as column}
{#if column.show}
<Table.Cell column={column.id} {root}>
{#if column.id === '$id'}
{#key columns}
<Table.Cell>
<Id value={identity[column.id]}>
{identity[column.id]}
</Id>
</Table.Cell>
<Id value={identity[column.id]}>
{identity[column.id]}
</Id>
{/key}
{:else if column.id === 'provider'}
{@const provider = oAuthProviders[identity[column.id]]}
<Table.Cell width={column.width + 'px'}>
<div class="u-inline-flex u-cross-center u-gap-8">
<div class="avatar is-size-small">
<img
style="--p-text-size: 1rem"
height="20"
width="20"
src={`${base}/icons/${$app.themeInUse}/color/${provider.icon}.svg`}
alt={provider.name} />
</div>
{provider.name}
<div class="u-inline-flex u-cross-center u-gap-8">
<div class="avatar is-size-small">
<img
style="--p-text-size: 1rem"
height="20"
width="20"
src={`${base}/icons/${$app.themeInUse}/color/${provider.icon}.svg`}
alt={provider.name} />
</div>
</Table.Cell>
{provider.name}
</div>
{:else if column.type === 'datetime'}
<Table.Cell width={column.width + 'px'}>
{#if !identity[column.id]}
-
{:else}
{toLocaleDateTime(identity[column.id])}
{/if}
</Table.Cell>
{#if !identity[column.id]}
-
{:else}
{toLocaleDateTime(identity[column.id])}
{/if}
{:else}
<Table.Cell width={column.width + 'px'}>
{identity[column.id]}
</Table.Cell>
{identity[column.id]}
{/if}
{/if}
</Table.Cell>
{/each}
</Table.Row>
</Table.Row.Base>
{/each}
</Table.Root>
@@ -21,29 +21,37 @@
<Container>
{#if data.memberships.total}
<Table.Root>
<svelte:fragment slot="header">
<Table.Header.Cell>Name</Table.Header.Cell>
<Table.Header.Cell>Roles</Table.Header.Cell>
<Table.Header.Cell>Joined</Table.Header.Cell>
<Table.Header.Cell width="40px" />
<Table.Root
let:root
columns={[
{ id: 'name' },
{ id: 'roles' },
{ id: 'joined' },
{ id: 'actions', width: 40 }
]}>
<svelte:fragment slot="header" let:root>
<Table.Header.Cell column="name" {root}>Name</Table.Header.Cell>
<Table.Header.Cell column="roles" {root}>Roles</Table.Header.Cell>
<Table.Header.Cell column="joined" {root}>Joined</Table.Header.Cell>
<Table.Header.Cell column="actions" {root} />
</svelte:fragment>
{#each data.memberships.memberships as membership}
<Table.Link
<Table.Row.Link
{root}
href={`${base}/project-${project}/auth/teams/team-${membership.teamId}`}>
<Table.Cell>
<Table.Cell column="name" {root}>
<Layout.Stack direction="row" alignItems="center">
<AvatarInitials size="xs" name={membership.teamName} />
<span>{membership.teamName ? membership.teamName : 'n/a'}</span>
</Layout.Stack>
</Table.Cell>
<Table.Cell>
<Table.Cell column="roles" {root}>
{membership.roles}
</Table.Cell>
<Table.Cell>
<Table.Cell column="joined" {root}>
{toLocaleDateTime(membership.joined)}
</Table.Cell>
<Table.Cell>
<Table.Cell column="actions" {root}>
<button
class="button is-only-icon is-text"
aria-label="Delete item"
@@ -55,7 +63,7 @@
<span class="icon-trash" aria-hidden="true" />
</button>
</Table.Cell>
</Table.Link>
</Table.Row.Link>
{/each}
</Table.Root>
{:else}
@@ -29,17 +29,24 @@
<span class="text">Delete All</span>
</Button>
</Layout.Stack>
<Table.Root>
<svelte:fragment slot="header">
<Table.Header.Cell>Client</Table.Header.Cell>
<Table.Header.Cell>Location</Table.Header.Cell>
<Table.Header.Cell>IP</Table.Header.Cell>
<Table.Header.Cell width="40px" />
<Table.Root
let:root
columns={[
{ id: 'client' },
{ id: 'location' },
{ id: 'ip' },
{ id: 'actions', width: 40 }
]}>
<svelte:fragment slot="header" let:root>
<Table.Header.Cell column="client" {root}>Client</Table.Header.Cell>
<Table.Header.Cell column="location" {root}>Location</Table.Header.Cell>
<Table.Header.Cell column="ip" {root}>IP</Table.Header.Cell>
<Table.Header.Cell column="actions" {root} />
</svelte:fragment>
{#each data.sessions.sessions as session}
{@const browser = getBrowser(session.clientCode)}
<Table.Row>
<Table.Cell>
<Table.Row.Base {root}>
<Table.Cell column="client" {root}>
<Layout.Stack direction="row" alignItems="center">
{#if session.clientName}
<div class="avatar is-size-small">
@@ -69,15 +76,17 @@
<Badge variant="secondary" content={session.provider} />
</Layout.Stack>
</Table.Cell>
<Table.Cell>
<Table.Cell column="location" {root}>
{#if session.countryCode !== '--'}
{session.countryName}
{:else}
Unknown
{/if}
</Table.Cell>
<Table.Cell>{session.ip}</Table.Cell>
<Table.Cell>
<Table.Cell column="ip" {root}>
{session.ip}
</Table.Cell>
<Table.Cell column="actions" {root}>
<Button
compact
icon
@@ -89,7 +98,7 @@
<span class="icon-trash" aria-hidden="true" />
</Button>
</Table.Cell>
</Table.Row>
</Table.Row.Base>
{/each}
</Table.Root>
{:else}
@@ -2,9 +2,9 @@ import type { Column } from '$lib/helpers/types';
import { writable } from 'svelte/store';
export const columns = writable<Column[]>([
{ id: '$id', title: 'Target ID', type: 'string', show: true, width: 140 },
{ id: 'target', title: 'Target', type: 'string', show: true, filter: false, width: 140 },
{ id: 'providerType', title: 'Type', type: 'string', show: true, filter: true, width: 80 },
{ id: 'provider', title: 'Provider', type: 'string', show: true, filter: false, width: 80 },
{ id: '$createdAt', title: 'Created', type: 'datetime', show: true, width: 100 }
{ id: '$id', title: 'Target ID', type: 'string', width: { min: 140 } },
{ id: 'target', title: 'Target', type: 'string', filter: false, width: { min: 140 } },
{ id: 'providerType', title: 'Type', type: 'string', filter: true, width: { min: 80 } },
{ id: 'provider', title: 'Provider', type: 'string', filter: false, width: { min: 80 } },
{ id: '$createdAt', title: 'Created', type: 'datetime', width: { min: 100 } }
]);
@@ -52,62 +52,43 @@
}
</script>
<Table.Root>
<svelte:fragment slot="header">
<Table.Header.Selector width="40px" />
{#each $columns as column}
{#if column.show}
<Table.Header.Cell width={column.width + 'px'}>{column.title}</Table.Header.Cell>
{/if}
<Table.Root columns={$columns} allowSelection let:root bind:selectedRows={selectedIds}>
<svelte:fragment slot="header" let:root>
{#each $columns as { id, title }}
<Table.Header.Cell column={id} {root}>{title}</Table.Header.Cell>
{/each}
</svelte:fragment>
{#each data.targets.targets as target (target.$id)}
{@const provider = data.providersById[target.providerId]}
<Table.Row>
<Table.Cell>
<Selector.Checkbox size="s" />
</Table.Cell>
<Table.Row.Base {root} id={target.$id}>
{#each $columns as column}
{#if column.show}
<Table.Cell column={column.id} {root}>
{#if column.id === '$id'}
{#key $columns}
<Table.Cell>
<Id value={target[column.id]}>
{target[column.id]}
</Id>
</Table.Cell>
<Id value={target[column.id]}>
{target[column.id]}
</Id>
{/key}
{:else if column.id === 'target'}
<Table.Cell>
{#if target.providerType === MessagingProviderType.Push}
{target.name}
{:else}
{target.identifier}
{/if}
</Table.Cell>
{#if target.providerType === MessagingProviderType.Push}
{target.name}
{:else}
{target.identifier}
{/if}
{:else if column.id === 'providerType'}
<Table.Cell width={column.width + 'px'}>
<ProviderType type={target.providerType} size="s" />
</Table.Cell>
<ProviderType type={target.providerType} size="s" />
{:else if column.id === 'provider'}
<Table.Cell width={column.width + 'px'}>
{#if provider}
<Provider provider={provider.provider} size="s" />
{/if}
</Table.Cell>
{#if provider}
<Provider provider={provider.provider} />
{/if}
{:else if column.id === '$createdAt'}
<Table.Cell width={column.width + 'px'}>
{toLocaleDateTime(target[column.id])}
</Table.Cell>
{toLocaleDateTime(target[column.id])}
{:else}
<Table.Cell width={column.width + 'px'}>
{target[column.id]}
</Table.Cell>
{target[column.id]}
{/if}
{/if}
</Table.Cell>
{/each}
</Table.Row>
</Table.Row.Base>
{/each}
</Table.Root>
@@ -49,14 +49,14 @@
label="Multi-factor authentication"
bind:value={userMfa} />
{#if $userFactors.totp}
<Table.Root>
<svelte:fragment slot="header">
<Table.Header.Cell>Authenticator</Table.Header.Cell>
<Table.Header.Cell width="40px" />
<Table.Root columns={[{ id: 'type' }, { id: 'actions', width: 40 }]} let:root>
<svelte:fragment slot="header" let:root>
<Table.Header.Cell column="type" {root}>Authenticator</Table.Header.Cell>
<Table.Header.Cell column="actions" {root} />
</svelte:fragment>
<Table.Row>
<Table.Cell>TOTP (One-time code)</Table.Cell>
<Table.Cell>
<Table.Row.Base {root}>
<Table.Cell column="type" {root}>TOTP (One-time code)</Table.Cell>
<Table.Cell column="actions" {root}>
<Button
on:click={() => (showDelete = true)}
icon
@@ -65,7 +65,7 @@
<span class="icon-trash" aria-hidden="true" />
</Button>
</Table.Cell>
</Table.Row>
</Table.Row.Base>
</Table.Root>
{:else}
<Card.Base variant="primary">
@@ -2,8 +2,8 @@ import type { Column } from '$lib/helpers/types';
import { writable } from 'svelte/store';
export const columns = writable<Column[]>([
{ id: '$id', title: 'Database ID', type: 'string', show: true, width: 150 },
{ id: 'name', title: 'Name', type: 'string', show: true, width: 120 },
{ id: '$createdAt', title: 'Created', type: 'string', show: true, width: 120 },
{ id: '$updatedAt', title: 'Updated', type: 'string', show: true, width: 120 }
{ id: '$id', title: 'Database ID', type: 'string', width: 150 },
{ id: 'name', title: 'Name', type: 'string', width: 120 },
{ id: '$createdAt', title: 'Created', type: 'string', width: 120 },
{ id: '$updatedAt', title: 'Updated', type: 'string', width: 120 }
]);
@@ -80,17 +80,24 @@
</Layout.Stack>
{#if $attributes.length}
<Table.Root>
<svelte:fragment slot="header">
<Table.Header.Cell>Key</Table.Header.Cell>
<Table.Header.Cell>Type</Table.Header.Cell>
<Table.Header.Cell>Default value</Table.Header.Cell>
<Table.Header.Cell width="40px" />
<Table.Root
let:root
columns={[
{ id: 'key' },
{ id: 'type' },
{ id: 'default' },
{ id: 'actions', width: 40 }
]}>
<svelte:fragment slot="header" let:root>
<Table.Header.Cell column="key" {root}>Key</Table.Header.Cell>
<Table.Header.Cell column="type" {root}>Type</Table.Header.Cell>
<Table.Header.Cell column="default" {root}>Default value</Table.Header.Cell>
<Table.Header.Cell column="actions" {root} />
</svelte:fragment>
{#each $attributes as attribute, index}
{@const option = attributeOptions.find((option) => option.type === attribute.type)}
<Table.Row>
<Table.Cell>
<Table.Row.Base {root}>
<Table.Cell column="key" {root}>
<Layout.Stack direction="row" alignItems="center">
{#if isRelationship(attribute)}
<Icon
@@ -125,7 +132,7 @@
{/if}
</Layout.Stack>
</Table.Cell>
<Table.Cell>
<Table.Cell column="type" {root}>
{#if 'format' in attribute && attribute.format}
<span class="u-capitalize">{attribute.format}</span>
{:else}
@@ -142,12 +149,12 @@
{attribute.array ? '[]' : ''}
</span>
</Table.Cell>
<Table.Cell>
<Table.Cell column="default" {root}>
{attribute?.default !== null && attribute?.default !== undefined
? attribute?.default
: '-'}
</Table.Cell>
<Table.Cell>
<Table.Cell column="actions" {root}>
<Popover let:toggle padding="none" placement="bottom-end">
<Button text icon ariaLabel="more options" on:click={toggle}>
<Icon icon={IconDotsHorizontal} size="s" />
@@ -188,7 +195,7 @@
</ActionMenu.Root>
</Popover>
</Table.Cell>
</Table.Row>
</Table.Row.Base>
{/each}
</Table.Root>
{:else}
@@ -61,15 +61,21 @@
{#if relAttributes?.length}
<p class="text">This document contains the following relationships:</p>
<Table.Root>
<svelte:fragment slot="header">
<Table.Header.Cell width="70px">Relation</Table.Header.Cell>
<Table.Header.Cell width="70px">Setting</Table.Header.Cell>
<Table.Header.Cell />
<Table.Root
let:root
columns={[
{ id: 'relations', width: 70 },
{ id: 'setting', width: 70 },
{ id: 'desc' }
]}>
<svelte:fragment slot="header" let:root>
<Table.Header.Cell column="relations" {root}>Relation</Table.Header.Cell>
<Table.Header.Cell column="setting" {root}>Setting</Table.Header.Cell>
<Table.Header.Cell column="desc" {root} />
</svelte:fragment>
{#each relAttributes as attr}
<Table.Row>
<Table.Cell>
<Table.Row.Base {root}>
<Table.Cell column="relations" {root}>
<span class="u-flex u-cross-center u-gap-8">
{#if attr.twoWay}
<span class="icon-switch-horizontal" />
@@ -79,13 +85,13 @@
<Trim>{attr.key}</Trim>
</span>
</Table.Cell>
<Table.Cell>
<Table.Cell column="setting" {root}>
{attr.onDelete}
</Table.Cell>
<Table.Cell>
<Table.Cell column="desc" {root}>
{Deletion[attr.onDelete]}
</Table.Cell>
</Table.Row>
</Table.Row.Base>
{/each}
</Table.Root>
<div class="u-flex u-flex-vertical u-gap-16">
@@ -33,6 +33,8 @@
import type { ComponentProps } from 'svelte';
import { Click, trackEvent } from '$lib/actions/analytics';
export let data;
let showDropdown = [];
let selectedIndex: Models.Index = null;
let showCreateIndex = false;
@@ -72,19 +74,27 @@
{/if}
</Layout.Stack>
{#if $collection?.attributes?.length}
{#if $indexes.length}
<Table.Root>
<svelte:fragment slot="header">
<Table.Header.Cell>Key</Table.Header.Cell>
<Table.Header.Cell>Type</Table.Header.Cell>
<Table.Header.Cell>Attributes</Table.Header.Cell>
<Table.Header.Cell>Asc/Desc</Table.Header.Cell>
<Table.Header.Cell width="40px" />
{#if data.collection?.attributes?.length}
{#if data.collection.indexes.length}
<Table.Root
let:root
columns={[
{ id: 'key' },
{ id: 'type' },
{ id: 'attributes' },
{ id: 'orders' },
{ id: 'actions', width: 40 }
]}>
<svelte:fragment slot="header" let:root>
<Table.Header.Cell column="key" {root}>Key</Table.Header.Cell>
<Table.Header.Cell column="type" {root}>Type</Table.Header.Cell>
<Table.Header.Cell column="attributes" {root}>Attributes</Table.Header.Cell>
<Table.Header.Cell column="orders" {root}>Asc/Desc</Table.Header.Cell>
<Table.Header.Cell column="actions" {root} />
</svelte:fragment>
{#each $indexes as index, i}
<Table.Row>
<Table.Cell>
{#each data.collection.indexes as index}
<Table.Row.Base {root}>
<Table.Cell column="key" {root}>
<Layout.Stack direction="row" alignItems="center">
{index.key}
{#if index.status !== 'available'}
@@ -105,14 +115,14 @@
{/if}
</Layout.Stack>
</Table.Cell>
<Table.Cell>{index.type}</Table.Cell>
<Table.Cell>
<Table.Cell column="type" {root}>{index.type}</Table.Cell>
<Table.Cell column="attributes" {root}>
{index.attributes}
</Table.Cell>
<Table.Cell>
<Table.Cell column="orders" {root}>
{index.orders}
</Table.Cell>
<Table.Cell>
<Table.Cell column="actions" {root}>
<Popover let:toggle padding="none" placement="bottom-end">
<Button text icon ariaLabel="more options" on:click={toggle}>
<Icon icon={IconDotsHorizontal} size="s" />
@@ -134,7 +144,7 @@
</ActionMenu.Root>
</Popover>
</Table.Cell>
</Table.Row>
</Table.Row.Base>
{/each}
</Table.Root>
{:else}
@@ -28,28 +28,31 @@
<Modal bind:show title={selectedRelationship?.key} hideFooter>
{#if data?.length}
<Paginator items={data} {limit} let:paginatedItems>
<Table.Root>
<svelte:fragment slot="header">
<Table.Header.Cell width="150px">Document ID</Table.Header.Cell>
<Table.Root
let:root
columns={[{ id: '$id', width: 150 }, ...args.map((id) => ({ id }))]}>
<svelte:fragment slot="header" let:root>
<Table.Header.Cell column="$id" {root}>Document ID</Table.Header.Cell>
{#if args?.length}
{#each args as arg}
<Table.Header.Cell>{arg}</Table.Header.Cell>
<Table.Header.Cell column={arg} {root}>{arg}</Table.Header.Cell>
{/each}
{/if}
</svelte:fragment>
{#each paginatedItems as doc}
<Table.Link
<Table.Row.Link
{root}
href={`${base}/project-${projectId}/databases/database-${databaseId}/collection-${selectedRelationship.relatedCollection}/document-${doc.$id}`}
on:click={() => (show = false)}>
<Table.Cell>
<Table.Cell column="$id" {root}>
<Id value={doc.$id}>{doc.$id}</Id>
</Table.Cell>
{#if args?.length}
{#each args as arg}
<Table.Cell>{doc[arg]}</Table.Cell>
<Table.Cell column={arg} {root}>{doc[arg]}</Table.Cell>
{/each}
{/if}
</Table.Link>
</Table.Row.Link>
{/each}
</Table.Root>
</Paginator>
@@ -17,7 +17,7 @@
import { attributes, collection, columns } from './store';
import type { ColumnType } from '$lib/helpers/types';
import { toLocaleDateTime } from '$lib/helpers/date';
import { Tooltip, Table, Selector, Button, Link } from '@appwrite.io/pink-svelte';
import { Tooltip, Table, Button, Link } from '@appwrite.io/pink-svelte';
export let data: PageData;
@@ -103,14 +103,14 @@
);
}
let selectedDb: string[] = [];
let selectedRows: string[] = [];
let showDelete = false;
let deleting = false;
async function handleDelete() {
showDelete = false;
const promises = selectedDb.map((documentId) =>
const promises = selectedRows.map((documentId) =>
sdk.forProject.databases.deleteDocument(databaseId, collectionId, documentId)
);
try {
@@ -118,7 +118,7 @@
trackEvent(Submit.DocumentDelete);
addNotification({
type: 'success',
message: `${selectedDb.length} document${selectedDb.length > 1 ? 's' : ''} deleted`
message: `${selectedRows.length} document${selectedRows.length > 1 ? 's' : ''} deleted`
});
invalidate(Dependencies.DOCUMENTS);
} catch (error) {
@@ -128,7 +128,7 @@
});
trackError(error, Submit.DocumentDelete);
} finally {
selectedDb = [];
selectedRows = [];
showDelete = false;
}
}
@@ -146,25 +146,25 @@
let checked = false;
</script>
<Table.Root>
<svelte:fragment slot="header">
<Table.Header.Selector width="40px" />
<Table.Header.Cell width="150px">Document ID</Table.Header.Cell>
{#each $columns.filter((n) => n.show) as column}
{#if column.show}
<Table.Header.Cell>{column.title}</Table.Header.Cell>
{/if}
<Table.Root
let:root
allowSelection
bind:selectedRows
columns={[{ id: '$id', width: 150 }, ...$columns, { id: '$created' }, { id: '$updated' }]}>
<svelte:fragment slot="header" let:root>
<Table.Header.Cell column="$id" {root}>Document ID</Table.Header.Cell>
{#each $columns as column}
<Table.Header.Cell column={column.id} {root}>{column.title}</Table.Header.Cell>
{/each}
<Table.Header.Cell>Created</Table.Header.Cell>
<Table.Header.Cell>Updated</Table.Header.Cell>
<Table.Header.Cell column="$created" {root}>Created</Table.Header.Cell>
<Table.Header.Cell column="$updated" {root}>Updated</Table.Header.Cell>
</svelte:fragment>
{#each data.documents.documents as document}
<Table.Link
<Table.Row.Link
{root}
id={document.$id}
href={`${base}/project-${projectId}/databases/database-${databaseId}/collection-${$collection.$id}/document-${document.$id}`}>
<Table.Cell>
<Selector.Checkbox size="s" />
</Table.Cell>
<Table.Cell width="150px">
<Table.Cell column="$id" {root}>
{#key document.$id}
<Id value={document.$id}>
{document.$id}
@@ -172,96 +172,92 @@
{/key}
</Table.Cell>
{#each $columns as column}
{#if column.show}
{@const attr = $attributes.find((n) => n.key === column.id)}
{#each $columns as { id }}
{@const attr = $attributes.find((n) => n.key === id)}
<Table.Cell column={id} {root}>
{#if isRelationship(attr)}
{@const args = displayNames?.[attr.relatedCollection] ?? ['$id']}
<Table.Cell>
{#if !isRelationshipToMany(attr)}
{#if document[column.id]}
{@const related = document[column.id]}
<Link.Button
variant="muted"
on:click={(e) => {
e.preventDefault();
e.stopPropagation();
goto(
`${base}/project-${projectId}/databases/database-${databaseId}/collection-${attr.relatedCollection}/document-${related.$id}`
);
}}>
{#each args as arg, i}
{#if arg !== undefined}
{#if i}
&nbsp;|
{/if}
<span class="text" data-private>
{related?.[arg]}
</span>
{/if}
{/each}
</Link.Button>
{:else}
<span class="text">n/a</span>
{/if}
{:else}
{@const itemsNum = document[column.id]?.length}
<Button.Button
variant="extra-compact"
disabled={!itemsNum}
badge={itemsNum ?? 0}
{#if !isRelationshipToMany(attr)}
{#if document[+id]}
{@const related = document[id]}
<Link.Button
variant="muted"
on:click={(e) => {
e.stopPropagation();
e.preventDefault();
relationshipData = document[column.id];
showRelationships = true;
selectedRelationship = attr;
e.stopPropagation();
goto(
`${base}/project-${projectId}/databases/database-${databaseId}/collection-${attr.relatedCollection}/document-${related.$id}`
);
}}>
Items
</Button.Button>
{#each args as arg, i}
{#if arg !== undefined}
{#if i}
&nbsp;|
{/if}
<span class="text" data-private>
{related?.[arg]}
</span>
{/if}
{/each}
</Link.Button>
{:else}
<span class="text">n/a</span>
{/if}
</Table.Cell>
{:else}
{@const itemsNum = document[id]?.length}
<Button.Button
variant="extra-compact"
disabled={!itemsNum}
badge={itemsNum ?? 0}
on:click={(e) => {
e.stopPropagation();
e.preventDefault();
relationshipData = document[id];
showRelationships = true;
selectedRelationship = attr;
}}>
Items
</Button.Button>
{/if}
{:else}
{@const formatted = formatColumn(document[column.id])}
<Table.Cell>
<Tooltip disabled={!formatted.truncated} placement="bottom">
<span>
{formatted.value}
</span>
<span style:white-space="pre-wrap" slot="tooltip">
{formatted.whole}
</span>
</Tooltip>
</Table.Cell>
{@const formatted = formatColumn(document[id])}
<Tooltip disabled={!formatted.truncated} placement="bottom">
<span>
{formatted.value}
</span>
<span style:white-space="pre-wrap" slot="tooltip">
{formatted.whole}
</span>
</Tooltip>
{/if}
{/if}
</Table.Cell>
{/each}
<Table.Cell>
<Table.Cell column="$created" {root}>
{toLocaleDateTime(document.$createdAt)}
</Table.Cell>
<Table.Cell>
<Table.Cell column="$updated" {root}>
{toLocaleDateTime(document.$updatedAt)}
</Table.Cell>
</Table.Link>
</Table.Row.Link>
{/each}
</Table.Root>
<RelationshipsModal bind:show={showRelationships} {selectedRelationship} data={relationshipData} />
<FloatingActionBar show={selectedDb.length > 0}>
<FloatingActionBar show={selectedRows.length > 0}>
<div class="u-flex u-cross-center u-main-space-between actions">
<div class="u-flex u-cross-center u-gap-8">
<span class="indicator body-text-2 u-bold">{selectedDb.length}</span>
<span class="indicator body-text-2 u-bold">{selectedRows.length}</span>
<p>
<span class="is-only-desktop">
{selectedDb.length > 1 ? 'documents' : 'document'}
{selectedRows.length > 1 ? 'documents' : 'document'}
</span>
selected
</p>
</div>
<div class="u-flex u-cross-center u-gap-8">
<ConsoleButton text on:click={() => (selectedDb = [])}>Cancel</ConsoleButton>
<ConsoleButton text on:click={() => (selectedRows = [])}>Cancel</ConsoleButton>
<ConsoleButton secondary on:click={() => (showDelete = true)}>
<p>Delete</p>
</ConsoleButton>
@@ -272,20 +268,26 @@
<Modal title="Delete Documents" bind:show={showDelete} onSubmit={handleDelete} closable={!deleting}>
<div>
<p class="text" data-private>
Are you sure you want to delete <b>{selectedDb.length}</b>
{selectedDb.length > 1 ? 'documents' : 'document'}?
Are you sure you want to delete <b>{selectedRows.length}</b>
{selectedRows.length > 1 ? 'documents' : 'document'}?
</p>
{#if relAttributes?.length}
<Table.Root>
<svelte:fragment slot="header">
<Table.Header.Cell width="50px">Relation</Table.Header.Cell>
<Table.Header.Cell width="50px">Setting</Table.Header.Cell>
<Table.Header.Cell />
<Table.Root
let:root
columns={[
{ id: 'relation', width: 150 },
{ id: 'setting', width: 150 },
{ id: 'desc' }
]}>
<svelte:fragment slot="header" let:root>
<Table.Header.Cell column="relation" {root}>Relation</Table.Header.Cell>
<Table.Header.Cell column="setting" {root}>Setting</Table.Header.Cell>
<Table.Header.Cell column="desc" {root} />
</svelte:fragment>
{#each relAttributes as attr}
<Table.Row>
<Table.Cell>
<Table.Row.Base {root}>
<Table.Cell column="relation" {root}>
<span class="u-flex u-cross-center u-gap-8">
{#if attr.twoWay}
<span class="icon-switch-horizontal" />
@@ -295,13 +297,13 @@
<span data-private>{attr.key}</span>
</span>
</Table.Cell>
<Table.Cell>
<Table.Cell column="setting" {root}>
{attr.onDelete}
</Table.Cell>
<Table.Cell>
<Table.Cell column="desc" {root}>
{Deletion[attr.onDelete]}
</Table.Cell>
</Table.Row>
</Table.Row.Base>
{/each}
</Table.Root>
<div class="u-flex u-flex-vertical u-gap-16">
@@ -326,28 +328,3 @@
</ConsoleButton>
</svelte:fragment>
</Modal>
<style lang="scss">
.actions {
.indicator {
border-radius: 0.25rem;
background: hsl(var(--color-information-100));
color: hsl(var(--color-neutral-0));
padding: 0rem 0.375rem;
display: inline-block;
}
}
:global(.truncated-content-table-cell .truncated) {
max-width: 50vw;
overflow: hidden;
white-space: nowrap;
display: inline-block;
text-overflow: ellipsis;
}
:global(.truncated-content-table-cell:has(.truncated)) {
inline-size: 0;
}
</style>
@@ -109,16 +109,16 @@
</p>
{:else if collectionItems.length > 0}
<div class="u-flex-vertical u-gap-16">
<Table.Root>
<svelte:fragment slot="header">
<Table.Header.Cell>Collection</Table.Header.Cell>
<Table.Header.Cell>Last Updated</Table.Header.Cell>
<Table.Root columns={2} let:root>
<svelte:fragment slot="header" let:root>
<Table.Header.Cell {root}>Collection</Table.Header.Cell>
<Table.Header.Cell {root}>Last Updated</Table.Header.Cell>
</svelte:fragment>
{#each collectionItems as collection}
<Table.Row>
<Table.Cell>{collection.name}</Table.Cell>
<Table.Cell>{toLocaleDate(collection.updatedAt)}</Table.Cell>
</Table.Row>
<Table.Row.Base {root}>
<Table.Cell {root}>{collection.name}</Table.Cell>
<Table.Cell {root}>{toLocaleDate(collection.updatedAt)}</Table.Cell>
</Table.Row.Base>
{/each}
</Table.Root>
@@ -7,10 +7,10 @@ export const database = derived(page, ($page) => $page.data.database as Models.D
export const showCreate = writable(false);
export const columns = writable<Column[]>([
{ id: '$id', title: 'Collection ID', type: 'string', show: true, width: 150 },
{ id: 'name', title: 'Name', type: 'string', show: true, width: 120 },
{ id: '$createdAt', title: 'Created', type: 'datetime', show: true, width: 120 },
{ id: '$updatedAt', title: 'Updated', type: 'datetime', show: true, width: 120 }
{ id: '$id', title: 'Collection ID', type: 'string', width: 150 },
{ id: 'name', title: 'Name', type: 'string', width: { min: 120 } },
{ id: '$createdAt', title: 'Created', type: 'datetime', width: { min: 120 } },
{ id: '$updatedAt', title: 'Updated', type: 'datetime', width: { min: 120 } }
]);
export const backupRetainingOptions = [
@@ -11,7 +11,7 @@
import { addNotification } from '$lib/stores/notifications';
import { canWriteCollections } from '$lib/stores/roles';
import { sdk } from '$lib/stores/sdk';
import { Badge, Selector, Table } from '@appwrite.io/pink-svelte';
import { Badge, Table } from '@appwrite.io/pink-svelte';
import type { PageData } from './$types';
import { columns } from './store';
@@ -19,14 +19,14 @@
const projectId = $page.params.project;
const databaseId = $page.params.database;
let selected: string[] = [];
let selectedRows: string[] = [];
let showDelete = false;
let deleting = false;
async function handleDelete() {
showDelete = false;
const promises = selected.map((collectionId) =>
const promises = selectedRows.map((collectionId) =>
sdk.forProject.databases.deleteCollection(databaseId, collectionId)
);
try {
@@ -34,7 +34,7 @@
trackEvent(Submit.CollectionDelete);
addNotification({
type: 'success',
message: `${selected.length} collection${selected.length > 1 ? 's' : ''} deleted`
message: `${selectedRows.length} collection${selectedRows.length > 1 ? 's' : ''} deleted`
});
invalidate(Dependencies.COLLECTIONS);
} catch (error) {
@@ -44,67 +44,52 @@
});
trackError(error, Submit.CollectionDelete);
} finally {
selected = [];
selectedRows = [];
showDelete = false;
}
}
</script>
<Table.Root>
<svelte:fragment slot="header">
{#if $canWriteCollections}
<Table.Header.Selector width="20px" />
{/if}
{#each $columns as column}
{#if column.show}
<Table.Header.Cell width={column.width + 'px'}>{column.title}</Table.Header.Cell>
{/if}
<Table.Root columns={$columns} allowSelection={$canWriteCollections} bind:selectedRows let:root>
<svelte:fragment slot="header" let:root>
{#each $columns as { id, title }}
<Table.Header.Cell column={id} {root}>{title}</Table.Header.Cell>
{/each}
</svelte:fragment>
{#each data.collections.collections as collection (collection.$id)}
<Table.Link
<Table.Row.Link
{root}
id={collection.$id}
href={`${base}/project-${projectId}/databases/database-${databaseId}/collection-${collection.$id}`}>
{#if $canWriteCollections}
<Table.Cell>
<Selector.Checkbox size="s" />
</Table.Cell>
{/if}
{#each $columns as column}
{#if column.show}
<Table.Cell column={column.id} {root}>
{#if column.id === '$id'}
{#key $columns}
<Table.Cell>
<Id value={collection.$id}>{collection.$id}</Id>
</Table.Cell>
<Id value={collection.$id}>{collection.$id}</Id>
{/key}
{:else if column.id === 'name'}
<Table.Cell>
{collection.name}
</Table.Cell>
{collection.name}
{:else}
<Table.Cell>
{toLocaleDateTime(collection[column.id])}
</Table.Cell>
{toLocaleDateTime(collection[column.id])}
{/if}
{/if}
</Table.Cell>
{/each}
</Table.Link>
</Table.Row.Link>
{/each}
</Table.Root>
<FloatingActionBar show={selected.length > 0}>
<FloatingActionBar show={selectedRows.length > 0}>
<svelte:fragment slot="start">
<Badge content={selected.length.toString()} />
<Badge content={selectedRows.length.toString()} />
<span>
<span class="is-only-desktop">
{selected.length > 1 ? 'collections' : 'collection'}
{selectedRows.length > 1 ? 'collections' : 'collection'}
</span>
selected
</span>
</svelte:fragment>
<svelte:fragment slot="end">
<Button text on:click={() => (selected = [])}>Cancel</Button>
<Button text on:click={() => (selectedRows = [])}>Cancel</Button>
<Button secondary on:click={() => (showDelete = true)}>Delete</Button>
</svelte:fragment>
</FloatingActionBar>
@@ -115,8 +100,8 @@
onSubmit={handleDelete}
closable={!deleting}>
<p class="text" data-private>
Are you sure you want to delete <b>{selected.length}</b>
{selected.length > 1 ? 'collections' : 'collection'}?
Are you sure you want to delete <b>{selectedRows.length}</b>
{selectedRows.length > 1 ? 'collections' : 'collection'}?
</p>
<svelte:fragment slot="footer">
<Button text on:click={() => (showDelete = false)} disabled={deleting}>Cancel</Button>
@@ -5,16 +5,16 @@ import { writable } from 'svelte/store';
export const columns = writable<Column[]>(
isCloud
? [
{ id: '$id', title: 'Database ID', type: 'string', show: true, width: 150 },
{ id: 'name', title: 'Name', type: 'string', show: true, width: 120 },
{ id: 'backup', title: 'Backups', type: 'string', show: true, width: 120 },
{ id: '$createdAt', title: 'Created', type: 'datetime', show: true, width: 120 },
{ id: '$updatedAt', title: 'Updated', type: 'datetime', show: true, width: 120 }
{ id: '$id', title: 'Database ID', type: 'string', width: 150 },
{ id: 'name', title: 'Name', type: 'string', width: { min: 120 } },
{ id: 'backup', title: 'Backups', type: 'string', width: { min: 120 } },
{ id: '$createdAt', title: 'Created', type: 'datetime', width: { min: 120 } },
{ id: '$updatedAt', title: 'Updated', type: 'datetime', width: { min: 120 } }
]
: [
{ id: '$id', title: 'Database ID', type: 'string', show: true, width: 150 },
{ id: 'name', title: 'Name', type: 'string', show: true, width: 120 },
{ id: '$createdAt', title: 'Created', type: 'datetime', show: true, width: 120 },
{ id: '$updatedAt', title: 'Updated', type: 'datetime', show: true, width: 120 }
{ id: '$id', title: 'Database ID', type: 'string', width: 150 },
{ id: 'name', title: 'Name', type: 'string', width: { min: 120 } },
{ id: '$createdAt', title: 'Created', type: 'datetime', width: { min: 120 } },
{ id: '$updatedAt', title: 'Updated', type: 'datetime', width: { min: 120 } }
]
);
@@ -20,30 +20,26 @@
}
</script>
<Table.Root>
<svelte:fragment slot="header">
{#each $columns as column}
{#if column.show}
<Table.Header.Cell width={column.width}>{column.title}</Table.Header.Cell>
{/if}
<Table.Root columns={$columns} let:root>
<svelte:fragment slot="header" let:root>
{#each $columns as { id, title }}
<Table.Header.Cell column={id} {root}>{title}</Table.Header.Cell>
{/each}
</svelte:fragment>
{#each data.databases.databases as database (database.$id)}
<Table.Link href={`${base}/project-${projectId}/databases/database-${database.$id}`}>
<Table.Row.Link
{root}
href={`${base}/project-${projectId}/databases/database-${database.$id}`}>
{#each $columns as column}
{#if column.show}
<Table.Cell column={column.id} {root}>
{#if column.id === '$id'}
{#key $columns}
<Table.Cell width={column.width + 'px'}>
<Id value={database.$id}>
{database.$id}
</Id>
</Table.Cell>
<Id value={database.$id}>
{database.$id}
</Id>
{/key}
{:else if column.id === 'name'}
<Table.Cell width={column.width + 'px'}>
{database.name}
</Table.Cell>
{database.name}
{:else if column.id === 'backup'}
{@const policies = data.policies?.[database.$id] ?? null}
{@const lastBackup = data.lastBackups?.[database.$id] ?? null}
@@ -51,31 +47,21 @@
?.map((policy) => getPolicyDescription(policy.schedule))
.join(', ')}
<Table.Cell width={column.width + 'px'}>
<Tooltip placement="bottom" disabled={!policies || !lastBackup}>
<span class="u-trim">
{#if !policies}
<span class="icon-exclamation" /> No backup policies
{:else}
{description}
{/if}
</span>
<span slot="tooltip">{`Last backup: ${lastBackup}`}</span>
</Tooltip>
</Table.Cell>
<Tooltip placement="bottom" disabled={!policies || !lastBackup}>
<span class="u-trim">
{#if !policies}
<span class="icon-exclamation" /> No backup policies
{:else}
{description}
{/if}
</span>
<span slot="tooltip">{`Last backup: ${lastBackup}`}</span>
</Tooltip>
{:else}
<Table.Cell width={column.width + 'px'}>
{toLocaleDateTime(database[column.id])}
</Table.Cell>
{toLocaleDateTime(database[column.id])}
{/if}
{/if}
</Table.Cell>
{/each}
</Table.Link>
</Table.Row.Link>
{/each}
</Table.Root>
<style lang="scss">
.icon-exclamation {
color: hsl(var(--color-warning-100)) !important;
}
</style>
@@ -67,16 +67,23 @@
</Layout.Stack>
{#if data.domains.total}
<Table.Root>
<svelte:fragment slot="header">
<Table.Header.Cell width="200px">Domain</Table.Header.Cell>
<Table.Header.Cell>Redirect to</Table.Header.Cell>
<Table.Header.Cell>Production branch</Table.Header.Cell>
<Table.Header.Cell />
<Table.Root
let:root
columns={[
{ id: 'domain', width: { min: 200 } },
{ id: 'redirect' },
{ id: 'branch' },
{ id: 'actions', width: 40 }
]}>
<svelte:fragment slot="header" let:root>
<Table.Header.Cell column="domain" {root}>Domain</Table.Header.Cell>
<Table.Header.Cell column="redirect" {root}>Redirect to</Table.Header.Cell>
<Table.Header.Cell column="branch" {root}>Production branch</Table.Header.Cell>
<Table.Header.Cell column="actions" {root} />
</svelte:fragment>
{#each data.domains.rules as domain}
<Table.Row>
<Table.Cell>
<Table.Row.Base {root}>
<Table.Cell column="domain" {root}>
<Link external href={`${$protocol}${domain.domain}`} variant="quiet">
<Layout.Stack direction="row" alignItems="center" gap="xs">
{domain.domain}
@@ -84,60 +91,58 @@
</Layout.Stack>
</Link>
</Table.Cell>
<Table.Cell>
<Table.Cell column="redirect" {root}>
{domain?.redirectUrl || 'No redirect'}
{domain?.redirectStatusCode ? `(${domain.redirectStatusCode})` : ''}
</Table.Cell>
<Table.Cell>
<Table.Cell column="branch" {root}>
{domain.deploymentVcsProviderBranch || '-'}
</Table.Cell>
<Table.Cell>
<Layout.Stack direction="row" justifyContent="flex-end">
<Popover let:toggle placement="bottom-start" padding="none">
<Button
text
icon
on:click={(e) => {
e.preventDefault();
toggle(e);
}}>
<Icon icon={IconDotsHorizontal} size="s" />
</Button>
<Table.Cell column="actions" {root}>
<Popover let:toggle placement="bottom-start" padding="none">
<Button
text
icon
on:click={(e) => {
e.preventDefault();
toggle(e);
}}>
<Icon icon={IconDotsHorizontal} size="s" />
</Button>
<svelte:fragment slot="tooltip" let:toggle>
<ActionMenu.Root>
{#if domain.status !== 'verified'}
<ActionMenu.Item.Button
leadingIcon={IconRefresh}
on:click={(e) => {
e.preventDefault();
selectedDomain = domain;
showRetry = true;
toggle(e);
}}>
Retry
</ActionMenu.Item.Button>
{/if}
<svelte:fragment slot="tooltip" let:toggle>
<ActionMenu.Root>
{#if domain.status !== 'verified'}
<ActionMenu.Item.Button
status="danger"
leadingIcon={IconTrash}
leadingIcon={IconRefresh}
on:click={(e) => {
e.preventDefault();
selectedDomain = domain;
showDelete = true;
showRetry = true;
toggle(e);
trackEvent(Click.DomainDeleteClick, {
source: 'functions_domain_overview'
});
}}>
Delete
Retry
</ActionMenu.Item.Button>
</ActionMenu.Root>
</svelte:fragment>
</Popover>
</Layout.Stack>
{/if}
<ActionMenu.Item.Button
status="danger"
leadingIcon={IconTrash}
on:click={(e) => {
e.preventDefault();
selectedDomain = domain;
showDelete = true;
toggle(e);
trackEvent(Click.DomainDeleteClick, {
source: 'functions_domain_overview'
});
}}>
Delete
</ActionMenu.Item.Button>
</ActionMenu.Root>
</svelte:fragment>
</Popover>
</Table.Cell>
</Table.Row>
</Table.Row.Base>
{/each}
</Table.Root>
@@ -94,23 +94,23 @@
</Typography.Text>
</Layout.Stack>
<Table.Root>
<svelte:fragment slot="header">
<Table.Header.Cell>Type</Table.Header.Cell>
<Table.Header.Cell>Name</Table.Header.Cell>
<Table.Header.Cell>Value</Table.Header.Cell>
<Table.Root columns={3} let:root>
<svelte:fragment slot="header" let:root>
<Table.Header.Cell {root}>Type</Table.Header.Cell>
<Table.Header.Cell {root}>Name</Table.Header.Cell>
<Table.Header.Cell {root}>Value</Table.Header.Cell>
</svelte:fragment>
<Table.Row>
<Table.Cell>CNAME</Table.Cell>
<Table.Cell>{data.domain?.domain}</Table.Cell>
<Table.Cell>
<Table.Row.Base {root}>
<Table.Cell {root}>CNAME</Table.Cell>
<Table.Cell {root}>{data.domain?.domain}</Table.Cell>
<Table.Cell {root}>
<InteractiveText
variant="copy"
isVisible
text={globalThis?.location?.origin}>
{globalThis?.location?.origin}</InteractiveText>
</Table.Cell>
</Table.Row>
</Table.Row.Base>
</Table.Root>
<Layout.Stack gap="s" direction="row" alignItems="center">
<Icon icon={IconInfo} size="s" color="--fgcolor-neutral-secondary" />
@@ -145,19 +145,19 @@
</Typography.Text>
</Layout.Stack>
<Table.Root>
<svelte:fragment slot="header">
<Table.Header.Cell>Type</Table.Header.Cell>
<Table.Header.Cell>Value</Table.Header.Cell>
<Table.Root columns={2} let:root>
<svelte:fragment slot="header" let:root>
<Table.Header.Cell {root}>Type</Table.Header.Cell>
<Table.Header.Cell {root}>Value</Table.Header.Cell>
</svelte:fragment>
{#each nameservers as nameserver}
<Table.Row>
<Table.Cell>NS</Table.Cell>
<Table.Cell>
<Table.Row.Base {root}>
<Table.Cell {root}>NS</Table.Cell>
<Table.Cell {root}>
<InteractiveText variant="copy" isVisible text={nameserver}>
{nameserver}</InteractiveText>
</Table.Cell>
</Table.Row>
</Table.Row.Base>
{/each}
</Table.Root>
<!-- <Layout.Stack gap="s" direction="row" alignItems="center">
@@ -22,20 +22,20 @@
</Typography.Text>
</Layout.Stack>
<Table.Root>
<svelte:fragment slot="header">
<Table.Header.Cell>Type</Table.Header.Cell>
<Table.Header.Cell>Name</Table.Header.Cell>
<Table.Header.Cell>Value</Table.Header.Cell>
<Table.Root columns={3} let:root>
<svelte:fragment slot="header" let:root>
<Table.Header.Cell {root}>Type</Table.Header.Cell>
<Table.Header.Cell {root}>Name</Table.Header.Cell>
<Table.Header.Cell {root}>Value</Table.Header.Cell>
</svelte:fragment>
<Table.Row>
<Table.Cell>CNAME</Table.Cell>
<Table.Cell>{domain?.domain}</Table.Cell>
<Table.Cell>
<Table.Row.Base {root}>
<Table.Cell {root}>CNAME</Table.Cell>
<Table.Cell {root}>{domain?.domain}</Table.Cell>
<Table.Cell {root}>
<Copy value={globalThis?.location?.origin}
>{globalThis?.location?.origin}</Copy>
</Table.Cell>
</Table.Row>
</Table.Row.Base>
</Table.Root>
<Layout.Stack gap="s" direction="row" alignItems="center">
<Icon icon={IconInfo} size="s" color="--fgcolor-neutral-secondary" />
@@ -67,16 +67,16 @@
</Layout.Stack>
{#if requestTab === 'parameters'}
{#if parameters?.length}
<Table.Root>
<svelte:fragment slot="header">
<Table.Header.Cell>Key</Table.Header.Cell>
<Table.Header.Cell>Value</Table.Header.Cell>
<Table.Root columns={2} let:root>
<svelte:fragment slot="header" let:root>
<Table.Header.Cell {root}>Key</Table.Header.Cell>
<Table.Header.Cell {root}>Value</Table.Header.Cell>
</svelte:fragment>
{#each parameters as parameter}
<Table.Row>
<Table.Cell>{parameter.name}</Table.Cell>
<Table.Cell>{parameter.value}</Table.Cell>
</Table.Row>
<Table.Row.Base {root}>
<Table.Cell {root}>{parameter.name}</Table.Cell>
<Table.Cell {root}>{parameter.value}</Table.Cell>
</Table.Row.Base>
{/each}
</Table.Root>
{:else}
@@ -86,16 +86,16 @@
{/if}
{:else if requestTab === 'headers'}
{#if selectedLog.requestHeaders?.length}
<Table.Root>
<svelte:fragment slot="header">
<Table.Header.Cell>Key</Table.Header.Cell>
<Table.Header.Cell>Value</Table.Header.Cell>
<Table.Root columns={2} let:root>
<svelte:fragment slot="header" let:root>
<Table.Header.Cell {root}>Key</Table.Header.Cell>
<Table.Header.Cell {root}>Value</Table.Header.Cell>
</svelte:fragment>
{#each selectedLog.requestHeaders as request}
<Table.Row>
<Table.Cell>{request.name}</Table.Cell>
<Table.Cell>{request.value}</Table.Cell>
</Table.Row>
<Table.Row.Base {root}>
<Table.Cell {root}>{request.name}</Table.Cell>
<Table.Cell {root}>{request.value}</Table.Cell>
</Table.Row.Base>
{/each}
</Table.Root>
@@ -81,16 +81,16 @@
{/if}
{:else if responseTab === 'headers'}
{#if selectedLog.responseHeaders?.length}
<Table.Root>
<svelte:fragment slot="header">
<Table.Header.Cell>Key</Table.Header.Cell>
<Table.Header.Cell>Value</Table.Header.Cell>
<Table.Root columns={2} let:root>
<svelte:fragment slot="header" let:root>
<Table.Header.Cell {root}>Key</Table.Header.Cell>
<Table.Header.Cell {root}>Value</Table.Header.Cell>
</svelte:fragment>
{#each selectedLog.responseHeaders as request}
<Table.Row>
<Table.Cell>{request.name}</Table.Cell>
<Table.Cell>{request.value}</Table.Cell>
</Table.Row>
<Table.Row.Base {root}>
<Table.Cell {root}>{request.name}</Table.Cell>
<Table.Cell {root}>{request.value}</Table.Cell>
</Table.Row.Base>
{/each}
</Table.Root>
@@ -2,12 +2,11 @@ import type { Column } from '$lib/helpers/types';
import { writable } from 'svelte/store';
export const columns = writable<Column[]>([
{ id: '$id', title: 'Execution ID', type: 'string', show: true, width: 150 },
{ id: '$id', title: 'Execution ID', type: 'string', width: 150 },
{
id: 'requestPath',
title: 'Path',
type: 'string',
show: true,
width: 90,
format: 'string'
},
@@ -16,7 +15,7 @@ export const columns = writable<Column[]>([
id: 'trigger',
title: 'Trigger',
type: 'string',
show: false,
hide: true,
width: 90,
array: true,
format: 'enum',
@@ -24,25 +23,23 @@ export const columns = writable<Column[]>([
{ value: 'http', label: 'HTTP' },
{ value: 'schedule', label: 'Schedule' },
{ value: 'event', label: 'Event' }
],
hide: true
]
},
{
id: 'requestMethod',
title: 'Method',
type: 'string',
show: false,
hide: true,
width: 70,
array: true,
format: 'enum',
elements: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE'],
hide: true
elements: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE']
},
{
id: 'responseStatusCode',
title: 'Status code',
type: 'integer',
show: false,
hide: true,
width: 100,
format: 'integer',
elements: [
@@ -62,15 +59,13 @@ export const columns = writable<Column[]>([
value: 599,
label: 'Server error (500-599)'
}
],
hide: true
]
},
{
id: 'status',
title: 'Status',
type: 'enum',
show: true,
width: 130,
array: true,
format: 'enum',
@@ -80,7 +75,6 @@ export const columns = writable<Column[]>([
id: 'duration',
title: 'Duration',
type: 'integer',
show: true,
width: 80,
format: 'integer',
elements: [
@@ -102,7 +96,6 @@ export const columns = writable<Column[]>([
id: '$createdAt',
title: 'Created',
type: 'datetime',
show: true,
width: 120,
format: 'datetime',
elements: [
@@ -16,81 +16,68 @@
let selectedLogId: string = null;
</script>
<Table.Root>
<svelte:fragment slot="header">
{#each columns as column}
{#if column.show && !column.hide}
<Table.Header.Cell width={column.width + 'px'}>{column.title}</Table.Header.Cell>
{/if}
<Table.Root {columns} let:root>
<svelte:fragment slot="header" let:root>
{#each columns as { id, title }}
<Table.Header.Cell column={id} {root}>{title}</Table.Header.Cell>
{/each}
</svelte:fragment>
{#each logs.executions as log}
<Table.Button
<Table.Row.Button
{root}
on:click={() => {
openSheet = true;
selectedLogId = log.$id;
}}>
{#each columns as column}
{#if column.show && !column.hide}
<Table.Cell column={column.id} {root}>
{#if column.id === '$id'}
{#key column.id}
<Table.Cell>
<Id value={log.$id}>{log.$id}</Id>
</Table.Cell>
<Id value={log.$id}>{log.$id}</Id>
{/key}
{:else if column.id === '$createdAt'}
<Table.Cell>
{capitalize(timeFromNow(log.$createdAt))}
</Table.Cell>
{capitalize(timeFromNow(log.$createdAt))}
{:else if column.id === 'requestPath'}
<Table.Cell>
<Layout.Stack direction="row" alignItems="center" gap="s">
<Badge
variant="secondary"
type={log.responseStatusCode >= 400
? 'error'
: log.responseStatusCode === 0
? undefined
: 'success'}
content={log.responseStatusCode.toString()} />
<Typography.Code size="m">
{log.requestMethod}
</Typography.Code>
<Typography.Code size="m">
{log.requestPath}
</Typography.Code>
</Layout.Stack>
</Table.Cell>
<Layout.Stack direction="row" alignItems="center" gap="s">
<Badge
variant="secondary"
type={log.responseStatusCode >= 400
? 'error'
: log.responseStatusCode === 0
? undefined
: 'success'}
content={log.responseStatusCode.toString()} />
<Typography.Code size="m">
{log.requestMethod}
</Typography.Code>
<Typography.Code size="m">
{log.requestPath}
</Typography.Code>
</Layout.Stack>
{:else if column.id === 'responseStatusCode'}
<Table.Cell>
{log.responseStatusCode}
</Table.Cell>
{log.responseStatusCode}
{:else if column.id === 'status'}
<Table.Cell>
{@const status = log.status}
<Tooltip
disabled={!log?.scheduledAt || status !== 'scheduled'}
maxWidth="400px">
<div>
<Status
status={logStatusConverter(status)}
label={capitalize(status)}>
{status}
</Status>
</div>
<span slot="tooltip">
{`Scheduled to execute on ${toLocaleDateTime(log.scheduledAt)}`}
</span>
</Tooltip>
</Table.Cell>
{@const status = log.status}
<Tooltip
disabled={!log?.scheduledAt || status !== 'scheduled'}
maxWidth="400px">
<div>
<Status
status={logStatusConverter(status)}
label={capitalize(status)}>
{status}
</Status>
</div>
<span slot="tooltip">
{`Scheduled to execute on ${toLocaleDateTime(log.scheduledAt)}`}
</span>
</Tooltip>
{:else if column.id === 'duration'}
<Table.Cell>
{formatTimeDetailed(log.duration)}
</Table.Cell>
{formatTimeDetailed(log.duration)}
{/if}
{/if}
</Table.Cell>
{/each}
</Table.Button>
</Table.Row.Button>
{/each}
</Table.Root>
@@ -92,10 +92,10 @@
Set the events that will trigger your function. Maximum 100 events allowed.
<svelte:fragment slot="aside">
{#if $eventSet.size}
<Table.Root>
<Table.Root columns={1} let:root>
{#each Array.from($eventSet) as event}
<Table.Row>
<Table.Cell>
<Table.Row.Base {root}>
<Table.Cell {root}>
<Layout.Stack
direction="row"
justifyContent="space-between"
@@ -112,7 +112,7 @@
</Button>
</Layout.Stack>
</Table.Cell>
</Table.Row>
</Table.Row.Base>
{/each}
</Table.Root>
<div>
@@ -26,12 +26,11 @@ export const repositories: Writable<{
export const showCreateDeployment: Writable<boolean> = writable(false);
export const columns = writable<Column[]>([
{ id: '$id', title: 'Deployment ID', type: 'string', show: true, width: 150 },
{ id: '$id', title: 'Deployment ID', type: 'string', width: 150 },
{
id: 'status',
title: 'Status',
type: 'enum',
show: true,
width: 110,
array: true,
format: 'enum',
@@ -42,7 +41,6 @@ export const columns = writable<Column[]>([
id: 'buildDuration',
title: 'Build duration',
type: 'integer',
show: true,
width: 90,
elements: [
{
@@ -64,7 +62,6 @@ export const columns = writable<Column[]>([
id: 'sourceSize',
title: 'Source size',
type: 'integer',
show: true,
width: 140,
elements: [
{
@@ -85,7 +82,7 @@ export const columns = writable<Column[]>([
id: 'buildSize',
title: 'Build size',
type: 'integer',
show: false,
hide: true,
filter: false,
width: 80
},
@@ -93,7 +90,6 @@ export const columns = writable<Column[]>([
id: 'type',
title: 'Source',
type: 'string',
show: true,
width: 90,
array: true,
format: 'enum',
@@ -107,7 +103,6 @@ export const columns = writable<Column[]>([
id: '$updatedAt',
title: 'Updated',
type: 'datetime',
show: true,
width: 150,
format: 'datetime'
}
@@ -46,140 +46,118 @@
}
</script>
<Table.Root>
<svelte:fragment slot="header">
{#each columns as column}
{#if column.show}
<Table.Header.Cell width={column.width.toString()}>
{column.title}
</Table.Header.Cell>
{/if}
<Table.Root columns={[...columns, { id: 'actions', width: 40 }]} let:root>
<svelte:fragment slot="header" let:root>
{#each columns as { id, title }}
<Table.Header.Cell column={id} {root}>
{title}
</Table.Header.Cell>
{/each}
<Table.Header.Cell width="40" />
<Table.Header.Cell column="actions" {root} />
</svelte:fragment>
{#each data.deploymentList.deployments as deployment (deployment.$id)}
<Table.Link
<Table.Row.Link
{root}
href={`${base}/project-${$page.params.project}/functions/function-${$page.params.function}/deployment-${deployment.$id}`}>
{#each columns as column}
{#if column.show}
<Table.Cell column={column.id} {root}>
{#if column.id === '$id'}
{#key column.id}
<Table.Cell>
<Id value={deployment.$id}>{deployment.$id}</Id>
</Table.Cell>
<Id value={deployment.$id}>{deployment.$id}</Id>
{/key}
{:else if column.id === 'status'}
<Table.Cell>
{@const status = deployment.status}
{@const status = deployment.status}
{#if data?.activeDeployment?.$id === deployment?.$id}
<Status status="complete" label="Active" />
{:else}
<Status
status={deploymentStatusConverter(status)}
label={capitalize(status)} />
{/if}
</Table.Cell>
{#if data?.activeDeployment?.$id === deployment?.$id}
<Status status="complete" label="Active" />
{:else}
<Status
status={deploymentStatusConverter(status)}
label={capitalize(status)} />
{/if}
{:else if column.id === 'type'}
<Table.Cell>
<DeploymentSource {deployment} />
</Table.Cell>
<DeploymentSource {deployment} />
{:else if column.id === '$updatedAt'}
<Table.Cell>
<DeploymentCreatedBy {deployment} />
</Table.Cell>
<DeploymentCreatedBy {deployment} />
{:else if column.id === 'buildDuration'}
<Table.Cell>
{#if ['waiting'].includes(deployment.status)}
-
{:else if ['processing', 'building'].includes(deployment.status)}
<span use:timer={{ start: deployment.$createdAt }} />
{:else}
{formatTimeDetailed(deployment.buildDuration)}
{/if}
</Table.Cell>
{#if ['waiting'].includes(deployment.status)}
-
{:else if ['processing', 'building'].includes(deployment.status)}
<span use:timer={{ start: deployment.$createdAt }} />
{:else}
{formatTimeDetailed(deployment.buildDuration)}
{/if}
{:else if column.id === 'sourceSize'}
<Table.Cell>
{calculateSize(deployment.sourceSize)}
</Table.Cell>
{calculateSize(deployment.sourceSize)}
{:else if column.id === 'buildSize'}
<Table.Cell>
{calculateSize(deployment.buildSize)}
</Table.Cell>
{calculateSize(deployment.buildSize)}
{/if}
{/if}
</Table.Cell>
{/each}
<Table.Cell>
<Layout.Stack
direction="row"
gap="s"
alignItems="center"
justifyContent="flex-end"
inline>
<Menu>
<Button text icon size="s">
<Icon size="s" icon={IconDotsHorizontal} />
</Button>
<Table.Cell column="actions" {root}>
<Menu>
<Button text icon size="s">
<Icon size="s" icon={IconDotsHorizontal} />
</Button>
<svelte:fragment slot="menu" let:toggle>
<ActionMenu.Root>
<svelte:fragment slot="menu" let:toggle>
<ActionMenu.Root>
<ActionMenu.Item.Button
trailingIcon={IconRefresh}
on:click={() => {
selectedDeployment = deployment;
showRedeploy = true;
toggle();
trackEvent(Click.FunctionsRedeployClick);
}}>
Redeploy
</ActionMenu.Item.Button>
{#if deployment.status === 'ready' && deployment.$id !== $func.deploymentId}
<ActionMenu.Item.Button
trailingIcon={IconRefresh}
trailingIcon={IconLightningBolt}
on:click={() => {
selectedDeployment = deployment;
showRedeploy = true;
showActivate = true;
toggle();
trackEvent(Click.FunctionsRedeployClick);
}}>
Redeploy
Activate
</ActionMenu.Item.Button>
{#if deployment.status === 'ready' && deployment.$id !== $func.deploymentId}
<ActionMenu.Item.Button
trailingIcon={IconLightningBolt}
on:click={() => {
selectedDeployment = deployment;
showActivate = true;
toggle();
}}>
Activate
</ActionMenu.Item.Button>
{/if}
{/if}
<DownloadActionMenuItem {deployment} {toggle} />
<DownloadActionMenuItem {deployment} {toggle} />
{#if deployment.status === 'processing' || deployment.status === 'building' || deployment.status === 'waiting'}
<ActionMenu.Item.Button
trailingIcon={IconXCircle}
on:click={() => {
selectedDeployment = deployment;
toggle();
{#if deployment.status === 'processing' || deployment.status === 'building' || deployment.status === 'waiting'}
<ActionMenu.Item.Button
trailingIcon={IconXCircle}
on:click={() => {
selectedDeployment = deployment;
toggle();
showCancel = true;
trackEvent(Click.FunctionsDeploymentCancelClick);
}}>
Cancel
</ActionMenu.Item.Button>
{/if}
{#if deployment.status !== 'building' && deployment.status !== 'processing' && deployment.status !== 'waiting'}
<ActionMenu.Item.Button
trailingIcon={IconTrash}
status="danger"
on:click={() => {
selectedDeployment = deployment;
toggle();
showCancel = true;
trackEvent(Click.FunctionsDeploymentCancelClick);
}}>
Cancel
</ActionMenu.Item.Button>
{/if}
{#if deployment.status !== 'building' && deployment.status !== 'processing' && deployment.status !== 'waiting'}
<ActionMenu.Item.Button
trailingIcon={IconTrash}
status="danger"
on:click={() => {
selectedDeployment = deployment;
toggle();
showDelete = true;
trackEvent(Click.FunctionsDeploymentDeleteClick);
}}>
Delete
</ActionMenu.Item.Button>
{/if}
</ActionMenu.Root>
</svelte:fragment>
</Menu>
</Layout.Stack>
showDelete = true;
trackEvent(Click.FunctionsDeploymentDeleteClick);
}}>
Delete
</ActionMenu.Item.Button>
{/if}
</ActionMenu.Root>
</svelte:fragment>
</Menu>
</Table.Cell>
</Table.Link>
</Table.Row.Link>
{/each}
</Table.Root>
@@ -17,7 +17,6 @@
import { toLocaleDateTime } from '$lib/helpers/date';
import { Container } from '$lib/layout';
import { MessagingProviderType } from '@appwrite.io/console';
import type { PageData } from './$types';
import CreateMessageDropdown from './createMessageDropdown.svelte';
import FailedModal from './failedModal.svelte';
import MessageStatusPill from './messageStatusPill.svelte';
@@ -31,23 +30,30 @@
import type { Column } from '$lib/helpers/types';
import { writable } from 'svelte/store';
import { canWriteMessages } from '$lib/stores/roles';
import { Layout, Selector, Table } from '@appwrite.io/pink-svelte';
import { Layout, Table } from '@appwrite.io/pink-svelte';
export let data;
let selected: string[] = [];
let showDelete = false;
let deleting = false;
let showFailed = false;
let errors: string[] = [];
let showCreateDropdownMobile = false;
let showCreateDropdownEmpty = false;
const columns = writable<Column[]>([
{ id: '$id', title: 'Message ID', type: 'string', show: true, width: 140 },
{ id: 'message', title: 'Message', type: 'string', show: false, filter: false, width: 140 },
{ id: 'providerType', title: 'Type', type: 'string', show: true, width: 100 },
{ id: 'status', title: 'Status', type: 'string', show: true, width: 120 },
{ id: 'scheduledAt', title: 'Scheduled at', type: 'datetime', show: true, width: 120 },
{ id: 'deliveredAt', title: 'Delivered at', type: 'datetime', show: false, width: 120 }
{ id: '$id', title: 'Message ID', type: 'string', width: 140 },
{
id: 'message',
title: 'Message',
type: 'string',
hide: true,
filter: false,
width: { min: 140 }
},
{ id: 'providerType', title: 'Type', type: 'string', width: { min: 100 } },
{ id: 'status', title: 'Status', type: 'string', width: { min: 120 } },
{ id: 'scheduledAt', title: 'Scheduled at', type: 'datetime', width: { min: 120 } },
{ id: 'deliveredAt', title: 'Delivered at', type: 'datetime', width: { min: 120 } }
]);
const project = $page.params.project;
@@ -97,78 +103,61 @@
</Layout.Stack>
{#if data.messages.total}
<Table.Root>
<svelte:fragment slot="header">
{#if $canWriteMessages}
<Table.Header.Selector width="40px" />
{/if}
{#each $columns as column}
{#if column.show}
<Table.Cell width={column.width + 'px'}>{column.title}</Table.Cell>
{/if}
<Table.Root
columns={$columns}
allowSelection={$canWriteMessages}
let:root
bind:selectedRows={selected}>
<svelte:fragment slot="header" let:root>
{#each $columns as { id, title }}
<Table.Cell column={id} {root}>{title}</Table.Cell>
{/each}
</svelte:fragment>
{#each data.messages.messages as message (message.$id)}
<Table.Link href={`${base}/project-${project}/messaging/message-${message.$id}`}>
{#if $canWriteMessages}
<Table.Cell>
<Selector.Checkbox
size="s"
disabled={message.status === 'processing'} />
</Table.Cell>
{/if}
<Table.Row.Link
{root}
id={message.$id}
href={`${base}/project-${project}/messaging/message-${message.$id}`}>
{#each $columns as column (column.id)}
{#if column.show}
<Table.Cell column={column.id} {root}>
{#if column.id === '$id'}
{#key $columns}
<Table.Cell>
<Id value={message.$id}>{message.$id}</Id>
</Table.Cell>
<Id value={message.$id}>{message.$id}</Id>
{/key}
{:else if column.id === 'message'}
<Table.Cell>
{#if message.providerType === MessagingProviderType.Push}
{message.data.title}
{:else if message.providerType === MessagingProviderType.Sms}
{message.data.content}
{:else if message.providerType === MessagingProviderType.Email}
{message.data.subject}
{:else}
Invalid provider
{/if}
</Table.Cell>
{#if message.providerType === MessagingProviderType.Push}
{message.data.title}
{:else if message.providerType === MessagingProviderType.Sms}
{message.data.content}
{:else if message.providerType === MessagingProviderType.Email}
{message.data.subject}
{:else}
Invalid provider
{/if}
{:else if column.id === 'providerType'}
<Table.Cell>
<ProviderType type={message.providerType} size="xs" />
</Table.Cell>
<ProviderType type={message.providerType} size="xs" />
{:else if column.id === 'status'}
<Table.Cell>
<MessageStatusPill status={message.status} />
{#if message.status === 'failed'}
<Button
on:click={(e) => {
e.preventDefault();
errors = message.deliveryErrors;
showFailed = true;
}}>Details</Button>
{/if}
</Table.Cell>
<MessageStatusPill status={message.status} />
{#if message.status === 'failed'}
<Button
on:click={(e) => {
e.preventDefault();
errors = message.deliveryErrors;
showFailed = true;
}}>Details</Button>
{/if}
{:else if column.type === 'datetime'}
<Table.Cell>
{#if !message[column.id]}
-
{:else}
{toLocaleDateTime(message[column.id])}
{/if}
</Table.Cell>
{#if !message[column.id]}
-
{:else}
{toLocaleDateTime(message[column.id])}
{/if}
{:else}
<Table.Cell>
{message[column.id]}
</Table.Cell>
{message[column.id]}
{/if}
{/if}
</Table.Cell>
{/each}
</Table.Link>
</Table.Row.Link>
{/each}
</Table.Root>
@@ -227,9 +216,7 @@
Documentation
</Button>
{#if $canWriteMessages}
<CreateMessageDropdown
bind:showCreateDropdown={showCreateDropdownEmpty}
let:toggle>
<CreateMessageDropdown let:toggle>
<Button secondary on:click={toggle} event="create_message">
<span class="text">Create message</span>
</Button>
@@ -75,18 +75,18 @@
</Card>
{:else}
<Layout.Stack>
<Table.Root>
<svelte:fragment slot="header">
<Table.Header.Cell>Target</Table.Header.Cell>
<Table.Header.Cell width="40px" />
<Table.Root columns={[{ id: 'target' }, { id: 'actions', width: 40 }]} let:root>
<svelte:fragment slot="header" let:root>
<Table.Header.Cell column="target" {root}>Target</Table.Header.Cell>
<Table.Header.Cell column="actions" {root} />
</svelte:fragment>
{#each Object.entries($topicsById) as [topicId, topic] (topicId)}
<Table.Row>
<Table.Cell>
<Table.Row.Base {root}>
<Table.Cell column="target" {root}>
{topic.name}
({getTotal(topic)} targets)
</Table.Cell>
<Table.Cell>
<Table.Cell column="actions" {root}>
<div class="u-flex u-main-end">
<button
class="button is-text is-only-icon"
@@ -97,15 +97,15 @@
</button>
</div>
</Table.Cell>
</Table.Row>
</Table.Row.Base>
{/each}
{#each Object.entries($targetsById) as [targetId, target] (targetId)}
<Table.Row>
<Table.Cell>
<Table.Row.Base {root}>
<Table.Cell column="target" {root}>
{target.name ? target.name : target.identifier}
</Table.Cell>
<Table.Cell>
<Table.Cell column="actions" {root}>
<div class="u-flex u-main-end">
<button
class="button is-text is-only-icon"
@@ -116,7 +116,7 @@
</button>
</div>
</Table.Cell>
</Table.Row>
</Table.Row.Base>
{/each}
</Table.Root>
<div>
@@ -159,50 +159,46 @@
{/if}
<div class="u-flex u-flex-vertical u-gap-24">
<Table.Root>
<svelte:fragment slot="header">
<Table.Header.Cell>Target</Table.Header.Cell>
{#if recipientsAvailable}
<Table.Header.Cell>Identifier</Table.Header.Cell>
{/if}
<Table.Header.Cell width="40px" />
<Table.Root
let:root
columns={[
{ id: 'target' },
{ id: 'identifier', hide: !recipientsAvailable },
{ id: 'actions', width: 40 }
]}>
<svelte:fragment slot="header" let:root>
<Table.Header.Cell column="target" {root}>Target</Table.Header.Cell>
<Table.Header.Cell column="identifier" {root}
>Identifier</Table.Header.Cell>
<Table.Header.Cell column="actions" {root} />
</svelte:fragment>
{#each dataSource.slice(offset, offset + limit) as source (source['$id'])}
<Table.Row>
<Table.Cell>
<Table.Row.Base {root}>
<Table.Cell column="target" {root}>
{#if source['providerType'] === MessagingProviderType.Push}
{source['name']}
{:else}
{source['identifier']}
{/if}
</Table.Cell>
{#if recipientsAvailable}
<Table.Cell>
{source['name']}
</Table.Cell>
{/if}
<Table.Cell>
<Table.Cell column="identifier" {root}>
{source['name']}
</Table.Cell>
<Table.Cell column="actions" {root}>
{#if isDraft}
<div
class="u-flex u-main-end"
style="--p-button-size: 1.25rem">
<Button
text
class="is-only-icon"
ariaLabel="delete"
disabled={!isDraft}
on:click={() => removeTarget(source['$id'])}>
<span
class="icon-x u-font-size-20"
aria-hidden="true" />
</Button>
</div>
<Button
text
class="is-only-icon"
ariaLabel="delete"
disabled={!isDraft}
on:click={() => removeTarget(source['$id'])}>
<span
class="icon-x u-font-size-20"
aria-hidden="true" />
</Button>
{/if}
</Table.Cell>
</Table.Row>
</Table.Row.Base>
{/each}
</Table.Root>
{#if sum > limit}
@@ -94,46 +94,42 @@
</Button>
{/if}
</Layout.Stack>
<div class="u-flex u-flex-vertical u-gap-24">
<Table.Root>
<svelte:fragment slot="header">
<Table.Header.Cell>Topic</Table.Header.Cell>
<Table.Header.Cell width="40px" />
</svelte:fragment>
{#each topics.slice(offset, offset + limit) as topic (topic.$id)}
<Table.Row>
<Table.Cell>
{topic.name}
({getTotal(topic)} targets)
</Table.Cell>
<Table.Cell>
{#if message.status === 'draft'}
<div
class="u-flex u-main-end"
style="--p-button-size: 1.25rem">
<Button
text
class="is-only-icon"
ariaLabel="delete"
disabled={message.status != 'draft'}
on:click={() => removeTopic(topic.$id)}>
<span
class="icon-x u-font-size-20"
aria-hidden="true" />
</Button>
</div>
{/if}
</Table.Cell>
</Table.Row>
{/each}
</Table.Root>
{#if sum > limit}
<div class="u-flex u-main-space-between">
<p class="text">Total topics: {sum}</p>
<PaginationInline {sum} {limit} bind:offset />
</div>
{/if}
</div>
<Table.Root columns={[{ id: 'topic' }, { id: 'actions', width: 40 }]} let:root>
<svelte:fragment slot="header" let:root>
<Table.Header.Cell column="topic" {root}>Topic</Table.Header.Cell>
<Table.Header.Cell column="actions" {root} />
</svelte:fragment>
{#each topics.slice(offset, offset + limit) as topic (topic.$id)}
<Table.Row.Base {root}>
<Table.Cell column="topic" {root}>
{topic.name}
({getTotal(topic)} targets)
</Table.Cell>
<Table.Cell column="actions" {root}>
{#if message.status === 'draft'}
<div class="u-flex u-main-end" style="--p-button-size: 1.25rem">
<Button
text
class="is-only-icon"
ariaLabel="delete"
disabled={message.status != 'draft'}
on:click={() => removeTopic(topic.$id)}>
<span
class="icon-x u-font-size-20"
aria-hidden="true" />
</Button>
</div>
{/if}
</Table.Cell>
</Table.Row.Base>
{/each}
</Table.Root>
{#if sum > limit}
<div class="u-flex u-main-space-between">
<p class="text">Total topics: {sum}</p>
<PaginationInline {sum} {limit} bind:offset />
</div>
{/if}
{:else if message.status == 'draft'}
<Empty on:click={() => (showTopics = true)}>Add a topic</Empty>
{:else}
@@ -7,11 +7,11 @@ import type { ComponentType } from 'svelte';
import { IconAnnotation, IconDeviceMobile, IconMail } from '@appwrite.io/pink-icons-svelte';
export const columns = writable<Column[]>([
{ id: '$id', title: 'Provider ID', type: 'string', show: true },
{ id: 'name', title: 'Name', type: 'string', show: true },
{ id: 'provider', title: 'Provider', type: 'string', show: true },
{ id: 'type', title: 'Type', type: 'string', show: true },
{ id: 'enabled', title: 'Status', type: 'boolean', show: true }
{ id: '$id', title: 'Provider ID', type: 'string' },
{ id: 'name', title: 'Name', type: 'string' },
{ id: 'provider', title: 'Provider', type: 'string' },
{ id: 'type', title: 'Type', type: 'string' },
{ id: 'enabled', title: 'Status', type: 'boolean' }
]);
export type ProviderInput = {
@@ -53,64 +53,51 @@
}
</script>
<Table.Root>
<svelte:fragment slot="header">
{#if $canWriteProviders}
<Table.Header.Selector width="40px" />
{/if}
{#each $columns as column}
{#if column.show}
<Table.Header.Cell width={column.width}>{column.title}</Table.Header.Cell>
{/if}
<Table.Root
columns={$columns}
allowSelection={$canWriteProviders}
let:root
bind:selectedRows={selectedIds}>
<svelte:fragment slot="header" let:root>
{#each $columns as { id, title }}
<Table.Header.Cell column={id} {root}>{title}</Table.Header.Cell>
{/each}
</svelte:fragment>
{#each data.providers.providers as provider (provider.$id)}
<Table.Link
<svelte:component
this={$canWriteProviders ? Table.Row.Link : Table.Row.Base}
id={provider.$id}
href={$canWriteProviders
? `${base}/project-${$project.$id}/messaging/providers/provider-${provider.$id}`
: '#'}>
{#if $canWriteProviders}
<Table.Cell>
<Selector.Checkbox size="s" id={provider.$id} />
</Table.Cell>
{/if}
: undefined}
{root}>
{#each $columns as column}
{#if column.show}
<Table.Cell column={column.id} {root}>
{#if column.id === '$id'}
{#key $columns}
<Table.Cell>
<Id value={provider.$id}>{provider.$id}</Id>
</Table.Cell>
<Id value={provider.$id}>{provider.$id}</Id>
{/key}
{:else if column.id === 'provider'}
<Table.Cell>
<Provider provider={provider.provider} />
</Table.Cell>
<Provider provider={provider.provider} />
{:else if column.id === 'type'}
<Table.Cell>
<ProviderType type={provider.type} size="xs" />
</Table.Cell>
<ProviderType type={provider.type} size="xs" />
{:else if column.id === 'enabled'}
<Table.Cell>
<Badge
variant="secondary"
type={provider.enabled ? 'success' : undefined}
content={provider.enabled ? 'enabled' : 'disabled'}>
<svelte:fragment slot="start">
{#if provider.enabled}
<Icon icon={IconCheckCircle} size="s" />
{/if}
</svelte:fragment>
</Badge>
</Table.Cell>
<Badge
variant="secondary"
type={provider.enabled ? 'success' : undefined}
content={provider.enabled ? 'enabled' : 'disabled'}>
<svelte:fragment slot="start">
{#if provider.enabled}
<Icon icon={IconCheckCircle} size="s" />
{/if}
</svelte:fragment>
</Badge>
{:else}
<Table.Cell>
{provider[column.id]}
</Table.Cell>
{provider[column.id]}
{/if}
{/if}
</Table.Cell>
{/each}
</Table.Link>
</svelte:component>
{/each}
</Table.Root>
@@ -30,20 +30,19 @@
const project = $page.params.project;
const columns = writable<Column[]>([
{ id: '$id', title: 'Topic ID', type: 'string', show: true, width: 140 },
{ id: 'name', title: 'Name', type: 'string', show: true, width: 140 },
{ id: 'emailTotal', title: 'Email Subscribers', type: 'integer', show: false, width: 140 },
{ id: 'smsTotal', title: 'SMS Subscribers', type: 'integer', show: false, width: 140 },
{ id: 'pushTotal', title: 'Push Subscribers', type: 'integer', show: false, width: 140 },
{ id: '$id', title: 'Topic ID', type: 'string', width: 140 },
{ id: 'name', title: 'Name', type: 'string', width: 140 },
{ id: 'emailTotal', title: 'Email Subscribers', type: 'integer', hide: true, width: 140 },
{ id: 'smsTotal', title: 'SMS Subscribers', type: 'integer', hide: true, width: 140 },
{ id: 'pushTotal', title: 'Push Subscribers', type: 'integer', hide: true, width: 140 },
{
id: 'total',
title: 'Subscribers',
type: 'integer',
show: true,
filter: false,
width: 140
},
{ id: '$createdAt', title: 'Created', type: 'datetime', show: true, width: 140 }
{ id: '$createdAt', title: 'Created', type: 'datetime', width: 140 }
]);
const topicCreated = async (event: CustomEvent<Models.Team<Record<string, unknown>>>) => {
@@ -58,7 +57,7 @@
</Layout.Stack>
<Layout.Stack direction="row" alignItems="center" justifyContent="flex-end">
<Filters query={data.query} {columns} analyticsSource="messaging_topics_filter" />
<ViewSelector view={View.Table} {columns} hideView allowNoColumns showColsTextMobile />
<ViewSelector view={View.Table} {columns} hideView allowNoColumns />
{#if $canWriteTopics}
<Button
on:click={() => {
@@ -12,7 +12,7 @@
import type { Column } from '$lib/helpers/types';
import { page } from '$app/stores';
import { canWriteTopics } from '$lib/stores/roles';
import { Selector, Table } from '@appwrite.io/pink-svelte';
import { Table } from '@appwrite.io/pink-svelte';
export let columns: Column[];
export let data: PageData;
@@ -49,53 +49,33 @@
}
</script>
<Table.Root>
<svelte:fragment slot="header">
{#if $canWriteTopics}
<Table.Header.Selector width="40px" />
{/if}
{#each columns as column}
{#if column.show}
<Table.Header.Cell width={column.width + 'px'}>{column.title}</Table.Header.Cell>
{/if}
<Table.Root {columns} allowSelection={$canWriteTopics} let:root bind:selectedRows={selectedIds}>
<svelte:fragment slot="header" let:root>
{#each columns as { id, title }}
<Table.Header.Cell column={id} {root}>{title}</Table.Header.Cell>
{/each}
</svelte:fragment>
{#each data.topics.topics as topic (topic.$id)}
<Table.Link
<Table.Row.Link
{root}
id={topic.$id}
href={`${base}/project-${$page.params.project}/messaging/topics/topic-${topic.$id}`}>
{#if $canWriteTopics}
<Table.Cell>
<Selector.Checkbox size="s" />
</Table.Cell>
{/if}
{#each columns as column (column.id)}
{#if column.show}
<Table.Cell column={column.id} {root}>
{#if column.id === '$id'}
{#key column.id}
<Table.Cell>
<Id value={topic.$id}>{topic.$id}</Id>
</Table.Cell>
<Id value={topic.$id}>{topic.$id}</Id>
{/key}
{:else if column.type === 'datetime'}
<Table.Cell>
{#if !topic[column.id]}
-
{:else}
{toLocaleDateTime(topic[column.id])}
{/if}
</Table.Cell>
{topic[column.id] ? toLocaleDateTime(topic[column.id]) : '-'}
{:else if column.id === 'total'}
<Table.Cell>
{topic.emailTotal + topic.smsTotal + topic.pushTotal}
</Table.Cell>
{topic.emailTotal + topic.smsTotal + topic.pushTotal}
{:else}
<Table.Cell>
{topic[column.id]}
</Table.Cell>
{topic[column.id]}
{/if}
{/if}
</Table.Cell>
{/each}
</Table.Link>
</Table.Row.Link>
{/each}
</Table.Root>
@@ -33,12 +33,12 @@
let showAdd = false;
let subscribersByTargetId: Record<string, Models.Subscriber> = {};
const columns = writable<Column[]>([
{ id: '$id', title: 'Subscriber ID', type: 'string', show: true, width: 140 },
{ id: 'userName', title: 'Name', type: 'string', show: true, filter: false, width: 100 },
{ id: 'targetId', title: 'Target ID', type: 'string', show: true, width: 140 },
{ id: 'target', title: 'Target', type: 'string', show: true, filter: false, width: 140 },
{ id: 'type', title: 'Type', type: 'string', show: true, width: 80 },
{ id: '$createdAt', title: 'Created', type: 'datetime', show: true, width: 100 }
{ id: '$id', title: 'Subscriber ID', type: 'string', width: 140 },
{ id: 'userName', title: 'Name', type: 'string', filter: false, width: 100 },
{ id: 'targetId', title: 'Target ID', type: 'string', width: 140 },
{ id: 'target', title: 'Target', type: 'string', filter: false, width: 140 },
{ id: 'type', title: 'Type', type: 'string', width: 80 },
{ id: '$createdAt', title: 'Created', type: 'datetime', width: 100 }
]);
onMount(() => {
@@ -5,17 +5,6 @@
import { FloatingActionBar, Id, Modal } from '$lib/components';
import { Dependencies } from '$lib/constants';
import { Button } from '$lib/elements/forms';
import {
TableBody,
TableCell,
TableCellCheck,
TableCellHead,
TableCellHeadCheck,
TableCellText,
TableHeader,
TableRowLink,
TableScroll
} from '$lib/elements/table';
import { addNotification } from '$lib/stores/notifications';
import type { PageData } from './$types';
import ProviderType from '../../providerType.svelte';
@@ -26,7 +15,7 @@
import { targetsById } from '../../store';
import { MessagingProviderType, type Models } from '@appwrite.io/console';
import type { Column } from '$lib/helpers/types';
import { Selector, Table } from '@appwrite.io/pink-svelte';
import { Table } from '@appwrite.io/pink-svelte';
export let columns: Column[];
export let data: PageData;
@@ -81,52 +70,46 @@
});
</script>
<Table.Root>
<svelte:fragment slot="header">
<Table.Header.Selector width="40px" />
{#each columns as column}
{#if column.show}
<Table.Header.Cell width={column.width + 'px'}>{column.title}</Table.Header.Cell>
{/if}
<Table.Root {columns} allowSelection let:root bind:selectedRows={selectedIds}>
<svelte:fragment slot="header" let:root>
{#each columns as { id, title }}
<Table.Header.Cell column={id} {root}>{title}</Table.Header.Cell>
{/each}
</svelte:fragment>
{#each data.subscribers.subscribers as subscriber (subscriber.$id)}
{@const target = subscriber.target}
<Table.Link href={`${base}/project-${$project.$id}/auth/user-${subscriber.target.userId}`}>
<Table.Cell>
<Selector.Checkbox size="s" />
</Table.Cell>
<Table.Row.Link
{root}
id={subscriber.$id}
href={`${base}/project-${$project.$id}/auth/user-${subscriber.target.userId}`}>
{#each columns as column}
{#if column.show}
<Table.Cell>
{#if column.id === '$id'}
{#key column.id}
<Id value={subscriber.$id}>
{subscriber.$id}
</Id>
{/key}
{:else if column.id === 'targetId'}
<Id value={subscriber[column.id]}>
{subscriber[column.id]}
<Table.Cell column={column.id} {root}>
{#if column.id === '$id'}
{#key column.id}
<Id value={subscriber.$id}>
{subscriber.$id}
</Id>
{:else if column.id === 'target'}
{#if target.providerType === MessagingProviderType.Push}
{target.name}
{:else}
{target.identifier}
{/if}
{:else if column.id === 'type'}
<ProviderType type={subscriber.target.providerType} size="s" />
{:else if column.id === '$createdAt'}
{toLocaleDateTime(subscriber[column.id])}
{:else}
{/key}
{:else if column.id === 'targetId'}
<Id value={subscriber[column.id]}>
{subscriber[column.id]}
</Id>
{:else if column.id === 'target'}
{#if target.providerType === MessagingProviderType.Push}
{target.name}
{:else}
{target.identifier}
{/if}
</Table.Cell>
{/if}
{:else if column.id === 'type'}
<ProviderType type={subscriber.target.providerType} size="s" />
{:else if column.id === '$createdAt'}
{toLocaleDateTime(subscriber[column.id])}
{:else}
{subscriber[column.id]}
{/if}
</Table.Cell>
{/each}
</Table.Link>
</Table.Row.Link>
{/each}
</Table.Root>
@@ -153,8 +136,6 @@
<Modal
title="Delete subscriber"
icon="exclamation"
state="warning"
bind:show={showDelete}
onSubmit={handleDelete}
closable={!deleting}>
@@ -116,10 +116,10 @@
placeholder="Search for topics"
bind:value={search} />
{#if Object.keys(topicResultsById).length > 0 && !emptyTopicsExists}
<Table.Root>
<Table.Root columns={1} let:root>
{#each Object.entries(topicResultsById) as [topicId, topic]}
<Table.Row>
<Table.Cell>
<Table.Row.Base {root}>
<Table.Cell {root}>
<Layout.Stack direction="row" alignItems="center" gap="s">
<Selector.Checkbox
id={topicId}
@@ -133,7 +133,7 @@
</span>
</Layout.Stack>
</Table.Cell>
</Table.Row>
</Table.Row.Base>
{/each}
</Table.Root>
<div class="u-flex u-main-space-between u-cross-center">
@@ -70,14 +70,14 @@
</Card>
{:else}
<div class="table-wrapper">
<Table.Root>
<svelte:fragment slot="header">
<Table.Header.Cell>Target</Table.Header.Cell>
<Table.Header.Cell width="32px" />
<Table.Root columns={[{ id: 'target' }, { id: 'actions', width: 40 }]} let:root>
<svelte:fragment slot="header" let:root>
<Table.Header.Cell column="target" {root}>Target</Table.Header.Cell>
<Table.Header.Cell column="actions" {root} />
</svelte:fragment>
{#each Object.entries($topicsById) as [topicId, topic] (topicId)}
<Table.Row>
<Table.Cell>
<Table.Row.Base {root}>
<Table.Cell column="target" {root}>
<div class="u-flex u-cross-center">
<span class="title">
<span class="u-line-height-1-5">
@@ -90,7 +90,7 @@
</span></span>
</div>
</Table.Cell>
<Table.Cell>
<Table.Cell column="actions" {root}>
<div class="u-flex u-main-end">
<button
class="button is-text is-only-icon"
@@ -101,19 +101,18 @@
</button>
</div>
</Table.Cell>
</Table.Row>
</Table.Row.Base>
{/each}
{#each Object.entries($targetsById) as [targetId, target] (targetId)}
<Table.Row>
<Table.Cell>
<Table.Row.Base {root}>
<Table.Cell column="target" {root}>
<div class="u-flex u-cross-center">
<span class="text">
{target.name ? target.name : target.identifier}
</span>
</div>
</Table.Cell>
<Table.Cell>
<Table.Cell column="actions" {root}>
<div class="u-flex u-main-end">
<button
class="button is-text is-only-icon"
@@ -124,7 +123,7 @@
</button>
</div>
</Table.Cell>
</Table.Row>
</Table.Row.Base>
{/each}
</Table.Root>
</div>
@@ -16,28 +16,28 @@
</script>
{#if data.keys.total}
<Table.Root>
<svelte:fragment slot="header">
<Table.Header.Cell>Name</Table.Header.Cell>
<Table.Header.Cell>Last accessed</Table.Header.Cell>
<Table.Header.Cell>Expiration date</Table.Header.Cell>
<Table.Header.Cell>Scopes</Table.Header.Cell>
<Table.Root columns={4} let:root>
<svelte:fragment slot="header" let:root>
<Table.Header.Cell {root}>Name</Table.Header.Cell>
<Table.Header.Cell {root}>Last accessed</Table.Header.Cell>
<Table.Header.Cell {root}>Expiration date</Table.Header.Cell>
<Table.Header.Cell {root}>Scopes</Table.Header.Cell>
</svelte:fragment>
{#each data.keys.keys as key}
<Table.Link href={`keys/${key.$id}`}>
<Table.Cell>
<Table.Row.Link href={`keys/${key.$id}`} {root}>
<Table.Cell {root}>
{key.name}
</Table.Cell>
<Table.Cell>
<Table.Cell {root}>
{key.accessedAt ? toLocaleDate(key.accessedAt) : 'never'}
</Table.Cell>
<Table.Cell>
<Table.Cell {root}>
{key.expire ? toLocaleDateTime(key.expire) : 'never'}
</Table.Cell>
<Table.Cell>
<Table.Cell {root}>
{key.scopes.length} Scopes
</Table.Cell>
</Table.Link>
</Table.Row.Link>
{/each}
</Table.Root>
{:else}
@@ -123,27 +123,27 @@
</script>
{#if data.platforms.platforms.length}
<Table.Root>
<svelte:fragment slot="header">
<Table.Header.Cell>Name</Table.Header.Cell>
<Table.Header.Cell>Platform type</Table.Header.Cell>
<Table.Header.Cell>Last updated</Table.Header.Cell>
<Table.Root columns={3} let:root>
<svelte:fragment slot="header" let:root>
<Table.Header.Cell {root}>Name</Table.Header.Cell>
<Table.Header.Cell {root}>Platform type</Table.Header.Cell>
<Table.Header.Cell {root}>Last updated</Table.Header.Cell>
</svelte:fragment>
{#each data.platforms.platforms as platform}
<Table.Link href={`${path}/${platform.$id}`}>
<Table.Cell>
<Table.Row.Link href={`${path}/${platform.$id}`} {root}>
<Table.Cell {root}>
{platform.name}
</Table.Cell>
<Table.Cell>
<Table.Cell {root}>
<Layout.Stack direction="row" gap="s" alignItems="center">
<Icon icon={getPlatformInfo(platform.type)} />
{PlatformTypes[platform.type]}
</Layout.Stack>
</Table.Cell>
<Table.Cell>
<Table.Cell {root}>
{platform.$updatedAt ? toLocaleDate(platform.$updatedAt) : 'never'}
</Table.Cell>
</Table.Link>
</Table.Row.Link>
{/each}
</Table.Root>
{:else}
@@ -152,32 +152,32 @@
</div>
</div>
<Table.Root>
<svelte:fragment slot="header">
<Table.Header.Cell>Date</Table.Header.Cell>
<Table.Header.Cell>Source</Table.Header.Cell>
<Table.Header.Cell>Status</Table.Header.Cell>
<Table.Header.Cell />
<Table.Root columns={4} let:root>
<svelte:fragment slot="header" let:root>
<Table.Header.Cell {root}>Date</Table.Header.Cell>
<Table.Header.Cell {root}>Source</Table.Header.Cell>
<Table.Header.Cell {root}>Status</Table.Header.Cell>
<Table.Header.Cell {root} />
</svelte:fragment>
{#each data.migrations as entry}
<Table.Row>
<Table.Row.Base {root}>
{@const status = getStatus(entry.status)}
<Table.Cell>
<Table.Cell {root}>
{isSameDay(new Date(), new Date(entry.$createdAt))
? 'Today'
: toLocaleDate(entry.$createdAt)}
</Table.Cell>
<Table.Cell>{entry.source}</Table.Cell>
<Table.Cell>
<Table.Cell {root}>{entry.source}</Table.Cell>
<Table.Cell {root}>
<Status label={capitalize(status)} {status} />
</Table.Cell>
<Table.Cell>
<Table.Cell {root}>
<div class="u-flex u-main-end">
<Button secondary on:click={() => showDetails(entry)}
>Details</Button>
</div>
</Table.Cell>
</Table.Row>
</Table.Row.Base>
{/each}
</Table.Root>
{:else}
@@ -94,15 +94,17 @@
</Button>
</Layout.Stack>
<Table.Root>
<svelte:fragment slot="header">
<Table.Header.Cell>Repository</Table.Header.Cell>
<Table.Header.Cell>Updated</Table.Header.Cell>
<Table.Header.Cell width="20px" />
<Table.Root
let:root
columns={[{ id: 'repo' }, { id: 'updated' }, { id: 'actions', width: 20 }]}>
<svelte:fragment slot="header" let:root>
<Table.Header.Cell column="repo" {root}>Repository</Table.Header.Cell>
<Table.Header.Cell column="updated" {root}>Updated</Table.Header.Cell>
<Table.Header.Cell column="actions" {root} />
</svelte:fragment>
{#each installations as installation, i}
<Table.Row>
<Table.Cell>
<Table.Row.Base {root}>
<Table.Cell column="repo" {root}>
<Layout.Stack direction="row" gap="s" alignItems="center">
<Avatar alt={installation.provider} size="xs">
<Icon
@@ -114,10 +116,10 @@
</Link>
</Layout.Stack>
</Table.Cell>
<Table.Cell>
<Table.Cell column="updated" {root}>
{dayjs().to(installation.$updatedAt)}
</Table.Cell>
<Table.Cell>
<Table.Cell column="actions" {root}>
<Popover let:toggle padding="none" placement="bottom-end">
<button
type="button"
@@ -145,7 +147,7 @@
</ActionMenu.Root>
</Popover>
</Table.Cell>
</Table.Row>
</Table.Row.Base>
{/each}
</Table.Root>
{#if total > limit}
@@ -219,20 +219,22 @@
}
]} />
{#if data.usage.executionsBreakdown.length > 0}
<Table.Root>
<svelte:fragment slot="header">
<Table.Header.Cell>Function</Table.Header.Cell>
<Table.Header.Cell>Usage</Table.Header.Cell>
<Table.Root columns={2} let:root>
<svelte:fragment slot="header" let:root>
<Table.Header.Cell {root}>Function</Table.Header.Cell>
<Table.Header.Cell {root}>Usage</Table.Header.Cell>
</svelte:fragment>
{#each data.usage.executionsBreakdown as func}
<Table.Link href={`${baseRoute}/functions/function-${func.resourceId}`}>
<Table.Cell>
<Table.Row.Link
href={`${baseRoute}/functions/function-${func.resourceId}`}
{root}>
<Table.Cell {root}>
{func.name ?? func.resourceId}
</Table.Cell>
<Table.Cell>
<Table.Cell {root}>
{formatNum(func.value)} executions
</Table.Cell>
</Table.Link>
</Table.Row.Link>
{/each}
</Table.Root>
{/if}
@@ -367,24 +369,24 @@
</div>
{#if data.usage.authPhoneCountryBreakdown.length > 0}
<Accordion title="Region breakdown">
<Table.Root>
<svelte:fragment slot="header">
<Table.Header.Cell>Region</Table.Header.Cell>
<Table.Header.Cell>Amount</Table.Header.Cell>
<Table.Header.Cell>Estimated cost</Table.Header.Cell>
<Table.Root columns={3} let:root>
<svelte:fragment slot="header" let:root>
<Table.Header.Cell {root}>Region</Table.Header.Cell>
<Table.Header.Cell {root}>Amount</Table.Header.Cell>
<Table.Header.Cell {root}>Estimated cost</Table.Header.Cell>
</svelte:fragment>
{#each data.usage.authPhoneCountryBreakdown as phone}
<Table.Row>
<Table.Cell>
<Table.Row.Base {root}>
<Table.Cell {root}>
{getCountryName(phone.name)}
</Table.Cell>
<Table.Cell>
<Table.Cell {root}>
{formatNumberWithCommas(phone.value)}
</Table.Cell>
<Table.Cell>
<Table.Cell {root}>
{formatCurrency(phone.estimate)}
</Table.Cell>
</Table.Row>
</Table.Row.Base>
{/each}
</Table.Root>
</Accordion>
@@ -45,63 +45,48 @@
</Layout.Stack>
{#if data.webhooks.total}
<Table.Root>
<svelte:fragment slot="header">
{#each $columns as column}
{#if column.show}
<Table.Header.Cell width={column.width + 'px'}
>{column.title}</Table.Header.Cell>
{/if}
<Table.Root columns={$columns} let:root>
<svelte:fragment slot="header" let:root>
{#each $columns as { id, title }}
<Table.Header.Cell column={id} {root}>{title}</Table.Header.Cell>
{/each}
</svelte:fragment>
{#each data.webhooks.webhooks as webhook}
<Table.Link href={`${base}/project-${projectId}/settings/webhooks/${webhook.$id}`}>
<Table.Row.Link
{root}
href={`${base}/project-${projectId}/settings/webhooks/${webhook.$id}`}>
{#each $columns as column (column.id)}
{#if column.show}
<Table.Cell column={column.id} {root}>
{#if column.id === '$id'}
{#key $columns}
<Table.Cell>
<Id value={webhook.$id}>{webhook.$id}</Id>
</Table.Cell>
<Id value={webhook.$id}>{webhook.$id}</Id>
{/key}
{:else if column.id === 'events'}
<Table.Cell>
{webhook.events.length}
</Table.Cell>
{webhook.events.length}
{:else if column.type === 'datetime'}
<Table.Cell>
{#if !webhook[column.id]}
-
{:else}
{toLocaleDateTime(webhook[column.id])}
{/if}
</Table.Cell>
{webhook[column.id] ? toLocaleDateTime(webhook[column.id]) : '-'}
{:else if column.id === 'enabled'}
<Table.Cell>
<Layout.Stack direction="row" gap="s" alignItems="normal">
<Status
label={webhook.enabled ? 'Enabled' : 'Disabled'}
status={webhook.enabled ? 'complete' : 'failed'} />
<Layout.Stack direction="row" gap="s" alignItems="normal">
<Status
label={webhook.enabled ? 'Enabled' : 'Disabled'}
status={webhook.enabled ? 'complete' : 'failed'} />
{#if webhook.enabled === false}
<Link.Button
variant="muted"
on:click={(e) => {
e.preventDefault();
selectedWebhook = webhook;
showFailed = true;
}}>Details</Link.Button>
{/if}
</Layout.Stack>
</Table.Cell>
{#if webhook.enabled === false}
<Link.Button
variant="muted"
on:click={(e) => {
e.preventDefault();
selectedWebhook = webhook;
showFailed = true;
}}>Details</Link.Button>
{/if}
</Layout.Stack>
{:else}
<Table.Cell>
{webhook[column.id]}
</Table.Cell>
{webhook[column.id]}
{/if}
{/if}
</Table.Cell>
{/each}
</Table.Link>
</Table.Row.Link>
{/each}
</Table.Root>
{:else}
@@ -72,10 +72,10 @@
Set the events that will trigger your webhook. Maximum 100 events allowed.
<svelte:fragment slot="aside">
{#if $eventSet.size}
<Table.Root>
<Table.Root columns={1} let:root>
{#each Array.from($eventSet) as event}
<Table.Row>
<Table.Cell>
<Table.Row.Base {root}>
<Table.Cell {root}>
<Layout.Stack
direction="row"
justifyContent="space-between"
@@ -92,7 +92,7 @@
</Button>
</Layout.Stack>
</Table.Cell>
</Table.Row>
</Table.Row.Base>
{/each}
</Table.Root>
<div>
@@ -20,10 +20,10 @@
<Fieldset legend="Events">
<Layout.Stack gap="s">
{#if events.length}
<Table.Root>
<Table.Root columns={1} let:root>
{#each events as event}
<Table.Row>
<Table.Cell>
<Table.Row.Base {root}>
<Table.Cell {root}>
<Layout.Stack
direction="row"
justifyContent="space-between"
@@ -40,7 +40,7 @@
</Button>
</Layout.Stack>
</Table.Cell>
</Table.Row>
</Table.Row.Base>
{/each}
</Table.Root>
<div>
@@ -1,17 +1,12 @@
// import { page } from '$app/stores';
import type { Column } from '$lib/helpers/types';
// import type { Models } from '@appwrite.io/console';
import { writable } from 'svelte/store';
// export const database = derived(page, ($page) => $page.data.database as Models.Database);
// export const showCreate = writable(false);
export const columns = writable<Column[]>([
{ id: '$id', title: 'Webhook ID', type: 'string', show: false, width: 200 },
{ id: 'name', title: 'Name', type: 'string', show: true, width: 200 },
{ id: 'url', title: 'Post URL', type: 'string', show: true, width: 200 },
{ id: 'events', title: 'Events', type: 'string', show: true, width: 120 },
{ id: 'enabled', title: 'Status', type: 'string', show: true, width: 180 },
{ id: '$createdAt', title: 'Created', type: 'datetime', show: true, width: 200 },
{ id: '$updatedAt', title: 'Updated', type: 'datetime', show: true, width: 200 }
{ id: '$id', title: 'Webhook ID', type: 'string', hide: true, width: 150 },
{ id: 'name', title: 'Name', type: 'string', width: { min: 200 } },
{ id: 'url', title: 'Post URL', type: 'string', width: { min: 200 } },
{ id: 'events', title: 'Events', type: 'string', width: { min: 120 } },
{ id: 'enabled', title: 'Status', type: 'string', width: { min: 180 } },
{ id: '$createdAt', title: 'Created', type: 'datetime', width: { min: 200 } },
{ id: '$updatedAt', title: 'Updated', type: 'datetime', width: { min: 200 } }
]);
@@ -189,16 +189,26 @@
limit={6}
let:paginatedItems
hideFooter={variables.length <= 6}>
<Table.Root>
<svelte:fragment slot="header">
<Table.Header.Cell width="200px">Key</Table.Header.Cell>
<Table.Header.Cell>Value</Table.Header.Cell>
<Table.Header.Cell width="10px"></Table.Header.Cell>
<Table.Root
let:root
columns={[
{ id: 'key', width: 200 },
{ id: 'value' },
{ id: 'actions', width: 40 }
]}>
<svelte:fragment slot="header" let:root>
<Table.Header.Cell column="key" {root}
>Key</Table.Header.Cell>
<Table.Header.Cell column="value" {root}
>Value</Table.Header.Cell>
<Table.Header.Cell column="actions" {root}
></Table.Header.Cell>
</svelte:fragment>
{#each paginatedItems as variable}
<Table.Row>
<Table.Cell>{variable.key}</Table.Cell>
<Table.Cell>
<Table.Row.Base {root}>
<Table.Cell column="key" {root}
>{variable.key}</Table.Cell>
<Table.Cell column="value" {root}>
<!-- TODO: fix max width -->
<div style="max-width: 20rem">
{#if variable.secret}
@@ -220,7 +230,7 @@
{/if}
</div>
</Table.Cell>
<Table.Cell>
<Table.Cell column="actions" {root}>
<div style="margin-inline-start: auto">
<Popover
padding="none"
@@ -282,7 +292,7 @@
</Popover>
</div>
</Table.Cell>
</Table.Row>
</Table.Row.Base>
{/each}
</Table.Root>
</Paginator>
@@ -9,7 +9,6 @@ export const columns = writable<Column[]>([
id: 'status',
title: 'Status',
type: 'enum',
show: true,
width: 110,
array: true,
format: 'enum',
@@ -20,7 +19,6 @@ export const columns = writable<Column[]>([
id: 'buildDuration',
title: 'Build duration',
type: 'integer',
show: true,
width: 90,
elements: [
{
@@ -42,7 +40,7 @@ export const columns = writable<Column[]>([
id: 'sourceSize',
title: 'Source size',
type: 'integer',
show: false,
hide: true,
width: 140,
elements: [
{
@@ -63,7 +61,6 @@ export const columns = writable<Column[]>([
id: 'buildSize',
title: 'Build size',
type: 'integer',
show: true,
filter: false,
width: 80
},
@@ -71,7 +68,6 @@ export const columns = writable<Column[]>([
id: 'type',
title: 'Source',
type: 'string',
show: true,
width: 90,
array: true,
format: 'enum',
@@ -85,7 +81,6 @@ export const columns = writable<Column[]>([
id: '$updatedAt',
title: 'Updated',
type: 'datetime',
show: true,
width: 150,
format: 'datetime'
}
@@ -28,69 +28,54 @@
let selectedDeployment: Models.Deployment = null;
</script>
<Table.Root>
<svelte:fragment slot="header">
{#each $columns as column}
{#if column.show}
<Table.Header.Cell width={column?.width?.toString() ?? ''}>
{column.title}
</Table.Header.Cell>
{/if}
<Table.Root columns={[...$columns, { id: 'actions', width: 40 }]} let:root>
<svelte:fragment slot="header" let:root>
{#each $columns as { id, title }}
<Table.Header.Cell column={id} {root}>
{title}
</Table.Header.Cell>
{/each}
<Table.Header.Cell width="40" />
<Table.Header.Cell column="actions" {root} />
</svelte:fragment>
{#each data.deploymentList.deployments as deployment}
<Table.Link
<Table.Row.Link
{root}
href={`${base}/project-${$page.params.project}/sites/site-${$page.params.site}/deployments/deployment-${deployment.$id}`}>
{#each $columns as column}
{#if column.show}
<Table.Cell column={column.id} {root}>
{#if column.id === '$id'}
{#key column.id}
<Table.Cell width={column?.width?.toString() ?? ''}>
<Id value={deployment.$id}>{deployment.$id}</Id>
</Table.Cell>
<Id value={deployment.$id}>{deployment.$id}</Id>
{/key}
{:else if column.id === 'status'}
<Table.Cell width={column?.width?.toString() ?? ''}>
{@const status = deployment.status}
{#if data?.activeDeployment?.$id === deployment?.$id}
<Status status="complete" label="Active" />
{:else}
<Status
status={deploymentStatusConverter(status)}
label={capitalize(status)} />
{/if}
</Table.Cell>
{@const status = deployment.status}
{#if data?.activeDeployment?.$id === deployment?.$id}
<Status status="complete" label="Active" />
{:else}
<Status
status={deploymentStatusConverter(status)}
label={capitalize(status)} />
{/if}
{:else if column.id === 'type'}
<Table.Cell width={column?.width?.toString() ?? ''}>
<DeploymentSource {deployment} />
</Table.Cell>
<DeploymentSource {deployment} />
{:else if column.id === '$updatedAt'}
<Table.Cell width={column?.width?.toString() ?? ''}>
<DeploymentCreatedBy {deployment} />
</Table.Cell>
<DeploymentCreatedBy {deployment} />
{:else if column.id === 'buildDuration'}
<Table.Cell width={column?.width?.toString() ?? ''}>
{#if ['waiting'].includes(deployment.status)}
-
{:else if ['processing', 'building'].includes(deployment.status)}
<span use:timer={{ start: deployment.$createdAt }} />
{:else}
{formatTimeDetailed(deployment.buildDuration)}
{/if}
</Table.Cell>
{#if ['waiting'].includes(deployment.status)}
-
{:else if ['processing', 'building'].includes(deployment.status)}
<span use:timer={{ start: deployment.$createdAt }} />
{:else}
{formatTimeDetailed(deployment.buildDuration)}
{/if}
{:else if column.id === 'sourceSize'}
<Table.Cell width={column?.width?.toString() ?? ''}>
{calculateSize(deployment.sourceSize)}
</Table.Cell>
{calculateSize(deployment.sourceSize)}
{:else if column.id === 'buildSize'}
<Table.Cell width={column?.width?.toString() ?? ''}>
{calculateSize(deployment.buildSize)}
</Table.Cell>
{calculateSize(deployment.buildSize)}
{/if}
{/if}
</Table.Cell>
{/each}
<Table.Cell>
<Table.Cell column="actions" {root}>
<Layout.Stack alignItems="flex-end">
<DeploymentActionMenu
{deployment}
@@ -102,7 +87,7 @@
activeDeployment={data.site.deploymentId} />
</Layout.Stack>
</Table.Cell>
</Table.Link>
</Table.Row.Link>
{/each}
</Table.Root>
@@ -43,21 +43,22 @@
</Button>
</Layout.Stack>
{#if deploymentList?.total}
<Table.Root>
<svelte:fragment slot="header">
<Table.Header.Cell>Deployment ID</Table.Header.Cell>
<Table.Header.Cell>Status</Table.Header.Cell>
<Table.Header.Cell>Source</Table.Header.Cell>
<Table.Header.Cell>Updated</Table.Header.Cell>
<Table.Header.Cell />
<Table.Root columns={5} let:root>
<svelte:fragment slot="header" let:root>
<Table.Header.Cell {root}>Deployment ID</Table.Header.Cell>
<Table.Header.Cell {root}>Status</Table.Header.Cell>
<Table.Header.Cell {root}>Source</Table.Header.Cell>
<Table.Header.Cell {root}>Updated</Table.Header.Cell>
<Table.Header.Cell {root} />
</svelte:fragment>
{#each deploymentList?.deployments as deployment}
<Table.Link
<Table.Row.Link
{root}
href={`${base}/project-${$page.params.project}/sites/site-${$page.params.site}/deployments/deployment-${deployment.$id}`}>
<Table.Cell>
<Table.Cell {root}>
<Id value={deployment.$id}>{deployment.$id}</Id>
</Table.Cell>
<Table.Cell>
<Table.Cell {root}>
{@const status = deployment.status}
{#if activeDeployment?.$id === deployment?.$id}
<Status status="complete" label="Active" />
@@ -68,13 +69,13 @@
{/if}
</Table.Cell>
<Table.Cell>
<Table.Cell {root}>
<DeploymentSource {deployment} />
</Table.Cell>
<Table.Cell>
<Table.Cell {root}>
<DeploymentCreatedBy {deployment} />
</Table.Cell>
<Table.Cell>
<Table.Cell {root}>
<Layout.Stack alignItems="flex-end">
<DeploymentActionMenu
{deployment}
@@ -86,7 +87,7 @@
activeDeployment={site.deploymentId} />
</Layout.Stack>
</Table.Cell>
</Table.Link>
</Table.Row.Link>
{/each}
</Table.Root>
{:else}
@@ -69,16 +69,16 @@
</Layout.Stack>
{#if data.domains.total}
<Table.Root>
<svelte:fragment slot="header">
<Table.Header.Cell width="200px">Domain</Table.Header.Cell>
<Table.Header.Cell>Redirect to</Table.Header.Cell>
<Table.Header.Cell>Production branch</Table.Header.Cell>
<Table.Header.Cell />
<Table.Root columns={4} let:root>
<svelte:fragment slot="header" let:root>
<Table.Header.Cell {root}>Domain</Table.Header.Cell>
<Table.Header.Cell {root}>Redirect to</Table.Header.Cell>
<Table.Header.Cell {root}>Production branch</Table.Header.Cell>
<Table.Header.Cell {root} />
</svelte:fragment>
{#each data.domains.rules as domain}
<Table.Row>
<Table.Cell>
<Table.Row.Base {root}>
<Table.Cell {root}>
<Link external href={`${$protocol}${domain.domain}`} variant="quiet">
<Layout.Stack direction="row" alignItems="center" gap="xs">
{domain.domain}
@@ -87,14 +87,14 @@
</Link>
</Table.Cell>
<!-- TODO design redirect status code -->
<Table.Cell>
<Table.Cell {root}>
{domain?.redirectUrl || 'No redirect'}
{domain?.redirectStatusCode ? `(${domain.redirectStatusCode})` : ''}
</Table.Cell>
<Table.Cell>
<Table.Cell {root}>
{domain.deploymentVcsProviderBranch || '-'}
</Table.Cell>
<Table.Cell>
<Table.Cell {root}>
<Layout.Stack direction="row" justifyContent="flex-end">
<Popover let:toggle placement="bottom-start" padding="none">
<Button
@@ -140,7 +140,7 @@
</Popover>
</Layout.Stack>
</Table.Cell>
</Table.Row>
</Table.Row.Base>
{/each}
</Table.Root>
@@ -94,23 +94,23 @@
</Typography.Text>
</Layout.Stack>
<Table.Root>
<svelte:fragment slot="header">
<Table.Header.Cell>Type</Table.Header.Cell>
<Table.Header.Cell>Name</Table.Header.Cell>
<Table.Header.Cell>Value</Table.Header.Cell>
<Table.Root columns={3} let:root>
<svelte:fragment slot="header" let:root>
<Table.Header.Cell {root}>Type</Table.Header.Cell>
<Table.Header.Cell {root}>Name</Table.Header.Cell>
<Table.Header.Cell {root}>Value</Table.Header.Cell>
</svelte:fragment>
<Table.Row>
<Table.Cell>CNAME</Table.Cell>
<Table.Cell>{data.domain?.domain}</Table.Cell>
<Table.Cell>
<Table.Row.Base {root}>
<Table.Cell {root}>CNAME</Table.Cell>
<Table.Cell {root}>{data.domain?.domain}</Table.Cell>
<Table.Cell {root}>
<InteractiveText
variant="copy"
isVisible
text={globalThis?.location?.origin}>
{globalThis?.location?.origin}</InteractiveText>
</Table.Cell>
</Table.Row>
</Table.Row.Base>
</Table.Root>
<Layout.Stack gap="s" direction="row" alignItems="center">
<Icon icon={IconInfo} size="s" color="--fgcolor-neutral-secondary" />
@@ -145,19 +145,19 @@
</Typography.Text>
</Layout.Stack>
<Table.Root>
<svelte:fragment slot="header">
<Table.Header.Cell>Type</Table.Header.Cell>
<Table.Header.Cell>Value</Table.Header.Cell>
<Table.Root columns={2} let:root>
<svelte:fragment slot="header" let:root>
<Table.Header.Cell {root}>Type</Table.Header.Cell>
<Table.Header.Cell {root}>Value</Table.Header.Cell>
</svelte:fragment>
{#each nameservers as nameserver}
<Table.Row>
<Table.Cell>NS</Table.Cell>
<Table.Cell>
<Table.Row.Base {root}>
<Table.Cell {root}>NS</Table.Cell>
<Table.Cell {root}>
<InteractiveText variant="copy" isVisible text={nameserver}>
{nameserver}</InteractiveText>
</Table.Cell>
</Table.Row>
</Table.Row.Base>
{/each}
</Table.Root>
<!-- <Layout.Stack gap="s" direction="row" alignItems="center">
@@ -22,20 +22,20 @@
</Typography.Text>
</Layout.Stack>
<Table.Root>
<svelte:fragment slot="header">
<Table.Header.Cell>Type</Table.Header.Cell>
<Table.Header.Cell>Name</Table.Header.Cell>
<Table.Header.Cell>Value</Table.Header.Cell>
<Table.Root columns={3} let:root>
<svelte:fragment slot="header" let:root>
<Table.Header.Cell {root}>Type</Table.Header.Cell>
<Table.Header.Cell {root}>Name</Table.Header.Cell>
<Table.Header.Cell {root}>Value</Table.Header.Cell>
</svelte:fragment>
<Table.Row>
<Table.Cell>CNAME</Table.Cell>
<Table.Cell>{domain?.domain}</Table.Cell>
<Table.Cell>
<Table.Row.Base {root}>
<Table.Cell {root}>CNAME</Table.Cell>
<Table.Cell {root}>{domain?.domain}</Table.Cell>
<Table.Cell {root}>
<Copy value={globalThis?.location?.origin}
>{globalThis?.location?.origin}</Copy>
</Table.Cell>
</Table.Row>
</Table.Row.Base>
</Table.Root>
<Layout.Stack gap="s" direction="row" alignItems="center">
<Icon icon={IconInfo} size="s" color="--fgcolor-neutral-secondary" />
@@ -67,16 +67,16 @@
</Layout.Stack>
{#if requestTab === 'parameters'}
{#if parameters?.length}
<Table.Root>
<svelte:fragment slot="header">
<Table.Header.Cell>Key</Table.Header.Cell>
<Table.Header.Cell>Value</Table.Header.Cell>
<Table.Root columns={2} let:root>
<svelte:fragment slot="header" let:root>
<Table.Header.Cell {root}>Key</Table.Header.Cell>
<Table.Header.Cell {root}>Value</Table.Header.Cell>
</svelte:fragment>
{#each parameters as parameter}
<Table.Row>
<Table.Cell>{parameter.name}</Table.Cell>
<Table.Cell>{parameter.value}</Table.Cell>
</Table.Row>
<Table.Row.Base {root}>
<Table.Cell {root}>{parameter.name}</Table.Cell>
<Table.Cell {root}>{parameter.value}</Table.Cell>
</Table.Row.Base>
{/each}
</Table.Root>
{:else}
@@ -86,16 +86,16 @@
{/if}
{:else if requestTab === 'headers'}
{#if selectedLog.requestHeaders?.length}
<Table.Root>
<svelte:fragment slot="header">
<Table.Header.Cell>Key</Table.Header.Cell>
<Table.Header.Cell>Value</Table.Header.Cell>
<Table.Root columns={2} let:root>
<svelte:fragment slot="header" let:root>
<Table.Header.Cell {root}>Key</Table.Header.Cell>
<Table.Header.Cell {root}>Value</Table.Header.Cell>
</svelte:fragment>
{#each selectedLog.requestHeaders as request}
<Table.Row>
<Table.Cell>{request.name}</Table.Cell>
<Table.Cell>{request.value}</Table.Cell>
</Table.Row>
<Table.Row.Base {root}>
<Table.Cell {root}>{request.name}</Table.Cell>
<Table.Cell {root}>{request.value}</Table.Cell>
</Table.Row.Base>
{/each}
</Table.Root>
@@ -73,16 +73,16 @@
{/if}
{:else if responseTab === 'headers'}
{#if selectedLog.responseHeaders?.length}
<Table.Root>
<svelte:fragment slot="header">
<Table.Header.Cell>Key</Table.Header.Cell>
<Table.Header.Cell>Value</Table.Header.Cell>
<Table.Root columns={2} let:root>
<svelte:fragment slot="header" let:root>
<Table.Header.Cell {root}>Key</Table.Header.Cell>
<Table.Header.Cell {root}>Value</Table.Header.Cell>
</svelte:fragment>
{#each selectedLog.responseHeaders as request}
<Table.Row>
<Table.Cell>{request.name}</Table.Cell>
<Table.Cell>{request.value}</Table.Cell>
</Table.Row>
<Table.Row.Base {root}>
<Table.Cell {root}>{request.name}</Table.Cell>
<Table.Cell {root}>{request.value}</Table.Cell>
</Table.Row.Base>
{/each}
</Table.Root>
@@ -2,12 +2,11 @@ import type { Column } from '$lib/helpers/types';
import { writable } from 'svelte/store';
export const columns = writable<Column[]>([
{ id: '$id', title: 'Log ID', type: 'string', show: true, width: 150 },
{ id: '$id', title: 'Log ID', type: 'string', width: 150 },
{
id: 'requestPath',
title: 'Path',
type: 'string',
show: true,
width: 90,
format: 'string'
},
@@ -16,7 +15,7 @@ export const columns = writable<Column[]>([
id: 'trigger',
title: 'Trigger',
type: 'string',
show: false,
hide: true,
width: 90,
array: true,
format: 'enum',
@@ -25,25 +24,23 @@ export const columns = writable<Column[]>([
{ value: 'schedule', label: 'Schedule' },
{ value: 'event', label: 'Event' }
],
filter: false,
hide: true
filter: false
},
{
id: 'requestMethod',
title: 'Method',
type: 'string',
show: false,
hide: true,
width: 70,
array: true,
format: 'enum',
elements: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE'],
hide: true
elements: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE']
},
{
id: 'responseStatusCode',
title: 'Status code',
type: 'integer',
show: false,
hide: true,
width: 100,
format: 'integer',
elements: [
@@ -64,7 +61,6 @@ export const columns = writable<Column[]>([
label: 'Server error (500-599)'
}
],
hide: true,
filter: false //TODO: re-enable
},
@@ -72,7 +68,6 @@ export const columns = writable<Column[]>([
id: 'duration',
title: 'Duration',
type: 'integer',
show: true,
width: 80,
format: 'integer',
elements: [
@@ -89,14 +84,12 @@ export const columns = writable<Column[]>([
label: 'more than 30 seconds'
}
],
filter: false,
hide: true
filter: false
},
{
id: '$createdAt',
title: 'Created',
type: 'datetime',
show: true,
width: 120,
format: 'datetime',
elements: [
@@ -14,55 +14,46 @@
let selectedLogId: string = null;
</script>
<Table.Root>
<svelte:fragment slot="header">
{#each columns as column}
{#if column.show}
<Table.Header.Cell width={column.width + 'px'}>{column.title}</Table.Header.Cell>
{/if}
<Table.Root {columns} let:root>
<svelte:fragment slot="header" let:root>
{#each columns as { id, title }}
<Table.Header.Cell column={id} {root}>{title}</Table.Header.Cell>
{/each}
</svelte:fragment>
{#each logs.executions as log}
<Table.Button
<Table.Row.Button
{root}
on:click={() => {
openSheet = true;
selectedLogId = log.$id;
}}>
{#each columns as column}
{#if column.show}
<Table.Cell column={column.id} {root}>
{#if column.id === '$id'}
{#key column.id}
<Table.Cell>
<Id value={log.$id}>{log.$id}</Id>
</Table.Cell>
<Id value={log.$id}>{log.$id}</Id>
{/key}
{:else if column.id === '$createdAt'}
<Table.Cell>
{capitalize(timeFromNow(log.$createdAt))}
</Table.Cell>
{capitalize(timeFromNow(log.$createdAt))}
{:else if column.id === 'responseStatusCode'}
<Table.Cell>
{log.responseStatusCode}
</Table.Cell>
{log.responseStatusCode}
{:else if column.id === 'requestPath'}
<Table.Cell>
<Layout.Stack direction="row" alignItems="center" gap="s">
<Badge
variant="secondary"
type={log.responseStatusCode >= 400 ? 'error' : 'success'}
content={log.responseStatusCode.toString()} />
<Typography.Code size="m">
{log.requestMethod}
</Typography.Code>
<Typography.Code size="m">
{log.requestPath}
</Typography.Code>
</Layout.Stack>
</Table.Cell>
<Layout.Stack direction="row" alignItems="center" gap="s">
<Badge
variant="secondary"
type={log.responseStatusCode >= 400 ? 'error' : 'success'}
content={log.responseStatusCode.toString()} />
<Typography.Code size="m">
{log.requestMethod}
</Typography.Code>
<Typography.Code size="m">
{log.requestPath}
</Typography.Code>
</Layout.Stack>
{/if}
{/if}
</Table.Cell>
{/each}
</Table.Button>
</Table.Row.Button>
{/each}
</Table.Root>

Some files were not shown because too many files have changed in this diff Show More