Merge branch 'dev_environment' of gitlab.com:brandx/bxt-app into dev_environment
This commit is contained in:
commit
cb952b284e
17 changed files with 2300 additions and 1599 deletions
2632
package-lock.json
generated
2632
package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
|
@ -69,6 +69,7 @@
|
||||||
"react-router-dom": "^6.28.0",
|
"react-router-dom": "^6.28.0",
|
||||||
"react-virtualized-auto-sizer": "^1.0.25",
|
"react-virtualized-auto-sizer": "^1.0.25",
|
||||||
"recharts": "^2.15.1",
|
"recharts": "^2.15.1",
|
||||||
|
"semver": "^7.7.1",
|
||||||
"victory": "^37.3.6",
|
"victory": "^37.3.6",
|
||||||
"weather-icons-react": "^1.2.0"
|
"weather-icons-react": "^1.2.0"
|
||||||
},
|
},
|
||||||
|
|
@ -85,6 +86,7 @@
|
||||||
"@types/react-dom": "^18.3.0",
|
"@types/react-dom": "^18.3.0",
|
||||||
"@types/react-infinite-scroller": "^1.2.5",
|
"@types/react-infinite-scroller": "^1.2.5",
|
||||||
"@types/react-map-gl": "^6.1.7",
|
"@types/react-map-gl": "^6.1.7",
|
||||||
|
"@types/semver": "^7.7.0",
|
||||||
"@vitejs/plugin-react": "^4.3.2",
|
"@vitejs/plugin-react": "^4.3.2",
|
||||||
"eslint": "^9.11.1",
|
"eslint": "^9.11.1",
|
||||||
"eslint-plugin-react-hooks": "^5.1.0-rc.0",
|
"eslint-plugin-react-hooks": "^5.1.0-rc.0",
|
||||||
|
|
|
||||||
|
|
@ -75,7 +75,7 @@ const useStyles = makeStyles((theme: Theme) => {
|
||||||
strokeOpacity: 0.85
|
strokeOpacity: 0.85
|
||||||
},
|
},
|
||||||
grainInventory: {
|
grainInventory: {
|
||||||
fill: theme.palette.secondary.main,
|
fill: GRAIN_COLOUR,
|
||||||
opacity: 0.5
|
opacity: 0.5
|
||||||
},
|
},
|
||||||
cableLine: {
|
cableLine: {
|
||||||
|
|
|
||||||
125
src/firmware/UpdateFirmwareChannel.tsx
Normal file
125
src/firmware/UpdateFirmwareChannel.tsx
Normal file
|
|
@ -0,0 +1,125 @@
|
||||||
|
import {
|
||||||
|
Button,
|
||||||
|
Dialog,
|
||||||
|
DialogActions,
|
||||||
|
DialogContent,
|
||||||
|
DialogTitle,
|
||||||
|
MenuItem,
|
||||||
|
TextField
|
||||||
|
} from "@mui/material";
|
||||||
|
import { useFirmwareAPI, usePrevious, useSnackbar } from "hooks";
|
||||||
|
import { Firmware } from "models";
|
||||||
|
import { pond } from "protobuf-ts/pond";
|
||||||
|
import { useEffect, useState } from "react";
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
firmware: Firmware;
|
||||||
|
isOpen: boolean;
|
||||||
|
closeCallback: Function;
|
||||||
|
refreshCallback: Function;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function UpdateFirmwareChannel(props: Props) {
|
||||||
|
const firmwareAPI = useFirmwareAPI();
|
||||||
|
const { isOpen, closeCallback, refreshCallback } = props;
|
||||||
|
const prevFirmware = usePrevious(props.firmware);
|
||||||
|
const { success, error } = useSnackbar();
|
||||||
|
const [firmware, setFirmware] = useState<Firmware>(Firmware.create());
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (prevFirmware !== props.firmware) {
|
||||||
|
setFirmware(Firmware.clone(props.firmware));
|
||||||
|
}
|
||||||
|
}, [firmware, prevFirmware, props.firmware]);
|
||||||
|
|
||||||
|
const closeDialog = () => {
|
||||||
|
closeCallback();
|
||||||
|
};
|
||||||
|
|
||||||
|
const changeChannel = (event: any) => {
|
||||||
|
let updatedFirmware = firmware;
|
||||||
|
updatedFirmware.settings.channel = event.target.value;
|
||||||
|
setFirmware(updatedFirmware);
|
||||||
|
};
|
||||||
|
|
||||||
|
const update = () => {
|
||||||
|
firmwareAPI
|
||||||
|
.updateChannel(
|
||||||
|
firmware.settings.platform,
|
||||||
|
firmware.settings.version,
|
||||||
|
firmware.settings.channel
|
||||||
|
)
|
||||||
|
.then((response: any) => {
|
||||||
|
success("Firmware channel updated");
|
||||||
|
closeDialog();
|
||||||
|
refreshCallback();
|
||||||
|
})
|
||||||
|
.catch((err: any) => {
|
||||||
|
error(err);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
if (firmware === null || firmware === undefined) {
|
||||||
|
return null;
|
||||||
|
} else {
|
||||||
|
return (
|
||||||
|
<Dialog fullWidth open={isOpen} onClose={closeDialog} aria-labelledby="form-dialog-title">
|
||||||
|
<DialogTitle id="form-dialog-title">Upload Firmware</DialogTitle>
|
||||||
|
<DialogContent>
|
||||||
|
<TextField
|
||||||
|
disabled
|
||||||
|
id="platform"
|
||||||
|
name="platform"
|
||||||
|
select
|
||||||
|
label="Platform"
|
||||||
|
margin="normal"
|
||||||
|
fullWidth
|
||||||
|
variant="outlined"
|
||||||
|
value={firmware.settings.platform}>
|
||||||
|
<MenuItem value={pond.DevicePlatform.DEVICE_PLATFORM_PHOTON}>Photon (Wifi)</MenuItem>
|
||||||
|
<MenuItem value={pond.DevicePlatform.DEVICE_PLATFORM_ELECTRON}>
|
||||||
|
Electron (Cellular)
|
||||||
|
</MenuItem>
|
||||||
|
</TextField>
|
||||||
|
<TextField
|
||||||
|
disabled
|
||||||
|
id="version"
|
||||||
|
name="version"
|
||||||
|
autoFocus
|
||||||
|
label="Version"
|
||||||
|
margin="normal"
|
||||||
|
type="text"
|
||||||
|
fullWidth
|
||||||
|
variant="outlined"
|
||||||
|
value={firmware.settings.version}
|
||||||
|
/>
|
||||||
|
<TextField
|
||||||
|
id="channel"
|
||||||
|
name="channel"
|
||||||
|
select
|
||||||
|
required
|
||||||
|
label="Channel"
|
||||||
|
margin="normal"
|
||||||
|
fullWidth
|
||||||
|
variant="outlined"
|
||||||
|
value={firmware.settings.channel}
|
||||||
|
onChange={changeChannel}>
|
||||||
|
<MenuItem value={pond.UpgradeChannel.UPGRADE_CHANNEL_RECOVERY}>Recovery</MenuItem>
|
||||||
|
<MenuItem value={pond.UpgradeChannel.UPGRADE_CHANNEL_DEVELOPMENT}>Development</MenuItem>
|
||||||
|
<MenuItem value={pond.UpgradeChannel.UPGRADE_CHANNEL_ALPHA}>Alpha</MenuItem>
|
||||||
|
<MenuItem value={pond.UpgradeChannel.UPGRADE_CHANNEL_BETA}>Beta</MenuItem>
|
||||||
|
<MenuItem value={pond.UpgradeChannel.UPGRADE_CHANNEL_STABLE}>Stable</MenuItem>
|
||||||
|
</TextField>
|
||||||
|
</DialogContent>
|
||||||
|
<DialogActions>
|
||||||
|
<Button onClick={closeDialog} color="primary">
|
||||||
|
Cancel
|
||||||
|
</Button>
|
||||||
|
<Button onClick={update} color="primary">
|
||||||
|
Update
|
||||||
|
</Button>
|
||||||
|
</DialogActions>
|
||||||
|
</Dialog>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
304
src/firmware/UploadFirmware.tsx
Normal file
304
src/firmware/UploadFirmware.tsx
Normal file
|
|
@ -0,0 +1,304 @@
|
||||||
|
import {
|
||||||
|
Button,
|
||||||
|
Dialog,
|
||||||
|
DialogActions,
|
||||||
|
DialogContent,
|
||||||
|
DialogTitle,
|
||||||
|
FormControlLabel,
|
||||||
|
IconButton,
|
||||||
|
InputBase,
|
||||||
|
MenuItem,
|
||||||
|
Paper,
|
||||||
|
Switch,
|
||||||
|
TextField,
|
||||||
|
Theme
|
||||||
|
} from "@mui/material";
|
||||||
|
import { createStyles } from "@mui/styles";
|
||||||
|
import withStyles, { WithStyles } from "@mui/styles/withStyles";
|
||||||
|
import CloudUploadIcon from "@mui/icons-material/CloudUpload";
|
||||||
|
import { pond } from "protobuf-ts/pond";
|
||||||
|
import { FirmwareAPIContext } from "providers/pond/firmwareAPI";
|
||||||
|
import React from "react";
|
||||||
|
import semver from "semver";
|
||||||
|
import { or } from "utils/types";
|
||||||
|
|
||||||
|
const styles = (theme: Theme) => {
|
||||||
|
return createStyles({
|
||||||
|
root: {
|
||||||
|
padding: "2px 4px",
|
||||||
|
display: "flex",
|
||||||
|
alignItems: "center"
|
||||||
|
},
|
||||||
|
input: {
|
||||||
|
marginLeft: 8,
|
||||||
|
flex: 1
|
||||||
|
},
|
||||||
|
iconButton: {
|
||||||
|
padding: 10
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
interface Props extends WithStyles<typeof styles> {
|
||||||
|
isOpen: boolean;
|
||||||
|
closeDialogCallback: () => void;
|
||||||
|
refreshCallback: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface State {
|
||||||
|
dirty: boolean;
|
||||||
|
version: string;
|
||||||
|
platform: pond.DevicePlatform;
|
||||||
|
channel: pond.UpgradeChannel;
|
||||||
|
file?: Blob;
|
||||||
|
breaksStorage: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
class UploadFirmware extends React.Component<Props, State> {
|
||||||
|
static contextType = FirmwareAPIContext;
|
||||||
|
declare context: React.ContextType<typeof FirmwareAPIContext>;
|
||||||
|
selector: any;
|
||||||
|
constructor(props: Props) {
|
||||||
|
super(props);
|
||||||
|
this.state = {
|
||||||
|
dirty: false,
|
||||||
|
version: "",
|
||||||
|
platform: pond.DevicePlatform.DEVICE_PLATFORM_PHOTON,
|
||||||
|
channel: pond.UpgradeChannel.UPGRADE_CHANNEL_DEVELOPMENT,
|
||||||
|
breaksStorage: false
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
isValid() {
|
||||||
|
const { version, platform, channel, file } = this.state;
|
||||||
|
var isPlatformValid = [
|
||||||
|
pond.DevicePlatform.DEVICE_PLATFORM_PHOTON,
|
||||||
|
pond.DevicePlatform.DEVICE_PLATFORM_ELECTRON,
|
||||||
|
pond.DevicePlatform.DEVICE_PLATFORM_V2_CELLULAR,
|
||||||
|
pond.DevicePlatform.DEVICE_PLATFORM_V2_WIFI_S3,
|
||||||
|
pond.DevicePlatform.DEVICE_PLATFORM_V2_CELLULAR_BLACK,
|
||||||
|
pond.DevicePlatform.DEVICE_PLATFORM_V2_CELLULAR_GREEN,
|
||||||
|
pond.DevicePlatform.DEVICE_PLATFORM_V2_CELLULAR_BLUE,
|
||||||
|
pond.DevicePlatform.DEVICE_PLATFORM_V2_WIFI_BLUE
|
||||||
|
].includes(platform);
|
||||||
|
var isChannelValid = [
|
||||||
|
pond.UpgradeChannel.UPGRADE_CHANNEL_ALPHA,
|
||||||
|
pond.UpgradeChannel.UPGRADE_CHANNEL_BETA,
|
||||||
|
pond.UpgradeChannel.UPGRADE_CHANNEL_STABLE,
|
||||||
|
pond.UpgradeChannel.UPGRADE_CHANNEL_DEVELOPMENT
|
||||||
|
].includes(channel);
|
||||||
|
|
||||||
|
var isVersionValid = semver.valid(version) !== null;
|
||||||
|
var isFileValid = file !== undefined;
|
||||||
|
return isPlatformValid && isChannelValid && isVersionValid && isFileValid;
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
this.selector = document.createElement("input");
|
||||||
|
this.selector.setAttribute("type", "file");
|
||||||
|
var self = this;
|
||||||
|
this.selector.addEventListener("change", function() {
|
||||||
|
if (self.selector.files && self.selector.files.length > 0) {
|
||||||
|
self.selectFile(self.selector.files[0]);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
closeDialog = () => {
|
||||||
|
this.props.closeDialogCallback();
|
||||||
|
};
|
||||||
|
|
||||||
|
openFileSelector = () => {
|
||||||
|
this.selector.click();
|
||||||
|
};
|
||||||
|
|
||||||
|
prefill = (filename: string) => {
|
||||||
|
filename = filename.toLowerCase();
|
||||||
|
var state: State = {} as State;
|
||||||
|
if (filename.includes("photon") || filename.includes("wifi")) {
|
||||||
|
state.platform = pond.DevicePlatform.DEVICE_PLATFORM_PHOTON;
|
||||||
|
}
|
||||||
|
if (filename.includes("electron") || filename.includes("cell")) {
|
||||||
|
state.platform = pond.DevicePlatform.DEVICE_PLATFORM_ELECTRON;
|
||||||
|
}
|
||||||
|
if (filename.includes("alpha")) {
|
||||||
|
state.channel = pond.UpgradeChannel.UPGRADE_CHANNEL_ALPHA;
|
||||||
|
}
|
||||||
|
if (filename.includes("beta")) {
|
||||||
|
state.channel = pond.UpgradeChannel.UPGRADE_CHANNEL_BETA;
|
||||||
|
}
|
||||||
|
if (filename.includes("stable")) {
|
||||||
|
state.channel = pond.UpgradeChannel.UPGRADE_CHANNEL_STABLE;
|
||||||
|
}
|
||||||
|
if (filename.includes("dev")) {
|
||||||
|
state.channel = pond.UpgradeChannel.UPGRADE_CHANNEL_DEVELOPMENT;
|
||||||
|
}
|
||||||
|
var version = filename.match(/^([0-9]+)\.([0-9]+)\.([0-9]+)*/g);
|
||||||
|
if (version && version.length > 0) {
|
||||||
|
state.version = version[0];
|
||||||
|
}
|
||||||
|
if (filename.includes("break")) {
|
||||||
|
state.breaksStorage = true;
|
||||||
|
}
|
||||||
|
this.setState(state);
|
||||||
|
};
|
||||||
|
|
||||||
|
selectFile = (file: any) => {
|
||||||
|
const { dirty } = this.state;
|
||||||
|
if (!dirty) {
|
||||||
|
this.prefill(file.name);
|
||||||
|
}
|
||||||
|
this.setState({ file: file });
|
||||||
|
};
|
||||||
|
|
||||||
|
uploadFirmware = () => {
|
||||||
|
const { refreshCallback } = this.props;
|
||||||
|
const { version, channel, platform, file, breaksStorage } = this.state;
|
||||||
|
const firmwareAPI = this.context;
|
||||||
|
if (file) {
|
||||||
|
firmwareAPI
|
||||||
|
.uploadFirmware(version, channel, platform, file, breaksStorage)
|
||||||
|
.then(() => {
|
||||||
|
this.closeDialog();
|
||||||
|
refreshCallback();
|
||||||
|
})
|
||||||
|
.catch((err: any) => {
|
||||||
|
console.error(err ? err.message : "Error occurred while uploading firmware");
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
console.error("Cannot upload firmware, file not found");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { classes, isOpen } = this.props;
|
||||||
|
const { version, channel, platform, file } = this.state;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Dialog
|
||||||
|
fullWidth
|
||||||
|
open={isOpen}
|
||||||
|
onClose={this.closeDialog}
|
||||||
|
aria-labelledby="form-dialog-title">
|
||||||
|
<DialogTitle id="form-dialog-title">Upload Firmware</DialogTitle>
|
||||||
|
<DialogContent>
|
||||||
|
<Paper className={classes.root} elevation={0}>
|
||||||
|
<IconButton
|
||||||
|
onClick={this.openFileSelector}
|
||||||
|
className={classes.iconButton}
|
||||||
|
aria-label="Select">
|
||||||
|
<CloudUploadIcon />
|
||||||
|
</IconButton>
|
||||||
|
<InputBase
|
||||||
|
disabled
|
||||||
|
className={classes.input}
|
||||||
|
placeholder="Select a binary"
|
||||||
|
value={or(file as any, { name: "" }).name}
|
||||||
|
/>
|
||||||
|
</Paper>
|
||||||
|
<TextField
|
||||||
|
id="platform"
|
||||||
|
name="platform"
|
||||||
|
select
|
||||||
|
required
|
||||||
|
label="Platform"
|
||||||
|
margin="normal"
|
||||||
|
fullWidth
|
||||||
|
variant="outlined"
|
||||||
|
value={platform}
|
||||||
|
onChange={event =>
|
||||||
|
this.setState({
|
||||||
|
platform: parseInt(event.target.value) as pond.DevicePlatform,
|
||||||
|
dirty: true
|
||||||
|
})
|
||||||
|
}>
|
||||||
|
<MenuItem value={pond.DevicePlatform.DEVICE_PLATFORM_PHOTON}>Photon (Wifi)</MenuItem>
|
||||||
|
<MenuItem value={pond.DevicePlatform.DEVICE_PLATFORM_ELECTRON}>
|
||||||
|
Electron (Cellular)
|
||||||
|
</MenuItem>
|
||||||
|
<MenuItem value={pond.DevicePlatform.DEVICE_PLATFORM_V2_CELLULAR}>V2 Cellular</MenuItem>
|
||||||
|
<MenuItem value={pond.DevicePlatform.DEVICE_PLATFORM_V2_WIFI_S3}>V2 Wifi S3</MenuItem>
|
||||||
|
<MenuItem value={pond.DevicePlatform.DEVICE_PLATFORM_V2_CELLULAR_BLACK}>
|
||||||
|
V2 Cellular Black
|
||||||
|
</MenuItem>
|
||||||
|
<MenuItem value={pond.DevicePlatform.DEVICE_PLATFORM_V2_CELLULAR_GREEN}>
|
||||||
|
V2 Cellular Green
|
||||||
|
</MenuItem>
|
||||||
|
<MenuItem value={pond.DevicePlatform.DEVICE_PLATFORM_V2_CELLULAR_BLUE}>
|
||||||
|
V2 Cellular Blue
|
||||||
|
</MenuItem>
|
||||||
|
<MenuItem value={pond.DevicePlatform.DEVICE_PLATFORM_V2_WIFI_BLUE}>
|
||||||
|
V2 Wifi Blue
|
||||||
|
</MenuItem>
|
||||||
|
</TextField>
|
||||||
|
<TextField
|
||||||
|
id="channel"
|
||||||
|
name="channel"
|
||||||
|
select
|
||||||
|
required
|
||||||
|
label="Channel"
|
||||||
|
margin="normal"
|
||||||
|
fullWidth
|
||||||
|
variant="outlined"
|
||||||
|
value={channel}
|
||||||
|
onChange={event => {
|
||||||
|
this.setState({
|
||||||
|
channel: parseInt(event.target.value) as pond.UpgradeChannel,
|
||||||
|
dirty: true
|
||||||
|
});
|
||||||
|
}}>
|
||||||
|
<MenuItem value={pond.UpgradeChannel.UPGRADE_CHANNEL_RECOVERY}>Recovery</MenuItem>
|
||||||
|
<MenuItem value={pond.UpgradeChannel.UPGRADE_CHANNEL_DEVELOPMENT}>Development</MenuItem>
|
||||||
|
<MenuItem value={pond.UpgradeChannel.UPGRADE_CHANNEL_ALPHA}>Alpha</MenuItem>
|
||||||
|
<MenuItem value={pond.UpgradeChannel.UPGRADE_CHANNEL_BETA}>Beta</MenuItem>
|
||||||
|
<MenuItem value={pond.UpgradeChannel.UPGRADE_CHANNEL_STABLE}>Stable</MenuItem>
|
||||||
|
</TextField>
|
||||||
|
<TextField
|
||||||
|
id="version"
|
||||||
|
name="version"
|
||||||
|
autoFocus
|
||||||
|
required
|
||||||
|
label="Version"
|
||||||
|
margin="normal"
|
||||||
|
type="text"
|
||||||
|
fullWidth
|
||||||
|
variant="outlined"
|
||||||
|
value={version}
|
||||||
|
onChange={event => {
|
||||||
|
this.setState({
|
||||||
|
version: event.target.value,
|
||||||
|
dirty: true
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<FormControlLabel
|
||||||
|
control={
|
||||||
|
<Switch
|
||||||
|
name="breaksStorage"
|
||||||
|
checked={this.state.breaksStorage}
|
||||||
|
onChange={event => {
|
||||||
|
this.setState({
|
||||||
|
breaksStorage: event.target.checked,
|
||||||
|
dirty: true
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
color="primary"
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
label="Breaks Storage"
|
||||||
|
/>
|
||||||
|
</DialogContent>
|
||||||
|
<DialogActions>
|
||||||
|
<Button onClick={this.closeDialog} color="primary">
|
||||||
|
Cancel
|
||||||
|
</Button>
|
||||||
|
<Button onClick={this.uploadFirmware} color="primary" disabled={!this.isValid()}>
|
||||||
|
Upload
|
||||||
|
</Button>
|
||||||
|
</DialogActions>
|
||||||
|
</Dialog>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default withStyles(styles)(UploadFirmware);
|
||||||
|
|
@ -7,7 +7,7 @@ export {
|
||||||
// useComponentWebsocket,
|
// useComponentWebsocket,
|
||||||
useDeviceAPI,
|
useDeviceAPI,
|
||||||
// useDeviceWebsocket,
|
// useDeviceWebsocket,
|
||||||
// useFirmwareAPI,
|
useFirmwareAPI,
|
||||||
// useGitlab,
|
// useGitlab,
|
||||||
useGroupAPI,
|
useGroupAPI,
|
||||||
useHTTP,
|
useHTTP,
|
||||||
|
|
|
||||||
|
|
@ -330,7 +330,7 @@ import { Result } from "@mapbox/mapbox-gl-geocoder";
|
||||||
closeDrawers();
|
closeDrawers();
|
||||||
setBagDrawer(true);
|
setBagDrawer(true);
|
||||||
setIgnoreFeatures(true);
|
setIgnoreFeatures(true);
|
||||||
setObjectKey(b.key);
|
setObjectKey(b.key());
|
||||||
moveMap(
|
moveMap(
|
||||||
b.centerLocation().latitude,
|
b.centerLocation().latitude,
|
||||||
b.centerLocation().longitude,
|
b.centerLocation().longitude,
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,12 @@ interface FeatureVersionByPlatform {
|
||||||
electron: string;
|
electron: string;
|
||||||
v2Wifi: string;
|
v2Wifi: string;
|
||||||
v2Cell: string;
|
v2Cell: string;
|
||||||
|
v2WifiS3: string;
|
||||||
|
v2CellS3: string;
|
||||||
|
v2CellBlack: string;
|
||||||
|
v2CellGreen: string;
|
||||||
|
v2WifiBlue: string;
|
||||||
|
v2CellBlue: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const featureVersions: Map<string, FeatureVersionByPlatform> = new Map([
|
const featureVersions: Map<string, FeatureVersionByPlatform> = new Map([
|
||||||
|
|
@ -17,7 +23,13 @@ const featureVersions: Map<string, FeatureVersionByPlatform> = new Map([
|
||||||
photon: "1.19.64",
|
photon: "1.19.64",
|
||||||
electron: "1.19.64",
|
electron: "1.19.64",
|
||||||
v2Cell: "2.0.44",
|
v2Cell: "2.0.44",
|
||||||
v2Wifi: "2.0.44"
|
v2Wifi: "2.0.44",
|
||||||
|
v2WifiS3: "2.0.44",
|
||||||
|
v2CellS3: "2.0.44",
|
||||||
|
v2CellBlack: "2.0.44",
|
||||||
|
v2CellGreen: "2.0.44",
|
||||||
|
v2WifiBlue: "2.0.44",
|
||||||
|
v2CellBlue: "2.0.44"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
]);
|
]);
|
||||||
|
|
@ -94,6 +106,18 @@ export class Device {
|
||||||
return this.status.firmwareVersion >= versions.v2Cell;
|
return this.status.firmwareVersion >= versions.v2Cell;
|
||||||
case pond.DevicePlatform.DEVICE_PLATFORM_V2_WIFI:
|
case pond.DevicePlatform.DEVICE_PLATFORM_V2_WIFI:
|
||||||
return this.status.firmwareVersion >= versions.v2Wifi;
|
return this.status.firmwareVersion >= versions.v2Wifi;
|
||||||
|
case pond.DevicePlatform.DEVICE_PLATFORM_V2_WIFI_S3:
|
||||||
|
return this.status.firmwareVersion >= versions.v2WifiS3;
|
||||||
|
case pond.DevicePlatform.DEVICE_PLATFORM_V2_CELLULAR_S3:
|
||||||
|
return this.status.firmwareVersion >= versions.v2CellS3;
|
||||||
|
case pond.DevicePlatform.DEVICE_PLATFORM_V2_CELLULAR_BLACK:
|
||||||
|
return this.status.firmwareVersion >= versions.v2CellBlack;
|
||||||
|
case pond.DevicePlatform.DEVICE_PLATFORM_V2_CELLULAR_GREEN:
|
||||||
|
return this.status.firmwareVersion >= versions.v2CellGreen;
|
||||||
|
case pond.DevicePlatform.DEVICE_PLATFORM_V2_WIFI_BLUE:
|
||||||
|
return this.status.firmwareVersion >= versions.v2WifiBlue;
|
||||||
|
case pond.DevicePlatform.DEVICE_PLATFORM_V2_CELLULAR_BLUE:
|
||||||
|
return this.status.firmwareVersion >= versions.v2CellBlue;
|
||||||
default:
|
default:
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -38,6 +38,8 @@ const APIDocs = lazy(() => import("pages/APIDocs"));
|
||||||
const Fields = lazy(()=> import("pages/Fields"));
|
const Fields = lazy(()=> import("pages/Fields"));
|
||||||
const Marketplace = lazy(() => import("userFeatures/UserFeatures"))
|
const Marketplace = lazy(() => import("userFeatures/UserFeatures"))
|
||||||
const Logs = lazy(() => import("pages/Logs"))
|
const Logs = lazy(() => import("pages/Logs"))
|
||||||
|
const Firmware = lazy(() => import("pages/Firmware"));
|
||||||
|
const Nfc = lazy(() => import("pages/Nfc"));
|
||||||
|
|
||||||
export const appendToUrl = (appendage: number | string) => {
|
export const appendToUrl = (appendage: number | string) => {
|
||||||
const basePath = location.pathname.replace(/\/$/, "");
|
const basePath = location.pathname.replace(/\/$/, "");
|
||||||
|
|
@ -311,6 +313,10 @@ export default function Router() {
|
||||||
}
|
}
|
||||||
<Route path="fields" element={<Fields />} />
|
<Route path="fields" element={<Fields />} />
|
||||||
<Route path="marketplace" element={<Marketplace />} />
|
<Route path="marketplace" element={<Marketplace />} />
|
||||||
|
{user.hasFeature("admin") && (
|
||||||
|
<Route path="firmware" element={<Firmware />} />
|
||||||
|
)}
|
||||||
|
<Route path="nfc" element={<Nfc />} />
|
||||||
|
|
||||||
{/* Map pages */}
|
{/* Map pages */}
|
||||||
<Route path="visualFarm" element={<FieldMap />} />
|
<Route path="visualFarm" element={<FieldMap />} />
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import { ChevronRight, Code, People, Person, SyncAlt } from "@mui/icons-material";
|
import { ChevronRight, Code, Memory, People, Person, SyncAlt, TapAndPlay } from "@mui/icons-material";
|
||||||
import ChevronLeft from "@mui/icons-material/ChevronLeft";
|
import ChevronLeft from "@mui/icons-material/ChevronLeft";
|
||||||
import {
|
import {
|
||||||
darken,
|
darken,
|
||||||
|
|
@ -368,6 +368,20 @@ export default function SideNavigator(props: Props) {
|
||||||
</ListItemButton>
|
</ListItemButton>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
}
|
}
|
||||||
|
{user.hasFeature("admin") &&
|
||||||
|
<Tooltip title="Firmware" placement="right">
|
||||||
|
<ListItemButton
|
||||||
|
id="tour-firmware"
|
||||||
|
onClick={() => goTo("/firmware")}
|
||||||
|
classes={getClasses("/firmware")}
|
||||||
|
>
|
||||||
|
<ListItemIcon>
|
||||||
|
<Memory />
|
||||||
|
</ListItemIcon>
|
||||||
|
{open && <ListItemText primary="Logs" />}
|
||||||
|
</ListItemButton>
|
||||||
|
</Tooltip>
|
||||||
|
}
|
||||||
{user.hasFeature("admin") &&
|
{user.hasFeature("admin") &&
|
||||||
<Tooltip title="Logs" placement="right">
|
<Tooltip title="Logs" placement="right">
|
||||||
<ListItemButton
|
<ListItemButton
|
||||||
|
|
@ -382,6 +396,20 @@ export default function SideNavigator(props: Props) {
|
||||||
</ListItemButton>
|
</ListItemButton>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
}
|
}
|
||||||
|
{user.hasFeature("admin") && window.NDEFReader &&
|
||||||
|
<Tooltip title="NFC" placement="right">
|
||||||
|
<ListItemButton
|
||||||
|
id="tour-nfc"
|
||||||
|
onClick={() => goTo("/nfc")}
|
||||||
|
classes={getClasses("/nfc")}
|
||||||
|
>
|
||||||
|
<ListItemIcon>
|
||||||
|
<TapAndPlay />
|
||||||
|
</ListItemIcon>
|
||||||
|
{open && <ListItemText primary="NFC" />}
|
||||||
|
</ListItemButton>
|
||||||
|
</Tooltip>
|
||||||
|
}
|
||||||
<Tooltip title="Marketplace" placement="right">
|
<Tooltip title="Marketplace" placement="right">
|
||||||
<ListItemButton
|
<ListItemButton
|
||||||
id="tour-marketplace"
|
id="tour-marketplace"
|
||||||
|
|
|
||||||
32
src/nfc/commandHistory.tsx
Normal file
32
src/nfc/commandHistory.tsx
Normal file
|
|
@ -0,0 +1,32 @@
|
||||||
|
import { Box, List, ListItem, ListItemIcon, ListItemText, Typography } from "@mui/material";
|
||||||
|
import { NfcCommand } from "pages/Nfc";
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
commands: NfcCommand[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function CommandHistory(props: Props) {
|
||||||
|
const { commands } = props;
|
||||||
|
return (
|
||||||
|
<Box>
|
||||||
|
<Typography style={{ fontSize: 20, fontWeight: 650 }}>Session Command History</Typography>
|
||||||
|
|
||||||
|
<Box height={250} overflow="scroll" border={"2px solid white"} borderRadius={10}>
|
||||||
|
<List>
|
||||||
|
{commands.map((command, i) => (
|
||||||
|
<ListItem key={i}>
|
||||||
|
<ListItemIcon style={{ marginRight: 10 }}>
|
||||||
|
<Typography style={{ fontWeight: 650 }}>
|
||||||
|
{command.command + " : " + command.type}
|
||||||
|
</Typography>
|
||||||
|
</ListItemIcon>
|
||||||
|
<ListItemText>
|
||||||
|
<Typography>{command.data}</Typography>
|
||||||
|
</ListItemText>
|
||||||
|
</ListItem>
|
||||||
|
))}
|
||||||
|
</List>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
109
src/nfc/nfcCommands.tsx
Normal file
109
src/nfc/nfcCommands.tsx
Normal file
|
|
@ -0,0 +1,109 @@
|
||||||
|
import { Box, Button, Grid, TextField } from "@mui/material";
|
||||||
|
import { NfcCommand } from "pages/Nfc";
|
||||||
|
import { useSnackbar } from "providers";
|
||||||
|
import { useState } from "react";
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
onCommandRun: (command: NfcCommand[]) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function NfcCommands(props: Props) {
|
||||||
|
const { onCommandRun } = props;
|
||||||
|
const [textCommand, setTextCommand] = useState("");
|
||||||
|
const { openSnack } = useSnackbar();
|
||||||
|
const ndef = window.NDEFReader ? new NDEFReader() : undefined;
|
||||||
|
|
||||||
|
const writeCommand = (commandData: string) => {
|
||||||
|
if (ndef) {
|
||||||
|
ndef
|
||||||
|
.write({
|
||||||
|
records: [{ recordType: "text", data: commandData }]
|
||||||
|
})
|
||||||
|
.then(resp => {
|
||||||
|
openSnack("Comand sent");
|
||||||
|
onCommandRun([{ type: "text", command: "write", data: commandData }]);
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
openSnack("Failed to send command");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const readCommand = () => {
|
||||||
|
if (ndef) {
|
||||||
|
ndef.scan().then(() => {
|
||||||
|
//the scan started looking for the tag
|
||||||
|
openSnack("Starting Scan");
|
||||||
|
|
||||||
|
//the tag is found and can be read
|
||||||
|
ndef.onreading = event => {
|
||||||
|
let records: NfcCommand[] = [];
|
||||||
|
openSnack("Scan Complete");
|
||||||
|
//goes through the records found on the tag
|
||||||
|
event.message.records.forEach(record => {
|
||||||
|
//if the records type is set as tex
|
||||||
|
if (record.recordType === "text") {
|
||||||
|
//create a decoder using the records encoding method
|
||||||
|
let decoder = new TextDecoder(record.encoding);
|
||||||
|
//push the record type and the decoded data into the records to show in the command history
|
||||||
|
records.push({
|
||||||
|
type: record.recordType,
|
||||||
|
command: "read",
|
||||||
|
data: decoder.decode(record.data) //decode the record into a string to display
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
onCommandRun(records);
|
||||||
|
};
|
||||||
|
|
||||||
|
ndef.onreadingerror = event => {
|
||||||
|
openSnack("Failed to Complete Scan");
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box>
|
||||||
|
<TextField
|
||||||
|
label="NFC Command entry"
|
||||||
|
fullWidth
|
||||||
|
value={textCommand}
|
||||||
|
onChange={e => {
|
||||||
|
setTextCommand(e.target.value);
|
||||||
|
}}
|
||||||
|
style={{ paddingTop: 5, paddingBottom: 5 }}
|
||||||
|
/>
|
||||||
|
<Grid container spacing={2}>
|
||||||
|
<Grid item>
|
||||||
|
<Button
|
||||||
|
variant="contained"
|
||||||
|
color="primary"
|
||||||
|
onClick={() => {
|
||||||
|
if (ndef) {
|
||||||
|
writeCommand(textCommand);
|
||||||
|
} else {
|
||||||
|
openSnack("Browser does not support web nfc");
|
||||||
|
}
|
||||||
|
}}>
|
||||||
|
Write to NFC Tag
|
||||||
|
</Button>
|
||||||
|
</Grid>
|
||||||
|
<Grid item>
|
||||||
|
<Button
|
||||||
|
variant="contained"
|
||||||
|
color="primary"
|
||||||
|
onClick={() => {
|
||||||
|
if (ndef) {
|
||||||
|
readCommand();
|
||||||
|
} else {
|
||||||
|
openSnack("Browser does not support web nfc");
|
||||||
|
}
|
||||||
|
}}>
|
||||||
|
Read NFC Tag
|
||||||
|
</Button>
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
482
src/pages/Firmware.tsx
Normal file
482
src/pages/Firmware.tsx
Normal file
|
|
@ -0,0 +1,482 @@
|
||||||
|
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, "asc", "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>
|
||||||
|
);
|
||||||
|
}
|
||||||
34
src/pages/Nfc.tsx
Normal file
34
src/pages/Nfc.tsx
Normal file
|
|
@ -0,0 +1,34 @@
|
||||||
|
import { Box } from "@mui/material";
|
||||||
|
import { cloneDeep } from "lodash";
|
||||||
|
import CommandHistory from "nfc/commandHistory";
|
||||||
|
import NfcCommands from "nfc/nfcCommands";
|
||||||
|
import React, { useState } from "react";
|
||||||
|
import PageContainer from "./PageContainer";
|
||||||
|
|
||||||
|
export interface NfcCommand {
|
||||||
|
type: string;
|
||||||
|
command: "read" | "write";
|
||||||
|
data: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function Nfc() {
|
||||||
|
const [executedCommands, setExecutedCommands] = useState<NfcCommand[]>([]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<PageContainer>
|
||||||
|
<React.Fragment>
|
||||||
|
<Box padding={2}>
|
||||||
|
<CommandHistory commands={executedCommands} />
|
||||||
|
<NfcCommands
|
||||||
|
onCommandRun={commands => {
|
||||||
|
//add to the list of executed commands
|
||||||
|
let c = cloneDeep(executedCommands);
|
||||||
|
c.push(...commands);
|
||||||
|
setExecutedCommands(c);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
</React.Fragment>
|
||||||
|
</PageContainer>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -80,6 +80,24 @@ const V2_Cell_Green: DevicePlatformDescriber = {
|
||||||
platformName: "v2cellgreen"
|
platformName: "v2cellgreen"
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const V2_Cell_Blue: DevicePlatformDescriber = {
|
||||||
|
platform: pond.DevicePlatform.DEVICE_PLATFORM_V2_CELLULAR_BLUE,
|
||||||
|
label: "V2 Cellular Blue",
|
||||||
|
description: "Communicates via cellular",
|
||||||
|
icon: <SignalCellular4Bar style={{ color: "#fff" }} />,
|
||||||
|
offlineIcon: <SignalCellularOff style={{ color: "#fff" }} />,
|
||||||
|
platformName: "v2cellblue"
|
||||||
|
};
|
||||||
|
|
||||||
|
const V2_Wifi_Blue: DevicePlatformDescriber = {
|
||||||
|
platform: pond.DevicePlatform.DEVICE_PLATFORM_V2_WIFI_BLUE,
|
||||||
|
label: "V2 Wifi Blue",
|
||||||
|
description: "Communicates via wifi",
|
||||||
|
icon: <SignalCellular4Bar style={{ color: "#fff" }} />,
|
||||||
|
offlineIcon: <SignalCellularOff style={{ color: "#fff" }} />,
|
||||||
|
platformName: "v2wifiblue"
|
||||||
|
};
|
||||||
|
|
||||||
const map = new Map<pond.DevicePlatform, DevicePlatformDescriber>([
|
const map = new Map<pond.DevicePlatform, DevicePlatformDescriber>([
|
||||||
[pond.DevicePlatform.DEVICE_PLATFORM_INVALID, Default],
|
[pond.DevicePlatform.DEVICE_PLATFORM_INVALID, Default],
|
||||||
[pond.DevicePlatform.DEVICE_PLATFORM_PHOTON, Photon],
|
[pond.DevicePlatform.DEVICE_PLATFORM_PHOTON, Photon],
|
||||||
|
|
@ -87,7 +105,9 @@ const map = new Map<pond.DevicePlatform, DevicePlatformDescriber>([
|
||||||
[pond.DevicePlatform.DEVICE_PLATFORM_V2_CELLULAR, V2_Cellular],
|
[pond.DevicePlatform.DEVICE_PLATFORM_V2_CELLULAR, V2_Cellular],
|
||||||
[pond.DevicePlatform.DEVICE_PLATFORM_V2_WIFI_S3, V2_Wifi_S3],
|
[pond.DevicePlatform.DEVICE_PLATFORM_V2_WIFI_S3, V2_Wifi_S3],
|
||||||
[pond.DevicePlatform.DEVICE_PLATFORM_V2_CELLULAR_BLACK, V2_Cell_Black],
|
[pond.DevicePlatform.DEVICE_PLATFORM_V2_CELLULAR_BLACK, V2_Cell_Black],
|
||||||
[pond.DevicePlatform.DEVICE_PLATFORM_V2_CELLULAR_GREEN, V2_Cell_Green]
|
[pond.DevicePlatform.DEVICE_PLATFORM_V2_CELLULAR_GREEN, V2_Cell_Green],
|
||||||
|
[pond.DevicePlatform.DEVICE_PLATFORM_V2_CELLULAR_BLUE, V2_Cell_Blue],
|
||||||
|
[pond.DevicePlatform.DEVICE_PLATFORM_V2_WIFI_BLUE, V2_Wifi_Blue]
|
||||||
]);
|
]);
|
||||||
|
|
||||||
export function getDevicePlatformDescriber(platform: pond.DevicePlatform): DevicePlatformDescriber {
|
export function getDevicePlatformDescriber(platform: pond.DevicePlatform): DevicePlatformDescriber {
|
||||||
|
|
|
||||||
|
|
@ -149,14 +149,10 @@ export default function FirmwareProvider(props: PropsWithChildren<Props>) {
|
||||||
file: Blob,
|
file: Blob,
|
||||||
breaksStorage: boolean
|
breaksStorage: boolean
|
||||||
) => {
|
) => {
|
||||||
// let headers: { "Content-Type": string } = {
|
|
||||||
// "Content-Type": "multipart/form-data"
|
|
||||||
// };
|
|
||||||
let headers = {
|
let headers = {
|
||||||
"Content-Type": "multipart/form-data",
|
...options().headers,
|
||||||
...options().headers
|
"Content-Type": "multipart/form-data"
|
||||||
};
|
};
|
||||||
headers = { ...headers, ...options().headers };
|
|
||||||
let opt = { headers };
|
let opt = { headers };
|
||||||
let data = new FormData();
|
let data = new FormData();
|
||||||
data.append("version", version);
|
data.append("version", version);
|
||||||
|
|
@ -164,6 +160,7 @@ export default function FirmwareProvider(props: PropsWithChildren<Props>) {
|
||||||
data.append("platform", platformBody(platform));
|
data.append("platform", platformBody(platform));
|
||||||
data.append("file", file);
|
data.append("file", file);
|
||||||
data.append("breaksStorage", breaksStorage.toString());
|
data.append("breaksStorage", breaksStorage.toString());
|
||||||
|
console.log(data)
|
||||||
return post(pondURL("/firmware"), data, opt);
|
return post(pondURL("/firmware"), data, opt);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
80
src/web-nfc.d.ts
vendored
Normal file
80
src/web-nfc.d.ts
vendored
Normal file
|
|
@ -0,0 +1,80 @@
|
||||||
|
// Type definitions for Web NFC
|
||||||
|
// Project: https://github.com/w3c/web-nfc
|
||||||
|
// Definitions by: Takefumi Yoshii <https://github.com/takefumi-yoshii>
|
||||||
|
// TypeScript Version: 3.9
|
||||||
|
|
||||||
|
// This type definitions referenced to WebIDL.
|
||||||
|
// https://w3c.github.io/web-nfc/#actual-idl-index
|
||||||
|
|
||||||
|
interface Window {
|
||||||
|
NDEFMessage: NDEFMessage;
|
||||||
|
}
|
||||||
|
declare class NDEFMessage {
|
||||||
|
constructor(messageInit: NDEFMessageInit);
|
||||||
|
records: ReadonlyArray<NDEFRecord>;
|
||||||
|
}
|
||||||
|
declare interface NDEFMessageInit {
|
||||||
|
records: NDEFRecordInit[];
|
||||||
|
}
|
||||||
|
|
||||||
|
declare type NDEFRecordDataSource = string | BufferSource | NDEFMessageInit;
|
||||||
|
|
||||||
|
interface Window {
|
||||||
|
NDEFRecord: NDEFRecord;
|
||||||
|
}
|
||||||
|
declare class NDEFRecord {
|
||||||
|
constructor(recordInit: NDEFRecordInit);
|
||||||
|
readonly recordType: string;
|
||||||
|
readonly mediaType?: string;
|
||||||
|
readonly id?: string;
|
||||||
|
readonly data?: DataView;
|
||||||
|
readonly encoding?: string;
|
||||||
|
readonly lang?: string;
|
||||||
|
toRecords?: () => NDEFRecord[];
|
||||||
|
}
|
||||||
|
declare interface NDEFRecordInit {
|
||||||
|
recordType: string;
|
||||||
|
mediaType?: string;
|
||||||
|
id?: string;
|
||||||
|
encoding?: string;
|
||||||
|
lang?: string;
|
||||||
|
data?: NDEFRecordDataSource;
|
||||||
|
}
|
||||||
|
|
||||||
|
declare type NDEFMessageSource = string | BufferSource | NDEFMessageInit;
|
||||||
|
|
||||||
|
interface Window {
|
||||||
|
NDEFReader: NDEFReader;
|
||||||
|
}
|
||||||
|
declare class NDEFReader extends EventTarget {
|
||||||
|
constructor();
|
||||||
|
onreading: (this: this, event: NDEFReadingEvent) => any;
|
||||||
|
onreadingerror: (this: this, error: Event) => any;
|
||||||
|
scan: (options?: NDEFScanOptions) => Promise<void>;
|
||||||
|
write: (message: NDEFMessageSource, options?: NDEFWriteOptions) => Promise<void>;
|
||||||
|
makeReadOnly: (options?: NDEFMakeReadOnlyOptions) => Promise<void>;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Window {
|
||||||
|
NDEFReadingEvent: NDEFReadingEvent;
|
||||||
|
}
|
||||||
|
declare class NDEFReadingEvent extends Event {
|
||||||
|
constructor(type: string, readingEventInitDict: NDEFReadingEventInit);
|
||||||
|
serialNumber: string;
|
||||||
|
message: NDEFMessage;
|
||||||
|
}
|
||||||
|
interface NDEFReadingEventInit extends EventInit {
|
||||||
|
serialNumber?: string;
|
||||||
|
message: NDEFMessageInit;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface NDEFWriteOptions {
|
||||||
|
overwrite?: boolean;
|
||||||
|
signal?: AbortSignal;
|
||||||
|
}
|
||||||
|
interface NDEFMakeReadOnlyOptions {
|
||||||
|
signal?: AbortSignal;
|
||||||
|
}
|
||||||
|
interface NDEFScanOptions {
|
||||||
|
signal: AbortSignal;
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue