Compare commits

..

No commits in common. "cbbb978e5031008f3b39fe17a6ca6b116bc30c5a" and "296c6ddff1ff78e11afb140908eb6a0a74ea4644" have entirely different histories.

3 changed files with 5 additions and 49 deletions

View File

@ -977,20 +977,14 @@ class AppsManager {
const passKeys = Object.keys(cfg).filter(k => loginKey('PASSWORD').test(k)); const passKeys = Object.keys(cfg).filter(k => loginKey('PASSWORD').test(k));
const emailVal = emailKeys[0] ? cfg[emailKeys[0]] : ''; const emailVal = emailKeys[0] ? cfg[emailKeys[0]] : '';
const userVal = userKeys[0] ? cfg[userKeys[0]] : ''; const userVal = userKeys[0] ? cfg[userKeys[0]] : '';
// Only claim a username when the app actually declares one. This used to const identifier = emailVal || userVal || 'admin';
// fall back to the literal 'admin', which invented a login name for const userLabel = (emailVal || (typeof identifier === 'string' && identifier.includes('@'))) ? 'Email' : 'User';
// password-only apps — speedtest has a single CFG_SPEEDTEST_PASSWORD_1 and
// no user concept at all, yet its card advertised "User: admin".
// eoCredList omits any row whose value is null, so passing undefined for
// either half renders just the half that exists.
const identifier = emailVal || userVal;
const userLabel = (emailVal || identifier.includes('@')) ? 'Email' : 'User';
if (userKeys[0] || emailKeys[0] || passKeys[0]) { if (userKeys[0] || emailKeys[0] || passKeys[0]) {
creds.push({ creds.push({
title: `${app.name.split(' - ')[0]} Login`, title: `${app.name.split(' - ')[0]} Login`,
username: identifier || undefined, username: identifier,
userLabel, userLabel,
password: passKeys[0] ? (cfg[passKeys[0]] || '(not generated)') : undefined password: cfg[passKeys[0]] || '(not generated)'
}); });
} }

View File

@ -117,22 +117,6 @@
background: rgba(var(--text-rgb), .03); background: rgba(var(--text-rgb), .03);
border-color: rgba(var(--text-rgb), .08); border-color: rgba(var(--text-rgb), .08);
} }
/* Rows whose action is just "go to that tab" are clickable end to end the
button inside remains the affordance (and the focusable control), the row
only widens the target. Hover lifts the whole row AND the button together so
it reads as one control, not a row that happens to sit under the cursor. */
.ov-board-row.ov-clickable { cursor: pointer; transition: background .12s ease, border-color .12s ease; }
.ov-board-row.ov-clickable:hover {
background: rgba(var(--page-rgb, var(--accent-rgb)), .11);
border-color: rgba(var(--page-rgb, var(--accent-rgb)), .28);
}
.ov-board-row.ov-clickable.ok:hover,
.ov-board-row.ov-clickable.none:hover {
background: rgba(var(--text-rgb), .06);
border-color: rgba(var(--text-rgb), .14);
}
/* Keep the button's own hover styling from reading as "only this bit is live". */
.ov-board-row.ov-clickable:hover .ov-board-actions .updater-btn { filter: brightness(1.08); }
.ov-dot { width: 9px; height: 9px; border-radius: 50%; flex: 0 0 auto; } .ov-dot { width: 9px; height: 9px; border-radius: 50%; flex: 0 0 auto; }
.ov-dot.ok { background: #36d399; } .ov-dot.ok { background: #36d399; }
.ov-dot.warn { background: #fbbd23; } .ov-dot.warn { background: #fbbd23; }

View File

@ -316,13 +316,6 @@ class OverviewManager {
if (oa) { if (oa) {
switch (oa.dataset.overviewAction) { switch (oa.dataset.overviewAction) {
case 'goto': case 'goto':
// Whole-row navigation is a convenience over the row's own button —
// don't fire it when the click was really the end of a text drag
// inside the row. (Clicks on the button itself always count.)
if (oa.classList.contains('ov-board-row')) {
const sel = window.getSelection && window.getSelection();
if (sel && !sel.isCollapsed && String(sel).trim()) break;
}
// Board rows can land on Updates pre-filtered (e.g. Security → the // Board rows can land on Updates pre-filtered (e.g. Security → the
// affected apps only), so honor an optional data-filter on the way. // affected apps only), so honor an optional data-filter on the way.
if (oa.dataset.filter) this.filter = oa.dataset.filter; if (oa.dataset.filter) this.filter = oa.dataset.filter;
@ -432,7 +425,6 @@ class OverviewManager {
text: `${waiting.length} update${waiting.length === 1 ? '' : 's'} waiting for you`, text: `${waiting.length} update${waiting.length === 1 ? '' : 's'} waiting for you`,
sub: `${esc(listOf(waiting))} — a recovery snapshot is taken before each update` sub: `${esc(listOf(waiting))} — a recovery snapshot is taken before each update`
+ (selfing ? ` · ${selfing} more will install automatically` : ''), + (selfing ? ` · ${selfing} more will install automatically` : ''),
nav: { tab: 'updates', filter: 'updates' },
actions: goto('updates', 'Review', 'updates') actions: goto('updates', 'Review', 'updates')
+ `<button class="updater-btn updater-btn-primary" data-updater-action="update-all">Update all</button>`, + `<button class="updater-btn updater-btn-primary" data-updater-action="update-all">Update all</button>`,
}); });
@ -446,7 +438,6 @@ class OverviewManager {
hue: 'updates', icon: '⬆️', kind: 'ok', hue: 'updates', icon: '⬆️', kind: 'ok',
text: `${pending.length} update${pending.length === 1 ? '' : 's'} installing automatically${winBit}`, text: `${pending.length} update${pending.length === 1 ? '' : 's'} installing automatically${winBit}`,
sub: `${esc(listOf(pending))} — each is snapshotted first, and rolled back if it fails`, sub: `${esc(listOf(pending))} — each is snapshotted first, and rolled back if it fails`,
nav: { tab: 'updates', filter: 'updates' },
actions: goto('updates', 'Review', 'updates'), actions: goto('updates', 'Review', 'updates'),
}); });
} }
@ -478,7 +469,6 @@ class OverviewManager {
text: `${u.totalCves} known CVE${u.totalCves === 1 ? '' : 's'} across ${hit} app${hit === 1 ? '' : 's'}`, text: `${u.totalCves} known CVE${u.totalCves === 1 ? '' : 's'} across ${hit} app${hit === 1 ? '' : 's'}`,
badge: worst ? ` <span class="updater-badge sev-${worst}">${worst}</span>` : '', badge: worst ? ` <span class="updater-badge sev-${worst}">${worst}</span>` : '',
sub: order.filter((s) => sev[s]).map((s) => `${sev[s]} ${s}`).join(' · '), sub: order.filter((s) => sev[s]).map((s) => `${sev[s]} ${s}`).join(' · '),
nav: { tab: 'updates', filter: 'security' },
actions: goto('updates', 'Review', 'security'), actions: goto('updates', 'Review', 'security'),
}); });
} else { } else {
@ -492,7 +482,6 @@ class OverviewManager {
hue: 'updater', icon: '✨', kind: 'warn', hue: 'updater', icon: '✨', kind: 'warn',
text: `${u.improvements} signed improvement${u.improvements === 1 ? '' : 's'} ready to apply`, text: `${u.improvements} signed improvement${u.improvements === 1 ? '' : 's'} ready to apply`,
sub: 'individually reversible — a snapshot is taken first', sub: 'individually reversible — a snapshot is taken first',
nav: { tab: 'improvements' },
actions: goto('improvements', 'View'), actions: goto('improvements', 'View'),
}); });
} else { } else {
@ -508,7 +497,6 @@ class OverviewManager {
hue: 'backups', icon: '💾', kind: 'warn', hue: 'backups', icon: '💾', kind: 'warn',
text: 'No backup location configured', text: 'No backup location configured',
sub: 'apps cant be protected until theres somewhere to back them up to', sub: 'apps cant be protected until theres somewhere to back them up to',
nav: { tab: 'backups' },
actions: goto('backups', 'Open Backups'), actions: goto('backups', 'Open Backups'),
}); });
} else if (b.never + b.old) { } else if (b.never + b.old) {
@ -517,7 +505,6 @@ class OverviewManager {
hue: 'backups', icon: '💾', kind: 'warn', hue: 'backups', icon: '💾', kind: 'warn',
text: `${n} app${n === 1 ? '' : 's'} need${n === 1 ? 's' : ''} a fresh backup`, text: `${n} app${n === 1 ? '' : 's'} need${n === 1 ? 's' : ''} a fresh backup`,
sub: [b.never && `${b.never} never backed up`, b.old && `${b.old} older than a week`].filter(Boolean).join(' · '), sub: [b.never && `${b.never} never backed up`, b.old && `${b.old} older than a week`].filter(Boolean).join(' · '),
nav: { tab: 'backups' },
actions: goto('backups', 'Open Backups'), actions: goto('backups', 'Open Backups'),
}); });
} else { } else {
@ -529,17 +516,8 @@ class OverviewManager {
return rows; return rows;
} }
// A row whose action is a plain "go to that tab" (Review / View / Open
// Backups) makes the WHOLE row a hit target for it — the button stays put as
// the visible affordance and the only focusable control, so the row is a
// mouse convenience layered on top rather than a nested role="button" (which
// would swallow the buttons it contains). Rows that only carry a real
// operation (Update all) or no action at all stay inert.
boardRow(r) { boardRow(r) {
const nav = r.nav return `<div class="ov-board-row ${r.kind}" style="--page: var(--page-${r.hue}); --page-rgb: var(--page-${r.hue}-rgb);">
? ` data-overview-action="goto" data-tab="${r.nav.tab}"${r.nav.filter ? ` data-filter="${r.nav.filter}"` : ''}`
: '';
return `<div class="ov-board-row ${r.kind}${r.nav ? ' ov-clickable' : ''}"${nav} style="--page: var(--page-${r.hue}); --page-rgb: var(--page-${r.hue}-rgb);">
<span class="ov-dot ${r.kind}" aria-hidden="true"></span> <span class="ov-dot ${r.kind}" aria-hidden="true"></span>
<span class="ov-board-icon" aria-hidden="true">${r.icon}</span> <span class="ov-board-icon" aria-hidden="true">${r.icon}</span>
<div class="ov-board-main"> <div class="ov-board-main">