feat(overview): tasks-style app rows on the Updates tab

Redesign the Fleet Overview Updates list rows to match the Tasks page:
each app now shows its icon tile, a status pill ('✓ Up to date' /
'↑ Update available' / '• Unscanned'), and a dedicated 'Details' toggle
button (chevron) in the row actions instead of the bare leading chevron.
Resolve the app icon from the slug (/core/icons/apps/<slug>.svg, hidden on
error) and prefer window.getAppDisplayName for a prettier title.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
librelad 2026-07-18 20:18:26 +01:00
parent 423f3163a9
commit 8adcb140e5
2 changed files with 71 additions and 19 deletions

View File

@ -195,17 +195,67 @@
padding: 12px 14px;
}
.ov-row-head .updater-row-ver { margin-left: auto; }
.ov-row-head .ov-row-actions { margin-left: 12px; }
.ov-chevron {
flex: 0 0 auto;
display: inline-block;
font-size: .8rem;
opacity: .7;
transition: transform .18s ease;
}
.ov-row-head[aria-expanded="true"] .ov-chevron { transform: rotate(90deg); }
.ov-row-head .ov-row-actions { margin-left: 12px; display: flex; align-items: center; gap: 8px; }
.ov-row-head:focus-visible { outline: 2px solid rgba(var(--page-updater-rgb), .7); outline-offset: -2px; }
/* Tasks-style app row: icon tile + status pill + Details toggle -------------- */
.ov-row-icon {
flex: 0 0 auto;
width: 32px;
height: 32px;
border-radius: 8px;
object-fit: contain;
padding: 3px;
border: 1px solid var(--border-color, rgba(255, 255, 255, .14));
background: rgba(255, 255, 255, .04);
}
.ov-status {
display: inline-flex;
align-items: center;
gap: 4px;
padding: 2px 9px;
border-radius: 999px;
font-size: .72rem;
font-weight: 600;
letter-spacing: .02em;
white-space: nowrap;
}
.ov-status-ok {
background: rgba(var(--status-success-rgb), .18);
border: 1px solid rgba(var(--status-success-rgb), .45);
color: #86efac;
}
.ov-status-update {
background: rgba(var(--status-warning-rgb), .18);
border: 1px solid rgba(var(--status-warning-rgb), .5);
color: #fcd34d;
}
.ov-status-unknown {
background: rgba(255, 255, 255, .06);
border: 1px solid rgba(255, 255, 255, .16);
color: var(--text-muted, rgba(255, 255, 255, .6));
}
.ov-details-btn {
display: inline-flex;
align-items: center;
gap: 4px;
padding: 4px 10px;
border-radius: 6px;
border: 1px solid var(--border-color, rgba(255, 255, 255, .14));
background: rgba(255, 255, 255, .04);
color: var(--text-secondary, rgba(255, 255, 255, .7));
font-size: .72rem;
cursor: pointer;
transition: background .15s ease, border-color .15s ease, color .15s ease;
}
.ov-details-btn:hover {
background: rgba(var(--page-updater-rgb), .16);
border-color: rgba(var(--page-updater-rgb), .5);
color: #fff;
}
.ov-details-btn svg { transition: transform .18s ease; }
.ov-row-head[aria-expanded="true"] .ov-details-btn svg { transform: rotate(180deg); }
.ov-row-details { display: none; padding: 4px 16px 16px; border-top: 1px solid var(--border-color, rgba(255, 255, 255, .1)); }
.ov-row-details.ov-open { display: block; }

View File

@ -538,23 +538,25 @@ class OverviewManager {
updateRow(a) {
const esc = (s) => this.escape(s);
const slug = esc(a.name);
const name = esc((window.getAppDisplayName ? window.getAppDisplayName(a.name) : null) || a.displayName || a.name);
const cur = esc(a.current_version || a.current_image || '—');
const avail = a.update_available ? esc(a.available_version || a.available_image || 'newer') : null;
const badge = a.update_available
? `<span class="updater-badge updater-badge-update">update</span>`
: (a.scanned ? `<span class="updater-badge updater-badge-ok">up to date</span>` : `<span class="updater-badge updater-badge-unknown">unscanned</span>`);
const status = a.update_available
? `<span class="ov-status ov-status-update">↑ Update available</span>`
: (a.scanned ? `<span class="ov-status ov-status-ok">✓ Up to date</span>` : `<span class="ov-status ov-status-unknown">• Unscanned</span>`);
const sev = a.worstSeverity ? `<span class="updater-badge sev-${a.worstSeverity}">${a.worstSeverity}</span>` : '';
const updBtn = a.update_available
? `<button class="updater-btn updater-btn-primary" data-updater-action="update" data-app="${esc(a.name)}">Update</button>`
? `<button class="updater-btn updater-btn-primary" data-updater-action="update" data-app="${slug}">Update</button>`
: '';
return `<div class="updater-row ov-row" data-app="${esc(a.name)}">
<div class="updater-row-head ov-row-head" data-overview-action="toggle" data-app="${esc(a.name)}" role="button" tabindex="0" aria-expanded="false" aria-controls="ov-detail-${esc(a.name)}">
<span class="ov-chevron" aria-hidden="true"></span>
<span class="updater-row-name">${esc(a.displayName)}</span> ${badge} ${sev}
return `<div class="updater-row ov-row" data-app="${slug}">
<div class="updater-row-head ov-row-head" data-overview-action="toggle" data-app="${slug}" role="button" tabindex="0" aria-expanded="false" aria-controls="ov-detail-${slug}">
<img class="ov-row-icon" src="/core/icons/apps/${slug}.svg" alt="" onerror="this.style.display='none'">
<span class="updater-row-name">${name}</span> ${status} ${sev}
<span class="updater-row-ver">${cur}${avail ? ` <span class="updater-arrow">→</span> <strong>${avail}</strong>` : ''}</span>
<span class="ov-row-actions">${updBtn}</span>
<span class="ov-row-actions">${updBtn}<button class="ov-details-btn" data-overview-action="toggle" data-app="${slug}" tabindex="-1" aria-hidden="true"><svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="6,9 12,15 18,9"></polyline></svg><span>Details</span></button></span>
</div>
<div class="updater-row-details ov-row-details" id="ov-detail-${esc(a.name)}" role="region" aria-label="${esc(a.displayName)} details" hidden></div>
<div class="updater-row-details ov-row-details" id="ov-detail-${slug}" role="region" aria-label="${name} details" hidden></div>
</div>`;
}