Skip to content

Commit 08d7e2f

Browse files
wildleo91claude
andcommitted
feat(ui): button taxonomy unification + audit dashboard polish (build 632)
Three user-reported issues triggered a wider audit. Root cause was structural: codebase had two button-class taxonomies coexisting and neither was fully styled. Splunk's bundled CSS ships .btn and .btn-primary only; .btn-success / .btn-danger / .btn-warning fall back to plain grey. The custom .wl-btn / .wl-btn-primary / .wl-btn-danger had no CSS rules at all beyond .wl-btn-locked (opacity helper). Result: every Approve/Reject/Remove/Purge button rendered the same grey as Cancel. - Add .btn-success / .btn-danger / .btn-warning rules with hover + focus + disabled states; add focus ring for all .btn variants; lift disabled-state contrast from ~1.3:1 to readable opacity 0.55. - Migrate 9 .wl-btn sites in control_panel.js to .btn taxonomy; preserve only .wl-btn-locked (used by approval-lock UX). - Cap Expiring Soon panel at max-height 400px (was auto-extending to ~14k px); page is now 86% shorter. - Fix empty-state inconsistency on 7 single-value audit panels via | append [| makeresults | eval x=0] | stats max(x) postlude. - Add visual separator between [Add/Edit/Destroy] and [Persist] groups on WM main toolbar via #btn-save::before. - Add .wl-link default colour + hover (was inheriting muted parent). - Update 1 E2E selector assertion + add header comment to QUnit fixtures documenting they don't mirror production class names. - CHANGELOG entry under Unreleased; CLAUDE.md Decision Log entry 2026-05-01 ("Kill wl-btn taxonomy entirely"); L3 span->button migration deferred to its own PR with full plan documented. Closes the 3 user-reported issues (Activate Emergency Lockdown visibility, Trash Change link contrast, Admin/Analyst Settings button mismatch) plus all wider audit findings except L3 (deferred) and L1/L2 (cosmetic, follow-up). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent c31fc4f commit 08d7e2f

8 files changed

Lines changed: 275 additions & 18 deletions

File tree

CHANGELOG.md

Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,127 @@ Detailed per-round entries below.
6464

6565
---
6666

