574 lines
19 KiB
TypeScript
574 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";
|
|
import { quack } from "protobuf-ts/quack";
|
|
|
|
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() && (
|
|
<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 showSupport = () => {
|
|
return user.hasAdmin() && !location.pathname.includes("support")
|
|
}
|
|
|
|
const buttons = () => {
|
|
return (
|
|
<React.Fragment>
|
|
{showSupport() && <Tooltip title="Support">
|
|
<IconButton onClick={() => navigate("support", { state: {device: device, devicePageData: devicePageData }})}>
|
|
<Visibility />
|
|
</IconButton>
|
|
</Tooltip>}
|
|
{canWrite && user.hasAdmin() && <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>
|
|
);
|
|
}
|