Display the list of OTA updates in the frontend

This commit is contained in:
Pierre HUBERT 2024-10-08 21:53:21 +02:00
parent 2924d14281
commit 6cf7c2cae1
8 changed files with 113 additions and 5 deletions

View File

@ -49,3 +49,14 @@ pub fn get_ota_updates_for_platform(platform: OTAPlatform) -> anyhow::Result<Vec
Ok(out) Ok(out)
} }
/// Get all the available OTA updates
pub fn get_all_ota_updates() -> anyhow::Result<Vec<OTAUpdate>> {
let mut out = vec![];
for p in OTAPlatform::supported_platforms() {
out.append(&mut get_ota_updates_for_platform(*p)?)
}
Ok(out)
}

View File

@ -7,6 +7,13 @@ pub enum OTAPlatform {
Wt32Eth01, Wt32Eth01,
} }
impl OTAPlatform {
/// Get the list of supported platforms
pub fn supported_platforms() -> &'static [Self] {
&[OTAPlatform::Wt32Eth01]
}
}
impl Display for OTAPlatform { impl Display for OTAPlatform {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
let s = serde_json::to_string(&self).unwrap().replace('"', ""); let s = serde_json::to_string(&self).unwrap().replace('"', "");

View File

@ -191,7 +191,7 @@ pub async fn secure_server(energy_actor: EnergyActorAddr) -> anyhow::Result<()>
"/web_api/ota/{platform}/{version}", "/web_api/ota/{platform}/{version}",
web::post().to(ota_controller::upload_firmware), web::post().to(ota_controller::upload_firmware),
) )
// TODO : list all ota software updates .route("/web_api/ota", web::get().to(ota_controller::list_all_ota))
.route( .route(
"/web_api/ota/{platform}", "/web_api/ota/{platform}",
web::get().to(ota_controller::list_updates_platform), web::get().to(ota_controller::list_updates_platform),

View File

@ -10,7 +10,7 @@ use actix_multipart::form::MultipartForm;
use actix_web::{web, HttpResponse}; use actix_web::{web, HttpResponse};
pub async fn supported_platforms() -> HttpResult { pub async fn supported_platforms() -> HttpResult {
Ok(HttpResponse::Ok().json(vec![OTAPlatform::Wt32Eth01])) Ok(HttpResponse::Ok().json(OTAPlatform::supported_platforms()))
} }
#[derive(Debug, MultipartForm)] #[derive(Debug, MultipartForm)]
@ -53,6 +53,11 @@ pub async fn upload_firmware(
Ok(HttpResponse::Accepted().body("OTA update successfully saved.")) Ok(HttpResponse::Accepted().body("OTA update successfully saved."))
} }
/// Get the list of all OTA updates
pub async fn list_all_ota() -> HttpResult {
Ok(HttpResponse::Ok().json(ota_manager::get_all_ota_updates()?))
}
#[derive(serde::Deserialize)] #[derive(serde::Deserialize)]
pub struct ListOTAPath { pub struct ListOTAPath {
platform: OTAPlatform, platform: OTAPlatform,

View File

@ -20,6 +20,7 @@
"@types/semver": "^7.5.8", "@types/semver": "^7.5.8",
"date-and-time": "^3.6.0", "date-and-time": "^3.6.0",
"dayjs": "^1.11.13", "dayjs": "^1.11.13",
"filesize": "^10.1.6",
"react": "^18.3.1", "react": "^18.3.1",
"react-dom": "^18.3.1", "react-dom": "^18.3.1",
"react-router-dom": "^6.26.2", "react-router-dom": "^6.26.2",
@ -3232,6 +3233,15 @@
"node": "^10.12.0 || >=12.0.0" "node": "^10.12.0 || >=12.0.0"
} }
}, },
"node_modules/filesize": {
"version": "10.1.6",
"resolved": "https://registry.npmjs.org/filesize/-/filesize-10.1.6.tgz",
"integrity": "sha512-sJslQKU2uM33qH5nqewAwVB2QgR6w1aMNsYUp3aN5rMRyXEwJGmZvaWzeJFNTOXWlHQyBFCWrdj3fV/fsTOX8w==",
"license": "BSD-3-Clause",
"engines": {
"node": ">= 10.4.0"
}
},
"node_modules/fill-range": { "node_modules/fill-range": {
"version": "7.1.1", "version": "7.1.1",
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",

View File

@ -22,6 +22,7 @@
"@types/semver": "^7.5.8", "@types/semver": "^7.5.8",
"date-and-time": "^3.6.0", "date-and-time": "^3.6.0",
"dayjs": "^1.11.13", "dayjs": "^1.11.13",
"filesize": "^10.1.6",
"react": "^18.3.1", "react": "^18.3.1",
"react-dom": "^18.3.1", "react-dom": "^18.3.1",
"react-router-dom": "^6.26.2", "react-router-dom": "^6.26.2",

View File

@ -1,5 +1,11 @@
import { APIClient } from "./ApiClient"; import { APIClient } from "./ApiClient";
export interface OTAUpdate {
platform: string;
version: string;
file_size: number;
}
export class OTAAPI { export class OTAAPI {
/** /**
* Get the list of supported OTA platforms * Get the list of supported OTA platforms
@ -30,4 +36,16 @@ export class OTAAPI {
formData: fd, formData: fd,
}); });
} }
/**
* Get the list of OTA updates
*/
static async ListOTAUpdates(): Promise<OTAUpdate[]> {
return (
await APIClient.exec({
method: "GET",
uri: "/ota",
})
).data;
}
} }

View File

@ -1,10 +1,21 @@
import { IconButton, Tooltip } from "@mui/material"; import {
IconButton,
Paper,
Table,
TableBody,
TableCell,
TableContainer,
TableHead,
TableRow,
Tooltip,
} from "@mui/material";
import { SolarEnergyRouteContainer } from "../widgets/SolarEnergyRouteContainer"; import { SolarEnergyRouteContainer } from "../widgets/SolarEnergyRouteContainer";
import FileUploadIcon from "@mui/icons-material/FileUpload"; import FileUploadIcon from "@mui/icons-material/FileUpload";
import { UploadUpdateDialog } from "../dialogs/UploadUpdateDialog"; import { UploadUpdateDialog } from "../dialogs/UploadUpdateDialog";
import React from "react"; import React from "react";
import { OTAAPI } from "../api/OTAApi"; import { OTAAPI, OTAUpdate } from "../api/OTAApi";
import { AsyncWidget } from "../widgets/AsyncWidget"; import { AsyncWidget } from "../widgets/AsyncWidget";
import { filesize } from "filesize";
export function OTARoute(): React.ReactElement { export function OTARoute(): React.ReactElement {
const [list, setList] = React.useState<string[] | undefined>(); const [list, setList] = React.useState<string[] | undefined>();
@ -24,10 +35,23 @@ export function OTARoute(): React.ReactElement {
} }
function _OTARoute(p: { platforms: Array<string> }): React.ReactElement { function _OTARoute(p: { platforms: Array<string> }): React.ReactElement {
const key = React.useRef(1);
const [showUploadDialog, setShowUploadDialog] = React.useState(false); const [showUploadDialog, setShowUploadDialog] = React.useState(false);
const [list, setList] = React.useState<undefined | OTAUpdate[]>();
const load = async () => {
const list = await OTAAPI.ListOTAUpdates();
list.sort((a, b) =>
`${a.platform}#${a.version}`.localeCompare(`${b.platform}#${b.version}`)
);
list.reverse();
setList(list);
};
const reload = async () => { const reload = async () => {
/*todo*/ key.current += 1;
setList(undefined);
}; };
return ( return (
@ -53,6 +77,38 @@ function _OTARoute(p: { platforms: Array<string> }): React.ReactElement {
}} }}
/> />
)} )}
<AsyncWidget
loadKey={key.current}
ready={!!list}
errMsg="Failed to load the list of OTA updates!"
load={load}
build={() => <_OTAList list={list!} />}
/>
</SolarEnergyRouteContainer> </SolarEnergyRouteContainer>
); );
} }
function _OTAList(p: { list: OTAUpdate[] }): React.ReactElement {
return (
<TableContainer component={Paper}>
<Table sx={{ minWidth: 650 }} aria-label="simple table">
<TableHead>
<TableRow>
<TableCell align="center">Platform</TableCell>
<TableCell align="center">Version</TableCell>
<TableCell align="center">File size</TableCell>
</TableRow>
</TableHead>
<TableBody>
{p.list.map((row, num) => (
<TableRow hover key={num}>
<TableCell align="center">{row.platform}</TableCell>
<TableCell align="center">{row.version}</TableCell>
<TableCell align="center">{filesize(row.file_size)}</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</TableContainer>
);
}