680 lines
22 KiB
TypeScript
680 lines
22 KiB
TypeScript
import {
|
|
Button,
|
|
Dialog,
|
|
DialogActions,
|
|
DialogContent,
|
|
DialogTitle,
|
|
FormControl,
|
|
FormControlLabel,
|
|
Grid2 as Grid,
|
|
List,
|
|
ListItem,
|
|
MenuItem,
|
|
Switch,
|
|
Tab,
|
|
Tabs,
|
|
TextField,
|
|
Theme,
|
|
Typography
|
|
} from "@mui/material";
|
|
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 { quack } from "protobuf-ts/quack";
|
|
import LinearMutationBuilder from "common/LinearMutationBuilder";
|
|
import { makeStyles } from "@mui/styles";
|
|
import { useNavigate } from "react-router-dom";
|
|
import CancelSubmit from "common/CancelSubmit";
|
|
|
|
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, as }] = 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, as)
|
|
.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(), as)
|
|
.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" width={"100%"}>
|
|
<Grid>
|
|
{canRemove() && <DeleteButton onClick={confirmRemoveDevice}>Delete</DeleteButton>}
|
|
</Grid>
|
|
<Grid container justifyContent="flex-end">
|
|
<CancelSubmit onSubmit={submitSettings} onCancel={close}/>
|
|
{/* <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>
|
|
);
|
|
}
|