From de9b6a2d6a68e93bca1ed96c6d3251eedadd0eab Mon Sep 17 00:00:00 2001 From: Bobban Rydh Date: Tue, 15 Sep 2026 20:46:35 +0200 Subject: [PATCH] Add start/restart/stop buttons to Proxmox-linked servers' detail page MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The Proxmox start/stop/restart routes already existed (used by the Integrations page's guest table) but weren't reachable from a server's own detail page, even when that server was linked to a Proxmox guest. Reuses the existing POST /api/integrations/:id/proxmox/nodes/:node/ :type/:vmid/{start,stop,restart} routes and the operator+ role gate already enforced there — no server-side changes needed. Buttons only render for hardware.source === "proxmox", mirroring the Integrations page's running/stopped button-set logic, with the same confirm-before- stop prompt for the non-reversible action. Verified end-to-end against the real dev server with a mock Proxmox HTTPS server: linked a server to a mock guest, confirmed the detail endpoint's live status, then triggered restart/stop/start and confirmed the mock actually received each action and every call was audit-logged. Co-Authored-By: Claude Sonnet 5 --- README.md | 3 +- web/src/pages/ServerDetail.tsx | 59 +++++++++++++++++++++++++++++++++- 2 files changed, 60 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 3a063b9..18de1c6 100644 --- a/README.md +++ b/README.md @@ -28,7 +28,8 @@ All modules from the original plan are built: overview opens a detail page with CPU/RAM/disk status, IP addresses, and matching DNS names (looked up from the DNS module's cache) — live from Proxmox for VM/LXC-backed servers, or from the agent's own hardware - report for everything else. + report for everything else. Proxmox-linked servers also get + start/stop/restart buttons right on the detail page. - **Integrations → Tailscale** — device list with online/authorized status, and authorize/deauthorize/remove actions; a live device-count widget. - **Integrations → Gitea** — repo list with each repo's last CI run status, diff --git a/web/src/pages/ServerDetail.tsx b/web/src/pages/ServerDetail.tsx index 20ae89e..67c2cee 100644 --- a/web/src/pages/ServerDetail.tsx +++ b/web/src/pages/ServerDetail.tsx @@ -79,6 +79,9 @@ export default function ServerDetail({ user }: { user: CurrentUser }) { const [linking, setLinking] = useState(false); const [linkError, setLinkError] = useState(null); + const [actingOnGuest, setActingOnGuest] = useState(false); + const [guestActionError, setGuestActionError] = useState(null); + const loadDetail = useCallback(() => { setRefreshing(true); return api.servers @@ -227,6 +230,26 @@ export default function ServerDetail({ user }: { user: CurrentUser }) { const { server, hardware, ipAddresses, dnsMatches } = detail; + async function guestAction(action: "start" | "stop" | "restart") { + if (!server.proxmoxIntegrationId || !server.proxmoxNode || !server.proxmoxGuestType || server.proxmoxVmid === null) return; + if (action === "stop" && !confirm(`Stop ${server.name}? This is an immediate power-off, not a graceful shutdown.`)) return; + setActingOnGuest(true); + setGuestActionError(null); + try { + await api.integrations.proxmox[action]( + server.proxmoxIntegrationId, + server.proxmoxNode, + server.proxmoxGuestType, + server.proxmoxVmid, + ); + await loadDetail(); + } catch (err) { + setGuestActionError(err instanceof Error ? err.message : String(err)); + } finally { + setActingOnGuest(false); + } + } + return ( <>
@@ -330,7 +353,41 @@ export default function ServerDetail({ user }: { user: CurrentUser }) { {hardware.error &&
{hardware.error}
} {hardware.source === "proxmox" && !hardware.error && ( <> -
Synced live from Proxmox just now.
+
+
+ Synced live from Proxmox just now. + {hardware.status && ( + + {hardware.status} + + )} +
+ {canEditTasks && ( +
+ {hardware.status === "running" ? ( + <> + + + + ) : ( + + )} +
+ )} +
+ {guestActionError &&
{guestActionError}
}
CPU