Merge branch 'master' into staging_environment

This commit is contained in:
csawatzky 2025-05-08 15:22:08 -06:00
commit 5f9d39e26d
13 changed files with 406 additions and 33 deletions

View file

@ -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"
}, },

View file

@ -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">

View file

@ -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 => {

View file

@ -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}

View 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
View 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;
}
}

View file

@ -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";

View file

@ -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 && (

View file

@ -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 })} />

View file

@ -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(

View file

@ -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});
} }

View file

@ -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[] = []

View file

@ -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({