diff --git a/central_frontend/src/api/OTAApi.ts b/central_frontend/src/api/OTAApi.ts index 62b9e36..0e2d9b2 100644 --- a/central_frontend/src/api/OTAApi.ts +++ b/central_frontend/src/api/OTAApi.ts @@ -12,4 +12,22 @@ export class OTAAPI { }) ).data; } + + /** + * Upload new OTA firwmare + */ + static async UploadFirmware( + platform: string, + version: string, + firmware: File + ): Promise { + const fd = new FormData(); + fd.append("firmware", firmware); + + await APIClient.exec({ + method: "POST", + uri: `/ota/${platform}/${version}`, + formData: fd, + }); + } } diff --git a/central_frontend/src/dialogs/UploadUpdateDialog.tsx b/central_frontend/src/dialogs/UploadUpdateDialog.tsx index 30b58e3..713cebc 100644 --- a/central_frontend/src/dialogs/UploadUpdateDialog.tsx +++ b/central_frontend/src/dialogs/UploadUpdateDialog.tsx @@ -1,3 +1,4 @@ +import CloudUploadIcon from "@mui/icons-material/CloudUpload"; import { Button, Dialog, @@ -12,9 +13,12 @@ import { styled, } from "@mui/material"; import React from "react"; +import { OTAAPI } from "../api/OTAApi"; +import { useAlert } from "../hooks/context_providers/AlertDialogProvider"; +import { useLoadingMessage } from "../hooks/context_providers/LoadingMessageProvider"; +import { useSnackbar } from "../hooks/context_providers/SnackbarProvider"; +import { checkVersion } from "../utils/StringsUtils"; import { TextInput } from "../widgets/forms/TextInput"; -import { SemVer } from "semver"; -import CloudUploadIcon from "@mui/icons-material/CloudUpload"; const VisuallyHiddenInput = styled("input")({ clip: "rect(0 0 0 0)", @@ -33,9 +37,32 @@ export function UploadUpdateDialog(p: { onClose: () => void; onCreated: () => void; }): React.ReactElement { + const alert = useAlert(); + const snackbar = useSnackbar(); + const loadingMessage = useLoadingMessage(); + const [platform, setPlatform] = React.useState(); const [version, setVersion] = React.useState(); const [file, setFile] = React.useState(); + + const canSubmit = platform && version && checkVersion(version) && file; + + const upload = async () => { + try { + loadingMessage.show("Uploading firmware..."); + await OTAAPI.UploadFirmware(platform!, version!, file!); + + snackbar("Successfully uploaded new firmware!"); + + p.onCreated(); + } catch (e) { + console.error(e); + alert(`Failed to upload firmware: ${e}`); + } finally { + loadingMessage.hide(); + } + }; + return ( Submit a new update @@ -67,15 +94,7 @@ export function UploadUpdateDialog(p: { helperText="The version shall follow semantics requirements" value={version} onValueChange={setVersion} - checkValue={(v) => { - try { - new SemVer(v, { loose: false }); - return true; - } catch (e) { - console.error(e); - return false; - } - }} + checkValue={checkVersion} />
@@ -104,7 +123,9 @@ export function UploadUpdateDialog(p: { - +
); diff --git a/central_frontend/src/utils/StringsUtils.ts b/central_frontend/src/utils/StringsUtils.ts index 29121d7..a6f6a51 100644 --- a/central_frontend/src/utils/StringsUtils.ts +++ b/central_frontend/src/utils/StringsUtils.ts @@ -1,3 +1,4 @@ +import { SemVer } from "semver"; import { LenConstraint } from "../api/ServerApi"; /** @@ -6,3 +7,16 @@ import { LenConstraint } from "../api/ServerApi"; export function lenValid(s: string, c: LenConstraint): boolean { return s.length >= c.min && s.length <= c.max; } + +/** + * Check out whether a given version number respect semantics requirements or not + */ +export function checkVersion(v: string): boolean { + try { + new SemVer(v, { loose: false }); + return true; + } catch (e) { + console.error(e); + return false; + } +}