Skip to content

fix(guest-agent): tighten the dashboard layout - #1186

Merged
kvinwang merged 2 commits into
nextfrom
fix/dashboard-compact-layout
Sep 7, 2026
Merged

fix(guest-agent): tighten the dashboard layout#1186
kvinwang merged 2 commits into
nextfrom
fix/dashboard-compact-layout

Conversation

@kvinwang

@kvinwang kvinwang commented Sep 7, 2026

Copy link
Copy Markdown
Collaborator

Layout only. No data, no template logic, no Rust — one file, templates/dashboard.html, and only its <style> block plus two wrapper elements.

Based on feat/guest-agent-gpu-observability (#1178) rather than next, because both change this file and one of the two tables being wrapped is the GPU table that PR adds. It carries no behaviour from #1178.

Every value sat inside three stacked surfaces

.info-section    white card, rounded, shadow
└─ .info-row     grey #f8f9fa, rounded, padding 4px
   └─ .info-value  white, 1px border, rounded, padding 6px

Three backgrounds to display one string. The innermost reads as a disabled text input — a claim the page cannot honour, since none of these values are editable. The card stays; the two inner boxes become a hairline between rows.

With rows separating themselves the 12px .info-grid gap is redundant. line-height: 1.6 is a prose setting on a page that is entirely single-line data. Headings were spending about seventy pixels each on margins.

A row goes from roughly 60px to 34px, about 43%. Over the thirteen rows a GPU host renders that is around 330px, most of a screenful.

Tables had the opposite problem in the same place

padding: 15px made rows tall while the columns stayed too narrow to hold an identifier, so a single GPU row wrapped into three lines of text: the UUID broke across two, the wattage broke between the number and the unit. Wasted vertically, starved horizontally.

Cells shrink to 7px 10px, td stops wrapping, and the table scrolls sideways when it has to.

The scrolling goes on a wrapper, deliberately

overflow-x on the <table> itself requires display: block, which drops the table formatting context. thead and tbody then become independent anonymous table boxes and compute their widths separately, so the header bar and the body rows disagree about where the right edge is, and width: 100% stops applying. I tried it that way first and it is visibly broken — the dark header of the containers table shrinks to its own content while the card behind it stays full width.

.table-scroll also takes over the rounded corners and the shadow, which lets the four border-top-left-radius / tr:last-child td:last-child rules go. Corners belong to the box that clips, not to individual cells. Net deletion.

Not in this PR

Two things visible on the same screen that are defects rather than layout, kept separate:

  • System Uptime renders 68. SystemInfo.uptime is uint64 seconds from System::uptime() and the template prints it raw, so the page shows a bare number with no unit.
  • Load Average renders 1min: 0.4%. Load average is not a percentage; the template divides by 100 and appends %.

Happy to fold either in if you would rather not have a third PR.

Validation

138 tests, cargo fmt --check, cargo clippy -p dstack-guest-agent --all-targets -- -D warnings all clean. Rendered against the live page from a GPU CVM on an H200 host and compared before/after in a browser at desktop and narrow widths.

Copilot AI lite review requested due to automatic review settings September 7, 2026 04:10

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

The reduced table cell padding will make the “View Logs” row noticeably taller due to the existing global link padding, undermining the intended table compaction.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Pull request overview

This PR tightens the dstack-guest-agent dashboard’s visual density by simplifying the “info” rows and making the GPU/containers tables more compact and horizontally scrollable when needed.

Changes:

  • Reduced vertical spacing across the page (body padding/leading, heading sizing/margins, info-section padding, and info-row styling).
  • Introduced a .table-scroll wrapper to handle horizontal overflow without breaking table layout, and compacted table cell padding + font sizing.
  • Removed the nested “input-like” styling from .info-value, relying on row separators instead of stacked surfaces.
File summaries
File Description
dstack/guest-agent/templates/dashboard.html CSS/layout adjustments: denser info rows, table scroll wrapper, tighter table spacing and typography.
Review details
  • Files reviewed: 1/1 changed files
  • Comments generated: 1
  • Review effort level: Lite

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread dstack/guest-agent/templates/dashboard.html
Base automatically changed from feat/guest-agent-gpu-observability to next September 7, 2026 04:18
Every value on this page sat inside three stacked surfaces: a white
`.info-section` card, a grey rounded `.info-row` inside it, and a white
bordered `.info-value` inside that. Three backgrounds to show one string,
and the innermost one reads as a disabled text input, which is a claim
the page cannot honour -- none of these values are editable.

The card stays and the two inner boxes become a hairline between rows.
With rows separating themselves the 12px grid gap is redundant, and the
1.6 line-height is a prose setting on a page that is entirely
single-line data. Headings were spending about seventy pixels each on
margins. A row goes from roughly 60px to 34px.

Tables had the opposite problem in the same place: 15px cells made rows
tall while the columns stayed too narrow to hold an identifier, so a
single GPU row wrapped into three lines of text. Cells shrink, values
stop wrapping, and the table scrolls sideways when it has to.

That scrolling goes on a wrapper. Setting overflow on the table itself
needs display:block, which drops the table formatting context and lets
thead and tbody compute their widths independently -- the header bar and
the body rows then disagree about where the right edge is. The wrapper
also owns the rounded corners and the shadow now, which deletes the four
per-corner rules that were placing them on individual cells.
@kvinwang
kvinwang force-pushed the fix/dashboard-compact-layout branch from 6472ea1 to db72fe3 Compare September 7, 2026 04:18
Three defects visible on the same screen as the layout work, all from a
unit that was lost between the wire and the reader.

`System Uptime` printed `68`. `SystemInfo.uptime` is a `uint64` of
seconds and the template printed it raw, so the page gave a number with
no unit at any magnitude. It now renders as `1m 8s`.

`Load Average` printed `1min: 0.4%`. The wire carries the load times 100,
so the page divided it back and then appended a `%`. A load average is a
count of runnable tasks, not a percentage: `1.00` on a single-core guest
means saturated, not one percent.

`/metrics` had the same field wrong in the opposite direction and worse.
`dstack_guest_load1` published the undivided integer, so a load of 0.40
was served as `40` under a gauge whose HELP text says "System load
average over 1 minute" -- two orders of magnitude off, to a consumer that
cannot notice. The deprecated `system_load_average_*` aliases carried the
same error. Both now divide.

Also scopes the link padding inside table cells. The global `a` rule
gives the hover pill 6px of vertical padding, which in a cell sets the
row height, so once the cells themselves are tight the one row holding a
"View Logs" link stands taller than every other row. Found by Copilot's
review of the layout change.
@kvinwang

kvinwang commented Sep 7, 2026

Copy link
Copy Markdown
Collaborator Author

Update: three unit bugs folded in, and Copilot's finding applied

Copilot was right about the link padding. The global a rule carries padding: 6px 12px, and inside a <td> that padding sets the row height. Once the cells themselves are tight, the one row holding a View Logs link stands ~12px taller than every other row — exactly the compaction this PR is for, undone in the one place a link appears. td a now uses 2px 8px with a matching negative left margin so the link text still lines up with the column header.

System Uptime printed 68. SystemInfo.uptime is a uint64 of seconds and the template printed it raw, so the page gave a bare number with no unit at any magnitude. Now 1m 8s.

Load Average printed 1min: 0.4%. The wire carries the load times 100, so the page divided it back and then appended a %. A load average is a count of runnable tasks, not a percentage — 1.00 on a single-core guest means saturated, not one percent.

⚠️ /metrics had the same field wrong, and worse

Chasing the % turned up a defect that is not cosmetic:

dstack_guest_load1 {{system_info.loadavg_one}}

No division at all. A load of 0.40 was published as 40, under a gauge whose HELP text reads "System load average over 1 minute". Two orders of magnitude off, to a consumer that has no way to notice. The deprecated system_load_average_* aliases carried the same error.

Both are fixed, which means this PR changes the value of six existing metric series:

Series Before After
dstack_guest_load1 / load5 / load15 40 0.40
system_load_average_1m / _5m / _15m 40 0.40

Flagging it prominently rather than burying it: anyone with an alert threshold tuned against the old values has tuned it against a number that was 100× the load. The new values are the ones the metric name and HELP text always promised, so I think correcting is right, but the call is yours — say the word and I will split the metric change into its own PR so the layout work can land without touching series values.

dstack_guest_uptime_seconds is untouched. It is a gauge of seconds and already correct; only the dashboard's rendering of the same field changed.

Validation

141 tests (up from 138), cargo fmt --check, cargo clippy -p dstack-guest-agent --all-targets -- -D warnings clean. New tests cover the duration boundaries (0s, 1m 8s, 1h 0m 0s, 1d 1h 1m 1s), the fixed-point recovery (40 -> 0.40, 1234 -> 12.34), and that /metrics now emits dstack_guest_load1 0.40.

Rebased onto next after #1178 merged.

@kvinwang
kvinwang merged commit 9f299d3 into next Sep 7, 2026
17 checks passed
@kvinwang
kvinwang deleted the fix/dashboard-compact-layout branch September 7, 2026 04:39
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.

2 participants