actually added device actions
This commit is contained in:
parent
aa3301ac35
commit
4b08791bc9
5 changed files with 726 additions and 6 deletions
537
src/common/DeviceActions.tsx
Normal file
537
src/common/DeviceActions.tsx
Normal file
|
|
@ -0,0 +1,537 @@
|
||||||
|
import {
|
||||||
|
Divider,
|
||||||
|
IconButton,
|
||||||
|
ListItemIcon,
|
||||||
|
ListItemText,
|
||||||
|
Menu,
|
||||||
|
MenuItem,
|
||||||
|
Theme,
|
||||||
|
Tooltip
|
||||||
|
} from "@mui/material";
|
||||||
|
// import { amber, blue, green } from "@mui/co";
|
||||||
|
import {
|
||||||
|
AddCircle as AddIcon,
|
||||||
|
ExitToApp as RemoveSelfIcon,
|
||||||
|
History as HistoryIcon,
|
||||||
|
MoreVert as MoreIcon,
|
||||||
|
PauseCircleFilled,
|
||||||
|
PlayCircleFilled,
|
||||||
|
Reorder as ComponentOrderIcon,
|
||||||
|
Save as SaveDeviceProfileIcon,
|
||||||
|
SaveAlt as LoadDeviceProfileIcon,
|
||||||
|
Settings as DeviceSettingsIcon,
|
||||||
|
Share as ShareObjectIcon,
|
||||||
|
AccountCircle as ObjectUsersIcon,
|
||||||
|
SupervisedUserCircle as ObjectTeamsIcon,
|
||||||
|
Sync as SyncDeviceIcon,
|
||||||
|
Wifi as WifiIcon
|
||||||
|
} from "@mui/icons-material";
|
||||||
|
import { Skeleton } from "@mui/material";
|
||||||
|
import Datadog from "assets/external/datadog.png";
|
||||||
|
import { ImgIcon } from "common/ImgIcon";
|
||||||
|
import NotificationButton from "common/NotificationButton";
|
||||||
|
// import ComponentOrder from "component/ComponentOrder";
|
||||||
|
// import ComponentSettings from "component/ComponentSettings";
|
||||||
|
// import DeviceSettings from "device/DeviceSettings";
|
||||||
|
// import LoadDeviceProfile from "device/LoadDeviceProfile";
|
||||||
|
// import { PauseData } from "device/PauseData";
|
||||||
|
// import { ResumeData } from "device/ResumeData";
|
||||||
|
// import SaveDeviceProfile from "device/SaveDeviceProfile";
|
||||||
|
// import SyncDevice from "device/SyncDevice";
|
||||||
|
// import UpgradeDevice from "device/UpgradeDevice";
|
||||||
|
// import InteractionSettings from "interactions/InteractionSettings";
|
||||||
|
import { cloneDeep } from "lodash";
|
||||||
|
// import { Component, Device, deviceScope, Interaction } from "models";
|
||||||
|
import { Device, deviceScope } from "models";
|
||||||
|
// import { MatchParams } from "navigation/Routes";
|
||||||
|
import { isShareableLink } from "pbHelpers/Device";
|
||||||
|
import { pond } from "protobuf-ts/pond";
|
||||||
|
import { useGlobalState } from "providers";
|
||||||
|
import React, { useState } from "react";
|
||||||
|
// import { useHistory, useRouteMatch } from "react-router";
|
||||||
|
import { isOffline } from "utils/environment";
|
||||||
|
import { or } from "utils/types";
|
||||||
|
import ObjectUsers from "user/ObjectUsers";
|
||||||
|
import RemoveSelfFromObject from "user/RemoveSelfFromObject";
|
||||||
|
import ShareObject from "user/ShareObject";
|
||||||
|
// import Connection from "./Connection";
|
||||||
|
// import ObjectTeams from "teams/ObjectTeams";
|
||||||
|
// import ArcGISDeviceData from "./ArcGISDeviceData";
|
||||||
|
// import { DeviceAvailabilityMap, OffsetAvailabilityMap } from "pbHelpers/DeviceAvailability";
|
||||||
|
import { amber, blue, green } from "@mui/material/colors";
|
||||||
|
import { makeStyles } from "@mui/styles";
|
||||||
|
import { useNavigate } from "react-router-dom";
|
||||||
|
|
||||||
|
const useStyles = makeStyles((theme: Theme) => {
|
||||||
|
// const isMobile = useMobile()
|
||||||
|
return ({
|
||||||
|
greenIcon: {
|
||||||
|
color: green["500"],
|
||||||
|
"&:hover": {
|
||||||
|
color: green["600"]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
amberIcon: {
|
||||||
|
color: amber["600"],
|
||||||
|
"&:hover": {
|
||||||
|
color: amber["700"]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
blueIcon: {
|
||||||
|
color: blue["500"],
|
||||||
|
"&:hover": {
|
||||||
|
color: blue["600"]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
red: {
|
||||||
|
color: "var(--status-alert)"
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
device: Device;
|
||||||
|
isPaused: boolean;
|
||||||
|
// components: Component[];
|
||||||
|
// interactions: Interaction[];
|
||||||
|
refreshCallback: () => void;
|
||||||
|
// availablePositions: DeviceAvailabilityMap;
|
||||||
|
// availableOffsets: OffsetAvailabilityMap;
|
||||||
|
permissions: Array<pond.Permission>;
|
||||||
|
preferences: pond.DevicePreferences;
|
||||||
|
isLoading: boolean;
|
||||||
|
toggleNotificationPreference: Function;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface DialogState {
|
||||||
|
isAddComponentDialogOpen: boolean;
|
||||||
|
isDeviceSettingsDialogOpen: boolean;
|
||||||
|
isShareObjectDialogOpen: boolean;
|
||||||
|
isSyncDeviceDialogOpen: boolean;
|
||||||
|
isPauseDialogOpen: boolean;
|
||||||
|
isResumeDialogOpen: boolean;
|
||||||
|
isInteractionSettingsOpen: boolean;
|
||||||
|
isObjectUsersDialogOpen: boolean;
|
||||||
|
isObjectTeamsDialogOpen: boolean;
|
||||||
|
isRemoveSelfDialogOpen: boolean;
|
||||||
|
isSaveDeviceProfileOpen: boolean;
|
||||||
|
isLoadDeviceProfileOpen: boolean;
|
||||||
|
isConnectionDialogOpen: boolean;
|
||||||
|
isComponentOrderDialogOpen: boolean;
|
||||||
|
isJsonDataDialogOpen: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function DeviceActions(props: Props) {
|
||||||
|
const {
|
||||||
|
device,
|
||||||
|
isPaused,
|
||||||
|
// components,
|
||||||
|
// interactions,
|
||||||
|
refreshCallback,
|
||||||
|
// availablePositions,
|
||||||
|
// availableOffsets,
|
||||||
|
permissions,
|
||||||
|
preferences,
|
||||||
|
isLoading,
|
||||||
|
toggleNotificationPreference
|
||||||
|
} = props;
|
||||||
|
const classes = useStyles();
|
||||||
|
const [{ user }] = useGlobalState();
|
||||||
|
const canWrite = permissions.includes(pond.Permission.PERMISSION_WRITE);
|
||||||
|
const navigate = useNavigate();
|
||||||
|
// const match = useRouteMatch<MatchParams>();
|
||||||
|
const [menuAnchorEl, setMenuAnchorEl] = useState<Element | null>(null);
|
||||||
|
const [dialogState, setDialogState] = useState<DialogState>({
|
||||||
|
isAddComponentDialogOpen: false,
|
||||||
|
isDeviceSettingsDialogOpen: false,
|
||||||
|
isShareObjectDialogOpen: false,
|
||||||
|
isSyncDeviceDialogOpen: false,
|
||||||
|
isPauseDialogOpen: false,
|
||||||
|
isResumeDialogOpen: false,
|
||||||
|
isInteractionSettingsOpen: false,
|
||||||
|
isObjectUsersDialogOpen: false,
|
||||||
|
isObjectTeamsDialogOpen: false,
|
||||||
|
isRemoveSelfDialogOpen: false,
|
||||||
|
isSaveDeviceProfileOpen: false,
|
||||||
|
isLoadDeviceProfileOpen: false,
|
||||||
|
isConnectionDialogOpen: false,
|
||||||
|
isComponentOrderDialogOpen: false,
|
||||||
|
isJsonDataDialogOpen: false
|
||||||
|
});
|
||||||
|
|
||||||
|
const openDialog = (target: keyof DialogState) => {
|
||||||
|
let updatedDialogState = cloneDeep(dialogState);
|
||||||
|
updatedDialogState[target] = true;
|
||||||
|
setDialogState(updatedDialogState);
|
||||||
|
setMenuAnchorEl(null);
|
||||||
|
};
|
||||||
|
|
||||||
|
const closeDialog = (target: keyof DialogState) => {
|
||||||
|
let updatedDialogState = cloneDeep(dialogState);
|
||||||
|
updatedDialogState[target] = false;
|
||||||
|
setDialogState(updatedDialogState);
|
||||||
|
};
|
||||||
|
|
||||||
|
const closeAndRefresh = (target: keyof DialogState) => (refresh: boolean) => {
|
||||||
|
closeDialog(target);
|
||||||
|
if (refresh) props.refreshCallback();
|
||||||
|
};
|
||||||
|
|
||||||
|
const deviceMenu = () => {
|
||||||
|
const canShare = permissions.includes(pond.Permission.PERMISSION_SHARE);
|
||||||
|
const canManageUsers = permissions.includes(pond.Permission.PERMISSION_USERS);
|
||||||
|
const canProvision = user.allowedTo("provision");
|
||||||
|
const isCellular = device.settings.platform === pond.DevicePlatform.DEVICE_PLATFORM_ELECTRON;
|
||||||
|
const canPause = canWrite && isCellular && user.allowedTo("pause-data");
|
||||||
|
const isWifi = device.settings.platform === pond.DevicePlatform.DEVICE_PLATFORM_PHOTON;
|
||||||
|
// const isShareLink = isShareableLink(match.params.deviceID);
|
||||||
|
const isShareLink = false
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Menu
|
||||||
|
id="deviceMenu"
|
||||||
|
anchorEl={menuAnchorEl ? menuAnchorEl : null}
|
||||||
|
open={menuAnchorEl !== null}
|
||||||
|
onClose={() => setMenuAnchorEl(null)}
|
||||||
|
disableAutoFocusItem>
|
||||||
|
{canWrite && (
|
||||||
|
<MenuItem
|
||||||
|
onClick={() => {
|
||||||
|
openDialog("isAddComponentDialogOpen");
|
||||||
|
}}
|
||||||
|
dense>
|
||||||
|
<ListItemIcon>
|
||||||
|
<AddIcon className={classes.greenIcon} />
|
||||||
|
</ListItemIcon>
|
||||||
|
<ListItemText secondary="Add Component" />
|
||||||
|
</MenuItem>
|
||||||
|
)}
|
||||||
|
{canWrite && (
|
||||||
|
<MenuItem onClick={() => openDialog("isInteractionSettingsOpen")} dense>
|
||||||
|
<ListItemIcon>
|
||||||
|
<AddIcon className={classes.greenIcon} />
|
||||||
|
</ListItemIcon>
|
||||||
|
<ListItemText secondary="Add Interaction" />
|
||||||
|
</MenuItem>
|
||||||
|
)}
|
||||||
|
{canWrite && <Divider />}
|
||||||
|
|
||||||
|
{!isOffline() && canShare && (
|
||||||
|
<MenuItem onClick={() => openDialog("isShareObjectDialogOpen")} dense>
|
||||||
|
<ListItemIcon>
|
||||||
|
<ShareObjectIcon className={classes.blueIcon} />
|
||||||
|
</ListItemIcon>
|
||||||
|
<ListItemText secondary="Share" />
|
||||||
|
</MenuItem>
|
||||||
|
)}
|
||||||
|
{canWrite &&
|
||||||
|
user.hasAdmin() && ( //TODO: once the resync issue has been resolved remove the admin check
|
||||||
|
<MenuItem onClick={() => openDialog("isSyncDeviceDialogOpen")} dense>
|
||||||
|
<ListItemIcon>
|
||||||
|
<SyncDeviceIcon className={classes.amberIcon} />
|
||||||
|
</ListItemIcon>
|
||||||
|
<ListItemText secondary="Resync" />
|
||||||
|
</MenuItem>
|
||||||
|
)}
|
||||||
|
{user.hasFeature("json") && (
|
||||||
|
<MenuItem onClick={() => openDialog("isJsonDataDialogOpen")} dense>
|
||||||
|
<ListItemIcon>
|
||||||
|
<LoadDeviceProfileIcon className={classes.greenIcon} />
|
||||||
|
</ListItemIcon>
|
||||||
|
<ListItemText secondary="Export Json Data" />
|
||||||
|
</MenuItem>
|
||||||
|
)}
|
||||||
|
<MenuItem onClick={() => openDialog("isComponentOrderDialogOpen")} dense>
|
||||||
|
<ListItemIcon>
|
||||||
|
<ComponentOrderIcon />
|
||||||
|
</ListItemIcon>
|
||||||
|
<ListItemText secondary="Component Order" />
|
||||||
|
</MenuItem>
|
||||||
|
<Divider />
|
||||||
|
|
||||||
|
{canPause && !isPaused && (
|
||||||
|
<MenuItem onClick={() => openDialog("isPauseDialogOpen")} dense>
|
||||||
|
<ListItemIcon>
|
||||||
|
<PauseCircleFilled />
|
||||||
|
</ListItemIcon>
|
||||||
|
<ListItemText secondary="Pause Data" />
|
||||||
|
</MenuItem>
|
||||||
|
)}
|
||||||
|
{canPause && isPaused && (
|
||||||
|
<MenuItem onClick={() => openDialog("isResumeDialogOpen")} dense>
|
||||||
|
<ListItemIcon>
|
||||||
|
<PlayCircleFilled className={classes.greenIcon} />
|
||||||
|
</ListItemIcon>
|
||||||
|
<ListItemText secondary="Resume Data" />
|
||||||
|
</MenuItem>
|
||||||
|
)}
|
||||||
|
{isWifi && canWrite && (
|
||||||
|
<MenuItem onClick={() => openDialog("isConnectionDialogOpen")} dense>
|
||||||
|
<ListItemIcon>
|
||||||
|
<WifiIcon />
|
||||||
|
</ListItemIcon>
|
||||||
|
<ListItemText secondary="Connection" />
|
||||||
|
</MenuItem>
|
||||||
|
)}
|
||||||
|
{(canPause || (isWifi && canWrite)) && <Divider />}
|
||||||
|
{canProvision && (
|
||||||
|
<MenuItem onClick={() => openDialog("isSaveDeviceProfileOpen")} dense>
|
||||||
|
<ListItemIcon>
|
||||||
|
<SaveDeviceProfileIcon className={classes.blueIcon} />
|
||||||
|
</ListItemIcon>
|
||||||
|
<ListItemText secondary="Save Profile" />
|
||||||
|
</MenuItem>
|
||||||
|
)}
|
||||||
|
{canProvision && (
|
||||||
|
<MenuItem onClick={() => openDialog("isLoadDeviceProfileOpen")} dense>
|
||||||
|
<ListItemIcon>
|
||||||
|
<LoadDeviceProfileIcon className={classes.greenIcon} />
|
||||||
|
</ListItemIcon>
|
||||||
|
<ListItemText secondary="Load Profile" />
|
||||||
|
</MenuItem>
|
||||||
|
)}
|
||||||
|
{canProvision && <Divider />}
|
||||||
|
{!isOffline() && canManageUsers && (
|
||||||
|
<MenuItem onClick={() => openDialog("isObjectUsersDialogOpen")} dense>
|
||||||
|
<ListItemIcon>
|
||||||
|
<ObjectUsersIcon />
|
||||||
|
</ListItemIcon>
|
||||||
|
<ListItemText secondary="Users" />
|
||||||
|
</MenuItem>
|
||||||
|
)}
|
||||||
|
{!isOffline() && canManageUsers && (
|
||||||
|
<MenuItem onClick={() => openDialog("isObjectTeamsDialogOpen")} dense>
|
||||||
|
<ListItemIcon>
|
||||||
|
<ObjectTeamsIcon />
|
||||||
|
</ListItemIcon>
|
||||||
|
<ListItemText secondary="Teams" />
|
||||||
|
</MenuItem>
|
||||||
|
)}
|
||||||
|
<MenuItem
|
||||||
|
onClick={() => {
|
||||||
|
// const groupID: number = parseInt(match.params.groupID, 10);
|
||||||
|
// const groupPath: string = groupID > 0 ? "/groups/" + groupID.toString() : "";
|
||||||
|
const devicePath: string = "/devices/" + device.id().toString();
|
||||||
|
// let path = groupPath + devicePath + "/history";
|
||||||
|
let path = devicePath + "/history";
|
||||||
|
navigate(path);
|
||||||
|
}}
|
||||||
|
dense>
|
||||||
|
<ListItemIcon>
|
||||||
|
<HistoryIcon />
|
||||||
|
</ListItemIcon>
|
||||||
|
<ListItemText secondary="History" />
|
||||||
|
</MenuItem>
|
||||||
|
{/* {canProvision && (
|
||||||
|
<MenuItem
|
||||||
|
onClick={() => {
|
||||||
|
window.open(
|
||||||
|
"https://app.datadoghq.com/logs?cols=core_host%2Ccore_service&index=main&live=true&messageDisplay=inline&stream_sort=desc&query=%40device%3A" +
|
||||||
|
device.id(),
|
||||||
|
"_blank"
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
dense>
|
||||||
|
<ListItemIcon>
|
||||||
|
<ImgIcon src={Datadog} />
|
||||||
|
</ListItemIcon>
|
||||||
|
<ListItemText secondary="Datadog" />
|
||||||
|
</MenuItem>
|
||||||
|
)} */}
|
||||||
|
<Divider />
|
||||||
|
{!isShareLink && (
|
||||||
|
<MenuItem onClick={() => openDialog("isRemoveSelfDialogOpen")} dense>
|
||||||
|
<ListItemIcon>
|
||||||
|
<RemoveSelfIcon className={classes.red} />
|
||||||
|
</ListItemIcon>
|
||||||
|
<ListItemText secondary="Leave" />
|
||||||
|
</MenuItem>
|
||||||
|
)}
|
||||||
|
</Menu>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const dialogs = () => {
|
||||||
|
const deviceName = device.name();
|
||||||
|
const {
|
||||||
|
isAddComponentDialogOpen,
|
||||||
|
isDeviceSettingsDialogOpen,
|
||||||
|
isShareObjectDialogOpen,
|
||||||
|
isSyncDeviceDialogOpen,
|
||||||
|
isPauseDialogOpen,
|
||||||
|
isResumeDialogOpen,
|
||||||
|
isInteractionSettingsOpen,
|
||||||
|
isObjectUsersDialogOpen,
|
||||||
|
isObjectTeamsDialogOpen,
|
||||||
|
isRemoveSelfDialogOpen,
|
||||||
|
isSaveDeviceProfileOpen,
|
||||||
|
isLoadDeviceProfileOpen,
|
||||||
|
isConnectionDialogOpen,
|
||||||
|
isComponentOrderDialogOpen,
|
||||||
|
isJsonDataDialogOpen
|
||||||
|
} = dialogState;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<React.Fragment>
|
||||||
|
{/* <DeviceSettings
|
||||||
|
device={device}
|
||||||
|
isDialogOpen={or(isDeviceSettingsDialogOpen, false)}
|
||||||
|
closeDialogCallback={() => closeDialog("isDeviceSettingsDialogOpen")}
|
||||||
|
refreshCallback={refreshCallback}
|
||||||
|
canEdit={canWrite}
|
||||||
|
components={components}
|
||||||
|
/> */}
|
||||||
|
{/* <ComponentSettings
|
||||||
|
mode="add"
|
||||||
|
device={device}
|
||||||
|
isDialogOpen={or(isAddComponentDialogOpen, false)}
|
||||||
|
closeDialogCallback={() => closeDialog("isAddComponentDialogOpen")}
|
||||||
|
availablePositions={availablePositions}
|
||||||
|
availableOffsets={availableOffsets}
|
||||||
|
refreshCallback={refreshCallback}
|
||||||
|
canEdit={canWrite}
|
||||||
|
/>
|
||||||
|
<SyncDevice
|
||||||
|
device={device}
|
||||||
|
isDialogOpen={or(isSyncDeviceDialogOpen, false)}
|
||||||
|
closeDialogCallback={() => closeDialog("isSyncDeviceDialogOpen")}
|
||||||
|
refreshCallback={refreshCallback}
|
||||||
|
/> */}
|
||||||
|
<ShareObject
|
||||||
|
scope={deviceScope(device.id().toString())}
|
||||||
|
label={deviceName}
|
||||||
|
permissions={permissions}
|
||||||
|
isDialogOpen={or(isShareObjectDialogOpen, false)}
|
||||||
|
closeDialogCallback={() => closeDialog("isShareObjectDialogOpen")}
|
||||||
|
/>
|
||||||
|
{/* <InteractionSettings
|
||||||
|
device={device}
|
||||||
|
components={cloneDeep(components)}
|
||||||
|
mode="add"
|
||||||
|
isDialogOpen={or(isInteractionSettingsOpen, false)}
|
||||||
|
closeDialogCallback={() => closeDialog("isInteractionSettingsOpen")}
|
||||||
|
refreshCallback={refreshCallback}
|
||||||
|
canEdit={canWrite}
|
||||||
|
/> */}
|
||||||
|
<ObjectUsers
|
||||||
|
scope={deviceScope(device.id().toString())}
|
||||||
|
label={deviceName}
|
||||||
|
permissions={permissions}
|
||||||
|
isDialogOpen={or(isObjectUsersDialogOpen, false)}
|
||||||
|
closeDialogCallback={() => closeDialog("isObjectUsersDialogOpen")}
|
||||||
|
refreshCallback={refreshCallback}
|
||||||
|
/>
|
||||||
|
{/* <ObjectTeams
|
||||||
|
scope={deviceScope(device.id().toString())}
|
||||||
|
label={deviceName}
|
||||||
|
permissions={permissions}
|
||||||
|
isDialogOpen={or(isObjectTeamsDialogOpen, false)}
|
||||||
|
closeDialogCallback={() => closeDialog("isObjectTeamsDialogOpen")}
|
||||||
|
refreshCallback={refreshCallback}
|
||||||
|
/> */}
|
||||||
|
<RemoveSelfFromObject
|
||||||
|
scope={deviceScope(device.id().toString())}
|
||||||
|
label={deviceName}
|
||||||
|
isDialogOpen={isRemoveSelfDialogOpen}
|
||||||
|
closeDialogCallback={() => closeDialog("isRemoveSelfDialogOpen")}
|
||||||
|
/>
|
||||||
|
{/* <SaveDeviceProfile
|
||||||
|
isDialogOpen={isSaveDeviceProfileOpen}
|
||||||
|
closeDialogCallback={() => closeDialog("isSaveDeviceProfileOpen")}
|
||||||
|
device={device}
|
||||||
|
components={components}
|
||||||
|
interactions={interactions}
|
||||||
|
tagIds={device.status.tagKeys}
|
||||||
|
refreshCallback={refreshCallback}
|
||||||
|
/> */}
|
||||||
|
{/* <LoadDeviceProfile
|
||||||
|
isDialogOpen={isLoadDeviceProfileOpen}
|
||||||
|
closeDialogCallback={() => closeDialog("isLoadDeviceProfileOpen")}
|
||||||
|
device={device}
|
||||||
|
refreshCallback={refreshCallback}
|
||||||
|
/> */}
|
||||||
|
{/* <PauseData
|
||||||
|
id={device.id()}
|
||||||
|
isOpen={isPauseDialogOpen}
|
||||||
|
close={closeAndRefresh("isPauseDialogOpen")}
|
||||||
|
/>
|
||||||
|
<ResumeData
|
||||||
|
id={device.id()}
|
||||||
|
isOpen={isResumeDialogOpen}
|
||||||
|
close={closeAndRefresh("isResumeDialogOpen")}
|
||||||
|
/>
|
||||||
|
<Connection
|
||||||
|
deviceID={device.id()}
|
||||||
|
open={isConnectionDialogOpen}
|
||||||
|
close={closeAndRefresh("isConnectionDialogOpen")}
|
||||||
|
deviceName={device.name()}
|
||||||
|
/>
|
||||||
|
<ComponentOrder
|
||||||
|
device={device}
|
||||||
|
devicePreferences={preferences}
|
||||||
|
components={components}
|
||||||
|
open={isComponentOrderDialogOpen}
|
||||||
|
close={closeAndRefresh("isComponentOrderDialogOpen")}
|
||||||
|
/>
|
||||||
|
<ArcGISDeviceData
|
||||||
|
open={isJsonDataDialogOpen}
|
||||||
|
close={closeAndRefresh("isJsonDataDialogOpen")}
|
||||||
|
device={device}
|
||||||
|
components={components}
|
||||||
|
/> */}
|
||||||
|
</React.Fragment>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const buttons = () => {
|
||||||
|
return (
|
||||||
|
<React.Fragment>
|
||||||
|
{/* {canWrite && user.allowedTo("provision") && <UpgradeDevice device={device} />} */}
|
||||||
|
{/* {!isShareableLink(match.params.deviceID) && ( */}
|
||||||
|
<NotificationButton
|
||||||
|
notify={preferences.notify}
|
||||||
|
onChange={toggleNotificationPreference}
|
||||||
|
tooltip={
|
||||||
|
"Notifications for " +
|
||||||
|
device.name() +
|
||||||
|
" are " +
|
||||||
|
(preferences.notify ? "enabled" : "disabled")
|
||||||
|
}
|
||||||
|
hidden={isLoading}
|
||||||
|
/>
|
||||||
|
{/* )} */}
|
||||||
|
<Tooltip title="Settings">
|
||||||
|
<IconButton onClick={() => openDialog("isDeviceSettingsDialogOpen")}>
|
||||||
|
<DeviceSettingsIcon />
|
||||||
|
</IconButton>
|
||||||
|
</Tooltip>
|
||||||
|
<Tooltip title="More">
|
||||||
|
<IconButton
|
||||||
|
aria-owns={"deviceMenu"}
|
||||||
|
aria-haspopup="true"
|
||||||
|
onClick={event => setMenuAnchorEl(event.currentTarget)}>
|
||||||
|
<MoreIcon />
|
||||||
|
</IconButton>
|
||||||
|
</Tooltip>
|
||||||
|
</React.Fragment>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
if (isLoading)
|
||||||
|
return (
|
||||||
|
<Skeleton>
|
||||||
|
<IconButton />
|
||||||
|
<IconButton />
|
||||||
|
<IconButton />
|
||||||
|
</Skeleton>
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<React.Fragment>
|
||||||
|
{buttons()}
|
||||||
|
|
||||||
|
{deviceMenu()}
|
||||||
|
{dialogs()}
|
||||||
|
</React.Fragment>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -5,7 +5,7 @@ import { makeStyles } from "@mui/styles";
|
||||||
import ResponsiveTable, { Column } from "common/ResponsiveTable";
|
import ResponsiveTable, { Column } from "common/ResponsiveTable";
|
||||||
import ProvisionDevice from "device/ProvisionDevice";
|
import ProvisionDevice from "device/ProvisionDevice";
|
||||||
import { pond } from "protobuf-ts/pond";
|
import { pond } from "protobuf-ts/pond";
|
||||||
import { useDeviceAPI } from "providers";
|
import { useDeviceAPI, useGroupAPI } from "providers";
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import PageContainer from "./PageContainer";
|
import PageContainer from "./PageContainer";
|
||||||
import { useMobile } from "hooks";
|
import { useMobile } from "hooks";
|
||||||
|
|
@ -34,6 +34,7 @@ export default function Devices() {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
const deviceAPI = useDeviceAPI();
|
const deviceAPI = useDeviceAPI();
|
||||||
|
const groupAPI = useGroupAPI();
|
||||||
const [limit, setLimit] = useState(10);
|
const [limit, setLimit] = useState(10);
|
||||||
const [page, setPage] = useState(0);
|
const [page, setPage] = useState(0);
|
||||||
const [order, ] = useState<"asc" | "desc">("asc");
|
const [order, ] = useState<"asc" | "desc">("asc");
|
||||||
|
|
@ -43,6 +44,13 @@ export default function Devices() {
|
||||||
const [devices, setDevices] = useState<pond.Device[]>([])
|
const [devices, setDevices] = useState<pond.Device[]>([])
|
||||||
const [isProvisionDialogOpen, setIsProvisionDialogOpen] = useState<boolean>(false);
|
const [isProvisionDialogOpen, setIsProvisionDialogOpen] = useState<boolean>(false);
|
||||||
|
|
||||||
|
const [groupLimit, setGroupLimit] = useState(10);
|
||||||
|
const [groupPage, setGroupPage] = useState(0);
|
||||||
|
const [orderGroup, ] = useState<"asc" | "desc">("asc");
|
||||||
|
const [orderGroupBy, ] = useState("name");
|
||||||
|
const [searchGroup, setGroupSearch] = useState("");
|
||||||
|
const [totalGroups, setTotalGroups] = useState(0);
|
||||||
|
|
||||||
const openProvisionDialog = () => {
|
const openProvisionDialog = () => {
|
||||||
setIsProvisionDialogOpen(true);
|
setIsProvisionDialogOpen(true);
|
||||||
};
|
};
|
||||||
|
|
@ -51,6 +59,18 @@ export default function Devices() {
|
||||||
setIsProvisionDialogOpen(false);
|
setIsProvisionDialogOpen(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const loadGroups = () => {
|
||||||
|
groupAPI.listGroups(
|
||||||
|
groupLimit,
|
||||||
|
groupPage*groupLimit,
|
||||||
|
orderGroup,
|
||||||
|
orderGroupBy,
|
||||||
|
searchGroup,
|
||||||
|
).then(resp => {
|
||||||
|
console.log(resp.data)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
const loadDevices = () => {
|
const loadDevices = () => {
|
||||||
deviceAPI.list(
|
deviceAPI.list(
|
||||||
limit,
|
limit,
|
||||||
|
|
@ -76,12 +96,17 @@ export default function Devices() {
|
||||||
loadDevices()
|
loadDevices()
|
||||||
}, [limit, page, order, orderBy, search])
|
}, [limit, page, order, orderBy, search])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
loadDevices()
|
||||||
|
}, [groupLimit, groupPage, orderGroup, orderGroupBy, searchGroup])
|
||||||
|
|
||||||
|
|
||||||
const handleChange = (event: any) => {
|
const handleChange = (event: any) => {
|
||||||
setLimit(event.target.value);
|
setLimit(event.target.value);
|
||||||
};
|
};
|
||||||
|
|
||||||
const appendToUrl = (appendage: number | string) => {
|
const appendToUrl = (appendage: number | string) => {
|
||||||
const basePath = location.pathname.replace(/\/$/, ""); // Ensure no trailing slash
|
const basePath = location.pathname.replace(/\/$/, "");
|
||||||
return(`${basePath}/${appendage}`);
|
return(`${basePath}/${appendage}`);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,7 @@ export {
|
||||||
// useFieldAPI,
|
// useFieldAPI,
|
||||||
// useFieldMarkerAPI,
|
// useFieldMarkerAPI,
|
||||||
// useFirmwareAPI,
|
// useFirmwareAPI,
|
||||||
// useGroupAPI,
|
useGroupAPI,
|
||||||
// useHarvestPlanAPI,
|
// useHarvestPlanAPI,
|
||||||
// useHarvestYearAPI,
|
// useHarvestYearAPI,
|
||||||
// useHomeMarkerAPI,
|
// useHomeMarkerAPI,
|
||||||
|
|
|
||||||
154
src/providers/pond/groupAPI.tsx
Normal file
154
src/providers/pond/groupAPI.tsx
Normal file
|
|
@ -0,0 +1,154 @@
|
||||||
|
import { useHTTP, usePermissionAPI } from "hooks";
|
||||||
|
import { createContext, PropsWithChildren, useContext } from "react";
|
||||||
|
import { pond } from "protobuf-ts/pond";
|
||||||
|
import { or } from "utils/types";
|
||||||
|
import { User, groupScope } from "models";
|
||||||
|
import { pondURL } from "./pond";
|
||||||
|
import { AxiosResponse } from "axios";
|
||||||
|
import { useGlobalState } from "providers";
|
||||||
|
import { getContextKeys, getContextTypes } from "pbHelpers/Context";
|
||||||
|
|
||||||
|
export interface IGroupAPIContext {
|
||||||
|
addGroup: (group: pond.GroupSettings) => Promise<any>;
|
||||||
|
addDevice: (group: number, device: number) => Promise<any>;
|
||||||
|
removeDevice: (group: number, device: number) => Promise<any>;
|
||||||
|
updateGroup: (id: number, group: pond.GroupSettings) => Promise<any>;
|
||||||
|
removeGroup: (id: number) => Promise<any>;
|
||||||
|
getGroup: (id: number) => Promise<any>;
|
||||||
|
listGroups: (
|
||||||
|
limit: number,
|
||||||
|
offset: number,
|
||||||
|
order?: "asc" | "desc",
|
||||||
|
orderBy?: string,
|
||||||
|
search?: string,
|
||||||
|
asRoot?: boolean
|
||||||
|
) => Promise<any>;
|
||||||
|
listGroupDevices: (
|
||||||
|
id: number,
|
||||||
|
limit: number,
|
||||||
|
offset: number,
|
||||||
|
order: "asc" | "desc",
|
||||||
|
search?: string,
|
||||||
|
comprehensive?: boolean
|
||||||
|
) => Promise<AxiosResponse<pond.GetMultiDeviceResponse>>;
|
||||||
|
updateGroupPermissions: (id: number, users: User[]) => Promise<any>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const GroupAPIContext = createContext<IGroupAPIContext>({} as IGroupAPIContext);
|
||||||
|
|
||||||
|
interface Props {}
|
||||||
|
|
||||||
|
export default function GroupProvider(props: PropsWithChildren<Props>) {
|
||||||
|
const { children } = props;
|
||||||
|
const { get, post, put, del } = useHTTP();
|
||||||
|
const permissionAPI = usePermissionAPI();
|
||||||
|
const [{ as }] = useGlobalState();
|
||||||
|
|
||||||
|
const addGroup = (group: pond.GroupSettings) => {
|
||||||
|
if (as) return post(pondURL("/groups?as=" + as), group);
|
||||||
|
return post(pondURL("/groups"), group);
|
||||||
|
};
|
||||||
|
|
||||||
|
const addDevice = (group: number, device: number) => {
|
||||||
|
let url = "/groups/" + group + "/devices/" + device + "/add";
|
||||||
|
if (as) return post(pondURL(url + as), group);
|
||||||
|
return post(pondURL(url), group);
|
||||||
|
};
|
||||||
|
|
||||||
|
const removeDevice = (group: number, device: number) => {
|
||||||
|
let url = "/groups/" + group + "/devices/" + device + "/remove";
|
||||||
|
if (as) return post(pondURL(url + as), group);
|
||||||
|
return post(pondURL(url), group);
|
||||||
|
};
|
||||||
|
|
||||||
|
const updateGroup = (id: number, group: pond.GroupSettings) => {
|
||||||
|
if (as) return put(pondURL("/groups/" + id + "?as=" + as), group);
|
||||||
|
return put(pondURL("/groups/" + id), group);
|
||||||
|
};
|
||||||
|
|
||||||
|
const removeGroup = (id: number) => {
|
||||||
|
if (as) return del(pondURL("/groups/" + id + "?as=" + as));
|
||||||
|
return del(pondURL("/groups/" + id));
|
||||||
|
};
|
||||||
|
|
||||||
|
const getGroup = (id: number) => {
|
||||||
|
if (as) return get(pondURL("/groups/" + id + "?as=" + as));
|
||||||
|
return get(pondURL("/groups/" + id));
|
||||||
|
};
|
||||||
|
|
||||||
|
const listGroups = (
|
||||||
|
limit: number,
|
||||||
|
offset: number,
|
||||||
|
order?: "asc" | "desc",
|
||||||
|
orderBy?: string,
|
||||||
|
search?: string,
|
||||||
|
asRoot?: boolean
|
||||||
|
) => {
|
||||||
|
let keys = getContextKeys()
|
||||||
|
let types = getContextTypes()
|
||||||
|
return get(
|
||||||
|
pondURL(
|
||||||
|
"/groups" +
|
||||||
|
"?limit=" +
|
||||||
|
limit +
|
||||||
|
"&offset=" +
|
||||||
|
offset +
|
||||||
|
("&order=" + or(order, "asc")) +
|
||||||
|
("&orderBy=" + or(orderBy, "id")) +
|
||||||
|
(search ? "&search=" + search : "") +
|
||||||
|
(asRoot ? "&asRoot=" + asRoot.toString() : "") +
|
||||||
|
(as ? "&as=" + as.toString() : "") +
|
||||||
|
(keys.length > 0 ? "&keys=" + keys : "") +
|
||||||
|
(types.length > 0 ? "&types=" + types : "")
|
||||||
|
)
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const listGroupDevices = (
|
||||||
|
id: number,
|
||||||
|
limit: number,
|
||||||
|
offset: number,
|
||||||
|
order: "asc" | "desc",
|
||||||
|
search?: string,
|
||||||
|
comprehensive: boolean = false
|
||||||
|
) => {
|
||||||
|
return get<pond.GetMultiDeviceResponse>(
|
||||||
|
pondURL(
|
||||||
|
"/groups/" +
|
||||||
|
id +
|
||||||
|
"/devices?limit=" +
|
||||||
|
limit +
|
||||||
|
"&offset=" +
|
||||||
|
offset +
|
||||||
|
"&order=" +
|
||||||
|
order +
|
||||||
|
(search ? "&search=" + search : "") +
|
||||||
|
(comprehensive ? "&comprehensive=" + comprehensive.toString() : "") +
|
||||||
|
(as ? "&as=" + as.toString() : "")
|
||||||
|
)
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const updateGroupPermissions = (id: number, users: User[]) => {
|
||||||
|
return permissionAPI.updatePermissions(groupScope(id.toString()), users);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<GroupAPIContext.Provider
|
||||||
|
value={{
|
||||||
|
addGroup,
|
||||||
|
addDevice,
|
||||||
|
removeDevice,
|
||||||
|
updateGroup,
|
||||||
|
removeGroup,
|
||||||
|
getGroup,
|
||||||
|
listGroups,
|
||||||
|
listGroupDevices,
|
||||||
|
updateGroupPermissions
|
||||||
|
}}>
|
||||||
|
{children}
|
||||||
|
</GroupAPIContext.Provider>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useGroupAPI = () => useContext(GroupAPIContext);
|
||||||
|
|
@ -9,6 +9,7 @@ import GateProvider, { useGateAPI } from "./gateAPI";
|
||||||
import NoteProvider, { useNoteAPI } from "./noteAPI";
|
import NoteProvider, { useNoteAPI } from "./noteAPI";
|
||||||
import DeviceProvider, { useDeviceAPI } from "./deviceAPI";
|
import DeviceProvider, { useDeviceAPI } from "./deviceAPI";
|
||||||
import BackpackProvider, { useBackpackAPI } from "./backpackAPI";
|
import BackpackProvider, { useBackpackAPI } from "./backpackAPI";
|
||||||
|
import GroupProvider, { useGroupAPI } from "./groupAPI";
|
||||||
// import NoteProvider from "providers/noteAPI";
|
// import NoteProvider from "providers/noteAPI";
|
||||||
|
|
||||||
export const pondURL = (partial: string, demo: boolean = false): string => {
|
export const pondURL = (partial: string, demo: boolean = false): string => {
|
||||||
|
|
@ -33,9 +34,11 @@ export default function PondProvider(props: PropsWithChildren<any>) {
|
||||||
<GateProvider>
|
<GateProvider>
|
||||||
<NoteProvider>
|
<NoteProvider>
|
||||||
<DeviceProvider>
|
<DeviceProvider>
|
||||||
<BackpackProvider>
|
<GroupProvider>
|
||||||
{children}
|
<BackpackProvider>
|
||||||
</BackpackProvider>
|
{children}
|
||||||
|
</BackpackProvider>
|
||||||
|
</GroupProvider>
|
||||||
</DeviceProvider>
|
</DeviceProvider>
|
||||||
</NoteProvider>
|
</NoteProvider>
|
||||||
</GateProvider>
|
</GateProvider>
|
||||||
|
|
@ -55,5 +58,6 @@ export {
|
||||||
usePermissionAPI,
|
usePermissionAPI,
|
||||||
useBinAPI,
|
useBinAPI,
|
||||||
useGateAPI,
|
useGateAPI,
|
||||||
|
useGroupAPI,
|
||||||
useNoteAPI,
|
useNoteAPI,
|
||||||
};
|
};
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue