diff --git a/app/components/Block.test.tsx b/app/components/Block.test.tsx
index 184896e..5dd11b4 100644
--- a/app/components/Block.test.tsx
+++ b/app/components/Block.test.tsx
@@ -1,4 +1,4 @@
-import { render } from "@testing-library/react";
+import { fireEvent, render } from "@testing-library/react";
import Block from "@/app/components/Block";
import { IfcBlock } from "@/app/types";
@@ -20,12 +20,7 @@ it("renders topbar unchanged", () => {
};
const instName = "Instrument";
const { container } = render(
- ,
+ ,
{
container: tableBody,
},
@@ -36,12 +31,7 @@ it("renders topbar unchanged", () => {
it("renders nothing if pv is hidden", () => {
const aBlock: IfcBlock = { pvaddress: "SOME:PV", visible: false };
const { container } = render(
- ,
+ ,
{
container: tableBody,
},
@@ -56,12 +46,7 @@ it("renders block with correct name", () => {
human_readable_name: "MyBlock",
};
const { container } = render(
- ,
+ ,
{
container: tableBody,
},
@@ -80,12 +65,7 @@ it("renders block with run control that is in range as a tick", () => {
runcontrol_enabled: true,
};
const { container } = render(
- ,
+ ,
{
container: tableBody,
},
@@ -105,12 +85,7 @@ it("renders block with run control that is not in range as a cross", () => {
runcontrol_enabled: true,
};
const { container } = render(
- ,
+ ,
{
container: tableBody,
},
@@ -130,12 +105,7 @@ it("renders block without run control without tick or cross", () => {
runcontrol_enabled: false,
};
const { container } = render(
- ,
+ ,
{
container: tableBody,
},
@@ -159,19 +129,49 @@ it("renders block with SP and shows SP value", () => {
value: expectedValue,
};
const { container } = render(
- ,
+ ,
{
container: tableBody,
},
);
+ const valueSpan = container.querySelector(
+ `#${aBlock.human_readable_name}_VALUE`,
+ )!;
+
+ fireEvent.click(valueSpan);
+ expect(valueSpan.innerHTML).toContain(`${expectedValue}`);
expect(
- container.querySelector(`#${aBlock.human_readable_name}_VALUE`)!.innerHTML,
- ).toContain(`${expectedValue}
(SP: ${expectedSpValue})`);
+ container.querySelector(`#${aBlock.human_readable_name}_SP`)!.innerHTML,
+ ).toContain(expectedSpValue.toString());
+});
+
+it("renders block with timestamp and shows timestamp value", () => {
+ const expectedValue = 123;
+ const expectedTimeStamp = 1731342022;
+ const aBlock: IfcBlock = {
+ pvaddress: "SOME:PV",
+ visible: true,
+ human_readable_name: "MyBlock",
+ runcontrol_inrange: false,
+ runcontrol_enabled: false,
+ updateSeconds: expectedTimeStamp,
+ value: expectedValue,
+ };
+ const { container } = render(
+ ,
+ {
+ container: tableBody,
+ },
+ );
+ const valueSpan = container.querySelector(
+ `#${aBlock.human_readable_name}_VALUE`,
+ )!;
+ fireEvent.click(valueSpan);
+ expect(valueSpan.innerHTML).toContain(`${expectedValue}`);
+ expect(
+ container.querySelector(`#${aBlock.human_readable_name}_TIMESTAMP`)!
+ .innerHTML,
+ ).toContain(new Date(expectedTimeStamp * 1000).toLocaleString());
});
it("renders block without SP and hides SP value", () => {
@@ -187,12 +187,7 @@ it("renders block without SP and hides SP value", () => {
sp_value: expectedSpValue,
};
const { container } = render(
- ,
+ ,
{
container: tableBody,
},
diff --git a/app/components/Block.tsx b/app/components/Block.tsx
index e679656..15c3d48 100644
--- a/app/components/Block.tsx
+++ b/app/components/Block.tsx
@@ -1,6 +1,6 @@
"use client";
import { IfcBlock } from "@/app/types";
-import { useState } from "react";
+import React, { useState } from "react";
const grafana_stub =
"https://shadow.nd.rl.ac.uk/grafana/d/wMlwwaHMk/block-history?viewPanel=2&orgId=1&var-block=";
@@ -9,16 +9,15 @@ export default function Block({
pv,
instName,
showHiddenBlocks,
- showSetpoints,
}: {
pv: IfcBlock;
instName: string;
showHiddenBlocks: boolean;
- showSetpoints: boolean;
}) {
const [currentValue, setCurrentValue] = useState<
string | number | undefined
>();
+ const [showAdvanced, setShowAdvanced] = useState(false);
if (!pv.visible && !showHiddenBlocks && !instName) {
return null;
}
@@ -38,12 +37,16 @@ export default function Block({
}, 2000);
}
+ const minimum_date_to_be_shown = 631152000; // This is what PVWS thinks epoch time is for some reason. don't bother showing it as the instrument wasn't running EPICS on 01/01/1990
return (
{
+ setShowAdvanced(!showAdvanced);
+ }}
>
- |
+ |
|
-
-
- {pv.value} {pv.units != null && pv.units}
- {showSetpoints && pv.sp_value != null ? (
- <>
-
- {`(SP: ${pv.sp_value})`}
- >
- ) : null}
- {pv.severity != "NONE" ? (
-
+
+
+
-
- {pv.severity}
-
- ) : null}
+ {showAdvanced && "Readback: "}
+ {pv.value} {pv.units != null && pv.units}
+
+
+
+
+ {showAdvanced && (
+
+
+ {pv.severity != "NONE" ? (
+
+ Alarm: {pv.severity}
+
+ ) : null}
+
+ {pv.sp_value != null ? (
+
+ {`Setpoint: ${pv.sp_value}`}
+
+
+ ) : null}
+ {pv.updateSeconds != null &&
+ pv.updateSeconds > minimum_date_to_be_shown ? (
+
+ {/*Multiply by 1000 here as Date() expects milliseconds*/}
+ {`Last update: ${new Date(pv.updateSeconds * 1000).toLocaleString()}`}
+
+ ) : null}
+
+ )}
|
-
-
+ |
+
{pv.runcontrol_enabled && (pv.runcontrol_inrange ? "✅" : "❌")}
-
+ {showAdvanced ? "-" : "+"}
+
|
);
diff --git a/app/components/Group.tsx b/app/components/Group.tsx
index a6dbe83..d9837a6 100644
--- a/app/components/Group.tsx
+++ b/app/components/Group.tsx
@@ -8,12 +8,10 @@ export default function Group({
group,
instName,
showHiddenBlocks,
- showSetpoints,
}: {
group: IfcGroup;
instName: string;
showHiddenBlocks: boolean;
- showSetpoints: boolean;
}) {
if (!group) {
return Loading...
;
@@ -30,14 +28,14 @@ export default function Group({
{group.name}
-
-
- | Block |
- Value |
- In-Range |
+
+
+ | Block |
+ Value |
+ |
-
+
{group.blocks.map((pv) => {
return (
);
})}
diff --git a/app/components/Groups.tsx b/app/components/Groups.tsx
index ef61b70..389dc22 100644
--- a/app/components/Groups.tsx
+++ b/app/components/Groups.tsx
@@ -7,19 +7,17 @@ export default function Groups({
groupsMap,
instName,
showHiddenBlocks,
- showSetpoints,
}: {
groupsMap: Array;
instName: string;
showHiddenBlocks: boolean;
- showSetpoints: boolean;
}) {
if (!groupsMap) {
return Loading...
;
}
return (
-
+
{groupsMap.map((group) => {
return (
);
})}
diff --git a/app/components/InstrumentPage.tsx b/app/components/InstrumentPage.tsx
index 16198a3..c5c67dd 100644
--- a/app/components/InstrumentPage.tsx
+++ b/app/components/InstrumentPage.tsx
@@ -99,6 +99,7 @@ export function toPrecision(
function InstrumentData({ instrumentName }: { instrumentName: string }) {
const [showHiddenBlocks, setShowHiddenBlocks] = useState(false);
const [showSetpoints, setShowSetpoints] = useState(false);
+ const [showTimestamps, setShowTimestamps] = useState(false);
const CONFIG_DETAILS = "CS:BLOCKSERVER:GET_CURR_CONFIG_DETAILS";
const [instlist, setInstlist] = useState
| null>(null);
const [currentInstrument, setCurrentInstrument] = useState(
@@ -242,6 +243,7 @@ function InstrumentData({ instrumentName }: { instrumentName: string }) {
}
// if a block has precision truncate it here
block.value = toPrecision(block, pvVal);
+ if (updatedPV.seconds) block.updateSeconds = updatedPV.seconds;
if (updatedPV.units) block.units = updatedPV.units;
if (updatedPV.severity) block.severity = updatedPV.severity;
@@ -262,29 +264,23 @@ function InstrumentData({ instrumentName }: { instrumentName: string }) {
return Loading...
;
}
return (
-
+
-
);
diff --git a/app/components/TopBar.test.ts b/app/components/TopBar.test.ts
index 2a8fad0..962be43 100644
--- a/app/components/TopBar.test.ts
+++ b/app/components/TopBar.test.ts
@@ -47,7 +47,7 @@ it("draws instName expectedly", () => {
runInfoPVs: instrument.runInfoPVs,
}),
);
- expect(container.querySelector("#instNameSpan")!.innerHTML).toBe(
+ expect(container.querySelector("#instNameLabel")!.innerHTML).toContain(
instName.toUpperCase(),
);
});
diff --git a/app/components/TopBar.tsx b/app/components/TopBar.tsx
index 458b051..07b1450 100644
--- a/app/components/TopBar.tsx
+++ b/app/components/TopBar.tsx
@@ -42,22 +42,6 @@ export default function TopBar({
id="top_bar"
className="w-full bg-white shadow-lg text-black rounded-xl text-md"
>
-
-
- Instrument:{" "}
-
- {instName.toUpperCase()}
-
-
-
- Config:{" "}
-
- {findPVByHumanReadableName(runInfoPVs, configName)
- ? findPVByHumanReadableName(runInfoPVs, configName)!.value
- : "UNKNOWN"}
-
-
-
{instName.toUpperCase()} is{" "}
{getRunstate(runInfoPVs)}
-
+
+ Config:{" "}
+
+ {findPVByHumanReadableName(runInfoPVs, configName)
+ ? findPVByHumanReadableName(runInfoPVs, configName)!.value
+ : "UNKNOWN"}
+
+
+
+
{dashboard.map((column: Array>, index: number) => (
-
+
{column.map((row: Array, index: number) => (
- | {row[0].value} |
-
+ |
+ {row[0].value}
+ |
+
{row[1].value != null ? row[1].value : "Hidden/unknown"}
@@ -98,17 +94,23 @@ export default function TopBar({
|
-
diff --git a/app/components/__snapshots__/Block.test.tsx.snap b/app/components/__snapshots__/Block.test.tsx.snap
index 4290876..11b2225 100644
--- a/app/components/__snapshots__/Block.test.tsx.snap
+++ b/app/components/__snapshots__/Block.test.tsx.snap
@@ -6,7 +6,7 @@ exports[`renders topbar unchanged 1`] = `
class="border-b border-blue-gray-200 transition duration-100 hover:bg-gray-100 hover:text-black"
>
|
- 123
-
- mm
-
-
-
+
+ 123
+
+ mm
+
+
+
|
-
+ +
+
|
diff --git a/app/components/__snapshots__/TopBar.test.ts.snap b/app/components/__snapshots__/TopBar.test.ts.snap
index b8dcccd..951e4da 100644
--- a/app/components/__snapshots__/TopBar.test.ts.snap
+++ b/app/components/__snapshots__/TopBar.test.ts.snap
@@ -6,32 +6,6 @@ exports[`renders topbar unchanged 1`] = `
class="w-full bg-white shadow-lg text-black rounded-xl text-md"
id="top_bar"
>
-
-
- Instrument:
-
-
- INSTRUMENT
-
-
-
- Config:
-
-
-
-
INSTRUMENT
is
@@ -53,26 +28,36 @@ exports[`renders topbar unchanged 1`] = `
UNKNOWN
+
+ Config:
+
+
+
|
Title:
|
|
|
Users:
|
|
|
|
|
|
|
|
|
|
|
|
|
- Click to show/hide all run information
+ Show/hide all run information
-
- Config name
- :
-
-
- Run state
- :
-
-
- Run number
- :
-
-
- Start number
- :
-
-
- Title
- :
-
-
- Users
- :
-
-
- Good frames
- :
-
-
- Raw frames (Total)
- :
-
-
- Raw frames (Period)
- :
-
-
- Current(uamps)
- :
-
-
- Total(uamps)
- :
-
-
- Monitor counts
- :
-
-
- Monitor Spectrum
- :
-
-
- Monitor From
- :
-
-
- Monitor To
- :
-
-
- Shutter Status
- :
-
-
- Number of Spectra
- :
-
-
- Number of Time Channels
- :
-
-
- DAE Simulation Mode
- :
-
-
- Instrument Time
- :
-
-
- Start time
- :
-
-
- Run time
- :
-
-
- Period
- :
-
-
- Num periods
- :
-
-
- Count Rate
- :
-
-
- RB Number
- :
-
-
- Total Run Time
- :
-
-
- Period Run Time
- :
-
-
- Period Sequence
- :
-
-
- DAE Memory Used
- :
-
+
+
+ Config name
+ :
+
+
+
+
+
+
+ Run state
+ :
+
+
+
+
+
+
+ Run number
+ :
+
+
+
+
+
+
+ Start number
+ :
+
+
+
+
+
+
+
+
+ Good frames
+ :
+
+
+
+
+
+
+ Raw frames (Total)
+ :
+
+
+
+
+
+
+ Raw frames (Period)
+ :
+
+
+
+
+
+
+ Current(uamps)
+ :
+
+
+
+
+
+
+ Total(uamps)
+ :
+
+
+
+
+
+
+ Monitor counts
+ :
+
+
+
+
+
+
+ Monitor Spectrum
+ :
+
+
+
+
+
+
+ Monitor From
+ :
+
+
+
+
+
+
+ Monitor To
+ :
+
+
+
+
+
+
+ Shutter Status
+ :
+
+
+
+
+
+
+ Number of Spectra
+ :
+
+
+
+
+
+
+ Number of Time Channels
+ :
+
+
+
+
+
+
+ DAE Simulation Mode
+ :
+
+
+
+
+
+
+ Instrument Time
+ :
+
+
+
+
+
+
+ Start time
+ :
+
+
+
+
+
+
+
+
+ Num periods
+ :
+
+
+
+
+
+
+ Count Rate
+ :
+
+
+
+
+
+
+ RB Number
+ :
+
+
+
+
+
+
+ Total Run Time
+ :
+
+
+
+
+
+
+ Period Run Time
+ :
+
+
+
+
+
+
+ Period Sequence
+ :
+
+
+
+
+
+
+ DAE Memory Used
+ :
+
+
+
+
+
diff --git a/app/types.ts b/app/types.ts
index a18fea3..8ac765f 100644
--- a/app/types.ts
+++ b/app/types.ts
@@ -15,6 +15,7 @@ export interface IfcPV {
alarm_low?: number;
alarm_high?: number;
value?: string | number;
+ updateSeconds?: number; // Seconds from epoch
}
export interface IfcBlock extends IfcPV {
|