Skip to content

feat: per-host IP, richer docker monitoring, 7th shell, scrollbar toggle#14

Merged
falkoro merged 1 commit into
masterfrom
feat/widget-config-shell-ip-docker
May 31, 2026
Merged

feat: per-host IP, richer docker monitoring, 7th shell, scrollbar toggle#14
falkoro merged 1 commit into
masterfrom
feat/widget-config-shell-ip-docker

Conversation

@falkoro
Copy link
Copy Markdown
Owner

@falkoro falkoro commented May 31, 2026

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

  • Per-container CPU% / mem via stats --no-stream, joined onto the container rows.
  • Stopped containers included (ps -a), greyed in the UI; unhealthy/restarting rows highlighted.
  • Per-host health summary line: N running · M stopped · K unhealthy.
  • (Deferred: image-update hints — needs cached, rate-limited registry digest lookups, not viable on the live SSH poll; separate follow-up.)

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-height so 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), tsc clean; end-to-end + screenshots against logan-gl502vs (IP, health summary, per-container CPU/mem, 7 shells, scrollbar toggle on/off).

🤖 Generated with Claude Code

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>
Copilot AI review requested due to automatic review settings May 31, 2026 11:59
@falkoro falkoro merged commit 7dbdfde into master May 31, 2026
@falkoro falkoro deleted the feat/widget-config-shell-ip-docker branch May 31, 2026 11:59
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

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(&sections) 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 thread src/remote.rs
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 thread src/remote.rs
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 thread src/metrics.rs
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 thread frontend/metrics.ts
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>`
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