diff --git a/virtweb_frontend/eslint.config.js b/virtweb_frontend/eslint.config.js index 1fd4d8a..56bdec9 100644 --- a/virtweb_frontend/eslint.config.js +++ b/virtweb_frontend/eslint.config.js @@ -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", }, } ); diff --git a/virtweb_frontend/src/api/ApiClient.ts b/virtweb_frontend/src/api/ApiClient.ts index 49df36b..7cb9445 100644 --- a/virtweb_frontend/src/api/ApiClient.ts +++ b/virtweb_frontend/src/api/ApiClient.ts @@ -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; } diff --git a/virtweb_frontend/src/routes/IsoFilesRoute.tsx b/virtweb_frontend/src/routes/IsoFilesRoute.tsx index 894ab54..c30a6fd 100644 --- a/virtweb_frontend/src/routes/IsoFilesRoute.tsx +++ b/virtweb_frontend/src/routes/IsoFilesRoute.tsx @@ -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 && } @@ -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); + }} /> { setFilename(e.target.value); }} + onChange={(e) => { + setFilename(e.target.value); + }} /> {url !== "" && actualFileName !== "" && ( @@ -238,7 +244,7 @@ function IsoFilesList(p: { ); - const columns: GridColDef[] = [ + const columns: GridColDef[] = [ { 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} /> diff --git a/virtweb_frontend/src/utils/FilesUtils.ts b/virtweb_frontend/src/utils/FilesUtils.ts index 80e9874..c397fdc 100644 --- a/virtweb_frontend/src/utils/FilesUtils.ts +++ b/virtweb_frontend/src/utils/FilesUtils.ts @@ -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"); diff --git a/virtweb_frontend/src/widgets/forms/MACInput.tsx b/virtweb_frontend/src/widgets/forms/MACInput.tsx index 6a121c0..b7c088c 100644 --- a/virtweb_frontend/src/widgets/forms/MACInput.tsx +++ b/virtweb_frontend/src/widgets/forms/MACInput.tsx @@ -1,3 +1,4 @@ +/* eslint-disable @typescript-eslint/no-unnecessary-condition */ import { TextInput } from "./TextInput"; export function MACInput(p: { diff --git a/virtweb_frontend/src/widgets/forms/NWFilterRules.tsx b/virtweb_frontend/src/widgets/forms/NWFilterRules.tsx index a0cedb0..3622a42 100644 --- a/virtweb_frontend/src/widgets/forms/NWFilterRules.tsx +++ b/virtweb_frontend/src/widgets/forms/NWFilterRules.tsx @@ -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); + }} /> ))} diff --git a/virtweb_frontend/src/widgets/forms/NetDHCPHostReservations.tsx b/virtweb_frontend/src/widgets/forms/NetDHCPHostReservations.tsx index 220cbf4..2425ffd 100644 --- a/virtweb_frontend/src/widgets/forms/NetDHCPHostReservations.tsx +++ b/virtweb_frontend/src/widgets/forms/NetDHCPHostReservations.tsx @@ -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"; diff --git a/virtweb_frontend/src/widgets/forms/NetNatConfiguration.tsx b/virtweb_frontend/src/widgets/forms/NetNatConfiguration.tsx index d4b5cb4..7390dd3 100644 --- a/virtweb_frontend/src/widgets/forms/NetNatConfiguration.tsx +++ b/virtweb_frontend/src/widgets/forms/NetNatConfiguration.tsx @@ -54,6 +54,7 @@ export function NetNatConfiguration(p: { <> {p.nat.map((e, num) => ( ( 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 ( { - return { - label: `${i.filename} ${filesize(i.size)}`, - value: i.filename, - }; - }) - } + return { + label: `${i.filename} ${filesize(i.size)}`, + value: i.filename, + }; + })} /> ); diff --git a/virtweb_frontend/src/widgets/net/NetworkDetails.tsx b/virtweb_frontend/src/widgets/net/NetworkDetails.tsx index faf8a66..238cf90 100644 --- a/virtweb_frontend/src/widgets/net/NetworkDetails.tsx +++ b/virtweb_frontend/src/widgets/net/NetworkDetails.tsx @@ -25,7 +25,7 @@ interface DetailsProps { } export function NetworkDetails(p: DetailsProps): React.ReactElement { - const [nicsList, setNicsList] = React.useState(); + const [nicsList, setNicsList] = React.useState(); 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={() => } + build={() => } /> ); } @@ -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!` )) diff --git a/virtweb_frontend/src/widgets/nwfilter/NWFilterDetails.tsx b/virtweb_frontend/src/widgets/nwfilter/NWFilterDetails.tsx index d72aef9..4db0fb2 100644 --- a/virtweb_frontend/src/widgets/nwfilter/NWFilterDetails.tsx +++ b/virtweb_frontend/src/widgets/nwfilter/NWFilterDetails.tsx @@ -28,7 +28,9 @@ interface DetailsProps { } export function NWFilterDetails(p: DetailsProps): ReactElement { - const [nwFiltersList, setNwFiltersList] = React.useState(); + 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={() => ( - + )} /> ); @@ -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} /> diff --git a/virtweb_frontend/src/widgets/tokens/APITokenDetails.tsx b/virtweb_frontend/src/widgets/tokens/APITokenDetails.tsx index d3eca49..18c5a97 100644 --- a/virtweb_frontend/src/widgets/tokens/APITokenDetails.tsx +++ b/virtweb_frontend/src/widgets/tokens/APITokenDetails.tsx @@ -161,14 +161,14 @@ function APITokenTabGeneral(p: DetailsInnerProps): React.ReactElement { {p.status === TokenWidgetStatus.Create && ( { - setIpVersion(Number(v) as any); + setIpVersion(Number(v) as 4 | 6); }} label="Token IP restriction version" /> diff --git a/virtweb_frontend/src/widgets/tokens/TokenRawRightsEditor.tsx b/virtweb_frontend/src/widgets/tokens/TokenRawRightsEditor.tsx index 1bcd837..e2294d1 100644 --- a/virtweb_frontend/src/widgets/tokens/TokenRawRightsEditor.tsx +++ b/virtweb_frontend/src/widgets/tokens/TokenRawRightsEditor.tsx @@ -63,6 +63,7 @@ export function TokenRawRightsEditor(p: { {p.token.rights.map((r, num) => ( + // eslint-disable-next-line react-x/no-array-index-key {p.editable && ( - { deleteRule(num); }}> + { + deleteRule(num); + }} + > diff --git a/virtweb_frontend/src/widgets/tokens/TokenRightsEditor.tsx b/virtweb_frontend/src/widgets/tokens/TokenRightsEditor.tsx index a5891ee..dbb3c9a 100644 --- a/virtweb_frontend/src/widgets/tokens/TokenRightsEditor.tsx +++ b/virtweb_frontend/src/widgets/tokens/TokenRightsEditor.tsx @@ -85,8 +85,8 @@ export function TokenRightsEditor(p: { {/* Per VM operations */} - {p.vms.map((v, n) => ( - + {p.vms.map((v) => ( + {v.name} {/* Per VM operations */} - {p.vms.map((v, n) => ( - + {p.vms.map((v) => ( + {v.name} {/* Per VM operations */} - {p.groups.map((v, n) => ( - + {p.groups.map((v) => ( + {v} {/* Per network operations */} - {p.networks.map((v, n) => ( - + {p.networks.map((v) => ( + {v.name} {/* Per network filter operations */} - {p.nwFilters.map((v, n) => ( - + {p.nwFilters.map((v) => ( + {v.name} {/* Per API token operations */} - {p.tokens.map((v, n) => ( - + {p.tokens.map((v) => ( + {v.name} { toggle(a); }} + onChange={(_e, a) => { + toggle(a); + }} /> } label={p.label} @@ -814,7 +816,9 @@ function RouteRight(p: RightOpts): React.ReactElement { { toggle(a); }} + onChange={(_e, a) => { + toggle(a); + }} /> )} diff --git a/virtweb_frontend/src/widgets/vms/VMDetails.tsx b/virtweb_frontend/src/widgets/vms/VMDetails.tsx index 1b338af..912885c 100644 --- a/virtweb_frontend/src/widgets/vms/VMDetails.tsx +++ b/virtweb_frontend/src/widgets/vms/VMDetails.tsx @@ -35,14 +35,16 @@ interface DetailsProps { } export function VMDetails(p: DetailsProps): React.ReactElement { - const [groupsList, setGroupsList] = React.useState(); - const [isoList, setIsoList] = React.useState(); + const [groupsList, setGroupsList] = React.useState(); + const [isoList, setIsoList] = React.useState(); const [vcpuCombinations, setVCPUCombinations] = React.useState< - number[] | any + number[] | undefined + >(); + const [networksList, setNetworksList] = React.useState< + NetworkInfo[] | undefined >(); - const [networksList, setNetworksList] = React.useState(); 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={() => ( )} @@ -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" } > - { setAddGroup(!addGroup); }}> + { + setAddGroup(!addGroup); + }} + > {addGroup ? : } diff --git a/virtweb_frontend/src/widgets/vms/VMScreenshot.tsx b/virtweb_frontend/src/widgets/vms/VMScreenshot.tsx index 9974452..e043ac9 100644 --- a/virtweb_frontend/src/widgets/vms/VMScreenshot.tsx +++ b/virtweb_frontend/src/widgets/vms/VMScreenshot.tsx @@ -9,7 +9,7 @@ export function VMScreenshot(p: { vm: VMInfo }): React.ReactElement { string | undefined >(); - const int = React.useRef(undefined); + const int = React.useRef(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 () => { diff --git a/virtweb_frontend/src/widgets/vms/VMStatusWidget.tsx b/virtweb_frontend/src/widgets/vms/VMStatusWidget.tsx index 5b4881a..015a7c3 100644 --- a/virtweb_frontend/src/widgets/vms/VMStatusWidget.tsx +++ b/virtweb_frontend/src/widgets/vms/VMStatusWidget.tsx @@ -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={} tooltip="Graphical remote control over the VM" performAction={async () => navigate(p.vm.VNCURL)} + // eslint-disable-next-line @typescript-eslint/no-empty-function onExecuted={() => {}} /> )