Drop the section headings — the step name is already at the top
Every step's name is in the progress bar. Repeating it as a heading directly beneath was the same word twice with nothing between them: "Step 4 of 8 — Storage" followed immediately by "STORAGE". Eight headings across both branches, all gone. Three of them carried a tooltip worth keeping — what a storage location is, that a backup password cannot be recovered, what a .lpapp is. Those moved to a tooltip beside the step name in the progress bar, which is where the heading actually is now. Steps that never had an explanation show no marker. Also removed the Backup step's "Where is it? Backups live in a repository…" paragraph. The step is called Backup, the first field asks where it lives, and the repository-not-a-file point is what the step tooltip is for. lp-restore-wizard-test asserted the old heading matched the step name; it now asserts there are no headings left anywhere and that the progress bar carries both the name and the explanation. Both browser tests reported "wizard handle missing" when the wizard simply was not on screen — it only renders while setup is incomplete, and a stray .setup_complete from an earlier run hides it. They now say which of the two it is, since the first sends you looking for a JS error that is not there. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
aff9b398da
commit
f736ec6501
@ -28,6 +28,18 @@ class SetupWizard {
|
|||||||
// must not be asked for an install name, domains or an app list — the
|
// must not be asked for an install name, domains or an app list — the
|
||||||
// backup already answers all three, and asking would invite someone to
|
// backup already answers all three, and asking would invite someone to
|
||||||
// contradict what is about to be restored over their answer.
|
// contradict what is about to be restored over their answer.
|
||||||
|
// Tips that used to hang off a section heading. The heading itself is the
|
||||||
|
// progress bar's step name — repeating it as a title directly beneath was
|
||||||
|
// the same word twice with nothing between them — so the explanation moves
|
||||||
|
// to where the name actually is.
|
||||||
|
this.stepTips = {
|
||||||
|
Storage: 'Apps normally live on the system disk. If you have another drive, you can register it here and choose per app where its data goes.',
|
||||||
|
Backups: 'Snapshots of your apps and settings, taken on a schedule. Encrypted, so keep the password somewhere other than this machine \u2014 without it a backup cannot be opened, not even by us.',
|
||||||
|
Import: "Bring apps over from another LibrePortal using .lpapp files made with 'libreportal app export'. Point at a file or a folder of them, somewhere on this machine.",
|
||||||
|
Backup: 'Backups live in a repository \u2014 a folder on a disk, or a remote server. Not a single file, so point us at the repository itself.',
|
||||||
|
Contents: 'What this backup holds, and what it would mean on this machine.',
|
||||||
|
Rebuild: 'What is about to happen, and in what order.'
|
||||||
|
};
|
||||||
this.stepNames = ['Start', 'Experience', 'Identity', 'Domains', 'Storage', 'Backups', 'Import', 'Recommended', 'Metrics',
|
this.stepNames = ['Start', 'Experience', 'Identity', 'Domains', 'Storage', 'Backups', 'Import', 'Recommended', 'Metrics',
|
||||||
'Backup', 'Contents', 'Rebuild'];
|
'Backup', 'Contents', 'Rebuild'];
|
||||||
this.stepIcons = ['\u{1F9ED}', '\u{1F331}', '\u{1FA90}', '\u{1F6F0}\u{FE0F}', '\u{1F4BE}', '\u{1F6DF}', '\u{1F4E6}', '\u{1F6E1}\u{FE0F}', '\u{1F4CA}',
|
this.stepIcons = ['\u{1F9ED}', '\u{1F331}', '\u{1FA90}', '\u{1F6F0}\u{FE0F}', '\u{1F4BE}', '\u{1F6DF}', '\u{1F4E6}', '\u{1F6E1}\u{FE0F}', '\u{1F4CA}',
|
||||||
@ -358,9 +370,6 @@ class SetupWizard {
|
|||||||
otherwise there is exactly one answer and the step is skipped. -->
|
otherwise there is exactly one answer and the step is skipped. -->
|
||||||
<section class="setup-step" data-step="4">
|
<section class="setup-step" data-step="4">
|
||||||
<div class="setup-section">
|
<div class="setup-section">
|
||||||
<div class="setup-section-title">Storage
|
|
||||||
<span class="setup-tooltip" tabindex="0" data-tip="Apps normally live on the system disk. If you have another drive, you can register it here and choose per app where its data goes.">?</span>
|
|
||||||
</div>
|
|
||||||
<div id="sw-storage-choices"></div>
|
<div id="sw-storage-choices"></div>
|
||||||
<div id="sw-storage-paths" style="display:none;"></div>
|
<div id="sw-storage-paths" style="display:none;"></div>
|
||||||
<div class="setup-storage-divider" id="sw-storage-drives-head" style="display:none;"><span>Drives</span></div>
|
<div class="setup-storage-divider" id="sw-storage-drives-head" style="display:none;"><span>Drives</span></div>
|
||||||
@ -374,9 +383,6 @@ class SetupWizard {
|
|||||||
this asks rather than waiting to be found in the Backup page. -->
|
this asks rather than waiting to be found in the Backup page. -->
|
||||||
<section class="setup-step" data-step="5">
|
<section class="setup-step" data-step="5">
|
||||||
<div class="setup-section">
|
<div class="setup-section">
|
||||||
<div class="setup-section-title">Backups
|
|
||||||
<span class="setup-tooltip" tabindex="0" data-tip="Snapshots of your apps and settings, taken on a schedule. Encrypted, so keep the password somewhere other than this machine — without it a backup cannot be opened, not even by us.">?</span>
|
|
||||||
</div>
|
|
||||||
<div id="sw-backup-choices"></div>
|
<div id="sw-backup-choices"></div>
|
||||||
<div class="setup-storage-divider">Destinations</div>
|
<div class="setup-storage-divider">Destinations</div>
|
||||||
<div id="sw-backup-dests"></div>
|
<div id="sw-backup-dests"></div>
|
||||||
@ -390,9 +396,6 @@ class SetupWizard {
|
|||||||
browser (unlike a backup repository — see first-run-restore.md). -->
|
browser (unlike a backup repository — see first-run-restore.md). -->
|
||||||
<section class="setup-step" data-step="6">
|
<section class="setup-step" data-step="6">
|
||||||
<div class="setup-section">
|
<div class="setup-section">
|
||||||
<div class="setup-section-title">Import
|
|
||||||
<span class="setup-tooltip" tabindex="0" data-tip="Bring apps over from another LibrePortal using .lpapp files made with 'libreportal app export'. Point at a file or a folder of them, somewhere on this machine.">?</span>
|
|
||||||
</div>
|
|
||||||
<div class="setup-storage-choice">
|
<div class="setup-storage-choice">
|
||||||
<span class="setup-storage-choice-label">Folder or file</span>
|
<span class="setup-storage-choice-label">Folder or file</span>
|
||||||
<input type="text" id="sw-import-path" class="form-control" placeholder="/mnt/usb/exports">
|
<input type="text" id="sw-import-path" class="form-control" placeholder="/mnt/usb/exports">
|
||||||
@ -408,7 +411,6 @@ class SetupWizard {
|
|||||||
<!-- Step 7: Recommended apps (Traefik + Fail2ban) -->
|
<!-- Step 7: Recommended apps (Traefik + Fail2ban) -->
|
||||||
<section class="setup-step" data-step="7">
|
<section class="setup-step" data-step="7">
|
||||||
<div class="setup-section">
|
<div class="setup-section">
|
||||||
<div class="setup-section-title">Recommended Apps</div>
|
|
||||||
<p class="setup-section-hint">Pre-selected to give you a working install out of the box.</p>
|
<p class="setup-section-hint">Pre-selected to give you a working install out of the box.</p>
|
||||||
<div id="sw-apps-recommended"></div>
|
<div id="sw-apps-recommended"></div>
|
||||||
|
|
||||||
@ -432,7 +434,6 @@ class SetupWizard {
|
|||||||
is skipped when the user chose Beginner on step 1. -->
|
is skipped when the user chose Beginner on step 1. -->
|
||||||
<section class="setup-step" data-step="8">
|
<section class="setup-step" data-step="8">
|
||||||
<div class="setup-section">
|
<div class="setup-section">
|
||||||
<div class="setup-section-title">Metrics Apps</div>
|
|
||||||
<p class="setup-section-hint">Optional. Install these to enable per-app "Export metrics to Grafana" later.</p>
|
<p class="setup-section-hint">Optional. Install these to enable per-app "Export metrics to Grafana" later.</p>
|
||||||
<div id="sw-apps-metrics"></div>
|
<div id="sw-apps-metrics"></div>
|
||||||
</div>
|
</div>
|
||||||
@ -443,11 +444,6 @@ class SetupWizard {
|
|||||||
<!-- Which backup to rebuild from -->
|
<!-- Which backup to rebuild from -->
|
||||||
<section class="setup-step" data-step="9">
|
<section class="setup-step" data-step="9">
|
||||||
<div class="setup-section">
|
<div class="setup-section">
|
||||||
<div class="setup-section-title">Backup</div>
|
|
||||||
<p class="setup-section-hint">
|
|
||||||
Where is it? Backups live in a repository \u2014 a folder on a
|
|
||||||
disk, or a remote server. Not a single file.
|
|
||||||
</p>
|
|
||||||
<div id="sw-rs-fields"></div>
|
<div id="sw-rs-fields"></div>
|
||||||
|
|
||||||
<div class="setup-field">
|
<div class="setup-field">
|
||||||
@ -468,8 +464,6 @@ class SetupWizard {
|
|||||||
<!-- What is in there -->
|
<!-- What is in there -->
|
||||||
<section class="setup-step" data-step="10">
|
<section class="setup-step" data-step="10">
|
||||||
<div class="setup-section">
|
<div class="setup-section">
|
||||||
<div class="setup-section-title">Contents</div>
|
|
||||||
<p class="setup-section-hint">What this backup would bring back.</p>
|
|
||||||
<div id="sw-rs-contents">
|
<div id="sw-rs-contents">
|
||||||
<p class="setup-section-hint">Go back a step and read the backup first.</p>
|
<p class="setup-section-hint">Go back a step and read the backup first.</p>
|
||||||
</div>
|
</div>
|
||||||
@ -479,8 +473,6 @@ class SetupWizard {
|
|||||||
<!-- Confirm -->
|
<!-- Confirm -->
|
||||||
<section class="setup-step" data-step="11">
|
<section class="setup-step" data-step="11">
|
||||||
<div class="setup-section">
|
<div class="setup-section">
|
||||||
<div class="setup-section-title">Rebuild</div>
|
|
||||||
<p class="setup-section-hint">What is about to happen, and in what order.</p>
|
|
||||||
<div id="sw-rs-summary"></div>
|
<div id="sw-rs-summary"></div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
@ -2327,7 +2319,10 @@ class SetupWizard {
|
|||||||
const icon = this.stepIcons[actual];
|
const icon = this.stepIcons[actual];
|
||||||
this.container.querySelector('#sw-progress-fill').style.width = `${pct}%`;
|
this.container.querySelector('#sw-progress-fill').style.width = `${pct}%`;
|
||||||
this.container.querySelector('#sw-progress-step').innerHTML =
|
this.container.querySelector('#sw-progress-step').innerHTML =
|
||||||
`Step ${this.currentStep + 1} of ${this.totalSteps} <span class="setup-progress-sep">—</span> <span class="setup-progress-icon">${icon}</span> <span class="setup-progress-name">${name}</span>`;
|
`Step ${this.currentStep + 1} of ${this.totalSteps} <span class="setup-progress-sep">—</span> <span class="setup-progress-icon">${icon}</span> <span class="setup-progress-name">${name}</span>` +
|
||||||
|
(this.stepTips[name]
|
||||||
|
? ` <span class="setup-tooltip" tabindex="0" data-tip="${this.escapeHtml(this.stepTips[name])}">?</span>`
|
||||||
|
: '');
|
||||||
this.container.querySelector('#sw-progress-pct').textContent = `${pct}%`;
|
this.container.querySelector('#sw-progress-pct').textContent = `${pct}%`;
|
||||||
|
|
||||||
this.container.querySelector('#sw-back').disabled = this.currentStep === 0;
|
this.container.querySelector('#sw-back').disabled = this.currentStep === 0;
|
||||||
|
|||||||
@ -32,7 +32,16 @@ lp_reachable() {
|
|||||||
read -r -d '' DRIVE <<'JS'
|
read -r -d '' DRIVE <<'JS'
|
||||||
const out = {};
|
const out = {};
|
||||||
const w = window.setupWizard;
|
const w = window.setupWizard;
|
||||||
if (!w) return JSON.stringify({ error: 'wizard handle missing' });
|
if (!w) {
|
||||||
|
// Distinguish "the wizard is broken" from "the wizard is not on screen".
|
||||||
|
// It only renders while setup is incomplete, so a stray .setup_complete —
|
||||||
|
// which a previous test run can leave behind — hides it entirely, and
|
||||||
|
// "wizard handle missing" sends you looking for a JS error that is not
|
||||||
|
// there.
|
||||||
|
return JSON.stringify({ error: typeof window.SetupWizard === 'function'
|
||||||
|
? 'the wizard did not open — setup is already marked complete on this install (remove frontend/data/.setup_complete)'
|
||||||
|
: 'wizard handle missing' });
|
||||||
|
}
|
||||||
const fire = (el, ev) => el.dispatchEvent(new Event(ev, { bubbles: true }));
|
const fire = (el, ev) => el.dispatchEvent(new Event(ev, { bubbles: true }));
|
||||||
const $ = s => document.querySelector(s);
|
const $ = s => document.querySelector(s);
|
||||||
const visible = () => w.stepNames.filter((n, i) => w._stepVisible(i));
|
const visible = () => w.stepNames.filter((n, i) => w._stepVisible(i));
|
||||||
@ -56,10 +65,17 @@ read -r -d '' DRIVE <<'JS'
|
|||||||
out.startAlwaysShown = out.newSteps[0] === 'Start' && out.restoreSteps[0] === 'Start';
|
out.startAlwaysShown = out.newSteps[0] === 'Start' && out.restoreSteps[0] === 'Start';
|
||||||
|
|
||||||
// The source form offers every backend, and shows only the chosen one.
|
// The source form offers every backend, and shows only the chosen one.
|
||||||
// The progress bar said "Source" while the heading said "Where is your
|
// The step's name lives in the progress bar. Repeating it as a heading
|
||||||
// backup?", which read as two different steps. They have to agree.
|
// directly beneath was the same word twice with nothing between them, so
|
||||||
const sec = document.querySelector('.setup-step[data-step="9"] .setup-section-title');
|
// there are no section headings left in the wizard at all — and what those
|
||||||
out.titleMatchesStepName = !!sec && sec.textContent.trim() === 'Backup';
|
// headings explained moved to a tooltip beside the name, which is where the
|
||||||
|
// name is.
|
||||||
|
out.noSectionHeadingsAnywhere = document.querySelectorAll('.setup-section-title').length;
|
||||||
|
w.showStep(1);
|
||||||
|
const bar = document.querySelector('#sw-progress-step');
|
||||||
|
out.progressShowsTheStepName =
|
||||||
|
(bar.querySelector('.setup-progress-name') || {}).textContent === 'Backup';
|
||||||
|
out.progressCarriesTheTip = !!bar.querySelector('.setup-tooltip');
|
||||||
// Fields laid out like the rest of the wizard: label with a tooltip, and an
|
// Fields laid out like the rest of the wizard: label with a tooltip, and an
|
||||||
// icon beside the input — not the label-left rows the Storage step uses.
|
// icon beside the input — not the label-left rows the Storage step uses.
|
||||||
out.fieldsHaveIcons = document.querySelectorAll('#sw-rs-fields .setup-field-icon').length > 0;
|
out.fieldsHaveIcons = document.querySelectorAll('#sw-rs-fields .setup-field-icon').length > 0;
|
||||||
@ -320,7 +336,9 @@ chk "restore shows no install steps" "$(g .restoreLeaksNewStep)" false
|
|||||||
chk "new shows no restore steps" "$(g .newLeaksRestoreStep)" false
|
chk "new shows no restore steps" "$(g .newLeaksRestoreStep)" false
|
||||||
chk "restore step set" "$(g '.restoreSteps | join(",")')" "Start,Backup,Contents,Rebuild"
|
chk "restore step set" "$(g '.restoreSteps | join(",")')" "Start,Backup,Contents,Rebuild"
|
||||||
|
|
||||||
chk "step name matches its title" "$(g .titleMatchesStepName)" true
|
chk "no section headings anywhere" "$(g .noSectionHeadingsAnywhere)" 0
|
||||||
|
chk "the progress bar names the step" "$(g .progressShowsTheStepName)" true
|
||||||
|
chk "and carries its explanation" "$(g .progressCarriesTheTip)" true
|
||||||
|
|
||||||
echo "the backup source form"
|
echo "the backup source form"
|
||||||
chk "every backend offered" "$(g '.kinds | join(",")')" "local,sftp,rest,s3,b2"
|
chk "every backend offered" "$(g '.kinds | join(",")')" "local,sftp,rest,s3,b2"
|
||||||
|
|||||||
@ -43,7 +43,16 @@ lp_reachable() {
|
|||||||
read -r -d '' DRIVE <<'JS'
|
read -r -d '' DRIVE <<'JS'
|
||||||
const out = {};
|
const out = {};
|
||||||
const w = window.setupWizard;
|
const w = window.setupWizard;
|
||||||
if (!w) return JSON.stringify({ error: 'wizard handle missing' });
|
if (!w) {
|
||||||
|
// Distinguish "the wizard is broken" from "the wizard is not on screen".
|
||||||
|
// It only renders while setup is incomplete, so a stray .setup_complete —
|
||||||
|
// which a previous test run can leave behind — hides it entirely, and
|
||||||
|
// "wizard handle missing" sends you looking for a JS error that is not
|
||||||
|
// there.
|
||||||
|
return JSON.stringify({ error: typeof window.SetupWizard === 'function'
|
||||||
|
? 'the wizard did not open — setup is already marked complete on this install (remove frontend/data/.setup_complete)'
|
||||||
|
: 'wizard handle missing' });
|
||||||
|
}
|
||||||
const fire = (el, ev) => el.dispatchEvent(new Event(ev, { bubbles: true }));
|
const fire = (el, ev) => el.dispatchEvent(new Event(ev, { bubbles: true }));
|
||||||
// Looked up by name, never hardcoded. Inserting the Start step moved Storage
|
// Looked up by name, never hardcoded. Inserting the Start step moved Storage
|
||||||
// from 3 to 4, and a test pinned to the old number silently began validating
|
// from 3 to 4, and a test pinned to the old number silently began validating
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user