Show VM status
This commit is contained in:
		@@ -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
 | 
			
		||||
   */
 | 
			
		||||
 
 | 
			
		||||
@@ -123,9 +123,11 @@ function VMListWidget(p: {
 | 
			
		||||
              </TableCell>
 | 
			
		||||
              <TableCell>
 | 
			
		||||
                <Tooltip title="View this VM">
 | 
			
		||||
                  <IconButton>
 | 
			
		||||
                    <VisibilityIcon />
 | 
			
		||||
                  </IconButton>
 | 
			
		||||
                  <RouterLink to={row.ViewURL}>
 | 
			
		||||
                    <IconButton>
 | 
			
		||||
                      <VisibilityIcon />
 | 
			
		||||
                    </IconButton>
 | 
			
		||||
                  </RouterLink>
 | 
			
		||||
                </Tooltip>
 | 
			
		||||
                <Tooltip title="Delete this VM">
 | 
			
		||||
                  <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: {
 | 
			
		||||
  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>
 | 
			
		||||
  );
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user