diff --git a/central_backend/src/ota/ota_manager.rs b/central_backend/src/ota/ota_manager.rs index 6e70516..f9c713d 100644 --- a/central_backend/src/ota/ota_manager.rs +++ b/central_backend/src/ota/ota_manager.rs @@ -31,6 +31,13 @@ pub fn get_ota_update(platform: OTAPlatform, version: &semver::Version) -> anyho Ok(std::fs::read(path)?) } +/// Delete an OTA update +pub fn delete_update(platform: OTAPlatform, version: &semver::Version) -> anyhow::Result<()> { + let path = AppConfig::get().path_ota_update(platform, version); + std::fs::remove_file(path)?; + Ok(()) +} + /// Get the list of OTA software updates for a platform pub fn get_ota_updates_for_platform(platform: OTAPlatform) -> anyhow::Result> { let ota_path = AppConfig::get().ota_platform_dir(platform); diff --git a/central_backend/src/server/servers.rs b/central_backend/src/server/servers.rs index 2a979f0..8f01cfe 100644 --- a/central_backend/src/server/servers.rs +++ b/central_backend/src/server/servers.rs @@ -195,7 +195,10 @@ pub async fn secure_server(energy_actor: EnergyActorAddr) -> anyhow::Result<()> "/web_api/ota/{platform}/{version}", web::get().to(ota_controller::download_firmware), ) - // TODO : delete an OTA file + .route( + "/web_api/ota/{platform}/{version}", + web::delete().to(ota_controller::delete_update), + ) .route("/web_api/ota", web::get().to(ota_controller::list_all_ota)) .route( "/web_api/ota/{platform}", diff --git a/central_backend/src/server/web_api/ota_controller.rs b/central_backend/src/server/web_api/ota_controller.rs index 278af05..291a05c 100644 --- a/central_backend/src/server/web_api/ota_controller.rs +++ b/central_backend/src/server/web_api/ota_controller.rs @@ -74,6 +74,17 @@ pub async fn download_firmware(path: web::Path) -> HttpR .body(firmware)) } +/// Delete an uploaded firmware update +pub async fn delete_update(path: web::Path) -> HttpResult { + if !ota_manager::update_exists(path.platform, &path.version)? { + return Ok(HttpResponse::NotFound().json("The requested update was not found!")); + } + + ota_manager::delete_update(path.platform, &path.version)?; + + Ok(HttpResponse::Accepted().finish()) +} + /// Get the list of all OTA updates pub async fn list_all_ota() -> HttpResult { Ok(HttpResponse::Ok().json(ota_manager::get_all_ota_updates()?)) diff --git a/central_frontend/src/api/OTAApi.ts b/central_frontend/src/api/OTAApi.ts index 0d1f180..b729df9 100644 --- a/central_frontend/src/api/OTAApi.ts +++ b/central_frontend/src/api/OTAApi.ts @@ -40,8 +40,18 @@ export class OTAAPI { /** * Get the link to download an OTA update */ - static DownloadOTAUpdateURL(platform: string, version: string): string { - return APIClient.backendURL() + `/ota/${platform}/${version}`; + static DownloadOTAUpdateURL(update: OTAUpdate): string { + return APIClient.backendURL() + `/ota/${update.platform}/${update.version}`; + } + + /** + * Delete an update + */ + static async DeleteUpdate(update: OTAUpdate): Promise { + await APIClient.exec({ + method: "DELETE", + uri: `/ota/${update.platform}/${update.version}`, + }); } /** diff --git a/central_frontend/src/routes/OTARoute.tsx b/central_frontend/src/routes/OTARoute.tsx index e8c8b52..2734978 100644 --- a/central_frontend/src/routes/OTARoute.tsx +++ b/central_frontend/src/routes/OTARoute.tsx @@ -1,3 +1,4 @@ +import DeleteIcon from "@mui/icons-material/Delete"; import DownloadIcon from "@mui/icons-material/Download"; import FileUploadIcon from "@mui/icons-material/FileUpload"; import { @@ -18,6 +19,10 @@ import { UploadUpdateDialog } from "../dialogs/UploadUpdateDialog"; import { AsyncWidget } from "../widgets/AsyncWidget"; import { RouterLink } from "../widgets/RouterLink"; import { SolarEnergyRouteContainer } from "../widgets/SolarEnergyRouteContainer"; +import { useConfirm } from "../hooks/context_providers/ConfirmDialogProvider"; +import { useAlert } from "../hooks/context_providers/AlertDialogProvider"; +import { useLoadingMessage } from "../hooks/context_providers/LoadingMessageProvider"; +import { useSnackbar } from "../hooks/context_providers/SnackbarProvider"; export function OTARoute(): React.ReactElement { const [list, setList] = React.useState(); @@ -84,13 +89,45 @@ function _OTARoute(p: { platforms: Array }): React.ReactElement { ready={!!list} errMsg="Failed to load the list of OTA updates!" load={load} - build={() => <_OTAList list={list!} />} + build={() => <_OTAList list={list!} onReload={reload} />} /> ); } -function _OTAList(p: { list: OTAUpdate[] }): React.ReactElement { +function _OTAList(p: { + list: OTAUpdate[]; + onReload: () => void; +}): React.ReactElement { + const alert = useAlert(); + const confirm = useConfirm(); + const loadingMessage = useLoadingMessage(); + const snackbar = useSnackbar(); + + const deleteUpdate = async (update: OTAUpdate) => { + if ( + !(await confirm( + `Do you really want to delete the update for platform ${update.platform} version ${update.version}?` + )) + ) + return; + + try { + loadingMessage.show("Deleting update..."); + + await OTAAPI.DeleteUpdate(update); + + snackbar("The update was successfully deleted!"); + + p.onReload(); + } catch (e) { + console.error("Failed to delete update!", e); + alert(`Failed to delete the update! ${e}`); + } finally { + loadingMessage.hide(); + } + }; + return ( @@ -110,14 +147,17 @@ function _OTAList(p: { list: OTAUpdate[] }): React.ReactElement { {filesize(row.file_size)} - + + + deleteUpdate(row)}> + + + ))}