Merge branch 'dev_environment' of gitlab.com:brandx/bxt-app into dev_environment
This commit is contained in:
commit
0a62a3a879
41 changed files with 8902 additions and 79 deletions
|
|
@ -42,7 +42,7 @@ import SyncDevice from "device/SyncDevice";
|
|||
// import InteractionSettings from "interactions/InteractionSettings";
|
||||
import { cloneDeep } from "lodash";
|
||||
// import { Component, Device, deviceScope, Interaction } from "models";
|
||||
import { Device, deviceScope } from "models";
|
||||
import { Component, Device, deviceScope } from "models";
|
||||
// import { MatchParams } from "navigation/Routes";
|
||||
// import { isShareableLink } from "pbHelpers/Device";
|
||||
import { pond } from "protobuf-ts/pond";
|
||||
|
|
@ -96,7 +96,7 @@ const useStyles = makeStyles((_theme: Theme) => {
|
|||
interface Props {
|
||||
device: Device;
|
||||
isPaused: boolean;
|
||||
// components: Component[];
|
||||
components: Component[];
|
||||
// interactions: Interaction[];
|
||||
refreshCallback: () => void;
|
||||
availablePositions: DeviceAvailabilityMap;
|
||||
|
|
@ -129,7 +129,7 @@ export default function DeviceActions(props: Props) {
|
|||
const {
|
||||
device,
|
||||
isPaused,
|
||||
// components,
|
||||
components,
|
||||
// interactions,
|
||||
refreshCallback,
|
||||
availablePositions,
|
||||
|
|
@ -383,8 +383,8 @@ export default function DeviceActions(props: Props) {
|
|||
closeDialogCallback={() => closeDialog("isDeviceSettingsDialogOpen")}
|
||||
refreshCallback={refreshCallback}
|
||||
canEdit={canWrite}
|
||||
// components={components}
|
||||
components={[]}
|
||||
components={components}
|
||||
//components={[]}
|
||||
/>
|
||||
<ComponentSettings
|
||||
mode="add"
|
||||
|
|
|
|||
391
src/device/DeviceViewer.tsx
Normal file
391
src/device/DeviceViewer.tsx
Normal file
|
|
@ -0,0 +1,391 @@
|
|||
import { Box, Grid2 as Grid, Theme, Typography } from "@mui/material";
|
||||
import {makeStyles} from "@mui/styles"
|
||||
//import { Theme } from "@material-ui/core/styles/createMuiTheme";
|
||||
import Loader from "common/Loader";
|
||||
import MapCard from "common/MapCard";
|
||||
import { Coordinate } from "common/MapGL";
|
||||
import ComponentCard from "component/ComponentCard";
|
||||
import ComponentSettings from "component/ComponentSettings";
|
||||
import DeviceOverview from "device/DeviceOverview";
|
||||
import {
|
||||
useComponentAPI,
|
||||
useDeviceAPI,
|
||||
useInteractionsAPI,
|
||||
useSnackbar,
|
||||
//useUsageAPI,
|
||||
useUserAPI
|
||||
} from "hooks";
|
||||
import { cloneDeep } from "lodash";
|
||||
import { Component, Device as DeviceModel, deviceScope, Interaction } from "models";
|
||||
import moment from "moment";
|
||||
import { sameComponentID, sortComponents } from "pbHelpers/Component";
|
||||
import {
|
||||
FindAvailablePositions,
|
||||
DeviceAvailabilityMap,
|
||||
OffsetAvailabilityMap
|
||||
} from "pbHelpers/DeviceAvailability";
|
||||
import { pond } from "protobuf-ts/pond";
|
||||
import { quack } from "protobuf-ts/quack";
|
||||
import { useGlobalState } from "providers";
|
||||
import React, { useCallback, useEffect, useState } from "react";
|
||||
//import { Redirect } from "react-router";
|
||||
import { or } from "utils/types";
|
||||
import DeviceActions from "./DeviceActions";
|
||||
|
||||
const useStyles = makeStyles((theme: Theme) => ({
|
||||
title: {
|
||||
whiteSpace: "nowrap",
|
||||
overflow: "hidden",
|
||||
textOverflow: "ellipsis"
|
||||
},
|
||||
noComponents: {
|
||||
marginTop: theme.spacing(16)
|
||||
},
|
||||
addComponentText: {
|
||||
...theme.typography.subtitle2,
|
||||
color: theme.palette.primary.main,
|
||||
cursor: "pointer"
|
||||
},
|
||||
deviceLoading: {
|
||||
position: "relative",
|
||||
height: "100%",
|
||||
width: "100%",
|
||||
display: "flex",
|
||||
marginTop: theme.spacing(16),
|
||||
alignItems: "center",
|
||||
justifyContent: "center"
|
||||
},
|
||||
rateLimit: {
|
||||
marginTop: theme.spacing(1),
|
||||
color: theme.palette.text.secondary
|
||||
}
|
||||
})
|
||||
);
|
||||
interface Props {
|
||||
device: DeviceModel;
|
||||
isMobile?: boolean;
|
||||
}
|
||||
|
||||
export default function Device(props: Props) {
|
||||
const classes = useStyles();
|
||||
const [{ user }] = useGlobalState();
|
||||
const { error } = useSnackbar();
|
||||
const deviceID = props.device.id();
|
||||
const deviceAPI = useDeviceAPI();
|
||||
const componentAPI = useComponentAPI();
|
||||
const interactionsAPI = useInteractionsAPI();
|
||||
//const usageAPI = useUsageAPI();
|
||||
const userAPI = useUserAPI();
|
||||
const [isLoading, setIsLoading] = useState<boolean>(false);
|
||||
const [device, setDevice] = useState<DeviceModel>(DeviceModel.create());
|
||||
const [components, setComponents] = useState<Map<string, Component>>(new Map());
|
||||
const [interactions, setInteractions] = useState<Interaction[]>([]);
|
||||
const [permissions, setPermissions] = useState<pond.Permission[]>([]);
|
||||
const [preferences, setPreferences] = useState<pond.UserPreferences>(
|
||||
pond.UserPreferences.create()
|
||||
);
|
||||
const [invalidDevice, setInvalidDevice] = useState<boolean>(false);
|
||||
const [isAddComponentDialogOpen, setIsAddComponentDialogOpen] = useState<boolean>(false);
|
||||
const [availablePositions, setAvailablePositions] = useState<DeviceAvailabilityMap>(new Map());
|
||||
const [availableOffsets, setAvailableOffsets] = useState<OffsetAvailabilityMap>(new Map());
|
||||
const [latitude, setLatitude] = useState<number>(NaN);
|
||||
const [longitude, setLongitude] = useState<number>(NaN);
|
||||
const [cellularUsage, setCellularUsage] = useState<number>(0);
|
||||
const [cellularStatus, setCellularStatus] = useState<string>("");
|
||||
const rate = 30;
|
||||
|
||||
const load = useCallback(() => {
|
||||
if (user.id() === "") return;
|
||||
setIsLoading(true);
|
||||
//let devicePromise = deviceAPI.get(deviceID);
|
||||
let userPromise = userAPI.getUser(user.id(), deviceScope(deviceID.toString()));
|
||||
let componentsPromise = componentAPI.list(
|
||||
deviceID,
|
||||
undefined,
|
||||
[deviceID.toString()],
|
||||
["device"],
|
||||
true
|
||||
);
|
||||
let interactionsPromise = interactionsAPI.listInteractionsByDevice(deviceID);
|
||||
let groupPromise: Promise<any> = Promise.resolve(undefined);
|
||||
Promise.all([userPromise, componentsPromise, interactionsPromise, groupPromise])
|
||||
.then(([userRes, componentsRes, interactionsRes]) => {
|
||||
//let rDevice = DeviceModel.any(deviceRes.data);
|
||||
let rawComponents: Array<any> = or(componentsRes.data.components, []);
|
||||
let rComponents: Map<string, Component> = new Map();
|
||||
rawComponents.forEach((rawComponent: any) => {
|
||||
let component = Component.any(rawComponent);
|
||||
rComponents.set(component.key(), component);
|
||||
});
|
||||
|
||||
setComponents(rComponents);
|
||||
|
||||
setInteractions(interactionsRes);
|
||||
setPermissions(userRes.permissions);
|
||||
setPreferences(userRes.preferences);
|
||||
|
||||
setDevice(props.device);
|
||||
setLongitude(props.device.status.longitude ? props.device.status.longitude : NaN);
|
||||
setLatitude(props.device.status.latitude ? props.device.status.latitude : NaN);
|
||||
let available = FindAvailablePositions(
|
||||
Array.from(rComponents.values()),
|
||||
props.device.settings.product
|
||||
);
|
||||
setAvailablePositions(available.GetAvailability());
|
||||
setAvailableOffsets(available.offsetAvailability);
|
||||
})
|
||||
.catch((err: any) => {
|
||||
setDevice(DeviceModel.create());
|
||||
setComponents(new Map());
|
||||
setInteractions([]);
|
||||
setAvailablePositions(new Map());
|
||||
setAvailableOffsets(new Map());
|
||||
setPermissions([]);
|
||||
setPreferences(pond.UserPreferences.create());
|
||||
setInvalidDevice(true);
|
||||
error(err);
|
||||
})
|
||||
.finally(() => setIsLoading(false));
|
||||
// usageAPI
|
||||
// .getUsage(deviceID, moment().subtract(1, "days"))
|
||||
// .then((res: any) => {
|
||||
// let usage = res.data;
|
||||
// if (usage) {
|
||||
// let rCellularStatus = "active";
|
||||
// let rCellularUsage = 0;
|
||||
// let sessions: any[] = [];
|
||||
// if (usage.sessions) {
|
||||
// sessions = usage.sessions.filter((session: any) => {
|
||||
// return moment(session.begin).isAfter(moment().subtract(1, "days"));
|
||||
// });
|
||||
// }
|
||||
// sessions.forEach((session: any) => (rCellularUsage += Number(or(session.bytes, 0))));
|
||||
// setCellularStatus(rCellularStatus);
|
||||
// setCellularUsage(rCellularUsage);
|
||||
// }
|
||||
// })
|
||||
// .catch(err => {});
|
||||
}, [componentAPI, props.device, deviceID, error, interactionsAPI, /*usageAPI*/, userAPI, user]);
|
||||
|
||||
useEffect(() => {
|
||||
load();
|
||||
}, [load]);
|
||||
|
||||
const getOrderedComponents = () => {
|
||||
if (preferences.childDisplayOrder.length === Array.from(components.values()).length) {
|
||||
return Array.from(components.values()).sort((a, b: Component) =>
|
||||
sortComponents(a, b, preferences.childDisplayOrder)
|
||||
);
|
||||
} else {
|
||||
return Array.from(components.values());
|
||||
}
|
||||
};
|
||||
|
||||
const displayedComponentsBlacklist = [
|
||||
quack.ComponentType.COMPONENT_TYPE_POWER,
|
||||
quack.ComponentType.COMPONENT_TYPE_MODEM
|
||||
];
|
||||
|
||||
const displayedComponents = () => {
|
||||
return [...components.values()].filter(
|
||||
c => !displayedComponentsBlacklist.includes(c.settings.type)
|
||||
);
|
||||
};
|
||||
|
||||
const handleComponentChanged = (component: Component) => {
|
||||
let updatedComponents = cloneDeep(components);
|
||||
let updatedComponent = cloneDeep(component);
|
||||
if (updatedComponents.has(updatedComponent.key())) {
|
||||
updatedComponents.set(updatedComponent.key(), updatedComponent);
|
||||
setComponents(updatedComponents);
|
||||
}
|
||||
};
|
||||
|
||||
// const productCard = () => {
|
||||
// return <DeviceProductCard device={device} components={components} />;
|
||||
// };
|
||||
|
||||
const componentCards = () => {
|
||||
let orderedComponents = getOrderedComponents();
|
||||
let componentCards = [];
|
||||
let hasGPS = false;
|
||||
for (let i = 0; i < orderedComponents.length; i++) {
|
||||
let c = orderedComponents[i];
|
||||
if (displayedComponentsBlacklist.includes(c.settings.type)) {
|
||||
continue;
|
||||
}
|
||||
let id: quack.IComponentID = quack.ComponentID.fromObject({
|
||||
type: c.settings.type,
|
||||
addressType: c.settings.addressType,
|
||||
address: c.settings.address
|
||||
});
|
||||
let filteredInteractions = interactions.filter(interaction => {
|
||||
let isSource = false;
|
||||
let isSink = false;
|
||||
if (interaction.settings) {
|
||||
isSource = sameComponentID(interaction.settings.source, id);
|
||||
isSink = sameComponentID(interaction.settings.sink, id);
|
||||
}
|
||||
return isSource || isSink;
|
||||
});
|
||||
if (id.type === quack.ComponentType.COMPONENT_TYPE_GPS) {
|
||||
hasGPS = true;
|
||||
}
|
||||
componentCards.push(
|
||||
<ComponentCard
|
||||
device={device}
|
||||
availablePositions={availablePositions}
|
||||
availableOffsets={availableOffsets}
|
||||
component={c}
|
||||
components={orderedComponents}
|
||||
interactions={filteredInteractions}
|
||||
permissions={permissions}
|
||||
key={i}
|
||||
showMobile={props.isMobile}
|
||||
refreshCallback={(updatedComponent?: Component) =>
|
||||
updatedComponent ? handleComponentChanged(updatedComponent) : load()
|
||||
}
|
||||
/>
|
||||
);
|
||||
}
|
||||
if (!hasGPS && !isNaN(latitude) && !isNaN(longitude)) {
|
||||
let path: Array<Coordinate> = [];
|
||||
path.push({ latitude: latitude, longitude: longitude, timestamp: "" });
|
||||
componentCards.push(<MapCard key={"map"} path={path} />);
|
||||
}
|
||||
|
||||
return componentCards;
|
||||
};
|
||||
|
||||
const getUsage = () => {
|
||||
let usage = undefined;
|
||||
if (cellularStatus && cellularStatus !== "") {
|
||||
usage = { status: cellularStatus, bytes: cellularUsage };
|
||||
}
|
||||
return usage;
|
||||
};
|
||||
|
||||
const anyComponentsReportingFast = () => {
|
||||
let fast = false;
|
||||
components.forEach(c => {
|
||||
if (
|
||||
c.settings.measurementPeriodMs > 0 &&
|
||||
c.settings.reportPeriodMs > 0 &&
|
||||
c.settings.reportPeriodMs < rate * 1000
|
||||
) {
|
||||
fast = true;
|
||||
}
|
||||
});
|
||||
return fast;
|
||||
};
|
||||
|
||||
const componentsTab = () => {
|
||||
return (
|
||||
<Box padding={1} marginTop={1}>
|
||||
{anyComponentsReportingFast() && (
|
||||
<Typography className={classes.rateLimit} variant="caption">
|
||||
Live updates are rate limited to {rate}s to ensure a smooth browsing experience
|
||||
</Typography>
|
||||
)}
|
||||
|
||||
{isLoading ? (
|
||||
<span className={classes.deviceLoading}>
|
||||
<Loader />
|
||||
</span>
|
||||
) : displayedComponents().length > 0 ? (
|
||||
<Grid container direction="row" spacing={2}>
|
||||
{componentCards()}
|
||||
</Grid>
|
||||
) : (
|
||||
<Box marginTop={4}>
|
||||
<Typography variant="subtitle1" align="center" color="textSecondary">
|
||||
No components found
|
||||
</Typography>
|
||||
{permissions.includes(pond.Permission.PERMISSION_WRITE) && (
|
||||
<React.Fragment>
|
||||
<Typography variant="body2" align="center" color="textSecondary">
|
||||
<span
|
||||
className={classes.addComponentText}
|
||||
onClick={() => setIsAddComponentDialogOpen(true)}>
|
||||
{" Add "}
|
||||
</span>
|
||||
a component to {device.name()}.
|
||||
</Typography>
|
||||
<ComponentSettings
|
||||
mode="add"
|
||||
device={device}
|
||||
isDialogOpen={or(isAddComponentDialogOpen, false)}
|
||||
closeDialogCallback={() => setIsAddComponentDialogOpen(false)}
|
||||
availablePositions={availablePositions}
|
||||
availableOffsets={availableOffsets}
|
||||
refreshCallback={load}
|
||||
canEdit={permissions.includes(pond.Permission.PERMISSION_WRITE)}
|
||||
/>
|
||||
</React.Fragment>
|
||||
)}
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
const isPaused = () => {
|
||||
let paused = false;
|
||||
if (cellularStatus && cellularStatus !== "") {
|
||||
if (cellularStatus.toLowerCase().includes("pause")) {
|
||||
paused = true;
|
||||
}
|
||||
}
|
||||
return paused;
|
||||
};
|
||||
|
||||
const toggleNotificationPreference = () => {
|
||||
let updatedPreferences = cloneDeep(preferences);
|
||||
updatedPreferences.notify = !preferences.notify;
|
||||
deviceAPI
|
||||
.updatePreferences(deviceID, updatedPreferences)
|
||||
.then(() => setPreferences(updatedPreferences))
|
||||
.catch(() => {
|
||||
error(
|
||||
"Error occured while " +
|
||||
(preferences.notify ? "enabling" : "disabling") +
|
||||
" notifications"
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
// if (invalidDevice) {
|
||||
// return <Redirect to="/404" />;
|
||||
// } else {
|
||||
return (
|
||||
<Box padding={1}>
|
||||
<Grid container direction="row" alignItems="center">
|
||||
<Grid style={{ order: 5 }}>
|
||||
<DeviceActions
|
||||
device={device}
|
||||
isPaused={isPaused()}
|
||||
components={getOrderedComponents()}
|
||||
//interactions={interactions}
|
||||
availablePositions={availablePositions}
|
||||
availableOffsets={availableOffsets}
|
||||
permissions={permissions}
|
||||
refreshCallback={load}
|
||||
preferences={preferences}
|
||||
toggleNotificationPreference={toggleNotificationPreference}
|
||||
isLoading={isLoading}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid style={{ order: 4 }}>
|
||||
<DeviceOverview
|
||||
device={device}
|
||||
components={[...components.values()]}
|
||||
usage={getUsage()}
|
||||
loading={isLoading}
|
||||
/>
|
||||
</Grid>
|
||||
</Grid>
|
||||
{componentsTab()}
|
||||
</Box>
|
||||
);
|
||||
//}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue