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