[Perl] DAQ system for the FAPG.
refactor remove toolbar status pill, keep header LED only
The status-summary pill in the toolbar duplicated the header LED indicator. Remove it to declutter the toolbar. - Remove status pill from toolbar template and its show_overview_status stash variable from all controller actions - Remove loadOverview, setStatusPill, statusTitle from status.js - Remove .status-summary CSS rules - Remove data-sensor-status hidden spans (only used by loadOverview) - Header LED continues to indicate overall DAQ health
Changed files
- roles/dashboard/lib/FAPG/DAQ/Dashboard/Controller/Dashboard.pm
- roles/dashboard/public/css/app.css
- roles/dashboard/public/js/dashboard/status.js
- roles/dashboard/t/01-graph-page.t
- roles/dashboard/templates/dashboard/index.html.ep
- roles/dashboard/templates/dashboard/toolbar.html.ep
- roles/dashboard/templates/layouts/default.html.ep
roles/dashboard/lib/FAPG/DAQ/Dashboard/Controller/Dashboard.pm
@@ -15,11 +15,10 @@
15
15
16
16
sub status ($self) {
17
17
$self->render(
18
Removed:
template => 'dashboard/status',
19
Removed:
items => $self->status_items,
20
Removed:
nav_page => 'status',
21
Removed:
show_overview_status => 1,
22
Removed:
breadcrumbs =>
18
Added:
template => 'dashboard/status',
19
Added:
items => $self->status_items,
20
Added:
nav_page => 'status',
21
Added:
breadcrumbs =>
23
22
[ { label => 'Home', href => '/' }, { label => 'Status' }, ],
24
23
);
25
24
}
@@ -30,7 +29,6 @@
30
29
probes => $self->probes,
31
30
wide_layout => 1,
32
31
nav_page => 'probes',
33
Removed:
show_overview_status => 1,
34
32
breadcrumbs =>
35
33
[ { label => 'Home', href => '/' }, { label => 'Probes' }, ],
36
34
graph_control_aria_label => 'All probe chart timeframes',
@@ -66,8 +64,7 @@
66
64
to => $to,
67
65
oldest => $oldest,
68
66
probes => $self->probes,
69
Removed:
nav_page => 'downloads',
70
Removed:
show_overview_status => 1,
67
Added:
nav_page => 'downloads',
71
68
breadcrumbs =>
72
69
[ { label => 'Home', href => '/' }, { label => 'Downloads' }, ],
73
70
);
@@ -83,7 +80,6 @@
83
80
template => 'dashboard/graph',
84
81
probe => $probe,
85
82
nav_page => 'probes',
86
Removed:
show_overview_status => 1,
87
83
single_probe => 1,
88
84
breadcrumbs => [
89
85
{ label => 'Home', href => '/' },
roles/dashboard/public/css/app.css
@@ -124,23 +124,6 @@
124
124
color: var(--fapg-unknown) !important;
125
125
}
126
126
127
Removed:
/* Status summary pill in the toolbar */
128
Removed:
.status-summary {
129
Removed:
display: inline-flex;
130
Removed:
align-items: center;
131
Removed:
justify-content: center;
132
Removed:
min-width: 11rem;
133
Removed:
border-radius: 999px;
134
Removed:
padding: 0.4rem 0.85rem;
135
Removed:
font-size: 0.82rem;
136
Removed:
font-weight: 700;
137
Removed:
text-decoration: none;
138
Removed:
}
139
Removed:
140
Removed:
.status-summary[href]:hover {
141
Removed:
filter: brightness(0.97);
142
Removed:
}
143
Removed:
144
127
/* ----------------------------------------------------------------
145
128
Status node marker — LED dot on the status page
146
129
---------------------------------------------------------------- */
roles/dashboard/public/js/dashboard/status.js
@@ -26,56 +26,7 @@
26
26
return "unknown";
27
27
}
28
28
29
Removed:
function setStatusPill(element, state, text = statusLabel(state)) {
30
Removed:
element.classList.remove(
31
Removed:
"is-online", "is-healthy", "is-stale", "is-probe-error",
32
Removed:
"is-offline", "is-unreachable", "is-unknown"
33
Removed:
);
34
Removed:
element.classList.add(`is-${state}`);
35
Removed:
element.querySelector("[data-status-text]").textContent = text;
36
Removed:
}
37
Removed:
38
Removed:
function statusTitle(nodeStatus) {
39
Removed:
if (!nodeStatus) return "No recent node status message has been stored.";
40
Removed:
41
Removed:
const detail = nodeStatus.error || nodeStatus.message || "";
42
Removed:
const timestamp = nodeStatus.received_at || nodeStatus.timestamp;
43
Removed:
const when = timestamp
44
Removed:
? `${formatDateTime(timestamp)} (${formatAge(ageMs(timestamp))})`
45
Removed:
: "unknown time";
46
Removed:
47
Removed:
return [detail, `Status received at ${when}`].filter(Boolean).join(" ");
48
Removed:
}
49
Removed:
50
Removed:
function loadOverview(statuses) {
51
Removed:
const overview = document.querySelector("[data-overview-status]");
52
Removed:
if (!overview) return;
53
Removed:
54
Removed:
const rows = Array.from(document.querySelectorAll("[data-sensor-status]"));
55
Removed:
const items = rows.map(row => {
56
Removed:
const status = statuses[row.dataset.sensorStatus] || null;
57
Removed:
58
Removed:
return { status, state: stateForNodeStatus(status) };
59
Removed:
});
60
Removed:
const unhealthy = items.filter(item => item.state !== "healthy");
61
Removed:
62
Removed:
if (!items.length) {
63
Removed:
setStatusPill(overview, "unknown", "No sensors configured");
64
Removed:
} else if (!unhealthy.length) {
65
Removed:
setStatusPill(overview, "healthy", "All sensors healthy");
66
Removed:
overview.title = "Every sensor node has sent a recent healthy status.";
67
Removed:
} else {
68
Removed:
const state = unhealthy.some(item => item.state === "unreachable")
69
Removed:
? "unreachable"
70
Removed:
: "probe-error";
71
Removed:
const label = unhealthy.length === 1 ? "sensor needs" : "sensors need";
72
Removed:
73
Removed:
setStatusPill(overview, state, `${unhealthy.length} ${label} attention`);
74
Removed:
overview.title = unhealthy.map(item => statusTitle(item.status)).join(" ");
75
Removed:
}
76
Removed:
}
77
Removed:
78
Removed:
function setHeaderStatus(state, text) {
29
Added:
function loadHeaderStatus(statuses) {
79
30
const element = document.querySelector("[data-header-status]");
80
31
if (!element) return;
81
32
@@ -137,7 +88,7 @@
137
88
export function createStatusLoader() {
138
89
return async function loadStatuses() {
139
90
const hasStatusElements = document.querySelector(
140
Removed:
"[data-overview-status], [data-header-status], [data-status-page-item]"
91
Added:
"[data-header-status], [data-status-page-item]"
141
92
);
142
93
if (!hasStatusElements) return;
143
94
@@ -148,7 +99,6 @@
148
99
// Render every configured item as unreachable when the bulk request fails.
149
100
}
150
101
151
Removed:
loadOverview(statuses);
152
102
loadHeaderStatus(statuses);
153
103
loadStatusPage(statuses);
154
104
};
roles/dashboard/t/01-graph-page.t
@@ -60,9 +60,7 @@
60
60
->element_exists('main.container-wide')
61
61
->element_exists('div.probes-grid')
62
62
->element_exists('div.toolbar-items div.graph-control')
63
Removed:
->element_exists('.navbar-nav a.nav-link.active[href="/probes"][aria-current="page"]')
64
Removed:
->element_exists(
65
Removed:
'div.toolbar-items a.status-summary[href="/status"][data-overview-status]');
63
Added:
->element_exists('.navbar-nav a.nav-link.active[href="/probes"][aria-current="page"]');
66
64
67
65
for my $probe (qw(ph do orp ec)) {
68
66
$t->element_exists(qq{div.probes-grid .card-header h1 a[href="/probes/$probe"]});
roles/dashboard/templates/dashboard/index.html.ep
@@ -5,13 +5,6 @@
5
5
% stash use_charts => 1;
6
6
7
7
<section class="mb-4" aria-label="DAQ status overview">
8
Removed:
% for my $probe ($probes->@*) {
9
Removed:
<span
10
Removed:
hidden
11
Removed:
data-sensor-status="<%= $probe->{key} %>"
12
Removed:
data-sensor-label="<%= $probe->{label} %>"></span>
13
Removed:
% }
14
Removed:
15
8
<div class="mb-3">
16
9
<h1 class="h4 fw-bold mb-1">FAPG DAQ Dashboard</h1>
17
10
<p class="text-muted mb-0">Live aquaponic sensor readings</p>
roles/dashboard/templates/dashboard/toolbar.html.ep
@@ -16,16 +16,9 @@
16
16
% }
17
17
</ol>
18
18
</nav>
19
Removed:
% if (defined $graph_control_aria_label || $show_overview_status) {
19
Added:
% if (defined $graph_control_aria_label) {
20
20
<div class="toolbar-items d-flex align-items-center gap-2 ms-auto">
21
Removed:
% if (defined $graph_control_aria_label) {
22
21
%= include 'dashboard/graph-control', aria_label => $graph_control_aria_label
23
Removed:
% }
24
Removed:
% if ($show_overview_status) {
25
Removed:
<a class="status-pill status-summary is-unknown" href="/status" data-overview-status>
26
Removed:
<span data-status-text>Checking sensors</span>
27
Removed:
</a>
28
Removed:
% }
29
22
</div>
30
23
% }
31
24
</div>
roles/dashboard/templates/layouts/default.html.ep
@@ -71,7 +71,7 @@
71
71
</nav>
72
72
73
73
<main class="<%= stash('wide_layout') ? 'container-wide' : 'container' %>">
74
Removed:
%= include 'dashboard/toolbar', breadcrumbs => (stash('breadcrumbs') // []), graph_control_aria_label => stash('graph_control_aria_label'), show_overview_status => stash('show_overview_status')
74
Added:
%= include 'dashboard/toolbar', breadcrumbs => (stash('breadcrumbs') // []), graph_control_aria_label => stash('graph_control_aria_label')
75
75
<%= content %>
76
76
</main>
77
77