Show VM status
This commit is contained in:
parent
3c00c23205
commit
7ef5afb978
@ -6,6 +6,17 @@
|
|||||||
|
|
||||||
import { APIClient } from "./ApiClient";
|
import { APIClient } from "./ApiClient";
|
||||||
|
|
||||||
|
export type VMState =
|
||||||
|
| "NoState"
|
||||||
|
| "Running"
|
||||||
|
| "Blocked"
|
||||||
|
| "Paused"
|
||||||
|
| "Shutdown"
|
||||||
|
| "Shutoff"
|
||||||
|
| "Crashed"
|
||||||
|
| "PowerManagementSuspended"
|
||||||
|
| "Other";
|
||||||
|
|
||||||
interface VMInfoInterface {
|
interface VMInfoInterface {
|
||||||
name: string;
|
name: string;
|
||||||
uuid?: string;
|
uuid?: string;
|
||||||
@ -40,6 +51,10 @@ export class VMInfo implements VMInfoInterface {
|
|||||||
this.memory = int.memory;
|
this.memory = int.memory;
|
||||||
this.vnc_access = int.vnc_access;
|
this.vnc_access = int.vnc_access;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get ViewURL(): string {
|
||||||
|
return `/api/vm/${this.uuid}`;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class VMApi {
|
export class VMApi {
|
||||||
@ -55,6 +70,90 @@ export class VMApi {
|
|||||||
).data.map((i: VMInfoInterface) => new VMInfo(i));
|
).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
|
* Delete a virtual machine
|
||||||
*/
|
*/
|
||||||
|
@ -123,9 +123,11 @@ function VMListWidget(p: {
|
|||||||
</TableCell>
|
</TableCell>
|
||||||
<TableCell>
|
<TableCell>
|
||||||
<Tooltip title="View this VM">
|
<Tooltip title="View this VM">
|
||||||
|
<RouterLink to={row.ViewURL}>
|
||||||
<IconButton>
|
<IconButton>
|
||||||
<VisibilityIcon />
|
<VisibilityIcon />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
|
</RouterLink>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
<Tooltip title="Delete this VM">
|
<Tooltip title="Delete this VM">
|
||||||
<IconButton onClick={() => deleteVM(row)}>
|
<IconButton onClick={() => deleteVM(row)}>
|
||||||
|
@ -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: {
|
export function VMStatusWidget(p: {
|
||||||
d: VMInfo;
|
d: VMInfo;
|
||||||
onChange?: () => void;
|
onChange?: (s: VMState) => void;
|
||||||
}): React.ReactElement {
|
}): 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>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user