fix(guest-agent): tighten the dashboard layout - #1186
Conversation
There was a problem hiding this comment.
🟡 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-scrollwrapper 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.
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.
6472ea1 to
db72fe3
Compare
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.
Update: three unit bugs folded in, and Copilot's finding appliedCopilot was right about the link padding. The global
|
| 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.
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 thannext, 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
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-gridgap is redundant.line-height: 1.6is 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: 15pxmade 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,tdstops wrapping, and the table scrolls sideways when it has to.The scrolling goes on a wrapper, deliberately
overflow-xon the<table>itself requiresdisplay: block, which drops the table formatting context.theadandtbodythen 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, andwidth: 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-scrollalso takes over the rounded corners and the shadow, which lets the fourborder-top-left-radius/tr:last-child td:last-childrules 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 Uptimerenders68.SystemInfo.uptimeisuint64seconds fromSystem::uptime()and the template prints it raw, so the page shows a bare number with no unit.Load Averagerenders1min: 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 warningsall 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.