device settings and actions made functional
This commit is contained in:
parent
3ce00facd1
commit
4e7c68401b
19 changed files with 3042 additions and 41 deletions
541
src/device/DeviceActions.tsx
Normal file
541
src/device/DeviceActions.tsx
Normal file
|
|
@ -0,0 +1,541 @@
|
|||
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";
|
||||
import DeviceSettings from "device/DeviceSettings";
|
||||
import ObjectTeams from "teams/ObjectTeams";
|
||||
|
||||
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;
|
||||
|
||||
console.log(canWrite)
|
||||
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>
|
||||
{/* {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>
|
||||
);
|
||||
}
|
||||
680
src/device/DeviceSettings.tsx
Normal file
680
src/device/DeviceSettings.tsx
Normal file
|
|
@ -0,0 +1,680 @@
|
|||
import {
|
||||
Button,
|
||||
Dialog,
|
||||
DialogActions,
|
||||
DialogContent,
|
||||
DialogTitle,
|
||||
FormControl,
|
||||
FormControlLabel,
|
||||
Grid2 as Grid,
|
||||
List,
|
||||
ListItem,
|
||||
MenuItem,
|
||||
Switch,
|
||||
Tab,
|
||||
Tabs,
|
||||
TextField,
|
||||
Theme,
|
||||
Typography
|
||||
} from "@mui/material";
|
||||
// import { Theme } from "@material-ui/core/styles/createMuiTheme";
|
||||
import DeleteButton from "common/DeleteButton";
|
||||
import PeriodSelect from "common/time/PeriodSelect";
|
||||
import ResponsiveDialog from "common/ResponsiveDialog";
|
||||
import { useDeviceAPI, usePrevious, useSnackbar } from "hooks";
|
||||
import { Component, Device } from "models";
|
||||
import { IsExtended, ListDeviceProductDescribers } from "products/DeviceProduct";
|
||||
import { pond } from "protobuf-ts/pond";
|
||||
import { useGlobalState } from "providers";
|
||||
import React, { useEffect, useState } from "react";
|
||||
// import { useHistory } from "react-router";
|
||||
import { quack } from "protobuf-ts/quack";
|
||||
import LinearMutationBuilder from "common/LinearMutationBuilder";
|
||||
import { makeStyles } from "@mui/styles";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
|
||||
interface TabPanelProps {
|
||||
children?: React.ReactNode;
|
||||
index: any;
|
||||
value: any;
|
||||
}
|
||||
|
||||
function TabPanelMine(props: TabPanelProps) {
|
||||
const { children, value, index, ...other } = props;
|
||||
|
||||
return (
|
||||
<div
|
||||
role="tabpanel"
|
||||
hidden={value !== index}
|
||||
aria-labelledby={`simple-tab-${index}`}
|
||||
{...other}>
|
||||
{value === index && <React.Fragment>{children}</React.Fragment>}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const useStyles = makeStyles((theme: Theme) => {
|
||||
return ({
|
||||
dialogContent: {
|
||||
minHeight: "25vh"
|
||||
},
|
||||
removeDeviceDialog: {
|
||||
zIndex: theme.zIndex.modal + 1
|
||||
},
|
||||
tabSmall: {
|
||||
width: "100%",
|
||||
boxSizing: "border-box",
|
||||
flexShrink: 1,
|
||||
minWidth: "48px",
|
||||
marginTop: 0,
|
||||
paddingTop: 0
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
interface Props {
|
||||
device: Device;
|
||||
isDialogOpen: boolean;
|
||||
closeDialogCallback: Function;
|
||||
canEdit: boolean;
|
||||
refreshCallback: Function;
|
||||
components?: Component[];
|
||||
}
|
||||
|
||||
const deviceFromForm = (device: Device): Device => {
|
||||
if (device.settings.upgradeChannel === pond.UpgradeChannel.UPGRADE_CHANNEL_INVALID) {
|
||||
device.settings.upgradeChannel = pond.UpgradeChannel.UPGRADE_CHANNEL_STABLE;
|
||||
}
|
||||
return device;
|
||||
};
|
||||
|
||||
export default function DeviceSettings(props: Props) {
|
||||
const classes = useStyles();
|
||||
const [{ user }] = useGlobalState();
|
||||
const navigate = useNavigate();
|
||||
const { success, error } = useSnackbar();
|
||||
const deviceAPI = useDeviceAPI();
|
||||
const { device, isDialogOpen, closeDialogCallback, canEdit, refreshCallback, components } = props;
|
||||
const prevDevice = usePrevious(device);
|
||||
const [deviceForm, setDeviceForm] = useState<Device>(deviceFromForm(Device.clone(device)));
|
||||
const [isRemoveDeviceDialogOpen, setIsRemoveDeviceDialogOpen] = useState<boolean>(false);
|
||||
const [sleeps, setSleeps] = useState<boolean>(device.settings.sleepDurationS > 0);
|
||||
const [compExtOne, setCompExtOne] = useState("");
|
||||
const [compExtTwo, setCompExtTwo] = useState("");
|
||||
const [compExtThree, setCompExtThree] = useState("");
|
||||
const [currentTab, setCurrentTab] = useState(0);
|
||||
const [linearMutations, setLinearMutations] = useState<pond.LinearMutation[]>([]);
|
||||
const [componentsByDevice, setComponentsByDevice] = useState<Map<string, Component[]>>(
|
||||
new Map<string, Component[]>()
|
||||
);
|
||||
const [newMutationDialog, setNewMutationDialog] = useState(false);
|
||||
const [existingMutation, setExistingMutation] = useState<pond.LinearMutation>();
|
||||
|
||||
useEffect(() => {
|
||||
if (prevDevice !== device) {
|
||||
setDeviceForm(deviceFromForm(Device.clone(props.device)));
|
||||
if (device.settings.extensionComponents[0]) {
|
||||
setCompExtOne(device.settings.extensionComponents[0]);
|
||||
}
|
||||
if (device.settings.extensionComponents[1]) {
|
||||
setCompExtTwo(device.settings.extensionComponents[1]);
|
||||
}
|
||||
if (device.settings.extensionComponents[2]) {
|
||||
setCompExtThree(device.settings.extensionComponents[2]);
|
||||
}
|
||||
if (device.settings.mutations) {
|
||||
setLinearMutations(device.settings.mutations);
|
||||
}
|
||||
if (components) {
|
||||
let cbd = new Map<string, Component[]>();
|
||||
cbd.set(device.id().toString(), components);
|
||||
setComponentsByDevice(cbd);
|
||||
}
|
||||
}
|
||||
}, [device, prevDevice, props.device, components]);
|
||||
|
||||
const close = () => {
|
||||
closeDialogCallback();
|
||||
};
|
||||
|
||||
const isSleepDurationValid = (device: Device): boolean => {
|
||||
return (
|
||||
!sleeps ||
|
||||
(sleeps && device.settings.sleepDurationS >= 10 && device.settings.sleepDelayMs >= 1000)
|
||||
);
|
||||
};
|
||||
|
||||
const minCheckPeriodS = (): number => {
|
||||
let defaultPeriod = 60;
|
||||
switch (device.settings.platform) {
|
||||
case pond.DevicePlatform.DEVICE_PLATFORM_ELECTRON:
|
||||
return user.hasFeature("admin") ? defaultPeriod : 300;
|
||||
default:
|
||||
return defaultPeriod;
|
||||
}
|
||||
};
|
||||
|
||||
const minCheckPeriodDescription = (): string => {
|
||||
let min = minCheckPeriodS() / 60;
|
||||
return min.toString() + " minute" + (min !== 1 ? "s" : "");
|
||||
};
|
||||
|
||||
const isCheckPeriodValid = (device: Device): boolean => {
|
||||
return (
|
||||
device.settings.pondCheckPeriodS >= minCheckPeriodS() &&
|
||||
device.settings.pondCheckPeriodS <= 3600
|
||||
);
|
||||
};
|
||||
|
||||
const isFormValid = (): boolean => {
|
||||
return isSleepDurationValid(deviceForm) && isCheckPeriodValid(deviceForm);
|
||||
};
|
||||
|
||||
const changeName = (event: any) => {
|
||||
let updatedForm = Device.clone(deviceForm);
|
||||
updatedForm.settings.name = String(event.target.value);
|
||||
setDeviceForm(updatedForm);
|
||||
};
|
||||
|
||||
const changeDescription = (event: any) => {
|
||||
let updatedForm = Device.clone(deviceForm);
|
||||
updatedForm.settings.description = String(event.target.value);
|
||||
setDeviceForm(updatedForm);
|
||||
};
|
||||
|
||||
const changeSleepDuration = (ms: number) => {
|
||||
let updatedForm = Device.clone(deviceForm);
|
||||
updatedForm.settings.sleepDurationS = ms / 1000;
|
||||
setDeviceForm(updatedForm);
|
||||
};
|
||||
|
||||
const changeSleepDelay = (ms: number) => {
|
||||
let updatedForm = Device.clone(deviceForm);
|
||||
updatedForm.settings.sleepDelayMs = ms;
|
||||
setDeviceForm(updatedForm);
|
||||
};
|
||||
|
||||
const changePondCheckPeriod = (ms: number) => {
|
||||
let updatedForm = Device.clone(deviceForm);
|
||||
updatedForm.settings.pondCheckPeriodS = ms / 1000;
|
||||
setDeviceForm(updatedForm);
|
||||
};
|
||||
|
||||
const changeUpgradeChannel = (event: any) => {
|
||||
let updatedForm = Device.clone(deviceForm);
|
||||
updatedForm.settings.upgradeChannel = event.target.value;
|
||||
setDeviceForm(updatedForm);
|
||||
};
|
||||
|
||||
const changeProduct = (event: any) => {
|
||||
let updatedForm = Device.clone(deviceForm);
|
||||
updatedForm.settings.product = event.target.value;
|
||||
setDeviceForm(updatedForm);
|
||||
};
|
||||
|
||||
const upgradeChannelHelper = (): string => {
|
||||
switch (deviceForm.settings.upgradeChannel) {
|
||||
case pond.UpgradeChannel.UPGRADE_CHANNEL_STABLE: {
|
||||
return "";
|
||||
}
|
||||
case pond.UpgradeChannel.UPGRADE_CHANNEL_BETA: {
|
||||
return "The newest field tested features as they become available";
|
||||
}
|
||||
case pond.UpgradeChannel.UPGRADE_CHANNEL_ALPHA: {
|
||||
return "The newest lab tested features as they become available";
|
||||
}
|
||||
case pond.UpgradeChannel.UPGRADE_CHANNEL_DEVELOPMENT: {
|
||||
return "Features as they're written, expect things to break";
|
||||
}
|
||||
}
|
||||
return "";
|
||||
};
|
||||
|
||||
const toggleAutomaticallyUpgrade = (event: any) => {
|
||||
let updatedForm = Device.clone(deviceForm);
|
||||
updatedForm.settings.automaticallyUpgrade = event.target.checked;
|
||||
setDeviceForm(updatedForm);
|
||||
};
|
||||
|
||||
const toggleSleeps = (event: any) => {
|
||||
let updatedForm = Device.clone(deviceForm);
|
||||
let updatedSleeps = event.target.checked;
|
||||
updatedSleeps
|
||||
? (updatedForm.settings.sleepType = quack.SleepType.SLEEP_TYPE_NAP)
|
||||
: (updatedForm.settings.sleepType = quack.SleepType.SLEEP_TYPE_NONE);
|
||||
setSleeps(updatedSleeps);
|
||||
setDeviceForm(updatedForm);
|
||||
};
|
||||
|
||||
const submitSettings = () => {
|
||||
const deviceName = deviceForm.name();
|
||||
const deviceID = device.id();
|
||||
if (!sleeps) {
|
||||
deviceForm.settings.sleepDurationS = 0;
|
||||
}
|
||||
deviceForm.settings.extensionComponents = [compExtOne, compExtTwo, compExtThree];
|
||||
deviceForm.settings.mutations = linearMutations;
|
||||
deviceAPI
|
||||
.update(deviceID, deviceForm.settings)
|
||||
.then((_response: any) => {
|
||||
success(deviceName + " was successfully updated!");
|
||||
close();
|
||||
refreshCallback();
|
||||
})
|
||||
.catch((_err: any) => {
|
||||
error("Error occured while configuring " + deviceName);
|
||||
close();
|
||||
});
|
||||
};
|
||||
|
||||
const confirmRemoveDevice = () => {
|
||||
setIsRemoveDeviceDialogOpen(true);
|
||||
};
|
||||
|
||||
const closeConfirmRemoveDeviceDialog = () => {
|
||||
setIsRemoveDeviceDialogOpen(false);
|
||||
};
|
||||
|
||||
const removeDevice = () => {
|
||||
const deviceName = deviceForm.name();
|
||||
deviceAPI
|
||||
.remove(device.id())
|
||||
.then((_response: any) => {
|
||||
success(deviceName + " was successfully deleted!");
|
||||
navigate("/");
|
||||
})
|
||||
.catch((_err: any) => {
|
||||
error("Error occured while deleting " + deviceName + ".");
|
||||
closeConfirmRemoveDeviceDialog();
|
||||
close();
|
||||
});
|
||||
};
|
||||
|
||||
const canRemove = (): boolean => {
|
||||
return user.allowedTo("remove-devices");
|
||||
};
|
||||
|
||||
const title = () => {
|
||||
return (
|
||||
<React.Fragment>
|
||||
Device Settings
|
||||
<Typography variant="body2" color="textSecondary">
|
||||
{deviceForm.name()} - ID: {deviceForm.id()}
|
||||
</Typography>
|
||||
</React.Fragment>
|
||||
);
|
||||
};
|
||||
|
||||
const content = () => {
|
||||
return (
|
||||
<React.Fragment>
|
||||
<TextField
|
||||
id="name"
|
||||
name="name"
|
||||
label="Name"
|
||||
defaultValue={deviceForm.settings.name}
|
||||
onChange={changeName}
|
||||
margin="normal"
|
||||
variant="outlined"
|
||||
type="text"
|
||||
fullWidth
|
||||
InputLabelProps={{ shrink: true }}
|
||||
disabled={!canEdit}
|
||||
/>
|
||||
<TextField
|
||||
id="description"
|
||||
name="description"
|
||||
label="Description"
|
||||
defaultValue={deviceForm.settings.description}
|
||||
onChange={changeDescription}
|
||||
multiline
|
||||
rows={2}
|
||||
// rowsMax={4}
|
||||
type="text"
|
||||
margin="normal"
|
||||
variant="outlined"
|
||||
fullWidth
|
||||
InputLabelProps={{ shrink: true }}
|
||||
disabled={!canEdit}
|
||||
/>
|
||||
<Grid container direction="row" justifyContent="space-between" alignItems="center" spacing={1}>
|
||||
<Grid size={{ xs: 12, sm: 12 }}>
|
||||
<PeriodSelect
|
||||
id="pondCheckPeriod"
|
||||
label="Check-in Period"
|
||||
isError={!isCheckPeriodValid(deviceForm)}
|
||||
helperText={
|
||||
isCheckPeriodValid(deviceForm)
|
||||
? "How often the device checks for updates"
|
||||
: "Value must be between " + minCheckPeriodDescription() + " and 1 hour"
|
||||
}
|
||||
initialMs={deviceForm.settings.pondCheckPeriodS * 1000}
|
||||
isDisabled={!canEdit}
|
||||
onChange={changePondCheckPeriod}
|
||||
units={["seconds", "minutes", "hours"]}
|
||||
/>
|
||||
</Grid>
|
||||
{user.hasFeature("sleep") && (
|
||||
<Grid container direction="row" justifyContent="space-between" alignItems="center" spacing={1}>
|
||||
<Grid container size={{ xs: 4, sm: 3 }} justifyContent="center">
|
||||
<FormControlLabel
|
||||
control={
|
||||
<Switch
|
||||
checked={sleeps}
|
||||
onChange={toggleSleeps}
|
||||
name="sleeps"
|
||||
aria-label="sleeps"
|
||||
color="secondary"
|
||||
/>
|
||||
}
|
||||
label={<Typography variant="caption">Sleeps</Typography>}
|
||||
labelPlacement="top"
|
||||
disabled={!canEdit}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid size={{ xs: 8, sm: 9 }}>
|
||||
<FormControl fullWidth>
|
||||
<PeriodSelect
|
||||
id="sleepDuration"
|
||||
label="Sleep Duration"
|
||||
isError={!isSleepDurationValid(deviceForm)}
|
||||
helperText={
|
||||
!isSleepDurationValid(deviceForm) ? "Must be at least 10 seconds" : undefined
|
||||
}
|
||||
isDisabled={!canEdit || !sleeps}
|
||||
initialMs={deviceForm.settings.sleepDurationS * 1000}
|
||||
onChange={changeSleepDuration}
|
||||
units={["seconds", "minutes", "hours"]}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormControl fullWidth>
|
||||
<PeriodSelect
|
||||
id="awakeDuration"
|
||||
label="Awake Duration"
|
||||
isError={!isSleepDurationValid(deviceForm)}
|
||||
helperText={
|
||||
!isSleepDurationValid(deviceForm) ? "Must be at least 1 second" : undefined
|
||||
}
|
||||
isDisabled={!canEdit || !sleeps}
|
||||
initialMs={deviceForm.settings.sleepDelayMs}
|
||||
onChange={changeSleepDelay}
|
||||
units={["seconds", "minutes", "hours"]}
|
||||
/>
|
||||
</FormControl>
|
||||
</Grid>
|
||||
</Grid>
|
||||
)}
|
||||
<Grid size={{ xs: 12, sm: 12 }}>
|
||||
<TextField
|
||||
id="upgradeChannel"
|
||||
label="Upgrade Channel"
|
||||
select
|
||||
value={deviceForm.settings.upgradeChannel}
|
||||
onChange={changeUpgradeChannel}
|
||||
helperText={upgradeChannelHelper()}
|
||||
margin="normal"
|
||||
fullWidth
|
||||
variant="outlined"
|
||||
disabled={!canEdit}>
|
||||
<MenuItem value={pond.UpgradeChannel.UPGRADE_CHANNEL_STABLE}>Stable</MenuItem>
|
||||
<MenuItem value={pond.UpgradeChannel.UPGRADE_CHANNEL_BETA}>Beta</MenuItem>
|
||||
<MenuItem value={pond.UpgradeChannel.UPGRADE_CHANNEL_ALPHA}>Alpha</MenuItem>
|
||||
{user.hasFeature("dev-channel") && (
|
||||
<MenuItem value={pond.UpgradeChannel.UPGRADE_CHANNEL_DEVELOPMENT}>
|
||||
Development
|
||||
</MenuItem>
|
||||
)}
|
||||
{user.hasFeature("dev-channel") && (
|
||||
<MenuItem value={pond.UpgradeChannel.UPGRADE_CHANNEL_RECOVERY}>Recovery</MenuItem>
|
||||
)}
|
||||
</TextField>
|
||||
</Grid>
|
||||
{user.hasFeature("admin") && (
|
||||
<TextField
|
||||
id="deviceProduct"
|
||||
label="Device Product"
|
||||
select
|
||||
value={deviceForm.settings.product}
|
||||
onChange={event => changeProduct(event)}
|
||||
margin="normal"
|
||||
fullWidth
|
||||
variant="outlined"
|
||||
disabled={!canEdit}>
|
||||
{ListDeviceProductDescribers().map(describer => (
|
||||
<MenuItem key={describer.product} value={describer.product}>
|
||||
{describer.label}
|
||||
</MenuItem>
|
||||
))}
|
||||
</TextField>
|
||||
)}
|
||||
{IsExtended(deviceForm.settings.product) && components && (
|
||||
<Grid size={{ xs: 12 }}>
|
||||
Select components to display on card
|
||||
<TextField
|
||||
id="component1"
|
||||
label="Component 1"
|
||||
select
|
||||
value={compExtOne}
|
||||
onChange={event => setCompExtOne(event.target.value)}
|
||||
margin="normal"
|
||||
fullWidth
|
||||
variant="outlined"
|
||||
disabled={!canEdit}>
|
||||
{components.map(comp => (
|
||||
<MenuItem key={comp.key()} value={comp.locationString()}>
|
||||
{comp.name()}
|
||||
</MenuItem>
|
||||
))}
|
||||
</TextField>
|
||||
<TextField
|
||||
id="component1"
|
||||
label="Component 2"
|
||||
select
|
||||
value={compExtTwo}
|
||||
onChange={event => setCompExtTwo(event.target.value)}
|
||||
margin="normal"
|
||||
fullWidth
|
||||
variant="outlined"
|
||||
disabled={!canEdit}>
|
||||
{components.map(comp => (
|
||||
<MenuItem key={comp.key()} value={comp.locationString()}>
|
||||
{comp.name()}
|
||||
</MenuItem>
|
||||
))}
|
||||
</TextField>
|
||||
<TextField
|
||||
id="component1"
|
||||
label="Component 3"
|
||||
select
|
||||
value={compExtThree}
|
||||
onChange={event => setCompExtThree(event.target.value)}
|
||||
margin="normal"
|
||||
fullWidth
|
||||
variant="outlined"
|
||||
disabled={!canEdit}>
|
||||
{components.map(comp => (
|
||||
<MenuItem key={comp.key()} value={comp.locationString()}>
|
||||
{comp.name()}
|
||||
</MenuItem>
|
||||
))}
|
||||
</TextField>
|
||||
</Grid>
|
||||
)}
|
||||
{user.hasAdmin() && (
|
||||
<Grid size={{ xs: 12, sm: 12 }}>
|
||||
<FormControlLabel
|
||||
control={
|
||||
<Switch
|
||||
checked={deviceForm.settings.automaticallyUpgrade}
|
||||
onChange={toggleAutomaticallyUpgrade}
|
||||
name="automaticallyUpgrade"
|
||||
aria-label="automaticallyUpgrade"
|
||||
color="secondary"
|
||||
/>
|
||||
}
|
||||
disabled={!canEdit}
|
||||
label="Automatically Upgrade"
|
||||
labelPlacement="end"
|
||||
/>
|
||||
</Grid>
|
||||
)}
|
||||
</Grid>
|
||||
</React.Fragment>
|
||||
);
|
||||
};
|
||||
|
||||
const mutationsContent = () => {
|
||||
let mutations: JSX.Element[] = [];
|
||||
if (device.settings.mutations) {
|
||||
device.settings.mutations.forEach((mut, i) => {
|
||||
mutations.push(
|
||||
<ListItem key={"mutation" + i}>
|
||||
<Grid
|
||||
container
|
||||
direction="row"
|
||||
alignContent="center"
|
||||
alignItems="center"
|
||||
justifyContent="space-between">
|
||||
<Grid size={{ xs: 5 }}>
|
||||
<Typography>{mut.mutationName}</Typography>
|
||||
</Grid>
|
||||
<Grid>
|
||||
<Button
|
||||
variant="contained"
|
||||
style={{ backgroundColor: "red" }}
|
||||
onClick={() => {
|
||||
let lm = linearMutations;
|
||||
lm.splice(i, 1);
|
||||
setLinearMutations([...lm]);
|
||||
}}>
|
||||
Remove
|
||||
</Button>
|
||||
<Button
|
||||
style={{ marginLeft: 10 }}
|
||||
variant="contained"
|
||||
color="primary"
|
||||
onClick={() => {
|
||||
setExistingMutation(mut);
|
||||
setNewMutationDialog(true);
|
||||
}}>
|
||||
Edit
|
||||
</Button>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</ListItem>
|
||||
);
|
||||
});
|
||||
}
|
||||
let mutationList: JSX.Element = <List>{mutations}</List>;
|
||||
return (
|
||||
<React.Fragment>
|
||||
<Button
|
||||
variant="contained"
|
||||
color="primary"
|
||||
onClick={() => {
|
||||
setExistingMutation(undefined);
|
||||
setNewMutationDialog(true);
|
||||
}}>
|
||||
Add New Mutation
|
||||
</Button>
|
||||
<List>{mutationList}</List>
|
||||
</React.Fragment>
|
||||
);
|
||||
};
|
||||
|
||||
const actions = () => {
|
||||
return (
|
||||
<Grid container justifyContent="space-between" direction="row">
|
||||
<Grid size={{ xs: 5 }}>
|
||||
{canRemove() && <DeleteButton onClick={confirmRemoveDevice}>Delete</DeleteButton>}
|
||||
</Grid>
|
||||
<Grid size={{ xs: 7 }} container justifyContent="flex-end">
|
||||
<Button onClick={close} color="primary">
|
||||
Cancel
|
||||
</Button>
|
||||
<Button onClick={submitSettings} color="primary" disabled={!isFormValid() || !canEdit}>
|
||||
Submit
|
||||
</Button>
|
||||
</Grid>
|
||||
</Grid>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
<ResponsiveDialog
|
||||
maxWidth="sm"
|
||||
fullWidth
|
||||
open={isDialogOpen}
|
||||
onClose={close}
|
||||
aria-labelledby="device-config-title">
|
||||
<DialogTitle id="device-config-title">{title()}</DialogTitle>
|
||||
<DialogContent className={classes.dialogContent}>
|
||||
{components ? (
|
||||
<React.Fragment>
|
||||
<Tabs
|
||||
value={currentTab}
|
||||
onChange={(_, value) => setCurrentTab(value)}
|
||||
indicatorColor="primary"
|
||||
textColor="primary"
|
||||
variant="fullWidth"
|
||||
aria-label="device tabs"
|
||||
classes={{ root: classes.tabSmall }}>
|
||||
<Tab label={"General"} className={classes.tabSmall} />
|
||||
{user.hasFeature("beta") && (
|
||||
<Tab label={"Mutations"} className={classes.tabSmall} />
|
||||
)}
|
||||
</Tabs>
|
||||
<TabPanelMine value={currentTab} index={0}>
|
||||
{content()}
|
||||
</TabPanelMine>
|
||||
<TabPanelMine value={currentTab} index={1}>
|
||||
{mutationsContent()}
|
||||
</TabPanelMine>
|
||||
</React.Fragment>
|
||||
) : (
|
||||
content()
|
||||
)}
|
||||
</DialogContent>
|
||||
<DialogActions>{actions()}</DialogActions>
|
||||
</ResponsiveDialog>
|
||||
<Dialog
|
||||
open={isRemoveDeviceDialogOpen}
|
||||
onClose={closeConfirmRemoveDeviceDialog}
|
||||
aria-labelledby="confirm-remove-device-label"
|
||||
aria-describedby="confirm-remove-device-description"
|
||||
className={classes.removeDeviceDialog}>
|
||||
<DialogTitle id="confirm-remove-device-title">Delete {deviceForm.name()}?</DialogTitle>
|
||||
<DialogContent>
|
||||
<Typography variant="subtitle1" color="error" align="left">
|
||||
WARNING:
|
||||
</Typography>
|
||||
<Typography variant="subtitle1" color="textSecondary" align="left">
|
||||
Clicking 'Accept' will remove the device from all users and groups associated with it.
|
||||
</Typography>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button onClick={closeConfirmRemoveDeviceDialog} color="primary">
|
||||
Cancel
|
||||
</Button>
|
||||
<Button onClick={removeDevice} color="primary">
|
||||
Accept
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
<LinearMutationBuilder
|
||||
existingMutation={existingMutation}
|
||||
componentsByDevice={componentsByDevice}
|
||||
open={newMutationDialog}
|
||||
onClose={() => {
|
||||
setNewMutationDialog(false);
|
||||
}}
|
||||
onSubmit={newMutation => {
|
||||
let lm = linearMutations;
|
||||
lm.push(newMutation);
|
||||
setLinearMutations(lm);
|
||||
}}
|
||||
/>
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
|
|
@ -1,4 +1,5 @@
|
|||
import {
|
||||
CircularProgress,
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogTitle,
|
||||
|
|
@ -18,10 +19,11 @@ import { Tag as TagUI } from "common/Tag";
|
|||
import TagSettings from "common/TagSettings";
|
||||
import { Device, Tag } from "models";
|
||||
import { filterByTag } from "pbHelpers/Tag";
|
||||
import { useDeviceAPI, useGlobalState, useSnackbar } from "providers";
|
||||
import { useDeviceAPI, useSnackbar, useTagAPI } from "providers";
|
||||
import React, { useEffect, useRef, useState } from "react";
|
||||
import { pond } from "protobuf-ts/pond";
|
||||
|
||||
const useStyles = makeStyles((theme: Theme) => {
|
||||
const useStyles = makeStyles((_theme: Theme) => {
|
||||
return ({
|
||||
addIcon: {
|
||||
color: "var(--status-ok)"
|
||||
|
|
@ -37,13 +39,27 @@ interface AddDeviceTagProps {
|
|||
|
||||
function AddDeviceTag(props: AddDeviceTagProps) {
|
||||
const classes = useStyles();
|
||||
const tagAPI = useTagAPI();
|
||||
const { device, deviceTags, addTagToDevice } = props;
|
||||
const [open, setOpen] = useState(false);
|
||||
const [createNewOpen, setCreateNewOpen] = useState(false);
|
||||
const [searchValue, setSearchValue] = useState("");
|
||||
// const [{ tags }] = useGlobalState();
|
||||
const [tags, setTags] = useState<Tag[]>([])
|
||||
|
||||
const loadTags = () => {
|
||||
tagAPI.listTags().then(resp => {
|
||||
let newTags: Tag[] = [];
|
||||
resp.data.tags.forEach((tag: pond.Tag) => {
|
||||
newTags.push(Tag.create(tag))
|
||||
})
|
||||
setTags(newTags)
|
||||
})
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
loadTags()
|
||||
}, [])
|
||||
|
||||
const tagItems = tags
|
||||
.filter(tag => !deviceTags.some(key => key === tag.settings.key))
|
||||
.filter(tag => filterByTag(searchValue, tag))
|
||||
|
|
@ -117,12 +133,35 @@ interface DeviceTagsProps {
|
|||
export default function DeviceTags(props: DeviceTagsProps) {
|
||||
const { device, disableAdd } = props;
|
||||
// const [{ tags }] = useGlobalState();
|
||||
const tagAPI = useTagAPI()
|
||||
const [tags, setTags] = useState<Tag[]>([])
|
||||
const [loading, setLoading] = useState(false)
|
||||
const deviceAPI = useDeviceAPI();
|
||||
const { error } = useSnackbar();
|
||||
const [deviceTags, setDeviceTags] = useState<string[]>(device.status.tagKeys);
|
||||
const previousDeviceRef = useRef(device);
|
||||
|
||||
const loadTags = () => {
|
||||
setLoading(true)
|
||||
tagAPI.listTags().then(resp => {
|
||||
let newTags: Tag[] = [];
|
||||
resp.data.tags.forEach((tag: pond.Tag) => {
|
||||
newTags.push(Tag.create(tag))
|
||||
})
|
||||
setTags(newTags)
|
||||
}).finally(() => {
|
||||
setLoading(false)
|
||||
})
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
console.log(tags)
|
||||
}, [tags])
|
||||
|
||||
useEffect(() => {
|
||||
loadTags()
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
if (previousDeviceRef.current !== device) {
|
||||
setDeviceTags(device.status.tagKeys);
|
||||
|
|
@ -156,6 +195,12 @@ export default function DeviceTags(props: DeviceTagsProps) {
|
|||
return null;
|
||||
}
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<CircularProgress />
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
{deviceTags.map(key => {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue