Display the list of OTA updates in the frontend
This commit is contained in:
		@@ -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)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -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('"', "");
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -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),
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -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,
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										10
									
								
								central_frontend/package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										10
									
								
								central_frontend/package-lock.json
									
									
									
										generated
									
									
									
								
							@@ -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",
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -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",
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -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;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -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>
 | 
				
			||||||
 | 
					  );
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user