482 lines
14 KiB
TypeScript
482 lines
14 KiB
TypeScript
import {
|
|
Avatar,
|
|
Box,
|
|
IconButton,
|
|
ListItemAvatar,
|
|
ListItemText,
|
|
Menu,
|
|
MenuItem,
|
|
Theme,
|
|
Toolbar,
|
|
Tooltip,
|
|
TypeBackground
|
|
} from "@mui/material";
|
|
import { blue, green, orange, red } from "@mui/material/colors";
|
|
import { createStyles, makeStyles } from "@mui/styles";
|
|
//import { TypeBackground } from "@material-ui/core/styles/createPalette";
|
|
import {
|
|
ArrowUpward,
|
|
CloudDownload,
|
|
CloudUpload,
|
|
Delete,
|
|
MoreVert,
|
|
OpenInNew
|
|
} from "@mui/icons-material";
|
|
import classNames from "classnames";
|
|
import ResponsiveTable, { Column } from "common/ResponsiveTable";
|
|
import SmartBreadcrumb from "common/SmartBreadcrumb";
|
|
import UpdateFirmwareChannel from "firmware/UpdateFirmwareChannel";
|
|
import UploadFirmware from "firmware/UploadFirmware";
|
|
import { useFirmwareAPI, useSnackbar, useThemeType, useWidth } from "hooks";
|
|
//import MaterialTable, { Column, MTableToolbar } from "material-table";
|
|
import { Firmware as FirmwareModel } from "models";
|
|
import moment from "moment";
|
|
import PageContainer from "pages/PageContainer";
|
|
import { getDevicePlatformLabel, getDevicePlatformName } from "pbHelpers/DevicePlatform";
|
|
import { getUpgradeChannelColour, getUpgradeChannelLabel } from "pbHelpers/UpgradeChannel";
|
|
import { pond } from "protobuf-ts/pond";
|
|
import { useGlobalState } from "providers";
|
|
import { useCallback, useEffect, useState } from "react";
|
|
import { downloadFile } from "utils/download";
|
|
import { or } from "utils/types";
|
|
|
|
const useStyles = makeStyles((theme: Theme) => {
|
|
const themeType = useThemeType()
|
|
return ({
|
|
gutter: {
|
|
paddingLeft: theme.spacing(2),
|
|
paddingRight: theme.spacing(2),
|
|
[theme.breakpoints.up("sm")]: {
|
|
paddingLeft: theme.spacing(3),
|
|
paddingRight: theme.spacing(3)
|
|
}
|
|
},
|
|
actionsMenu: {
|
|
background:
|
|
themeType === "light"
|
|
? theme.palette.background["200" as keyof TypeBackground]
|
|
: theme.palette.background["700" as keyof TypeBackground]
|
|
},
|
|
avatar: {
|
|
margin: theme.spacing(1)
|
|
},
|
|
downloadIcon: {
|
|
color: theme.palette.text.primary,
|
|
backgroundColor: blue["500"],
|
|
"&:hover": {
|
|
backgroundColor: blue["600"]
|
|
}
|
|
},
|
|
updateIcon: {
|
|
color: theme.palette.text.primary,
|
|
backgroundColor: orange["500"],
|
|
"&:hover": {
|
|
backgroundColor: orange["600"]
|
|
}
|
|
},
|
|
changelogIcon: {
|
|
color: theme.palette.text.primary,
|
|
backgroundColor: green["500"],
|
|
"&:hover": {
|
|
backgroundColor: green["600"]
|
|
}
|
|
},
|
|
deleteIcon: {
|
|
color: theme.palette.text.primary,
|
|
backgroundColor: red["500"],
|
|
"&:hover": {
|
|
backgroundColor: red["600"]
|
|
}
|
|
},
|
|
menuItem: {
|
|
paddingTop: theme.spacing(2),
|
|
paddingBottom: theme.spacing(2)
|
|
},
|
|
title: {
|
|
flex: "0 0 auto"
|
|
},
|
|
toolbar:
|
|
themeType === "light"
|
|
? {
|
|
position: "sticky",
|
|
justifyContent: "space-between",
|
|
background: "#fff",
|
|
zIndex: theme.zIndex.mobileStepper,
|
|
top: "55px",
|
|
paddingRight: theme.spacing(1),
|
|
[theme.breakpoints.up("sm")]: {
|
|
paddingRight: theme.spacing(2),
|
|
top: "63px"
|
|
}
|
|
}
|
|
: {
|
|
position: "sticky",
|
|
justifyContent: "space-between",
|
|
background: theme.palette.background["800" as keyof TypeBackground],
|
|
zIndex: theme.zIndex.mobileStepper,
|
|
top: "55px",
|
|
paddingRight: theme.spacing(1),
|
|
[theme.breakpoints.up("sm")]: {
|
|
paddingRight: theme.spacing(2),
|
|
top: "63px"
|
|
}
|
|
},
|
|
toolbarActions: {
|
|
color: theme.palette.text.secondary
|
|
},
|
|
cellPadding: {
|
|
paddingLeft: theme.spacing(0.5) + "px",
|
|
paddingRight: theme.spacing(0.5) + "px",
|
|
"@media (min-width: 321px)": {
|
|
paddingLeft: theme.spacing(1) + "px",
|
|
paddingRight: theme.spacing(1) + "px"
|
|
},
|
|
[theme.breakpoints.up("sm")]: {
|
|
paddingLeft: theme.spacing(4) + "px",
|
|
paddingRight: theme.spacing(4) + "px"
|
|
}
|
|
},
|
|
pointer: {
|
|
cursor: "pointer"
|
|
},
|
|
tableHead:
|
|
themeType === "light"
|
|
? {
|
|
position: "sticky",
|
|
background: "#fff",
|
|
zIndex: theme.zIndex.mobileStepper,
|
|
top: "110px",
|
|
[theme.breakpoints.up("sm")]: {
|
|
top: "126px"
|
|
}
|
|
}
|
|
: {
|
|
position: "sticky",
|
|
background: theme.palette.background["800" as keyof TypeBackground],
|
|
zIndex: theme.zIndex.mobileStepper,
|
|
top: "110px",
|
|
[theme.breakpoints.up("sm")]: {
|
|
top: "126px"
|
|
}
|
|
},
|
|
noFirmware: {
|
|
marginTop: theme.spacing(16)
|
|
},
|
|
darkIcon: {
|
|
color: theme.palette.primary.dark
|
|
},
|
|
loading: {
|
|
display: "flex",
|
|
alignItems: "center",
|
|
justifyContent: "center",
|
|
marginTop: theme.spacing(16)
|
|
}
|
|
})
|
|
})
|
|
|
|
export default function Firmware() {
|
|
const classes = useStyles();
|
|
const width = useWidth();
|
|
const { error, success } = useSnackbar();
|
|
const firmwareAPI = useFirmwareAPI();
|
|
const [{ user }] = useGlobalState();
|
|
const [pageSize, setPageSize] = useState<number>(5);
|
|
const [tablePage, setTablePage] = useState(0)
|
|
const [searchText, setSearchText] = useState("")
|
|
const [tableData, setTableData] = useState<FirmwareModel[]>([])
|
|
const [total, setTotal] = useState(0)
|
|
const [selected, setSelected] = useState<FirmwareModel | undefined>(undefined);
|
|
const [actionsOpen, setActionsOpen] = useState<boolean>(false);
|
|
const [isUploadFirmwareDialogOpen, setIsUploadFirmwareDialogOpen] = useState<boolean>(false);
|
|
const [isUpdateFirmwareChannelDialogOpen, setIsUpdateFirmwareChannelDialogOpen] = useState<
|
|
boolean
|
|
>(false);
|
|
const [anchor, setAnchor] = useState<any>(null);
|
|
// const TitleMap = new Map([
|
|
// ["Uploaded", "uploaded"],
|
|
// ["Version", "version"],
|
|
// ["Platform", "platform"],
|
|
// ["Channel", "channel"],
|
|
// ["Size", "size"]
|
|
// ]);
|
|
// let tableRef = useRef<any>(React.createRef());
|
|
|
|
// const reloadTable = () => {
|
|
// tableRef.current.onQueryChange();
|
|
// };
|
|
|
|
const load = useCallback(() => {
|
|
firmwareAPI.listFirmware(pageSize, pageSize * tablePage, "desc", "version", searchText)
|
|
.then((response) => {
|
|
setTotal(response.data.total ? response.data.total : 0)
|
|
let rows: FirmwareModel[] = or(response.data.firmware, []).map(
|
|
(firmwareData: any) => {
|
|
return FirmwareModel.create(pond.Firmware.fromObject(firmwareData));
|
|
}
|
|
);
|
|
setTableData(rows)
|
|
})
|
|
.catch(() => {
|
|
//failed to load
|
|
error("Failed to load firmware versions")
|
|
});
|
|
},[pageSize, tablePage, searchText])
|
|
|
|
useEffect(()=>{
|
|
load()
|
|
},[load])
|
|
|
|
const download = () => {
|
|
if (selected) {
|
|
firmwareAPI
|
|
.downloadInstaller(selected.settings.platform, selected.settings.version)
|
|
.then((response: Response) => {
|
|
var filename: string =
|
|
selected.settings.version +
|
|
"-" +
|
|
getDevicePlatformName(selected.settings.platform) +
|
|
".tar";
|
|
downloadFile(response, filename);
|
|
})
|
|
.catch((err: any) => {
|
|
error(
|
|
err.response ? err.response.statusText : "Error occured while downloading firmware"
|
|
);
|
|
})
|
|
.then(() => closeActionsMenu());
|
|
}
|
|
};
|
|
|
|
const hasChangelog = () => {
|
|
return (
|
|
selected && selected.settings.channel !== pond.UpgradeChannel.UPGRADE_CHANNEL_DEVELOPMENT
|
|
);
|
|
};
|
|
|
|
const viewChangelog = () => {
|
|
if (selected && selected.settings.version) {
|
|
window.open(
|
|
"https://gitlab.com/brandx/gadwall/-/tags/" + selected.settings.version,
|
|
"_blank"
|
|
);
|
|
}
|
|
};
|
|
|
|
const deleteFirmware = () => {
|
|
if (selected) {
|
|
firmwareAPI
|
|
.removeFirmware(selected.settings.platform, selected.settings.version)
|
|
.then((response: any) => {
|
|
success("Successfully removed firmware");
|
|
})
|
|
.catch((err: any) => {
|
|
error(
|
|
err.response
|
|
? err.response.statusText
|
|
: "Error occured while delete the firmware release"
|
|
);
|
|
})
|
|
.then(() => {
|
|
closeActionsMenu();
|
|
load();
|
|
});
|
|
}
|
|
};
|
|
|
|
const openActionsMenu = (firmware: FirmwareModel) => (event: any) => {
|
|
if (user.settings.actions.includes("upload-firmware")) {
|
|
setActionsOpen(true);
|
|
setAnchor(event.currentTarget);
|
|
setSelected(firmware);
|
|
} else {
|
|
error("Missing upload-firmware action");
|
|
}
|
|
};
|
|
|
|
const closeActionsMenu = () => {
|
|
setActionsOpen(false);
|
|
setAnchor(null);
|
|
setSelected(undefined);
|
|
};
|
|
|
|
const openUploadFirmwareDialog = (event: any) => {
|
|
if (user.settings.actions.includes("upload-firmware")) {
|
|
setActionsOpen(false);
|
|
setAnchor(event.currentTarget);
|
|
setIsUploadFirmwareDialogOpen(true);
|
|
} else {
|
|
error("Missing upload-firmware action");
|
|
}
|
|
};
|
|
|
|
const closeUploadFirmwareDialog = () => {
|
|
setActionsOpen(false);
|
|
setAnchor(undefined);
|
|
setSelected(undefined);
|
|
setIsUploadFirmwareDialogOpen(false);
|
|
};
|
|
|
|
const openUpdateFirmwareChannelDialog = (event: any) => {
|
|
setActionsOpen(false);
|
|
setAnchor(event.currentTarget);
|
|
setIsUpdateFirmwareChannelDialogOpen(true);
|
|
};
|
|
|
|
const closeUpdateFirmwareChannelDialog = () => {
|
|
setActionsOpen(false);
|
|
setAnchor(undefined);
|
|
setSelected(undefined);
|
|
setIsUpdateFirmwareChannelDialogOpen(false);
|
|
};
|
|
|
|
const renderActionsMenu = () => {
|
|
return (
|
|
<Menu
|
|
id="actionsMenu"
|
|
anchorEl={anchor}
|
|
open={actionsOpen}
|
|
onClose={closeActionsMenu}
|
|
disableAutoFocusItem
|
|
MenuListProps={{ classes: { root: classes.actionsMenu } }}>
|
|
<MenuItem className={classes.menuItem} onClick={download}>
|
|
<ListItemAvatar>
|
|
<Avatar
|
|
className={classNames(classes.avatar, classes.downloadIcon)}
|
|
alt="Cloud Download">
|
|
<CloudDownload className={classes.downloadIcon} />
|
|
</Avatar>
|
|
</ListItemAvatar>
|
|
<ListItemText primary="Download Installer" />
|
|
</MenuItem>
|
|
<MenuItem className={classes.menuItem} onClick={openUpdateFirmwareChannelDialog}>
|
|
<ListItemAvatar>
|
|
<Avatar className={classNames(classes.avatar, classes.updateIcon)} alt="Update">
|
|
<ArrowUpward className={classes.updateIcon} />
|
|
</Avatar>
|
|
</ListItemAvatar>
|
|
<ListItemText primary="Update Channel" />
|
|
</MenuItem>
|
|
<MenuItem
|
|
disabled={!hasChangelog()}
|
|
className={classes.menuItem}
|
|
onClick={viewChangelog}>
|
|
<ListItemAvatar>
|
|
<Avatar className={classNames(classes.avatar, classes.changelogIcon)} alt="Open New">
|
|
<OpenInNew className={classes.changelogIcon} />
|
|
</Avatar>
|
|
</ListItemAvatar>
|
|
<ListItemText primary="View Changelog" />
|
|
</MenuItem>
|
|
<MenuItem className={classes.menuItem} onClick={deleteFirmware}>
|
|
<ListItemAvatar>
|
|
<Avatar className={classNames(classes.avatar, classes.deleteIcon)} alt="Delete">
|
|
<Delete className={classes.deleteIcon} />
|
|
</Avatar>
|
|
</ListItemAvatar>
|
|
<ListItemText primary="Delete Version" />
|
|
</MenuItem>
|
|
</Menu>
|
|
);
|
|
};
|
|
|
|
const columns = (small: boolean): Column<FirmwareModel>[] => {
|
|
return [
|
|
{
|
|
title: "Uploaded",
|
|
hidden: small,
|
|
//defaultSort: "desc",
|
|
cellStyle: {padding: 2},
|
|
render: row => {
|
|
return <span>{moment(row.status.uploaded).fromNow()}</span>;
|
|
}
|
|
},
|
|
{
|
|
title: "Version",
|
|
cellStyle: {padding: 2},
|
|
render: row => <span>{row.settings.version}</span>
|
|
},
|
|
{
|
|
title: "Platform",
|
|
cellStyle: {padding: 2},
|
|
render: row => <span>{getDevicePlatformLabel(row.settings.platform)}</span>
|
|
},
|
|
{
|
|
title: "Channel",
|
|
cellStyle: {padding: 2},
|
|
render: row => (
|
|
<span style={{ color: getUpgradeChannelColour(row.settings.channel) }}>
|
|
{getUpgradeChannelLabel(row.settings.channel)}
|
|
</span>
|
|
)
|
|
},
|
|
{
|
|
title: "Size",
|
|
cellStyle: {padding: 2},
|
|
//type: "numeric",
|
|
hidden: small,
|
|
render: row => <span>{row.settings.size} bytes</span>
|
|
},
|
|
{
|
|
title: "Actions",
|
|
cellStyle: {padding: 2},
|
|
//type: "numeric",
|
|
//sorting: false,
|
|
render: row => (
|
|
<IconButton aria-label="Actions" onClick={openActionsMenu(row)}>
|
|
<MoreVert fontSize={small ? "small" : "inherit"} />
|
|
</IconButton>
|
|
)
|
|
}
|
|
];
|
|
};
|
|
|
|
const actionToolbar = () => {
|
|
return (
|
|
<Toolbar className={classes.gutter}>
|
|
{user.allowedTo("upload-firmware") && (
|
|
<Tooltip title="Upload Firmware" placement="bottom">
|
|
<span>
|
|
<IconButton aria-label="Upload Firmware" onClick={openUploadFirmwareDialog}>
|
|
<CloudUpload
|
|
fontSize={width === "xs" ? "small" : "inherit"}
|
|
className={classes.darkIcon}
|
|
/>
|
|
</IconButton>
|
|
</span>
|
|
</Tooltip>
|
|
)}
|
|
</Toolbar>
|
|
);
|
|
};
|
|
|
|
return (
|
|
<PageContainer>
|
|
<Box margin={2}>
|
|
<ResponsiveTable<FirmwareModel>
|
|
subtitle={actionToolbar()}
|
|
setSearchText={(search) => setSearchText(search)}
|
|
title={<SmartBreadcrumb />}
|
|
page={tablePage}
|
|
pageSize={pageSize}
|
|
rows={tableData}
|
|
total={total}
|
|
columns={columns(width === "xs" || width === "sm")}
|
|
setPage={(page)=>{setTablePage(page)}}
|
|
handleRowsPerPageChange={(e)=>{setPageSize(e.target.value)}}
|
|
/>
|
|
</Box>
|
|
<UploadFirmware
|
|
isOpen={isUploadFirmwareDialogOpen}
|
|
closeDialogCallback={closeUploadFirmwareDialog}
|
|
refreshCallback={() => load()}
|
|
/>
|
|
<UpdateFirmwareChannel
|
|
firmware={selected ? selected : FirmwareModel.create()}
|
|
isOpen={isUpdateFirmwareChannelDialogOpen}
|
|
closeCallback={closeUpdateFirmwareChannelDialog}
|
|
refreshCallback={() => load()}
|
|
/>
|
|
{renderActionsMenu()}
|
|
</PageContainer>
|
|
);
|
|
}
|