code quality improvements and improved file error handling

This commit is contained in:
2025-09-17 11:39:26 +01:00
parent b98e3ed85d
commit 0b7ab3b0de
20 changed files with 226 additions and 144 deletions

View File

@@ -13,9 +13,9 @@ const NPEDFields = () => {
const initialValues = user
? {
username: user.propUsername.value,
password: user.propPassword.value,
clientId: user.propClientID.value,
username: user?.propUsername?.value,
password: user?.propPassword?.value,
clientId: user?.propClientID?.value,
frontId: "NPED",
rearId: "NPED",
}

View File

@@ -1,13 +1,26 @@
import { Form, Formik } from "formik";
import type { HotlistUploadType } from "../../../types/types";
import { useSystemConfig } from "../../../hooks/useSystemConfig";
const NPEDHotlist = () => {
const { uploadSettings } = useSystemConfig();
const initialValue = {
file: null,
};
const handleSubmit = (values: HotlistUploadType) => console.log(values.file);
// upload/hotlist-upload/2
const handleSubmit = (values: HotlistUploadType) => {
const settings = {
file: values.file,
opts: {
timeoutMs: 30000,
fieldName: "upload",
uploadUrl: "http://192.168.75.11/upload/hotlist-upload/2",
},
};
uploadSettings(settings);
};
return (
<Formik initialValues={initialValue} onSubmit={handleSubmit}>
{({ setFieldValue, setErrors, errors }) => {
@@ -24,21 +37,21 @@ const NPEDHotlist = () => {
setErrors({
file: "This file is not a CSV, please select a different one",
});
return;
}
setFieldValue("file", e.target.files[0]);
} else {
setErrors({ file: "no file" });
}
}}
/>
<button
type="submit"
className="w-1/4 text-white bg-green-700 hover:bg-green-800 font-small rounded-lg text-sm px-2 py-2.5"
disabled={errors ? true : false}
// disabled={errors ? true : false}
>
Upload
</button>
<p>{errors && errors.file}</p>
<p>{errors.file && errors.file}</p>
</Form>
);
}}

View File

