NorthStar-HMI/static/multi_pu_dashboard.html
2025-08-21 09:10:17 +02:00

225 lines
6.4 KiB
HTML

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<title>Multi-PU Dashboard</title>
<script src="https://cdn.plot.ly/plotly-latest.min.js"></script>
<style>
body {
font-family: Arial, sans-serif;
margin: 0;
padding: 20px;
}
h1 {
text-align: center;
}
.status-container {
display: flex;
justify-content: center;
gap: 20px;
margin: 10px 0;
padding: 10px;
background-color: #f5f5f5;
border-radius: 8px;
}
.status-box {
padding: 8px 15px;
border-radius: 5px;
background-color: #e0e0e0;
font-weight: bold;
min-width: 100px;
text-align: center;
}
.plot-container {
display: flex;
flex-direction: column;
gap: 5px;
align-items: center;
}
.plot {
width: 90%;
height: 300px;
}
</style>
</head>
<body>
<h1>Multi-PU Monitoring Dashboard</h1>
<!-- Statuses for each PU -->
<div class="status-container" id="statusContainer">
<div id="PU1-status" class="status-box">PU1: Loading...</div>
<div id="PU2-status" class="status-box">PU2: Loading...</div>
<div id="PU3-status" class="status-box">PU3: Loading...</div>
</div>
<div class="plot-container">
<div id="Qperm-plot" class="plot"></div>
<div id="Pdilute-plot" class="plot"></div>
<div id="Pro-plot" class="plot"></div>
<div id="Qdilute-plot" class="plot"></div>
<div id="Qdrain-plot" class="plot"></div>
<div id="Cdilute-plot" class="plot"></div>
</div>
<script>
const time0 = [new Date()];
const zero = [0];
const maxPoints = 100;
const puList = ['PU_1', 'PU_2', 'PU_3'];
const windowMs = 30 * 1000; // 30 seconds
const plots = [
{ id: 'Qperm-plot', quantity: 'Qperm', title: 'Qperm per PU', refKey: 'Qperm_sp' },
{ id: 'Qdilute-plot', quantity: 'Qdilute', title: 'Qdilute per PU' },
{ id: 'Qdrain-plot', quantity: 'Qdrain', title: 'Qdrain per PU' },
{ id: 'Pro-plot', quantity: 'Pro', title: 'Pro per PU' },
{ id: 'Pdilute-plot', quantity: 'Pdilute', title: 'Pdilute per PU' , refKey: 'Ploop_sp'},
{ id: 'Cdilute-plot', quantity: 'Cdilute', title: 'Cdilute per PU' },
];
const plotTraceMap = {}; // track trace indices per plot
function initAllPlots() {
plots.forEach(plot => {
const data = makeTraces(plot.quantity);
plotTraceMap[plot.id] = { pu: [0,1,2], extra: {} }; // base 3 PUs
if (plot.refKey) {
data.push({
x: time0.slice(),
y: [0],
mode: 'lines',
line: { dash: 'dash', color: 'red' },
name: `${plot.refKey} (PU2)`,
});
plotTraceMap[plot.id].extra.ref = data.length - 1;
}
if (plot.id === 'Qperm-plot') {
data.push({ x: time0.slice(), y: zero.slice(), name: 'QSkid', mode: 'lines' });
plotTraceMap[plot.id].extra.qSkid = data.length - 1;
data.push({ x: time0.slice(), y: zero.slice(), name: 'Qconso', mode: 'lines' });
plotTraceMap[plot.id].extra.qConso = data.length - 1;
}
if (plot.id === 'Qdrain-plot') {
data.push({ x: time0.slice(), y: zero.slice(), name: 'QSkid', mode: 'lines' });
plotTraceMap[plot.id].extra.qSkid = data.length - 1;
data.push({ x: time0.slice(), y: zero.slice(), name: 'Qconso', mode: 'lines' });
plotTraceMap[plot.id].extra.qConso = data.length - 1;
}
Plotly.newPlot(plot.id, data, {
title: plot.title,
xaxis: { type: 'date' },
yaxis: { title: plot.id.includes('P') ? 'Pressure (bar)' : 'Flow (L/h)' }
});
});
}
async function updateAllPlots() {
try {
const res = await fetch('/monitor');
if (!res.ok) throw new Error(`HTTP error! status: ${res.status}`);
const allData = await res.json();
const timestamp = new Date();
const SkidData = allData["PatientSkid"] || {};
const DSData = allData["DS"] || {};
const pu2Data = allData["PU_2"] || {}; // <--- take ref values from PU_3
plots.forEach(plot => {
const xUpdates = [];
const yUpdates = [];
// Extend PU1, PU2, PU3 values
puList.forEach(pu => {
const puData = allData[pu] || {};
const value = puData[plot.quantity];
xUpdates.push([timestamp]);
yUpdates.push([value !== undefined ? value : null]);
});
Plotly.extendTraces(plot.id, { x: xUpdates, y: yUpdates }, puList.map((_, i) => i), maxPoints);
// Update PU2 reference line dynamically
Plotly.extendTraces(plot.id,
{ x: xUpdates, y: yUpdates },
plotTraceMap[plot.id].pu,
maxPoints
);
if (plot.refKey) {
const refVal = pu2Data[plot.refKey];
Plotly.extendTraces(plot.id,
{ x: [[timestamp]], y: [[refVal ?? null]] },
[plotTraceMap[plot.id].extra.ref],
maxPoints
);
}
if (plot.id === 'Qperm-plot') {
const qSkid = SkidData["QSkid"];
const qConso = DSData["Qconso"];
Plotly.extendTraces(plot.id, { x: [[timestamp]], y: [[qSkid ?? null]] }, [plotTraceMap[plot.id].extra.qSkid], maxPoints);
Plotly.extendTraces(plot.id, { x: [[timestamp]], y: [[qConso ?? null]] }, [plotTraceMap[plot.id].extra.qConso], maxPoints);
}
if (plot.id === 'Qdrain-plot') {
const qSkid = SkidData["QSkid"];
const qConso = DSData["Qconso"];
Plotly.extendTraces(plot.id, { x: [[timestamp]], y: [[qSkid ?? null]] }, [plotTraceMap[plot.id].extra.qSkid], maxPoints);
Plotly.extendTraces(plot.id, { x: [[timestamp]], y: [[qConso ?? null]] }, [plotTraceMap[plot.id].extra.qConso], maxPoints);
}
// Sliding window (30s)
const layoutUpdate = {
'xaxis.range': [new Date(timestamp - windowMs), timestamp]
};
Plotly.relayout(plot.id, layoutUpdate);
});
} catch (err) {
console.error("Failed to update plots:", err);
}
}
function makeTraces(quantity) {
return puList.map((pu) => ({
x: time0.slice(),
y: zero.slice(),
name: pu,
mode: 'lines',
line: { width: 2 },
legendgroup: pu
}));
}
async function updateStatuses() {
try {
const res = await fetch("/api/pu_status");
const statuses = await res.json();
puList.forEach((pu, i) => {
const el = document.getElementById(`PU${i+1}-status`);
el.textContent = `${pu}: ${statuses[`PU${i+1}`] || "Unknown"}`;
});
} catch (err) {
console.error("Error fetching PU status:", err);
}
}
initAllPlots();
setInterval(updateAllPlots, 1000);
setInterval(updateStatuses, 1000);
</script>
</body>
</html>