start: one-container stacks as a single row (project · service), groups only for multi-container stacks; same memory units in rows and headers
All checks were successful
Deploy production / deploy (push) Successful in 1m7s

This commit is contained in:
2026-10-25 17:41:10 +00:00
parent 48ec29beac
commit eba5f9eda3

View File

@@ -528,36 +528,57 @@
</p> </p>
<div class="stacks"> <div class="stacks">
{#each containerGroups as g (g.key)} {#each containerGroups as g (g.key)}
<details {#if g.items.length === 1 && g.key}
class="group stack" <!-- A one-container stack is a single row, not a group with one child. -->
open={!closedStacks[g.key]} {@const c = g.items[0]}
ontoggle={(e) => toggleStack(g.key, e.currentTarget.open)} <div
> class="stack solo"
<summary title="{c.name} · {c.image} · {c.state}{c.health ? ` (${c.health})` : ''}"
title={g.key
? `compose project ${g.key}`
: 'containers without a compose project'}
> >
<i class="dot {g.dot}"></i> <i class="dot {containerDot(c)}"></i>
<span class="sname">{g.name}</span> <span class="sname"
<small class={{ bad: g.up < g.items.length && g.dot !== 'off' }} >{g.name}{#if c.service && c.service !== g.name}<small>
>{g.up}/{g.items.length}</small · {c.service}</small
>{/if}</span
> >
<span class="num">{g.cpu ? `${g.cpu.toFixed(2)}%` : ''}</span> {#if c.restarts}<span class="tag warn">↻{c.restarts}</span>{/if}
<span class="num">{g.mem ? bytes(g.mem) : ''}</span> <span class="num">{c.cpu ?? ''}</span>
</summary> <span class="num">{c.mem ? bytes(memBytes(c.mem)) : ''}</span>
<ul class="containers"> </div>
{#each g.items as c (c.name)} {:else}
<li title="{c.name} · {c.image} · {c.state}{c.health ? ` (${c.health})` : ''}"> <details
<i class="dot {containerDot(c)}"></i> class="group stack"
<span class="name">{label(c)}</span> open={!closedStacks[g.key]}
{#if c.restarts}<span class="tag warn">↻{c.restarts}</span>{/if} ontoggle={(e) => toggleStack(g.key, e.currentTarget.open)}
<span class="num">{c.cpu ?? ''}</span> >
<span class="num">{c.mem ?? ''}</span> <summary
</li> title={g.key
{/each} ? `compose project ${g.key}`
</ul> : 'containers without a compose project'}
</details> >
<i class="dot {g.dot}"></i>
<span class="sname">{g.name}</span>
<small class={{ bad: g.up < g.items.length && g.dot !== 'off' }}
>{g.up}/{g.items.length}</small
>
<span class="num">{g.cpu ? `${g.cpu.toFixed(2)}%` : ''}</span>
<span class="num">{g.mem ? bytes(g.mem) : ''}</span>
</summary>
<ul class="containers">
{#each g.items as c (c.name)}
<li
title="{c.name} · {c.image} · {c.state}{c.health ? ` (${c.health})` : ''}"
>
<i class="dot {containerDot(c)}"></i>
<span class="name">{label(c)}</span>
{#if c.restarts}<span class="tag warn">↻{c.restarts}</span>{/if}
<span class="num">{c.cpu ?? ''}</span>
<span class="num">{c.mem ? bytes(memBytes(c.mem)) : ''}</span>
</li>
{/each}
</ul>
</details>
{/if}
{/each} {/each}
</div> </div>
{:else} {:else}
@@ -1006,6 +1027,40 @@
display: grid; display: grid;
gap: 2px; gap: 2px;
} }
.stack.solo {
display: flex;
align-items: center;
gap: 5px;
/* line up with group headers, whose chevron sits where this padding is */
padding: 3px 6px 3px 18px;
border-radius: 6px;
border: 1px solid transparent;
font-size: 11px;
text-transform: uppercase;
letter-spacing: 0.04em;
color: var(--site-muted);
transition:
background 120ms ease,
border-color 120ms ease,
color 120ms ease;
}
.stack.solo:hover {
background: rgba(255, 255, 255, 0.04);
border-color: var(--line);
color: var(--site-text);
}
.stack.solo .dot {
margin-right: 0;
}
.stack.solo small {
opacity: 0.7;
text-transform: none;
}
.stack.solo .num {
font-size: 11px;
text-transform: none;
letter-spacing: 0;
}
.group.stack > summary { .group.stack > summary {
align-items: center; align-items: center;
gap: 5px; gap: 5px;
@@ -1024,6 +1079,7 @@
.group.stack > summary .dot { .group.stack > summary .dot {
margin-right: 0; margin-right: 0;
} }
.stack.solo .sname,
.group.stack .sname { .group.stack .sname {
flex: 1; flex: 1;
min-width: 0; min-width: 0;