frontend/src/device/DeviceActions.tsx

569 lines
19 KiB
TypeScript

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,
Visibility
} 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 { Component, Device, deviceScope, Interaction } from "models";
// import { MatchParams } from "navigation/Routes";
// import { isShareableLink } from "pbHelpers/Device";
import { pond } from "protobuf-ts/pond";
import { useGlobalState } from "providers";
import React, { useEffect, 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";
import DeviceSettings from "device/DeviceSettings";
import ObjectTeams from "teams/ObjectTeams";
import ComponentSettings from "component/ComponentSettings";
import LoadDeviceProfile from "./LoadDeviceProfile";
import InteractionSettings from "interactions/InteractionSettings";
import SaveDeviceProfile from "./SaveDeviceProfile";
import Connection from "./Connection";
import ComponentOrder from "component/ComponentOrder";
import ArcGISDeviceData from "./ArcGISDeviceData";
import UpgradeDevice from "./UpgradeDevice";
import { DevicePageData } from "pages/Device";
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 [devicePageData, setDevicePageData] = useState<DevicePageData | undefined>(undefined);
useEffect(() => {
let newPageData: DevicePageData = {
device: device,
components: components,
interactions: interactions,
preferences: preferences,
permissions: permissions,
}
setDevicePageData(newPageData)
}, [device, components, interactions, preferences, permissions])
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
const isV2 = device.settings.platform >= pond.DevicePlatform.DEVICE_PLATFORM_V2_CELLULAR
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 || isV2) && 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}
//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>
{user.allowedTo("provision") && <Tooltip title="Support">
<IconButton onClick={() => navigate("support", { state: {device: device, devicePageData: devicePageData }})}>
<Visibility />
</IconButton>
</Tooltip>}
{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>
);
}