merged pending changes branch
This commit is contained in:
commit
e1a4443a38
3 changed files with 82 additions and 9 deletions
|
|
@ -4,6 +4,7 @@ import {
|
|||
CardActions,
|
||||
CardContent,
|
||||
CardHeader,
|
||||
CircularProgress,
|
||||
darken,
|
||||
Grid2 as Grid,
|
||||
IconButton,
|
||||
|
|
@ -14,7 +15,7 @@ import {
|
|||
Tooltip,
|
||||
Typography
|
||||
} from "@mui/material";
|
||||
import { Settings } from "@mui/icons-material";
|
||||
import { CheckCircleOutline, Settings } from "@mui/icons-material";
|
||||
import { useInteractionsAPI, usePrevious, useSnackbar } from "hooks";
|
||||
import { cloneDeep } from "lodash";
|
||||
import { Component, Device, Interaction } from "models";
|
||||
|
|
@ -80,6 +81,7 @@ export default function InteractionsOverview(props: Props) {
|
|||
const [interactions, setInteractions] = useState<Interaction[]>(props.interactions);
|
||||
const [dirtyInteractions, setDirtyInteractions] = useState<Map<number, boolean>>(new Map());
|
||||
const [mappedComponents, setMappedComponents] = useState<Map<string, Component>>(new Map());
|
||||
const [acceptedInteractions, setAcceptedInteractions] = useState<Set<string>>(new Set());
|
||||
const [renderToggle, setRenderToggle] = useState(false);
|
||||
const [selectedInteraction, setSelectedInteraction] = useState<Interaction | undefined>(
|
||||
undefined
|
||||
|
|
@ -95,6 +97,20 @@ export default function InteractionsOverview(props: Props) {
|
|||
}
|
||||
|
||||
if (props.interactions !== prevInteractions) {
|
||||
if (prevInteractions) {
|
||||
setAcceptedInteractions((prevAccepted) => {
|
||||
const updatedAccepted = new Set(prevAccepted);
|
||||
props.interactions.forEach((interaction: Interaction) => {
|
||||
const previous = prevInteractions.find(prevInteraction => prevInteraction.key() === interaction.key());
|
||||
if (!interaction.status.synced) {
|
||||
updatedAccepted.delete(interaction.key());
|
||||
} else if (previous && !previous.status.synced) {
|
||||
updatedAccepted.add(interaction.key());
|
||||
}
|
||||
});
|
||||
return updatedAccepted;
|
||||
});
|
||||
}
|
||||
setInteractions(cloneDeep(props.interactions));
|
||||
}
|
||||
}, [components, prevComponents, props.interactions, prevInteractions]);
|
||||
|
|
@ -226,10 +242,16 @@ export default function InteractionsOverview(props: Props) {
|
|||
let key = interaction.key();
|
||||
let source = mappedComponents.get(componentIDToString(interaction.settings.source));
|
||||
let sink = mappedComponents.get(componentIDToString(interaction.settings.sink));
|
||||
const isPending = !interaction.status.synced && Boolean(interaction.status.lastUpdate);
|
||||
const isAccepted = interaction.status.synced && acceptedInteractions.has(interaction.key());
|
||||
let statusText =
|
||||
!interaction.status.synced && interaction.status.lastUpdate
|
||||
!interaction.status.synced
|
||||
? interaction.status.lastUpdate
|
||||
? "Pending " +
|
||||
moment(interaction.status.lastUpdate && interaction.status.lastUpdate).fromNow()
|
||||
: ""
|
||||
: acceptedInteractions.has(interaction.key())
|
||||
? "Pending change accepted"
|
||||
: "";
|
||||
let schedule = pond.InteractionSchedule.create(
|
||||
interaction.settings.schedule !== null ? interaction.settings.schedule : undefined
|
||||
|
|
@ -272,7 +294,20 @@ export default function InteractionsOverview(props: Props) {
|
|||
className={classes.header}
|
||||
titleTypographyProps={{ variant: "subtitle2" }}
|
||||
title={interactionResultText(interaction, sink)}
|
||||
subheader={statusText}
|
||||
subheader={
|
||||
statusText ? (
|
||||
<Grid container spacing={0.5} alignItems="center">
|
||||
{isPending && <CircularProgress size={10} thickness={5} color="inherit" />}
|
||||
{isAccepted && (
|
||||
<CheckCircleOutline
|
||||
sx={{ fontSize: 13, color: "var(--status-ok)" }}
|
||||
/>
|
||||
)}
|
||||
<Grid>
|
||||
{statusText}
|
||||
</Grid>
|
||||
</Grid>
|
||||
) : ""}
|
||||
subheaderTypographyProps={{ variant: "caption" }}
|
||||
action={action}
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -282,6 +282,45 @@ export default function DevicePage() {
|
|||
enabled: !!deviceID,
|
||||
});
|
||||
|
||||
// --- Real-time interaction updates ---
|
||||
// Streams interaction changes for this device, including status changes
|
||||
// when the device accepts pending interaction updates.
|
||||
useWebSocket<{ key: string; interaction: Interaction } | null>({
|
||||
path: `/v1/live/devices/${deviceID}/interactions`,
|
||||
parse: (e) => {
|
||||
try {
|
||||
const raw = JSON.parse(e.data);
|
||||
const interaction = Interaction.any(raw);
|
||||
if (!interaction?.settings) {
|
||||
console.warn("[ws] received interaction without settings:", raw);
|
||||
return null;
|
||||
}
|
||||
return { key: interaction.key(), interaction };
|
||||
} catch (err) {
|
||||
console.warn("[ws] failed to parse interaction:", err);
|
||||
return null;
|
||||
}
|
||||
},
|
||||
onMessage: (data) => {
|
||||
if (!data) return;
|
||||
const { key, interaction } = data;
|
||||
setInteractions((prev) => {
|
||||
const index = prev.findIndex((existing) => existing.key() === key);
|
||||
if (index < 0) {
|
||||
return [...prev, interaction];
|
||||
}
|
||||
const updated = [...prev];
|
||||
updated[index] = interaction;
|
||||
return updated;
|
||||
});
|
||||
},
|
||||
keys: liveContextKeys,
|
||||
types: liveContextTypes,
|
||||
as: liveAs,
|
||||
token,
|
||||
enabled: !!deviceID,
|
||||
});
|
||||
|
||||
const loadPortScan = () => {
|
||||
deviceAPI.listFoundComponents(parseInt(deviceID))
|
||||
.then(resp => {
|
||||
|
|
@ -395,7 +434,7 @@ export default function DevicePage() {
|
|||
interactions={filteredInteractions}
|
||||
permissions={permissions}
|
||||
deviceComponentPreferences={prefsMap.get(c.key())}
|
||||
key={i}
|
||||
key={c.key()}
|
||||
refreshCallback={(updatedComponent?: Component) =>
|
||||
updatedComponent ? handleComponentChanged(updatedComponent) : loadDevice()
|
||||
}
|
||||
|
|
@ -412,7 +451,7 @@ export default function DevicePage() {
|
|||
interactions={filteredInteractions}
|
||||
permissions={permissions}
|
||||
deviceComponentPreferences={prefsMap.get(c.key())}
|
||||
key={i}
|
||||
key={c.key()}
|
||||
refreshCallback={(updatedComponent?: Component) =>
|
||||
updatedComponent ? handleComponentChanged(updatedComponent) : loadDevice()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -174,10 +174,9 @@ export class MeasurementDescriber {
|
|||
this.details.path = "power.inputVoltageTimes10";
|
||||
this.details.decimals = 1;
|
||||
}
|
||||
if (componentType === quack.ComponentType.COMPONENT_TYPE_DRAGER_GAS_DONGLE) {
|
||||
if (componentType === quack.ComponentType.COMPONENT_TYPE_DRAGER_GAS_DONGLE || componentType === quack.ComponentType.COMPONENT_TYPE_ANALOG_PRESSURE) {
|
||||
this.details.unit = "mV";
|
||||
this.details.graph = GraphType.MULTILINE;
|
||||
this.details.unit = "mV";
|
||||
this.details.decimals = 0
|
||||
// this.details.nodeDetails = {
|
||||
// colours: ["white", "black", "red", "blue"],
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue