From 00114a6ce2fe0b8ff562e724cac809b14028877e Mon Sep 17 00:00:00 2001 From: librelad Date: Fri, 28 Aug 2026 10:48:39 +0100 Subject: [PATCH] setup: fix the Backups dialog, and make dialogs testable at all MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Reported after looking at the step: the add button unstyled, the dialog missing the fields a backup location actually has, and its dropdowns not working. Three real faults, and one reason all three shipped. * "+ Add destination" carried class .setup-add-domain, which I invented. The real one is .setup-domain-add, so no rule matched and it rendered as a bare browser button in the middle of a styled form. * The dialog asked for name / type / host / user / path / password. A backup location has SSH port and auth method (key or password — key is the default and needs nothing typed), S3 access and secret keys, B2 account id and key, and a path mode. It now asks for what each backend needs, with the wording taken from the location config so the wizard and the Backup page describe the same thing the same way. * .setup-field styled input[type=text] and [type=email] but not [type=password] or [type=number], so a credential field and the SSH port rendered unstyled even inside a correct container. Only the credentials go through the secret channel — SSH password, S3 secret key, B2 account key. The rest is ordinary configuration and travels as itself. The reason all three shipped is that I checked the step by querying the DOM and never looked at it. Structural checks cannot see an unstyled control, and a dialog is behind a click so a screenshot cannot reach it either. So: lp-shot --eval run JS in the page and print the result LP_SHOT_EVAL= run JS before a capture — open a dialog, then shoot and scripts/dev/lp-backup-dialog-test drives the whole thing in a real browser: opens it, swaps every backend and asserts only that backend's fields show, toggles SSH auth and asserts the password field follows, submits, and asserts the credential is not left in the DOM. Its styling check needed two attempts, which is the point of mutation-testing it: "is the background transparent" passes for an unstyled button, because a native button is grey rather than transparent. It now compares the control against a bare `; + `; 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'))}
-