[Perl] DAQ system for the FAPG.
feat rework status page nodes
Replace the status table with probe-grouped node sequences and live state markers. Add narrow and wide page containers, placing the toolbar within each page container. Update dashboard page coverage for the new status and layout structure.
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/00-homepage.t
- roles/dashboard/t/01-graph-page.t
- roles/dashboard/t/05-status-page.t
- roles/dashboard/templates/dashboard/status.html.ep
- roles/dashboard/templates/layouts/default.html.ep
roles/dashboard/lib/FAPG/DAQ/Dashboard/Controller/Dashboard.pm
@@ -25,9 +25,10 @@
25
25
26
26
sub probe_list ($self) {
27
27
$self->render(
28
Removed:
template => 'dashboard/probes',
29
Removed:
probes => $self->probes,
30
Removed:
breadcrumbs =>
28
Added:
template => 'dashboard/probes',
29
Added:
probes => $self->probes,
30
Added:
container_class => 'container-wide',
31
Added:
breadcrumbs =>
31
32
[ { label => 'Home', href => '/' }, { label => 'Probes' }, ],
32
33
graph_control_aria_label => 'All probe chart timeframes',
33
34
);
roles/dashboard/public/css/app.css
@@ -27,11 +27,20 @@
27
27
color: var(--text);
28
28
}
29
29
30
Removed:
.site-main {
30
Added:
.container,
31
Added:
.container-wide {
31
32
width: calc(100% - 2rem);
32
33
margin: 0 auto 2rem;
33
34
}
34
35
36
Added:
.container {
37
Added:
max-width: 56rem;
38
Added:
}
39
Added:
40
Added:
.container-wide {
41
Added:
max-width: 100rem;
42
Added:
}
43
Added:
35
44
.site-footer {
36
45
width: min(1100px, calc(100% - 2rem));
37
46
margin: 0 auto 2rem;
@@ -72,8 +81,8 @@
72
81
align-items: center;
73
82
justify-content: space-between;
74
83
gap: 1rem;
75
Removed:
width: calc(100% - 2rem);
76
Removed:
margin: 0 auto 1rem;
84
Added:
width: 100%;
85
Added:
margin: 0 0 1rem;
77
86
}
78
87
79
88
.breadcrumbs {
@@ -1011,15 +1020,122 @@
1011
1020
border-bottom: 0;
1012
1021
}
1013
1022
1014
Removed:
.status-table {
1015
Removed:
min-width: 760px;
1023
Added:
.status-probe-groups {
1024
Added:
display: grid;
1025
Added:
gap: 1.5rem;
1016
1026
}
1017
1027
1018
Removed:
.status-table .status-pill {
1028
Added:
.status-probe-group h2 {
1029
Added:
margin: 0 0 0.75rem;
1030
Added:
font-size: 1rem;
1031
Added:
}
1032
Added:
1033
Added:
.status-node-sequence {
1034
Added:
margin: 0;
1035
Added:
padding: 0;
1036
Added:
list-style: none;
1037
Added:
}
1038
Added:
1039
Added:
.status-node {
1040
Added:
position: relative;
1041
Added:
display: grid;
1042
Added:
grid-template-columns: 1.25rem minmax(0, 1fr);
1043
Added:
gap: 0.75rem;
1044
Added:
}
1045
Added:
1046
Added:
.status-node + .status-node {
1047
Added:
margin-top: 0.75rem;
1048
Added:
}
1049
Added:
1050
Added:
.status-node + .status-node::before {
1051
Added:
position: absolute;
1052
Added:
top: -0.75rem;
1053
Added:
bottom: calc(100% - 0.75rem);
1054
Added:
left: 0.55rem;
1055
Added:
width: 0.15rem;
1056
Added:
border-radius: 999px;
1057
Added:
background: var(--border);
1058
Added:
content: "";
1059
Added:
}
1060
Added:
1061
Added:
.status-node-marker {
1062
Added:
z-index: 1;
1063
Added:
width: 1.25rem;
1064
Added:
height: 1.25rem;
1065
Added:
margin-top: 1rem;
1066
Added:
border: 0.3rem solid var(--unknown-soft);
1067
Added:
border-radius: 50%;
1068
Added:
background: var(--unknown);
1069
Added:
}
1070
Added:
1071
Added:
.status-node-marker.is-online,
1072
Added:
.status-node-marker.is-healthy {
1073
Added:
border-color: var(--accent-soft);
1074
Added:
background: var(--accent);
1075
Added:
}
1076
Added:
1077
Added:
.status-node-marker.is-stale,
1078
Added:
.status-node-marker.is-probe-error {
1079
Added:
border-color: var(--warn-soft);
1080
Added:
background: var(--warn);
1081
Added:
}
1082
Added:
1083
Added:
.status-node-marker.is-offline,
1084
Added:
.status-node-marker.is-unreachable {
1085
Added:
border-color: var(--danger-soft);
1086
Added:
background: var(--danger);
1087
Added:
}
1088
Added:
1089
Added:
.status-node-content {
1090
Added:
min-width: 0;
1091
Added:
border: 1px solid var(--border);
1092
Added:
border-radius: 0.5rem;
1093
Added:
padding: 1rem;
1094
Added:
background: #fafcfb;
1095
Added:
}
1096
Added:
1097
Added:
.status-node-summary {
1098
Added:
display: flex;
1099
Added:
flex-wrap: wrap;
1100
Added:
align-items: center;
1101
Added:
justify-content: space-between;
1102
Added:
gap: 0.75rem;
1103
Added:
}
1104
Added:
1105
Added:
.status-node-name {
1106
Added:
min-width: 0;
1107
Added:
margin: 0;
1108
Added:
overflow-wrap: anywhere;
1109
Added:
font-weight: 700;
1110
Added:
}
1111
Added:
1112
Added:
.status-node-summary .status-pill {
1019
1113
min-width: 7rem;
1020
1114
padding-block: 0.3rem;
1021
1115
}
1022
1116
1117
Added:
.status-node-details {
1118
Added:
display: grid;
1119
Added:
grid-template-columns: minmax(0, 1fr) minmax(0, 2fr);
1120
Added:
gap: 0.75rem 1rem;
1121
Added:
margin: 1rem 0 0;
1122
Added:
}
1123
Added:
1124
Added:
.status-node-details div {
1125
Added:
min-width: 0;
1126
Added:
}
1127
Added:
1128
Added:
.status-node-details dt {
1129
Added:
color: var(--muted);
1130
Added:
font-size: 0.78rem;
1131
Added:
font-weight: 700;
1132
Added:
}
1133
Added:
1134
Added:
.status-node-details dd {
1135
Added:
margin: 0.25rem 0 0;
1136
Added:
overflow-wrap: anywhere;
1137
Added:
}
1138
Added:
1023
1139
@media (max-width: 900px) {
1024
1140
.reading-quick-grid {
1025
1141
grid-template-columns: repeat(2, minmax(0, 1fr));
@@ -1027,7 +1143,8 @@
1027
1143
}
1028
1144
1029
1145
@media (max-width: 700px) {
1030
Removed:
.site-main {
1146
Added:
.container,
1147
Added:
.container-wide {
1031
1148
width: min(100% - 1rem, 1100px);
1032
1149
margin: 0 auto 1rem;
1033
1150
}
@@ -1040,7 +1157,7 @@
1040
1157
.toolbar {
1041
1158
flex-direction: column;
1042
1159
align-items: stretch;
1043
Removed:
width: min(100% - 1rem, 1100px);
1160
Added:
width: 100%;
1044
1161
margin-bottom: 1rem;
1045
1162
}
1046
1163
@@ -1208,6 +1325,16 @@
1208
1325
padding-inline: 0.65rem;
1209
1326
text-align: center;
1210
1327
white-space: normal;
1328
Added:
}
1329
Added:
1330
Added:
.status-node-summary .status-pill {
1331
Added:
width: auto;
1332
Added:
min-height: 0;
1333
Added:
}
1334
Added:
1335
Added:
.status-node-details {
1336
Added:
grid-template-columns: 1fr;
1337
Added:
gap: 0.75rem;
1211
1338
}
1212
1339
1213
1340
.download-range-control {
roles/dashboard/public/js/dashboard/status.js
@@ -118,9 +118,11 @@
118
118
const state = stateForNodeStatus(status);
119
119
const timestamp = status?.received_at || status?.timestamp;
120
120
const stateElement = row.querySelector("[data-status-page-state]");
121
Added:
const marker = row.querySelector(".status-node-marker");
121
122
122
123
stateElement.className = `status-pill is-${state}`;
123
124
stateElement.textContent = statusLabel(state);
125
Added:
marker.className = `status-node-marker is-${state}`;
124
126
row.querySelector("[data-status-page-node]").textContent = status?.node || "Unknown";
125
127
row.querySelector("[data-status-page-last-seen]").textContent = timestamp
126
128
? `${formatDateTime(timestamp)} (${formatAge(ageMs(timestamp))})`
roles/dashboard/t/00-homepage.t
@@ -14,7 +14,7 @@
14
14
$t->get_ok('/')
15
15
->status_is(200)
16
16
->element_exists(
17
Removed:
'body > header.page-header + div.toolbar + main.site-main')
17
Added:
'body > header.page-header + main.container > div.toolbar')
18
18
->text_is( 'a.page-brand[href="/"]', 'FAPG' )
19
19
->text_is( 'div.nav-links a[href="/probes"]', 'Probes' )
20
20
->text_is( 'div.nav-links a[href="/downloads"]', 'Downloads' )
roles/dashboard/t/01-graph-page.t
@@ -43,6 +43,7 @@
43
43
->status_is(200)
44
44
->text_is( 'nav.breadcrumbs a[href="/"]', 'Home' )
45
45
->text_is( 'nav.breadcrumbs [aria-current="page"]', 'Probes' )
46
Added:
->element_exists('main.container-wide')
46
47
->element_exists('div.probes-grid')
47
48
->element_exists('div.toolbar-items div.graph-control');
48
49
roles/dashboard/t/05-status-page.t
@@ -16,12 +16,12 @@
16
16
->text_is( 'nav.breadcrumbs a[href="/"]', 'Home' )
17
17
->text_is( 'nav.breadcrumbs [aria-current="page"]', 'Status' )
18
18
->text_is( 'h1', 'DAQ Status' )
19
Removed:
->element_exists('table.status-table')
20
Removed:
->element_exists('table.status-table thead th[scope="col"]');
19
Added:
->element_exists('main.container')
20
Added:
->element_exists('[data-status-page-groups]');
21
21
22
22
for my $item (qw(hub ph do orp ec)) {
23
23
$t->element_exists(
24
Removed:
qq{tr[data-status-page-item="$item"] [data-status-page-state]});
24
Added:
qq{[data-status-page-probe="$item"] [data-status-page-item="$item"] [data-status-page-state]});
25
25
}
26
26
27
27
done_testing;
roles/dashboard/templates/dashboard/status.html.ep
@@ -12,29 +12,36 @@
12
12
</div>
13
13
14
14
<div class="card-body">
15
Removed:
<div class="message-table-wrap">
16
Removed:
<table class="message-table status-table">
17
Removed:
<thead>
18
Removed:
<tr>
19
Removed:
<th scope="col">Probe</th>
20
Removed:
<th scope="col">Node</th>
21
Removed:
<th scope="col">Status</th>
22
Removed:
<th scope="col">Last seen online</th>
23
Removed:
<th scope="col">Message</th>
24
Removed:
</tr>
25
Removed:
</thead>
26
Removed:
<tbody data-status-page-rows>
27
Removed:
% for my $item ($items->@*) {
28
Removed:
<tr data-status-page-item="<%= $item->{key} %>">
29
Removed:
<td data-status-page-label><%= $item->{label} %></td>
30
Removed:
<td data-status-page-node>Checking…</td>
31
Removed:
<td><span class="status-pill is-unknown" data-status-page-state>Checking</span></td>
32
Removed:
<td data-status-page-last-seen>Checking…</td>
33
Removed:
<td data-status-page-message>Checking status…</td>
34
Removed:
</tr>
35
Removed:
% }
36
Removed:
</tbody>
37
Removed:
</table>
15
Added:
<div class="status-probe-groups" data-status-page-groups>
16
Added:
% for my $item ($items->@*) {
17
Added:
<section
18
Added:
class="status-probe-group"
19
Added:
data-status-page-probe="<%= $item->{key} %>"
20
Added:
aria-labelledby="status-probe-<%= $item->{key} %>">
21
Added:
<h2 id="status-probe-<%= $item->{key} %>" data-status-page-label><%= $item->{label} %></h2>
22
Added:
<ol class="status-node-sequence" aria-label="<%= $item->{label} %> nodes">
23
Added:
<li class="status-node" data-status-page-item="<%= $item->{key} %>">
24
Added:
<span class="status-node-marker is-unknown" aria-hidden="true"></span>
25
Added:
<div class="status-node-content">
26
Added:
<div class="status-node-summary">
27
Added:
<p class="status-node-name" data-status-page-node>Checking…</p>
28
Added:
<span class="status-pill is-unknown" data-status-page-state>Checking</span>
29
Added:
</div>
30
Added:
<dl class="status-node-details">
31
Added:
<div>
32
Added:
<dt>Last status</dt>
33
Added:
<dd data-status-page-last-seen>Checking…</dd>
34
Added:
</div>
35
Added:
<div>
36
Added:
<dt>Message</dt>
37
Added:
<dd data-status-page-message>Checking status…</dd>
38
Added:
</div>
39
Added:
</dl>
40
Added:
</div>
41
Added:
</li>
42
Added:
</ol>
43
Added:
</section>
44
Added:
% }
38
45
</div>
39
46
</div>
40
47
</div>
roles/dashboard/templates/layouts/default.html.ep
@@ -39,8 +39,8 @@
39
39
data-header-status-label="<%= $item->{label} %>"></span>
40
40
% }
41
41
</header>
42
Removed:
%= include 'dashboard/toolbar', breadcrumbs => (stash('breadcrumbs') // []), graph_control_aria_label => stash('graph_control_aria_label'), show_overview_status => stash('show_overview_status')
43
Removed:
<main class="site-main">
42
Added:
<main class="<%= stash('container_class') // 'container' %>">
43
Added:
%= include 'dashboard/toolbar', breadcrumbs => (stash('breadcrumbs') // []), graph_control_aria_label => stash('graph_control_aria_label'), show_overview_status => stash('show_overview_status')
44
44
<%= content %>
45
45
</main>
46
46
<footer class="site-footer">