Compare commits
No commits in common. "1adc480099106da160b03699dcd8fec60226aa69" and "35c37b50868778e72896b2619be53f4dd95c68c5" have entirely different histories.
1adc480099
...
35c37b5086
@ -936,6 +936,62 @@ body.setup-wizard-open {
|
|||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* "No apps selected" confirm — a self-contained dialog layered over the
|
||||||
|
wizard card (the shared confirmation-dialog component isn't loaded this
|
||||||
|
early in boot). Backdrop dims the wizard; the box mirrors .setup-card. */
|
||||||
|
.setup-confirm {
|
||||||
|
/* Fixed + explicit offsets (not `inset`/`absolute`) so it reliably covers
|
||||||
|
the whole wizard regardless of the flex flow it's appended into. */
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
z-index: 10000;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 20px;
|
||||||
|
background: rgba(var(--bg-rgb), 0.55);
|
||||||
|
backdrop-filter: blur(4px);
|
||||||
|
opacity: 0;
|
||||||
|
transition: opacity 0.2s ease;
|
||||||
|
}
|
||||||
|
.setup-confirm.active { opacity: 1; }
|
||||||
|
|
||||||
|
.setup-confirm-box {
|
||||||
|
width: min(440px, 100%);
|
||||||
|
background: rgba(var(--bg-rgb), 0.92);
|
||||||
|
border: 1px solid rgba(var(--accent-rgb), 0.28);
|
||||||
|
border-radius: 16px;
|
||||||
|
padding: 26px 24px 22px;
|
||||||
|
text-align: center;
|
||||||
|
box-shadow: 0 24px 60px rgba(0, 0, 0, 0.45);
|
||||||
|
transform: translateY(8px) scale(0.98);
|
||||||
|
transition: transform 0.2s ease;
|
||||||
|
}
|
||||||
|
.setup-confirm.active .setup-confirm-box { transform: translateY(0) scale(1); }
|
||||||
|
|
||||||
|
.setup-confirm-icon { font-size: 34px; line-height: 1; margin-bottom: 10px; }
|
||||||
|
|
||||||
|
.setup-confirm-title {
|
||||||
|
margin: 0 0 8px;
|
||||||
|
font-size: 18px;
|
||||||
|
font-weight: 700;
|
||||||
|
color: var(--text-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.setup-confirm-text {
|
||||||
|
margin: 0 0 20px;
|
||||||
|
font-size: 13.5px;
|
||||||
|
line-height: 1.5;
|
||||||
|
color: var(--text-secondary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.setup-confirm-actions { display: flex; gap: 10px; }
|
||||||
|
.setup-confirm-actions .setup-btn-back { flex: 1; }
|
||||||
|
.setup-confirm-proceed { flex: 1.4; }
|
||||||
|
|
||||||
/* Top-nav disabled state — applied while setup isn't complete. */
|
/* Top-nav disabled state — applied while setup isn't complete. */
|
||||||
.topbar-nav.setup-needed .nav-item {
|
.topbar-nav.setup-needed .nav-item {
|
||||||
opacity: 0.35;
|
opacity: 0.35;
|
||||||
|
|||||||
@ -702,6 +702,23 @@ class SetupWizard {
|
|||||||
|
|
||||||
const apps = Array.from(this.container.querySelectorAll('.setup-app input[type=checkbox][data-app]:checked'))
|
const apps = Array.from(this.container.querySelectorAll('.setup-app input[type=checkbox][data-app]:checked'))
|
||||||
.map(cb => cb.dataset.app);
|
.map(cb => cb.dataset.app);
|
||||||
|
|
||||||
|
// Zero apps is valid but almost always a mistake — the whole point of the
|
||||||
|
// wizard is to stand up a *working* install. Without a guard, setup would
|
||||||
|
// finish in seconds having installed nothing and fast-forward the user into
|
||||||
|
// an empty App Center wondering why "setup complete" left them with nothing.
|
||||||
|
// Confirm first, with a self-contained in-wizard dialog (the shared
|
||||||
|
// confirmation-dialog component isn't loaded this early in boot, so we
|
||||||
|
// can't rely on window.showConfirmation here). _zeroAppsConfirmed lets the
|
||||||
|
// confirm callback re-enter submit() and skip this branch the second time.
|
||||||
|
if (apps.length === 0 && !this._zeroAppsConfirmed) {
|
||||||
|
this._submitting = false;
|
||||||
|
this.showZeroAppConfirm(
|
||||||
|
() => { this._zeroAppsConfirmed = true; this.submit(); }, // Continue
|
||||||
|
() => { this.showStep(3); } // Go back to apps
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
// Sub-options live under their parent app and only matter if the parent
|
// Sub-options live under their parent app and only matter if the parent
|
||||||
// is installed. Shape: { crowdsec: { dashboard: true }, ... }
|
// is installed. Shape: { crowdsec: { dashboard: true }, ... }
|
||||||
const appOptions = {};
|
const appOptions = {};
|
||||||
@ -767,6 +784,60 @@ class SetupWizard {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Self-contained "no apps selected" confirm. Rendered inside the wizard's
|
||||||
|
// own full-screen shell (so it inherits the aurora aesthetic and doesn't
|
||||||
|
// depend on the shared confirmation-dialog component, which hasn't loaded
|
||||||
|
// this early in boot). Calls onProceed / onCancel exactly once.
|
||||||
|
showZeroAppConfirm(onProceed, onCancel) {
|
||||||
|
// Never stack two.
|
||||||
|
document.querySelector('.setup-confirm')?.remove();
|
||||||
|
|
||||||
|
const overlay = document.createElement('div');
|
||||||
|
overlay.className = 'setup-confirm';
|
||||||
|
overlay.innerHTML = `
|
||||||
|
<div class="setup-confirm-box" role="dialog" aria-modal="true" aria-labelledby="sw-confirm-title">
|
||||||
|
<div class="setup-confirm-icon" aria-hidden="true">🌑</div>
|
||||||
|
<h3 class="setup-confirm-title" id="sw-confirm-title">Install with no apps?</h3>
|
||||||
|
<p class="setup-confirm-text">
|
||||||
|
You haven't selected any apps, so nothing will be installed. Setup will
|
||||||
|
still finish, but you'll start with an empty install — you can always add
|
||||||
|
apps from the App Center later.
|
||||||
|
</p>
|
||||||
|
<div class="setup-confirm-actions">
|
||||||
|
<button type="button" class="setup-btn-back" data-act="cancel">← Pick apps</button>
|
||||||
|
<button type="button" class="setup-launch setup-confirm-proceed" data-act="proceed">
|
||||||
|
<span>Continue with no apps</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
// Mount on <body>, not the wizard: the wizard is an `.aurora-bg` surface
|
||||||
|
// whose `.aurora-bg > :not(.aurora-stars){position:relative}` FX-stacking
|
||||||
|
// rule would otherwise force this fixed overlay back to `relative` and
|
||||||
|
// collapse it into the flex flow. A modal belongs above everything anyway.
|
||||||
|
document.body.appendChild(overlay);
|
||||||
|
// Entrance on next frame so the CSS transition plays.
|
||||||
|
requestAnimationFrame(() => overlay.classList.add('active'));
|
||||||
|
|
||||||
|
let done = false;
|
||||||
|
const close = (cb) => {
|
||||||
|
if (done) return;
|
||||||
|
done = true;
|
||||||
|
overlay.classList.remove('active');
|
||||||
|
setTimeout(() => overlay.remove(), 250);
|
||||||
|
if (typeof cb === 'function') cb();
|
||||||
|
};
|
||||||
|
overlay.querySelector('[data-act="proceed"]').addEventListener('click', () => close(onProceed));
|
||||||
|
overlay.querySelector('[data-act="cancel"]').addEventListener('click', () => close(onCancel));
|
||||||
|
// Backdrop click / Escape cancels (same as "Pick apps").
|
||||||
|
overlay.addEventListener('click', (e) => { if (e.target === overlay) close(onCancel); });
|
||||||
|
const onKey = (e) => {
|
||||||
|
if (e.key === 'Escape') { document.removeEventListener('keydown', onKey); close(onCancel); }
|
||||||
|
};
|
||||||
|
document.addEventListener('keydown', onKey);
|
||||||
|
setTimeout(() => overlay.querySelector('[data-act="proceed"]')?.focus(), 260);
|
||||||
|
}
|
||||||
|
|
||||||
handoffToTasks({ firstTaskId, finalizeTaskId, setupGroup, installName, taskIds }) {
|
handoffToTasks({ firstTaskId, finalizeTaskId, setupGroup, installName, taskIds }) {
|
||||||
try {
|
try {
|
||||||
sessionStorage.setItem('libreportal_setup_handoff', JSON.stringify({
|
sessionStorage.setItem('libreportal_setup_handoff', JSON.stringify({
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user