Show VM status

This commit is contained in:
Pierre HUBERT 2023-10-16 13:46:46 +02:00
parent 3c00c23205
commit 7ef5afb978
3 changed files with 253 additions and 6 deletions

View File

@ -6,6 +6,17 @@
import { APIClient } from "./ApiClient";
export type VMState =
| "NoState"
| "Running"
| "Blocked"
| "Paused"
| "Shutdown"
| "Shutoff"
| "Crashed"
| "PowerManagementSuspended"
| "Other";
interface VMInfoInterface {
name: string;
uuid?: string;
@ -40,6 +51,10 @@ export class VMInfo implements VMInfoInterface {
this.memory = int.memory;
this.vnc_access = int.vnc_access;
}
get ViewURL(): string {
return `/api/vm/${this.uuid}`;
}
}
export class VMApi {
@ -55,6 +70,90 @@ export class VMApi {
).data.map((i: VMInfoInterface) => new VMInfo(i));
}
/**
* Get the state of a VM
*/
static async GetState(vm: VMInfo): Promise<VMState> {
return (
await APIClient.exec({
uri: `/vm/${vm.uuid}/state`,
method: "GET",
})
).data.state;
}
/**
* Start the VM
*/
static async StartVM(vm: VMInfo): Promise<void> {
return (
await APIClient.exec({
uri: `/vm/${vm.uuid}/start`,
method: "GET",
})
).data.state;
}
/**
* Shutdown the VM
*/
static async ShutdownVM(vm: VMInfo): Promise<void> {
return (
await APIClient.exec({
uri: `/vm/${vm.uuid}/shutdown`,
method: "GET",
})
).data.state;
}
/**
* Restt the VM
*/
static async ResetVM(vm: VMInfo): Promise<void> {
return (
await APIClient.exec({
uri: `/vm/${vm.uuid}/reset`,
method: "GET",
})
).data.state;
}
/**
* Kill the VM
*/
static async KillVM(vm: VMInfo): Promise<void> {
return (
await APIClient.exec({
uri: `/vm/${vm.uuid}/kill`,
method: "GET",
})
).data.state;
}
/**
* Suspend the VM
*/
static async SuspendVM(vm: VMInfo): Promise<void> {
return (
await APIClient.exec({
uri: `/vm/${vm.uuid}/suspend`,
method: "GET",
})
).data.state;
}
/**
* Resume the VM
*/
static async ResumeVM(vm: VMInfo): Promise<void> {
return (
await APIClient.exec({
uri: `/vm/${vm.uuid}/resume`,
method: "GET",
})
).data.state;
}
/**
* Delete a virtual machine
*/

View File

@ -123,9 +123,11 @@ function VMListWidget(p: {
</TableCell>
<TableCell>
<Tooltip title="View this VM">
<RouterLink to={row.ViewURL}>
<IconButton>
<VisibilityIcon />
</IconButton>
</RouterLink>
</Tooltip>
<Tooltip title="Delete this VM">
<IconButton onClick={() => deleteVM(row)}>

View File

@ -1,8 +1,154 @@
import { VMInfo } from "../../api/VMApi";
import PauseIcon from "@mui/icons-material/Pause";
import PlayArrowIcon from "@mui/icons-material/PlayArrow";
import PowerSettingsNewIcon from "@mui/icons-material/PowerSettingsNew";
import ReplayIcon from "@mui/icons-material/Replay";
import StopIcon from "@mui/icons-material/Stop";
import { CircularProgress, IconButton, Tooltip } from "@mui/material";
import React from "react";
import { VMApi, VMInfo, VMState } from "../../api/VMApi";
import { useAlert } from "../../hooks/providers/AlertDialogProvider";
import { useConfirm } from "../../hooks/providers/ConfirmDialogProvider";
import { useSnackbar } from "../../hooks/providers/SnackbarProvider";
export function VMStatusWidget(p: {
d: VMInfo;
onChange?: () => void;
onChange?: (s: VMState) => void;
}): React.ReactElement {
return <>TODO</>;
const snackbar = useSnackbar();
const [state, setState] = React.useState<undefined | VMState>(undefined);
const refresh = async () => {
try {
const s = await VMApi.GetState(p.d);
if (s !== state) p.onChange?.(s);
setState(s);
} catch (e) {
console.error(e);
snackbar("Failed to refresh VM status!");
}
};
const changedAction = () => setState(undefined);
React.useEffect(() => {
const i = setInterval(() => refresh(), 3000);
return () => clearInterval(i);
});
if (state === undefined)
return (
<>
<CircularProgress size={"1rem"} />
</>
);
return (
<div style={{ display: "inline-flex" }}>
{state}
{/* Start VM */}
<ActionButton
currState={state}
cond={["Shutdown", "Shutoff", "Crashed"]}
icon={<PlayArrowIcon />}
tooltip="Start the Virtual Machine"
performAction={() => VMApi.StartVM(p.d)}
onExecuted={changedAction}
/>
{/* Resume VM */}
<ActionButton
currState={state}
cond={["Paused", "PowerManagementSuspended"]}
icon={<PlayArrowIcon />}
tooltip="Resume the Virtual Machine"
performAction={() => VMApi.ResumeVM(p.d)}
onExecuted={changedAction}
/>
{/* Suspend VM */}
<ActionButton
currState={state}
cond={["Running"]}
icon={<PauseIcon />}
tooltip="Suspend the Virtual Machine"
confirmMessage="Do you really want to supsend this VM?"
performAction={() => VMApi.SuspendVM(p.d)}
onExecuted={changedAction}
/>
{/* Shutdown VM */}
<ActionButton
currState={state}
cond={["Running"]}
icon={<PowerSettingsNewIcon />}
tooltip="Shutdown the Virtual Machine"
confirmMessage="Do you really want to shutdown this VM?"
performAction={() => VMApi.ShutdownVM(p.d)}
onExecuted={changedAction}
/>
{/* Kill VM */}
<ActionButton
currState={state}
cond={["Running", "Paused", "PowerManagementSuspended", "Blocked"]}
icon={<StopIcon />}
tooltip="Kill the Virtual Machine"
confirmMessage="Do you really want to kill this VM? This could lead to data loss / corruption!"
performAction={() => VMApi.KillVM(p.d)}
onExecuted={changedAction}
/>
{/* Reset VM */}
<ActionButton
currState={state}
cond={["Running", "Paused", "PowerManagementSuspended", "Blocked"]}
icon={<ReplayIcon />}
tooltip="Reset the Virtual Machine"
confirmMessage="Do you really want to reset this VM?"
performAction={() => VMApi.ResetVM(p.d)}
onExecuted={changedAction}
/>
</div>
);
}
function ActionButton(p: {
currState: VMState;
cond: VMState[];
icon: React.ReactElement;
tooltip: string;
confirmMessage?: string;
performAction: () => Promise<void>;
onExecuted: () => void;
}): React.ReactElement {
const confirm = useConfirm();
const alert = useAlert();
if (!p.cond.includes(p.currState)) return <></>;
const performAction = async () => {
try {
if (p.confirmMessage && !(await confirm(p.confirmMessage))) return;
await p.performAction();
p.onExecuted();
} catch (e) {
console.error(e);
alert("Failed to perform action! " + e);
}
};
return (
<Tooltip title={p.tooltip}>
<IconButton
size="small"
onClick={performAction}
style={{ paddingBottom: "0px", paddingTop: "0px" }}
>
{p.icon}
</IconButton>
</Tooltip>
);
}