67+
## Unreleased — 2026-05-01 (build 632, UI consistency sweep)
68+
69+
### UI consistency: button taxonomy + audit dashboard polish (builds 631-632)
70+
71+
Three user-reported issues triggered a wider audit of every page,
72+
dropdown, modal, and form. The root cause was structural: the codebase
73+
had two button-class taxonomies coexisting, and **neither was fully
74+
styled**. Splunk's bundled CSS ships rules for `.btn` and `.btn-primary`
75+
only — `.btn-success`, `.btn-danger`, and `.btn-warning` silently fall
76+
back to plain `.btn` grey. The custom `.wl-btn` / `.wl-btn-primary` /
77+
`.wl-btn-danger` classes had **no CSS rules at all** beyond
78+
`.wl-btn-locked` (an opacity helper). Result: every "Approve / Reject /
79+
Remove / Purge" button across the app rendered the same grey as
80+
"Cancel" — destructive actions had no colour signal.
81+
82+
Fixed by (a) defining `.btn-success`, `.btn-danger`, `.btn-warning`
83+
with hover + focus + disabled states in `whitelist_manager.css`, and
84+
(b) migrating the 9 `wl-btn` sites in `control_panel.js` to the
85+
Splunk-bundled `btn` taxonomy. Custom `.wl-btn-locked` is preserved
86+
(it's the approval-lock opacity helper, used by `wl_table.js` and
87+
`wl_approval_ui.js`). See Decision Log entry 2026-05-01 for the
88+
"kill `wl-btn` taxonomy entirely" rationale.
89+
90+
#### Fixed — user-reported
91+
92+
- **"Activate Emergency Lockdown"** in Control Panel header now
93+
renders as a proper red button instead of 12 px red plain text on
94+
the page background. The single most consequential action in the
95+
app is now visually appropriate.
96+
- **"Change" retention link** on the Trash tab now uses the
97+
`.wl-link` rule's new default accent colour (was inheriting muted
98+
grey from parent).
99+
- **Admin Settings "Save Changes" / "Reset to Defaults"** now match
100+
Analyst Settings visually (green primary + neutral grey). Both
101+
pages were previously using completely different button systems.
102+
103+
#### Fixed — surfaced by the wider audit
104+
105+
- **Whitelist Manager "Save Changes"** (`btn-success`) renders green
106+
for the first time. Previously rendered grey, identical to
107+
"Discard Changes".
108+
- **Row-level "Remove" button** in the Actions column now red
109+
(`btn-danger` rule added). Previously grey, identical to "Export CSV".
110+
- **Modal "Approve" / "Reject"** now green / red. Previously both grey.
111+
- **CSV Import "Replace"** button (destructive) now red. Was grey.
112+
- **Disabled-state contrast** on `btn-primary` / `btn-success` /
113+
`btn-danger` / `btn-warning` lifted from ~1.3:1 (failed WCAG AA)
114+
to a uniform `opacity: 0.55` so the colour identity stays readable
115+
while still signalling disabled.
116+
- **`.wl-link` default colour** added — sites without an inline
117+
`color` attribute (conflict-reload link in `wl_save.js`, presence
118+
hooks in `wl_presence.js`, nav links in `wl_nav.js`) are now
119+
readable on dark theme.
120+
- **Keyboard-focus ring** added for all `.btn` variants. `<span
121+
class="btn">` elements previously had no focus indication; they
122+
now show the same accent outline as `<button>` / `<a>` variants.
123+
(Note: `<span class="btn">` are still NOT keyboard-tab-able —
124+
see CLAUDE.md "Pending / Future Work" for the L3 span→button
125+
migration plan.)
126+
127+
#### Fixed — Audit Trail dashboard layout
128+
129+
- **"Expiring Soon" panel** capped at `max-height: 400px` with
130+
internal scroll. Previously auto-extended to ~14,000 px on
131+
dashboards with many expiring rows, pushing the rest of the page
132+
off-screen and producing a 17,850-px-tall dashboard. Page is now
133+
~2,600 px after the cap (86% reduction). The Splunk SimpleXML
134+
`<option name="height">` is ignored on table panels so the cap is
135+
CSS-side via `#expiring_soon_table { max-height: 400px; ... }`.
136+
- **Empty-state inconsistency** fixed across 7 single-value panels
137+
(Rows Added / Removed / Auto-Removed / Edited / Columns Added /
138+
Removed / Renamed). They previously showed "No results found." on
139+
empty input because `stats sum(...)` produces zero rows when no
140+
events match the `where action=X` filter. Now post-fixed with
141+
`| append [| makeresults count=1 | eval x=0] | stats max(x) as x`
142+
so the panels always render `0` when empty, matching the
143+
`stats count` panels next to them.
144+
145+
#### Polish
146+
147+
- **Visual separator** between [Add/Edit/Destroy] group and
148+
[Save/Discard persist] group on the WM main action bar
149+
(`#btn-save::before` thin border). The two semantic groups
150+
previously sat in one undifferentiated row of buttons.
151+
- **`urlArgs: "_b=632"`** in `whitelist_manager.js` keeps cache-bust
152+
in sync with `app.conf [install] build = 632` per the CLAUDE.md
153+
maintenance rule.
154+
155+
#### Tests touched
156+
157+
- `tests/e2e/test_admin_limits.cjs` — assertion `saveClass.includes("wl-btn")`
158+
updated to `saveClass.includes("btn-primary")` since the migrated
159+
Save Changes button no longer carries the legacy class.
160+
- `tests/qunit/test_wl_modals.js` — header comment added
161+
documenting that the test fixtures use synthetic `wl-btn-*`
162+
identifiers that DO NOT mirror production class names. The
163+
fixtures are self-contained and tests still pass; the comment
164+
prevents future-reader confusion.
165+
166+
#### Deferred to separate work
167+
168+
- **`<span class="btn">``<button class="btn">` accessibility
169+
migration** (audit finding L3). Visible buttons are tab-focusable
170+
via the new focus ring, but `<span>` still doesn't receive
171+
Splunk's tab traversal or ARIA "button" role. Doing this right
172+
requires unifying three different "disabled" patterns
173+
(`<button disabled>`, inline `opacity:0.5;pointer-events:none`,
174+
`wl-btn-locked` class) into one. See CLAUDE.md "Pending / Future
175+
Work" for the full scope.
176+
- **Sigstore E2E verification** — already on the Pending list,
177+
unchanged.
178+
179+
#### Migration / rollback
180+
181+
CSS-only and class-rename changes; click handlers find by `#id` not
182+
class. Rollback: `git revert` the build-631 + build-632 commits and
183+
redeploy at the next build number. The `.wl-btn-locked` class is
184+
preserved.
185+
186+
---
187+
67188
## Unreleased — 2026-04-29 (build 629, no app changes)
68189

