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:
1 parent
de9b6a2d6a
commit
a6ab69316d
5 files changed
+23
-6
No files matched your search
@@ -643,6 +643,8 @@ export const api = {
|
||||
request<void>(`/api/integrations/${integrationId}/proxmox/nodes/${node}/${type}/${vmid}/stop`, { method: "POST" }),
|
||||
restart: (integrationId: number, node: string, type: ProxmoxGuestType, vmid: number) =>
|
||||
request<void>(`/api/integrations/${integrationId}/proxmox/nodes/${node}/${type}/${vmid}/restart`, { method: "POST" }),
|
||||
shutdown: (integrationId: number, node: string, type: ProxmoxGuestType, vmid: number) =>
|
||||
request<void>(`/api/integrations/${integrationId}/proxmox/nodes/${node}/${type}/${vmid}/shutdown`, { method: "POST" }),
|
||||
},
|
||||
synology: {
|
||||
storage: (integrationId: number) => request<SynologyStorageResponse>(`/api/integrations/${integrationId}/synology/storage`),
|
||||
|
||||
@@ -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")}
|
||||
|
||||
@@ -230,9 +230,10 @@ export default function ServerDetail({ user }: { user: CurrentUser }) {
|
||||
|
||||
const { server, hardware, ipAddresses, dnsMatches } = detail;
|
||||
|
||||
async function guestAction(action: "start" | "stop" | "restart") {
|
||||
async function guestAction(action: "start" | "stop" | "restart" | "shutdown") {
|
||||
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;
|
||||
if (action === "stop" && !confirm(`Stop ${server.name} immediately? This is a hard power-off, not a graceful shutdown — use Shutdown instead if the guest OS should get a chance to close down cleanly.`))
|
||||
return;
|
||||
setActingOnGuest(true);
|
||||
setGuestActionError(null);
|
||||
try {
|
||||
@@ -371,6 +372,9 @@ export default function ServerDetail({ user }: { user: CurrentUser }) {
|
||||
<button className="btn btn-sm" onClick={() => guestAction("restart")} disabled={actingOnGuest}>
|
||||
Restart
|
||||
</button>
|
||||
<button className="btn btn-sm" onClick={() => guestAction("shutdown")} disabled={actingOnGuest}>
|
||||
Shutdown
|
||||
</button>
|
||||
<button
|
||||
className="btn btn-sm btn-outline-danger"
|
||||
onClick={() => guestAction("stop")}
|
||||
|
||||
Reference in new issue
Block a user