Merge branch 'pending_changes' into staging_environment
This commit is contained in:
commit
cca981308d
7 changed files with 168 additions and 14 deletions
|
|
@ -213,7 +213,8 @@ export default function ComponentSettings(props: Props) {
|
||||||
}, [props.component, componentTypeOptions]);
|
}, [props.component, componentTypeOptions]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (props.component !== prevComponent || (!isDialogOpen && prevIsDialogOpen)) {
|
if (isDialogOpen && prevIsDialogOpen) return;
|
||||||
|
if (props.component !== prevComponent || isDialogOpen !== prevIsDialogOpen) {
|
||||||
init();
|
init();
|
||||||
}
|
}
|
||||||
}, [props.component, prevComponent, init, isDialogOpen, prevIsDialogOpen]);
|
}, [props.component, prevComponent, init, isDialogOpen, prevIsDialogOpen]);
|
||||||
|
|
|
||||||
|
|
@ -95,6 +95,7 @@ export default function DeviceSettings(props: Props) {
|
||||||
const deviceAPI = useDeviceAPI();
|
const deviceAPI = useDeviceAPI();
|
||||||
const { device, isDialogOpen, closeDialogCallback, canEdit, refreshCallback, components } = props;
|
const { device, isDialogOpen, closeDialogCallback, canEdit, refreshCallback, components } = props;
|
||||||
const prevDevice = usePrevious(device);
|
const prevDevice = usePrevious(device);
|
||||||
|
const prevIsDialogOpen = usePrevious(isDialogOpen);
|
||||||
const [deviceForm, setDeviceForm] = useState<Device>(deviceFromForm(Device.clone(device)));
|
const [deviceForm, setDeviceForm] = useState<Device>(deviceFromForm(Device.clone(device)));
|
||||||
const [isRemoveDeviceDialogOpen, setIsRemoveDeviceDialogOpen] = useState<boolean>(false);
|
const [isRemoveDeviceDialogOpen, setIsRemoveDeviceDialogOpen] = useState<boolean>(false);
|
||||||
const [sleeps, setSleeps] = useState<boolean>(device.settings.sleepDurationS > 0);
|
const [sleeps, setSleeps] = useState<boolean>(device.settings.sleepDurationS > 0);
|
||||||
|
|
@ -110,7 +111,8 @@ export default function DeviceSettings(props: Props) {
|
||||||
const [existingMutation, setExistingMutation] = useState<pond.LinearMutation>();
|
const [existingMutation, setExistingMutation] = useState<pond.LinearMutation>();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (prevDevice !== device) {
|
if (isDialogOpen && prevIsDialogOpen) return;
|
||||||
|
if (prevDevice !== device || isDialogOpen !== prevIsDialogOpen) {
|
||||||
setDeviceForm(deviceFromForm(Device.clone(props.device)));
|
setDeviceForm(deviceFromForm(Device.clone(props.device)));
|
||||||
if (device.settings.extensionComponents[0]) {
|
if (device.settings.extensionComponents[0]) {
|
||||||
setCompExtOne(device.settings.extensionComponents[0]);
|
setCompExtOne(device.settings.extensionComponents[0]);
|
||||||
|
|
@ -130,7 +132,7 @@ export default function DeviceSettings(props: Props) {
|
||||||
setComponentsByDevice(cbd);
|
setComponentsByDevice(cbd);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, [device, prevDevice, props.device, components]);
|
}, [device, prevDevice, props.device, components, isDialogOpen, prevIsDialogOpen]);
|
||||||
|
|
||||||
const close = () => {
|
const close = () => {
|
||||||
closeDialogCallback();
|
closeDialogCallback();
|
||||||
|
|
|
||||||
|
|
@ -22,6 +22,14 @@ class VersionChip extends React.Component<Props, State> {
|
||||||
variant="outlined"
|
variant="outlined"
|
||||||
label={firmwareVersionHelper.description}
|
label={firmwareVersionHelper.description}
|
||||||
icon={firmwareVersionHelper.icon}
|
icon={firmwareVersionHelper.icon}
|
||||||
|
sx={
|
||||||
|
firmwareVersionHelper.colour
|
||||||
|
? {
|
||||||
|
borderColor: firmwareVersionHelper.colour,
|
||||||
|
color: firmwareVersionHelper.colour
|
||||||
|
}
|
||||||
|
: undefined
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -133,6 +133,7 @@ export default function InteractionSettings(props: Props) {
|
||||||
const prevInitialInteraction = usePrevious(initialInteraction);
|
const prevInitialInteraction = usePrevious(initialInteraction);
|
||||||
const prevComponents = usePrevious(components);
|
const prevComponents = usePrevious(components);
|
||||||
const prevInitialComponent = usePrevious(initialComponent);
|
const prevInitialComponent = usePrevious(initialComponent);
|
||||||
|
const prevIsDialogOpen = usePrevious(isDialogOpen);
|
||||||
const classes = useStyles();
|
const classes = useStyles();
|
||||||
const [{ user, as }] = useGlobalState();
|
const [{ user, as }] = useGlobalState();
|
||||||
const interactionsAPI = useInteractionsAPI();
|
const interactionsAPI = useInteractionsAPI();
|
||||||
|
|
@ -232,10 +233,12 @@ export default function InteractionSettings(props: Props) {
|
||||||
if (user && user.settings.timezone) {
|
if (user && user.settings.timezone) {
|
||||||
setTimezone(user.settings.timezone);
|
setTimezone(user.settings.timezone);
|
||||||
}
|
}
|
||||||
|
if (isDialogOpen && prevIsDialogOpen) return;
|
||||||
if (
|
if (
|
||||||
prevInitialInteraction !== initialInteraction ||
|
prevInitialInteraction !== initialInteraction ||
|
||||||
(prevComponents && prevComponents.length !== components.length) ||
|
(prevComponents && prevComponents.length !== components.length) ||
|
||||||
getComponentIDString(prevInitialComponent) !== getComponentIDString(initialComponent)
|
getComponentIDString(prevInitialComponent) !== getComponentIDString(initialComponent) ||
|
||||||
|
isDialogOpen !== prevIsDialogOpen
|
||||||
) {
|
) {
|
||||||
setDefaultState();
|
setDefaultState();
|
||||||
}
|
}
|
||||||
|
|
@ -247,7 +250,9 @@ export default function InteractionSettings(props: Props) {
|
||||||
prevInitialComponent,
|
prevInitialComponent,
|
||||||
prevInitialInteraction,
|
prevInitialInteraction,
|
||||||
setDefaultState,
|
setDefaultState,
|
||||||
user
|
user,
|
||||||
|
isDialogOpen,
|
||||||
|
prevIsDialogOpen
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const getAvailableSinks = () => {
|
const getAvailableSinks = () => {
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ import {
|
||||||
CardActions,
|
CardActions,
|
||||||
CardContent,
|
CardContent,
|
||||||
CardHeader,
|
CardHeader,
|
||||||
|
CircularProgress,
|
||||||
darken,
|
darken,
|
||||||
Grid2 as Grid,
|
Grid2 as Grid,
|
||||||
IconButton,
|
IconButton,
|
||||||
|
|
@ -14,7 +15,7 @@ import {
|
||||||
Tooltip,
|
Tooltip,
|
||||||
Typography
|
Typography
|
||||||
} from "@mui/material";
|
} from "@mui/material";
|
||||||
import { Settings } from "@mui/icons-material";
|
import { CheckCircleOutline, Settings } from "@mui/icons-material";
|
||||||
import { useInteractionsAPI, usePrevious, useSnackbar } from "hooks";
|
import { useInteractionsAPI, usePrevious, useSnackbar } from "hooks";
|
||||||
import { cloneDeep } from "lodash";
|
import { cloneDeep } from "lodash";
|
||||||
import { Component, Device, Interaction } from "models";
|
import { Component, Device, Interaction } from "models";
|
||||||
|
|
@ -80,11 +81,21 @@ export default function InteractionsOverview(props: Props) {
|
||||||
const [interactions, setInteractions] = useState<Interaction[]>(props.interactions);
|
const [interactions, setInteractions] = useState<Interaction[]>(props.interactions);
|
||||||
const [dirtyInteractions, setDirtyInteractions] = useState<Map<number, boolean>>(new Map());
|
const [dirtyInteractions, setDirtyInteractions] = useState<Map<number, boolean>>(new Map());
|
||||||
const [mappedComponents, setMappedComponents] = useState<Map<string, Component>>(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 [renderToggle, setRenderToggle] = useState(false);
|
||||||
const [selectedInteraction, setSelectedInteraction] = useState<Interaction | undefined>(
|
const [selectedInteraction, setSelectedInteraction] = useState<Interaction | undefined>(
|
||||||
undefined
|
undefined
|
||||||
);
|
);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
console.log("[interactions:mount] initial interactions:", props.interactions.map(i => ({
|
||||||
|
key: i.key(),
|
||||||
|
synced: i.status.synced,
|
||||||
|
lastUpdate: i.status.lastUpdate,
|
||||||
|
lastSynced: i.status.lastSynced,
|
||||||
|
})));
|
||||||
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (components !== prevComponents) {
|
if (components !== prevComponents) {
|
||||||
let initMappedComponents: Map<string, Component> = new Map();
|
let initMappedComponents: Map<string, Component> = new Map();
|
||||||
|
|
@ -95,6 +106,31 @@ export default function InteractionsOverview(props: Props) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (props.interactions !== prevInteractions) {
|
if (props.interactions !== prevInteractions) {
|
||||||
|
console.log("[interactions:effect] props.interactions changed, prevInteractions was", prevInteractions ? "defined" : "undefined");
|
||||||
|
props.interactions.forEach((interaction: Interaction) => {
|
||||||
|
const key = interaction.key();
|
||||||
|
const prevInteraction = prevInteractions?.find(p => p.key() === key);
|
||||||
|
console.log("[interactions:effect]", key, {
|
||||||
|
synced: interaction.status.synced,
|
||||||
|
lastUpdate: interaction.status.lastUpdate,
|
||||||
|
prevSynced: prevInteraction?.status.synced,
|
||||||
|
prevLastUpdate: prevInteraction?.status.lastUpdate,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
setAcceptedInteractions(prev => {
|
||||||
|
const updated = new Set(prev);
|
||||||
|
props.interactions.forEach((interaction: Interaction) => {
|
||||||
|
const key = interaction.key();
|
||||||
|
const prevInteraction = prevInteractions?.find(p => p.key() === key);
|
||||||
|
if (!interaction.status.synced) {
|
||||||
|
updated.delete(key);
|
||||||
|
} else if (prevInteraction && !prevInteraction.status.synced) {
|
||||||
|
console.log("[interactions:effect] ACCEPTED", key);
|
||||||
|
updated.add(key);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return updated;
|
||||||
|
});
|
||||||
setInteractions(cloneDeep(props.interactions));
|
setInteractions(cloneDeep(props.interactions));
|
||||||
}
|
}
|
||||||
}, [components, prevComponents, props.interactions, prevInteractions]);
|
}, [components, prevComponents, props.interactions, prevInteractions]);
|
||||||
|
|
@ -204,11 +240,28 @@ export default function InteractionsOverview(props: Props) {
|
||||||
interactionsAPI
|
interactionsAPI
|
||||||
.updateInteraction(Number(deviceID), settings, as)
|
.updateInteraction(Number(deviceID), settings, as)
|
||||||
.then((_response: any) => {
|
.then((_response: any) => {
|
||||||
|
console.log("[interactions:submit] API success, setting synced=false for index", index);
|
||||||
let updatedDirtyInteractions = cloneDeep(dirtyInteractions);
|
let updatedDirtyInteractions = cloneDeep(dirtyInteractions);
|
||||||
updatedDirtyInteractions.set(index, false);
|
updatedDirtyInteractions.set(index, false);
|
||||||
setDirtyInteractions(updatedDirtyInteractions);
|
setDirtyInteractions(updatedDirtyInteractions);
|
||||||
|
setInteractions(prev => {
|
||||||
|
const updated = cloneDeep(prev);
|
||||||
|
if (updated[index]) {
|
||||||
|
updated[index].status.synced = false;
|
||||||
|
updated[index].status.lastUpdate = moment().toISOString();
|
||||||
|
console.log("[interactions:submit] local state updated for", updated[index].key());
|
||||||
|
}
|
||||||
|
return updated;
|
||||||
|
});
|
||||||
|
const interactionKey = settings.key ?? "";
|
||||||
|
if (interactionKey) {
|
||||||
|
setAcceptedInteractions(prev => {
|
||||||
|
const updated = new Set(prev);
|
||||||
|
updated.delete(interactionKey);
|
||||||
|
return updated;
|
||||||
|
});
|
||||||
|
}
|
||||||
success("Successfully updated the interaction for " + component.name());
|
success("Successfully updated the interaction for " + component.name());
|
||||||
refreshCallback();
|
|
||||||
})
|
})
|
||||||
.catch((_err: any) => {
|
.catch((_err: any) => {
|
||||||
error("Error occurred while updating the interaction for " + component.name());
|
error("Error occurred while updating the interaction for " + component.name());
|
||||||
|
|
@ -226,10 +279,12 @@ export default function InteractionsOverview(props: Props) {
|
||||||
let key = interaction.key();
|
let key = interaction.key();
|
||||||
let source = mappedComponents.get(componentIDToString(interaction.settings.source));
|
let source = mappedComponents.get(componentIDToString(interaction.settings.source));
|
||||||
let sink = mappedComponents.get(componentIDToString(interaction.settings.sink));
|
let sink = mappedComponents.get(componentIDToString(interaction.settings.sink));
|
||||||
let statusText =
|
const isAccepted = acceptedInteractions.has(interaction.key());
|
||||||
!interaction.status.synced && interaction.status.lastUpdate
|
const isPending = !interaction.status.synced;
|
||||||
? "Pending " +
|
let statusText = isAccepted
|
||||||
moment(interaction.status.lastUpdate && interaction.status.lastUpdate).fromNow()
|
? "Pending change accepted"
|
||||||
|
: isPending
|
||||||
|
? "Pending " + moment(interaction.status.lastUpdate).fromNow()
|
||||||
: "";
|
: "";
|
||||||
let schedule = pond.InteractionSchedule.create(
|
let schedule = pond.InteractionSchedule.create(
|
||||||
interaction.settings.schedule !== null ? interaction.settings.schedule : undefined
|
interaction.settings.schedule !== null ? interaction.settings.schedule : undefined
|
||||||
|
|
@ -272,7 +327,20 @@ export default function InteractionsOverview(props: Props) {
|
||||||
className={classes.header}
|
className={classes.header}
|
||||||
titleTypographyProps={{ variant: "subtitle2" }}
|
titleTypographyProps={{ variant: "subtitle2" }}
|
||||||
title={interactionResultText(interaction, sink)}
|
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" }}
|
subheaderTypographyProps={{ variant: "caption" }}
|
||||||
action={action}
|
action={action}
|
||||||
/>
|
/>
|
||||||
|
|
|
||||||
|
|
@ -282,6 +282,64 @@ export default function DevicePage() {
|
||||||
enabled: !!deviceID,
|
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;
|
||||||
|
console.log("[ws:interaction] received", key, {
|
||||||
|
synced: interaction.status.synced,
|
||||||
|
lastUpdate: interaction.status.lastUpdate,
|
||||||
|
lastSynced: interaction.status.lastSynced,
|
||||||
|
});
|
||||||
|
setInteractions((prev) => {
|
||||||
|
const index = prev.findIndex((existing) => existing.key() === key);
|
||||||
|
if (index < 0) {
|
||||||
|
return [...prev, interaction];
|
||||||
|
}
|
||||||
|
const existing = prev[index];
|
||||||
|
const existingLastUpdate = getTimestampMillis(existing.status.lastUpdate);
|
||||||
|
const incomingLastUpdate = getTimestampMillis(interaction.status.lastUpdate);
|
||||||
|
const incomingLastSynced = getTimestampMillis(interaction.status.lastSynced);
|
||||||
|
if (existingLastUpdate !== undefined && incomingLastUpdate !== undefined &&
|
||||||
|
existingLastUpdate > incomingLastUpdate) {
|
||||||
|
return prev;
|
||||||
|
}
|
||||||
|
if (!existing.status.synced && interaction.status.synced &&
|
||||||
|
incomingLastUpdate !== undefined &&
|
||||||
|
existingLastUpdate === incomingLastUpdate &&
|
||||||
|
(incomingLastSynced === undefined || incomingLastSynced < incomingLastUpdate)) {
|
||||||
|
return prev;
|
||||||
|
}
|
||||||
|
const updated = [...prev];
|
||||||
|
updated[index] = interaction;
|
||||||
|
return updated;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
keys: liveContextKeys,
|
||||||
|
types: liveContextTypes,
|
||||||
|
as: liveAs,
|
||||||
|
token,
|
||||||
|
enabled: !!deviceID,
|
||||||
|
});
|
||||||
|
|
||||||
const loadPortScan = () => {
|
const loadPortScan = () => {
|
||||||
deviceAPI.listFoundComponents(parseInt(deviceID))
|
deviceAPI.listFoundComponents(parseInt(deviceID))
|
||||||
.then(resp => {
|
.then(resp => {
|
||||||
|
|
@ -395,7 +453,7 @@ export default function DevicePage() {
|
||||||
interactions={filteredInteractions}
|
interactions={filteredInteractions}
|
||||||
permissions={permissions}
|
permissions={permissions}
|
||||||
deviceComponentPreferences={prefsMap.get(c.key())}
|
deviceComponentPreferences={prefsMap.get(c.key())}
|
||||||
key={i}
|
key={c.key()}
|
||||||
refreshCallback={(updatedComponent?: Component) =>
|
refreshCallback={(updatedComponent?: Component) =>
|
||||||
updatedComponent ? handleComponentChanged(updatedComponent) : loadDevice()
|
updatedComponent ? handleComponentChanged(updatedComponent) : loadDevice()
|
||||||
}
|
}
|
||||||
|
|
@ -412,7 +470,7 @@ export default function DevicePage() {
|
||||||
interactions={filteredInteractions}
|
interactions={filteredInteractions}
|
||||||
permissions={permissions}
|
permissions={permissions}
|
||||||
deviceComponentPreferences={prefsMap.get(c.key())}
|
deviceComponentPreferences={prefsMap.get(c.key())}
|
||||||
key={i}
|
key={c.key()}
|
||||||
refreshCallback={(updatedComponent?: Component) =>
|
refreshCallback={(updatedComponent?: Component) =>
|
||||||
updatedComponent ? handleComponentChanged(updatedComponent) : loadDevice()
|
updatedComponent ? handleComponentChanged(updatedComponent) : loadDevice()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@ export interface FirmwareVersionHelper {
|
||||||
|
|
||||||
const oldFirmwareColour = "var(--status-warning)";
|
const oldFirmwareColour = "var(--status-warning)";
|
||||||
const currentFirmwareColour = "var(--status-ok)";
|
const currentFirmwareColour = "var(--status-ok)";
|
||||||
|
const unknownFirmwareColour = "var(--status-warning)";
|
||||||
|
|
||||||
export function getFirmwareVersionHelper(
|
export function getFirmwareVersionHelper(
|
||||||
version: string,
|
version: string,
|
||||||
|
|
@ -17,24 +18,35 @@ export function getFirmwareVersionHelper(
|
||||||
const firmwareIcon = <FirmwareIcon />;
|
const firmwareIcon = <FirmwareIcon />;
|
||||||
const oldFirmwareIcon = <FirmwareIcon style={{ color: oldFirmwareColour }} />;
|
const oldFirmwareIcon = <FirmwareIcon style={{ color: oldFirmwareColour }} />;
|
||||||
const currentFirmwareIcon = <FirmwareIcon style={{ color: currentFirmwareColour }} />;
|
const currentFirmwareIcon = <FirmwareIcon style={{ color: currentFirmwareColour }} />;
|
||||||
|
const unknownFirmwareIcon = <FirmwareIcon style={{ color: unknownFirmwareColour }} />;
|
||||||
let helper = {} as FirmwareVersionHelper;
|
let helper = {} as FirmwareVersionHelper;
|
||||||
if (!version || version === "") {
|
if (!version || version === "") {
|
||||||
helper.description = "Unknown version";
|
helper.description = "Unknown version";
|
||||||
helper.icon = oldFirmwareIcon;
|
helper.icon = oldFirmwareIcon;
|
||||||
helper.tooltip = "We don't know what version your device is, it may behave unexpectedly";
|
helper.tooltip = "We don't know what version your device is, it may behave unexpectedly";
|
||||||
|
helper.colour = oldFirmwareColour;
|
||||||
|
} else if (version.startsWith("!")) {
|
||||||
|
const cleanVersion = version.substring(1);
|
||||||
|
helper.description = cleanVersion;
|
||||||
|
helper.icon = unknownFirmwareIcon;
|
||||||
|
helper.tooltip = "Firmware " + cleanVersion + " was reported by the device but is missing from the firmware list";
|
||||||
|
helper.colour = unknownFirmwareColour;
|
||||||
} else if (available !== "" && version !== available) {
|
} else if (available !== "" && version !== available) {
|
||||||
helper.description = version;
|
helper.description = version;
|
||||||
helper.icon = oldFirmwareIcon;
|
helper.icon = oldFirmwareIcon;
|
||||||
helper.tooltip =
|
helper.tooltip =
|
||||||
"A firmware upgrade is available, some features may be disabled for out-of-date devices";
|
"A firmware upgrade is available, some features may be disabled for out-of-date devices";
|
||||||
|
helper.colour = oldFirmwareColour;
|
||||||
} else if (available === "") {
|
} else if (available === "") {
|
||||||
helper.description = version;
|
helper.description = version;
|
||||||
helper.icon = firmwareIcon;
|
helper.icon = firmwareIcon;
|
||||||
helper.tooltip = "Running version " + version;
|
helper.tooltip = "Running version " + version;
|
||||||
|
helper.colour = "";
|
||||||
} else {
|
} else {
|
||||||
helper.description = version;
|
helper.description = version;
|
||||||
helper.icon = currentFirmwareIcon;
|
helper.icon = currentFirmwareIcon;
|
||||||
helper.tooltip = "Your device is running the latest firmware";
|
helper.tooltip = "Your device is running the latest firmware";
|
||||||
|
helper.colour = currentFirmwareColour;
|
||||||
}
|
}
|
||||||
return helper;
|
return helper;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue