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
+3 -12
View File
@@ -16,7 +16,6 @@ const csrfHeaderName = "X-CSRF-Token"
//
// Exempt cases:
// - Auth is disabled (no password configured)
// - Request has a valid Authorization: Bearer header (API key / hub auth)
func (s *Server) CsrfProtect(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// Safe methods: no CSRF check needed
@@ -53,17 +52,9 @@ func (s *Server) CsrfProtect(next http.Handler) http.Handler {
return
}
// Skip CSRF for Bearer-token authenticated requests.
// Validate the token against the configured API key before skipping.
if auth := r.Header.Get("Authorization"); strings.HasPrefix(auth, "Bearer ") {
token := strings.TrimPrefix(auth, "Bearer ")
apiKey := s.cfg.Hub.APIKey
if apiKey != "" && subtle.ConstantTimeCompare([]byte(token), []byte(apiKey)) == 1 {
next.ServeHTTP(w, r)
return
}
// Invalid Bearer token — fall through to CSRF validation
}
// v0.322.0 (R-970): no Bearer exemption. It existed for the routes that accepted the box's hub API key
// (/api/config/, /api/selfupdate/, /api/geo/); none does now, so a request carrying that key is a dashboard
// request like any other and needs the session's CSRF token. Pinned by TestR970_HubKeyIsNoCSRFExemption.
// Get the session's CSRF token
cookie, err := r.Cookie(sessionCookieName)
@@ -45,28 +45,10 @@ func renderDeployPage(t *testing.T, alreadyDeployed bool) string {
return buf.String()
}
// RED-PROOF: drop the `{{if $isDeployed}}` branch so the deployed page renders `value="{{$val}}"`
// again — this fails, showing the secret returning to the body of a page with nothing to submit.
func TestDeployPage_DeployedApp_DoesNotCarryTheSecret(t *testing.T) {
html := renderDeployPage(t, true)
if strings.Contains(html, testDeploySecret) {
t.Error("R-254 site two: an already-deployed app's generated secret is in the response body " +
"of its settings page — nothing there submits it, so there is no form reason for it to " +
"be in the page at all")
}
// Assert the CONTROL, not the URL: the revealAutoField() function ships in the page script on
// both variants, so a substring match on the endpoint path matches the script and would report a
// control that is not there. (This test caught exactly that on itself.)
if !strings.Contains(html, `onclick="revealAutoField('vaultwarden','DB_PASSWORD'`) {
t.Error("no reveal control rendered, so the customer cannot see their own generated secret")
}
// The hidden input must NOT appear on a deployed app — it never did, and that is the asymmetry
// that makes the readonly input indefensible there.
if strings.Contains(html, `<input type="hidden" name="DB_PASSWORD"`) {
t.Error("a submit-carrying hidden input rendered on an already-deployed app")
}
}
// v0.322.0 (R-1103): TestDeployPage_DeployedApp_DoesNotCarryTheSecret is retired — the install form no longer renders
// for an installed app (R-1095, 302 to the settings tab). Its rule (an installed app's generated secret is not in the
// page; an eye fetches it) is asserted where the value is shown now: TestAppPage_LoginPairPanel (SECRET_KEY,
// ADMIN_USER, ADMIN_PASSWORD never in the settings page; every eye answers) and TestR255_NoPlantedSecretReachesAnyPage.
// The OTHER half of §7.2's answer: the pre-deploy form still carries the value, deliberately. If this
// ever starts failing, someone has "fixed" a form by stopping it submitting what it must submit —
+2 -12
View File
@@ -187,18 +187,8 @@ func i18nCasesA() []i18nCase {
{"deploy_new_memory_blocked", "deploy", func() map[string]interface{} {
return i18nDeployData(false, stacks.StateNotDeployed, 3)
}},
// v0.319.0: `/apps/{slug}/install` opened for an app that IS installed — the form read-only, its
// secrets fetched on demand. The settings tab stopped rendering this form, so these two cases are
// what renders its installed-app branches now.
{"deploy_installed", "deploy", func() map[string]interface{} {
return i18nDeployData(true, stacks.StateRunning, 1)
}},
{"deploy_installed_restored", "deploy", func() map[string]interface{} {
d := i18nDeployData(true, stacks.StateRunning, 1)
d["DeployedFieldValues"] = map[string]string{"SUBDOMAIN": "paste"}
d["RestoredLogins"] = map[string]bool{"ADMIN_PASSWORD": true}
return d
}},
// v0.322.0 (R-1103): `deploy_installed` and `deploy_installed_restored` are retired — the install form no longer
// renders for an installed app (R-1095). The settings tab's cases cover what an installed app shows.
{"app_settings_running", "app_settings", func() map[string]interface{} {
return i18nDeployData(true, stacks.StateRunning, 1)
}},
+14 -3
View File
@@ -388,6 +388,7 @@ var lfOffDriveOrPBS = regexp.MustCompile(`(?i)off-drive|\bPBS\b`)
// The Data tab has ONE „Biztonsagi mentes" section, with no „off-drive" and no „PBS" in either language.
// RED-PROOF (2026-10-16, REPORT): with the old `tier2_config.a_2_mentes_egy_masik` text put back in hu.json, this
// failed at „hu: off-drive/PBS on the Data tab"; with the second <div class="app-section"> put back, at „2 sections".
// v0.322.0: with the status rows put back, at „status rows repeat the form"; without autocomplete, at „lacks".
func TestAppData_OneBackupSection(t *testing.T) {
if !lfOffDriveOrPBS.MatchString("a teljes rendszermentés/PBS — off-drive") {
t.Fatal("control: the matcher does not find the words it looks for")
@@ -407,9 +408,19 @@ func TestAppData_OneBackupSection(t *testing.T) {
if n := strings.Count(html, `data-backup-section="1"`); n != 1 {
t.Errorf("%s: %d backup sections, want 1", lang, n)
}
if !strings.Contains(html, `data-backup-row="nightly"`) || !strings.Contains(html, `data-backup-row="second"`) ||
!strings.Contains(html, `data-second-backup-form="1"`) {
t.Errorf("%s: the nightly row, the 2nd-copy row or its form is missing", lang)
if !strings.Contains(html, `data-backup-row="nightly"`) || !strings.Contains(html, `data-second-backup-form="1"`) {
t.Errorf("%s: the nightly row or the 2nd-copy form is missing", lang)
}
// v0.322.0: the 2nd copy's state is said once — by the form; no status rows repeat it.
if strings.Contains(html, `data-backup-row="second"`) || strings.Contains(html, `data-backup-row="target"`) {
t.Errorf("%s: the 2nd copy's status rows repeat the form", lang)
}
if n := strings.Count(html, `<label for="target">`); n != 1 {
t.Errorf("%s: the target drive is labelled %d times, want once (the form)", lang, n)
}
// The export key: the browser must not fill a saved login into it.
if !strings.Contains(html, `name="fab_encryption_key" autocomplete="new-password"`) {
t.Errorf("%s: the export password field lacks autocomplete=\"new-password\" or still has a login-like name", lang)
}
if m := lfOffDriveOrPBS.FindString(html); m != "" {
t.Errorf("%s: off-drive/PBS on the Data tab (%q)", lang, m)
@@ -1,34 +1,45 @@
package web
import (
"net/http"
"path/filepath"
"strings"
"testing"
"gitea.dooplex.hu/admin/felhom-controller/internal/stacks"
)
// R-694 (v0.275.0) — a deployed app whose admin login was GENERATED by a restore: the page shows no value
// for it and says the old password is the one that works; an app without that record renders as before.
// Rendered through the production template path (renderI18nCase), both languages.
func TestR694_TheDeployPageDoesNotOfferAGeneratedLogin(t *testing.T) {
s := i18nTestServer(t)
base := func() map[string]interface{} { return i18nDeployData(true, stacks.StateRunning, 1) }
restored := func() map[string]interface{} {
d := base()
d["DeployedFieldValues"] = map[string]string{"SUBDOMAIN": "paste"} // the handler removed the value
d["RestoredLogins"] = map[string]bool{"ADMIN_PASSWORD": true}
return d
// R-694 (v0.275.0) — an installed app whose admin login was GENERATED by a restore: the page shows no value and no eye
// for it and says the old password is the one that works; an app without that record shows the eye.
//
// v0.322.0 (R-1103): moved from the install form (which no longer renders for an installed app, R-1095) to the settings
// tab, through the production route (s.ServeHTTP) on a real stack manager.
// RED-PROOF (2026-10-16, REPORT): with the `{{- if .Restored}}` branch removed from the login panel, this failed at
// „the restored login is offered for reveal".
func TestR694_TheSettingsTabDoesNotOfferAGeneratedLogin(t *testing.T) {
s := tuningHarness(t, tuningEnv())
dir := filepath.Join(s.cfg.Paths.StacksDir, "calibre-web")
if err := stacks.SaveAppConfig(dir, &stacks.AppConfig{Deployed: true, Env: tuningEnv(), RestoredLogins: []string{"ADMIN_PASSWORD"}}, nil, nil); err != nil {
t.Fatal(err)
}
hu := renderI18nCase(t, s, "hu", i18nCase{"r694", "deploy", restored})
if !strings.Contains(hu, "Mentésből töltötted vissza") || strings.Contains(hu, "rejtett érték") || strings.Contains(hu, "Telepítéskor beállított kezdeti jelszó") {
t.Fatalf("hu page does not say to use the old password, or still shows a value")
_ = s.stackMgr.ScanStacks()
rec := appGet(t, s, "/apps/calibre-web/settings")
if rec.Code != http.StatusOK {
t.Fatalf("GET settings = %d", rec.Code)
}
en := renderI18nCase(t, s, "en", i18nCase{"r694", "deploy", restored})
if !strings.Contains(en, "Restored from a backup: log in with the password that was valid when the backup was taken") {
t.Fatal("en page lacks the sentence")
body := rec.Body.String()
if strings.Contains(body, tuningPassword) {
t.Fatal("the restore-generated password is in the page")
}
plain := renderI18nCase(t, s, "hu", i18nCase{"r694-plain", "deploy", base})
if strings.Contains(plain, "Mentésből töltötted vissza") || !strings.Contains(plain, "Telepítéskor beállított kezdeti jelszó") {
t.Fatal("an app without restored_logins changed")
if strings.Contains(body, `data-login-reveal="ADMIN_PASSWORD"`) {
t.Fatal("the restored login is offered for reveal")
}
if !strings.Contains(body, `data-login-restored="ADMIN_PASSWORD"`) {
t.Fatal("the page does not say to use the old password")
}
// Control: without the record the same app offers the eye.
plain := tuningHarness(t, tuningEnv())
if b := appGet(t, plain, "/apps/calibre-web/settings").Body.String(); !strings.Contains(b, `data-login-reveal="ADMIN_PASSWORD"`) || strings.Contains(b, `data-login-restored`) {
t.Fatal("control: an app without restored_logins does not offer its eye")
}
}
@@ -41,21 +41,11 @@ func renderDeployWithPassword(t *testing.T, alreadyDeployed bool, restored bool)
return buf.String()
}
// COMPANION RED-PROOF: put `value="{{index $.DeployedFieldValues .EnvVar}}"` back on the deployed password input
// → the first assertion fails with the password in the body.
func TestR709_AnInstalledAppsPasswordIsNotInThePage(t *testing.T) {
html := renderDeployWithPassword(t, true, false)
if strings.Contains(html, testAdminPassword) {
t.Fatal("R-709: the installed app's admin password is in the HTML of its settings page")
}
if !strings.Contains(html, `onclick="revealPasswordField('grafana', 'ADMIN_PASSWORD', this)"`) {
t.Fatal("no reveal control: the household cannot see its own password")
}
// A login a restore generated is not the app's login (R-694): no reveal for it.
if html := renderDeployWithPassword(t, true, true); strings.Contains(html, `revealPasswordField('grafana'`) || strings.Contains(html, testAdminPassword) {
t.Fatal("a restore-generated login is offered for reveal")
}
// The pre-deploy form keeps its generator, with the field's own spec.
// v0.322.0 (R-1103): the installed half of this test is retired — the install form no longer renders for an installed
// app (R-1095). „The password is not in the page, an eye fetches it" is asserted on the settings tab
// (TestAppPage_LoginPairPanel), and „no eye for a restore-generated login" too (TestR694_TheSettingsTabDoesNotOfferAGeneratedLogin).
// What stays here is the install form's own rule: it keeps its generator, with the field's spec.
func TestR709_TheInstallFormKeepsItsGenerator(t *testing.T) {
if html := renderDeployWithPassword(t, false, false); !strings.Contains(html, `generatePassword('field-ADMIN_PASSWORD', 'field-confirm-ADMIN_PASSWORD', 'password:24:special')`) {
t.Fatal("the install form lost its generator (or its spec)")
}
+42
View File
@@ -0,0 +1,42 @@
package web
import (
"io"
"log"
"net/http"
"net/http/httptest"
"path/filepath"
"testing"
"gitea.dooplex.hu/admin/felhom-controller/internal/config"
"gitea.dooplex.hu/admin/felhom-controller/internal/settings"
)
// R-970 (v0.322.0): the box's hub API key is no CSRF exemption any more — no route accepts it, so a POST that carries
// it and no session CSRF token is refused like any other.
// RED-PROOF (2026-10-16, REPORT): with the Bearer exemption put back in CsrfProtect, this failed at „passed CSRF".
func TestR970_HubKeyIsNoCSRFExemption(t *testing.T) {
cfg := config.Default()
cfg.Hub.APIKey = "TESTONLY-hub-key-csrf"
lg := log.New(io.Discard, "", 0)
sett, err := settings.Load(filepath.Join(t.TempDir(), "settings.json"), lg)
if err != nil {
t.Fatal(err)
}
if err := sett.SetPasswordHash("$2a$10$abcdefghijklmnopqrstuuJ1A9pDq6s9F2x1H3bQb0v8Y3wq6z2Om"); err != nil {
t.Fatal(err)
}
s := &Server{cfg: cfg, settings: sett, logger: lg}
if !s.authEnabled() {
t.Fatal("control: auth is not on, so CSRF would pass everything and this test would measure nothing")
}
reached := false
h := s.CsrfProtect(http.HandlerFunc(func(http.ResponseWriter, *http.Request) { reached = true }))
req := httptest.NewRequest(http.MethodPost, "/api/selfupdate/update", nil)
req.Header.Set("Authorization", "Bearer "+cfg.Hub.APIKey)
w := httptest.NewRecorder()
h.ServeHTTP(w, req)
if reached || w.Code/100 == 2 {
t.Fatalf("a POST carrying the hub key passed CSRF (code %d)", w.Code)
}
}
+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")}}
@@ -299,18 +299,6 @@ function appMigrate(btn,app,label){
<div class="app-field-label">Éjszakai mentés</div>
<div class="app-field-value">Az alkalmazás adatbázisa és belső tárolói minden éjjel bekerülnek a mentésbe. <a href="/backups" class="app-info-link">Mentési állapot →</a></div>
</div>
<div class="app-field-row" data-backup-row="second">
<div class="app-field-label">2. mentés</div>
<div class="app-field-value">Bekapcsolva</div>
</div>
<div class="app-field-row" data-backup-row="target">
<div class="app-field-label">Cél meghajtó</div>
<div class="app-field-value">HDD kettő</div>
</div>
<div class="app-field-row">
<div class="app-field-label">Kiválasztás módja</div>
<div class="app-field-value">automatikus — a legnagyobb szabad hely</div>
</div>
<p class="app-field-hint" style="margin-top:12px">A 2. mentés egy <strong>másik meghajtóra</strong> másolja az alkalmazás helyreállítási csomagját és adatait. A böngészhető fájljaidat csak ez védi meg egy meghajtóhiba ellen — a teljes rendszermentés ezeket nem tartalmazza.</p>
@@ -331,6 +319,7 @@ function appMigrate(btn,app,label){
</select>
<div style="font-size:.8rem;color:var(--text-3);margin-top:.4rem">
Nincs másik adatmeghajtó — automatikusan a belső SSD-re kerül (csak az adatbázis és a beállítások). Egy 2. adatmeghajtóval a teljes adat is másik meghajtóra menthető.
</div>
@@ -392,7 +381,8 @@ function appMigrate(btn,app,label){
<h3>Jelszó (opcionális)</h3>
<div style="display:flex;gap:.5rem;margin-bottom:1rem">
<input type="password" id="exportPassword" class="form-control" placeholder="Titkosítási jelszó" style="flex:1">
<input type="password" id="fabEncryptionKey" name="fab_encryption_key" autocomplete="new-password" data-export-key="1" class="form-control" placeholder="Titkosítási jelszó" style="flex:1">
<button class="btn btn-sm btn-outline" onclick="togglePw(this)" type="button" id="exportPwToggle" aria-label="Jelszó mutatása">Megjelenítés</button>
</div>
@@ -435,7 +425,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 ? 'Elrejtés' : 'Megjelenítés'; }
@@ -557,7 +547,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;
@@ -287,7 +287,8 @@
<h3>Jelszó (opcionális)</h3>
<div style="display:flex;gap:.5rem;margin-bottom:1rem">
<input type="password" id="exportPassword" class="form-control" placeholder="Titkosítási jelszó" style="flex:1">
<input type="password" id="fabEncryptionKey" name="fab_encryption_key" autocomplete="new-password" data-export-key="1" class="form-control" placeholder="Titkosítási jelszó" style="flex:1">
<button class="btn btn-sm btn-outline" onclick="togglePw(this)" type="button" id="exportPwToggle" aria-label="Jelszó mutatása">Megjelenítés</button>
</div>
@@ -330,7 +331,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 ? 'Elrejtés' : 'Megjelenítés'; }
@@ -452,7 +453,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;
@@ -233,18 +233,6 @@
<div class="app-field-label">Éjszakai mentés</div>
<div class="app-field-value">Az alkalmazás adatbázisa és belső tárolói minden éjjel bekerülnek a mentésbe. <a href="/backups" class="app-info-link">Mentési állapot →</a></div>
</div>
<div class="app-field-row" data-backup-row="second">
<div class="app-field-label">2. mentés</div>
<div class="app-field-value">Kikapcsolva</div>
</div>
<div class="app-field-row" data-backup-row="target">
<div class="app-field-label">Cél</div>
<div class="app-field-value text-error">Nincs másik meghajtó, ahová másolhatna</div>
</div>
<div class="app-field-row">
<div class="app-field-label">Megjegyzés</div>
<div class="app-field-value">Nincs második meghajtó csatlakoztatva</div>
</div>
<p class="app-field-hint" style="margin-top:12px">A 2. mentés egy <strong>másik meghajtóra</strong> másolja az alkalmazás helyreállítási csomagját és adatait. A böngészhető fájljaidat csak ez védi meg egy meghajtóhiba ellen — a teljes rendszermentés ezeket nem tartalmazza.</p>
@@ -265,6 +253,9 @@
</select>
<div class="form-hint text-error" style="margin-top:.4rem" data-second-backup-notarget="1">Nincs másik meghajtó, ahová másolhatna — Nincs második meghajtó csatlakoztatva</div>
<div style="font-size:.8rem;color:var(--text-3);margin-top:.4rem">
Nincs másik adatmeghajtó — automatikusan a belső SSD-re kerül (csak az adatbázis és a beállítások). Egy 2. adatmeghajtóval a teljes adat is másik meghajtóra menthető.
</div>
@@ -233,18 +233,6 @@
<div class="app-field-label">Éjszakai mentés</div>
<div class="app-field-value">Az alkalmazás adatbázisa és belső tárolói minden éjjel bekerülnek a mentésbe. <a href="/backups" class="app-info-link">Mentési állapot →</a></div>
</div>
<div class="app-field-row" data-backup-row="second">
<div class="app-field-label">2. mentés</div>
<div class="app-field-value">Bekapcsolva</div>
</div>
<div class="app-field-row" data-backup-row="target">
<div class="app-field-label">Cél meghajtó</div>
<div class="app-field-value">HDD kettő</div>
</div>
<div class="app-field-row">
<div class="app-field-label">Kiválasztás módja</div>
<div class="app-field-value">automatikus — a legnagyobb szabad hely</div>
</div>
<p class="app-field-hint" style="margin-top:12px">A 2. mentés egy <strong>másik meghajtóra</strong> másolja az alkalmazás helyreállítási csomagját és adatait. A böngészhető fájljaidat csak ez védi meg egy meghajtóhiba ellen — a teljes rendszermentés ezeket nem tartalmazza.</p>
@@ -265,6 +253,7 @@
</select>
<div style="font-size:.8rem;color:var(--text-3);margin-top:.4rem">
Nincs másik adatmeghajtó — automatikusan a belső SSD-re kerül (csak az adatbázis és a beállítások). Egy 2. adatmeghajtóval a teljes adat is másik meghajtóra menthető.
</div>
@@ -233,18 +233,6 @@
<div class="app-field-label">Éjszakai mentés</div>
<div class="app-field-value">Az alkalmazás adatbázisa és belső tárolói minden éjjel bekerülnek a mentésbe. <a href="/backups" class="app-info-link">Mentési állapot →</a></div>
</div>
<div class="app-field-row" data-backup-row="second">
<div class="app-field-label">2. mentés</div>
<div class="app-field-value">Bekapcsolva</div>
</div>
<div class="app-field-row" data-backup-row="target">
<div class="app-field-label">Cél meghajtó</div>
<div class="app-field-value">SSD meghajtó egy — csak az adatbázis és a beállítások</div>
</div>
<div class="app-field-row">
<div class="app-field-label">Kiválasztás módja</div>
<div class="app-field-value">kézi választás — a leggyorsabb meghajtó</div>
</div>
<p class="app-field-hint" style="margin-top:12px">A 2. mentés egy <strong>másik meghajtóra</strong> másolja az alkalmazás helyreállítási csomagját és adatait. A böngészhető fájljaidat csak ez védi meg egy meghajtóhiba ellen — a teljes rendszermentés ezeket nem tartalmazza.</p>
@@ -271,6 +259,7 @@
</select>
</div>
<button type="submit" class="btn btn-primary">Mentés</button>
</form>
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -251,26 +251,20 @@
<div class="form-section">
<h4>Automatikusan generált értékek</h4>
<p class="form-section-desc">Ezek az értékek a telepítéssel együtt mentésre kerülnek. Jegyezd fel a szükséges jelszavakat!</p>
<div class="form-group form-group-auto">
<label>Titkos kulcs érték <span class="auto-generated-badge"><svg class="ico ico-sm"><use href="#i-check"/></svg> Automatikusan generálva</span></label>
<div class="input-with-button">
<input type="password" id="auto-field-SECRET_KEY" class="form-control" value="generated-secret-value" readonly>
<button type="button" class="btn btn-sm btn-outline" onclick="toggleAutoField('auto-field-SECRET_KEY', this)">Megjelenítés</button>
</div>
<input type="hidden" name="SECRET_KEY" value="generated-secret-value">
@@ -300,8 +294,6 @@
<div class="form-section">
<h4>Beállítások</h4>
<div class="form-group">
<label for="field-SUBDOMAIN">
Webcím eleje
@@ -318,20 +310,15 @@
placeholder="aldomain"
pattern="[a-z0-9]([a-z0-9-]*[a-z0-9])?"
required
oninput="this.value=this.value.toLowerCase().replace(/[^a-z0-9-]/g,'')">
<span class="subdomain-suffix">.example.hu</span>
</div>
<span class="form-hint">Az aldomain telepítés után nem módosítható. Megváltoztatáshoz az alkalmazás eltávolítása és újratelepítése szükséges (minden adat törlődik).</span>
</div>
<div class="form-group">
<label for="field-LANG_SEL">
Felület nyelve
@@ -340,8 +327,7 @@
</label>
<select id="field-LANG_SEL" name="LANG_SEL" class="form-control"
>
<select id="field-LANG_SEL" name="LANG_SEL" class="form-control">
<option value="hu">Magyar nyelv</option>
@@ -351,8 +337,6 @@
</div>
<div class="form-group">
<label for="field-ADMIN_PASSWORD">
Admin jelszó mező
@@ -373,20 +357,16 @@
<button type="button" class="btn btn-sm btn-outline"
onclick="generatePassword('field-ADMIN_PASSWORD', 'field-confirm-ADMIN_PASSWORD', '')">Generálás</button>
</div>
<div class="input-with-button" style="margin-top:.25rem">
<input type="password" id="field-confirm-ADMIN_PASSWORD"
class="form-control" placeholder="Jelszó megerősítése"
data-confirm-for="field-ADMIN_PASSWORD">
</div>
</div>
<div class="form-group">
<label for="field-FLAG">
Kapcsoló mező itt
@@ -396,8 +376,7 @@
<label class="toggle">
<input type="checkbox" id="field-FLAG" name="FLAG" value="true"
>
<input type="checkbox" id="field-FLAG" name="FLAG" value="true">
<span class="toggle-label">Igen</span>
</label>
@@ -407,8 +386,6 @@
</div>
<div class="form-group">
<label for="field-HDD_PATH">
Adatok helye itt
@@ -419,7 +396,6 @@
<select id="field-HDD_PATH" name="HDD_PATH" class="form-control"
onchange="checkStorageSpace(this)">
<option value="/mnt/hdd1" data-free-percent="50"
@@ -443,7 +419,6 @@
HDD kettő — 100 GB szabad
</option>
</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> A kiválasztott tárhely majdnem megtelt.
@@ -458,8 +433,6 @@
</div>
<div class="form-group">
<label for="field-NOTE">
Megjegyzés mező
@@ -471,7 +444,6 @@
<input type="text" id="field-NOTE" name="NOTE"
class="form-control" value=""
placeholder="Írj ide valamit"
>
@@ -233,26 +233,20 @@
<div class="form-section">
<h4>Automatikusan generált értékek</h4>
<p class="form-section-desc">Ezek az értékek a telepítéssel együtt mentésre kerülnek. Jegyezd fel a szükséges jelszavakat!</p>
<div class="form-group form-group-auto">
<label>Titkos kulcs érték <span class="auto-generated-badge"><svg class="ico ico-sm"><use href="#i-check"/></svg> Automatikusan generálva</span></label>
<div class="input-with-button">
<input type="password" id="auto-field-SECRET_KEY" class="form-control" value="generated-secret-value" readonly>
<button type="button" class="btn btn-sm btn-outline" onclick="toggleAutoField('auto-field-SECRET_KEY', this)">Megjelenítés</button>
</div>
<input type="hidden" name="SECRET_KEY" value="generated-secret-value">
@@ -282,8 +276,6 @@
<div class="form-section">
<h4>Beállítások</h4>
<div class="form-group">
<label for="field-SUBDOMAIN">
Webcím eleje
@@ -300,20 +292,15 @@
placeholder="aldomain"
pattern="[a-z0-9]([a-z0-9-]*[a-z0-9])?"
required
oninput="this.value=this.value.toLowerCase().replace(/[^a-z0-9-]/g,'')">
<span class="subdomain-suffix">.example.hu</span>
</div>
<span class="form-hint">Az aldomain telepítés után nem módosítható. Megváltoztatáshoz az alkalmazás eltávolítása és újratelepítése szükséges (minden adat törlődik).</span>
</div>
<div class="form-group">
<label for="field-LANG_SEL">
Felület nyelve
@@ -322,8 +309,7 @@
</label>
<select id="field-LANG_SEL" name="LANG_SEL" class="form-control"
>
<select id="field-LANG_SEL" name="LANG_SEL" class="form-control">
<option value="hu">Magyar nyelv</option>
@@ -333,8 +319,6 @@
</div>
<div class="form-group">
<label for="field-ADMIN_PASSWORD">
Admin jelszó mező
@@ -355,20 +339,16 @@
<button type="button" class="btn btn-sm btn-outline"
onclick="generatePassword('field-ADMIN_PASSWORD', 'field-confirm-ADMIN_PASSWORD', '')">Generálás</button>
</div>
<div class="input-with-button" style="margin-top:.25rem">
<input type="password" id="field-confirm-ADMIN_PASSWORD"
class="form-control" placeholder="Jelszó megerősítése"
data-confirm-for="field-ADMIN_PASSWORD">
</div>
</div>
<div class="form-group">
<label for="field-FLAG">
Kapcsoló mező itt
@@ -378,8 +358,7 @@
<label class="toggle">
<input type="checkbox" id="field-FLAG" name="FLAG" value="true"
>
<input type="checkbox" id="field-FLAG" name="FLAG" value="true">
<span class="toggle-label">Igen</span>
</label>
@@ -389,8 +368,6 @@
</div>
<div class="form-group">
<label for="field-HDD_PATH">
Adatok helye itt
@@ -403,8 +380,7 @@
<input type="text" id="field-HDD_PATH" name="HDD_PATH"
class="form-control" value=""
placeholder="/mnt/valahol"
required
>
required>
<span class="form-hint" style="color:var(--warn)">Nincs regisztrált adattároló — add meg kézzel az útvonalat</span>
@@ -412,8 +388,6 @@
</div>
<div class="form-group">
<label for="field-NOTE">
Megjegyzés mező
@@ -425,7 +399,6 @@
<input type="text" id="field-NOTE" name="NOTE"
class="form-control" value=""
placeholder="Írj ide valamit"
>
@@ -228,26 +228,20 @@
<div class="form-section">
<h4>Automatikusan generált értékek</h4>
<p class="form-section-desc">Ezek az értékek a telepítéssel együtt mentésre kerülnek. Jegyezd fel a szükséges jelszavakat!</p>
<div class="form-group form-group-auto">
<label>Titkos kulcs érték <span class="auto-generated-badge"><svg class="ico ico-sm"><use href="#i-check"/></svg> Automatikusan generálva</span></label>
<div class="input-with-button">
<input type="password" id="auto-field-SECRET_KEY" class="form-control" value="generated-secret-value" readonly>
<button type="button" class="btn btn-sm btn-outline" onclick="toggleAutoField('auto-field-SECRET_KEY', this)">Megjelenítés</button>
</div>
<input type="hidden" name="SECRET_KEY" value="generated-secret-value">
@@ -277,8 +271,6 @@
<div class="form-section">
<h4>Beállítások</h4>
<div class="form-group">
<label for="field-SUBDOMAIN">
Webcím eleje
@@ -295,20 +287,15 @@
placeholder="aldomain"
pattern="[a-z0-9]([a-z0-9-]*[a-z0-9])?"
required
oninput="this.value=this.value.toLowerCase().replace(/[^a-z0-9-]/g,'')">
<span class="subdomain-suffix">.example.hu</span>
</div>
<span class="form-hint">Az aldomain telepítés után nem módosítható. Megváltoztatáshoz az alkalmazás eltávolítása és újratelepítése szükséges (minden adat törlődik).</span>
</div>
<div class="form-group">
<label for="field-LANG_SEL">
Felület nyelve
@@ -317,8 +304,7 @@
</label>
<select id="field-LANG_SEL" name="LANG_SEL" class="form-control"
>
<select id="field-LANG_SEL" name="LANG_SEL" class="form-control">
<option value="hu">Magyar nyelv</option>
@@ -328,8 +314,6 @@
</div>
<div class="form-group">
<label for="field-ADMIN_PASSWORD">
Admin jelszó mező
@@ -350,20 +334,16 @@
<button type="button" class="btn btn-sm btn-outline"
onclick="generatePassword('field-ADMIN_PASSWORD', 'field-confirm-ADMIN_PASSWORD', '')">Generálás</button>
</div>
<div class="input-with-button" style="margin-top:.25rem">
<input type="password" id="field-confirm-ADMIN_PASSWORD"
class="form-control" placeholder="Jelszó megerősítése"
data-confirm-for="field-ADMIN_PASSWORD">
</div>
</div>
<div class="form-group">
<label for="field-FLAG">
Kapcsoló mező itt
@@ -373,8 +353,7 @@
<label class="toggle">
<input type="checkbox" id="field-FLAG" name="FLAG" value="true"
>
<input type="checkbox" id="field-FLAG" name="FLAG" value="true">
<span class="toggle-label">Igen</span>
</label>
@@ -384,8 +363,6 @@
</div>
<div class="form-group">
<label for="field-HDD_PATH">
Adatok helye itt
@@ -396,7 +373,6 @@
<select id="field-HDD_PATH" name="HDD_PATH" class="form-control"
onchange="checkStorageSpace(this)">
<option value="/mnt/hdd1" data-free-percent="50"
@@ -420,9 +396,6 @@
HDD kettő — 100 GB szabad
</option>
<option value="/mnt/gone" selected disabled>/mnt/gone (nem elérhető)</option>
</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> A kiválasztott tárhely majdnem megtelt.
@@ -437,8 +410,6 @@
</div>
<div class="form-group">
<label for="field-NOTE">
Megjegyzés mező
@@ -450,7 +421,6 @@
<input type="text" id="field-NOTE" name="NOTE"
class="form-control" value=""
placeholder="Írj ide valamit"
>