Can edit more network settings
This commit is contained in:
		
							
								
								
									
										41
									
								
								virtweb_frontend/src/widgets/StateActionButton.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										41
									
								
								virtweb_frontend/src/widgets/StateActionButton.tsx
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,41 @@
 | 
			
		||||
import { IconButton, Tooltip } from "@mui/material";
 | 
			
		||||
import { useAlert } from "../hooks/providers/AlertDialogProvider";
 | 
			
		||||
import { useConfirm } from "../hooks/providers/ConfirmDialogProvider";
 | 
			
		||||
 | 
			
		||||
export function StateActionButton<S>(p: {
 | 
			
		||||
  currState: S;
 | 
			
		||||
  cond: S[];
 | 
			
		||||
  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>
 | 
			
		||||
  );
 | 
			
		||||
}
 | 
			
		||||
@@ -28,7 +28,11 @@ export function SelectInput(p: {
 | 
			
		||||
        onChange={(e) => p.onValueChange(e.target.value)}
 | 
			
		||||
      >
 | 
			
		||||
        {p.options.map((e) => (
 | 
			
		||||
          <MenuItem key={e.value} value={e.value}>
 | 
			
		||||
          <MenuItem
 | 
			
		||||
            key={e.value}
 | 
			
		||||
            value={e.value}
 | 
			
		||||
            style={{ fontStyle: e.value === undefined ? "italic" : undefined }}
 | 
			
		||||
          >
 | 
			
		||||
            {e.label}
 | 
			
		||||
          </MenuItem>
 | 
			
		||||
        ))}
 | 
			
		||||
 
 | 
			
		||||
@@ -34,7 +34,7 @@ export function TextInput(p: {
 | 
			
		||||
  return (
 | 
			
		||||
    <TextField
 | 
			
		||||
      label={p.label}
 | 
			
		||||
      value={p.value}
 | 
			
		||||
      value={p.value ?? ""}
 | 
			
		||||
      onChange={(e) =>
 | 
			
		||||
        p.onValueChange?.(
 | 
			
		||||
          e.target.value.length === 0 ? undefined : e.target.value
 | 
			
		||||
@@ -47,7 +47,7 @@ export function TextInput(p: {
 | 
			
		||||
        readOnly: !p.editable,
 | 
			
		||||
        type: p.type,
 | 
			
		||||
      }}
 | 
			
		||||
      variant={p.editable ? "standard" : "standard"}
 | 
			
		||||
      variant={"standard"}
 | 
			
		||||
      style={{ width: "100%", marginBottom: "15px" }}
 | 
			
		||||
      multiline={p.multiline}
 | 
			
		||||
      minRows={p.minRows}
 | 
			
		||||
 
 | 
			
		||||
@@ -3,19 +3,44 @@ import { NetworkInfo } from "../../api/NetworksApi";
 | 
			
		||||
import { ServerApi } from "../../api/ServerApi";
 | 
			
		||||
import { EditSection } from "../forms/EditSection";
 | 
			
		||||
import { TextInput } from "../forms/TextInput";
 | 
			
		||||
import { SelectInput } from "../forms/SelectInput";
 | 
			
		||||
import React from "react";
 | 
			
		||||
import { AsyncWidget } from "../AsyncWidget";
 | 
			
		||||
import { IPv4Input } from "../forms/IPv4Input";
 | 
			
		||||
 | 
			
		||||
export function NetworkDetails(p: {
 | 
			
		||||
interface DetailsProps {
 | 
			
		||||
  net: NetworkInfo;
 | 
			
		||||
  editable: boolean;
 | 
			
		||||
  onChange?: () => void;
 | 
			
		||||
}): React.ReactElement {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function NetworkDetails(p: DetailsProps): React.ReactElement {
 | 
			
		||||
  const [cardsList, setCardsList] = React.useState<string[] | any>();
 | 
			
		||||
 | 
			
		||||
  const load = async () => {
 | 
			
		||||
    setCardsList(await ServerApi.GetNetworksList());
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <AsyncWidget
 | 
			
		||||
      loadKey={"1"}
 | 
			
		||||
      load={load}
 | 
			
		||||
      errMsg="Failed to load the list of host network cards!"
 | 
			
		||||
      build={() => <NetworkDetailsInner cardsList={cardsList} {...p} />}
 | 
			
		||||
    />
 | 
			
		||||
  );
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function NetworkDetailsInner(
 | 
			
		||||
  p: DetailsProps & { cardsList: string[] }
 | 
			
		||||
): React.ReactElement {
 | 
			
		||||
  return (
 | 
			
		||||
    <Grid container spacing={2}>
 | 
			
		||||
      {/* Metadata section */}
 | 
			
		||||
      <EditSection title="Metadata">
 | 
			
		||||
        <TextInput
 | 
			
		||||
          label="Name"
 | 
			
		||||
          editable={p.editable}
 | 
			
		||||
          editable={p.editable && !p.net.uuid}
 | 
			
		||||
          value={p.net.name}
 | 
			
		||||
          onValueChange={(v) => {
 | 
			
		||||
            p.net.name = v ?? "";
 | 
			
		||||
@@ -49,7 +74,58 @@ export function NetworkDetails(p: {
 | 
			
		||||
          multiline={true}
 | 
			
		||||
        />
 | 
			
		||||
      </EditSection>
 | 
			
		||||
      TODO:continue
 | 
			
		||||
      {/* TODO :  autostart */}
 | 
			
		||||
 | 
			
		||||
      <EditSection title="General settings">
 | 
			
		||||
        <SelectInput
 | 
			
		||||
          editable={p.editable}
 | 
			
		||||
          label="Forward mode"
 | 
			
		||||
          onValueChange={(v) => {
 | 
			
		||||
            p.net.forward_mode = v as any;
 | 
			
		||||
            p.onChange?.();
 | 
			
		||||
          }}
 | 
			
		||||
          value={p.net.forward_mode}
 | 
			
		||||
          options={[
 | 
			
		||||
            {
 | 
			
		||||
              label: "NAT",
 | 
			
		||||
              value: "NAT",
 | 
			
		||||
            },
 | 
			
		||||
 | 
			
		||||
            {
 | 
			
		||||
              label: "Isolated network",
 | 
			
		||||
              value: "Isolated",
 | 
			
		||||
            },
 | 
			
		||||
          ]}
 | 
			
		||||
        />
 | 
			
		||||
 | 
			
		||||
        {p.net.forward_mode === "NAT" && (
 | 
			
		||||
          <SelectInput
 | 
			
		||||
            editable={p.editable}
 | 
			
		||||
            label="Network output device"
 | 
			
		||||
            onValueChange={(v) => {
 | 
			
		||||
              p.net.device = v;
 | 
			
		||||
              p.onChange?.();
 | 
			
		||||
            }}
 | 
			
		||||
            value={p.net.device}
 | 
			
		||||
            options={[
 | 
			
		||||
              { label: "Default interface", value: undefined },
 | 
			
		||||
              ...p.cardsList.map((d) => {
 | 
			
		||||
                return { label: d, value: d };
 | 
			
		||||
              }),
 | 
			
		||||
            ]}
 | 
			
		||||
          />
 | 
			
		||||
        )}
 | 
			
		||||
 | 
			
		||||
        <IPv4Input
 | 
			
		||||
          editable={p.editable}
 | 
			
		||||
          label="DNS server to use"
 | 
			
		||||
          value={p.net.dns_server}
 | 
			
		||||
          onValueChange={(v) => {
 | 
			
		||||
            p.net.dns_server = v;
 | 
			
		||||
            p.onChange?.();
 | 
			
		||||
          }}
 | 
			
		||||
        />
 | 
			
		||||
      </EditSection>
 | 
			
		||||
    </Grid>
 | 
			
		||||
  );
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										74
									
								
								virtweb_frontend/src/widgets/net/NetworkStatusWidget.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										74
									
								
								virtweb_frontend/src/widgets/net/NetworkStatusWidget.tsx
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,74 @@
 | 
			
		||||
import PlayArrowIcon from "@mui/icons-material/PlayArrow";
 | 
			
		||||
import StopIcon from "@mui/icons-material/Stop";
 | 
			
		||||
import { CircularProgress, Typography } from "@mui/material";
 | 
			
		||||
import React from "react";
 | 
			
		||||
import {
 | 
			
		||||
  NetworkApi,
 | 
			
		||||
  NetworkInfo,
 | 
			
		||||
  NetworkStatus as NetworkState,
 | 
			
		||||
} from "../../api/NetworksApi";
 | 
			
		||||
import { useSnackbar } from "../../hooks/providers/SnackbarProvider";
 | 
			
		||||
import { StateActionButton } from "../StateActionButton";
 | 
			
		||||
 | 
			
		||||
export function NetworkStatusWidget(p: {
 | 
			
		||||
  net: NetworkInfo;
 | 
			
		||||
  onChange?: (s: NetworkState) => void;
 | 
			
		||||
}): React.ReactElement {
 | 
			
		||||
  const snackbar = useSnackbar();
 | 
			
		||||
 | 
			
		||||
  const [state, setState] = React.useState<undefined | NetworkState>();
 | 
			
		||||
 | 
			
		||||
  const refresh = async () => {
 | 
			
		||||
    try {
 | 
			
		||||
      const s = await NetworkApi.GetState(p.net);
 | 
			
		||||
      if (s !== state) p.onChange?.(s);
 | 
			
		||||
      setState(s);
 | 
			
		||||
    } catch (e) {
 | 
			
		||||
      console.error(e);
 | 
			
		||||
      snackbar("Failed to refresh network status!");
 | 
			
		||||
    }
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  const changedAction = () => setState(undefined);
 | 
			
		||||
 | 
			
		||||
  React.useEffect(() => {
 | 
			
		||||
    refresh();
 | 
			
		||||
    const i = setInterval(() => refresh(), 3000);
 | 
			
		||||
 | 
			
		||||
    return () => clearInterval(i);
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  if (state === undefined)
 | 
			
		||||
    return (
 | 
			
		||||
      <>
 | 
			
		||||
        <CircularProgress size={"1rem"} />
 | 
			
		||||
      </>
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <div style={{ display: "inline-flex" }}>
 | 
			
		||||
      <Typography>{state}</Typography>
 | 
			
		||||
 | 
			
		||||
      {/* Start Network */}
 | 
			
		||||
      <StateActionButton
 | 
			
		||||
        currState={state}
 | 
			
		||||
        cond={["Stopped"]}
 | 
			
		||||
        icon={<PlayArrowIcon />}
 | 
			
		||||
        tooltip="Start the Network"
 | 
			
		||||
        performAction={() => NetworkApi.Start(p.net)}
 | 
			
		||||
        onExecuted={changedAction}
 | 
			
		||||
      />
 | 
			
		||||
 | 
			
		||||
      {/* Stop network */}
 | 
			
		||||
      <StateActionButton
 | 
			
		||||
        currState={state}
 | 
			
		||||
        cond={["Started"]}
 | 
			
		||||
        icon={<StopIcon />}
 | 
			
		||||
        tooltip="Stop the network"
 | 
			
		||||
        confirmMessage="Do you really want to kill stop this network?"
 | 
			
		||||
        performAction={() => NetworkApi.Stop(p.net)}
 | 
			
		||||
        onExecuted={changedAction}
 | 
			
		||||
      />
 | 
			
		||||
    </div>
 | 
			
		||||
  );
 | 
			
		||||
}
 | 
			
		||||
@@ -4,18 +4,12 @@ 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,
 | 
			
		||||
  Typography,
 | 
			
		||||
} from "@mui/material";
 | 
			
		||||
import { CircularProgress, Typography } from "@mui/material";
 | 
			
		||||
import React from "react";
 | 
			
		||||
import { useNavigate } from "react-router-dom";
 | 
			
		||||
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";
 | 
			
		||||
import { StateActionButton } from "../StateActionButton";
 | 
			
		||||
 | 
			
		||||
export function VMStatusWidget(p: {
 | 
			
		||||
  vm: VMInfo;
 | 
			
		||||
@@ -59,7 +53,7 @@ export function VMStatusWidget(p: {
 | 
			
		||||
 | 
			
		||||
      {
 | 
			
		||||
        /* VNC console */ p.vm.vnc_access && (
 | 
			
		||||
          <ActionButton
 | 
			
		||||
          <StateActionButton
 | 
			
		||||
            currState={state}
 | 
			
		||||
            cond={["Running"]}
 | 
			
		||||
            icon={<PersonalVideoIcon />}
 | 
			
		||||
@@ -71,7 +65,7 @@ export function VMStatusWidget(p: {
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      {/* Start VM */}
 | 
			
		||||
      <ActionButton
 | 
			
		||||
      <StateActionButton
 | 
			
		||||
        currState={state}
 | 
			
		||||
        cond={["Shutdown", "Shutoff", "Crashed"]}
 | 
			
		||||
        icon={<PlayArrowIcon />}
 | 
			
		||||
@@ -81,7 +75,7 @@ export function VMStatusWidget(p: {
 | 
			
		||||
      />
 | 
			
		||||
 | 
			
		||||
      {/* Resume VM */}
 | 
			
		||||
      <ActionButton
 | 
			
		||||
      <StateActionButton
 | 
			
		||||
        currState={state}
 | 
			
		||||
        cond={["Paused", "PowerManagementSuspended"]}
 | 
			
		||||
        icon={<PlayArrowIcon />}
 | 
			
		||||
@@ -91,7 +85,7 @@ export function VMStatusWidget(p: {
 | 
			
		||||
      />
 | 
			
		||||
 | 
			
		||||
      {/* Suspend VM */}
 | 
			
		||||
      <ActionButton
 | 
			
		||||
      <StateActionButton
 | 
			
		||||
        currState={state}
 | 
			
		||||
        cond={["Running"]}
 | 
			
		||||
        icon={<PauseIcon />}
 | 
			
		||||
@@ -102,7 +96,7 @@ export function VMStatusWidget(p: {
 | 
			
		||||
      />
 | 
			
		||||
 | 
			
		||||
      {/* Shutdown VM */}
 | 
			
		||||
      <ActionButton
 | 
			
		||||
      <StateActionButton
 | 
			
		||||
        currState={state}
 | 
			
		||||
        cond={["Running"]}
 | 
			
		||||
        icon={<PowerSettingsNewIcon />}
 | 
			
		||||
@@ -113,7 +107,7 @@ export function VMStatusWidget(p: {
 | 
			
		||||
      />
 | 
			
		||||
 | 
			
		||||
      {/* Kill VM */}
 | 
			
		||||
      <ActionButton
 | 
			
		||||
      <StateActionButton
 | 
			
		||||
        currState={state}
 | 
			
		||||
        cond={["Running", "Paused", "PowerManagementSuspended", "Blocked"]}
 | 
			
		||||
        icon={<StopIcon />}
 | 
			
		||||
@@ -124,7 +118,7 @@ export function VMStatusWidget(p: {
 | 
			
		||||
      />
 | 
			
		||||
 | 
			
		||||
      {/* Reset VM */}
 | 
			
		||||
      <ActionButton
 | 
			
		||||
      <StateActionButton
 | 
			
		||||
        currState={state}
 | 
			
		||||
        cond={["Running", "Paused", "PowerManagementSuspended", "Blocked"]}
 | 
			
		||||
        icon={<ReplayIcon />}
 | 
			
		||||
@@ -136,41 +130,3 @@ export function VMStatusWidget(p: {
 | 
			
		||||
    </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