Add a graceful Shutdown action alongside Proxmox's Start/Restart/Stop

Stop maps to Proxmox's hard power-off (/status/stop) — fine for a
crashed guest, but risky for anything with a filesystem that'd rather
flush cleanly first. Proxmox exposes a separate /status/shutdown
endpoint that asks the guest to power itself down (ACPI event for a
VM, SIGTERM-then-wait for a container), so add it as its own action
rather than overloading Stop.

- New shutdownGuest() on the Proxmox adapter, calling /status/shutdown.
- The existing generic action route/loop already dispatches by
  ${action}Guest, so adding "shutdown" to that list was enough on the
  server side — no new route needed.
- New Shutdown button next to Restart/Stop on both the Integrations
  page's guest table and a Proxmox-linked server's detail page.
  Stop's confirm prompt now explicitly points at Shutdown as the
  gentler alternative.

Verified end-to-end against a mock Proxmox server: the shutdown call
hits /status/shutdown (never /status/stop) and is audit-logged as
shutdown_guest.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
bobbanandClaude Sonnet 5 committed 2026-09-15 20:52:31 +02:00
1 parent de9b6a2d6a
commit a6ab69316d
5 files changed
+23 -6

No files matched your search

+10 -2
View File
@@ -361,9 +361,10 @@ export default function Integrations({ user }: { user: CurrentUser }) {
}
}
async function guestAction(g: ProxmoxGuest, action: "start" | "stop" | "restart") {
async function guestAction(g: ProxmoxGuest, action: "start" | "stop" | "restart" | "shutdown") {
if (!selectedId) return;
if (action === "stop" && !confirm(`Stop ${g.type === "qemu" ? "VM" : "container"} "${g.name}"?`)) return;
if (action === "stop" && !confirm(`Stop ${g.type === "qemu" ? "VM" : "container"} "${g.name}" immediately? Use Shutdown instead for a graceful power-off.`))
return;
setActingOnGuest(g.vmid);
setProxmoxError(null);
try {
@@ -845,6 +846,13 @@ export default function Integrations({ user }: { user: CurrentUser }) {
>
Restart
</button>
<button
className="btn btn-sm"
onClick={() => guestAction(g, "shutdown")}
disabled={actingOnGuest === g.vmid}
>
Shutdown
</button>
<button
className="btn btn-sm btn-outline-danger"
onClick={() => guestAction(g, "stop")}