code quality improvements and improved file error handling
This commit is contained in:
@@ -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",
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}}
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
64
src/components/SettingForms/System/Upload.ts
Normal file
64
src/components/SettingForms/System/Upload.ts
Normal 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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
|
||||
@@ -36,7 +36,7 @@ const SightingModal = ({
|
||||
className="w-full h-56 sm:h-72 md:h-96 rounded-lg object-cover border border-gray-700"
|
||||
/>
|
||||
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="flex flex-col md:flex-row items-center gap-3">
|
||||
<img
|
||||
src={sighting?.plateUrlColour}
|
||||
alt="plate patch"
|
||||
|
||||
@@ -16,13 +16,15 @@ const SightingOverview = () => {
|
||||
setOverlayMode((m) => ((m + 1) % 3) as 0 | 1 | 2);
|
||||
}, []);
|
||||
|
||||
const { side, mostRecent } = useSightingFeedContext();
|
||||
const { side, mostRecent, isError, isLoading } = useSightingFeedContext();
|
||||
|
||||
useOverviewOverlay(mostRecent, overlayMode, imgRef, canvasRef);
|
||||
|
||||
const { sync } = useHiDPICanvas(imgRef, canvasRef);
|
||||
|
||||
// if (noSighting || isPending) return <p>loading</p>;
|
||||
if (isLoading) return <p>Loading</p>;
|
||||
|
||||
if (isError) return <p>An error occurred, Cannot display footage</p>;
|
||||
|
||||
return (
|
||||
<div className="flex flex-col">
|
||||
|
||||
@@ -16,7 +16,7 @@ const ModalComponent = ({
|
||||
<Modal
|
||||
isOpen={isModalOpen}
|
||||
onRequestClose={close}
|
||||
className="bg-[#1e2a38] p-6 rounded-lg shadow-lg max-w-[90%] mx-auto mt-20 md:w-[70%] md:h-[80%] z-100"
|
||||
className="bg-[#1e2a38] p-6 rounded-lg shadow-lg max-w-[90%] mx-auto mt-[1%] md:w-[70%] md:h-[90%] z-[100] overflow-y-auto max-h-screen"
|
||||
overlayClassName="fixed inset-0 bg-[#1e2a38]/70 flex justify-center items-start z-100"
|
||||
>
|
||||
{children}
|
||||
|
||||
Reference in New Issue
Block a user