feat: per-host IP, richer docker monitoring, 7th shell, scrollbar toggle#14
Merged
Merged
Conversation
Remote host cards + local Machine widget now show:
- IP address (primary outbound IP; local via UDP-connect trick, remote via hostname -I)
- Docker monitoring upgrades: per-container live CPU%/mem (stats --no-stream),
stopped containers included (ps -a, greyed in the UI), and a per-host health
summary line ("N running · M stopped · K unhealthy"; unhealthy rows highlighted)
Plus:
- Seventh shell slot (slot7) in the default session set
- "Expand lists (no scrollbars)" Configure toggle — drops the container list
max-height so it grows instead of scrolling; thinner subtle scrollbars otherwise
Remote gathering now feeds its multi-line script over SSH stdin (sh -ls) instead of
as a command-line arg: ssh joined the args with spaces and the remote shell re-parsed
them, which silently ate the first section marker. stdin keeps the script intact.
Deferred (separate follow-up): image-update hints — needs cached, rate-limited
registry digest lookups, not viable on the live SSH poll.
Verified end-to-end against logan-gl502vs; cargo test (29) green.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
This PR adds opt-in UI/widget enhancements around host identification, container monitoring fidelity, and configurability. It extends both the Rust backend (data gathering + API payloads) and the frontend (rendering, styling, and settings) to show per-host IPs, richer container state/health, an additional default shell slot, and a “expand lists” scrollbar toggle.
Changes:
- Add IP reporting for the local machine and remote hosts, and surface it in the UI.
- Expand container monitoring to include stopped containers and per-container CPU/mem stats, plus health/state styling and a health summary line.
- Add Shell Slot 7 and a new “Expand lists (no scrollbars)” dashboard setting with CSS support.
Reviewed changes
Copilot reviewed 13 out of 13 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
src/settings.rs |
Adds expand_lists panel setting (serde rename + legacy parsing). |
src/remote.rs |
Reworks remote SSH probe to sectioned output over stdin; adds stopped containers, IP capture, and stats attachment. |
src/remote_metrics.rs |
Exposes section-splitting for reuse; switches parsing to parse_from(§ions) style. |
src/metrics.rs |
Adds local machine IP to /api/metrics payload. |
src/containers.rs |
Includes stopped containers and attaches stats --no-stream CPU/mem; adds unit test. |
src/config.rs |
Adds default “Shell Slot 7”. |
public/metrics.js |
Updates rendered container rows (state, stats), summary text, and shows IPs. |
public/core.js |
Adds default expandLists setting client-side. |
public/app.css |
Adds container state styling, stats line styling, and scrollbar/expand-lists rules for remote containers. |
public/actions.js |
Toggles expand-lists body class and adds settings UI checkbox. |
frontend/metrics.ts |
Source TS for metrics UI: mirrors JS changes (IP, state, stats, health summary). |
frontend/core.ts |
Source TS: adds expandLists to panel settings defaults/types. |
frontend/actions.ts |
Source TS: toggles expand-lists and adds settings UI checkbox. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Comment on lines
+241
to
+250
| // `engine\tname\tcpu\tmem` rows from the STATS section → name -> (cpu, mem). | ||
| fn parse_remote_stats(raw: &str) -> HashMap<String, (String, String)> { | ||
| raw.lines() | ||
| .filter_map(|line| { | ||
| let mut parts = line.splitn(4, '\t'); | ||
| let engine = parts.next()?.trim(); | ||
| let name = parts.next()?.trim().to_string(); | ||
| let cpu = parts.next()?.trim().to_string(); | ||
| let mem = parts.next()?.trim().to_string(); | ||
| if !matches!(engine, "docker" | "podman") || name.is_empty() { |
Comment on lines
+172
to
+175
| if let Some(mut stdin) = child.stdin.take() { | ||
| let _ = stdin.write_all(script.as_bytes()).await; | ||
| // Drop closes the pipe so `sh -ls` sees EOF and runs. | ||
| } |
Comment on lines
+29
to
+40
| // Primary outbound IP, found by asking the kernel which source address it would use to reach a | ||
| // public address. No packets are sent (UDP connect just selects the route); falls back to "". | ||
| fn local_ip() -> String { | ||
| use std::net::UdpSocket; | ||
| UdpSocket::bind("0.0.0.0:0") | ||
| .and_then(|sock| { | ||
| sock.connect("1.1.1.1:80")?; | ||
| sock.local_addr() | ||
| }) | ||
| .map(|addr| addr.ip().to_string()) | ||
| .unwrap_or_default() | ||
| } |
Comment on lines
271
to
+275
| const containers = host.containers || []; | ||
| const total = typeof host.container_total === 'number' ? host.container_total : containers.length; | ||
| const countLabel = total === 1 ? '1 container' : `${total} containers`; | ||
| const shownNote = total > containers.length ? ` · showing ${containers.length}` : ''; | ||
| const shownNote = total > containers.length ? ` · showing ${containers.length} of ${total}` : ''; | ||
| const containerHtml = containers.length | ||
| ? `<div class="remote-count">${escapeHtml(countLabel)}${escapeHtml(shownNote)}</div><div class="remote-containers">${containers.map((container) => `<div class="container-item remote-container"><div><b>${escapeHtml(container.name)}</b><span>${escapeHtml(container.image)}</span></div><small>${escapeHtml(container.engine)}</small><em>${escapeHtml(container.status)}</em></div>`).join('')}</div>` | ||
| : `<div class="muted remote-empty">${host.online ? 'No running containers' : escapeHtml(host.error || 'Remote host is offline')}</div>`; | ||
| ? `<div class="remote-count">${escapeHtml(containerHealth(containers))}${escapeHtml(shownNote)}</div><div class="remote-containers">${containers.map((c) => containerRowHtml(c, 'remote-container')).join('')}</div>` |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Four widget improvements (all opt-in / non-breaking).
1. Show each box's IP
Local Machine widget and every Remote host card now show the host's primary IP (local via a UDP-connect route lookup, no packets sent; remote via
hostname -I).2. Richer Docker monitoring
stats --no-stream, joined onto the container rows.ps -a), greyed in the UI; unhealthy/restarting rows highlighted.N running · M stopped · K unhealthy.3. One more shell
Adds Shell Slot 7 to the default session set.
4. Scrollbar toggle
New Configure option "Expand lists (no scrollbars)" — drops the container list
max-heightso it grows instead of scrolling. Scrollbars are thinner/subtler by default too. Public-repo friendly: it's just a per-user setting.Notable fix
Remote gathering now feeds its multi-line script over SSH stdin (
sh -ls) instead of as a command-line argument. ssh joins remote args with spaces and the remote shell re-parsed them, which silently ate the first section marker (PS section came back empty). stdin keeps the script intact — verified gl502vs now reports all 24 containers with stats.Verified
cargo test(29 pass),tscclean; end-to-end + screenshots againstlogan-gl502vs(IP, health summary, per-container CPU/mem, 7 shells, scrollbar toggle on/off).🤖 Generated with Claude Code