fix(setup): use the shared eo-modal for drive details

The details popup was a hand-rolled overlay instead of openEoModal, which
every other modal in the WebUI uses. Two things were wrong with that.

It was appended to the wizard container rather than document.body. The
wizard has a backdrop-filter, which makes it a containing block, so the
modal's position:fixed resolved against the wizard instead of the
viewport — the backdrop covered part of the screen and the panel sat
off-centre.

And once switched to eo-modal it still didn't appear at all: .eo-modal is
z-index 1100 while the wizard overlay is 9999, so it rendered behind the
wizard and the Details button looked inert. Raised only under
body.setup-wizard-open, so no other overlay's stacking is touched — the
wizard is the exceptional full-screen surface, so it is the wizard's
stylesheet that declares modals must sit above it.

The bespoke modal chrome is gone from setup-wizard.css; what remains is
the storage-specific content (spec table, checks list, fstab box) that
renders inside .eo-modal-body. Header, sections, backdrop, Escape,
click-outside and the footer action now all come from the shared helper.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
librelad 2026-08-26 01:57:57 +01:00
parent 7eb6d36d55
commit e71fc15f38
2 changed files with 62 additions and 76 deletions

View File

@ -1268,36 +1268,15 @@ body.setup-wizard-open {
.setup-storage-details:hover { background: rgba(255, 255, 255, 0.16); }
.setup-storage-flags { opacity: 0.9; }
/* Modal */
.setup-modal-backdrop {
position: fixed; inset: 0; z-index: 10000;
background: rgba(4, 20, 38, 0.62);
backdrop-filter: blur(3px);
display: flex; align-items: center; justify-content: center;
padding: 24px;
}
.setup-modal {
width: min(680px, 100%);
max-height: 82vh;
overflow: auto;
border-radius: 14px;
border: 1px solid rgba(255, 255, 255, 0.18);
background: rgba(12, 48, 84, 0.97);
box-shadow: 0 24px 64px rgba(0, 0, 0, 0.45);
}
.setup-modal-head {
display: flex; align-items: center; justify-content: space-between;
gap: 12px; padding: 16px 20px;
border-bottom: 1px solid rgba(255, 255, 255, 0.14);
}
.setup-modal-title { font-weight: 700; font-size: 1.05em; word-break: break-all; }
.setup-modal-close {
background: none; border: none; color: inherit;
font-size: 1.6em; line-height: 1; cursor: pointer; opacity: 0.75;
}
.setup-modal-close:hover { opacity: 1; }
.setup-modal-body { padding: 18px 20px 22px; }
/* The wizard is a full-screen overlay at z-index 9999; the shared modal sits at
1100. A modal opened from inside the wizard therefore rendered BEHIND it and
looked like the Details button did nothing. Raise it only while the wizard is
up, so no other overlay's stacking is affected. */
body.setup-wizard-open .eo-modal { z-index: 10000; }
/* Modal chrome comes from the shared eo-modal (core/overlays). Only the
storage-specific CONTENT styles below are ours they render inside
.eo-modal-body. */
.setup-storage-spec { width: 100%; border-collapse: collapse; margin-bottom: 18px; }
.setup-storage-spec th {
text-align: left; font-weight: 500; opacity: 0.72;

View File

@ -504,14 +504,20 @@ class SetupWizard {
}
}
// Details modal: the technical spec, every check with its full explanation,
// and — when the drive isn\u2019t in fstab — the offer to make it permanent.
// Details modal — the technical spec, every check with its full explanation,
// and (when the drive isn't in fstab) the offer to make it permanent.
//
// Uses the shared openEoModal rather than a bespoke overlay. Two reasons: it
// is what every other modal in the WebUI looks like, and it appends to
// document.body — a hand-rolled position:fixed backdrop appended INSIDE the
// wizard container resolves against the wizard's containing block (it has a
// backdrop-filter), so it covered part of the screen instead of all of it.
showStorageDetails(idx) {
const c = this.storageCandidates[idx];
if (!c) return;
const esc = (s) => this.escapeHtml(s);
const specRows = [
const spec = [
['Mount point', c.path],
['Device', c.device],
['Filesystem', c.fstype],
@ -523,7 +529,7 @@ class SetupWizard {
].map(([k, v]) => `<tr><th>${esc(k)}</th><td><code>${esc(v)}</code></td></tr>`).join('');
const icon = { refuse: '\u26d4', warn: '\u26a0\ufe0f', info: '\u2705' };
const checkRows = (c.checks || []).map(k => `
const checks = (c.checks || []).map(k => `
<li class="setup-storage-check setup-storage-check-${esc(k.severity)}">
<span class="setup-storage-check-icon">${icon[k.severity] || ''}</span>
<span>
@ -532,64 +538,65 @@ class SetupWizard {
</span>
</li>`).join('');
const fstab = c.fstab_line ? `
<div class="setup-storage-fstab">
<div class="setup-storage-fstab-title">Make this drive mount automatically</div>
<p>
The system doesn\u2019t mount this drive at boot, so apps stored on it won\u2019t start
until it is mounted again. LibrePortal can add it to <code>/etc/fstab</code> for you.
</p>
<pre><code>${esc(c.fstab_line)}</code></pre>
<label class="setup-storage-fstab-opt">
<input type="checkbox" data-storage-fstab="${esc(c.path)}"
${this.fstabWanted.includes(c.path) ? 'checked' : ''}>
<span>Add this line during install</span>
</label>
<p class="setup-storage-fstab-note">
Written with <code>nofail</code>, so if the drive is missing the machine still boots
normally. Your current <code>/etc/fstab</code> is backed up first.
</p>
</div>` : '';
const parts = [
window.eoSection ? window.eoSection('Drive', `<table class="setup-storage-spec">${spec}</table>`)
: `<table class="setup-storage-spec">${spec}</table>`,
window.eoSection ? window.eoSection('Checks', `<ul class="setup-storage-checks">${checks}</ul>`)
: `<ul class="setup-storage-checks">${checks}</ul>`,
];
const modal = document.createElement('div');
modal.className = 'setup-modal-backdrop';
modal.innerHTML = `
<div class="setup-modal" role="dialog" aria-modal="true" aria-label="Drive details">
<div class="setup-modal-head">
<span class="setup-modal-title">${esc(c.path)}</span>
<button type="button" class="setup-modal-close" aria-label="Close">&times;</button>
</div>
<div class="setup-modal-body">
<table class="setup-storage-spec">${specRows}</table>
<ul class="setup-storage-checks">${checkRows}</ul>
${fstab}
</div>
</div>`;
if (c.fstab_line) {
parts.push(`
<div class="setup-storage-fstab">
<div class="setup-storage-fstab-title">Make this drive mount automatically</div>
<p>
The system doesn\u2019t mount this drive at boot, so apps stored on it won\u2019t start
until it is mounted again. LibrePortal can add it to <code>/etc/fstab</code> for you.
</p>
<pre><code>${esc(c.fstab_line)}</code></pre>
<label class="setup-storage-fstab-opt">
<input type="checkbox" data-storage-fstab="${esc(c.path)}"
${this.fstabWanted.includes(c.path) ? 'checked' : ''}>
<span>Add this line during install</span>
</label>
<p class="setup-storage-fstab-note">
Written with <code>nofail</code>, so if the drive is missing the machine still boots
normally. Your current <code>/etc/fstab</code> is backed up first.
</p>
</div>`);
}
const close = () => modal.remove();
modal.addEventListener('click', (e) => { if (e.target === modal) close(); });
modal.querySelector('.setup-modal-close').addEventListener('click', close);
document.addEventListener('keydown', function onKey(e) {
if (e.key === 'Escape') { close(); document.removeEventListener('keydown', onKey); }
// No openEoModal (shouldn't happen — index.html loads it before this file,
// but the wizard must not become unusable if it ever doesn't).
if (typeof window.openEoModal !== 'function') {
console.warn('[setup] openEoModal unavailable; skipping details modal');
return;
}
const m = window.openEoModal({
id: 'lp-storage-details',
size: 'md',
title: c.path,
desc: `${c.size} drive \u00b7 ${c.free} free \u00b7 ${c.fstype}${c.removable ? ' \u00b7 removable' : ''}`,
body: parts.join(''),
actions: [{ label: 'Close', variant: 'secondary' }],
});
// Remember the fstab choice on the wizard, not in the DOM: the modal is
// destroyed on close and the choice has to survive to submit().
const fstabBox = modal.querySelector('[data-storage-fstab]');
// The choice is remembered on the wizard, not in the DOM: the modal is
// destroyed on close and this has to survive until submit().
const fstabBox = m.bodyEl.querySelector('[data-storage-fstab]');
if (fstabBox) {
fstabBox.addEventListener('change', () => {
const path = fstabBox.dataset.storageFstab;
this.fstabWanted = this.fstabWanted.filter(p => p !== path);
if (fstabBox.checked) {
this.fstabWanted.push(path);
// Adding it to fstab only makes sense if the drive is actually used.
// Mounting it at boot only means anything if the drive is used.
const own = this.container.querySelector(`[data-storage-path="${CSS.escape(path)}"]`);
if (own && !own.disabled) own.checked = true;
}
});
}
this.container.appendChild(modal);
}
escapeHtml(s) {