69190
### Round 9: housekeeping — doc-drift, dead artifacts, PR-time anti-pattern gating

appserver/static/control_panel.js

Lines changed: 11 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -190,7 +190,7 @@ require([
190190
'<p style="font-size:18px;font-weight:bold;">Error loading Control Panel</p>' +
191191
'<p>The server may be restarting or temporarily unavailable. ' +
192192
'Please try refreshing the page.</p>' +
193-
'<span class="wl-btn wl-btn-primary" style="margin-top:10px;' +
193+
'<span class="btn btn-primary" style="margin-top:10px;' +
194194
'cursor:pointer;" onclick="location.reload()">Refresh</span></div>'
195195
);
196196
}
@@ -319,9 +319,8 @@ require([
319319
'</span></div>';
320320
// Deactivate button — only if superadmin AND not the one who locked
321321
if (cpIsSuperAdmin && lockdown.locked_by !== cpUser) {
322-
html += '<span class="wl-btn" id="wl-cp-deactivate-lockdown" ' +
323-
'style="cursor:pointer;background:#fff;color:#c0392b;' +
324-
'font-weight:600;white-space:nowrap">' +
322+
html += '<span class="btn btn-primary" id="wl-cp-deactivate-lockdown" ' +
323+
'style="cursor:pointer;white-space:nowrap">' +
325324
'Deactivate Lockdown</span>';
326325
}
327326
html += '</div>';
@@ -350,9 +349,8 @@ require([
350349
if (cpIsSuperAdmin) {
351350
$banner.html(
352351
'<div style="margin-bottom:8px;text-align:right">' +
353-
'<span class="wl-btn" id="wl-cp-activate-lockdown" ' +
354-
'style="cursor:pointer;color:#c0392b;border-color:#c0392b;' +
355-
'font-size:12px">Activate Emergency Lockdown</span></div>');
352+
'<span class="btn btn-danger" id="wl-cp-activate-lockdown" ' +
353+
'style="cursor:pointer">Activate Emergency Lockdown</span></div>');
356354

357355
$("#wl-cp-activate-lockdown").on("click", function () {
358356
showCpPrompt("Activate Emergency Lockdown",
@@ -2406,12 +2404,12 @@ require([
24062404
_.escape((item.comment || "").substring(0, 60)) +
24072405
(item.comment && item.comment.length > 60 ? "..." : "") + '</td>' +
24082406
'<td>' +
2409-
'<span class="wl-btn wl-btn-primary wl-trash-restore" ' +
2407+
'<span class="btn btn-primary wl-trash-restore" ' +
24102408
'data-trash-id="' + tid + '" style="cursor:pointer;margin-right:4px">' +
24112409
'Restore</span>';
24122410

24132411
if (cpIsSuperAdmin) {
2414-
html += '<span class="wl-btn wl-btn-danger wl-trash-purge" ' +
2412+
html += '<span class="btn btn-danger wl-trash-purge" ' +
24152413
'data-trash-id="' + tid + '" data-name="' +
24162414
_.escape(item.name || "") + '" style="cursor:pointer">' +
24172415
'Request Purge</span>';
@@ -2770,9 +2768,9 @@ require([
27702768

27712769
// ── Buttons ──
27722770
html += '<div style="margin-top:16px;display:flex;align-items:center;gap:10px">' +
2773-
'<span class="wl-btn wl-btn-primary" id="wl-save-admin-limits" ' +
2771+
'<span class="btn btn-primary" id="wl-save-admin-limits" ' +
27742772
'style="cursor:pointer">Save Changes</span> ' +
2775-
'<span class="wl-btn" id="wl-reset-admin-limits" ' +
2773+
'<span class="btn" id="wl-reset-admin-limits" ' +
27762774
'style="cursor:pointer">Reset to Defaults</span>' +
27772775
'<span id="wl-cp-admin-limits-msg" style="display:none;font-size:13px;' +
27782776
'font-weight:500;margin-left:8px"></span>' +
@@ -3004,9 +3002,9 @@ require([
30043002
'border:1px solid var(--wl-border,#444);border-radius:4px;margin-bottom:12px;' +
30053003
'box-sizing:border-box">' +
30063004
'<div style="text-align:right">' +
3007-
'<span class="wl-btn" id="wl-cp-prompt-cancel" style="cursor:pointer;margin-right:8px">' +
3005+
'<span class="btn" id="wl-cp-prompt-cancel" style="cursor:pointer;margin-right:8px">' +
30083006
'Cancel</span>' +
3009-
'<span class="wl-btn wl-btn-primary" id="wl-cp-prompt-ok" style="cursor:pointer">' +
3007+
'<span class="btn btn-primary" id="wl-cp-prompt-ok" style="cursor:pointer">' +
30103008
'OK</span></div></div></div>';
30113009

30123010
$("body").append(html);

appserver/static/whitelist_manager.css

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -514,6 +514,21 @@ body.wl-resizing * {
514514
margin-left: auto;
515515
}
516516

517+
/* Visual separator between the [Add/Edit/Destroy] group and the
518+
* [Save/Discard persist] group on the WM main action bar. The two
519+
* groups previously sat in one undifferentiated row with colour as
520+
* the only signal of role. The separator + extra margin establishes
521+
* a clear semantic boundary without restructuring the HTML.
522+
* (See 2026-04-30 UI consistency audit, finding M4-M5.) */
523+
.wl-buttons #btn-save::before {
524+
content: "";
525+
display: inline-block;
526+
border-left: 1px solid var(--wl-border-section);
527+
margin-right: 12px;
528+
height: 22px;
529+
vertical-align: middle;
530+
}
531+
517532
.wl-buttons-right .btn {
518533
margin-right: 0;
519534
margin-left: 8px;
@@ -1051,6 +1066,16 @@ body.wl-dark > .wl-modal-overlay {
10511066
cursor: pointer;
10521067
text-decoration: underline;
10531068
font-weight: bold;
1069+
/* Default to the existing accent token so wl-link sites without an
1070+
* inline color attribute (e.g. the conflict-reload link in
1071+
* wl_save.js, presence indicators in wl_presence.js) stay readable
1072+
* on dark theme. Trash "Change" still has its own inline colour
1073+
* which wins via specificity. (See 2026-04-30 UI consistency audit.) */
1074+
color: var(--wl-accent, #2962ff);
1075+
}
1076+
.wl-link:hover {
1077+
color: #5b8dff;
1078+
text-decoration: none;
10541079
}
10551080

10561081
.wl-kbd {
@@ -1253,6 +1278,88 @@ th.wl-pending-approval-header {
12531278
pointer-events: none;
12541279
}
12551280

1281+
/* ── Button colour rules ───────────────────────────────────────────────
1282+
* Splunk's bundled CSS only ships .btn and .btn-primary. .btn-success,
1283+
* .btn-danger, and .btn-warning silently fall back to .btn grey without
1284+
* these rules. Without them, every "Approve / Reject / Remove / Purge"
1285+
* button in the app rendered the same grey as Cancel — destructive
1286+
* actions had no colour signal. (See 2026-04-30 UI consistency audit
1287+
* and Decision Log entry.)
1288+
*
1289+
* Specificity is `.btn.btn-X` so these override the base .btn rule but
1290+
* not inline style overrides. Hover/disabled states are explicit so
1291+
* disabled doesn't fall back to the failing-AA Splunk default.
1292+
*/
1293+
.btn.btn-success {
1294+
background-color: #1a8929;
1295+
color: #fff;
1296+
border: 1px solid #146a20;
1297+
}
1298+
.btn.btn-success:hover:not([disabled]):not(.wl-btn-locked) {
1299+
background-color: #20a532;
1300+
border-color: #1a8929;
1301+
}
1302+
1303+
.btn.btn-danger {
1304+
background-color: #c62828;
1305+
color: #fff;
1306+
border: 1px solid #8e1d1d;
1307+
}
1308+
.btn.btn-danger:hover:not([disabled]):not(.wl-btn-locked) {
1309+
background-color: #d83232;
1310+
border-color: #c62828;
1311+
}
1312+
1313+
.btn.btn-warning {
1314+
background-color: #e65100;
1315+
color: #fff;
1316+
border: 1px solid #b34000;
1317+
}
1318+
.btn.btn-warning:hover:not([disabled]):not(.wl-btn-locked) {
1319+
background-color: #f57c00;
1320+
border-color: #e65100;
1321+
}
1322+
1323+
/* Disabled-state contrast fix.
1324+
* Splunk's default .btn:disabled keeps full bg colour but lowers text
1325+
* to ~1.3:1 contrast (fails WCAG AA). For the buttons that signal
1326+
* danger / success / primary action, drop overall opacity instead so
1327+
* the colour identity stays readable. (See 2026-04-30 audit.)
1328+
*/
1329+
.btn.btn-primary[disabled],
1330+
.btn.btn-primary.wl-btn-locked,
1331+
.btn.btn-success[disabled],
1332+
.btn.btn-success.wl-btn-locked,
1333+
.btn.btn-danger[disabled],
1334+
.btn.btn-danger.wl-btn-locked,
1335+
.btn.btn-warning[disabled],
1336+
.btn.btn-warning.wl-btn-locked {
1337+
opacity: 0.55;
1338+
cursor: not-allowed;
1339+
}
1340+
1341+
/* Focus ring for keyboard navigation — applies to all .btn variants
1342+
* including <span class="btn"> elements that don't get the browser
1343+
* default focus outline. Uses the existing accent token so light/dark
1344+
* themes match. */
1345+
.btn:focus-visible {
1346+
outline: 2px solid #2962ff;
1347+
outline-offset: 2px;
1348+
}
1349+
1350+
/* ── Audit Trail "Expiring Soon" height cap ────────────────────────────
1351+
* Splunk SimpleXML <option name="height"> is ignored on table panels,
1352+
* so we cap the panel via CSS instead. Without the cap the table
1353+
* auto-extends to ~14000 px on dashboards with many expiring rows
1354+
* (5+ pages of 10), pushing the rest of the page off-screen. The
1355+
* inner scroll keeps every row reachable while restoring page balance.
1356+
* (See 2026-04-30 UI consistency audit.)
1357+
*/
1358+
#expiring_soon_table {
1359+
max-height: 400px;
1360+
overflow-y: auto;
1361+
}
1362+
12561363
/* ── Inline approval action bar (admin only) ─────────────────────── */
12571364
.wl-approval-bar {
12581365
margin: 8px 0;

appserver/static/whitelist_manager.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
// disk cache. Splunk serves /static/@<server-hash>/... with Cache-Control:
1212
// public, max-age=31536000; without urlArgs, bumped build numbers don't force
1313
// a re-fetch and clients run stale JS until they hard-refresh.
14-
require.config({ urlArgs: "_b=629" });
14+
require.config({ urlArgs: "_b=632" });
1515
require([
1616
"jquery",
1717
"underscore",

default/app.conf

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55

66
[install]
77
is_configured = false
8-
build = 629
8+
build = 632
99

1010
[launcher]
1111
author = Security Engineering

0 commit comments

Comments
 (0)