This commit is contained in:
		@@ -1,12 +1,11 @@
 | 
			
		||||
import js from "@eslint/js";
 | 
			
		||||
import reactDom from 'eslint-plugin-react-dom';
 | 
			
		||||
import reactDom from "eslint-plugin-react-dom";
 | 
			
		||||
import reactHooks from "eslint-plugin-react-hooks";
 | 
			
		||||
import reactRefresh from "eslint-plugin-react-refresh";
 | 
			
		||||
import reactX from 'eslint-plugin-react-x';
 | 
			
		||||
import reactX from "eslint-plugin-react-x";
 | 
			
		||||
import globals from "globals";
 | 
			
		||||
import tseslint from "typescript-eslint";
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
export default tseslint.config(
 | 
			
		||||
  { ignores: ["dist"] },
 | 
			
		||||
  {
 | 
			
		||||
@@ -38,7 +37,17 @@ export default tseslint.config(
 | 
			
		||||
      ],
 | 
			
		||||
      ...reactX.configs["recommended-typescript"].rules,
 | 
			
		||||
      ...reactDom.configs.recommended.rules,
 | 
			
		||||
      "@typescript-eslint/no-non-null-assertion": "off"
 | 
			
		||||
      "@typescript-eslint/no-non-null-assertion": "off",
 | 
			
		||||
      "@typescript-eslint/no-misused-promises": "off",
 | 
			
		||||
      "@typescript-eslint/no-floating-promises": "off",
 | 
			
		||||
      "@typescript-eslint/restrict-template-expressions": "off",
 | 
			
		||||
      "@typescript-eslint/no-extraneous-class": "off",
 | 
			
		||||
      "@typescript-eslint/no-explicit-any": "off",
 | 
			
		||||
      "@typescript-eslint/no-unsafe-assignment": "off",
 | 
			
		||||
      "@typescript-eslint/no-unsafe-return": "off",
 | 
			
		||||
      "@typescript-eslint/no-unsafe-call": "off",
 | 
			
		||||
      "@typescript-eslint/no-unsafe-argument": "off",
 | 
			
		||||
      "react-refresh/only-export-components": "off",
 | 
			
		||||
    },
 | 
			
		||||
  }
 | 
			
		||||
);
 | 
			
		||||
 
 | 
			
		||||
