W12 family names keep their accents, login made without them; W9 banner link colour; W10 recovery-code page one plain line; W11/W24 remote page hides provider login and key fields behind Haladó; unreleased
gates / gates (push) Successful in 1m33s

This commit is contained in:
admin committed 2026-10-21 22:35:24 +02:00
1 parent 37b79caad9
commit 8c1e290f63
33 files changed
+1278 -93

No files matched your search

+29
View File
@@ -35,6 +35,35 @@
answer for minutes. Tell Felhom; we will fix it."). An older agent is not judged (`agentapi.GuestMemoryInfo` answer for minutes. Tell Felhom; we will fix it."). An older agent is not judged (`agentapi.GuestMemoryInfo`
`host_swap_mb`/`guest_swap_mb`, 2 s timeout). The `deploy_new` i18n fixture regenerated on purpose (one line). `host_swap_mb`/`guest_swap_mb`, 2 s timeout). The `deploy_new` i18n fixture regenerated on purpose (one line).
- Tests (each seen failing first): `TestR1177_*` (5), `TestR1195_*` (5), `TestR1178_*`, `TestR1180_*`, `TestR1192_SwapShort*`. - Tests (each seen failing first): `TestR1177_*` (5), `TestR1195_*` (5), `TestR1178_*`, `TestR1180_*`, `TestR1192_SwapShort*`.
- **W12 (rehearsal word table) — the family card takes a family's own names.** „Ödön", „Írisz" were refused („A név
1–32 karakter: kisbetű, szám…"). The name is now kept exactly as typed and the LOGIN is made from it: accents removed,
lower case, a space becomes a dot, other signs dropped (`family.LoginFrom`; „Ödön" → `odon`, „Írisz" → `irisz`). A
second person whose name makes the same login gets the next number (`odon2`); the same name again is still „Már van
ilyen nevű családtag." The login's own check is unchanged (1–32 of `a-z 0-9 . _ -`). The card lists „Ödön
belépési név: odon" (EN „login: odon"), the once-shown password names the login, and a hint says „Írd be a nevet úgy,
ahogy szeretnéd, ékezettel is. A belépési név ebből készül: ékezet nélkül, kisbetűvel. A listában látod." (EN „Type
the name as you like it, accents too. The login is made from it: no accents, lower case. You see it in the list.").
The family sign-in page accepts the name typed either way. New refusal text: „Ebből a névből nem tudunk belépési
nevet készíteni. Írj be egy legfeljebb 64 betűs nevet latin betűkkel. Az ékezet jó." (EN „We cannot make a login from
this name. Type a name of at most 64 letters in the Latin alphabet. Accents are fine."). `family.json` gains
`display` (omitted when it equals the login). Tests `TestW12_AccentedNamesGetAPlainLogin`,
`TestW12_NumberedLoginStaysInsideTheLimit`, `TestW12_CardTakesAccentedNamesAndShowsTheLogin`.
- **W9 — the dashboard's yellow bar link** („Helyreállítási kód létrehozása →") takes the banner's colour, underlined,
instead of browser-default blue (`.alert-message a`). `TestW9_BannerSentenceLinkTakesTheBannerColour`.
- **W10 — the recovery-code page.** When every check passed the household reads one line, „Minden előfeltétel
rendben." (EN „All prerequisites are in place."); the rows (storage name, encryption tool path) wait behind
„Részletek" / „Details". A failed check still shows the list open. The step headings lose their numbers (cards 2–4
hide as the wizard moves on, so the page read 1 → 5 → 6).
- **W11 + W24 — the „Távoli mentés" page.** The Felhom-provided store shows „Felhom távoli tárhely (Németország)" /
„Ide kerül a titkosított másolat." (EN „Felhom remote storage (Germany)" / „The encrypted copy goes here.") instead
of the provider login; the login waits behind „Haladó" / „Advanced". The SSH key and known-host fields of the target
form sit behind „Haladó" too; their hint reads „A célgép saját kulcsa. Ezzel a doboz csak ehhez a géphez kapcsolódik.
Így kérdezheted le:" (EN „This is the key of the target machine. With it, the box connects only to this machine. To
look it up:"; was „no blind TOFU"). The page's intro no longer says „restic + SFTP" or „3-2-1": „Az alkalmazásaid
mentéseiről titkosított másolat készül egy távoli helyre: a Felhom tárhelyére vagy a saját NAS-odra. Ott csak
titkosított adat van. Ez a másolat független a helyi másodpéldánytól és a teljes rendszermentéstől." New parity case
`backups_remote_pinned`; the `backups_remote_*`, `escrow_first`, `escrow_receremony` and `settings_security_*`
fixtures regenerated on purpose (only the lines above changed).
## v0.337.0 (2026-10-21) — kept second-drive copies (D25: R-1175, R-1181, R-1179); a `.fab` import keeps the family gate (R-1191); a stale restore temp no longer blocks the retry (R-1188 c); the overcommit sentence made true (R-1192); escrow recorded before the floor (R-1176); a finished off-site abandonment closes on the hub's word (R-1182); storage labels follow the language (W26) ## v0.337.0 (2026-10-21) — kept second-drive copies (D25: R-1175, R-1181, R-1179); a `.fab` import keeps the family gate (R-1191); a stale restore temp no longer blocks the retry (R-1188 c); the overcommit sentence made true (R-1192); escrow recorded before the floor (R-1176); a finished off-site abandonment closes on the hub's word (R-1182); storage labels follow the language (W26)
+1 -1
View File
@@ -29,7 +29,7 @@
| `stacks.RunAfterInstall` / `expandAfterInstall` / `web.defaultLoginInEffect` (v0.279.0, decision 45) | controller/internal/stacks/after_install.go · controller/internal/web/known_login.go | `(name, wait)` / `(cmd, allowed, env)` / `(meta, cfg, installed)` | A fresh install replaces a known default login; the page says when a default is still in effect | **Only from the deploy-done hook** — never after a restore/kept load (R-694). A `success:` marker is required (exit 0 lies). Never log the expanded command | | `stacks.RunAfterInstall` / `expandAfterInstall` / `web.defaultLoginInEffect` (v0.279.0, decision 45) | controller/internal/stacks/after_install.go · controller/internal/web/known_login.go | `(name, wait)` / `(cmd, allowed, env)` / `(meta, cfg, installed)` | A fresh install replaces a known default login; the page says when a default is still in effect | **Only from the deploy-done hook** — never after a restore/kept load (R-694). A `success:` marker is required (exit 0 lies). Never log the expanded command |
| `stacks.OpenSetupGate` / `SetupGateTick` / `SetupGateHost` · `web.ServeGateAuth` / `ServeGateStart` (v0.280.0, decision 46) | controller/internal/stacks/setup_gate.go · controller/internal/web/setup_gate.go | `(name, by)` / `()` / `(host)` · handlers | The setup gate: a `setup_gate: true` install is closed to everyone but the household until its probe or the household's press opens it | **Write the gate BEFORE the first start** (spike F2). Open = record first, then remove the file. Never widen the dashboard cookie — the handshake mints a host-bound one-use token | | `stacks.OpenSetupGate` / `SetupGateTick` / `SetupGateHost` · `web.ServeGateAuth` / `ServeGateStart` (v0.280.0, decision 46) | controller/internal/stacks/setup_gate.go · controller/internal/web/setup_gate.go | `(name, by)` / `()` / `(host)` · handlers | The setup gate: a `setup_gate: true` install is closed to everyone but the household until its probe or the household's press opens it | **Write the gate BEFORE the first start** (spike F2). Open = record first, then remove the file. Never widen the dashboard cookie — the handshake mints a host-bound one-use token |
| `stacks.OpenSignupWindow` / `SignupBlocked` / `SetupGateProbe` · `web.ServeSignupClosed` (v0.281.0, decision 47) | controller/internal/stacks/signup_block.go · controller/internal/web/setup_gate.go | `(name)` | Sign-up closed at the app's own address once the gate opens; the household's 15-minute window; the press asks the probe | **The block goes up BEFORE the gate comes down** (a failed write keeps the gate closed). Never on an app this box did not gate | | `stacks.OpenSignupWindow` / `SignupBlocked` / `SetupGateProbe` · `web.ServeSignupClosed` (v0.281.0, decision 47) | controller/internal/stacks/signup_block.go · controller/internal/web/setup_gate.go | `(name)` | Sign-up closed at the app's own address once the gate opens; the household's 15-minute window; the press asks the probe | **The block goes up BEFORE the gate comes down** (a failed write keeps the gate closed). Never on an app this box did not gate |
| `family.Store` · `stacks.FamilyGateHost` / `familyGateTick` / `FamilyExceptRegexp` · `web.ServeFamilyGateAuth` / `ServeFamilyStart` / `ServeFamilyLogin` / `ServeFamilyLogout` (v0.287.0, decisions 63/64) | controller/internal/family/family.go · controller/internal/stacks/family_gate.go · controller/internal/web/family_gate.go | store `Add/Reset/Remove/Verify/NewSession/HouseholdSession/Valid/EndSession/Find/EndAll` + `family.Fingerprint` · `(host)` / `()` / `(prefix)` · handlers | The PERMANENT family gate: family members with their own logins in front of a `family_gate: true` app | **A family cookie never opens the dashboard** (RequireAuth reads only `felhom_session`); the app cookie carries a token DERIVED per app host from a STORE session (`familyAppToken`, R-976 — never the session id itself), so the store is asked on every request (reset/remove/logout end access at once); **every exception goes through `FamilyExceptRegexp`** (anchored — never a hand-written PathPrefix); door written before the first start, like the setup gate. `familyStoreOverride` is the test seam. Fifth atomic-write helper (family.json, fsync) — see §6. **R-1112: a session is stored as its FINGERPRINT (`Session.FP`, the R-35 shape), never the cookie value** — `NewSession` returns the value once; `Valid`/`EndSession` take the value and hash it; `Find` and `familyAppToken` work on the fingerprint. An old file (`"id"` = the value) is converted and rewritten in `Open`; a failed rewrite is an error. **R-1113: the household path uses `HouseholdSession`** (its live session, a new one only past half its life) — never `NewSession("")` per pass | | `family.Store` · `stacks.FamilyGateHost` / `familyGateTick` / `FamilyExceptRegexp` · `web.ServeFamilyGateAuth` / `ServeFamilyStart` / `ServeFamilyLogin` / `ServeFamilyLogout` (v0.287.0, decisions 63/64) | controller/internal/family/family.go · controller/internal/stacks/family_gate.go · controller/internal/web/family_gate.go | store `Add/AddPerson/People/Reset/Remove/Verify/NewSession/HouseholdSession/Valid/EndSession/Find/EndAll` + `family.Fingerprint` + `family.LoginFrom` · `(host)` / `()` / `(prefix)` · handlers | The PERMANENT family gate: family members with their own logins in front of a `family_gate: true` app | **A family cookie never opens the dashboard** (RequireAuth reads only `felhom_session`); the app cookie carries a token DERIVED per app host from a STORE session (`familyAppToken`, R-976 — never the session id itself), so the store is asked on every request (reset/remove/logout end access at once); **every exception goes through `FamilyExceptRegexp`** (anchored — never a hand-written PathPrefix); door written before the first start, like the setup gate. `familyStoreOverride` is the test seam. Fifth atomic-write helper (family.json, fsync) — see §6. **R-1112: a session is stored as its FINGERPRINT (`Session.FP`, the R-35 shape), never the cookie value** — `NewSession` returns the value once; `Valid`/`EndSession` take the value and hash it; `Find` and `familyAppToken` work on the fingerprint. An old file (`"id"` = the value) is converted and rewritten in `Open`; a failed rewrite is an error. **R-1113: the household path uses `HouseholdSession`** (its live session, a new one only past half its life) — never `NewSession("")` per pass. **W12: a typed name goes through `AddPerson` (keeps the display, makes the login with `LoginFrom`, numbers a taken login); `Add` stays the strict login-only primitive. Any place that turns a typed member name into a login calls `LoginFrom` — never a second fold table** |
| `stacks.OpenInstallHold` / `installHoldTick` (v0.284.0, R-741) | controller/internal/stacks/install_hold.go | `(name, by)` / `()` | An `after_install` app held behind the setup gate's door until its known login is replaced | **Written before the first start**, like the gate; opens on `after_install` success or the household's "I changed it"; the door (`SetupGateHost`) reads holds first | | `stacks.OpenInstallHold` / `installHoldTick` (v0.284.0, R-741) | controller/internal/stacks/install_hold.go | `(name, by)` / `()` | An `after_install` app held behind the setup gate's door until its known login is replaced | **Written before the first start**, like the gate; opens on `after_install` success or the household's "I changed it"; the door (`SetupGateHost`) reads holds first |
| `stacks.RetainImagesAfterUpdate` / `RetainImagesAfterRemove` / `RunImageRetentionOnce` (→ `(deleted, done)` since v0.294.0) / `RunImageRetentionOnceUntilDone` · seam `imageDocker` (v0.284.0, decision 53) | controller/internal/stacks/image_retention.go | `(name, previous)` / `(name, repos)` / `()` | Deletes an app's images older than its running + previous one | **The keep set is box-wide and read at delete time** (containers, installed composes, installed/previous records); exact id, never forced or pruned; skipped while any update runs AND while any image-pulling compose command runs (R-863, `dockerexec.TryImageCleanup`); the one-time pass writes its marker only after a pass that RAN; tests use the `imageDocker` seam, never Docker | | `stacks.RetainImagesAfterUpdate` / `RetainImagesAfterRemove` / `RunImageRetentionOnce` (→ `(deleted, done)` since v0.294.0) / `RunImageRetentionOnceUntilDone` · seam `imageDocker` (v0.284.0, decision 53) | controller/internal/stacks/image_retention.go | `(name, previous)` / `(name, repos)` / `()` | Deletes an app's images older than its running + previous one | **The keep set is box-wide and read at delete time** (containers, installed composes, installed/previous records); exact id, never forced or pruned; skipped while any update runs AND while any image-pulling compose command runs (R-863, `dockerexec.TryImageCleanup`); the one-time pass writes its marker only after a pass that RAN; tests use the `imageDocker` seam, never Docker |
| `dockerexec.BeginImageWork(args)` / `TryImageCleanup()` / `ImagePulling(args)` (v0.294.0, R-863) | controller/internal/dockerexec/imagework.go | `defer dockerexec.BeginImageWork(args)()` | **Every NEW place that runs `compose up/pull/create/run` must hold it** (the stacks compose helpers, appexport's restore and the FileBrowser sync already do) | An image pulled by compose is named by no container until compose creates one; a clean-up pass in that window deleted it (measured, R-863). The pass takes the lock exclusively without waiting; pulling commands wait for a running pass (seconds) | | `dockerexec.BeginImageWork(args)` / `TryImageCleanup()` / `ImagePulling(args)` (v0.294.0, R-863) | controller/internal/dockerexec/imagework.go | `defer dockerexec.BeginImageWork(args)()` | **Every NEW place that runs `compose up/pull/create/run` must hold it** (the stacks compose helpers, appexport's restore and the FileBrowser sync already do) | An image pulled by compose is named by no container until compose creates one; a clean-up pass in that window deleted it (measured, R-863). The pass takes the lock exclusively without waiting; pulling commands wait for a running pass (seconds) |
+2
View File
@@ -2190,6 +2190,8 @@ that folder is never a dead end, and an install never runs into it silently (R-6
`family.json`, stored as their FINGERPRINT (sha256 of the cookie value, the dashboard's R-35 shape — v0.325.0, R-1112; `family.json`, stored as their FINGERPRINT (sha256 of the cookie value, the dashboard's R-35 shape — v0.325.0, R-1112;
an older file is converted at load and rewritten). The household's pass reuses its one live family session (a new one an older file is converted at load and rewritten). The household's pass reuses its one live family session (a new one
only when less than half of its life is left — R-1113). A family cookie never opens the dashboard. `min_controller:` refuses a template too new for the box. only when less than half of its life is left — R-1113). A family cookie never opens the dashboard. `min_controller:` refuses a template too new for the box.
A member's name is kept as the household typed it („Ödön"); the LOGIN is made from it — accents removed, lower case
(`family.LoginFrom`: `odon`), the next number when taken (`odon2`) — and the card shows both (W12, unreleased).
Code: `internal/family/`, `internal/stacks/family_gate.go`, `internal/web/family_gate.go`. Code: `internal/family/`, `internal/stacks/family_gate.go`, `internal/web/family_gate.go`.
- **A removed app restored from its backup gets its lock back (v0.286.0, R-773)** — with no app.yaml left, the restore - **A removed app restored from its backup gets its lock back (v0.286.0, R-773)** — with no app.yaml left, the restore
(`PersistUnitRedeployConfig`) writes the lock record (`opened_by: restore`) and the block BEFORE anything starts; the (`PersistUnitRedeployConfig`) writes the lock record (`opened_by: restore`) and the block BEFORE anything starts; the
+139 -1
View File
@@ -28,8 +28,12 @@ import (
"path/filepath" "path/filepath"
"regexp" "regexp"
"sort" "sort"
"strconv"
"strings"
"sync" "sync"
"time" "time"
"unicode"
"unicode/utf8"
"golang.org/x/crypto/bcrypt" "golang.org/x/crypto/bcrypt"
) )
@@ -39,7 +43,8 @@ const SessionLife = 30 * 24 * time.Hour
// Member is one family member. Gen moves on at every password reset, ending the member's earlier sessions. // Member is one family member. Gen moves on at every password reset, ending the member's earlier sessions.
type Member struct { type Member struct {
Name string `json:"name"` Name string `json:"name"` // the LOGIN: 1-32 of [a-z0-9._-] (nameRE); the key everywhere
Display string `json:"display,omitempty"` // the name as the household typed it (W12); "" for a member made before
Hash string `json:"hash"` Hash string `json:"hash"`
Gen int `json:"gen"` Gen int `json:"gen"`
Created string `json:"created"` Created string `json:"created"`
@@ -231,6 +236,139 @@ func (s *Store) Add(name string) (string, error) {
return pw, nil return pw, nil
} }
// MaxDisplayRunes is the longest name the household may type for a family member (W12).
const MaxDisplayRunes = 64
// Person is one member as the dashboard's card shows them: the name the household typed and the login made from it.
type Person struct {
Login string `json:"login"`
Display string `json:"display"`
}
// AddPerson makes a member from the name the household typed (W12, 2026-10-21): the display name is kept exactly as
// typed; the LOGIN is made from it by LoginFrom (accents removed, lower case) and must still pass nameRE — the login's
// own check is unchanged. When the login is taken by someone with another name, the next free numbered login is used
// („Ödön" → odon, then „Odon" → odon2). The same name again (letter case aside) is ErrExists, never a second login.
// Returns the login and the generated password (once). Pinned by TestW12_AccentedNamesGetAPlainLogin.
func (s *Store) AddPerson(display string) (login, pw string, err error) {
display = strings.TrimSpace(display)
base := LoginFrom(display)
if display == "" || utf8.RuneCountInString(display) > MaxDisplayRunes || !nameRE.MatchString(base) {
return "", "", ErrBadName
}
for _, r := range display {
if unicode.IsControl(r) {
return "", "", ErrBadName
}
}
pw = newPassword()
h, err := bcrypt.GenerateFromPassword([]byte(pw), s.cost)
if err != nil {
return "", "", err
}
s.mu.Lock()
defer s.mu.Unlock()
for _, m := range s.members {
if strings.EqualFold(m.displayName(), display) {
return "", "", ErrExists
}
}
login = base
for n := 2; s.members[login] != nil; n++ {
suffix := strconv.Itoa(n)
stem := base
if len(stem)+len(suffix) > 32 {
stem = stem[:32-len(suffix)]
}
login = stem + suffix
}
if !nameRE.MatchString(login) {
return "", "", ErrBadName
}
stored := display
if stored == login {
stored = "" // nothing to remember: the login IS the name
}
s.members[login] = &Member{Name: login, Display: stored, Hash: string(h), Gen: 1, Created: s.now().UTC().Format(time.RFC3339)}
if err := s.saveLocked(); err != nil {
delete(s.members, login)
return "", "", err
}
return login, pw, nil
}
func (m *Member) displayName() string {
if m.Display != "" {
return m.Display
}
return m.Name
}
// foldMap removes the accents of the letters a Hungarian (and a European) household types; sharp s becomes "ss".
// The letters are written as escapes: several of them are the mojibake gate's cp1250 signatures.
var foldMap = func() map[rune]string {
out := map[rune]string{'\u00df': "ss", '\u00e6': "ae", '\u0153': "oe", '\u00f8': "o", '\u0142': "l", '\u0111': "d", '\u00f0': "d", '\u00fe': "th"}
for base, letters := range map[string]string{
"a": "\u00e1\u00e0\u00e2\u00e4\u00e3\u00e5\u0101\u0103\u0105", "c": "\u00e7\u0107\u010d\u0109\u010b", "d": "\u010f", "e": "\u00e9\u00e8\u00ea\u00eb\u0113\u0117\u0119\u011b\u0115", "g": "\u011f\u011d\u0121\u0123", "h": "\u0125\u0127", "i": "\u00ed\u00ec\u00ee\u00ef\u012b\u012f\u0129\u0131",
"j": "\u0135", "k": "\u0137", "l": "\u013a\u013c\u013e\u0140", "n": "\u00f1\u0144\u0148\u0146", "o": "\u00f3\u00f2\u00f4\u00f6\u00f5\u0151\u014d\u014f", "r": "\u0155\u0159\u0157", "s": "\u015b\u0161\u015f\u015d\u0219", "t": "\u0165\u0163\u021b",
"u": "\u00fa\u00f9\u00fb\u00fc\u0171\u016b\u016f\u0173\u0169\u016d", "y": "\u00fd\u00ff\u0177", "z": "\u017a\u017c\u017e",
} {
for _, r := range letters {
out[r] = base
}
}
return out
}()
// LoginFrom makes a login from a typed name (W12): lower case, accents removed („Ödön" → odon, „Írisz" → irisz),
// a run of spaces becomes one dot („Kis Anna" → kis.anna), and every other sign outside a-z 0-9 . _ - is left out.
// Leading signs are dropped, and the result is cut to 32 characters. The caller still checks it with nameRE.
func LoginFrom(display string) string {
var b strings.Builder
space := false
for _, r := range strings.ToLower(strings.TrimSpace(display)) {
if unicode.IsSpace(r) {
space = true
continue
}
var part string
switch {
case r >= 'a' && r <= 'z', r >= '0' && r <= '9', r == '.', r == '_', r == '-':
part = string(r)
default:
part = foldMap[r]
}
if part == "" {
continue
}
if space && b.Len() > 0 {
b.WriteByte('.')
}
space = false
b.WriteString(part)
}
out := strings.TrimLeft(b.String(), "._-")
if len(out) > 32 {
out = out[:32]
}
return out
}
// People lists the members with their typed names, sorted by login.
func (s *Store) People() []Person {
if s == nil {
return nil
}
s.mu.Lock()
defer s.mu.Unlock()
out := make([]Person, 0, len(s.members))
for _, m := range s.members {
out = append(out, Person{Login: m.Name, Display: m.displayName()})
}
sort.Slice(out, func(i, j int) bool { return out[i].Login < out[j].Login })
return out
}
// Reset gives a member a new generated password (returned once) and ends every earlier session of theirs. // Reset gives a member a new generated password (returned once) and ends every earlier session of theirs.
func (s *Store) Reset(name string) (string, error) { func (s *Store) Reset(name string) (string, error) {
pw := newPassword() pw := newPassword()
+76
View File
@@ -0,0 +1,76 @@
package family
import (
"strings"
"testing"
)
// W12 (rehearsal 2026-10-20): a Hungarian family's own names („Ödön", „Írisz") were refused. The name is now kept as
// typed and the LOGIN is made from it — accents removed, lower case — and still passes the login's own check.
func TestW12_AccentedNamesGetAPlainLogin(t *testing.T) {
for in, want := range map[string]string{
"Ödön": "odon", "Írisz": "irisz", "ŐRSÉG": "orseg", "Kis Anna": "kis.anna", "Anna-Mária": "anna-maria",
"anna": "anna", "odon2": "odon2", "O'Brien": "obrien", " Ünő ": "uno", "Анна": "", "!!!": "", ".x": "x",
} {
if got := LoginFrom(in); got != want {
t.Errorf("LoginFrom(%q) = %q, want %q", in, got, want)
}
}
if got := LoginFrom(strings.Repeat("é", 40)); got != strings.Repeat("e", 32) {
t.Errorf("a long name is not cut to 32: %q", got)
}
s, dir := testStore(t)
login, pw, err := s.AddPerson("Ödön")
if err != nil || login != "odon" || !s.Verify("odon", pw) {
t.Fatalf("Ödön: %q %v", login, err)
}
if login, _, err := s.AddPerson("Írisz"); err != nil || login != "irisz" {
t.Fatalf("Írisz: %q %v", login, err)
}
// a second person whose name makes the same login gets the next number
login2, pw2, err := s.AddPerson("Odon")
if err != nil || login2 != "odon2" || !s.Verify("odon2", pw2) || s.Verify("odon", pw2) {
t.Fatalf("the second odon: %q %v", login2, err)
}
// the same name again is the same person: refused, never a third login
if _, _, err := s.AddPerson("ödön"); err != ErrExists {
t.Fatalf("the same name twice: %v", err)
}
for _, bad := range []string{"", " ", "Анна", "!!!", strings.Repeat("a", MaxDisplayRunes+1), "an\x00na"} {
if _, _, err := s.AddPerson(bad); err != ErrBadName {
t.Fatalf("name %q accepted: %v", bad, err)
}
}
// the display name is kept exactly as typed, and survives a reopen
s2, err := Open(dir)
if err != nil {
t.Fatal(err)
}
got := map[string]string{}
for _, p := range s2.People() {
got[p.Login] = p.Display
}
if got["odon"] != "Ödön" || got["irisz"] != "Írisz" || got["odon2"] != "Odon" || len(got) != 3 {
t.Fatalf("people: %v", got)
}
// the login's own check is unchanged
for _, bad := range []string{"Ödön", "a b", "Anna"} {
if _, err := s.Add(bad); err != ErrBadName {
t.Fatalf("Add(%q) accepted a login outside the safe set", bad)
}
}
}
// A long name whose login is taken: the numbered login still fits 32 characters.
func TestW12_NumberedLoginStaysInsideTheLimit(t *testing.T) {
s, _ := testStore(t)
long := strings.Repeat("a", 32)
if l, _, err := s.AddPerson(long); err != nil || l != long {
t.Fatalf("%q %v", l, err)
}
l, _, err := s.AddPerson(strings.Repeat("á", 32))
if err != nil || l != strings.Repeat("a", 31)+"2" {
t.Fatalf("%q %v", l, err)
}
}
+18 -11
View File
@@ -350,13 +350,15 @@
"backups_apps.visszaallitas_a_mentesbol": "Restore from the backup", "backups_apps.visszaallitas_a_mentesbol": "Restore from the backup",
"backups_apps.visszaallitja_a_hianyzo_fajlokat_a": "Restore the missing files from the secondary copy? Existing files are NOT changed and NOT deleted. The app stops during the operation. Last copy: {{fmtTimeStr .Tier2LastRun}}", "backups_apps.visszaallitja_a_hianyzo_fajlokat_a": "Restore the missing files from the secondary copy? Existing files are NOT changed and NOT deleted. The app stops during the operation. Last copy: {{fmtTimeStr .Tier2LastRun}}",
"backups_apps.visszaallitja_legutobbi_sikeres_masolat": "Restore the missing files from the secondary copy? Existing files are NOT changed and NOT deleted. The app stops during the operation. Last successful copy: {{fmtTimeStr .Tier2LastSuccess}}.", "backups_apps.visszaallitja_legutobbi_sikeres_masolat": "Restore the missing files from the secondary copy? Existing files are NOT changed and NOT deleted. The app stops during the operation. Last successful copy: {{fmtTimeStr .Tier2LastSuccess}}.",
"backups_escrow.1_elofeltetelek_ellenorzese": "1. Checking the requirements", "backups_escrow.1_elofeltetelek_ellenorzese": "Checking the requirements",
"backups_escrow.2_fontos_tudnivalok": "2. Important to know", "backups_escrow.all_prereqs_ok": "All prerequisites are in place.",
"backups_escrow.3_megerosites": "3. Confirmation", "backups_escrow.prereq_details": "Details",
"backups_escrow.4_letrehozas": "4. Creation", "backups_escrow.2_fontos_tudnivalok": "Important to know",
"backups_escrow.5_a_helyreallitasi_kod": "5. The recovery code", "backups_escrow.3_megerosites": "Confirmation",
"backups_escrow.6_ellenorzes": "6. Check", "backups_escrow.4_letrehozas": "Creation",
"backups_escrow.7_befejezes": "7. Finish", "backups_escrow.5_a_helyreallitasi_kod": "The recovery code",
"backups_escrow.6_ellenorzes": "Check",
"backups_escrow.7_befejezes": "Finish",
"backups_escrow.a_dr_szint_nincs_alkalmazva": "the DR level is not applied on this machine", "backups_escrow.a_dr_szint_nincs_alkalmazva": "the DR level is not applied on this machine",
"backups_escrow.a_folyamat_a_piros_feltetelek": "The process cannot start until the red requirements are met.", "backups_escrow.a_folyamat_a_piros_feltetelek": "The process cannot start until the red requirements are met.",
"backups_escrow.a_folyamat_inditasa_nem_sikerult": "Starting the process failed.", "backups_escrow.a_folyamat_inditasa_nem_sikerult": "Starting the process failed.",
@@ -412,7 +414,7 @@
"backups_remote.a_funkciohoz_az_ugynok_frissitese": "This needs an agent update — the update arrives automatically.", "backups_remote.a_funkciohoz_az_ugynok_frissitese": "This needs an agent update — the update arrives automatically.",
"backups_remote.a_helyreallitasi_csomag_elkuldve_a": "The recovery package was sent to the central system and is waiting for confirmation — usually a few seconds, at most 15 minutes.", "backups_remote.a_helyreallitasi_csomag_elkuldve_a": "The recovery package was sent to the central system and is waiting for confirmation — usually a few seconds, at most 15 minutes.",
"backups_remote.a_helyreallitasi_kod_letetbe_helyezve": "The recovery code is in escrow.", "backups_remote.a_helyreallitasi_kod_letetbe_helyezve": "The recovery code is in escrow.",
"backups_remote.a_host_kulcs_rogzitese_no": "Pins the host key (no blind TOFU). To look it up:", "backups_remote.a_host_kulcs_rogzitese_no": "This is the key of the target machine. With it, the box connects only to this machine. To look it up:",
"backups_remote.a_keresed_szerint_a_korabbi": "As you asked, we will permanently delete your earlier remote backups on <strong>{{.AbandonDate}}</strong>\n (<strong>{{.AbandonDaysLeft}} {{if eq .AbandonDaysLeft 1}}day{{else}}days{{end}}</strong> left).", "backups_remote.a_keresed_szerint_a_korabbi": "As you asked, we will permanently delete your earlier remote backups on <strong>{{.AbandonDate}}</strong>\n (<strong>{{.AbandonDaysLeft}} {{if eq .AbandonDaysLeft 1}}day{{else}}days{{end}}</strong> left).",
"backups_remote.a_korabbi_hazon_kivuli_menteseid": "<strong>Your earlier off-site backups can be retrieved.</strong>\n This machine was reinstalled, and the Felhom central system keeps a sealed package for it. With your\n recovery code you can unlock your earlier backups and see what is in them.", "backups_remote.a_korabbi_hazon_kivuli_menteseid": "<strong>Your earlier off-site backups can be retrieved.</strong>\n This machine was reinstalled, and the Felhom central system keeps a sealed package for it. With your\n recovery code you can unlock your earlier backups and see what is in them.",
"backups_remote.a_korabbi_menteseid_felre_vannak": "Your earlier backups are set aside — we did not delete them.", "backups_remote.a_korabbi_menteseid_felre_vannak": "Your earlier backups are set aside — we did not delete them.",
@@ -435,7 +437,10 @@
"backups_remote.addig_meggondolhatod_magad_ha_megvan": "Until then you can change your mind: if you have your recovery code,\n your backups can be retrieved, and the deletion is cancelled.", "backups_remote.addig_meggondolhatod_magad_ha_megvan": "Until then you can change your mind: if you have your recovery code,\n your backups can be retrieved, and the deletion is cancelled.",
"backups_remote.aktiv_nincs_kijelolt_alkalmazas": "Active — no app selected", "backups_remote.aktiv_nincs_kijelolt_alkalmazas": "Active — no app selected",
"backups_remote.amikor_uj_mentesi_kulcsot_kapott": "When your machine got a new backup key, we moved the older history\n on the remote storage, and it stayed there. <strong>It cannot be opened from here for now</strong>, and\n that does not depend on your code. If you need it, contact Felhom support.", "backups_remote.amikor_uj_mentesi_kulcsot_kapott": "When your machine got a new backup key, we moved the older history\n on the remote storage, and it stayed there. <strong>It cannot be opened from here for now</strong>, and\n that does not depend on your code. If you need it, contact Felhom support.",
"backups_remote.az_alkalmazas_mentesek_titkositott_masol": "An encrypted copy of the app backups on a remote store — your own NAS or Felhom offsite storage — over restic + SFTP. The store sees only encrypted data. This is the „1 off-site\" leg of the 3-2-1 rule — independent of the local second copy and the full system backup.", "backups_remote.az_alkalmazas_mentesek_titkositott_masol": "An encrypted copy of your app backups goes to a remote place: the Felhom storage or your own NAS. Only encrypted data is there. This copy is independent of the local second copy and of the full system backup.",
"backups_remote.felhom_store_name": "Felhom remote storage (Germany)",
"backups_remote.felhom_store_where": "The encrypted copy goes here.",
"backups_remote.halado": "Advanced",
"backups_remote.biztonsagi_mentes_tavoli_mentes": "Backup — Remote backup", "backups_remote.biztonsagi_mentes_tavoli_mentes": "Backup — Remote backup",
"backups_remote.cel_cime_ip_vagy_hosztnev": "Target address (IP or hostname — NAS or SFTP server)", "backups_remote.cel_cime_ip_vagy_hosztnev": "Target address (IP or hostname — NAS or SFTP server)",
"backups_remote.ehhez_a_gephez_egy_korabbi": "This machine <strong>has an earlier recovery code</strong>, and your earlier backups still exist.\n Creating a new code <strong>would make your old backups unreachable</strong>, so it cannot start now.\n First <a href=\"/recovery\">enter your existing code</a> — or tell us there if you do not want the earlier data back.", "backups_remote.ehhez_a_gephez_egy_korabbi": "This machine <strong>has an earlier recovery code</strong>, and your earlier backups still exist.\n Creating a new code <strong>would make your old backups unreachable</strong>, so it cannot start now.\n First <a href=\"/recovery\">enter your existing code</a> — or tell us there if you do not want the earlier data back.",
@@ -2729,13 +2734,15 @@
"family_gate.msg.signed_in": "You are signed in. Open the app again.", "family_gate.msg.signed_in": "You are signed in. Open the app again.",
"family_gate.msg.signed_out": "You are signed out. The family apps ask you to sign in again.", "family_gate.msg.signed_out": "You are signed out. The family apps ask you to sign in again.",
"family_gate.msg.store_unreadable": "The family list cannot be read right now.", "family_gate.msg.store_unreadable": "The family list cannot be read right now.",
"family_gate.msg.bad_name": "A name is 1–32 characters: lower-case letters, digits, dot, dash or underscore.", "family_gate.msg.bad_name": "We cannot make a login from this name. Type a name of at most 64 letters in the Latin alphabet. Accents are fine.",
"family_gate.msg.exists": "A family member with that name already exists.", "family_gate.msg.exists": "A family member with that name already exists.",
"family_gate.msg.no_member": "No family member with that name.", "family_gate.msg.no_member": "No family member with that name.",
"family_gate.card_title": "Family", "family_gate.card_title": "Family",
"family_gate.card_desc": "Family members sign in to the family-gated apps with their own name and password. It does not open this dashboard.", "family_gate.card_desc": "Family members sign in to the family-gated apps with their own name and password. It does not open this dashboard.",
"family_gate.add": "Add a family member", "family_gate.add": "Add a family member",
"family_gate.name_placeholder": "e.g. anna", "family_gate.name_placeholder": "e.g. Anna",
"family_gate.name_hint": "Type the name as you like it, accents too. The login is made from it: no accents, lower case. You see it in the list.",
"family_gate.login_label": "login:",
"family_gate.reset": "New password", "family_gate.reset": "New password",
"family_gate.remove": "Remove", "family_gate.remove": "Remove",
"family_gate.none": "No family members yet.", "family_gate.none": "No family members yet.",
+18 -11
View File
@@ -346,13 +346,15 @@
"backups_apps.visszaallitas_a_mentesbol": "Visszaállítás a mentésből", "backups_apps.visszaallitas_a_mentesbol": "Visszaállítás a mentésből",
"backups_apps.visszaallitja_a_hianyzo_fajlokat_a": "Visszaállítod a hiányzó fájlokat a másodlagos másolatból? A meglévő fájlok NEM módosulnak és NEM törlődnek. Az alkalmazás a művelet idejére leáll. Legutóbbi másolat: {{fmtTimeStr .Tier2LastRun}}", "backups_apps.visszaallitja_a_hianyzo_fajlokat_a": "Visszaállítod a hiányzó fájlokat a másodlagos másolatból? A meglévő fájlok NEM módosulnak és NEM törlődnek. Az alkalmazás a művelet idejére leáll. Legutóbbi másolat: {{fmtTimeStr .Tier2LastRun}}",
"backups_apps.visszaallitja_legutobbi_sikeres_masolat": "Visszaállítod a hiányzó fájlokat a másodlagos másolatból? A meglévő fájlok NEM módosulnak és NEM törlődnek. Az alkalmazás a művelet idejére leáll. Legutóbbi sikeres másolat: {{fmtTimeStr .Tier2LastSuccess}}.", "backups_apps.visszaallitja_legutobbi_sikeres_masolat": "Visszaállítod a hiányzó fájlokat a másodlagos másolatból? A meglévő fájlok NEM módosulnak és NEM törlődnek. Az alkalmazás a művelet idejére leáll. Legutóbbi sikeres másolat: {{fmtTimeStr .Tier2LastSuccess}}.",
"backups_escrow.1_elofeltetelek_ellenorzese": "1. Előfeltételek ellenőrzése", "backups_escrow.1_elofeltetelek_ellenorzese": "Előfeltételek ellenőrzése",
"backups_escrow.2_fontos_tudnivalok": "2. Fontos tudnivalók", "backups_escrow.all_prereqs_ok": "Minden előfeltétel rendben.",
"backups_escrow.3_megerosites": "3. Megerősítés", "backups_escrow.prereq_details": "Részletek",
"backups_escrow.4_letrehozas": "4. Létrehozás", "backups_escrow.2_fontos_tudnivalok": "Fontos tudnivalók",
"backups_escrow.5_a_helyreallitasi_kod": "5. A helyreállítási kód", "backups_escrow.3_megerosites": "Megerősítés",
"backups_escrow.6_ellenorzes": "6. Ellenőrzés", "backups_escrow.4_letrehozas": "Létrehozás",
"backups_escrow.7_befejezes": "7. Befejezés", "backups_escrow.5_a_helyreallitasi_kod": "A helyreállítási kód",
"backups_escrow.6_ellenorzes": "Ellenőrzés",
"backups_escrow.7_befejezes": "Befejezés",
"backups_escrow.a_dr_szint_nincs_alkalmazva": "a DR-szint nincs alkalmazva ezen a gépen", "backups_escrow.a_dr_szint_nincs_alkalmazva": "a DR-szint nincs alkalmazva ezen a gépen",
"backups_escrow.a_folyamat_a_piros_feltetelek": "A folyamat a piros feltételek teljesüléséig nem indítható.", "backups_escrow.a_folyamat_a_piros_feltetelek": "A folyamat a piros feltételek teljesüléséig nem indítható.",
"backups_escrow.a_folyamat_inditasa_nem_sikerult": "A folyamat indítása nem sikerült.", "backups_escrow.a_folyamat_inditasa_nem_sikerult": "A folyamat indítása nem sikerült.",
@@ -408,7 +410,7 @@
"backups_remote.a_funkciohoz_az_ugynok_frissitese": "A funkcióhoz az ügynök frissítése szükséges — a frissítés automatikusan megérkezik.", "backups_remote.a_funkciohoz_az_ugynok_frissitese": "A funkcióhoz az ügynök frissítése szükséges — a frissítés automatikusan megérkezik.",
"backups_remote.a_helyreallitasi_csomag_elkuldve_a": "A helyreállítási csomag elküldve a központba, megerősítésre vár — ez általában néhány másodperc, legfeljebb 15 perc.", "backups_remote.a_helyreallitasi_csomag_elkuldve_a": "A helyreállítási csomag elküldve a központba, megerősítésre vár — ez általában néhány másodperc, legfeljebb 15 perc.",
"backups_remote.a_helyreallitasi_kod_letetbe_helyezve": "A helyreállítási kód letétbe helyezve.", "backups_remote.a_helyreallitasi_kod_letetbe_helyezve": "A helyreállítási kód letétbe helyezve.",
"backups_remote.a_host_kulcs_rogzitese_no": "A host-kulcs rögzítése (no blind TOFU). Lekérdezhető:", "backups_remote.a_host_kulcs_rogzitese_no": "A célgép saját kulcsa. Ezzel a doboz csak ehhez a géphez kapcsolódik. Így kérdezheted le:",
"backups_remote.a_keresed_szerint_a_korabbi": "A kérésed szerint a korábbi távoli mentéseidet <strong>{{.AbandonDate}}</strong> napján véglegesen töröljük\n (még <strong>{{.AbandonDaysLeft}} nap</strong>).", "backups_remote.a_keresed_szerint_a_korabbi": "A kérésed szerint a korábbi távoli mentéseidet <strong>{{.AbandonDate}}</strong> napján véglegesen töröljük\n (még <strong>{{.AbandonDaysLeft}} nap</strong>).",
"backups_remote.a_korabbi_hazon_kivuli_menteseid": "<strong>A korábbi, házon kívüli mentéseid visszaszerezhetők.</strong>\n Ezt a gépet újratelepítették, és a Felhom központi rendszere őriz hozzá egy lezárt csomagot. A\n helyreállítási kódoddal feloldhatod a korábbi mentéseidet, és megnézheted, mi van bennük.", "backups_remote.a_korabbi_hazon_kivuli_menteseid": "<strong>A korábbi, házon kívüli mentéseid visszaszerezhetők.</strong>\n Ezt a gépet újratelepítették, és a Felhom központi rendszere őriz hozzá egy lezárt csomagot. A\n helyreállítási kódoddal feloldhatod a korábbi mentéseidet, és megnézheted, mi van bennük.",
"backups_remote.a_korabbi_menteseid_felre_vannak": "A korábbi mentéseid félre vannak téve — nem töröltük őket.", "backups_remote.a_korabbi_menteseid_felre_vannak": "A korábbi mentéseid félre vannak téve — nem töröltük őket.",
@@ -431,7 +433,10 @@
"backups_remote.addig_meggondolhatod_magad_ha_megvan": "Addig meggondolhatod magad: ha megvan a helyreállítási kódod,\n a mentéseid visszaszerezhetők, és a törlés elmarad.", "backups_remote.addig_meggondolhatod_magad_ha_megvan": "Addig meggondolhatod magad: ha megvan a helyreállítási kódod,\n a mentéseid visszaszerezhetők, és a törlés elmarad.",
"backups_remote.aktiv_nincs_kijelolt_alkalmazas": "Aktív — nincs kijelölt alkalmazás", "backups_remote.aktiv_nincs_kijelolt_alkalmazas": "Aktív — nincs kijelölt alkalmazás",
"backups_remote.amikor_uj_mentesi_kulcsot_kapott": "Amikor új mentési kulcsot kapott a géped, a régebbi előzményt átmozgattuk\n a távoli tárhelyen, és ott is maradt. <strong>Megnyitni innen egyelőre nem lehet</strong>, és\n ez nem a kódodon múlik. Ha szükséged van rá, keresd a Felhom ügyfélszolgálatát.", "backups_remote.amikor_uj_mentesi_kulcsot_kapott": "Amikor új mentési kulcsot kapott a géped, a régebbi előzményt átmozgattuk\n a távoli tárhelyen, és ott is maradt. <strong>Megnyitni innen egyelőre nem lehet</strong>, és\n ez nem a kódodon múlik. Ha szükséged van rá, keresd a Felhom ügyfélszolgálatát.",
"backups_remote.az_alkalmazas_mentesek_titkositott_masol": "Az alkalmazás-mentések titkosított másolata egy távoli tárolóra — saját NAS vagy Felhom offsite tárhely — restic + SFTP kapcsolaton. A tároló csak titkosított adatot lát. Ez a 3-2-1 szabály „1 off-site\" lába — független a helyi másodpéldánytól és a teljes rendszermentéstől.", "backups_remote.az_alkalmazas_mentesek_titkositott_masol": "Az alkalmazásaid mentéseiről titkosított másolat készül egy távoli helyre: a Felhom tárhelyére vagy a saját NAS-odra. Ott csak titkosított adat van. Ez a másolat független a helyi másodpéldánytól és a teljes rendszermentéstől.",
"backups_remote.felhom_store_name": "Felhom távoli tárhely (Németország)",
"backups_remote.felhom_store_where": "Ide kerül a titkosított másolat.",
"backups_remote.halado": "Haladó",
"backups_remote.biztonsagi_mentes_tavoli_mentes": "Biztonsági mentés — Távoli mentés", "backups_remote.biztonsagi_mentes_tavoli_mentes": "Biztonsági mentés — Távoli mentés",
"backups_remote.cel_cime_ip_vagy_hosztnev": "Cél címe (IP vagy hosztnév — NAS vagy SFTP-kiszolgáló)", "backups_remote.cel_cime_ip_vagy_hosztnev": "Cél címe (IP vagy hosztnév — NAS vagy SFTP-kiszolgáló)",
"backups_remote.ehhez_a_gephez_egy_korabbi": "Ehhez a géphez <strong>egy korábbi helyreállítási kód tartozik</strong>, és a korábbi mentéseid még megvannak.\n Új kód létrehozása <strong>a régi mentéseidet elérhetetlenné tenné</strong>, ezért most nem indítható.\n Előbb <a href=\"/recovery\">add meg a meglévő kódodat</a> — vagy ott jelezheted, ha nem kéred vissza a korábbi adatokat.", "backups_remote.ehhez_a_gephez_egy_korabbi": "Ehhez a géphez <strong>egy korábbi helyreállítási kód tartozik</strong>, és a korábbi mentéseid még megvannak.\n Új kód létrehozása <strong>a régi mentéseidet elérhetetlenné tenné</strong>, ezért most nem indítható.\n Előbb <a href=\"/recovery\">add meg a meglévő kódodat</a> — vagy ott jelezheted, ha nem kéred vissza a korábbi adatokat.",
@@ -2717,13 +2722,15 @@
"family_gate.msg.signed_in": "Beléptél. Nyisd meg újra az alkalmazást.", "family_gate.msg.signed_in": "Beléptél. Nyisd meg újra az alkalmazást.",
"family_gate.msg.signed_out": "Kiléptél. A családi alkalmazások újra belépést kérnek.", "family_gate.msg.signed_out": "Kiléptél. A családi alkalmazások újra belépést kérnek.",
"family_gate.msg.store_unreadable": "A családi lista most nem olvasható.", "family_gate.msg.store_unreadable": "A családi lista most nem olvasható.",
"family_gate.msg.bad_name": "A név 1–32 karakter: kisbetű, szám, pont, kötőjel vagy aláhúzás.", "family_gate.msg.bad_name": "Ebből a névből nem tudunk belépési nevet készíteni. Írj be egy legfeljebb 64 betűs nevet latin betűkkel. Az ékezet jó.",
"family_gate.msg.exists": "Már van ilyen nevű családtag.", "family_gate.msg.exists": "Már van ilyen nevű családtag.",
"family_gate.msg.no_member": "Nincs ilyen nevű családtag.", "family_gate.msg.no_member": "Nincs ilyen nevű családtag.",
"family_gate.card_title": "Család", "family_gate.card_title": "Család",
"family_gate.card_desc": "A családtagok a saját nevükkel és jelszavukkal lépnek be a családi kapus alkalmazásokba. Ezzel a vezérlőpultot nem érik el.", "family_gate.card_desc": "A családtagok a saját nevükkel és jelszavukkal lépnek be a családi kapus alkalmazásokba. Ezzel a vezérlőpultot nem érik el.",
"family_gate.add": "Családtag hozzáadása", "family_gate.add": "Családtag hozzáadása",
"family_gate.name_placeholder": "pl. anna", "family_gate.name_placeholder": "pl. Ödön",
"family_gate.name_hint": "Írd be a nevet úgy, ahogy szeretnéd, ékezettel is. A belépési név ebből készül: ékezet nélkül, kisbetűvel. A listában látod.",
"family_gate.login_label": "belépési név:",
"family_gate.reset": "Új jelszó", "family_gate.reset": "Új jelszó",
"family_gate.remove": "Eltávolítás", "family_gate.remove": "Eltávolítás",
"family_gate.none": "Még nincs családtag.", "family_gate.none": "Még nincs családtag.",
@@ -146,3 +146,24 @@ func TestR907_PageHeaderWrapsOnAPhone(t *testing.T) {
} }
t.Fatal("no launcher_full parity case") t.Fatal("no launcher_full parity case")
} }
// W9 (rehearsal 2026-10-20): the recovery-code banner's link sits INSIDE the sentence („Helyreállítási kód
// létrehozása →") and rendered browser-default blue on the amber bar. A link in a banner sentence takes the banner's
// colour and stays underlined.
func TestW9_BannerSentenceLinkTakesTheBannerColour(t *testing.T) {
data, err := StyleCSS()
if err != nil {
t.Fatal(err)
}
r := cssRule(t, string(data), ".alert-message a")
if !strings.Contains(r, "color: inherit") || !strings.Contains(r, "text-decoration: underline") {
t.Errorf(".alert-message a must take the banner's colour and stay underlined: %s", r)
}
// the banner that carries such a link still has it inside its message
s := i18nTestServer(t)
for _, lang := range []string{"hu", "en"} {
if !strings.Contains(s.i18n.Msg(lang, "layout.a_tavoli_mentes_szunetel_amig"), `<a href="/backup/escrow">`) {
t.Fatalf("%s: the escrow banner no longer links inside its sentence — revisit this rule", lang)
}
}
}
+12 -4
View File
@@ -391,7 +391,9 @@ func (s *Server) ServeFamilyLogin(w http.ResponseWriter, r *http.Request) {
s.renderFamilyPage(w, r, rd, s.msg(r, "family_gate.msg.form_expired"), http.StatusForbidden) s.renderFamilyPage(w, r, rd, s.msg(r, "family_gate.msg.form_expired"), http.StatusForbidden)
return return
} }
name := strings.ToLower(strings.TrimSpace(r.FormValue("name"))) // W12: a member may type their name as the household wrote it („Ödön"); the login is made the same way the card
// made it. A login typed as shown („odon2") is unchanged by LoginFrom.
name := family.LoginFrom(r.FormValue("name"))
visitor := rateKey(r) visitor := rateKey(r)
// R-983 (v0.324.0): the try is counted BEFORE the password is checked, in the same step as the lock check, so // R-983 (v0.324.0): the try is counted BEFORE the password is checked, in the same step as the lock check, so
// parallel tries cannot all pass a check none of them has counted yet. A right password clears both counts. // parallel tries cannot all pass a check none of them has counted yet. A right password clears both counts.
@@ -484,7 +486,7 @@ func (s *Server) familyMembersHandler(w http.ResponseWriter, r *http.Request) {
escrowJSON(w, http.StatusServiceUnavailable, nil, s.msg(r, "family_gate.msg.store_unreadable")) escrowJSON(w, http.StatusServiceUnavailable, nil, s.msg(r, "family_gate.msg.store_unreadable"))
return return
} }
escrowJSON(w, http.StatusOK, map[string]any{"members": st.Names()}, "") escrowJSON(w, http.StatusOK, map[string]any{"members": st.Names(), "people": st.People()}, "")
} }
// familyMemberActionHandler: POST /family/members/{add,reset,remove} with `name`. add/reset answer the new password // familyMemberActionHandler: POST /family/members/{add,reset,remove} with `name`. add/reset answer the new password
@@ -496,13 +498,19 @@ func (s *Server) familyMemberActionHandler(w http.ResponseWriter, r *http.Reques
return return
} }
_ = r.ParseForm() _ = r.ParseForm()
// W12 (2026-10-21): „add" takes the name as the household typed it („Ödön") and answers the login made from it
// (odon); reset and remove name the LOGIN, which the card holds.
name := strings.ToLower(strings.TrimSpace(r.FormValue("name"))) name := strings.ToLower(strings.TrimSpace(r.FormValue("name")))
w.Header().Set("Cache-Control", "no-store") w.Header().Set("Cache-Control", "no-store")
var pw string var pw string
var err error var err error
switch action { switch action {
case "add": case "add":
pw, err = st.Add(name) var login string
login, pw, err = st.AddPerson(r.FormValue("name"))
if err == nil {
name = login
}
case "reset": case "reset":
pw, err = st.Reset(name) pw, err = st.Reset(name)
case "remove": case "remove":
@@ -527,7 +535,7 @@ func (s *Server) familyMemberActionHandler(w http.ResponseWriter, r *http.Reques
return return
} }
s.logger.Printf("[INFO] [web] family: the household's %s of %q from %s (password never logged)", action, name, clientIP(r)) s.logger.Printf("[INFO] [web] family: the household's %s of %q from %s (password never logged)", action, name, clientIP(r))
out := map[string]any{"name": name, "members": st.Names()} out := map[string]any{"name": name, "members": st.Names(), "people": st.People()}
if pw != "" { if pw != "" {
out["password"] = pw out["password"] = pw
} }
+35 -1
View File
@@ -369,7 +369,7 @@ func TestFamilyGate_CardActsAndNoPasswordInThePage(t *testing.T) {
if w := act("add", "anna"); w.Code != http.StatusConflict { if w := act("add", "anna"); w.Code != http.StatusConflict {
t.Fatalf("duplicate: %d", w.Code) t.Fatalf("duplicate: %d", w.Code)
} }
if w := act("add", "Not Valid"); w.Code != http.StatusBadRequest { if w := act("add", "!!!"); w.Code != http.StatusBadRequest { // W12: „Not Valid" is a name now (login not.valid)
t.Fatalf("bad name: %d", w.Code) t.Fatalf("bad name: %d", w.Code)
} }
if w := act("remove", "anna"); w.Code != http.StatusOK || strings.Contains(w.Body.String(), "password") { if w := act("remove", "anna"); w.Code != http.StatusOK || strings.Contains(w.Body.String(), "password") {
@@ -379,3 +379,37 @@ func TestFamilyGate_CardActsAndNoPasswordInThePage(t *testing.T) {
t.Fatalf("reset of a removed member: %d", w.Code) t.Fatalf("reset of a removed member: %d", w.Code)
} }
} }
// W12 (rehearsal 2026-10-20): the card takes „Ödön" as typed, answers the login made from it (odon) and lists both;
// a second name that makes the same login gets odon2; the member may sign in typing „Ödön" or „odon".
func TestW12_CardTakesAccentedNamesAndShowsTheLogin(t *testing.T) {
s, st := familyHarness(t)
add := func(name string) *httptest.ResponseRecorder {
r := httptest.NewRequest(http.MethodPost, "/family/members/add", strings.NewReader(url.Values{"name": {name}}.Encode()))
r.Header.Set("Content-Type", "application/x-www-form-urlencoded")
w := httptest.NewRecorder()
s.familyMemberActionHandler(w, r, "add")
return w
}
w := add("Ödön")
body := w.Body.String()
m := regexp.MustCompile(`"password":"([a-z0-9-]{19})"`).FindStringSubmatch(body)
if w.Code != http.StatusOK || m == nil || !strings.Contains(body, `"name":"odon"`) ||
!strings.Contains(body, `{"login":"odon","display":"Ödön"}`) || !st.Verify("odon", m[1]) {
t.Fatalf("add Ödön: %d %s", w.Code, body)
}
if w := add("Odon"); w.Code != http.StatusOK || !strings.Contains(w.Body.String(), `"name":"odon2"`) {
t.Fatalf("the second odon: %d %s", w.Code, w.Body.String())
}
for _, typed := range []string{"Ödön", "odon", " ÖDÖN "} {
if w := famLogin(s, "203.0.113.20", typed, m[1], ""); famCookie(w, familySessionCookie) == nil {
t.Fatalf("sign-in typing %q: %d", typed, w.Code)
}
}
r := httptest.NewRequest(http.MethodGet, "/family/members", nil)
lw := httptest.NewRecorder()
s.familyMembersHandler(lw, r)
if !strings.Contains(lw.Body.String(), `{"login":"odon2","display":"Odon"}`) {
t.Fatalf("the list: %s", lw.Body.String())
}
}
@@ -193,6 +193,13 @@ func i18nCasesB() []i18nCase {
{"backups_remote_escrowed", "backups_remote", func() map[string]interface{} { {"backups_remote_escrowed", "backups_remote", func() map[string]interface{} {
return i18nRemoteData(func(d, o m) { d["EscrowAgentOK"] = true }) return i18nRemoteData(func(d, o m) { d["EscrowAgentOK"] = true })
}}, }},
// W11: the Felhom-provided store shows its name; the provider login waits behind „Haladó".
{"backups_remote_pinned", "backups_remote", func() map[string]interface{} {
return i18nRemoteData(func(d, o m) {
d["EscrowAgentOK"] = true
o["Pinned"], o["User"], o["Host"], o["RepoPath"] = true, "u1-sub5", "u1-sub5.your-storagebox.de", "/home/felhom-repo"
})
}},
{"backups_remote_notconf_hub", "backups_remote", func() map[string]interface{} { {"backups_remote_notconf_hub", "backups_remote", func() map[string]interface{} {
d := i18nLayoutData("backups-remote", "Távoli mentés beállítása") d := i18nLayoutData("backups-remote", "Távoli mentés beállítása")
d["Backup"] = i18nBackupStatus(false, false) d["Backup"] = i18nBackupStatus(false, false)
@@ -76,7 +76,8 @@
<div id="progress-error" class="alert alert-error" style="display:none"></div> <div id="progress-error" class="alert alert-error" style="display:none"></div>
</div> </div>
<!-- 5. Reveal + 6. Typed-back + 7. Finish --> <!-- 5. Reveal + 6. Typed-back + 7. Finish. W10: the headings carry no numbers — cards 2-4 hide as the wizard moves on,
so a numbered page read 1 → 5 → 6. -->
<div class="settings-card" id="reveal-card" style="display:none"> <div class="settings-card" id="reveal-card" style="display:none">
<h3>{{T "backups_escrow.5_a_helyreallitasi_kod"}}</h3> <h3>{{T "backups_escrow.5_a_helyreallitasi_kod"}}</h3>
<div id="reveal-gate"> <div id="reveal-gate">
@@ -182,6 +183,12 @@ async function loadPreflight(){
html+='<li style="padding:.35rem 0"><span style="color:var(--crit)">&#10007;</span> {{T "backups_escrow.a_funkciohoz_az_ugynok_frissitese_2"}}</li>'; html+='<li style="padding:.35rem 0"><span style="color:var(--crit)">&#10007;</span> {{T "backups_escrow.a_funkciohoz_az_ugynok_frissitese_2"}}</li>';
} }
html+='</ul>'; html+='</ul>';
// W10 (rehearsal 2026-10-20): when every check passed the household sees ONE plain line; the rows (storage ids,
// the binary path — operator words) stay behind „Részletek". A failed check still shows the list open.
if(d.ok){
html='<p><span style="color:var(--blue-bright)">&#10003;</span> {{T "backups_escrow.all_prereqs_ok"}}</p>'+
'<details><summary class="form-hint">{{T "backups_escrow.prereq_details"}}</summary>'+html+'</details>';
}
document.getElementById('pf-list').innerHTML=html; document.getElementById('pf-list').innerHTML=html;
if(d.ok){ if(d.ok){
document.getElementById('warn-card').style.display='block'; document.getElementById('warn-card').style.display='block';
@@ -50,11 +50,20 @@
<div class="stat-value" style="font-size:1.15rem">{{if and .Offbox.StatsKnown .Offbox.RepoSizeHuman}}{{numText .Offbox.RepoSizeHuman}}{{else}}–{{end}}</div> <div class="stat-value" style="font-size:1.15rem">{{if and .Offbox.StatsKnown .Offbox.RepoSizeHuman}}{{numText .Offbox.RepoSizeHuman}}{{else}}–{{end}}</div>
<div class="stat-label">{{T "backups_remote.tarolo_meret"}} {{if .Offbox.StatsKnown}}{{T "backups_remote.pillanatkep"}}{{else}}{{T "backups_remote.a_pillanatkepek_szama_meg_ismeretlen"}}{{end}}</div> <div class="stat-label">{{T "backups_remote.tarolo_meret"}} {{if .Offbox.StatsKnown}}{{T "backups_remote.pillanatkep"}}{{else}}{{T "backups_remote.a_pillanatkepek_szama_meg_ismeretlen"}}{{end}}</div>
</div> </div>
{{/* W11 (rehearsal 2026-10-20): the Felhom-provided store showed its provider login
(„u…@….your-storagebox.de /home/felhom-repo") to the household. It shows its name; the login waits
behind „Haladó" below. The household's own NAS keeps its address: they typed it. */}}
<div class="stat-card"> <div class="stat-card">
<div class="stat-value" style="font-size:1.05rem">{{if .Offbox.Enabled}}{{.Offbox.User}}@{{.Offbox.Host}}{{else}}{{T "backups_remote.kikapcsolva"}}{{end}}</div> <div class="stat-value" style="font-size:1.05rem">{{if .Offbox.Enabled}}{{if .Offbox.Pinned}}{{T "backups_remote.felhom_store_name"}}{{else}}{{.Offbox.User}}@{{.Offbox.Host}}{{end}}{{else}}{{T "backups_remote.kikapcsolva"}}{{end}}</div>
<div class="stat-label">{{if .Offbox.Enabled}}{{.Offbox.RepoPath}}{{else}}{{T "backups_remote.a_tavoli_mentes_ki_van"}}{{end}}</div> <div class="stat-label">{{if .Offbox.Enabled}}{{if .Offbox.Pinned}}{{T "backups_remote.felhom_store_where"}}{{else}}{{.Offbox.RepoPath}}{{end}}{{else}}{{T "backups_remote.a_tavoli_mentes_ki_van"}}{{end}}</div>
</div> </div>
</div> </div>
{{if .Offbox.Pinned}}
<details class="form-hint" id="offbox-advanced" style="margin:.5rem 0">
<summary style="cursor:pointer">{{T "backups_remote.halado"}}</summary>
<span class="mono">{{.Offbox.User}}@{{.Offbox.Host}} {{.Offbox.RepoPath}}</span>
</details>
{{end}}
{{if and .Offbox.Enabled (gt .Offbox.QuotaGB 0)}} {{if and .Offbox.Enabled (gt .Offbox.QuotaGB 0)}}
<!-- R-225: the BAR renders only when the fill is known. A 0%-wide bar over an unread store is a <!-- R-225: the BAR renders only when the fill is known. A 0%-wide bar over an unread store is a
picture of emptiness, and a picture is a claim. --> picture of emptiness, and a picture is a claim. -->
@@ -261,12 +270,17 @@
<div class="form-row"><label>{{T "backups_remote.felhasznalo"}}</label><input type="text" name="user" class="form-input" value="{{if .Offbox}}{{.Offbox.User}}{{end}}" placeholder="{{T "backups_remote.pl_felhom"}}"></div> <div class="form-row"><label>{{T "backups_remote.felhasznalo"}}</label><input type="text" name="user" class="form-input" value="{{if .Offbox}}{{.Offbox.User}}{{end}}" placeholder="{{T "backups_remote.pl_felhom"}}"></div>
<div class="form-row"><label>{{T "backups_remote.tarolo_utvonala_a_celgepen"}}</label><input type="text" name="repo_path" class="form-input" value="{{if .Offbox}}{{.Offbox.RepoPath}}{{end}}" placeholder="{{T "backups_remote.pl_repo_path"}}"></div> <div class="form-row"><label>{{T "backups_remote.tarolo_utvonala_a_celgepen"}}</label><input type="text" name="repo_path" class="form-input" value="{{if .Offbox}}{{.Offbox.RepoPath}}{{end}}" placeholder="{{T "backups_remote.pl_repo_path"}}"></div>
<div class="form-row"><label><input type="checkbox" name="enabled" {{if .Offbox}}{{if .Offbox.Enabled}}checked{{end}}{{else}}checked{{end}}> {{T "backups_remote.napi_automatikus_tavoli_mentes_engedelye"}}</label></div> <div class="form-row"><label><input type="checkbox" name="enabled" {{if .Offbox}}{{if .Offbox.Enabled}}checked{{end}}{{else}}checked{{end}}> {{T "backups_remote.napi_automatikus_tavoli_mentes_engedelye"}}</label></div>
{{/* W24 (rehearsal 2026-10-20): the key and host-key fields are operator detail; they sit behind
„Haladó". A closed <details> still submits its fields. */}}
<details class="form-row" id="offbox-key-advanced">
<summary style="cursor:pointer">{{T "backups_remote.halado"}}</summary>
<div class="form-row"><label>{{T "backups_remote.ssh_privat_kulcs"}} {{if .OffboxConfigured}}{{T "backups_remote.uresen_hagyva_valtozatlan"}}{{end}}</label> <div class="form-row"><label>{{T "backups_remote.ssh_privat_kulcs"}} {{if .OffboxConfigured}}{{T "backups_remote.uresen_hagyva_valtozatlan"}}{{end}}</label>
<textarea name="ssh_key" class="form-input" rows="4" autocomplete="off" placeholder="-----BEGIN OPENSSH PRIVATE KEY-----"></textarea> <textarea name="ssh_key" class="form-input" rows="4" autocomplete="off" placeholder="-----BEGIN OPENSSH PRIVATE KEY-----"></textarea>
<span class="form-hint">{{T "backups_remote.a_kulcsot_0600_as_fajlba"}}</span></div> <span class="form-hint">{{T "backups_remote.a_kulcsot_0600_as_fajlba"}}</span></div>
<div class="form-row"><label>{{T "backups_remote.a_celgep_ismert_host_sora"}} {{if .OffboxConfigured}}{{T "backups_remote.uresen_hagyva_valtozatlan"}}{{end}}</label> <div class="form-row"><label>{{T "backups_remote.a_celgep_ismert_host_sora"}} {{if .OffboxConfigured}}{{T "backups_remote.uresen_hagyva_valtozatlan"}}{{end}}</label>
<textarea name="known_hosts" class="form-input" rows="2" autocomplete="off" placeholder="nas.local ssh-ed25519 AAAA…"></textarea> <textarea name="known_hosts" class="form-input" rows="2" autocomplete="off" placeholder="nas.local ssh-ed25519 AAAA…"></textarea>
<span class="form-hint">{{T "backups_remote.a_host_kulcs_rogzitese_no"}} <span class="mono">ssh-keyscan -p &lt;port&gt; &lt;host&gt;</span></span></div> <span class="form-hint">{{T "backups_remote.a_host_kulcs_rogzitese_no"}} <span class="mono">ssh-keyscan -p &lt;port&gt; &lt;host&gt;</span></span></div>
</details>
<button type="submit" class="btn btn-sm btn-primary" style="margin-top:.5rem">{{T "backups_remote.mentes"}}</button> <button type="submit" class="btn btn-sm btn-primary" style="margin-top:.5rem">{{T "backups_remote.mentes"}}</button>
</form> </form>
</details> </details>
@@ -411,25 +411,31 @@ function openDialog(opts){
<div id="family-list" class="settings-grid"></div> <div id="family-list" class="settings-grid"></div>
<p id="family-none" class="form-hint" style="display:none">{{T "family_gate.none"}}</p> <p id="family-none" class="form-hint" style="display:none">{{T "family_gate.none"}}</p>
<div id="family-pw" class="alert alert-info" style="display:none"> <div id="family-pw" class="alert alert-info" style="display:none">
{{T "family_gate.pw_once"}} <strong id="family-pw-name"></strong> — <span id="family-pw-value" class="mono"></span> {{T "family_gate.pw_once"}} {{T "family_gate.login_label"}} <strong id="family-pw-name" class="mono"></strong> — <span id="family-pw-value" class="mono"></span>
</div> </div>
<form id="family-add" class="inline-form" onsubmit="familyAct(event, 'add', document.getElementById('family-new').value)"> <form id="family-add" class="inline-form" onsubmit="familyAct(event, 'add', document.getElementById('family-new').value)">
<input type="text" id="family-new" class="form-control" placeholder="{{T "family_gate.name_placeholder"}}" autocapitalize="none" required> <input type="text" id="family-new" class="form-control" placeholder="{{T "family_gate.name_placeholder"}}" required>
<button type="submit" class="btn btn-sm btn-primary">{{T "family_gate.add"}}</button> <button type="submit" class="btn btn-sm btn-primary">{{T "family_gate.add"}}</button>
</form> </form>
<p class="form-hint">{{T "family_gate.name_hint"}}</p>
<span id="family-err" class="form-hint" style="display:none;color:var(--red)"></span> <span id="family-err" class="form-hint" style="display:none;color:var(--red)"></span>
</div> </div>
<script> <script>
(function () { (function () {
var T = {reset: '{{T "family_gate.reset"}}', remove: '{{T "family_gate.remove"}}', var T = {reset: '{{T "family_gate.reset"}}', remove: '{{T "family_gate.remove"}}', login: '{{T "family_gate.login_label"}}',
cReset: '{{T "family_gate.confirm_reset"}}', cRemove: '{{T "family_gate.confirm_remove"}}', err: '{{T "family_gate.error"}}'}; cReset: '{{T "family_gate.confirm_reset"}}', cRemove: '{{T "family_gate.confirm_remove"}}', err: '{{T "family_gate.error"}}'};
// W12: each member is {login, display} — the name as the household typed it, and the login made from it.
function render(members) { function render(members) {
var list = document.getElementById('family-list'); var list = document.getElementById('family-list');
list.textContent = ''; list.textContent = '';
document.getElementById('family-none').style.display = members.length ? 'none' : ''; document.getElementById('family-none').style.display = members.length ? 'none' : '';
members.forEach(function (n) { members.forEach(function (p) {
var n = p.login;
var row = document.createElement('div'); row.className = 'settings-row'; row.setAttribute('data-family-member', n); var row = document.createElement('div'); row.className = 'settings-row'; row.setAttribute('data-family-member', n);
var label = document.createElement('span'); label.className = 'settings-label mono'; label.textContent = n; var label = document.createElement('span'); label.className = 'settings-label'; label.textContent = p.display;
var login = document.createElement('span'); login.className = 'form-hint'; login.textContent = ' ' + T.login + ' ';
var code = document.createElement('span'); code.className = 'mono'; code.textContent = n;
login.appendChild(code); label.appendChild(login);
var val = document.createElement('span'); val.className = 'settings-value'; var val = document.createElement('span'); val.className = 'settings-value';
[['reset', T.reset, T.cReset, 'btn-outline'], ['remove', T.remove, T.cRemove, 'btn-danger']].forEach(function (a) { [['reset', T.reset, T.cReset, 'btn-outline'], ['remove', T.remove, T.cRemove, 'btn-danger']].forEach(function (a) {
var b = document.createElement('button'); b.type = 'button'; b.className = 'btn btn-xs ' + a[3]; b.textContent = a[1]; var b = document.createElement('button'); b.type = 'button'; b.className = 'btn btn-xs ' + a[3]; b.textContent = a[1];
@@ -447,7 +453,7 @@ function openDialog(opts){
headers: {'X-CSRF-Token': '{{.CSRFToken}}', 'Content-Type': 'application/x-www-form-urlencoded'}, body: body}) headers: {'X-CSRF-Token': '{{.CSRFToken}}', 'Content-Type': 'application/x-www-form-urlencoded'}, body: body})
.then(function (r) { return r.json(); }).then(function (j) { .then(function (r) { return r.json(); }).then(function (j) {
if (!j.ok) { err.textContent = j.error || T.err; err.style.display = 'inline'; return; } if (!j.ok) { err.textContent = j.error || T.err; err.style.display = 'inline'; return; }
render(j.data.members || []); render(j.data.people || []);
var box = document.getElementById('family-pw'); var box = document.getElementById('family-pw');
if (j.data.password) { if (j.data.password) {
document.getElementById('family-pw-name').textContent = j.data.name; document.getElementById('family-pw-name').textContent = j.data.name;
@@ -458,7 +464,7 @@ function openDialog(opts){
}).catch(function () { err.textContent = T.err; err.style.display = 'inline'; }); }).catch(function () { err.textContent = T.err; err.style.display = 'inline'; });
}; };
fetch('/family/members', {credentials: 'same-origin'}).then(function (r) { return r.json(); }) fetch('/family/members', {credentials: 'same-origin'}).then(function (r) { return r.json(); })
.then(function (j) { if (j.ok) render(j.data.members || []); }); .then(function (j) { if (j.ok) render(j.data.people || []); });
})(); })();
</script> </script>
@@ -904,6 +904,10 @@ select.form-control option { background: var(--bg-2); color: var(--text-1); }
/* The link takes the banner's own colour (it had none and rendered browser-default blue, 2026-10-08). */ /* The link takes the banner's own colour (it had none and rendered browser-default blue, 2026-10-08). */
.alert-link { margin-left: auto; white-space: nowrap; color: inherit; text-decoration: none; } .alert-link { margin-left: auto; white-space: nowrap; color: inherit; text-decoration: none; }
.alert-link:hover { text-decoration: underline; } .alert-link:hover { text-decoration: underline; }
/* W9 (rehearsal 2026-10-20): a link INSIDE a banner's sentence („Helyreállítási kód létrehozása →") rendered
browser-default blue on the amber bar. It takes the banner's colour too, underlined so it still reads as a link. */
.alert-message a { color: inherit; text-decoration: underline; text-underline-offset: 2px; }
.alert-message a:hover { text-decoration-thickness: 2px; }
/* Inline storage warnings (under storage bars on dashboard and monitoring) */ /* Inline storage warnings (under storage bars on dashboard and monitoring) */
.inline-warnings { margin-top: 0.75rem; } .inline-warnings { margin-top: 0.75rem; }
@@ -191,7 +191,7 @@
<h3 id="offbox-section" class="backup-tier-divider">Távoli mentés (3. mentés) — titkosított, offsite</h3> <h3 id="offbox-section" class="backup-tier-divider">Távoli mentés (3. mentés) — titkosított, offsite</h3>
<p class="form-hint" style="margin:-0.25rem 0 1rem">Az alkalmazás-mentések titkosított másolata egy távoli tárolóra — saját NAS vagy Felhom offsite tárhely — restic + SFTP kapcsolaton. A tároló csak titkosított adatot lát. Ez a 3-2-1 szabály „1 off-site" lába — független a helyi másodpéldánytól és a teljes rendszermentéstől.</p> <p class="form-hint" style="margin:-0.25rem 0 1rem">Az alkalmazásaid mentéseiről titkosított másolat készül egy távoli helyre: a Felhom tárhelyére vagy a saját NAS-odra. Ott csak titkosított adat van. Ez a másolat független a helyi másodpéldánytól és a teljes rendszermentéstől.</p>
<div class="card" style="margin-bottom:1.5rem"> <div class="card" style="margin-bottom:1.5rem">
<div class="stats-grid backup-page-cards"> <div class="stats-grid backup-page-cards">
@@ -203,6 +203,7 @@
<div class="stat-value" style="font-size:1.15rem">5 GB</div> <div class="stat-value" style="font-size:1.15rem">5 GB</div>
<div class="stat-label">Tároló méret · 10 pillanatkép</div> <div class="stat-label">Tároló méret · 10 pillanatkép</div>
</div> </div>
<div class="stat-card"> <div class="stat-card">
<div class="stat-value" style="font-size:1.05rem">felhom@nas.example</div> <div class="stat-value" style="font-size:1.05rem">felhom@nas.example</div>
<div class="stat-label">/srv/repo</div> <div class="stat-label">/srv/repo</div>
@@ -211,6 +212,7 @@
<div id="offbox-quota-bar" style="max-width:560px;margin:.5rem 0"> <div id="offbox-quota-bar" style="max-width:560px;margin:.5rem 0">
<div class="stat-label" style="margin-bottom:.25rem">Tárhelykeret: 5 GB / 100 GB (5%)</div> <div class="stat-label" style="margin-bottom:.25rem">Tárhelykeret: 5 GB / 100 GB (5%)</div>
<div style="background:var(--border,#334);border-radius:4px;height:8px;overflow:hidden"> <div style="background:var(--border,#334);border-radius:4px;height:8px;overflow:hidden">
@@ -272,12 +274,16 @@
<div class="form-row"><label>Felhasználó</label><input type="text" name="user" class="form-input" value="felhom" placeholder="pl. felhom"></div> <div class="form-row"><label>Felhasználó</label><input type="text" name="user" class="form-input" value="felhom" placeholder="pl. felhom"></div>
<div class="form-row"><label>Tároló útvonala a célgépen</label><input type="text" name="repo_path" class="form-input" value="/srv/repo" placeholder="pl. /volume1/felhom-backup/repo"></div> <div class="form-row"><label>Tároló útvonala a célgépen</label><input type="text" name="repo_path" class="form-input" value="/srv/repo" placeholder="pl. /volume1/felhom-backup/repo"></div>
<div class="form-row"><label><input type="checkbox" name="enabled" checked> Napi automatikus távoli mentés engedélyezve</label></div> <div class="form-row"><label><input type="checkbox" name="enabled" checked> Napi automatikus távoli mentés engedélyezve</label></div>
<details class="form-row" id="offbox-key-advanced">
<summary style="cursor:pointer">Haladó</summary>
<div class="form-row"><label>SSH privát kulcs (üresen hagyva változatlan)</label> <div class="form-row"><label>SSH privát kulcs (üresen hagyva változatlan)</label>
<textarea name="ssh_key" class="form-input" rows="4" autocomplete="off" placeholder="-----BEGIN OPENSSH PRIVATE KEY-----"></textarea> <textarea name="ssh_key" class="form-input" rows="4" autocomplete="off" placeholder="-----BEGIN OPENSSH PRIVATE KEY-----"></textarea>
<span class="form-hint">A kulcsot 0600-as fájlba írjuk; sosem naplózzuk és nem tároljuk a beállításokban.</span></div> <span class="form-hint">A kulcsot 0600-as fájlba írjuk; sosem naplózzuk és nem tároljuk a beállításokban.</span></div>
<div class="form-row"><label>A célgép ismert-host sora (known_hosts) (üresen hagyva változatlan)</label> <div class="form-row"><label>A célgép ismert-host sora (known_hosts) (üresen hagyva változatlan)</label>
<textarea name="known_hosts" class="form-input" rows="2" autocomplete="off" placeholder="nas.local ssh-ed25519 AAAA…"></textarea> <textarea name="known_hosts" class="form-input" rows="2" autocomplete="off" placeholder="nas.local ssh-ed25519 AAAA…"></textarea>
<span class="form-hint">A host-kulcs rögzítése (no blind TOFU). Lekérdezhető: <span class="mono">ssh-keyscan -p &lt;port&gt; &lt;host&gt;</span></span></div> <span class="form-hint">A célgép saját kulcsa. Ezzel a doboz csak ehhez a géphez kapcsolódik. Így kérdezheted le: <span class="mono">ssh-keyscan -p &lt;port&gt; &lt;host&gt;</span></span></div>
</details>
<button type="submit" class="btn btn-sm btn-primary" style="margin-top:.5rem">Mentés</button> <button type="submit" class="btn btn-sm btn-primary" style="margin-top:.5rem">Mentés</button>
</form> </form>
</details> </details>
@@ -191,7 +191,7 @@
<h3 id="offbox-section" class="backup-tier-divider">Távoli mentés (3. mentés) — titkosított, offsite</h3> <h3 id="offbox-section" class="backup-tier-divider">Távoli mentés (3. mentés) — titkosított, offsite</h3>
<p class="form-hint" style="margin:-0.25rem 0 1rem">Az alkalmazás-mentések titkosított másolata egy távoli tárolóra — saját NAS vagy Felhom offsite tárhely — restic + SFTP kapcsolaton. A tároló csak titkosított adatot lát. Ez a 3-2-1 szabály „1 off-site" lába — független a helyi másodpéldánytól és a teljes rendszermentéstől.</p> <p class="form-hint" style="margin:-0.25rem 0 1rem">Az alkalmazásaid mentéseiről titkosított másolat készül egy távoli helyre: a Felhom tárhelyére vagy a saját NAS-odra. Ott csak titkosított adat van. Ez a másolat független a helyi másodpéldánytól és a teljes rendszermentéstől.</p>
<div class="card" style="margin-bottom:1.5rem"> <div class="card" style="margin-bottom:1.5rem">
<div class="stats-grid backup-page-cards"> <div class="stats-grid backup-page-cards">
@@ -203,6 +203,7 @@
<div class="stat-value" style="font-size:1.15rem">–</div> <div class="stat-value" style="font-size:1.15rem">–</div>
<div class="stat-label">Tároló méret · 10 pillanatkép</div> <div class="stat-label">Tároló méret · 10 pillanatkép</div>
</div> </div>
<div class="stat-card"> <div class="stat-card">
<div class="stat-value" style="font-size:1.05rem">Kikapcsolva</div> <div class="stat-value" style="font-size:1.05rem">Kikapcsolva</div>
<div class="stat-label">A távoli mentés ki van kapcsolva</div> <div class="stat-label">A távoli mentés ki van kapcsolva</div>
@@ -219,6 +220,7 @@
<div class="card" style="border-left:3px solid var(--warn);margin:.75rem 0;padding:.75rem 1rem"> <div class="card" style="border-left:3px solid var(--warn);margin:.75rem 0;padding:.75rem 1rem">
<p class="form-hint" style="color:var(--warn);margin:0 0 .5rem">A megerősítés nem érkezett meg a várt időn belül. Ellenőrizd az internetkapcsolatot, majd készíts új helyreállítási kódot.</p> <p class="form-hint" style="color:var(--warn);margin:0 0 .5rem">A megerősítés nem érkezett meg a várt időn belül. Ellenőrizd az internetkapcsolatot, majd készíts új helyreállítási kódot.</p>
@@ -258,12 +260,16 @@
<div class="form-row"><label>Felhasználó</label><input type="text" name="user" class="form-input" value="felhom" placeholder="pl. felhom"></div> <div class="form-row"><label>Felhasználó</label><input type="text" name="user" class="form-input" value="felhom" placeholder="pl. felhom"></div>
<div class="form-row"><label>Tároló útvonala a célgépen</label><input type="text" name="repo_path" class="form-input" value="/srv/repo" placeholder="pl. /volume1/felhom-backup/repo"></div> <div class="form-row"><label>Tároló útvonala a célgépen</label><input type="text" name="repo_path" class="form-input" value="/srv/repo" placeholder="pl. /volume1/felhom-backup/repo"></div>
<div class="form-row"><label><input type="checkbox" name="enabled" > Napi automatikus távoli mentés engedélyezve</label></div> <div class="form-row"><label><input type="checkbox" name="enabled" > Napi automatikus távoli mentés engedélyezve</label></div>
<details class="form-row" id="offbox-key-advanced">
<summary style="cursor:pointer">Haladó</summary>
<div class="form-row"><label>SSH privát kulcs (üresen hagyva változatlan)</label> <div class="form-row"><label>SSH privát kulcs (üresen hagyva változatlan)</label>
<textarea name="ssh_key" class="form-input" rows="4" autocomplete="off" placeholder="-----BEGIN OPENSSH PRIVATE KEY-----"></textarea> <textarea name="ssh_key" class="form-input" rows="4" autocomplete="off" placeholder="-----BEGIN OPENSSH PRIVATE KEY-----"></textarea>
<span class="form-hint">A kulcsot 0600-as fájlba írjuk; sosem naplózzuk és nem tároljuk a beállításokban.</span></div> <span class="form-hint">A kulcsot 0600-as fájlba írjuk; sosem naplózzuk és nem tároljuk a beállításokban.</span></div>
<div class="form-row"><label>A célgép ismert-host sora (known_hosts) (üresen hagyva változatlan)</label> <div class="form-row"><label>A célgép ismert-host sora (known_hosts) (üresen hagyva változatlan)</label>
<textarea name="known_hosts" class="form-input" rows="2" autocomplete="off" placeholder="nas.local ssh-ed25519 AAAA…"></textarea> <textarea name="known_hosts" class="form-input" rows="2" autocomplete="off" placeholder="nas.local ssh-ed25519 AAAA…"></textarea>
<span class="form-hint">A host-kulcs rögzítése (no blind TOFU). Lekérdezhető: <span class="mono">ssh-keyscan -p &lt;port&gt; &lt;host&gt;</span></span></div> <span class="form-hint">A célgép saját kulcsa. Ezzel a doboz csak ehhez a géphez kapcsolódik. Így kérdezheted le: <span class="mono">ssh-keyscan -p &lt;port&gt; &lt;host&gt;</span></span></div>
</details>
<button type="submit" class="btn btn-sm btn-primary" style="margin-top:.5rem">Mentés</button> <button type="submit" class="btn btn-sm btn-primary" style="margin-top:.5rem">Mentés</button>
</form> </form>
</details> </details>
@@ -191,7 +191,7 @@
<h3 id="offbox-section" class="backup-tier-divider">Távoli mentés (3. mentés) — titkosított, offsite</h3> <h3 id="offbox-section" class="backup-tier-divider">Távoli mentés (3. mentés) — titkosított, offsite</h3>
<p class="form-hint" style="margin:-0.25rem 0 1rem">Az alkalmazás-mentések titkosított másolata egy távoli tárolóra — saját NAS vagy Felhom offsite tárhely — restic + SFTP kapcsolaton. A tároló csak titkosított adatot lát. Ez a 3-2-1 szabály „1 off-site" lába — független a helyi másodpéldánytól és a teljes rendszermentéstől.</p> <p class="form-hint" style="margin:-0.25rem 0 1rem">Az alkalmazásaid mentéseiről titkosított másolat készül egy távoli helyre: a Felhom tárhelyére vagy a saját NAS-odra. Ott csak titkosított adat van. Ez a másolat független a helyi másodpéldánytól és a teljes rendszermentéstől.</p>
<div class="card" style="margin-bottom:1.5rem"> <div class="card" style="margin-bottom:1.5rem">
<div class="stats-grid backup-page-cards"> <div class="stats-grid backup-page-cards">
@@ -203,6 +203,7 @@
<div class="stat-value" style="font-size:1.15rem">5 GB</div> <div class="stat-value" style="font-size:1.15rem">5 GB</div>
<div class="stat-label">Tároló méret · 10 pillanatkép</div> <div class="stat-label">Tároló méret · 10 pillanatkép</div>
</div> </div>
<div class="stat-card"> <div class="stat-card">
<div class="stat-value" style="font-size:1.05rem">felhom@nas.example</div> <div class="stat-value" style="font-size:1.05rem">felhom@nas.example</div>
<div class="stat-label">/srv/repo</div> <div class="stat-label">/srv/repo</div>
@@ -211,6 +212,7 @@
<div id="offbox-quota-bar" style="max-width:560px;margin:.5rem 0"> <div id="offbox-quota-bar" style="max-width:560px;margin:.5rem 0">
<div class="stat-label" style="margin-bottom:.25rem">Tárhelykeret: 5 GB / 100 GB (5%)</div> <div class="stat-label" style="margin-bottom:.25rem">Tárhelykeret: 5 GB / 100 GB (5%)</div>
<div style="background:var(--border,#334);border-radius:4px;height:8px;overflow:hidden"> <div style="background:var(--border,#334);border-radius:4px;height:8px;overflow:hidden">
@@ -261,12 +263,16 @@
<div class="form-row"><label>Felhasználó</label><input type="text" name="user" class="form-input" value="felhom" placeholder="pl. felhom"></div> <div class="form-row"><label>Felhasználó</label><input type="text" name="user" class="form-input" value="felhom" placeholder="pl. felhom"></div>
<div class="form-row"><label>Tároló útvonala a célgépen</label><input type="text" name="repo_path" class="form-input" value="/srv/repo" placeholder="pl. /volume1/felhom-backup/repo"></div> <div class="form-row"><label>Tároló útvonala a célgépen</label><input type="text" name="repo_path" class="form-input" value="/srv/repo" placeholder="pl. /volume1/felhom-backup/repo"></div>
<div class="form-row"><label><input type="checkbox" name="enabled" checked> Napi automatikus távoli mentés engedélyezve</label></div> <div class="form-row"><label><input type="checkbox" name="enabled" checked> Napi automatikus távoli mentés engedélyezve</label></div>
<details class="form-row" id="offbox-key-advanced">
<summary style="cursor:pointer">Haladó</summary>
<div class="form-row"><label>SSH privát kulcs (üresen hagyva változatlan)</label> <div class="form-row"><label>SSH privát kulcs (üresen hagyva változatlan)</label>
<textarea name="ssh_key" class="form-input" rows="4" autocomplete="off" placeholder="-----BEGIN OPENSSH PRIVATE KEY-----"></textarea> <textarea name="ssh_key" class="form-input" rows="4" autocomplete="off" placeholder="-----BEGIN OPENSSH PRIVATE KEY-----"></textarea>
<span class="form-hint">A kulcsot 0600-as fájlba írjuk; sosem naplózzuk és nem tároljuk a beállításokban.</span></div> <span class="form-hint">A kulcsot 0600-as fájlba írjuk; sosem naplózzuk és nem tároljuk a beállításokban.</span></div>
<div class="form-row"><label>A célgép ismert-host sora (known_hosts) (üresen hagyva változatlan)</label> <div class="form-row"><label>A célgép ismert-host sora (known_hosts) (üresen hagyva változatlan)</label>
<textarea name="known_hosts" class="form-input" rows="2" autocomplete="off" placeholder="nas.local ssh-ed25519 AAAA…"></textarea> <textarea name="known_hosts" class="form-input" rows="2" autocomplete="off" placeholder="nas.local ssh-ed25519 AAAA…"></textarea>
<span class="form-hint">A host-kulcs rögzítése (no blind TOFU). Lekérdezhető: <span class="mono">ssh-keyscan -p &lt;port&gt; &lt;host&gt;</span></span></div> <span class="form-hint">A célgép saját kulcsa. Ezzel a doboz csak ehhez a géphez kapcsolódik. Így kérdezheted le: <span class="mono">ssh-keyscan -p &lt;port&gt; &lt;host&gt;</span></span></div>
</details>
<button type="submit" class="btn btn-sm btn-primary" style="margin-top:.5rem">Mentés</button> <button type="submit" class="btn btn-sm btn-primary" style="margin-top:.5rem">Mentés</button>
</form> </form>
</details> </details>
@@ -203,7 +203,7 @@
<h3 id="offbox-section" class="backup-tier-divider">Távoli mentés (3. mentés) — titkosított, offsite</h3> <h3 id="offbox-section" class="backup-tier-divider">Távoli mentés (3. mentés) — titkosított, offsite</h3>
<p class="form-hint" style="margin:-0.25rem 0 1rem">Az alkalmazás-mentések titkosított másolata egy távoli tárolóra — saját NAS vagy Felhom offsite tárhely — restic + SFTP kapcsolaton. A tároló csak titkosított adatot lát. Ez a 3-2-1 szabály „1 off-site" lába — független a helyi másodpéldánytól és a teljes rendszermentéstől.</p> <p class="form-hint" style="margin:-0.25rem 0 1rem">Az alkalmazásaid mentéseiről titkosított másolat készül egy távoli helyre: a Felhom tárhelyére vagy a saját NAS-odra. Ott csak titkosított adat van. Ez a másolat független a helyi másodpéldánytól és a teljes rendszermentéstől.</p>
<div class="card" style="margin-bottom:1.5rem"> <div class="card" style="margin-bottom:1.5rem">
<div class="stats-grid backup-page-cards"> <div class="stats-grid backup-page-cards">
@@ -215,6 +215,7 @@
<div class="stat-value" style="font-size:1.15rem">12 GB</div> <div class="stat-value" style="font-size:1.15rem">12 GB</div>
<div class="stat-label">Tároló méret · 40 pillanatkép</div> <div class="stat-label">Tároló méret · 40 pillanatkép</div>
</div> </div>
<div class="stat-card"> <div class="stat-card">
<div class="stat-value" style="font-size:1.05rem">felhom@nas.example</div> <div class="stat-value" style="font-size:1.05rem">felhom@nas.example</div>
<div class="stat-label">/srv/repo</div> <div class="stat-label">/srv/repo</div>
@@ -223,6 +224,7 @@
<div id="offbox-quota-bar" style="max-width:560px;margin:.5rem 0"> <div id="offbox-quota-bar" style="max-width:560px;margin:.5rem 0">
<div class="stat-label" style="margin-bottom:.25rem">Tárhelykeret: 12 GB / 100 GB (110%)</div> <div class="stat-label" style="margin-bottom:.25rem">Tárhelykeret: 12 GB / 100 GB (110%)</div>
<div style="background:var(--border,#334);border-radius:4px;height:8px;overflow:hidden"> <div style="background:var(--border,#334);border-radius:4px;height:8px;overflow:hidden">
@@ -359,12 +361,16 @@
<div class="form-row"><label>Felhasználó</label><input type="text" name="user" class="form-input" value="felhom" placeholder="pl. felhom"></div> <div class="form-row"><label>Felhasználó</label><input type="text" name="user" class="form-input" value="felhom" placeholder="pl. felhom"></div>
<div class="form-row"><label>Tároló útvonala a célgépen</label><input type="text" name="repo_path" class="form-input" value="/srv/repo" placeholder="pl. /volume1/felhom-backup/repo"></div> <div class="form-row"><label>Tároló útvonala a célgépen</label><input type="text" name="repo_path" class="form-input" value="/srv/repo" placeholder="pl. /volume1/felhom-backup/repo"></div>
<div class="form-row"><label><input type="checkbox" name="enabled" checked> Napi automatikus távoli mentés engedélyezve</label></div> <div class="form-row"><label><input type="checkbox" name="enabled" checked> Napi automatikus távoli mentés engedélyezve</label></div>
<details class="form-row" id="offbox-key-advanced">
<summary style="cursor:pointer">Haladó</summary>
<div class="form-row"><label>SSH privát kulcs (üresen hagyva változatlan)</label> <div class="form-row"><label>SSH privát kulcs (üresen hagyva változatlan)</label>
<textarea name="ssh_key" class="form-input" rows="4" autocomplete="off" placeholder="-----BEGIN OPENSSH PRIVATE KEY-----"></textarea> <textarea name="ssh_key" class="form-input" rows="4" autocomplete="off" placeholder="-----BEGIN OPENSSH PRIVATE KEY-----"></textarea>
<span class="form-hint">A kulcsot 0600-as fájlba írjuk; sosem naplózzuk és nem tároljuk a beállításokban.</span></div> <span class="form-hint">A kulcsot 0600-as fájlba írjuk; sosem naplózzuk és nem tároljuk a beállításokban.</span></div>
<div class="form-row"><label>A célgép ismert-host sora (known_hosts) (üresen hagyva változatlan)</label> <div class="form-row"><label>A célgép ismert-host sora (known_hosts) (üresen hagyva változatlan)</label>
<textarea name="known_hosts" class="form-input" rows="2" autocomplete="off" placeholder="nas.local ssh-ed25519 AAAA…"></textarea> <textarea name="known_hosts" class="form-input" rows="2" autocomplete="off" placeholder="nas.local ssh-ed25519 AAAA…"></textarea>
<span class="form-hint">A host-kulcs rögzítése (no blind TOFU). Lekérdezhető: <span class="mono">ssh-keyscan -p &lt;port&gt; &lt;host&gt;</span></span></div> <span class="form-hint">A célgép saját kulcsa. Ezzel a doboz csak ehhez a géphez kapcsolódik. Így kérdezheted le: <span class="mono">ssh-keyscan -p &lt;port&gt; &lt;host&gt;</span></span></div>
</details>
<button type="submit" class="btn btn-sm btn-primary" style="margin-top:.5rem">Mentés</button> <button type="submit" class="btn btn-sm btn-primary" style="margin-top:.5rem">Mentés</button>
</form> </form>
</details> </details>
@@ -191,7 +191,7 @@
<h3 id="offbox-section" class="backup-tier-divider">Távoli mentés (3. mentés) — titkosított, offsite</h3> <h3 id="offbox-section" class="backup-tier-divider">Távoli mentés (3. mentés) — titkosított, offsite</h3>
<p class="form-hint" style="margin:-0.25rem 0 1rem">Az alkalmazás-mentések titkosított másolata egy távoli tárolóra — saját NAS vagy Felhom offsite tárhely — restic + SFTP kapcsolaton. A tároló csak titkosított adatot lát. Ez a 3-2-1 szabály „1 off-site" lába — független a helyi másodpéldánytól és a teljes rendszermentéstől.</p> <p class="form-hint" style="margin:-0.25rem 0 1rem">Az alkalmazásaid mentéseiről titkosított másolat készül egy távoli helyre: a Felhom tárhelyére vagy a saját NAS-odra. Ott csak titkosított adat van. Ez a másolat független a helyi másodpéldánytól és a teljes rendszermentéstől.</p>
<div class="card" style="margin-bottom:1.5rem"> <div class="card" style="margin-bottom:1.5rem">
<div class="stats-grid backup-page-cards"> <div class="stats-grid backup-page-cards">
@@ -203,6 +203,7 @@
<div class="stat-value" style="font-size:1.15rem">–</div> <div class="stat-value" style="font-size:1.15rem">–</div>
<div class="stat-label">Tároló méret · a pillanatképek száma még ismeretlen</div> <div class="stat-label">Tároló méret · a pillanatképek száma még ismeretlen</div>
</div> </div>
<div class="stat-card"> <div class="stat-card">
<div class="stat-value" style="font-size:1.05rem">felhom@nas.example</div> <div class="stat-value" style="font-size:1.05rem">felhom@nas.example</div>
<div class="stat-label">/srv/repo</div> <div class="stat-label">/srv/repo</div>
@@ -211,6 +212,7 @@
<div id="offbox-quota-bar" style="max-width:560px;margin:.5rem 0"> <div id="offbox-quota-bar" style="max-width:560px;margin:.5rem 0">
<div class="stat-label" style="margin-bottom:.25rem">Tárhelykeret: még nem tudjuk, mennyi van a tárolóban — legfeljebb 50 GB</div> <div class="stat-label" style="margin-bottom:.25rem">Tárhelykeret: még nem tudjuk, mennyi van a tárolóban — legfeljebb 50 GB</div>
@@ -273,12 +275,16 @@
<div class="form-row"><label>Felhasználó</label><input type="text" name="user" class="form-input" value="felhom" placeholder="pl. felhom"></div> <div class="form-row"><label>Felhasználó</label><input type="text" name="user" class="form-input" value="felhom" placeholder="pl. felhom"></div>
<div class="form-row"><label>Tároló útvonala a célgépen</label><input type="text" name="repo_path" class="form-input" value="/srv/repo" placeholder="pl. /volume1/felhom-backup/repo"></div> <div class="form-row"><label>Tároló útvonala a célgépen</label><input type="text" name="repo_path" class="form-input" value="/srv/repo" placeholder="pl. /volume1/felhom-backup/repo"></div>
<div class="form-row"><label><input type="checkbox" name="enabled" checked> Napi automatikus távoli mentés engedélyezve</label></div> <div class="form-row"><label><input type="checkbox" name="enabled" checked> Napi automatikus távoli mentés engedélyezve</label></div>
<details class="form-row" id="offbox-key-advanced">
<summary style="cursor:pointer">Haladó</summary>
<div class="form-row"><label>SSH privát kulcs (üresen hagyva változatlan)</label> <div class="form-row"><label>SSH privát kulcs (üresen hagyva változatlan)</label>
<textarea name="ssh_key" class="form-input" rows="4" autocomplete="off" placeholder="-----BEGIN OPENSSH PRIVATE KEY-----"></textarea> <textarea name="ssh_key" class="form-input" rows="4" autocomplete="off" placeholder="-----BEGIN OPENSSH PRIVATE KEY-----"></textarea>
<span class="form-hint">A kulcsot 0600-as fájlba írjuk; sosem naplózzuk és nem tároljuk a beállításokban.</span></div> <span class="form-hint">A kulcsot 0600-as fájlba írjuk; sosem naplózzuk és nem tároljuk a beállításokban.</span></div>
<div class="form-row"><label>A célgép ismert-host sora (known_hosts) (üresen hagyva változatlan)</label> <div class="form-row"><label>A célgép ismert-host sora (known_hosts) (üresen hagyva változatlan)</label>
<textarea name="known_hosts" class="form-input" rows="2" autocomplete="off" placeholder="nas.local ssh-ed25519 AAAA…"></textarea> <textarea name="known_hosts" class="form-input" rows="2" autocomplete="off" placeholder="nas.local ssh-ed25519 AAAA…"></textarea>
<span class="form-hint">A host-kulcs rögzítése (no blind TOFU). Lekérdezhető: <span class="mono">ssh-keyscan -p &lt;port&gt; &lt;host&gt;</span></span></div> <span class="form-hint">A célgép saját kulcsa. Ezzel a doboz csak ehhez a géphez kapcsolódik. Így kérdezheted le: <span class="mono">ssh-keyscan -p &lt;port&gt; &lt;host&gt;</span></span></div>
</details>
<button type="submit" class="btn btn-sm btn-primary" style="margin-top:.5rem">Mentés</button> <button type="submit" class="btn btn-sm btn-primary" style="margin-top:.5rem">Mentés</button>
</form> </form>
</details> </details>
@@ -193,7 +193,7 @@
<h3 id="offbox-section" class="backup-tier-divider">Távoli mentés (3. mentés) — titkosított, offsite</h3> <h3 id="offbox-section" class="backup-tier-divider">Távoli mentés (3. mentés) — titkosított, offsite</h3>
<p class="form-hint" style="margin:-0.25rem 0 1rem">Az alkalmazás-mentések titkosított másolata egy távoli tárolóra — saját NAS vagy Felhom offsite tárhely — restic + SFTP kapcsolaton. A tároló csak titkosított adatot lát. Ez a 3-2-1 szabály „1 off-site" lába — független a helyi másodpéldánytól és a teljes rendszermentéstől.</p> <p class="form-hint" style="margin:-0.25rem 0 1rem">Az alkalmazásaid mentéseiről titkosított másolat készül egy távoli helyre: a Felhom tárhelyére vagy a saját NAS-odra. Ott csak titkosított adat van. Ez a másolat független a helyi másodpéldánytól és a teljes rendszermentéstől.</p>
<div class="card" style="margin-bottom:1.5rem"> <div class="card" style="margin-bottom:1.5rem">
@@ -209,12 +209,16 @@
<div class="form-row"><label>Felhasználó</label><input type="text" name="user" class="form-input" value="" placeholder="pl. felhom"></div> <div class="form-row"><label>Felhasználó</label><input type="text" name="user" class="form-input" value="" placeholder="pl. felhom"></div>
<div class="form-row"><label>Tároló útvonala a célgépen</label><input type="text" name="repo_path" class="form-input" value="" placeholder="pl. /volume1/felhom-backup/repo"></div> <div class="form-row"><label>Tároló útvonala a célgépen</label><input type="text" name="repo_path" class="form-input" value="" placeholder="pl. /volume1/felhom-backup/repo"></div>
<div class="form-row"><label><input type="checkbox" name="enabled" checked> Napi automatikus távoli mentés engedélyezve</label></div> <div class="form-row"><label><input type="checkbox" name="enabled" checked> Napi automatikus távoli mentés engedélyezve</label></div>
<details class="form-row" id="offbox-key-advanced">
<summary style="cursor:pointer">Haladó</summary>
<div class="form-row"><label>SSH privát kulcs </label> <div class="form-row"><label>SSH privát kulcs </label>
<textarea name="ssh_key" class="form-input" rows="4" autocomplete="off" placeholder="-----BEGIN OPENSSH PRIVATE KEY-----"></textarea> <textarea name="ssh_key" class="form-input" rows="4" autocomplete="off" placeholder="-----BEGIN OPENSSH PRIVATE KEY-----"></textarea>
<span class="form-hint">A kulcsot 0600-as fájlba írjuk; sosem naplózzuk és nem tároljuk a beállításokban.</span></div> <span class="form-hint">A kulcsot 0600-as fájlba írjuk; sosem naplózzuk és nem tároljuk a beállításokban.</span></div>
<div class="form-row"><label>A célgép ismert-host sora (known_hosts) </label> <div class="form-row"><label>A célgép ismert-host sora (known_hosts) </label>
<textarea name="known_hosts" class="form-input" rows="2" autocomplete="off" placeholder="nas.local ssh-ed25519 AAAA…"></textarea> <textarea name="known_hosts" class="form-input" rows="2" autocomplete="off" placeholder="nas.local ssh-ed25519 AAAA…"></textarea>
<span class="form-hint">A host-kulcs rögzítése (no blind TOFU). Lekérdezhető: <span class="mono">ssh-keyscan -p &lt;port&gt; &lt;host&gt;</span></span></div> <span class="form-hint">A célgép saját kulcsa. Ezzel a doboz csak ehhez a géphez kapcsolódik. Így kérdezheted le: <span class="mono">ssh-keyscan -p &lt;port&gt; &lt;host&gt;</span></span></div>
</details>
<button type="submit" class="btn btn-sm btn-primary" style="margin-top:.5rem">Mentés</button> <button type="submit" class="btn btn-sm btn-primary" style="margin-top:.5rem">Mentés</button>
</form> </form>
</details> </details>
@@ -194,7 +194,7 @@
<h3 id="offbox-section" class="backup-tier-divider">Távoli mentés (3. mentés) — titkosított, offsite</h3> <h3 id="offbox-section" class="backup-tier-divider">Távoli mentés (3. mentés) — titkosított, offsite</h3>
<p class="form-hint" style="margin:-0.25rem 0 1rem">Az alkalmazás-mentések titkosított másolata egy távoli tárolóra — saját NAS vagy Felhom offsite tárhely — restic + SFTP kapcsolaton. A tároló csak titkosított adatot lát. Ez a 3-2-1 szabály „1 off-site" lába — független a helyi másodpéldánytól és a teljes rendszermentéstől.</p> <p class="form-hint" style="margin:-0.25rem 0 1rem">Az alkalmazásaid mentéseiről titkosított másolat készül egy távoli helyre: a Felhom tárhelyére vagy a saját NAS-odra. Ott csak titkosított adat van. Ez a másolat független a helyi másodpéldánytól és a teljes rendszermentéstől.</p>
<div class="card" style="margin-bottom:1.5rem"> <div class="card" style="margin-bottom:1.5rem">
@@ -210,12 +210,16 @@
<div class="form-row"><label>Felhasználó</label><input type="text" name="user" class="form-input" value="" placeholder="pl. felhom"></div> <div class="form-row"><label>Felhasználó</label><input type="text" name="user" class="form-input" value="" placeholder="pl. felhom"></div>
<div class="form-row"><label>Tároló útvonala a célgépen</label><input type="text" name="repo_path" class="form-input" value="" placeholder="pl. /volume1/felhom-backup/repo"></div> <div class="form-row"><label>Tároló útvonala a célgépen</label><input type="text" name="repo_path" class="form-input" value="" placeholder="pl. /volume1/felhom-backup/repo"></div>
<div class="form-row"><label><input type="checkbox" name="enabled" checked> Napi automatikus távoli mentés engedélyezve</label></div> <div class="form-row"><label><input type="checkbox" name="enabled" checked> Napi automatikus távoli mentés engedélyezve</label></div>
<details class="form-row" id="offbox-key-advanced">
<summary style="cursor:pointer">Haladó</summary>
<div class="form-row"><label>SSH privát kulcs </label> <div class="form-row"><label>SSH privát kulcs </label>
<textarea name="ssh_key" class="form-input" rows="4" autocomplete="off" placeholder="-----BEGIN OPENSSH PRIVATE KEY-----"></textarea> <textarea name="ssh_key" class="form-input" rows="4" autocomplete="off" placeholder="-----BEGIN OPENSSH PRIVATE KEY-----"></textarea>
<span class="form-hint">A kulcsot 0600-as fájlba írjuk; sosem naplózzuk és nem tároljuk a beállításokban.</span></div> <span class="form-hint">A kulcsot 0600-as fájlba írjuk; sosem naplózzuk és nem tároljuk a beállításokban.</span></div>
<div class="form-row"><label>A célgép ismert-host sora (known_hosts) </label> <div class="form-row"><label>A célgép ismert-host sora (known_hosts) </label>
<textarea name="known_hosts" class="form-input" rows="2" autocomplete="off" placeholder="nas.local ssh-ed25519 AAAA…"></textarea> <textarea name="known_hosts" class="form-input" rows="2" autocomplete="off" placeholder="nas.local ssh-ed25519 AAAA…"></textarea>
<span class="form-hint">A host-kulcs rögzítése (no blind TOFU). Lekérdezhető: <span class="mono">ssh-keyscan -p &lt;port&gt; &lt;host&gt;</span></span></div> <span class="form-hint">A célgép saját kulcsa. Ezzel a doboz csak ehhez a géphez kapcsolódik. Így kérdezheted le: <span class="mono">ssh-keyscan -p &lt;port&gt; &lt;host&gt;</span></span></div>
</details>
<button type="submit" class="btn btn-sm btn-primary" style="margin-top:.5rem">Mentés</button> <button type="submit" class="btn btn-sm btn-primary" style="margin-top:.5rem">Mentés</button>
</form> </form>
</details> </details>
@@ -191,7 +191,7 @@
<h3 id="offbox-section" class="backup-tier-divider">Távoli mentés (3. mentés) — titkosított, offsite</h3> <h3 id="offbox-section" class="backup-tier-divider">Távoli mentés (3. mentés) — titkosított, offsite</h3>
<p class="form-hint" style="margin:-0.25rem 0 1rem">Az alkalmazás-mentések titkosított másolata egy távoli tárolóra — saját NAS vagy Felhom offsite tárhely — restic + SFTP kapcsolaton. A tároló csak titkosított adatot lát. Ez a 3-2-1 szabály „1 off-site" lába — független a helyi másodpéldánytól és a teljes rendszermentéstől.</p> <p class="form-hint" style="margin:-0.25rem 0 1rem">Az alkalmazásaid mentéseiről titkosított másolat készül egy távoli helyre: a Felhom tárhelyére vagy a saját NAS-odra. Ott csak titkosított adat van. Ez a másolat független a helyi másodpéldánytól és a teljes rendszermentéstől.</p>
<div class="card" style="margin-bottom:1.5rem"> <div class="card" style="margin-bottom:1.5rem">
<div class="stats-grid backup-page-cards"> <div class="stats-grid backup-page-cards">
@@ -203,6 +203,7 @@
<div class="stat-value" style="font-size:1.15rem">5 GB</div> <div class="stat-value" style="font-size:1.15rem">5 GB</div>
<div class="stat-label">Tároló méret · 10 pillanatkép</div> <div class="stat-label">Tároló méret · 10 pillanatkép</div>
</div> </div>
<div class="stat-card"> <div class="stat-card">
<div class="stat-value" style="font-size:1.05rem">felhom@nas.example</div> <div class="stat-value" style="font-size:1.05rem">felhom@nas.example</div>
<div class="stat-label">/srv/repo</div> <div class="stat-label">/srv/repo</div>
@@ -211,6 +212,7 @@
<div id="offbox-quota-bar" style="max-width:560px;margin:.5rem 0"> <div id="offbox-quota-bar" style="max-width:560px;margin:.5rem 0">
<div class="stat-label" style="margin-bottom:.25rem">Tárhelykeret: 5 GB / 100 GB (5%)</div> <div class="stat-label" style="margin-bottom:.25rem">Tárhelykeret: 5 GB / 100 GB (5%)</div>
<div style="background:var(--border,#334);border-radius:4px;height:8px;overflow:hidden"> <div style="background:var(--border,#334);border-radius:4px;height:8px;overflow:hidden">
@@ -274,12 +276,16 @@
<div class="form-row"><label>Felhasználó</label><input type="text" name="user" class="form-input" value="felhom" placeholder="pl. felhom"></div> <div class="form-row"><label>Felhasználó</label><input type="text" name="user" class="form-input" value="felhom" placeholder="pl. felhom"></div>
<div class="form-row"><label>Tároló útvonala a célgépen</label><input type="text" name="repo_path" class="form-input" value="/srv/repo" placeholder="pl. /volume1/felhom-backup/repo"></div> <div class="form-row"><label>Tároló útvonala a célgépen</label><input type="text" name="repo_path" class="form-input" value="/srv/repo" placeholder="pl. /volume1/felhom-backup/repo"></div>
<div class="form-row"><label><input type="checkbox" name="enabled" checked> Napi automatikus távoli mentés engedélyezve</label></div> <div class="form-row"><label><input type="checkbox" name="enabled" checked> Napi automatikus távoli mentés engedélyezve</label></div>
<details class="form-row" id="offbox-key-advanced">
<summary style="cursor:pointer">Haladó</summary>
<div class="form-row"><label>SSH privát kulcs (üresen hagyva változatlan)</label> <div class="form-row"><label>SSH privát kulcs (üresen hagyva változatlan)</label>
<textarea name="ssh_key" class="form-input" rows="4" autocomplete="off" placeholder="-----BEGIN OPENSSH PRIVATE KEY-----"></textarea> <textarea name="ssh_key" class="form-input" rows="4" autocomplete="off" placeholder="-----BEGIN OPENSSH PRIVATE KEY-----"></textarea>
<span class="form-hint">A kulcsot 0600-as fájlba írjuk; sosem naplózzuk és nem tároljuk a beállításokban.</span></div> <span class="form-hint">A kulcsot 0600-as fájlba írjuk; sosem naplózzuk és nem tároljuk a beállításokban.</span></div>
<div class="form-row"><label>A célgép ismert-host sora (known_hosts) (üresen hagyva változatlan)</label> <div class="form-row"><label>A célgép ismert-host sora (known_hosts) (üresen hagyva változatlan)</label>
<textarea name="known_hosts" class="form-input" rows="2" autocomplete="off" placeholder="nas.local ssh-ed25519 AAAA…"></textarea> <textarea name="known_hosts" class="form-input" rows="2" autocomplete="off" placeholder="nas.local ssh-ed25519 AAAA…"></textarea>
<span class="form-hint">A host-kulcs rögzítése (no blind TOFU). Lekérdezhető: <span class="mono">ssh-keyscan -p &lt;port&gt; &lt;host&gt;</span></span></div> <span class="form-hint">A célgép saját kulcsa. Ezzel a doboz csak ehhez a géphez kapcsolódik. Így kérdezheted le: <span class="mono">ssh-keyscan -p &lt;port&gt; &lt;host&gt;</span></span></div>
</details>
<button type="submit" class="btn btn-sm btn-primary" style="margin-top:.5rem">Mentés</button> <button type="submit" class="btn btn-sm btn-primary" style="margin-top:.5rem">Mentés</button>
</form> </form>
</details> </details>
@@ -191,7 +191,7 @@
<h3 id="offbox-section" class="backup-tier-divider">Távoli mentés (3. mentés) — titkosított, offsite</h3> <h3 id="offbox-section" class="backup-tier-divider">Távoli mentés (3. mentés) — titkosított, offsite</h3>
<p class="form-hint" style="margin:-0.25rem 0 1rem">Az alkalmazás-mentések titkosított másolata egy távoli tárolóra — saját NAS vagy Felhom offsite tárhely — restic + SFTP kapcsolaton. A tároló csak titkosított adatot lát. Ez a 3-2-1 szabály „1 off-site" lába — független a helyi másodpéldánytól és a teljes rendszermentéstől.</p> <p class="form-hint" style="margin:-0.25rem 0 1rem">Az alkalmazásaid mentéseiről titkosított másolat készül egy távoli helyre: a Felhom tárhelyére vagy a saját NAS-odra. Ott csak titkosított adat van. Ez a másolat független a helyi másodpéldánytól és a teljes rendszermentéstől.</p>
<div class="card" style="margin-bottom:1.5rem"> <div class="card" style="margin-bottom:1.5rem">
<div class="stats-grid backup-page-cards"> <div class="stats-grid backup-page-cards">
@@ -203,6 +203,7 @@
<div class="stat-value" style="font-size:1.15rem">5 GB</div> <div class="stat-value" style="font-size:1.15rem">5 GB</div>
<div class="stat-label">Tároló méret · 10 pillanatkép</div> <div class="stat-label">Tároló méret · 10 pillanatkép</div>
</div> </div>
<div class="stat-card"> <div class="stat-card">
<div class="stat-value" style="font-size:1.05rem">felhom@nas.example</div> <div class="stat-value" style="font-size:1.05rem">felhom@nas.example</div>
<div class="stat-label">/srv/repo</div> <div class="stat-label">/srv/repo</div>
@@ -211,6 +212,7 @@
<div id="offbox-quota-bar" style="max-width:560px;margin:.5rem 0"> <div id="offbox-quota-bar" style="max-width:560px;margin:.5rem 0">
<div class="stat-label" style="margin-bottom:.25rem">Tárhelykeret: 5 GB / 100 GB (5%)</div> <div class="stat-label" style="margin-bottom:.25rem">Tárhelykeret: 5 GB / 100 GB (5%)</div>
<div style="background:var(--border,#334);border-radius:4px;height:8px;overflow:hidden"> <div style="background:var(--border,#334);border-radius:4px;height:8px;overflow:hidden">
@@ -264,12 +266,16 @@
<div class="form-row"><label>Felhasználó</label><input type="text" name="user" class="form-input" value="felhom" placeholder="pl. felhom"></div> <div class="form-row"><label>Felhasználó</label><input type="text" name="user" class="form-input" value="felhom" placeholder="pl. felhom"></div>
<div class="form-row"><label>Tároló útvonala a célgépen</label><input type="text" name="repo_path" class="form-input" value="/srv/repo" placeholder="pl. /volume1/felhom-backup/repo"></div> <div class="form-row"><label>Tároló útvonala a célgépen</label><input type="text" name="repo_path" class="form-input" value="/srv/repo" placeholder="pl. /volume1/felhom-backup/repo"></div>
<div class="form-row"><label><input type="checkbox" name="enabled" checked> Napi automatikus távoli mentés engedélyezve</label></div> <div class="form-row"><label><input type="checkbox" name="enabled" checked> Napi automatikus távoli mentés engedélyezve</label></div>
<details class="form-row" id="offbox-key-advanced">
<summary style="cursor:pointer">Haladó</summary>
<div class="form-row"><label>SSH privát kulcs (üresen hagyva változatlan)</label> <div class="form-row"><label>SSH privát kulcs (üresen hagyva változatlan)</label>
<textarea name="ssh_key" class="form-input" rows="4" autocomplete="off" placeholder="-----BEGIN OPENSSH PRIVATE KEY-----"></textarea> <textarea name="ssh_key" class="form-input" rows="4" autocomplete="off" placeholder="-----BEGIN OPENSSH PRIVATE KEY-----"></textarea>
<span class="form-hint">A kulcsot 0600-as fájlba írjuk; sosem naplózzuk és nem tároljuk a beállításokban.</span></div> <span class="form-hint">A kulcsot 0600-as fájlba írjuk; sosem naplózzuk és nem tároljuk a beállításokban.</span></div>
<div class="form-row"><label>A célgép ismert-host sora (known_hosts) (üresen hagyva változatlan)</label> <div class="form-row"><label>A célgép ismert-host sora (known_hosts) (üresen hagyva változatlan)</label>
<textarea name="known_hosts" class="form-input" rows="2" autocomplete="off" placeholder="nas.local ssh-ed25519 AAAA…"></textarea> <textarea name="known_hosts" class="form-input" rows="2" autocomplete="off" placeholder="nas.local ssh-ed25519 AAAA…"></textarea>
<span class="form-hint">A host-kulcs rögzítése (no blind TOFU). Lekérdezhető: <span class="mono">ssh-keyscan -p &lt;port&gt; &lt;host&gt;</span></span></div> <span class="form-hint">A célgép saját kulcsa. Ezzel a doboz csak ehhez a géphez kapcsolódik. Így kérdezheted le: <span class="mono">ssh-keyscan -p &lt;port&gt; &lt;host&gt;</span></span></div>
</details>
<button type="submit" class="btn btn-sm btn-primary" style="margin-top:.5rem">Mentés</button> <button type="submit" class="btn btn-sm btn-primary" style="margin-top:.5rem">Mentés</button>
</form> </form>
</details> </details>
@@ -191,7 +191,7 @@
<h3 id="offbox-section" class="backup-tier-divider">Távoli mentés (3. mentés) — titkosított, offsite</h3> <h3 id="offbox-section" class="backup-tier-divider">Távoli mentés (3. mentés) — titkosított, offsite</h3>
<p class="form-hint" style="margin:-0.25rem 0 1rem">Az alkalmazás-mentések titkosított másolata egy távoli tárolóra — saját NAS vagy Felhom offsite tárhely — restic + SFTP kapcsolaton. A tároló csak titkosított adatot lát. Ez a 3-2-1 szabály „1 off-site" lába — független a helyi másodpéldánytól és a teljes rendszermentéstől.</p> <p class="form-hint" style="margin:-0.25rem 0 1rem">Az alkalmazásaid mentéseiről titkosított másolat készül egy távoli helyre: a Felhom tárhelyére vagy a saját NAS-odra. Ott csak titkosított adat van. Ez a másolat független a helyi másodpéldánytól és a teljes rendszermentéstől.</p>
<div class="card" style="margin-bottom:1.5rem"> <div class="card" style="margin-bottom:1.5rem">
<div class="stats-grid backup-page-cards"> <div class="stats-grid backup-page-cards">
@@ -203,6 +203,7 @@
<div class="stat-value" style="font-size:1.15rem">5 GB</div> <div class="stat-value" style="font-size:1.15rem">5 GB</div>
<div class="stat-label">Tároló méret · 10 pillanatkép</div> <div class="stat-label">Tároló méret · 10 pillanatkép</div>
</div> </div>
<div class="stat-card"> <div class="stat-card">
<div class="stat-value" style="font-size:1.05rem">felhom@nas.example</div> <div class="stat-value" style="font-size:1.05rem">felhom@nas.example</div>
<div class="stat-label">/srv/repo</div> <div class="stat-label">/srv/repo</div>
@@ -211,6 +212,7 @@
<div id="offbox-quota-bar" style="max-width:560px;margin:.5rem 0"> <div id="offbox-quota-bar" style="max-width:560px;margin:.5rem 0">
<div class="stat-label" style="margin-bottom:.25rem">Tárhelykeret: 5 GB / 100 GB (5%)</div> <div class="stat-label" style="margin-bottom:.25rem">Tárhelykeret: 5 GB / 100 GB (5%)</div>
<div style="background:var(--border,#334);border-radius:4px;height:8px;overflow:hidden"> <div style="background:var(--border,#334);border-radius:4px;height:8px;overflow:hidden">
@@ -264,12 +266,16 @@
<div class="form-row"><label>Felhasználó</label><input type="text" name="user" class="form-input" value="felhom" placeholder="pl. felhom"></div> <div class="form-row"><label>Felhasználó</label><input type="text" name="user" class="form-input" value="felhom" placeholder="pl. felhom"></div>
<div class="form-row"><label>Tároló útvonala a célgépen</label><input type="text" name="repo_path" class="form-input" value="/srv/repo" placeholder="pl. /volume1/felhom-backup/repo"></div> <div class="form-row"><label>Tároló útvonala a célgépen</label><input type="text" name="repo_path" class="form-input" value="/srv/repo" placeholder="pl. /volume1/felhom-backup/repo"></div>
<div class="form-row"><label><input type="checkbox" name="enabled" checked> Napi automatikus távoli mentés engedélyezve</label></div> <div class="form-row"><label><input type="checkbox" name="enabled" checked> Napi automatikus távoli mentés engedélyezve</label></div>
<details class="form-row" id="offbox-key-advanced">
<summary style="cursor:pointer">Haladó</summary>
<div class="form-row"><label>SSH privát kulcs (üresen hagyva változatlan)</label> <div class="form-row"><label>SSH privát kulcs (üresen hagyva változatlan)</label>
<textarea name="ssh_key" class="form-input" rows="4" autocomplete="off" placeholder="-----BEGIN OPENSSH PRIVATE KEY-----"></textarea> <textarea name="ssh_key" class="form-input" rows="4" autocomplete="off" placeholder="-----BEGIN OPENSSH PRIVATE KEY-----"></textarea>
<span class="form-hint">A kulcsot 0600-as fájlba írjuk; sosem naplózzuk és nem tároljuk a beállításokban.</span></div> <span class="form-hint">A kulcsot 0600-as fájlba írjuk; sosem naplózzuk és nem tároljuk a beállításokban.</span></div>
<div class="form-row"><label>A célgép ismert-host sora (known_hosts) (üresen hagyva változatlan)</label> <div class="form-row"><label>A célgép ismert-host sora (known_hosts) (üresen hagyva változatlan)</label>
<textarea name="known_hosts" class="form-input" rows="2" autocomplete="off" placeholder="nas.local ssh-ed25519 AAAA…"></textarea> <textarea name="known_hosts" class="form-input" rows="2" autocomplete="off" placeholder="nas.local ssh-ed25519 AAAA…"></textarea>
<span class="form-hint">A host-kulcs rögzítése (no blind TOFU). Lekérdezhető: <span class="mono">ssh-keyscan -p &lt;port&gt; &lt;host&gt;</span></span></div> <span class="form-hint">A célgép saját kulcsa. Ezzel a doboz csak ehhez a géphez kapcsolódik. Így kérdezheted le: <span class="mono">ssh-keyscan -p &lt;port&gt; &lt;host&gt;</span></span></div>
</details>
<button type="submit" class="btn btn-sm btn-primary" style="margin-top:.5rem">Mentés</button> <button type="submit" class="btn btn-sm btn-primary" style="margin-top:.5rem">Mentés</button>
</form> </form>
</details> </details>
@@ -0,0 +1,727 @@
<!DOCTYPE html>
<html lang="hu" class="no-js">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Távoli mentés beállítása — Felhom.eu</title>
<link rel="icon" type="image/svg+xml" href="/static/favicon.svg?v=0.247.0">
<link rel="stylesheet" href="/static/style.css?v=ASSET">
<meta name="csrf-token" content="tok">
<script>
document.documentElement.className=document.documentElement.className.replace('no-js','js');
function csrfHeaders(){var el=document.querySelector('meta[name="csrf-token"]');return el?{'X-CSRF-Token':el.content}:{};}
var fhEN=document.documentElement.lang==='en';
function fhNum(v,prec){var s=Number(v).toFixed(prec);return fhEN?s:s.replace('.',',');}
function fhIsoDate(iso){return fhEN?iso:iso.replace(/^(\d{4})-(\d{2})-(\d{2})/,'$1. $2. $3.');}
function fhDateTime(d){return fhIsoDate(d.toLocaleString('sv-SE',{timeZone:'Europe/Budapest',year:'numeric',month:'2-digit',day:'2-digit',hour:'2-digit',minute:'2-digit'}));}
function fhMonthDay(d){var s=d.toLocaleDateString('sv-SE',{timeZone:'Europe/Budapest',month:'2-digit',day:'2-digit'});return fhEN?s:s.replace(/^(\d{2})-(\d{2})$/,'$1. $2.');}
function felhomConfirm(el,question,onYes){
if(!el||el.dataset.fcOpen)return;
el.dataset.fcOpen='1';
var wrap=document.createElement('span');wrap.className='inline-confirm';
var q=document.createElement('span');q.className='inline-confirm-q';q.textContent=question;
var yes=document.createElement('button');yes.type='button';yes.className='btn btn-xs btn-danger';yes.textContent='Igen';
var no=document.createElement('button');no.type='button';no.className='btn btn-xs btn-outline';no.textContent='Mégse';
wrap.appendChild(q);wrap.appendChild(yes);wrap.appendChild(no);
el.style.display='none';el.parentNode.insertBefore(wrap,el.nextSibling);
function close(){wrap.remove();el.style.display='';delete el.dataset.fcOpen;}
no.addEventListener('click',close);
yes.addEventListener('click',function(){close();onYes();});
}
document.addEventListener('click',function(e){
var btn=e.target.closest?e.target.closest('[data-confirm]'):null;
if(!btn)return;
e.preventDefault();
felhomConfirm(btn,btn.getAttribute('data-confirm'),function(){
var form=btn.closest('form');
if(form){if(form.requestSubmit)form.requestSubmit(btn);else form.submit();}
});
});
document.addEventListener('click',function(e){
var btn=e.target.closest?e.target.closest('.nav-group-toggle'):null;
if(!btn)return;
var group=btn.closest('.nav-group');
if(!group)return;
var willOpen=!group.classList.contains('is-open');
document.querySelectorAll('.nav-group.is-open').forEach(function(g){
g.classList.remove('is-open');
var t=g.querySelector('.nav-group-toggle');
if(t)t.setAttribute('aria-expanded','false');
});
if(willOpen){group.classList.add('is-open');btn.setAttribute('aria-expanded','true');}
});
function showAlert(msg){var o=document.createElement('div');o.className='modal-overlay';o.id='alert-modal';o.addEventListener('click',function(e){if(e.target===o)o.remove();});var c=document.createElement('div');c.className='modal-card';c.innerHTML='<h3>Üzenet</h3><pre style="white-space:pre-wrap;word-break:break-word;background:var(--bg-2);padding:.75rem;border-radius:.375rem;font-size:.85rem;max-height:60vh;overflow-y:auto;user-select:text;cursor:text">'+msg.replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;')+'</pre><div class="modal-actions"><button class="btn btn-primary" onclick="document.getElementById(\'alert-modal\').remove()">OK</button></div>';o.appendChild(c);document.body.appendChild(o);}
</script>
</head>
<body>
<svg xmlns="http://www.w3.org/2000/svg" style="display:none" aria-hidden="true">
<symbol id="i-hard-drive" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 16h.01" /> <path d="M2.212 11.577a2 2 0 0 0-.212.896V18a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2v-5.527a2 2 0 0 0-.212-.896L18.55 5.11A2 2 0 0 0 16.76 4H7.24a2 2 0 0 0-1.79 1.11z" /> <path d="M21.946 12.013H2.054" /> <path d="M6 16h.01" /></symbol>
<symbol id="i-cpu" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 20v2" /> <path d="M12 2v2" /> <path d="M17 20v2" /> <path d="M17 2v2" /> <path d="M2 12h2" /> <path d="M2 17h2" /> <path d="M2 7h2" /> <path d="M20 12h2" /> <path d="M20 17h2" /> <path d="M20 7h2" /> <path d="M7 20v2" /> <path d="M7 2v2" /> <rect x="4" y="4" width="16" height="16" rx="2" /> <rect x="8" y="8" width="8" height="8" rx="1" /></symbol>
<symbol id="i-memory-stick" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 12v-2" /> <path d="M12 18v-2" /> <path d="M16 12v-2" /> <path d="M16 18v-2" /> <path d="M2 11h1.5" /> <path d="M20 18v-2" /> <path d="M20.5 11H22" /> <path d="M4 18v-2" /> <path d="M8 12v-2" /> <path d="M8 18v-2" /> <rect x="2" y="6" width="20" height="10" rx="2" /></symbol>
<symbol id="i-thermometer" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M14 4v10.54a4 4 0 1 1-4 0V4a2 2 0 0 1 4 0Z" /></symbol>
<symbol id="i-triangle-alert" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3" /> <path d="M12 9v4" /> <path d="M12 17h.01" /></symbol>
<symbol id="i-lock" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect width="18" height="11" x="3" y="11" rx="2" ry="2" /> <path d="M7 11V7a5 5 0 0 1 10 0v4" /></symbol>
<symbol id="i-cloud" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M17.5 19H9a7 7 0 1 1 6.71-9h1.79a4.5 4.5 0 1 1 0 9Z" /></symbol>
<symbol id="i-square" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect width="18" height="18" x="3" y="3" rx="2" /></symbol>
<symbol id="i-rotate-cw" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 12a9 9 0 1 1-9-9c2.52 0 4.93 1 6.74 2.74L21 8" /> <path d="M21 3v5h-5" /></symbol>
<symbol id="i-file-text" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M6 22a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h8a2.4 2.4 0 0 1 1.704.706l3.588 3.588A2.4 2.4 0 0 1 20 8v12a2 2 0 0 1-2 2z" /> <path d="M14 2v5a1 1 0 0 0 1 1h5" /> <path d="M10 9H8" /> <path d="M16 13H8" /> <path d="M16 17H8" /></symbol>
<symbol id="i-external-link" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M15 3h6v6" /> <path d="M10 14 21 3" /> <path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6" /></symbol>
<symbol id="i-shield" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M20 13c0 5-3.5 7.5-7.66 8.95a1 1 0 0 1-.67-.01C7.5 20.5 4 18 4 13V6a1 1 0 0 1 1-1c2 0 4.5-1.2 6.24-2.72a1.17 1.17 0 0 1 1.52 0C14.51 3.81 17 5 19 5a1 1 0 0 1 1 1z" /></symbol>
<symbol id="i-server" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect width="20" height="8" x="2" y="2" rx="2" ry="2" /> <rect width="20" height="8" x="2" y="14" rx="2" ry="2" /> <line x1="6" x2="6.01" y1="6" y2="6" /> <line x1="6" x2="6.01" y1="18" y2="18" /></symbol>
<symbol id="i-share" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="18" cy="5" r="3" /> <circle cx="6" cy="12" r="3" /> <circle cx="18" cy="19" r="3" /> <line x1="8.59" x2="15.42" y1="13.51" y2="17.49" /> <line x1="15.41" x2="8.59" y1="6.51" y2="10.49" /></symbol>
<symbol id="i-layout-grid" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect width="7" height="7" x="3" y="3" rx="1" /> <rect width="7" height="7" x="14" y="3" rx="1" /> <rect width="7" height="7" x="14" y="14" rx="1" /> <rect width="7" height="7" x="3" y="14" rx="1" /></symbol>
<symbol id="i-rocket" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M4.5 16.5c-1.5 1.26-2 5-2 5s3.74-.5 5-2c.71-.84.7-2.13-.09-2.91a2.18 2.18 0 0 0-2.91 0z" /> <path d="m12 15-3-3a22 22 0 0 1 2-3.95A12.88 12.88 0 0 1 22 2c0 2.72-.78 7.5-6 11a22.35 22.35 0 0 1-4 2z" /> <path d="M9 12H4s.55-3.03 2-4c1.62-1.08 5 0 5 0" /> <path d="M12 15v5s3.03-.55 4-2c1.08-1.62 0-5 0-5" /></symbol>
<symbol id="i-settings" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M9.671 4.136a2.34 2.34 0 0 1 4.659 0 2.34 2.34 0 0 0 3.319 1.915 2.34 2.34 0 0 1 2.33 4.033 2.34 2.34 0 0 0 0 3.831 2.34 2.34 0 0 1-2.33 4.033 2.34 2.34 0 0 0-3.319 1.915 2.34 2.34 0 0 1-4.659 0 2.34 2.34 0 0 0-3.32-1.915 2.34 2.34 0 0 1-2.33-4.033 2.34 2.34 0 0 0 0-3.831A2.34 2.34 0 0 1 6.35 6.051a2.34 2.34 0 0 0 3.319-1.915" /> <circle cx="12" cy="12" r="3" /></symbol>
<symbol id="i-bell" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10.268 21a2 2 0 0 0 3.464 0" /> <path d="M3.262 15.326A1 1 0 0 0 4 17h16a1 1 0 0 0 .74-1.673C19.41 13.956 18 12.499 18 8A6 6 0 0 0 6 8c0 4.499-1.411 5.956-2.738 7.326" /></symbol>
<symbol id="i-x" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M18 6 6 18" /> <path d="m6 6 12 12" /></symbol>
<symbol id="i-check" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M20 6 9 17l-5-5" /></symbol>
<symbol id="i-pencil" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21.174 6.812a1 1 0 0 0-3.986-3.987L3.842 16.174a2 2 0 0 0-.5.83l-1.321 4.352a.5.5 0 0 0 .623.622l4.353-1.32a2 2 0 0 0 .83-.497z" /> <path d="m15 5 4 4" /></symbol>
<symbol id="i-info" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10" /> <path d="M12 16v-4" /> <path d="M12 8h.01" /></symbol>
<symbol id="i-download" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 15V3" /> <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" /> <path d="m7 10 5 5 5-5" /></symbol>
<symbol id="i-upload" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 3v12" /> <path d="m17 8-5-5-5 5" /> <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" /></symbol>
<symbol id="i-trash-2" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 11v6" /> <path d="M14 11v6" /> <path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6" /> <path d="M3 6h18" /> <path d="M8 6V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2" /></symbol>
<symbol id="i-wrench" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M14.7 6.3a1 1 0 0 0 0 1.4l1.6 1.6a1 1 0 0 0 1.4 0l3.106-3.105c.32-.322.863-.22.983.218a6 6 0 0 1-8.259 7.057l-7.91 7.91a1 1 0 0 1-2.999-3l7.91-7.91a6 6 0 0 1 7.057-8.259c.438.12.54.662.219.984z" /></symbol>
<symbol id="i-link" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71" /> <path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71" /></symbol>
<symbol id="i-search" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m21 21-4.34-4.34" /> <circle cx="11" cy="11" r="8" /></symbol>
<symbol id="i-plus" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M5 12h14" /> <path d="M12 5v14" /></symbol>
<symbol id="i-chevron-down" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m6 9 6 6 6-6" /></symbol>
<symbol id="i-play" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M5 5a2 2 0 0 1 3.008-1.728l11.997 6.998a2 2 0 0 1 .003 3.458l-12 7A2 2 0 0 1 5 19z" /></symbol>
<symbol id="i-pause" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="14" y="3" width="5" height="18" rx="1" /> <rect x="5" y="3" width="5" height="18" rx="1" /></symbol>
<symbol id="i-menu" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M4 12h16" /> <path d="M4 6h16" /> <path d="M4 18h16" /></symbol>
</svg>
<header class="mobile-topbar">
<a href="/" class="mobile-topbar-logo"><img src="/static/felhom-logo.svg?v=0.247.0" alt="Felhom.eu"></a>
<button type="button" class="nav-burger" aria-expanded="false" aria-controls="sidebar" aria-label="Menü">
<svg class="ico"><use href="#i-menu"/></svg>
</button>
</header>
<div class="nav-backdrop" hidden></div>
<nav class="sidebar" id="sidebar">
<div class="sidebar-header">
<img src="/static/felhom-logo.svg?v=0.247.0" alt="Felhom.eu" class="sidebar-logo">
</div>
<ul class="nav-links">
<li><a href="/launcher" class=""><svg class="ico"><use href="#i-rocket"/></svg>Indítópult</a></li>
<li><a href="/dashboard" class=""><svg class="ico"><use href="#i-layout-grid"/></svg>Vezérlőpult</a></li>
<li><a href="/stacks" class=""><svg class="ico"><use href="#i-cloud"/></svg>Alkalmazások</a></li>
<li class="nav-group">
<button type="button" class="nav-group-toggle" aria-expanded="false" aria-controls="nav-group-storage"><svg class="ico"><use href="#i-hard-drive"/></svg>Tárhely<svg class="ico nav-chevron"><use href="#i-chevron-down"/></svg></button>
<ul id="nav-group-storage" class="nav-links nav-links-sub nav-links-nested">
<li><a href="/storage" class="">Meghajtók</a></li>
<li><a href="/storage/network" class="">Hálózati tárhely</a></li>
</ul>
</li>
<li class="nav-group is-open">
<button type="button" class="nav-group-toggle active" aria-expanded="true" aria-controls="nav-group-backups"><svg class="ico"><use href="#i-shield"/></svg>Biztonsági mentés<svg class="ico nav-chevron"><use href="#i-chevron-down"/></svg></button>
<ul id="nav-group-backups" class="nav-links nav-links-sub nav-links-nested">
<li><a href="/backups" class="">Áttekintés</a></li>
<li><a href="/backups/remote" class="active">Távoli mentés</a></li>
<li><a href="/backups/apps" class="">Alkalmazások</a></li>
<li><a href="/backups/restore" class="">Visszaállítás</a></li>
</ul>
</li>
<li class="nav-group">
<button type="button" class="nav-group-toggle" aria-expanded="false" aria-controls="nav-group-sharing"><svg class="ico"><use href="#i-share"/></svg>Megosztás<svg class="ico nav-chevron"><use href="#i-chevron-down"/></svg></button>
<ul id="nav-group-sharing" class="nav-links nav-links-sub nav-links-nested">
<li><a href="/sharing" class="">Hálózati megosztás</a></li>
</ul>
</li>
<li><a href="/monitoring" class=""><svg class="ico"><use href="#i-cpu"/></svg>Rendszermonitor</a></li>
<li><a href="/debug" class=""><svg class="ico"><use href="#i-wrench"/></svg>Hibakeresés</a></li>
</ul>
<div class="sidebar-bottom">
<div class="nav-group-label">Beállítások</div>
<ul class="nav-links nav-links-sub">
<li><a href="/settings" class=""><svg class="ico"><use href="#i-settings"/></svg>Rendszer</a></li>
<li><a href="/settings/notifications" class=""><svg class="ico"><use href="#i-bell"/></svg>Értesítések</a></li>
<li><a href="/settings/security" class=""><svg class="ico"><use href="#i-lock"/></svg>Biztonság és hozzáférés</a></li>
</ul>
<div class="sidebar-footer">
<span class="version">0.247.0</span><details class="lang-globe">
<summary class="lang-globe-btn" aria-label="Nyelv" title="Nyelv"><svg class="ico" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="12" cy="12" r="10" /><path d="M2 12h20" /><path d="M12 2a15.3 15.3 0 0 1 4 10 15.3 15.3 0 0 1-4 10 15.3 15.3 0 0 1-4-10 15.3 15.3 0 0 1 4-10z" /></svg></summary>
<ul class="lang-globe-menu">
<li><form method="POST" action="/settings/language"><input type="hidden" name="_csrf" value="CSRF"><input type="hidden" name="back" value="/i18n-fixture"><button type="submit" name="lang" value="hu" lang="hu" class="lang-globe-item current" aria-current="true">Magyar</button></form></li>
<li><form method="POST" action="/settings/language"><input type="hidden" name="_csrf" value="CSRF"><input type="hidden" name="back" value="/i18n-fixture"><button type="submit" name="lang" value="en" lang="en" class="lang-globe-item">English</button></form></li>
</ul>
</details>
<form method="post" action="/logout" class="logout-form"><button type="submit" class="logout-link">Kijelentkezés ↗</button></form>
</div>
</div>
</nav>
<main class="content">
<div class="page-header">
<h2>Biztonsági mentés — Távoli mentés</h2>
<span class="domain-badge">example.hu</span>
</div>
<h3 id="offbox-section" class="backup-tier-divider">Távoli mentés (3. mentés) — titkosított, offsite</h3>
<p class="form-hint" style="margin:-0.25rem 0 1rem">Az alkalmazásaid mentéseiről titkosított másolat készül egy távoli helyre: a Felhom tárhelyére vagy a saját NAS-odra. Ott csak titkosított adat van. Ez a másolat független a helyi másodpéldánytól és a teljes rendszermentéstől.</p>
<div class="card" style="margin-bottom:1.5rem">
<div class="stats-grid backup-page-cards">
<div class="stat-card ">
<div class="stat-value" id="offbox-status-value" data-status="ok" style="font-size:1.15rem">&#10003; Rendben</div>
<div class="stat-label">Utolsó távoli mentés<br><span class="relative-time"># napja</span></div>
</div>
<div class="stat-card">
<div class="stat-value" style="font-size:1.15rem">5 GB</div>
<div class="stat-label">Tároló méret · 10 pillanatkép</div>
</div>
<div class="stat-card">
<div class="stat-value" style="font-size:1.05rem">Felhom távoli tárhely (Németország)</div>
<div class="stat-label">Ide kerül a titkosított másolat.</div>
</div>
</div>
<details class="form-hint" id="offbox-advanced" style="margin:.5rem 0">
<summary style="cursor:pointer">Haladó</summary>
<span class="mono">u1-sub5@u1-sub5.your-storagebox.de /home/felhom-repo</span>
</details>
<div id="offbox-quota-bar" style="max-width:560px;margin:.5rem 0">
<div class="stat-label" style="margin-bottom:.25rem">Tárhelykeret: 5 GB / 100 GB (5%)</div>
<div style="background:var(--border,#334);border-radius:4px;height:8px;overflow:hidden">
<div style="height:8px;border-radius:4px;width:5%;background:var(--ok,#30a46c)"></div>
</div>
</div>
<div style="margin:.5rem 0">
<p class="form-hint" style="margin:0 0 .4rem">A helyreállítási kód letétbe helyezve.</p>
<a href="/backup/escrow" class="btn btn-sm btn-outline">Új helyreállítási kód készítése</a>
</div>
<div class="schedule-actions" style="margin-top:1rem">
<form method="POST" action="/backup/offbox/run" style="display:inline">
<button type="submit" class="btn btn-sm btn-primary">Távoli mentés most</button>
</form>
</div>
<div id="offbox-progress" class="alert alert-info" style="display:none;margin-top:.75rem">
<span id="offbox-progress-text"></span>
<div class="progress-bar-task" style="margin-top:.5rem">
<div id="offbox-progress-bar" class="progress-fill" style="width:0%"></div>
</div>
</div>
<h4 style="margin-top:1.25rem">Mely alkalmazások mentődnek a távoli tárolóra?</h4>
<p class="form-hint">Nincs telepített alkalmazás.</p>
<details style="margin-top:1rem">
<summary class="btn btn-xs btn-primary" style="cursor:pointer;display:inline-block">Távoli mentési cél beállítása</summary>
<form method="POST" action="/backup/offbox/config" style="margin-top:.75rem;padding:1rem;border:1px solid var(--border,#ddd);border-radius:6px;max-width:560px">
<div class="form-row"><label>Cél címe (IP vagy hosztnév — NAS vagy SFTP-kiszolgáló)</label><input type="text" name="host" class="form-input" value="u1-sub5.your-storagebox.de" placeholder="pl. 192.168.0.10"></div>
<div class="form-row"><label>SSH port</label><input type="number" name="port" class="form-input" value="22"></div>
<div class="form-row"><label>Felhasználó</label><input type="text" name="user" class="form-input" value="u1-sub5" placeholder="pl. felhom"></div>
<div class="form-row"><label>Tároló útvonala a célgépen</label><input type="text" name="repo_path" class="form-input" value="/home/felhom-repo" placeholder="pl. /volume1/felhom-backup/repo"></div>
<div class="form-row"><label><input type="checkbox" name="enabled" checked> Napi automatikus távoli mentés engedélyezve</label></div>
<details class="form-row" id="offbox-key-advanced">
<summary style="cursor:pointer">Haladó</summary>
<div class="form-row"><label>SSH privát kulcs (üresen hagyva változatlan)</label>
<textarea name="ssh_key" class="form-input" rows="4" autocomplete="off" placeholder="-----BEGIN OPENSSH PRIVATE KEY-----"></textarea>
<span class="form-hint">A kulcsot 0600-as fájlba írjuk; sosem naplózzuk és nem tároljuk a beállításokban.</span></div>
<div class="form-row"><label>A célgép ismert-host sora (known_hosts) (üresen hagyva változatlan)</label>
<textarea name="known_hosts" class="form-input" rows="2" autocomplete="off" placeholder="nas.local ssh-ed25519 AAAA…"></textarea>
<span class="form-hint">A célgép saját kulcsa. Ezzel a doboz csak ehhez a géphez kapcsolódik. Így kérdezheted le: <span class="mono">ssh-keyscan -p &lt;port&gt; &lt;host&gt;</span></span></div>
</details>
<button type="submit" class="btn btn-sm btn-primary" style="margin-top:.5rem">Mentés</button>
</form>
</details>
</div>
<script>
(function(){
var v = document.getElementById('offbox-status-value');
var box = document.getElementById('offbox-progress');
var text = document.getElementById('offbox-progress-text');
var bar = document.getElementById('offbox-progress-bar');
var GRACE = 5;
var quiet = 0;
var sawRunning = (v && v.dataset.status === 'running');
var timer = setInterval(function(){
fetch('/backup/offbox/status',{headers:{'Accept':'application/json'}})
.then(function(r){return r.json();})
.then(function(d){
if(!d) return;
var p = d.progress || {};
var running = (d.status === 'running') || p.active;
if(running){
sawRunning = true; quiet = 0;
if(p.active && box){
if(p.phase === 'dump'){
text.textContent = 'Adatbázisok mentése a pillanatképhez…';
bar.style.width = '100%';
box.className = 'alert alert-info'; box.style.display = '';
return;
}
if(p.phase === 'retention'){
text.textContent = 'Karbantartás: régi mentések rendezése a távoli tárolón…';
bar.style.width = '100%';
box.className = 'alert alert-info'; box.style.display = '';
return;
}
if(p.phase === 'shares'){
text.textContent = 'Megosztott mappák mentése…';
bar.style.width = '100%';
box.className = 'alert alert-info'; box.style.display = '';
return;
}
var label = p.current_app ? ('Mentés: ' + p.current_app) : 'Mentés folyamatban';
if(p.bytes_done > 0){
label += ' — ' + Math.round(p.percent) + '% (' + p.done_human + ' / ' + p.total_human + ')';
bar.style.width = Math.max(0, Math.min(100, p.percent)) + '%';
} else if(p.total_files > 0){
var fpct = Math.round((p.files_done / p.total_files) * 100);
label += ' — ' + p.files_done + ' / ' + p.total_files + ' fájl'
+ (p.total_human ? ' (' + p.total_human + ')' : '');
if(p.current_file){
var base = p.current_file.split('/').pop();
label += ' · feldolgozás alatt: ' + base;
}
if(p.elapsed_sec > 0){ label += ' · ' + p.elapsed_sec + ' mp'; }
bar.style.width = Math.max(0, Math.min(100, fpct)) + '%';
} else {
label += ' — a mentendő adatok felmérése…';
bar.style.width = '0%';
}
text.textContent = label;
box.style.display = '';
}
return;
}
if(sawRunning){ clearInterval(timer); location.reload(); return; }
if(++quiet >= GRACE){ clearInterval(timer); }
})
.catch(function(){ });
}, 3000);
})();
</script>
</main>
<script>
(function(){
var burger=document.querySelector('.nav-burger');
var sidebar=document.getElementById('sidebar');
var backdrop=document.querySelector('.nav-backdrop');
if(!burger||!sidebar||!backdrop)return;
function setOpen(open){
sidebar.classList.toggle('is-open',open);
document.body.classList.toggle('nav-open',open);
backdrop.hidden=!open;
burger.setAttribute('aria-expanded',open?'true':'false');
}
burger.addEventListener('click',function(){setOpen(!sidebar.classList.contains('is-open'));});
backdrop.addEventListener('click',function(){setOpen(false);});
document.addEventListener('keydown',function(e){
if(e.key==='Escape'&&sidebar.classList.contains('is-open'))setOpen(false);
});
})();
document.addEventListener('click', function(e) {
if (e.target.closest('a, button, .btn, input, select, textarea, .app-row-actions, .stack-detail-actions')) return;
var card = e.target.closest('[data-href]');
if (card) window.location.href = card.dataset.href;
});
async function checkBeforeDeploy(e, name) {
try {
var resp = await fetch('/api/stacks/' + name);
var data = await resp.json();
if (data.ok && data.data && data.data.deployed) {
e.preventDefault();
showAlert('Ez az alkalmazás már telepítve van.');
window.location.reload();
return false;
}
} catch(err) {}
return true;
}
async function syncTemplates() {
const btn = document.getElementById('sync-btn');
const toast = document.getElementById('sync-toast');
if (!btn) return;
const origText = btn.innerHTML;
btn.disabled = true;
btn.innerHTML = '↻ Frissítés...';
btn.classList.add('loading');
try {
const resp = await fetch('/api/sync', {
method: 'POST',
headers: Object.assign({'Content-Type': 'application/json'}, csrfHeaders())
});
const data = await resp.json();
if (toast) {
toast.textContent = data.ok ? (data.message || 'Sablonok frissítve') : ('Hiba: ' + (data.error || 'Ismeretlen hiba'));
toast.className = 'sync-toast ' + (data.ok ? 'sync-toast-ok' : 'sync-toast-err');
toast.style.display = 'block';
setTimeout(function() { toast.style.display = 'none'; }, 5000);
}
if (data.ok && data.data && (data.data.new_apps && data.data.new_apps.length > 0 || data.data.updated && data.data.updated.length > 0)) {
setTimeout(function() { window.location.reload(); }, 1500);
}
} catch (err) {
if (toast) {
toast.textContent = 'Hálózati hiba: ' + err.message;
toast.className = 'sync-toast sync-toast-err';
toast.style.display = 'block';
setTimeout(function() { toast.style.display = 'none'; }, 5000);
}
}
btn.innerHTML = origText;
btn.disabled = false;
btn.classList.remove('loading');
}
async function stackAction(event, name, action) {
const btn = event.currentTarget;
const origText = btn.textContent;
btn.disabled = true;
btn.textContent = 'Folyamatban...';
btn.classList.add('loading');
try {
const resp = await fetch('/api/stacks/' + name + '/' + action, {
method: 'POST',
headers: Object.assign({'Content-Type': 'application/json'}, csrfHeaders())
});
const data = await resp.json();
if (!data.ok) {
showAlert('Hiba: ' + (data.error || 'Ismeretlen hiba'));
btn.textContent = origText;
btn.disabled = false;
btn.classList.remove('loading');
return;
}
if (action === 'update') {
followUpdate(name, btn);
return;
}
window.location.reload();
} catch (err) {
showAlert('Hálózati hiba: ' + err.message);
btn.textContent = origText;
btn.disabled = false;
btn.classList.remove('loading');
}
}
function followUpdate(name, btn) {
var started = Date.now();
var timer = setInterval(async function() {
if (Date.now() - started > 30 * 60 * 1000) { clearInterval(timer); window.location.reload(); return; }
try {
var r = await fetch('/api/stacks/' + name);
var d = await r.json();
if (!d.ok || !d.data) return;
if (d.data.update_phase_label) btn.textContent = d.data.update_phase_label;
if (!d.data.updating) { clearInterval(timer); window.location.reload(); }
} catch (e) {}
}, 3000);
}
async function deleteOrphanStack(name) {
var modal = document.createElement('div');
modal.className = 'modal-overlay';
modal.id = 'delete-modal';
modal.innerHTML = '<div class="modal-card"><h3>Betöltés...</h3></div>';
modal.addEventListener('click', function(e) { if (e.target === modal) closeDeleteModal(); });
document.body.appendChild(modal);
try {
var resp = await fetch('/api/stacks/' + name + '/hdd-data');
var data = await resp.json();
var hddInfo = '';
var checkboxHTML = '';
if (data.ok && data.data && data.data.has_hdd_data) {
hddInfo = '<div class="modal-hdd-info"><strong>Felhasználói adatok a merevlemezen:</strong>';
data.data.hdd_paths.forEach(function(p) {
hddInfo += '<div class="modal-hdd-path">' + p.path + ' (' + (p.exists ? p.size_human : 'nem létezik') + ')</div>';
});
hddInfo += '</div>';
checkboxHTML = '<label class="modal-checkbox"><input type="checkbox" id="delete-hdd-check"> Felhasználói adatok törlése a merevlemezről</label>';
}
if (data.ok && data.data && data.data.userdata_kept && data.data.userdata_kept.length > 0) {
var udPaths = '';
data.data.userdata_kept.forEach(function(p) { udPaths += '<div class="modal-hdd-path">' + p.path + ' (' + p.size_human + ')</div>'; });
hddInfo += '<div class="modal-section" data-userdata-kept="true"><strong>A fájljaid ezekben a mappákban megmaradnak — a fájlböngészőben látod őket:</strong>' + udPaths + '</div>';
}
modal.querySelector('.modal-card').innerHTML =
'<h3>Alkalmazás törlése: ' + name + '</h3>' +
'<p style="color:var(--text-2);font-size:.9rem;margin-bottom:.75rem">Ez a művelet eltávolítja a konténereket, a köteteket és a konfigurációs fájlokat.</p>' +
'<div class="alert alert-warning" style="margin-bottom:.75rem">Ez a művelet nem visszavonható!</div>' +
hddInfo + checkboxHTML +
'<div class="modal-actions">' +
'<button class="btn btn-outline" onclick="closeDeleteModal()">Mégsem</button>' +
'<button class="btn btn-danger" id="confirm-delete-btn" onclick="confirmDelete(\'' + name + '\')">Törlés</button>' +
'</div>';
} catch (err) {
modal.querySelector('.modal-card').innerHTML =
'<h3>Hiba</h3><p style="color:var(--text-2)">Nem sikerült lekérni az adatokat: ' + err.message + '</p>' +
'<div class="modal-actions"><button class="btn btn-outline" onclick="closeDeleteModal()">Bezárás</button></div>';
}
}
function closeDeleteModal() {
var modal = document.getElementById('delete-modal');
if (modal) modal.remove();
}
async function confirmDelete(name) {
var btn = document.getElementById('confirm-delete-btn');
var checkbox = document.getElementById('delete-hdd-check');
var removeHDD = checkbox ? checkbox.checked : false;
btn.disabled = true;
btn.textContent = 'Törlés folyamatban...';
try {
var resp = await fetch('/api/stacks/' + name, {
method: 'DELETE',
headers: Object.assign({'Content-Type': 'application/json'}, csrfHeaders()),
body: JSON.stringify({remove_hdd_data: removeHDD})
});
var data = await resp.json();
if (data.ok) {
var modal = document.getElementById('delete-modal');
var removedInfo = '';
if (data.data && data.data.hdd_paths_removed && data.data.hdd_paths_removed.length > 0) {
removedInfo = '<p style="color:var(--text-2);font-size:.85rem;margin-top:.5rem">Törölt adatok: ' + data.data.hdd_paths_removed.join(', ') + '</p>';
}
var preservedInfo = '';
if (data.data && data.data.hdd_paths_preserved && data.data.hdd_paths_preserved.length > 0) {
preservedInfo = '<p style="color:var(--text-2);font-size:.85rem;margin-top:.5rem">Megőrzött adatok: ' + data.data.hdd_paths_preserved.join(', ') + '</p>';
}
if (data.data && data.data.hdd_note) {
preservedInfo += '<p style="color:var(--text-2);font-size:.85rem;margin-top:.5rem">' + data.data.hdd_note + '</p>';
}
if (data.data && data.data.userdata_kept && data.data.userdata_kept.length > 0) {
preservedInfo += '<p style="color:var(--text-2);font-size:.85rem;margin-top:.5rem" data-userdata-kept="true">A fájljaid ezekben a mappákban megmaradnak — a fájlböngészőben látod őket: ' + data.data.userdata_kept.join(', ') + '</p>';
}
modal.querySelector('.modal-card').innerHTML =
'<h3>Sikeresen törölve!</h3>' +
'<p style="color:var(--text-2)">Az alkalmazás (' + name + ') törölve lett.</p>' +
removedInfo + preservedInfo +
'<div class="modal-actions"><button class="btn btn-primary" onclick="window.location.href=\'/stacks\'">Bezárás</button></div>';
} else {
showAlert('Hiba: ' + (data.error || 'Ismeretlen hiba'));
btn.disabled = false;
btn.textContent = 'Törlés';
}
} catch (err) {
showAlert('Hálózati hiba: ' + err.message);
btn.disabled = false;
btn.textContent = 'Törlés';
}
}
async function removeStack(name) {
var modal = document.createElement('div');
modal.className = 'modal-overlay';
modal.id = 'remove-modal';
modal.innerHTML = '<div class="modal-card"><h3>Betöltés...</h3></div>';
modal.addEventListener('click', function(e) { if (e.target === modal) closeRemoveModal(); });
document.body.appendChild(modal);
try {
var [hddResp, backupResp] = await Promise.all([
fetch('/api/stacks/' + name + '/hdd-data').then(function(r) { return r.json(); }),
fetch('/api/stacks/' + name + '/backup-data').then(function(r) { return r.json(); })
]);
var sections = '';
var keepOnly = hddResp.ok && hddResp.data && hddResp.data.keep_data_only;
if (keepOnly) {
sections += '<div class="alert alert-info" style="margin-bottom:.75rem" data-remove-keep-only="true">Az ügyfélszolgálat foglalkozik ezzel az alkalmazással, ezért az adatai megmaradnak: most csak magát az alkalmazást távolíthatod el.</div>';
}
sections += '<div class="modal-section">' +
'<strong>Mindig törlődik:</strong>' +
'<ul style="margin:.25rem 0;padding-left:1.2rem;color:var(--text-2);font-size:.85rem">' +
'<li>Docker kötetek (adatbázis, alkalmazás konfiguráció)</li>' +
'<li>Telepítési konfiguráció (app.yaml)</li>' +
'<li>Másodlagos mentés ütemezése</li>' +
'</ul></div>';
var hddCheckbox = '';
if (!keepOnly && hddResp.ok && hddResp.data && hddResp.data.has_hdd_data) {
var hddPaths = '';
hddResp.data.hdd_paths.forEach(function(p) {
hddPaths += '<div class="modal-hdd-path">' + p.path + ' (' + (p.exists ? p.size_human : 'nem létezik') + ')</div>';
});
sections += '<div class="modal-section">' +
'<strong>Felhasználói adatok a merevlemezen:</strong>' + hddPaths +
'<label class="modal-checkbox"><input type="checkbox" id="remove-hdd-check"> Felhasználói adatok törlése</label>' +
'<div class="alert alert-info" style="margin-top:.5rem;font-size:.8rem" id="remove-hdd-keep-warning">' +
'Ha újratelepíted az alkalmazást, az adatokat újra importálnod kell, mivel az adatbázis törlődik. A megtartott adatok a továbbiakban NEM lesznek automatikusan mentve.' +
'</div></div>';
hddCheckbox = '\x3Cscript>document.getElementById("remove-hdd-check").addEventListener("change",function(){document.getElementById("remove-hdd-keep-warning").style.display=this.checked?"none":"";});<\/script>';
}
var backupCheckbox = '';
if (!keepOnly && backupResp.ok && backupResp.data && backupResp.data.has_backups) {
var bkPaths = '';
backupResp.data.backup_paths.forEach(function(p) {
if (p.exists) bkPaths += '<div class="modal-hdd-path">' + p.path + ' (' + p.size_human + ')</div>';
});
if (bkPaths) {
sections += '<div class="modal-section">' +
'<strong>Mentési adatok:</strong>' + bkPaths +
'<label class="modal-checkbox"><input type="checkbox" id="remove-backup-check"> Mentési adatok törlése</label>' +
'<div class="alert alert-info" style="margin-top:.5rem;font-size:.8rem">' +
'Az éjszakai restic pillanatképek nem törölhetők egyenként — a megőrzési szabályok szerint automatikusan elavulnak.' +
'</div></div>';
}
}
if (hddResp.ok && hddResp.data && hddResp.data.userdata_kept && hddResp.data.userdata_kept.length > 0) {
var udPaths = '';
hddResp.data.userdata_kept.forEach(function(p) { udPaths += '<div class="modal-hdd-path">' + p.path + ' (' + p.size_human + ')</div>'; });
sections += '<div class="modal-section" data-userdata-kept="true"><strong>A fájljaid ezekben a mappákban megmaradnak — a fájlböngészőben látod őket:</strong>' + udPaths + '</div>';
}
modal.querySelector('.modal-card').innerHTML =
'<h3>Alkalmazás eltávolítása: ' + name + '</h3>' +
'<p style="color:var(--text-2);font-size:.9rem;margin-bottom:.75rem">Az alkalmazás visszaáll "Nincs telepítve" állapotba. A sablon megmarad, újratelepíthető.</p>' +
'<div class="alert alert-warning" style="margin-bottom:.75rem">Ez a művelet nem visszavonható!</div>' +
sections +
'<div class="modal-actions">' +
'<button class="btn btn-outline" onclick="closeRemoveModal()">Mégsem</button>' +
'<button class="btn btn-danger" id="confirm-remove-btn" onclick="confirmRemoveStack(\'' + name + '\')">Eltávolítás</button>' +
'</div>' + hddCheckbox;
} catch (err) {
modal.querySelector('.modal-card').innerHTML =
'<h3>Hiba</h3><p style="color:var(--text-2)">Nem sikerült lekérni az adatokat: ' + err.message + '</p>' +
'<div class="modal-actions"><button class="btn btn-outline" onclick="closeRemoveModal()">Bezárás</button></div>';
}
}
function closeRemoveModal() {
var modal = document.getElementById('remove-modal');
if (modal) modal.remove();
}
async function confirmRemoveStack(name) {
var btn = document.getElementById('confirm-remove-btn');
var hddCheck = document.getElementById('remove-hdd-check');
var backupCheck = document.getElementById('remove-backup-check');
var removeHDD = hddCheck ? hddCheck.checked : false;
var removeBackups = backupCheck ? backupCheck.checked : false;
btn.disabled = true;
btn.textContent = 'Eltávolítás folyamatban...';
try {
var resp = await fetch('/api/stacks/' + name + '/remove', {
method: 'POST',
headers: Object.assign({'Content-Type': 'application/json'}, csrfHeaders()),
body: JSON.stringify({remove_hdd_data: removeHDD, remove_backups: removeBackups})
});
var data = await resp.json();
if (data.ok) {
var modal = document.getElementById('remove-modal');
var removedInfo = '';
if (data.data && data.data.hdd_paths_removed && data.data.hdd_paths_removed.length > 0) {
removedInfo += '<p style="color:var(--text-2);font-size:.85rem;margin-top:.5rem">Törölt adatok: ' + data.data.hdd_paths_removed.join(', ') + '</p>';
}
if (data.data && data.data.backup_paths_removed && data.data.backup_paths_removed.length > 0) {
removedInfo += '<p style="color:var(--text-2);font-size:.85rem;margin-top:.5rem">Törölt mentések: ' + data.data.backup_paths_removed.join(', ') + '</p>';
}
var preservedInfo = '';
if (data.data && data.data.hdd_paths_preserved && data.data.hdd_paths_preserved.length > 0) {
preservedInfo = '<p style="color:var(--text-2);font-size:.85rem;margin-top:.5rem">Megőrzött adatok: ' + data.data.hdd_paths_preserved.join(', ') + '</p>';
}
if (data.data && data.data.hdd_note) {
preservedInfo += '<p style="color:var(--text-2);font-size:.85rem;margin-top:.5rem">' + data.data.hdd_note + '</p>';
}
if (data.data && data.data.userdata_kept && data.data.userdata_kept.length > 0) {
preservedInfo += '<p style="color:var(--text-2);font-size:.85rem;margin-top:.5rem" data-userdata-kept="true">A fájljaid ezekben a mappákban megmaradnak — a fájlböngészőben látod őket: ' + data.data.userdata_kept.join(', ') + '</p>';
}
if (data.data && data.data.backup_paths_refused && data.data.backup_paths_refused.length > 0) {
preservedInfo += '<p style="color:var(--text-2);font-size:.85rem;margin-top:.5rem">Nem törölt mentések: ' + data.data.backup_paths_refused.join('; ') + '</p>';
}
modal.querySelector('.modal-card').innerHTML =
'<h3>Sikeresen eltávolítva!</h3>' +
'<p style="color:var(--text-2)">Az alkalmazás (' + name + ') eltávolítva. Újratelepíthető a Telepítés gombbal.</p>' +
removedInfo + preservedInfo +
'<div class="modal-actions"><button class="btn btn-primary" onclick="window.location.href=\'/stacks\'">Bezárás</button></div>';
} else {
showAlert('Hiba: ' + (data.error || 'Ismeretlen hiba'));
btn.disabled = false;
btn.textContent = 'Eltávolítás';
}
} catch (err) {
showAlert('Hálózati hiba: ' + err.message);
btn.disabled = false;
btn.textContent = 'Eltávolítás';
}
}
</script>
</body>
</html>
@@ -201,7 +201,7 @@
<h3 id="offbox-section" class="backup-tier-divider">Távoli mentés (3. mentés) — titkosított, offsite</h3> <h3 id="offbox-section" class="backup-tier-divider">Távoli mentés (3. mentés) — titkosított, offsite</h3>
<p class="form-hint" style="margin:-0.25rem 0 1rem">Az alkalmazás-mentések titkosított másolata egy távoli tárolóra — saját NAS vagy Felhom offsite tárhely — restic + SFTP kapcsolaton. A tároló csak titkosított adatot lát. Ez a 3-2-1 szabály „1 off-site" lába — független a helyi másodpéldánytól és a teljes rendszermentéstől.</p> <p class="form-hint" style="margin:-0.25rem 0 1rem">Az alkalmazásaid mentéseiről titkosított másolat készül egy távoli helyre: a Felhom tárhelyére vagy a saját NAS-odra. Ott csak titkosított adat van. Ez a másolat független a helyi másodpéldánytól és a teljes rendszermentéstől.</p>
<div class="card" style="margin-bottom:1.5rem"> <div class="card" style="margin-bottom:1.5rem">
<div class="stats-grid backup-page-cards"> <div class="stats-grid backup-page-cards">
@@ -213,6 +213,7 @@
<div class="stat-value" style="font-size:1.15rem">–</div> <div class="stat-value" style="font-size:1.15rem">–</div>
<div class="stat-label">Tároló méret · 10 pillanatkép</div> <div class="stat-label">Tároló méret · 10 pillanatkép</div>
</div> </div>
<div class="stat-card"> <div class="stat-card">
<div class="stat-value" style="font-size:1.05rem">felhom@nas.example</div> <div class="stat-value" style="font-size:1.05rem">felhom@nas.example</div>
<div class="stat-label">/srv/repo</div> <div class="stat-label">/srv/repo</div>
@@ -221,6 +222,7 @@
<div id="offbox-quota-bar" style="max-width:560px;margin:.5rem 0"> <div id="offbox-quota-bar" style="max-width:560px;margin:.5rem 0">
<div class="stat-label" style="margin-bottom:.25rem">Tárhelykeret: 0 / 10 GB (10%)</div> <div class="stat-label" style="margin-bottom:.25rem">Tárhelykeret: 0 / 10 GB (10%)</div>
<div style="background:var(--border,#334);border-radius:4px;height:8px;overflow:hidden"> <div style="background:var(--border,#334);border-radius:4px;height:8px;overflow:hidden">
@@ -278,12 +280,16 @@
<div class="form-row"><label>Felhasználó</label><input type="text" name="user" class="form-input" value="felhom" placeholder="pl. felhom"></div> <div class="form-row"><label>Felhasználó</label><input type="text" name="user" class="form-input" value="felhom" placeholder="pl. felhom"></div>
<div class="form-row"><label>Tároló útvonala a célgépen</label><input type="text" name="repo_path" class="form-input" value="/srv/repo" placeholder="pl. /volume1/felhom-backup/repo"></div> <div class="form-row"><label>Tároló útvonala a célgépen</label><input type="text" name="repo_path" class="form-input" value="/srv/repo" placeholder="pl. /volume1/felhom-backup/repo"></div>
<div class="form-row"><label><input type="checkbox" name="enabled" checked> Napi automatikus távoli mentés engedélyezve</label></div> <div class="form-row"><label><input type="checkbox" name="enabled" checked> Napi automatikus távoli mentés engedélyezve</label></div>
<details class="form-row" id="offbox-key-advanced">
<summary style="cursor:pointer">Haladó</summary>
<div class="form-row"><label>SSH privát kulcs (üresen hagyva változatlan)</label> <div class="form-row"><label>SSH privát kulcs (üresen hagyva változatlan)</label>
<textarea name="ssh_key" class="form-input" rows="4" autocomplete="off" placeholder="-----BEGIN OPENSSH PRIVATE KEY-----"></textarea> <textarea name="ssh_key" class="form-input" rows="4" autocomplete="off" placeholder="-----BEGIN OPENSSH PRIVATE KEY-----"></textarea>
<span class="form-hint">A kulcsot 0600-as fájlba írjuk; sosem naplózzuk és nem tároljuk a beállításokban.</span></div> <span class="form-hint">A kulcsot 0600-as fájlba írjuk; sosem naplózzuk és nem tároljuk a beállításokban.</span></div>
<div class="form-row"><label>A célgép ismert-host sora (known_hosts) (üresen hagyva változatlan)</label> <div class="form-row"><label>A célgép ismert-host sora (known_hosts) (üresen hagyva változatlan)</label>
<textarea name="known_hosts" class="form-input" rows="2" autocomplete="off" placeholder="nas.local ssh-ed25519 AAAA…"></textarea> <textarea name="known_hosts" class="form-input" rows="2" autocomplete="off" placeholder="nas.local ssh-ed25519 AAAA…"></textarea>
<span class="form-hint">A host-kulcs rögzítése (no blind TOFU). Lekérdezhető: <span class="mono">ssh-keyscan -p &lt;port&gt; &lt;host&gt;</span></span></div> <span class="form-hint">A célgép saját kulcsa. Ezzel a doboz csak ehhez a géphez kapcsolódik. Így kérdezheted le: <span class="mono">ssh-keyscan -p &lt;port&gt; &lt;host&gt;</span></span></div>
</details>
<button type="submit" class="btn btn-sm btn-primary" style="margin-top:.5rem">Mentés</button> <button type="submit" class="btn btn-sm btn-primary" style="margin-top:.5rem">Mentés</button>
</form> </form>
</details> </details>
@@ -191,7 +191,7 @@
<h3 id="offbox-section" class="backup-tier-divider">Távoli mentés (3. mentés) — titkosított, offsite</h3> <h3 id="offbox-section" class="backup-tier-divider">Távoli mentés (3. mentés) — titkosított, offsite</h3>
<p class="form-hint" style="margin:-0.25rem 0 1rem">Az alkalmazás-mentések titkosított másolata egy távoli tárolóra — saját NAS vagy Felhom offsite tárhely — restic + SFTP kapcsolaton. A tároló csak titkosított adatot lát. Ez a 3-2-1 szabály „1 off-site" lába — független a helyi másodpéldánytól és a teljes rendszermentéstől.</p> <p class="form-hint" style="margin:-0.25rem 0 1rem">Az alkalmazásaid mentéseiről titkosított másolat készül egy távoli helyre: a Felhom tárhelyére vagy a saját NAS-odra. Ott csak titkosított adat van. Ez a másolat független a helyi másodpéldánytól és a teljes rendszermentéstől.</p>
<div class="card" style="margin-bottom:1.5rem"> <div class="card" style="margin-bottom:1.5rem">
<div class="stats-grid backup-page-cards"> <div class="stats-grid backup-page-cards">
@@ -203,6 +203,7 @@
<div class="stat-value" style="font-size:1.15rem">5 GB</div> <div class="stat-value" style="font-size:1.15rem">5 GB</div>
<div class="stat-label">Tároló méret · 10 pillanatkép</div> <div class="stat-label">Tároló méret · 10 pillanatkép</div>
</div> </div>
<div class="stat-card"> <div class="stat-card">
<div class="stat-value" style="font-size:1.05rem">felhom@nas.example</div> <div class="stat-value" style="font-size:1.05rem">felhom@nas.example</div>
<div class="stat-label">/srv/repo</div> <div class="stat-label">/srv/repo</div>
@@ -211,6 +212,7 @@
<div id="offbox-quota-bar" style="max-width:560px;margin:.5rem 0"> <div id="offbox-quota-bar" style="max-width:560px;margin:.5rem 0">
<div class="stat-label" style="margin-bottom:.25rem">Tárhelykeret: 5 GB / 100 GB (5%)</div> <div class="stat-label" style="margin-bottom:.25rem">Tárhelykeret: 5 GB / 100 GB (5%)</div>
<div style="background:var(--border,#334);border-radius:4px;height:8px;overflow:hidden"> <div style="background:var(--border,#334);border-radius:4px;height:8px;overflow:hidden">
@@ -262,12 +264,16 @@
<div class="form-row"><label>Felhasználó</label><input type="text" name="user" class="form-input" value="felhom" placeholder="pl. felhom"></div> <div class="form-row"><label>Felhasználó</label><input type="text" name="user" class="form-input" value="felhom" placeholder="pl. felhom"></div>
<div class="form-row"><label>Tároló útvonala a célgépen</label><input type="text" name="repo_path" class="form-input" value="/srv/repo" placeholder="pl. /volume1/felhom-backup/repo"></div> <div class="form-row"><label>Tároló útvonala a célgépen</label><input type="text" name="repo_path" class="form-input" value="/srv/repo" placeholder="pl. /volume1/felhom-backup/repo"></div>
<div class="form-row"><label><input type="checkbox" name="enabled" checked> Napi automatikus távoli mentés engedélyezve</label></div> <div class="form-row"><label><input type="checkbox" name="enabled" checked> Napi automatikus távoli mentés engedélyezve</label></div>
<details class="form-row" id="offbox-key-advanced">
<summary style="cursor:pointer">Haladó</summary>
<div class="form-row"><label>SSH privát kulcs (üresen hagyva változatlan)</label> <div class="form-row"><label>SSH privát kulcs (üresen hagyva változatlan)</label>
<textarea name="ssh_key" class="form-input" rows="4" autocomplete="off" placeholder="-----BEGIN OPENSSH PRIVATE KEY-----"></textarea> <textarea name="ssh_key" class="form-input" rows="4" autocomplete="off" placeholder="-----BEGIN OPENSSH PRIVATE KEY-----"></textarea>
<span class="form-hint">A kulcsot 0600-as fájlba írjuk; sosem naplózzuk és nem tároljuk a beállításokban.</span></div> <span class="form-hint">A kulcsot 0600-as fájlba írjuk; sosem naplózzuk és nem tároljuk a beállításokban.</span></div>
<div class="form-row"><label>A célgép ismert-host sora (known_hosts) (üresen hagyva változatlan)</label> <div class="form-row"><label>A célgép ismert-host sora (known_hosts) (üresen hagyva változatlan)</label>
<textarea name="known_hosts" class="form-input" rows="2" autocomplete="off" placeholder="nas.local ssh-ed25519 AAAA…"></textarea> <textarea name="known_hosts" class="form-input" rows="2" autocomplete="off" placeholder="nas.local ssh-ed25519 AAAA…"></textarea>
<span class="form-hint">A host-kulcs rögzítése (no blind TOFU). Lekérdezhető: <span class="mono">ssh-keyscan -p &lt;port&gt; &lt;host&gt;</span></span></div> <span class="form-hint">A célgép saját kulcsa. Ezzel a doboz csak ehhez a géphez kapcsolódik. Így kérdezheted le: <span class="mono">ssh-keyscan -p &lt;port&gt; &lt;host&gt;</span></span></div>
</details>
<button type="submit" class="btn btn-sm btn-primary" style="margin-top:.5rem">Mentés</button> <button type="submit" class="btn btn-sm btn-primary" style="margin-top:.5rem">Mentés</button>
</form> </form>
</details> </details>
@@ -191,7 +191,7 @@
<h3 id="offbox-section" class="backup-tier-divider">Távoli mentés (3. mentés) — titkosított, offsite</h3> <h3 id="offbox-section" class="backup-tier-divider">Távoli mentés (3. mentés) — titkosított, offsite</h3>
<p class="form-hint" style="margin:-0.25rem 0 1rem">Az alkalmazás-mentések titkosított másolata egy távoli tárolóra — saját NAS vagy Felhom offsite tárhely — restic + SFTP kapcsolaton. A tároló csak titkosított adatot lát. Ez a 3-2-1 szabály „1 off-site" lába — független a helyi másodpéldánytól és a teljes rendszermentéstől.</p> <p class="form-hint" style="margin:-0.25rem 0 1rem">Az alkalmazásaid mentéseiről titkosított másolat készül egy távoli helyre: a Felhom tárhelyére vagy a saját NAS-odra. Ott csak titkosított adat van. Ez a másolat független a helyi másodpéldánytól és a teljes rendszermentéstől.</p>
<div class="card" style="margin-bottom:1.5rem"> <div class="card" style="margin-bottom:1.5rem">
<div class="stats-grid backup-page-cards"> <div class="stats-grid backup-page-cards">
@@ -203,6 +203,7 @@
<div class="stat-value" style="font-size:1.15rem">5 GB</div> <div class="stat-value" style="font-size:1.15rem">5 GB</div>
<div class="stat-label">Tároló méret · 10 pillanatkép</div> <div class="stat-label">Tároló méret · 10 pillanatkép</div>
</div> </div>
<div class="stat-card"> <div class="stat-card">
<div class="stat-value" style="font-size:1.05rem">felhom@nas.example</div> <div class="stat-value" style="font-size:1.05rem">felhom@nas.example</div>
<div class="stat-label">/srv/repo</div> <div class="stat-label">/srv/repo</div>
@@ -211,6 +212,7 @@
<div id="offbox-quota-bar" style="max-width:560px;margin:.5rem 0"> <div id="offbox-quota-bar" style="max-width:560px;margin:.5rem 0">
<div class="stat-label" style="margin-bottom:.25rem">Tárhelykeret: 5 GB / 100 GB (5%)</div> <div class="stat-label" style="margin-bottom:.25rem">Tárhelykeret: 5 GB / 100 GB (5%)</div>
<div style="background:var(--border,#334);border-radius:4px;height:8px;overflow:hidden"> <div style="background:var(--border,#334);border-radius:4px;height:8px;overflow:hidden">
@@ -262,12 +264,16 @@
<div class="form-row"><label>Felhasználó</label><input type="text" name="user" class="form-input" value="felhom" placeholder="pl. felhom"></div> <div class="form-row"><label>Felhasználó</label><input type="text" name="user" class="form-input" value="felhom" placeholder="pl. felhom"></div>
<div class="form-row"><label>Tároló útvonala a célgépen</label><input type="text" name="repo_path" class="form-input" value="/srv/repo" placeholder="pl. /volume1/felhom-backup/repo"></div> <div class="form-row"><label>Tároló útvonala a célgépen</label><input type="text" name="repo_path" class="form-input" value="/srv/repo" placeholder="pl. /volume1/felhom-backup/repo"></div>
<div class="form-row"><label><input type="checkbox" name="enabled" checked> Napi automatikus távoli mentés engedélyezve</label></div> <div class="form-row"><label><input type="checkbox" name="enabled" checked> Napi automatikus távoli mentés engedélyezve</label></div>
<details class="form-row" id="offbox-key-advanced">
<summary style="cursor:pointer">Haladó</summary>
<div class="form-row"><label>SSH privát kulcs (üresen hagyva változatlan)</label> <div class="form-row"><label>SSH privát kulcs (üresen hagyva változatlan)</label>
<textarea name="ssh_key" class="form-input" rows="4" autocomplete="off" placeholder="-----BEGIN OPENSSH PRIVATE KEY-----"></textarea> <textarea name="ssh_key" class="form-input" rows="4" autocomplete="off" placeholder="-----BEGIN OPENSSH PRIVATE KEY-----"></textarea>
<span class="form-hint">A kulcsot 0600-as fájlba írjuk; sosem naplózzuk és nem tároljuk a beállításokban.</span></div> <span class="form-hint">A kulcsot 0600-as fájlba írjuk; sosem naplózzuk és nem tároljuk a beállításokban.</span></div>
<div class="form-row"><label>A célgép ismert-host sora (known_hosts) (üresen hagyva változatlan)</label> <div class="form-row"><label>A célgép ismert-host sora (known_hosts) (üresen hagyva változatlan)</label>
<textarea name="known_hosts" class="form-input" rows="2" autocomplete="off" placeholder="nas.local ssh-ed25519 AAAA…"></textarea> <textarea name="known_hosts" class="form-input" rows="2" autocomplete="off" placeholder="nas.local ssh-ed25519 AAAA…"></textarea>
<span class="form-hint">A host-kulcs rögzítése (no blind TOFU). Lekérdezhető: <span class="mono">ssh-keyscan -p &lt;port&gt; &lt;host&gt;</span></span></div> <span class="form-hint">A célgép saját kulcsa. Ezzel a doboz csak ehhez a géphez kapcsolódik. Így kérdezheted le: <span class="mono">ssh-keyscan -p &lt;port&gt; &lt;host&gt;</span></span></div>
</details>
<button type="submit" class="btn btn-sm btn-primary" style="margin-top:.5rem">Mentés</button> <button type="submit" class="btn btn-sm btn-primary" style="margin-top:.5rem">Mentés</button>
</form> </form>
</details> </details>
@@ -186,14 +186,14 @@
<div class="settings-card" id="pf-card"> <div class="settings-card" id="pf-card">
<h3>1. Előfeltételek ellenőrzése</h3> <h3>Előfeltételek ellenőrzése</h3>
<div id="pf-error" class="alert alert-error" style="display:none;margin-bottom:1rem"></div> <div id="pf-error" class="alert alert-error" style="display:none;margin-bottom:1rem"></div>
<div id="pf-list"><p class="form-hint">Ellenőrzés folyamatban…</p></div> <div id="pf-list"><p class="form-hint">Ellenőrzés folyamatban…</p></div>
</div> </div>
<div class="settings-card" id="warn-card" style="display:none"> <div class="settings-card" id="warn-card" style="display:none">
<h3>2. Fontos tudnivalók</h3> <h3>Fontos tudnivalók</h3>
<div class="alert alert-warning" style="margin-bottom:.75rem"> <div class="alert alert-warning" style="margin-bottom:.75rem">
A helyreállítási kód a mentéseid utolsó kulcsa. Pontosan egyszer jelenik meg — a rendszer A helyreállítási kód a mentéseid utolsó kulcsa. Pontosan egyszer jelenik meg — a rendszer
sehol nem tárolja, és a Felhom sem ismeri. Ha a szerver megsemmisül, a távoli mentések CSAK sehol nem tárolja, és a Felhom sem ismeri. Ha a szerver megsemmisül, a távoli mentések CSAK
@@ -201,7 +201,7 @@
szerveren tárold. szerveren tárold.
</div> </div>
<h3 style="margin-top:1.25rem">3. Megerősítés</h3> <h3 style="margin-top:1.25rem">Megerősítés</h3>
<form id="start-form" onsubmit="return startCeremony(event)"> <form id="start-form" onsubmit="return startCeremony(event)">
<div class="form-group"> <div class="form-group">
<label for="reauth-password">A folytatáshoz add meg a bejelentkezési jelszavadat</label> <label for="reauth-password">A folytatáshoz add meg a bejelentkezési jelszavadat</label>
@@ -217,14 +217,14 @@
<div class="settings-card" id="progress-card" style="display:none"> <div class="settings-card" id="progress-card" style="display:none">
<h3>4. Létrehozás</h3> <h3>Létrehozás</h3>
<p class="form-hint" id="progress-line">A kód létrehozása folyamatban… (általában néhány másodperc)</p> <p class="form-hint" id="progress-line">A kód létrehozása folyamatban… (általában néhány másodperc)</p>
<div id="progress-error" class="alert alert-error" style="display:none"></div> <div id="progress-error" class="alert alert-error" style="display:none"></div>
</div> </div>
<div class="settings-card" id="reveal-card" style="display:none"> <div class="settings-card" id="reveal-card" style="display:none">
<h3>5. A helyreállítási kód</h3> <h3>A helyreállítási kód</h3>
<div id="reveal-gate"> <div id="reveal-gate">
<p class="form-hint">A kód pontosan egyszer jelenik meg. Készíts elő papírt és tollat, mielőtt megnyomod a gombot.</p> <p class="form-hint">A kód pontosan egyszer jelenik meg. Készíts elő papírt és tollat, mielőtt megnyomod a gombot.</p>
<button class="btn btn-primary" id="reveal-btn" onclick="claimCode()">Kód megjelenítése</button> <button class="btn btn-primary" id="reveal-btn" onclick="claimCode()">Kód megjelenítése</button>
@@ -235,7 +235,7 @@
<div class="alert alert-warning" id="live-now" style="display:none;margin-bottom:.5rem">Ez mostantól az élő helyreállítási kód — a korábbi kód érvényét vesztette. Mentsd el most: a kód többé nem jelenik meg.</div> <div class="alert alert-warning" id="live-now" style="display:none;margin-bottom:.5rem">Ez mostantól az élő helyreállítási kód — a korábbi kód érvényét vesztette. Mentsd el most: a kód többé nem jelenik meg.</div>
<pre class="mono" id="code-block" style="white-space:pre-wrap;word-break:break-all;background:var(--bg-0);border:1px solid var(--line);border-radius:var(--radius);padding:1rem;font-size:1.05rem"></pre> <pre class="mono" id="code-block" style="white-space:pre-wrap;word-break:break-all;background:var(--bg-0);border:1px solid var(--line);border-radius:var(--radius);padding:1rem;font-size:1.05rem"></pre>
<button type="button" class="btn btn-ghost btn-sm" id="code-blur-toggle" onclick="toggleCodeBlur()">Elrejtés</button> <button type="button" class="btn btn-ghost btn-sm" id="code-blur-toggle" onclick="toggleCodeBlur()">Elrejtés</button>
<h3 style="margin-top:1.25rem">6. Ellenőrzés</h3> <h3 style="margin-top:1.25rem">Ellenőrzés</h3>
<div class="form-group"> <div class="form-group">
<label id="verify-label" for="verify-w1">Megerősítés: írd be a kód szavait</label> <label id="verify-label" for="verify-w1">Megerősítés: írd be a kód szavait</label>
<div style="display:flex;gap:.5rem"> <div style="display:flex;gap:.5rem">
@@ -247,7 +247,7 @@
<button class="btn btn-sm" id="verify-btn" onclick="verifyWords()">Szavak ellenőrzése</button> <button class="btn btn-sm" id="verify-btn" onclick="verifyWords()">Szavak ellenőrzése</button>
<a href="/backup/escrow" class="btn btn-sm btn-primary" id="verify-restart" style="display:none">Újraindítás</a> <a href="/backup/escrow" class="btn btn-sm btn-primary" id="verify-restart" style="display:none">Újraindítás</a>
<div id="finish-block" style="display:none;margin-top:1rem"> <div id="finish-block" style="display:none;margin-top:1rem">
<h3>7. Befejezés</h3> <h3>Befejezés</h3>
<div class="alert alert-info" style="margin-bottom:.75rem"> <div class="alert alert-info" style="margin-bottom:.75rem">
<strong>Mi történik ezután?</strong> A helyreállítási kód elkészült, és a rendszer elküldi a központba megerősítésre — ez általában néhány másodperc, legfeljebb 15 perc. Addig a Távoli mentés oldalon a „megerősítésre vár" üzenet látható; a megerősítés után indítható az első távoli mentés. <strong>Mi történik ezután?</strong> A helyreállítási kód elkészült, és a rendszer elküldi a központba megerősítésre — ez általában néhány másodperc, legfeljebb 15 perc. Addig a Távoli mentés oldalon a „megerősítésre vár" üzenet látható; a megerősítés után indítható az első távoli mentés.
@@ -327,6 +327,12 @@ async function loadPreflight(){
html+='<li style="padding:.35rem 0"><span style="color:var(--crit)">&#10007;</span> A funkcióhoz az ügynök frissítése szükséges — a frissítés automatikusan megérkezik.</li>'; html+='<li style="padding:.35rem 0"><span style="color:var(--crit)">&#10007;</span> A funkcióhoz az ügynök frissítése szükséges — a frissítés automatikusan megérkezik.</li>';
} }
html+='</ul>'; html+='</ul>';
if(d.ok){
html='<p><span style="color:var(--blue-bright)">&#10003;</span> Minden előfeltétel rendben.</p>'+
'<details><summary class="form-hint">Részletek</summary>'+html+'</details>';
}
document.getElementById('pf-list').innerHTML=html; document.getElementById('pf-list').innerHTML=html;
if(d.ok){ if(d.ok){
document.getElementById('warn-card').style.display='block'; document.getElementById('warn-card').style.display='block';
@@ -186,14 +186,14 @@
<div class="settings-card" id="pf-card"> <div class="settings-card" id="pf-card">
<h3>1. Előfeltételek ellenőrzése</h3> <h3>Előfeltételek ellenőrzése</h3>
<div id="pf-error" class="alert alert-error" style="display:none;margin-bottom:1rem"></div> <div id="pf-error" class="alert alert-error" style="display:none;margin-bottom:1rem"></div>
<div id="pf-list"><p class="form-hint">Ellenőrzés folyamatban…</p></div> <div id="pf-list"><p class="form-hint">Ellenőrzés folyamatban…</p></div>
</div> </div>
<div class="settings-card" id="warn-card" style="display:none"> <div class="settings-card" id="warn-card" style="display:none">
<h3>2. Fontos tudnivalók</h3> <h3>Fontos tudnivalók</h3>
<div class="alert alert-warning" style="margin-bottom:.75rem"> <div class="alert alert-warning" style="margin-bottom:.75rem">
A helyreállítási kód a mentéseid utolsó kulcsa. Pontosan egyszer jelenik meg — a rendszer A helyreállítási kód a mentéseid utolsó kulcsa. Pontosan egyszer jelenik meg — a rendszer
sehol nem tárolja, és a Felhom sem ismeri. Ha a szerver megsemmisül, a távoli mentések CSAK sehol nem tárolja, és a Felhom sem ismeri. Ha a szerver megsemmisül, a távoli mentések CSAK
@@ -206,7 +206,7 @@
mentési előzményekhez érvényes marad. mentési előzményekhez érvényes marad.
</div> </div>
<h3 style="margin-top:1.25rem">3. Megerősítés</h3> <h3 style="margin-top:1.25rem">Megerősítés</h3>
<form id="start-form" onsubmit="return startCeremony(event)"> <form id="start-form" onsubmit="return startCeremony(event)">
<div class="form-group"> <div class="form-group">
<label for="reauth-password">A folytatáshoz add meg a bejelentkezési jelszavadat</label> <label for="reauth-password">A folytatáshoz add meg a bejelentkezési jelszavadat</label>
@@ -222,14 +222,14 @@
<div class="settings-card" id="progress-card" style="display:none"> <div class="settings-card" id="progress-card" style="display:none">
<h3>4. Létrehozás</h3> <h3>Létrehozás</h3>
<p class="form-hint" id="progress-line">A kód létrehozása folyamatban… (általában néhány másodperc)</p> <p class="form-hint" id="progress-line">A kód létrehozása folyamatban… (általában néhány másodperc)</p>
<div id="progress-error" class="alert alert-error" style="display:none"></div> <div id="progress-error" class="alert alert-error" style="display:none"></div>
</div> </div>
<div class="settings-card" id="reveal-card" style="display:none"> <div class="settings-card" id="reveal-card" style="display:none">
<h3>5. A helyreállítási kód</h3> <h3>A helyreállítási kód</h3>
<div id="reveal-gate"> <div id="reveal-gate">
<p class="form-hint">A kód pontosan egyszer jelenik meg. Készíts elő papírt és tollat, mielőtt megnyomod a gombot.</p> <p class="form-hint">A kód pontosan egyszer jelenik meg. Készíts elő papírt és tollat, mielőtt megnyomod a gombot.</p>
<button class="btn btn-primary" id="reveal-btn" onclick="claimCode()">Kód megjelenítése</button> <button class="btn btn-primary" id="reveal-btn" onclick="claimCode()">Kód megjelenítése</button>
@@ -240,7 +240,7 @@
<div class="alert alert-warning" id="live-now" style="display:none;margin-bottom:.5rem">Ez mostantól az élő helyreállítási kód — a korábbi kód érvényét vesztette. Mentsd el most: a kód többé nem jelenik meg.</div> <div class="alert alert-warning" id="live-now" style="display:none;margin-bottom:.5rem">Ez mostantól az élő helyreállítási kód — a korábbi kód érvényét vesztette. Mentsd el most: a kód többé nem jelenik meg.</div>
<pre class="mono" id="code-block" style="white-space:pre-wrap;word-break:break-all;background:var(--bg-0);border:1px solid var(--line);border-radius:var(--radius);padding:1rem;font-size:1.05rem"></pre> <pre class="mono" id="code-block" style="white-space:pre-wrap;word-break:break-all;background:var(--bg-0);border:1px solid var(--line);border-radius:var(--radius);padding:1rem;font-size:1.05rem"></pre>
<button type="button" class="btn btn-ghost btn-sm" id="code-blur-toggle" onclick="toggleCodeBlur()">Elrejtés</button> <button type="button" class="btn btn-ghost btn-sm" id="code-blur-toggle" onclick="toggleCodeBlur()">Elrejtés</button>
<h3 style="margin-top:1.25rem">6. Ellenőrzés</h3> <h3 style="margin-top:1.25rem">Ellenőrzés</h3>
<div class="form-group"> <div class="form-group">
<label id="verify-label" for="verify-w1">Megerősítés: írd be a kód szavait</label> <label id="verify-label" for="verify-w1">Megerősítés: írd be a kód szavait</label>
<div style="display:flex;gap:.5rem"> <div style="display:flex;gap:.5rem">
@@ -252,7 +252,7 @@
<button class="btn btn-sm" id="verify-btn" onclick="verifyWords()">Szavak ellenőrzése</button> <button class="btn btn-sm" id="verify-btn" onclick="verifyWords()">Szavak ellenőrzése</button>
<a href="/backup/escrow" class="btn btn-sm btn-primary" id="verify-restart" style="display:none">Újraindítás</a> <a href="/backup/escrow" class="btn btn-sm btn-primary" id="verify-restart" style="display:none">Újraindítás</a>
<div id="finish-block" style="display:none;margin-top:1rem"> <div id="finish-block" style="display:none;margin-top:1rem">
<h3>7. Befejezés</h3> <h3>Befejezés</h3>
<div class="alert alert-info" style="margin-bottom:.75rem"> <div class="alert alert-info" style="margin-bottom:.75rem">
<strong>Mi történik ezután?</strong> A helyreállítási kód elkészült, és a rendszer elküldi a központba megerősítésre — ez általában néhány másodperc, legfeljebb 15 perc. Addig a Távoli mentés oldalon a „megerősítésre vár" üzenet látható; a megerősítés után indítható az első távoli mentés. <strong>Mi történik ezután?</strong> A helyreállítási kód elkészült, és a rendszer elküldi a központba megerősítésre — ez általában néhány másodperc, legfeljebb 15 perc. Addig a Távoli mentés oldalon a „megerősítésre vár" üzenet látható; a megerősítés után indítható az első távoli mentés.
@@ -332,6 +332,12 @@ async function loadPreflight(){
html+='<li style="padding:.35rem 0"><span style="color:var(--crit)">&#10007;</span> A funkcióhoz az ügynök frissítése szükséges — a frissítés automatikusan megérkezik.</li>'; html+='<li style="padding:.35rem 0"><span style="color:var(--crit)">&#10007;</span> A funkcióhoz az ügynök frissítése szükséges — a frissítés automatikusan megérkezik.</li>';
} }
html+='</ul>'; html+='</ul>';
if(d.ok){
html='<p><span style="color:var(--blue-bright)">&#10003;</span> Minden előfeltétel rendben.</p>'+
'<details><summary class="form-hint">Részletek</summary>'+html+'</details>';
}
document.getElementById('pf-list').innerHTML=html; document.getElementById('pf-list').innerHTML=html;
if(d.ok){ if(d.ok){
document.getElementById('warn-card').style.display='block'; document.getElementById('warn-card').style.display='block';
@@ -573,25 +573,31 @@ function openDialog(opts){
<div id="family-list" class="settings-grid"></div> <div id="family-list" class="settings-grid"></div>
<p id="family-none" class="form-hint" style="display:none">Még nincs családtag.</p> <p id="family-none" class="form-hint" style="display:none">Még nincs családtag.</p>
<div id="family-pw" class="alert alert-info" style="display:none"> <div id="family-pw" class="alert alert-info" style="display:none">
A jelszó csak most látszik. Írd fel, vagy add át a családtagnak: <strong id="family-pw-name"></strong> — <span id="family-pw-value" class="mono"></span> A jelszó csak most látszik. Írd fel, vagy add át a családtagnak: belépési név: <strong id="family-pw-name" class="mono"></strong> — <span id="family-pw-value" class="mono"></span>
</div> </div>
<form id="family-add" class="inline-form" onsubmit="familyAct(event, 'add', document.getElementById('family-new').value)"> <form id="family-add" class="inline-form" onsubmit="familyAct(event, 'add', document.getElementById('family-new').value)">
<input type="text" id="family-new" class="form-control" placeholder="pl. anna" autocapitalize="none" required> <input type="text" id="family-new" class="form-control" placeholder="pl. Ödön" required>
<button type="submit" class="btn btn-sm btn-primary">Családtag hozzáadása</button> <button type="submit" class="btn btn-sm btn-primary">Családtag hozzáadása</button>
</form> </form>
<p class="form-hint">Írd be a nevet úgy, ahogy szeretnéd, ékezettel is. A belépési név ebből készül: ékezet nélkül, kisbetűvel. A listában látod.</p>
<span id="family-err" class="form-hint" style="display:none;color:var(--red)"></span> <span id="family-err" class="form-hint" style="display:none;color:var(--red)"></span>
</div> </div>
<script> <script>
(function () { (function () {
var T = {reset: 'Új jelszó', remove: 'Eltávolítás', var T = {reset: 'Új jelszó', remove: 'Eltávolítás', login: 'belépési név:',
cReset: 'Új jelszót adsz? A régi azonnal megszűnik.', cRemove: 'Eltávolítod? A belépései azonnal megszűnnek.', err: 'Nem sikerült. Próbáld újra.'}; cReset: 'Új jelszót adsz? A régi azonnal megszűnik.', cRemove: 'Eltávolítod? A belépései azonnal megszűnnek.', err: 'Nem sikerült. Próbáld újra.'};
function render(members) { function render(members) {
var list = document.getElementById('family-list'); var list = document.getElementById('family-list');
list.textContent = ''; list.textContent = '';
document.getElementById('family-none').style.display = members.length ? 'none' : ''; document.getElementById('family-none').style.display = members.length ? 'none' : '';
members.forEach(function (n) { members.forEach(function (p) {
var n = p.login;
var row = document.createElement('div'); row.className = 'settings-row'; row.setAttribute('data-family-member', n); var row = document.createElement('div'); row.className = 'settings-row'; row.setAttribute('data-family-member', n);
var label = document.createElement('span'); label.className = 'settings-label mono'; label.textContent = n; var label = document.createElement('span'); label.className = 'settings-label'; label.textContent = p.display;
var login = document.createElement('span'); login.className = 'form-hint'; login.textContent = ' ' + T.login + ' ';
var code = document.createElement('span'); code.className = 'mono'; code.textContent = n;
login.appendChild(code); label.appendChild(login);
var val = document.createElement('span'); val.className = 'settings-value'; var val = document.createElement('span'); val.className = 'settings-value';
[['reset', T.reset, T.cReset, 'btn-outline'], ['remove', T.remove, T.cRemove, 'btn-danger']].forEach(function (a) { [['reset', T.reset, T.cReset, 'btn-outline'], ['remove', T.remove, T.cRemove, 'btn-danger']].forEach(function (a) {
var b = document.createElement('button'); b.type = 'button'; b.className = 'btn btn-xs ' + a[3]; b.textContent = a[1]; var b = document.createElement('button'); b.type = 'button'; b.className = 'btn btn-xs ' + a[3]; b.textContent = a[1];
@@ -609,7 +615,7 @@ function openDialog(opts){
headers: {'X-CSRF-Token': 'tok', 'Content-Type': 'application/x-www-form-urlencoded'}, body: body}) headers: {'X-CSRF-Token': 'tok', 'Content-Type': 'application/x-www-form-urlencoded'}, body: body})
.then(function (r) { return r.json(); }).then(function (j) { .then(function (r) { return r.json(); }).then(function (j) {
if (!j.ok) { err.textContent = j.error || T.err; err.style.display = 'inline'; return; } if (!j.ok) { err.textContent = j.error || T.err; err.style.display = 'inline'; return; }
render(j.data.members || []); render(j.data.people || []);
var box = document.getElementById('family-pw'); var box = document.getElementById('family-pw');
if (j.data.password) { if (j.data.password) {
document.getElementById('family-pw-name').textContent = j.data.name; document.getElementById('family-pw-name').textContent = j.data.name;
@@ -620,7 +626,7 @@ function openDialog(opts){
}).catch(function () { err.textContent = T.err; err.style.display = 'inline'; }); }).catch(function () { err.textContent = T.err; err.style.display = 'inline'; });
}; };
fetch('/family/members', {credentials: 'same-origin'}).then(function (r) { return r.json(); }) fetch('/family/members', {credentials: 'same-origin'}).then(function (r) { return r.json(); })
.then(function (j) { if (j.ok) render(j.data.members || []); }); .then(function (j) { if (j.ok) render(j.data.people || []); });
})(); })();
</script> </script>
@@ -504,25 +504,31 @@ function openDialog(opts){
<div id="family-list" class="settings-grid"></div> <div id="family-list" class="settings-grid"></div>
<p id="family-none" class="form-hint" style="display:none">Még nincs családtag.</p> <p id="family-none" class="form-hint" style="display:none">Még nincs családtag.</p>
<div id="family-pw" class="alert alert-info" style="display:none"> <div id="family-pw" class="alert alert-info" style="display:none">
A jelszó csak most látszik. Írd fel, vagy add át a családtagnak: <strong id="family-pw-name"></strong> — <span id="family-pw-value" class="mono"></span> A jelszó csak most látszik. Írd fel, vagy add át a családtagnak: belépési név: <strong id="family-pw-name" class="mono"></strong> — <span id="family-pw-value" class="mono"></span>
</div> </div>
<form id="family-add" class="inline-form" onsubmit="familyAct(event, 'add', document.getElementById('family-new').value)"> <form id="family-add" class="inline-form" onsubmit="familyAct(event, 'add', document.getElementById('family-new').value)">
<input type="text" id="family-new" class="form-control" placeholder="pl. anna" autocapitalize="none" required> <input type="text" id="family-new" class="form-control" placeholder="pl. Ödön" required>
<button type="submit" class="btn btn-sm btn-primary">Családtag hozzáadása</button> <button type="submit" class="btn btn-sm btn-primary">Családtag hozzáadása</button>
</form> </form>
<p class="form-hint">Írd be a nevet úgy, ahogy szeretnéd, ékezettel is. A belépési név ebből készül: ékezet nélkül, kisbetűvel. A listában látod.</p>
<span id="family-err" class="form-hint" style="display:none;color:var(--red)"></span> <span id="family-err" class="form-hint" style="display:none;color:var(--red)"></span>
</div> </div>
<script> <script>
(function () { (function () {
var T = {reset: 'Új jelszó', remove: 'Eltávolítás', var T = {reset: 'Új jelszó', remove: 'Eltávolítás', login: 'belépési név:',
cReset: 'Új jelszót adsz? A régi azonnal megszűnik.', cRemove: 'Eltávolítod? A belépései azonnal megszűnnek.', err: 'Nem sikerült. Próbáld újra.'}; cReset: 'Új jelszót adsz? A régi azonnal megszűnik.', cRemove: 'Eltávolítod? A belépései azonnal megszűnnek.', err: 'Nem sikerült. Próbáld újra.'};
function render(members) { function render(members) {
var list = document.getElementById('family-list'); var list = document.getElementById('family-list');
list.textContent = ''; list.textContent = '';
document.getElementById('family-none').style.display = members.length ? 'none' : ''; document.getElementById('family-none').style.display = members.length ? 'none' : '';
members.forEach(function (n) { members.forEach(function (p) {
var n = p.login;
var row = document.createElement('div'); row.className = 'settings-row'; row.setAttribute('data-family-member', n); var row = document.createElement('div'); row.className = 'settings-row'; row.setAttribute('data-family-member', n);
var label = document.createElement('span'); label.className = 'settings-label mono'; label.textContent = n; var label = document.createElement('span'); label.className = 'settings-label'; label.textContent = p.display;
var login = document.createElement('span'); login.className = 'form-hint'; login.textContent = ' ' + T.login + ' ';
var code = document.createElement('span'); code.className = 'mono'; code.textContent = n;
login.appendChild(code); label.appendChild(login);
var val = document.createElement('span'); val.className = 'settings-value'; var val = document.createElement('span'); val.className = 'settings-value';
[['reset', T.reset, T.cReset, 'btn-outline'], ['remove', T.remove, T.cRemove, 'btn-danger']].forEach(function (a) { [['reset', T.reset, T.cReset, 'btn-outline'], ['remove', T.remove, T.cRemove, 'btn-danger']].forEach(function (a) {
var b = document.createElement('button'); b.type = 'button'; b.className = 'btn btn-xs ' + a[3]; b.textContent = a[1]; var b = document.createElement('button'); b.type = 'button'; b.className = 'btn btn-xs ' + a[3]; b.textContent = a[1];
@@ -540,7 +546,7 @@ function openDialog(opts){
headers: {'X-CSRF-Token': 'tok', 'Content-Type': 'application/x-www-form-urlencoded'}, body: body}) headers: {'X-CSRF-Token': 'tok', 'Content-Type': 'application/x-www-form-urlencoded'}, body: body})
.then(function (r) { return r.json(); }).then(function (j) { .then(function (r) { return r.json(); }).then(function (j) {
if (!j.ok) { err.textContent = j.error || T.err; err.style.display = 'inline'; return; } if (!j.ok) { err.textContent = j.error || T.err; err.style.display = 'inline'; return; }
render(j.data.members || []); render(j.data.people || []);
var box = document.getElementById('family-pw'); var box = document.getElementById('family-pw');
if (j.data.password) { if (j.data.password) {
document.getElementById('family-pw-name').textContent = j.data.name; document.getElementById('family-pw-name').textContent = j.data.name;
@@ -551,7 +557,7 @@ function openDialog(opts){
}).catch(function () { err.textContent = T.err; err.style.display = 'inline'; }); }).catch(function () { err.textContent = T.err; err.style.display = 'inline'; });
}; };
fetch('/family/members', {credentials: 'same-origin'}).then(function (r) { return r.json(); }) fetch('/family/members', {credentials: 'same-origin'}).then(function (r) { return r.json(); })
.then(function (j) { if (j.ok) render(j.data.members || []); }); .then(function (j) { if (j.ok) render(j.data.people || []); });
})(); })();
</script> </script>