Merge branch 'master' of gitlab.com:brandx/bxt-app
This commit is contained in:
commit
e612ef4e7f
6 changed files with 359 additions and 5 deletions
|
|
@ -70,6 +70,7 @@ import SaveDeviceProfile from "./SaveDeviceProfile";
|
|||
import Connection from "./Connection";
|
||||
import ComponentOrder from "component/ComponentOrder";
|
||||
import ArcGISDeviceData from "./ArcGISDeviceData";
|
||||
import UpgradeDevice from "./UpgradeDevice";
|
||||
|
||||
const useStyles = makeStyles((_theme: Theme) => {
|
||||
// const isMobile = useMobile()
|
||||
|
|
@ -497,7 +498,7 @@ export default function DeviceActions(props: Props) {
|
|||
const buttons = () => {
|
||||
return (
|
||||
<React.Fragment>
|
||||
{/* {canWrite && user.allowedTo("provision") && <UpgradeDevice device={device} />} */}
|
||||
{canWrite && user.allowedTo("provision") && <UpgradeDevice device={device} />}
|
||||
{/* {!isShareableLink(match.params.deviceID) && ( */}
|
||||
<NotificationButton
|
||||
notify={preferences.notify}
|
||||
|
|
|
|||
321
src/device/UpgradeDevice.tsx
Normal file
321
src/device/UpgradeDevice.tsx
Normal file
|
|
@ -0,0 +1,321 @@
|
|||
import {
|
||||
Button,
|
||||
CircularProgress,
|
||||
Dialog,
|
||||
DialogActions,
|
||||
DialogContent,
|
||||
DialogContentText,
|
||||
DialogTitle,
|
||||
Divider,
|
||||
IconButton,
|
||||
LinearProgress,
|
||||
Link,
|
||||
Theme,
|
||||
Tooltip,
|
||||
Typography
|
||||
} from "@mui/material";
|
||||
// import { createStyles, makeStyles, Theme } from "@material-ui/core/styles";
|
||||
import UpdateIcon from "@mui/icons-material/Update";
|
||||
import { useDeviceAPI, useFirmwareAPI, usePrevious, useSnackbar } from "hooks";
|
||||
import { Device, latestFirmwareVersion, Upgrade } from "models";
|
||||
import moment from "moment";
|
||||
import { getContextKeys, getContextTypes } from "pbHelpers/Context";
|
||||
import { useGlobalState } from "providers";
|
||||
import React, { useCallback, useEffect, useState } from "react";
|
||||
import { or } from "utils/types";
|
||||
import { makeStyles } from "@mui/styles";
|
||||
|
||||
const useStyles = makeStyles((theme: Theme) => {
|
||||
return ({
|
||||
dialogContent: {
|
||||
marginTop: theme.spacing(2)
|
||||
},
|
||||
buttonProgress: {
|
||||
color: theme.palette.primary.main,
|
||||
position: "absolute",
|
||||
zIndex: 1
|
||||
},
|
||||
failed: {
|
||||
color: theme.palette.error.main
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
interface Props {
|
||||
device: Device;
|
||||
}
|
||||
|
||||
export default function UpgradeDevice(props: Props) {
|
||||
const classes = useStyles();
|
||||
const deviceAPI = useDeviceAPI();
|
||||
const { success, error } = useSnackbar();
|
||||
const firmwareAPI = useFirmwareAPI();
|
||||
const [{ firmware }] = useGlobalState();
|
||||
const prevFirmware = usePrevious(firmware);
|
||||
const { device } = props;
|
||||
const prevDevice = usePrevious(device);
|
||||
const [open, setOpen] = useState<boolean>(false);
|
||||
const prevOpen = usePrevious(open);
|
||||
const [upgrade, setUpgrade] = useState<Upgrade>(Upgrade.create());
|
||||
const [gettingStatus, setGettingStatus] = useState(0);
|
||||
|
||||
const progress = useCallback(() => {
|
||||
return upgrade.status.progress;
|
||||
}, [upgrade.status.progress]);
|
||||
|
||||
const currentFirmware = () => {
|
||||
return device.status.firmwareVersion;
|
||||
};
|
||||
|
||||
const latestFirmware = () => {
|
||||
return latestFirmwareVersion(
|
||||
firmware,
|
||||
device.settings.platform,
|
||||
device.settings.upgradeChannel
|
||||
);
|
||||
};
|
||||
|
||||
const upgradeVersion = () => {
|
||||
return upgrade.settings.version;
|
||||
};
|
||||
|
||||
const available = () => {
|
||||
return currentFirmware() !== latestFirmware();
|
||||
};
|
||||
|
||||
const upgrading = useCallback(() => {
|
||||
return progress() === "upgrading";
|
||||
}, [progress]);
|
||||
|
||||
const completed = () => {
|
||||
return progress() === "completed" && currentFirmware() === latestFirmware();
|
||||
};
|
||||
const failed = () => {
|
||||
return progress() === "failed" && upgradeVersion() === latestFirmware();
|
||||
};
|
||||
|
||||
const percent = () => {
|
||||
return Math.round((upgrade.status.offset / upgrade.status.size) * 100);
|
||||
};
|
||||
|
||||
const tooltip = () => {
|
||||
return upgrading()
|
||||
? "Upgrade in progress: " + percent() + "%"
|
||||
: failed()
|
||||
? "Upgrade failed"
|
||||
: "Upgrade Device";
|
||||
};
|
||||
|
||||
const upgradeTimingDesc = () => {
|
||||
let desc = "requested " + moment(or(upgrade.status.started, 0)).fromNow();
|
||||
if (completed()) {
|
||||
desc = "completed " + moment(or(upgrade.status.stopped, 0)).fromNow();
|
||||
} else if (failed()) {
|
||||
desc = "failed " + moment(or(upgrade.status.changed, 0)).fromNow();
|
||||
}
|
||||
|
||||
return desc;
|
||||
};
|
||||
|
||||
const mode = () => {
|
||||
let mode = "prompt";
|
||||
if (upgrading() || failed() || completed()) {
|
||||
mode = "progress";
|
||||
}
|
||||
return mode;
|
||||
};
|
||||
|
||||
const getStatus = useCallback(() => {
|
||||
if (gettingStatus === device.id() && !upgrading()) {
|
||||
return;
|
||||
}
|
||||
setGettingStatus(device.id());
|
||||
deviceAPI
|
||||
.getUpgradeStatus(device.id(), getContextKeys(), getContextTypes())
|
||||
.then((res: any) => {
|
||||
if (res && res.data && res.data.upgrade) {
|
||||
setUpgrade(Upgrade.any(res.data.upgrade));
|
||||
}
|
||||
})
|
||||
.catch((err: any) => error(err));
|
||||
}, [device, deviceAPI, error, gettingStatus, upgrading]);
|
||||
|
||||
useEffect(() => {
|
||||
const deviceIdUnchanged = !prevDevice || prevDevice.id() === device.id();
|
||||
if (
|
||||
(open && !prevOpen) ||
|
||||
firmware !== prevFirmware ||
|
||||
(device !== prevDevice && deviceIdUnchanged)
|
||||
) {
|
||||
getStatus();
|
||||
}
|
||||
}, [open, prevOpen, getStatus, device, prevDevice, firmware, prevFirmware]);
|
||||
|
||||
useEffect(() => {
|
||||
function getStatusAgain() {
|
||||
if (upgrading()) {
|
||||
getStatus();
|
||||
}
|
||||
}
|
||||
const interval = setInterval(() => getStatusAgain(), 5000);
|
||||
return () => {
|
||||
clearInterval(interval);
|
||||
};
|
||||
}, [open, getStatus, upgrading]);
|
||||
|
||||
const close = () => {
|
||||
setOpen(false);
|
||||
};
|
||||
|
||||
const handleUpgrade = () => {
|
||||
firmwareAPI
|
||||
.upgradeFirmware(device.id())
|
||||
.then(() => {
|
||||
success(device.name() + " will upgrade to " + latestFirmware() + " next time it checks in");
|
||||
getStatus();
|
||||
})
|
||||
.catch((err: any) => {
|
||||
console.error(err);
|
||||
error(err);
|
||||
});
|
||||
};
|
||||
|
||||
const restart = () => {
|
||||
firmwareAPI
|
||||
.upgradeFirmware(device.id())
|
||||
.then(() => {
|
||||
success(
|
||||
"Upgrade successfully restarted, " +
|
||||
device.name() +
|
||||
" will upgrade next time it checks in"
|
||||
);
|
||||
getStatus();
|
||||
})
|
||||
.catch((err: any) => {
|
||||
console.error(err);
|
||||
error(err);
|
||||
});
|
||||
};
|
||||
|
||||
const cancel = () => {
|
||||
firmwareAPI
|
||||
.cancelUpgrade(device.id())
|
||||
.then(() => {
|
||||
success("Upgrade cancelled");
|
||||
})
|
||||
.catch((err: any) => {
|
||||
console.error(err);
|
||||
error(err);
|
||||
});
|
||||
};
|
||||
|
||||
const button = () => {
|
||||
return (
|
||||
<Tooltip title={tooltip()}>
|
||||
<IconButton
|
||||
color="default"
|
||||
className={failed() ? classes.failed : undefined}
|
||||
onClick={() => setOpen(true)}
|
||||
aria-label={tooltip()}>
|
||||
<UpdateIcon />
|
||||
{upgrading() && (
|
||||
<CircularProgress
|
||||
// variant="static"
|
||||
value={percent()}
|
||||
size={"2rem"}
|
||||
className={classes.buttonProgress}
|
||||
/>
|
||||
)}
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
{(available() || upgrading() || failed()) && button()}
|
||||
<Dialog fullWidth open={open} onClose={close} aria-labelledby="form-dialog-title">
|
||||
{mode() === "prompt" && (
|
||||
<React.Fragment>
|
||||
<DialogTitle id="form-dialog-title">
|
||||
Upgrade Device
|
||||
<Typography variant="body2" color="textSecondary">
|
||||
{device.name()}
|
||||
</Typography>
|
||||
</DialogTitle>
|
||||
<Divider />
|
||||
<DialogContent className={classes.dialogContent}>
|
||||
<DialogContentText id="alert-dialog-slide-description">
|
||||
Upgrading from {currentFirmware()} to {latestFirmware()}
|
||||
<br />
|
||||
<br />
|
||||
It is recommended that the device be plugged in during the upgrade as it will take
|
||||
several minutes to complete
|
||||
<br />
|
||||
<br />
|
||||
While the upgrade is in progress you can return to this dialog to view its status
|
||||
</DialogContentText>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button onClick={close} color="primary">
|
||||
Close
|
||||
</Button>
|
||||
<Button onClick={handleUpgrade} color="primary">
|
||||
Upgrade
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</React.Fragment>
|
||||
)}
|
||||
{mode() === "progress" && (
|
||||
<React.Fragment>
|
||||
<DialogTitle id="form-dialog-title">
|
||||
Upgrading Device
|
||||
<Typography variant="body2" color="textSecondary">
|
||||
{device.name()}
|
||||
</Typography>
|
||||
</DialogTitle>
|
||||
<Divider />
|
||||
<DialogContent className={classes.dialogContent}>
|
||||
<DialogContentText id="alert-dialog-slide-description">
|
||||
Upgrade to {latestFirmware()} {upgradeTimingDesc()}
|
||||
</DialogContentText>
|
||||
<br />
|
||||
<LinearProgress variant="determinate" value={percent()} />
|
||||
<DialogContentText>
|
||||
{or(upgrade.status.offset, 0)} / {upgrade.status.size} bytes ({percent()}%)
|
||||
</DialogContentText>
|
||||
{failed() && (
|
||||
<React.Fragment>
|
||||
<DialogContentText>
|
||||
<br />
|
||||
Uh oh – something went wrong. You can try to{" "}
|
||||
<Link variant="body2" onClick={restart} style={{ cursor: "pointer" }}>
|
||||
restart
|
||||
</Link>{" "}
|
||||
the upgrade, if the problem persists we'll be alerted and follow up as soon as
|
||||
possible.
|
||||
</DialogContentText>
|
||||
</React.Fragment>
|
||||
)}
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button onClick={close} color="primary">
|
||||
Close
|
||||
</Button>
|
||||
{upgrading() && (
|
||||
<Button onClick={cancel} style={{ color: "red" }}>
|
||||
Cancel
|
||||
</Button>
|
||||
)}
|
||||
{failed() && (
|
||||
<Button onClick={restart} color="primary">
|
||||
Restart
|
||||
</Button>
|
||||
)}
|
||||
</DialogActions>
|
||||
</React.Fragment>
|
||||
)}
|
||||
</Dialog>
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
32
src/models/Upgrade.ts
Normal file
32
src/models/Upgrade.ts
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
import { pond } from "protobuf-ts/pond";
|
||||
import { or } from "utils/types";
|
||||
import { cloneDeep } from "lodash";
|
||||
|
||||
export class Upgrade {
|
||||
public settings: pond.UpgradeSettings = pond.UpgradeSettings.create();
|
||||
public status: pond.UpgradeStatus = pond.UpgradeStatus.create();
|
||||
|
||||
public static create(pb?: pond.Upgrade): Upgrade {
|
||||
let my = new Upgrade();
|
||||
if (pb) {
|
||||
my.settings = pond.UpgradeSettings.fromObject(or(pb.settings, {}));
|
||||
my.status = pond.UpgradeStatus.fromObject(or(pb.status, {}));
|
||||
}
|
||||
return my;
|
||||
}
|
||||
|
||||
public static any(data: any): Upgrade {
|
||||
return Upgrade.create(pond.Upgrade.fromObject(cloneDeep(data)));
|
||||
}
|
||||
|
||||
public static clone(other: Upgrade): Upgrade {
|
||||
let my = new Upgrade();
|
||||
my.settings = pond.UpgradeSettings.fromObject(cloneDeep(other.settings));
|
||||
my.status = pond.UpgradeStatus.fromObject(cloneDeep(other.status));
|
||||
return my;
|
||||
}
|
||||
|
||||
public key(): string {
|
||||
return this.settings.key;
|
||||
}
|
||||
}
|
||||
|
|
@ -11,7 +11,7 @@ export * from "./ShareableLink";
|
|||
export * from "./Site";
|
||||
export * from "./Tag";
|
||||
export * from "./Task";
|
||||
// export * from "./Upgrade";
|
||||
export * from "./Upgrade";
|
||||
// export * from "./User";
|
||||
export * from "./user";
|
||||
export * from "./Field";
|
||||
|
|
|
|||
|
|
@ -206,7 +206,7 @@ export default function Firmware() {
|
|||
// };
|
||||
|
||||
const load = useCallback(() => {
|
||||
firmwareAPI.listFirmware(pageSize, pageSize * tablePage, "asc", "version", searchText)
|
||||
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(
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@ export const HTTPContext = createContext<IHTTPContext>({} as IHTTPContext);
|
|||
|
||||
export default function HTTPProvider(props: Props) {
|
||||
const { children, token } = props;
|
||||
const { isAuthenticated, /*loginWithRedirect,*/ loginWithPopup } = useAuth0();
|
||||
const { isAuthenticated, loginWithRedirect } = useAuth0();
|
||||
|
||||
const defaultOptions = (demo: boolean = false) => {
|
||||
if (demo || !isAuthenticated || !token) {
|
||||
|
|
@ -79,7 +79,7 @@ export default function HTTPProvider(props: Props) {
|
|||
|
||||
function get<T>(url: string, spreadOptions?: AxiosRequestConfig): Promise<AxiosResponse<T>> {
|
||||
if (isTokenExpired(token)) {
|
||||
loginWithPopup()
|
||||
loginWithRedirect()
|
||||
}
|
||||
return axios.get(url, {...defaultOptions(), ...spreadOptions});
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue