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) <noreply@anthropic.com>
This commit is contained in:
Ilker ManapandClaude Opus 5.5 committed 2026-09-30 10:50:02 +02:00
1 parent a32ea0e5ab
commit cbf95c034e
6 files changed
+75 -20

No files matched your search

+3 -1
View File
@@ -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
+35 -2
View File
@@ -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 ""
}
+1 -1
View File
@@ -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<br>set in server.yaml") {
if !strings.Contains(body, `title="set in server.yaml, which is read again on every start">on demand · yaml</span>`) {
t.Error("a declared model should say where its mode is set")
}
if strings.Count(body, `name="mode" class="mode"`) != 1 {
+18 -1
View File
@@ -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, `<span class="runs-now">1/1 ready · <span class="node">n2</span></span>`) {
if !strings.Contains(body, `title="replicas ready / wanted">1/1 · <span class="node" title="n2">n2</span></span>`) {
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)
}
}
}
+10 -8
View File
@@ -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; }
+8 -7
View File
@@ -67,13 +67,8 @@ reachable from this machine.</p>
{{/* 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}}<span class="as" title="{{.Name}}">as {{.Name}}</span>{{end}}
{{if .Fleet}}<span class="runs-now{{if or .Parked .Unplaceable}} bad{{else if lt .Ready .Wanted}} short{{end}}">
{{- 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}}<span class="node">{{$n}}</span>{{end}}{{end}}</span>{{end}}
{{if .Declared}}<span class="opt">{{if eq .Mode "always"}}always on{{else if eq .Mode "ondemand"}}on demand{{else}}stopped{{end}}<br>set in server.yaml</span>
{{if .Declared}}<span class="mode-fixed" title="set in server.yaml, which is read again on every start">
{{- if eq .Mode "always"}}always on{{else if eq .Mode "ondemand"}}on demand{{else}}stopped{{end}} · yaml</span>
{{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.</p>
<noscript><button type="submit" class="link">set</button></noscript>
</form>
{{end}}
{{if .Fleet}}<span class="runs-now{{if or .Parked .Unplaceable}} bad{{else if lt .Ready .Wanted}} short{{end}}" title="replicas ready / wanted{{if .Starting}}, + starting{{end}}">
{{- 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}}<span class="node" title="{{$n.Full}}">{{$n.Short}}</span>{{end}}{{end}}</span>{{end}}
</div>{{end}}
{{else}}<span class="opt">not served</span>{{end}}
</td>