feat(dashboard): delete button on server detail page
All checks were successful
Deploy to Production / deploy (push) Successful in 1m27s

The DELETE /v1/servers/:id endpoint existed (tears down the runner
container + removes the row) but nothing in the UI called it, so
servers could only be removed via SSH+psql. Adds a danger-variant
button in the top-right of the detail header with a native confirm,
spinner state, and inline error surfacing. Redirects to /servers
on success.
This commit is contained in:
Marco Sadjadi 2026-05-28 21:44:52 +02:00
parent ec819082a6
commit 31bfeed9dd

View File

@ -1,7 +1,7 @@
'use client';
import { useEffect, useState } from 'react';
import { useParams } from 'next/navigation';
import { useParams, useRouter } from 'next/navigation';
import { apiFetch } from '@/lib/api';
import { StatusPill } from '@/components/status-pill';
import { CodeBlock } from '@/components/code-block';
@ -38,11 +38,14 @@ type Tab = 'overview' | 'tools' | 'logs' | 'metrics' | 'secrets' | 'iterate' | '
export default function ServerDetailPage() {
const params = useParams<{ id: string }>();
const router = useRouter();
const [server, setServer] = useState<ServerDetail | null>(null);
const [builds, setBuilds] = useState<BuildSummary[]>([]);
const [tab, setTab] = useState<Tab>('overview');
const [iteratePrompt, setIteratePrompt] = useState('');
const [latestBuildId, setLatestBuildId] = useState<string | null>(null);
const [deleting, setDeleting] = useState(false);
const [deleteError, setDeleteError] = useState<string | null>(null);
async function refresh() {
const r = await apiFetch<{ server: ServerDetail; builds: BuildSummary[] }>(
@ -72,6 +75,27 @@ export default function ServerDetailPage() {
setTab('logs');
}
async function onDelete() {
if (!server) return;
// Destructive — the running container is torn down and the row is gone.
// Browser confirm is enough at this scope (single operator, no users yet);
// upgrade to a typed-confirmation dialog once we have customer-tier data.
const sure = window.confirm(
`Delete "${server.name}" (${server.slug})? The running container is stopped and the server row is removed. This cannot be undone.`,
);
if (!sure) return;
setDeleting(true);
setDeleteError(null);
try {
await apiFetch(`/v1/servers/${server.id}`, { method: 'DELETE' });
router.push('/servers');
} catch (e) {
const detail = (e as { detail?: { error?: string; detail?: string } }).detail;
setDeleteError(detail?.detail ?? detail?.error ?? (e as Error).message);
setDeleting(false);
}
}
if (!server) {
return (
<div className="mx-auto max-w-7xl px-6 py-8 text-[12.5px] text-[--color-fg-muted]">Loading</div>
@ -114,6 +138,16 @@ export default function ServerDetailPage() {
</>
)}
</div>
{deleteError && (
<div className="mt-2 text-[12px] text-[--color-danger]">
Delete failed: {deleteError}
</div>
)}
</div>
<div className="flex shrink-0 items-center gap-2">
<Button variant="danger" size="sm" onClick={onDelete} disabled={deleting}>
{deleting ? 'Deleting…' : 'Delete server'}
</Button>
</div>
</div>