@@ -139,6 +139,10 @@ const SystemConfigFields = () => {
name={"softwareUpdate"}
selectedFile={values.softwareUpdate}
/>
<div className="border-b border-gray-600">
<p>Reboot</p>
</div>
<button
type="button"
className="bg-red-600 text-white px-4 py-2 rounded hover:bg-red-700 transition w-full max-w-md"

View File

@@ -14,16 +14,24 @@ const SystemFileUpload = ({ name, selectedFile }: SystemFileUploadProps) => {
const handleFileUploadClick = () => {
if (!selectedFile) return;
uploadSettings(selectedFile, {
timeoutMs: 30000,
fieldName: "upload",
});
const settings = {
file: selectedFile,
opts: {
timeoutMs: 30000,
fieldName: "upload",
uploadUrl: "http://192.168.75.11/upload/software-update/2'",
},
};
uploadSettings(settings);
};
return (
<div className="py-8 w-full">
<div className="border-b border-gray-600">
<h2>Software Update file upload</h2>
</div>
<FormGroup>
<div className="flex-1 flex justify-end md:w-2/3">
<div className="flex-1 flex md:w-2/3 my-5">
<input
type="file"
name="softwareUpdate"

View File

@@ -0,0 +1,64 @@
// CORS (server missing Access-Control-Allow-* headers)??
type BlobFileUpload = {
file: File | null;
opts?: {
timeoutMs?: number;
fieldName?: string;
overrideFileName?: string;
uploadUrl: URL | string;
};
};
export async function sendBlobFileUpload({
file,
opts,
}: BlobFileUpload): Promise<string> {
if (!file) throw new Error("No file supplied");
if (!opts?.uploadUrl) throw new Error("No URL supplied");
if (file?.type !== "text/csv") {
throw new Error("This file is not supported, please upload a CSV file.");
}
const timeoutMs = opts?.timeoutMs ?? 30000;
const fieldName = opts?.fieldName ?? "upload";
const fileName = opts?.overrideFileName ?? file?.name;
const controller = new AbortController();
const timeout = setTimeout(() => controller.abort(), timeoutMs);
try {
const form = new FormData();
form.append(fieldName, file, fileName);
const resp = await fetch(opts?.uploadUrl, {
method: "POST",
body: form,
signal: controller.signal,
redirect: "follow",
});
const bodyText = await resp.text();
if (!resp.ok) {
throw new Error(
`Upload failed (${resp.status} ${resp.statusText}) from ${opts.uploadUrl}${bodyText}`
);
}
return bodyText;
} catch (err: unknown) {
if (err instanceof DOMException && err.name === "AbortError") {
throw new Error(`Timeout uploading to ${opts.uploadUrl}.`);
}
// In browsers, fetch throws TypeError on network-level failures
if (err instanceof TypeError) {
throw new Error(
`HTTP error uploading to ${opts.uploadUrl}: ${err.message}`
);
}
throw new Error("HTTP method POST is not supported by this URL");
} finally {
clearTimeout(timeout);
}
}

View File

@@ -1,47 +0,0 @@
// CORS (server missing Access-Control-Allow-* headers)??
export async function sendBlobFileUpload(
file: File,
opts?: { timeoutMs?: number; fieldName?: string; overrideFileName?: string }
): Promise<string> {
const timeoutMs = opts?.timeoutMs ?? 30000;
const fieldName = opts?.fieldName ?? "upload";
const fileName = opts?.overrideFileName ?? file.name;
const controller = new AbortController();
const timeout = setTimeout(() => controller.abort(), timeoutMs);
try {
const form = new FormData();
form.append(fieldName, file, fileName);
const resp = await fetch("http://192.168.75.11/upload/hotlist-upload/2", {
method: "POST",
body: form,
signal: controller.signal,
redirect: "follow",
});
const bodyText = await resp.text();
if (!resp.ok) {
// throw new Error(
// `Server returned ${resp.status}: ${resp.statusText}. Details: ${bodyText}`
// );
return `Server returned ${resp.status}: ${resp.statusText}. Details: ${bodyText}`;
}
return bodyText;
} catch (err: any) {
if (err?.name === "AbortError") {
return `Timeout uploading to /upload/software-update/2.`;
}
// In browsers, fetch throws TypeError on network-level failures
if (err instanceof TypeError) {
return `HTTP error uploading to /upload/software-update/2: ${err.message}`;
}
return `Unexpected error uploading to /upload/software-update/2: ${
err?.message ?? String(err)
} ${err?.cause ?? ""}`;
} finally {
clearTimeout(timeout);
}
}

View File

@@ -11,9 +11,15 @@ const WiFiCard = () => {
return (
<Card className="mb-4">
<CardHeader title={"WiFi"} />
<div className="flex flex-col gap-4">
<FormGroup>
<label htmlFor="ssid" className="font-medium whitespace-nowrap md:w-2/3">SSID</label>
<label
htmlFor="ssid"
className="font-medium whitespace-nowrap md:w-2/3"
>
SSID
</label>
<input
id="ssid"
name="ssid"
@@ -21,11 +27,16 @@ const WiFiCard = () => {
className="p-2 border border-gray-400 rounded-lg flex-1 md:w-2/3"
placeholder="Enter SSID"
value={ssid}
onChange={e => setSsid(e.target.value)}
onChange={(e) => setSsid(e.target.value)}
/>
</FormGroup>
<FormGroup>
<label htmlFor="password" className="font-medium whitespace-nowrap md:w-2/3">Password</label>
<label
htmlFor="password"
className="font-medium whitespace-nowrap md:w-2/3"
>
Password
</label>
<input
id="password"
name="password"
@@ -33,17 +44,22 @@ const WiFiCard = () => {
className="p-2 border border-gray-400 rounded-lg flex-1 md:w-2/3"
placeholder="Enter Password"
value={password}
onChange={e => setPassword(e.target.value)}
onChange={(e) => setPassword(e.target.value)}
/>
</FormGroup>
<FormGroup>
<label htmlFor="encryption" className="font-medium whitespace-nowrap md:w-2/3">WPA/Encryption Type</label>
<label
htmlFor="encryption"
className="font-medium whitespace-nowrap md:w-2/3"
>
WPA/Encryption Type
</label>
<select
id="encryption"
name="encryption"
className="p-2 border border-gray-400 rounded-lg text-white bg-[#253445] flex-1 md:w-2/3"
value={encryption}
onChange={e => setEncryption(e.target.value)}
onChange={(e) => setEncryption(e.target.value)}
>
<option value="WPA2">WPA2</option>
<option value="WPA3">WPA3</option>