diff --git a/containers/libreportal/frontend/core/setup/css/setup-wizard.css b/containers/libreportal/frontend/core/setup/css/setup-wizard.css index 447d8b8..d88cea7 100755 --- a/containers/libreportal/frontend/core/setup/css/setup-wizard.css +++ b/containers/libreportal/frontend/core/setup/css/setup-wizard.css @@ -295,6 +295,11 @@ body.setup-wizard-open { .setup-field input[type=text], .setup-field input[type=email], +/* password and number were missing, so any field using them — a backup + destination's credentials, an SSH port — rendered as a bare browser input in + the middle of styled ones. */ +.setup-field input[type=password], +.setup-field input[type=number], .setup-field select { width: 100%; background: rgba(var(--text-rgb), 0.06); @@ -312,6 +317,8 @@ body.setup-wizard-open { .setup-field input[type=text]:focus, .setup-field input[type=email]:focus, +.setup-field input[type=password]:focus, +.setup-field input[type=number]:focus, .setup-field select:focus { outline: none; background: rgba(var(--text-rgb), 0.10); diff --git a/containers/libreportal/frontend/core/setup/js/setup-wizard.js b/containers/libreportal/frontend/core/setup/js/setup-wizard.js index c17667b..b0ff706 100755 --- a/containers/libreportal/frontend/core/setup/js/setup-wizard.js +++ b/containers/libreportal/frontend/core/setup/js/setup-wizard.js @@ -71,6 +71,22 @@ class SetupWizard { // Indices of the steps actually shown, in order. Everything else (progress, // next/prev, validation, submit) works off this rather than raw indices, so // hiding a step never leaves a gap in the numbering. + // Which step to open on. Out-of-range or missing means the first one; the + // value is an index into the VISIBLE steps, so it matches what the progress + // bar says rather than the raw list. + _stepFromQuery() { + try { + const raw = new URLSearchParams(window.location.search).get('step'); + if (raw === null) return 0; + const n = parseInt(raw, 10); + if (!Number.isFinite(n)) return 0; + const max = this._visibleSteps().length - 1; + return Math.min(Math.max(n, 0), Math.max(max, 0)); + } catch (e) { + return 0; + } + } + _visibleSteps() { return this.stepNames.map((_, i) => i).filter((i) => this._stepVisible(i)); } @@ -92,7 +108,11 @@ class SetupWizard { // Same shape: the step is usable immediately and fills in when the // install's existing destinations come back. this.loadBackupLocations(); - this.showStep(0); + // ?step=N opens the wizard on a given step. Nothing about setup depends on + // the order, and it makes a step reachable without clicking through the + // ones before it — which is what lets a screenshot or a headless test look + // at, say, Backups directly. + this.showStep(this._stepFromQuery()); } getWizardApps() { @@ -787,7 +807,7 @@ class SetupWizard { }).join(''); box.innerHTML = rows + ` - `; + `; box.querySelectorAll('[data-backup-edit]').forEach(b => { b.addEventListener('click', () => this.showBackupDestModal(Number(b.dataset.backupEdit))); @@ -813,43 +833,67 @@ class SetupWizard { if (typeof window.openEoModal !== 'function') return; const adding = index < 0; const loc = adding - ? { name: '', type: 'local', path: '' } - : Object.assign({}, this.backupLocations[index]); + ? { name: '', type: 'local', path: '', ssh_port: '22', ssh_auth: 'key' } + : Object.assign({ ssh_port: '22', ssh_auth: 'key' }, this.backupLocations[index]); - const types = [ - ['local', 'This machine or a plugged-in disk'], - ['sftp', 'SFTP server'], - ['s3', 'S3'], - ['b2', 'Backblaze B2'] - ]; + const esc = (v) => this.escapeHtml(v == null ? '' : String(v)); + // Fields and wording follow the location config itself, so what is asked + // here and what the Backup page shows afterwards are the same thing. + const field = (id, label, hint, input) => ` +
+ + ${input} + ${hint ? `${esc(hint)}` : ''} +
`; + const text = (id, val, ph = '') => + ``; + const secret = (id) => + ``; const body = ` -
- - -
-
- + ${field('bk-name', 'Name', 'Shown wherever this destination appears.', + text('bk-name', loc.name, 'Offsite'))} + ${field('bk-type', 'Type', 'Backend this destination uses.', ` + + + + + `)} + +
+ ${field('bk-path', 'Custom Path', 'Filesystem path on this server. Leave blank to use the default backup folder.', + text('bk-path', loc.path, '/mnt/usb/libreportal-backups'))}
-
-
- - + +
+ ${field('bk-host', 'SSH Host', '', text('bk-host', loc.ssh_host, 'backup.example.org'))} + ${field('bk-user', 'SSH User', '', text('bk-user', loc.ssh_user, 'libreportal'))} + ${field('bk-rpath', 'SSH Remote Path', 'Path on the remote host where the repo lives.', + text('bk-rpath', loc.ssh_path, '/srv/backups'))} + ${field('bk-port', 'SSH Port', '', ``)} + ${field('bk-auth', 'SSH Authentication', 'A key is managed by LibrePortal and needs nothing from you here.', ` + `)} +
+ ${field('bk-sshpass', 'SSH Password', 'Sent straight to this machine and stored where only LibrePortal can read it — never part of the task log.', + secret('bk-sshpass'))}
-