Compare commits

...

2 Commits

Author SHA1 Message Date
librelad
93c5076225 Merge claude/2 2026-06-25 13:17:14 +01:00
librelad
9850b9d8e7 fix(admin/storage): make the Storage page tablet/mobile friendly
The donut legend collided into the stat cards and the image-row details
were squished on tablet widths — the 220px sidebar leaves the content
cramped while the old breakpoints assumed full-viewport width.

- Headline collapses to a single column at <=1024px (was 800px), the two
  stat cards reflow side-by-side, and on phones the donut stacks above its
  legend with one stat per row. Legend labels now ellipsis instead of
  overflowing into the stats.
- Image rows group the name+pill and the size/shared/age metadata so the
  metadata drops onto its own line under the name at <=1024px instead of
  competing for width; on phones the Delete button collapses to an icon.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Signed-off-by: librelad <librelad@digitalangels.vip>
2026-06-25 13:17:14 +01:00
2 changed files with 74 additions and 7 deletions

View File

@ -136,6 +136,48 @@
.sys-img-pill.is-inuse { background: rgba(var(--accent-rgb), 0.16); color: var(--accent); }
.sys-img-pill.is-unused { background: rgba(var(--text-rgb), 0.10); color: rgba(var(--text-rgb), 0.6); }
.sys-img-pill.is-dangling { background: rgba(251, 189, 35, 0.16); color: #fbbd23; }
/* Image row body: name + pill on the left, size/shared/age metadata pushed to
the right on a wide row. On narrow screens (see the @media below) the meta
drops onto its own line under the name so the details stop being squished. */
.sys-img-body {
display: flex;
align-items: center;
gap: 6px 10px;
flex: 1;
min-width: 0;
}
.sys-img-headline {
display: flex;
align-items: center;
gap: 8px;
min-width: 0; /* lets a long image name ellipsis instead of pushing */
}
.sys-image-item .task-title {
min-width: 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.sys-img-pill { flex-shrink: 0; }
.sys-img-meta {
display: flex;
align-items: center;
flex-wrap: wrap;
gap: 6px 8px;
margin-left: auto; /* right-align the details on a single-line row */
flex-shrink: 0;
}
/* Tablet & phone (the 220px sidebar leaves the content cramped until 768px):
stack the metadata under the name+pill so the details get their own line. */
@media (max-width: 1024px) {
.sys-img-body { flex-direction: column; align-items: flex-start; gap: 5px; }
.sys-img-meta { margin-left: 0; }
}
/* Phones: collapse the Delete button to an icon so a long name + actions fit. */
@media (max-width: 560px) {
.sys-image-item .task-btn.delete .task-btn-label { display: none; }
.sys-image-item .task-btn.delete { padding: 4px 7px; }
}
.admin-card-body {
display: flex;
@ -1060,8 +1102,23 @@ table.sys-apps tr:hover td { background: rgba(var(--text-rgb), 0.03); }
gap: 18px;
margin-top: 14px;
}
@media (max-width: 800px) {
/* Tablet/narrow: with the 220px sidebar eating the width, the two headline
columns (donut+legend / stat cards) can't share a row without the legend
colliding into the stats stack them, and let the two stat cards sit side
by side while there's still room for them. */
@media (max-width: 1024px) {
.sys-storage-headline { grid-template-columns: 1fr; }
.sys-storage-head-stats {
grid-template-rows: none;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
}
}
/* Phones (sidebar is now a drawer, content is full-width): donut above its
legend, one stat card per row. */
@media (max-width: 560px) {
.sys-storage-head-card { flex-direction: column; text-align: center; }
.sys-storage-legend { width: 100%; }
.sys-storage-head-stats { grid-template-columns: 1fr; }
}
.sys-storage-head-card {
background: var(--card-bg);
@ -1100,7 +1157,7 @@ table.sys-apps tr:hover td { background: rgba(var(--text-rgb), 0.03); }
}
.sys-storage-legend li {
display: grid;
grid-template-columns: 14px auto 1fr auto;
grid-template-columns: 14px minmax(0, auto) 1fr auto;
align-items: center;
gap: 10px;
font-size: 0.85rem;
@ -1112,6 +1169,10 @@ table.sys-apps tr:hover td { background: rgba(var(--text-rgb), 0.03); }
.sys-storage-leg-k {
color: var(--text-primary);
font-weight: 600;
min-width: 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.sys-storage-leg-v {
color: rgba(var(--text-rgb), 0.65);

View File

@ -518,11 +518,17 @@ class SystemStoragePage {
<div class="task-header">
<div class="task-info">
${this._imageIconHtml(im)}
<div class="sys-img-body">
<span class="sys-img-headline">
<span class="task-title" title="${fmt.escape((im.repo_tags || []).join(', '))}">${fmt.escape(this._imageName(im))}</span>
<span class="task-status sys-img-pill ${pill.cls}">${fmt.escape(pill.label)}</span>
</span>
<span class="sys-img-meta">
<span class="task-time">${fmt.bytes(im.size || 0)}</span>
${im.shared_size ? `<span class="task-duration" title="shared layers">${fmt.bytes(im.shared_size)} shared</span>` : ''}
${im.created ? `<span class="task-duration">${fmt.timeAgo(im.created)}</span>` : ''}
</span>
</div>
</div>
<div class="task-actions">
<button type="button" class="task-btn delete" data-image-delete="${id}" title="Remove image">