Skip to content

feat: act on a host from its row, and fit its details on one screen - #17

Merged
stahnma merged 1 commit into
mainfrom
feat/dashboard-row-actions
Oct 3, 2026
Merged

stahnma merged 1 commit into
mainfrom
feat/dashboard-row-actions

Conversation

@stahnma

@stahnma stahnma commented Oct 3, 2026

Copy link
Copy Markdown
Owner

Every everyday job on the dashboard started with the same detour: expand the host's row, scroll past the system information and the group editor, and find the button at the bottom right of a panel that ran the full width of the table. This moves the common actions onto the row and packs the expanded panel into three columns.

Task Before After
Check in a host 2 clicks + scroll 1
Run updates on a host 2 1
Reboot a host 3 2 (tick, Reboot)
See a host's pending packages 1 on a 10px chevron + scroll 1 anywhere on the row; packages are the first column
Change a host's groups 3 + scroll 1 per group
 ▸ giles  Debian 13  aarch64  192.168.1.139  [UPDATES (5)]  3d 6h  just now   [Check in] [Update] [☐|Reboot]
 ▾ funk   Fedora 44  x86_64   192.53.120.69  [UPDATES (1)]  1d19h  just now   [Check in] [Update] [☐|Reboot]
 ┌─ Pending updates 1 ────────────┬─ Host ───────────────┬─ Groups ──────────────────┐
 │ libssl3  3.0.15  trixie-sec…   │ Client 0.1.13        │ ☑ lab  ☐ media  ☐ dmz     │
 │                                │ CPU    EPYC 7713     │ ◆os:fedora ◆pkg:rpm       │
 │                                │ Cores 2 · RAM 4 GB   │             Remove host   │
 └────────────────────────────────┴──────────────────────┴───────────────────────────┘

Design notes

Row controls are always drawn, and disabled rather than hidden when they cannot act. A control the server does not offer at all is left out, so a fleet without remote updates never sees the column. One the server offers but the host has not opted into, or that has nothing to do, is disabled with the reason on hover. Every row keeps its buttons in the same place, so the column reads downward as "which hosts can I do this to". Hover-only buttons were considered and rejected: invisible on touch, and you cannot scan them.

Update is disabled on a host that reports nothing to install, except when its status is unknown. This is a behaviour change: before, the button was live on any opted-in host.

Reboot keeps its confirmation checkbox, now fused to the button and turning orange when armed. The tick no longer clears when a row collapses, because the control is no longer inside the panel that collapses. It clears when the reboot is sent or the box is unticked. The group-strip reboot keeps its labelled "Confirm reboot" checkbox: it acts on many hosts, so it says so in words.

The whole row opens the details, not just the chevron, which is now a <button> with aria-expanded so the keyboard can reach it. Clicks on row controls, and a click that ends a text selection, are left alone: copying an IP out of a row should not fold it.

One delegated listener for the table. The rows are rebuilt on every check-in from any host, so per-button addEventListener calls had to be re-run on every render. The handlers now take the element rather than the event.

The panel draws from the list payload at once. /api/systems already carries nearly everything the panel shows, so it renders immediately instead of blanking to "Loading…" on every expand. The full record is fetched behind it for the client version, and drawn into whichever panel is on screen when it lands.

Group membership saves on each tick. There's no Save button, and no half-finished edit to lose to a re-render. A host's boxes are held disabled while its save is in flight: a second tick computed from not-yet-refreshed groups would undo the first.

Removed: Expand all / Collapse all (with actions on the row there is little reason to open every panel), and the inline sort script in index.html, which reordered the DOM by hand only for app.js to re-render over it. Delete is now "Remove host", at the foot of the groups column, beside a "Silent for N days" note after a week of silence.

Look

