mirror of
https://github.com/appwrite/console.git
synced 2026-06-06 19:27:48 +00:00
add: auto notify support.
add: `deleteContent` snippet support. add: `multiSelectTable` to functions.
This commit is contained in:
@@ -14,23 +14,28 @@
|
||||
} from '@appwrite.io/pink-svelte';
|
||||
import { Button } from '$lib/elements/forms';
|
||||
import { Confirm } from '$lib/components/index';
|
||||
import { addNotification } from '$lib/stores/notifications';
|
||||
|
||||
let {
|
||||
columns,
|
||||
resource,
|
||||
allowSelection = true,
|
||||
confirmDeletion = false,
|
||||
showSuccessNotification = true,
|
||||
header,
|
||||
children,
|
||||
onDelete,
|
||||
onCancel
|
||||
onCancel,
|
||||
deleteContent
|
||||
}: {
|
||||
resource: string;
|
||||
allowSelection?: boolean;
|
||||
confirmDeletion?: boolean;
|
||||
showSuccessNotification?: boolean;
|
||||
columns: Array<TableColumn> | number;
|
||||
header: Snippet<[root: TableRootProps]>;
|
||||
children: Snippet<[root: TableRootProps]>;
|
||||
deleteContent?: Snippet<[count: number]>;
|
||||
onDelete?: (selectedRows: string[]) => Promise<DeleteOperationState> | DeleteOperationState;
|
||||
onCancel?: () => Promise<void> | void;
|
||||
} = $props();
|
||||
@@ -39,6 +44,20 @@
|
||||
let disableModal: boolean = $state(false);
|
||||
let onDeleteError: string | null = $state(null);
|
||||
let showConfirmDeletion: boolean = $state(false);
|
||||
|
||||
function notifySuccess() {
|
||||
if (!showSuccessNotification) return;
|
||||
|
||||
const count = selectedRows.length;
|
||||
if (count === 0) return;
|
||||
|
||||
const label = `${resource}${count > 1 ? 's' : ''}`;
|
||||
|
||||
addNotification({
|
||||
type: 'success',
|
||||
message: `${count} ${label} deleted`
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<Table.Root let:root {columns} {allowSelection} bind:selectedRows>
|
||||
@@ -75,6 +94,7 @@
|
||||
if (typeof state === 'string') {
|
||||
// user should handle error on their own!
|
||||
} else {
|
||||
notifySuccess();
|
||||
selectedRows = [];
|
||||
}
|
||||
}
|
||||
@@ -100,14 +120,21 @@
|
||||
disableModal = false;
|
||||
onDeleteError = state || `Failed to delete ${resource}s`;
|
||||
} else {
|
||||
notifySuccess();
|
||||
selectedRows = [];
|
||||
disableModal = false;
|
||||
showConfirmDeletion = false;
|
||||
}
|
||||
}}>
|
||||
<Typography.Text>
|
||||
Are you sure you want to delete <strong>{selectedRows.length}</strong>
|
||||
{selectedRows.length > 1 ? `${resource}s` : resource}.
|
||||
{@const selectionCount = selectedRows.length}
|
||||
{#if deleteContent}
|
||||
<!-- because some show extra info -->
|
||||
{@render deleteContent(selectionCount)}
|
||||
{:else}
|
||||
Are you sure you want to delete <strong>{selectionCount}</strong>
|
||||
{selectionCount > 1 ? `${resource}s` : resource}.
|
||||
{/if}
|
||||
</Typography.Text>
|
||||
|
||||
<Typography.Text variant="m-500">This action is irreversible.</Typography.Text>
|
||||
|
||||
-5
@@ -30,11 +30,6 @@
|
||||
subNavigation.update();
|
||||
|
||||
trackEvent(Submit.TableDelete);
|
||||
addNotification({
|
||||
type: 'success',
|
||||
message: `${selectedTables.length} table${selectedTables.length > 1 ? 's' : ''} deleted`
|
||||
});
|
||||
|
||||
return true;
|
||||
} catch (error) {
|
||||
trackError(error, Submit.TableDelete);
|
||||
|
||||
+143
-171
@@ -1,5 +1,5 @@
|
||||
<script lang="ts">
|
||||
import { Confirm, Id } from '$lib/components';
|
||||
import { type DeleteOperationState, Id, MultiSelectionTable } from '$lib/components';
|
||||
import type { PageData } from './$types';
|
||||
import { type Models } from '@appwrite.io/console';
|
||||
import type { Column } from '$lib/helpers/types';
|
||||
@@ -16,8 +16,6 @@
|
||||
import { base } from '$app/paths';
|
||||
import {
|
||||
ActionMenu,
|
||||
Badge,
|
||||
FloatingActionBar,
|
||||
Icon,
|
||||
Status,
|
||||
Table,
|
||||
@@ -39,28 +37,26 @@
|
||||
import DownloadActionMenuItem from './(components)/downloadActionMenuItem.svelte';
|
||||
import { Menu } from '$lib/components/menu';
|
||||
import { sdk } from '$lib/stores/sdk';
|
||||
import { addNotification } from '$lib/stores/notifications';
|
||||
|
||||
export let columns: Column[];
|
||||
export let data: PageData;
|
||||
|
||||
let showDelete = false;
|
||||
let showActivate = false;
|
||||
let showRedeploy = false;
|
||||
let showCancel = false;
|
||||
|
||||
let selectedDeployment: Models.Deployment = null;
|
||||
let {
|
||||
data,
|
||||
columns,
|
||||
}: {
|
||||
data: PageData,
|
||||
columns: Column[]
|
||||
} = $props()
|
||||
|
||||
let showDelete = $state(false);
|
||||
let showCancel = $state(false);
|
||||
let showActivate = $state(false);
|
||||
let showRedeploy = $state(false);
|
||||
let selectedDeployment: Models.Deployment | null = $state(null);
|
||||
|
||||
function handleActivate() {
|
||||
invalidate(Dependencies.DEPLOYMENTS);
|
||||
}
|
||||
|
||||
let selectedRows = [];
|
||||
let showBatchDeletion = false;
|
||||
|
||||
async function deleteDeployments() {
|
||||
showBatchDeletion = false;
|
||||
|
||||
async function deleteDeployments(selectedRows: string[]): Promise<DeleteOperationState> {
|
||||
const promises = selectedRows.map((deploymentId) =>
|
||||
sdk.forProject(page.params.region, page.params.project).functions.deleteDeployment({
|
||||
functionId: page.params.function,
|
||||
@@ -69,187 +65,163 @@
|
||||
);
|
||||
try {
|
||||
await Promise.all(promises);
|
||||
await invalidate(Dependencies.DEPLOYMENTS);
|
||||
|
||||
trackEvent(Submit.DeploymentDelete);
|
||||
addNotification({
|
||||
type: 'success',
|
||||
message: `${selectedRows.length} deployment${selectedRows.length > 1 ? 's' : ''} deleted`
|
||||
});
|
||||
return true;
|
||||
} catch (error) {
|
||||
addNotification({
|
||||
type: 'error',
|
||||
message: error.message
|
||||
});
|
||||
trackError(error, Submit.DeploymentDelete);
|
||||
} finally {
|
||||
selectedRows = [];
|
||||
showBatchDeletion = false;
|
||||
invalidate(Dependencies.DEPLOYMENTS);
|
||||
return error.message;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<Table.Root
|
||||
let:root
|
||||
<MultiSelectionTable
|
||||
allowSelection
|
||||
bind:selectedRows
|
||||
confirmDeletion
|
||||
resource="deployment"
|
||||
onDelete={deleteDeployments}
|
||||
columns={[...columns, { id: 'actions', width: 40 }]}>
|
||||
<svelte:fragment slot="header" let:root>
|
||||
{#snippet header(root)}
|
||||
{#each columns as { id, title }}
|
||||
<Table.Header.Cell column={id} {root}>
|
||||
{title}
|
||||
</Table.Header.Cell>
|
||||
{/each}
|
||||
<Table.Header.Cell column="actions" {root} />
|
||||
</svelte:fragment>
|
||||
{#each data.deploymentList.deployments as deployment (deployment.$id)}
|
||||
<Table.Row.Link
|
||||
{root}
|
||||
id={deployment.$id}
|
||||
href={`${base}/project-${page.params.region}-${page.params.project}/functions/function-${page.params.function}/deployment-${deployment.$id}`}>
|
||||
{#each columns as column}
|
||||
<Table.Cell column={column.id} {root}>
|
||||
{#if column.id === '$id'}
|
||||
{#key column.id}
|
||||
<Id value={deployment.$id}>{deployment.$id}</Id>
|
||||
{/key}
|
||||
{:else if column.id === 'status'}
|
||||
{@const status = deployment.status}
|
||||
{/snippet}
|
||||
|
||||
{#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'}
|
||||
<DeploymentSource {deployment} />
|
||||
{:else if column.id === '$updatedAt'}
|
||||
<DeploymentCreatedBy {deployment} />
|
||||
{:else if column.id === 'buildDuration'}
|
||||
{#if ['waiting'].includes(deployment.status)}
|
||||
-
|
||||
{:else if ['processing', 'building'].includes(deployment.status)}
|
||||
<span use:timer={{ start: deployment.$createdAt }}></span>
|
||||
{:else}
|
||||
{formatTimeDetailed(deployment.buildDuration)}
|
||||
{/if}
|
||||
{:else if column.id === 'totalSize'}
|
||||
{calculateSize(deployment.totalSize)}
|
||||
{:else if column.id === 'sourceSize'}
|
||||
{calculateSize(deployment.sourceSize)}
|
||||
{:else if column.id === 'buildSize'}
|
||||
{calculateSize(deployment.buildSize)}
|
||||
{/if}
|
||||
</Table.Cell>
|
||||
{/each}
|
||||
<Table.Cell column="actions" {root}>
|
||||
<Menu>
|
||||
<Button text icon size="s">
|
||||
<Icon size="s" icon={IconDotsHorizontal} />
|
||||
</Button>
|
||||
{#snippet children(root)}
|
||||
{#each data.deploymentList.deployments as deployment (deployment.$id)}
|
||||
<Table.Row.Link
|
||||
{root}
|
||||
id={deployment.$id}
|
||||
href={`${base}/project-${page.params.region}-${page.params.project}/functions/function-${page.params.function}/deployment-${deployment.$id}`}>
|
||||
{#each columns as column}
|
||||
<Table.Cell column={column.id} {root}>
|
||||
{#if column.id === '$id'}
|
||||
{#key column.id}
|
||||
<Id value={deployment.$id}>{deployment.$id}</Id>
|
||||
{/key}
|
||||
{:else if column.id === 'status'}
|
||||
{@const status = deployment.status}
|
||||
|
||||
<svelte:fragment slot="menu" let:toggle>
|
||||
<ActionMenu.Root>
|
||||
<Tooltip disabled={deployment.sourceSize !== 0} placement={'bottom'}>
|
||||
<div>
|
||||
{#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'}
|
||||
<DeploymentSource {deployment} />
|
||||
{:else if column.id === '$updatedAt'}
|
||||
<DeploymentCreatedBy {deployment} />
|
||||
{:else if column.id === 'buildDuration'}
|
||||
{#if ['waiting'].includes(deployment.status)}
|
||||
-
|
||||
{:else if ['processing', 'building'].includes(deployment.status)}
|
||||
<span use:timer={{ start: deployment.$createdAt }}></span>
|
||||
{:else}
|
||||
{formatTimeDetailed(deployment.buildDuration)}
|
||||
{/if}
|
||||
{:else if column.id === 'totalSize'}
|
||||
{calculateSize(deployment.totalSize)}
|
||||
{:else if column.id === 'sourceSize'}
|
||||
{calculateSize(deployment.sourceSize)}
|
||||
{:else if column.id === 'buildSize'}
|
||||
{calculateSize(deployment.buildSize)}
|
||||
{/if}
|
||||
</Table.Cell>
|
||||
{/each}
|
||||
<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>
|
||||
<Tooltip
|
||||
disabled={deployment.sourceSize !== 0}
|
||||
placement={'bottom'}>
|
||||
<div>
|
||||
<ActionMenu.Item.Button
|
||||
trailingIcon={IconRefresh}
|
||||
disabled={deployment.sourceSize === 0}
|
||||
on:click={() => {
|
||||
selectedDeployment = deployment;
|
||||
showRedeploy = true;
|
||||
toggle();
|
||||
trackEvent(Click.FunctionsRedeployClick);
|
||||
}}
|
||||
style="width: 100%">
|
||||
Redeploy
|
||||
</ActionMenu.Item.Button>
|
||||
</div>
|
||||
<div slot="tooltip">Source is empty</div>
|
||||
</Tooltip>
|
||||
{#if deployment.status === 'ready' && deployment.$id !== $func.deploymentId}
|
||||
<ActionMenu.Item.Button
|
||||
trailingIcon={IconRefresh}
|
||||
disabled={deployment.sourceSize === 0}
|
||||
trailingIcon={IconLightningBolt}
|
||||
on:click={() => {
|
||||
selectedDeployment = deployment;
|
||||
showRedeploy = true;
|
||||
showActivate = true;
|
||||
toggle();
|
||||
trackEvent(Click.FunctionsRedeployClick);
|
||||
}}
|
||||
style="width: 100%">
|
||||
Redeploy
|
||||
}}>
|
||||
Activate
|
||||
</ActionMenu.Item.Button>
|
||||
</div>
|
||||
<div slot="tooltip">Source is empty</div>
|
||||
</Tooltip>
|
||||
{#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>
|
||||
</Table.Cell>
|
||||
</Table.Row.Link>
|
||||
{/each}
|
||||
</Table.Root>
|
||||
showDelete = true;
|
||||
trackEvent(Click.FunctionsDeploymentDeleteClick);
|
||||
}}>
|
||||
Delete
|
||||
</ActionMenu.Item.Button>
|
||||
{/if}
|
||||
</ActionMenu.Root>
|
||||
</svelte:fragment>
|
||||
</Menu>
|
||||
</Table.Cell>
|
||||
</Table.Row.Link>
|
||||
{/each}
|
||||
{/snippet}
|
||||
|
||||
{#snippet deleteContent(count)}
|
||||
<p>
|
||||
Are you sure you want to delete <strong>{count}</strong>
|
||||
{count > 1 ? 'deployments' : 'deployment'} from your function -
|
||||
<strong>{page.data.function.name}</strong>?
|
||||
</p>
|
||||
{/snippet}
|
||||
</MultiSelectionTable>
|
||||
|
||||
{#if selectedDeployment}
|
||||
<Delete {selectedDeployment} bind:showDelete />
|
||||
<Activate {selectedDeployment} bind:showActivate on:activated={handleActivate} />
|
||||
<Cancel {selectedDeployment} bind:showCancel />
|
||||
<RedeployModal {selectedDeployment} bind:show={showRedeploy} />
|
||||
<Activate {selectedDeployment} bind:showActivate on:activated={handleActivate} />
|
||||
{/if}
|
||||
|
||||
{#if selectedRows.length > 0}
|
||||
<FloatingActionBar>
|
||||
<svelte:fragment slot="start">
|
||||
<Badge content={selectedRows.length.toString()} />
|
||||
<span style="white-space: nowrap">
|
||||
{selectedRows.length > 1 ? 'deployments' : 'deployment'}
|
||||
selected
|
||||
</span>
|
||||
</svelte:fragment>
|
||||
<svelte:fragment slot="end">
|
||||
<Button text on:click={() => (selectedRows = [])}>Cancel</Button>
|
||||
<Button secondary on:click={() => (showBatchDeletion = true)}>Delete</Button>
|
||||
</svelte:fragment>
|
||||
</FloatingActionBar>
|
||||
{/if}
|
||||
|
||||
<Confirm
|
||||
title="Delete deployments"
|
||||
bind:open={showBatchDeletion}
|
||||
confirmDeletion
|
||||
onSubmit={deleteDeployments}>
|
||||
<p>
|
||||
Are you sure you want to delete <strong>{selectedRows.length}</strong>
|
||||
{selectedRows.length > 1 ? 'deployments' : 'deployment'} from your function -
|
||||
<strong>{$func.name}</strong>?
|
||||
</p>
|
||||
|
||||
<p class="u-bold">This action is irreversible.</p>
|
||||
</Confirm>
|
||||
|
||||
@@ -39,11 +39,6 @@
|
||||
await Promise.all(promises);
|
||||
await invalidate(Dependencies.EXECUTIONS);
|
||||
|
||||
addNotification({
|
||||
type: 'success',
|
||||
message: `${selectedRows.length} log${selectedRows.length > 1 ? 's' : ''} deleted`
|
||||
});
|
||||
|
||||
trackEvent(Submit.LogDelete);
|
||||
return true;
|
||||
} catch (error) {
|
||||
|
||||
Reference in New Issue
Block a user