Can manually download an OTA update

This commit is contained in:
Pierre HUBERT 2024-10-08 22:13:36 +02:00
parent 6cf7c2cae1
commit 2e4a2b68dd
4 changed files with 53 additions and 8 deletions

View File

@ -191,13 +191,16 @@ 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),
) )
.route(
"/web_api/ota/{platform}/{version}",
web::get().to(ota_controller::download_firmware),
)
// TODO : delete an OTA file
.route("/web_api/ota", web::get().to(ota_controller::list_all_ota)) .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),
) )
// TODO : download a OTA file
// TODO : delete an OTA file
.route( .route(
"/web_api/ota/set_desired_version", "/web_api/ota/set_desired_version",
web::post().to(ota_controller::set_desired_version), web::post().to(ota_controller::set_desired_version),

View File

@ -20,14 +20,15 @@ pub struct UploadForm {
} }
#[derive(serde::Deserialize)] #[derive(serde::Deserialize)]
pub struct UploadPath { pub struct SpecificOTAVersionPath {
platform: OTAPlatform, platform: OTAPlatform,
version: semver::Version, version: semver::Version,
} }
/// Upload a new firmware update
pub async fn upload_firmware( pub async fn upload_firmware(
MultipartForm(form): MultipartForm<UploadForm>, MultipartForm(form): MultipartForm<UploadForm>,
path: web::Path<UploadPath>, path: web::Path<SpecificOTAVersionPath>,
) -> HttpResult { ) -> HttpResult {
if ota_manager::update_exists(path.platform, &path.version)? { if ota_manager::update_exists(path.platform, &path.version)? {
return Ok(HttpResponse::Conflict() return Ok(HttpResponse::Conflict()
@ -53,6 +54,26 @@ pub async fn upload_firmware(
Ok(HttpResponse::Accepted().body("OTA update successfully saved.")) Ok(HttpResponse::Accepted().body("OTA update successfully saved."))
} }
/// Download a firmware update
pub async fn download_firmware(path: web::Path<SpecificOTAVersionPath>) -> HttpResult {
if !ota_manager::update_exists(path.platform, &path.version)? {
return Ok(HttpResponse::NotFound().json("The requested firmware was not found!"));
}
let firmware = ota_manager::get_ota_update(path.platform, &path.version)?;
Ok(HttpResponse::Ok()
.content_type("application/octet-stream")
.append_header((
"content-disposition",
format!(
"attachment; filename=\"{}-{}.bin\"",
path.platform, path.version
),
))
.body(firmware))
}
/// Get the list of all OTA updates /// Get the list of all OTA updates
pub async fn list_all_ota() -> HttpResult { pub async fn list_all_ota() -> HttpResult {
Ok(HttpResponse::Ok().json(ota_manager::get_all_ota_updates()?)) Ok(HttpResponse::Ok().json(ota_manager::get_all_ota_updates()?))

View File

@ -37,6 +37,13 @@ export class OTAAPI {
}); });
} }
/**
* Get the link to download an OTA update
*/
static DownloadOTAUpdateURL(platform: string, version: string): string {
return APIClient.backendURL() + `/ota/${platform}/${version}`;
}
/** /**
* Get the list of OTA updates * Get the list of OTA updates
*/ */

View File

@ -1,3 +1,5 @@
import DownloadIcon from "@mui/icons-material/Download";
import FileUploadIcon from "@mui/icons-material/FileUpload";
import { import {
IconButton, IconButton,
Paper, Paper,
@ -9,13 +11,13 @@ import {
TableRow, TableRow,
Tooltip, Tooltip,
} from "@mui/material"; } from "@mui/material";
import { SolarEnergyRouteContainer } from "../widgets/SolarEnergyRouteContainer"; import { filesize } from "filesize";
import FileUploadIcon from "@mui/icons-material/FileUpload";
import { UploadUpdateDialog } from "../dialogs/UploadUpdateDialog";
import React from "react"; import React from "react";
import { OTAAPI, OTAUpdate } from "../api/OTAApi"; import { OTAAPI, OTAUpdate } from "../api/OTAApi";
import { UploadUpdateDialog } from "../dialogs/UploadUpdateDialog";
import { AsyncWidget } from "../widgets/AsyncWidget"; import { AsyncWidget } from "../widgets/AsyncWidget";
import { filesize } from "filesize"; import { RouterLink } from "../widgets/RouterLink";
import { SolarEnergyRouteContainer } from "../widgets/SolarEnergyRouteContainer";
export function OTARoute(): React.ReactElement { export function OTARoute(): React.ReactElement {
const [list, setList] = React.useState<string[] | undefined>(); const [list, setList] = React.useState<string[] | undefined>();
@ -97,6 +99,7 @@ function _OTAList(p: { list: OTAUpdate[] }): React.ReactElement {
<TableCell align="center">Platform</TableCell> <TableCell align="center">Platform</TableCell>
<TableCell align="center">Version</TableCell> <TableCell align="center">Version</TableCell>
<TableCell align="center">File size</TableCell> <TableCell align="center">File size</TableCell>
<TableCell align="center"></TableCell>
</TableRow> </TableRow>
</TableHead> </TableHead>
<TableBody> <TableBody>
@ -105,6 +108,17 @@ function _OTAList(p: { list: OTAUpdate[] }): React.ReactElement {
<TableCell align="center">{row.platform}</TableCell> <TableCell align="center">{row.platform}</TableCell>
<TableCell align="center">{row.version}</TableCell> <TableCell align="center">{row.version}</TableCell>
<TableCell align="center">{filesize(row.file_size)}</TableCell> <TableCell align="center">{filesize(row.file_size)}</TableCell>
<TableCell align="center">
<Tooltip title="Download a copy of the firmware">
<RouterLink
to={OTAAPI.DownloadOTAUpdateURL(row.platform, row.version)}
>
<IconButton>
<DownloadIcon />
</IconButton>
</RouterLink>
</Tooltip>
</TableCell>
</TableRow> </TableRow>
))} ))}
</TableBody> </TableBody>