Four fixes to the Updates tab, all reported from the same screenshot. Upgrade is green and just says "Upgrade". It was accent-styled and carried the target version, which duplicated the confirmation it opens — the dialog already names the version, so the button only had to name the action. Green also stops it reading as the same control as the cyan Update beside it. The picker moved from the head of the row to the tail, matching the Tasks list. It used to lead with an `ov-pick-empty` spacer holding the column open, so with nothing updatable every row carried a left gutter for a checkbox that was never coming — the gap in the report. Unselectable rows now render no picker at all. Select all joins the toolbar, right-aligned above that column, again mirroring Tasks. It acts on the selectable rows in view, so a narrowing filter chip cannot leave it quietly picking rows nobody can see. The confirmation dialog takes an optional icon and the upgrade flow passes the app's, so it leads with the same icon-in-holder the instance modals use. Styled in base.css rather than borrowing .app-card-icon: this dialog is global and appears on pages that never load the apps stylesheet. Its title now uses the display name too — "Upgrade matrix to…" beside the Matrix logo read as a different thing. Verified against the live WebUI: green Upgrade with the gutter gone; Select all and the trailing checkboxes with two apps temporarily flagged (data restored byte-identical after); the master toggling 0 -> 2 -> 0; and the dialog showing matrix.svg under the title "Upgrade Matrix to v1.159.0?".
207 lines
10 KiB
CSS
207 lines
10 KiB
CSS
/* App Updater feature styles. Layout mirrors the Backup page: shared
|
|
.sidebar/.category + .page-header chrome, with an updater-owned .config-section
|
|
card + padded body. Content widgets (stat grid, rows, badges, CVEs) are
|
|
updater-specific. Uses the shared palette tokens + the --page-updater hue
|
|
set on #updater-page. Eager-linked from index.html. */
|
|
|
|
/* ---- Layout (copied from .backup-* so the two pages match exactly) ---- */
|
|
.updater-layout {
|
|
display: flex;
|
|
min-height: calc(100vh - var(--topbar-height, 60px));
|
|
}
|
|
.updater-layout .main {
|
|
flex: 1;
|
|
min-width: 0;
|
|
overflow-y: auto;
|
|
}
|
|
.updater-page {
|
|
color: var(--text-primary);
|
|
width: 100%;
|
|
padding-bottom: 48px;
|
|
}
|
|
/* One .config-section card holding the .page-header (flush, its border acts as
|
|
the divider) + the body, which carries the padding. */
|
|
.updater-page-section {
|
|
padding: 0;
|
|
overflow: hidden;
|
|
}
|
|
.updater-page-section > .page-header {
|
|
margin-bottom: 0;
|
|
}
|
|
.updater-page-body {
|
|
padding: 22px;
|
|
}
|
|
/* The page-header icon tints with the page hue. */
|
|
#updater-page .page-header-icon-slot {
|
|
color: rgb(var(--page-rgb, var(--accent-rgb)));
|
|
}
|
|
|
|
/* ---- Overview stat cards ---- */
|
|
.updater-stat-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(190px, 1fr)); gap: 14px; }
|
|
.updater-stat {
|
|
position: relative; padding: 18px; border-radius: 14px;
|
|
background: rgba(var(--text-rgb), 0.035);
|
|
border: 1px solid rgba(var(--page-rgb, var(--accent-rgb)), 0.22);
|
|
}
|
|
.updater-stat-big { font-size: 2rem; font-weight: 700; line-height: 1; color: rgb(var(--page-rgb, var(--accent-rgb))); }
|
|
.updater-stat-label { margin-top: 6px; font-weight: 600; font-size: 0.9rem; }
|
|
.updater-stat-sub { color: rgba(var(--text-rgb), 0.5); font-size: 0.78rem; margin-top: 2px; }
|
|
.updater-stat .updater-btn { margin-top: 12px; }
|
|
|
|
/* ---- Lists / rows ---- */
|
|
.updater-toolbar { display: flex; gap: 10px; margin-bottom: 14px; }
|
|
.updater-list { display: flex; flex-direction: column; gap: 8px; }
|
|
.updater-row {
|
|
display: grid; grid-template-columns: 1fr auto auto; align-items: center; gap: 14px;
|
|
padding: 12px 15px; border-radius: 11px;
|
|
background: rgba(var(--text-rgb), 0.035);
|
|
border: 1px solid rgba(var(--text-rgb), 0.07);
|
|
}
|
|
.updater-row-name { font-weight: 600; }
|
|
.updater-row-ver { color: rgba(var(--text-rgb), 0.6); font-family: var(--font-mono); font-size: 0.8rem; }
|
|
.updater-arrow { color: rgb(var(--page-rgb, var(--accent-rgb))); }
|
|
|
|
/* ---- Badges ---- */
|
|
.updater-badge {
|
|
display: inline-block; padding: 1px 8px; border-radius: 999px;
|
|
font-size: 0.68rem; font-weight: 700; text-transform: uppercase; letter-spacing: 0.04em;
|
|
vertical-align: middle; margin-left: 6px;
|
|
}
|
|
.updater-badge-update { background: rgba(var(--page-updater-rgb), 0.18); color: rgb(var(--page-updater-rgb)); }
|
|
.updater-badge-ok { background: rgba(var(--page-verify-rgb), 0.16); color: rgb(var(--page-verify-rgb)); }
|
|
.updater-badge-unknown { background: rgba(var(--text-rgb), 0.1); color: rgba(var(--text-rgb), 0.55); }
|
|
.sev-critical { background: rgba(220, 38, 38, 0.18); color: #f87171; }
|
|
.sev-high { background: rgba(234, 88, 12, 0.18); color: #fb923c; }
|
|
.sev-medium { background: rgba(234, 179, 8, 0.16); color: #fcd34d; }
|
|
.sev-low { background: rgba(var(--text-rgb), 0.1); color: rgba(var(--text-rgb), 0.6); }
|
|
|
|
/* ---- CVE blocks ---- */
|
|
.updater-cve-app { padding: 12px 15px; border-radius: 11px; background: rgba(var(--text-rgb), 0.035); border: 1px solid rgba(var(--text-rgb), 0.07); }
|
|
.updater-cve-app-name { font-weight: 600; margin-bottom: 8px; }
|
|
.updater-cve { display: flex; align-items: center; gap: 10px; padding: 6px 0; border-top: 1px solid rgba(var(--text-rgb), 0.06); font-size: 0.82rem; }
|
|
.updater-cve-sev { font-weight: 700; font-size: 0.66rem; min-width: 58px; }
|
|
.updater-cve.sev-critical .updater-cve-sev { color: #f87171; }
|
|
.updater-cve.sev-high .updater-cve-sev { color: #fb923c; }
|
|
.updater-cve.sev-medium .updater-cve-sev { color: #fcd34d; }
|
|
.updater-cve-id { font-family: var(--font-mono); color: rgb(var(--page-rgb, var(--accent-rgb))); text-decoration: none; }
|
|
.updater-cve-id:hover { text-decoration: underline; }
|
|
.updater-cve-pkg { color: rgba(var(--text-rgb), 0.6); }
|
|
.updater-cve-origin {
|
|
font-size: 0.64rem; font-weight: 600; letter-spacing: .02em; white-space: nowrap;
|
|
padding: 1px 7px; border-radius: 999px;
|
|
color: rgba(var(--text-rgb), 0.7);
|
|
background: rgba(var(--text-rgb), 0.07);
|
|
border: 1px solid rgba(var(--text-rgb), 0.10);
|
|
}
|
|
.updater-cve-ver { margin-left: auto; color: rgba(var(--page-verify-rgb), 0.95); font-size: 0.76rem; white-space: nowrap; }
|
|
.updater-cve-ver .updater-arrow { color: rgba(var(--text-rgb), 0.4); }
|
|
.updater-cve-ver-none { color: rgba(var(--text-rgb), 0.4); font-style: italic; }
|
|
|
|
/* Security CVEs live in their own inset dark panel so the list reads as a
|
|
contained block with breathing room on both sides — matching the app rows
|
|
instead of bleeding to the detail-panel edges. The rows themselves keep no
|
|
horizontal padding; the box supplies it. */
|
|
.updater-cve-box {
|
|
padding: 2px 14px;
|
|
border-radius: 10px;
|
|
background: rgba(10, 16, 32, 0.45);
|
|
border: 1px solid rgba(var(--text-rgb), 0.10);
|
|
}
|
|
|
|
/* Explainer + status above the CVE box: name what these CVEs actually are
|
|
(image dependencies, not the app version) and tell the user honestly whether
|
|
there's anything they can do — keyed on a real app update, not a package
|
|
patch that only lands when the maintainer rebuilds. */
|
|
.updater-cve-explain { margin: 0 0 5px; font-size: 0.8rem; color: rgba(var(--text-rgb), 0.6); }
|
|
.updater-cve-status { margin: 0 0 10px; font-size: 0.8rem; color: rgba(var(--text-rgb), 0.55); }
|
|
|
|
/* Triage groups inside the box: "Patch released upstream" leads, the dimmed
|
|
"No patch yet" pile follows. The count sits at the row's right edge. */
|
|
.updater-cve-group + .updater-cve-group { margin-top: 2px; }
|
|
.updater-cve-group-head {
|
|
display: flex; align-items: center; gap: 6px;
|
|
padding: 10px 0 6px;
|
|
}
|
|
.updater-cve-group-title {
|
|
font-size: 0.68rem; font-weight: 700; text-transform: uppercase; letter-spacing: .05em;
|
|
color: rgba(var(--text-rgb), 0.72);
|
|
}
|
|
.updater-cve-group-hint { font-size: 0.72rem; font-weight: 500; color: rgba(var(--text-rgb), 0.45); }
|
|
.updater-cve-group-hint::before { content: '·'; margin-right: 6px; opacity: 0.55; }
|
|
.updater-cve-group-n {
|
|
margin-left: auto;
|
|
font-variant-numeric: tabular-nums; font-weight: 700; font-size: 0.64rem;
|
|
padding: 1px 7px; border-radius: 999px;
|
|
background: rgba(var(--text-rgb), 0.12); color: rgba(var(--text-rgb), 0.7);
|
|
}
|
|
.updater-cve-group.is-nofix .updater-cve-group-n { background: rgba(var(--text-rgb), 0.08); color: rgba(var(--text-rgb), 0.55); }
|
|
.updater-cve-group.is-nofix .updater-cve { opacity: 0.72; }
|
|
.updater-cve-group .updater-cve:first-of-type { border-top: 0; }
|
|
|
|
/* Count pill next to the "Security" detail heading (e.g. "Security 28"). */
|
|
.updater-cve-count {
|
|
display: inline-block; vertical-align: middle; margin-left: 4px;
|
|
font-size: 0.68rem; font-weight: 700; line-height: 1; padding: 3px 8px;
|
|
border-radius: 999px;
|
|
background: rgba(var(--page-verify-rgb, var(--accent-rgb)), 0.16);
|
|
color: rgb(var(--page-verify-rgb, var(--accent-rgb)));
|
|
}
|
|
|
|
/* A long CVE list stays compact: once past a handful of rows it caps its height
|
|
and scrolls internally rather than pushing the whole page down. The first row
|
|
already carries a top border, so no seam is needed at the box edge. */
|
|
.updater-cve-scroll.is-scrollable {
|
|
max-height: 260px; overflow-y: auto;
|
|
padding-right: 8px; margin-right: -4px;
|
|
scrollbar-width: thin;
|
|
scrollbar-color: rgba(var(--text-rgb), 0.28) transparent;
|
|
/* Bottom-only fade hints there's more below; the top stays crisp so the
|
|
worst-severity rows (sorted first) are never dimmed. */
|
|
-webkit-mask-image: linear-gradient(to bottom, #000 calc(100% - 18px), transparent 100%);
|
|
mask-image: linear-gradient(to bottom, #000 calc(100% - 18px), transparent 100%);
|
|
}
|
|
.updater-cve-scroll.is-scrollable::-webkit-scrollbar { width: 9px; }
|
|
.updater-cve-scroll.is-scrollable::-webkit-scrollbar-track {
|
|
background: rgba(var(--text-rgb), 0.05); border-radius: 999px;
|
|
}
|
|
.updater-cve-scroll.is-scrollable::-webkit-scrollbar-thumb {
|
|
background: rgba(var(--text-rgb), 0.38); border-radius: 999px;
|
|
border: 2px solid transparent; background-clip: padding-box;
|
|
}
|
|
.updater-cve-scroll.is-scrollable::-webkit-scrollbar-thumb:hover { background: rgba(var(--text-rgb), 0.55); background-clip: padding-box; }
|
|
|
|
/* ---- Buttons ---- */
|
|
.updater-btn {
|
|
display: inline-flex; align-items: center; gap: 6px;
|
|
padding: 7px 14px; border-radius: 9px; cursor: pointer; font-size: 0.82rem; font-weight: 600;
|
|
background: rgba(var(--text-rgb), 0.07); color: rgba(var(--text-rgb), 0.9);
|
|
border: 1px solid rgba(var(--text-rgb), 0.12); transition: background 0.15s, border-color 0.15s;
|
|
}
|
|
.updater-btn:hover { background: rgba(var(--text-rgb), 0.12); }
|
|
.updater-btn-primary {
|
|
background: rgba(var(--page-rgb, var(--accent-rgb)), 0.2);
|
|
border-color: rgba(var(--page-rgb, var(--accent-rgb)), 0.45);
|
|
color: rgb(var(--page-rgb, var(--accent-rgb)));
|
|
}
|
|
.updater-btn-primary:hover { background: rgba(var(--page-rgb, var(--accent-rgb)), 0.3); }
|
|
/* Green, for moving to a newer release line. Distinct from the primary (accent)
|
|
Update so the two actions on a row never read as the same thing.
|
|
Pastel #86efac text on a saturated green rather than --status-success itself,
|
|
matching .app-tag.installed-tag: the raw #28a745 is too dark to read at button
|
|
size on dark themes. */
|
|
.updater-btn-success {
|
|
background: rgba(var(--status-success-rgb), 0.30);
|
|
border-color: rgba(var(--status-success-rgb), 0.65);
|
|
color: #86efac;
|
|
}
|
|
.updater-btn-success:hover { background: rgba(var(--status-success-rgb), 0.45); }
|
|
|
|
/* ---- Hints / empty states ---- */
|
|
.updater-hint { padding: 12px 15px; border-radius: 11px; margin-bottom: 14px; font-size: 0.84rem;
|
|
background: rgba(var(--page-rgb, var(--accent-rgb)), 0.08); border: 1px solid rgba(var(--page-rgb, var(--accent-rgb)), 0.2); color: rgba(var(--text-rgb), 0.75); }
|
|
.updater-empty { padding: 40px 20px; text-align: center; color: var(--text-secondary); display: flex; flex-direction: column; gap: 14px; align-items: center; }
|
|
|
|
@media (max-width: 600px) {
|
|
.updater-row { grid-template-columns: 1fr; gap: 6px; }
|
|
}
|