From 31bfeed9ddce9867519180a503c460c43082a6ee Mon Sep 17 00:00:00 2001 From: Marco Sadjadi Date: Thu, 28 May 2026 21:44:52 +0200 Subject: [PATCH] feat(dashboard): delete button on server detail page 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. --- .../web/app/(dashboard)/servers/[id]/page.tsx | 36 ++++++++++++++++++- 1 file changed, 35 insertions(+), 1 deletion(-) diff --git a/apps/web/app/(dashboard)/servers/[id]/page.tsx b/apps/web/app/(dashboard)/servers/[id]/page.tsx index b83ed5d..1a51846 100644 --- a/apps/web/app/(dashboard)/servers/[id]/page.tsx +++ b/apps/web/app/(dashboard)/servers/[id]/page.tsx @@ -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(null); const [builds, setBuilds] = useState([]); const [tab, setTab] = useState('overview'); const [iteratePrompt, setIteratePrompt] = useState(''); const [latestBuildId, setLatestBuildId] = useState(null); + const [deleting, setDeleting] = useState(false); + const [deleteError, setDeleteError] = useState(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 (
Loading…
@@ -114,6 +138,16 @@ export default function ServerDetailPage() { )} + {deleteError && ( +
+ Delete failed: {deleteError} +
+ )} + +
+