@@ -26,7 +26,7 @@ export class APIClient {
 | 
			
		||||
   * Get backend URL
 | 
			
		||||
   */
 | 
			
		||||
  static backendURL(): string {
 | 
			
		||||
    const URL = import.meta.env.VITE_APP_BACKEND ?? "";
 | 
			
		||||
    const URL = String(import.meta.env.VITE_APP_BACKEND ?? "");
 | 
			
		||||
    if (URL.length === 0) throw new Error("Backend URL undefined!");
 | 
			
		||||
    return URL;
 | 
			
		||||
  }
 | 
			
		||||
 
 | 
			
		||||
@@ -96,7 +96,7 @@ function UploadIsoFileCard(p: {
 | 
			
		||||
      p.onFileUploaded();
 | 
			
		||||
    } catch (e) {
 | 
			
		||||
      console.error(e);
 | 
			
		||||
      await alert("Failed to perform file upload! " + e);
 | 
			
		||||
      await alert(`Failed to perform file upload! ${e}`);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    setUploadProgress(null);
 | 
			
		||||
@@ -120,7 +120,9 @@ function UploadIsoFileCard(p: {
 | 
			
		||||
          value={value}
 | 
			
		||||
          onChange={handleChange}
 | 
			
		||||
          style={{ flex: 1 }}
 | 
			
		||||
          inputProps={{ accept: ServerApi.Config.iso_mimetypes.join(",") }}
 | 
			
		||||
          slotProps={{
 | 
			
		||||
            htmlInput: { accept: ServerApi.Config.iso_mimetypes.join(",") },
 | 
			
		||||
          }}
 | 
			
		||||
        />
 | 
			
		||||
 | 
			
		||||
        {value && <Button onClick={upload}>Upload file</Button>}
 | 
			
		||||
@@ -166,14 +168,18 @@ function UploadIsoFileFromUrlCard(p: {
 | 
			
		||||
          label="URL"
 | 
			
		||||
          value={url}
 | 
			
		||||
          style={{ flex: 3 }}
 | 
			
		||||
          onChange={(e) => { setURL(e.target.value); }}
 | 
			
		||||
          onChange={(e) => {
 | 
			
		||||
            setURL(e.target.value);
 | 
			
		||||
          }}
 | 
			
		||||
        />
 | 
			
		||||
        <span style={{ width: "10px" }}></span>
 | 
			
		||||
        <TextField
 | 
			
		||||
          label="Filename"
 | 
			
		||||
          value={actualFileName}
 | 
			
		||||
          style={{ flex: 2 }}
 | 
			
		||||
          onChange={(e) => { setFilename(e.target.value); }}
 | 
			
		||||
          onChange={(e) => {
 | 
			
		||||
            setFilename(e.target.value);
 | 
			
		||||
          }}
 | 
			
		||||
        />
 | 
			
		||||
        {url !== "" && actualFileName !== "" && (
 | 
			
		||||
          <Button onClick={upload}>Upload file</Button>
 | 
			
		||||
@@ -238,7 +244,7 @@ function IsoFilesList(p: {
 | 
			
		||||
      </Typography>
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
  const columns: GridColDef[] = [
 | 
			
		||||
  const columns: GridColDef<IsoFile>[] = [
 | 
			
		||||
    { field: "filename", headerName: "File name", flex: 3 },
 | 
			
		||||
    {
 | 
			
		||||
      field: "size",
 | 
			
		||||
@@ -303,7 +309,6 @@ function IsoFilesList(p: {
 | 
			
		||||
          getRowId={(c) => c.filename}
 | 
			
		||||
          rows={p.list}
 | 
			
		||||
          columns={columns}
 | 
			
		||||
          autoHeight={true}
 | 
			
		||||
        />
 | 
			
		||||
      </VirtWebPaper>
 | 
			
		||||
    </>
 | 
			
		||||
 
 | 
			
		||||
@@ -1,4 +1,4 @@
 | 
			
		||||
export async function downloadBlob(blob: Blob, filename: string) {
 | 
			
		||||
export function downloadBlob(blob: Blob, filename: string) {
 | 
			
		||||
  const url = URL.createObjectURL(blob);
 | 
			
		||||
 | 
			
		||||
  const link = document.createElement("a");
 | 
			
		||||
 
 | 
			
		||||
@@ -1,3 +1,4 @@
 | 
			
		||||
/* eslint-disable @typescript-eslint/no-unnecessary-condition */
 | 
			
		||||
import { TextInput } from "./TextInput";
 | 
			
		||||
 | 
			
		||||
export function MACInput(p: {
 | 
			
		||||
 
 | 
			
		||||
@@ -1,3 +1,4 @@
 | 
			
		||||
/* eslint-disable react-x/no-array-index-key */
 | 
			
		||||
import ArrowDownwardIcon from "@mui/icons-material/ArrowDownward";
 | 
			
		||||
import ArrowUpwardIcon from "@mui/icons-material/ArrowUpward";
 | 
			
		||||
import DeleteIcon from "@mui/icons-material/Delete";
 | 
			
		||||
@@ -66,9 +67,19 @@ export function NWFilterRules(p: {
 | 
			
		||||
            deleteRule(n);
 | 
			
		||||
          }}
 | 
			
		||||
          onGoDown={
 | 
			
		||||
            n < p.rules.length - 1 ? () => { swapRules(n, n + 1); } : undefined
 | 
			
		||||
            n < p.rules.length - 1
 | 
			
		||||
              ? () => {
 | 
			
		||||
                  swapRules(n, n + 1);
 | 
			
		||||
                }
 | 
			
		||||
              : undefined
 | 
			
		||||
          }
 | 
			
		||||
          onGoUp={
 | 
			
		||||
            n > 0
 | 
			
		||||
              ? () => {
 | 
			
		||||
                  swapRules(n, n - 1);
 | 
			
		||||
                }
 | 
			
		||||
              : undefined
 | 
			
		||||
          }
 | 
			
		||||
          onGoUp={n > 0 ? () => { swapRules(n, n - 1); } : undefined}
 | 
			
		||||
          {...p}
 | 
			
		||||
        />
 | 
			
		||||
      ))}
 | 
			
		||||
@@ -153,7 +164,9 @@ function NWRuleEdit(p: {
 | 
			
		||||
            editable={p.editable}
 | 
			
		||||
            onChange={p.onChange}
 | 
			
		||||
            selector={s}
 | 
			
		||||
            onDelete={() => { deleteSelector(n); }}
 | 
			
		||||
            onDelete={() => {
 | 
			
		||||
              deleteSelector(n);
 | 
			
		||||
            }}
 | 
			
		||||
          />
 | 
			
		||||
        ))}
 | 
			
		||||
      </CardContent>
 | 
			
		||||
 
 | 
			
		||||
@@ -1,3 +1,4 @@
 | 
			
		||||
/* eslint-disable react-x/no-array-index-key */
 | 
			
		||||
import { mdiIp } from "@mdi/js";
 | 
			
		||||
import Icon from "@mdi/react";
 | 
			
		||||
import DeleteIcon from "@mui/icons-material/Delete";
 | 
			
		||||
 
 | 
			
		||||
@@ -54,6 +54,7 @@ export function NetNatConfiguration(p: {
 | 
			
		||||
    <>
 | 
			
		||||
      {p.nat.map((e, num) => (
 | 
			
		||||
        <NatEntryForm
 | 
			
		||||
          // eslint-disable-next-line react-x/no-array-index-key
 | 
			
		||||
          key={num}
 | 
			
		||||
          {...p}
 | 
			
		||||
          entry={e}
 | 
			
		||||
 
 | 
			
		||||
@@ -2,7 +2,7 @@ import { TextField } from "@mui/material";
 | 
			
		||||
import { LenConstraint } from "../../api/ServerApi";
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Couple / Member property edition
 | 
			
		||||
 * Text input property edition
 | 
			
		||||
 */
 | 
			
		||||
export function TextInput(p: {
 | 
			
		||||
  label?: string;
 | 
			
		||||
@@ -42,12 +42,14 @@ export function TextInput(p: {
 | 
			
		||||
          e.target.value.length === 0 ? undefined : e.target.value
 | 
			
		||||
        )
 | 
			
		||||
      }
 | 
			
		||||
      inputProps={{
 | 
			
		||||
      slotProps={{
 | 
			
		||||
        htmlInput: {
 | 
			
		||||
          maxLength: p.size?.max,
 | 
			
		||||
      }}
 | 
			
		||||
      InputProps={{
 | 
			
		||||
        },
 | 
			
		||||
        input: {
 | 
			
		||||
          readOnly: !p.editable,
 | 
			
		||||
          type: p.type,
 | 
			
		||||
        },
 | 
			
		||||
      }}
 | 
			
		||||
      variant={"standard"}
 | 
			
		||||
      style={p.style ?? { width: "100%", marginBottom: "15px" }}
 | 
			
		||||
 
 | 
			
		||||
@@ -40,6 +40,7 @@ export function VMDisksList(p: {
 | 
			
		||||
      {/* disks list */}
 | 
			
		||||
      {p.vm.disks.map((d, num) => (
 | 
			
		||||
        <DiskInfo
 | 
			
		||||
          // eslint-disable-next-line react-x/no-array-index-key
 | 
			
		||||
          key={num}
 | 
			
		||||
          editable={p.editable}
 | 
			
		||||
          disk={d}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,3 +1,4 @@
 | 
			
		||||
/* eslint-disable react-x/no-array-index-key */
 | 
			
		||||
import DeleteIcon from "@mui/icons-material/Delete";
 | 
			
		||||
import {
 | 
			
		||||
  Button,
 | 
			
		||||
 
 | 
			
		||||
@@ -19,7 +19,7 @@ export function VMSelectIsoInput(p: {
 | 
			
		||||
  attachedISOs: string[];
 | 
			
		||||
  onChange: (newVal: string[]) => void;
 | 
			
		||||
}): React.ReactElement {
 | 
			
		||||
  if (!p.attachedISOs && !p.editable) return <></>;
 | 
			
		||||
  if (p.attachedISOs.length === 0 && !p.editable) return <></>;
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <>
 | 
			
		||||
@@ -27,7 +27,7 @@ export function VMSelectIsoInput(p: {
 | 
			
		||||
        const iso = p.isoList.find((d) => d.filename === isoName);
 | 
			
		||||
        return (
 | 
			
		||||
          <ListItem
 | 
			
		||||
            key={num}
 | 
			
		||||
            key={isoName}
 | 
			
		||||
            secondaryAction={
 | 
			
		||||
              p.editable && (
 | 
			
		||||
                <IconButton
 | 
			
		||||
@@ -73,8 +73,7 @@ export function VMSelectIsoInput(p: {
 | 
			
		||||
            label: `${i.filename} ${filesize(i.size)}`,
 | 
			
		||||
            value: i.filename,
 | 
			
		||||
          };
 | 
			
		||||
          })
 | 
			
		||||
        }
 | 
			
		||||
        })}
 | 
			
		||||
      />
 | 
			
		||||
    </>
 | 
			
		||||
  );
 | 
			
		||||
 
 | 
			
		||||
@@ -25,7 +25,7 @@ interface DetailsProps {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function NetworkDetails(p: DetailsProps): React.ReactElement {
 | 
			
		||||
  const [nicsList, setNicsList] = React.useState<string[] | any>();
 | 
			
		||||
  const [nicsList, setNicsList] = React.useState<string[] | undefined>();
 | 
			
		||||
 | 
			
		||||
  const load = async () => {
 | 
			
		||||
    setNicsList(await ServerApi.GetNetworksList());
 | 
			
		||||
@@ -36,7 +36,7 @@ export function NetworkDetails(p: DetailsProps): React.ReactElement {
 | 
			
		||||
      loadKey={"1"}
 | 
			
		||||
      load={load}
 | 
			
		||||
      errMsg="Failed to load the list of host network cards!"
 | 
			
		||||
      build={() => <NetworkDetailsInner nicsList={nicsList} {...p} />}
 | 
			
		||||
      build={() => <NetworkDetailsInner nicsList={nicsList!} {...p} />}
 | 
			
		||||
    />
 | 
			
		||||
  );
 | 
			
		||||
}
 | 
			
		||||
@@ -306,7 +306,7 @@ function IPSection(p: {
 | 
			
		||||
      p.config!.nat = [];
 | 
			
		||||
    } else {
 | 
			
		||||
      if (
 | 
			
		||||
        (p.config?.nat?.length ?? 0 > 0) &&
 | 
			
		||||
        (p.config?.nat?.length ?? 0) > 0 &&
 | 
			
		||||
        !(await confirm(
 | 
			
		||||
          `Do you really want to disable IPv${p.version} NAT port forwarding on this network? Specific configuration will be deleted!`
 | 
			
		||||
        ))
 | 
			
		||||
 
 | 
			
		||||
@@ -28,7 +28,9 @@ interface DetailsProps {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function NWFilterDetails(p: DetailsProps): ReactElement {
 | 
			
		||||
  const [nwFiltersList, setNwFiltersList] = React.useState<NWFilter[] | any>();
 | 
			
		||||
  const [nwFiltersList, setNwFiltersList] = React.useState<
 | 
			
		||||
    NWFilter[] | undefined
 | 
			
		||||
  >();
 | 
			
		||||
 | 
			
		||||
  const load = async () => {
 | 
			
		||||
    setNwFiltersList(await NWFilterApi.GetList());
 | 
			
		||||
@@ -40,7 +42,7 @@ export function NWFilterDetails(p: DetailsProps): ReactElement {
 | 
			
		||||
      load={load}
 | 
			
		||||
      errMsg="Failed to load the list of network filters!"
 | 
			
		||||
      build={() => (
 | 
			
		||||
        <NetworkFilterDetailsInner nwFiltersList={nwFiltersList} {...p} />
 | 
			
		||||
        <NetworkFilterDetailsInner nwFiltersList={nwFiltersList!} {...p} />
 | 
			
		||||
      )}
 | 
			
		||||
    />
 | 
			
		||||
  );
 | 
			
		||||
@@ -116,7 +118,7 @@ function NetworkFilterDetailsTabGeneral(
 | 
			
		||||
            p.nwfilter.name = v ?? "";
 | 
			
		||||
            p.onChange?.();
 | 
			
		||||
          }}
 | 
			
		||||
          checkValue={(v) => /^[a-zA-Z0-9\_\-]+$/.test(v)}
 | 
			
		||||
          checkValue={(v) => /^[a-zA-Z0-9_-]+$/.test(v)}
 | 
			
		||||
          size={ServerApi.Config.constraints.nwfilter_name_size}
 | 
			
		||||
        />
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -161,14 +161,14 @@ function APITokenTabGeneral(p: DetailsInnerProps): React.ReactElement {
 | 
			
		||||
        {p.status === TokenWidgetStatus.Create && (
 | 
			
		||||
          <RadioGroupInput
 | 
			
		||||
            {...p}
 | 
			
		||||
            editable={p.status === TokenWidgetStatus.Create}
 | 
			
		||||
            editable={true}
 | 
			
		||||
            options={[
 | 
			
		||||
              { label: "IPv4", value: "4" },
 | 
			
		||||
              { label: "IPv6", value: "6" },
 | 
			
		||||
            ]}
 | 
			
		||||
            value={ipVersion.toString()}
 | 
			
		||||
            onValueChange={(v) => {
 | 
			
		||||
              setIpVersion(Number(v) as any);
 | 
			
		||||
              setIpVersion(Number(v) as 4 | 6);
 | 
			
		||||
            }}
 | 
			
		||||
            label="Token IP restriction version"
 | 
			
		||||
          />
 | 
			
		||||
 
 | 
			
		||||
@@ -63,6 +63,7 @@ export function TokenRawRightsEditor(p: {
 | 
			
		||||
        </TableHead>
 | 
			
		||||
        <TableBody>
 | 
			
		||||
          {p.token.rights.map((r, num) => (
 | 
			
		||||
            // eslint-disable-next-line react-x/no-array-index-key
 | 
			
		||||
            <TableRow key={num} hover>
 | 
			
		||||
              <TableCell style={{ width: "100px" }}>
 | 
			
		||||
                <SelectInput
 | 
			
		||||
@@ -95,7 +96,11 @@ export function TokenRawRightsEditor(p: {
 | 
			
		||||
              </TableCell>
 | 
			
		||||
              {p.editable && (
 | 
			
		||||
                <TableCell style={{ width: "100px" }}>
 | 
			
		||||
                  <IconButton onClick={() => { deleteRule(num); }}>
 | 
			
		||||
                  <IconButton
 | 
			
		||||
                    onClick={() => {
 | 
			
		||||
                      deleteRule(num);
 | 
			
		||||
                    }}
 | 
			
		||||
                  >
 | 
			
		||||
                    <Tooltip title="Remove the rule">
 | 
			
		||||
                      <DeleteIcon />
 | 
			
		||||
                    </Tooltip>
 | 
			
		||||
 
 | 
			
		||||
@@ -85,8 +85,8 @@ export function TokenRightsEditor(p: {
 | 
			
		||||
            </TableRow>
 | 
			
		||||
 | 
			
		||||
            {/* Per VM operations */}
 | 
			
		||||
            {p.vms.map((v, n) => (
 | 
			
		||||
              <TableRow hover key={n}>
 | 
			
		||||
            {p.vms.map((v) => (
 | 
			
		||||
              <TableRow hover key={v.uuid}>
 | 
			
		||||
                <TableCell>{v.name}</TableCell>
 | 
			
		||||
                <CellRight
 | 
			
		||||
                  {...p}
 | 
			
		||||
@@ -185,8 +185,8 @@ export function TokenRightsEditor(p: {
 | 
			
		||||
            </TableRow>
 | 
			
		||||
 | 
			
		||||
            {/* Per VM operations */}
 | 
			
		||||
            {p.vms.map((v, n) => (
 | 
			
		||||
              <TableRow hover key={n}>
 | 
			
		||||
            {p.vms.map((v) => (
 | 
			
		||||
              <TableRow hover key={v.uuid}>
 | 
			
		||||
                <TableCell>{v.name}</TableCell>
 | 
			
		||||
                <CellRight
 | 
			
		||||
                  {...p}
 | 
			
		||||
@@ -306,8 +306,8 @@ export function TokenRightsEditor(p: {
 | 
			
		||||
            </TableRow>
 | 
			
		||||
 | 
			
		||||
            {/* Per VM operations */}
 | 
			
		||||
            {p.groups.map((v, n) => (
 | 
			
		||||
              <TableRow hover key={n}>
 | 
			
		||||
            {p.groups.map((v) => (
 | 
			
		||||
              <TableRow hover key={v}>
 | 
			
		||||
                <TableCell>{v}</TableCell>
 | 
			
		||||
                <CellRight
 | 
			
		||||
                  {...p}
 | 
			
		||||
@@ -448,8 +448,8 @@ export function TokenRightsEditor(p: {
 | 
			
		||||
            </TableRow>
 | 
			
		||||
 | 
			
		||||
            {/* Per network operations */}
 | 
			
		||||
            {p.networks.map((v, n) => (
 | 
			
		||||
              <TableRow hover key={n}>
 | 
			
		||||
            {p.networks.map((v) => (
 | 
			
		||||
              <TableRow hover key={v.uuid}>
 | 
			
		||||
                <TableCell>{v.name}</TableCell>
 | 
			
		||||
                <CellRight
 | 
			
		||||
                  {...p}
 | 
			
		||||
@@ -568,8 +568,8 @@ export function TokenRightsEditor(p: {
 | 
			
		||||
            </TableRow>
 | 
			
		||||
 | 
			
		||||
            {/* Per network filter operations */}
 | 
			
		||||
            {p.nwFilters.map((v, n) => (
 | 
			
		||||
              <TableRow hover key={n}>
 | 
			
		||||
            {p.nwFilters.map((v) => (
 | 
			
		||||
              <TableRow hover key={v.uuid}>
 | 
			
		||||
                <TableCell>{v.name}</TableCell>
 | 
			
		||||
                <CellRight
 | 
			
		||||
                  {...p}
 | 
			
		||||
@@ -645,8 +645,8 @@ export function TokenRightsEditor(p: {
 | 
			
		||||
            </TableRow>
 | 
			
		||||
 | 
			
		||||
            {/* Per API token operations */}
 | 
			
		||||
            {p.tokens.map((v, n) => (
 | 
			
		||||
              <TableRow hover key={n}>
 | 
			
		||||
            {p.tokens.map((v) => (
 | 
			
		||||
              <TableRow hover key={v.id}>
 | 
			
		||||
                <TableCell>{v.name}</TableCell>
 | 
			
		||||
                <CellRight
 | 
			
		||||
                  {...p}
 | 
			
		||||
@@ -804,7 +804,9 @@ function RouteRight(p: RightOpts): React.ReactElement {
 | 
			
		||||
              <Checkbox
 | 
			
		||||
                checked={activated || parentActivated}
 | 
			
		||||
                disabled={!p.editable || parentActivated}
 | 
			
		||||
                onChange={(_e, a) => { toggle(a); }}
 | 
			
		||||
                onChange={(_e, a) => {
 | 
			
		||||
                  toggle(a);
 | 
			
		||||
                }}
 | 
			
		||||
              />
 | 
			
		||||
            }
 | 
			
		||||
            label={p.label}
 | 
			
		||||
@@ -814,7 +816,9 @@ function RouteRight(p: RightOpts): React.ReactElement {
 | 
			
		||||
            <Checkbox
 | 
			
		||||
              checked={activated || parentActivated}
 | 
			
		||||
              disabled={!p.editable || parentActivated}
 | 
			
		||||
              onChange={(_e, a) => { toggle(a); }}
 | 
			
		||||
              onChange={(_e, a) => {
 | 
			
		||||
                toggle(a);
 | 
			
		||||
              }}
 | 
			
		||||
            />
 | 
			
		||||
          </span>
 | 
			
		||||
        )}
 | 
			
		||||
 
 | 
			
		||||
@@ -35,14 +35,16 @@ interface DetailsProps {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function VMDetails(p: DetailsProps): React.ReactElement {
 | 
			
		||||
  const [groupsList, setGroupsList] = React.useState<string[] | any>();
 | 
			
		||||
  const [isoList, setIsoList] = React.useState<IsoFile[] | any>();
 | 
			
		||||
  const [groupsList, setGroupsList] = React.useState<string[] | undefined>();
 | 
			
		||||
  const [isoList, setIsoList] = React.useState<IsoFile[] | undefined>();
 | 
			
		||||
  const [vcpuCombinations, setVCPUCombinations] = React.useState<
 | 
			
		||||
    number[] | any
 | 
			
		||||
    number[] | undefined
 | 
			
		||||
  >();
 | 
			
		||||
  const [networksList, setNetworksList] = React.useState<
 | 
			
		||||
    NetworkInfo[] | undefined
 | 
			
		||||
  >();
 | 
			
		||||
  const [networksList, setNetworksList] = React.useState<NetworkInfo[] | any>();
 | 
			
		||||
  const [networkFiltersList, setNetworkFiltersList] = React.useState<
 | 
			
		||||
    NWFilter[] | any
 | 
			
		||||
    NWFilter[] | undefined
 | 
			
		||||
  >();
 | 
			
		||||
 | 
			
		||||
  const load = async () => {
 | 
			
		||||
@@ -60,11 +62,11 @@ export function VMDetails(p: DetailsProps): React.ReactElement {
 | 
			
		||||
      errMsg="Failed to load the list of ISO files"
 | 
			
		||||
      build={() => (
 | 
			
		||||
        <VMDetailsInner
 | 
			
		||||
          groupsList={groupsList}
 | 
			
		||||
          isoList={isoList}
 | 
			
		||||
          vcpuCombinations={vcpuCombinations}
 | 
			
		||||
          networksList={networksList}
 | 
			
		||||
          networkFiltersList={networkFiltersList}
 | 
			
		||||
          groupsList={groupsList!}
 | 
			
		||||
          isoList={isoList!}
 | 
			
		||||
          vcpuCombinations={vcpuCombinations!}
 | 
			
		||||
          networksList={networksList!}
 | 
			
		||||
          networkFiltersList={networkFiltersList!}
 | 
			
		||||
          {...p}
 | 
			
		||||
        />
 | 
			
		||||
      )}
 | 
			
		||||
@@ -202,7 +204,7 @@ function VMDetailsTabGeneral(p: DetailsInnerProps): React.ReactElement {
 | 
			
		||||
              editable={p.editable}
 | 
			
		||||
              label="Group"
 | 
			
		||||
              onValueChange={(v) => {
 | 
			
		||||
                p.vm.group = v! as any;
 | 
			
		||||
                p.vm.group = v!;
 | 
			
		||||
                p.onChange?.();
 | 
			
		||||
              }}
 | 
			
		||||
              value={p.vm.group}
 | 
			
		||||
@@ -222,7 +224,11 @@ function VMDetailsTabGeneral(p: DetailsInnerProps): React.ReactElement {
 | 
			
		||||
                  : "Add a new group instead of using existing one"
 | 
			
		||||
              }
 | 
			
		||||
            >
 | 
			
		||||
              <IconButton onClick={() => { setAddGroup(!addGroup); }}>
 | 
			
		||||
              <IconButton
 | 
			
		||||
                onClick={() => {
 | 
			
		||||
                  setAddGroup(!addGroup);
 | 
			
		||||
                }}
 | 
			
		||||
              >
 | 
			
		||||
                {addGroup ? <ListIcon /> : <AddIcon />}
 | 
			
		||||
              </IconButton>
 | 
			
		||||
            </Tooltip>
 | 
			
		||||
 
 | 
			
		||||
@@ -9,7 +9,7 @@ export function VMScreenshot(p: { vm: VMInfo }): React.ReactElement {
 | 
			
		||||
    string | undefined
 | 
			
		||||
  >();
 | 
			
		||||
 | 
			
		||||
  const int = React.useRef<any | undefined>(undefined);
 | 
			
		||||
  const int = React.useRef<NodeJS.Timeout | undefined>(undefined);
 | 
			
		||||
 | 
			
		||||
  React.useEffect(() => {
 | 
			
		||||
    const refresh = async () => {
 | 
			
		||||
@@ -25,7 +25,9 @@ export function VMScreenshot(p: { vm: VMInfo }): React.ReactElement {
 | 
			
		||||
 | 
			
		||||
    if (int.current === undefined) {
 | 
			
		||||
      refresh();
 | 
			
		||||
      int.current = setInterval(() => refresh(), 5000);
 | 
			
		||||
      int.current = setInterval(() => {
 | 
			
		||||
        refresh();
 | 
			
		||||
      }, 5000);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return () => {
 | 
			
		||||
 
 | 
			
		||||
@@ -31,13 +31,19 @@ export function VMStatusWidget(p: {
 | 
			
		||||
    }
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  const changedAction = () => { setState(undefined); };
 | 
			
		||||
  const changedAction = () => {
 | 
			
		||||
    setState(undefined);
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  React.useEffect(() => {
 | 
			
		||||
    refresh();
 | 
			
		||||
    const i = setInterval(() => refresh(), 3000);
 | 
			
		||||
    const i = setInterval(() => {
 | 
			
		||||
      refresh();
 | 
			
		||||
    }, 3000);
 | 
			
		||||
 | 
			
		||||
    return () => { clearInterval(i); };
 | 
			
		||||
    return () => {
 | 
			
		||||
      clearInterval(i);
 | 
			
		||||
    };
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  if (state === undefined)
 | 
			
		||||
@@ -59,6 +65,7 @@ export function VMStatusWidget(p: {
 | 
			
		||||
            icon={<PersonalVideoIcon />}
 | 
			
		||||
            tooltip="Graphical remote control over the VM"
 | 
			
		||||
            performAction={async () => navigate(p.vm.VNCURL)}
 | 
			
		||||
            // eslint-disable-next-line @typescript-eslint/no-empty-function
 | 
			
		||||
            onExecuted={() => {}}
 | 
			
		||||
          />
 | 
			
		||||
        )
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user