can provision devices

This commit is contained in:
Carter 2024-12-09 17:26:12 -06:00
parent 0ae37f1370
commit 03f8ed20ca
18 changed files with 1467 additions and 17 deletions

View file

@ -25,7 +25,7 @@ html, body, #root {
}
/* styles.css or a CSS module */
.MuiDialog-root {
/* .MuiDialog-root {
z-index: 1500 !important;
}
@ -34,5 +34,5 @@ html, body, #root {
}
.MuiMenu-root {
z-index: 1400 !important; /* Ensure it's above the drawer */
}
z-index: 1400 !important;
} */

View file

@ -120,6 +120,11 @@ export default function ChatInput(props: Props) {
clearEntry();
};
const getPlaceholder = () => {
if (type === pond.NoteType.NOTE_TYPE_TEAM) return "Message team..."
return "Type to chat..."
}
return (
<React.Fragment>
<Grid
@ -139,6 +144,7 @@ export default function ChatInput(props: Props) {
onChange={e => setMessage(e.target.value)}
onKeyDown={onKeyDown}
onKeyUp={onKeyUp}
placeholder={getPlaceholder()}
/>
<Box
style={{

View file

@ -22,6 +22,7 @@ import { pond } from "protobuf-ts/pond";
import { red } from "@mui/material/colors";
import { makeStyles } from "@mui/styles";
import CancelSubmit from "common/CancelSubmit";
import { getThemeType } from "theme/themeType";
interface Props {
index: number;
@ -35,12 +36,10 @@ const useStyles = makeStyles((theme: Theme) => ({
},
oddGrid: {
backgroundColor: theme.palette.background.paper,
// padding: 4,
padding: theme.spacing(1)
},
evenGrid: {
// backgroundColor: theme.palette.background.default,
// padding: 4
backgroundColor: getThemeType() === "light" ? theme.palette.background.default : undefined,
padding: theme.spacing(1)
},
userAvatar: {
@ -51,8 +50,6 @@ const useStyles = makeStyles((theme: Theme) => ({
}
}))
/**
* Takes in a note and renders it
* handles deleting of the note
@ -132,7 +129,6 @@ export default function ChatMessage(props: Props) {
<Dialog open={dialog} onClose={closeDialog}>
<DialogTitle>Delete Message?</DialogTitle>
<DialogContent>Are you sure you wish to delete this message</DialogContent>
<DialogActions>
<CancelSubmit
onCancel={closeDialog}

View file

@ -0,0 +1,403 @@
import {
Button,
CircularProgress,
DialogActions,
DialogContent,
DialogTitle,
Divider,
Grid2 as Grid,
IconButton,
List,
ListItem,
TextField,
Tooltip,
Typography
} from "@mui/material";
import { FileCopy } from "@mui/icons-material";
import ResponsiveDialog from "common/ResponsiveDialog";
import SearchSelect, { Option } from "common/SearchSelect";
import SelectDevicePlatform from "device/SelectDevicePlatform";
import { useBackpackAPI, useDeviceAPI, usePrevious, useSnackbar } from "hooks";
import { cloneDeep } from "lodash";
import { Backpack, Device } from "models";
import { backpackOptions, genericBackpacks } from "pbHelpers/Backpack";
import { pond } from "protobuf-ts/pond";
import React, { useEffect, useState } from "react";
interface Props {
isOpen: boolean;
refreshCallback: Function;
closeDialogCallback: Function;
}
export default function ProvisionDevice(props: Props) {
const deviceAPI = useDeviceAPI();
const backpackAPI = useBackpackAPI();
const { error, success, warning, info } = useSnackbar();
const { isOpen, closeDialogCallback, refreshCallback } = props;
const [provisioned, setProvisioned] = useState<boolean>(false);
const [idHex, setIdHex] = useState<string>("");
const [keyHex, setKeyHex] = useState<string>("");
const [backpack, setBackpack] = useState<Backpack | undefined>(undefined);
const [name, setName] = useState<string>("");
const [description, setDescription] = useState<string>("");
const [loading, setLoading] = useState<boolean>(false);
const [backpacks, setBackpacks] = useState<Backpack[]>([]);
const [command, setCommand] = useState<string>("");
const [deviceLoading, setDeviceLoading] = useState<boolean>(false);
const [platform, setPlatform] = useState<pond.DevicePlatform>(
pond.DevicePlatform.DEVICE_PLATFORM_PHOTON
);
const [device, setDevice] = useState<Device | undefined>(undefined);
const prevIsOpen = usePrevious(isOpen);
const deviceID: number = parseInt(idHex, 16);
const setDefaultState = () => {
setProvisioned(false);
setIdHex("");
setKeyHex("");
setBackpack(undefined);
setName("");
setDescription("");
setLoading(false);
setBackpacks([]);
setCommand("");
setDeviceLoading(false);
setDevice(undefined);
};
useEffect(() => {
if (isOpen && !prevIsOpen) {
setLoading(true);
backpackAPI
.listBackpacks()
.then((response: any) => {
const rawBackpacks = response.data.backpacks;
let updatedBackpacks: Backpack[] = genericBackpacks();
if (rawBackpacks && rawBackpacks.length > 0) {
rawBackpacks.forEach((backpack: any) => {
updatedBackpacks.push(Backpack.any(backpack));
});
}
setLoading(false);
setBackpacks(updatedBackpacks.sort((a, b) => (a.name() > b.name() ? 1 : -1)));
setBackpack(undefined);
})
.catch((err: any) => {
setDefaultState();
error(err ? err : "Error occured while loading device profiles");
})
.finally(() => setLoading(false));
}
}, [backpackAPI, isOpen, prevIsOpen, error]);
useEffect(() => {
if (isOpen && provisioned && !device && !deviceLoading) {
setDeviceLoading(true);
const deviceID = parseInt(idHex, 16);
deviceAPI
.get(deviceID)
.then(response => {
let rDevice = Device.any(response.data);
setDevice(rDevice);
setName(rDevice.name());
setDescription(rDevice.settings.description);
})
.catch(error => {
console.error(error);
setDevice(
Device.create(
pond.Device.create({ settings: pond.DeviceSettings.create({ deviceId: deviceID }) })
)
);
})
.finally(() => setDeviceLoading(false));
}
}, [device, deviceAPI, deviceLoading, idHex, isOpen, provisioned]);
const close = () => {
if (provisioned) {
refreshCallback();
}
closeDialogCallback();
setDefaultState();
};
const generateCommand = (idHex: any, keyHex: any): string => {
if (!idHex || !keyHex) {
return "";
}
return "set creds " + idHex + ":" + keyHex;
};
const assembleBackpack = (): pond.BackpackSettings => {
let assembledBackpack = backpack ? cloneDeep(backpack) : Backpack.create();
let agnosticDevice = assembledBackpack.settings.device
? cloneDeep(assembledBackpack.settings.device)
: ({} as pond.DeviceSettings);
agnosticDevice.deviceId = 0;
agnosticDevice.platform = platform;
agnosticDevice.product = assembledBackpack.settings.product;
assembledBackpack.settings.device = agnosticDevice;
return assembledBackpack.settings;
};
const submitProvision = () => {
setProvisioned(false);
deviceAPI
.add("", "", assembleBackpack())
.then((response: any) => {
let updatedIdHex = response.data.idHex;
let updatedKeyHex = response.data.keyHex;
setIdHex(updatedIdHex);
setKeyHex(updatedKeyHex);
success("Successfully provisioned a new device (id: " + parseInt(updatedIdHex, 16) + ")");
setProvisioned(true);
setCommand(generateCommand(updatedIdHex, updatedKeyHex));
})
.catch((error: any) => {
error.response.data.error
? warning(error.response.data.error)
: error("Error occured while provisioning a new device.");
close();
});
};
const submitDeviceUpdate = () => {
const deviceID = parseInt(idHex, 16);
let settings = pond.DeviceSettings.create(device ? device.settings : { deviceId: deviceID });
settings.name = name;
settings.description = description;
deviceAPI
.update(deviceID, settings)
.then(() => {
success("Device " + deviceID.toString() + " was successfully updated");
})
.catch((error: any) => {
error.response.data.error
? warning(error.response.data.error)
: error("Error occured while updating device " + deviceID.toString() + ".");
close();
})
.finally(() => {
setDevice(undefined);
});
};
const copyID = () => {
navigator.clipboard.writeText(idHex);
info("ID copied to clipboard");
};
const copyKey = () => {
navigator.clipboard.writeText(keyHex);
info("Key copied to clipboard");
};
const copyCommand = () => {
navigator.clipboard.writeText(command + "\n");
info("Command copied to clipboard");
};
const changeBackpack = (option: Option | null) => {
let selectedBackpack = undefined;
if (option) {
const value = Number(option.value);
selectedBackpack = backpacks.find(backpack => {
let idMatch: boolean = backpack.id() === value;
return idMatch && (value === 0 ? backpack.name() === option.label : true);
});
}
setBackpack(selectedBackpack);
};
const content = () => {
if (provisioned) {
return (
<Grid container spacing={1}>
<Grid size={{xs:12}} container justifyContent="center" alignItems="center">
<Grid size={{xs: 11}}>
<TextField
id="id"
name="ID"
label="ID Hex"
disabled
value={idHex}
margin="normal"
variant="outlined"
type="text"
fullWidth
InputLabelProps={{ shrink: true }}
/>
</Grid>
<Grid size={{xs:1}}>
<Tooltip title="Copy ID">
<IconButton onClick={copyID}>
<FileCopy />
</IconButton>
</Tooltip>
</Grid>
</Grid>
<Grid size={{xs:12}} container justifyContent="center" alignItems="center">
<Grid size={{xs:11}}>
<TextField
id="key"
name="Key"
label="Key Hex"
disabled
value={keyHex}
margin="normal"
variant="outlined"
type="text"
fullWidth
InputLabelProps={{ shrink: true }}
/>
</Grid>
<Grid size={{xs:1}}>
<Tooltip title="Copy Key">
<IconButton onClick={copyKey}>
<FileCopy />
</IconButton>
</Tooltip>
</Grid>
</Grid>
<Grid size={{xs:12}} container justifyContent="center" alignItems="center">
<Grid size={{xs:11}}>
<TextField
id="command"
name="command"
label="Command"
variant="outlined"
value={command}
disabled
fullWidth
InputLabelProps={{ shrink: true }}
/>
</Grid>
<Grid size={{xs:1}}>
<Tooltip title="Copy Command">
<IconButton onClick={copyCommand}>
<FileCopy />
</IconButton>
</Tooltip>
</Grid>
</Grid>
{/* {window.NDEFReader && (
<Button
variant="contained"
color="primary"
onClick={() => {
const ndef = new NDEFReader();
ndef
.write({
records: [{ recordType: "text", data: command }]
})
.then(resp => {
success("Comand sent");
})
.catch(error => {
error("Failed to send command");
});
}}>
Write to Device
</Button>
)} */}
<Grid size={{xs:12}}>
<Divider />
</Grid>
{deviceLoading ? (
<CircularProgress />
) : (
<Grid size={{xs:12}}>
<Typography color="textPrimary" variant="body1" gutterBottom>
Update device name/description
</Typography>
<TextField
margin="normal"
id="deviceName"
label="Name"
type="text"
fullWidth
variant="outlined"
value={name}
onChange={(event: any) => setName(event.target.value)}
/>
<TextField
margin="normal"
id="deviceDescription"
label="Description"
type="text"
multiline
rows="4"
// rowsMax="4"
fullWidth
variant="outlined"
value={description}
onChange={(event: any) => setDescription(event.target.value)}
/>
</Grid>
)}
</Grid>
);
}
const selectedBackpack = backpack
? ({ label: backpack.name(), value: backpack.id() } as Option)
: undefined;
return (
<List>
<ListItem>
<SelectDevicePlatform value={platform} onChange={platform => setPlatform(platform)} />
</ListItem>
<ListItem>
<SearchSelect
label="Select a profile to provision with"
selected={selectedBackpack}
options={backpackOptions(backpacks)}
changeSelection={changeBackpack}
loading={loading}
/>
</ListItem>
</List>
);
};
const actions = () => {
return (
<React.Fragment>
<Button onClick={close} color="primary">
Close
</Button>
{!provisioned ? (
<Button onClick={submitProvision} color="primary" disabled={!backpack}>
Provision
</Button>
) : (
<Button onClick={submitDeviceUpdate} color="primary" disabled={!backpack}>
Update
</Button>
)}
</React.Fragment>
);
};
return (
<ResponsiveDialog open={isOpen} onClose={close} aria-labelledby="form-dialog-title" fullWidth>
<DialogTitle id="form-dialog-title">
Provision Device
{provisioned && (
<Typography variant="body2" color="textSecondary">
Device ID: {deviceID}
</Typography>
)}
</DialogTitle>
<DialogContent>{content()}</DialogContent>
<DialogActions>{actions()}</DialogActions>
</ResponsiveDialog>
);
}

View file

@ -0,0 +1,31 @@
import { MenuItem, TextField } from "@mui/material";
import { GetAllDevicePlatformDescribers } from "pbHelpers/DevicePlatform";
import { pond } from "protobuf-ts/pond";
interface Props {
value: pond.DevicePlatform;
onChange: (platform: pond.DevicePlatform) => void;
}
export default function SelectDevicePlatform(props: Props) {
const { value, onChange } = props;
return (
<TextField
id="select-device-platform"
select
label="Device Platform"
value={pond.DevicePlatform[value]}
fullWidth
variant="outlined"
onChange={event =>
onChange(pond.DevicePlatform[event.target.value as keyof typeof pond.DevicePlatform])
}>
{GetAllDevicePlatformDescribers().map(p => (
<MenuItem key={pond.DevicePlatform[p.platform]} value={pond.DevicePlatform[p.platform]}>
{p.label}
</MenuItem>
))}
</TextField>
);
}

View file

@ -1,11 +1,11 @@
export {
// useAuth,
// useBackpackAPI,
useBackpackAPI,
// useBilling,
// useComponentAPI,
// useComponentsWebsocket,
// useComponentWebsocket,
// useDeviceAPI,
useDeviceAPI,
// useDeviceWebsocket,
// useFirmwareAPI,
// useGitlab,

26
src/models/Backpack.ts Normal file
View file

@ -0,0 +1,26 @@
import { pond } from "protobuf-ts/pond";
import { or } from "utils/types";
import { cloneDeep } from "lodash";
export class Backpack {
public settings: pond.BackpackSettings = pond.BackpackSettings.create();
public static create(pb?: pond.Backpack): Backpack {
let my = new Backpack();
if (pb) {
my.settings = pond.BackpackSettings.fromObject(cloneDeep(or(pb.settings, {})));
}
return my;
}
public static any(data: any): Backpack {
return Backpack.create(pond.Backpack.fromObject(cloneDeep(data)));
}
public id(): number {
return Number(this.settings.backpackId);
}
public name(): string {
return this.settings.name !== "" ? this.settings.name : "Device Profile " + this.id();
}
}

64
src/models/Device.ts Normal file
View file

@ -0,0 +1,64 @@
import { pond } from "protobuf-ts/pond";
import { or } from "utils/types";
import { cloneDeep } from "lodash";
import { MarkerData } from "Maps/mapMarkers/Markers";
export class Device {
public settings: pond.DeviceSettings = pond.DeviceSettings.create();
public status: pond.DeviceStatus = pond.DeviceStatus.create();
public static create(pb?: pond.Device): Device {
let my = new Device();
if (pb) {
my.settings = pond.DeviceSettings.fromObject(or(pb.settings, {}));
my.status = pond.DeviceStatus.fromObject(or(pb.status, {}));
}
return my;
}
public static any(data: any): Device {
if (data.device) return Device.create(pond.Device.fromObject(cloneDeep(data.device)));
return Device.create(pond.Device.fromObject(cloneDeep(data)));
}
public static clone(other: Device): Device {
let my = new Device();
my.settings = pond.DeviceSettings.fromObject(cloneDeep(other.settings));
my.status = pond.DeviceStatus.fromObject(cloneDeep(other.status));
return my;
}
public id(): number {
return Number(this.settings.deviceId);
}
public name(): string {
return this.settings.name !== ""
? this.settings.name
: this.id() > 0
? "Device " + this.id()
: "";
}
public location(): pond.Location {
let loc = pond.Location.create();
loc.longitude = this.settings.longitude;
loc.latitude = this.settings.latitude;
return loc;
}
public getMarkerData(
clickFunc?: (event: React.PointerEvent<HTMLElement>, index: number, isMobile: boolean) => void,
updateFunc?: (location: pond.Location) => void
): MarkerData {
let m: MarkerData = {
longitude: this.location()?.longitude ?? 0,
latitude: this.location()?.latitude ?? 0,
title: this.name(),
colour: this.settings.theme?.color ?? "blue",
clickFunc: clickFunc,
updateFunc: updateFunc
};
return m;
}
}

View file

@ -1,8 +1,8 @@
// export * from "./Backpack";
export * from "./Backpack";
// export * from "./Bin";
// export * from "./BinYard";
// export * from "./Component";
// export * from "./Device";
export * from "./Device";
// export * from "./Firmware";
// export * from "./Group";
// export * from "./Interaction";

View file

@ -11,6 +11,7 @@ import BottomNavigator from "./BottomNavigator";
import TeamPage from "pages/Team";
import Header from "app/Header";
import Logout from "pages/Logout";
import Devices from "pages/Devices";
interface Props {
open: boolean,
@ -70,6 +71,7 @@ export default function Router(props: Props) {
<Route path="/teams" element={<Teams/>} />
<Route path="/teams/:teamID" element={<TeamPage/>} />
<Route path="/users" element={<Users/>} />
<Route path="/devices" element={<Devices/>} />
{/* <Route
path="/teams/:teamID"
element={<Team />}

42
src/pages/Devices.tsx Normal file
View file

@ -0,0 +1,42 @@
import { DeveloperBoard as ProvisionIcon } from "@mui/icons-material";
import { IconButton, Theme, Tooltip } from "@mui/material";
import { blue } from "@mui/material/colors";
import { makeStyles } from "@mui/styles";
import ProvisionDevice from "device/ProvisionDevice";
import { useState } from "react";
const useStyles = makeStyles((theme: Theme) => {
return ({
provisionIcon: {
color: blue["700"]
}
});
});
export default function Devices() {
const classes = useStyles();
const [isProvisionDialogOpen, setIsProvisionDialogOpen] = useState<boolean>(false);
const openProvisionDialog = () => {
setIsProvisionDialogOpen(true);
};
const closeProvisionDialog = () => {
setIsProvisionDialogOpen(false);
};
return(
<>
<Tooltip title="Provision Device">
<IconButton onClick={openProvisionDialog} aria-label="Provision Device">
<ProvisionIcon className={classes.provisionIcon} />
</IconButton>
</Tooltip>
<ProvisionDevice
isOpen={isProvisionDialogOpen}
refreshCallback={/*load*/()=>{}}
closeDialogCallback={closeProvisionDialog}
/>
</>
)
}

25
src/pbHelpers/Backpack.ts Normal file
View file

@ -0,0 +1,25 @@
import { Backpack } from "models";
import { pond } from "protobuf-ts/pond";
import { Option } from "common/SearchSelect";
export function backpackOptions(backpacks: Backpack[]): Option[] {
return backpacks.map(b => {
return { value: b.settings.backpackId, label: b.name() } as Option;
});
}
export function genericBackpacks(): Backpack[] {
let basic = pond.Backpack.fromObject({
settings: {
backpackId: 0,
name: "Basic",
device: pond.DeviceSettings.fromObject({
name: "Basic",
pondCheckPeriodS: 60,
upgradeChannel: pond.UpgradeChannel.UPGRADE_CHANNEL_STABLE,
automaticallyUpgrade: false
})
}
});
return [Backpack.create(basic)];
}

View file

@ -0,0 +1,116 @@
import React from "react";
import {
DeviceUnknown,
SignalCellular4Bar,
SignalWifi4Bar,
SignalCellularOff,
SignalWifiOff
} from "@mui/icons-material";
import { pond } from "protobuf-ts/pond";
export interface DevicePlatformDescriber {
platform: pond.DevicePlatform;
label: string;
description: string;
icon: any;
offlineIcon: any;
platformName: string;
}
const Default: DevicePlatformDescriber = {
platform: pond.DevicePlatform.DEVICE_PLATFORM_INVALID,
label: "Unknown",
description: "Unknown communication platform",
icon: <DeviceUnknown style={{ color: "#fff" }} />,
offlineIcon: <DeviceUnknown style={{ color: "#fff" }} />,
platformName: "unknown"
};
const Photon: DevicePlatformDescriber = {
platform: pond.DevicePlatform.DEVICE_PLATFORM_PHOTON,
label: "Wi-Fi",
description: "Communicates via Wi-Fi",
icon: <SignalWifi4Bar style={{ color: "#fff" }} />,
offlineIcon: <SignalWifiOff style={{ color: "#fff" }} />,
platformName: "photon"
};
const Electron: DevicePlatformDescriber = {
platform: pond.DevicePlatform.DEVICE_PLATFORM_ELECTRON,
label: "Cellular",
description: "Communicates via cellular",
icon: <SignalCellular4Bar style={{ color: "#fff" }} />,
offlineIcon: <SignalCellularOff style={{ color: "#fff" }} />,
platformName: "electron"
};
const V2_Cellular: DevicePlatformDescriber = {
platform: pond.DevicePlatform.DEVICE_PLATFORM_V2_CELLULAR,
label: "V2 Cellular",
description: "Communicates via cellular",
icon: <SignalCellular4Bar style={{ color: "#fff" }} />,
offlineIcon: <SignalCellularOff style={{ color: "#fff" }} />,
platformName: "v2cellular"
};
const V2_Wifi_S3: DevicePlatformDescriber = {
platform: pond.DevicePlatform.DEVICE_PLATFORM_V2_WIFI_S3,
label: "V2 Wifi S3",
description: "Communicates via cellular",
icon: <SignalCellular4Bar style={{ color: "#fff" }} />,
offlineIcon: <SignalCellularOff style={{ color: "#fff" }} />,
platformName: "v2wifis3"
};
const V2_Cell_Black: DevicePlatformDescriber = {
platform: pond.DevicePlatform.DEVICE_PLATFORM_V2_CELLULAR_BLACK,
label: "V2 Cellular Black",
description: "Communicates via cellular",
icon: <SignalCellular4Bar style={{ color: "#fff" }} />,
offlineIcon: <SignalCellularOff style={{ color: "#fff" }} />,
platformName: "v2cellblack"
};
const V2_Cell_Green: DevicePlatformDescriber = {
platform: pond.DevicePlatform.DEVICE_PLATFORM_V2_CELLULAR_GREEN,
label: "V2 Cellular Green",
description: "Communicates via cellular",
icon: <SignalCellular4Bar style={{ color: "#fff" }} />,
offlineIcon: <SignalCellularOff style={{ color: "#fff" }} />,
platformName: "v2cellgreen"
};
const map = new Map<pond.DevicePlatform, DevicePlatformDescriber>([
[pond.DevicePlatform.DEVICE_PLATFORM_INVALID, Default],
[pond.DevicePlatform.DEVICE_PLATFORM_PHOTON, Photon],
[pond.DevicePlatform.DEVICE_PLATFORM_ELECTRON, Electron],
[pond.DevicePlatform.DEVICE_PLATFORM_V2_CELLULAR, V2_Cellular],
[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_GREEN, V2_Cell_Green]
]);
export function getDevicePlatformDescriber(platform: pond.DevicePlatform): DevicePlatformDescriber {
const describer = map.get(platform);
return describer ? describer : Default;
}
export function getDevicePlatformLabel(platform: pond.DevicePlatform): string {
return getDevicePlatformDescriber(platform).label;
}
export function getDevicePlatformDescription(platform: pond.DevicePlatform): string {
return getDevicePlatformDescriber(platform).description;
}
export function getDevicePlatformIcon(platform: pond.DevicePlatform): any {
return getDevicePlatformDescriber(platform).icon;
}
export function getDevicePlatformName(platform: pond.DevicePlatform): string {
return getDevicePlatformDescriber(platform).platformName;
}
export function GetAllDevicePlatformDescribers(): DevicePlatformDescriber[] {
return [...map.values()].filter(p => p.platform !== pond.DevicePlatform.DEVICE_PLATFORM_INVALID);
}

View file

@ -3,14 +3,14 @@
// export { GitlabContext, useGitlab } from "./gitlab";
export { HTTPContext, useHTTP } from "./http";
export {
// useBackpackAPI,
useBackpackAPI,
useBinAPI,
// useBinYardAPI,
useNoteAPI,
// useComponentAPI,
// useComponentsWebsocket,
// useComponentWebsocket,
// useDeviceAPI,
useDeviceAPI,
// useDeviceWebsocket,
// useFieldAPI,
// useFieldMarkerAPI,

View file

@ -0,0 +1,59 @@
import { useHTTP } from "hooks";
import { pond } from "protobuf-ts/pond";
import { createContext, PropsWithChildren, useContext } from "react";
import { pondURL } from "./pond";
export interface IBackpackAPI {
addBackpack: (backpack: pond.Backpack) => Promise<any>;
updateBackpack: (backpackID: number | Long, backpack: pond.Backpack) => Promise<any>;
removeBackpack: (backpackID: number | Long) => Promise<any>;
getBackpack: (backpackID: number | Long) => Promise<any>;
listBackpacks: () => Promise<any>;
}
export const BackpackAPIContext = createContext<IBackpackAPI>({} as IBackpackAPI);
export default function BackpackProvider(props: PropsWithChildren<any>) {
const { children } = props;
const { get, post, put, del } = useHTTP();
const addBackpack = (backpack: pond.Backpack) => {
const url = pondURL("/backpacks");
return post(url, backpack);
};
const updateBackpack = (backpackID: number | Long, backpack: pond.Backpack) => {
const url = pondURL("/backpacks/" + backpackID);
return put(url, backpack);
};
const removeBackpack = (backpackID: number | Long) => {
const url = pondURL("/backpacks/" + backpackID);
return del(url);
};
const getBackpack = (backpackID: number | Long) => {
const url = pondURL("/backpacks/" + backpackID);
return get(url);
};
const listBackpacks = () => {
const url = pondURL("/backpacks");
return get(url);
};
return (
<BackpackAPIContext.Provider
value={{
addBackpack,
updateBackpack,
removeBackpack,
getBackpack,
listBackpacks
}}>
{children}
</BackpackAPIContext.Provider>
);
}
export const useBackpackAPI = () => useContext(BackpackAPIContext);

View file

@ -0,0 +1,668 @@
import { useHTTP, usePermissionAPI } from "hooks";
// import { useWebsocket } from "websocket";
import { /*Component,*/ deviceScope, User } from "models";
import { pond } from "protobuf-ts/pond";
import { createContext, PropsWithChildren, useContext } from "react";
import { pondURL } from "./pond";
import { AxiosResponse } from "axios";
import { useGlobalState } from "providers";
import { dateRange } from "providers/http";
import moment from "moment";
export interface IDeviceAPIContext {
add: (name: string, description: string, backpack: pond.BackpackSettings) => Promise<any>;
update: (id: number, settings: pond.DeviceSettings) => Promise<any>;
remove: (id: number) => Promise<any>;
get: (id: number | string, demo?: boolean, keys?: string[], types?: string[]) => Promise<any>;
getPageData: (
id: number | string,
keys?: string[],
types?: string[]
) => Promise<AxiosResponse<pond.GetDevicePageDataResponse>>;
getMulti: (ids: number[] | string[]) => Promise<any>;
getGeoJson: (id: number | string, demo?: boolean) => Promise<any>;
getMultiGeoJson: (ids: number[] | string[]) => Promise<any>;
list: (
limit: number,
offset: number,
order?: "asc" | "desc",
orderBy?: string,
search?: string,
settings?: string,
status?: string,
ids?: number[] | string[] | Long[],
asRoot?: boolean,
comprehensive?: boolean,
withMeasurements?: boolean,
keys?: string[],
types?: string[]
) => Promise<AxiosResponse<pond.ListDevicesResponse>>;
listForUser: (
asRoot: boolean,
user?: string,
search?: string,
hologram?: boolean,
keys?: string[],
types?: string[]
) => Promise<AxiosResponse<pond.ListDevicesResponse>>;
claim: (id: number | Long, body: any) => Promise<any>;
listHistory: (id: number, limit: number, offset: number) => Promise<any>;
listCompleteHistory: (
id: number,
limit: number,
offset: number,
filters?: pond.ObjectType[]
) => Promise<AxiosResponse<pond.ListCompleteHistoryResponse>>;
updatePermissions: (id: number | string, users: User[]) => Promise<any>;
updatePreferences: (
id: number | string,
preferences: pond.UserPreferences,
keys?: string[],
types?: string[]
) => Promise<any>;
updateComponentPreferences: (
id: number | string,
component: string,
preferences: pond.DeviceComponentPreferences,
keys?: string[],
types?: string[]
) => Promise<any>;
listDeviceComponentPreferences: (
id: number | string,
keys?: string[],
types?: string[]
) => Promise<AxiosResponse<pond.ListDeviceComponentPreferencesResponse>>;
updateStatus: (
id: number | string,
status: pond.DeviceStatus,
keys?: string[],
types?: string[]
) => Promise<any>;
sync: (id: number) => Promise<any>;
clearPending: (id: number) => Promise<any>;
pause: (id: number) => Promise<any>;
bulkPause: (
ids: number[],
setPaused: boolean
) => Promise<AxiosResponse<pond.BulkPauseDeviceResponse>>;
bulkChangeDataCaps: (
ids: number[],
newCap: number
) => Promise<AxiosResponse<pond.BulkChangeDataCapsResponse>>;
resume: (id: number) => Promise<any>;
getUpgradeStatus: (id: number, keys?: string[], types?: string[]) => Promise<any>;
loadBackpack: (id: number, backpack: number) => Promise<any>;
setTags: (id: number, tags: string[]) => Promise<any>;
setWifi: (
id: number,
gateway: string,
password: string,
keys?: string[],
types?: string[]
) => Promise<any>;
tag: (id: number, tag: string) => Promise<any>;
untag: (id: number, tag: string) => Promise<any>;
getDatacap: (id: number) => Promise<any>;
isOverLimit: (id: number) => Promise<any>;
isPaused: (id: number) => Promise<any>;
setDatacap: (id: number, newCap: number) => Promise<any>;
// listJSONMeasurements: (
// id: number,
// components: Component[],
// startDate: any,
// endDate: any,
// limit: number,
// offset: number,
// order: string,
// orderBy: string
// ) => Promise<AxiosResponse<pond.ListDeviceExportedMeasurementsResponse>>;
listSimpleJSON: (device: number) => Promise<any>;
resetQuackCount: (id: number) => Promise<any>;
resetQuackCountTx: (id: number) => Promise<any>;
resetQuackCountTx1000: (id: number) => Promise<any>;
linearMutation: (
id: number,
mutation: pond.LinearMutation
) => Promise<AxiosResponse<pond.DeviceLinearMutationResponse>>;
getDataUsage: (id: number, start?: any) => Promise<any>;
buyData: (
id: number | string,
MB: number,
source?: string,
keys?: string[],
types?: string[]
) => Promise<any>;
}
export const DeviceAPIContext = createContext<IDeviceAPIContext>({} as IDeviceAPIContext);
interface Props {}
export default function DeviceProvider(props: PropsWithChildren<Props>) {
const { children } = props;
const { get, post, put, del } = useHTTP();
const permissionAPI = usePermissionAPI();
const [{ as, team }] = useGlobalState();
const add = (name: string, description: string, backpack: pond.BackpackSettings) => {
if (as) return post(pondURL("/devices?as=" + as), { name, description, backpack });
return post(pondURL("/devices"), { name, description, backpack });
};
const getDevice = (
id: number | string,
demo: boolean = false,
keys?: string[],
types?: string[]
) => {
if (as && !(types && types.length > 0 && types[0] === "team"))
return get(
pondURL(
"/devices/" +
id +
"?as=" +
as +
(keys ? "&keys=" + keys.toString() : "") +
(types ? "&types=" + types.toString() : ""),
demo
)
);
const url = pondURL(
"/devices/" +
id +
(keys ? "?keys=" + keys.toString() : "") +
(types ? "&types=" + types.toString() : ""),
demo
);
return get(url);
};
const getDevicePageData = (
id: number | string,
keys?: string[],
types?: string[]
): Promise<AxiosResponse<pond.GetDevicePageDataResponse>> => {
if (as && !(types && types.length > 0 && types[0] === "team"))
return get(
pondURL(
"/devicePageData/" +
id +
"?as=" +
as +
(keys ? "&keys=" + keys.toString() : "") +
(types ? "&types=" + types.toString() : "")
)
);
const url = pondURL(
"/devicePageData/" +
id +
(keys ? "?keys=" + keys.toString() : "") +
(types ? "&types=" + types.toString() : "")
);
return get<pond.GetDevicePageDataResponse>(url);
};
const getDeviceGeoJSON = (id: number | string, demo: boolean = false) => {
if (as) return get(pondURL("/devices/" + id + "/geojson?as=" + as, demo));
const url = pondURL("/devices/" + id + "/geojson", demo);
return get(url);
};
const getMulti = (ids: number[] | string[]) => {
let idString = "";
ids.forEach((id: number | string, i: number) => {
if (i === 0) {
idString = idString + id;
} else {
idString = idString + "," + id;
}
});
if (as) return get(pondURL("/multidevices?devices=" + idString + "&as=" + as));
return get(pondURL("/multidevices?devices=" + idString));
};
const getMultiDevicesGeoJSON = (ids: number[] | string[]) => {
let idString = "";
ids.forEach((id: number | string, i: number) => {
if (i === 0) {
idString = idString + id;
} else {
idString = idString + "," + id;
}
});
if (as) return get(pondURL("/geojson/devices?devices=" + idString + "&as=" + as));
return get(pondURL("/geojson/devices?devices=" + idString));
};
const list = (
limit: number,
offset: number,
order?: "asc" | "desc",
orderBy?: string,
search?: string,
settings?: string,
status?: string,
ids?: number[] | string[] | Long[],
asRoot?: boolean,
comprehensive?: boolean,
withMeasurements?: boolean,
keys?: string[],
types?: string[]
) => {
const url = pondURL(
"/devices" +
"?limit=" +
limit +
"&offset=" +
offset +
("&order=" + (order ? order : "asc")) +
("&by=" + (orderBy ? orderBy : "deviceId")) +
(search ? "&search=" + search : "") +
(settings ? "&settings=" + settings : "") +
(status ? "&status=" + status : "") +
(ids ? "&ids=" + ids.join(",") : "") +
(asRoot ? "&asRoot=" + asRoot.toString() : "") +
(as && !(types && types.length > 0 && types[0] === "team") ? "&as=" + as : "") +
(comprehensive ? "&comprehensive=" + comprehensive.toString() : "") +
(withMeasurements ? "&measurements=" + withMeasurements.toString() : "") +
(keys ? "&keys=" + keys.toString() : "") +
(types ? "&types=" + types.toString() : "")
);
return get<pond.ListDevicesResponse>(url);
};
const listForUser = (
asRoot: boolean,
user?: string,
search?: string,
hologram?: boolean,
keys?: string[],
types?: string[]
) => {
const url = pondURL(
"/devices" +
(user ? "?as=" + user : "?asRoot=" + asRoot.toString()) +
(search ? "&search=" + search : "") +
(hologram ? "&hologram=" + hologram.toString() : "") +
(keys ? "&keys=" + keys.toString() : "") +
(types ? "&types=" + types.toString() : "")
);
return get<pond.ListDevicesResponse>(url);
};
const remove = (id: number) => {
if (as) return del(pondURL("/devices/" + id + "?as=" + as));
const url = pondURL("/devices/" + id);
return del(url);
};
const update = (id: number, settings: pond.DeviceSettings) => {
if (as) return put(pondURL(`/devices/${id}/update?as=${as}`), settings);
const url = pondURL("/devices/" + id + "/update");
return put(url, settings);
};
const claim = (id: number | Long, body: any) => {
return put(pondURL("/devices/" + id), body);
};
const listHistory = (id: number, limit: number, offset: number) => {
return get(pondURL("/devices/" + id + "/history?limit=" + limit + "&offset=" + offset));
};
const listCompleteHistory = (
id: number,
limit: number,
offset: number,
filters?: pond.ObjectType[]
) => {
return get<pond.ListCompleteHistoryResponse>(
pondURL(
"/devices/" +
id +
"/completehistory?limit=" +
limit +
"&offset=" +
offset +
(filters ? "&sources=" + filters.toString() : "")
)
);
};
const updateDevicePermissions = (id: number | string, users: User[]) => {
return permissionAPI.updatePermissions(deviceScope(id.toString()), users);
};
const updatePreferences = (
id: number | string,
preferences: pond.UserPreferences,
keys?: string[],
types?: string[]
) => {
return put(
pondURL(
"/devices/" +
id +
"/preferences" +
"?as=" +
as +
(keys ? "&keys=" + keys.toString() : "") +
(types ? "&types=" + types.toString() : "")
),
preferences
);
};
const updateComponentPreferences = (
id: number | string,
component: string,
preferences: pond.DeviceComponentPreferences,
keys?: string[],
types?: string[]
) => {
return put(
pondURL(
"/devices/" +
id +
"/components/" +
component +
"/componentPreferences" +
"?as=" +
as +
(keys ? "&keys=" + keys.toString() : "") +
(types ? "&types=" + types.toString() : "")
),
preferences
);
};
const listDeviceComponentPreferences = (
id: number | string,
keys?: string[],
types?: string[]
) => {
return get<pond.ListDeviceComponentPreferencesResponse>(
pondURL(
"/devices/" +
id +
"/componentPreferences?as=" +
as +
(keys ? "&keys=" + keys.toString() : "") +
(types ? "&types=" + types.toString() : "")
)
);
};
const updateStatus = (
id: number | string,
status: pond.DeviceStatus,
keys?: string[],
types?: string[]
) => {
return put(
pondURL(
"/devices/" +
id +
"/updateStatus" +
"?as=" +
as +
(keys ? "&keys=" + keys.toString() : "") +
(types ? "&types=" + types.toString() : "")
),
status
);
};
const sync = (id: number) => {
return post(pondURL("/devices/" + id + "/sync"), {});
};
const clearPending = (id: number) => {
return post(pondURL("/devices/" + id + "/clearPending"), {});
};
const pause = (id: number) => {
return post(pondURL("/devices/" + id + "/pause"), {});
};
const bulkPause = (ids: number[], setPaused: boolean) => {
return post<pond.BulkPauseDeviceResponse>(
pondURL(
"/bulkPauseDevices?ids=" +
ids.toString() +
(setPaused ? "&pause=" + setPaused.toString() : "")
),
{}
);
};
const bulkChangeDataCaps = (ids: number[], newCap: number) => {
return post<pond.BulkChangeDataCapsResponse>(
pondURL("/bulkChangeDataCap?ids=" + ids.toString() + "&dataCap=" + newCap),
{}
);
};
const resume = (id: number) => {
return post(pondURL("/devices/" + id + "/resume"), {});
};
const getUpgradeStatus = (id: number, keys?: string[], types?: string[]) => {
let a = as ? "?as=" + as : "";
if (keys && keys.length > 0 && types && types.length > 0) {
a = as ? a + "&keys=" + keys + "&types=" + types : "?keys=" + keys + "&types=" + types;
}
let url = pondURL("/devices/" + id + "/firmware" + a);
return get(url);
};
const loadBackpack = (id: number, backpack: number) => {
return put(pondURL("/devices/" + id + "/load/" + backpack), {});
};
const setTags = (id: number, tags: string[]) => {
return put(pondURL("/devices/" + id + "/tags"), { tags });
};
const setWifi = (
id: number,
gateway: string,
password: string,
keys?: string[],
types?: string[]
) => {
let a = as ? "?as=" + as : "";
if (keys && keys.length > 0 && types && types.length > 0) {
a = as ? a + "&keys=" + keys + "&types=" + types : "?keys=" + keys + "&types=" + types;
}
return put(pondURL("/devices/" + id + "/wifi" + a), { gateway, password });
};
const tag = (id: number, tag: string) => {
return put(pondURL("/devices/" + id + "/tags/" + tag), {});
};
const untag = (id: number, tag: string) => {
return del(pondURL("/devices/" + id + "/tags/" + tag));
};
const getDatacap = (id: number) => {
return get(pondURL("/devices/" + id + "/datacap/"));
};
const isOverLimit = (id: number) => {
return get(pondURL("/devices/" + id + "/overlimit/"));
};
const isPaused = (id: number) => {
return get(pondURL("/devices/" + id + "/paused/"));
};
const setDatacap = (id: number, newCap: number) => {
return post(pondURL("/devices/" + id + "/datacap?limit=" + newCap));
};
const getDataUsage = (id: number, start?: any) => {
if (start) {
start = "?start=" + moment(start).toISOString();
} else {
start = "";
}
return get(pondURL("/devices/" + id + "/usage/" + start));
};
// const listJSONMeasurements = (
// id: number,
// components: Component[],
// startDate: any,
// endDate: any,
// limit: number,
// offset: number,
// order: string,
// orderBy: string
// ) => {
// let keyString = "";
// components.forEach((comp, i) => {
// if (i === 0) {
// keyString = keyString + comp.key();
// } else {
// keyString = keyString + "," + comp.key();
// }
// });
// if (as) {
// return get<pond.ListDeviceExportedMeasurementsResponse>(
// pondURL(
// "/devices/" +
// id +
// "/measurements/exportComplex" +
// dateRange(startDate, endDate) +
// "&components=" +
// keyString +
// "&limit=" +
// limit +
// "&offset=" +
// offset +
// "&order=" +
// order +
// "&orderBy=" +
// orderBy +
// "&as=" +
// as
// )
// );
// }
// return get<pond.ListDeviceExportedMeasurementsResponse>(
// pondURL(
// "/devices/" +
// id +
// "/measurements/exportComplex" +
// dateRange(startDate, endDate) +
// "&components=" +
// keyString +
// "&limit=" +
// limit +
// "&offset=" +
// offset +
// "&order=" +
// order +
// "&orderBy=" +
// orderBy
// )
// );
// };
const listSimpleJSON = (device: number) => {
return get(pondURL("/devices/" + device + "/measurements/exportSimple"));
};
const resetQuackCount = (id: number) => {
return post(pondURL("/devices/" + id + "/resetCounts"));
};
const resetQuackCountTx = (id: number) => {
return post(pondURL("/devices/" + id + "/resetCountTx"));
};
const resetQuackCountTx1000 = (id: number) => {
return post(pondURL("/devices/" + id + "/partialResetCountTx"));
};
const linearMutation = (id: number, mutation: pond.LinearMutation) => {
return post<pond.DeviceLinearMutationResponse>(
pondURL("/devices/" + id + "/linearMutation"),
mutation
);
};
const buyData = (
id: number | string,
MB: number,
source?: string,
keys?: string[],
types?: string[]
) => {
let url = "/devices/" + id + "/buyData?MB=" + MB;
if (as === team.key()) url = url + "&team=" + team.key();
if (source) url = url + "&source=" + source;
if (keys) url = url + "&keys=" + keys;
if (types) url = url + "&types=" + types;
return put(pondURL(url));
};
return (
<DeviceAPIContext.Provider
value={{
add,
update,
remove,
get: getDevice,
getPageData: getDevicePageData,
getMulti,
getGeoJson: getDeviceGeoJSON,
getMultiGeoJson: getMultiDevicesGeoJSON,
list,
claim,
listHistory,
listCompleteHistory,
updatePermissions: updateDevicePermissions,
updateStatus,
updatePreferences,
clearPending,
sync,
pause,
bulkPause,
bulkChangeDataCaps,
resume,
getUpgradeStatus,
loadBackpack,
setTags,
setWifi,
tag,
untag,
getDatacap,
isOverLimit,
isPaused,
setDatacap,
// listJSONMeasurements,
listSimpleJSON,
resetQuackCount,
resetQuackCountTx,
resetQuackCountTx1000,
listForUser,
linearMutation,
getDataUsage,
buyData,
updateComponentPreferences,
listDeviceComponentPreferences
}}>
{children}
</DeviceAPIContext.Provider>
);
}
export const useDeviceAPI = () => useContext(DeviceAPIContext);
// export const useDeviceWebsocket = (
// id: number | string,
// emitter: (m: any) => void,
// rate: number = 0
// ) => useWebsocket("/devices/" + id, m => Device.any(JSON.parse(m.data)), emitter, rate);

View file

@ -7,6 +7,8 @@ import { Scope } from "models";
import BinProvider, { useBinAPI } from "./binAPI";
import GateProvider, { useGateAPI } from "./gateAPI";
import NoteProvider, { useNoteAPI } from "./noteAPI";
import DeviceProvider, { useDeviceAPI } from "./deviceAPI";
import BackpackProvider, { useBackpackAPI } from "./backpackAPI";
// import NoteProvider from "providers/noteAPI";
export const pondURL = (partial: string, demo: boolean = false): string => {
@ -30,7 +32,11 @@ export default function PondProvider(props: PropsWithChildren<any>) {
<BinProvider>
<GateProvider>
<NoteProvider>
{children}
<DeviceProvider>
<BackpackProvider>
{children}
</BackpackProvider>
</DeviceProvider>
</NoteProvider>
</GateProvider>
</BinProvider>
@ -41,6 +47,8 @@ export default function PondProvider(props: PropsWithChildren<any>) {
}
export {
useBackpackAPI,
useDeviceAPI,
useUserAPI,
useTeamAPI,
useImagekitAPI,

View file

@ -30,6 +30,10 @@ function options(themeType: "light" | "dark"): ThemeOptions {
const highlight = themeType === "light" ? "black" : "white";
const bg = generateBackgroundShades(themeType)
return {
zIndex: {
modal: 1300,
popover: 1350
},
palette: {
primary: Colours[getPrimaryColour() as keyof typeof Colours],
secondary: Colours[getSecondaryColour() as keyof typeof Colours],
@ -122,7 +126,7 @@ function options(themeType: "light" | "dark"): ThemeOptions {
overflowX: "hidden"
},
root: {
zIndex: 1500
// zIndex: 1500
}
},
},