diff --git a/src/lib/components/ConfirmPopover.svelte b/src/lib/components/ConfirmPopover.svelte index 9fe0c28..9f81377 100644 --- a/src/lib/components/ConfirmPopover.svelte +++ b/src/lib/components/ConfirmPopover.svelte @@ -19,6 +19,7 @@ onConfirm: () => void; onOpenChange: (open: boolean) => void; children: Snippet<[{ open: boolean }]>; + extraContent?: Snippet; } let { @@ -35,7 +36,8 @@ disabled = false, onConfirm, onOpenChange, - children + children, + extraContent }: Props = $props(); const triggerClass = $derived(unstyled @@ -103,11 +105,16 @@ align={position === 'left' ? 'start' : 'end'} sideOffset={8} > -
- {action} {itemType} {#if displayName}{displayName}{/if}? - +
+
+ {action} {itemType} {#if displayName}{displayName}{/if}? + +
+ {#if extraContent} + {@render extraContent()} + {/if}
diff --git a/src/lib/server/stacks.ts b/src/lib/server/stacks.ts index a4a5017..dd77beb 100644 --- a/src/lib/server/stacks.ts +++ b/src/lib/server/stacks.ts @@ -1982,7 +1982,8 @@ export async function downStack( export async function removeStack( stackName: string, envId?: number | null, - force = false + force = false, + removeVolumes = false ): Promise { return withStackLock(stackName, async () => { // Get compose file (may not exist for external stacks) @@ -2000,6 +2001,7 @@ export async function removeStack( { stackName, envId, + removeVolumes, workingDir: composeResult.stackDir, composePath: composeResult.composePath ?? undefined, envPath: composeResult.envPath ?? undefined diff --git a/src/routes/api/stacks/[name]/+server.ts b/src/routes/api/stacks/[name]/+server.ts index 9ed0945..29f3740 100644 --- a/src/routes/api/stacks/[name]/+server.ts +++ b/src/routes/api/stacks/[name]/+server.ts @@ -9,6 +9,7 @@ export const DELETE: RequestHandler = async (event) => { const auth = await authorize(cookies); const force = url.searchParams.get('force') === 'true'; + const volumes = url.searchParams.get('volumes') === 'true'; const envId = url.searchParams.get('env'); const envIdNum = envId ? parseInt(envId) : undefined; @@ -24,10 +25,10 @@ export const DELETE: RequestHandler = async (event) => { try { const stackName = decodeURIComponent(params.name); - const result = await removeStack(stackName, envIdNum, force); + const result = await removeStack(stackName, envIdNum, force, volumes); // Audit log - await auditStack(event, 'delete', stackName, envIdNum, { force }); + await auditStack(event, 'delete', stackName, envIdNum, { force, volumes }); if (!result.success) { return json({ success: false, error: result.error }, { status: 400 }); diff --git a/src/routes/stacks/+page.svelte b/src/routes/stacks/+page.svelte index 31c896c..0c8869b 100644 --- a/src/routes/stacks/+page.svelte +++ b/src/routes/stacks/+page.svelte @@ -10,6 +10,7 @@ import { Badge } from '$lib/components/ui/badge'; import { Button } from '$lib/components/ui/button'; import { Input } from '$lib/components/ui/input'; + import { Checkbox } from '$lib/components/ui/checkbox'; import * as Tooltip from '$lib/components/ui/tooltip'; import * as Popover from '$lib/components/ui/popover'; import MultiSelectFilter from '$lib/components/MultiSelectFilter.svelte'; @@ -418,6 +419,7 @@ let confirmDeleteName = $state(null); let confirmStopName = $state(null); let confirmDownName = $state(null); + let deleteVolumes = $state(false); // Stack operation loading state let stackActionLoading = $state(null); @@ -968,15 +970,18 @@ async function removeStack(name: string) { operationError = null; + const withVolumes = deleteVolumes; + deleteVolumes = false; try { - const response = await fetch(appendEnvParam(`/api/stacks/${encodeURIComponent(name)}?force=true`, envId), { method: 'DELETE' }); + const params = `force=true${withVolumes ? '&volumes=true' : ''}`; + const response = await fetch(appendEnvParam(`/api/stacks/${encodeURIComponent(name)}?${params}`, envId), { method: 'DELETE' }); if (!response.ok) { const data = await response.json(); const errorMsg = data.error || 'Failed to remove stack'; showErrorDialog(`Failed to remove ${name}`, errorMsg); return; } - toast.success(`Removed ${name}`); + toast.success(`Removed ${name}${withVolumes ? ' (volumes deleted)' : ''}`); await fetchStacks(); } catch (error) { console.error('Failed to remove stack:', error); @@ -1505,7 +1510,7 @@ @@ -1648,7 +1653,7 @@ {@const stats = getStackStats(stack)}
{#if stats} - {formatBytes(stats.memoryUsage)} + {formatBytes(stats.memoryUsage)}/{formatBytes(stats.memoryLimit, 0)} {:else if stack.status === 'running' || stack.status === 'partial' || stack.status === 'restarting'} ... {:else} @@ -1753,7 +1758,7 @@ {#if source.sourceType === 'git' && source.gitStack}