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}