e66d4669d6
gates / gates (push) Failing after 47s
An app was six pages — details, install/settings, logs, export, the per-app backup panel and a
details card for moving data — and every "Vissza" on them went to the app LIST, so there was no way
back to the app you were looking at. It is now ONE page with four tab URLs under /apps/{slug}/,
one shared header and one set of banners.
settings Mukodes (Start/Restart/Stop, Stop confirms) + the cards that want something from the
household + the install-time and optional settings. The default for an installed app,
RENDERED at the bare URL and never redirected to.
data the data folders, move to another drive, the nightly copy, the 2nd-copy setting, export
guide tagline, screenshots, use cases, first steps, the catalog's guide, the FAQ, the docs
link — and the WHOLE page for an app that is not installed
logs the log, and the only tab whose poller runs
The tabs are links, not panels: a link in an alert has to land ON a tab, and the log poller must not
run on the other three. Start/Restart/Stop left the page header, where they read as menu buttons.
The old /stacks/{name}/{deploy,logs,export,backup} GETs answer 302 — never 301, which a browser
caches beyond anyone's reach — to the tab that now holds them, query string carried through, with
name->slug resolved through the stack list rather than assumed equal. Every POST on those paths is
untouched: the backup form, the app-email toggle and the reveal endpoints keep their URLs.
A value asked AFTER the app starts is now generic (D2). The catalog marks a field `after_start: true`
and supplies its label; the ACTION is Go, in a registry keyed by slug + env var. The catalog can
never declare what runs — a declared field with no registered action renders nothing, POSTs 404 and
logs one WARN with the env var KEY, never the value. A red-proof found the second half of that guard
unpinned: deleting the "the catalog must have DECLARED this field" check left the suite green, and a
POST for an undeclared field then reached Plex and opened the setup gate.
TestAfterStart_RegistryAloneIsNotAnInvitation pins it.
New catalog block app_info.guide (sections + faq + checked_version), parsed, localised field by
field and rendered with the household's own address filled in. It renders only when checked_version
names the image tag its author clicked through. No template carries one yet.
The Pi and "csak x86" badges are gone; PiCompatible and the catalog key stay this release, or a box
on 0.314.0 would read "csak x86" on every app (R-1067).
template_id_gate.py now follows {{template}} includes, with a scope decoy proving the walk did not
become vacuous.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0117kRTSgs3rJWUFGxyoWN6Y
425 lines
21 KiB
Go
425 lines
21 KiB
Go
package web
|
|
|
|
import (
|
|
"time"
|
|
|
|
"gitea.dooplex.hu/admin/felhom-controller/internal/backup"
|
|
"gitea.dooplex.hu/admin/felhom-controller/internal/stacks"
|
|
)
|
|
|
|
// ── i18n slice 1, release B (v0.249.0) — parity cases for the backup pages ─────────────────────────
|
|
//
|
|
// Captured from the UNCONVERTED templates (commit in REPORT.md). Data: multi-word or ASCII values only.
|
|
|
|
type m = map[string]interface{}
|
|
|
|
func i18nBackupStatus(running bool, withResults bool) m {
|
|
at := i18nFixtureTime()
|
|
b := m{"DBDumpSchedule": "02:30", "NextDBDump": at, "Running": running,
|
|
"AppDataInfo": []m{{"StackName": "privatebin"}},
|
|
"DiscoveredDBs": []m{{"StackName": "kimai"}},
|
|
}
|
|
if withResults {
|
|
b["LastDBDump"] = m{"LastRun": at, "Results": []m{
|
|
{"DB": m{"StackName": "kimai", "DBType": backup.DBTypeMariaDB}, "Error": "dump failed", "Size": int64(0), "Validation": m{}},
|
|
{"DB": m{"StackName": "bookstack", "DBType": backup.DBTypeMariaDB}, "Error": "", "Size": int64(1 << 20), "Validation": m{"Valid": true, "TableCount": 12}},
|
|
{"DB": m{"StackName": "immich", "DBType": backup.DBTypePostgres}, "Error": "", "Size": int64(2 << 20), "Validation": m{"Valid": false, "Error": "no create table"}},
|
|
{"DB": m{"StackName": "romm", "DBType": backup.DBTypePostgres}, "Error": "", "Size": int64(3 << 20), "Validation": m{"Valid": false}},
|
|
}}
|
|
} else {
|
|
b["DumpFiles"] = []m{
|
|
{"StackName": "bookstack", "DBType": backup.DBTypeMariaDB, "Size": int64(1 << 20), "ModTime": at, "Validation": m{"Valid": true, "TableCount": 12}},
|
|
{"StackName": "immich", "DBType": backup.DBTypePostgres, "Size": int64(2 << 20), "ModTime": at, "Validation": m{"Error": "no create table"}},
|
|
{"StackName": "romm", "DBType": backup.DBTypePostgres, "Size": int64(3 << 20), "ModTime": at, "Validation": m{}},
|
|
}
|
|
}
|
|
return b
|
|
}
|
|
|
|
func i18nAppRows() []m {
|
|
base := func(name string) m {
|
|
return m{"StackName": name, "Slug": name, "DisplayName": "App " + name, "Status": "ok", "StatusText": "Minden rendben van",
|
|
"Tier1Contents": "Adatbázis és beállítások", "Tier23Contents": "Teljes helyreállítási egység", "Tier2Dest": "HDD kettő",
|
|
"Tier2Schedule": "naponta 03:00", "Tier2StatusBadge": "Sikeres mentés", "Tier3State": "none"}
|
|
}
|
|
rows := []m{}
|
|
r := base("removed")
|
|
r["Removed"], r["StorageLabel"], r["RemovedUnitTime"] = true, "HDD egy", "2026-09-14T03:12:00Z"
|
|
rows = append(rows, r)
|
|
r = base("disconnected")
|
|
r["DriveDisconnected"] = true
|
|
r["Tier1LastRun"], r["Tier1LastStatus"] = "2026-09-14T03:12:00Z", "ok"
|
|
r["Tier2UserDisabled"], r["Tier3State"] = true, "active"
|
|
r["Warnings"] = []string{"Egy figyelmeztetés szövege"}
|
|
rows = append(rows, r)
|
|
r = base("hdd")
|
|
r["HasHDDData"], r["StorageLabel"], r["HDDSizeHuman"] = true, "HDD egy", "12 GB"
|
|
r["Tier1LastRun"], r["Tier1LastStatus"] = "2026-09-14T03:12:00Z", "error"
|
|
r["DriveFilesNote"], r["DriveFilesNoteLink"], r["DriveFilesNoteLinkText"] = "A fájlok nincsenek benne", "/backups/remote", "Távoli mentés oldal"
|
|
r["HasDB"], r["Tier1DBStatus"] = true, "error"
|
|
r["Tier2Configured"], r["Tier2DestDisconnected"], r["Tier2SuccessTracked"], r["Tier2LastRun"] = true, true, false, "2026-09-14T03:12:00Z"
|
|
r["Tier3State"] = "escrow_pending"
|
|
rows = append(rows, r)
|
|
r = base("volume")
|
|
r["HasVolumeData"], r["HasDB"] = true, true
|
|
r["Tier1LastRun"], r["Tier1LastStatus"] = "2026-09-14T03:12:00Z", "unknown"
|
|
r["Tier2Configured"], r["Tier2DestDisconnected"], r["Tier2SuccessTracked"], r["Tier2LastSuccess"] = true, true, true, "2026-09-14T03:12:00Z"
|
|
r["Tier3State"] = "off"
|
|
rows = append(rows, r)
|
|
r = base("confonly")
|
|
r["Tier2Configured"], r["Tier2DestDisconnected"], r["Tier2SuccessTracked"] = true, true, true
|
|
rows = append(rows, r)
|
|
for i, v := range []m{{"Tier2SuccessTracked": false, "Tier2LastRun": "2026-09-14T03:12:00Z"}, {"Tier2SuccessTracked": true, "Tier2LastSuccess": "2026-09-14T03:12:00Z"}, {"Tier2SuccessTracked": true}} {
|
|
r = base([]string{"inact1", "inact2", "inact3"}[i])
|
|
r["HasVolumeData"] = true
|
|
r["Tier2Configured"], r["Tier2DestInactive"] = true, true
|
|
for k, x := range v {
|
|
r[k] = x
|
|
}
|
|
rows = append(rows, r)
|
|
}
|
|
r = base("okrun")
|
|
r["Tier2Configured"], r["Tier2SuccessTracked"], r["Tier2LastRun"], r["Tier2LastStatus"] = true, false, "2026-09-14T03:12:00Z", "ok"
|
|
r["Tier2SizeHuman"], r["Tier2LastWarning"], r["Tier2UnitStaleNotice"] = "3 GB", "Egy figyelmeztetés a másolatról", "A másolat régebbi a mentésnél"
|
|
r["Tier2UnitRestorable"], r["Tier2UnitConfirm"] = true, "Biztosan visszaállítod a teljes egységet?"
|
|
rows = append(rows, r)
|
|
r = base("oksucc")
|
|
r["Tier2Configured"], r["Tier2SuccessTracked"], r["Tier2LastSuccess"], r["Tier2LastStatus"] = true, true, "2026-09-14T03:12:00Z", "error"
|
|
rows = append(rows, r)
|
|
r = base("nosucc")
|
|
r["Tier2Configured"], r["Tier2SuccessTracked"], r["Tier2LastStatus"] = true, true, "error"
|
|
rows = append(rows, r)
|
|
r = base("nosucc2")
|
|
r["Tier2Configured"], r["Tier2SuccessTracked"], r["Tier2LastRun"], r["Tier2LastStatus"] = true, false, "", "running"
|
|
rows = append(rows, r)
|
|
r = base("t2err")
|
|
r["Tier2LastError"] = "Nincs második meghajtó csatlakoztatva"
|
|
rows = append(rows, r)
|
|
return rows
|
|
}
|
|
|
|
func i18nAppsData(offboxStatus string, running bool, withResults bool, dbState string) map[string]interface{} {
|
|
d := i18nLayoutData("backups-apps", "Alkalmazás mentések")
|
|
d["Backup"] = i18nBackupStatus(running, withResults)
|
|
d["DBSectionState"] = dbState
|
|
d["NoUserDataBackupWarning"] = true
|
|
d["AppBackupRows"] = i18nAppRows()
|
|
d["Offbox"] = m{"LastStatus": offboxStatus, "Host": "nas.example", "LastRun": "2026-09-14T03:12:00Z"}
|
|
return d
|
|
}
|
|
|
|
func i18nCasesB() []i18nCase {
|
|
return []i18nCase{
|
|
{"backups_apps_ok", "backups_apps", func() map[string]interface{} { return i18nAppsData("ok", false, true, "dumps") }},
|
|
{"backups_apps_error", "backups_apps", func() map[string]interface{} { return i18nAppsData("error", true, false, "dumps") }},
|
|
{"backups_apps_running", "backups_apps", func() map[string]interface{} {
|
|
d := i18nAppsData("running", false, false, "pending")
|
|
d["Backup"].(m)["DumpFiles"] = []m{}
|
|
d["Backup"].(m)["DiscoveredDBs"] = []m{}
|
|
return d
|
|
}},
|
|
{"backups_apps_other", "backups_apps", func() map[string]interface{} {
|
|
d := i18nAppsData("incomplete", false, false, "embedded")
|
|
d["Backup"].(m)["DumpFiles"] = []m{}
|
|
d["Backup"].(m)["DiscoveredDBs"] = []m{}
|
|
return d
|
|
}},
|
|
{"backups_apps_stale_copy", "backups_apps", func() map[string]interface{} {
|
|
d := i18nAppsData("ok", false, true, "dumps")
|
|
rows := d["AppBackupRows"].([]m)
|
|
for _, r := range rows {
|
|
if r["StackName"] == "oksucc" {
|
|
r["Tier2StaleCopy"] = true
|
|
}
|
|
}
|
|
return d
|
|
}},
|
|
{"backups_apps_empty", "backups_apps", func() map[string]interface{} { return i18nLayoutData("backups-apps", "Alkalmazás mentések") }},
|
|
{"backups_remote_full", "backups_remote", func() map[string]interface{} {
|
|
return i18nRemoteData(func(d, o m) {
|
|
d["RecoveryOffer"], d["OffboxToggledCount"] = true, 0
|
|
o["LastStatus"], o["StatsKnown"], o["RepoSizeHuman"], o["SnapshotCount"] = "ok", true, "12 GB", 40
|
|
o["OrphanedRenamedTo"], o["LastError"], o["LastWarning"], o["RepoState"] = "repo-old-2026", "timeout while uploading", "quota near limit", "orphaned"
|
|
d["OffboxQuotaPct"], d["OffboxWarningDisplay"] = 110, "quota near limit"
|
|
d["OffboxCeremonyAwaiting"] = true
|
|
d["AbandonActive"], d["AbandonDate"], d["AbandonDaysLeft"], d["AbandonRetrievalOffered"] = true, "2026-09-30", 13, true
|
|
d["OffboxApps"] = []m{{"Slug": "immich", "DisplayName": "Immich Photos", "Name": "immich", "Enabled": true},
|
|
{"Slug": "kimai", "DisplayName": "Kimai Time", "Name": "kimai", "Enabled": false}}
|
|
d["OffboxBlockedSet"] = map[string]bool{"immich": true}
|
|
})
|
|
}},
|
|
{"backups_remote_incomplete", "backups_remote", func() map[string]interface{} {
|
|
return i18nRemoteData(func(d, o m) {
|
|
d["OffboxToggledCount"] = 2
|
|
o["LastStatus"], o["StatsKnown"], o["QuotaGB"], o["EscrowState"] = "incomplete", false, 50, ""
|
|
d["OffboxQuotaPct"], d["OffboxWarningDisplay"] = 85, "The selection changed since the warning"
|
|
d["OffboxCeremonyTimedOut"], d["EscrowAgentOK"] = true, true
|
|
d["AbandonActive"], d["AbandonDate"], d["AbandonDaysLeft"] = true, "2026-09-30", 13
|
|
})
|
|
}},
|
|
// R-365: the countdown's due moment has passed and the daily sweep has not run yet.
|
|
{"backups_remote_abandon_overdue", "backups_remote", func() map[string]interface{} {
|
|
return i18nRemoteData(func(d, o m) {
|
|
d["OffboxToggledCount"] = 1
|
|
o["LastStatus"], o["StatsKnown"] = "ok", true
|
|
d["AbandonActive"], d["AbandonDate"], d["AbandonDaysLeft"], d["AbandonOverdue"] = true, "2026-09-30", 0, true
|
|
})
|
|
}},
|
|
{"backups_remote_error", "backups_remote", func() map[string]interface{} {
|
|
return i18nRemoteData(func(d, o m) {
|
|
o["LastStatus"], o["Enabled"], o["StatsKnown"], o["RepoSizeHuman"] = "error", false, true, ""
|
|
d["OffboxCeremonyTimedOut"], d["EscrowAgentOK"] = true, false
|
|
d["AbandonPurging"] = true
|
|
})
|
|
}},
|
|
{"backups_remote_running", "backups_remote", func() map[string]interface{} {
|
|
return i18nRemoteData(func(d, o m) {
|
|
o["LastStatus"], o["QuotaGB"], o["StatsKnown"], o["RepoSizeHuman"], o["EscrowState"] = "running", 10, true, "", "pending"
|
|
d["OffboxQuotaPct"], d["RecoveryOffer"] = 10, true
|
|
})
|
|
}},
|
|
{"backups_remote_pending_agent", "backups_remote", func() map[string]interface{} {
|
|
return i18nRemoteData(func(d, o m) { o["LastStatus"], o["EscrowState"] = "never", "pending"; d["EscrowAgentOK"] = true })
|
|
}},
|
|
{"backups_remote_pending_old", "backups_remote", func() map[string]interface{} {
|
|
return i18nRemoteData(func(d, o m) { o["EscrowState"] = "pending"; d["EscrowAgentOK"] = false })
|
|
}},
|
|
{"backups_remote_stale", "backups_remote", func() map[string]interface{} {
|
|
return i18nRemoteData(func(d, o m) { d["EscrowStale"], d["EscrowAgentOK"] = true, true })
|
|
}},
|
|
{"backups_remote_stale_old", "backups_remote", func() map[string]interface{} {
|
|
return i18nRemoteData(func(d, o m) { d["EscrowStale"], d["EscrowAgentOK"] = true, false })
|
|
}},
|
|
{"backups_remote_escrowed", "backups_remote", func() map[string]interface{} {
|
|
return i18nRemoteData(func(d, o m) { d["EscrowAgentOK"] = true })
|
|
}},
|
|
{"backups_remote_notconf_hub", "backups_remote", func() map[string]interface{} {
|
|
d := i18nLayoutData("backups-remote", "Távoli mentés beállítása")
|
|
d["Backup"] = i18nBackupStatus(false, false)
|
|
d["OffboxConfigured"], d["OffsiteHubEnabled"] = false, true
|
|
return d
|
|
}},
|
|
{"backups_remote_notconf", "backups_remote", func() map[string]interface{} {
|
|
d := i18nLayoutData("backups-remote", "Távoli mentés beállítása")
|
|
d["Backup"] = i18nBackupStatus(false, false)
|
|
d["OffboxConfigured"], d["OffsiteHubEnabled"] = false, false
|
|
return d
|
|
}},
|
|
{"backups_remote_empty", "backups_remote", func() map[string]interface{} {
|
|
return i18nLayoutData("backups-remote", "Távoli mentés beállítása")
|
|
}},
|
|
{"backups_restore_full", "backups_restore", func() map[string]interface{} {
|
|
d := i18nLayoutData("backups-restore", "Visszaállítás a mentésből")
|
|
d["InterruptedRestores"] = []m{{"Stack": "immich", "Message": "A visszaállítás megszakadt, indítsd újra"}}
|
|
b := i18nBackupStatus(false, false)
|
|
b["AppDataInfo"] = []m{{"StackName": "immich", "HasHDDData": true, "HasDBDump": true, "HasVolumeData": true, "DisplayName": "Immich Photos"}}
|
|
d["Backup"] = b
|
|
d["RemovedApps"] = []m{{"StackName": "oldapp", "DisplayName": "Régi alkalmazás"}}
|
|
d["OffboxConfigured"] = true
|
|
d["NoRestoreDestination"], d["HasAttachDestination"] = true, true
|
|
d["OffsiteStoreState"] = "unreadable"
|
|
d["OffsiteRestoreRows"] = []m{
|
|
{"Slug": "immich", "DisplayName": "Immich Photos", "App": "immich", "Restorable": true, "InStore": true, "Installed": false, "StoreUnknown": true},
|
|
{"Slug": "kimai", "DisplayName": "Kimai Time", "App": "kimai", "Restorable": false},
|
|
}
|
|
d["SharesRestoreOffered"], d["SharesDisplayName"], d["SharesScratchReady"] = true, "Hálózati megosztások", true
|
|
d["OffsiteRestoreCopies"] = []m{{"Stack": "immich", "SizeHuman": "2 GB", "Created": i18nFixtureTime(), "Path": "/mnt/hdd1/copy"}}
|
|
d["OffboxApps"] = []m{{"Slug": "immich", "DisplayName": "Immich Photos", "Name": "immich"}}
|
|
return d
|
|
}},
|
|
{"backups_restore_known_empty", "backups_restore", func() map[string]interface{} {
|
|
d := i18nLayoutData("backups-restore", "Visszaállítás a mentésből")
|
|
d["Backup"] = i18nBackupStatus(false, false)
|
|
d["OffboxConfigured"] = true
|
|
d["NoRestoreDestination"], d["HasAttachDestination"] = true, false
|
|
d["OffsiteStoreState"] = "known"
|
|
return d
|
|
}},
|
|
{"backups_restore_notarget", "backups_restore", func() map[string]interface{} {
|
|
d := i18nLayoutData("backups-restore", "Visszaállítás a mentésből")
|
|
d["Backup"] = i18nBackupStatus(false, false)
|
|
d["OffboxConfigured"] = true
|
|
d["OffsiteStoreState"] = "no-target"
|
|
return d
|
|
}},
|
|
{"backups_restore_empty", "backups_restore", func() map[string]interface{} {
|
|
return i18nLayoutData("backups-restore", "Visszaállítás a mentésből")
|
|
}},
|
|
{"restore_wizard_exec", "backups_restore_wizard", func() map[string]interface{} {
|
|
d := i18nWizardData("vegrehajtas", "execution")
|
|
d["LastResult"] = m{"OK": true, "Message": "A visszaállítás elkészült rendben", "Interrupted": false, "FinishedAt": i18nFixtureTime()}
|
|
d["RunningStack"] = "immich"
|
|
return d
|
|
}},
|
|
{"restore_wizard_prepare_confirm", "backups_restore_wizard", func() map[string]interface{} {
|
|
d := i18nWizardData("megerosites", "prepare-confirm")
|
|
d["LastResult"] = m{"OK": false, "Message": "A visszaállítás megszakadt közben", "Interrupted": true, "FinishedAt": i18nFixtureTime()}
|
|
d["FullPrepSize"] = "3 GB"
|
|
return d
|
|
}},
|
|
{"restore_wizard_ready", "backups_restore_wizard", func() map[string]interface{} {
|
|
d := i18nWizardData("elokeszites", "verify")
|
|
w := d["Wizard"].(m)
|
|
w["VerifyEnabled"], w["PlaceEnabled"], w["RestoreEnabled"] = true, true, true
|
|
d["Pair"] = m{"DumpsAt": i18nFixtureTime(), "Skewed": true, "LooksEmpty": true}
|
|
return d
|
|
}},
|
|
{"restore_wizard_nodumptime", "backups_restore_wizard", func() map[string]interface{} {
|
|
d := i18nWizardData("eredmeny", "verify")
|
|
d["Wizard"].(m)["RestoreEnabled"] = true
|
|
d["Pair"] = m{"DumpsAt": time.Time{}, "Skewed": false, "LooksEmpty": false}
|
|
return d
|
|
}},
|
|
{"escrow_not_ready", "backups_escrow", func() map[string]interface{} {
|
|
d := i18nLayoutData("backups-remote", "Helyreállítási kód létrehozása")
|
|
d["EscrowNotReady"], d["EscrowNotReadyMessage"] = true, "A doboz még nem áll készen erre"
|
|
return d
|
|
}},
|
|
{"escrow_agent_old", "backups_escrow", func() map[string]interface{} {
|
|
d := i18nLayoutData("backups-remote", "Helyreállítási kód létrehozása")
|
|
d["AgentSupported"] = false
|
|
return d
|
|
}},
|
|
{"escrow_receremony", "backups_escrow", func() map[string]interface{} {
|
|
d := i18nLayoutData("backups-remote", "Helyreállítási kód létrehozása")
|
|
d["AgentSupported"], d["Receremony"] = true, true
|
|
return d
|
|
}},
|
|
{"escrow_first", "backups_escrow", func() map[string]interface{} {
|
|
d := i18nLayoutData("backups-remote", "Helyreállítási kód létrehozása")
|
|
d["AgentSupported"] = true
|
|
return d
|
|
}},
|
|
{"app_data_not_hdd", "app_data", func() map[string]interface{} {
|
|
d := i18nLayoutData("stacks", "Private Bin")
|
|
i18nAppDataShell(d)
|
|
d["DisplayName"], d["StackName"] = "Private Bin", "privatebin"
|
|
d["Flash"], d["FlashError"] = "A beállítás elmentve rendben.", "A mentés nem sikerült most."
|
|
d["Tier2"] = m{"IsHDDApp": false, "Disabled": false, "NoTarget": true}
|
|
return d
|
|
}},
|
|
// R-499 (v0.267.0): the system-disk sentence has one branch per whole-system backup target
|
|
// state. tier2_not_hdd above carries no SystemBackup, which renders the `unknown` branch.
|
|
{"app_data_not_hdd_protected", "app_data", func() map[string]interface{} {
|
|
d := i18nLayoutData("stacks", "Private Bin")
|
|
i18nAppDataShell(d)
|
|
d["DisplayName"], d["StackName"] = "Private Bin", "privatebin"
|
|
d["Tier2"] = m{"IsHDDApp": false}
|
|
d["SystemBackup"] = "protected"
|
|
return d
|
|
}},
|
|
{"app_data_not_hdd_same_disk", "app_data", func() map[string]interface{} {
|
|
d := i18nLayoutData("stacks", "Private Bin")
|
|
i18nAppDataShell(d)
|
|
d["DisplayName"], d["StackName"] = "Private Bin", "privatebin"
|
|
d["Tier2"] = m{"IsHDDApp": false}
|
|
d["SystemBackup"] = "same_disk"
|
|
return d
|
|
}},
|
|
{"app_data_not_hdd_absent", "app_data", func() map[string]interface{} {
|
|
d := i18nLayoutData("stacks", "Private Bin")
|
|
i18nAppDataShell(d)
|
|
d["DisplayName"], d["StackName"] = "Private Bin", "privatebin"
|
|
d["Tier2"] = m{"IsHDDApp": false}
|
|
d["SystemBackup"] = "absent"
|
|
return d
|
|
}},
|
|
{"app_data_notarget_disabled", "app_data", func() map[string]interface{} {
|
|
d := i18nLayoutData("stacks", "Private Bin")
|
|
i18nAppDataShell(d)
|
|
d["DisplayName"], d["StackName"] = "Immich Photos", "immich"
|
|
d["Tier2"] = m{"IsHDDApp": true, "Disabled": true, "NoTarget": true, "NoTargetReason": "Nincs második meghajtó csatlakoztatva"}
|
|
return d
|
|
}},
|
|
{"app_data_target_ssd", "app_data", func() map[string]interface{} {
|
|
d := i18nLayoutData("stacks", "Private Bin")
|
|
i18nAppDataShell(d)
|
|
d["DisplayName"], d["StackName"] = "Immich Photos", "immich"
|
|
d["Tier2"] = m{"IsHDDApp": true, "Disabled": false, "NoTarget": false, "EffectiveLabel": "SSD meghajtó egy", "EffectiveIsSSD": true,
|
|
"Preferred": "/mnt/ssd", "EffectiveDesc": "a leggyorsabb meghajtó", "Alternatives": []m{{"Path": "/mnt/ssd", "Label": "SSD meghajtó egy"}}}
|
|
return d
|
|
}},
|
|
{"app_data_target_auto", "app_data", func() map[string]interface{} {
|
|
d := i18nLayoutData("stacks", "Private Bin")
|
|
i18nAppDataShell(d)
|
|
d["DisplayName"], d["StackName"] = "Immich Photos", "immich"
|
|
d["Tier2"] = m{"IsHDDApp": true, "Disabled": false, "NoTarget": false, "EffectiveLabel": "HDD kettő", "EffectiveIsSSD": false,
|
|
"Preferred": "", "EffectiveDesc": "a legnagyobb szabad hely"}
|
|
return d
|
|
}},
|
|
{"recovery_unlocked_apps", "recovery", func() map[string]interface{} {
|
|
d := i18nLayoutData("recovery", "Korábbi mentések visszaszerzése")
|
|
d["Unlocked"], d["Flash"], d["Error"] = true, "A kód elfogadva rendben.", "Egy hiba történt közben."
|
|
d["InvApps"] = []m{{"App": "immich", "LatestAt": i18nFixtureTime(), "SizeBytes": int64(5 << 30)}, {"App": "kimai", "LatestAt": i18nFixtureTime(), "SizeBytes": int64(0)}}
|
|
return d
|
|
}},
|
|
{"recovery_unlocked_unavailable", "recovery", func() map[string]interface{} {
|
|
d := i18nLayoutData("recovery", "Korábbi mentések visszaszerzése")
|
|
d["Unlocked"], d["InvUnavailable"] = true, true
|
|
return d
|
|
}},
|
|
{"recovery_unlocked_empty", "recovery", func() map[string]interface{} {
|
|
d := i18nLayoutData("recovery", "Korábbi mentések visszaszerzése")
|
|
d["Unlocked"], d["InvEmpty"] = true, true
|
|
return d
|
|
}},
|
|
{"recovery_unlocked_untagged", "recovery", func() map[string]interface{} {
|
|
d := i18nLayoutData("recovery", "Korábbi mentések visszaszerzése")
|
|
d["Unlocked"], d["InvUntagged"] = true, true
|
|
return d
|
|
}},
|
|
{"recovery_locked_confirm", "recovery", func() map[string]interface{} {
|
|
d := i18nLayoutData("recovery", "Korábbi mentések visszaszerzése")
|
|
d["Flash"], d["Error"] = "A kód elfogadva rendben.", "Egy hiba történt közben."
|
|
d["SealedAt"], d["ConfirmSetAside"], d["AbandonGraceDays"] = "2026-09-01 10:00", true, 30
|
|
return d
|
|
}},
|
|
{"recovery_locked_can", "recovery", func() map[string]interface{} {
|
|
d := i18nLayoutData("recovery", "Korábbi mentések visszaszerzése")
|
|
d["CanSetAside"], d["AbandonGraceDays"] = true, 30
|
|
return d
|
|
}},
|
|
{"recovery_locked_cannot", "recovery", func() map[string]interface{} {
|
|
d := i18nLayoutData("recovery", "Korábbi mentések visszaszerzése")
|
|
d["CanSetAside"], d["AbandonGraceDays"] = false, 30
|
|
return d
|
|
}},
|
|
{"restore_wizard_blocked", "backups_restore_wizard", func() map[string]interface{} {
|
|
d := i18nWizardData("eredmeny", "verify")
|
|
d["Pair"] = m{"DumpsAt": time.Time{}, "Skewed": true, "LooksEmpty": false}
|
|
return d
|
|
}},
|
|
}
|
|
}
|
|
|
|
func i18nRemoteData(tweak func(d, o m)) map[string]interface{} {
|
|
d := i18nLayoutData("backups-remote", "Távoli mentés beállítása")
|
|
d["Backup"] = i18nBackupStatus(false, false)
|
|
o := m{"LastStatus": "ok", "LastRun": "2026-09-14T03:12:00Z", "Enabled": true, "User": "felhom", "Host": "nas.example",
|
|
"RepoPath": "/srv/repo", "Port": 22, "QuotaGB": 100, "StatsKnown": true, "RepoSizeHuman": "5 GB", "SnapshotCount": 10,
|
|
"EscrowState": "escrowed", "RepoState": "ok"}
|
|
d["Offbox"] = o
|
|
d["OffboxConfigured"] = true
|
|
d["OffboxToggledCount"] = 1
|
|
d["OffboxQuotaPct"] = 5
|
|
tweak(d, o)
|
|
return d
|
|
}
|
|
|
|
func i18nWizardData(phase, step string) map[string]interface{} {
|
|
d := i18nLayoutData("backups-restore", "Visszaállítás varázsló")
|
|
d["AppDisplayName"], d["App"] = "Immich Photos", "immich"
|
|
d["Wizard"] = m{"Phase": phase, "Step": step}
|
|
d["Pair"] = m{"DumpsAt": time.Time{}}
|
|
return d
|
|
}
|
|
|
|
// i18nAppDataShell gives a Tier-2 scenario the app it now belongs to. The per-app backup panel was a
|
|
// page of its own until v0.315.0; it is a section of the app page's „Adatok es mentes" tab, so the
|
|
// shell above it needs the app's identity. One app for all seven scenarios: what they vary is the
|
|
// backup state, not the app.
|
|
func i18nAppDataShell(d map[string]interface{}) {
|
|
st := i18nStack("privatebin", "Private Bin", stacks.StateRunning, true)
|
|
d["Stack"], d["Meta"], d["AppInfo"] = st, st.Meta, st.Meta.AppInfo
|
|
}
|