From 7099043c7527cb6ec9035cdc388171f7309d45f6 Mon Sep 17 00:00:00 2001
From: Ilker Manap
Date: Wed, 30 Sep 2026 10:50:02 +0200
Subject: [PATCH] Keep each held row on one line
The replica count, its nodes and the mode selector sit side by side instead
of stacking in a fixed-width cell, and node names drop the prefix every node
shares, with the full name on hover.
Co-Authored-By: Claude Opus 5.5 (1M context)
---
CHANGELOG.md | 4 ++-
internal/web/admin_mirror.go | 37 ++++++++++++++++++++++--
internal/web/admin_mirror_test.go | 2 +-
internal/web/mirror_detail_test.go | 19 +++++++++++-
internal/web/static/base.css | 18 +++++++-----
internal/web/templates/admin_mirror.html | 15 +++++-----
6 files changed, 75 insertions(+), 20 deletions(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index e618caa..50b2eb3 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -51,7 +51,9 @@ API.
Each row also says how its model runs without being opened: replicas ready of
those it should have, how many are still starting, and the nodes they are on —
-in amber when short, in red when parked or when it does not fit.
+in amber when short, in red when parked or when it does not fit. It is one line
+per model beside its mode, and nodes drop the part of the name every node
+shares (node05 for llmgw-node05, the full name on hover).
## v0.10.4 — 2026-09-30
diff --git a/internal/web/admin_mirror.go b/internal/web/admin_mirror.go
index df7ace0..f683585 100644
--- a/internal/web/admin_mirror.go
+++ b/internal/web/admin_mirror.go
@@ -78,7 +78,7 @@ type modelUse struct {
Fleet bool
Ready, Wanted int
Starting int
- Nodes []string
+ Nodes []nodeName
Parked bool
Unplaceable bool
// Declared models are read from server.yaml on every start, so a mode
@@ -446,11 +446,17 @@ func (p *AccountPages) renderMirror(w http.ResponseWriter, r *http.Request,
d.Rows = append(d.Rows, row)
}
var views map[string]adminapi.ModelView
+ prefix := ""
if p.fleet != nil && len(sources) > 0 {
views = map[string]adminapi.ModelView{}
for _, v := range p.fleet.Models() {
views[v.Name] = v
}
+ var ids []string
+ for _, n := range p.fleet.Nodes() {
+ ids = append(ids, n.ID)
+ }
+ prefix = sharedPrefix(ids)
}
for i := range d.Rows {
for _, m := range sources[d.Rows[i].URL] {
@@ -462,7 +468,11 @@ func (p *AccountPages) renderMirror(w http.ResponseWriter, r *http.Request,
if v, ok := views[m.Name]; ok {
u.Fleet, u.Ready, u.Wanted = true, v.Ready, v.Desired
u.Parked, u.Unplaceable = v.Parked, v.Unplaceable
- u.Nodes, u.Starting = runningOn(p.fleet.Replicas(m.Name))
+ var nodes []string
+ nodes, u.Starting = runningOn(p.fleet.Replicas(m.Name))
+ for _, n := range nodes {
+ u.Nodes = append(u.Nodes, nodeName{Full: n, Short: strings.TrimPrefix(n, prefix)})
+ }
}
d.Rows[i].UsedBy = append(d.Rows[i].UsedBy, u)
}
@@ -803,3 +813,26 @@ func runningOn(reps []adminapi.ReplicaView) (nodes []string, starting int) {
slices.Sort(nodes)
return nodes, starting
}
+
+// nodeName is a node as a row names it: short, with the full name beside.
+type nodeName struct{ Short, Full string }
+
+// sharedPrefix is what every node's name starts with, up to and including a
+// separator — "llmgw-" of llmgw-node01 and llmgw-node02 — so a row can say
+// node01 and fit on a line. Nothing when the names share no such part, or
+// when there is one node and dropping it would leave only a number.
+func sharedPrefix(ids []string) string {
+ if len(ids) < 2 {
+ return ""
+ }
+ p := ids[0]
+ for _, id := range ids[1:] {
+ for !strings.HasPrefix(id, p) {
+ p = p[:len(p)-1]
+ }
+ }
+ if i := strings.LastIndexAny(p, "-._"); i >= 0 {
+ return p[:i+1]
+ }
+ return ""
+}
diff --git a/internal/web/admin_mirror_test.go b/internal/web/admin_mirror_test.go
index fcdc35d..c32c63e 100644
--- a/internal/web/admin_mirror_test.go
+++ b/internal/web/admin_mirror_test.go
@@ -592,7 +592,7 @@ func TestHowAModelRunsIsChangedWhereItsFileIsShown(t *testing.T) {
t.Error("the running model's mode is not shown as chosen")
}
// Declared in server.yaml: shown, not offered, since it would revert.
- if !strings.Contains(body, "on demand set in server.yaml") {
+ if !strings.Contains(body, `title="set in server.yaml, which is read again on every start">on demand · yaml`) {
t.Error("a declared model should say where its mode is set")
}
if strings.Count(body, `name="mode" class="mode"`) != 1 {
diff --git a/internal/web/mirror_detail_test.go b/internal/web/mirror_detail_test.go
index 879c5d6..988d44b 100644
--- a/internal/web/mirror_detail_test.go
+++ b/internal/web/mirror_detail_test.go
@@ -149,7 +149,7 @@ func TestAHeldRowSaysHowItsModelRunsWithoutOpening(t *testing.T) {
if strings.Contains(body, "held-card") {
t.Fatal("the card opened by itself")
}
- if !strings.Contains(body, `1/1 ready · n2`) {
+ if !strings.Contains(body, `title="replicas ready / wanted">1/1 · n2`) {
t.Errorf("the row does not say where its replicas run: %s", body)
}
}
@@ -164,3 +164,20 @@ func TestRunningOnNamesEachNodeOnceAndCountsWhatIsStarting(t *testing.T) {
t.Errorf("nodes %v, starting %d", nodes, starting)
}
}
+
+func TestNodeNamesLoseOnlyWhatEveryNodeShares(t *testing.T) {
+ for _, c := range []struct {
+ ids []string
+ want string
+ }{
+ {[]string{"llmgw-node01", "llmgw-node02", "llmgw-node06"}, "llmgw-"},
+ {[]string{"gpu-a.dc1", "gpu-b.dc1"}, "gpu-"},
+ {[]string{"n1", "n2"}, ""},
+ {[]string{"llmgw-node01"}, ""},
+ {[]string{"alpha", "beta"}, ""},
+ } {
+ if got := sharedPrefix(c.ids); got != c.want {
+ t.Errorf("%v: %q, expected %q", c.ids, got, c.want)
+ }
+ }
+}
diff --git a/internal/web/static/base.css b/internal/web/static/base.css
index 34d77b7..e7d23e8 100644
--- a/internal/web/static/base.css
+++ b/internal/web/static/base.css
@@ -444,14 +444,16 @@ h3.limits-for { font-size: .92rem; margin: 1rem 0 .4rem; }
table.narrow-table { max-width: 30rem; }
.q-act a + a { margin-left: .8rem; }
.chip-role { font-size: .68rem; font-weight: 600; color: var(--accent); text-transform: uppercase; letter-spacing: .04em; }
-/* Runs: one fixed-width selector per model, so they line up down the column
- whatever the names; a catalog name differing from the mirror's sits above. */
-table.held .use { width: 9.5rem; margin: .1rem 0; }
-table.held .use .as { display: block; font-size: .72rem; color: var(--muted);
- overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
-table.held .use .opt { font-size: .75rem; line-height: 1.3; }
+/* Runs: one line per model — a catalog name that differs from the mirror's,
+ how it runs, and where — with the selector at a fixed width so the
+ replica counts line up down the column. */
+table.held .use { display: flex; align-items: center; gap: .5rem; margin: .1rem 0; white-space: nowrap; }
+table.held .use .as { font-size: .72rem; color: var(--muted); max-width: 10rem;
+ overflow: hidden; text-overflow: ellipsis; }
+table.held .use form.inline { margin: 0; }
+table.held .use .mode-fixed { width: 7.5rem; font-size: .8rem; color: var(--muted); }
table.held td.num { white-space: nowrap; }
-table.held select.mode { width: 100%; padding: .15rem .3rem; font-size: .8rem; }
+table.held select.mode { width: 7.5rem; margin: 0; padding: .1rem .3rem; font-size: .8rem; }
/* Link pills: the same look as .tabs, for choices that are addresses rather
than panels on one page — a filtered listing should survive a reload and be
@@ -831,7 +833,7 @@ a.open-held { text-decoration: none; color: inherit; }
a.open-held:hover strong { text-decoration: underline; }
a.open-held[aria-expanded=true]::before { content: "▾ "; color: var(--muted); }
a.open-held[aria-expanded=false]::before { content: "▸ "; color: var(--muted); }
-.use .runs-now { display: block; font-size: .78rem; color: var(--muted); margin: .1rem 0 .25rem; }
+.use .runs-now { font-size: .78rem; color: var(--muted); }
.use .runs-now.short { color: var(--warn); }
.use .runs-now.bad { color: var(--bad); }
.use .runs-now .node { white-space: nowrap; }
diff --git a/internal/web/templates/admin_mirror.html b/internal/web/templates/admin_mirror.html
index a6ca040..7574a7e 100644
--- a/internal/web/templates/admin_mirror.html
+++ b/internal/web/templates/admin_mirror.html
@@ -67,13 +67,8 @@ reachable from this machine.
{{/* Named only when the catalog calls it something other than the
mirror does; otherwise it is the model column again. */}}
{{if ne .Name $row.Model}}as {{.Name}}{{end}}
- {{if .Fleet}}
- {{- if .Parked}}parked
- {{- else if and (eq .Wanted 0) (eq .Ready 0)}}no replicas
- {{- else}}{{.Ready}}/{{.Wanted}} ready{{if .Starting}}, {{.Starting}} starting{{end}}{{end}}
- {{- if .Unplaceable}} · does not fit{{end}}
- {{- with .Nodes}} · {{range $i, $n := .}}{{if $i}}, {{end}}{{$n}}{{end}}{{end}}{{end}}
- {{if .Declared}}{{if eq .Mode "always"}}always on{{else if eq .Mode "ondemand"}}on demand{{else}}stopped{{end}} set in server.yaml
+ {{if .Declared}}
+ {{- if eq .Mode "always"}}always on{{else if eq .Mode "ondemand"}}on demand{{else}}stopped{{end}} · yaml
{{else}}
{{/* Submitted on change: the choice is the action, and a second click
to confirm it would only be a way to forget to. */}}
@@ -89,6 +84,12 @@ reachable from this machine.
{{end}}
+ {{if .Fleet}}
+ {{- if .Parked}}parked
+ {{- else if and (eq .Wanted 0) (eq .Ready 0)}}none running
+ {{- else}}{{.Ready}}/{{.Wanted}}{{if .Starting}} +{{.Starting}}{{end}}{{end}}
+ {{- if .Unplaceable}} · no room{{end}}
+ {{- with .Nodes}} · {{range $i, $n := .}}{{if $i}}, {{end}}{{$n.Short}}{{end}}{{end}}{{end}}
{{end}}
{{else}}not served{{end}}