This commit is contained in:
		@@ -46,6 +46,7 @@ export default tseslint.config(
 | 
			
		||||
      "@typescript-eslint/no-unsafe-assignment": "off",
 | 
			
		||||
      "@typescript-eslint/no-unsafe-return": "off",
 | 
			
		||||
      "@typescript-eslint/no-unsafe-call": "off",
 | 
			
		||||
      "@typescript-eslint/no-unsafe-member-access": "off",
 | 
			
		||||
      "@typescript-eslint/no-unsafe-argument": "off",
 | 
			
		||||
      "react-refresh/only-export-components": "off",
 | 
			
		||||
    },
 | 
			
		||||
 
 | 
			
		||||
@@ -66,22 +66,25 @@ export class APIClient {
 | 
			
		||||
    if (args.upProgress) {
 | 
			
		||||
      const res: XMLHttpRequest = await new Promise((resolve, reject) => {
 | 
			
		||||
        const xhr = new XMLHttpRequest();
 | 
			
		||||
        xhr.upload.addEventListener("progress", (e) =>
 | 
			
		||||
          { args.upProgress!(e.loaded / e.total); }
 | 
			
		||||
        );
 | 
			
		||||
        xhr.addEventListener("load", () => { resolve(xhr); });
 | 
			
		||||
        xhr.addEventListener("error", () =>
 | 
			
		||||
          { reject(new Error("File upload failed")); }
 | 
			
		||||
        );
 | 
			
		||||
        xhr.addEventListener("abort", () =>
 | 
			
		||||
          { reject(new Error("File upload aborted")); }
 | 
			
		||||
        );
 | 
			
		||||
        xhr.addEventListener("timeout", () =>
 | 
			
		||||
          { reject(new Error("File upload timeout")); }
 | 
			
		||||
        );
 | 
			
		||||
        xhr.upload.addEventListener("progress", (e) => {
 | 
			
		||||
          args.upProgress!(e.loaded / e.total);
 | 
			
		||||
        });
 | 
			
		||||
        xhr.addEventListener("load", () => {
 | 
			
		||||
          resolve(xhr);
 | 
			
		||||
        });
 | 
			
		||||
        xhr.addEventListener("error", () => {
 | 
			
		||||
          reject(new Error("File upload failed"));
 | 
			
		||||
        });
 | 
			
		||||
        xhr.addEventListener("abort", () => {
 | 
			
		||||
          reject(new Error("File upload aborted"));
 | 
			
		||||
        });
 | 
			
		||||
        xhr.addEventListener("timeout", () => {
 | 
			
		||||
          reject(new Error("File upload timeout"));
 | 
			
		||||
        });
 | 
			
		||||
        xhr.open(args.method, url, true);
 | 
			
		||||
        xhr.withCredentials = true;
 | 
			
		||||
        for (const key in headers) {
 | 
			
		||||
          // eslint-disable-next-line no-prototype-builtins
 | 
			
		||||
          if (headers.hasOwnProperty(key))
 | 
			
		||||
            xhr.setRequestHeader(key, headers[key]);
 | 
			
		||||
        }
 | 
			
		||||
 
 | 
			
		||||
@@ -15,7 +15,7 @@ export function CreateVMRoute(): React.ReactElement {
 | 
			
		||||
  const alert = useAlert();
 | 
			
		||||
  const navigate = useNavigate();
 | 
			
		||||
 | 
			
		||||
  const [vm, setVM] = React.useState(VMInfo.NewEmpty);
 | 
			
		||||
  const [vm, setVM] = React.useState(VMInfo.NewEmpty());
 | 
			
		||||
 | 
			
		||||
  const create = async (v: VMInfo) => {
 | 
			
		||||
    try {
 | 
			
		||||
@@ -103,7 +103,9 @@ function EditVMInner(p: {
 | 
			
		||||
  const [changed, setChanged] = React.useState(false);
 | 
			
		||||
 | 
			
		||||
  const [, updateState] = React.useState<any>();
 | 
			
		||||
  const forceUpdate = React.useCallback(() => { updateState({}); }, []);
 | 
			
		||||
  const forceUpdate = React.useCallback(() => {
 | 
			
		||||
    updateState({});
 | 
			
		||||
  }, []);
 | 
			
		||||
 | 
			
		||||
  const valueChanged = () => {
 | 
			
		||||
    setChanged(true);
 | 
			
		||||
 
 | 
			
		||||
@@ -206,7 +206,7 @@ function IsoFilesList(p: {
 | 
			
		||||
    try {
 | 
			
		||||
      const blob = await IsoFilesApi.Download(entry, setDlProgress);
 | 
			
		||||
 | 
			
		||||
      await downloadBlob(blob, entry.filename);
 | 
			
		||||
      downloadBlob(blob, entry.filename);
 | 
			
		||||
    } catch (e) {
 | 
			
		||||
      console.error(e);
 | 
			
		||||
      alert("Failed to download iso file!");
 | 
			
		||||
 
 | 
			
		||||
@@ -66,7 +66,7 @@ function NetworkFiltersListRouteInner(p: {
 | 
			
		||||
    const onlyBuiltin = visibleFilters === VisibleFilters.Builtin;
 | 
			
		||||
 | 
			
		||||
    return p.list.filter((f) => NWFilterIsBuiltin(f) === onlyBuiltin);
 | 
			
		||||
  }, [visibleFilters]);
 | 
			
		||||
  }, [visibleFilters, p.list]);
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <VirtWebRouteContainer
 | 
			
		||||
@@ -78,7 +78,9 @@ function NetworkFiltersListRouteInner(p: {
 | 
			
		||||
            size="small"
 | 
			
		||||
            value={visibleFilters}
 | 
			
		||||
            exclusive
 | 
			
		||||
            onChange={(_ev, v) => { setVisibleFilters(v); }}
 | 
			
		||||
            onChange={(_ev, v) => {
 | 
			
		||||
              setVisibleFilters(v);
 | 
			
		||||
            }}
 | 
			
		||||
            aria-label="visible filters"
 | 
			
		||||
          >
 | 
			
		||||
            <ToggleButton value={VisibleFilters.All}>All</ToggleButton>
 | 
			
		||||
@@ -130,8 +132,8 @@ function NetworkFiltersListRouteInner(p: {
 | 
			
		||||
                  </TableCell>
 | 
			
		||||
                  <TableCell>
 | 
			
		||||
                    <ul>
 | 
			
		||||
                      {t.join_filters.map((f, n) => (
 | 
			
		||||
                        <li key={n}>{f}</li>
 | 
			
		||||
                      {t.join_filters.map((f) => (
 | 
			
		||||
                        <li key={f}>{f}</li>
 | 
			
		||||
                      ))}
 | 
			
		||||
                    </ul>
 | 
			
		||||
                  </TableCell>
 | 
			
		||||
 
 | 
			
		||||
@@ -1,3 +1,4 @@
 | 
			
		||||
/* eslint-disable react-x/no-array-index-key */
 | 
			
		||||
import {
 | 
			
		||||
  mdiHarddisk,
 | 
			
		||||
  mdiInformation,
 | 
			
		||||
 
 | 
			
		||||
@@ -1,3 +1,4 @@
 | 
			
		||||
/* eslint-disable react-x/no-array-index-key */
 | 
			
		||||
import VisibilityIcon from "@mui/icons-material/Visibility";
 | 
			
		||||
import {
 | 
			
		||||
  Button,
 | 
			
		||||
@@ -99,9 +100,9 @@ export function TokensListRouteInner(p: {
 | 
			
		||||
                    {t.max_inactivity && timeDiff(0, t.max_inactivity)}
 | 
			
		||||
                  </TableCell>
 | 
			
		||||
                  <TableCell>
 | 
			
		||||
                    {t.rights.map((r) => {
 | 
			
		||||
                    {t.rights.map((r, n) => {
 | 
			
		||||
                      return (
 | 
			
		||||
                        <div>
 | 
			
		||||
                        <div key={n}>
 | 
			
		||||
                          {r.verb} {r.path}
 | 
			
		||||
                        </div>
 | 
			
		||||
                      );
 | 
			
		||||
 
 | 
			
		||||
@@ -115,8 +115,8 @@ function VMListWidget(p: {
 | 
			
		||||
          </TableRow>
 | 
			
		||||
        </TableHead>
 | 
			
		||||
        <TableBody>
 | 
			
		||||
          {p.groups.map((g, num) => (
 | 
			
		||||
            <React.Fragment key={num}>
 | 
			
		||||
          {p.groups.map((g) => (
 | 
			
		||||
            <React.Fragment key={g}>
 | 
			
		||||
              {p.groups.length > 1 && (
 | 
			
		||||
                <TableRow>
 | 
			
		||||
                  <TableCell
 | 
			
		||||
@@ -125,7 +125,9 @@ function VMListWidget(p: {
 | 
			
		||||
                  >
 | 
			
		||||
                    <IconButton
 | 
			
		||||
                      size="small"
 | 
			
		||||
                      onClick={() => { toggleHiddenGroup(g); }}
 | 
			
		||||
                      onClick={() => {
 | 
			
		||||
                        toggleHiddenGroup(g);
 | 
			
		||||
                      }}
 | 
			
		||||
                    >
 | 
			
		||||
                      {!hiddenGroups.has(g) ? (
 | 
			
		||||
                        <KeyboardArrowUpIcon />
 | 
			
		||||
@@ -157,7 +159,9 @@ function VMListWidget(p: {
 | 
			
		||||
                      <TableCell>
 | 
			
		||||
                        <VMStatusWidget
 | 
			
		||||
                          vm={row}
 | 
			
		||||
                          onChange={(s) => { updateVMState(row, s); }}
 | 
			
		||||
                          onChange={(s) => {
 | 
			
		||||
                            updateVMState(row, s);
 | 
			
		||||
                          }}
 | 
			
		||||
                        />
 | 
			
		||||
                      </TableCell>
 | 
			
		||||
                      <TableCell>
 | 
			
		||||
 
 | 
			
		||||
@@ -44,8 +44,8 @@ function VNCInner(p: { vm: VMInfo }): React.ReactElement {
 | 
			
		||||
  const [counter, setCounter] = React.useState(1);
 | 
			
		||||
  const [connected, setConnected] = React.useState(false);
 | 
			
		||||
 | 
			
		||||
  const vncRef = React.createRef<HTMLDivElement>();
 | 
			
		||||
  const vncScreenRef = React.createRef<VncScreenHandle>();
 | 
			
		||||
  const vncRef = React.useRef<HTMLDivElement>(null);
 | 
			
		||||
  const vncScreenRef = React.useRef<VncScreenHandle>(null);
 | 
			
		||||
 | 
			
		||||
  const connect = async (force: boolean) => {
 | 
			
		||||
    try {
 | 
			
		||||
@@ -91,7 +91,9 @@ function VNCInner(p: { vm: VMInfo }): React.ReactElement {
 | 
			
		||||
    connect(false);
 | 
			
		||||
 | 
			
		||||
    if (vncRef.current) {
 | 
			
		||||
      vncRef.current.onfullscreenchange = () => { setCounter(counter + 1); };
 | 
			
		||||
      vncRef.current.onfullscreenchange = () => {
 | 
			
		||||
        setCounter(counter + 1);
 | 
			
		||||
      };
 | 
			
		||||
    }
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
@@ -143,7 +145,9 @@ function VNCInner(p: { vm: VMInfo }): React.ReactElement {
 | 
			
		||||
            console.info("VNC disconnected " + token.url);
 | 
			
		||||
            disconnected();
 | 
			
		||||
          }}
 | 
			
		||||
          onConnect={() => { setConnected(true); }}
 | 
			
		||||
          onConnect={() => {
 | 
			
		||||
            setConnected(true);
 | 
			
		||||
          }}
 | 
			
		||||
        />
 | 
			
		||||
      </div>
 | 
			
		||||
    </div>
 | 
			
		||||
 
 | 
			
		||||
@@ -2,8 +2,9 @@
 | 
			
		||||
 * Generate a random MAC address
 | 
			
		||||
 */
 | 
			
		||||
export function randomMacAddress(prefix: string | undefined): string {
 | 
			
		||||
  prefix = prefix ?? "";
 | 
			
		||||
  let mac = "XX:XX:XX:XX:XX:XX";
 | 
			
		||||
  mac = prefix + mac.slice(prefix?.length);
 | 
			
		||||
  mac = prefix + mac.slice(prefix.length);
 | 
			
		||||
 | 
			
		||||
  return mac.replace(/X/g, () =>
 | 
			
		||||
    "0123456789abcdef".charAt(Math.floor(Math.random() * 16))
 | 
			
		||||
 
 | 
			
		||||
@@ -19,7 +19,7 @@ export function AsyncWidget(p: {
 | 
			
		||||
}): React.ReactElement {
 | 
			
		||||
  const [state, setState] = useState(State.Loading);
 | 
			
		||||
 | 
			
		||||
  const counter = useRef<any | null>(null);
 | 
			
		||||
  const counter = useRef<any>(null);
 | 
			
		||||
 | 
			
		||||
  const load = async () => {
 | 
			
		||||
    try {
 | 
			
		||||
 
 | 
			
		||||
@@ -31,9 +31,11 @@ export function ConfigImportExportButtons(p: {
 | 
			
		||||
      fileEl.click();
 | 
			
		||||
 | 
			
		||||
      // Wait for a file to be chosen
 | 
			
		||||
      await new Promise((res, _rej) =>
 | 
			
		||||
        { fileEl.addEventListener("change", () => { res(null); }); }
 | 
			
		||||
      );
 | 
			
		||||
      await new Promise((res) => {
 | 
			
		||||
        fileEl.addEventListener("change", () => {
 | 
			
		||||
          res(null);
 | 
			
		||||
        });
 | 
			
		||||
      });
 | 
			
		||||
 | 
			
		||||
      if ((fileEl.files?.length ?? 0) === 0) return null;
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -23,7 +23,7 @@ export function StateActionButton<S>(p: {
 | 
			
		||||
      p.onExecuted();
 | 
			
		||||
    } catch (e) {
 | 
			
		||||
      console.error(e);
 | 
			
		||||
      alert("Failed to perform action! " + e);
 | 
			
		||||
      alert(`Failed to perform action! ${e}`);
 | 
			
		||||
    }
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,3 +1,4 @@
 | 
			
		||||
/* eslint-disable react-x/no-array-index-key */
 | 
			
		||||
import { Box, Tab, Tabs } from "@mui/material";
 | 
			
		||||
 | 
			
		||||
export interface TabWidgetOption<E> {
 | 
			
		||||
@@ -24,7 +25,9 @@ export function TabsWidget<E>(p: {
 | 
			
		||||
    <Box sx={{ borderBottom: 1, borderColor: "divider" }}>
 | 
			
		||||
      <Tabs
 | 
			
		||||
        value={currTabIndex}
 | 
			
		||||
        onChange={(_ev, newVal) => { updateActiveTab(newVal); }}
 | 
			
		||||
        onChange={(_ev, newVal) => {
 | 
			
		||||
          updateActiveTab(newVal);
 | 
			
		||||
        }}
 | 
			
		||||
      >
 | 
			
		||||
        {activeOptions.map((o, index) => (
 | 
			
		||||
          <Tab key={index} label={o.label} style={{ color: o.color }} />
 | 
			
		||||
 
 | 
			
		||||
@@ -1,3 +1,4 @@
 | 
			
		||||
/* eslint-disable @typescript-eslint/prefer-nullish-coalescing */
 | 
			
		||||
import { Paper, Typography } from "@mui/material";
 | 
			
		||||
import React, { PropsWithChildren } from "react";
 | 
			
		||||
import Grid from "@mui/material/Grid";
 | 
			
		||||
 
 | 
			
		||||
@@ -1,3 +1,4 @@
 | 
			
		||||
/* eslint-disable @typescript-eslint/no-unnecessary-condition */
 | 
			
		||||
import React from "react";
 | 
			
		||||
import { TextInput } from "./TextInput";
 | 
			
		||||
 | 
			
		||||
@@ -32,7 +33,7 @@ export function IPInputWithMask(p: {
 | 
			
		||||
 | 
			
		||||
  const currValue =
 | 
			
		||||
    p.ipAndMask ??
 | 
			
		||||
    (p.ip ?? "") + (p.mask || showSlash.current ? "/" : "") + (p.mask ?? "");
 | 
			
		||||
    `${p.ip ?? ""}${p.mask || showSlash.current ? "/" : ""}${p.mask ?? ""}`;
 | 
			
		||||
 | 
			
		||||
  const { onValueChange, ...props } = p;
 | 
			
		||||
  return (
 | 
			
		||||
 
 | 
			
		||||
@@ -1,3 +1,5 @@
 | 
			
		||||
/* eslint-disable react-x/no-array-index-key */
 | 
			
		||||
/* eslint-disable react-hooks/exhaustive-deps */
 | 
			
		||||
import React from "react";
 | 
			
		||||
import { useNavigate } from "react-router-dom";
 | 
			
		||||
import { NWFilter, NWFilterURL } from "../../api/NWFilterApi";
 | 
			
		||||
 
 | 
			
		||||
@@ -25,9 +25,7 @@ export function NWFilterSelectInput(p: {
 | 
			
		||||
        value={selectedValue}
 | 
			
		||||
        onDelete={p.editable ? () => p.onChange?.(undefined) : undefined}
 | 
			
		||||
        onClick={
 | 
			
		||||
          !p.editable && selectedValue
 | 
			
		||||
            ? () => navigate(NWFilterURL(selectedValue))
 | 
			
		||||
            : undefined
 | 
			
		||||
          !p.editable ? () => navigate(NWFilterURL(selectedValue)) : undefined
 | 
			
		||||
        }
 | 
			
		||||
      />
 | 
			
		||||
    );
 | 
			
		||||
@@ -48,7 +46,7 @@ export function NWFilterSelectInput(p: {
 | 
			
		||||
      renderInput={(params) => (
 | 
			
		||||
        <TextField {...params} variant="standard" label={p.label} />
 | 
			
		||||
      )}
 | 
			
		||||
      renderOption={(_props, option, _state) => (
 | 
			
		||||
      renderOption={(_props, option) => (
 | 
			
		||||
        <NWFilterItem
 | 
			
		||||
          dense
 | 
			
		||||
          onClick={() => {
 | 
			
		||||
 
 | 
			
		||||
@@ -24,10 +24,13 @@ export function RadioGroupInput(p: {
 | 
			
		||||
      <RadioGroup
 | 
			
		||||
        row
 | 
			
		||||
        value={p.value}
 | 
			
		||||
        onChange={(_ev, v) => { p.onValueChange(v); }}
 | 
			
		||||
        onChange={(_ev, v) => {
 | 
			
		||||
          p.onValueChange(v);
 | 
			
		||||
        }}
 | 
			
		||||
      >
 | 
			
		||||
        {p.options.map((o) => (
 | 
			
		||||
          <FormControlLabel
 | 
			
		||||
            key={o.value}
 | 
			
		||||
            disabled={!p.editable}
 | 
			
		||||
            value={o.value}
 | 
			
		||||
            control={<Radio />}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,3 +1,4 @@
 | 
			
		||||
/* eslint-disable react-x/no-array-index-key */
 | 
			
		||||
import { mdiNetworkOutline } from "@mdi/js";
 | 
			
		||||
import Icon from "@mdi/react";
 | 
			
		||||
import DeleteIcon from "@mui/icons-material/Delete";
 | 
			
		||||
@@ -51,7 +52,6 @@ export function VMNetworksList(p: {
 | 
			
		||||
        {p.vm.networks.map((n, num) => (
 | 
			
		||||
          <EditSection key={num}>
 | 
			
		||||
            <NetworkInfoWidget
 | 
			
		||||
              key={num}
 | 
			
		||||
              network={n}
 | 
			
		||||
              removeFromList={() => {
 | 
			
		||||
                p.vm.networks.splice(num, 1);
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user