diff --git a/src/app/App.css b/src/app/App.css index 6d337d1..924b773 100644 --- a/src/app/App.css +++ b/src/app/App.css @@ -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 */ -} \ No newline at end of file + z-index: 1400 !important; +} */ \ No newline at end of file diff --git a/src/chat/ChatInput.tsx b/src/chat/ChatInput.tsx index 07a0496..a3753b8 100644 --- a/src/chat/ChatInput.tsx +++ b/src/chat/ChatInput.tsx @@ -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 ( setMessage(e.target.value)} onKeyDown={onKeyDown} onKeyUp={onKeyUp} + placeholder={getPlaceholder()} /> ({ }, 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) { Delete Message? Are you sure you wish to delete this message - (false); + const [idHex, setIdHex] = useState(""); + const [keyHex, setKeyHex] = useState(""); + const [backpack, setBackpack] = useState(undefined); + const [name, setName] = useState(""); + const [description, setDescription] = useState(""); + const [loading, setLoading] = useState(false); + const [backpacks, setBackpacks] = useState([]); + const [command, setCommand] = useState(""); + const [deviceLoading, setDeviceLoading] = useState(false); + const [platform, setPlatform] = useState( + pond.DevicePlatform.DEVICE_PLATFORM_PHOTON + ); + const [device, setDevice] = useState(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 ( + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {/* {window.NDEFReader && ( + + )} */} + + + + {deviceLoading ? ( + + ) : ( + + + Update device name/description + + setName(event.target.value)} + /> + setDescription(event.target.value)} + /> + + )} + + ); + } + + const selectedBackpack = backpack + ? ({ label: backpack.name(), value: backpack.id() } as Option) + : undefined; + + return ( + + + setPlatform(platform)} /> + + + + + + ); + }; + + const actions = () => { + return ( + + + {!provisioned ? ( + + ) : ( + + )} + + ); + }; + + return ( + + + Provision Device + {provisioned && ( + + Device ID: {deviceID} + + )} + + {content()} + {actions()} + + ); +} diff --git a/src/device/SelectDevicePlatform.tsx b/src/device/SelectDevicePlatform.tsx new file mode 100644 index 0000000..9022a02 --- /dev/null +++ b/src/device/SelectDevicePlatform.tsx @@ -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 ( + + onChange(pond.DevicePlatform[event.target.value as keyof typeof pond.DevicePlatform]) + }> + {GetAllDevicePlatformDescribers().map(p => ( + + {p.label} + + ))} + + ); +} diff --git a/src/hooks/index.ts b/src/hooks/index.ts index 4f5b58b..e018477 100644 --- a/src/hooks/index.ts +++ b/src/hooks/index.ts @@ -1,11 +1,11 @@ export { // useAuth, -// useBackpackAPI, + useBackpackAPI, // useBilling, // useComponentAPI, // useComponentsWebsocket, // useComponentWebsocket, -// useDeviceAPI, + useDeviceAPI, // useDeviceWebsocket, // useFirmwareAPI, // useGitlab, diff --git a/src/models/Backpack.ts b/src/models/Backpack.ts new file mode 100644 index 0000000..cb796f1 --- /dev/null +++ b/src/models/Backpack.ts @@ -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(); + } +} diff --git a/src/models/Device.ts b/src/models/Device.ts new file mode 100644 index 0000000..33bcb49 --- /dev/null +++ b/src/models/Device.ts @@ -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, 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; + } +} diff --git a/src/models/index.ts b/src/models/index.ts index ef5ba61..8f71e36 100644 --- a/src/models/index.ts +++ b/src/models/index.ts @@ -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"; diff --git a/src/navigation/Router.tsx b/src/navigation/Router.tsx index 0215bd4..804cbbd 100644 --- a/src/navigation/Router.tsx +++ b/src/navigation/Router.tsx @@ -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) { } /> } /> } /> + } /> {/* } diff --git a/src/pages/Devices.tsx b/src/pages/Devices.tsx new file mode 100644 index 0000000..5f95428 --- /dev/null +++ b/src/pages/Devices.tsx @@ -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(false); + + const openProvisionDialog = () => { + setIsProvisionDialogOpen(true); + }; + + const closeProvisionDialog = () => { + setIsProvisionDialogOpen(false); + }; + + return( + <> + + + + + + {}} + closeDialogCallback={closeProvisionDialog} + /> + + ) +} \ No newline at end of file diff --git a/src/pbHelpers/Backpack.ts b/src/pbHelpers/Backpack.ts new file mode 100644 index 0000000..bc66982 --- /dev/null +++ b/src/pbHelpers/Backpack.ts @@ -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)]; +} diff --git a/src/pbHelpers/DevicePlatform.tsx b/src/pbHelpers/DevicePlatform.tsx new file mode 100644 index 0000000..86cb4a8 --- /dev/null +++ b/src/pbHelpers/DevicePlatform.tsx @@ -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: , + offlineIcon: , + platformName: "unknown" +}; + +const Photon: DevicePlatformDescriber = { + platform: pond.DevicePlatform.DEVICE_PLATFORM_PHOTON, + label: "Wi-Fi", + description: "Communicates via Wi-Fi", + icon: , + offlineIcon: , + platformName: "photon" +}; + +const Electron: DevicePlatformDescriber = { + platform: pond.DevicePlatform.DEVICE_PLATFORM_ELECTRON, + label: "Cellular", + description: "Communicates via cellular", + icon: , + offlineIcon: , + platformName: "electron" +}; + +const V2_Cellular: DevicePlatformDescriber = { + platform: pond.DevicePlatform.DEVICE_PLATFORM_V2_CELLULAR, + label: "V2 Cellular", + description: "Communicates via cellular", + icon: , + offlineIcon: , + platformName: "v2cellular" +}; + +const V2_Wifi_S3: DevicePlatformDescriber = { + platform: pond.DevicePlatform.DEVICE_PLATFORM_V2_WIFI_S3, + label: "V2 Wifi S3", + description: "Communicates via cellular", + icon: , + offlineIcon: , + platformName: "v2wifis3" +}; + +const V2_Cell_Black: DevicePlatformDescriber = { + platform: pond.DevicePlatform.DEVICE_PLATFORM_V2_CELLULAR_BLACK, + label: "V2 Cellular Black", + description: "Communicates via cellular", + icon: , + offlineIcon: , + platformName: "v2cellblack" +}; + +const V2_Cell_Green: DevicePlatformDescriber = { + platform: pond.DevicePlatform.DEVICE_PLATFORM_V2_CELLULAR_GREEN, + label: "V2 Cellular Green", + description: "Communicates via cellular", + icon: , + offlineIcon: , + platformName: "v2cellgreen" +}; + +const map = new Map([ + [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); +} diff --git a/src/providers/index.ts b/src/providers/index.ts index 2733726..5e3403b 100644 --- a/src/providers/index.ts +++ b/src/providers/index.ts @@ -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, diff --git a/src/providers/pond/backpackAPI.tsx b/src/providers/pond/backpackAPI.tsx new file mode 100644 index 0000000..ff16fd6 --- /dev/null +++ b/src/providers/pond/backpackAPI.tsx @@ -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; + updateBackpack: (backpackID: number | Long, backpack: pond.Backpack) => Promise; + removeBackpack: (backpackID: number | Long) => Promise; + getBackpack: (backpackID: number | Long) => Promise; + listBackpacks: () => Promise; +} + +export const BackpackAPIContext = createContext({} as IBackpackAPI); + +export default function BackpackProvider(props: PropsWithChildren) { + 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 ( + + {children} + + ); +} + +export const useBackpackAPI = () => useContext(BackpackAPIContext); diff --git a/src/providers/pond/deviceAPI.tsx b/src/providers/pond/deviceAPI.tsx new file mode 100644 index 0000000..0d0290f --- /dev/null +++ b/src/providers/pond/deviceAPI.tsx @@ -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; + update: (id: number, settings: pond.DeviceSettings) => Promise; + remove: (id: number) => Promise; + get: (id: number | string, demo?: boolean, keys?: string[], types?: string[]) => Promise; + getPageData: ( + id: number | string, + keys?: string[], + types?: string[] + ) => Promise>; + getMulti: (ids: number[] | string[]) => Promise; + getGeoJson: (id: number | string, demo?: boolean) => Promise; + getMultiGeoJson: (ids: number[] | string[]) => Promise; + 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>; + listForUser: ( + asRoot: boolean, + user?: string, + search?: string, + hologram?: boolean, + keys?: string[], + types?: string[] + ) => Promise>; + claim: (id: number | Long, body: any) => Promise; + listHistory: (id: number, limit: number, offset: number) => Promise; + listCompleteHistory: ( + id: number, + limit: number, + offset: number, + filters?: pond.ObjectType[] + ) => Promise>; + updatePermissions: (id: number | string, users: User[]) => Promise; + updatePreferences: ( + id: number | string, + preferences: pond.UserPreferences, + keys?: string[], + types?: string[] + ) => Promise; + updateComponentPreferences: ( + id: number | string, + component: string, + preferences: pond.DeviceComponentPreferences, + keys?: string[], + types?: string[] + ) => Promise; + listDeviceComponentPreferences: ( + id: number | string, + keys?: string[], + types?: string[] + ) => Promise>; + updateStatus: ( + id: number | string, + status: pond.DeviceStatus, + keys?: string[], + types?: string[] + ) => Promise; + sync: (id: number) => Promise; + clearPending: (id: number) => Promise; + pause: (id: number) => Promise; + bulkPause: ( + ids: number[], + setPaused: boolean + ) => Promise>; + bulkChangeDataCaps: ( + ids: number[], + newCap: number + ) => Promise>; + resume: (id: number) => Promise; + getUpgradeStatus: (id: number, keys?: string[], types?: string[]) => Promise; + loadBackpack: (id: number, backpack: number) => Promise; + setTags: (id: number, tags: string[]) => Promise; + setWifi: ( + id: number, + gateway: string, + password: string, + keys?: string[], + types?: string[] + ) => Promise; + tag: (id: number, tag: string) => Promise; + untag: (id: number, tag: string) => Promise; + getDatacap: (id: number) => Promise; + isOverLimit: (id: number) => Promise; + isPaused: (id: number) => Promise; + setDatacap: (id: number, newCap: number) => Promise; + // listJSONMeasurements: ( + // id: number, + // components: Component[], + // startDate: any, + // endDate: any, + // limit: number, + // offset: number, + // order: string, + // orderBy: string + // ) => Promise>; + listSimpleJSON: (device: number) => Promise; + resetQuackCount: (id: number) => Promise; + resetQuackCountTx: (id: number) => Promise; + resetQuackCountTx1000: (id: number) => Promise; + linearMutation: ( + id: number, + mutation: pond.LinearMutation + ) => Promise>; + getDataUsage: (id: number, start?: any) => Promise; + buyData: ( + id: number | string, + MB: number, + source?: string, + keys?: string[], + types?: string[] + ) => Promise; +} + +export const DeviceAPIContext = createContext({} as IDeviceAPIContext); + +interface Props {} + +export default function DeviceProvider(props: PropsWithChildren) { + 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> => { + 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(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(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(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( + 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( + 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( + pondURL( + "/bulkPauseDevices?ids=" + + ids.toString() + + (setPaused ? "&pause=" + setPaused.toString() : "") + ), + {} + ); + }; + + const bulkChangeDataCaps = (ids: number[], newCap: number) => { + return post( + 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( + // pondURL( + // "/devices/" + + // id + + // "/measurements/exportComplex" + + // dateRange(startDate, endDate) + + // "&components=" + + // keyString + + // "&limit=" + + // limit + + // "&offset=" + + // offset + + // "&order=" + + // order + + // "&orderBy=" + + // orderBy + + // "&as=" + + // as + // ) + // ); + // } + // return get( + // 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( + 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 ( + + {children} + + ); +} + +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); diff --git a/src/providers/pond/pond.tsx b/src/providers/pond/pond.tsx index 65eeca6..4301791 100644 --- a/src/providers/pond/pond.tsx +++ b/src/providers/pond/pond.tsx @@ -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) { - {children} + + + {children} + + @@ -41,6 +47,8 @@ export default function PondProvider(props: PropsWithChildren) { } export { + useBackpackAPI, + useDeviceAPI, useUserAPI, useTeamAPI, useImagekitAPI, diff --git a/src/theme/theme.ts b/src/theme/theme.ts index c7a6c1d..44625f9 100644 --- a/src/theme/theme.ts +++ b/src/theme/theme.ts @@ -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 } }, },