The page is powder-coated steel (petrol in dark, aluminium in light), and status is label-maker tape in the colours real tape comes in: yellow for pending updates, red for security updates (replacing the pulsing badge), white for unknown. "Up to date" is quiet green text, since a healthy host needs no label. Archivo sets labels and Martian Mono sets anything a machine reported. Both are vendored under static/fonts (SIL OFL 1.1, license included), so a dashboard with no internet access still renders as designed. The --accent-* variable names are kept, so inline styles in app.js pick up the new palette. apidoc.html is untouched and keeps its own stylesheet.

Testing

make test and make build pass. No Go code changed.

Driven in headless Chromium (Playwright) against an isolated server: copied DB, throwaway ports, Consul pointed at a dead address. Feature flags and the POST endpoints were mocked. Checked with no page errors:

  • clicking a row expands it,
  • clicking a row button does not,
  • Enter on the chevron expands,
  • Check in switches to "Checking…",
  • ticking reboot arms the button,
  • the panel shows the package list.

Screenshots were taken in dark, light and phone widths.

Also installed on the dev host via ./localtest. The production server serves the new page, reports both features on, has 13 opted-in hosts, and logged no warnings after restart.

Not verified:

  • Check in, update and reboot against real hosts from the new buttons; every request in the browser run was mocked.
  • Save-on-tick for groups; the test DB had no hand-made groups.

Known rough edges:

  • To fit the actions column at 1440px, long OS names like "Fedora Linux 44 (Server Edition)" wrap to two lines.
  • On a phone the table still scrolls sideways, so row actions and the right side of an open panel need a horizontal scroll. A stacked-card layout would be its own change.

Every everyday job on the dashboard started with the same detour:
expand the host's row, scroll past the system information and the group
editor, and find the button at the bottom right of a panel that ran the
full width of the table. Checking a host in took two clicks and a
scroll, a reboot took three, and seeing what a host was waiting to
install meant opening a panel whose package list sat below everything
else in it.

Check in, Update and Reboot now sit on every row. A control the server
does not offer is left out entirely; one the server offers but this host
has not opted into, or that has nothing to do, is drawn disabled with
the reason on hover. Every row keeps its buttons in the same place, so
the column reads downward as "which hosts can I do this to". Update is
disabled on a host that reports nothing to install, except when its
status is unknown. Reboot keeps its confirmation checkbox, now fused to
the button, and the tick no longer clears on collapse because the
control is no longer inside the panel that collapses.

Clicking anywhere on a row opens it, not just the 10px chevron, which
is now a real button so the keyboard can reach it. Controls in the row
and a click that ends a text selection are left alone: copying an
address out of a row should not fold it. All table events go through
one delegated listener, since the rows are rebuilt on every check-in
and per-button listeners had to be re-attached on every render.

The expanded panel is three columns instead of one long stack: pending
packages first (they are the reason to open the row, and a long list
scrolls inside its column), then the host's facts, then its groups. The
heading that repeated the row is gone, and the update-run and reboot
records appear beneath only when there is one. The panel draws at once
from the list payload, which already carries nearly everything in it,
rather than blanking to "Loading..." on every expand; the full record is
fetched behind it for the client version. Group membership saves as
each box is ticked, so there is no Save button and no half-finished
edit to lose to a re-render. Delete becomes "Remove host" at the foot of
the groups column, beside a "Silent for N days" note when that is the
likely reason to reach for it.

Expand all and Collapse all are gone: with the actions on the row there
is little reason to open every panel at once. So is the inline sort
script in index.html, which reordered the DOM by hand only for app.js
to re-render over it a moment later.

The look changes with it. The page is powder-coated steel, and status
is label-maker tape in the colours real tape comes in: yellow for
pending updates, red for security updates (replacing the pulsing
badge), white for unknown. "Up to date" is quiet text, since a healthy
host needs no label. Archivo sets the labels and Martian Mono sets
anything a machine reported. Both are vendored under static/fonts (SIL
OFL 1.1) so a dashboard on a network with no internet still renders as
designed.
@stahnma
stahnma merged commit 58f7a6d into main Oct 3, 2026
1 check passed
@stahnma
stahnma deleted the feat/dashboard-row-actions branch October 3, 2026 18:02
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant