Merge branch 'master' into staging_environment
This commit is contained in:
commit
5f9d39e26d
13 changed files with 406 additions and 33 deletions
|
|
@ -11,9 +11,9 @@
|
||||||
"build": "tsc -b && vite build",
|
"build": "tsc -b && vite build",
|
||||||
"lint": "eslint .",
|
"lint": "eslint .",
|
||||||
"preview": "vite preview",
|
"preview": "vite preview",
|
||||||
"build:staging": "VITE_AUTH0_CLIENT_DOMAIN=adaptivestaging.us.auth0.com VITE_AUTH0_AUDIENCE=stagingapi.brandxtech.ca VITE_AUTH0_CLIENT_ID=3ib460VvLwdeyse5iUSQfxkVdQaUmphP VITE_APP_API_URL=https://stagingapi.brandxtech.ca/v1 VITE_APP_WS_URL=wss://stagingapi.brandxtech.ca/v1/live NODE_OPTIONS=--max_old_space_size=4096 VITE_APP_GOOGLE_API_KEY=${GOOGLE_API_KEY} VITE_APP_STRIPE_PUBLIC_KEY=${STRIPE_PUBLIC_KEY_STAGING} VITE_MAPBOX_ACCESS_TOKEN=${MAPBOX_ACCESS_TOKEN} VITE_CNHI_CLIENT_ID=${CNHI_CLIENT_ID} VITE_CNHI_AUTHORIZE_URL=${CNHI_AUTHORIZE_URL} VITE_CNHI_REDIRECT_URI=${CNHI_REDIRECT_URI} VITE_CNHI_SCOPES=${CNHI_SCOPES} VITE_CNHI_CONNECTION=${CNHI_CONNECTION} VITE_CNHI_AUDIENCE=${CNHI_AUDIENCE} VITE_APP_IMAGE4IO_USERNAME=${IMAGE4IO_USERNAME} VITE_APP_IMAGE4IO_PASSWORD=${IMAGE4IO_PASSWORD} VITE_JD_CLIENT_ID=${JD_CLIENT_ID} VITE_JD_AUTHORIZE_URL=${JD_AUTHORIZE_URL} VITE_JD_REDIRECT_URI=${JD_REDIRECT_URI} VITE_JD_SCOPES=${JD_SCOPES} VITE_JD_STATE=${JD_STATE} vite build",
|
"build:staging": "VITE_AUTH0_CLIENT_DOMAIN=adaptivestaging.us.auth0.com VITE_AUTH0_AUDIENCE=stagingapi.brandxtech.ca VITE_AUTH0_CLIENT_ID=3ib460VvLwdeyse5iUSQfxkVdQaUmphP VITE_APP_API_URL=https://stagingapi.brandxtech.ca/v1 VITE_APP_WS_URL=wss://stagingapi.brandxtech.ca/v1/live NODE_OPTIONS=--max_old_space_size=4096 VITE_APP_GOOGLE_API_KEY=${GOOGLE_API_KEY} VITE_APP_STRIPE_PUBLIC_KEY=${STRIPE_PUBLIC_KEY_STAGING} VITE_MAPBOX_ACCESS_TOKEN=${MAPBOX_ACCESS_TOKEN} VITE_CNHI_CLIENT_ID=${CNHI_CLIENT_ID} VITE_CNHI_AUTHORIZE_URL=${CNHI_AUTHORIZE_URL} VITE_CNHI_REDIRECT_URI=${CNHI_REDIRECT_URI} VITE_CNHI_SCOPES=${CNHI_SCOPES} VITE_CNHI_CONNECTION=${CNHI_CONNECTION} VITE_CNHI_AUDIENCE=${CNHI_AUDIENCE} VITE_APP_IMAGE4IO_USERNAME=${IMAGE4IO_USERNAME} VITE_APP_IMAGE4IO_PASSWORD=${IMAGE4IO_PASSWORD} VITE_JD_CLIENT_ID=${JD_CLIENT_ID} VITE_JD_AUTHORIZE_URL=${JD_AUTHORIZE_URL} VITE_JD_REDIRECT_URI=${JD_REDIRECT_URI} VITE_JD_SCOPES=${JD_SCOPES} VITE_JD_STATE=${JD_STATE} VITE_OPEN_WEATHERMAP={OPEN_WEATHERMAP} vite build",
|
||||||
"build:development": "VITE_AUTH0_CLIENT_ID=dzJTpqIeMA4Rwk4xujtwPbAO3TY32bM1 VITE_AUTH0_AUDIENCE=bxt-dev.api.adaptiveagriculture.ca VITE_APP_API_URL=https://bxt-dev.api.adaptiveagriculture.ca/v1 NODE_OPTIONS=--max_old_space_size=4096 VITE_APP_GOOGLE_API_KEY=${GOOGLE_API_KEY} VITE_APP_STRIPE_PUBLIC_KEY=${STRIPE_PUBLIC_KEY_DEVELOPMENT} VITE_MAPBOX_ACCESS_TOKEN=${MAPBOX_ACCESS_TOKEN} VITE_CNHI_CLIENT_ID=${CNHI_CLIENT_ID} VITE_CNHI_AUTHORIZE_URL=${CNHI_AUTHORIZE_URL} VITE_CNHI_REDIRECT_URI=${CNHI_REDIRECT_URI} VITE_CNHI_SCOPES=${CNHI_SCOPES} VITE_CNHI_CONNECTION=${CNHI_CONNECTION} VITE_CNHI_AUDIENCE=${CNHI_AUDIENCE} VITE_APP_IMAGE4IO_USERNAME=${IMAGE4IO_USERNAME} VITE_APP_IMAGE4IO_PASSWORD=${IMAGE4IO_PASSWORD} VITE_JD_CLIENT_ID=${JD_CLIENT_ID} VITE_JD_AUTHORIZE_URL=${JD_AUTHORIZE_URL} VITE_JD_REDIRECT_URI=${JD_REDIRECT_URI} VITE_JD_SCOPES=${JD_SCOPES} VITE_JD_STATE=${JD_STATE} vite build",
|
"build:development": "VITE_AUTH0_CLIENT_ID=dzJTpqIeMA4Rwk4xujtwPbAO3TY32bM1 VITE_AUTH0_AUDIENCE=bxt-dev.api.adaptiveagriculture.ca VITE_APP_API_URL=https://bxt-dev.api.adaptiveagriculture.ca/v1 NODE_OPTIONS=--max_old_space_size=4096 VITE_APP_GOOGLE_API_KEY=${GOOGLE_API_KEY} VITE_APP_STRIPE_PUBLIC_KEY=${STRIPE_PUBLIC_KEY_DEVELOPMENT} VITE_MAPBOX_ACCESS_TOKEN=${MAPBOX_ACCESS_TOKEN} VITE_CNHI_CLIENT_ID=${CNHI_CLIENT_ID} VITE_CNHI_AUTHORIZE_URL=${CNHI_AUTHORIZE_URL} VITE_CNHI_REDIRECT_URI=${CNHI_REDIRECT_URI} VITE_CNHI_SCOPES=${CNHI_SCOPES} VITE_CNHI_CONNECTION=${CNHI_CONNECTION} VITE_CNHI_AUDIENCE=${CNHI_AUDIENCE} VITE_APP_IMAGE4IO_USERNAME=${IMAGE4IO_USERNAME} VITE_APP_IMAGE4IO_PASSWORD=${IMAGE4IO_PASSWORD} VITE_JD_CLIENT_ID=${JD_CLIENT_ID} VITE_JD_AUTHORIZE_URL=${JD_AUTHORIZE_URL} VITE_JD_REDIRECT_URI=${JD_REDIRECT_URI} VITE_JD_SCOPES=${JD_SCOPES} VITE_JD_STATE=${JD_STATE} VITE_OPEN_WEATHERMAP={OPEN_WEATHERMAP} vite build",
|
||||||
"build:production": "VITE_AUTH0_CLIENT_ID=5pUCkl2SfogkWmM244UDcLEUOp8EFdHd VITE_AUTH0_CLIENT_DOMAIN=brandxtech.auth0.com VITE_AUTH0_AUDIENCE=api.brandxtech.ca VITE_APP_API_URL=https://api.brandxtech.ca/v1 NODE_OPTIONS=--max_old_space_size=4096 VITE_APP_GOOGLE_API_KEY=${GOOGLE_API_KEY} VITE_APP_STRIPE_PUBLIC_KEY=${STRIPE_PUBLIC_KEY_PRODUCTION} VITE_MAPBOX_ACCESS_TOKEN=${MAPBOX_ACCESS_TOKEN} VITE_CNHI_CLIENT_ID=${CNHI_CLIENT_ID} VITE_CNHI_AUTHORIZE_URL=${CNHI_AUTHORIZE_URL} VITE_CNHI_REDIRECT_URI=${CNHI_REDIRECT_URI} VITE_CNHI_SCOPES=${CNHI_SCOPES} VITE_CNHI_CONNECTION=${CNHI_CONNECTION} VITE_CNHI_AUDIENCE=${CNHI_AUDIENCE} VITE_APP_IMAGE4IO_USERNAME=${IMAGE4IO_USERNAME} VITE_APP_IMAGE4IO_PASSWORD=${IMAGE4IO_PASSWORD} VITE_JD_CLIENT_ID=${JD_CLIENT_ID} VITE_JD_AUTHORIZE_URL=${JD_AUTHORIZE_URL} VITE_JD_REDIRECT_URI=${JD_REDIRECT_URI} VITE_JD_SCOPES=${JD_SCOPES} VITE_JD_STATE=${JD_STATE} vite build",
|
"build:production": "VITE_AUTH0_CLIENT_ID=5pUCkl2SfogkWmM244UDcLEUOp8EFdHd VITE_AUTH0_CLIENT_DOMAIN=brandxtech.auth0.com VITE_AUTH0_AUDIENCE=api.brandxtech.ca VITE_APP_API_URL=https://api.brandxtech.ca/v1 NODE_OPTIONS=--max_old_space_size=4096 VITE_APP_GOOGLE_API_KEY=${GOOGLE_API_KEY} VITE_APP_STRIPE_PUBLIC_KEY=${STRIPE_PUBLIC_KEY_PRODUCTION} VITE_MAPBOX_ACCESS_TOKEN=${MAPBOX_ACCESS_TOKEN} VITE_CNHI_CLIENT_ID=${CNHI_CLIENT_ID} VITE_CNHI_AUTHORIZE_URL=${CNHI_AUTHORIZE_URL} VITE_CNHI_REDIRECT_URI=${CNHI_REDIRECT_URI} VITE_CNHI_SCOPES=${CNHI_SCOPES} VITE_CNHI_CONNECTION=${CNHI_CONNECTION} VITE_CNHI_AUDIENCE=${CNHI_AUDIENCE} VITE_APP_IMAGE4IO_USERNAME=${IMAGE4IO_USERNAME} VITE_APP_IMAGE4IO_PASSWORD=${IMAGE4IO_PASSWORD} VITE_JD_CLIENT_ID=${JD_CLIENT_ID} VITE_JD_AUTHORIZE_URL=${JD_AUTHORIZE_URL} VITE_JD_REDIRECT_URI=${JD_REDIRECT_URI} VITE_JD_SCOPES=${JD_SCOPES} VITE_JD_STATE=${JD_STATE} VITE_OPEN_WEATHERMAP={OPEN_WEATHERMAP} vite build",
|
||||||
"build:offline": "npx env-cmd offline,whitelabel npm run build",
|
"build:offline": "npx env-cmd offline,whitelabel npm run build",
|
||||||
"test": "vitest"
|
"test": "vitest"
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ import {
|
||||||
DialogActions,
|
DialogActions,
|
||||||
DialogContent,
|
DialogContent,
|
||||||
Grid,
|
Grid,
|
||||||
|
TextField,
|
||||||
Theme,
|
Theme,
|
||||||
Typography
|
Typography
|
||||||
} from "@mui/material";
|
} from "@mui/material";
|
||||||
|
|
@ -13,6 +14,7 @@ import ResponsiveDialog from "common/ResponsiveDialog";
|
||||||
import { DateRangePreset, GetDefaultDateRange, SetDefaultPreset } from "./DateRange";
|
import { DateRangePreset, GetDefaultDateRange, SetDefaultPreset } from "./DateRange";
|
||||||
import { useThemeType } from "hooks";
|
import { useThemeType } from "hooks";
|
||||||
import { makeStyles } from "@mui/styles";
|
import { makeStyles } from "@mui/styles";
|
||||||
|
import { cloneDeep } from "lodash";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
startDate: Moment;
|
startDate: Moment;
|
||||||
|
|
@ -49,7 +51,7 @@ export default function TimeBar(props: Props) {
|
||||||
|
|
||||||
const submitDateRange = () => {
|
const submitDateRange = () => {
|
||||||
const startDate = dateRange[0] ? dateRange[0].clone() : moment();
|
const startDate = dateRange[0] ? dateRange[0].clone() : moment();
|
||||||
const endDate = dateRange[1] ? dateRange[1].clone() : moment();
|
const endDate = dateRange[1] ? dateRange[1].add(1, "day").clone() : moment(); //add 1 day to the end time to include that day rather than have it be the cutoff
|
||||||
updateDateRange(startDate, endDate, false);
|
updateDateRange(startDate, endDate, false);
|
||||||
setDateRangeDialog(false);
|
setDateRangeDialog(false);
|
||||||
setActiveView("selectRange");
|
setActiveView("selectRange");
|
||||||
|
|
@ -85,19 +87,30 @@ export default function TimeBar(props: Props) {
|
||||||
onClose={() => setDateRangeDialog(false)}
|
onClose={() => setDateRangeDialog(false)}
|
||||||
aria-labelledby="date-range-dialog">
|
aria-labelledby="date-range-dialog">
|
||||||
<DialogContent>
|
<DialogContent>
|
||||||
{/* <StaticDateRangePicker
|
<TextField
|
||||||
disableFuture
|
fullWidth
|
||||||
value={dateRange}
|
margin="normal"
|
||||||
onChange={date => setDateRange(date)}
|
type="date"
|
||||||
renderInput={(startProps, endProps) => (
|
label="Start Time"
|
||||||
<React.Fragment>
|
value={dateRange[0]?.format("YYYY-MM-DD")}
|
||||||
<TextField {...startProps} />
|
onChange={e => {
|
||||||
<DateRangeDelimiter> to </DateRangeDelimiter>
|
let range = cloneDeep(dateRange)
|
||||||
<TextField {...endProps} />
|
range[0] = moment(e.target.value)
|
||||||
</React.Fragment>
|
setDateRange(range)
|
||||||
)}
|
}}
|
||||||
/> */}
|
/>
|
||||||
DATE PICKER GOES HERE
|
<TextField
|
||||||
|
fullWidth
|
||||||
|
margin="normal"
|
||||||
|
type="date"
|
||||||
|
label="End Time"
|
||||||
|
value={dateRange[1]?.format("YYYY-MM-DD")}
|
||||||
|
onChange={e => {
|
||||||
|
let range = cloneDeep(dateRange)
|
||||||
|
range[1] = moment(e.target.value)
|
||||||
|
setDateRange(range)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
<DialogActions>
|
<DialogActions>
|
||||||
<Button onClick={() => setDateRangeDialog(false)} color="primary">
|
<Button onClick={() => setDateRangeDialog(false)} color="primary">
|
||||||
|
|
|
||||||
|
|
@ -237,8 +237,7 @@ export default function ComponentForm(props: Props) {
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
let comp = tidyComponent(form.component);
|
componentChanged(form.component, isFormValid());
|
||||||
componentChanged(comp, isFormValid());
|
|
||||||
}, [form.component, componentChanged]); // eslint-disable-line react-hooks/exhaustive-deps
|
}, [form.component, componentChanged]); // eslint-disable-line react-hooks/exhaustive-deps
|
||||||
|
|
||||||
const isNameValid = (name: string): boolean => {
|
const isNameValid = (name: string): boolean => {
|
||||||
|
|
@ -950,7 +949,7 @@ export default function ComponentForm(props: Props) {
|
||||||
<FormControlLabel
|
<FormControlLabel
|
||||||
control={
|
control={
|
||||||
<Switch
|
<Switch
|
||||||
checked={measure}
|
checked={measure}
|
||||||
onChange={toggleMeasure}
|
onChange={toggleMeasure}
|
||||||
name="measure"
|
name="measure"
|
||||||
aria-label="measure"
|
aria-label="measure"
|
||||||
|
|
|
||||||
|
|
@ -70,6 +70,7 @@ import SaveDeviceProfile from "./SaveDeviceProfile";
|
||||||
import Connection from "./Connection";
|
import Connection from "./Connection";
|
||||||
import ComponentOrder from "component/ComponentOrder";
|
import ComponentOrder from "component/ComponentOrder";
|
||||||
import ArcGISDeviceData from "./ArcGISDeviceData";
|
import ArcGISDeviceData from "./ArcGISDeviceData";
|
||||||
|
import UpgradeDevice from "./UpgradeDevice";
|
||||||
|
|
||||||
const useStyles = makeStyles((_theme: Theme) => {
|
const useStyles = makeStyles((_theme: Theme) => {
|
||||||
// const isMobile = useMobile()
|
// const isMobile = useMobile()
|
||||||
|
|
@ -497,7 +498,7 @@ export default function DeviceActions(props: Props) {
|
||||||
const buttons = () => {
|
const buttons = () => {
|
||||||
return (
|
return (
|
||||||
<React.Fragment>
|
<React.Fragment>
|
||||||
{/* {canWrite && user.allowedTo("provision") && <UpgradeDevice device={device} />} */}
|
{canWrite && user.allowedTo("provision") && <UpgradeDevice device={device} />}
|
||||||
{/* {!isShareableLink(match.params.deviceID) && ( */}
|
{/* {!isShareableLink(match.params.deviceID) && ( */}
|
||||||
<NotificationButton
|
<NotificationButton
|
||||||
notify={preferences.notify}
|
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 "./Site";
|
||||||
export * from "./Tag";
|
export * from "./Tag";
|
||||||
export * from "./Task";
|
export * from "./Task";
|
||||||
// export * from "./Upgrade";
|
export * from "./Upgrade";
|
||||||
// export * from "./User";
|
// export * from "./User";
|
||||||
export * from "./user";
|
export * from "./user";
|
||||||
export * from "./Field";
|
export * from "./Field";
|
||||||
|
|
|
||||||
|
|
@ -105,7 +105,7 @@ export default function BottomNavigator(props: Props) {
|
||||||
return (
|
return (
|
||||||
<BottomNavigation value={route} onChange={(_, newValue) => handleRouteChange(newValue)}>
|
<BottomNavigation value={route} onChange={(_, newValue) => handleRouteChange(newValue)}>
|
||||||
{isAdaptive && (
|
{isAdaptive && (
|
||||||
<BottomNavigationAction label="Farm" icon={<FieldsIcon type={getType()} />} value="fields" />
|
<BottomNavigationAction label="Farm" icon={<FieldsIcon type={getType()} />} value="visualFarm" />
|
||||||
)}
|
)}
|
||||||
{isAdaptive && (
|
{isAdaptive && (
|
||||||
<BottomNavigationAction label="Bins" icon={<BinsIcon type={getType()} />} value="bins" />
|
<BottomNavigationAction label="Bins" icon={<BinsIcon type={getType()} />} value="bins" />
|
||||||
|
|
@ -114,7 +114,7 @@ export default function BottomNavigator(props: Props) {
|
||||||
<BottomNavigationAction
|
<BottomNavigationAction
|
||||||
label="Site Map"
|
label="Site Map"
|
||||||
icon={<FieldsIcon type={getType()} />}
|
icon={<FieldsIcon type={getType()} />}
|
||||||
value="constructionsiteMap"
|
value="constructionMap"
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{isOmni && (
|
{isOmni && (
|
||||||
|
|
|
||||||
|
|
@ -57,7 +57,14 @@ const useStyles = makeStyles((theme: Theme) => {
|
||||||
margin: theme.spacing(1),
|
margin: theme.spacing(1),
|
||||||
marginLeft: theme.spacing(2),
|
marginLeft: theme.spacing(2),
|
||||||
display: "flex",
|
display: "flex",
|
||||||
width: theme.spacing(26)
|
width: theme.spacing(32)
|
||||||
|
// justifyContent: "center",
|
||||||
|
},
|
||||||
|
tagsCellContainer: {
|
||||||
|
margin: theme.spacing(1),
|
||||||
|
marginLeft: theme.spacing(2),
|
||||||
|
display: "flex",
|
||||||
|
width: theme.spacing(24)
|
||||||
// justifyContent: "center",
|
// justifyContent: "center",
|
||||||
},
|
},
|
||||||
green: {
|
green: {
|
||||||
|
|
@ -328,8 +335,8 @@ export default function Devices() {
|
||||||
<Box className={classes.descriptionCellContainer}>
|
<Box className={classes.descriptionCellContainer}>
|
||||||
<Tooltip title={device.settings?.description}>
|
<Tooltip title={device.settings?.description}>
|
||||||
<span>
|
<span>
|
||||||
{description.length > 32
|
{description.length > 36
|
||||||
? description.substring(0, 32) + "..."
|
? description.substring(0, 36) + "..."
|
||||||
: description}
|
: description}
|
||||||
</span>
|
</span>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
|
|
@ -341,7 +348,7 @@ export default function Devices() {
|
||||||
title: "Tags",
|
title: "Tags",
|
||||||
render: (device: Device) => {
|
render: (device: Device) => {
|
||||||
return (
|
return (
|
||||||
<Grid2 container spacing={1} className={classes.descriptionCellContainer}>
|
<Grid2 container spacing={1} className={classes.tagsCellContainer}>
|
||||||
{device.status.tags.map((tag: pond.TagSettings) => (
|
{device.status.tags.map((tag: pond.TagSettings) => (
|
||||||
<Grid2 key={"device-"+device.id()+"-tag-"+tag.key}>
|
<Grid2 key={"device-"+device.id()+"-tag-"+tag.key}>
|
||||||
<TagUI tag={Tag.any({ settings: tag })} />
|
<TagUI tag={Tag.any({ settings: tag })} />
|
||||||
|
|
|
||||||
|
|
@ -206,7 +206,7 @@ export default function Firmware() {
|
||||||
// };
|
// };
|
||||||
|
|
||||||
const load = useCallback(() => {
|
const load = useCallback(() => {
|
||||||
firmwareAPI.listFirmware(pageSize, pageSize * tablePage, "asc", "version", searchText)
|
firmwareAPI.listFirmware(pageSize, pageSize * tablePage, "desc", "version", searchText)
|
||||||
.then((response) => {
|
.then((response) => {
|
||||||
setTotal(response.data.total ? response.data.total : 0)
|
setTotal(response.data.total ? response.data.total : 0)
|
||||||
let rows: FirmwareModel[] = or(response.data.firmware, []).map(
|
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) {
|
export default function HTTPProvider(props: Props) {
|
||||||
const { children, token } = props;
|
const { children, token } = props;
|
||||||
const { isAuthenticated, /*loginWithRedirect,*/ loginWithPopup } = useAuth0();
|
const { isAuthenticated, loginWithRedirect } = useAuth0();
|
||||||
|
|
||||||
const defaultOptions = (demo: boolean = false) => {
|
const defaultOptions = (demo: boolean = false) => {
|
||||||
if (demo || !isAuthenticated || !token) {
|
if (demo || !isAuthenticated || !token) {
|
||||||
|
|
@ -79,7 +79,7 @@ export default function HTTPProvider(props: Props) {
|
||||||
|
|
||||||
function get<T>(url: string, spreadOptions?: AxiosRequestConfig): Promise<AxiosResponse<T>> {
|
function get<T>(url: string, spreadOptions?: AxiosRequestConfig): Promise<AxiosResponse<T>> {
|
||||||
if (isTokenExpired(token)) {
|
if (isTokenExpired(token)) {
|
||||||
loginWithPopup()
|
loginWithRedirect()
|
||||||
}
|
}
|
||||||
return axios.get(url, {...defaultOptions(), ...spreadOptions});
|
return axios.get(url, {...defaultOptions(), ...spreadOptions});
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -80,7 +80,7 @@ export default function TeamList() {
|
||||||
viewAs = undefined
|
viewAs = undefined
|
||||||
}
|
}
|
||||||
teamAPI
|
teamAPI
|
||||||
.listTeams(limit, limit * page, undefined, "name", undefined, undefined, viewAs, search)
|
.listTeams(limit, limit * page, undefined, "name", undefined, undefined, " ", search)
|
||||||
.then(resp => {
|
.then(resp => {
|
||||||
setTotal(resp.data.total ? resp.data.total : 0);
|
setTotal(resp.data.total ? resp.data.total : 0);
|
||||||
let newTeams: Team[] = []
|
let newTeams: Team[] = []
|
||||||
|
|
|
||||||
|
|
@ -41,7 +41,7 @@ export default function TeamSearch(props: Props) {
|
||||||
let options: Option[] = [];
|
let options: Option[] = [];
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
teamAPI
|
teamAPI
|
||||||
.listTeams(10, 0, "desc", undefined, undefined, undefined, undefined, search)
|
.listTeams(10, 0, "desc", undefined, undefined, undefined, " ", search)
|
||||||
.then(resp => {
|
.then(resp => {
|
||||||
if (loadUsers)
|
if (loadUsers)
|
||||||
options.push({
|
options.push({
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue