R-970: the box's hub key opens no route; Data tab leftovers; R-1103: the install form's dead branches
gates / gates (push) Successful in 48s

R-970: /api/config, /api/config/hash, /api/config/apply removed (they read and replaced controller.yaml with
every secret, reachable with the box's own hub API key, unsigned; nothing called them). The hub-key Bearer
door on /api/selfupdate/ and /api/geo/ and the matching CSRF exemption are gone; the dashboard keeps using
those with a session. The route table is buildMux, so a test drives the real one.

Data tab: the export password field is autocomplete=new-password with a non-login name; the 2nd copy's
status rows that repeated the form are gone (the no-target reason moves into the form).

R-1103: app_fields.html loses its installed-only branches (no page serves them since R-1095); the tests
that rendered that dead path are retired, their rules asserted on the settings tab (R-694 moved there).

Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
This commit is contained in:
2026-10-16 18:24:00 +02:00
parent 8fe46a1313
commit f037be7a6a
25 changed files with 331 additions and 2987 deletions
+12 -29
View File
@@ -90,7 +90,8 @@ function appMigrate(btn,app,label){
{{/* „BIZTONSAGI MENTES" — ONE section (v0.321.0). The nightly backup and the 2nd copy were two sections with
two titles for one subject, and the 2nd copy's state sat in a two-column grid of monospace values. Now: the
nightly backup is a row, the 2nd copy's state is rows, then what it means and the form to change it. The words
nightly backup is a row, then what the 2nd copy means and the form that shows and changes it (v0.322.0: the
rows that repeated the form are gone). The words
are plain: no „off-drive", no „PBS", no „DB/konfiguráció". Pinned by TestAppData_OneBackupSection. */}}
<div class="app-section" id="app-backup-section" data-backup-section="1">
<h3>{{T "deploy.biztonsagi_mentes"}}</h3>
@@ -99,31 +100,8 @@ function appMigrate(btn,app,label){
<div class="app-field-value">{{T "app_page.backup_nightly_text"}} <a href="/backups" class="app-info-link">{{T "app_page.backup_status_link"}}</a></div>
</div>
{{- if .Tier2}}{{with .Tier2}}
{{- if .IsHDDApp}}
<div class="app-field-row" data-backup-row="second">
<div class="app-field-label">{{T "tier2_config.2_mentes"}}</div>
<div class="app-field-value">{{if .Disabled}}{{T "tier2_config.kikapcsolva"}}{{else}}{{T "tier2_config.bekapcsolva"}}{{end}}</div>
</div>
{{- if .NoTarget}}
<div class="app-field-row" data-backup-row="target">
<div class="app-field-label">{{T "tier2_config.cel"}}</div>
<div class="app-field-value text-error">{{T "tier2_config.nincs_elerheto_off_drive_cel"}}</div>
</div>
<div class="app-field-row">
<div class="app-field-label">{{T "tier2_config.megjegyzes"}}</div>
<div class="app-field-value">{{.NoTargetReason}}</div>
</div>
{{- else}}
<div class="app-field-row" data-backup-row="target">
<div class="app-field-label">{{T "tier2_config.cel_meghajto"}}</div>
<div class="app-field-value">{{.EffectiveLabel}}{{if .EffectiveIsSSD}} {{T "tier2_config.csak_db_konfiguracio"}}{{end}}</div>
</div>
<div class="app-field-row">
<div class="app-field-label">{{T "tier2_config.kivalasztas_modja"}}</div>
<div class="app-field-value">{{if .Preferred}}{{T "tier2_config.kezi_valasztas"}}{{else}}{{T "tier2_config.automatikus_kisbetu"}}{{end}} — {{.EffectiveDesc}}</div>
</div>
{{- end}}
{{- end}}
{{- /* v0.322.0: the 2nd copy's state is said ONCE — by the form below (checkbox, target, hint). Its rows here
repeated the same three facts. */}}
<p class="app-field-hint" style="margin-top:12px">{{T "tier2_config.a_2_mentes_egy_masik"}}</p>
{{if not .IsHDDApp}}
@@ -167,6 +145,9 @@ function appMigrate(btn,app,label){
<option value="{{.Path}}" {{if eq .Path $.Tier2.Preferred}}selected{{end}}>{{.Label}}</option>
{{end}}
</select>
{{if .NoTarget}}
<div class="form-hint text-error" style="margin-top:.4rem" data-second-backup-notarget="1">{{T "tier2_config.nincs_elerheto_off_drive_cel"}} — {{.NoTargetReason}}</div>
{{end}}
{{if not .Alternatives}}
<div style="font-size:.8rem;color:var(--text-3);margin-top:.4rem">
{{T "tier2_config.nincs_masik_adatmeghajto_automatikus_cel"}}
@@ -229,7 +210,9 @@ function appMigrate(btn,app,label){
<h3>{{T "app_export.jelszo_opcionalis"}}</h3>
<div style="display:flex;gap:.5rem;margin-bottom:1rem">
<input type="password" id="exportPassword" class="form-control" placeholder="{{T "app_export.titkositasi_jelszo"}}" style="flex:1">
{{/* v0.322.0: autocomplete="new-password" and a name that is not a login field — the browser filled the household's
saved dashboard password in here, and an export could be locked with it unnoticed. */}}
<input type="password" id="fabEncryptionKey" name="fab_encryption_key" autocomplete="new-password" data-export-key="1" class="form-control" placeholder="{{T "app_export.titkositasi_jelszo"}}" style="flex:1">
<button class="btn btn-sm btn-outline" onclick="togglePw(this)" type="button" id="exportPwToggle" aria-label="{{T "app_export.jelszo_mutatasa"}}">{{T "deploy.megjelenites"}}</button>
</div>
@@ -272,7 +255,7 @@ function csrfH() {
}
function togglePw(btn) {
var inp = document.getElementById('exportPassword');
var inp = document.getElementById('fabEncryptionKey');
var show = inp.type === 'password';
inp.type = show ? 'text' : 'password';
if (btn) { btn.textContent = show ? '{{T "app_info.elrejtes"}}' : '{{T "deploy.megjelenites"}}'; }
@@ -394,7 +377,7 @@ function esc(s) { return String(s).replace(/[&<>"]/g, function(c){ return {'&':'
async function startExport() {
var drive = document.getElementById('destDrive').value;
var password = document.getElementById('exportPassword').value;
var password = document.getElementById('fabEncryptionKey').value;
var stopApp = document.getElementById('stopApp').checked;
document.getElementById('startBtn').disabled = true;
@@ -1,55 +1,34 @@
{{/* THE APP'S CONFIGURATION FIELDS — the install wizard's form (deploy.html).
v0.319.0: the settings tab no longer renders this — it shows the install answers as rows and
the login as one panel (app_settings.html, app_login.go). The `.AlreadyDeployed` branches below
are NOT dead: `/apps/{slug}/install` still answers for an app that is installed, and renders
this form read-only. Their guards are what keep an installed app's secrets out of that page
(TestDeployPage_DeployedApp_DoesNotCarryTheSecret, TestR709_AnInstalledAppsPasswordIsNotInThePage)
and the parity case `deploy_installed` renders them.
It renders ONLY for an app that is not installed (v0.322.0, R-1103): since v0.321.0 (R-1095)
`/apps/{slug}/install` of an installed app answers 302 to its settings tab, and the settings tab
shows the install answers as rows and the login as one panel (app_settings.html, app_login.go).
The old read-only „installed" branches are gone; their secret rules live where the values are
shown now — R-254/R-709 on the settings tab (TestAppPage_LoginPairPanel, TestR255_NoPlantedSecretReachesAnyPage).
The secret rules are unchanged and load-bearing:
R-254 — an INSTALLED app's `type: secret` value is not in the page; the eye fetches it.
R-709 — nor is a `type: password` one.
The PRE-DEPLOY hidden input is deliberate: the form must carry what it submits, so the value
saved is the one the household was shown (controller/README §318). */}}
The PRE-DEPLOY hidden input is deliberate: the form must carry what it submits, so the value
saved is the one the household was shown (controller/README §318). */}}
{{define "app_deploy_fields"}}
{{if .AutoFields}}
<div class="form-section">
<h4>{{T "deploy.automatikusan_generalt_ertekek"}}</h4>
{{if .AlreadyDeployed}}
<p class="form-section-desc">{{T "deploy.ezek_az_ertekek_automatikusan_jottek"}}</p>
{{else}}
<p class="form-section-desc">{{T "deploy.ezek_az_ertekek_a_telepitessel"}}</p>
{{end}}
{{$autoValues := .AutoFieldValues}}
{{$stackName := .Stack.Name}}
{{$isDeployed := .AlreadyDeployed}}
{{range .AutoFields}}
{{$val := index $autoValues .EnvVar}}
<div class="form-group form-group-auto">
<label>{{.Label}} {{if eq .Type "secret"}}<span class="auto-generated-badge"><svg class="ico ico-sm"><use href="#i-check"/></svg> {{T "deploy.automatikusan_generalva"}}</span>{{end}}</label>
{{if $val}}
{{if eq .Type "secret"}}
{{if $isDeployed}}
<!-- R-254 site two: on an ALREADY-DEPLOYED app nothing is being submitted, so there is
no reason for the value to be in this page at all. It is fetched on demand. The
pre-deploy branch below is different and deliberate — the form must carry what it
submits, so the saved value is the one the customer was shown (README §318). -->
<div class="input-with-button">
<input type="password" id="auto-field-{{.EnvVar}}" class="form-control" value="" placeholder="••••••••••••" readonly>
<button type="button" class="btn btn-sm btn-outline" onclick="revealAutoField('{{$stackName}}','{{.EnvVar}}', this)">{{T "deploy.megjelenites"}}</button>
</div>
{{else}}
<div class="input-with-button">
<input type="password" id="auto-field-{{.EnvVar}}" class="form-control" value="{{$val}}" readonly>
<button type="button" class="btn btn-sm btn-outline" onclick="toggleAutoField('auto-field-{{.EnvVar}}', this)">{{T "deploy.megjelenites"}}</button>
</div>
{{end}}
{{else}}
<input type="text" id="auto-field-{{.EnvVar}}" class="form-control" value="{{$val}}" readonly>
{{end}}
{{if and (not $isDeployed) (eq .Type "secret")}}
{{if eq .Type "secret"}}
<input type="hidden" name="{{.EnvVar}}" value="{{$val}}">
{{end}}
{{end}}
@@ -62,10 +41,6 @@
<div class="form-section">
<h4>{{T "deploy.beallitasok"}}</h4>
{{range .UserFields}}
{{/* v0.316.0: an INSTALLED app shows its address once, as the „Cim" row above these
fields, so the disabled subdomain input saying the same thing is skipped. The
install wizard still asks for it — this is the read-only view's duplicate only. */}}
{{if and $.AlreadyDeployed (eq .Type "subdomain")}}{{continue}}{{end}}
<div class="form-group">
<label for="field-{{.EnvVar}}">
{{.Label}}
@@ -79,35 +54,22 @@
class="form-control subdomain-input"
{{/* R-351: a reinstall offers the address the customer's own backup recorded,
ahead of the catalog default. Editable — a domain can legitimately change. */}}
value="{{if and $.AlreadyDeployed $.DeployedFieldValues}}{{index $.DeployedFieldValues .EnvVar}}{{else if index $.RestoreFieldValues .EnvVar}}{{index $.RestoreFieldValues .EnvVar}}{{else}}{{.Default}}{{end}}"
value="{{if index $.RestoreFieldValues .EnvVar}}{{index $.RestoreFieldValues .EnvVar}}{{else}}{{.Default}}{{end}}"
placeholder="{{T "deploy.aldomain_placeholder"}}"
pattern="[a-z0-9]([a-z0-9-]*[a-z0-9])?"
required
{{if $.AlreadyDeployed}}disabled{{end}}
oninput="this.value=this.value.toLowerCase().replace(/[^a-z0-9-]/g,'')">
<span class="subdomain-suffix">.{{$.Domain}}</span>
</div>
{{if not $.AlreadyDeployed}}
<span class="form-hint">{{T "deploy.az_aldomain_telepites_utan_nem"}}</span>
{{end}}
{{else if eq .Type "select"}}
<select id="field-{{.EnvVar}}" name="{{.EnvVar}}" class="form-control"
{{if $.AlreadyDeployed}}disabled{{end}}>
<select id="field-{{.EnvVar}}" name="{{.EnvVar}}" class="form-control">
{{range .Options}}
<option value="{{.Value}}">{{.Label}}</option>
{{end}}
</select>
{{else if eq .Type "password"}}
<div class="input-with-button">
{{- if $.AlreadyDeployed}}
{{- /* R-709 (v0.280.0): an installed app's password is never in this page; the eye fetches it. */}}
<input type="password" id="field-{{.EnvVar}}" class="form-control" value="" placeholder="••••••••••••" disabled>
{{- if not (and $.RestoredLogins (index $.RestoredLogins .EnvVar))}}
<button type="button" class="btn btn-sm btn-outline pw-toggle-btn"
onclick="revealPasswordField('{{$.Stack.Name}}', '{{.EnvVar}}', this)"
title="{{T "deploy.megjelenites"}}">&#128065;</button>
{{- end}}
{{- else}}
<input type="password" id="field-{{.EnvVar}}" name="{{.EnvVar}}"
class="form-control" value="{{.Default}}"
placeholder="{{.Placeholder}}"
@@ -118,27 +80,20 @@
title="{{T "deploy.megjelenites"}}">&#128065;</button>
<button type="button" class="btn btn-sm btn-outline"
onclick="generatePassword('field-{{.EnvVar}}', 'field-confirm-{{.EnvVar}}', '{{.Generate}}')">{{T "deploy.generalas"}}</button>
{{- end}}
</div>
{{if $.AlreadyDeployed}}
<span class="form-hint">{{if and $.RestoredLogins (index $.RestoredLogins .EnvVar)}}{{T "deploy.login_from_backup"}}{{else}}{{T "deploy.telepiteskor_beallitott_kezdeti_jelszo_h"}}{{end}}</span>
{{else}}
<div class="input-with-button" style="margin-top:.25rem">
<input type="password" id="field-confirm-{{.EnvVar}}"
class="form-control" placeholder="{{T "deploy.jelszo_megerositese"}}"
data-confirm-for="field-{{.EnvVar}}">
</div>
{{end}}
{{else if eq .Type "boolean"}}
<label class="toggle">
<input type="checkbox" id="field-{{.EnvVar}}" name="{{.EnvVar}}" value="true"
{{if $.AlreadyDeployed}}disabled{{end}}>
<input type="checkbox" id="field-{{.EnvVar}}" name="{{.EnvVar}}" value="true">
<span class="toggle-label">{{T "deploy.igen"}}</span>
</label>
{{else if eq .Type "path"}}
{{if $.StoragePaths}}
<select id="field-{{.EnvVar}}" name="{{.EnvVar}}" class="form-control"
{{if $.AlreadyDeployed}}disabled{{end}}
onchange="checkStorageSpace(this)">
{{range $.StoragePaths}}
<option value="{{.Path}}" data-free-percent="{{printf "%.0f" .FreePercent}}"
@@ -147,13 +102,10 @@
configured default — it is where this app's data actually lived, and
restoring into a different drive is the mismatch the restore then has
to stop and name. The customer can still pick another. */}}
{{if $.AlreadyDeployed}}{{if eq .Path $.CurrentHDDPath}}selected{{end}}{{else if $.RestorePrefillHDDPath}}{{if and (eq .Path $.RestorePrefillHDDPath) (not .NotAllowed)}}selected{{end}}{{else if and .IsDefault (not .NotAllowed)}}selected{{end}}>
{{if $.RestorePrefillHDDPath}}{{if and (eq .Path $.RestorePrefillHDDPath) (not .NotAllowed)}}selected{{end}}{{else if and .IsDefault (not .NotAllowed)}}selected{{end}}>
{{T "deploy.szabad_2"}}{{if .NotAllowed}} ({{.NotAllowedNote}}){{else if .IsDefault}} {{T "deploy.alapertelmezett"}}{{end}}
</option>
{{end}}
{{if $.CurrentHDDPathMissing}}
<option value="{{$.CurrentHDDPath}}" selected disabled>{{T "deploy.nem_elerheto"}}</option>
{{end}}
</select>
<div id="storage-space-warn" class="form-hint" style="color:var(--warn);display:none">
<svg class="ico ico-sm"><use href="#i-triangle-alert"/></svg> {{T "deploy.a_kivalasztott_tarhely_majdnem_megtelt"}}
@@ -165,16 +117,14 @@
<input type="text" id="field-{{.EnvVar}}" name="{{.EnvVar}}"
class="form-control" value="{{.Default}}"
placeholder="{{.Placeholder}}"
{{if .Required}}required{{end}}
{{if $.AlreadyDeployed}}disabled{{end}}>
{{if .Required}}required{{end}}>
<span class="form-hint" style="color:var(--warn)">{{T "deploy.nincs_regisztralt_adattarolo_adja_meg"}}</span>
{{end}}
{{else}}
<input type="text" id="field-{{.EnvVar}}" name="{{.EnvVar}}"
class="form-control" value="{{.Default}}"
placeholder="{{.Placeholder}}"
{{if .Required}}required{{end}}
{{if $.AlreadyDeployed}}disabled{{end}}>
{{if .Required}}required{{end}}>
{{end}}
{{if and .Description (ne .Type "subdomain")}}