refactor align with Bootstrap UI metaphors

Breadcrumbs: - Remove custom pill styling (bg-white border rounded-pill), use standard Bootstrap .breadcrumb Quick links: - Convert from list-inline to nav nav-pills Reading gauge cards: - Replace custom is-good/is-fair/is-poor/is-unknown classes with Bootstrap border-success/border-warning/border-danger utilities - Marker color remains set via inline style from JS Status page: - Rewrite using Bootstrap list-group-flush inside the card - Each status item is a flat list-group-item with marker, title, badge, and detail fields - Remove nested section/ol wrapper structure

Commit
f1ed035696d34273a32e7df84f6e041bdff8d921
Author
GPT-5 medium <codex@openai.com>
Author date
Committer
GPT-5 medium <codex@openai.com>
Committer date
Changed files
roles/dashboard/public/js/dashboard/quick-readings.js
index ab2db8a0..61e724b3 100644..100644
@@ -76,6 +76,10 @@
76 76 return `rgb(${r}, ${g}, ${b})`;
77 77 }
78 78
79 Added: function qualityBorderClass(state) {
80 Added: return { good: "border-success", fair: "border-warning", poor: "border-danger", unknown: "" }[state] || "";
81 Added: }
82 Added:
79 83 function updateItem(item, reading) {
80 84 const probe = item.dataset.readingQuickProbe;
81 85 const range = READING_RANGES[probe];
@@ -89,8 +93,9 @@
89 93 const age = timestamp ? formatAge(ageMs(timestamp)) : "unknown age";
90 94
91 95 configureRange(item, range, unit);
92 Removed: item.classList.remove("is-good", "is-fair", "is-poor", "is-unknown");
93 Removed: item.classList.add(`is-${state}`);
96 Added: item.classList.remove("border-success", "border-warning", "border-danger");
97 Added: const borderClass = qualityBorderClass(state);
98 Added: if (borderClass) item.classList.add(borderClass);
94 99
95 100 const marker = item.querySelector("[data-reading-quick-marker]");
96 101 marker.style.left = `${percent}%`;
roles/dashboard/t/05-status-page.t
index c31ec5ab..d0dd49d1 100644..100644
@@ -19,18 +19,18 @@
19 19 'nav[aria-label="Breadcrumb"] [aria-current="page"]', 'Status' )
20 20 ->text_is( 'h1', 'DAQ Status' )
21 21 ->element_exists('main.container')
22 Removed: ->element_exists('[data-status-page-groups]');
22 Added: ->element_exists('.list-group');
23 23
24 24 for my $item (qw(hub ph do orp ec)) {
25 25 $t->element_exists(
26 Removed: qq{[data-status-page-probe="$item"] [data-status-page-item="$item"] [data-status-page-state]});
26 Added: qq{[data-status-page-item="$item"] [data-status-page-state]});
27 27 }
28 28
29 29 for my $probe (qw(ph do orp ec)) {
30 30 $t->element_exists(
31 Removed: qq{[data-status-page-probe="$probe"] h2 a[href="/probes/$probe"]});
31 Added: qq{[data-status-page-item="$probe"] h2 a[href="/probes/$probe"]});
32 32 }
33 33
34 Removed: $t->element_exists_not('[data-status-page-probe="hub"] h2 a');
34 Added: $t->element_exists_not('[data-status-page-item="hub"] h2 a');
35 35
36 36 done_testing;
roles/dashboard/templates/dashboard/index.html.ep
index 86f1d760..38d89377 100644..100644
@@ -9,10 +9,10 @@
9 9 <h1 class="display-5 fw-bold mb-1">FAPG DAQ Dashboard</h1>
10 10 <p class="text-muted mb-2">Live aquaponic sensor readings</p>
11 11 <nav aria-label="Quick links">
12 Removed: <ul class="list-inline mb-0">
13 Removed: <li class="list-inline-item"><a class="text-success fw-semibold text-decoration-none" href="/probes">Probes</a></li>
14 Removed: <li class="list-inline-item"><a class="text-success fw-semibold text-decoration-none" href="/status">Status</a></li>
15 Removed: <li class="list-inline-item"><a class="text-success fw-semibold text-decoration-none" href="/downloads">Downloads</a></li>
12 Added: <ul class="nav nav-pills">
13 Added: <li class="nav-item"><a class="nav-link" href="/probes">Probes</a></li>
14 Added: <li class="nav-item"><a class="nav-link" href="/status">Status</a></li>
15 Added: <li class="nav-item"><a class="nav-link" href="/downloads">Downloads</a></li>
16 16 </ul>
17 17 </nav>
18 18 </div>
@@ -24,7 +24,7 @@
24 24 <div class="col-12 col-sm-6 col-xl-3">
25 25 <a class="reading-quick-link text-decoration-none text-body d-block h-100" href="/probes/<%= $probe->{key} %>">
26 26 <article
27 Removed: class="card h-100 reading-quick-item is-unknown"
27 Added: class="card h-100 reading-quick-item"
28 28 data-reading-quick-probe="<%= $probe->{key} %>"
29 29 data-reading-quick-label="<%= $probe->{label} %>"
30 30 data-reading-quick-unit="<%= $probe->{unit} %>">
roles/dashboard/templates/dashboard/status.html.ep
index f0178b81..2331edeb 100644..100644
@@ -9,42 +9,39 @@
9 9 <p class="text-muted small mb-0 mt-1">Current connection status for the hub and sensor probes.</p>
10 10 </div>
11 11
12 Removed: <div class="card-body">
13 Removed: <div class="d-grid gap-4" data-status-page-groups>
14 Removed: % for my $item ($items->@*) {
15 Removed: <section
16 Removed: data-status-page-probe="<%= $item->{key} %>"
17 Removed: aria-labelledby="status-probe-<%= $item->{key} %>">
18 Removed: <h2 id="status-probe-<%= $item->{key} %>" class="h6 fw-semibold mb-2 text-uppercase text-muted" data-status-page-label>
19 Removed: % if ($item->{key} ne 'hub') {
12 Added: <div class="list-group list-group-flush">
13 Added: % for my $item ($items->@*) {
14 Added: <div class="list-group-item" data-status-page-item="<%= $item->{key} %>">
15 Added: <div class="d-flex align-items-center gap-3">
16 Added: <span class="status-node-marker is-unknown" aria-hidden="true"></span>
17 Added: <div class="flex-grow-1 min-w-0">
18 Added: <div class="d-flex flex-wrap justify-content-between align-items-center gap-2">
19 Added: <h2 class="h6 fw-semibold mb-0">
20 Added: % if ($item->{key} ne 'hub') {
20 21 <a class="text-body text-decoration-none" href="/probes/<%= $item->{key} %>"><%= $item->{label} %></a>
21 Removed: % } else {
22 Added: % } else {
22 23 <%= $item->{label} %>
23 Removed: % }
24 Removed: </h2>
25 Removed: <ol class="list-unstyled mb-0" aria-label="<%= $item->{label} %> nodes">
26 Removed: <li class="d-flex gap-3 align-items-start" data-status-page-item="<%= $item->{key} %>">
27 Removed: <span class="status-node-marker is-unknown flex-shrink-0 mt-2" aria-hidden="true"></span>
28 Removed: <div class="border rounded p-3 flex-grow-1 bg-white">
29 Removed: <div class="d-flex flex-wrap justify-content-between align-items-center gap-2 mb-2">
30 Removed: <p class="fw-bold mb-0 status-node-name" data-status-page-node>Checking…</p>
31 Removed: <span class="badge status-pill is-unknown" data-status-page-state>Checking</span>
32 Removed: </div>
33 Removed: <dl class="row row-cols-1 row-cols-sm-2 g-2 mb-0 small">
34 Removed: <div class="col">
35 Removed: <dt class="text-muted text-uppercase fw-semibold status-node-dt">Last status</dt>
36 Removed: <dd class="mb-0" data-status-page-last-seen>Checking…</dd>
37 Removed: </div>
38 Removed: <div class="col">
39 Removed: <dt class="text-muted text-uppercase fw-semibold status-node-dt">Message</dt>
40 Removed: <dd class="mb-0" data-status-page-message>Checking status…</dd>
41 Removed: </div>
42 Removed: </dl>
43 Removed: </div>
44 Removed: </li>
45 Removed: </ol>
46 Removed: </section>
47 Removed: % }
24 Added: % }
25 Added: </h2>
26 Added: <span class="badge status-pill is-unknown" data-status-page-state>Checking</span>
27 Added: </div>
28 Added: <div class="row row-cols-1 row-cols-sm-2 g-1 mt-1 small text-muted">
29 Added: <div class="col">
30 Added: <span class="fw-semibold text-uppercase status-node-dt">Node:</span>
31 Added: <span data-status-page-node>Checking…</span>
32 Added: </div>
33 Added: <div class="col">
34 Added: <span class="fw-semibold text-uppercase status-node-dt">Last seen:</span>
35 Added: <span data-status-page-last-seen>Checking…</span>
36 Added: </div>
37 Added: <div class="col col-sm-12">
38 Added: <span class="fw-semibold text-uppercase status-node-dt">Message:</span>
39 Added: <span data-status-page-message>Checking status…</span>
40 Added: </div>
41 Added: </div>
42 Added: </div>
43 Added: </div>
48 44 </div>
45 Added: % }
49 46 </div>
50 47 </div>
roles/dashboard/templates/dashboard/toolbar.html.ep
index 49ed16fd..240c9fbb 100644..100644
@@ -3,13 +3,13 @@
3 3 <div id="toolbar" class="d-flex flex-column flex-md-row align-items-start align-items-md-center justify-content-between gap-2 mb-3 toolbar">
4 4 % if ($breadcrumbs->@*) {
5 5 <nav aria-label="Breadcrumb">
6 Removed: <ol class="breadcrumb mb-0 bg-white border rounded-pill px-3 py-2 small fw-bold">
6 Added: <ol class="breadcrumb mb-0 small">
7 7 % for my $index (0 .. $breadcrumbs->$#*) {
8 8 % my $breadcrumb = $breadcrumbs->[$index];
9 9 % my $is_last = $index == $breadcrumbs->$#*;
10 10 <li class="breadcrumb-item<%= $is_last ? ' active' : '' %>">
11 11 % if (defined $breadcrumb->{href}) {
12 Removed: <a class="text-success text-decoration-none fw-bold" href="<%= $breadcrumb->{href} %>"><%= $breadcrumb->{label} %></a>
12 Added: <a href="<%= $breadcrumb->{href} %>"><%= $breadcrumb->{label} %></a>
13 13 % } else {
14 14 <span aria-current="page"><%= $breadcrumb->{label} %></span>
15 15 % }