added the alerts to the bin details part of the bin page, also fixed bug in the new alert dialog
This commit is contained in:
parent
f72aeb802f
commit
6ccc1ba263
6 changed files with 167 additions and 14 deletions
|
|
@ -230,7 +230,7 @@ export default function OrbitCameraControls(props: Props) {
|
||||||
window.removeEventListener("mouseup", handleMouseUp);
|
window.removeEventListener("mouseup", handleMouseUp);
|
||||||
canvas.removeEventListener("wheel", handleWheel);
|
canvas.removeEventListener("wheel", handleWheel);
|
||||||
};
|
};
|
||||||
}, [camera, gl, target, clampVerticalRotation, minPhi, maxPhi]);
|
}, [camera, gl, target, clampVerticalRotation, minPhi, maxPhi, initialRadius, maxRadius]);
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
@ -27,14 +27,14 @@ interface Props {
|
||||||
}
|
}
|
||||||
export default function BinSummary(props: Props){
|
export default function BinSummary(props: Props){
|
||||||
const {bin, devices, fans, heaters, permissions, componentDevices, componentMap, binPrefs, updateBinCallback} = props
|
const {bin, devices, fans, heaters, permissions, componentDevices, componentMap, binPrefs, updateBinCallback} = props
|
||||||
const [currentDevice, setCurrentDevice] = useState<Device | undefined>(undefined)
|
//const [currentDevice, setCurrentDevice] = useState<Device | undefined>(undefined)
|
||||||
const isMobile = useMobile()
|
const isMobile = useMobile()
|
||||||
|
|
||||||
useEffect(() => {
|
// useEffect(() => {
|
||||||
if(devices.length > 0){
|
// if(devices.length > 0){
|
||||||
setCurrentDevice(devices[0])
|
// setCurrentDevice(devices[0])
|
||||||
}
|
// }
|
||||||
},[devices])
|
// },[devices])
|
||||||
|
|
||||||
const activity = (reading: string) => {
|
const activity = (reading: string) => {
|
||||||
//if the device hasnt checked in in 6 hours = missing 12 hours = inactive within 6 hours = live
|
//if the device hasnt checked in in 6 hours = missing 12 hours = inactive within 6 hours = live
|
||||||
|
|
@ -173,7 +173,13 @@ export default function BinSummary(props: Props){
|
||||||
</Grid2>
|
</Grid2>
|
||||||
<Grid2 size={isMobile ? 12 : 5}>
|
<Grid2 size={isMobile ? 12 : 5}>
|
||||||
<Card raised sx={{height: 600}}>
|
<Card raised sx={{height: 600}}>
|
||||||
<BinDetails bin={bin} updateBinCallback={updateBinCallback}/>
|
<BinDetails
|
||||||
|
bin={bin}
|
||||||
|
updateBinCallback={updateBinCallback}
|
||||||
|
componentDevices={componentDevices}
|
||||||
|
devices={devices}
|
||||||
|
linkedComponents={componentMap}
|
||||||
|
permissions={permissions}/>
|
||||||
</Card>
|
</Card>
|
||||||
</Grid2>
|
</Grid2>
|
||||||
<Grid2 size={12}>
|
<Grid2 size={12}>
|
||||||
|
|
|
||||||
137
src/bin/binSummary/components/binAlerts.tsx
Normal file
137
src/bin/binSummary/components/binAlerts.tsx
Normal file
|
|
@ -0,0 +1,137 @@
|
||||||
|
import { LinearProgress } from "@mui/material";
|
||||||
|
import { Bin, Component, Device, Interaction } from "models";
|
||||||
|
import Alerts, { Alert } from "objects/objectInteractions/Alerts";
|
||||||
|
import NewObjectInteraction from "objects/objectInteractions/NewObjectInteraction";
|
||||||
|
import { extension } from "pbHelpers/ComponentType";
|
||||||
|
import { pond } from "protobuf-ts/pond";
|
||||||
|
import { useGlobalState, useInteractionsAPI } from "providers";
|
||||||
|
import React, { useCallback, useEffect, useState } from "react";
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
linkedComponents: Map<string, Component>; //the component key to the component object
|
||||||
|
componentDevices: Map<string, number>;
|
||||||
|
bin: Bin
|
||||||
|
devices: Device[];
|
||||||
|
permissions: pond.Permission[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function BinAlerts(props: Props){
|
||||||
|
const {linkedComponents, componentDevices, permissions} = props
|
||||||
|
const [{as}] = useGlobalState();
|
||||||
|
const interactionsAPI = useInteractionsAPI();
|
||||||
|
|
||||||
|
//list of alerts, each alert contains a list of interactions with matching conditions
|
||||||
|
const [alerts, setAlerts] = useState<Alert[]>([]);
|
||||||
|
const [loading, setLoading] = useState(false)
|
||||||
|
const [newAlert, setNewAlert] = useState(false)
|
||||||
|
|
||||||
|
const matchConditions = (interaction: Interaction, set: Alert) => {
|
||||||
|
//if the number of conditions are not the same they are different
|
||||||
|
if (interaction.settings.conditions.length !== set.conditions.length) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
//continue with the comparison
|
||||||
|
let matching = true;
|
||||||
|
interaction.settings.conditions.forEach((condition, i) => {
|
||||||
|
if (
|
||||||
|
condition.comparison !== set.conditions[i].comparison ||
|
||||||
|
condition.measurementType !== set.conditions[i].measurementType ||
|
||||||
|
condition.value !== set.conditions[i].value
|
||||||
|
) {
|
||||||
|
matching = false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return matching;
|
||||||
|
};
|
||||||
|
|
||||||
|
const load = useCallback(()=>{
|
||||||
|
//load the interactions for the linked components
|
||||||
|
setLoading(true)
|
||||||
|
const newInteractions: Interaction[] = [];
|
||||||
|
const interactionSourceMap = new Map<string, string>();
|
||||||
|
const sinkOptions: Component[] = [];
|
||||||
|
const run = async () => {
|
||||||
|
// Run all component API fetches in parallel
|
||||||
|
await Promise.all(
|
||||||
|
[...linkedComponents].map(async ([key, comp]) => {
|
||||||
|
const device = componentDevices.get(key);
|
||||||
|
|
||||||
|
if (device) {
|
||||||
|
const resp = await interactionsAPI.listInteractionsByComponent(
|
||||||
|
device,
|
||||||
|
comp.location(),
|
||||||
|
undefined,
|
||||||
|
as
|
||||||
|
);
|
||||||
|
|
||||||
|
resp.forEach(interaction => interactionSourceMap.set(interaction.key(), key));
|
||||||
|
newInteractions.push(...resp);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Collect controller components
|
||||||
|
if (extension(comp.type()).isController) {
|
||||||
|
sinkOptions.push(comp);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
run().then(() => {
|
||||||
|
newInteractions.forEach(interaction => {
|
||||||
|
//alert and control are not mutally exclusive, it is possible to be both if the control is sending notifications
|
||||||
|
if (interaction.isAlert()) {
|
||||||
|
// Find matching alert
|
||||||
|
let similarAlertIndex = alerts.findIndex(alert =>
|
||||||
|
matchConditions(interaction, alert)
|
||||||
|
);
|
||||||
|
|
||||||
|
const compKey = interactionSourceMap.get(interaction.key());
|
||||||
|
const component = compKey ? linkedComponents.get(compKey) : undefined;
|
||||||
|
|
||||||
|
if (component) {
|
||||||
|
if (similarAlertIndex === -1) {
|
||||||
|
alerts.push({
|
||||||
|
conditions: interaction.settings.conditions,
|
||||||
|
sourceComponents: [component],
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
alerts[similarAlertIndex].sourceComponents.push(component);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
});
|
||||||
|
setAlerts(alerts);
|
||||||
|
}).catch(err => {
|
||||||
|
console.error("Interaction fetch error:", err)
|
||||||
|
}).finally(() => {
|
||||||
|
setLoading(false)
|
||||||
|
})
|
||||||
|
},[linkedComponents, interactionsAPI, componentDevices, as])
|
||||||
|
|
||||||
|
useEffect(()=>{
|
||||||
|
load()
|
||||||
|
},[load])
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
return (
|
||||||
|
<React.Fragment>
|
||||||
|
<NewObjectInteraction
|
||||||
|
open={newAlert}
|
||||||
|
onClose={(refresh) => {
|
||||||
|
setNewAlert(false)
|
||||||
|
if(refresh){
|
||||||
|
load()
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
linkedComponents={linkedComponents}
|
||||||
|
componentDevices={componentDevices}/>
|
||||||
|
{loading ?
|
||||||
|
<LinearProgress />
|
||||||
|
:
|
||||||
|
<Alerts alerts={alerts} permissions={permissions} addNew={() => {setNewAlert(true)}}/>
|
||||||
|
}
|
||||||
|
</React.Fragment>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
@ -2,11 +2,18 @@ import { Box, Tab, Tabs } from "@mui/material";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import BinTableView from "./binTableView";
|
import BinTableView from "./binTableView";
|
||||||
import { Bin } from "models";
|
import { Bin, Component, Device } from "models";
|
||||||
|
import Alerts from "objects/objectInteractions/Alerts";
|
||||||
|
import BinAlerts from "./binAlerts";
|
||||||
|
import { pond } from "protobuf-ts/pond";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
bin: Bin
|
bin: Bin
|
||||||
updateBinCallback?: (bin: Bin) => void
|
updateBinCallback?: (bin: Bin) => void
|
||||||
|
linkedComponents: Map<string, Component>; //the component key to the component object
|
||||||
|
componentDevices: Map<string, number>;
|
||||||
|
devices: Device[];
|
||||||
|
permissions: pond.Permission[]
|
||||||
}
|
}
|
||||||
|
|
||||||
interface TabPanelProps {
|
interface TabPanelProps {
|
||||||
|
|
@ -30,7 +37,7 @@ function TabPanelMine(props: TabPanelProps) {
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function BinDetails (props: Props) {
|
export default function BinDetails (props: Props) {
|
||||||
const {bin, updateBinCallback} = props
|
const {bin, updateBinCallback, linkedComponents, componentDevices, devices, permissions} = props
|
||||||
const [currentTab, setCurrentTab] = useState(0)
|
const [currentTab, setCurrentTab] = useState(0)
|
||||||
|
|
||||||
//this is where we will have the tabs abd tab panels for the table view, alerts, interactions, and analysis
|
//this is where we will have the tabs abd tab panels for the table view, alerts, interactions, and analysis
|
||||||
|
|
@ -45,14 +52,14 @@ export default function BinDetails (props: Props) {
|
||||||
variant="fullWidth">
|
variant="fullWidth">
|
||||||
<Tab label={"Table View"} value={0} />
|
<Tab label={"Table View"} value={0} />
|
||||||
<Tab label={"Alerts"} value={1} />
|
<Tab label={"Alerts"} value={1} />
|
||||||
<Tab label={"Interactions"} value={2} />
|
<Tab label={"Controls"} value={2} />
|
||||||
<Tab label={"Analysis"} value={3} />
|
<Tab label={"Analysis"} value={3} />
|
||||||
</Tabs>
|
</Tabs>
|
||||||
<TabPanelMine value={currentTab} index={0}>
|
<TabPanelMine value={currentTab} index={0}>
|
||||||
<BinTableView bin={bin} updateBinCallback={updateBinCallback}/>
|
<BinTableView bin={bin} updateBinCallback={updateBinCallback}/>
|
||||||
</TabPanelMine>
|
</TabPanelMine>
|
||||||
<TabPanelMine value={currentTab} index={1}>
|
<TabPanelMine value={currentTab} index={1}>
|
||||||
|
<BinAlerts bin={bin} componentDevices={componentDevices} devices={devices} linkedComponents={linkedComponents} permissions={permissions}/>
|
||||||
</TabPanelMine>
|
</TabPanelMine>
|
||||||
<TabPanelMine value={currentTab} index={2}>
|
<TabPanelMine value={currentTab} index={2}>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -179,7 +179,6 @@ export default function NewObjectInteraction(props: Props){
|
||||||
setNewAlertComponentType(quack.ComponentType.COMPONENT_TYPE_INVALID);
|
setNewAlertComponentType(quack.ComponentType.COMPONENT_TYPE_INVALID);
|
||||||
setConditions([]);
|
setConditions([]);
|
||||||
setSelectedAlertComponents([]);
|
setSelectedAlertComponents([]);
|
||||||
setValidComponents([])
|
|
||||||
setValStrings(["0", "0"]);
|
setValStrings(["0", "0"]);
|
||||||
setNumConditions(0)
|
setNumConditions(0)
|
||||||
setNodeOptions([]);
|
setNodeOptions([]);
|
||||||
|
|
@ -190,7 +189,6 @@ export default function NewObjectInteraction(props: Props){
|
||||||
setResultType(quack.InteractionResultType.INTERACTION_RESULT_TYPE_REPORT)
|
setResultType(quack.InteractionResultType.INTERACTION_RESULT_TYPE_REPORT)
|
||||||
setSinkMotor(false)
|
setSinkMotor(false)
|
||||||
setSelectedSink("")//the key of the component to be the sink
|
setSelectedSink("")//the key of the component to be the sink
|
||||||
setSinkOptions([])
|
|
||||||
setResultMode(0)
|
setResultMode(0)
|
||||||
setResultValue("")
|
setResultValue("")
|
||||||
setDutyCycleEnabled(false)
|
setDutyCycleEnabled(false)
|
||||||
|
|
@ -215,12 +213,15 @@ export default function NewObjectInteraction(props: Props){
|
||||||
|
|
||||||
//display the components that match the type that was selected for the new alert
|
//display the components that match the type that was selected for the new alert
|
||||||
const listMatchingComponents = () => {
|
const listMatchingComponents = () => {
|
||||||
|
console.log("listing")
|
||||||
let matching: Component[] = [];
|
let matching: Component[] = [];
|
||||||
|
console.log(validComponents)
|
||||||
validComponents.forEach(comp => {
|
validComponents.forEach(comp => {
|
||||||
if (comp.type() === newAlertComponentType) {
|
if (comp.type() === newAlertComponentType) {
|
||||||
matching.push(comp);
|
matching.push(comp);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
console.log(matching)
|
||||||
return (
|
return (
|
||||||
<List>
|
<List>
|
||||||
{matching.map(comp => (
|
{matching.map(comp => (
|
||||||
|
|
@ -1018,6 +1019,7 @@ export default function NewObjectInteraction(props: Props){
|
||||||
displayEmpty
|
displayEmpty
|
||||||
value={newAlertComponentType}
|
value={newAlertComponentType}
|
||||||
onChange={e => {
|
onChange={e => {
|
||||||
|
console.log("type change")
|
||||||
setNewAlertComponentType(e.target.value as quack.ComponentType);
|
setNewAlertComponentType(e.target.value as quack.ComponentType);
|
||||||
setSelectedAlertComponents([]);
|
setSelectedAlertComponents([]);
|
||||||
setConditions(initialConditions(e.target.value as quack.ComponentType));
|
setConditions(initialConditions(e.target.value as quack.ComponentType));
|
||||||
|
|
|
||||||
|
|
@ -126,6 +126,7 @@ export default function BinV2(){
|
||||||
attachedDevIds.push(resp.data.componentDevices[k]);
|
attachedDevIds.push(resp.data.componentDevices[k]);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
setComponentDevices(newComponentDevices);
|
setComponentDevices(newComponentDevices);
|
||||||
|
|
||||||
let newInteractionDevices = new Map<string, number>();
|
let newInteractionDevices = new Map<string, number>();
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue