195 lines
5.9 KiB
TypeScript
195 lines
5.9 KiB
TypeScript
import { mdiFolderUploadOutline } from "@mdi/js";
|
|
import Icon from "@mdi/react";
|
|
import DeleteIcon from "@mui/icons-material/Delete";
|
|
import DownloadIcon from "@mui/icons-material/Download";
|
|
import FileUploadIcon from "@mui/icons-material/FileUpload";
|
|
import RefreshIcon from "@mui/icons-material/Refresh";
|
|
import {
|
|
IconButton,
|
|
Paper,
|
|
Table,
|
|
TableBody,
|
|
TableCell,
|
|
TableContainer,
|
|
TableHead,
|
|
TableRow,
|
|
Tooltip,
|
|
} from "@mui/material";
|
|
import { filesize } from "filesize";
|
|
import React from "react";
|
|
import { OTAAPI, OTAUpdate } from "../api/OTAApi";
|
|
import { DeployOTAUpdateDialogProvider } from "../dialogs/DeployOTAUpdateDialogProvider";
|
|
import { UploadUpdateDialog } from "../dialogs/UploadUpdateDialog";
|
|
import { useAlert } from "../hooks/context_providers/AlertDialogProvider";
|
|
import { useConfirm } from "../hooks/context_providers/ConfirmDialogProvider";
|
|
import { useLoadingMessage } from "../hooks/context_providers/LoadingMessageProvider";
|
|
import { useSnackbar } from "../hooks/context_providers/SnackbarProvider";
|
|
import { AsyncWidget } from "../widgets/AsyncWidget";
|
|
import { RouterLink } from "../widgets/RouterLink";
|
|
import { SolarEnergyRouteContainer } from "../widgets/SolarEnergyRouteContainer";
|
|
|
|
export function OTARoute(): React.ReactElement {
|
|
const [list, setList] = React.useState<string[] | undefined>();
|
|
const load = async () => {
|
|
setList(await OTAAPI.SupportedPlatforms());
|
|
};
|
|
|
|
return (
|
|
<AsyncWidget
|
|
loadKey={1}
|
|
ready={!!list}
|
|
load={load}
|
|
errMsg="Failed to load OTA screen!"
|
|
build={() => <_OTARoute platforms={list!} />}
|
|
/>
|
|
);
|
|
}
|
|
|
|
function _OTARoute(p: { platforms: Array<string> }): React.ReactElement {
|
|
const key = React.useRef(1);
|
|
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 () => {
|
|
key.current += 1;
|
|
setList(undefined);
|
|
};
|
|
|
|
return (
|
|
<SolarEnergyRouteContainer
|
|
label="OTA"
|
|
actions={
|
|
<span>
|
|
<Tooltip title="Refresh the list of updates">
|
|
<IconButton onClick={reload}>
|
|
<RefreshIcon />
|
|
</IconButton>
|
|
</Tooltip>
|
|
<Tooltip title="Upload a new update">
|
|
<IconButton onClick={() => setShowUploadDialog(true)}>
|
|
<FileUploadIcon />
|
|
</IconButton>
|
|
</Tooltip>
|
|
</span>
|
|
}
|
|
>
|
|
{showUploadDialog && (
|
|
<UploadUpdateDialog
|
|
platforms={p.platforms}
|
|
onClose={() => setShowUploadDialog(false)}
|
|
onCreated={() => {
|
|
setShowUploadDialog(false);
|
|
reload();
|
|
}}
|
|
/>
|
|
)}
|
|
<AsyncWidget
|
|
loadKey={key.current}
|
|
ready={!!list}
|
|
errMsg="Failed to load the list of OTA updates!"
|
|
load={load}
|
|
build={() => <_OTAList list={list!} onReload={reload} />}
|
|
/>
|
|
</SolarEnergyRouteContainer>
|
|
);
|
|
}
|
|
|
|
function _OTAList(p: {
|
|
list: OTAUpdate[];
|
|
onReload: () => void;
|
|
}): React.ReactElement {
|
|
const alert = useAlert();
|
|
const confirm = useConfirm();
|
|
const loadingMessage = useLoadingMessage();
|
|
const snackbar = useSnackbar();
|
|
|
|
const [deployUpdate, setDeployUpdate] = React.useState<
|
|
OTAUpdate | undefined
|
|
>();
|
|
|
|
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 (
|
|
<>
|
|
{deployUpdate && (
|
|
<DeployOTAUpdateDialogProvider
|
|
update={deployUpdate!}
|
|
onClose={() => setDeployUpdate(undefined)}
|
|
/>
|
|
)}
|
|
<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>
|
|
<TableCell align="center"></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>
|
|
<TableCell align="center">
|
|
<Tooltip title="Deploy the update to devices">
|
|
<IconButton onClick={() => setDeployUpdate(row)}>
|
|
<Icon path={mdiFolderUploadOutline} size={1} />
|
|
</IconButton>
|
|
</Tooltip>
|
|
<Tooltip title="Download a copy of the firmware">
|
|
<RouterLink to={OTAAPI.DownloadOTAUpdateURL(row)}>
|
|
<IconButton>
|
|
<DownloadIcon />
|
|
</IconButton>
|
|
</RouterLink>
|
|
</Tooltip>
|
|
<Tooltip title="Delete firmware update">
|
|
<IconButton onClick={() => deleteUpdate(row)}>
|
|
<DeleteIcon />
|
|
</IconButton>
|
|
</Tooltip>
|
|
</TableCell>
|
|
</TableRow>
|
|
))}
|
|
</TableBody>
|
|
</Table>
|
|
</TableContainer>
|
|
</>
|
|
);
|
|
}
|