v0.248.0: i18n slice 1 release A — apps and settings in English, Hungarian byte-identical (R-556)
gates / gates (push) Successful in 20s

Ten pages converted against fixtures captured from unconverted templates (6be55a0); marker
coverage test; English page mask narrowed; English retrieval-promise stems; common keys;
title keys and infraMeta from the bundle; old wording tests read the Hungarian expansion.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
This commit is contained in:
2026-09-17 17:36:08 +02:00
parent 6be55a0eb3
commit ff68b0b433
30 changed files with 1690 additions and 651 deletions
+43
View File
@@ -1,3 +1,46 @@
## v0.248.0 — English for apps and settings: ten more pages, Hungarian byte-identical (2026-09-17, R-556 release A)
**MinAgent: 0.131.0** (unchanged). **No hub release needed.** Nothing changes for a Hungarian household:
the switch is still shown only on English pages (decision 6 stands until release C).
- **Converted:** `dashboard`, `stacks`, `logs`, `monitoring`, `app_import`, `app_export`, `deploy`,
`settings_system`, `settings_security`, `settings_notifications` (572 markers). `app_row`,
`meta_badge`, `icons` carry no copy (verified). 16 repeated strings now share `common.*` keys
where the meaning is the same everywhere; „Mentés" (Save/Backup), „Frissítés" (Update/Refresh),
„Indítás" (Start/Boot) deliberately stay per page.
- **Parity:** 25 new fixture states captured from the UNCONVERTED templates (commit `6be55a0`) before
any of these pages carried a marker. Five v0.247.0 fixtures were re-captured from v0.246.0
(`89dd3e94`) because their one-word Hungarian data had to become multi-word (below).
- **New test — `TestI18nParityCoversEveryMarker`:** every marker occurrence must be rendered by some
parity case. It found two gaps in v0.247.0's own pages (the app page's Install button; one „Due"
tag), closed with two new cases captured from v0.246.0.
- **English page test widened:** only fixture strings of 2+ words or 12+ characters are masked; titles
go through `addLanguageData`. Measured: with one English nav key deleted, the old test flagged 8
cases and missed the 6 launcher cases; the new one flags all 14.
- **Retrieval-promise gate reads English** (`EN_PATTERNS`, `ALLOWLIST_EN`): HEAD passed a planted
„Your old backups can be restored at any time." in `en.json`; the gate now convicts it and accepts the
same place carrying a sentence that promises nothing. Decoys in `test_gate_decoys.py`.
- **Go-side copy on these pages:** titles via `TitleKey` (dashboard, stacks, monitoring, the three
settings pages); `infraMeta` descriptions (and samba's name) from the bundle, Hungarian pinned equal
to `inframeta.go`.
- **Found, not converted:** `backups_remote.html` decides with `indexOf('Fut')` against its own
Hungarian status text (row filed); converting that word would silently break the English page's
progress poll. Composite page titles (logs, deploy, export) and `lifecycleBadge`/`updateBadge`
remain Hungarian — slice 2.
- **Extractor:** ASCII-only Hungarian it missed on these pages — Mind, Folyamat, Titkos, Processzor,
Hamarosan, aldomain, „Az adatai itt voltak", Figyelem, pelda@email.hu — converted by hand; words added
to its list (not „Mind": an English homograph). It also learnt that a JS string can start inside an
attribute value.
- `HU_FORMAL_CEILING` 6 → 10: the converted copy carries four more „ön" forms (R-516), unchanged by rule.
- Old wording tests (`TestDiskMeter_*`, `TestNetworkBadgeTemplates_DistinctStrings`,
`TestSettingsSectionInventory`) read the Hungarian expansion (`huTemplateSource`), not the raw file.
- `loadTemplates` logs its parse duration per language (DEBUG).
**Red-proofs** (`felhom.eu/documentation/audits/i18n-slice1-2026-09-17/A/redproofs.txt`): parity (one byte
of `dashboard.lemezek_allapota` → 4 dashboard cases name line and bytes); coverage (a marker in an
unreachable launcher branch → named, while parity stayed green); mask widening (8 vs 14 cases);
retrieval gate (HEAD rc 0 on the planted promise → fixed gate convicts, negative control accepted).
## v0.247.0 — the dashboard can speak English: mechanism proven on three pages, Hungarian byte-identical (2026-09-17, i18n spike) ## v0.247.0 — the dashboard can speak English: mechanism proven on three pages, Hungarian byte-identical (2026-09-17, i18n spike)
**MinAgent: 0.131.0** (unchanged — nothing here talks to the agent). **No hub release needed:** the **MinAgent: 0.131.0** (unchanged — nothing here talks to the agent). **No hub release needed:** the
+1
View File
@@ -231,6 +231,7 @@
| `getBudapestLocation` | controller/internal/scheduler/scheduler.go | `() *time.Location` | Local-time math | web has its own `getTimezone` (§6); quiesce has its own `budapestLocation` (window gate) — 3rd copy, see §6 | | `getBudapestLocation` | controller/internal/scheduler/scheduler.go | `() *time.Location` | Local-time math | web has its own `getTimezone` (§6); quiesce has its own `budapestLocation` (window gate) — 3rd copy, see §6 |
| `Server.templateFuncMap` | controller/internal/web/funcmap.go | template.FuncMap | ALL template functions | `stateColor` outputs v2 suffixes `run/progress/warn/neutral/off`; stopped = NEUTRAL not red (operator-approved); `stateLabel` copy is frozen byte-identical (unit-tested) | | `Server.templateFuncMap` | controller/internal/web/funcmap.go | template.FuncMap | ALL template functions | `stateColor` outputs v2 suffixes `run/progress/warn/neutral/off`; stopped = NEUTRAL not red (operator-approved); `stateLabel` copy is frozen byte-identical (unit-tested) |
| `i18n.Bundle` (`Shared` / `Msg` / `Plural` / `Expand`) | controller/internal/i18n/i18n.go | `Msg(lang, key) string` | ALL customer copy that has been converted (v0.247.0) | hu.json is authoritative; a key missing in en shows Hungarian (counted), never blank; `Expand` runs BEFORE template parse — never add a runtime `T` func (escaping would change Hungarian bytes). A template edit to converted copy goes in hu.json + en.json, then `TestI18nParity` must still pass | | `i18n.Bundle` (`Shared` / `Msg` / `Plural` / `Expand`) | controller/internal/i18n/i18n.go | `Msg(lang, key) string` | ALL customer copy that has been converted (v0.247.0) | hu.json is authoritative; a key missing in en shows Hungarian (counted), never blank; `Expand` runs BEFORE template parse — never add a runtime `T` func (escaping would change Hungarian bytes). A template edit to converted copy goes in hu.json + en.json, then `TestI18nParity` must still pass |
| `huTemplateSource` (test) | controller/internal/web/i18n_wiring_test.go | `(name) ([]byte, error)` | A test that asserts a template's Hungarian WORDING | Reads the expansion, not the raw file — since slice 1 the wording lives in hu.json. Never use `templateFS.ReadFile` for a wording assertion |
| `Server.langFor` / `templatesFor` / `addLanguageData` | controller/internal/web/server.go + i18n_web.go | request → lang → template set | Rendering a page in the household's language | Only `executeTemplate` is language-aware; `s.tmpl` is the Hungarian set; Go-side titles via `data["TitleKey"]` | | `Server.langFor` / `templatesFor` / `addLanguageData` | controller/internal/web/server.go + i18n_web.go | request → lang → template set | Rendering a page in the household's language | Only `executeTemplate` is language-aware; `s.tmpl` is the Hungarian set; Go-side titles via `data["TitleKey"]` |
| `timeAgoStr` | controller/internal/web/funcmap.go | `(s RFC3339 string) string` | Ago-format for STRING timestamps | Exists because `timeAgo(time.Time)` 500'd on strings (v0.93 bug) | | `timeAgoStr` | controller/internal/web/funcmap.go | `(s RFC3339 string) string` | Ago-format for STRING timestamps | Exists because `timeAgo(time.Time)` 500'd on strings (v0.93 bug) |
| `Server.baseData` / `executeTemplate` | controller/internal/web/handlers.go + server.go | page-data plumbing | New pages | baseData injects nav/alerts/version; templates must pass `controller/scripts/template_id_gate.py` + `controller/scripts/emoji_gate.py` | | `Server.baseData` / `executeTemplate` | controller/internal/web/handlers.go + server.go | page-data plumbing | New pages | baseData injects nav/alerts/version; templates must pass `controller/scripts/template_id_gate.py` + `controller/scripts/emoji_gate.py` |
+7 -3
View File
@@ -3621,8 +3621,10 @@ Design: `felhom.eu/documentation/architecture/10-localisation.md`. Inventory:
**What a household sees:** nothing new. Hungarian is the default and the only language shown unless **What a household sees:** nothing new. Hungarian is the default and the only language shown unless
the language is switched. English covers the **launcher**, the **backups overview** (`/backups`), the the language is switched. English covers the **launcher**, the **backups overview** (`/backups`), the
**app page** (`/apps/<slug>`) and the shared **layout** (menu, banners, the layout's modals) — other **app page** (`/apps/<slug>`), the shared **layout**, and since v0.248.0 the **dashboard, apps list,
pages keep their Hungarian body inside an English menu. logs, monitoring, import, export, deploy/settings-of-an-app and the three settings pages** — other
pages keep their Hungarian body inside an English menu. Server-built messages and catalog copy stay
Hungarian (slices 2 and 5).
- **Bundles:** `internal/i18n/locales/hu.json` (authoritative, every key) and `en.json`, embedded. - **Bundles:** `internal/i18n/locales/hu.json` (authoritative, every key) and `en.json`, embedded.
Flat `key → text`; a value may carry template actions (`{{.RecoveryAbandonDate}}`) and inline Flat `key → text`; a value may carry template actions (`{{.RecoveryAbandonDate}}`) and inline
@@ -3650,7 +3652,9 @@ pages keep their Hungarian body inside an English menu.
exist, no orphans, the English gap and the formal „ön" count only shrink (ratchets), no pleading exist, no orphans, the English gap and the formal „ön" count only shrink (ratchets), no pleading
English. The emoji, native-confirm, secret-in-markup and retrieval-promise gates read templates English. The emoji, native-confirm, secret-in-markup and retrieval-promise gates read templates
**expanded** through `scripts/i18n_bundle.py`; mojibake scans the bundles. **expanded** through `scripts/i18n_bundle.py`; mojibake scans the bundles.
- **Release gate:** `TestI18nParity` compares the Hungarian render of 14 fixture states with HTML - **Coverage:** `TestI18nParityCoversEveryMarker` fails when any marker occurrence is rendered by no
parity case. Fixtures are written only when missing (`-update-i18n-golden`, `-i18n-golden-only`).
- **Release gate:** `TestI18nParity` compares the Hungarian render of every fixture state with HTML
captured from the unconverted templates (`internal/web/testdata/i18n_parity/`). Fixtures are never captured from the unconverted templates (`internal/web/testdata/i18n_parity/`). Fixtures are never
regenerated to make a conversion pass. regenerated to make a conversion pass.
+6 -2
View File
@@ -111,7 +111,9 @@ func TestEmbeddedBundles(t *testing.T) {
} }
var fieldRefRe = regexp.MustCompile(`\.[A-Z][A-Za-z0-9_]*(\.[A-Z][A-Za-z0-9_]*)*`) var fieldRefRe = regexp.MustCompile(`\.[A-Z][A-Za-z0-9_]*(\.[A-Z][A-Za-z0-9_]*)*`)
var verbRe = regexp.MustCompile(`%[-+# 0-9.]*[sdvq]`) // verbRe counts printf verbs OUTSIDE template actions ({{printf "%.0f" .X}} is a parameter, not a verb
// of the message), and without the space flag — „50% szabad" is prose, not „% s".
var verbRe = regexp.MustCompile(`%[-+#0-9.]*[sdvq]`)
func refs(s string) []string { func refs(s string) []string {
var out []string var out []string
@@ -153,10 +155,12 @@ func TestBundleParametersMatchAcrossLanguages(t *testing.T) {
if a, c := strings.Join(refs(hu), ","), strings.Join(refs(v), ","); a != c { if a, c := strings.Join(refs(hu), ","), strings.Join(refs(v), ","); a != c {
t.Errorf("%s %q: template fields %q, Hungarian has %q", lang, f, c, a) t.Errorf("%s %q: template fields %q, Hungarian has %q", lang, f, c, a)
} }
if a, c := len(verbRe.FindAllString(hu, -1)), len(verbRe.FindAllString(v, -1)); a != c { if a, c := len(verbRe.FindAllString(stripActions(hu), -1)), len(verbRe.FindAllString(stripActions(v), -1)); a != c {
t.Errorf("%s %q: %d printf verbs, Hungarian has %d", lang, f, c, a) t.Errorf("%s %q: %d printf verbs, Hungarian has %d", lang, f, c, a)
} }
} }
} }
} }
} }
func stripActions(s string) string { return regexp.MustCompile(`\{\{.*?\}\}`).ReplaceAllString(s, "") }
+485 -20
View File
@@ -1,4 +1,20 @@
{ {
"common.bezaras": "Close",
"common.elavult": "Outdated",
"common.eltavolitas": "Remove",
"common.hdd_szukseges": "Needs a hard drive",
"common.hiba": "Error",
"common.hiba_kettospont": "Error:",
"common.hiba_tortent": "An error occurred",
"common.jelszo": "Password",
"common.levalasztva": "Disconnected",
"common.magyar_felulet": "Hungarian interface",
"common.megsem": "Cancel",
"common.pi_kompatibilis": "Runs on Pi",
"common.rendszer_gyoker": "System (/)",
"common.torles": "Delete",
"common.ugyfel_azonosito": "Customer ID",
"common.ujrainditas": "Restart",
"layout.html_lang": "en", "layout.html_lang": "en",
"launcher.inditopult": "Launcher", "launcher.inditopult": "Launcher",
"launcher.inditopult_megosztasa": "Share the launcher", "launcher.inditopult_megosztasa": "Share the launcher",
@@ -10,7 +26,6 @@
"launcher.megosztas": "Share…", "launcher.megosztas": "Share…",
"launcher.qr_kod_a_megosztasi_linkhez": "QR code for the share link", "launcher.qr_kod_a_megosztasi_linkhez": "QR code for the share link",
"launcher.olvassa_be_telefonnal_a_gyors": "Scan it with your phone to open it quickly.", "launcher.olvassa_be_telefonnal_a_gyors": "Scan it with your phone to open it quickly.",
"launcher.jelszo": "Password",
"launcher.a_megosztas_jelszoval_vedett": "The share is protected by a password.", "launcher.a_megosztas_jelszoval_vedett": "The share is protected by a password.",
"launcher.jelszo_torlese": "Remove password", "launcher.jelszo_torlese": "Remove password",
"launcher.jelszo_nem_kotelezo": "Password (optional)", "launcher.jelszo_nem_kotelezo": "Password (optional)",
@@ -21,7 +36,6 @@
"launcher.biztosan_kikapcsolja_a_megosztast_a": "Turn off sharing? The link stops working immediately.", "launcher.biztosan_kikapcsolja_a_megosztast_a": "Turn off sharing? The link stops working immediately.",
"launcher.megosztas_kikapcsolasa": "Turn off sharing", "launcher.megosztas_kikapcsolasa": "Turn off sharing",
"launcher.a_megosztas_jelenleg_ki_van": "Sharing is off.", "launcher.a_megosztas_jelenleg_ki_van": "Sharing is off.",
"launcher.bezaras": "Close",
"launcher.megosztas_bekapcsolasa": "Turn on sharing", "launcher.megosztas_bekapcsolasa": "Turn on sharing",
"launcher.masolva": "Copied", "launcher.masolva": "Copied",
"launcher.inditopult_2": "Launcher — {{.Domain}}", "launcher.inditopult_2": "Launcher — {{.Domain}}",
@@ -30,8 +44,6 @@
"backups.kijelolod_ezt_a_meghajtot_a": "Use this drive for the system backup?", "backups.kijelolod_ezt_a_meghajtot_a": "Use this drive for the system backup?",
"backups.kijelolom": "Use this drive", "backups.kijelolom": "Use this drive",
"backups.tarhely_attekintes": "Storage overview", "backups.tarhely_attekintes": "Storage overview",
"backups.rendszer": "System (/)",
"backups.levalasztva": "Disconnected",
"backups.db_mentesek": "Database backups", "backups.db_mentesek": "Database backups",
"backups.fajl": "{{$n := len .Backup.DumpFiles}}{{$n}} {{if eq $n 1}}file{{else}}files{{end}}", "backups.fajl": "{{$n := len .Backup.DumpFiles}}{{$n}} {{if eq $n 1}}file{{else}}files{{end}}",
"backups.rendszermentes_teljes_mentes": "System backup (full backup)", "backups.rendszermentes_teljes_mentes": "System backup (full backup)",
@@ -68,9 +80,7 @@
"backups.beagyazott_db_k_a_kotetmentesben": "embedded databases, inside the volume backup", "backups.beagyazott_db_k_a_kotetmentesben": "embedded databases, inside the volume backup",
"backups.elinditod_a_teljes_rendszermentest_most": "Start the full system backup now? Your apps stop during the backup — usually for a few minutes, longer with more data.", "backups.elinditod_a_teljes_rendszermentest_most": "Start the full system backup now? Your apps stop during the backup — usually for a few minutes, longer with more data.",
"backups.mentes_inditasa": "Starting the backup…", "backups.mentes_inditasa": "Starting the backup…",
"backups.hiba": "Error",
"backups.mentes_folyamatban": "Backup in progress…", "backups.mentes_folyamatban": "Backup in progress…",
"backups.hiba_2": "Error:",
"backups.allapot": "Status:", "backups.allapot": "Status:",
"backups.a_rendszermentes_elkeszult": "The system backup is done.", "backups.a_rendszermentes_elkeszult": "The system backup is done.",
"backups.a_mentes_sikertelen": "The backup failed.", "backups.a_mentes_sikertelen": "The backup failed.",
@@ -79,20 +89,15 @@
"backups.a_meghajto_kijelolve_a_beallitas": "The drive is assigned. The setting takes effect after the host agent next restarts.", "backups.a_meghajto_kijelolve_a_beallitas": "The drive is assigned. The setting takes effect after the host agent next restarts.",
"backups.a_meghajto_kijelolve": "The drive is assigned.", "backups.a_meghajto_kijelolve": "The drive is assigned.",
"app_info.alkalmazasok": "← Apps", "app_info.alkalmazasok": "← Apps",
"app_info.elavult": "Outdated",
"app_info.megnyitas": "Open ↗", "app_info.megnyitas": "Open ↗",
"app_info.naplo": "Log", "app_info.naplo": "Log",
"app_info.torles": "Delete",
"app_info.exportalas": "Export", "app_info.exportalas": "Export",
"app_info.beallitasok": "Settings", "app_info.beallitasok": "Settings",
"app_info.telepites": "Install", "app_info.telepites": "Install",
"app_info.mentesek": "Backups", "app_info.mentesek": "Backups",
"app_info.hianyzo_tarhely": "Missing storage:", "app_info.hianyzo_tarhely": "Missing storage:",
"app_info.ennek_az_alkalmazasnak_az_adattaroloja": "This app’s data storage is not available right now, so the app is stopped. Connect the drive again, or move the data to another storage.", "app_info.ennek_az_alkalmazasnak_az_adattaroloja": "This app’s data storage is not available right now, so the app is stopped. Connect the drive again, or move the data to another storage.",
"app_info.hdd_szukseges": "Needs a hard drive",
"app_info.pi_kompatibilis": "Runs on Pi",
"app_info.csak_x86": "x86 only", "app_info.csak_x86": "x86 only",
"app_info.magyar_felulet": "Hungarian interface",
"app_info.az_alkalmazas_fejlesztoje_felhagyott_a": "The developer of this app has stopped working on it. The installed version still works,\n but it gets no more updates or security fixes.", "app_info.az_alkalmazas_fejlesztoje_felhagyott_a": "The developer of this app has stopped working on it. The installed version still works,\n but it gets no more updates or security fixes.",
"app_info.hova_tegyem_a_fajlokat": "Where do I put my files?", "app_info.hova_tegyem_a_fajlokat": "Where do I put my files?",
"app_info.ezeket_a_mappakat_a_fajlkezeloben": "You reach these folders in the File manager. You may need to sign in to the File manager first.", "app_info.ezeket_a_mappakat_a_fajlkezeloben": "You reach these folders in the File manager. You may need to sign in to the File manager first.",
@@ -113,14 +118,12 @@
"app_info.athelyezed_a_z": "Move the", "app_info.athelyezed_a_z": "Move the",
"app_info.adatait_ide": "data to:", "app_info.adatait_ide": "data to:",
"app_info.az_alkalmazas_rovid_idore_leall": "? The app stops briefly. The old data is deleted only after a successful move.", "app_info.az_alkalmazas_rovid_idore_leall": "? The app stops briefly. The old data is deleted only after a successful move.",
"app_info.hiba": "Error:",
"app_info.ismeretlen": "unknown", "app_info.ismeretlen": "unknown",
"app_info.mire_hasznalhato": "What is it for?", "app_info.mire_hasznalhato": "What is it for?",
"app_info.elso_lepesek": "First steps", "app_info.elso_lepesek": "First steps",
"app_info.elofeltetelek": "Before you start", "app_info.elofeltetelek": "Before you start",
"app_info.kezdeti_belepesi_adatok": "First sign-in details", "app_info.kezdeti_belepesi_adatok": "First sign-in details",
"app_info.felhasznalonev": "Username", "app_info.felhasznalonev": "Username",
"app_info.jelszo": "Password",
"app_info.az_uzemelteto_allitotta_be": "set by the operator", "app_info.az_uzemelteto_allitotta_be": "set by the operator",
"app_info.megjelenites": "Show", "app_info.megjelenites": "Show",
"app_info.masolas": "Copy", "app_info.masolas": "Copy",
@@ -169,7 +172,6 @@
"layout.ez_az_alkalmazas_mar_telepitve": "This app is already installed.", "layout.ez_az_alkalmazas_mar_telepitve": "This app is already installed.",
"layout.frissites": "↻ Refreshing...", "layout.frissites": "↻ Refreshing...",
"layout.sablonok_frissitve": "Templates refreshed", "layout.sablonok_frissitve": "Templates refreshed",
"layout.hiba": "Error:",
"layout.ismeretlen_hiba": "Unknown error", "layout.ismeretlen_hiba": "Unknown error",
"layout.halozati_hiba": "Network error:", "layout.halozati_hiba": "Network error:",
"layout.folyamatban": "In progress...", "layout.folyamatban": "In progress...",
@@ -180,11 +182,7 @@
"layout.alkalmazas_torlese": "Delete app:", "layout.alkalmazas_torlese": "Delete app:",
"layout.ez_a_muvelet_eltavolitja_a": "This removes the containers, the volumes and the configuration files.", "layout.ez_a_muvelet_eltavolitja_a": "This removes the containers, the volumes and the configuration files.",
"layout.ez_a_muvelet_nem_visszavonhato": "You cannot undo this!", "layout.ez_a_muvelet_nem_visszavonhato": "You cannot undo this!",
"layout.megsem": "Cancel",
"layout.torles": "Delete",
"layout.hiba_2": "Error",
"layout.nem_sikerult_lekerni_az_adatokat": "Could not load the details:", "layout.nem_sikerult_lekerni_az_adatokat": "Could not load the details:",
"layout.bezaras": "Close",
"layout.torles_folyamatban": "Deleting...", "layout.torles_folyamatban": "Deleting...",
"layout.torolt_adatok": "Deleted data:", "layout.torolt_adatok": "Deleted data:",
"layout.megorzott_adatok": "Kept data:", "layout.megorzott_adatok": "Kept data:",
@@ -202,7 +200,6 @@
"layout.az_ejszakai_restic_pillanatkepek_nem": "The nightly restic snapshots cannot be deleted one by one — they expire automatically under the retention rules.", "layout.az_ejszakai_restic_pillanatkepek_nem": "The nightly restic snapshots cannot be deleted one by one — they expire automatically under the retention rules.",
"layout.alkalmazas_eltavolitasa": "Remove app:", "layout.alkalmazas_eltavolitasa": "Remove app:",
"layout.az_alkalmazas_visszaall_nincs_telepitve": "The app goes back to \"Not installed\". The template stays, and you can install it again.", "layout.az_alkalmazas_visszaall_nincs_telepitve": "The app goes back to \"Not installed\". The template stays, and you can install it again.",
"layout.eltavolitas": "Remove",
"layout.eltavolitas_folyamatban": "Removing...", "layout.eltavolitas_folyamatban": "Removing...",
"layout.torolt_mentesek": "Deleted backups:", "layout.torolt_mentesek": "Deleted backups:",
"layout.nem_torolt_mentesek": "Backups not deleted:", "layout.nem_torolt_mentesek": "Backups not deleted:",
@@ -236,5 +233,473 @@
"func.restore_status.restoring": "Restoring...", "func.restore_status.restoring": "Restoring...",
"func.restore_status.done": "Done", "func.restore_status.done": "Done",
"func.restore_status.failed": "Failed", "func.restore_status.failed": "Failed",
"func.restore_status.skipped": "Skipped" "func.restore_status.skipped": "Skipped",
"dashboard.vezerlopult": "Dashboard",
"dashboard.futo_alkalmazas": "Running apps",
"dashboard.leallitva": "Stopped",
"dashboard.osszes_alkalmazas": "All apps",
"dashboard.memoria": "Memory",
"dashboard.homerseklet": "Temperature",
"dashboard.nem_ismert": "unknown",
"dashboard.a_tarhely_merete_most_nem": "The storage size cannot be read right now.",
"dashboard.fogyoban_a_hely": "Running out of space",
"dashboard.kritikusan_keves_hely": "Critically low on space",
"dashboard.lemezek_allapota": "Disk health",
"dashboard.nincs_adat": "No data",
"dashboard.biztonsagi_mentes": "Backup",
"dashboard.utolso_mentes": "Last backup:",
"dashboard.sikertelen": "Failed",
"dashboard.meg_nem_futott": "Not run yet",
"dashboard.adatbazisok": "Databases:",
"dashboard.mentve": "{{len .DBDumpStatus.Results}} backed up",
"dashboard.telepitett_alkalmazasok": "Installed apps",
"dashboard.az_alkalmazas_adattaroloja_nem_elerheto": "The app’s data storage is not available. Connect the drive again, or move the data to another storage.",
"dashboard.hianyzo_tarhely": "Missing storage: {{$ms}}",
"dashboard.az_alkalmazas_egyik_folyamatat_a": "The system stopped one of the app’s processes because it ran out of memory. A task may not have finished. Restart the app; if it happens again, tell the operator.",
"dashboard.memoria_elfogyott": "Out of memory",
"dashboard.az_alkalmazas_kornyezeteben_a_halozati": "In the app’s environment there is an empty local folder where the network storage should be — the data is not going to the NAS. Tell the operator.",
"dashboard.halozati_tarhely_hibas_az_alkalmazas": "Network storage broken — the app does not see the NAS",
"dashboard.a_halozati_tarhely_nas_jelenleg": "The network storage (NAS) is not reachable right now. The app is running; access to the data comes back when the NAS returns.",
"dashboard.halozati_tarhely_nem_elerheto": "Network storage not reachable: {{$nw}}",
"dashboard.a_proxy_traefik_csak_egeszseges": "The proxy (Traefik) publishes a public route only for a healthy container. While the app is not healthy, the URL returns 404, even though the container is running.",
"dashboard.url_nem_elerheto": "URL not reachable",
"dashboard.vedett": "Protected",
"dashboard.megnyitas": "Open",
"dashboard.telepites": "Install",
"dashboard.leallitas": "Stop",
"dashboard.inditas": "Start",
"dashboard.naplo": "Log",
"dashboard.nincs_elerheto_alkalmazas": "No app available.",
"stacks.alkalmazasok": "Apps",
"stacks.alkalmazas_visszaallitasa_exportalt_csom": "Restore an app from an exported package",
"stacks.importalas": "Import",
"stacks.sablonok_frissitese_a_kozponti_katalogus": "Refresh the templates from the central catalog",
"stacks.sablonok_frissitese": "↻ Refresh templates",
"stacks.futo": "Running",
"stacks.leallitva": "Stopped",
"stacks.telepitheto": "Installable",
"stacks.a_proxy_traefik_csak_egeszseges": "The proxy (Traefik) publishes a public route only for a healthy container. While the app is not healthy, the URL is not reachable (404), even though the container is running.",
"stacks.url_nem_elerheto_utvonal_nincs": "URL not reachable – route not published",
"stacks.az_alkalmazas_adattaroloja_nem_elerheto": "The app’s data storage is not available. Connect the drive again, or move the data.",
"stacks.hianyzo_tarhely": "Missing storage: {{$ms}}",
"stacks.az_alkalmazas_kornyezeteben_a_halozati": "In the app’s environment there is an empty local folder where the network storage should be — the data is not going to the NAS. Tell the operator.",
"stacks.halozati_tarhely_hibas_az_alkalmazas": "Network storage broken — the app does not see the NAS",
"stacks.a_halozati_tarhely_nas_jelenleg": "The network storage (NAS) is not reachable right now. The app is running; access to the data comes back when the NAS returns.",
"stacks.halozati_tarhely_nem_elerheto": "Network storage not reachable: {{$nw}}",
"stacks.adattarolo": "Data storage: {{index $.StorageLabels .Name}}",
"stacks.vedett_rendszerkomponens": "Protected system component",
"stacks.reszletek": "Details",
"stacks.telepites": "Install",
"stacks.mentesek": "Backups",
"stacks.frissites": "Update",
"stacks.leallitas": "Stop",
"stacks.inditas": "Start",
"stacks.naplok": "Logs",
"logs.vissza": "← Back",
"logs.naplok": "{{.Stack.Meta.DisplayName}} — Logs",
"logs.elo": "Live",
"logs.szuneteltetes": "Pause",
"logs.frissites": "Refresh",
"logs.szunetelve": "Paused",
"logs.folytatas": "Resume",
"monitoring.rendszermonitor": "System monitor",
"monitoring.szerver_allapota_gazdagep": "Server health (host)",
"monitoring.a_gazdagep_metrikai_jelenleg_nem": "The host metrics are not available right now.",
"monitoring.cpu_hasznalat": "CPU usage",
"monitoring.terheles_load": "Load",
"monitoring.memoria": "Memory",
"monitoring.cpu_homerseklet": "CPU temperature",
"monitoring.uzemido": "Uptime",
"monitoring.tarolok_kapacitasa": "Storage capacity",
"monitoring.rendszer_attekintes": "System overview",
"monitoring.gepnev": "Hostname",
"monitoring.operacios_rendszer": "Operating system",
"monitoring.inditas": "Boot time",
"monitoring.tarhely": "Storage",
"monitoring.hub_kapcsolat": "Hub connection",
"monitoring.kapcsolodva_a_kozponti_rendszer_aktivan": "Connected — the central system is actively watching the server.",
"monitoring.nem_elerheto_a_kozponti_rendszer": "Not reachable — the central system has not received a recent report.",
"monitoring.utolso_sikeres_jelentes": "Last successful report",
"monitoring.utolso_hiba": "Last error",
"monitoring.a_hub_kapcsolat_nincs_bekapcsolva": "The hub connection is not turned on — central monitoring is not active.",
"monitoring.rendszer_metrikak": "System metrics",
"monitoring.1_ora": "1 hour",
"monitoring.6_ora": "6 hours",
"monitoring.24_ora": "24 hours",
"monitoring.7_nap": "7 days",
"monitoring.30_nap": "30 days",
"monitoring.cpu_hasznalat_2": "CPU usage (%)",
"monitoring.memoria_hasznalat_gb": "Memory usage (GB)",
"monitoring.homerseklet_c": "Temperature (°C)",
"monitoring.terheles_load_average": "Load average",
"monitoring.meg_nincsenek_adatok_a_metrikak": "No data yet. Metric collection has started; the first data appears in a few minutes.",
"monitoring.memoria_eloszlas": "Memory breakdown",
"monitoring.alkalmazas_eroforrasok": "App resources",
"monitoring.memoria_hasznalat_mb": "Memory usage (MB)",
"monitoring.meg_nincsenek_kontener_adatok": "No container data yet.",
"monitoring.memoria_mb": "Memory (MB)",
"monitoring.hasznalt": "Used:",
"monitoring.rendszer_title": "System:",
"monitoring.rendszer_2": "System (",
"monitoring.szabad": "Free (",
"monitoring.eroforras_elozmenyek": "— Resource history",
"monitoring.nincs_tarolo_adat": "No storage data.",
"monitoring.nem_elerheto": "Not reachable",
"monitoring.kopas": "wear:",
"monitoring.frissitve": "Updated:",
"monitoring.nap": "d,",
"monitoring.ora": "h",
"monitoring.ora_2": "h,",
"monitoring.perc": "min",
"app_import.alkalmazasok": "&larr; Apps",
"app_import.alkalmazas_importalas": "Import an app",
"app_import.fab_csomag_feltoltese_huzza_ide": "Upload a .fab package &mdash; drag it here, or choose the file",
"app_import.fajl_kivalasztasa": "Choose file",
"app_import.feltoltes_0": "Upload: 0%",
"app_import.megszakitas": "Cancel upload",
"app_import.nem_talalhato_fab_csomag_a": "No .fab package found on the registered storages.",
"app_import.exportalj_egy_alkalmazast_az_alkalmazas": "Export an app from its page, upload a .fab file above, or copy one into the <code>{storage}/felhom-data/exports/</code> folder.",
"app_import.alkalmazas": "App",
"app_import.datum": "Date",
"app_import.meret": "Size",
"app_import.tarolo": "Storage",
"app_import.reszletek": "Details &raquo;",
"app_import.csomag_reszletei": "Package details",
"app_import.ez_a_csomag_jelszoval_vedett": "This package is protected by a password. Enter the password:",
"app_import.megnyitas": "Open",
"app_import.alkalmazas_2": "App:",
"app_import.exportalva": "Exported:",
"app_import.controller_verzio": "Controller version:",
"app_import.adatbazis": "Database:",
"app_import.adatok": "Data:",
"app_import.meret_2": "Size:",
"app_import.figyelem_a_meglevo": "<strong>&#9888; WARNING:</strong> The existing",
"app_import.alkalmazas_konfiguracioja_es_osszes_adat": "app’s configuration and all its data will be overwritten!",
"app_import.visszaallitas_inditasa": "Start restore",
"app_import.importalas_folyamata": "Import progress",
"app_import.importalas_kesz": "Import done!",
"app_import.az_alkalmazas_sikeresen_visszaallitva": "The app was restored.",
"app_import.alkalmazasok_megtekintese": "View apps",
"app_import.hibas_jelszo": "Wrong password",
"app_import.csomag_reszletei_2": "— Package details",
"app_import.nincs": "None",
"app_import.alkalmazas_megtekintese": "View app",
"app_import.a_feltoltes_megszakadt_ellenorizze_a": "The upload stopped — check the connection, then try again.",
"app_import.feltoltes": "Upload:",
"app_import.csak_fab_fajl_toltheto_fel": "Only a .fab file can be uploaded.",
"app_import.feltoltes_kesz": "Upload done:",
"app_export.alkalmazasok": "&larr; Apps",
"app_export.exportalas": "{{.Stack.Meta.DisplayName}} &mdash; Export",
"app_export.mentes_helye": "Save to",
"app_export.valassz_tarolot": "Choose a storage...",
"app_export.konfiguracio": "Configuration:",
"app_export.felhasznaloi_adatok": "Your data:",
"app_export.osszesen": "Total:",
"app_export.szabad": "(free: -)",
"app_export.becsult_ido": "Estimated time:",
"app_export.mindig_resze_a_mentesnek": "Always part of the export:",
"app_export.valaszthato_tartalom": "Optional content:",
"app_export.a_kihagyott_mappak_tartalma_a": "The content of the skipped folders is always reachable in the File manager, and you can save it separately.",
"app_export.kihagyott_tartalom_megjelenitese": "Show skipped content",
"app_export.jelszo_opcionalis": "Password (optional)",
"app_export.titkositasi_jelszo": "Encryption password",
"app_export.jelszo_mutatasa": "Show password",
"app_export.alkalmazas_leallitasa_mentes_elott_ajanl": "Stop the app before the export (recommended)",
"app_export.az_adatok_konzisztenciaja_erdekeben_java": "To keep the data consistent, stop the app during the export.",
"app_export.exportalas_inditasa": "Start export",
"app_export.kesz": "Done!",
"app_export.megnyitas_filebrowser_ben": "Open in FileBrowser &nearr;",
"app_export.szabad_2": "(free:",
"app_export.perc": "min",
"app_export.nincs_eleg_szabad_hely_a": "Not enough free space on the chosen storage!",
"app_export.alap_mentes": "Base export: ~",
"app_export.a_kihagyott_nagy_meretu_tartalommal": ". With the large skipped content: ~",
"app_export.kihagyott_tartalom_elrejtese": "Hide skipped content",
"deploy.vissza": "← Back",
"deploy.beallitasok": "Settings",
"deploy.telepites": "Install",
"deploy.leallitas": "Stop",
"deploy.inditas": "Start",
"deploy.megnyitas": "Open ↗",
"deploy.reszletek": "Details",
"deploy.reszletes_leiras_kepernyokepek": "Full description, screenshots",
"deploy.ez_az_alkalmazas_mar_telepitve": "This app is already installed. The settings below are read-only.",
"deploy.adattarolas": "Data storage",
"deploy.tarhely": "Storage",
"deploy.adatmeret": "Data size",
"deploy.szabad_hely": "Free space",
"deploy.szabad": "{{.StorageInfo.FreeHuman}} ({{printf \"%.0f\" .StorageInfo.FreePercent}}% free)",
"deploy.mozgatas_masik_tarolora": "Move to another storage",
"deploy.korabbi_adatok": "Earlier data",
"deploy.az_alkalmazas_adatainak_masolata_megtala": "A copy of this app’s data is also on another storage.\n This is usually left behind after a move.",
"deploy.meret": "Size",
"deploy.mappak": "Folders",
"deploy.korabbi_adatok_torlese": "Delete earlier data",
"deploy.biztonsagi_mentes": "Backup",
"deploy.az_alkalmazas_adatbazisa_es_docker": "The app’s database and Docker volumes go into the nightly backup automatically.\n <a href=\"/backups\" style=\"color:var(--blue)\">Backup status →</a> · <a href=\"/stacks/{{.Stack.Name}}/backup\" style=\"color:var(--blue)\">Backup settings →</a>",
"deploy.integraciok": "Integrations",
"deploy.mas_telepitett_alkalmazasokkal_valo_ossz": "Connections to other installed apps. An integration pauses by itself when either app stops, and turns on again when it starts.",
"deploy.nincs_telepitve": "Not installed",
"deploy.celalkalmazas_leallitva": "Target app stopped",
"deploy.aktiv": "Active",
"deploy.a_celalkalmazas_nincs_telepitve": "The target app is not installed",
"deploy.a_celalkalmazas_nem_fut": "The target app is not running",
"deploy.halozati_hiba": "Network error",
"deploy.foldrajzi_korlatozas": "Country restriction",
"deploy.az_alkalmazas_egyeni_orszagkorlatozas_ne": "Without its own country restriction, the app follows the global setting.",
"deploy.egyeni_orszagkorlatozas": "Own country restriction",
"deploy.engedelyezett_orszagok_ehhez_az_alkalmaz": "Allowed countries for this app",
"deploy.orszag_keresese": "Search country...",
"deploy.mentes": "Save",
"deploy.nincs_talalat": "No match",
"deploy.mentve": "Saved",
"deploy.opcionalis_beallitasok": "Optional settings",
"deploy.regisztracios_utmutato": "Sign-up guide ↗",
"deploy.mentes_2": "Saving...",
"deploy.email_kuldes": "Sending e-mail",
"deploy.ez_az_alkalmazas_tud_emailt": "This app can send e-mail (for example password resets, invitations) through Felhom,\n without setting up a separate provider. The sender address: <strong>{{.AppEmailFromAddress}}</strong>.",
"deploy.az_alkalmazas_email_jelenleg_ki": "App e-mail is turned off globally right now. Turn it on on the\n <a href=\"/settings\">Settings</a> page for it to work here too.",
"deploy.email_kuldes_engedelyezese_ennel_az": "Allow sending e-mail for this app",
"deploy.a_mentes_ujrainditja_az_alkalmazast": "Saving restarts the app.",
"deploy.nincs_eleg_memoria_foglalas_telepites": "Not enough memory! Reserved after install: {{.AfterMB}} MB / {{.UsableMB}} MB",
"deploy.memoria": "Memory",
"deploy.jelenlegi_hasznalat_mb": "Current usage: {{.UsedMB}} MB",
"deploy.jelenlegi_hasznalat_mb_2": "Current usage ({{.UsedMB}} MB)",
"deploy.az_alkalmazasok_csucsterhelese_meghaladh": "The apps’ peak load can go above the available memory.\n In normal use this causes no problem.",
"deploy.ennek_az_alkalmazasnak_van_korabbi": "This app has an earlier backup, so we filled in the fields below from its own backup.",
"deploy.korabbi_webcim": "Earlier web address: <strong>{{.RestoreRecordedAddress}}</strong>.",
"deploy.ha_most_mashova_szeretned_telepiteni": "If you want to install it somewhere else now, change it — we will point out the difference before the restore.",
"deploy.az_alkalmazas_adatai_az_alabb": "The app’s data goes to the data drive chosen below.",
"deploy.hol_lesznek_az_adatok_ennel": "<strong>Where the data goes:</strong> this app has no separate data drive choice,\n so its data goes to the system drive (<code>{{.SystemDataPath}}</code>). Its backups are made all the same.",
"deploy.nincs_eleg_szabad_tarhely_a": "Not enough free storage for the install. {{.DockerFreeHuman}} is free now, and the system keeps\n {{.DockerReserveHuman}} in reserve to protect the core services (controller, proxy).\n So the install is blocked for now — free up space, or add storage.",
"deploy.automatikusan_generalt_ertekek": "Automatically generated values",
"deploy.ezek_az_ertekek_automatikusan_jottek": "These values were created automatically at install.",
"deploy.ezek_az_ertekek_a_telepitessel": "These values are saved together with the install. Write down the passwords you need!",
"deploy.automatikusan_generalva": "Generated automatically",
"deploy.megjelenites": "Show",
"deploy.telepites_utan_nem_modosithato": "cannot be changed after install",
"deploy.az_aldomain_telepites_utan_nem": "The subdomain cannot be changed after install. To change it, remove the app and install it again (all data is deleted).",
"deploy.generalas": "Generate",
"deploy.telepiteskor_beallitott_kezdeti_jelszo_h": "The first password set at install — if you changed it in the app, it is not updated here.",
"deploy.jelszo_megerositese": "Confirm password",
"deploy.igen": "Yes",
"deploy.szabad_2": "{{.Label}} — {{.FreeHuman}} free",
"deploy.alapertelmezett": "(default)",
"deploy.nem_elerheto": "{{$.CurrentHDDPath}} (not available)",
"deploy.a_kivalasztott_tarhely_majdnem_megtelt": "The chosen storage is almost full.",
"deploy.a_kivalasztott_meghajton_az_alkalmazas": "The chosen drive stores the app’s <strong>files</strong> (media, documents).\n The <strong>database runs on the fast internal SSD</strong> — and it is backed up together with the app.",
"deploy.nincs_regisztralt_adattarolo_adja_meg": "No registered data storage — enter the path by hand",
"deploy.nincs_eleg_szabad_tarhely": "Not enough free storage",
"deploy.nincs_eleg_memoria": "Not enough memory",
"deploy.telepites_inditasa": "Start install",
"deploy.telepites_folyamatban": "Installing...",
"deploy.konfiguracio_mentese": "Saving the configuration...",
"deploy.kontener_ek_inditasa": "Starting the container(s)...",
"deploy.alkalmazas_inicializalasa": "Initialising the app...",
"deploy.alkalmazas_megnyitasa": "Open the app ↗",
"deploy.elso_lepesek": "First steps",
"deploy.masolas": "Copy",
"deploy.bejelentkezes": "Sign-in",
"deploy.dokumentacio": "Documentation ↗",
"deploy.beallitasok_es_jelszavak_megtekintese": "View settings and passwords",
"deploy.alkalmazasok": "← Apps",
"deploy.a_lekeres_nem_sikerult": "The request failed.",
"deploy.elrejtes": "Hide",
"deploy.masolva": "Copied!",
"deploy.biztosan_torolni_szeretned_a_korabbi": "Delete the earlier data? Storage:",
"deploy.figyelem_a_muvelet_visszavonhatatlan_elo": "— WARNING: this cannot be undone! First make sure the app works properly from the new storage.",
"deploy.utolso_figyelmeztetes_a_torles_visszavon": "LAST WARNING! The deletion cannot be undone. Continue?",
"deploy.torles_folyamatban": "Deleting...",
"deploy.ismeretlen_hiba": "Unknown error",
"deploy.korabbi_adatok_torolve_n_nfelszabaditott": "Earlier data deleted!\\n\\nSpace freed:",
"deploy.n_nnehany_hiba_tortent_n": "\\n\\nSome errors occurred:\\n",
"deploy.halozati_hiba_2": "Network error:",
"deploy.kotelezo_mezo": "Required field:",
"deploy.nhasznalja_a_generalas_gombot_vagy": "\\nUse the Generate button or type a password.",
"deploy.a_ket_jelszo_nem_egyezik": "The two passwords do not match:",
"deploy.az_aldomain_csak_kisbetuket_szamokat": "The subdomain may contain only lowercase letters, digits and hyphens, and cannot start or end with a hyphen.",
"deploy.konfiguracio_mentve": "Configuration saved",
"deploy.masodperce": "seconds ago...",
"deploy.idotullepes_az_alkalmazas_meg_indulhat": "Timed out — the app may still be starting",
"deploy.a_telepites_idotullepesbe_futott_az": "The install timed out. The app may still be starting.",
"deploy.telepites_sikertelen": "Install failed",
"deploy.a_telepites_nem_sikerult": "The install failed:",
"deploy.naplok_megtekintese": "View logs",
"deploy.alkalmazasok_2": "Apps",
"deploy.kepek_letoltese_kontenerek_inditasa": "Downloading images, starting containers...",
"deploy.kontenerek_elindultak": "Containers started",
"deploy.alkalmazas_kesz": "App ready!",
"deploy.telepites_sikeres": "Install succeeded!",
"deploy.az_alkalmazas_sikeresen_telepitve_es": "The app is installed and running!",
"deploy.allapotjelzo_meg_inicializal": "Health check: still initialising",
"deploy.az_alkalmazas_elindult_de_meg": "The app started, but it is still initialising.",
"deploy.ez_normalis_az_elso_percekben": "This is normal in the first minutes — the app may already be reachable.",
"deploy.a_kontener_leallt": "The container stopped",
"deploy.a_kontener_leallt_ellenorizze_a": "The container stopped. Check the logs.",
"settings_system.beallitasok_rendszer": "Settings — System",
"settings_system.rendszer_konfiguracio": "System configuration",
"settings_system.az_uzemelteto_altal_beallitott_ertekek": "Values set by the operator. Ask the operator to change them.",
"settings_system.ugyfel_neve": "Customer name",
"settings_system.alkalmazas_sablon_forras": "App template source",
"settings_system.sablon_szinkronizalas": "Template sync",
"settings_system.biztonsagi_mentes": "Backup",
"settings_system.aktiv": "Active",
"settings_system.inaktiv": "Inactive",
"settings_system.mentes_utemezes": "Backup schedule",
"settings_system.hub_jelentes": "Hub reporting",
"settings_system.verzio_es_frissites": "Version and update",
"settings_system.jelenlegi_verzio": "Current version",
"settings_system.legujabb_verzio": "Latest version",
"settings_system.frissites_elerheto": "● Update available",
"settings_system.naprakesz": "— up to date",
"settings_system.utolso_ellenorzes": "Last check",
"settings_system.nyilvanos_hitelesites_nelkul": "public (no authentication)",
"settings_system.hitelesitett": "authenticated",
"settings_system.automatikus_frissites": "Automatic update",
"settings_system.minimalis_verzio_uzemelteto": "Minimum version (operator)",
"settings_system.a_rendszer_automatikusan_erre_a": "— the system updates to this version automatically if it is older",
"settings_system.allapot": "Status",
"settings_system.frissites_folyamatban_a_vezerlo_hamarosa": "Update in progress — the controller restarts soon…",
"settings_system.utolso_frissites": "Last update",
"settings_system.sikeres": "Succeeded",
"settings_system.sikertelen": "Failed",
"settings_system.folyamatban": "In progress",
"settings_system.frissites_keresese": "Check for updates",
"settings_system.frissites_telepitese": "Install update",
"settings_system.halozat": "Network",
"settings_system.helyi_cim_lan": "Local address (LAN)",
"settings_system.nem_allapithato_meg": "cannot be determined",
"settings_system.halozati_nev": "Network name",
"settings_system.atjaro": "Gateway",
"settings_system.ezek_az_ertekek_a_helyi": "These values depend on the local network and can change. For remote troubleshooting, we ask you to read out this page.",
"settings_system.szerver_memoria_ram": "Server memory (RAM)",
"settings_system.jelenlegi_memoria": "Current memory",
"settings_system.felhasznalt": "Used",
"settings_system.megengedett_tartomany": "Allowed range",
"settings_system.uj_meret_mb": "New size (MB)",
"settings_system.atmeretezes": "Resize",
"settings_system.a_kiszolgalo_ugynok_jelenleg_nem": "The host agent is not reachable right now — try again later.",
"settings_system.a_memoria_atmeretezesehez_a_kiszolgalo": "Resizing memory needs a system update on the host.",
"settings_system.megerosites": "Confirm",
"settings_system.ellenorzes": "Checking...",
"settings_system.kapcsolodasi_hiba": "Connection error",
"settings_system.vezerlo_frissitese": "Update controller",
"settings_system.frissites": "Update",
"settings_system.biztosan_frissiti_a_controllert_n": "Update the controller?\\n\\nThe dashboard is unreachable for a short time during the update.",
"settings_system.frissites_2": "Updating...",
"settings_system.frissites_folyamatban": "Update in progress...",
"settings_system.ujraindulas": "Restarting...",
"settings_system.ervenytelen_memoria_ertek": "Invalid memory value",
"settings_system.memoria_csokkentese": "Reduce memory",
"settings_system.csokkentes": "Reduce",
"settings_system.a_memoria_csokkentese_hatassal_lehet": "Reducing memory can affect the performance of running apps. Continue?",
"settings_system.atmeretezes_2": "Resizing...",
"settings_system.kesz": "Done.",
"settings_system.a_muvelet_nem_sikerult": "The operation failed",
"settings_system.vezerlo_ujrainditasa": "Restart controller",
"settings_system.ha_a_vezerlo_hibasan_mukodik": "If the controller misbehaves, you can safely restart it here — no need to restart the whole server.\n Your apps keep running; only the controller restarts (a few seconds).",
"settings_system.kiszolgalo_ujrainditasa": "Restart server",
"settings_system.az_egesz_kiszolgalo_szerver_ujrainditasa": "Restarts the whole server. Every app stops for a short time, and the dashboard is unreachable for about 30 seconds. Use it when the whole system needs a restart — otherwise “Restart controller” above is enough.",
"settings_system.biztosan_ujrainditja_a_vezerlot_a": "Restart the controller? It takes a few seconds, and the page is unreachable for a short time.",
"settings_system.ujrainditas_folyamatban_ujracsatlakozas": "Restarting… reconnecting…",
"settings_system.biztosan_ujrainditja_a_kiszolgalot_az": "Restart the server? The apps and the dashboard become unreachable for about 30 seconds.",
"settings_system.ujrainditas_folyamatban_a_vezerlopult_ne": "Restarting… the dashboard reloads in a few seconds.",
"settings_system.az_ujrainditas_a_vartnal_tovabb": "The restart is taking longer than expected. Reload the page by hand.",
"settings_security.beallitasok_biztonsag_es_hozzaferes": "Settings — Security and access",
"settings_security.jelszo_modositas": "Change password",
"settings_security.jelenlegi_jelszo": "Current password",
"settings_security.adja_meg_a_jelenlegi_jelszavat": "Enter your current password",
"settings_security.uj_jelszo": "New password",
"settings_security.legalabb_8_karakter": "At least 8 characters",
"settings_security.uj_jelszo_megerositese": "Confirm new password",
"settings_security.jelszo_megegyszer": "Password again",
"settings_security.jelszo_modositasa": "Change password",
"settings_security.a_jelszavas_vedelem_nincs_beallitva": "Password protection is not set up. Ask the operator to set it up.",
"settings_security.foldrajzi_korlatozas": "Country restriction",
"settings_security.orszag_alapjan_korlatozhato_a_webes": "Access to the web apps can be limited by country with the Cloudflare WAF.",
"settings_security.a_helyi_halozati_hozzaferes_mindig": "Access from the local network is always allowed (it does not pass through Cloudflare).",
"settings_security.a_cloudflare_api_token_nincs": "The Cloudflare API token is not configured. Ask the operator to set it up.<br>\n <small>The token needs the <strong>Zone WAF:Edit</strong> permission.</small>",
"settings_security.geo_korlatozas_aktiv": "Country restriction active",
"settings_security.engedelyezett_orszagok_globalis": "Allowed countries (global)",
"settings_security.orszag_keresese": "Search country...",
"settings_security.csak_a_kivalasztott_orszagokbol_erheto": "The system is reachable only from the chosen countries.",
"settings_security.alkalmazas_specifikus_felulirasok": "Per-app overrides",
"settings_security.alkalmazas_kivalasztasa": "— Choose an app —",
"settings_security.hozzaadas": "+ Add",
"settings_security.mentes_es_szinkronizalas": "Save and sync",
"settings_security.kezi_szinkronizalas": "Sync by hand",
"settings_security.utolso_szinkronizalas": "Last sync: {{.GeoLastSync}}",
"settings_security.megerosites": "Confirm",
"settings_security.nincs_talalat": "No match",
"settings_security.figyelem_magyarorszag_eltavolitasa_azt_j": "Warning: removing Hungary means the system is not reachable remotely from a Hungarian IP either. Continue?",
"settings_security.magyarorszag_eltavolitasa_nem_ajanlott_f": "Removing Hungary is not recommended. Continue?",
"settings_security.nincs_alkalmazas_specifikus_beallitas_mi": "No per-app setting. Every app follows the global setting.",
"settings_security.szerkesztes": "Edit",
"settings_security.engedelyezett_orszagok": "Allowed countries:",
"settings_security.kesz": "Done",
"settings_security.mentes": "Saving...",
"settings_security.mentve": "Saved",
"settings_security.halozati_hiba": "Network error",
"settings_security.szinkronizalas_elinditva": "Sync started...",
"settings_security.utolso": "Last:",
"settings_security.veszhelyzeti_informaciok": "Emergency information",
"settings_security.ezeket_az_adatokat_mentse_el": "Keep these details in a safe place. You need them to restore the system after a reinstall.",
"settings_security.visszaallitasi_jelszo": "Retrieval password",
"settings_security.megjelenit": "Show",
"settings_security.tamogatas": "Support",
"settings_security.felhom_eu_kapcsolat": "<a href=\"mailto:{{.SupportEmail}}\" style=\"color: var(--blue);\">{{.SupportEmail}}</a>\n &nbsp;|&nbsp;\n <a href=\"{{.SupportURL}}\" target=\"_blank\" style=\"color: var(--blue);\">felhom.eu/kapcsolat</a>",
"settings_security.a_visszaallitasi_jelszo_lekerese_nem": "The retrieval password could not be loaded.",
"settings_security.elrejt": "Hide",
"settings_notifications.beallitasok_ertesitesek": "Settings — Notifications",
"settings_notifications.ertesitesek": "Notifications",
"settings_notifications.e_mail_cim": "E-mail address",
"settings_notifications.hibak_es_figyelmeztetesek": "Errors and warnings:",
"settings_notifications.biztonsagi_mentes_sikertelen": "Backup failed",
"settings_notifications.adatbazis_mentes_sikertelen": "Database backup failed",
"settings_notifications.mentes_serules_eszlelve": "Backup damage detected",
"settings_notifications.masodlagos_mentes_sikertelen": "Secondary backup failed",
"settings_notifications.tavoli_mentes_tarhelykeret_figyelmeztete": "Remote backup — storage quota warning",
"settings_notifications.lemez_beteloben_90_felett": "Disk filling up (over 90%)",
"settings_notifications.lemez_megtelt_vagy_meghibasodott": "Disk full or failed",
"settings_notifications.meghajto_levalasztva": "Drive disconnected",
"settings_notifications.szerver_nem_elerheto": "Server not reachable",
"settings_notifications.rendszer_allapot_kritikus": "System health critical",
"settings_notifications.elvart_fajlmentes_elmaradt": "Expected file backup missed",
"settings_notifications.elvart_adatbazismentes_elmaradt": "Expected database backup missed",
"settings_notifications.alkalmazas_nem_fut": "App not running",
"settings_notifications.tajekoztato": "For information:",
"settings_notifications.meghajto_ujra_csatlakoztatva": "Drive connected again",
"settings_notifications.rendszer_allapot_helyreallt": "System health recovered",
"settings_notifications.ertesitesi_szunet": "Notification pause",
"settings_notifications.ora_azonos_problema_eseten_ennyi": "hours (for the same problem, no new message is sent for this long)",
"settings_notifications.mentes": "Save",
"settings_notifications.teszt_email_kuldese": "Send test e-mail",
"settings_notifications.az_ertesitesek_a_kozponti_rendszeren": "Notifications work through the central system, which is not turned on right now.",
"settings_notifications.alkalmazas_email": "App e-mail",
"settings_notifications.az_alkalmazasok_a_felhom_on": "Apps can send e-mail through Felhom (for example password resets, invitations),\n without setting up a separate e-mail provider. Each app’s sender address is its own\n <em>&lt;app&gt;@felhom.eu</em> address.",
"settings_notifications.alkalmazas_email_engedelyezese": "Allow app e-mail",
"settings_notifications.felado_neve_opcionalis": "Sender name (optional)",
"settings_notifications.pl_a_haztartas_neve": "for example the household name",
"settings_notifications.ez_jelenik_meg_a_kimeno": "This appears as the sender of outgoing e-mails, next to the app name.",
"settings_notifications.bekapcsolas_utan_az_egyes_alkalmazasokna": "After turning it on, allow sending e-mail for each app too (on the app’s page).",
"stacks.mind": "All",
"app_export.folyamat": "Progress",
"app_import.titkos": "Encrypted",
"monitoring.processzor": "Processor",
"deploy.hamarosan": "Coming soon",
"deploy.az_adatai_itt_voltak": "Its data was here: <strong>{{.RestoreRecordedDrive}}</strong>.",
"deploy.aldomain_placeholder": "subdomain",
"settings_security.figyelem": "Warning",
"settings_notifications.pelda_email": "example@email.com",
"page.title.dashboard": "Dashboard",
"page.title.stacks": "Apps",
"page.title.monitoring": "System monitor",
"page.title.settings": "Settings",
"page.title.settings_notifications": "Notifications",
"page.title.settings_security": "Security and access",
"func.infra.cloudflared.description": "Secure internet connection — the server is reachable from outside without opening ports.",
"func.infra.traefik.description": "Traffic router (reverse proxy) — sends each request to the right app.",
"func.infra.filebrowser.description": "File manager — browse the storage files from your browser.",
"func.infra.samba.display": "Network sharing",
"func.infra.samba.description": "File sharing on the local network — the folders appear in Windows Explorer and the Mac Finder."
} }
+485 -20
View File
@@ -1,4 +1,20 @@
{ {
"common.bezaras": "Bezárás",
"common.elavult": "Elavult",
"common.eltavolitas": "Eltávolítás",
"common.hdd_szukseges": "HDD szükséges",
"common.hiba": "Hiba",
"common.hiba_kettospont": "Hiba:",
"common.hiba_tortent": "Hiba történt",
"common.jelszo": "Jelszó",
"common.levalasztva": "Leválasztva",
"common.magyar_felulet": "Magyar felület",
"common.megsem": "Mégsem",
"common.pi_kompatibilis": "Pi kompatibilis",
"common.rendszer_gyoker": "Rendszer (/)",
"common.torles": "Törlés",
"common.ugyfel_azonosito": "Ügyfél azonosító",
"common.ujrainditas": "Újraindítás",
"layout.html_lang": "hu", "layout.html_lang": "hu",
"launcher.inditopult": "Indítópult", "launcher.inditopult": "Indítópult",
"launcher.inditopult_megosztasa": "Indítópult megosztása", "launcher.inditopult_megosztasa": "Indítópult megosztása",
@@ -10,7 +26,6 @@
"launcher.megosztas": "Megosztás…", "launcher.megosztas": "Megosztás…",
"launcher.qr_kod_a_megosztasi_linkhez": "QR-kód a megosztási linkhez", "launcher.qr_kod_a_megosztasi_linkhez": "QR-kód a megosztási linkhez",
"launcher.olvassa_be_telefonnal_a_gyors": "Olvassa be telefonnal a gyors megnyitáshoz.", "launcher.olvassa_be_telefonnal_a_gyors": "Olvassa be telefonnal a gyors megnyitáshoz.",
"launcher.jelszo": "Jelszó",
"launcher.a_megosztas_jelszoval_vedett": "A megosztás jelszóval védett.", "launcher.a_megosztas_jelszoval_vedett": "A megosztás jelszóval védett.",
"launcher.jelszo_torlese": "Jelszó törlése", "launcher.jelszo_torlese": "Jelszó törlése",
"launcher.jelszo_nem_kotelezo": "Jelszó (nem kötelező)", "launcher.jelszo_nem_kotelezo": "Jelszó (nem kötelező)",
@@ -21,7 +36,6 @@
"launcher.biztosan_kikapcsolja_a_megosztast_a": "Biztosan kikapcsolja a megosztást? A link azonnal érvénytelenné válik.", "launcher.biztosan_kikapcsolja_a_megosztast_a": "Biztosan kikapcsolja a megosztást? A link azonnal érvénytelenné válik.",
"launcher.megosztas_kikapcsolasa": "Megosztás kikapcsolása", "launcher.megosztas_kikapcsolasa": "Megosztás kikapcsolása",
"launcher.a_megosztas_jelenleg_ki_van": "A megosztás jelenleg ki van kapcsolva.", "launcher.a_megosztas_jelenleg_ki_van": "A megosztás jelenleg ki van kapcsolva.",
"launcher.bezaras": "Bezárás",
"launcher.megosztas_bekapcsolasa": "Megosztás bekapcsolása", "launcher.megosztas_bekapcsolasa": "Megosztás bekapcsolása",
"launcher.masolva": "Másolva", "launcher.masolva": "Másolva",
"launcher.inditopult_2": "Indítópult — {{.Domain}}", "launcher.inditopult_2": "Indítópult — {{.Domain}}",
@@ -30,8 +44,6 @@
"backups.kijelolod_ezt_a_meghajtot_a": "Kijelölöd ezt a meghajtót a rendszermentés helyéül?", "backups.kijelolod_ezt_a_meghajtot_a": "Kijelölöd ezt a meghajtót a rendszermentés helyéül?",
"backups.kijelolom": "Kijelölöm", "backups.kijelolom": "Kijelölöm",
"backups.tarhely_attekintes": "Tárhely áttekintés", "backups.tarhely_attekintes": "Tárhely áttekintés",
"backups.rendszer": "Rendszer (/)",
"backups.levalasztva": "Leválasztva",
"backups.db_mentesek": "DB mentések", "backups.db_mentesek": "DB mentések",
"backups.fajl": "{{len .Backup.DumpFiles}} fájl", "backups.fajl": "{{len .Backup.DumpFiles}} fájl",
"backups.rendszermentes_teljes_mentes": "Rendszermentés (teljes mentés)", "backups.rendszermentes_teljes_mentes": "Rendszermentés (teljes mentés)",
@@ -68,9 +80,7 @@
"backups.beagyazott_db_k_a_kotetmentesben": "beágyazott DB-k a kötetmentésben", "backups.beagyazott_db_k_a_kotetmentesben": "beágyazott DB-k a kötetmentésben",
"backups.elinditod_a_teljes_rendszermentest_most": "Elindítod a teljes rendszermentést most? A mentés alatt az alkalmazások leállnak — általában néhány perc, nagyobb adatnál több.", "backups.elinditod_a_teljes_rendszermentest_most": "Elindítod a teljes rendszermentést most? A mentés alatt az alkalmazások leállnak — általában néhány perc, nagyobb adatnál több.",
"backups.mentes_inditasa": "Mentés indítása…", "backups.mentes_inditasa": "Mentés indítása…",
"backups.hiba": "Hiba",
"backups.mentes_folyamatban": "Mentés folyamatban…", "backups.mentes_folyamatban": "Mentés folyamatban…",
"backups.hiba_2": "Hiba:",
"backups.allapot": "Állapot:", "backups.allapot": "Állapot:",
"backups.a_rendszermentes_elkeszult": "A rendszermentés elkészült.", "backups.a_rendszermentes_elkeszult": "A rendszermentés elkészült.",
"backups.a_mentes_sikertelen": "A mentés sikertelen.", "backups.a_mentes_sikertelen": "A mentés sikertelen.",
@@ -79,20 +89,15 @@
"backups.a_meghajto_kijelolve_a_beallitas": "A meghajtó kijelölve. A beállítás a host-ügynök következő újraindulása után lép életbe.", "backups.a_meghajto_kijelolve_a_beallitas": "A meghajtó kijelölve. A beállítás a host-ügynök következő újraindulása után lép életbe.",
"backups.a_meghajto_kijelolve": "A meghajtó kijelölve.", "backups.a_meghajto_kijelolve": "A meghajtó kijelölve.",
"app_info.alkalmazasok": "← Alkalmazások", "app_info.alkalmazasok": "← Alkalmazások",
"app_info.elavult": "Elavult",
"app_info.megnyitas": "Megnyitás ↗", "app_info.megnyitas": "Megnyitás ↗",
"app_info.naplo": "Napló", "app_info.naplo": "Napló",
"app_info.torles": "Törlés",
"app_info.exportalas": "Exportálás", "app_info.exportalas": "Exportálás",
"app_info.beallitasok": "Beállítások", "app_info.beallitasok": "Beállítások",
"app_info.telepites": "Telepítés", "app_info.telepites": "Telepítés",
"app_info.mentesek": "Mentések", "app_info.mentesek": "Mentések",
"app_info.hianyzo_tarhely": "Hiányzó tárhely:", "app_info.hianyzo_tarhely": "Hiányzó tárhely:",
"app_info.ennek_az_alkalmazasnak_az_adattaroloja": "Ennek az alkalmazásnak az adattárolója jelenleg nem elérhető, ezért le van állítva. Csatlakoztasd újra a meghajtót, vagy helyezd át az adatokat egy másik tárhelyre.", "app_info.ennek_az_alkalmazasnak_az_adattaroloja": "Ennek az alkalmazásnak az adattárolója jelenleg nem elérhető, ezért le van állítva. Csatlakoztasd újra a meghajtót, vagy helyezd át az adatokat egy másik tárhelyre.",
"app_info.hdd_szukseges": "HDD szükséges",
"app_info.pi_kompatibilis": "Pi kompatibilis",
"app_info.csak_x86": "Csak x86", "app_info.csak_x86": "Csak x86",
"app_info.magyar_felulet": "Magyar felület",
"app_info.az_alkalmazas_fejlesztoje_felhagyott_a": "Az alkalmazás fejlesztője felhagyott a fejlesztéssel. A telepített verzió továbbra is használható,\n de frissítések és biztonsági javítások már nem érkeznek hozzá.", "app_info.az_alkalmazas_fejlesztoje_felhagyott_a": "Az alkalmazás fejlesztője felhagyott a fejlesztéssel. A telepített verzió továbbra is használható,\n de frissítések és biztonsági javítások már nem érkeznek hozzá.",
"app_info.hova_tegyem_a_fajlokat": "Hova tegyem a fájlokat?", "app_info.hova_tegyem_a_fajlokat": "Hova tegyem a fájlokat?",
"app_info.ezeket_a_mappakat_a_fajlkezeloben": "Ezeket a mappákat a Fájlkezelőben éred el. Előfordulhat, hogy először be kell jelentkezned a Fájlkezelőbe.", "app_info.ezeket_a_mappakat_a_fajlkezeloben": "Ezeket a mappákat a Fájlkezelőben éred el. Előfordulhat, hogy először be kell jelentkezned a Fájlkezelőbe.",
@@ -113,14 +118,12 @@
"app_info.athelyezed_a_z": "Áthelyezed a(z)", "app_info.athelyezed_a_z": "Áthelyezed a(z)",
"app_info.adatait_ide": "adatait ide:", "app_info.adatait_ide": "adatait ide:",
"app_info.az_alkalmazas_rovid_idore_leall": "? Az alkalmazás rövid időre leáll. A régi adatok csak sikeres áthelyezés után törlődnek.", "app_info.az_alkalmazas_rovid_idore_leall": "? Az alkalmazás rövid időre leáll. A régi adatok csak sikeres áthelyezés után törlődnek.",
"app_info.hiba": "Hiba:",
"app_info.ismeretlen": "ismeretlen", "app_info.ismeretlen": "ismeretlen",
"app_info.mire_hasznalhato": "Mire használható?", "app_info.mire_hasznalhato": "Mire használható?",
"app_info.elso_lepesek": "Első lépések", "app_info.elso_lepesek": "Első lépések",
"app_info.elofeltetelek": "Előfeltételek", "app_info.elofeltetelek": "Előfeltételek",
"app_info.kezdeti_belepesi_adatok": "Kezdeti belépési adatok", "app_info.kezdeti_belepesi_adatok": "Kezdeti belépési adatok",
"app_info.felhasznalonev": "Felhasználónév", "app_info.felhasznalonev": "Felhasználónév",
"app_info.jelszo": "Jelszó",
"app_info.az_uzemelteto_allitotta_be": "az üzemeltető állította be", "app_info.az_uzemelteto_allitotta_be": "az üzemeltető állította be",
"app_info.megjelenites": "Megjelenítés", "app_info.megjelenites": "Megjelenítés",
"app_info.masolas": "Másolás", "app_info.masolas": "Másolás",
@@ -169,7 +172,6 @@
"layout.ez_az_alkalmazas_mar_telepitve": "Ez az alkalmazás már telepítve van.", "layout.ez_az_alkalmazas_mar_telepitve": "Ez az alkalmazás már telepítve van.",
"layout.frissites": "↻ Frissítés...", "layout.frissites": "↻ Frissítés...",
"layout.sablonok_frissitve": "Sablonok frissítve", "layout.sablonok_frissitve": "Sablonok frissítve",
"layout.hiba": "Hiba:",
"layout.ismeretlen_hiba": "Ismeretlen hiba", "layout.ismeretlen_hiba": "Ismeretlen hiba",
"layout.halozati_hiba": "Hálózati hiba:", "layout.halozati_hiba": "Hálózati hiba:",
"layout.folyamatban": "Folyamatban...", "layout.folyamatban": "Folyamatban...",
@@ -180,11 +182,7 @@
"layout.alkalmazas_torlese": "Alkalmazás törlése:", "layout.alkalmazas_torlese": "Alkalmazás törlése:",
"layout.ez_a_muvelet_eltavolitja_a": "Ez a művelet eltávolítja a konténereket, a köteteket és a konfigurációs fájlokat.", "layout.ez_a_muvelet_eltavolitja_a": "Ez a művelet eltávolítja a konténereket, a köteteket és a konfigurációs fájlokat.",
"layout.ez_a_muvelet_nem_visszavonhato": "Ez a művelet nem visszavonható!", "layout.ez_a_muvelet_nem_visszavonhato": "Ez a művelet nem visszavonható!",
"layout.megsem": "Mégsem",
"layout.torles": "Törlés",
"layout.hiba_2": "Hiba",
"layout.nem_sikerult_lekerni_az_adatokat": "Nem sikerült lekérni az adatokat:", "layout.nem_sikerult_lekerni_az_adatokat": "Nem sikerült lekérni az adatokat:",
"layout.bezaras": "Bezárás",
"layout.torles_folyamatban": "Törlés folyamatban...", "layout.torles_folyamatban": "Törlés folyamatban...",
"layout.torolt_adatok": "Törölt adatok:", "layout.torolt_adatok": "Törölt adatok:",
"layout.megorzott_adatok": "Megőrzött adatok:", "layout.megorzott_adatok": "Megőrzött adatok:",
@@ -202,7 +200,6 @@
"layout.az_ejszakai_restic_pillanatkepek_nem": "Az éjszakai restic pillanatképek nem törölhetők egyenként — a megőrzési szabályok szerint automatikusan elavulnak.", "layout.az_ejszakai_restic_pillanatkepek_nem": "Az éjszakai restic pillanatképek nem törölhetők egyenként — a megőrzési szabályok szerint automatikusan elavulnak.",
"layout.alkalmazas_eltavolitasa": "Alkalmazás eltávolítása:", "layout.alkalmazas_eltavolitasa": "Alkalmazás eltávolítása:",
"layout.az_alkalmazas_visszaall_nincs_telepitve": "Az alkalmazás visszaáll \"Nincs telepítve\" állapotba. A sablon megmarad, újratelepíthető.", "layout.az_alkalmazas_visszaall_nincs_telepitve": "Az alkalmazás visszaáll \"Nincs telepítve\" állapotba. A sablon megmarad, újratelepíthető.",
"layout.eltavolitas": "Eltávolítás",
"layout.eltavolitas_folyamatban": "Eltávolítás folyamatban...", "layout.eltavolitas_folyamatban": "Eltávolítás folyamatban...",
"layout.torolt_mentesek": "Törölt mentések:", "layout.torolt_mentesek": "Törölt mentések:",
"layout.nem_torolt_mentesek": "Nem törölt mentések:", "layout.nem_torolt_mentesek": "Nem törölt mentések:",
@@ -233,5 +230,473 @@
"func.restore_status.restoring": "Visszaállítás...", "func.restore_status.restoring": "Visszaállítás...",
"func.restore_status.done": "Kész", "func.restore_status.done": "Kész",
"func.restore_status.failed": "Sikertelen", "func.restore_status.failed": "Sikertelen",
"func.restore_status.skipped": "Kihagyva" "func.restore_status.skipped": "Kihagyva",
"dashboard.vezerlopult": "Vezérlőpult",
"dashboard.futo_alkalmazas": "Futó alkalmazás",
"dashboard.leallitva": "Leállítva",
"dashboard.osszes_alkalmazas": "Összes alkalmazás",
"dashboard.memoria": "Memória",
"dashboard.homerseklet": "Hőmérséklet",
"dashboard.nem_ismert": "nem ismert",
"dashboard.a_tarhely_merete_most_nem": "A tárhely mérete most nem olvasható ki.",
"dashboard.fogyoban_a_hely": "Fogyóban a hely",
"dashboard.kritikusan_keves_hely": "Kritikusan kevés hely",
"dashboard.lemezek_allapota": "Lemezek állapota",
"dashboard.nincs_adat": "Nincs adat",
"dashboard.biztonsagi_mentes": "Biztonsági mentés",
"dashboard.utolso_mentes": "Utolsó mentés:",
"dashboard.sikertelen": "Sikertelen",
"dashboard.meg_nem_futott": "Még nem futott",
"dashboard.adatbazisok": "Adatbázisok:",
"dashboard.mentve": "{{len .DBDumpStatus.Results}} mentve",
"dashboard.telepitett_alkalmazasok": "Telepített alkalmazások",
"dashboard.az_alkalmazas_adattaroloja_nem_elerheto": "Az alkalmazás adattárolója nem elérhető. Csatlakoztasd újra a meghajtót, vagy helyezd át az adatokat egy másik tárhelyre.",
"dashboard.hianyzo_tarhely": "Hiányzó tárhely: {{$ms}}",
"dashboard.az_alkalmazas_egyik_folyamatat_a": "Az alkalmazás egyik folyamatát a rendszer leállította, mert elfogyott a memóriája. Lehet, hogy egy feladat nem fejeződött be. Indítsd újra az alkalmazást; ha ismétlődik, jelezd az üzemeltetőnek.",
"dashboard.memoria_elfogyott": "Memória elfogyott",
"dashboard.az_alkalmazas_kornyezeteben_a_halozati": "Az alkalmazás környezetében a hálózati tárhely helyén üres helyi könyvtár van — az adatok nem a NAS-ra kerülnek. Jelezze az üzemeltetőnek.",
"dashboard.halozati_tarhely_hibas_az_alkalmazas": "Hálózati tárhely hibás — az alkalmazás nem a NAS-t látja",
"dashboard.a_halozati_tarhely_nas_jelenleg": "A hálózati tárhely (NAS) jelenleg nem érhető el. Az alkalmazás fut; az adatok elérése a NAS visszatértével helyreáll.",
"dashboard.halozati_tarhely_nem_elerheto": "Hálózati tárhely nem elérhető: {{$nw}}",
"dashboard.a_proxy_traefik_csak_egeszseges": "A proxy (Traefik) csak egészséges konténerhez publikál nyilvános útvonalat. Amíg az alkalmazás nem egészséges, az URL 404-et ad, pedig a konténer fut.",
"dashboard.url_nem_elerheto": "URL nem elérhető",
"dashboard.vedett": "Védett",
"dashboard.megnyitas": "Megnyitás",
"dashboard.telepites": "Telepítés",
"dashboard.leallitas": "Leállítás",
"dashboard.inditas": "Indítás",
"dashboard.naplo": "Napló",
"dashboard.nincs_elerheto_alkalmazas": "Nincs elérhető alkalmazás.",
"stacks.alkalmazasok": "Alkalmazások",
"stacks.alkalmazas_visszaallitasa_exportalt_csom": "Alkalmazás visszaállítása exportált csomagból",
"stacks.importalas": "Importálás",
"stacks.sablonok_frissitese_a_kozponti_katalogus": "Sablonok frissítése a központi katalógusból",
"stacks.sablonok_frissitese": "↻ Sablonok frissítése",
"stacks.futo": "Futó",
"stacks.leallitva": "Leállítva",
"stacks.telepitheto": "Telepíthető",
"stacks.a_proxy_traefik_csak_egeszseges": "A proxy (Traefik) csak egészséges konténerhez publikál nyilvános útvonalat. Amíg az alkalmazás nem egészséges, az URL nem érhető el (404), pedig a konténer fut.",
"stacks.url_nem_elerheto_utvonal_nincs": "URL nem elérhető – útvonal nincs publikálva",
"stacks.az_alkalmazas_adattaroloja_nem_elerheto": "Az alkalmazás adattárolója nem elérhető. Csatlakoztasd újra a meghajtót, vagy helyezd át az adatokat.",
"stacks.hianyzo_tarhely": "Hiányzó tárhely: {{$ms}}",
"stacks.az_alkalmazas_kornyezeteben_a_halozati": "Az alkalmazás környezetében a hálózati tárhely helyén üres helyi könyvtár van — az adatok nem a NAS-ra kerülnek. Jelezze az üzemeltetőnek.",
"stacks.halozati_tarhely_hibas_az_alkalmazas": "Hálózati tárhely hibás — az alkalmazás nem a NAS-t látja",
"stacks.a_halozati_tarhely_nas_jelenleg": "A hálózati tárhely (NAS) jelenleg nem érhető el. Az alkalmazás fut; az adatok elérése a NAS visszatértével helyreáll.",
"stacks.halozati_tarhely_nem_elerheto": "Hálózati tárhely nem elérhető: {{$nw}}",
"stacks.adattarolo": "Adattároló: {{index $.StorageLabels .Name}}",
"stacks.vedett_rendszerkomponens": "Védett rendszerkomponens",
"stacks.reszletek": "Részletek",
"stacks.telepites": "Telepítés",
"stacks.mentesek": "Mentések",
"stacks.frissites": "Frissítés",
"stacks.leallitas": "Leállítás",
"stacks.inditas": "Indítás",
"stacks.naplok": "Naplók",
"logs.vissza": "← Vissza",
"logs.naplok": "{{.Stack.Meta.DisplayName}} — Naplók",
"logs.elo": "Élő",
"logs.szuneteltetes": "Szüneteltetés",
"logs.frissites": "Frissítés",
"logs.szunetelve": "Szünetelve",
"logs.folytatas": "Folytatás",
"monitoring.rendszermonitor": "Rendszermonitor",
"monitoring.szerver_allapota_gazdagep": "Szerver állapota (gazdagép)",
"monitoring.a_gazdagep_metrikai_jelenleg_nem": "A gazdagép metrikái jelenleg nem elérhetők.",
"monitoring.cpu_hasznalat": "CPU használat",
"monitoring.terheles_load": "Terhelés (load)",
"monitoring.memoria": "Memória",
"monitoring.cpu_homerseklet": "CPU hőmérséklet",
"monitoring.uzemido": "Üzemidő",
"monitoring.tarolok_kapacitasa": "Tárolók kapacitása",
"monitoring.rendszer_attekintes": "Rendszer áttekintés",
"monitoring.gepnev": "Gépnév",
"monitoring.operacios_rendszer": "Operációs rendszer",
"monitoring.inditas": "Indítás",
"monitoring.tarhely": "Tárhely",
"monitoring.hub_kapcsolat": "Hub kapcsolat",
"monitoring.kapcsolodva_a_kozponti_rendszer_aktivan": "Kapcsolódva — a központi rendszer aktívan figyeli a szervert.",
"monitoring.nem_elerheto_a_kozponti_rendszer": "Nem elérhető — a központi rendszer nem kapott friss jelentést.",
"monitoring.utolso_sikeres_jelentes": "Utolsó sikeres jelentés",
"monitoring.utolso_hiba": "Utolsó hiba",
"monitoring.a_hub_kapcsolat_nincs_bekapcsolva": "A Hub kapcsolat nincs bekapcsolva — a központi monitoring nem aktív.",
"monitoring.rendszer_metrikak": "Rendszer metrikák",
"monitoring.1_ora": "1 óra",
"monitoring.6_ora": "6 óra",
"monitoring.24_ora": "24 óra",
"monitoring.7_nap": "7 nap",
"monitoring.30_nap": "30 nap",
"monitoring.cpu_hasznalat_2": "CPU használat (%)",
"monitoring.memoria_hasznalat_gb": "Memória használat (GB)",
"monitoring.homerseklet_c": "Hőmérséklet (°C)",
"monitoring.terheles_load_average": "Terhelés (Load Average)",
"monitoring.meg_nincsenek_adatok_a_metrikak": "Még nincsenek adatok. A metrikák gyűjtése elindult, az első adatok néhány perc múlva megjelennek.",
"monitoring.memoria_eloszlas": "Memória eloszlás",
"monitoring.alkalmazas_eroforrasok": "Alkalmazás erőforrások",
"monitoring.memoria_hasznalat_mb": "Memória használat (MB)",
"monitoring.meg_nincsenek_kontener_adatok": "Még nincsenek konténer adatok.",
"monitoring.memoria_mb": "Memória (MB)",
"monitoring.hasznalt": "Használt:",
"monitoring.rendszer_title": "Rendszer:",
"monitoring.rendszer_2": "Rendszer (",
"monitoring.szabad": "Szabad (",
"monitoring.eroforras_elozmenyek": "— Erőforrás előzmények",
"monitoring.nincs_tarolo_adat": "Nincs tároló adat.",
"monitoring.nem_elerheto": "Nem elérhető",
"monitoring.kopas": "kopás:",
"monitoring.frissitve": "Frissítve:",
"monitoring.nap": "nap,",
"monitoring.ora": "óra",
"monitoring.ora_2": "óra,",
"monitoring.perc": "perc",
"app_import.alkalmazasok": "&larr; Alkalmazások",
"app_import.alkalmazas_importalas": "Alkalmazás importálás",
"app_import.fab_csomag_feltoltese_huzza_ide": ".fab csomag feltöltése &mdash; húzza ide, vagy válassza ki a fájlt",
"app_import.fajl_kivalasztasa": "Fájl kiválasztása",
"app_import.feltoltes_0": "Feltöltés: 0%",
"app_import.megszakitas": "Megszakítás",
"app_import.nem_talalhato_fab_csomag_a": "Nem található .fab csomag a regisztrált tárolókon.",
"app_import.exportalj_egy_alkalmazast_az_alkalmazas": "Exportálj egy alkalmazást az alkalmazás oldaláról, tölts fel egy .fab fájlt itt fent, vagy másolj egyet a <code>{tároló}/felhom-data/exports/</code> könyvtárba.",
"app_import.alkalmazas": "Alkalmazás",
"app_import.datum": "Dátum",
"app_import.meret": "Méret",
"app_import.tarolo": "Tároló",
"app_import.reszletek": "Részletek &raquo;",
"app_import.csomag_reszletei": "Csomag részletei",
"app_import.ez_a_csomag_jelszoval_vedett": "Ez a csomag jelszóval védett. Kérlek add meg a jelszót:",
"app_import.megnyitas": "Megnyitás",
"app_import.alkalmazas_2": "Alkalmazás:",
"app_import.exportalva": "Exportálva:",
"app_import.controller_verzio": "Controller verzió:",
"app_import.adatbazis": "Adatbázis:",
"app_import.adatok": "Adatok:",
"app_import.meret_2": "Méret:",
"app_import.figyelem_a_meglevo": "<strong>&#9888; FIGYELEM:</strong> A meglévő",
"app_import.alkalmazas_konfiguracioja_es_osszes_adat": "alkalmazás konfigurációja és összes adata felül lesz írva!",
"app_import.visszaallitas_inditasa": "Visszaállítás indítása",
"app_import.importalas_folyamata": "Importálás folyamata",
"app_import.importalas_kesz": "Importálás kész!",
"app_import.az_alkalmazas_sikeresen_visszaallitva": "Az alkalmazás sikeresen visszaállítva.",
"app_import.alkalmazasok_megtekintese": "Alkalmazások megtekintése",
"app_import.hibas_jelszo": "Hibás jelszó",
"app_import.csomag_reszletei_2": "— Csomag részletei",
"app_import.nincs": "Nincs",
"app_import.alkalmazas_megtekintese": "Alkalmazás megtekintése",
"app_import.a_feltoltes_megszakadt_ellenorizze_a": "A feltöltés megszakadt — ellenőrizze a kapcsolatot, majd próbálja újra.",
"app_import.feltoltes": "Feltöltés:",
"app_import.csak_fab_fajl_toltheto_fel": "Csak .fab fájl tölthető fel.",
"app_import.feltoltes_kesz": "Feltöltés kész:",
"app_export.alkalmazasok": "&larr; Alkalmazások",
"app_export.exportalas": "{{.Stack.Meta.DisplayName}} &mdash; Exportálás",
"app_export.mentes_helye": "Mentés helye",
"app_export.valassz_tarolot": "Válassz tárolót...",
"app_export.konfiguracio": "Konfiguráció:",
"app_export.felhasznaloi_adatok": "Felhasználói adatok:",
"app_export.osszesen": "Összesen:",
"app_export.szabad": "(szabad: -)",
"app_export.becsult_ido": "Becsült idő:",
"app_export.mindig_resze_a_mentesnek": "Mindig része a mentésnek:",
"app_export.valaszthato_tartalom": "Választható tartalom:",
"app_export.a_kihagyott_mappak_tartalma_a": "A kihagyott mappák tartalma a Fájlkezelőben bármikor elérhető, és külön is lementhető.",
"app_export.kihagyott_tartalom_megjelenitese": "Kihagyott tartalom megjelenítése",
"app_export.jelszo_opcionalis": "Jelszó (opcionális)",
"app_export.titkositasi_jelszo": "Titkosítási jelszó",
"app_export.jelszo_mutatasa": "Jelszó mutatása",
"app_export.alkalmazas_leallitasa_mentes_elott_ajanl": "Alkalmazás leállítása mentés előtt (ajánlott)",
"app_export.az_adatok_konzisztenciaja_erdekeben_java": "Az adatok konzisztenciája érdekében javasolt leállítani az alkalmazást mentés közben.",
"app_export.exportalas_inditasa": "Exportálás indítása",
"app_export.kesz": "Kész!",
"app_export.megnyitas_filebrowser_ben": "Megnyitás FileBrowser-ben &nearr;",
"app_export.szabad_2": "(szabad:",
"app_export.perc": "perc",
"app_export.nincs_eleg_szabad_hely_a": "Nincs elég szabad hely a kiválasztott tárolón!",
"app_export.alap_mentes": "Alap mentés: ~",
"app_export.a_kihagyott_nagy_meretu_tartalommal": ". A kihagyott, nagy méretű tartalommal együtt: ~",
"app_export.kihagyott_tartalom_elrejtese": "Kihagyott tartalom elrejtése",
"deploy.vissza": "← Vissza",
"deploy.beallitasok": "Beállítások",
"deploy.telepites": "Telepítés",
"deploy.leallitas": "Leállítás",
"deploy.inditas": "Indítás",
"deploy.megnyitas": "Megnyitás ↗",
"deploy.reszletek": "Részletek",
"deploy.reszletes_leiras_kepernyokepek": "Részletes leírás, képernyőképek",
"deploy.ez_az_alkalmazas_mar_telepitve": "Ez az alkalmazás már telepítve van. Az alábbi beállítások csak olvashatók.",
"deploy.adattarolas": "Adattárolás",
"deploy.tarhely": "Tárhely",
"deploy.adatmeret": "Adatméret",
"deploy.szabad_hely": "Szabad hely",
"deploy.szabad": "{{.StorageInfo.FreeHuman}} ({{printf \"%.0f\" .StorageInfo.FreePercent}}% szabad)",
"deploy.mozgatas_masik_tarolora": "Mozgatás másik tárolóra",
"deploy.korabbi_adatok": "Korábbi adatok",
"deploy.az_alkalmazas_adatainak_masolata_megtala": "Az alkalmazás adatainak másolata megtalálható egy másik tárolón is.\n Ez általában áthelyezés után marad hátra.",
"deploy.meret": "Méret",
"deploy.mappak": "Mappák",
"deploy.korabbi_adatok_torlese": "Korábbi adatok törlése",
"deploy.biztonsagi_mentes": "Biztonsági mentés",
"deploy.az_alkalmazas_adatbazisa_es_docker": "Az alkalmazás adatbázisa és Docker kötetei automatikusan bekerülnek az éjszakai biztonsági mentésbe.\n <a href=\"/backups\" style=\"color:var(--blue)\">Mentési állapot →</a> · <a href=\"/stacks/{{.Stack.Name}}/backup\" style=\"color:var(--blue)\">Mentési beállítások →</a>",
"deploy.integraciok": "Integrációk",
"deploy.mas_telepitett_alkalmazasokkal_valo_ossz": "Más telepített alkalmazásokkal való összekapcsolás. Az integráció automatikusan felfüggesztődik, ha bármelyik alkalmazás leáll, és újraaktiválódik indításkor.",
"deploy.nincs_telepitve": "Nincs telepítve",
"deploy.celalkalmazas_leallitva": "Célalkalmazás leállítva",
"deploy.aktiv": "Aktív",
"deploy.a_celalkalmazas_nincs_telepitve": "A célalkalmazás nincs telepítve",
"deploy.a_celalkalmazas_nem_fut": "A célalkalmazás nem fut",
"deploy.halozati_hiba": "Hálózati hiba",
"deploy.foldrajzi_korlatozas": "Földrajzi korlátozás",
"deploy.az_alkalmazas_egyeni_orszagkorlatozas_ne": "Az alkalmazás egyéni országkorlátozás nélkül a globális beállítást követi.",
"deploy.egyeni_orszagkorlatozas": "Egyéni országkorlátozás",
"deploy.engedelyezett_orszagok_ehhez_az_alkalmaz": "Engedélyezett országok ehhez az alkalmazáshoz",
"deploy.orszag_keresese": "Ország keresése...",
"deploy.mentes": "Mentés",
"deploy.nincs_talalat": "Nincs találat",
"deploy.mentve": "Mentve",
"deploy.opcionalis_beallitasok": "Opcionális beállítások",
"deploy.regisztracios_utmutato": "Regisztrációs útmutató ↗",
"deploy.mentes_2": "Mentés...",
"deploy.email_kuldes": "Email-küldés",
"deploy.ez_az_alkalmazas_tud_emailt": "Ez az alkalmazás tud emailt küldeni (pl. jelszó-visszaállítás, meghívók) a Felhom-on keresztül,\n külön szolgáltató beállítása nélkül. A feladó címe: <strong>{{.AppEmailFromAddress}}</strong>.",
"deploy.az_alkalmazas_email_jelenleg_ki": "Az alkalmazás-email jelenleg ki van kapcsolva globálisan. Kapcsold be a\n <a href=\"/settings\">Beállítások</a> oldalon, hogy itt is működjön.",
"deploy.email_kuldes_engedelyezese_ennel_az": "Email-küldés engedélyezése ennél az alkalmazásnál",
"deploy.a_mentes_ujrainditja_az_alkalmazast": "A mentés újraindítja az alkalmazást.",
"deploy.nincs_eleg_memoria_foglalas_telepites": "Nincs elég memória! Foglalás telepítés után: {{.AfterMB}} MB / {{.UsableMB}} MB",
"deploy.memoria": "Memória",
"deploy.jelenlegi_hasznalat_mb": "Jelenlegi használat: {{.UsedMB}} MB",
"deploy.jelenlegi_hasznalat_mb_2": "Jelenlegi használat ({{.UsedMB}} MB)",
"deploy.az_alkalmazasok_csucsterhelese_meghaladh": "Az alkalmazások csúcsterhelése meghaladhatja a rendelkezésre álló memóriát.\n Normál használat mellett ez nem okoz problémát.",
"deploy.ennek_az_alkalmazasnak_van_korabbi": "Ennek az alkalmazásnak van korábbi mentése, ezért az alábbi mezőket a saját mentése alapján töltöttük ki.",
"deploy.korabbi_webcim": "Korábbi webcím: <strong>{{.RestoreRecordedAddress}}</strong>.",
"deploy.ha_most_mashova_szeretned_telepiteni": "Ha most máshová szeretnéd telepíteni, nyugodtan átírhatod — a visszaállítás előtt jelezni fogjuk az eltérést.",
"deploy.az_alkalmazas_adatai_az_alabb": "Az alkalmazás adatai az alább kiválasztott adatmeghajtóra kerülnek.",
"deploy.hol_lesznek_az_adatok_ennel": "<strong>Hol lesznek az adatok:</strong> ennél az alkalmazásnál nincs külön adatmeghajtó-választás,\n ezért az adatai a rendszermeghajtóra kerülnek (<code>{{.SystemDataPath}}</code>). A mentései így is elkészülnek.",
"deploy.nincs_eleg_szabad_tarhely_a": "Nincs elég szabad tárhely a telepítéshez. Jelenleg {{.DockerFreeHuman}} szabad, és a rendszer\n {{.DockerReserveHuman}} tartalékot tart fenn az alapszolgáltatások (vezérlő, proxy) védelmében.\n A telepítés ezért átmenetileg le van tiltva — szabadítson fel helyet, vagy bővítse a tárhelyet.",
"deploy.automatikusan_generalt_ertekek": "Automatikusan generált értékek",
"deploy.ezek_az_ertekek_automatikusan_jottek": "Ezek az értékek automatikusan jöttek létre a telepítéskor.",
"deploy.ezek_az_ertekek_a_telepitessel": "Ezek az értékek a telepítéssel együtt mentésre kerülnek. Jegyezze fel a szükséges jelszavakat!",
"deploy.automatikusan_generalva": "Automatikusan generálva",
"deploy.megjelenites": "Megjelenítés",
"deploy.telepites_utan_nem_modosithato": "telepítés után nem módosítható",
"deploy.az_aldomain_telepites_utan_nem": "Az aldomain telepítés után nem módosítható. Megváltoztatáshoz az alkalmazás eltávolítása és újratelepítése szükséges (minden adat törlődik).",
"deploy.generalas": "Generálás",
"deploy.telepiteskor_beallitott_kezdeti_jelszo_h": "Telepítéskor beállított kezdeti jelszó — ha az alkalmazásban megváltoztattad, az itt nem frissül.",
"deploy.jelszo_megerositese": "Jelszó megerősítése",
"deploy.igen": "Igen",
"deploy.szabad_2": "{{.Label}} — {{.FreeHuman}} szabad",
"deploy.alapertelmezett": "(alapértelmezett)",
"deploy.nem_elerheto": "{{$.CurrentHDDPath}} (nem elérhető)",
"deploy.a_kivalasztott_tarhely_majdnem_megtelt": "A kiválasztott tárhely majdnem megtelt.",
"deploy.a_kivalasztott_meghajton_az_alkalmazas": "A kiválasztott meghajtón az alkalmazás <strong>fájljai</strong> (média, dokumentumok) tárolódnak.\n Az <strong>adatbázis a gyors belső SSD-n</strong> fut — és az alkalmazással együtt készül róla biztonsági mentés.",
"deploy.nincs_regisztralt_adattarolo_adja_meg": "Nincs regisztrált adattároló — adja meg kézzel az útvonalat",
"deploy.nincs_eleg_szabad_tarhely": "Nincs elég szabad tárhely",
"deploy.nincs_eleg_memoria": "Nincs elég memória",
"deploy.telepites_inditasa": "Telepítés indítása",
"deploy.telepites_folyamatban": "Telepítés folyamatban...",
"deploy.konfiguracio_mentese": "Konfiguráció mentése...",
"deploy.kontener_ek_inditasa": "Konténer(ek) indítása...",
"deploy.alkalmazas_inicializalasa": "Alkalmazás inicializálása...",
"deploy.alkalmazas_megnyitasa": "Alkalmazás megnyitása ↗",
"deploy.elso_lepesek": "Első lépések",
"deploy.masolas": "Másolás",
"deploy.bejelentkezes": "Bejelentkezés",
"deploy.dokumentacio": "Dokumentáció ↗",
"deploy.beallitasok_es_jelszavak_megtekintese": "Beállítások és jelszavak megtekintése",
"deploy.alkalmazasok": "← Alkalmazások",
"deploy.a_lekeres_nem_sikerult": "A lekérés nem sikerült.",
"deploy.elrejtes": "Elrejtés",
"deploy.masolva": "Másolva!",
"deploy.biztosan_torolni_szeretned_a_korabbi": "Biztosan törölni szeretnéd a korábbi adatokat? Tárhely:",
"deploy.figyelem_a_muvelet_visszavonhatatlan_elo": "— FIGYELEM: a művelet visszavonhatatlan! Előtte győződj meg róla, hogy az alkalmazás az új tárolóról megfelelően működik.",
"deploy.utolso_figyelmeztetes_a_torles_visszavon": "UTOLSÓ FIGYELMEZTETÉS! A törlés visszavonhatatlan. Biztosan folytatod?",
"deploy.torles_folyamatban": "Törlés folyamatban...",
"deploy.ismeretlen_hiba": "Ismeretlen hiba",
"deploy.korabbi_adatok_torolve_n_nfelszabaditott": "Korábbi adatok törölve!\\n\\nFelszabadított hely:",
"deploy.n_nnehany_hiba_tortent_n": "\\n\\nNéhány hiba történt:\\n",
"deploy.halozati_hiba_2": "Hálózati hiba:",
"deploy.kotelezo_mezo": "Kötelező mező:",
"deploy.nhasznalja_a_generalas_gombot_vagy": "\\nHasználja a Generálás gombot vagy írjon be egy jelszót.",
"deploy.a_ket_jelszo_nem_egyezik": "A két jelszó nem egyezik:",
"deploy.az_aldomain_csak_kisbetuket_szamokat": "Az aldomain csak kisbetűket, számokat és kötőjelet tartalmazhat, és nem kezdődhet/végződhet kötőjellel.",
"deploy.konfiguracio_mentve": "Konfiguráció mentve",
"deploy.masodperce": "másodperce...",
"deploy.idotullepes_az_alkalmazas_meg_indulhat": "Időtúllépés — az alkalmazás még indulhat",
"deploy.a_telepites_idotullepesbe_futott_az": "A telepítés időtúllépésbe futott. Az alkalmazás még indulhat.",
"deploy.telepites_sikertelen": "Telepítés sikertelen",
"deploy.a_telepites_nem_sikerult": "A telepítés nem sikerült:",
"deploy.naplok_megtekintese": "Naplók megtekintése",
"deploy.alkalmazasok_2": "Alkalmazások",
"deploy.kepek_letoltese_kontenerek_inditasa": "Képek letöltése, konténerek indítása...",
"deploy.kontenerek_elindultak": "Konténerek elindultak",
"deploy.alkalmazas_kesz": "Alkalmazás kész!",
"deploy.telepites_sikeres": "Telepítés sikeres!",
"deploy.az_alkalmazas_sikeresen_telepitve_es": "Az alkalmazás sikeresen telepítve és fut!",
"deploy.allapotjelzo_meg_inicializal": "Állapotjelző: még inicializál",
"deploy.az_alkalmazas_elindult_de_meg": "Az alkalmazás elindult, de még inicializálódik.",
"deploy.ez_normalis_az_elso_percekben": "Ez normális az első percekben — az alkalmazás már elérhető lehet.",
"deploy.a_kontener_leallt": "A konténer leállt",
"deploy.a_kontener_leallt_ellenorizze_a": "A konténer leállt. Ellenőrizze a naplókat.",
"settings_system.beallitasok_rendszer": "Beállítások — Rendszer",
"settings_system.rendszer_konfiguracio": "Rendszer konfiguráció",
"settings_system.az_uzemelteto_altal_beallitott_ertekek": "Az üzemeltető által beállított értékek. Módosításhoz kérd az üzemeltetőt.",
"settings_system.ugyfel_neve": "Ügyfél neve",
"settings_system.alkalmazas_sablon_forras": "Alkalmazás sablon forrás",
"settings_system.sablon_szinkronizalas": "Sablon szinkronizálás",
"settings_system.biztonsagi_mentes": "Biztonsági mentés",
"settings_system.aktiv": "Aktív",
"settings_system.inaktiv": "Inaktív",
"settings_system.mentes_utemezes": "Mentés ütemezés",
"settings_system.hub_jelentes": "Hub jelentés",
"settings_system.verzio_es_frissites": "Verzió és frissítés",
"settings_system.jelenlegi_verzio": "Jelenlegi verzió",
"settings_system.legujabb_verzio": "Legújabb verzió",
"settings_system.frissites_elerheto": "● Frissítés elérhető",
"settings_system.naprakesz": "— naprakész",
"settings_system.utolso_ellenorzes": "Utolsó ellenőrzés",
"settings_system.nyilvanos_hitelesites_nelkul": "nyilvános (hitelesítés nélkül)",
"settings_system.hitelesitett": "hitelesített",
"settings_system.automatikus_frissites": "Automatikus frissítés",
"settings_system.minimalis_verzio_uzemelteto": "Minimális verzió (üzemeltető)",
"settings_system.a_rendszer_automatikusan_erre_a": "— a rendszer automatikusan erre a verzióra frissít, ha régebbi",
"settings_system.allapot": "Állapot",
"settings_system.frissites_folyamatban_a_vezerlo_hamarosa": "Frissítés folyamatban — a vezérlő hamarosan újraindul…",
"settings_system.utolso_frissites": "Utolsó frissítés",
"settings_system.sikeres": "Sikeres",
"settings_system.sikertelen": "Sikertelen",
"settings_system.folyamatban": "Folyamatban",
"settings_system.frissites_keresese": "Frissítés keresése",
"settings_system.frissites_telepitese": "Frissítés telepítése",
"settings_system.halozat": "Hálózat",
"settings_system.helyi_cim_lan": "Helyi cím (LAN)",
"settings_system.nem_allapithato_meg": "nem állapítható meg",
"settings_system.halozati_nev": "Hálózati név",
"settings_system.atjaro": "Átjáró",
"settings_system.ezek_az_ertekek_a_helyi": "Ezek az értékek a helyi hálózattól függenek és változhatnak. Távoli hibaelhárításnál ezt az oldalt kérjük felolvasni.",
"settings_system.szerver_memoria_ram": "Szerver memória (RAM)",
"settings_system.jelenlegi_memoria": "Jelenlegi memória",
"settings_system.felhasznalt": "Felhasznált",
"settings_system.megengedett_tartomany": "Megengedett tartomány",
"settings_system.uj_meret_mb": "Új méret (MB)",
"settings_system.atmeretezes": "Átméretezés",
"settings_system.a_kiszolgalo_ugynok_jelenleg_nem": "A kiszolgáló ügynök jelenleg nem elérhető — próbáld meg később.",
"settings_system.a_memoria_atmeretezesehez_a_kiszolgalo": "A memória átméretezéséhez a kiszolgáló rendszerfrissítése szükséges.",
"settings_system.megerosites": "Megerősítés",
"settings_system.ellenorzes": "Ellenőrzés...",
"settings_system.kapcsolodasi_hiba": "Kapcsolódási hiba",
"settings_system.vezerlo_frissitese": "Vezérlő frissítése",
"settings_system.frissites": "Frissítés",
"settings_system.biztosan_frissiti_a_controllert_n": "Biztosan frissíti a controllert?\\n\\nA folyamat alatt a vezérlőpult rövid időre elérhetetlenné válik.",
"settings_system.frissites_2": "Frissítés...",
"settings_system.frissites_folyamatban": "Frissítés folyamatban...",
"settings_system.ujraindulas": "Újraindulás...",
"settings_system.ervenytelen_memoria_ertek": "Érvénytelen memória érték",
"settings_system.memoria_csokkentese": "Memória csökkentése",
"settings_system.csokkentes": "Csökkentés",
"settings_system.a_memoria_csokkentese_hatassal_lehet": "A memória csökkentése hatással lehet a futó alkalmazások teljesítményére. Biztosan folytatod?",
"settings_system.atmeretezes_2": "Átméretezés...",
"settings_system.kesz": "Kész.",
"settings_system.a_muvelet_nem_sikerult": "A művelet nem sikerült",
"settings_system.vezerlo_ujrainditasa": "Vezérlő újraindítása",
"settings_system.ha_a_vezerlo_hibasan_mukodik": "Ha a vezérlő hibásan működik, itt biztonságosan újraindíthatja — nem kell az egész szervert újraindítani.\n Az alkalmazásai futnak tovább; csak a vezérlő indul újra (néhány másodperc).",
"settings_system.kiszolgalo_ujrainditasa": "Kiszolgáló újraindítása",
"settings_system.az_egesz_kiszolgalo_szerver_ujrainditasa": "Az egész kiszolgáló (szerver) újraindítása. Minden alkalmazás rövid időre leáll, és a vezérlőpult kb. 30 másodpercig nem elérhető. Akkor használja, ha a teljes rendszer újraindítására van szükség — egyébként a fenti „Vezérlő újraindítása” elegendő.",
"settings_system.biztosan_ujrainditja_a_vezerlot_a": "Biztosan újraindítja a vezérlőt? A művelet néhány másodpercig tart, és a felület rövid időre elérhetetlen lesz.",
"settings_system.ujrainditas_folyamatban_ujracsatlakozas": "Újraindítás folyamatban… újracsatlakozás…",
"settings_system.biztosan_ujrainditja_a_kiszolgalot_az": "Biztosan újraindítja a kiszolgálót? Az alkalmazások és a vezérlőpult kb. 30 másodpercre elérhetetlenné válnak.",
"settings_system.ujrainditas_folyamatban_a_vezerlopult_ne": "Újraindítás folyamatban… a vezérlőpult néhány másodperc múlva újratölt.",
"settings_system.az_ujrainditas_a_vartnal_tovabb": "Az újraindítás a vártnál tovább tart. Töltse újra az oldalt kézzel.",
"settings_security.beallitasok_biztonsag_es_hozzaferes": "Beállítások — Biztonság és hozzáférés",
"settings_security.jelszo_modositas": "Jelszó módosítás",
"settings_security.jelenlegi_jelszo": "Jelenlegi jelszó",
"settings_security.adja_meg_a_jelenlegi_jelszavat": "Adja meg a jelenlegi jelszavát",
"settings_security.uj_jelszo": "Új jelszó",
"settings_security.legalabb_8_karakter": "Legalább 8 karakter",
"settings_security.uj_jelszo_megerositese": "Új jelszó megerősítése",
"settings_security.jelszo_megegyszer": "Jelszó mégegyszer",
"settings_security.jelszo_modositasa": "Jelszó módosítása",
"settings_security.a_jelszavas_vedelem_nincs_beallitva": "A jelszavas védelem nincs beállítva. Kérd az üzemeltetőt a beállításhoz.",
"settings_security.foldrajzi_korlatozas": "Földrajzi korlátozás",
"settings_security.orszag_alapjan_korlatozhato_a_webes": "Ország alapján korlátozható a webes alkalmazások elérése a Cloudflare WAF segítségével.",
"settings_security.a_helyi_halozati_hozzaferes_mindig": "A helyi hálózati hozzáférés mindig engedélyezett (nem halad át a Cloudflare-en).",
"settings_security.a_cloudflare_api_token_nincs": "A Cloudflare API token nincs konfigurálva. Kérd az üzemeltetőt a beállításhoz.<br>\n <small>A tokennek <strong>Zone WAF:Edit</strong> jogosultsággal kell rendelkeznie.</small>",
"settings_security.geo_korlatozas_aktiv": "Geo-korlátozás aktív",
"settings_security.engedelyezett_orszagok_globalis": "Engedélyezett országok (globális)",
"settings_security.orszag_keresese": "Ország keresése...",
"settings_security.csak_a_kivalasztott_orszagokbol_erheto": "Csak a kiválasztott országokból érhető el a rendszer.",
"settings_security.alkalmazas_specifikus_felulirasok": "Alkalmazás-specifikus felülírások",
"settings_security.alkalmazas_kivalasztasa": "— Alkalmazás kiválasztása —",
"settings_security.hozzaadas": "+ Hozzáadás",
"settings_security.mentes_es_szinkronizalas": "Mentés és szinkronizálás",
"settings_security.kezi_szinkronizalas": "Kézi szinkronizálás",
"settings_security.utolso_szinkronizalas": "Utolsó szinkronizálás: {{.GeoLastSync}}",
"settings_security.megerosites": "Megerősítés",
"settings_security.nincs_talalat": "Nincs találat",
"settings_security.figyelem_magyarorszag_eltavolitasa_azt_j": "Figyelem: Magyarország eltávolítása azt jelenti, hogy magyar IP-ről sem lesz elérhető a rendszer távolról. Biztosan folytatja?",
"settings_security.magyarorszag_eltavolitasa_nem_ajanlott_f": "Magyarország eltávolítása nem ajánlott. Folytatja?",
"settings_security.nincs_alkalmazas_specifikus_beallitas_mi": "Nincs alkalmazás-specifikus beállítás. Minden alkalmazás a globális beállítást követi.",
"settings_security.szerkesztes": "Szerkesztés",
"settings_security.engedelyezett_orszagok": "Engedélyezett országok:",
"settings_security.kesz": "Kész",
"settings_security.mentes": "Mentés...",
"settings_security.mentve": "Mentve",
"settings_security.halozati_hiba": "Hálózati hiba",
"settings_security.szinkronizalas_elinditva": "Szinkronizálás elindítva...",
"settings_security.utolso": "Utolsó:",
"settings_security.veszhelyzeti_informaciok": "Vészhelyzeti információk",
"settings_security.ezeket_az_adatokat_mentse_el": "Ezeket az adatokat mentse el biztos helyre. Újratelepítés esetén szükség lesz rájuk a rendszer visszaállításához.",
"settings_security.visszaallitasi_jelszo": "Visszaállítási jelszó",
"settings_security.megjelenit": "Megjelenít",
"settings_security.tamogatas": "Támogatás",
"settings_security.felhom_eu_kapcsolat": "<a href=\"mailto:{{.SupportEmail}}\" style=\"color: var(--blue);\">{{.SupportEmail}}</a>\n &nbsp;|&nbsp;\n <a href=\"{{.SupportURL}}\" target=\"_blank\" style=\"color: var(--blue);\">felhom.eu/kapcsolat</a>",
"settings_security.a_visszaallitasi_jelszo_lekerese_nem": "A visszaállítási jelszó lekérése nem sikerült.",
"settings_security.elrejt": "Elrejt",
"settings_notifications.beallitasok_ertesitesek": "Beállítások — Értesítések",
"settings_notifications.ertesitesek": "Értesítések",
"settings_notifications.e_mail_cim": "E-mail cím",
"settings_notifications.hibak_es_figyelmeztetesek": "Hibák és figyelmeztetések:",
"settings_notifications.biztonsagi_mentes_sikertelen": "Biztonsági mentés sikertelen",
"settings_notifications.adatbazis_mentes_sikertelen": "Adatbázis mentés sikertelen",
"settings_notifications.mentes_serules_eszlelve": "Mentés sérülés észlelve",
"settings_notifications.masodlagos_mentes_sikertelen": "Másodlagos mentés sikertelen",
"settings_notifications.tavoli_mentes_tarhelykeret_figyelmeztete": "Távoli mentés — tárhelykeret-figyelmeztetés",
"settings_notifications.lemez_beteloben_90_felett": "Lemez betelőben (90% felett)",
"settings_notifications.lemez_megtelt_vagy_meghibasodott": "Lemez megtelt vagy meghibásodott",
"settings_notifications.meghajto_levalasztva": "Meghajtó leválasztva",
"settings_notifications.szerver_nem_elerheto": "Szerver nem elérhető",
"settings_notifications.rendszer_allapot_kritikus": "Rendszer állapot kritikus",
"settings_notifications.elvart_fajlmentes_elmaradt": "Elvárt fájlmentés elmaradt",
"settings_notifications.elvart_adatbazismentes_elmaradt": "Elvárt adatbázismentés elmaradt",
"settings_notifications.alkalmazas_nem_fut": "Alkalmazás nem fut",
"settings_notifications.tajekoztato": "Tájékoztató:",
"settings_notifications.meghajto_ujra_csatlakoztatva": "Meghajtó újra csatlakoztatva",
"settings_notifications.rendszer_allapot_helyreallt": "Rendszer állapot helyreállt",
"settings_notifications.ertesitesi_szunet": "Értesítési szünet",
"settings_notifications.ora_azonos_problema_eseten_ennyi": "óra (azonos probléma esetén ennyi ideig nem küld újat)",
"settings_notifications.mentes": "Mentés",
"settings_notifications.teszt_email_kuldese": "Teszt email küldése",
"settings_notifications.az_ertesitesek_a_kozponti_rendszeren": "Az értesítések a központi rendszeren keresztül működnek, ami jelenleg nincs bekapcsolva.",
"settings_notifications.alkalmazas_email": "Alkalmazás-email",
"settings_notifications.az_alkalmazasok_a_felhom_on": "Az alkalmazások a Felhom-on keresztül küldhetnek emailt (pl. jelszó-visszaállítás, meghívók),\n külön email-szolgáltató beállítása nélkül. A feladó címe minden alkalmazásnál a saját\n <em>&lt;alkalmazás&gt;@felhom.eu</em> címe lesz.",
"settings_notifications.alkalmazas_email_engedelyezese": "Alkalmazás-email engedélyezése",
"settings_notifications.felado_neve_opcionalis": "Feladó neve (opcionális)",
"settings_notifications.pl_a_haztartas_neve": "pl. a háztartás neve",
"settings_notifications.ez_jelenik_meg_a_kimeno": "Ez jelenik meg a kimenő emailek feladójaként az alkalmazás neve mellett.",
"settings_notifications.bekapcsolas_utan_az_egyes_alkalmazasokna": "Bekapcsolás után az egyes alkalmazásoknál is engedélyezni kell az email-küldést (az alkalmazás oldalán).",
"stacks.mind": "Mind",
"app_export.folyamat": "Folyamat",
"app_import.titkos": "Titkos",
"monitoring.processzor": "Processzor",
"deploy.hamarosan": "Hamarosan",
"deploy.az_adatai_itt_voltak": "Az adatai itt voltak: <strong>{{.RestoreRecordedDrive}}</strong>.",
"deploy.aldomain_placeholder": "aldomain",
"settings_security.figyelem": "Figyelem",
"settings_notifications.pelda_email": "pelda@email.hu",
"page.title.dashboard": "Vezérlőpult",
"page.title.stacks": "Alkalmazások",
"page.title.monitoring": "Rendszermonitor",
"page.title.settings": "Beállítások",
"page.title.settings_notifications": "Értesítések",
"page.title.settings_security": "Biztonság és hozzáférés",
"func.infra.cloudflared.description": "Biztonságos internetkapcsolat — a szerver portnyitás nélkül érhető el kívülről.",
"func.infra.traefik.description": "Forgalomirányító (reverse proxy) — a kéréseket a megfelelő alkalmazáshoz irányítja.",
"func.infra.filebrowser.description": "Fájlkezelő — a tárhely fájljainak böngészése a böngészőből.",
"func.infra.samba.display": "Hálózati megosztás",
"func.infra.samba.description": "Fájlmegosztás a helyi hálózaton — a mappák a Windows Intézőben és a Mac Finderben jelennek meg."
} }
@@ -20,7 +20,7 @@ import (
// now hit twice (R-262, and the OOB fixture in hub v0.99.0). // now hit twice (R-262, and the OOB fixture in hub v0.99.0).
func diskMeterBlock(t *testing.T) string { func diskMeterBlock(t *testing.T) string {
t.Helper() t.Helper()
raw, err := templateFS.ReadFile("templates/dashboard.html") raw, err := huTemplateSource("templates/dashboard.html")
if err != nil { if err != nil {
t.Fatalf("read dashboard.html: %v", err) t.Fatalf("read dashboard.html: %v", err)
} }
+6
View File
@@ -176,6 +176,7 @@ func (s *Server) dashboardHandler(w http.ResponseWriter, r *http.Request) {
sysInfo := system.GetInfo(s.primaryHDDPath(), s.cpuCollector) sysInfo := system.GetInfo(s.primaryHDDPath(), s.cpuCollector)
data := s.baseData("dashboard", "Vezérlőpult") data := s.baseData("dashboard", "Vezérlőpult")
data["TitleKey"] = "page.title.dashboard" // i18n: the Hungarian title above is what hu renders
s.addRecoveryBanner(data, r) // R-241: the reminder bar, per visit s.addRecoveryBanner(data, r) // R-241: the reminder bar, per visit
data["SettingsWarning"] = s.settings.LoadWarning // non-empty if settings.json was recovered from corruption data["SettingsWarning"] = s.settings.LoadWarning // non-empty if settings.json was recovered from corruption
data["Stacks"] = deployedStacks data["Stacks"] = deployedStacks
@@ -356,6 +357,7 @@ func visibleCatalogStacks(in []stacks.Stack) []stacks.Stack {
func (s *Server) stacksHandler(w http.ResponseWriter, r *http.Request) { func (s *Server) stacksHandler(w http.ResponseWriter, r *http.Request) {
data := s.baseData("stacks", "Alkalmazások") data := s.baseData("stacks", "Alkalmazások")
data["TitleKey"] = "page.title.stacks" // i18n: the Hungarian title above is what hu renders
allStacks := visibleCatalogStacks(s.stackMgr.GetStacks()) allStacks := visibleCatalogStacks(s.stackMgr.GetStacks())
data["Stacks"] = allStacks data["Stacks"] = allStacks
data["MissingStorage"] = s.missingStorageMap(allStacks) data["MissingStorage"] = s.missingStorageMap(allStacks)
@@ -793,6 +795,7 @@ func (s *Server) appDetailHandler(w http.ResponseWriter, r *http.Request, slug s
func (s *Server) monitoringHandler(w http.ResponseWriter, r *http.Request) { func (s *Server) monitoringHandler(w http.ResponseWriter, r *http.Request) {
data := s.baseData("monitoring", "Rendszermonitor") data := s.baseData("monitoring", "Rendszermonitor")
data["TitleKey"] = "page.title.monitoring" // i18n: the Hungarian title above is what hu renders
data["SystemInfo"] = system.GetInfo(s.primaryHDDPath(), s.cpuCollector) data["SystemInfo"] = system.GetInfo(s.primaryHDDPath(), s.cpuCollector)
data["StorageBars"] = s.buildStorageBars() data["StorageBars"] = s.buildStorageBars()
@@ -2043,6 +2046,7 @@ func (s *Server) settingsBaseData(page, title string) map[string]interface{} {
// controller + server restart. // controller + server restart.
func (s *Server) systemPageData() map[string]interface{} { func (s *Server) systemPageData() map[string]interface{} {
data := s.settingsBaseData("settings", "Beállítások") data := s.settingsBaseData("settings", "Beállítások")
data["TitleKey"] = "page.title.settings" // i18n: the Hungarian title above is what hu renders
data["GitRepoURL"] = s.cfg.Git.RepoURL data["GitRepoURL"] = s.cfg.Git.RepoURL
data["GitSyncInterval"] = s.cfg.Git.SyncInterval data["GitSyncInterval"] = s.cfg.Git.SyncInterval
data["BackupEnabled"] = s.cfg.Backup.Enabled data["BackupEnabled"] = s.cfg.Backup.Enabled
@@ -2157,6 +2161,7 @@ func (s *Server) networkStoragePageData() map[string]interface{} {
// notificationsPageData builds the Értesítések subpage: notification prefs + app-email. // notificationsPageData builds the Értesítések subpage: notification prefs + app-email.
func (s *Server) notificationsPageData() map[string]interface{} { func (s *Server) notificationsPageData() map[string]interface{} {
data := s.settingsBaseData("settings-notifications", "Értesítések") data := s.settingsBaseData("settings-notifications", "Értesítések")
data["TitleKey"] = "page.title.settings_notifications" // i18n: the Hungarian title above is what hu renders
data["HubEnabled"] = s.cfg.Hub.Enabled data["HubEnabled"] = s.cfg.Hub.Enabled
data["NotificationPrefs"] = s.settings.GetNotificationPrefs() data["NotificationPrefs"] = s.settings.GetNotificationPrefs()
@@ -2173,6 +2178,7 @@ func (s *Server) notificationsPageData() map[string]interface{} {
// emergency/recovery info. // emergency/recovery info.
func (s *Server) securityPageData() map[string]interface{} { func (s *Server) securityPageData() map[string]interface{} {
data := s.settingsBaseData("settings-security", "Biztonság és hozzáférés") data := s.settingsBaseData("settings-security", "Biztonság és hozzáférés")
data["TitleKey"] = "page.title.settings_security" // i18n: the Hungarian title above is what hu renders
// Recovery info for emergency section. // Recovery info for emergency section.
// //
+18 -3
View File
@@ -35,6 +35,7 @@ import (
// TestI18nParity fails naming the page and the first differing line. // TestI18nParity fails naming the page and the first differing line.
var updateI18nGolden = flag.Bool("update-i18n-golden", false, "write MISSING testdata/i18n_parity fixtures from the current templates (an existing fixture is never overwritten)") var updateI18nGolden = flag.Bool("update-i18n-golden", false, "write MISSING testdata/i18n_parity fixtures from the current templates (an existing fixture is never overwritten)")
var i18nDumpDir = flag.String("i18n-dump-dir", "", "TestI18nEnglishPages: also write every case's hu and en render to this directory (evidence, per-page review)")
var i18nGoldenOnly = flag.String("i18n-golden-only", "", "with -update-i18n-golden: only cases whose name matches this regexp") var i18nGoldenOnly = flag.String("i18n-golden-only", "", "with -update-i18n-golden: only cases whose name matches this regexp")
var relativeAgeRe = regexp.MustCompile(`\d+ (napja|órája|perce)`) var relativeAgeRe = regexp.MustCompile(`\d+ (napja|órája|perce)`)
@@ -52,6 +53,12 @@ func i18nFixtureTime() time.Time { return time.Date(2026, 9, 14, 3, 12, 0, 0, ti
var i18nTitleKeys = map[string]string{ var i18nTitleKeys = map[string]string{
"Indítópult": "page.title.launcher", "Indítópult": "page.title.launcher",
"Biztonsági mentés": "page.title.backups", "Biztonsági mentés": "page.title.backups",
"Vezérlőpult": "page.title.dashboard",
"Alkalmazások": "page.title.stacks",
"Rendszermonitor": "page.title.monitoring",
"Beállítások": "page.title.settings",
"Értesítések": "page.title.settings_notifications",
"Biztonság és hozzáférés": "page.title.settings_security",
} }
func i18nLayoutData(page, title string) map[string]interface{} { func i18nLayoutData(page, title string) map[string]interface{} {
@@ -376,7 +383,7 @@ func fixtureStrings(v reflect.Value, out map[string]bool) {
} }
} }
var i18nKeyLikeRe = regexp.MustCompile(`\b(launcher|backups|backups_shared|app_info|layout|func)\.[a-z0-9_]+\b`) var i18nKeyLikeRe = regexp.MustCompile(`\b[a-z][a-z_]*\.[a-z0-9_.]+\b`)
var emptyElemRe = regexp.MustCompile(`<(h[1-6]|button|label|li|strong|option|p)(\s[^>]*)?>\s*</(h[1-6]|button|label|li|strong|option|p)>`) var emptyElemRe = regexp.MustCompile(`<(h[1-6]|button|label|li|strong|option|p)(\s[^>]*)?>\s*</(h[1-6]|button|label|li|strong|option|p)>`)
// TestI18nEnglishPages — the three pages in English: no Hungarian letter from the TEMPLATE copy, no // TestI18nEnglishPages — the three pages in English: no Hungarian letter from the TEMPLATE copy, no
@@ -395,14 +402,22 @@ func TestI18nEnglishPages(t *testing.T) {
for _, c := range i18nCases() { for _, c := range i18nCases() {
hu := renderI18nCase(t, s, "hu", c) hu := renderI18nCase(t, s, "hu", c)
en := renderI18nCase(t, s, "en", c) en := renderI18nCase(t, s, "en", c)
if *i18nDumpDir != "" {
_ = os.MkdirAll(*i18nDumpDir, 0o755)
_ = os.WriteFile(filepath.Join(*i18nDumpDir, c.name+".hu.html"), []byte(hu), 0o644)
_ = os.WriteFile(filepath.Join(*i18nDumpDir, c.name+".en.html"), []byte(en), 0o644)
}
if !huLetter(hu) { if !huLetter(hu) {
t.Fatalf("%s: positive control failed — the Hungarian render carries no Hungarian letter", c.name) t.Fatalf("%s: positive control failed — the Hungarian render carries no Hungarian letter", c.name)
} }
if strings.Contains(en, `{{T `) { if strings.Contains(en, `{{T `) {
t.Errorf("%s: an unexpanded marker reached the English page", c.name) t.Errorf("%s: an unexpanded marker reached the English page", c.name)
} }
if m := i18nKeyLikeRe.FindString(en); m != "" && s.i18n.Has(i18n.Default, m) { for _, m := range i18nKeyLikeRe.FindAllString(en, -1) {
t.Errorf("%s: a raw bundle key reached the English page: %q", c.name, m) if s.i18n.Has(i18n.Default, m) {
t.Errorf("%s: a raw bundle key reached the English page: %q", c.name, m)
break
}
} }
if a, b := len(emptyElemRe.FindAllString(hu, -1)), len(emptyElemRe.FindAllString(en, -1)); b > a { if a, b := len(emptyElemRe.FindAllString(hu, -1)), len(emptyElemRe.FindAllString(en, -1)); b > a {
t.Errorf("%s: English has %d empty elements, Hungarian %d — a translation rendered blank", c.name, b, a) t.Errorf("%s: English has %d empty elements, Hungarian %d — a translation rendered blank", c.name, b, a)
+16
View File
@@ -129,6 +129,22 @@ func (s *Server) localeFuncs(lang string) template.FuncMap {
} }
return lt.Format("2006-01-02") + " " + hm return lt.Format("2006-01-02") + " " + hm
}, },
// infraMeta: the protected infra stacks' curated identity (inframeta.go). Names that are product
// names stay; descriptions — and samba's display name — come from the bundle.
"infraMeta": func(name string) *InfraMeta {
m := infraMetaFor(name)
if m == nil {
return nil
}
out := *m
if v, _, ok := b.Text(lang, "func.infra."+name+".description"); ok {
out.Description = v
}
if v, _, ok := b.Text(lang, "func.infra."+name+".display"); ok {
out.DisplayName = v
}
return &out
},
"statusText": func(status string) string { "statusText": func(status string) string {
switch status { switch status {
case "pending", "restoring", "done", "failed", "skipped": case "pending", "restoring", "done", "failed", "skipped":
@@ -156,6 +156,15 @@ func TestLocaleFuncsHungarianBundleMatchesFuncMap(t *testing.T) {
if got, want := b.Msg("hu", "func.time.yesterday"), timeAgo(now.Add(-30*time.Hour)); got != want { if got, want := b.Msg("hu", "func.time.yesterday"), timeAgo(now.Add(-30*time.Hour)); got != want {
t.Errorf("yesterday: %q vs %q", got, want) t.Errorf("yesterday: %q vs %q", got, want)
} }
// infraMeta (slice 1): the Hungarian bundle carries the same curated words as inframeta.go.
for name, m := range infraMetaMap {
if got := b.Msg("hu", "func.infra."+name+".description"); got != m.Description {
t.Errorf("infra %s description: hu.json %q, inframeta.go %q", name, got, m.Description)
}
if k := "func.infra." + name + ".display"; b.Has("hu", k) && b.Msg("hu", k) != m.DisplayName {
t.Errorf("infra %s display: hu.json %q, inframeta.go %q", name, b.Msg("hu", k), m.DisplayName)
}
}
// And the English funcs really are English. // And the English funcs really are English.
en := s.localeFuncs("en") en := s.localeFuncs("en")
if got := en["timeAgo"].(func(time.Time) string)(now.Add(-time.Minute - time.Second)); got != "1 minute ago" { if got := en["timeAgo"].(func(time.Time) string)(now.Add(-time.Minute - time.Second)); got != "1 minute ago" {
@@ -238,3 +247,19 @@ func TestLanguageSwitch_EndToEnd(t *testing.T) {
t.Error("back on Hungarian: the page must be Hungarian with no switch") t.Error("back on Hungarian: the page must be Hungarian with no switch")
} }
} }
// huTemplateSource returns an embedded template AS THE HUNGARIAN SET PARSES IT — markers expanded. Tests
// that assert on a template's Hungarian wording read this, never the raw file: since slice 1 the
// wording lives in hu.json, and a raw read would find a marker where the sentence used to be.
func huTemplateSource(name string) ([]byte, error) {
raw, err := templateFS.ReadFile(name)
if err != nil {
return nil, err
}
b, err := i18n.Shared()
if err != nil {
return nil, err
}
out, _ := b.Expand(i18n.Default, string(raw))
return []byte(out), nil
}
+1 -1
View File
@@ -92,7 +92,7 @@ func TestNetworkStorageWarningsIn_UnreachableAloneUnchanged(t *testing.T) {
// must never reuse the recoverable-unreachable copy; the unreachable line stays byte-identical). // must never reuse the recoverable-unreachable copy; the unreachable line stays byte-identical).
func TestNetworkBadgeTemplates_DistinctStrings(t *testing.T) { func TestNetworkBadgeTemplates_DistinctStrings(t *testing.T) {
for _, tpl := range []string{"templates/dashboard.html", "templates/stacks.html"} { for _, tpl := range []string{"templates/dashboard.html", "templates/stacks.html"} {
body, err := os.ReadFile(tpl) body, err := huTemplateSource(tpl)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@@ -114,7 +114,7 @@ func TestSettingsSectionInventory(t *testing.T) {
} }
var union strings.Builder var union strings.Builder
for _, f := range []string{"settings_system.html", "settings_notifications.html", "settings_security.html", "storage.html", "storage_network.html"} { for _, f := range []string{"settings_system.html", "settings_notifications.html", "settings_security.html", "storage.html", "storage_network.html"} {
b, err := templateFS.ReadFile("templates/" + f) b, err := huTemplateSource("templates/" + f)
if err != nil { if err != nil {
t.Fatalf("read %s: %v", f, err) t.Fatalf("read %s: %v", f, err)
} }
@@ -224,7 +224,7 @@ func TestStorageNoNativeConfirm(t *testing.T) {
// allow the pre-existing type-to-confirm overlay helper names // allow the pre-existing type-to-confirm overlay helper names
allow := regexp.MustCompile(`openConfirm|__closeConfirm|confirmEject|confirmWipe|typeToConfirm|confirm-`) allow := regexp.MustCompile(`openConfirm|__closeConfirm|confirmEject|confirmWipe|typeToConfirm|confirm-`)
for _, f := range []string{"storage.html", "storage_network.html", "settings_system.html", "settings_notifications.html", "settings_security.html"} { for _, f := range []string{"storage.html", "storage_network.html", "settings_system.html", "settings_notifications.html", "settings_security.html"} {
b, err := templateFS.ReadFile("templates/" + f) b, err := huTemplateSource("templates/" + f)
if err != nil { if err != nil {
t.Fatalf("read %s: %v", f, err) t.Fatalf("read %s: %v", f, err)
} }
@@ -239,7 +239,7 @@ func TestStorageNoNativeConfirm(t *testing.T) {
// TestStorageAgentDownNote (D1 Scenario C, static): the enrichment JS has an error path that // TestStorageAgentDownNote (D1 Scenario C, static): the enrichment JS has an error path that
// renders the exact warn note into #agent-warn-note (graceful degradation when the agent is down). // renders the exact warn note into #agent-warn-note (graceful degradation when the agent is down).
func TestStorageAgentDownNote(t *testing.T) { func TestStorageAgentDownNote(t *testing.T) {
b, err := templateFS.ReadFile("templates/storage.html") b, err := huTemplateSource("templates/storage.html")
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@@ -282,7 +282,7 @@ func TestNoEmojiInTemplates(t *testing.T) {
if !strings.HasSuffix(e.Name(), ".html") { if !strings.HasSuffix(e.Name(), ".html") {
continue continue
} }
b, err := templateFS.ReadFile("templates/" + e.Name()) b, err := huTemplateSource("templates/" + e.Name())
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@@ -342,7 +342,7 @@ func TestStorageInit_PollsAgentFormatStatusOnTimeout(t *testing.T) {
// /storage, not /settings. One assertion per template. // /storage, not /settings. One assertion per template.
func TestStorageWizardBackAnchors_PointToStorage(t *testing.T) { func TestStorageWizardBackAnchors_PointToStorage(t *testing.T) {
for _, file := range []string{"templates/storage_init.html", "templates/storage_attach.html"} { for _, file := range []string{"templates/storage_init.html", "templates/storage_attach.html"} {
b, err := templateFS.ReadFile(file) b, err := huTemplateSource(file)
if err != nil { if err != nil {
t.Fatalf("read %s: %v", file, err) t.Fatalf("read %s: %v", file, err)
} }
@@ -3,15 +3,15 @@
<div class="page-header"> <div class="page-header">
<div style="display:flex;align-items:center;gap:1rem"> <div style="display:flex;align-items:center;gap:1rem">
<a href="/stacks" class="btn btn-sm btn-outline">&larr; Alkalmazások</a> <a href="/stacks" class="btn btn-sm btn-outline">{{T "app_export.alkalmazasok"}}</a>
<h2>{{.Stack.Meta.DisplayName}} &mdash; Exportálás</h2> <h2>{{T "app_export.exportalas"}}</h2>
</div> </div>
</div> </div>
<div class="card" style="max-width:700px"> <div class="card" style="max-width:700px">
<h3>Mentés helye</h3> <h3>{{T "app_export.mentes_helye"}}</h3>
<select id="destDrive" onchange="loadEstimate()" style="width:100%;padding:.5rem;margin-bottom:1rem"> <select id="destDrive" onchange="loadEstimate()" style="width:100%;padding:.5rem;margin-bottom:1rem">
<option value="">Válassz tárolót...</option> <option value="">{{T "app_export.valassz_tarolot"}}</option>
{{range .Drives}} {{range .Drives}}
<option value="{{.Path}}">{{if .Label}}{{.Label}} ({{.Path}}){{else}}{{.Path}}{{end}}</option> <option value="{{.Path}}">{{if .Label}}{{.Label}} ({{.Path}}){{else}}{{.Path}}{{end}}</option>
{{end}} {{end}}
@@ -19,19 +19,19 @@
<div id="estimateBox" style="display:none;background:var(--bg-2);border-radius:8px;padding:1rem;margin-bottom:1.5rem"> <div id="estimateBox" style="display:none;background:var(--bg-2);border-radius:8px;padding:1rem;margin-bottom:1.5rem">
<div style="display:flex;justify-content:space-between;margin-bottom:.25rem"> <div style="display:flex;justify-content:space-between;margin-bottom:.25rem">
<span>Konfiguráció:</span> <span>{{T "app_export.konfiguracio"}}</span>
<span id="estConfig">-</span> <span id="estConfig">-</span>
</div> </div>
<div style="display:flex;justify-content:space-between;margin-bottom:.25rem"> <div style="display:flex;justify-content:space-between;margin-bottom:.25rem">
<span>Felhasználói adatok:</span> <span>{{T "app_export.felhasznaloi_adatok"}}</span>
<span id="estData">-</span> <span id="estData">-</span>
</div> </div>
<div style="display:flex;justify-content:space-between;font-weight:600;margin-bottom:.25rem;border-top:1px solid var(--line);padding-top:.5rem"> <div style="display:flex;justify-content:space-between;font-weight:600;margin-bottom:.25rem;border-top:1px solid var(--line);padding-top:.5rem">
<span>Összesen:</span> <span>{{T "app_export.osszesen"}}</span>
<span><span id="estTotal">-</span> <span id="estFree" style="color:var(--text-3)">(szabad: -)</span></span> <span><span id="estTotal">-</span> <span id="estFree" style="color:var(--text-3)">{{T "app_export.szabad"}}</span></span>
</div> </div>
<div style="display:flex;justify-content:space-between"> <div style="display:flex;justify-content:space-between">
<span>Becsült idő:</span> <span>{{T "app_export.becsult_ido"}}</span>
<span id="estTime">-</span> <span id="estTime">-</span>
</div> </div>
<div id="estWarning" style="display:none;color:var(--crit);margin-top:.5rem;font-weight:600"></div> <div id="estWarning" style="display:none;color:var(--crit);margin-top:.5rem;font-weight:600"></div>
@@ -39,53 +39,53 @@
<div id="classSelect" style="display:none;margin-bottom:1.5rem"> <div id="classSelect" style="display:none;margin-bottom:1.5rem">
<div id="mandBox" style="display:none;margin-bottom:1rem"> <div id="mandBox" style="display:none;margin-bottom:1rem">
<div style="font-weight:600;margin-bottom:.25rem">Mindig része a mentésnek:</div> <div style="font-weight:600;margin-bottom:.25rem">{{T "app_export.mindig_resze_a_mentesnek"}}</div>
<div id="mandList" style="color:var(--text-3);font-size:.9rem"></div> <div id="mandList" style="color:var(--text-3);font-size:.9rem"></div>
</div> </div>
<div id="optBox" style="display:none;margin-bottom:1rem"> <div id="optBox" style="display:none;margin-bottom:1rem">
<div style="font-weight:600;margin-bottom:.25rem">Választható tartalom:</div> <div style="font-weight:600;margin-bottom:.25rem">{{T "app_export.valaszthato_tartalom"}}</div>
<div id="optList"></div> <div id="optList"></div>
</div> </div>
<div id="exclBox" style="display:none"> <div id="exclBox" style="display:none">
<div id="exclSummary" style="margin-bottom:.25rem"></div> <div id="exclSummary" style="margin-bottom:.25rem"></div>
<div style="color:var(--text-3);font-size:.85rem;margin-bottom:.5rem">A kihagyott mappák tartalma a Fájlkezelőben bármikor elérhető, és külön is lementhető.</div> <div style="color:var(--text-3);font-size:.85rem;margin-bottom:.5rem">{{T "app_export.a_kihagyott_mappak_tartalma_a"}}</div>
<button type="button" class="btn btn-xs btn-outline" onclick="toggleExcl()" id="exclToggle">Kihagyott tartalom megjelenítése</button> <button type="button" class="btn btn-xs btn-outline" onclick="toggleExcl()" id="exclToggle">{{T "app_export.kihagyott_tartalom_megjelenitese"}}</button>
<div id="exclList" style="display:none;margin-top:.5rem"></div> <div id="exclList" style="display:none;margin-top:.5rem"></div>
</div> </div>
</div> </div>
<h3>Jelszó (opcionális)</h3> <h3>{{T "app_export.jelszo_opcionalis"}}</h3>
<div style="display:flex;gap:.5rem;margin-bottom:1rem"> <div style="display:flex;gap:.5rem;margin-bottom:1rem">
<input type="password" id="exportPassword" placeholder="Titkosítási jelszó" style="flex:1;padding:.5rem"> <input type="password" id="exportPassword" placeholder="{{T "app_export.titkositasi_jelszo"}}" style="flex:1;padding:.5rem">
<button class="btn btn-sm btn-outline" onclick="togglePw()" type="button" title="Jelszó mutatása">&#128065;</button> <button class="btn btn-sm btn-outline" onclick="togglePw()" type="button" title="{{T "app_export.jelszo_mutatasa"}}">&#128065;</button>
</div> </div>
<label style="display:flex;align-items:flex-start;gap:.5rem;margin-bottom:.5rem;cursor:pointer"> <label style="display:flex;align-items:flex-start;gap:.5rem;margin-bottom:.5rem;cursor:pointer">
<input type="checkbox" id="stopApp" checked style="margin-top:3px"> <input type="checkbox" id="stopApp" checked style="margin-top:3px">
<div> <div>
<strong>Alkalmazás leállítása mentés előtt (ajánlott)</strong> <strong>{{T "app_export.alkalmazas_leallitasa_mentes_elott_ajanl"}}</strong>
<div style="color:var(--text-3);font-size:.85rem">Az adatok konzisztenciája érdekében javasolt leállítani az alkalmazást mentés közben.</div> <div style="color:var(--text-3);font-size:.85rem">{{T "app_export.az_adatok_konzisztenciaja_erdekeben_java"}}</div>
</div> </div>
</label> </label>
<button id="startBtn" class="btn btn-primary" onclick="startExport()" style="margin-top:1rem;width:100%" disabled> <button id="startBtn" class="btn btn-primary" onclick="startExport()" style="margin-top:1rem;width:100%" disabled>
Exportálás indítása {{T "app_export.exportalas_inditasa"}}
</button> </button>
</div> </div>
<div id="progressCard" class="card" style="max-width:700px;display:none"> <div id="progressCard" class="card" style="max-width:700px;display:none">
<h3>Folyamat</h3> <h3>{{T "app_export.folyamat"}}</h3>
<div id="progressSteps"></div> <div id="progressSteps"></div>
<div id="progressError" style="display:none;color:var(--crit);margin-top:1rem;font-weight:600"></div> <div id="progressError" style="display:none;color:var(--crit);margin-top:1rem;font-weight:600"></div>
</div> </div>
<div id="doneCard" class="card" style="max-width:700px;display:none"> <div id="doneCard" class="card" style="max-width:700px;display:none">
<h3 style="color:var(--blue)">Kész!</h3> <h3 style="color:var(--blue)">{{T "app_export.kesz"}}</h3>
<div style="margin-bottom:1rem"> <div style="margin-bottom:1rem">
<span id="doneFile" style="font-weight:600"></span> <span id="doneFile" style="font-weight:600"></span>
<span id="doneSize" style="color:var(--text-3)"></span> <span id="doneSize" style="color:var(--text-3)"></span>
</div> </div>
<a id="doneFBLink" href="#" target="_blank" class="btn btn-outline">Megnyitás FileBrowser-ben &nearr;</a> <a id="doneFBLink" href="#" target="_blank" class="btn btn-outline">{{T "app_export.megnyitas_filebrowser_ben"}}</a>
</div> </div>
<script> <script>
@@ -126,12 +126,12 @@ async function loadEstimate() {
document.getElementById('estConfig').textContent = est.config_size_human; document.getElementById('estConfig').textContent = est.config_size_human;
document.getElementById('estData').textContent = est.data_size_human; document.getElementById('estData').textContent = est.data_size_human;
document.getElementById('estTotal').textContent = '~' + est.total_size_human; document.getElementById('estTotal').textContent = '~' + est.total_size_human;
document.getElementById('estFree').textContent = '(szabad: ' + est.dest_free_human + ')'; document.getElementById('estFree').textContent = '{{T "app_export.szabad_2"}} ' + est.dest_free_human + ')';
document.getElementById('estTime').textContent = '~' + est.estimated_minutes + ' perc'; document.getElementById('estTime').textContent = '~' + est.estimated_minutes + ' {{T "app_export.perc"}}';
var warn = document.getElementById('estWarning'); var warn = document.getElementById('estWarning');
if (!est.fits_on_dest) { if (!est.fits_on_dest) {
warn.textContent = 'Nincs elég szabad hely a kiválasztott tárolón!'; warn.textContent = '{{T "app_export.nincs_eleg_szabad_hely_a"}}';
warn.style.display = 'block'; warn.style.display = 'block';
btn.disabled = true; btn.disabled = true;
} else { } else {
@@ -200,14 +200,14 @@ function recomputeTotal() {
var full = alap + exclAll; var full = alap + exclAll;
var s = document.getElementById('exclSummary'); var s = document.getElementById('exclSummary');
if ((lastEst.excluded_items||[]).length) { if ((lastEst.excluded_items||[]).length) {
s.innerHTML = 'Alap mentés: ~' + humanBytes(alap) + '. A kihagyott, nagy méretű tartalommal együtt: ~' + humanBytes(full) + '.'; s.innerHTML = '{{T "app_export.alap_mentes"}}' + humanBytes(alap) + '{{T "app_export.a_kihagyott_nagy_meretu_tartalommal"}}' + humanBytes(full) + '.';
} }
} }
function toggleExcl() { function toggleExcl() {
var l = document.getElementById('exclList'); var l = document.getElementById('exclList');
var shown = l.style.display !== 'none'; var shown = l.style.display !== 'none';
l.style.display = shown ? 'none' : 'block'; l.style.display = shown ? 'none' : 'block';
document.getElementById('exclToggle').textContent = shown ? 'Kihagyott tartalom megjelenítése' : 'Kihagyott tartalom elrejtése'; document.getElementById('exclToggle').textContent = shown ? '{{T "app_export.kihagyott_tartalom_megjelenitese"}}' : '{{T "app_export.kihagyott_tartalom_elrejtese"}}';
} }
function gatherSelections() { function gatherSelections() {
var deselect = [], optIn = []; var deselect = [], optIn = [];
@@ -242,7 +242,7 @@ async function startExport() {
}); });
var data = await resp.json(); var data = await resp.json();
if (!data.ok) { if (!data.ok) {
showError(data.error || 'Hiba történt'); showError(data.error || '{{T "common.hiba_tortent"}}');
return; return;
} }
pollStatus(); pollStatus();
@@ -3,22 +3,22 @@
<div class="page-header"> <div class="page-header">
<div style="display:flex;align-items:center;gap:1rem"> <div style="display:flex;align-items:center;gap:1rem">
<a href="/stacks" class="btn btn-sm btn-outline">&larr; Alkalmazások</a> <a href="/stacks" class="btn btn-sm btn-outline">{{T "app_import.alkalmazasok"}}</a>
<h2>Alkalmazás importálás</h2> <h2>{{T "app_import.alkalmazas_importalas"}}</h2>
</div> </div>
</div> </div>
<!-- Browser .fab upload (v0.128.0) — chunked, because the Cloudflare tunnel caps request bodies (~100 MB) --> <!-- Browser .fab upload (v0.128.0) — chunked, because the Cloudflare tunnel caps request bodies (~100 MB) -->
<div class="card" style="max-width:900px;margin-bottom:1rem"> <div class="card" style="max-width:900px;margin-bottom:1rem">
<div id="fabUploadZone" style="border:1px dashed var(--line);border-radius:var(--radius);padding:1.25rem;text-align:center;cursor:pointer" onclick="fabPick()"> <div id="fabUploadZone" style="border:1px dashed var(--line);border-radius:var(--radius);padding:1.25rem;text-align:center;cursor:pointer" onclick="fabPick()">
<p style="color:var(--text-2);margin:0 0 .75rem">.fab csomag feltöltése &mdash; húzza ide, vagy válassza ki a fájlt</p> <p style="color:var(--text-2);margin:0 0 .75rem">{{T "app_import.fab_csomag_feltoltese_huzza_ide"}}</p>
<button type="button" class="btn btn-sm btn-outline">Fájl kiválasztása</button> <button type="button" class="btn btn-sm btn-outline">{{T "app_import.fajl_kivalasztasa"}}</button>
<input type="file" id="fabFileInput" accept=".fab" style="display:none"> <input type="file" id="fabFileInput" accept=".fab" style="display:none">
</div> </div>
<div id="fabUpProgress" style="display:none;margin-top:1rem"> <div id="fabUpProgress" style="display:none;margin-top:1rem">
<div style="display:flex;justify-content:space-between;align-items:center;gap:1rem"> <div style="display:flex;justify-content:space-between;align-items:center;gap:1rem">
<span id="fabUpText" style="color:var(--text-2)">Feltöltés: 0%</span> <span id="fabUpText" style="color:var(--text-2)">{{T "app_import.feltoltes_0"}}</span>
<button type="button" class="btn btn-sm btn-outline" onclick="fabCancel()">Megszakítás</button> <button type="button" class="btn btn-sm btn-outline" onclick="fabCancel()">{{T "app_import.megszakitas"}}</button>
</div> </div>
<div style="background:var(--bg-2);border-radius:var(--radius);height:6px;margin-top:.5rem;overflow:hidden"> <div style="background:var(--bg-2);border-radius:var(--radius);height:6px;margin-top:.5rem;overflow:hidden">
<div id="fabUpBar" style="background:var(--blue);height:100%;width:0%"></div> <div id="fabUpBar" style="background:var(--blue);height:100%;width:0%"></div>
@@ -29,19 +29,19 @@
{{if not .Bundles}} {{if not .Bundles}}
<div class="card" style="max-width:700px"> <div class="card" style="max-width:700px">
<p style="color:var(--text-3)">Nem található .fab csomag a regisztrált tárolókon.</p> <p style="color:var(--text-3)">{{T "app_import.nem_talalhato_fab_csomag_a"}}</p>
<p style="color:var(--text-3);font-size:.85rem">Exportálj egy alkalmazást az alkalmazás oldaláról, tölts fel egy .fab fájlt itt fent, vagy másolj egyet a <code>{tároló}/felhom-data/exports/</code> könyvtárba.</p> <p style="color:var(--text-3);font-size:.85rem">{{T "app_import.exportalj_egy_alkalmazast_az_alkalmazas"}}</p>
</div> </div>
{{else}} {{else}}
<div class="card" style="max-width:900px"> <div class="card" style="max-width:900px">
<table class="table" style="width:100%"> <table class="table" style="width:100%">
<thead> <thead>
<tr> <tr>
<th>Alkalmazás</th> <th>{{T "app_import.alkalmazas"}}</th>
<th>Dátum</th> <th>{{T "app_import.datum"}}</th>
<th>Méret</th> <th>{{T "app_import.meret"}}</th>
<th>Tároló</th> <th>{{T "app_import.tarolo"}}</th>
<th>Titkos</th> <th>{{T "app_import.titkos"}}</th>
<th></th> <th></th>
</tr> </tr>
</thead> </thead>
@@ -53,7 +53,7 @@
<td>{{.SizeHuman}}</td> <td>{{.SizeHuman}}</td>
<td>{{if .DriveLabel}}{{.DriveLabel}}{{else}}{{.DrivePath}}{{end}}</td> <td>{{if .DriveLabel}}{{.DriveLabel}}{{else}}{{.DrivePath}}{{end}}</td>
<td>{{if .Encrypted}}&#128274;{{end}}</td> <td>{{if .Encrypted}}&#128274;{{end}}</td>
<td><button class="btn btn-sm btn-outline" onclick="showPreview('{{.Path}}', {{.Encrypted}})">Részletek &raquo;</button></td> <td><button class="btn btn-sm btn-outline" onclick="showPreview('{{.Path}}', {{.Encrypted}})">{{T "app_import.reszletek"}}</button></td>
</tr> </tr>
{{end}} {{end}}
</tbody> </tbody>
@@ -63,13 +63,13 @@
<!-- Preview / password modal --> <!-- Preview / password modal -->
<div id="previewCard" class="card" style="max-width:700px;display:none"> <div id="previewCard" class="card" style="max-width:700px;display:none">
<h3 id="previewTitle">Csomag részletei</h3> <h3 id="previewTitle">{{T "app_import.csomag_reszletei"}}</h3>
<div id="passwordPrompt" style="display:none;margin-bottom:1rem"> <div id="passwordPrompt" style="display:none;margin-bottom:1rem">
<p>Ez a csomag jelszóval védett. Kérlek add meg a jelszót:</p> <p>{{T "app_import.ez_a_csomag_jelszoval_vedett"}}</p>
<div style="display:flex;gap:.5rem"> <div style="display:flex;gap:.5rem">
<input type="password" id="importPassword" class="form-input" autocomplete="off" placeholder="Jelszó" style="flex:1"> <input type="password" id="importPassword" class="form-input" autocomplete="off" placeholder="{{T "common.jelszo"}}" style="flex:1">
<button class="btn btn-primary" onclick="loadManifest()">Megnyitás</button> <button class="btn btn-primary" onclick="loadManifest()">{{T "app_import.megnyitas"}}</button>
</div> </div>
<div id="passwordError" style="display:none;color:var(--crit);margin-top:.5rem"></div> <div id="passwordError" style="display:none;color:var(--crit);margin-top:.5rem"></div>
</div> </div>
@@ -77,52 +77,52 @@
<div id="manifestInfo" style="display:none"> <div id="manifestInfo" style="display:none">
<div style="background:var(--bg-2);border-radius:8px;padding:1rem;margin-bottom:1rem"> <div style="background:var(--bg-2);border-radius:8px;padding:1rem;margin-bottom:1rem">
<div style="display:flex;justify-content:space-between;margin-bottom:.25rem"> <div style="display:flex;justify-content:space-between;margin-bottom:.25rem">
<span>Alkalmazás:</span> <span>{{T "app_import.alkalmazas_2"}}</span>
<strong id="mfName">-</strong> <strong id="mfName">-</strong>
</div> </div>
<div style="display:flex;justify-content:space-between;margin-bottom:.25rem"> <div style="display:flex;justify-content:space-between;margin-bottom:.25rem">
<span>Exportálva:</span> <span>{{T "app_import.exportalva"}}</span>
<span id="mfDate">-</span> <span id="mfDate">-</span>
</div> </div>
<div style="display:flex;justify-content:space-between;margin-bottom:.25rem"> <div style="display:flex;justify-content:space-between;margin-bottom:.25rem">
<span>Controller verzió:</span> <span>{{T "app_import.controller_verzio"}}</span>
<span id="mfVersion">-</span> <span id="mfVersion">-</span>
</div> </div>
<div id="mfDBRow" style="display:flex;justify-content:space-between;margin-bottom:.25rem"> <div id="mfDBRow" style="display:flex;justify-content:space-between;margin-bottom:.25rem">
<span>Adatbázis:</span> <span>{{T "app_import.adatbazis"}}</span>
<span id="mfDB">-</span> <span id="mfDB">-</span>
</div> </div>
<div style="display:flex;justify-content:space-between;margin-bottom:.25rem"> <div style="display:flex;justify-content:space-between;margin-bottom:.25rem">
<span>Adatok:</span> <span>{{T "app_import.adatok"}}</span>
<span id="mfDataType">-</span> <span id="mfDataType">-</span>
</div> </div>
<div style="display:flex;justify-content:space-between"> <div style="display:flex;justify-content:space-between">
<span>Méret:</span> <span>{{T "app_import.meret_2"}}</span>
<span id="mfSize">-</span> <span id="mfSize">-</span>
</div> </div>
</div> </div>
<div id="overwriteWarning" style="display:none;background:var(--warning-bg, #fff3cd);border:1px solid var(--warning-border, #ffc107);border-radius:8px;padding:1rem;margin-bottom:1rem"> <div id="overwriteWarning" style="display:none;background:var(--warning-bg, #fff3cd);border:1px solid var(--warning-border, #ffc107);border-radius:8px;padding:1rem;margin-bottom:1rem">
<strong>&#9888; FIGYELEM:</strong> A meglévő <span id="overwriteAppName"></span> alkalmazás konfigurációja és összes adata felül lesz írva! {{T "app_import.figyelem_a_meglevo"}} <span id="overwriteAppName"></span> {{T "app_import.alkalmazas_konfiguracioja_es_osszes_adat"}}
</div> </div>
<button id="importBtn" class="btn btn-primary" onclick="startImport()" style="width:100%"> <button id="importBtn" class="btn btn-primary" onclick="startImport()" style="width:100%">
Visszaállítás indítása {{T "app_import.visszaallitas_inditasa"}}
</button> </button>
</div> </div>
</div> </div>
<!-- Progress --> <!-- Progress -->
<div id="progressCard" class="card" style="max-width:700px;display:none"> <div id="progressCard" class="card" style="max-width:700px;display:none">
<h3>Importálás folyamata</h3> <h3>{{T "app_import.importalas_folyamata"}}</h3>
<div id="progressSteps"></div> <div id="progressSteps"></div>
<div id="progressError" style="display:none;color:var(--crit);margin-top:1rem;font-weight:600"></div> <div id="progressError" style="display:none;color:var(--crit);margin-top:1rem;font-weight:600"></div>
</div> </div>
<div id="doneCard" class="card" style="max-width:700px;display:none"> <div id="doneCard" class="card" style="max-width:700px;display:none">
<h3 style="color:var(--blue)">Importálás kész!</h3> <h3 style="color:var(--blue)">{{T "app_import.importalas_kesz"}}</h3>
<p>Az alkalmazás sikeresen visszaállítva.</p> <p>{{T "app_import.az_alkalmazas_sikeresen_visszaallitva"}}</p>
<a id="doneLink" href="/stacks" class="btn btn-primary">Alkalmazások megtekintése</a> <a id="doneLink" href="/stacks" class="btn btn-primary">{{T "app_import.alkalmazasok_megtekintese"}}</a>
</div> </div>
<script> <script>
@@ -170,7 +170,7 @@ async function loadManifest() {
if (!data.ok) { if (!data.ok) {
if (selectedEncrypted) { if (selectedEncrypted) {
document.getElementById('passwordError').textContent = data.error || 'Hibás jelszó'; document.getElementById('passwordError').textContent = data.error || '{{T "app_import.hibas_jelszo"}}';
document.getElementById('passwordError').style.display = 'block'; document.getElementById('passwordError').style.display = 'block';
} }
return; return;
@@ -193,7 +193,7 @@ function showManifest(m) {
document.getElementById('passwordPrompt').style.display = 'none'; document.getElementById('passwordPrompt').style.display = 'none';
document.getElementById('manifestInfo').style.display = 'block'; document.getElementById('manifestInfo').style.display = 'block';
document.getElementById('previewTitle').textContent = (m.display_name || m.app_name) + ' — Csomag részletei'; document.getElementById('previewTitle').textContent = (m.display_name || m.app_name) + ' {{T "app_import.csomag_reszletei_2"}}';
document.getElementById('mfName').textContent = m.display_name || m.app_name; document.getElementById('mfName').textContent = m.display_name || m.app_name;
document.getElementById('mfDate').textContent = m.exported_at ? new Date(m.exported_at).toLocaleString('hu-HU') : '-'; document.getElementById('mfDate').textContent = m.exported_at ? new Date(m.exported_at).toLocaleString('hu-HU') : '-';
document.getElementById('mfVersion').textContent = m.controller_version || '-'; document.getElementById('mfVersion').textContent = m.controller_version || '-';
@@ -208,7 +208,7 @@ function showManifest(m) {
var dataType = []; var dataType = [];
if (m.has_hdd_data) dataType.push('HDD'); if (m.has_hdd_data) dataType.push('HDD');
if (m.has_volume_data) dataType.push('Docker volume'); if (m.has_volume_data) dataType.push('Docker volume');
document.getElementById('mfDataType').textContent = dataType.length ? dataType.join(', ') : 'Nincs'; document.getElementById('mfDataType').textContent = dataType.length ? dataType.join(', ') : '{{T "app_import.nincs"}}';
// Format size // Format size
var bytes = m.total_size_bytes || 0; var bytes = m.total_size_bytes || 0;
@@ -240,7 +240,7 @@ async function startImport() {
}); });
var data = await resp.json(); var data = await resp.json();
if (!data.ok) { if (!data.ok) {
showError(data.error || 'Hiba történt'); showError(data.error || '{{T "common.hiba_tortent"}}');
return; return;
} }
pollStatus(); pollStatus();
@@ -269,7 +269,7 @@ function pollStatus() {
document.getElementById('doneCard').style.display = 'block'; document.getElementById('doneCard').style.display = 'block';
if (data.stack_name) { if (data.stack_name) {
document.getElementById('doneLink').href = '/stacks/' + data.stack_name + '/deploy'; document.getElementById('doneLink').href = '/stacks/' + data.stack_name + '/deploy';
document.getElementById('doneLink').textContent = 'Alkalmazás megtekintése'; document.getElementById('doneLink').textContent = '{{T "app_import.alkalmazas_megtekintese"}}';
} }
} }
} catch(e) { } catch(e) {
@@ -305,7 +305,7 @@ function showError(msg) {
// network error, re-syncing from the server's 409 received_bytes echo; progress from bytes acked. // network error, re-syncing from the server's 409 received_bytes echo; progress from bytes acked.
var fabUploadId = null; var fabUploadId = null;
var fabCancelled = false; var fabCancelled = false;
var fabNetErrText = 'A feltöltés megszakadt — ellenőrizze a kapcsolatot, majd próbálja újra.'; var fabNetErrText = '{{T "app_import.a_feltoltes_megszakadt_ellenorizze_a"}}';
function csrfRawH() { function csrfRawH() {
var el = document.querySelector('meta[name="csrf-token"]'); var el = document.querySelector('meta[name="csrf-token"]');
@@ -323,7 +323,7 @@ function fabGB(b) {
function fabProgress(done, total) { function fabProgress(done, total) {
var pct = total > 0 ? Math.floor(done * 100 / total) : 0; var pct = total > 0 ? Math.floor(done * 100 / total) : 0;
document.getElementById('fabUpText').textContent = document.getElementById('fabUpText').textContent =
'Feltöltés: ' + pct + '% (' + fabGB(done) + ' / ' + fabGB(total) + ' GB)'; '{{T "app_import.feltoltes"}} ' + pct + '% (' + fabGB(done) + ' / ' + fabGB(total) + ' GB)';
document.getElementById('fabUpBar').style.width = pct + '%'; document.getElementById('fabUpBar').style.width = pct + '%';
} }
@@ -355,7 +355,7 @@ async function fabStart(file) {
if (!file) return; if (!file) return;
document.getElementById('fabUpError').style.display = 'none'; document.getElementById('fabUpError').style.display = 'none';
if (!file.name.toLowerCase().endsWith('.fab')) { if (!file.name.toLowerCase().endsWith('.fab')) {
fabFail('Csak .fab fájl tölthető fel.'); fabFail('{{T "app_import.csak_fab_fajl_toltheto_fel"}}');
return; return;
} }
fabCancelled = false; fabCancelled = false;
@@ -410,7 +410,7 @@ async function fabStart(file) {
var fd = await fin.json(); var fd = await fin.json();
if (!fd.ok) { fabFail(fd.error || fabNetErrText); return; } if (!fd.ok) { fabFail(fd.error || fabNetErrText); return; }
fabUploadId = null; fabUploadId = null;
document.getElementById('fabUpText').textContent = 'Feltöltés kész: ' + fd.filename; document.getElementById('fabUpText').textContent = '{{T "app_import.feltoltes_kesz"}} ' + fd.filename;
document.getElementById('fabUpBar').style.width = '100%'; document.getElementById('fabUpBar').style.width = '100%';
// Refresh the bundle list through the EXISTING scan (the page render runs it). // Refresh the bundle list through the EXISTING scan (the page render runs it).
setTimeout(function(){ window.location.reload(); }, 800); setTimeout(function(){ window.location.reload(); }, 800);
@@ -9,13 +9,13 @@
<div style="display:flex;align-items:center;gap:.5rem"> <div style="display:flex;align-items:center;gap:.5rem">
{{if .Stack.Deployed}} {{if .Stack.Deployed}}
<span class="stack-state-badge state-{{stateColor .Stack.State}}">{{stateLabel .Stack.State}}</span> <span class="stack-state-badge state-{{stateColor .Stack.State}}">{{stateLabel .Stack.State}}</span>
{{if .Stack.Orphaned}}<span class="badge badge-orphaned">{{T "app_info.elavult"}}</span>{{end}} {{if .Stack.Orphaned}}<span class="badge badge-orphaned">{{T "common.elavult"}}</span>{{end}}
{{template "meta_badge" (lifecycleBadge .Meta)}} {{template "meta_badge" (lifecycleBadge .Meta)}}
{{template "meta_badge" (updateBadge .Stack)}} {{template "meta_badge" (updateBadge .Stack)}}
{{if .EffectiveSubdomain}}<a href="https://{{.EffectiveSubdomain}}.{{.Domain}}{{.Meta.OpenPath}}" target="_blank" class="btn btn-sm btn-outline">{{T "app_info.megnyitas"}}</a>{{end}} {{if .EffectiveSubdomain}}<a href="https://{{.EffectiveSubdomain}}.{{.Domain}}{{.Meta.OpenPath}}" target="_blank" class="btn btn-sm btn-outline">{{T "app_info.megnyitas"}}</a>{{end}}
<a href="/stacks/{{.Stack.Name}}/logs" class="btn btn-sm btn-outline">{{T "app_info.naplo"}}</a> <a href="/stacks/{{.Stack.Name}}/logs" class="btn btn-sm btn-outline">{{T "app_info.naplo"}}</a>
{{if .Stack.Orphaned}} {{if .Stack.Orphaned}}
<button class="btn btn-sm btn-danger" onclick="deleteOrphanStack('{{.Stack.Name}}')">{{T "app_info.torles"}}</button> <button class="btn btn-sm btn-danger" onclick="deleteOrphanStack('{{.Stack.Name}}')">{{T "common.torles"}}</button>
{{else}} {{else}}
<a href="/stacks/{{.Stack.Name}}/export" class="btn btn-sm btn-outline">{{T "app_info.exportalas"}}</a> <a href="/stacks/{{.Stack.Name}}/export" class="btn btn-sm btn-outline">{{T "app_info.exportalas"}}</a>
<a href="/stacks/{{.Stack.Name}}/deploy" class="btn btn-sm btn-outline">{{T "app_info.beallitasok"}}</a> <a href="/stacks/{{.Stack.Name}}/deploy" class="btn btn-sm btn-outline">{{T "app_info.beallitasok"}}</a>
@@ -57,9 +57,9 @@
<div class="stack-meta-badges"> <div class="stack-meta-badges">
<span class="meta-badge">~{{.Meta.Resources.MemRequest}} RAM</span> <span class="meta-badge">~{{.Meta.Resources.MemRequest}} RAM</span>
<span class="meta-badge">{{.Meta.Category}}</span> <span class="meta-badge">{{.Meta.Category}}</span>
{{if .Meta.Resources.NeedsHDD}}<span class="meta-badge meta-badge-warn">{{T "app_info.hdd_szukseges"}}</span>{{end}} {{if .Meta.Resources.NeedsHDD}}<span class="meta-badge meta-badge-warn">{{T "common.hdd_szukseges"}}</span>{{end}}
{{if .Meta.Resources.PiCompatible}}<span class="meta-badge meta-badge-ok">{{T "app_info.pi_kompatibilis"}}</span>{{else}}<span class="meta-badge meta-badge-warn">{{T "app_info.csak_x86"}}</span>{{end}} {{if .Meta.Resources.PiCompatible}}<span class="meta-badge meta-badge-ok">{{T "common.pi_kompatibilis"}}</span>{{else}}<span class="meta-badge meta-badge-warn">{{T "app_info.csak_x86"}}</span>{{end}}
{{if .Meta.Resources.HungarianUI}}<span class="meta-badge meta-badge-ok">{{T "app_info.magyar_felulet"}}</span>{{end}} {{if .Meta.Resources.HungarianUI}}<span class="meta-badge meta-badge-ok">{{T "common.magyar_felulet"}}</span>{{end}}
</div> </div>
{{if .Meta.IsAbandoned}} {{if .Meta.IsAbandoned}}
<div class="alert alert-warning" style="margin-top:.75rem"> <div class="alert alert-warning" style="margin-top:.75rem">
@@ -139,8 +139,8 @@ function appMigrate(btn,app,label){
if(!target){ showAlert('{{T "app_info.valassz_celtarhelyet_2"}}'); return; } if(!target){ showAlert('{{T "app_info.valassz_celtarhelyet_2"}}'); return; }
felhomConfirm(btn,'{{T "app_info.athelyezed_a_z"}} '+label+' {{T "app_info.adatait_ide"}} '+target+'{{T "app_info.az_alkalmazas_rovid_idore_leall"}}',function(){ felhomConfirm(btn,'{{T "app_info.athelyezed_a_z"}} '+label+' {{T "app_info.adatait_ide"}} '+target+'{{T "app_info.az_alkalmazas_rovid_idore_leall"}}',function(){
fetch('/api/storage/migrate-app',{method:'POST',headers:Object.assign({'Content-Type':'application/json'},csrfHeaders()),body:JSON.stringify({app:app,target:target})}) fetch('/api/storage/migrate-app',{method:'POST',headers:Object.assign({'Content-Type':'application/json'},csrfHeaders()),body:JSON.stringify({app:app,target:target})})
.then(function(r){return r.json();}).then(function(d){ if(d.ok){ appMigWatch(); } else { showAlert('{{T "app_info.hiba"}} '+(d.error||'{{T "app_info.ismeretlen"}}')); } }) .then(function(r){return r.json();}).then(function(d){ if(d.ok){ appMigWatch(); } else { showAlert('{{T "common.hiba_kettospont"}} '+(d.error||'{{T "app_info.ismeretlen"}}')); } })
.catch(function(e){ showAlert('{{T "app_info.hiba"}} '+e); }); .catch(function(e){ showAlert('{{T "common.hiba_kettospont"}} '+e); });
}); });
} }
(function(){ fetch('/api/storage/migrate/status').then(function(r){return r.json();}).then(function(d){ if(d.data&&d.data.job){ appMigWatch(); } }).catch(function(){}); })(); (function(){ fetch('/api/storage/migrate/status').then(function(r){return r.json();}).then(function(d){ if(d.data&&d.data.job){ appMigWatch(); } }).catch(function(){}); })();
@@ -188,12 +188,12 @@ function appMigrate(btn,app,label){
{{end}} {{end}}
{{if .InitialCredsOperatorSet}} {{if .InitialCredsOperatorSet}}
<tr> <tr>
<td class="initcred-label" style="padding:.25rem .75rem .25rem 0;color:var(--text-3);white-space:nowrap">{{T "app_info.jelszo"}}</td> <td class="initcred-label" style="padding:.25rem .75rem .25rem 0;color:var(--text-3);white-space:nowrap">{{T "common.jelszo"}}</td>
<td>{{T "app_info.az_uzemelteto_allitotta_be"}}</td> <td>{{T "app_info.az_uzemelteto_allitotta_be"}}</td>
</tr> </tr>
{{else}} {{else}}
<tr> <tr>
<td class="initcred-label" style="padding:.25rem .75rem .25rem 0;color:var(--text-3);white-space:nowrap;vertical-align:middle">{{T "app_info.jelszo"}}</td> <td class="initcred-label" style="padding:.25rem .75rem .25rem 0;color:var(--text-3);white-space:nowrap;vertical-align:middle">{{T "common.jelszo"}}</td>
<!-- R-254: the value is NOT in this page. It used to be rendered into a `hidden` <!-- R-254: the value is NOT in this page. It used to be rendered into a `hidden`
span, which stops a browser drawing it and nothing else — a fetch of this page span, which stops a browser drawing it and nothing else — a fetch of this page
returned a real per-install password. Both buttons now ask the server. --> returned a real per-install password. Both buttons now ask the server. -->
@@ -38,7 +38,7 @@
{{with $.SystemInfo}} {{with $.SystemInfo}}
<div class="storage-item meter {{usageColor .DiskPercent}}"> <div class="storage-item meter {{usageColor .DiskPercent}}">
<div class="storage-header"> <div class="storage-header">
<span class="storage-label">{{T "backups.rendszer"}}</span> <span class="storage-label">{{T "common.rendszer_gyoker"}}</span>
<span class="storage-value">{{fmtGB .DiskUsedGB}} / {{fmtGB .DiskTotalGB}} ({{printf "%.0f" .DiskPercent}}%)</span> <span class="storage-value">{{fmtGB .DiskUsedGB}} / {{fmtGB .DiskTotalGB}} ({{printf "%.0f" .DiskPercent}}%)</span>
</div> </div>
<div class="meter-track"> <div class="meter-track">
@@ -50,7 +50,7 @@
<div class="storage-item meter meter-empty storage-disconnected"> <div class="storage-item meter meter-empty storage-disconnected">
<div class="storage-header"> <div class="storage-header">
<span class="storage-label">{{.Label}}</span> <span class="storage-label">{{.Label}}</span>
<span class="storage-value">— <span class="state-text-neutral">{{T "backups.levalasztva"}}</span></span> <span class="storage-value">— <span class="state-text-neutral">{{T "common.levalasztva"}}</span></span>
</div> </div>
<div class="meter-track"></div> <div class="meter-track"></div>
</div> </div>
@@ -227,11 +227,11 @@ function triggerGuestBackup() {
fetch('/api/guest-backup/trigger', { method: 'POST', headers: Object.assign({'Content-Type':'application/json'}, csrfHeaders()) }) fetch('/api/guest-backup/trigger', { method: 'POST', headers: Object.assign({'Content-Type':'application/json'}, csrfHeaders()) })
.then(r => r.json()) .then(r => r.json())
.then(data => { .then(data => {
if (!data.ok) { out.innerHTML = '<div class="alert alert-error">' + (data.error || '{{T "backups.hiba"}}') + '</div>'; btn.disabled = false; return; } if (!data.ok) { out.innerHTML = '<div class="alert alert-error">' + (data.error || '{{T "common.hiba"}}') + '</div>'; btn.disabled = false; return; }
out.innerHTML = '<span class="form-hint">{{T "backups.mentes_folyamatban"}}</span>'; out.innerHTML = '<span class="form-hint">{{T "backups.mentes_folyamatban"}}</span>';
pollGuestBackup(out, btn); pollGuestBackup(out, btn);
}) })
.catch(e => { out.innerHTML = '<div class="alert alert-error">{{T "backups.hiba_2"}} ' + e + '</div>'; btn.disabled = false; }); .catch(e => { out.innerHTML = '<div class="alert alert-error">{{T "common.hiba_kettospont"}} ' + e + '</div>'; btn.disabled = false; });
}); });
} }
@@ -285,7 +285,7 @@ function pollGuestBackup(out, btn) {
} }
}) })
.catch(function (e) { .catch(function (e) {
out.innerHTML = '<div class="alert alert-error">{{T "backups.hiba_2"}} ' + e.message + '</div>'; out.innerHTML = '<div class="alert alert-error">{{T "common.hiba_kettospont"}} ' + e.message + '</div>';
btn.disabled = false; btn.disabled = false;
}); });
}); });
@@ -2,22 +2,22 @@
{{template "layout_start" .}} {{template "layout_start" .}}
<div class="page-header"> <div class="page-header">
<h2>Vezérlőpult</h2> <h2>{{T "dashboard.vezerlopult"}}</h2>
<span class="domain-badge">{{.Domain}}</span> <span class="domain-badge">{{.Domain}}</span>
</div> </div>
<div class="stats-grid"> <div class="stats-grid">
<a href="/stacks?filter=running" class="stat-card"> <a href="/stacks?filter=running" class="stat-card">
<div class="stat-value">{{.RunningCount}}</div> <div class="stat-value">{{.RunningCount}}</div>
<div class="stat-label">Futó alkalmazás</div> <div class="stat-label">{{T "dashboard.futo_alkalmazas"}}</div>
</a> </a>
<a href="/stacks?filter=stopped" class="stat-card {{if gt .StoppedCount 0}}stat-warn{{else}}stat-zero{{end}}"> <a href="/stacks?filter=stopped" class="stat-card {{if gt .StoppedCount 0}}stat-warn{{else}}stat-zero{{end}}">
<div class="stat-value">{{.StoppedCount}}</div> <div class="stat-value">{{.StoppedCount}}</div>
<div class="stat-label">Leállítva</div> <div class="stat-label">{{T "dashboard.leallitva"}}</div>
</a> </a>
<a href="/stacks" class="stat-card"> <a href="/stacks" class="stat-card">
<div class="stat-value">{{.TotalCount}}</div> <div class="stat-value">{{.TotalCount}}</div>
<div class="stat-label">Összes alkalmazás</div> <div class="stat-label">{{T "dashboard.osszes_alkalmazas"}}</div>
</a> </a>
</div> </div>
@@ -26,7 +26,7 @@
<div class="system-info-items"> <div class="system-info-items">
<div class="system-info-item meter {{usageColor .SystemInfo.MemPercent}}"> <div class="system-info-item meter {{usageColor .SystemInfo.MemPercent}}">
<div class="system-info-header"> <div class="system-info-header">
<span class="system-info-label">Memória</span> <span class="system-info-label">{{T "dashboard.memoria"}}</span>
<span class="system-info-value">{{fmtMB .SystemInfo.UsedMemMB}} / {{fmtMB .SystemInfo.TotalMemMB}} ({{printf "%.0f" .SystemInfo.MemPercent}}%)</span> <span class="system-info-value">{{fmtMB .SystemInfo.UsedMemMB}} / {{fmtMB .SystemInfo.TotalMemMB}} ({{printf "%.0f" .SystemInfo.MemPercent}}%)</span>
</div> </div>
<div class="meter-track"> <div class="meter-track">
@@ -46,7 +46,7 @@
{{if .SystemInfo.TemperatureCelsius}} {{if .SystemInfo.TemperatureCelsius}}
<div class="system-info-item system-info-item-compact"> <div class="system-info-item system-info-item-compact">
<div class="system-info-header"> <div class="system-info-header">
<span class="system-info-label"><svg class="ico ico-sm"><use href="#i-thermometer"/></svg> Hőmérséklet</span> <span class="system-info-label"><svg class="ico ico-sm"><use href="#i-thermometer"/></svg> {{T "dashboard.homerseklet"}}</span>
<span class="system-info-value temp-value state-text-{{tempColor .SystemInfo.TemperatureCelsius}}">{{fmtTemp .SystemInfo.TemperatureCelsius}}</span> <span class="system-info-value temp-value state-text-{{tempColor .SystemInfo.TemperatureCelsius}}">{{fmtTemp .SystemInfo.TemperatureCelsius}}</span>
</div> </div>
</div> </div>
@@ -61,23 +61,23 @@
{{if not .SystemInfo.DiskKnown}} {{if not .SystemInfo.DiskKnown}}
<div class="system-info-item meter meter-empty"> <div class="system-info-item meter meter-empty">
<div class="system-info-header"> <div class="system-info-header">
<span class="system-info-label">Rendszer (/)</span> <span class="system-info-label">{{T "common.rendszer_gyoker"}}</span>
<span class="system-info-value">— <span class="state-text-neutral">nem ismert</span></span> <span class="system-info-value">— <span class="state-text-neutral">{{T "dashboard.nem_ismert"}}</span></span>
</div> </div>
<div class="meter-track"></div> <div class="meter-track"></div>
<div class="meter-flag">A tárhely mérete most nem olvasható ki.</div> <div class="meter-flag">{{T "dashboard.a_tarhely_merete_most_nem"}}</div>
</div> </div>
{{else}} {{else}}
{{$duc := usageColor .SystemInfo.DiskPercent}} {{$duc := usageColor .SystemInfo.DiskPercent}}
<div class="system-info-item meter {{$duc}}"> <div class="system-info-item meter {{$duc}}">
<div class="system-info-header"> <div class="system-info-header">
<span class="system-info-label">Rendszer (/)</span> <span class="system-info-label">{{T "common.rendszer_gyoker"}}</span>
<span class="system-info-value">{{fmtGB .SystemInfo.DiskUsedGB}} / {{fmtGB .SystemInfo.DiskTotalGB}} ({{printf "%.0f" .SystemInfo.DiskPercent}}%)</span> <span class="system-info-value">{{fmtGB .SystemInfo.DiskUsedGB}} / {{fmtGB .SystemInfo.DiskTotalGB}} ({{printf "%.0f" .SystemInfo.DiskPercent}}%)</span>
</div> </div>
<div class="meter-track"> <div class="meter-track">
<div class="meter-fill" style="width:{{printf "%.0f" .SystemInfo.DiskPercent}}%"></div> <div class="meter-fill" style="width:{{printf "%.0f" .SystemInfo.DiskPercent}}%"></div>
</div> </div>
{{if eq $duc "warn"}}<div class="meter-flag"><svg class="ico"><use href="#i-triangle-alert"/></svg>Fogyóban a hely</div>{{else if eq $duc "crit"}}<div class="meter-flag"><svg class="ico"><use href="#i-triangle-alert"/></svg>Kritikusan kevés hely</div>{{end}} {{if eq $duc "warn"}}<div class="meter-flag"><svg class="ico"><use href="#i-triangle-alert"/></svg>{{T "dashboard.fogyoban_a_hely"}}</div>{{else if eq $duc "crit"}}<div class="meter-flag"><svg class="ico"><use href="#i-triangle-alert"/></svg>{{T "dashboard.kritikusan_keves_hely"}}</div>{{end}}
</div> </div>
{{end}} {{end}}
{{range .StorageBars}} {{range .StorageBars}}
@@ -85,7 +85,7 @@
<div class="system-info-item meter meter-empty storage-disconnected"> <div class="system-info-item meter meter-empty storage-disconnected">
<div class="system-info-header"> <div class="system-info-header">
<span class="system-info-label">{{.Label}}</span> <span class="system-info-label">{{.Label}}</span>
<span class="system-info-value">— <span class="state-text-neutral">Leválasztva</span></span> <span class="system-info-value">— <span class="state-text-neutral">{{T "common.levalasztva"}}</span></span>
</div> </div>
<div class="meter-track"></div> <div class="meter-track"></div>
</div> </div>
@@ -99,7 +99,7 @@
<div class="meter-track"> <div class="meter-track">
<div class="meter-fill" style="width:{{printf "%.0f" .Percent}}%"></div> <div class="meter-fill" style="width:{{printf "%.0f" .Percent}}%"></div>
</div> </div>
{{if eq $uc "warn"}}<div class="meter-flag"><svg class="ico"><use href="#i-triangle-alert"/></svg>Fogyóban a hely</div>{{else if eq $uc "crit"}}<div class="meter-flag"><svg class="ico"><use href="#i-triangle-alert"/></svg>Kritikusan kevés hely</div>{{end}} {{if eq $uc "warn"}}<div class="meter-flag"><svg class="ico"><use href="#i-triangle-alert"/></svg>{{T "dashboard.fogyoban_a_hely"}}</div>{{else if eq $uc "crit"}}<div class="meter-flag"><svg class="ico"><use href="#i-triangle-alert"/></svg>{{T "dashboard.kritikusan_keves_hely"}}</div>{{end}}
</div> </div>
{{end}} {{end}}
{{end}} {{end}}
@@ -127,7 +127,7 @@
{{end}} {{end}}
<div class="disk-health-card"> <div class="disk-health-card">
<h3>Lemezek állapota</h3> <h3>{{T "dashboard.lemezek_allapota"}}</h3>
{{if .DiskHealthRows}} {{if .DiskHealthRows}}
<ul class="disk-health-list"> <ul class="disk-health-list">
{{range .DiskHealthRows}} {{range .DiskHealthRows}}
@@ -139,34 +139,34 @@
{{end}} {{end}}
</ul> </ul>
{{else}} {{else}}
<p class="form-hint state-text-neutral">Nincs adat</p> <p class="form-hint state-text-neutral">{{T "dashboard.nincs_adat"}}</p>
{{end}} {{end}}
</div> </div>
{{if .BackupEnabled}} {{if .BackupEnabled}}
<div class="backup-status-card"> <div class="backup-status-card">
<h3><a href="/backups" class="backup-card-link">Biztonsági mentés</a></h3> <h3><a href="/backups" class="backup-card-link">{{T "dashboard.biztonsagi_mentes"}}</a></h3>
{{if .BackupStatus}} {{if .BackupStatus}}
<div class="backup-info-row"> <div class="backup-info-row">
<span class="backup-label">Utolsó mentés:</span> <span class="backup-label">{{T "dashboard.utolso_mentes"}}</span>
<span class="backup-value"> <span class="backup-value">
{{if .BackupStatus.Success}} {{if .BackupStatus.Success}}
<span>{{.BackupStatus.LastRun.Format "2006-01-02 15:04"}}</span> <span>{{.BackupStatus.LastRun.Format "2006-01-02 15:04"}}</span>
{{else}} {{else}}
<span class="backup-status-fail">Sikertelen</span> <span class="backup-status-fail">{{T "dashboard.sikertelen"}}</span>
{{end}} {{end}}
</span> </span>
</div> </div>
{{else}} {{else}}
<div class="backup-info-row"> <div class="backup-info-row">
<span class="backup-label">Utolsó mentés:</span> <span class="backup-label">{{T "dashboard.utolso_mentes"}}</span>
<span class="backup-value backup-status-none">Még nem futott</span> <span class="backup-value backup-status-none">{{T "dashboard.meg_nem_futott"}}</span>
</div> </div>
{{end}} {{end}}
{{if .DBDumpStatus}} {{if .DBDumpStatus}}
<div class="backup-info-row"> <div class="backup-info-row">
<span class="backup-label">Adatbázisok:</span> <span class="backup-label">{{T "dashboard.adatbazisok"}}</span>
<span class="backup-value">{{len .DBDumpStatus.Results}} mentve</span> <span class="backup-value">{{T "dashboard.mentve"}}</span>
</div> </div>
{{end}} {{end}}
{{/* Cross-drive ("2. mentés") moved to the host agent in slice 8C — the dashboard handler no {{/* Cross-drive ("2. mentés") moved to the host agent in slice 8C — the dashboard handler no
@@ -175,7 +175,7 @@
</div> </div>
{{end}} {{end}}
<h3>Telepített alkalmazások</h3> <h3>{{T "dashboard.telepitett_alkalmazasok"}}</h3>
<div class="stack-list"> <div class="stack-list">
{{range .Stacks}} {{range .Stacks}}
@@ -186,44 +186,44 @@
{{template "app_list_row" dict "Slug" .Meta.Slug "Name" .Meta.DisplayName "Secondary" .Meta.Description "RowClass" (printf "stack-state-%s" (stateColor .State)) "Href" (appHref .Meta.Slug)}} {{template "app_list_row" dict "Slug" .Meta.Slug "Name" .Meta.DisplayName "Secondary" .Meta.Description "RowClass" (printf "stack-state-%s" (stateColor .State)) "Href" (appHref .Meta.Slug)}}
{{end}} {{end}}
<span class="tag tag-{{stateColor .State}}"><span class="dot"></span>{{stateLabel .State}}</span> <span class="tag tag-{{stateColor .State}}"><span class="dot"></span>{{stateLabel .State}}</span>
{{if .Orphaned}}<span class="tag tag-warn">Elavult</span>{{end}} {{if .Orphaned}}<span class="tag tag-warn">{{T "common.elavult"}}</span>{{end}}
{{template "meta_badge" (lifecycleBadge .Meta)}} {{template "meta_badge" (lifecycleBadge .Meta)}}
{{$ms := index $.MissingStorage .Name}}{{if $ms}}<span class="tag tag-warn" title="Az alkalmazás adattárolója nem elérhető. Csatlakoztasd újra a meghajtót, vagy helyezd át az adatokat egy másik tárhelyre."><svg class="ico ico-sm"><use href="#i-triangle-alert"/></svg>Hiányzó tárhely: {{$ms}}</span>{{end}} {{$ms := index $.MissingStorage .Name}}{{if $ms}}<span class="tag tag-warn" title="{{T "dashboard.az_alkalmazas_adattaroloja_nem_elerheto"}}"><svg class="ico ico-sm"><use href="#i-triangle-alert"/></svg>{{T "dashboard.hianyzo_tarhely"}}</span>{{end}}
{{if $.OOMKilled}}{{if index $.OOMKilled .Name}}<span class="tag tag-warn" title="Az alkalmazás egyik folyamatát a rendszer leállította, mert elfogyott a memóriája. Lehet, hogy egy feladat nem fejeződött be. Indítsd újra az alkalmazást; ha ismétlődik, jelezd az üzemeltetőnek."><svg class="ico ico-sm"><use href="#i-triangle-alert"/></svg>Memória elfogyott</span>{{end}}{{end}} {{if $.OOMKilled}}{{if index $.OOMKilled .Name}}<span class="tag tag-warn" title="{{T "dashboard.az_alkalmazas_egyik_folyamatat_a"}}"><svg class="ico ico-sm"><use href="#i-triangle-alert"/></svg>{{T "dashboard.memoria_elfogyott"}}</span>{{end}}{{end}}
{{$ns := index $.NetworkStubs .Name}}{{if $ns}}<span class="tag tag-warn" title="Az alkalmazás környezetében a hálózati tárhely helyén üres helyi könyvtár van — az adatok nem a NAS-ra kerülnek. Jelezze az üzemeltetőnek."><svg class="ico ico-sm"><use href="#i-triangle-alert"/></svg>Hálózati tárhely hibás — az alkalmazás nem a NAS-t látja</span>{{end}} {{$ns := index $.NetworkStubs .Name}}{{if $ns}}<span class="tag tag-warn" title="{{T "dashboard.az_alkalmazas_kornyezeteben_a_halozati"}}"><svg class="ico ico-sm"><use href="#i-triangle-alert"/></svg>{{T "dashboard.halozati_tarhely_hibas_az_alkalmazas"}}</span>{{end}}
{{$nw := index $.NetworkWarnings .Name}}{{if $nw}}<span class="tag tag-warn" title="A hálózati tárhely (NAS) jelenleg nem érhető el. Az alkalmazás fut; az adatok elérése a NAS visszatértével helyreáll."><svg class="ico ico-sm"><use href="#i-triangle-alert"/></svg>Hálózati tárhely nem elérhető: {{$nw}}</span>{{end}} {{$nw := index $.NetworkWarnings .Name}}{{if $nw}}<span class="tag tag-warn" title="{{T "dashboard.a_halozati_tarhely_nas_jelenleg"}}"><svg class="ico ico-sm"><use href="#i-triangle-alert"/></svg>{{T "dashboard.halozati_tarhely_nem_elerheto"}}</span>{{end}}
{{if and .Deployed (routeUnpublished .State)}}<span class="tag tag-warn" title="A proxy (Traefik) csak egészséges konténerhez publikál nyilvános útvonalat. Amíg az alkalmazás nem egészséges, az URL 404-et ad, pedig a konténer fut."><svg class="ico ico-sm"><use href="#i-triangle-alert"/></svg>URL nem elérhető</span>{{end}} {{if and .Deployed (routeUnpublished .State)}}<span class="tag tag-warn" title="{{T "dashboard.a_proxy_traefik_csak_egeszseges"}}"><svg class="ico ico-sm"><use href="#i-triangle-alert"/></svg>{{T "dashboard.url_nem_elerheto"}}</span>{{end}}
{{if .Protected}} {{if .Protected}}
<span class="tag"><svg class="ico ico-sm"><use href="#i-lock"/></svg>Védett</span> <span class="tag"><svg class="ico ico-sm"><use href="#i-lock"/></svg>{{T "dashboard.vedett"}}</span>
{{/* Customer link ONLY for Linked infra (filebrowser → files.<domain>) — {{/* Customer link ONLY for Linked infra (filebrowser → files.<domain>) —
cloudflared/traefik have no customer UI (guarded by render test). */}} cloudflared/traefik have no customer UI (guarded by render test). */}}
{{if and $im $im.Linked}} {{if and $im $im.Linked}}
{{$subdomain := index $.Subdomains .Name}} {{$subdomain := index $.Subdomains .Name}}
{{if $subdomain}}<a href="https://{{$subdomain}}.{{$.Domain}}{{.Meta.OpenPath}}" target="_blank" class="btn btn-sm btn-outline" onclick="event.stopPropagation()">Megnyitás <svg class="ico ico-sm"><use href="#i-external-link"/></svg></a>{{end}} {{if $subdomain}}<a href="https://{{$subdomain}}.{{$.Domain}}{{.Meta.OpenPath}}" target="_blank" class="btn btn-sm btn-outline" onclick="event.stopPropagation()">{{T "dashboard.megnyitas"}} <svg class="ico ico-sm"><use href="#i-external-link"/></svg></a>{{end}}
{{end}} {{end}}
{{if isOperational .State}} {{if isOperational .State}}
<button class="btn btn-sm btn-warning" onclick="stackAction(event, '{{.Name}}', 'restart')" title="Újraindítás"><svg class="ico ico-sm"><use href="#i-rotate-cw"/></svg></button> <button class="btn btn-sm btn-warning" onclick="stackAction(event, '{{.Name}}', 'restart')" title="{{T "common.ujrainditas"}}"><svg class="ico ico-sm"><use href="#i-rotate-cw"/></svg></button>
{{end}} {{end}}
{{else if not .Deployed}} {{else if not .Deployed}}
<a href="/stacks/{{.Name}}/deploy" class="btn btn-sm btn-primary" onclick="return checkBeforeDeploy(event, '{{.Name}}')">Telepítés</a> <a href="/stacks/{{.Name}}/deploy" class="btn btn-sm btn-primary" onclick="return checkBeforeDeploy(event, '{{.Name}}')">{{T "dashboard.telepites"}}</a>
{{else}} {{else}}
{{if isOperational .State}} {{if isOperational .State}}
{{$subdomain := index $.Subdomains .Name}} {{$subdomain := index $.Subdomains .Name}}
{{if $subdomain}}<a href="https://{{$subdomain}}.{{$.Domain}}{{.Meta.OpenPath}}" target="_blank" class="btn btn-sm btn-outline" onclick="event.stopPropagation()">Megnyitás <svg class="ico ico-sm"><use href="#i-external-link"/></svg></a>{{end}} {{if $subdomain}}<a href="https://{{$subdomain}}.{{$.Domain}}{{.Meta.OpenPath}}" target="_blank" class="btn btn-sm btn-outline" onclick="event.stopPropagation()">{{T "dashboard.megnyitas"}} <svg class="ico ico-sm"><use href="#i-external-link"/></svg></a>{{end}}
<button class="btn btn-sm btn-warning" onclick="stackAction(event, '{{.Name}}', 'restart')" title="Újraindítás"><svg class="ico ico-sm"><use href="#i-rotate-cw"/></svg></button> <button class="btn btn-sm btn-warning" onclick="stackAction(event, '{{.Name}}', 'restart')" title="{{T "common.ujrainditas"}}"><svg class="ico ico-sm"><use href="#i-rotate-cw"/></svg></button>
<button class="btn btn-sm btn-danger" onclick="stackAction(event, '{{.Name}}', 'stop')" title="Leállítás"><svg class="ico ico-sm"><use href="#i-square"/></svg></button> <button class="btn btn-sm btn-danger" onclick="stackAction(event, '{{.Name}}', 'stop')" title="{{T "dashboard.leallitas"}}"><svg class="ico ico-sm"><use href="#i-square"/></svg></button>
{{else}} {{else}}
<button class="btn btn-sm btn-success" onclick="stackAction(event, '{{.Name}}', 'start')" title="Indítás"><svg class="ico ico-sm"><use href="#i-play"/></svg></button> <button class="btn btn-sm btn-success" onclick="stackAction(event, '{{.Name}}', 'start')" title="{{T "dashboard.inditas"}}"><svg class="ico ico-sm"><use href="#i-play"/></svg></button>
{{if not .Orphaned}}<button class="btn btn-sm btn-danger" onclick="removeStack('{{.Name}}')">Eltávolítás</button>{{end}} {{if not .Orphaned}}<button class="btn btn-sm btn-danger" onclick="removeStack('{{.Name}}')">{{T "common.eltavolitas"}}</button>{{end}}
{{end}} {{end}}
<a href="/stacks/{{.Name}}/logs" class="btn btn-sm btn-outline">Napló</a> <a href="/stacks/{{.Name}}/logs" class="btn btn-sm btn-outline">{{T "dashboard.naplo"}}</a>
{{if .Orphaned}}<button class="btn btn-sm btn-danger" onclick="deleteOrphanStack('{{.Name}}')">Törlés</button>{{end}} {{if .Orphaned}}<button class="btn btn-sm btn-danger" onclick="deleteOrphanStack('{{.Name}}')">{{T "common.torles"}}</button>{{end}}
{{end}} {{end}}
{{template "app_list_row_end"}} {{template "app_list_row_end"}}
{{else}} {{else}}
<div class="empty-state"> <div class="empty-state">
<p>Nincs elérhető alkalmazás.</p> <p>{{T "dashboard.nincs_elerheto_alkalmazas"}}</p>
</div> </div>
{{end}} {{end}}
</div> </div>
+161 -170
View File
@@ -3,22 +3,22 @@
<div class="page-header"> <div class="page-header">
<div style="display:flex;align-items:center;gap:.5rem"> <div style="display:flex;align-items:center;gap:.5rem">
<a href="/stacks" class="btn btn-sm btn-outline">← Vissza</a> <a href="/stacks" class="btn btn-sm btn-outline">{{T "deploy.vissza"}}</a>
<h2>{{.Meta.DisplayName}} — {{if .AlreadyDeployed}}Beállítások{{else}}Telepítés{{end}}</h2> <h2>{{.Meta.DisplayName}} — {{if .AlreadyDeployed}}{{T "deploy.beallitasok"}}{{else}}{{T "deploy.telepites"}}{{end}}</h2>
</div> </div>
<div style="display:flex;align-items:center;gap:.5rem"> <div style="display:flex;align-items:center;gap:.5rem">
{{if .AlreadyDeployed}} {{if .AlreadyDeployed}}
{{if isOperational .Stack.State}} {{if isOperational .Stack.State}}
<button class="btn btn-sm btn-warning" onclick="stackAction(event, '{{.Stack.Name}}', 'restart')">Újraindítás</button> <button class="btn btn-sm btn-warning" onclick="stackAction(event, '{{.Stack.Name}}', 'restart')">{{T "common.ujrainditas"}}</button>
<button class="btn btn-sm btn-danger" onclick="stackAction(event, '{{.Stack.Name}}', 'stop')">Leállítás</button> <button class="btn btn-sm btn-danger" onclick="stackAction(event, '{{.Stack.Name}}', 'stop')">{{T "deploy.leallitas"}}</button>
{{else}} {{else}}
<button class="btn btn-sm btn-success" onclick="stackAction(event, '{{.Stack.Name}}', 'start')">Indítás</button> <button class="btn btn-sm btn-success" onclick="stackAction(event, '{{.Stack.Name}}', 'start')">{{T "deploy.inditas"}}</button>
{{end}} {{end}}
{{if and (isOperational .Stack.State) .EffectiveSubdomain}} {{if and (isOperational .Stack.State) .EffectiveSubdomain}}
<a href="https://{{.EffectiveSubdomain}}.{{.Domain}}{{.Meta.OpenPath}}" target="_blank" class="btn btn-sm btn-outline">Megnyitás ↗</a> <a href="https://{{.EffectiveSubdomain}}.{{.Domain}}{{.Meta.OpenPath}}" target="_blank" class="btn btn-sm btn-outline">{{T "deploy.megnyitas"}}</a>
{{end}} {{end}}
{{end}} {{end}}
<a href="/apps/{{.Meta.Slug}}" class="btn btn-sm btn-outline"><svg class="ico ico-sm"><use href="#i-info"/></svg> Részletek</a> <a href="/apps/{{.Meta.Slug}}" class="btn btn-sm btn-outline"><svg class="ico ico-sm"><use href="#i-info"/></svg> {{T "deploy.reszletek"}}</a>
</div> </div>
</div> </div>
@@ -33,73 +33,72 @@
{{if .Meta.Description}}<p>{{.Meta.Description}}</p>{{end}} {{if .Meta.Description}}<p>{{.Meta.Description}}</p>{{end}}
<div class="metarows"> <div class="metarows">
{{if .Meta.Resources.MemRequest}}<span class="metarow"><svg class="ico"><use href="#i-memory-stick"/></svg><span class="mono">~{{.Meta.Resources.MemRequest}}</span></span>{{end}} {{if .Meta.Resources.MemRequest}}<span class="metarow"><svg class="ico"><use href="#i-memory-stick"/></svg><span class="mono">~{{.Meta.Resources.MemRequest}}</span></span>{{end}}
{{if .Meta.Resources.PiCompatible}}<span class="metarow"><svg class="ico"><use href="#i-check"/></svg>Pi kompatibilis</span>{{end}} {{if .Meta.Resources.PiCompatible}}<span class="metarow"><svg class="ico"><use href="#i-check"/></svg>{{T "common.pi_kompatibilis"}}</span>{{end}}
{{if .Meta.Resources.NeedsHDD}}<span class="metarow"><svg class="ico"><use href="#i-hard-drive"/></svg>HDD szükséges</span>{{end}} {{if .Meta.Resources.NeedsHDD}}<span class="metarow"><svg class="ico"><use href="#i-hard-drive"/></svg>{{T "common.hdd_szukseges"}}</span>{{end}}
{{if .Meta.Resources.HungarianUI}}<span class="metarow"><svg class="ico"><use href="#i-check"/></svg>Magyar felület</span>{{end}} {{if .Meta.Resources.HungarianUI}}<span class="metarow"><svg class="ico"><use href="#i-check"/></svg>{{T "common.magyar_felulet"}}</span>{{end}}
</div> </div>
<a href="/apps/{{.Meta.Slug}}" class="btn btn-sm btn-outline" style="margin-top:0.5rem"> <a href="/apps/{{.Meta.Slug}}" class="btn btn-sm btn-outline" style="margin-top:0.5rem">
Részletes leírás, képernyőképek {{T "deploy.reszletes_leiras_kepernyokepek"}}
</a> </a>
</div> </div>
</div> </div>
{{if .AlreadyDeployed}} {{if .AlreadyDeployed}}
<div class="alert alert-info"> <div class="alert alert-info">
Ez az alkalmazás már telepítve van. Az alábbi beállítások csak olvashatók. {{T "deploy.ez_az_alkalmazas_mar_telepitve"}}
</div> </div>
{{if .StorageInfo}} {{if .StorageInfo}}
<div class="deploy-storage-info"> <div class="deploy-storage-info">
<h4>Adattárolás</h4> <h4>{{T "deploy.adattarolas"}}</h4>
<div class="settings-grid"> <div class="settings-grid">
<div class="settings-row"> <div class="settings-row">
<span class="settings-label">Tárhely</span> <span class="settings-label">{{T "deploy.tarhely"}}</span>
<span class="settings-value">{{.StorageInfo.Label}} <span class="mono" style="color:var(--text-2)">({{.StorageInfo.Path}})</span></span> <span class="settings-value">{{.StorageInfo.Label}} <span class="mono" style="color:var(--text-2)">({{.StorageInfo.Path}})</span></span>
</div> </div>
{{if .StorageInfo.DataSizeHuman}} {{if .StorageInfo.DataSizeHuman}}
<div class="settings-row"> <div class="settings-row">
<span class="settings-label">Adatméret</span> <span class="settings-label">{{T "deploy.adatmeret"}}</span>
<span class="settings-value mono">{{.StorageInfo.DataSizeHuman}}</span> <span class="settings-value mono">{{.StorageInfo.DataSizeHuman}}</span>
</div> </div>
{{end}} {{end}}
{{if .StorageInfo.FreeHuman}} {{if .StorageInfo.FreeHuman}}
<div class="settings-row"> <div class="settings-row">
<span class="settings-label">Szabad hely</span> <span class="settings-label">{{T "deploy.szabad_hely"}}</span>
<span class="settings-value mono">{{.StorageInfo.FreeHuman}} ({{printf "%.0f" .StorageInfo.FreePercent}}% szabad)</span> <span class="settings-value mono">{{T "deploy.szabad"}}</span>
</div> </div>
{{end}} {{end}}
</div> </div>
{{if .OtherStoragePaths}} {{if .OtherStoragePaths}}
<span class="btn btn-sm btn-outline" style="margin-top:.75rem;opacity:.45;cursor:not-allowed" title="Hamarosan"> <span class="btn btn-sm btn-outline" style="margin-top:.75rem;opacity:.45;cursor:not-allowed" title="{{T "deploy.hamarosan"}}">
<svg class="ico ico-sm"><use href="#i-upload"/></svg> Mozgatás másik tárolóra <svg class="ico ico-sm"><use href="#i-upload"/></svg> {{T "deploy.mozgatas_masik_tarolora"}}
</span> </span>
{{end}} {{end}}
</div> </div>
{{end}} {{end}}
{{if .StaleData}} {{if .StaleData}}
<div class="deploy-stale-data"> <div class="deploy-stale-data">
<h4>Korábbi adatok</h4> <h4>{{T "deploy.korabbi_adatok"}}</h4>
<p class="form-hint" style="margin-bottom:1rem"> <p class="form-hint" style="margin-bottom:1rem">
Az alkalmazás adatainak másolata megtalálható egy másik tárolón is. {{T "deploy.az_alkalmazas_adatainak_masolata_megtala"}}
Ez általában áthelyezés után marad hátra.
</p> </p>
{{range .StaleData}} {{range .StaleData}}
<div class="stale-data-item"> <div class="stale-data-item">
<div class="settings-grid" style="margin-bottom:.75rem"> <div class="settings-grid" style="margin-bottom:.75rem">
<div class="settings-row"> <div class="settings-row">
<span class="settings-label">Tárhely</span> <span class="settings-label">{{T "deploy.tarhely"}}</span>
<span class="settings-value">{{.Label}} <span class="mono" style="color:var(--text-2)">({{.Path}})</span></span> <span class="settings-value">{{.Label}} <span class="mono" style="color:var(--text-2)">({{.Path}})</span></span>
</div> </div>
<div class="settings-row"> <div class="settings-row">
<span class="settings-label">Méret</span> <span class="settings-label">{{T "deploy.meret"}}</span>
<span class="settings-value mono">{{.SizeHuman}}</span> <span class="settings-value mono">{{.SizeHuman}}</span>
</div> </div>
<div class="settings-row"> <div class="settings-row">
<span class="settings-label">Mappák</span> <span class="settings-label">{{T "deploy.mappak"}}</span>
<span class="settings-value mono" style="font-size:.85rem">{{range .Mounts}}{{.}}<br>{{end}}</span> <span class="settings-value mono" style="font-size:.85rem">{{range .Mounts}}{{.}}<br>{{end}}</span>
</div> </div>
</div> </div>
<button class="btn btn-sm btn-danger" onclick="deleteStaleData('{{$.Meta.Slug}}', '{{.Path}}', this)"> <button class="btn btn-sm btn-danger" onclick="deleteStaleData('{{$.Meta.Slug}}', '{{.Path}}', this)">
Korábbi adatok törlése {{T "deploy.korabbi_adatok_torlese"}}
</button> </button>
</div> </div>
{{end}} {{end}}
@@ -109,12 +108,11 @@
{{if .AlreadyDeployed}} {{if .AlreadyDeployed}}
<div class="deploy-cross-drive"> <div class="deploy-cross-drive">
<h4>Biztonsági mentés</h4> <h4>{{T "deploy.biztonsagi_mentes"}}</h4>
<div class="cross-drive-nightly"> <div class="cross-drive-nightly">
<span class="form-hint" style="display:block;margin-top:.25rem"> <span class="form-hint" style="display:block;margin-top:.25rem">
Az alkalmazás adatbázisa és Docker kötetei automatikusan bekerülnek az éjszakai biztonsági mentésbe. {{T "deploy.az_alkalmazas_adatbazisa_es_docker"}}
<a href="/backups" style="color:var(--blue)">Mentési állapot →</a> · <a href="/stacks/{{.Stack.Name}}/backup" style="color:var(--blue)">Mentési beállítások →</a>
</span> </span>
</div> </div>
</div> </div>
@@ -122,9 +120,9 @@
{{if .HasIntegrations}} {{if .HasIntegrations}}
<div class="app-optional-config"> <div class="app-optional-config">
<h3>Integrációk</h3> <h3>{{T "deploy.integraciok"}}</h3>
<p class="config-group-desc"> <p class="config-group-desc">
Más telepített alkalmazásokkal való összekapcsolás. Az integráció automatikusan felfüggesztődik, ha bármelyik alkalmazás leáll, és újraaktiválódik indításkor. {{T "deploy.mas_telepitett_alkalmazasokkal_valo_ossz"}}
</p> </p>
{{range .Integrations}} {{range .Integrations}}
@@ -133,20 +131,20 @@
<strong>{{.Label}}</strong> <strong>{{.Label}}</strong>
<p class="config-field-help" style="margin:0">{{.Description}}</p> <p class="config-field-help" style="margin:0">{{.Description}}</p>
{{if not .TargetDeployed}} {{if not .TargetDeployed}}
<span class="tag tag-off" style="margin-top:.25rem"><span class="dot"></span>Nincs telepítve</span> <span class="tag tag-off" style="margin-top:.25rem"><span class="dot"></span>{{T "deploy.nincs_telepitve"}}</span>
{{else if not .TargetRunning}} {{else if not .TargetRunning}}
<span class="tag tag-neutral" style="margin-top:.25rem"><span class="dot"></span>Célalkalmazás leállítva</span> <span class="tag tag-neutral" style="margin-top:.25rem"><span class="dot"></span>{{T "deploy.celalkalmazas_leallitva"}}</span>
{{else if eq .Status "error"}} {{else if eq .Status "error"}}
<span class="tag tag-crit" style="margin-top:.25rem"><span class="dot"></span>Hiba</span> <span class="tag tag-crit" style="margin-top:.25rem"><span class="dot"></span>{{T "common.hiba"}}</span>
{{else if .Enabled}} {{else if .Enabled}}
<span class="tag tag-run" style="margin-top:.25rem"><span class="dot"></span>Aktív</span> <span class="tag tag-run" style="margin-top:.25rem"><span class="dot"></span>{{T "deploy.aktiv"}}</span>
{{end}} {{end}}
</div> </div>
<label class="toggle"> <label class="toggle">
<input type="checkbox" <input type="checkbox"
{{if .Enabled}}checked{{end}} {{if .Enabled}}checked{{end}}
{{if not .TargetDeployed}}disabled title="A célalkalmazás nincs telepítve"{{end}} {{if not .TargetDeployed}}disabled title="{{T "deploy.a_celalkalmazas_nincs_telepitve"}}"{{end}}
{{if and .TargetDeployed (not .TargetRunning)}}disabled title="A célalkalmazás nem fut"{{end}} {{if and .TargetDeployed (not .TargetRunning)}}disabled title="{{T "deploy.a_celalkalmazas_nem_fut"}}"{{end}}
onchange="toggleIntegration('{{$.Stack.Name}}', '{{.Target}}', this.checked, this)"> onchange="toggleIntegration('{{$.Stack.Name}}', '{{.Target}}', this.checked, this)">
<span class="toggle-label"></span> <span class="toggle-label"></span>
</label> </label>
@@ -166,14 +164,14 @@
var data = await resp.json(); var data = await resp.json();
if (!data.ok) { if (!data.ok) {
checkbox.checked = !enable; checkbox.checked = !enable;
showAlert(data.error || 'Hiba történt'); showAlert(data.error || '{{T "common.hiba_tortent"}}');
} else { } else {
setTimeout(function(){ location.reload(); }, 500); setTimeout(function(){ location.reload(); }, 500);
return; return;
} }
} catch(err) { } catch(err) {
checkbox.checked = !enable; checkbox.checked = !enable;
showAlert('Hálózati hiba'); showAlert('{{T "deploy.halozati_hiba"}}');
} }
checkbox.disabled = false; checkbox.disabled = false;
} }
@@ -182,24 +180,24 @@
{{if .GeoGlobalEnabled}} {{if .GeoGlobalEnabled}}
<div class="app-optional-config"> <div class="app-optional-config">
<h3>Földrajzi korlátozás</h3> <h3>{{T "deploy.foldrajzi_korlatozas"}}</h3>
<p class="config-group-desc"> <p class="config-group-desc">
Az alkalmazás egyéni országkorlátozás nélkül a globális beállítást követi. {{T "deploy.az_alkalmazas_egyeni_orszagkorlatozas_ne"}}
</p> </p>
<label class="toggle" style="margin-bottom:1rem"> <label class="toggle" style="margin-bottom:1rem">
<input type="checkbox" id="app-geo-override-toggle" <input type="checkbox" id="app-geo-override-toggle"
{{if .GeoAppOverride}}checked{{end}} {{if .GeoAppOverride}}checked{{end}}
onchange="toggleAppGeoOverride(this.checked)"> onchange="toggleAppGeoOverride(this.checked)">
<span class="toggle-label">Egyéni országkorlátozás</span> <span class="toggle-label">{{T "deploy.egyeni_orszagkorlatozas"}}</span>
</label> </label>
<div id="app-geo-override-details" {{if not .GeoAppOverride}}style="display:none"{{end}}> <div id="app-geo-override-details" {{if not .GeoAppOverride}}style="display:none"{{end}}>
<div class="form-group"> <div class="form-group">
<label>Engedélyezett országok ehhez az alkalmazáshoz</label> <label>{{T "deploy.engedelyezett_orszagok_ehhez_az_alkalmaz"}}</label>
<div class="geo-country-selector"> <div class="geo-country-selector">
<input type="text" id="app-geo-search" class="form-control config-input" <input type="text" id="app-geo-search" class="form-control config-input"
placeholder="Ország keresése..." autocomplete="off" placeholder="{{T "deploy.orszag_keresese"}}" autocomplete="off"
oninput="filterAppGeoCountries(this.value)" oninput="filterAppGeoCountries(this.value)"
onfocus="showAppGeoList()" onfocus="showAppGeoList()"
onblur="setTimeout(function(){hideAppGeoList()},200)"> onblur="setTimeout(function(){hideAppGeoList()},200)">
@@ -208,7 +206,7 @@
<div class="geo-selected-tags" id="app-geo-tags"></div> <div class="geo-selected-tags" id="app-geo-tags"></div>
</div> </div>
<div class="config-actions"> <div class="config-actions">
<button class="btn btn-primary" onclick="saveAppGeoOverride()">Mentés</button> <button class="btn btn-primary" onclick="saveAppGeoOverride()">{{T "deploy.mentes"}}</button>
<span id="app-geo-status" class="config-save-status"></span> <span id="app-geo-status" class="config-save-status"></span>
</div> </div>
</div> </div>
@@ -255,7 +253,7 @@
+ esc(c.name) + ' <small>(' + c.code + ')</small></div>'; + esc(c.name) + ' <small>(' + c.code + ')</small></div>';
count++; count++;
} }
list.innerHTML = html || '<div class="geo-country-option" style="opacity:.5">Nincs találat</div>'; list.innerHTML = html || '<div class="geo-country-option" style="opacity:.5">{{T "deploy.nincs_talalat"}}</div>';
list.style.display = (count > 0 || q) ? '' : 'none'; list.style.display = (count > 0 || q) ? '' : 'none';
}; };
@@ -296,12 +294,12 @@
}) })
.then(function(r){return r.json()}) .then(function(r){return r.json()})
.then(function(d){ .then(function(d){
status.textContent = d.ok ? (d.message || 'Mentve') : (d.error || 'Hiba'); status.textContent = d.ok ? (d.message || '{{T "deploy.mentve"}}') : (d.error || '{{T "common.hiba"}}');
status.className = 'config-save-status ' + (d.ok ? 'config-save-ok' : 'config-save-err'); status.className = 'config-save-status ' + (d.ok ? 'config-save-ok' : 'config-save-err');
setTimeout(function(){ status.textContent=''; }, 5000); setTimeout(function(){ status.textContent=''; }, 5000);
}) })
.catch(function(){ .catch(function(){
status.textContent = 'Hálózati hiba'; status.textContent = '{{T "deploy.halozati_hiba"}}';
status.className = 'config-save-status config-save-err'; status.className = 'config-save-status config-save-err';
}); });
}; };
@@ -317,7 +315,7 @@
{{if .HasOptionalConfig}} {{if .HasOptionalConfig}}
<div class="app-optional-config"> <div class="app-optional-config">
<h3>Opcionális beállítások</h3> <h3>{{T "deploy.opcionalis_beallitasok"}}</h3>
{{range .OptionalConfig}} {{range .OptionalConfig}}
<div class="config-group"> <div class="config-group">
<h4>{{.Group}}</h4> <h4>{{.Group}}</h4>
@@ -328,7 +326,7 @@
<div class="config-field"> <div class="config-field">
<label for="opt-{{.EnvVar}}">{{.Label}}</label> <label for="opt-{{.EnvVar}}">{{.Label}}</label>
{{if .HelpText}}<p class="config-field-help">{{.HelpText}}</p>{{end}} {{if .HelpText}}<p class="config-field-help">{{.HelpText}}</p>{{end}}
{{if .HelpURL}}<p class="config-field-help"><a href="{{.HelpURL}}" target="_blank">Regisztrációs útmutató ↗</a></p>{{end}} {{if .HelpURL}}<p class="config-field-help"><a href="{{.HelpURL}}" target="_blank">{{T "deploy.regisztracios_utmutato"}}</a></p>{{end}}
<input type="{{if eq .Type "secret_input"}}password{{else}}text{{end}}" <input type="{{if eq .Type "secret_input"}}password{{else}}text{{end}}"
id="opt-{{.EnvVar}}" id="opt-{{.EnvVar}}"
name="{{.EnvVar}}" name="{{.EnvVar}}"
@@ -344,7 +342,7 @@
<div class="config-actions"> <div class="config-actions">
<button class="btn btn-primary" id="save-optional-config" onclick="saveOptionalConfig('{{.Stack.Name}}')"> <button class="btn btn-primary" id="save-optional-config" onclick="saveOptionalConfig('{{.Stack.Name}}')">
Mentés {{T "deploy.mentes"}}
</button> </button>
<span id="config-save-status" class="config-save-status"></span> <span id="config-save-status" class="config-save-status"></span>
</div> </div>
@@ -362,7 +360,7 @@
}); });
btn.disabled = true; btn.disabled = true;
btn.textContent = 'Mentés...'; btn.textContent = '{{T "deploy.mentes_2"}}';
status.textContent = ''; status.textContent = '';
status.className = 'config-save-status'; status.className = 'config-save-status';
@@ -375,19 +373,19 @@
var data = await resp.json(); var data = await resp.json();
if (data.ok) { if (data.ok) {
status.textContent = (data.message || 'Mentve'); status.textContent = (data.message || '{{T "deploy.mentve"}}');
status.className = 'config-save-status config-save-ok'; status.className = 'config-save-status config-save-ok';
} else { } else {
status.textContent = (data.error || 'Hiba'); status.textContent = (data.error || '{{T "common.hiba"}}');
status.className = 'config-save-status config-save-err'; status.className = 'config-save-status config-save-err';
} }
} catch(err) { } catch(err) {
status.textContent = 'Hálózati hiba'; status.textContent = '{{T "deploy.halozati_hiba"}}';
status.className = 'config-save-status config-save-err'; status.className = 'config-save-status config-save-err';
} }
btn.disabled = false; btn.disabled = false;
btn.textContent = 'Mentés'; btn.textContent = '{{T "deploy.mentes"}}';
setTimeout(function() { status.textContent = ''; }, 5000); setTimeout(function() { status.textContent = ''; }, 5000);
} }
@@ -396,26 +394,24 @@
{{if .AppEmailSupported}} {{if .AppEmailSupported}}
<div class="app-optional-config"> <div class="app-optional-config">
<h3>Email-küldés</h3> <h3>{{T "deploy.email_kuldes"}}</h3>
<p class="config-group-desc"> <p class="config-group-desc">
Ez az alkalmazás tud emailt küldeni (pl. jelszó-visszaállítás, meghívók) a Felhom-on keresztül, {{T "deploy.ez_az_alkalmazas_tud_emailt"}}
külön szolgáltató beállítása nélkül. A feladó címe: <strong>{{.AppEmailFromAddress}}</strong>.
</p> </p>
{{if not .AppEmailGlobalOn}} {{if not .AppEmailGlobalOn}}
<p class="alert alert-warning" style="margin-bottom:1rem"> <p class="alert alert-warning" style="margin-bottom:1rem">
Az alkalmazás-email jelenleg ki van kapcsolva globálisan. Kapcsold be a {{T "deploy.az_alkalmazas_email_jelenleg_ki"}}
<a href="/settings">Beállítások</a> oldalon, hogy itt is működjön.
</p> </p>
{{end}} {{end}}
<form method="POST" action="/stacks/{{.Stack.Name}}/app-email"> <form method="POST" action="/stacks/{{.Stack.Name}}/app-email">
{{$.CSRFField}} {{$.CSRFField}}
<label style="display:flex;align-items:center;gap:.5rem;margin-bottom:1rem"> <label style="display:flex;align-items:center;gap:.5rem;margin-bottom:1rem">
<input type="checkbox" name="app_email_enabled" value="on" {{if .AppEmailAppOn}}checked{{end}}> <input type="checkbox" name="app_email_enabled" value="on" {{if .AppEmailAppOn}}checked{{end}}>
Email-küldés engedélyezése ennél az alkalmazásnál {{T "deploy.email_kuldes_engedelyezese_ennel_az"}}
</label> </label>
<div class="config-actions"> <div class="config-actions">
<button class="btn btn-primary" type="submit">Mentés</button> <button class="btn btn-primary" type="submit">{{T "deploy.mentes"}}</button>
<span class="config-group-desc" style="margin-left:.5rem">A mentés újraindítja az alkalmazást.</span> <span class="config-group-desc" style="margin-left:.5rem">{{T "deploy.a_mentes_ujrainditja_az_alkalmazast"}}</span>
</div> </div>
</form> </form>
</div> </div>
@@ -427,25 +423,24 @@
<div class="memory-summary{{if .Blocked}} memory-blocked{{end}}"> <div class="memory-summary{{if .Blocked}} memory-blocked{{end}}">
{{if .Blocked}} {{if .Blocked}}
<div class="alert alert-error" style="margin-bottom:0"> <div class="alert alert-error" style="margin-bottom:0">
Nincs elég memória! Foglalás telepítés után: {{.AfterMB}} MB / {{.UsableMB}} MB {{T "deploy.nincs_eleg_memoria_foglalas_telepites"}}
</div> </div>
{{else}} {{else}}
<div class="memory-summary-header"> <div class="memory-summary-header">
<span class="memory-summary-label">Memória</span> <span class="memory-summary-label">{{T "deploy.memoria"}}</span>
<span class="memory-summary-value">{{.AfterMB}} MB / {{.UsableMB}} MB ({{.Percent}}%)</span> <span class="memory-summary-value">{{.AfterMB}} MB / {{.UsableMB}} MB ({{.Percent}}%)</span>
</div> </div>
<div class="meter-track meter-track-stacked"> <div class="meter-track meter-track-stacked">
<div class="memory-bar-segment memory-bar-committed" style="width:{{.UsedPercent}}%" title="Jelenlegi használat: {{.UsedMB}} MB"></div> <div class="memory-bar-segment memory-bar-committed" style="width:{{.UsedPercent}}%" title="{{T "deploy.jelenlegi_hasznalat_mb"}}"></div>
<div class="memory-bar-segment memory-bar-new" style="width:{{subtract .Percent .UsedPercent}}%" title="{{$.Meta.DisplayName}}: +{{.NewRequestMB}} MB"></div> <div class="memory-bar-segment memory-bar-new" style="width:{{subtract .Percent .UsedPercent}}%" title="{{$.Meta.DisplayName}}: +{{.NewRequestMB}} MB"></div>
</div> </div>
<div class="memory-bar-legend"> <div class="memory-bar-legend">
<span class="memory-legend-item"><span class="memory-legend-dot memory-legend-committed"></span>Jelenlegi használat ({{.UsedMB}} MB)</span> <span class="memory-legend-item"><span class="memory-legend-dot memory-legend-committed"></span>{{T "deploy.jelenlegi_hasznalat_mb_2"}}</span>
<span class="memory-legend-item"><span class="memory-legend-dot memory-legend-new"></span>{{$.Meta.DisplayName}} (+{{.NewRequestMB}} MB)</span> <span class="memory-legend-item"><span class="memory-legend-dot memory-legend-new"></span>{{$.Meta.DisplayName}} (+{{.NewRequestMB}} MB)</span>
</div> </div>
{{if .OvercommitWarn}} {{if .OvercommitWarn}}
<div class="alert alert-warning" style="margin-top:0.5rem;margin-bottom:0"> <div class="alert alert-warning" style="margin-top:0.5rem;margin-bottom:0">
Az alkalmazások csúcsterhelése meghaladhatja a rendelkezésre álló memóriát. {{T "deploy.az_alkalmazasok_csucsterhelese_meghaladh"}}
Normál használat mellett ez nem okoz problémát.
</div> </div>
{{end}} {{end}}
{{end}} {{end}}
@@ -461,10 +456,10 @@
{{if and (not .AlreadyDeployed) .RestoreHasRecord}} {{if and (not .AlreadyDeployed) .RestoreHasRecord}}
<div class="alert alert-info" style="margin-bottom:1rem"> <div class="alert alert-info" style="margin-bottom:1rem">
<svg class="ico ico-sm"><use href="#i-info"/></svg> <svg class="ico ico-sm"><use href="#i-info"/></svg>
Ennek az alkalmazásnak van korábbi mentése, ezért az alábbi mezőket a saját mentése alapján töltöttük ki. {{T "deploy.ennek_az_alkalmazasnak_van_korabbi"}}
{{if .RestoreRecordedAddress}}Korábbi webcím: <strong>{{.RestoreRecordedAddress}}</strong>.{{end}} {{if .RestoreRecordedAddress}}{{T "deploy.korabbi_webcim"}}{{end}}
{{if .RestoreRecordedDrive}}Az adatai itt voltak: <strong>{{.RestoreRecordedDrive}}</strong>.{{end}} {{if .RestoreRecordedDrive}}{{T "deploy.az_adatai_itt_voltak"}}{{end}}
Ha most máshová szeretnéd telepíteni, nyugodtan átírhatod — a visszaállítás előtt jelezni fogjuk az eltérést. {{T "deploy.ha_most_mashova_szeretned_telepiteni"}}
</div> </div>
{{end}} {{end}}
{{/* R-351 / Part 1 — WHERE THE DATA WILL LIVE, before the button is pressed. Measured {{/* R-351 / Part 1 — WHERE THE DATA WILL LIVE, before the button is pressed. Measured
@@ -474,27 +469,24 @@
{{if not .AlreadyDeployed}} {{if not .AlreadyDeployed}}
<div class="form-hint" style="margin-bottom:1rem"> <div class="form-hint" style="margin-bottom:1rem">
{{if .RestoreRecordedDeclaresPath}} {{if .RestoreRecordedDeclaresPath}}
Az alkalmazás adatai az alább kiválasztott adatmeghajtóra kerülnek. {{T "deploy.az_alkalmazas_adatai_az_alabb"}}
{{else}} {{else}}
<strong>Hol lesznek az adatok:</strong> ennél az alkalmazásnál nincs külön adatmeghajtó-választás, {{T "deploy.hol_lesznek_az_adatok_ennel"}}
ezért az adatai a rendszermeghajtóra kerülnek (<code>{{.SystemDataPath}}</code>). A mentései így is elkészülnek.
{{end}} {{end}}
</div> </div>
{{end}} {{end}}
{{if .DockerBelowReserve}} {{if .DockerBelowReserve}}
<div class="alert alert-warning" style="margin-bottom:1rem"> <div class="alert alert-warning" style="margin-bottom:1rem">
<svg class="ico ico-sm"><use href="#i-triangle-alert"/></svg> Nincs elég szabad tárhely a telepítéshez. Jelenleg {{.DockerFreeHuman}} szabad, és a rendszer <svg class="ico ico-sm"><use href="#i-triangle-alert"/></svg> {{T "deploy.nincs_eleg_szabad_tarhely_a"}}
{{.DockerReserveHuman}} tartalékot tart fenn az alapszolgáltatások (vezérlő, proxy) védelmében.
A telepítés ezért átmenetileg le van tiltva — szabadítson fel helyet, vagy bővítse a tárhelyet.
</div> </div>
{{end}} {{end}}
{{if .AutoFields}} {{if .AutoFields}}
<div class="form-section"> <div class="form-section">
<h4>Automatikusan generált értékek</h4> <h4>{{T "deploy.automatikusan_generalt_ertekek"}}</h4>
{{if .AlreadyDeployed}} {{if .AlreadyDeployed}}
<p class="form-section-desc">Ezek az értékek automatikusan jöttek létre a telepítéskor.</p> <p class="form-section-desc">{{T "deploy.ezek_az_ertekek_automatikusan_jottek"}}</p>
{{else}} {{else}}
<p class="form-section-desc">Ezek az értékek a telepítéssel együtt mentésre kerülnek. Jegyezze fel a szükséges jelszavakat!</p> <p class="form-section-desc">{{T "deploy.ezek_az_ertekek_a_telepitessel"}}</p>
{{end}} {{end}}
{{$autoValues := .AutoFieldValues}} {{$autoValues := .AutoFieldValues}}
{{$stackName := .Stack.Name}} {{$stackName := .Stack.Name}}
@@ -502,7 +494,7 @@
{{range .AutoFields}} {{range .AutoFields}}
{{$val := index $autoValues .EnvVar}} {{$val := index $autoValues .EnvVar}}
<div class="form-group form-group-auto"> <div class="form-group form-group-auto">
<label>{{.Label}} {{if eq .Type "secret"}}<span class="auto-generated-badge"><svg class="ico ico-sm"><use href="#i-check"/></svg> Automatikusan generálva</span>{{end}}</label> <label>{{.Label}} {{if eq .Type "secret"}}<span class="auto-generated-badge"><svg class="ico ico-sm"><use href="#i-check"/></svg> {{T "deploy.automatikusan_generalva"}}</span>{{end}}</label>
{{if $val}} {{if $val}}
{{if eq .Type "secret"}} {{if eq .Type "secret"}}
{{if $isDeployed}} {{if $isDeployed}}
@@ -512,12 +504,12 @@
submits, so the saved value is the one the customer was shown (README §318). --> submits, so the saved value is the one the customer was shown (README §318). -->
<div class="input-with-button"> <div class="input-with-button">
<input type="password" id="auto-field-{{.EnvVar}}" class="form-control" value="" placeholder="••••••••••••" readonly> <input type="password" id="auto-field-{{.EnvVar}}" class="form-control" value="" placeholder="••••••••••••" readonly>
<button type="button" class="btn btn-sm btn-outline" onclick="revealAutoField('{{$stackName}}','{{.EnvVar}}', this)">Megjelenítés</button> <button type="button" class="btn btn-sm btn-outline" onclick="revealAutoField('{{$stackName}}','{{.EnvVar}}', this)">{{T "deploy.megjelenites"}}</button>
</div> </div>
{{else}} {{else}}
<div class="input-with-button"> <div class="input-with-button">
<input type="password" id="auto-field-{{.EnvVar}}" class="form-control" value="{{$val}}" readonly> <input type="password" id="auto-field-{{.EnvVar}}" class="form-control" value="{{$val}}" readonly>
<button type="button" class="btn btn-sm btn-outline" onclick="toggleAutoField('auto-field-{{.EnvVar}}', this)">Megjelenítés</button> <button type="button" class="btn btn-sm btn-outline" onclick="toggleAutoField('auto-field-{{.EnvVar}}', this)">{{T "deploy.megjelenites"}}</button>
</div> </div>
{{end}} {{end}}
{{else}} {{else}}
@@ -534,13 +526,13 @@
{{if .UserFields}} {{if .UserFields}}
<div class="form-section"> <div class="form-section">
<h4>Beállítások</h4> <h4>{{T "deploy.beallitasok"}}</h4>
{{range .UserFields}} {{range .UserFields}}
<div class="form-group"> <div class="form-group">
<label for="field-{{.EnvVar}}"> <label for="field-{{.EnvVar}}">
{{.Label}} {{.Label}}
{{if or .Required (eq .Type "password")}}<span class="required">*</span>{{end}} {{if or .Required (eq .Type "password")}}<span class="required">*</span>{{end}}
{{if .LockedAfterDeploy}}<span class="locked-hint">telepítés után nem módosítható</span>{{end}} {{if .LockedAfterDeploy}}<span class="locked-hint">{{T "deploy.telepites_utan_nem_modosithato"}}</span>{{end}}
</label> </label>
{{if eq .Type "subdomain"}} {{if eq .Type "subdomain"}}
@@ -550,7 +542,7 @@
{{/* R-351: a reinstall offers the address the customer's own backup recorded, {{/* R-351: a reinstall offers the address the customer's own backup recorded,
ahead of the catalog default. Editable — a domain can legitimately change. */}} ahead of the catalog default. Editable — a domain can legitimately change. */}}
value="{{if and $.AlreadyDeployed $.DeployedFieldValues}}{{index $.DeployedFieldValues .EnvVar}}{{else if index $.RestoreFieldValues .EnvVar}}{{index $.RestoreFieldValues .EnvVar}}{{else}}{{.Default}}{{end}}" value="{{if and $.AlreadyDeployed $.DeployedFieldValues}}{{index $.DeployedFieldValues .EnvVar}}{{else if index $.RestoreFieldValues .EnvVar}}{{index $.RestoreFieldValues .EnvVar}}{{else}}{{.Default}}{{end}}"
placeholder="aldomain" placeholder="{{T "deploy.aldomain_placeholder"}}"
pattern="[a-z0-9]([a-z0-9-]*[a-z0-9])?" pattern="[a-z0-9]([a-z0-9-]*[a-z0-9])?"
required required
{{if $.AlreadyDeployed}}disabled{{end}} {{if $.AlreadyDeployed}}disabled{{end}}
@@ -558,7 +550,7 @@
<span class="subdomain-suffix">.{{$.Domain}}</span> <span class="subdomain-suffix">.{{$.Domain}}</span>
</div> </div>
{{if not $.AlreadyDeployed}} {{if not $.AlreadyDeployed}}
<span class="form-hint">Az aldomain telepítés után nem módosítható. Megváltoztatáshoz az alkalmazás eltávolítása és újratelepítése szükséges (minden adat törlődik).</span> <span class="form-hint">{{T "deploy.az_aldomain_telepites_utan_nem"}}</span>
{{end}} {{end}}
{{else if eq .Type "select"}} {{else if eq .Type "select"}}
<select id="field-{{.EnvVar}}" name="{{.EnvVar}}" class="form-control" <select id="field-{{.EnvVar}}" name="{{.EnvVar}}" class="form-control"
@@ -577,18 +569,18 @@
{{if $.AlreadyDeployed}}disabled{{end}}> {{if $.AlreadyDeployed}}disabled{{end}}>
<button type="button" class="btn btn-sm btn-outline pw-toggle-btn" <button type="button" class="btn btn-sm btn-outline pw-toggle-btn"
onclick="togglePasswordField('field-{{.EnvVar}}', 'field-confirm-{{.EnvVar}}', this)" onclick="togglePasswordField('field-{{.EnvVar}}', 'field-confirm-{{.EnvVar}}', this)"
title="Megjelenítés">&#128065;</button> title="{{T "deploy.megjelenites"}}">&#128065;</button>
{{if not $.AlreadyDeployed}} {{if not $.AlreadyDeployed}}
<button type="button" class="btn btn-sm btn-outline" <button type="button" class="btn btn-sm btn-outline"
onclick="generatePassword('field-{{.EnvVar}}', 'field-confirm-{{.EnvVar}}')">Generálás</button> onclick="generatePassword('field-{{.EnvVar}}', 'field-confirm-{{.EnvVar}}')">{{T "deploy.generalas"}}</button>
{{end}} {{end}}
</div> </div>
{{if $.AlreadyDeployed}} {{if $.AlreadyDeployed}}
<span class="form-hint">Telepítéskor beállított kezdeti jelszó — ha az alkalmazásban megváltoztattad, az itt nem frissül.</span> <span class="form-hint">{{T "deploy.telepiteskor_beallitott_kezdeti_jelszo_h"}}</span>
{{else}} {{else}}
<div class="input-with-button" style="margin-top:.25rem"> <div class="input-with-button" style="margin-top:.25rem">
<input type="password" id="field-confirm-{{.EnvVar}}" <input type="password" id="field-confirm-{{.EnvVar}}"
class="form-control" placeholder="Jelszó megerősítése" class="form-control" placeholder="{{T "deploy.jelszo_megerositese"}}"
data-confirm-for="field-{{.EnvVar}}"> data-confirm-for="field-{{.EnvVar}}">
</div> </div>
{{end}} {{end}}
@@ -596,7 +588,7 @@
<label class="toggle"> <label class="toggle">
<input type="checkbox" id="field-{{.EnvVar}}" name="{{.EnvVar}}" value="true" <input type="checkbox" id="field-{{.EnvVar}}" name="{{.EnvVar}}" value="true"
{{if $.AlreadyDeployed}}disabled{{end}}> {{if $.AlreadyDeployed}}disabled{{end}}>
<span class="toggle-label">Igen</span> <span class="toggle-label">{{T "deploy.igen"}}</span>
</label> </label>
{{else if eq .Type "path"}} {{else if eq .Type "path"}}
{{if $.StoragePaths}} {{if $.StoragePaths}}
@@ -611,19 +603,18 @@
restoring into a different drive is the mismatch the restore then has restoring into a different drive is the mismatch the restore then has
to stop and name. The customer can still pick another. */}} to stop and name. The customer can still pick another. */}}
{{if $.AlreadyDeployed}}{{if eq .Path $.CurrentHDDPath}}selected{{end}}{{else if $.RestorePrefillHDDPath}}{{if and (eq .Path $.RestorePrefillHDDPath) (not .NotAllowed)}}selected{{end}}{{else if and .IsDefault (not .NotAllowed)}}selected{{end}}> {{if $.AlreadyDeployed}}{{if eq .Path $.CurrentHDDPath}}selected{{end}}{{else if $.RestorePrefillHDDPath}}{{if and (eq .Path $.RestorePrefillHDDPath) (not .NotAllowed)}}selected{{end}}{{else if and .IsDefault (not .NotAllowed)}}selected{{end}}>
{{.Label}} — {{.FreeHuman}} szabad{{if .NotAllowed}} ({{.NotAllowedNote}}){{else if .IsDefault}} (alapértelmezett){{end}} {{T "deploy.szabad_2"}}{{if .NotAllowed}} ({{.NotAllowedNote}}){{else if .IsDefault}} {{T "deploy.alapertelmezett"}}{{end}}
</option> </option>
{{end}} {{end}}
{{if $.CurrentHDDPathMissing}} {{if $.CurrentHDDPathMissing}}
<option value="{{$.CurrentHDDPath}}" selected disabled>{{$.CurrentHDDPath}} (nem elérhető)</option> <option value="{{$.CurrentHDDPath}}" selected disabled>{{T "deploy.nem_elerheto"}}</option>
{{end}} {{end}}
</select> </select>
<div id="storage-space-warn" class="form-hint" style="color:var(--warn);display:none"> <div id="storage-space-warn" class="form-hint" style="color:var(--warn);display:none">
<svg class="ico ico-sm"><use href="#i-triangle-alert"/></svg> A kiválasztott tárhely majdnem megtelt. <svg class="ico ico-sm"><use href="#i-triangle-alert"/></svg> {{T "deploy.a_kivalasztott_tarhely_majdnem_megtelt"}}
</div> </div>
<div class="form-hint" style="margin-top:.4rem;opacity:.8"> <div class="form-hint" style="margin-top:.4rem;opacity:.8">
<svg class="ico ico-sm"><use href="#i-info"/></svg> A kiválasztott meghajtón az alkalmazás <strong>fájljai</strong> (média, dokumentumok) tárolódnak. <svg class="ico ico-sm"><use href="#i-info"/></svg> {{T "deploy.a_kivalasztott_meghajton_az_alkalmazas"}}
Az <strong>adatbázis a gyors belső SSD-n</strong> fut — és az alkalmazással együtt készül róla biztonsági mentés.
</div> </div>
{{else}} {{else}}
<input type="text" id="field-{{.EnvVar}}" name="{{.EnvVar}}" <input type="text" id="field-{{.EnvVar}}" name="{{.EnvVar}}"
@@ -631,7 +622,7 @@
placeholder="{{.Placeholder}}" placeholder="{{.Placeholder}}"
{{if .Required}}required{{end}} {{if .Required}}required{{end}}
{{if $.AlreadyDeployed}}disabled{{end}}> {{if $.AlreadyDeployed}}disabled{{end}}>
<span class="form-hint" style="color:var(--warn)">Nincs regisztrált adattároló — adja meg kézzel az útvonalat</span> <span class="form-hint" style="color:var(--warn)">{{T "deploy.nincs_regisztralt_adattarolo_adja_meg"}}</span>
{{end}} {{end}}
{{else}} {{else}}
<input type="text" id="field-{{.EnvVar}}" name="{{.EnvVar}}" <input type="text" id="field-{{.EnvVar}}" name="{{.EnvVar}}"
@@ -651,26 +642,26 @@
{{if not .AlreadyDeployed}} {{if not .AlreadyDeployed}}
<div class="deploy-actions"> <div class="deploy-actions">
<button type="submit" class="btn btn-primary btn-lg"{{if .DockerBelowReserve}} disabled title="Nincs elég szabad tárhely"{{else if and .MemoryInfo (index .MemoryInfo "Blocked")}} disabled title="Nincs elég memória"{{end}}>Telepítés indítása</button> <button type="submit" class="btn btn-primary btn-lg"{{if .DockerBelowReserve}} disabled title="{{T "deploy.nincs_eleg_szabad_tarhely"}}"{{else if and .MemoryInfo (index .MemoryInfo "Blocked")}} disabled title="{{T "deploy.nincs_eleg_memoria"}}"{{end}}>{{T "deploy.telepites_inditasa"}}</button>
<a href="/stacks" class="btn btn-outline">Mégsem</a> <a href="/stacks" class="btn btn-outline">{{T "common.megsem"}}</a>
</div> </div>
{{end}} {{end}}
</form> </form>
<div id="deploy-progress" class="deploy-progress" style="display:none"> <div id="deploy-progress" class="deploy-progress" style="display:none">
<h3>Telepítés folyamatban...</h3> <h3>{{T "deploy.telepites_folyamatban"}}</h3>
<div class="deploy-steps"> <div class="deploy-steps">
<div class="deploy-step active" id="step-config"> <div class="deploy-step active" id="step-config">
<span class="step-icon"><svg class="ico"><use href="#i-rotate-cw"/></svg></span> <span class="step-icon"><svg class="ico"><use href="#i-rotate-cw"/></svg></span>
<span class="step-text">Konfiguráció mentése...</span> <span class="step-text">{{T "deploy.konfiguracio_mentese"}}</span>
</div> </div>
<div class="deploy-step" id="step-containers"> <div class="deploy-step" id="step-containers">
<span class="step-icon"><svg class="ico"><use href="#i-rotate-cw"/></svg></span> <span class="step-icon"><svg class="ico"><use href="#i-rotate-cw"/></svg></span>
<span class="step-text">Konténer(ek) indítása...</span> <span class="step-text">{{T "deploy.kontener_ek_inditasa"}}</span>
</div> </div>
<div class="deploy-step" id="step-health"> <div class="deploy-step" id="step-health">
<span class="step-icon"><svg class="ico"><use href="#i-rotate-cw"/></svg></span> <span class="step-icon"><svg class="ico"><use href="#i-rotate-cw"/></svg></span>
<span class="step-text">Alkalmazás inicializálása...</span> <span class="step-text">{{T "deploy.alkalmazas_inicializalasa"}}</span>
</div> </div>
</div> </div>
<div id="deploy-warning" class="alert alert-warning" style="display:none"></div> <div id="deploy-warning" class="alert alert-warning" style="display:none"></div>
@@ -701,13 +692,13 @@ function buildPostDeployCard(stackName) {
if (subdomain && postDeployInfo.domain) { if (subdomain && postDeployInfo.domain) {
var appURL = 'https://' + subdomain + '.' + postDeployInfo.domain; var appURL = 'https://' + subdomain + '.' + postDeployInfo.domain;
html += '<div style="text-align:center;margin:1.5rem 0">' + html += '<div style="text-align:center;margin:1.5rem 0">' +
'<a href="' + appURL + '" target="_blank" class="btn btn-primary btn-lg">Alkalmazás megnyitása ↗</a>' + '<a href="' + appURL + '" target="_blank" class="btn btn-primary btn-lg">{{T "deploy.alkalmazas_megnyitasa"}}</a>' +
'</div>'; '</div>';
} }
// First steps — with dynamic credential hint // First steps — with dynamic credential hint
if (postDeployInfo.firstSteps && postDeployInfo.firstSteps.length > 0) { if (postDeployInfo.firstSteps && postDeployInfo.firstSteps.length > 0) {
html += '<div class="app-info-card" style="margin-top:1rem"><h4>Első lépések</h4><ol class="app-info-list">'; html += '<div class="app-info-card" style="margin-top:1rem"><h4>{{T "deploy.elso_lepesek"}}</h4><ol class="app-info-list">';
for (var i = 0; i < postDeployInfo.firstSteps.length; i++) { for (var i = 0; i < postDeployInfo.firstSteps.length; i++) {
var step = postDeployInfo.firstSteps[i].replace(/DOMAIN/g, postDeployInfo.domain); var step = postDeployInfo.firstSteps[i].replace(/DOMAIN/g, postDeployInfo.domain);
html += '<li>' + step + '</li>'; html += '<li>' + step + '</li>';
@@ -742,8 +733,8 @@ function buildPostDeployCard(stackName) {
credRows += '<tr><td style="padding:.25rem .75rem .25rem 0;color:var(--text-3);white-space:nowrap">' + f.label + '</td>' + credRows += '<tr><td style="padding:.25rem .75rem .25rem 0;color:var(--text-3);white-space:nowrap">' + f.label + '</td>' +
'<td style="padding:.25rem 0;display:flex;align-items:center;gap:.5rem">' + '<td style="padding:.25rem 0;display:flex;align-items:center;gap:.5rem">' +
'<code id="' + credId + '" style="font-family:var(--font-data);letter-spacing:1px">••••••••••••</code>' + '<code id="' + credId + '" style="font-family:var(--font-data);letter-spacing:1px">••••••••••••</code>' +
'<button type="button" class="btn btn-sm btn-outline" onclick="togglePostDeployCred(this,\'' + credId + '\',\'' + val.replace(/'/g, "\\'") + '\')" style="padding:.15rem .4rem;font-size:.75rem">Megjelenítés</button>' + '<button type="button" class="btn btn-sm btn-outline" onclick="togglePostDeployCred(this,\'' + credId + '\',\'' + val.replace(/'/g, "\\'") + '\')" style="padding:.15rem .4rem;font-size:.75rem">{{T "deploy.megjelenites"}}</button>' +
'<button type="button" class="btn btn-sm btn-outline" onclick="copyPostDeployCred(this,\'' + val.replace(/'/g, "\\'") + '\')" style="padding:.15rem .4rem;font-size:.75rem">Másolás</button>' + '<button type="button" class="btn btn-sm btn-outline" onclick="copyPostDeployCred(this,\'' + val.replace(/'/g, "\\'") + '\')" style="padding:.15rem .4rem;font-size:.75rem">{{T "deploy.masolas"}}</button>' +
'</td></tr>'; '</td></tr>';
} else { } else {
credRows += '<tr><td style="padding:.25rem .75rem .25rem 0;color:var(--text-3);white-space:nowrap">' + f.label + '</td>' + credRows += '<tr><td style="padding:.25rem .75rem .25rem 0;color:var(--text-3);white-space:nowrap">' + f.label + '</td>' +
@@ -752,23 +743,23 @@ function buildPostDeployCard(stackName) {
} }
} }
if (credRows) { if (credRows) {
html += '<div class="app-info-card" style="margin-top:1rem"><h4>Bejelentkezés</h4>' + html += '<div class="app-info-card" style="margin-top:1rem"><h4>{{T "deploy.bejelentkezes"}}</h4>' +
'<table style="margin:.5rem 0">' + credRows + '</table></div>'; '<table style="margin:.5rem 0">' + credRows + '</table></div>';
} else if (postDeployInfo.defaultCreds) { } else if (postDeployInfo.defaultCreds) {
var creds = postDeployInfo.defaultCreds.replace(/DOMAIN/g, postDeployInfo.domain); var creds = postDeployInfo.defaultCreds.replace(/DOMAIN/g, postDeployInfo.domain);
html += '<div class="app-info-card" style="margin-top:1rem"><h4>Bejelentkezés</h4>' + html += '<div class="app-info-card" style="margin-top:1rem"><h4>{{T "deploy.bejelentkezes"}}</h4>' +
'<p class="app-info-creds">' + creds + '</p></div>'; '<p class="app-info-creds">' + creds + '</p></div>';
} }
// Docs link // Docs link
if (postDeployInfo.docsURL) { if (postDeployInfo.docsURL) {
html += '<div style="margin-top:1rem"><a href="' + postDeployInfo.docsURL + '" target="_blank" class="btn btn-sm btn-outline">Dokumentáció ↗</a></div>'; html += '<div style="margin-top:1rem"><a href="' + postDeployInfo.docsURL + '" target="_blank" class="btn btn-sm btn-outline">{{T "deploy.dokumentacio"}}</a></div>';
} }
// Action buttons // Action buttons
html += '<div style="display:flex;gap:.75rem;margin-top:1.5rem;flex-wrap:wrap">' + html += '<div style="display:flex;gap:.75rem;margin-top:1.5rem;flex-wrap:wrap">' +
'<a href="/stacks/' + stackName + '/deploy" class="btn btn-outline">Beállítások és jelszavak megtekintése</a>' + '<a href="/stacks/' + stackName + '/deploy" class="btn btn-outline">{{T "deploy.beallitasok_es_jelszavak_megtekintese"}}</a>' +
'<a href="/stacks" class="btn btn-sm btn-outline">← Alkalmazások</a>' + '<a href="/stacks" class="btn btn-sm btn-outline">{{T "deploy.alkalmazasok"}}</a>' +
'</div>'; '</div>';
return html; return html;
@@ -796,7 +787,7 @@ function revealAutoField(stackName, envVar, btn) {
el.value = ''; el.value = '';
el.type = 'password'; el.type = 'password';
btn.dataset.shown = ''; btn.dataset.shown = '';
btn.textContent = 'Megjelenítés'; btn.textContent = '{{T "deploy.megjelenites"}}';
return; return;
} }
btn.disabled = true; btn.disabled = true;
@@ -807,18 +798,18 @@ function revealAutoField(stackName, envVar, btn) {
body: 'env_var=' + encodeURIComponent(envVar) body: 'env_var=' + encodeURIComponent(envVar)
}).then(function (r) { return r.json(); }).then(function (j) { }).then(function (r) { return r.json(); }).then(function (j) {
btn.disabled = false; btn.disabled = false;
if (!j.ok) { showAlert(j.error || 'A lekérés nem sikerült.'); return; } if (!j.ok) { showAlert(j.error || '{{T "deploy.a_lekeres_nem_sikerult"}}'); return; }
el.value = j.data.value; el.value = j.data.value;
el.type = 'text'; el.type = 'text';
btn.dataset.shown = '1'; btn.dataset.shown = '1';
btn.textContent = 'Elrejtés'; btn.textContent = '{{T "deploy.elrejtes"}}';
}).catch(function () { btn.disabled = false; showAlert('A lekérés nem sikerült.'); }); }).catch(function () { btn.disabled = false; showAlert('{{T "deploy.a_lekeres_nem_sikerult"}}'); });
} }
function toggleAutoField(fieldId, btn) { function toggleAutoField(fieldId, btn) {
var el = document.getElementById(fieldId); var el = document.getElementById(fieldId);
if (!el) return; if (!el) return;
el.type = el.type === 'password' ? 'text' : 'password'; el.type = el.type === 'password' ? 'text' : 'password';
btn.textContent = el.type === 'password' ? 'Megjelenítés' : 'Elrejtés'; btn.textContent = el.type === 'password' ? '{{T "deploy.megjelenites"}}' : '{{T "deploy.elrejtes"}}';
} }
function generatePassword(fieldId, confirmFieldId) { function generatePassword(fieldId, confirmFieldId) {
const chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'; const chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
@@ -841,7 +832,7 @@ function togglePasswordField(fieldId, confirmFieldId, btn) {
el.type = newType; el.type = newType;
var ce = document.getElementById(confirmFieldId); var ce = document.getElementById(confirmFieldId);
if (ce) ce.type = newType; if (ce) ce.type = newType;
btn.title = newType === 'password' ? 'Megjelenítés' : 'Elrejtés'; btn.title = newType === 'password' ? '{{T "deploy.megjelenites"}}' : '{{T "deploy.elrejtes"}}';
} }
function togglePostDeployCred(btn, credId, val) { function togglePostDeployCred(btn, credId, val) {
@@ -849,17 +840,17 @@ function togglePostDeployCred(btn, credId, val) {
if (!el) return; if (!el) return;
if (el.textContent === val) { if (el.textContent === val) {
el.textContent = '••••••••••••'; el.textContent = '••••••••••••';
btn.textContent = 'Megjelenítés'; btn.textContent = '{{T "deploy.megjelenites"}}';
} else { } else {
el.textContent = val; el.textContent = val;
btn.textContent = 'Elrejtés'; btn.textContent = '{{T "deploy.elrejtes"}}';
} }
} }
function copyPostDeployCred(btn, val) { function copyPostDeployCred(btn, val) {
if (navigator.clipboard) { if (navigator.clipboard) {
navigator.clipboard.writeText(val).then(function() { navigator.clipboard.writeText(val).then(function() {
var orig = btn.textContent; var orig = btn.textContent;
btn.textContent = 'Másolva!'; btn.textContent = '{{T "deploy.masolva"}}';
setTimeout(function() { btn.textContent = orig; }, 1500); setTimeout(function() { btn.textContent = orig; }, 1500);
}); });
} }
@@ -867,8 +858,8 @@ function copyPostDeployCred(btn, val) {
function deleteStaleData(stackName, stalePath, btn) { function deleteStaleData(stackName, stalePath, btn) {
// Two-step inline confirm (the irreversible delete keeps its double acknowledgement). // Two-step inline confirm (the irreversible delete keeps its double acknowledgement).
felhomConfirm(btn, 'Biztosan törölni szeretnéd a korábbi adatokat? Tárhely: ' + stalePath + ' — FIGYELEM: a művelet visszavonhatatlan! Előtte győződj meg róla, hogy az alkalmazás az új tárolóról megfelelően működik.', function () { felhomConfirm(btn, '{{T "deploy.biztosan_torolni_szeretned_a_korabbi"}} ' + stalePath + ' {{T "deploy.figyelem_a_muvelet_visszavonhatatlan_elo"}}', function () {
felhomConfirm(btn, 'UTOLSÓ FIGYELMEZTETÉS! A törlés visszavonhatatlan. Biztosan folytatod?', function () { felhomConfirm(btn, '{{T "deploy.utolso_figyelmeztetes_a_torles_visszavon"}}', function () {
deleteStaleDataRun(stackName, stalePath, btn); deleteStaleDataRun(stackName, stalePath, btn);
}); });
}); });
@@ -876,7 +867,7 @@ function deleteStaleData(stackName, stalePath, btn) {
function deleteStaleDataRun(stackName, stalePath, btn) { function deleteStaleDataRun(stackName, stalePath, btn) {
btn.disabled = true; btn.disabled = true;
btn.textContent = 'Törlés folyamatban...'; btn.textContent = '{{T "deploy.torles_folyamatban"}}';
fetch('/api/storage/stale-cleanup', { fetch('/api/storage/stale-cleanup', {
method: 'POST', method: 'POST',
@@ -886,14 +877,14 @@ function deleteStaleDataRun(stackName, stalePath, btn) {
.then(function(r) { return r.json(); }) .then(function(r) { return r.json(); })
.then(function(data) { .then(function(data) {
if (!data.ok) { if (!data.ok) {
showAlert('Hiba: ' + (data.error || 'Ismeretlen hiba')); showAlert('{{T "common.hiba_kettospont"}} ' + (data.error || '{{T "deploy.ismeretlen_hiba"}}'));
btn.disabled = false; btn.disabled = false;
btn.textContent = 'Korábbi adatok törlése'; btn.textContent = '{{T "deploy.korabbi_adatok_torlese"}}';
return; return;
} }
var msg = 'Korábbi adatok törölve!\n\nFelszabadított hely: ' + (data.freed_human || '?'); var msg = '{{T "deploy.korabbi_adatok_torolve_n_nfelszabaditott"}} ' + (data.freed_human || '?');
if (data.errors && data.errors.length > 0) { if (data.errors && data.errors.length > 0) {
msg += '\n\nNéhány hiba történt:\n' + data.errors.join('\n'); msg += '{{T "deploy.n_nnehany_hiba_tortent_n"}}' + data.errors.join('\n');
} }
showAlert(msg); showAlert(msg);
// Remove the stale data card from DOM // Remove the stale data card from DOM
@@ -906,9 +897,9 @@ function deleteStaleDataRun(stackName, stalePath, btn) {
} }
}) })
.catch(function(e) { .catch(function(e) {
showAlert('Hálózati hiba: ' + e.message); showAlert('{{T "deploy.halozati_hiba_2"}} ' + e.message);
btn.disabled = false; btn.disabled = false;
btn.textContent = 'Korábbi adatok törlése'; btn.textContent = '{{T "deploy.korabbi_adatok_torlese"}}';
}); });
} }
@@ -920,7 +911,7 @@ document.getElementById('deploy-form').addEventListener('submit', async function
for (const pf of passwordFields) { for (const pf of passwordFields) {
if (!pf.disabled && pf.value.trim() === '') { if (!pf.disabled && pf.value.trim() === '') {
const label = pf.closest('.form-group').querySelector('label').textContent.trim(); const label = pf.closest('.form-group').querySelector('label').textContent.trim();
showAlert('Kötelező mező: ' + label + '\nHasználja a Generálás gombot vagy írjon be egy jelszót.'); showAlert('{{T "deploy.kotelezo_mezo"}} ' + label + '{{T "deploy.nhasznalja_a_generalas_gombot_vagy"}}');
pf.focus(); pf.focus();
return; return;
} }
@@ -932,7 +923,7 @@ document.getElementById('deploy-form').addEventListener('submit', async function
const mainEl = document.getElementById(ci.getAttribute('data-confirm-for')); const mainEl = document.getElementById(ci.getAttribute('data-confirm-for'));
if (mainEl && !mainEl.disabled && ci.value !== mainEl.value) { if (mainEl && !mainEl.disabled && ci.value !== mainEl.value) {
const label = mainEl.closest('.form-group').querySelector('label').textContent.trim(); const label = mainEl.closest('.form-group').querySelector('label').textContent.trim();
showAlert('A két jelszó nem egyezik: ' + label); showAlert('{{T "deploy.a_ket_jelszo_nem_egyezik"}} ' + label);
ci.focus(); ci.focus();
return; return;
} }
@@ -943,7 +934,7 @@ document.getElementById('deploy-form').addEventListener('submit', async function
if (subdomainField && !subdomainField.disabled) { if (subdomainField && !subdomainField.disabled) {
const sd = subdomainField.value.trim().toLowerCase(); const sd = subdomainField.value.trim().toLowerCase();
if (!sd || !/^[a-z0-9]([a-z0-9-]*[a-z0-9])?$/.test(sd)) { if (!sd || !/^[a-z0-9]([a-z0-9-]*[a-z0-9])?$/.test(sd)) {
showAlert('Az aldomain csak kisbetűket, számokat és kötőjelet tartalmazhat, és nem kezdődhet/végződhet kötőjellel.'); showAlert('{{T "deploy.az_aldomain_csak_kisbetuket_szamokat"}}');
subdomainField.focus(); subdomainField.focus();
return; return;
} }
@@ -954,7 +945,7 @@ document.getElementById('deploy-form').addEventListener('submit', async function
for (const rf of requiredFields) { for (const rf of requiredFields) {
if (!rf.disabled && rf.value.trim() === '') { if (!rf.disabled && rf.value.trim() === '') {
const label = rf.closest('.form-group').querySelector('label').textContent.trim(); const label = rf.closest('.form-group').querySelector('label').textContent.trim();
showAlert('Kötelező mező: ' + label); showAlert('{{T "deploy.kotelezo_mezo"}} ' + label);
rf.focus(); rf.focus();
return; return;
} }
@@ -962,7 +953,7 @@ document.getElementById('deploy-form').addEventListener('submit', async function
const btn = e.target.querySelector('[type=submit]'); const btn = e.target.querySelector('[type=submit]');
btn.disabled = true; btn.disabled = true;
btn.textContent = 'Telepítés folyamatban...'; btn.textContent = '{{T "deploy.telepites_folyamatban"}}';
const values = {}; const values = {};
const inputs = e.target.querySelectorAll('input, select'); const inputs = e.target.querySelectorAll('input, select');
@@ -1005,8 +996,8 @@ document.getElementById('deploy-form').addEventListener('submit', async function
}); });
var data = await resp.json(); var data = await resp.json();
if (!data.ok) { if (!data.ok) {
showAlert('Hiba: ' + data.error); showAlert('{{T "common.hiba_kettospont"}} ' + data.error);
btn.textContent = 'Telepítés indítása'; btn.textContent = '{{T "deploy.telepites_inditasa"}}';
btn.disabled = false; btn.disabled = false;
return; return;
} }
@@ -1014,8 +1005,8 @@ document.getElementById('deploy-form').addEventListener('submit', async function
// Deploy API returned success — switch to progress view // Deploy API returned success — switch to progress view
formEl.style.display = 'none'; formEl.style.display = 'none';
progressEl.style.display = 'block'; progressEl.style.display = 'block';
setStep(stepConfig, 'done', 'Konfiguráció mentve'); setStep(stepConfig, 'done', '{{T "deploy.konfiguracio_mentve"}}');
setStep(stepContainers, 'active', 'Konténer(ek) indítása...'); setStep(stepContainers, 'active', '{{T "deploy.kontener_ek_inditasa"}}');
if (data.data && data.data.warning) { if (data.data && data.data.warning) {
warningEl.textContent = data.data.warning; warningEl.textContent = data.data.warning;
@@ -1027,13 +1018,13 @@ document.getElementById('deploy-form').addEventListener('submit', async function
var pollTimeout = 120000; var pollTimeout = 120000;
var pollTimer = setInterval(async function() { var pollTimer = setInterval(async function() {
var elapsed = Math.round((Date.now() - startTime) / 1000); var elapsed = Math.round((Date.now() - startTime) / 1000);
elapsedEl.textContent = elapsed + ' másodperce...'; elapsedEl.textContent = elapsed + ' {{T "deploy.masodperce"}}';
if (Date.now() - startTime > pollTimeout) { if (Date.now() - startTime > pollTimeout) {
clearInterval(pollTimer); clearInterval(pollTimer);
setStep(stepHealth, 'warn', 'Időtúllépés — az alkalmazás még indulhat'); setStep(stepHealth, 'warn', '{{T "deploy.idotullepes_az_alkalmazas_meg_indulhat"}}');
resultEl.innerHTML = '<div class="alert alert-warning" style="margin-top:1rem">' + resultEl.innerHTML = '<div class="alert alert-warning" style="margin-top:1rem">' +
'A telepítés időtúllépésbe futott. Az alkalmazás még indulhat.' + '{{T "deploy.a_telepites_idotullepesbe_futott_az"}}' +
'</div>' + buildPostDeployCard(stackName); '</div>' + buildPostDeployCard(stackName);
resultEl.style.display = 'block'; resultEl.style.display = 'block';
return; return;
@@ -1049,56 +1040,56 @@ document.getElementById('deploy-form').addEventListener('submit', async function
if (deployError) { if (deployError) {
// Async compose-up failed // Async compose-up failed
clearInterval(pollTimer); clearInterval(pollTimer);
setStep(stepContainers, 'error', 'Telepítés sikertelen'); setStep(stepContainers, 'error', '{{T "deploy.telepites_sikertelen"}}');
setStep(stepHealth, 'error'); setStep(stepHealth, 'error');
progressEl.querySelector('h3').textContent = 'Telepítés sikertelen'; progressEl.querySelector('h3').textContent = '{{T "deploy.telepites_sikertelen"}}';
resultEl.innerHTML = '<div class="alert alert-error" style="margin-top:1rem">' + resultEl.innerHTML = '<div class="alert alert-error" style="margin-top:1rem">' +
'A telepítés nem sikerült: ' + deployError + '{{T "deploy.a_telepites_nem_sikerult"}} ' + deployError +
'</div><a href="/stacks/' + stackName + '/logs" class="btn btn-outline" style="margin-top:.75rem">Naplók megtekintése</a>' + '</div><a href="/stacks/' + stackName + '/logs" class="btn btn-outline" style="margin-top:.75rem">{{T "deploy.naplok_megtekintese"}}</a>' +
' <a href="/stacks" class="btn btn-primary" style="margin-top:.75rem">Alkalmazások</a>'; ' <a href="/stacks" class="btn btn-primary" style="margin-top:.75rem">{{T "deploy.alkalmazasok_2"}}</a>';
resultEl.style.display = 'block'; resultEl.style.display = 'block';
} else if (state === 'deploying') { } else if (state === 'deploying') {
// Compose up in progress (pulling images, creating containers) // Compose up in progress (pulling images, creating containers)
setStep(stepContainers, 'active', 'Képek letöltése, konténerek indítása...'); setStep(stepContainers, 'active', '{{T "deploy.kepek_letoltese_kontenerek_inditasa"}}');
} else if (state === 'running') { } else if (state === 'running') {
clearInterval(pollTimer); clearInterval(pollTimer);
setStep(stepContainers, 'done', 'Konténerek elindultak'); setStep(stepContainers, 'done', '{{T "deploy.kontenerek_elindultak"}}');
setStep(stepHealth, 'done', 'Alkalmazás kész!'); setStep(stepHealth, 'done', '{{T "deploy.alkalmazas_kesz"}}');
progressEl.querySelector('h3').textContent = 'Telepítés sikeres!'; progressEl.querySelector('h3').textContent = '{{T "deploy.telepites_sikeres"}}';
resultEl.innerHTML = '<div class="alert alert-info" style="margin-top:1rem">' + resultEl.innerHTML = '<div class="alert alert-info" style="margin-top:1rem">' +
'Az alkalmazás sikeresen telepítve és fut!' + '{{T "deploy.az_alkalmazas_sikeresen_telepitve_es"}}' +
'</div>' + buildPostDeployCard(stackName); '</div>' + buildPostDeployCard(stackName);
resultEl.style.display = 'block'; resultEl.style.display = 'block';
} else if (state === 'starting') { } else if (state === 'starting') {
setStep(stepContainers, 'done', 'Konténerek elindultak'); setStep(stepContainers, 'done', '{{T "deploy.kontenerek_elindultak"}}');
setStep(stepHealth, 'active', 'Alkalmazás inicializálása...'); setStep(stepHealth, 'active', '{{T "deploy.alkalmazas_inicializalasa"}}');
} else if (state === 'unhealthy') { } else if (state === 'unhealthy') {
clearInterval(pollTimer); clearInterval(pollTimer);
setStep(stepContainers, 'done', 'Konténerek elindultak'); setStep(stepContainers, 'done', '{{T "deploy.kontenerek_elindultak"}}');
setStep(stepHealth, 'warn', 'Állapotjelző: még inicializál'); setStep(stepHealth, 'warn', '{{T "deploy.allapotjelzo_meg_inicializal"}}');
progressEl.querySelector('h3').textContent = 'Telepítés sikeres!'; progressEl.querySelector('h3').textContent = '{{T "deploy.telepites_sikeres"}}';
resultEl.innerHTML = '<div class="alert alert-warning" style="margin-top:1rem">' + resultEl.innerHTML = '<div class="alert alert-warning" style="margin-top:1rem">' +
'Az alkalmazás elindult, de még inicializálódik. ' + '{{T "deploy.az_alkalmazas_elindult_de_meg"}} ' +
'Ez normális az első percekben — az alkalmazás már elérhető lehet.' + '{{T "deploy.ez_normalis_az_elso_percekben"}}' +
'</div>' + buildPostDeployCard(stackName); '</div>' + buildPostDeployCard(stackName);
resultEl.style.display = 'block'; resultEl.style.display = 'block';
} else if (state === 'exited' || state === 'stopped') { } else if (state === 'exited' || state === 'stopped') {
clearInterval(pollTimer); clearInterval(pollTimer);
setStep(stepContainers, 'error', 'A konténer leállt'); setStep(stepContainers, 'error', '{{T "deploy.a_kontener_leallt"}}');
setStep(stepHealth, 'error'); setStep(stepHealth, 'error');
progressEl.querySelector('h3').textContent = 'Telepítés sikertelen'; progressEl.querySelector('h3').textContent = '{{T "deploy.telepites_sikertelen"}}';
resultEl.innerHTML = '<div class="alert alert-error" style="margin-top:1rem">' + resultEl.innerHTML = '<div class="alert alert-error" style="margin-top:1rem">' +
'A konténer leállt. Ellenőrizze a naplókat.' + '{{T "deploy.a_kontener_leallt_ellenorizze_a"}}' +
'</div><a href="/stacks/' + stackName + '/logs" class="btn btn-outline" style="margin-top:.75rem">Naplók megtekintése</a>' + '</div><a href="/stacks/' + stackName + '/logs" class="btn btn-outline" style="margin-top:.75rem">{{T "deploy.naplok_megtekintese"}}</a>' +
' <a href="/stacks" class="btn btn-primary" style="margin-top:.75rem">Alkalmazások</a>'; ' <a href="/stacks" class="btn btn-primary" style="margin-top:.75rem">{{T "deploy.alkalmazasok_2"}}</a>';
resultEl.style.display = 'block'; resultEl.style.display = 'block';
} }
} catch(pollErr) {} } catch(pollErr) {}
}, 3000); }, 3000);
} catch (err) { } catch (err) {
showAlert('Hálózati hiba: ' + err.message); showAlert('{{T "deploy.halozati_hiba_2"}} ' + err.message);
btn.textContent = 'Telepítés indítása'; btn.textContent = '{{T "deploy.telepites_inditasa"}}';
btn.disabled = false; btn.disabled = false;
} }
}); });
@@ -68,7 +68,7 @@
</div> </div>
<div class="share-field"> <div class="share-field">
{{if .SharePasswordSet}} {{if .SharePasswordSet}}
<label class="share-label">{{T "launcher.jelszo"}}</label> <label class="share-label">{{T "common.jelszo"}}</label>
<p class="share-note">{{T "launcher.a_megosztas_jelszoval_vedett"}}</p> <p class="share-note">{{T "launcher.a_megosztas_jelszoval_vedett"}}</p>
<form method="POST" action="/launcher/share/password"> <form method="POST" action="/launcher/share/password">
{{.CSRFField}} {{.CSRFField}}
@@ -101,14 +101,14 @@
<form method="POST" action="/launcher/share/enable"> <form method="POST" action="/launcher/share/enable">
{{.CSRFField}} {{.CSRFField}}
<div class="modal-actions"> <div class="modal-actions">
<button type="button" class="btn btn-outline" onclick="closeShareModal()">{{T "launcher.bezaras"}}</button> <button type="button" class="btn btn-outline" onclick="closeShareModal()">{{T "common.bezaras"}}</button>
<button type="submit" class="btn btn-primary">{{T "launcher.megosztas_bekapcsolasa"}}</button> <button type="submit" class="btn btn-primary">{{T "launcher.megosztas_bekapcsolasa"}}</button>
</div> </div>
</form> </form>
{{end}} {{end}}
{{if .ShareEnabled}} {{if .ShareEnabled}}
<div class="modal-actions"> <div class="modal-actions">
<button type="button" class="btn btn-outline" onclick="closeShareModal()">{{T "launcher.bezaras"}}</button> <button type="button" class="btn btn-outline" onclick="closeShareModal()">{{T "common.bezaras"}}</button>
</div> </div>
{{end}} {{end}}
</div> </div>
+18 -18
View File
@@ -265,7 +265,7 @@
}); });
const data = await resp.json(); const data = await resp.json();
if (toast) { if (toast) {
toast.textContent = data.ok ? (data.message || '{{T "layout.sablonok_frissitve"}}') : ('{{T "layout.hiba"}} ' + (data.error || '{{T "layout.ismeretlen_hiba"}}')); toast.textContent = data.ok ? (data.message || '{{T "layout.sablonok_frissitve"}}') : ('{{T "common.hiba_kettospont"}} ' + (data.error || '{{T "layout.ismeretlen_hiba"}}'));
toast.className = 'sync-toast ' + (data.ok ? 'sync-toast-ok' : 'sync-toast-err'); toast.className = 'sync-toast ' + (data.ok ? 'sync-toast-ok' : 'sync-toast-err');
toast.style.display = 'block'; toast.style.display = 'block';
setTimeout(function() { toast.style.display = 'none'; }, 5000); setTimeout(function() { toast.style.display = 'none'; }, 5000);
@@ -298,7 +298,7 @@
}); });
const data = await resp.json(); const data = await resp.json();
if (!data.ok) { if (!data.ok) {
showAlert('{{T "layout.hiba"}} ' + (data.error || '{{T "layout.ismeretlen_hiba"}}')); showAlert('{{T "common.hiba_kettospont"}} ' + (data.error || '{{T "layout.ismeretlen_hiba"}}'));
btn.textContent = origText; btn.textContent = origText;
btn.disabled = false; btn.disabled = false;
btn.classList.remove('loading'); btn.classList.remove('loading');
@@ -358,13 +358,13 @@
'<div class="alert alert-warning" style="margin-bottom:.75rem">{{T "layout.ez_a_muvelet_nem_visszavonhato"}}</div>' + '<div class="alert alert-warning" style="margin-bottom:.75rem">{{T "layout.ez_a_muvelet_nem_visszavonhato"}}</div>' +
hddInfo + checkboxHTML + hddInfo + checkboxHTML +
'<div class="modal-actions">' + '<div class="modal-actions">' +
'<button class="btn btn-outline" onclick="closeDeleteModal()">{{T "layout.megsem"}}</button>' + '<button class="btn btn-outline" onclick="closeDeleteModal()">{{T "common.megsem"}}</button>' +
'<button class="btn btn-danger" id="confirm-delete-btn" onclick="confirmDelete(\'' + name + '\')">{{T "layout.torles"}}</button>' + '<button class="btn btn-danger" id="confirm-delete-btn" onclick="confirmDelete(\'' + name + '\')">{{T "common.torles"}}</button>' +
'</div>'; '</div>';
} catch (err) { } catch (err) {
modal.querySelector('.modal-card').innerHTML = modal.querySelector('.modal-card').innerHTML =
'<h3>{{T "layout.hiba_2"}}</h3><p style="color:var(--text-2)">{{T "layout.nem_sikerult_lekerni_az_adatokat"}} ' + err.message + '</p>' + '<h3>{{T "common.hiba"}}</h3><p style="color:var(--text-2)">{{T "layout.nem_sikerult_lekerni_az_adatokat"}} ' + err.message + '</p>' +
'<div class="modal-actions"><button class="btn btn-outline" onclick="closeDeleteModal()">{{T "layout.bezaras"}}</button></div>'; '<div class="modal-actions"><button class="btn btn-outline" onclick="closeDeleteModal()">{{T "common.bezaras"}}</button></div>';
} }
} }
function closeDeleteModal() { function closeDeleteModal() {
@@ -401,16 +401,16 @@
'<h3>{{T "layout.sikeresen_torolve"}}</h3>' + '<h3>{{T "layout.sikeresen_torolve"}}</h3>' +
'<p style="color:var(--text-2)">{{T "layout.az_alkalmazas"}}' + name + '{{T "layout.torolve_lett"}}</p>' + '<p style="color:var(--text-2)">{{T "layout.az_alkalmazas"}}' + name + '{{T "layout.torolve_lett"}}</p>' +
removedInfo + preservedInfo + removedInfo + preservedInfo +
'<div class="modal-actions"><button class="btn btn-primary" onclick="window.location.href=\'/stacks\'">{{T "layout.bezaras"}}</button></div>'; '<div class="modal-actions"><button class="btn btn-primary" onclick="window.location.href=\'/stacks\'">{{T "common.bezaras"}}</button></div>';
} else { } else {
showAlert('{{T "layout.hiba"}} ' + (data.error || '{{T "layout.ismeretlen_hiba"}}')); showAlert('{{T "common.hiba_kettospont"}} ' + (data.error || '{{T "layout.ismeretlen_hiba"}}'));
btn.disabled = false; btn.disabled = false;
btn.textContent = '{{T "layout.torles"}}'; btn.textContent = '{{T "common.torles"}}';
} }
} catch (err) { } catch (err) {
showAlert('{{T "layout.halozati_hiba"}} ' + err.message); showAlert('{{T "layout.halozati_hiba"}} ' + err.message);
btn.disabled = false; btn.disabled = false;
btn.textContent = '{{T "layout.torles"}}'; btn.textContent = '{{T "common.torles"}}';
} }
} }
async function removeStack(name) { async function removeStack(name) {
@@ -471,13 +471,13 @@
'<div class="alert alert-warning" style="margin-bottom:.75rem">{{T "layout.ez_a_muvelet_nem_visszavonhato"}}</div>' + '<div class="alert alert-warning" style="margin-bottom:.75rem">{{T "layout.ez_a_muvelet_nem_visszavonhato"}}</div>' +
sections + sections +
'<div class="modal-actions">' + '<div class="modal-actions">' +
'<button class="btn btn-outline" onclick="closeRemoveModal()">{{T "layout.megsem"}}</button>' + '<button class="btn btn-outline" onclick="closeRemoveModal()">{{T "common.megsem"}}</button>' +
'<button class="btn btn-danger" id="confirm-remove-btn" onclick="confirmRemoveStack(\'' + name + '\')">{{T "layout.eltavolitas"}}</button>' + '<button class="btn btn-danger" id="confirm-remove-btn" onclick="confirmRemoveStack(\'' + name + '\')">{{T "common.eltavolitas"}}</button>' +
'</div>' + hddCheckbox; '</div>' + hddCheckbox;
} catch (err) { } catch (err) {
modal.querySelector('.modal-card').innerHTML = modal.querySelector('.modal-card').innerHTML =
'<h3>{{T "layout.hiba_2"}}</h3><p style="color:var(--text-2)">{{T "layout.nem_sikerult_lekerni_az_adatokat"}} ' + err.message + '</p>' + '<h3>{{T "common.hiba"}}</h3><p style="color:var(--text-2)">{{T "layout.nem_sikerult_lekerni_az_adatokat"}} ' + err.message + '</p>' +
'<div class="modal-actions"><button class="btn btn-outline" onclick="closeRemoveModal()">{{T "layout.bezaras"}}</button></div>'; '<div class="modal-actions"><button class="btn btn-outline" onclick="closeRemoveModal()">{{T "common.bezaras"}}</button></div>';
} }
} }
function closeRemoveModal() { function closeRemoveModal() {
@@ -522,16 +522,16 @@
'<h3>{{T "layout.sikeresen_eltavolitva"}}</h3>' + '<h3>{{T "layout.sikeresen_eltavolitva"}}</h3>' +
'<p style="color:var(--text-2)">{{T "layout.az_alkalmazas"}}' + name + '{{T "layout.eltavolitva_ujratelepitheto_a_telepites"}}</p>' + '<p style="color:var(--text-2)">{{T "layout.az_alkalmazas"}}' + name + '{{T "layout.eltavolitva_ujratelepitheto_a_telepites"}}</p>' +
removedInfo + preservedInfo + removedInfo + preservedInfo +
'<div class="modal-actions"><button class="btn btn-primary" onclick="window.location.href=\'/stacks\'">{{T "layout.bezaras"}}</button></div>'; '<div class="modal-actions"><button class="btn btn-primary" onclick="window.location.href=\'/stacks\'">{{T "common.bezaras"}}</button></div>';
} else { } else {
showAlert('{{T "layout.hiba"}} ' + (data.error || '{{T "layout.ismeretlen_hiba"}}')); showAlert('{{T "common.hiba_kettospont"}} ' + (data.error || '{{T "layout.ismeretlen_hiba"}}'));
btn.disabled = false; btn.disabled = false;
btn.textContent = '{{T "layout.eltavolitas"}}'; btn.textContent = '{{T "common.eltavolitas"}}';
} }
} catch (err) { } catch (err) {
showAlert('{{T "layout.halozati_hiba"}} ' + err.message); showAlert('{{T "layout.halozati_hiba"}} ' + err.message);
btn.disabled = false; btn.disabled = false;
btn.textContent = '{{T "layout.eltavolitas"}}'; btn.textContent = '{{T "common.eltavolitas"}}';
} }
} }
</script> </script>
+9 -9
View File
@@ -1,18 +1,18 @@
{{define "logs"}} {{define "logs"}}
{{template "layout_start" .}} {{template "layout_start" .}}
<div class="page-header"> <div class="page-header">
<a href="/stacks" class="btn btn-sm btn-outline">← Vissza</a> <a href="/stacks" class="btn btn-sm btn-outline">{{T "logs.vissza"}}</a>
<h2>{{.Stack.Meta.DisplayName}} — Naplók</h2> <h2>{{T "logs.naplok"}}</h2>
</div> </div>
<div class="logs-container" id="logs-container"> <div class="logs-container" id="logs-container">
<pre class="logs-output" id="logs-output">{{.Logs}}</pre> <pre class="logs-output" id="logs-output">{{.Logs}}</pre>
</div> </div>
<div class="logs-actions"> <div class="logs-actions">
<span class="logs-live-indicator" id="live-indicator"> <span class="logs-live-indicator" id="live-indicator">
<span class="logs-live-dot"></span> Élő <span class="logs-live-dot"></span> {{T "logs.elo"}}
</span> </span>
<button class="btn btn-outline btn-sm" id="logs-toggle" onclick="toggleLive()">Szüneteltetés</button> <button class="btn btn-outline btn-sm" id="logs-toggle" onclick="toggleLive()">{{T "logs.szuneteltetes"}}</button>
<button class="btn btn-outline btn-sm" onclick="fetchLogs()">Frissítés</button> <button class="btn btn-outline btn-sm" onclick="fetchLogs()">{{T "logs.frissites"}}</button>
</div> </div>
<script> <script>
(function() { (function() {
@@ -53,13 +53,13 @@
if (live) { if (live) {
startPolling(); startPolling();
indicator.className = 'logs-live-indicator'; indicator.className = 'logs-live-indicator';
indicator.innerHTML = '<span class="logs-live-dot"></span> Élő'; indicator.innerHTML = '<span class="logs-live-dot"></span> {{T "logs.elo"}}';
toggleBtn.textContent = 'Szüneteltetés'; toggleBtn.textContent = '{{T "logs.szuneteltetes"}}';
} else { } else {
stopPolling(); stopPolling();
indicator.className = 'logs-live-indicator logs-live-paused'; indicator.className = 'logs-live-indicator logs-live-paused';
indicator.innerHTML = 'Szünetelve'; indicator.innerHTML = '{{T "logs.szunetelve"}}';
toggleBtn.textContent = 'Folytatás'; toggleBtn.textContent = '{{T "logs.folytatas"}}';
} }
}; };
@@ -2,57 +2,57 @@
{{template "layout_start" .}} {{template "layout_start" .}}
<div class="page-header"> <div class="page-header">
<h2>Rendszermonitor</h2> <h2>{{T "monitoring.rendszermonitor"}}</h2>
</div> </div>
<!-- Section 0: Host (Proxmox box) health — slice 9, served by the host agent over the local API. <!-- Section 0: Host (Proxmox box) health — slice 9, served by the host agent over the local API.
The de-privileged controller can't read the host itself; this card shows the real box. --> The de-privileged controller can't read the host itself; this card shows the real box. -->
<div class="monitor-card" id="host-health-card"> <div class="monitor-card" id="host-health-card">
<div class="monitor-card-header"> <div class="monitor-card-header">
<h3>Szerver állapota (gazdagép)</h3> <h3>{{T "monitoring.szerver_allapota_gazdagep"}}</h3>
<span class="sysinfo-value" id="host-health-updated" style="font-size:.75rem;color:var(--text-3)"></span> <span class="sysinfo-value" id="host-health-updated" style="font-size:.75rem;color:var(--text-3)"></span>
</div> </div>
<div id="host-health-unavailable" class="monitoring-banner monitoring-banner-yellow" style="display:none"> <div id="host-health-unavailable" class="monitoring-banner monitoring-banner-yellow" style="display:none">
A gazdagép metrikái jelenleg nem elérhetők. {{T "monitoring.a_gazdagep_metrikai_jelenleg_nem"}}
</div> </div>
<div id="host-health-body" style="display:none"> <div id="host-health-body" style="display:none">
<div class="sysinfo-grid"> <div class="sysinfo-grid">
<div class="sysinfo-row"> <div class="sysinfo-row">
<span class="sysinfo-label">CPU használat</span> <span class="sysinfo-label">{{T "monitoring.cpu_hasznalat"}}</span>
<span class="sysinfo-value" id="host-cpu">–</span> <span class="sysinfo-value" id="host-cpu">–</span>
</div> </div>
<div class="sysinfo-row"> <div class="sysinfo-row">
<span class="sysinfo-label">Terhelés (load)</span> <span class="sysinfo-label">{{T "monitoring.terheles_load"}}</span>
<span class="sysinfo-value" id="host-load">–</span> <span class="sysinfo-value" id="host-load">–</span>
</div> </div>
<div class="sysinfo-row"> <div class="sysinfo-row">
<span class="sysinfo-label">Memória</span> <span class="sysinfo-label">{{T "monitoring.memoria"}}</span>
<span class="sysinfo-value" id="host-mem">–</span> <span class="sysinfo-value" id="host-mem">–</span>
</div> </div>
<div class="sysinfo-row"> <div class="sysinfo-row">
<span class="sysinfo-label">CPU hőmérséklet</span> <span class="sysinfo-label">{{T "monitoring.cpu_homerseklet"}}</span>
<span class="sysinfo-value" id="host-temp">–</span> <span class="sysinfo-value" id="host-temp">–</span>
</div> </div>
<div class="sysinfo-row"> <div class="sysinfo-row">
<span class="sysinfo-label">Üzemidő</span> <span class="sysinfo-label">{{T "monitoring.uzemido"}}</span>
<span class="sysinfo-value" id="host-uptime">–</span> <span class="sysinfo-value" id="host-uptime">–</span>
</div> </div>
</div> </div>
<h4 style="margin:1rem 0 .5rem">Tárolók kapacitása</h4> <h4 style="margin:1rem 0 .5rem">{{T "monitoring.tarolok_kapacitasa"}}</h4>
<div class="storage-bars" id="host-storage-bars"></div> <div class="storage-bars" id="host-storage-bars"></div>
</div> </div>
</div> </div>
<!-- Section 1: System Overview --> <!-- Section 1: System Overview -->
<div class="monitor-card"> <div class="monitor-card">
<h3>Rendszer áttekintés</h3> <h3>{{T "monitoring.rendszer_attekintes"}}</h3>
<div class="sysinfo-grid"> <div class="sysinfo-grid">
<div class="sysinfo-row"> <div class="sysinfo-row">
<span class="sysinfo-label">Gépnév</span> <span class="sysinfo-label">{{T "monitoring.gepnev"}}</span>
<span class="sysinfo-value" id="sysinfo-hostname">–</span> <span class="sysinfo-value" id="sysinfo-hostname">–</span>
</div> </div>
<div class="sysinfo-row"> <div class="sysinfo-row">
<span class="sysinfo-label">Operációs rendszer</span> <span class="sysinfo-label">{{T "monitoring.operacios_rendszer"}}</span>
<span class="sysinfo-value" id="sysinfo-os">–</span> <span class="sysinfo-value" id="sysinfo-os">–</span>
</div> </div>
<div class="sysinfo-row"> <div class="sysinfo-row">
@@ -60,15 +60,15 @@
<span class="sysinfo-value" id="sysinfo-kernel">–</span> <span class="sysinfo-value" id="sysinfo-kernel">–</span>
</div> </div>
<div class="sysinfo-row"> <div class="sysinfo-row">
<span class="sysinfo-label">Processzor</span> <span class="sysinfo-label">{{T "monitoring.processzor"}}</span>
<span class="sysinfo-value" id="sysinfo-cpu">–</span> <span class="sysinfo-value" id="sysinfo-cpu">–</span>
</div> </div>
<div class="sysinfo-row"> <div class="sysinfo-row">
<span class="sysinfo-label">Üzemidő</span> <span class="sysinfo-label">{{T "monitoring.uzemido"}}</span>
<span class="sysinfo-value" id="sysinfo-uptime">–</span> <span class="sysinfo-value" id="sysinfo-uptime">–</span>
</div> </div>
<div class="sysinfo-row"> <div class="sysinfo-row">
<span class="sysinfo-label">Indítás</span> <span class="sysinfo-label">{{T "monitoring.inditas"}}</span>
<span class="sysinfo-value" id="sysinfo-boot">–</span> <span class="sysinfo-value" id="sysinfo-boot">–</span>
</div> </div>
</div> </div>
@@ -76,12 +76,12 @@
<!-- Section 1.5: Storage (moved here for visibility) --> <!-- Section 1.5: Storage (moved here for visibility) -->
<div class="monitor-card"> <div class="monitor-card">
<h3>Tárhely</h3> <h3>{{T "monitoring.tarhely"}}</h3>
<div class="storage-bars"> <div class="storage-bars">
{{with .SystemInfo}} {{with .SystemInfo}}
<div class="storage-item meter {{usageColor .DiskPercent}}"> <div class="storage-item meter {{usageColor .DiskPercent}}">
<div class="storage-header"> <div class="storage-header">
<span class="storage-label">Rendszer (/)</span> <span class="storage-label">{{T "common.rendszer_gyoker"}}</span>
<span class="storage-value">{{fmtGB .DiskUsedGB}} / {{fmtGB .DiskTotalGB}} ({{printf "%.0f" .DiskPercent}}%)</span> <span class="storage-value">{{fmtGB .DiskUsedGB}} / {{fmtGB .DiskTotalGB}} ({{printf "%.0f" .DiskPercent}}%)</span>
</div> </div>
<div class="meter-track"> <div class="meter-track">
@@ -93,7 +93,7 @@
<div class="storage-item meter meter-empty storage-disconnected"> <div class="storage-item meter meter-empty storage-disconnected">
<div class="storage-header"> <div class="storage-header">
<span class="storage-label">{{.Label}}</span> <span class="storage-label">{{.Label}}</span>
<span class="storage-value">— <span class="state-text-neutral">Leválasztva</span></span> <span class="storage-value">— <span class="state-text-neutral">{{T "common.levalasztva"}}</span></span>
</div> </div>
<div class="meter-track"></div> <div class="meter-track"></div>
</div> </div>
@@ -127,15 +127,15 @@
<!-- Section 2: Hub Connection Status --> <!-- Section 2: Hub Connection Status -->
<div class="monitor-card"> <div class="monitor-card">
<h3>Hub kapcsolat</h3> <h3>{{T "monitoring.hub_kapcsolat"}}</h3>
{{if .HubEnabled}} {{if .HubEnabled}}
{{if .HubConnected}} {{if .HubConnected}}
<div class="monitoring-banner monitoring-banner-green"> <div class="monitoring-banner monitoring-banner-green">
Kapcsolódva — a központi rendszer aktívan figyeli a szervert. {{T "monitoring.kapcsolodva_a_kozponti_rendszer_aktivan"}}
</div> </div>
{{else}} {{else}}
<div class="monitoring-banner monitoring-banner-red"> <div class="monitoring-banner monitoring-banner-red">
Nem elérhető — a központi rendszer nem kapott friss jelentést. {{T "monitoring.nem_elerheto_a_kozponti_rendszer"}}
</div> </div>
{{end}} {{end}}
<div class="sysinfo-grid" style="margin-top: 0.75rem"> <div class="sysinfo-grid" style="margin-top: 0.75rem">
@@ -144,25 +144,25 @@
<span class="sysinfo-value"><code>{{.HubURL}}</code></span> <span class="sysinfo-value"><code>{{.HubURL}}</code></span>
</div> </div>
<div class="sysinfo-row"> <div class="sysinfo-row">
<span class="sysinfo-label">Ügyfél azonosító</span> <span class="sysinfo-label">{{T "common.ugyfel_azonosito"}}</span>
<span class="sysinfo-value"><code>{{.CustomerID}}</code></span> <span class="sysinfo-value"><code>{{.CustomerID}}</code></span>
</div> </div>
{{if not .HubLastSuccess.IsZero}} {{if not .HubLastSuccess.IsZero}}
<div class="sysinfo-row"> <div class="sysinfo-row">
<span class="sysinfo-label">Utolsó sikeres jelentés</span> <span class="sysinfo-label">{{T "monitoring.utolso_sikeres_jelentes"}}</span>
<span class="sysinfo-value">{{.HubLastSuccess | timeAgo}}</span> <span class="sysinfo-value">{{.HubLastSuccess | timeAgo}}</span>
</div> </div>
{{end}} {{end}}
{{if .HubLastError}} {{if .HubLastError}}
<div class="sysinfo-row"> <div class="sysinfo-row">
<span class="sysinfo-label">Utolsó hiba</span> <span class="sysinfo-label">{{T "monitoring.utolso_hiba"}}</span>
<span class="sysinfo-value"><span class="text-error">{{.HubLastError}}</span></span> <span class="sysinfo-value"><span class="text-error">{{.HubLastError}}</span></span>
</div> </div>
{{end}} {{end}}
</div> </div>
{{else}} {{else}}
<div class="monitoring-banner monitoring-banner-yellow"> <div class="monitoring-banner monitoring-banner-yellow">
A Hub kapcsolat nincs bekapcsolva — a központi monitoring nem aktív. {{T "monitoring.a_hub_kapcsolat_nincs_bekapcsolva"}}
</div> </div>
{{end}} {{end}}
</div> </div>
@@ -170,41 +170,41 @@
<!-- Section 3: System Metrics Charts --> <!-- Section 3: System Metrics Charts -->
<div class="monitor-card"> <div class="monitor-card">
<div class="monitor-card-header"> <div class="monitor-card-header">
<h3>Rendszer metrikák</h3> <h3>{{T "monitoring.rendszer_metrikak"}}</h3>
<div class="time-range-bar" id="system-range-bar"> <div class="time-range-bar" id="system-range-bar">
<button class="filter-btn active" data-range="1h">1 óra</button> <button class="filter-btn active" data-range="1h">{{T "monitoring.1_ora"}}</button>
<button class="filter-btn" data-range="6h">6 óra</button> <button class="filter-btn" data-range="6h">{{T "monitoring.6_ora"}}</button>
<button class="filter-btn" data-range="24h">24 óra</button> <button class="filter-btn" data-range="24h">{{T "monitoring.24_ora"}}</button>
<button class="filter-btn" data-range="7d">7 nap</button> <button class="filter-btn" data-range="7d">{{T "monitoring.7_nap"}}</button>
<button class="filter-btn" data-range="30d">30 nap</button> <button class="filter-btn" data-range="30d">{{T "monitoring.30_nap"}}</button>
</div> </div>
</div> </div>
<div class="charts-grid" id="system-charts"> <div class="charts-grid" id="system-charts">
<div class="chart-box"> <div class="chart-box">
<div class="chart-title">CPU használat (%)</div> <div class="chart-title">{{T "monitoring.cpu_hasznalat_2"}}</div>
<div class="chart-wrap"><canvas id="chart-cpu"></canvas></div> <div class="chart-wrap"><canvas id="chart-cpu"></canvas></div>
</div> </div>
<div class="chart-box"> <div class="chart-box">
<div class="chart-title">Memória használat (GB)</div> <div class="chart-title">{{T "monitoring.memoria_hasznalat_gb"}}</div>
<div class="chart-wrap"><canvas id="chart-memory"></canvas></div> <div class="chart-wrap"><canvas id="chart-memory"></canvas></div>
</div> </div>
<div class="chart-box"> <div class="chart-box">
<div class="chart-title">Hőmérséklet (°C)</div> <div class="chart-title">{{T "monitoring.homerseklet_c"}}</div>
<div class="chart-wrap"><canvas id="chart-temp"></canvas></div> <div class="chart-wrap"><canvas id="chart-temp"></canvas></div>
</div> </div>
<div class="chart-box"> <div class="chart-box">
<div class="chart-title">Terhelés (Load Average)</div> <div class="chart-title">{{T "monitoring.terheles_load_average"}}</div>
<div class="chart-wrap"><canvas id="chart-load"></canvas></div> <div class="chart-wrap"><canvas id="chart-load"></canvas></div>
</div> </div>
</div> </div>
<div class="chart-empty" id="system-charts-empty" style="display:none"> <div class="chart-empty" id="system-charts-empty" style="display:none">
Még nincsenek adatok. A metrikák gyűjtése elindult, az első adatok néhány perc múlva megjelennek. {{T "monitoring.meg_nincsenek_adatok_a_metrikak"}}
</div> </div>
</div> </div>
<!-- Section 3.5: Memory Distribution Bar --> <!-- Section 3.5: Memory Distribution Bar -->
<div class="monitor-card" id="memory-distribution-card" style="display:none"> <div class="monitor-card" id="memory-distribution-card" style="display:none">
<h3>Memória eloszlás</h3> <h3>{{T "monitoring.memoria_eloszlas"}}</h3>
<div id="mem-dist-header" class="memory-dist-header"></div> <div id="mem-dist-header" class="memory-dist-header"></div>
<div class="memory-dist-bar" id="mem-dist-bar"></div> <div class="memory-dist-bar" id="mem-dist-bar"></div>
<div class="memory-bar-legend" id="mem-dist-legend"></div> <div class="memory-bar-legend" id="mem-dist-legend"></div>
@@ -212,19 +212,19 @@
<!-- Section 4: Container Resources --> <!-- Section 4: Container Resources -->
<div class="monitor-card"> <div class="monitor-card">
<h3>Alkalmazás erőforrások</h3> <h3>{{T "monitoring.alkalmazas_eroforrasok"}}</h3>
<div class="container-charts-row" id="container-charts"> <div class="container-charts-row" id="container-charts">
<div class="chart-box chart-box-half"> <div class="chart-box chart-box-half">
<div class="chart-title">CPU használat (%)</div> <div class="chart-title">{{T "monitoring.cpu_hasznalat_2"}}</div>
<div class="chart-wrap chart-wrap-bar"><canvas id="chart-container-cpu"></canvas></div> <div class="chart-wrap chart-wrap-bar"><canvas id="chart-container-cpu"></canvas></div>
</div> </div>
<div class="chart-box chart-box-half"> <div class="chart-box chart-box-half">
<div class="chart-title">Memória használat (MB)</div> <div class="chart-title">{{T "monitoring.memoria_hasznalat_mb"}}</div>
<div class="chart-wrap chart-wrap-bar"><canvas id="chart-container-mem"></canvas></div> <div class="chart-wrap chart-wrap-bar"><canvas id="chart-container-mem"></canvas></div>
</div> </div>
</div> </div>
<div class="chart-empty" id="container-charts-empty" style="display:none"> <div class="chart-empty" id="container-charts-empty" style="display:none">
Még nincsenek konténer adatok. {{T "monitoring.meg_nincsenek_kontener_adatok"}}
</div> </div>
</div> </div>
@@ -233,12 +233,12 @@
<div class="monitor-card-header"> <div class="monitor-card-header">
<h3 id="container-detail-title">–</h3> <h3 id="container-detail-title">–</h3>
<div class="time-range-bar" id="container-range-bar"> <div class="time-range-bar" id="container-range-bar">
<button class="filter-btn active" data-range="1h">1 óra</button> <button class="filter-btn active" data-range="1h">{{T "monitoring.1_ora"}}</button>
<button class="filter-btn" data-range="6h">6 óra</button> <button class="filter-btn" data-range="6h">{{T "monitoring.6_ora"}}</button>
<button class="filter-btn" data-range="24h">24 óra</button> <button class="filter-btn" data-range="24h">{{T "monitoring.24_ora"}}</button>
<button class="filter-btn" data-range="7d">7 nap</button> <button class="filter-btn" data-range="7d">{{T "monitoring.7_nap"}}</button>
</div> </div>
<button class="btn btn-outline btn-sm" onclick="closeContainerDetail()">Bezárás</button> <button class="btn btn-outline btn-sm" onclick="closeContainerDetail()">{{T "common.bezaras"}}</button>
</div> </div>
<div class="charts-grid charts-grid-2"> <div class="charts-grid charts-grid-2">
<div class="chart-box"> <div class="chart-box">
@@ -246,7 +246,7 @@
<div class="chart-wrap"><canvas id="chart-detail-cpu"></canvas></div> <div class="chart-wrap"><canvas id="chart-detail-cpu"></canvas></div>
</div> </div>
<div class="chart-box"> <div class="chart-box">
<div class="chart-title">Memória (MB)</div> <div class="chart-title">{{T "monitoring.memoria_mb"}}</div>
<div class="chart-wrap"><canvas id="chart-detail-mem"></canvas></div> <div class="chart-wrap"><canvas id="chart-detail-mem"></canvas></div>
</div> </div>
</div> </div>
@@ -588,7 +588,7 @@
function fmtMB(mb) { return mb >= 1024 ? (mb/1024).toFixed(1) + ' GB' : Math.round(mb) + ' MB'; } function fmtMB(mb) { return mb >= 1024 ? (mb/1024).toFixed(1) + ' GB' : Math.round(mb) + ' MB'; }
header.textContent = 'Használt: ' + fmtMB(usedMB) + ' / ' + fmtMB(totalMB) + ' (' + Math.round(usedMB/totalMB*100) + '%)'; header.textContent = '{{T "monitoring.hasznalt"}} ' + fmtMB(usedMB) + ' / ' + fmtMB(totalMB) + ' (' + Math.round(usedMB/totalMB*100) + '%)';
// Sort containers by memory usage descending // Sort containers by memory usage descending
containers.sort(function(a, b) { return (b.mem_usage_mb || 0) - (a.mem_usage_mb || 0); }); containers.sort(function(a, b) { return (b.mem_usage_mb || 0) - (a.mem_usage_mb || 0); });
@@ -608,12 +608,12 @@
// OS / system overhead // OS / system overhead
if (osMB > 10) { if (osMB > 10) {
var osPct = (osMB / totalMB * 100).toFixed(2); var osPct = (osMB / totalMB * 100).toFixed(2);
html += '<div class="memory-bar-segment" style="width:' + osPct + '%;background:var(--text-3);opacity:0.5" title="Rendszer: ' + fmtMB(osMB) + '"></div>'; html += '<div class="memory-bar-segment" style="width:' + osPct + '%;background:var(--text-3);opacity:0.5" title="{{T "monitoring.rendszer_title"}} ' + fmtMB(osMB) + '"></div>';
legendHtml += '<div class="memory-legend-item"><span class="memory-legend-dot" style="background:var(--text-3);opacity:0.5"></span>Rendszer (' + fmtMB(osMB) + ')</div>'; legendHtml += '<div class="memory-legend-item"><span class="memory-legend-dot" style="background:var(--text-3);opacity:0.5"></span>{{T "monitoring.rendszer_2"}}' + fmtMB(osMB) + ')</div>';
} }
// Free space legend only // Free space legend only
legendHtml += '<div class="memory-legend-item"><span class="memory-legend-dot" style="background:var(--bg-2);border:1px solid var(--line)"></span>Szabad (' + fmtMB(freeMB) + ')</div>'; legendHtml += '<div class="memory-legend-item"><span class="memory-legend-dot" style="background:var(--bg-2);border:1px solid var(--line)"></span>{{T "monitoring.szabad"}}' + fmtMB(freeMB) + ')</div>';
bar.innerHTML = html; bar.innerHTML = html;
legend.innerHTML = legendHtml; legend.innerHTML = legendHtml;
@@ -641,7 +641,7 @@
window.showContainerDetail = async function(name) { window.showContainerDetail = async function(name) {
detailContainer = name; detailContainer = name;
document.getElementById('container-detail-title').textContent = name + ' — Erőforrás előzmények'; document.getElementById('container-detail-title').textContent = name + ' {{T "monitoring.eroforras_elozmenyek"}}';
document.getElementById('container-detail-panel').style.display = ''; document.getElementById('container-detail-panel').style.display = '';
document.getElementById('container-detail-panel').scrollIntoView({behavior: 'smooth'}); document.getElementById('container-detail-panel').scrollIntoView({behavior: 'smooth'});
await loadContainerDetail(); await loadContainerDetail();
@@ -745,7 +745,7 @@
var bars = document.getElementById('host-storage-bars'); var bars = document.getElementById('host-storage-bars');
var targets = Array.isArray(d.storage_targets) ? d.storage_targets : []; var targets = Array.isArray(d.storage_targets) ? d.storage_targets : [];
if (!targets.length) { if (!targets.length) {
bars.innerHTML = '<div class="sysinfo-value" style="color:var(--text-3)">Nincs tároló adat.</div>'; bars.innerHTML = '<div class="sysinfo-value" style="color:var(--text-3)">{{T "monitoring.nincs_tarolo_adat"}}</div>';
} else { } else {
var html = ''; var html = '';
targets.forEach(function(t) { targets.forEach(function(t) {
@@ -758,7 +758,7 @@
if (t.state && t.state !== 'attached') { if (t.state && t.state !== 'attached') {
html += '<div class="storage-item meter meter-empty storage-disconnected">' + html += '<div class="storage-item meter meter-empty storage-disconnected">' +
'<div class="storage-header"><span class="storage-label">' + label + '</span>' + '<div class="storage-header"><span class="storage-label">' + label + '</span>' +
'<span class="storage-value">— <span class="state-text-neutral">Nem elérhető</span></span></div>' + '<span class="storage-value">— <span class="state-text-neutral">{{T "monitoring.nem_elerheto"}}</span></span></div>' +
'<div class="meter-track"></div>' + purposeHtml + '</div>'; '<div class="meter-track"></div>' + purposeHtml + '</div>';
return; return;
} }
@@ -770,7 +770,7 @@
} }
if (t.smart) { if (t.smart) {
if (t.smart.temperature_c != null) extra.push(t.smart.temperature_c + ' °C'); if (t.smart.temperature_c != null) extra.push(t.smart.temperature_c + ' °C');
if (t.smart.percentage_used != null) extra.push('kopás: ' + t.smart.percentage_used + '%'); if (t.smart.percentage_used != null) extra.push('{{T "monitoring.kopas"}} ' + t.smart.percentage_used + '%');
} }
var extraHtml = extra.length ? ' <span style="color:var(--text-3);font-size:.75rem">· ' + escapeHtml(extra.join(' · ')) + '</span>' : ''; var extraHtml = extra.length ? ' <span style="color:var(--text-3);font-size:.75rem">· ' + escapeHtml(extra.join(' · ')) + '</span>' : '';
html += '<div class="storage-item meter ' + usageColorClass(pct) + '">' + html += '<div class="storage-item meter ' + usageColorClass(pct) + '">' +
@@ -786,7 +786,7 @@
var now = new Date(); var now = new Date();
document.getElementById('host-health-updated').textContent = document.getElementById('host-health-updated').textContent =
'Frissítve: ' + now.toLocaleTimeString('hu-HU', {timeZone: budaTZ, hour: '2-digit', minute: '2-digit', second: '2-digit'}); '{{T "monitoring.frissitve"}} ' + now.toLocaleTimeString('hu-HU', {timeZone: budaTZ, hour: '2-digit', minute: '2-digit', second: '2-digit'});
} }
async function loadHostMetrics() { async function loadHostMetrics() {
@@ -835,9 +835,9 @@
var days = Math.floor(seconds / 86400); var days = Math.floor(seconds / 86400);
var hours = Math.floor((seconds % 86400) / 3600); var hours = Math.floor((seconds % 86400) / 3600);
var minutes = Math.floor((seconds % 3600) / 60); var minutes = Math.floor((seconds % 3600) / 60);
if (days > 0) return days + ' nap, ' + hours + ' óra'; if (days > 0) return days + ' {{T "monitoring.nap"}} ' + hours + ' {{T "monitoring.ora"}}';
if (hours > 0) return hours + ' óra, ' + minutes + ' perc'; if (hours > 0) return hours + ' {{T "monitoring.ora_2"}} ' + minutes + ' {{T "monitoring.perc"}}';
return minutes + ' perc'; return minutes + ' {{T "monitoring.perc"}}';
} }
// ============================================= // =============================================
@@ -2,110 +2,110 @@
{{template "layout_start" .}} {{template "layout_start" .}}
<div class="page-header"> <div class="page-header">
<h2>Beállítások — Értesítések</h2> <h2>{{T "settings_notifications.beallitasok_ertesitesek"}}</h2>
</div> </div>
<!-- Section C: Notification Preferences --> <!-- Section C: Notification Preferences -->
<div class="settings-card"> <div class="settings-card">
<h3>Értesítések</h3> <h3>{{T "settings_notifications.ertesitesek"}}</h3>
{{if .HubEnabled}} {{if .HubEnabled}}
{{if .NotificationSuccess}}<div class="alert alert-info">{{.NotificationSuccess}}</div>{{end}} {{if .NotificationSuccess}}<div class="alert alert-info">{{.NotificationSuccess}}</div>{{end}}
{{if .NotificationError}}<div class="alert alert-error">{{.NotificationError}}</div>{{end}} {{if .NotificationError}}<div class="alert alert-error">{{.NotificationError}}</div>{{end}}
<form method="POST" action="/settings/notifications"> <form method="POST" action="/settings/notifications">
{{.CSRFField}} {{.CSRFField}}
<div class="form-group"> <div class="form-group">
<label for="notification_email">E-mail cím</label> <label for="notification_email">{{T "settings_notifications.e_mail_cim"}}</label>
<input type="email" id="notification_email" name="notification_email" <input type="email" id="notification_email" name="notification_email"
value="{{with .NotificationPrefs}}{{.Email}}{{end}}" value="{{with .NotificationPrefs}}{{.Email}}{{end}}"
placeholder="pelda@email.hu" class="form-control"> placeholder="{{T "settings_notifications.pelda_email"}}" class="form-control">
</div> </div>
<div class="form-group"> <div class="form-group">
<label>Hibák és figyelmeztetések:</label> <label>{{T "settings_notifications.hibak_es_figyelmeztetesek"}}</label>
<div class="checkbox-group"> <div class="checkbox-group">
<label class="toggle"> <label class="toggle">
<input type="checkbox" name="event_backup_failed" {{with .NotificationPrefs}}{{range .EnabledEvents}}{{if eq . "backup_failed"}}checked{{end}}{{end}}{{end}}> <input type="checkbox" name="event_backup_failed" {{with .NotificationPrefs}}{{range .EnabledEvents}}{{if eq . "backup_failed"}}checked{{end}}{{end}}{{end}}>
<span class="toggle-label">Biztonsági mentés sikertelen</span> <span class="toggle-label">{{T "settings_notifications.biztonsagi_mentes_sikertelen"}}</span>
</label> </label>
<label class="toggle"> <label class="toggle">
<input type="checkbox" name="event_db_dump_failed" {{with .NotificationPrefs}}{{range .EnabledEvents}}{{if eq . "db_dump_failed"}}checked{{end}}{{end}}{{end}}> <input type="checkbox" name="event_db_dump_failed" {{with .NotificationPrefs}}{{range .EnabledEvents}}{{if eq . "db_dump_failed"}}checked{{end}}{{end}}{{end}}>
<span class="toggle-label">Adatbázis mentés sikertelen</span> <span class="toggle-label">{{T "settings_notifications.adatbazis_mentes_sikertelen"}}</span>
</label> </label>
<label class="toggle"> <label class="toggle">
<input type="checkbox" name="event_backup_integrity_failed" {{with .NotificationPrefs}}{{range .EnabledEvents}}{{if eq . "backup_integrity_failed"}}checked{{end}}{{end}}{{end}}> <input type="checkbox" name="event_backup_integrity_failed" {{with .NotificationPrefs}}{{range .EnabledEvents}}{{if eq . "backup_integrity_failed"}}checked{{end}}{{end}}{{end}}>
<span class="toggle-label">Mentés sérülés észlelve</span> <span class="toggle-label">{{T "settings_notifications.mentes_serules_eszlelve"}}</span>
</label> </label>
<label class="toggle"> <label class="toggle">
<input type="checkbox" name="event_crossdrive_failed" {{with .NotificationPrefs}}{{range .EnabledEvents}}{{if eq . "crossdrive_failed"}}checked{{end}}{{end}}{{end}}> <input type="checkbox" name="event_crossdrive_failed" {{with .NotificationPrefs}}{{range .EnabledEvents}}{{if eq . "crossdrive_failed"}}checked{{end}}{{end}}{{end}}>
<span class="toggle-label">Másodlagos mentés sikertelen</span> <span class="toggle-label">{{T "settings_notifications.masodlagos_mentes_sikertelen"}}</span>
</label> </label>
<label class="toggle"> <label class="toggle">
<input type="checkbox" name="event_offbox_enlarge_blocked" {{with .NotificationPrefs}}{{range .EnabledEvents}}{{if eq . "offbox_enlarge_blocked"}}checked{{end}}{{end}}{{end}}> <input type="checkbox" name="event_offbox_enlarge_blocked" {{with .NotificationPrefs}}{{range .EnabledEvents}}{{if eq . "offbox_enlarge_blocked"}}checked{{end}}{{end}}{{end}}>
<span class="toggle-label">Távoli mentés — tárhelykeret-figyelmeztetés</span> <span class="toggle-label">{{T "settings_notifications.tavoli_mentes_tarhelykeret_figyelmeztete"}}</span>
</label> </label>
<label class="toggle"> <label class="toggle">
<input type="checkbox" name="event_disk_warning" {{with .NotificationPrefs}}{{range .EnabledEvents}}{{if eq . "disk_warning"}}checked{{end}}{{end}}{{end}}> <input type="checkbox" name="event_disk_warning" {{with .NotificationPrefs}}{{range .EnabledEvents}}{{if eq . "disk_warning"}}checked{{end}}{{end}}{{end}}>
<span class="toggle-label">Lemez betelőben (90% felett)</span> <span class="toggle-label">{{T "settings_notifications.lemez_beteloben_90_felett"}}</span>
</label> </label>
<label class="toggle"> <label class="toggle">
<input type="checkbox" name="event_disk_critical" {{with .NotificationPrefs}}{{range .EnabledEvents}}{{if eq . "disk_critical"}}checked{{end}}{{end}}{{end}}> <input type="checkbox" name="event_disk_critical" {{with .NotificationPrefs}}{{range .EnabledEvents}}{{if eq . "disk_critical"}}checked{{end}}{{end}}{{end}}>
<span class="toggle-label">Lemez megtelt vagy meghibásodott</span> <span class="toggle-label">{{T "settings_notifications.lemez_megtelt_vagy_meghibasodott"}}</span>
</label> </label>
<label class="toggle"> <label class="toggle">
<input type="checkbox" name="event_storage_disconnected" {{with .NotificationPrefs}}{{range .EnabledEvents}}{{if eq . "storage_disconnected"}}checked{{end}}{{end}}{{end}}> <input type="checkbox" name="event_storage_disconnected" {{with .NotificationPrefs}}{{range .EnabledEvents}}{{if eq . "storage_disconnected"}}checked{{end}}{{end}}{{end}}>
<span class="toggle-label">Meghajtó leválasztva</span> <span class="toggle-label">{{T "settings_notifications.meghajto_levalasztva"}}</span>
</label> </label>
<label class="toggle"> <label class="toggle">
<input type="checkbox" name="event_node_down" {{with .NotificationPrefs}}{{range .EnabledEvents}}{{if eq . "node_down"}}checked{{end}}{{end}}{{end}}> <input type="checkbox" name="event_node_down" {{with .NotificationPrefs}}{{range .EnabledEvents}}{{if eq . "node_down"}}checked{{end}}{{end}}{{end}}>
<span class="toggle-label">Szerver nem elérhető</span> <span class="toggle-label">{{T "settings_notifications.szerver_nem_elerheto"}}</span>
</label> </label>
<label class="toggle"> <label class="toggle">
<input type="checkbox" name="event_health_critical" {{with .NotificationPrefs}}{{range .EnabledEvents}}{{if eq . "health_critical"}}checked{{end}}{{end}}{{end}}> <input type="checkbox" name="event_health_critical" {{with .NotificationPrefs}}{{range .EnabledEvents}}{{if eq . "health_critical"}}checked{{end}}{{end}}{{end}}>
<span class="toggle-label">Rendszer állapot kritikus</span> <span class="toggle-label">{{T "settings_notifications.rendszer_allapot_kritikus"}}</span>
</label> </label>
<label class="toggle"> <label class="toggle">
<input type="checkbox" name="event_expected_backup_missed" {{with .NotificationPrefs}}{{range .EnabledEvents}}{{if eq . "expected_backup_missed"}}checked{{end}}{{end}}{{end}}> <input type="checkbox" name="event_expected_backup_missed" {{with .NotificationPrefs}}{{range .EnabledEvents}}{{if eq . "expected_backup_missed"}}checked{{end}}{{end}}{{end}}>
<span class="toggle-label">Elvárt fájlmentés elmaradt</span> <span class="toggle-label">{{T "settings_notifications.elvart_fajlmentes_elmaradt"}}</span>
</label> </label>
<label class="toggle"> <label class="toggle">
<input type="checkbox" name="event_expected_dbdump_missed" {{with .NotificationPrefs}}{{range .EnabledEvents}}{{if eq . "expected_dbdump_missed"}}checked{{end}}{{end}}{{end}}> <input type="checkbox" name="event_expected_dbdump_missed" {{with .NotificationPrefs}}{{range .EnabledEvents}}{{if eq . "expected_dbdump_missed"}}checked{{end}}{{end}}{{end}}>
<span class="toggle-label">Elvárt adatbázismentés elmaradt</span> <span class="toggle-label">{{T "settings_notifications.elvart_adatbazismentes_elmaradt"}}</span>
</label> </label>
<label class="toggle"> <label class="toggle">
<input type="checkbox" name="event_app_start_failed" {{with .NotificationPrefs}}{{range .EnabledEvents}}{{if eq . "app_start_failed"}}checked{{end}}{{end}}{{end}}> <input type="checkbox" name="event_app_start_failed" {{with .NotificationPrefs}}{{range .EnabledEvents}}{{if eq . "app_start_failed"}}checked{{end}}{{end}}{{end}}>
<span class="toggle-label">Alkalmazás nem fut</span> <span class="toggle-label">{{T "settings_notifications.alkalmazas_nem_fut"}}</span>
</label> </label>
</div> </div>
</div> </div>
<div class="form-group"> <div class="form-group">
<label>Tájékoztató:</label> <label>{{T "settings_notifications.tajekoztato"}}</label>
<div class="checkbox-group"> <div class="checkbox-group">
<label class="toggle"> <label class="toggle">
<input type="checkbox" name="event_storage_reconnected" {{with .NotificationPrefs}}{{range .EnabledEvents}}{{if eq . "storage_reconnected"}}checked{{end}}{{end}}{{end}}> <input type="checkbox" name="event_storage_reconnected" {{with .NotificationPrefs}}{{range .EnabledEvents}}{{if eq . "storage_reconnected"}}checked{{end}}{{end}}{{end}}>
<span class="toggle-label">Meghajtó újra csatlakoztatva</span> <span class="toggle-label">{{T "settings_notifications.meghajto_ujra_csatlakoztatva"}}</span>
</label> </label>
<label class="toggle"> <label class="toggle">
<input type="checkbox" name="event_health_recovered" {{with .NotificationPrefs}}{{range .EnabledEvents}}{{if eq . "health_recovered"}}checked{{end}}{{end}}{{end}}> <input type="checkbox" name="event_health_recovered" {{with .NotificationPrefs}}{{range .EnabledEvents}}{{if eq . "health_recovered"}}checked{{end}}{{end}}{{end}}>
<span class="toggle-label">Rendszer állapot helyreállt</span> <span class="toggle-label">{{T "settings_notifications.rendszer_allapot_helyreallt"}}</span>
</label> </label>
</div> </div>
</div> </div>
<div class="form-group"> <div class="form-group">
<label for="cooldown_hours">Értesítési szünet</label> <label for="cooldown_hours">{{T "settings_notifications.ertesitesi_szunet"}}</label>
<div class="form-inline"> <div class="form-inline">
<input type="number" id="cooldown_hours" name="cooldown_hours" min="1" max="168" <input type="number" id="cooldown_hours" name="cooldown_hours" min="1" max="168"
value="{{with .NotificationPrefs}}{{.CooldownHours}}{{end}}" value="{{with .NotificationPrefs}}{{.CooldownHours}}{{end}}"
class="form-control form-control-narrow"> class="form-control form-control-narrow">
<span class="form-hint">óra (azonos probléma esetén ennyi ideig nem küld újat)</span> <span class="form-hint">{{T "settings_notifications.ora_azonos_problema_eseten_ennyi"}}</span>
</div> </div>
</div> </div>
<div class="form-actions"> <div class="form-actions">
<button type="submit" class="btn btn-primary">Mentés</button> <button type="submit" class="btn btn-primary">{{T "settings_notifications.mentes"}}</button>
<button type="submit" formaction="/settings/notifications/test" class="btn btn-outline">Teszt email küldése</button> <button type="submit" formaction="/settings/notifications/test" class="btn btn-outline">{{T "settings_notifications.teszt_email_kuldese"}}</button>
</div> </div>
</form> </form>
{{else}} {{else}}
<div class="alert alert-info"> <div class="alert alert-info">
Az értesítések a központi rendszeren keresztül működnek, ami jelenleg nincs bekapcsolva. {{T "settings_notifications.az_ertesitesek_a_kozponti_rendszeren"}}
</div> </div>
{{end}} {{end}}
</div> </div>
@@ -113,11 +113,9 @@
<!-- Section: App email (SMTP relay) --> <!-- Section: App email (SMTP relay) -->
{{if .AppEmailAvailable}} {{if .AppEmailAvailable}}
<div class="settings-card"> <div class="settings-card">
<h3>Alkalmazás-email</h3> <h3>{{T "settings_notifications.alkalmazas_email"}}</h3>
<p class="settings-card-desc"> <p class="settings-card-desc">
Az alkalmazások a Felhom-on keresztül küldhetnek emailt (pl. jelszó-visszaállítás, meghívók), {{T "settings_notifications.az_alkalmazasok_a_felhom_on"}}
külön email-szolgáltató beállítása nélkül. A feladó címe minden alkalmazásnál a saját
<em>&lt;alkalmazás&gt;@felhom.eu</em> címe lesz.
</p> </p>
{{if .AppEmailSuccess}}<div class="alert alert-info">{{.AppEmailSuccess}}</div>{{end}} {{if .AppEmailSuccess}}<div class="alert alert-info">{{.AppEmailSuccess}}</div>{{end}}
{{if .AppEmailError}}<div class="alert alert-error">{{.AppEmailError}}</div>{{end}} {{if .AppEmailError}}<div class="alert alert-error">{{.AppEmailError}}</div>{{end}}
@@ -126,21 +124,21 @@
<div class="form-group"> <div class="form-group">
<label style="display:flex;align-items:center;gap:.5rem"> <label style="display:flex;align-items:center;gap:.5rem">
<input type="checkbox" name="app_email_enabled" value="on" {{if .AppEmailEnabled}}checked{{end}}> <input type="checkbox" name="app_email_enabled" value="on" {{if .AppEmailEnabled}}checked{{end}}>
Alkalmazás-email engedélyezése {{T "settings_notifications.alkalmazas_email_engedelyezese"}}
</label> </label>
</div> </div>
<div class="form-group"> <div class="form-group">
<label for="app_email_from_name">Feladó neve (opcionális)</label> <label for="app_email_from_name">{{T "settings_notifications.felado_neve_opcionalis"}}</label>
<input type="text" id="app_email_from_name" name="app_email_from_name" <input type="text" id="app_email_from_name" name="app_email_from_name"
value="{{.AppEmailFromName}}" placeholder="pl. a háztartás neve" class="form-control"> value="{{.AppEmailFromName}}" placeholder="{{T "settings_notifications.pl_a_haztartas_neve"}}" class="form-control">
<span class="form-hint">Ez jelenik meg a kimenő emailek feladójaként az alkalmazás neve mellett.</span> <span class="form-hint">{{T "settings_notifications.ez_jelenik_meg_a_kimeno"}}</span>
</div> </div>
<div class="form-actions"> <div class="form-actions">
<button type="submit" class="btn btn-primary">Mentés</button> <button type="submit" class="btn btn-primary">{{T "settings_notifications.mentes"}}</button>
</div> </div>
</form> </form>
<p class="settings-card-desc" style="margin-top:.75rem"> <p class="settings-card-desc" style="margin-top:.75rem">
Bekapcsolás után az egyes alkalmazásoknál is engedélyezni kell az email-küldést (az alkalmazás oldalán). {{T "settings_notifications.bekapcsolas_utan_az_egyes_alkalmazasokna"}}
</p> </p>
</div> </div>
{{end}} {{end}}
@@ -2,52 +2,51 @@
{{template "layout_start" .}} {{template "layout_start" .}}
<div class="page-header"> <div class="page-header">
<h2>Beállítások — Biztonság és hozzáférés</h2> <h2>{{T "settings_security.beallitasok_biztonsag_es_hozzaferes"}}</h2>
</div> </div>
<!-- Section B: Password Change --> <!-- Section B: Password Change -->
<div class="settings-card"> <div class="settings-card">
<h3>Jelszó módosítás</h3> <h3>{{T "settings_security.jelszo_modositas"}}</h3>
{{if .AuthEnabled}} {{if .AuthEnabled}}
{{if .PasswordError}}<div class="alert alert-error">{{.PasswordError}}</div>{{end}} {{if .PasswordError}}<div class="alert alert-error">{{.PasswordError}}</div>{{end}}
<form method="POST" action="/settings/password"> <form method="POST" action="/settings/password">
{{.CSRFField}} {{.CSRFField}}
<div class="form-group"> <div class="form-group">
<label for="current_password">Jelenlegi jelszó</label> <label for="current_password">{{T "settings_security.jelenlegi_jelszo"}}</label>
<input type="password" id="current_password" name="current_password" required <input type="password" id="current_password" name="current_password" required
placeholder="Adja meg a jelenlegi jelszavát" class="form-control"> placeholder="{{T "settings_security.adja_meg_a_jelenlegi_jelszavat"}}" class="form-control">
</div> </div>
<div class="form-group"> <div class="form-group">
<label for="new_password">Új jelszó</label> <label for="new_password">{{T "settings_security.uj_jelszo"}}</label>
<input type="password" id="new_password" name="new_password" required minlength="8" <input type="password" id="new_password" name="new_password" required minlength="8"
placeholder="Legalább 8 karakter" class="form-control"> placeholder="{{T "settings_security.legalabb_8_karakter"}}" class="form-control">
</div> </div>
<div class="form-group"> <div class="form-group">
<label for="confirm_password">Új jelszó megerősítése</label> <label for="confirm_password">{{T "settings_security.uj_jelszo_megerositese"}}</label>
<input type="password" id="confirm_password" name="confirm_password" required minlength="8" <input type="password" id="confirm_password" name="confirm_password" required minlength="8"
placeholder="Jelszó mégegyszer" class="form-control"> placeholder="{{T "settings_security.jelszo_megegyszer"}}" class="form-control">
</div> </div>
<button type="submit" class="btn btn-primary">Jelszó módosítása</button> <button type="submit" class="btn btn-primary">{{T "settings_security.jelszo_modositasa"}}</button>
</form> </form>
{{else}} {{else}}
<div class="alert alert-info"> <div class="alert alert-info">
A jelszavas védelem nincs beállítva. Kérd az üzemeltetőt a beállításhoz. {{T "settings_security.a_jelszavas_vedelem_nincs_beallitva"}}
</div> </div>
{{end}} {{end}}
</div> </div>
<!-- Section: Geo-Restriction --> <!-- Section: Geo-Restriction -->
<div class="settings-card"> <div class="settings-card">
<h3>Földrajzi korlátozás</h3> <h3>{{T "settings_security.foldrajzi_korlatozas"}}</h3>
<p class="settings-card-desc"> <p class="settings-card-desc">
Ország alapján korlátozható a webes alkalmazások elérése a Cloudflare WAF segítségével. {{T "settings_security.orszag_alapjan_korlatozhato_a_webes"}}
<br><span class="form-hint">A helyi hálózati hozzáférés mindig engedélyezett (nem halad át a Cloudflare-en).</span> <br><span class="form-hint">{{T "settings_security.a_helyi_halozati_hozzaferes_mindig"}}</span>
</p> </p>
{{if not .CFConfigured}} {{if not .CFConfigured}}
<div class="alert alert-info"> <div class="alert alert-info">
A Cloudflare API token nincs konfigurálva. Kérd az üzemeltetőt a beállításhoz.<br> {{T "settings_security.a_cloudflare_api_token_nincs"}}
<small>A tokennek <strong>Zone WAF:Edit</strong> jogosultsággal kell rendelkeznie.</small>
</div> </div>
{{else}} {{else}}
<div id="geo-status-msg"></div> <div id="geo-status-msg"></div>
@@ -55,16 +54,16 @@
<label class="toggle" style="margin-bottom:1rem"> <label class="toggle" style="margin-bottom:1rem">
<input type="checkbox" id="geo-enabled" {{if .GeoEnabled}}checked{{end}} <input type="checkbox" id="geo-enabled" {{if .GeoEnabled}}checked{{end}}
onchange="toggleGeo(this.checked)"> onchange="toggleGeo(this.checked)">
<span class="toggle-label">Geo-korlátozás aktív</span> <span class="toggle-label">{{T "settings_security.geo_korlatozas_aktiv"}}</span>
</label> </label>
<div id="geo-details" {{if not .GeoEnabled}}style="display:none"{{end}}> <div id="geo-details" {{if not .GeoEnabled}}style="display:none"{{end}}>
<!-- Global allowed countries --> <!-- Global allowed countries -->
<div class="form-group"> <div class="form-group">
<label>Engedélyezett országok (globális)</label> <label>{{T "settings_security.engedelyezett_orszagok_globalis"}}</label>
<div class="geo-country-selector" id="geo-countries"> <div class="geo-country-selector" id="geo-countries">
<input type="text" id="geo-search" class="form-control" <input type="text" id="geo-search" class="form-control"
placeholder="Ország keresése..." placeholder="{{T "settings_security.orszag_keresese"}}"
autocomplete="off" autocomplete="off"
oninput="filterCountries(this.value)" oninput="filterCountries(this.value)"
onfocus="showCountryList()" onfocus="showCountryList()"
@@ -72,22 +71,22 @@
<div class="geo-country-list" id="geo-country-list"></div> <div class="geo-country-list" id="geo-country-list"></div>
</div> </div>
<div class="geo-selected-tags" id="geo-selected-tags"></div> <div class="geo-selected-tags" id="geo-selected-tags"></div>
<span class="form-hint">Csak a kiválasztott országokból érhető el a rendszer.</span> <span class="form-hint">{{T "settings_security.csak_a_kivalasztott_orszagokbol_erheto"}}</span>
</div> </div>
<!-- Per-app overrides --> <!-- Per-app overrides -->
<div class="form-group" style="margin-top:1.5rem"> <div class="form-group" style="margin-top:1.5rem">
<label>Alkalmazás-specifikus felülírások</label> <label>{{T "settings_security.alkalmazas_specifikus_felulirasok"}}</label>
<div id="geo-app-overrides"></div> <div id="geo-app-overrides"></div>
{{if .DeployedApps}} {{if .DeployedApps}}
<div style="margin-top:.5rem;display:flex;align-items:center;gap:.5rem"> <div style="margin-top:.5rem;display:flex;align-items:center;gap:.5rem">
<select id="geo-add-app-select" class="form-control" style="max-width:250px"> <select id="geo-add-app-select" class="form-control" style="max-width:250px">
<option value="">— Alkalmazás kiválasztása —</option> <option value="">{{T "settings_security.alkalmazas_kivalasztasa"}}</option>
{{range .DeployedApps}} {{range .DeployedApps}}
<option value="{{.Name}}">{{.Display}}</option> <option value="{{.Name}}">{{.Display}}</option>
{{end}} {{end}}
</select> </select>
<button class="btn btn-sm btn-outline" onclick="addAppOverride()">+ Hozzáadás</button> <button class="btn btn-sm btn-outline" onclick="addAppOverride()">{{T "settings_security.hozzaadas"}}</button>
</div> </div>
{{end}} {{end}}
</div> </div>
@@ -96,11 +95,11 @@
<div class="form-group" style="margin-top:1.5rem"> <div class="form-group" style="margin-top:1.5rem">
<div style="display:flex;align-items:center;gap:1rem;flex-wrap:wrap"> <div style="display:flex;align-items:center;gap:1rem;flex-wrap:wrap">
<button class="btn btn-primary" id="btn-geo-save" onclick="saveGeoSettings()"> <button class="btn btn-primary" id="btn-geo-save" onclick="saveGeoSettings()">
Mentés és szinkronizálás {{T "settings_security.mentes_es_szinkronizalas"}}
</button> </button>
<button class="btn btn-sm btn-outline" onclick="triggerGeoSync()">Kézi szinkronizálás</button> <button class="btn btn-sm btn-outline" onclick="triggerGeoSync()">{{T "settings_security.kezi_szinkronizalas"}}</button>
<span id="geo-sync-status" class="form-hint"> <span id="geo-sync-status" class="form-hint">
{{if .GeoLastSync}}Utolsó szinkronizálás: {{.GeoLastSync}}{{end}} {{if .GeoLastSync}}{{T "settings_security.utolso_szinkronizalas"}}{{end}}
{{if .GeoLastError}} <span class="state-text-crit">{{.GeoLastError}}</span>{{end}} {{if .GeoLastError}} <span class="state-text-crit">{{.GeoLastError}}</span>{{end}}
</span> </span>
</div> </div>
@@ -118,10 +117,10 @@ function openDialog(opts){
var root=document.getElementById('dialog-root'); var root=document.getElementById('dialog-root');
if(!root) return; if(!root) return;
root.innerHTML='<div class="confirm-overlay" onclick="if(event.target===this)document.getElementById(\'dialog-cancel\').click()"><div class="confirm-box">' root.innerHTML='<div class="confirm-overlay" onclick="if(event.target===this)document.getElementById(\'dialog-cancel\').click()"><div class="confirm-box">'
+'<h3>'+escText(opts.title||'Megerősítés')+'</h3>' +'<h3>'+escText(opts.title||'{{T "settings_security.megerosites"}}')+'</h3>'
+'<p style="white-space:pre-line">'+escText(opts.message||'')+'</p>' +'<p style="white-space:pre-line">'+escText(opts.message||'')+'</p>'
+'<div class="form-actions"><button id="dialog-go" class="btn btn-primary">'+escText(opts.confirmLabel||'Megerősítés')+'</button>' +'<div class="form-actions"><button id="dialog-go" class="btn btn-primary">'+escText(opts.confirmLabel||'{{T "settings_security.megerosites"}}')+'</button>'
+'<button type="button" class="btn btn-outline" id="dialog-cancel">Mégsem</button></div>' +'<button type="button" class="btn btn-outline" id="dialog-cancel">{{T "common.megsem"}}</button></div>'
+'</div></div>'; +'</div></div>';
document.getElementById('dialog-go').onclick=function(){ closeDialog(); if(opts.onConfirm) opts.onConfirm(); }; document.getElementById('dialog-go').onclick=function(){ closeDialog(); if(opts.onConfirm) opts.onConfirm(); };
document.getElementById('dialog-cancel').onclick=function(){ closeDialog(); if(opts.onCancel) opts.onCancel(); }; document.getElementById('dialog-cancel').onclick=function(){ closeDialog(); if(opts.onCancel) opts.onCancel(); };
@@ -171,7 +170,7 @@ function openDialog(opts){
+ escHtml(c.name) + ' <small>(' + c.code + ')</small></div>'; + escHtml(c.name) + ' <small>(' + c.code + ')</small></div>';
count++; count++;
} }
list.innerHTML = html || '<div class="geo-country-option" style="opacity:.5">Nincs találat</div>'; list.innerHTML = html || '<div class="geo-country-option" style="opacity:.5">{{T "settings_security.nincs_talalat"}}</div>';
// Reveal with 'block', NOT '' — the .geo-country-list CSS default is display:none, // Reveal with 'block', NOT '' — the .geo-country-list CSS default is display:none,
// and clearing the inline style ('') would fall back to that and keep the (populated) // and clearing the inline style ('') would fall back to that and keep the (populated)
// list hidden. This was the country-autocomplete "no list" bug. // list hidden. This was the country-autocomplete "no list" bug.
@@ -188,8 +187,8 @@ function openDialog(opts){
window.removeCountry = function(code) { window.removeCountry = function(code) {
if (code === 'HU') { if (code === 'HU') {
openDialog({title:'Figyelem', confirmLabel:'Eltávolítás', openDialog({title:'{{T "settings_security.figyelem"}}', confirmLabel:'{{T "common.eltavolitas"}}',
message:'Figyelem: Magyarország eltávolítása azt jelenti, hogy magyar IP-ről sem lesz elérhető a rendszer távolról. Biztosan folytatja?', message:'{{T "settings_security.figyelem_magyarorszag_eltavolitasa_azt_j"}}',
onConfirm:function(){ doRemoveCountry(code); }}); onConfirm:function(){ doRemoveCountry(code); }});
return; return;
} }
@@ -247,8 +246,8 @@ function openDialog(opts){
var idx = ov.allowed_countries.indexOf(code); var idx = ov.allowed_countries.indexOf(code);
if (idx >= 0) { if (idx >= 0) {
if (code === 'HU') { if (code === 'HU') {
openDialog({title:'Figyelem', confirmLabel:'Eltávolítás', openDialog({title:'{{T "settings_security.figyelem"}}', confirmLabel:'{{T "common.eltavolitas"}}',
message:'Magyarország eltávolítása nem ajánlott. Folytatja?', message:'{{T "settings_security.magyarorszag_eltavolitasa_nem_ajanlott_f"}}',
onConfirm:function(){ ov.allowed_countries.splice(ov.allowed_countries.indexOf(code), 1); }, onConfirm:function(){ ov.allowed_countries.splice(ov.allowed_countries.indexOf(code), 1); },
onCancel:function(){ el.checked = true; }}); onCancel:function(){ el.checked = true; }});
return; return;
@@ -262,7 +261,7 @@ function openDialog(opts){
function renderAppOverrides() { function renderAppOverrides() {
var el = document.getElementById('geo-app-overrides'); var el = document.getElementById('geo-app-overrides');
if (!appOverrides || Object.keys(appOverrides).length === 0) { if (!appOverrides || Object.keys(appOverrides).length === 0) {
el.innerHTML = '<p class="form-hint">Nincs alkalmazás-specifikus beállítás. Minden alkalmazás a globális beállítást követi.</p>'; el.innerHTML = '<p class="form-hint">{{T "settings_security.nincs_alkalmazas_specifikus_beallitas_mi"}}</p>';
return; return;
} }
var html = ''; var html = '';
@@ -284,8 +283,8 @@ function openDialog(opts){
html += '<span class="geo-tag geo-tag-sm">' + code + '</span>'; html += '<span class="geo-tag geo-tag-sm">' + code + '</span>';
} }
html += '</div>'; html += '</div>';
html += '<button class="btn btn-sm btn-outline" onclick="editAppOverride(\'' + appName + '\')">Szerkesztés</button>'; html += '<button class="btn btn-sm btn-outline" onclick="editAppOverride(\'' + appName + '\')">{{T "settings_security.szerkesztes"}}</button>';
html += '<button class="btn btn-sm btn-danger-outline" onclick="removeAppOverride(\'' + appName + '\')">Törlés</button>'; html += '<button class="btn btn-sm btn-danger-outline" onclick="removeAppOverride(\'' + appName + '\')">{{T "common.torles"}}</button>';
html += '</div>'; html += '</div>';
} }
el.innerHTML = html; el.innerHTML = html;
@@ -298,7 +297,7 @@ function openDialog(opts){
var checked = {}; var checked = {};
for (var i = 0; i < ov.allowed_countries.length; i++) checked[ov.allowed_countries[i]] = true; for (var i = 0; i < ov.allowed_countries.length; i++) checked[ov.allowed_countries[i]] = true;
var html = '<div class="geo-edit-overlay" id="geo-edit-' + appName + '">'; var html = '<div class="geo-edit-overlay" id="geo-edit-' + appName + '">';
html += '<h4>Engedélyezett országok: ' + escHtml(appName) + '</h4>'; html += '<h4>{{T "settings_security.engedelyezett_orszagok"}} ' + escHtml(appName) + '</h4>';
html += '<div class="geo-edit-grid">'; html += '<div class="geo-edit-grid">';
for (var i = 0; i < allCountries.length; i++) { for (var i = 0; i < allCountries.length; i++) {
var c = allCountries[i]; var c = allCountries[i];
@@ -307,7 +306,7 @@ function openDialog(opts){
+ ' ' + escHtml(c.name) + ' (' + c.code + ')</label>'; + ' ' + escHtml(c.name) + ' (' + c.code + ')</label>';
} }
html += '</div>'; html += '</div>';
html += '<button class="btn btn-sm btn-primary" style="margin-top:.5rem" onclick="closeAppEdit(\'' + appName + '\')">Kész</button>'; html += '<button class="btn btn-sm btn-primary" style="margin-top:.5rem" onclick="closeAppEdit(\'' + appName + '\')">{{T "settings_security.kesz"}}</button>';
html += '</div>'; html += '</div>';
document.getElementById('geo-app-overrides').innerHTML += html; document.getElementById('geo-app-overrides').innerHTML += html;
}); });
@@ -324,7 +323,7 @@ function openDialog(opts){
var btn = document.getElementById('btn-geo-save'); var btn = document.getElementById('btn-geo-save');
var status = document.getElementById('geo-status-msg'); var status = document.getElementById('geo-status-msg');
btn.disabled = true; btn.disabled = true;
btn.textContent = 'Mentés...'; btn.textContent = '{{T "settings_security.mentes"}}';
var payload = { var payload = {
enabled: document.getElementById('geo-enabled').checked, enabled: document.getElementById('geo-enabled').checked,
@@ -339,21 +338,21 @@ function openDialog(opts){
.then(function(r){return r.json()}) .then(function(r){return r.json()})
.then(function(d){ .then(function(d){
if (d.ok) { if (d.ok) {
status.innerHTML = '<div class="alert alert-info">' + (d.message || 'Mentve') + '</div>'; status.innerHTML = '<div class="alert alert-info">' + (d.message || '{{T "settings_security.mentve"}}') + '</div>';
// Save per-app overrides // Save per-app overrides
if (appOverrides && Object.keys(appOverrides).length > 0) { if (appOverrides && Object.keys(appOverrides).length > 0) {
saveAllAppOverrides(); saveAllAppOverrides();
} }
} else { } else {
status.innerHTML = '<div class="alert alert-error">' + (d.error || 'Hiba') + '</div>'; status.innerHTML = '<div class="alert alert-error">' + (d.error || '{{T "common.hiba"}}') + '</div>';
} }
}) })
.catch(function(err){ .catch(function(err){
status.innerHTML = '<div class="alert alert-error">Hálózati hiba</div>'; status.innerHTML = '<div class="alert alert-error">{{T "settings_security.halozati_hiba"}}</div>';
}) })
.finally(function(){ .finally(function(){
btn.disabled = false; btn.disabled = false;
btn.textContent = 'Mentés és szinkronizálás'; btn.textContent = '{{T "settings_security.mentes_es_szinkronizalas"}}';
setTimeout(function(){ status.innerHTML = ''; }, 8000); setTimeout(function(){ status.innerHTML = ''; }, 8000);
}); });
}; };
@@ -375,7 +374,7 @@ function openDialog(opts){
.then(function(r){return r.json()}) .then(function(r){return r.json()})
.then(function(d){ .then(function(d){
var status = document.getElementById('geo-sync-status'); var status = document.getElementById('geo-sync-status');
status.textContent = d.ok ? 'Szinkronizálás elindítva...' : (d.error || 'Hiba'); status.textContent = d.ok ? '{{T "settings_security.szinkronizalas_elinditva"}}' : (d.error || '{{T "common.hiba"}}');
setTimeout(function(){ setTimeout(function(){
fetch('/api/geo/status', {headers: csrfHeaders()}) fetch('/api/geo/status', {headers: csrfHeaders()})
.then(function(r){return r.json()}) .then(function(r){return r.json()})
@@ -383,7 +382,7 @@ function openDialog(opts){
if (d.ok && d.data) { if (d.ok && d.data) {
var sync = d.data.last_sync || ''; var sync = d.data.last_sync || '';
var err = d.data.last_sync_error || ''; var err = d.data.last_sync_error || '';
status.innerHTML = sync ? ('Utolsó: ' + sync.substring(0,19).replace('T',' ')) : ''; status.innerHTML = sync ? ('{{T "settings_security.utolso"}} ' + sync.substring(0,19).replace('T',' ')) : '';
if (err) status.innerHTML += ' <span class="state-text-crit">' + escHtml(err) + '</span>'; if (err) status.innerHTML += ' <span class="state-text-crit">' + escHtml(err) + '</span>';
} }
}); });
@@ -407,13 +406,13 @@ function openDialog(opts){
<!-- Section: Recovery Info --> <!-- Section: Recovery Info -->
{{if .HasRetrievalPassword}} {{if .HasRetrievalPassword}}
<div class="settings-card"> <div class="settings-card">
<h3>Vészhelyzeti információk</h3> <h3>{{T "settings_security.veszhelyzeti_informaciok"}}</h3>
<p class="settings-card-desc"> <p class="settings-card-desc">
Ezeket az adatokat mentse el biztos helyre. Újratelepítés esetén szükség lesz rájuk a rendszer visszaállításához. {{T "settings_security.ezeket_az_adatokat_mentse_el"}}
</p> </p>
<div class="settings-grid"> <div class="settings-grid">
<div class="settings-row"> <div class="settings-row">
<span class="settings-label">Ügyfél azonosító</span> <span class="settings-label">{{T "common.ugyfel_azonosito"}}</span>
<span class="settings-value mono">{{.CustomerID}}</span> <span class="settings-value mono">{{.CustomerID}}</span>
</div> </div>
<div class="settings-row"> <div class="settings-row">
@@ -421,22 +420,20 @@ function openDialog(opts){
<span class="settings-value mono">{{.HubURL}}</span> <span class="settings-value mono">{{.HubURL}}</span>
</div> </div>
<div class="settings-row"> <div class="settings-row">
<span class="settings-label">Visszaállítási jelszó</span> <span class="settings-label">{{T "settings_security.visszaallitasi_jelszo"}}</span>
<!-- R-249: the value is NOT in this page. The old markup rendered it into a <!-- R-249: the value is NOT in this page. The old markup rendered it into a
display:none span, which hid it from the eye and from nothing else — a fetch of the display:none span, which hid it from the eye and from nothing else — a fetch of the
page returned the plaintext. „Megjelenít" now asks the server for it. --> page returned the plaintext. „Megjelenít" now asks the server for it. -->
<span class="settings-value"> <span class="settings-value">
<span id="retrieval-pw-slot" class="mono">••••••••••••••••</span> <span id="retrieval-pw-slot" class="mono">••••••••••••••••</span>
<button type="button" id="retrieval-pw-btn" class="btn btn-xs btn-outline" onclick="revealRetrievalPw()">Megjelenít</button> <button type="button" id="retrieval-pw-btn" class="btn btn-xs btn-outline" onclick="revealRetrievalPw()">{{T "settings_security.megjelenit"}}</button>
<span id="retrieval-pw-err" class="form-hint" style="display:none;color:var(--red)"></span> <span id="retrieval-pw-err" class="form-hint" style="display:none;color:var(--red)"></span>
</span> </span>
</div> </div>
<div class="settings-row"> <div class="settings-row">
<span class="settings-label">Támogatás</span> <span class="settings-label">{{T "settings_security.tamogatas"}}</span>
<span class="settings-value"> <span class="settings-value">
<a href="mailto:{{.SupportEmail}}" style="color: var(--blue);">{{.SupportEmail}}</a> {{T "settings_security.felhom_eu_kapcsolat"}}
&nbsp;|&nbsp;
<a href="{{.SupportURL}}" target="_blank" style="color: var(--blue);">felhom.eu/kapcsolat</a>
</span> </span>
</div> </div>
</div> </div>
@@ -451,7 +448,7 @@ function revealRetrievalPw() {
err.style.display = 'none'; err.style.display = 'none';
if (btn.dataset.shown === '1') { // „Elrejt" — drop the value out of the DOM again if (btn.dataset.shown === '1') { // „Elrejt" — drop the value out of the DOM again
slot.textContent = '••••••••••••••••'; slot.textContent = '••••••••••••••••';
btn.textContent = 'Megjelenít'; btn.textContent = '{{T "settings_security.megjelenit"}}';
btn.dataset.shown = ''; btn.dataset.shown = '';
return; return;
} }
@@ -463,16 +460,16 @@ function revealRetrievalPw() {
}).then(function (r) { return r.json(); }).then(function (j) { }).then(function (r) { return r.json(); }).then(function (j) {
btn.disabled = false; btn.disabled = false;
if (!j.ok) { if (!j.ok) {
err.textContent = j.error || 'A visszaállítási jelszó lekérése nem sikerült.'; err.textContent = j.error || '{{T "settings_security.a_visszaallitasi_jelszo_lekerese_nem"}}';
err.style.display = 'inline'; err.style.display = 'inline';
return; return;
} }
slot.textContent = j.data.password; slot.textContent = j.data.password;
btn.textContent = 'Elrejt'; btn.textContent = '{{T "settings_security.elrejt"}}';
btn.dataset.shown = '1'; btn.dataset.shown = '1';
}).catch(function () { }).catch(function () {
btn.disabled = false; btn.disabled = false;
err.textContent = 'A visszaállítási jelszó lekérése nem sikerült.'; err.textContent = '{{T "settings_security.a_visszaallitasi_jelszo_lekerese_nem"}}';
err.style.display = 'inline'; err.style.display = 'inline';
}); });
} }
@@ -2,20 +2,20 @@
{{template "layout_start" .}} {{template "layout_start" .}}
<div class="page-header"> <div class="page-header">
<h2>Beállítások — Rendszer</h2> <h2>{{T "settings_system.beallitasok_rendszer"}}</h2>
</div> </div>
<!-- Section A: System Configuration (read-only) --> <!-- Section A: System Configuration (read-only) -->
<div class="settings-card"> <div class="settings-card">
<h3>Rendszer konfiguráció</h3> <h3>{{T "settings_system.rendszer_konfiguracio"}}</h3>
<p class="settings-card-desc">Az üzemeltető által beállított értékek. Módosításhoz kérd az üzemeltetőt.</p> <p class="settings-card-desc">{{T "settings_system.az_uzemelteto_altal_beallitott_ertekek"}}</p>
<div class="settings-grid"> <div class="settings-grid">
<div class="settings-row"> <div class="settings-row">
<span class="settings-label">Ügyfél azonosító</span> <span class="settings-label">{{T "common.ugyfel_azonosito"}}</span>
<span class="settings-value mono">{{.CustomerID}}</span> <span class="settings-value mono">{{.CustomerID}}</span>
</div> </div>
<div class="settings-row"> <div class="settings-row">
<span class="settings-label">Ügyfél neve</span> <span class="settings-label">{{T "settings_system.ugyfel_neve"}}</span>
<span class="settings-value">{{.CustomerName}}</span> <span class="settings-value">{{.CustomerName}}</span>
</div> </div>
<div class="settings-row"> <div class="settings-row">
@@ -24,27 +24,27 @@
</div> </div>
{{if .GitRepoURL}} {{if .GitRepoURL}}
<div class="settings-row"> <div class="settings-row">
<span class="settings-label">Alkalmazás sablon forrás</span> <span class="settings-label">{{T "settings_system.alkalmazas_sablon_forras"}}</span>
<span class="settings-value mono settings-value-truncate">{{.GitRepoURL}}</span> <span class="settings-value mono settings-value-truncate">{{.GitRepoURL}}</span>
</div> </div>
{{end}} {{end}}
<div class="settings-row"> <div class="settings-row">
<span class="settings-label">Sablon szinkronizálás</span> <span class="settings-label">{{T "settings_system.sablon_szinkronizalas"}}</span>
<span class="settings-value mono">{{.GitSyncInterval}}</span> <span class="settings-value mono">{{.GitSyncInterval}}</span>
</div> </div>
<div class="settings-row"> <div class="settings-row">
<span class="settings-label">Biztonsági mentés</span> <span class="settings-label">{{T "settings_system.biztonsagi_mentes"}}</span>
<span class="settings-value">{{if .BackupEnabled}}<span class="state-text-run"><svg class="ico ico-sm"><use href="#i-check"/></svg> Aktív</span>{{else}}<span class="state-text-neutral">Inaktív</span>{{end}}</span> <span class="settings-value">{{if .BackupEnabled}}<span class="state-text-run"><svg class="ico ico-sm"><use href="#i-check"/></svg> {{T "settings_system.aktiv"}}</span>{{else}}<span class="state-text-neutral">{{T "settings_system.inaktiv"}}</span>{{end}}</span>
</div> </div>
{{if .BackupEnabled}} {{if .BackupEnabled}}
<div class="settings-row"> <div class="settings-row">
<span class="settings-label">Mentés ütemezés</span> <span class="settings-label">{{T "settings_system.mentes_utemezes"}}</span>
<span class="settings-value mono">{{.DBDumpSchedule}} / {{.ResticSchedule}}</span> <span class="settings-value mono">{{.DBDumpSchedule}} / {{.ResticSchedule}}</span>
</div> </div>
{{end}} {{end}}
<div class="settings-row"> <div class="settings-row">
<span class="settings-label">Monitoring</span> <span class="settings-label">Monitoring</span>
<span class="settings-value">{{if .MonitoringEnabled}}<span class="state-text-run"><svg class="ico ico-sm"><use href="#i-check"/></svg> Aktív</span>{{else}}<span class="state-text-neutral">Inaktív</span>{{end}}</span> <span class="settings-value">{{if .MonitoringEnabled}}<span class="state-text-run"><svg class="ico ico-sm"><use href="#i-check"/></svg> {{T "settings_system.aktiv"}}</span>{{else}}<span class="state-text-neutral">{{T "settings_system.inaktiv"}}</span>{{end}}</span>
</div> </div>
{{if .MonitoringEnabled}} {{if .MonitoringEnabled}}
<div class="settings-row"> <div class="settings-row">
@@ -53,78 +53,78 @@
</div> </div>
{{end}} {{end}}
<div class="settings-row"> <div class="settings-row">
<span class="settings-label">Hub jelentés</span> <span class="settings-label">{{T "settings_system.hub_jelentes"}}</span>
<span class="settings-value">{{if .HubEnabled}}<span class="state-text-run"><svg class="ico ico-sm"><use href="#i-check"/></svg> Aktív</span>{{else}}–{{end}}</span> <span class="settings-value">{{if .HubEnabled}}<span class="state-text-run"><svg class="ico ico-sm"><use href="#i-check"/></svg> {{T "settings_system.aktiv"}}</span>{{else}}–{{end}}</span>
</div> </div>
</div> </div>
</div> </div>
<!-- Section: Version & Update --> <!-- Section: Version & Update -->
<div class="settings-card"> <div class="settings-card">
<h3>Verzió és frissítés</h3> <h3>{{T "settings_system.verzio_es_frissites"}}</h3>
<div class="settings-grid"> <div class="settings-grid">
<div class="settings-row"> <div class="settings-row">
<span class="settings-label">Jelenlegi verzió</span> <span class="settings-label">{{T "settings_system.jelenlegi_verzio"}}</span>
<span class="settings-value mono">{{.Version}}</span> <span class="settings-value mono">{{.Version}}</span>
</div> </div>
{{if .SelfUpdateEnabled}} {{if .SelfUpdateEnabled}}
{{if .LatestVersion}} {{if .LatestVersion}}
<div class="settings-row"> <div class="settings-row">
<span class="settings-label">Legújabb verzió</span> <span class="settings-label">{{T "settings_system.legujabb_verzio"}}</span>
<span class="settings-value mono"> <span class="settings-value mono">
{{.LatestVersion}} {{.LatestVersion}}
{{if .UpdateAvailable}} {{if .UpdateAvailable}}
<span class="state-text-run" style="margin-left:0.5em;">● Frissítés elérhető</span> <span class="state-text-run" style="margin-left:0.5em;">{{T "settings_system.frissites_elerheto"}}</span>
{{else}} {{else}}
<span style="margin-left:0.5em; color:var(--text-3);">— naprakész</span> <span style="margin-left:0.5em; color:var(--text-3);">{{T "settings_system.naprakesz"}}</span>
{{end}} {{end}}
</span> </span>
</div> </div>
{{end}} {{end}}
{{if .LastCheckTime}} {{if .LastCheckTime}}
<div class="settings-row"> <div class="settings-row">
<span class="settings-label">Utolsó ellenőrzés</span> <span class="settings-label">{{T "settings_system.utolso_ellenorzes"}}</span>
<span class="settings-value mono">{{.LastCheckTime}}</span> <span class="settings-value mono">{{.LastCheckTime}}</span>
</div> </div>
{{end}} {{end}}
<div class="settings-row"> <div class="settings-row">
<span class="settings-label">Registry</span> <span class="settings-label">Registry</span>
<span class="settings-value">{{if .RegistryAnonymous}}nyilvános (hitelesítés nélkül){{else}}hitelesített{{end}}</span> <span class="settings-value">{{if .RegistryAnonymous}}{{T "settings_system.nyilvanos_hitelesites_nelkul"}}{{else}}{{T "settings_system.hitelesitett"}}{{end}}</span>
</div> </div>
{{if .LastCheckError}} {{if .LastCheckError}}
<div class="settings-row"> <div class="settings-row">
<span class="settings-label">Hiba</span> <span class="settings-label">{{T "common.hiba"}}</span>
<span class="settings-value state-text-crit">{{.LastCheckError}}</span> <span class="settings-value state-text-crit">{{.LastCheckError}}</span>
</div> </div>
{{end}} {{end}}
<div class="settings-row"> <div class="settings-row">
<span class="settings-label">Automatikus frissítés</span> <span class="settings-label">{{T "settings_system.automatikus_frissites"}}</span>
<span class="settings-value"> <span class="settings-value">
{{if .AutoUpdateEnabled}}<span class="state-text-run"><svg class="ico ico-sm"><use href="#i-check"/></svg> Aktív</span> <span class="mono">({{.AutoUpdateTime}})</span>{{else}}–{{end}} {{if .AutoUpdateEnabled}}<span class="state-text-run"><svg class="ico ico-sm"><use href="#i-check"/></svg> {{T "settings_system.aktiv"}}</span> <span class="mono">({{.AutoUpdateTime}})</span>{{else}}–{{end}}
</span> </span>
</div> </div>
{{if .ControllerFloor}} {{if .ControllerFloor}}
<div class="settings-row"> <div class="settings-row">
<span class="settings-label">Minimális verzió (üzemeltető)</span> <span class="settings-label">{{T "settings_system.minimalis_verzio_uzemelteto"}}</span>
<span class="settings-value mono"> <span class="settings-value mono">
{{.ControllerFloor}} {{.ControllerFloor}}
<span style="margin-left:0.5em; color:#888;">— a rendszer automatikusan erre a verzióra frissít, ha régebbi</span> <span style="margin-left:0.5em; color:#888;">{{T "settings_system.a_rendszer_automatikusan_erre_a"}}</span>
</span> </span>
</div> </div>
{{end}} {{end}}
{{if .UpdateRunning}} {{if .UpdateRunning}}
<div class="settings-row"> <div class="settings-row">
<span class="settings-label">Állapot</span> <span class="settings-label">{{T "settings_system.allapot"}}</span>
<span class="settings-value state-text-progress" id="auto-update-status"><svg class="ico ico-sm ico-spin"><use href="#i-rotate-cw"/></svg> Frissítés folyamatban — a vezérlő hamarosan újraindul…</span> <span class="settings-value state-text-progress" id="auto-update-status"><svg class="ico ico-sm ico-spin"><use href="#i-rotate-cw"/></svg> {{T "settings_system.frissites_folyamatban_a_vezerlo_hamarosa"}}</span>
</div> </div>
{{end}} {{end}}
{{with .LastUpdateState}} {{with .LastUpdateState}}
<div class="settings-row"> <div class="settings-row">
<span class="settings-label">Utolsó frissítés</span> <span class="settings-label">{{T "settings_system.utolso_frissites"}}</span>
<span class="settings-value"> <span class="settings-value">
{{if eq .Status "success"}}<span class="state-text-run"><svg class="ico ico-sm"><use href="#i-check"/></svg> Sikeres</span> ({{.PreviousVersion}} → {{.TargetVersion}}) {{if eq .Status "success"}}<span class="state-text-run"><svg class="ico ico-sm"><use href="#i-check"/></svg> {{T "settings_system.sikeres"}}</span> ({{.PreviousVersion}} → {{.TargetVersion}})
{{else if eq .Status "failed"}}<span class="state-text-crit"><svg class="ico ico-sm"><use href="#i-x"/></svg> Sikertelen</span> — {{.Error}} {{else if eq .Status "failed"}}<span class="state-text-crit"><svg class="ico ico-sm"><use href="#i-x"/></svg> {{T "settings_system.sikertelen"}}</span> — {{.Error}}
{{else if eq .Status "pending"}}<span class="state-text-progress"><svg class="ico ico-sm ico-spin"><use href="#i-rotate-cw"/></svg> Folyamatban</span> {{else if eq .Status "pending"}}<span class="state-text-progress"><svg class="ico ico-sm ico-spin"><use href="#i-rotate-cw"/></svg> {{T "settings_system.folyamatban"}}</span>
{{end}} {{end}}
</span> </span>
</div> </div>
@@ -132,9 +132,9 @@
<div class="settings-row" style="padding-top: 0.5em;"> <div class="settings-row" style="padding-top: 0.5em;">
<span class="settings-label"></span> <span class="settings-label"></span>
<span class="settings-value"> <span class="settings-value">
<button class="btn btn-secondary btn-sm" id="btn-check-update" onclick="checkUpdate()">Frissítés keresése</button> <button class="btn btn-secondary btn-sm" id="btn-check-update" onclick="checkUpdate()">{{T "settings_system.frissites_keresese"}}</button>
{{if .UpdateAvailable}} {{if .UpdateAvailable}}
<button class="btn btn-primary btn-sm" id="btn-trigger-update" onclick="triggerUpdate()" style="margin-left:0.5em;">Frissítés telepítése</button> <button class="btn btn-primary btn-sm" id="btn-trigger-update" onclick="triggerUpdate()" style="margin-left:0.5em;">{{T "settings_system.frissites_telepitese"}}</button>
{{end}} {{end}}
<span id="update-status-msg" style="margin-left:0.5em; display:none;"></span> <span id="update-status-msg" style="margin-left:0.5em; display:none;"></span>
</span> </span>
@@ -147,50 +147,50 @@
stored NOWHERE (S-5: a DHCP lease persisted anywhere eventually misdirects people); a missing stored NOWHERE (S-5: a DHCP lease persisted anywhere eventually misdirects people); a missing
value renders „—" because an address-less row beats a wrong address. --> value renders „—" because an address-less row beats a wrong address. -->
<div class="settings-card"> <div class="settings-card">
<h3>Hálózat</h3> <h3>{{T "settings_system.halozat"}}</h3>
<div class="settings-grid"> <div class="settings-grid">
<div class="settings-row"> <div class="settings-row">
<span class="settings-label">Helyi cím (LAN)</span> <span class="settings-label">{{T "settings_system.helyi_cim_lan"}}</span>
<span class="settings-value mono">{{if .NetLANAddress}}{{.NetLANAddress}}{{else}}— <span style="color:var(--text-3);">nem állapítható meg</span>{{end}}</span> <span class="settings-value mono">{{if .NetLANAddress}}{{.NetLANAddress}}{{else}}— <span style="color:var(--text-3);">{{T "settings_system.nem_allapithato_meg"}}</span>{{end}}</span>
</div> </div>
{{if .NetSMBName}} {{if .NetSMBName}}
<div class="settings-row"> <div class="settings-row">
<span class="settings-label">Hálózati név</span> <span class="settings-label">{{T "settings_system.halozati_nev"}}</span>
<span class="settings-value mono">\\{{.NetSMBName}}</span> <span class="settings-value mono">\\{{.NetSMBName}}</span>
</div> </div>
{{end}} {{end}}
<div class="settings-row"> <div class="settings-row">
<span class="settings-label">Átjáró</span> <span class="settings-label">{{T "settings_system.atjaro"}}</span>
<span class="settings-value mono">{{if .NetGateway}}{{.NetGateway}}{{else}}— <span style="color:var(--text-3);">nem állapítható meg</span>{{end}}</span> <span class="settings-value mono">{{if .NetGateway}}{{.NetGateway}}{{else}}— <span style="color:var(--text-3);">{{T "settings_system.nem_allapithato_meg"}}</span>{{end}}</span>
</div> </div>
</div> </div>
<p class="settings-card-desc" style="margin-top:0.5em;">Ezek az értékek a helyi hálózattól függenek és változhatnak. Távoli hibaelhárításnál ezt az oldalt kérjük felolvasni.</p> <p class="settings-card-desc" style="margin-top:0.5em;">{{T "settings_system.ezek_az_ertekek_a_helyi"}}</p>
</div> </div>
<div class="settings-card"> <div class="settings-card">
<h3>Szerver memória (RAM)</h3> <h3>{{T "settings_system.szerver_memoria_ram"}}</h3>
{{if .MemorySupported}} {{if .MemorySupported}}
{{if .MemoryReachable}} {{if .MemoryReachable}}
<div class="settings-grid"> <div class="settings-grid">
<div class="settings-row"> <div class="settings-row">
<span class="settings-label">Jelenlegi memória</span> <span class="settings-label">{{T "settings_system.jelenlegi_memoria"}}</span>
<span class="settings-value mono">{{.MemoryAllocatedMB}} MB</span> <span class="settings-value mono">{{.MemoryAllocatedMB}} MB</span>
</div> </div>
<div class="settings-row"> <div class="settings-row">
<span class="settings-label">Felhasznált</span> <span class="settings-label">{{T "settings_system.felhasznalt"}}</span>
<span class="settings-value mono">{{.MemoryUsageMB}} MB</span> <span class="settings-value mono">{{.MemoryUsageMB}} MB</span>
</div> </div>
<div class="settings-row"> <div class="settings-row">
<span class="settings-label">Megengedett tartomány</span> <span class="settings-label">{{T "settings_system.megengedett_tartomany"}}</span>
<span class="settings-value mono">{{.MemoryMinMB}} MB – {{.MemoryMaxMB}} MB</span> <span class="settings-value mono">{{.MemoryMinMB}} MB – {{.MemoryMaxMB}} MB</span>
</div> </div>
<div class="settings-row" style="padding-top: 0.5em;"> <div class="settings-row" style="padding-top: 0.5em;">
<span class="settings-label">Új méret (MB)</span> <span class="settings-label">{{T "settings_system.uj_meret_mb"}}</span>
<span class="settings-value"> <span class="settings-value">
<input type="number" id="mem-input" class="input mono" style="width:8em;" <input type="number" id="mem-input" class="input mono" style="width:8em;"
value="{{.MemoryAllocatedMB}}" min="{{.MemoryMinMB}}" max="{{.MemoryMaxMB}}" step="256" value="{{.MemoryAllocatedMB}}" min="{{.MemoryMinMB}}" max="{{.MemoryMaxMB}}" step="256"
data-current="{{.MemoryAllocatedMB}}"> data-current="{{.MemoryAllocatedMB}}">
<button class="btn btn-primary btn-sm" id="btn-mem-resize" onclick="resizeMemory()" style="margin-left:0.5em;">Átméretezés</button> <button class="btn btn-primary btn-sm" id="btn-mem-resize" onclick="resizeMemory()" style="margin-left:0.5em;">{{T "settings_system.atmeretezes"}}</button>
<span id="mem-status-msg" style="margin-left:0.5em; display:none;"></span> <span id="mem-status-msg" style="margin-left:0.5em; display:none;"></span>
</span> </span>
</div> </div>
@@ -198,14 +198,14 @@
{{else}} {{else}}
<div class="settings-grid"> <div class="settings-grid">
<div class="settings-row"> <div class="settings-row">
<span class="settings-label">Jelenlegi memória</span> <span class="settings-label">{{T "settings_system.jelenlegi_memoria"}}</span>
<span class="settings-value mono">{{if .MemoryProcMB}}{{.MemoryProcMB}} MB{{else}}n/a{{end}}</span> <span class="settings-value mono">{{if .MemoryProcMB}}{{.MemoryProcMB}} MB{{else}}n/a{{end}}</span>
</div> </div>
</div> </div>
<p class="settings-card-desc">A kiszolgáló ügynök jelenleg nem elérhető — próbáld meg később.</p> <p class="settings-card-desc">{{T "settings_system.a_kiszolgalo_ugynok_jelenleg_nem"}}</p>
{{end}} {{end}}
{{else}} {{else}}
<p class="settings-card-desc">A memória átméretezéséhez a kiszolgáló rendszerfrissítése szükséges.</p> <p class="settings-card-desc">{{T "settings_system.a_memoria_atmeretezesehez_a_kiszolgalo"}}</p>
{{end}} {{end}}
</div> </div>
@@ -218,10 +218,10 @@ function openDialog(opts){
var root=document.getElementById('dialog-root'); var root=document.getElementById('dialog-root');
if(!root) return; if(!root) return;
root.innerHTML='<div class="confirm-overlay" onclick="if(event.target===this)document.getElementById(\'dialog-cancel\').click()"><div class="confirm-box">' root.innerHTML='<div class="confirm-overlay" onclick="if(event.target===this)document.getElementById(\'dialog-cancel\').click()"><div class="confirm-box">'
+'<h3>'+escText(opts.title||'Megerősítés')+'</h3>' +'<h3>'+escText(opts.title||'{{T "settings_system.megerosites"}}')+'</h3>'
+'<p style="white-space:pre-line">'+escText(opts.message||'')+'</p>' +'<p style="white-space:pre-line">'+escText(opts.message||'')+'</p>'
+'<div class="form-actions"><button id="dialog-go" class="btn btn-primary">'+escText(opts.confirmLabel||'Megerősítés')+'</button>' +'<div class="form-actions"><button id="dialog-go" class="btn btn-primary">'+escText(opts.confirmLabel||'{{T "settings_system.megerosites"}}')+'</button>'
+'<button type="button" class="btn btn-outline" id="dialog-cancel">Mégsem</button></div>' +'<button type="button" class="btn btn-outline" id="dialog-cancel">{{T "common.megsem"}}</button></div>'
+'</div></div>'; +'</div></div>';
document.getElementById('dialog-go').onclick=function(){ closeDialog(); if(opts.onConfirm) opts.onConfirm(); }; document.getElementById('dialog-go').onclick=function(){ closeDialog(); if(opts.onConfirm) opts.onConfirm(); };
document.getElementById('dialog-cancel').onclick=function(){ closeDialog(); if(opts.onCancel) opts.onCancel(); }; document.getElementById('dialog-cancel').onclick=function(){ closeDialog(); if(opts.onCancel) opts.onCancel(); };
@@ -231,7 +231,7 @@ function checkUpdate() {
var btn = document.getElementById('btn-check-update'); var btn = document.getElementById('btn-check-update');
var msg = document.getElementById('update-status-msg'); var msg = document.getElementById('update-status-msg');
btn.disabled = true; btn.disabled = true;
btn.textContent = 'Ellenőrzés...'; btn.textContent = '{{T "settings_system.ellenorzes"}}';
msg.style.display = 'none'; msg.style.display = 'none';
fetch('/api/selfupdate/check', {method:'POST', headers: csrfHeaders()}) fetch('/api/selfupdate/check', {method:'POST', headers: csrfHeaders()})
.then(function(r) { return r.json(); }) .then(function(r) { return r.json(); })
@@ -239,23 +239,23 @@ function checkUpdate() {
if (data.ok) { if (data.ok) {
location.reload(); location.reload();
} else { } else {
msg.textContent = data.error || 'Hiba történt'; msg.textContent = data.error || '{{T "common.hiba_tortent"}}';
msg.style.display = 'inline'; msg.style.display = 'inline';
btn.disabled = false; btn.disabled = false;
btn.textContent = 'Frissítés keresése'; btn.textContent = '{{T "settings_system.frissites_keresese"}}';
} }
}) })
.catch(function() { .catch(function() {
msg.textContent = 'Kapcsolódási hiba'; msg.textContent = '{{T "settings_system.kapcsolodasi_hiba"}}';
msg.style.display = 'inline'; msg.style.display = 'inline';
btn.disabled = false; btn.disabled = false;
btn.textContent = 'Frissítés keresése'; btn.textContent = '{{T "settings_system.frissites_keresese"}}';
}); });
} }
function triggerUpdate() { function triggerUpdate() {
openDialog({title:'Vezérlő frissítése', confirmLabel:'Frissítés', openDialog({title:'{{T "settings_system.vezerlo_frissitese"}}', confirmLabel:'{{T "settings_system.frissites"}}',
message:'Biztosan frissíti a controllert?\n\nA folyamat alatt a vezérlőpult rövid időre elérhetetlenné válik.', message:'{{T "settings_system.biztosan_frissiti_a_controllert_n"}}',
onConfirm:doTriggerUpdate}); onConfirm:doTriggerUpdate});
} }
function doTriggerUpdate() { function doTriggerUpdate() {
@@ -263,25 +263,25 @@ function doTriggerUpdate() {
var checkBtn = document.getElementById('btn-check-update'); var checkBtn = document.getElementById('btn-check-update');
var msg = document.getElementById('update-status-msg'); var msg = document.getElementById('update-status-msg');
btn.disabled = true; btn.disabled = true;
btn.textContent = 'Frissítés...'; btn.textContent = '{{T "settings_system.frissites_2"}}';
if (checkBtn) checkBtn.disabled = true; if (checkBtn) checkBtn.disabled = true;
msg.textContent = 'Frissítés folyamatban...'; msg.textContent = '{{T "settings_system.frissites_folyamatban"}}';
msg.style.display = 'inline'; msg.style.display = 'inline';
fetch('/api/selfupdate/update', {method:'POST', headers: csrfHeaders()}) fetch('/api/selfupdate/update', {method:'POST', headers: csrfHeaders()})
.then(function(r) { return r.json(); }) .then(function(r) { return r.json(); })
.then(function(data) { .then(function(data) {
if (data.ok) { if (data.ok) {
msg.textContent = 'Újraindulás...'; msg.textContent = '{{T "settings_system.ujraindulas"}}';
pollUntilBack(); pollUntilBack();
} else { } else {
msg.textContent = data.error || 'Hiba történt'; msg.textContent = data.error || '{{T "common.hiba_tortent"}}';
btn.disabled = false; btn.disabled = false;
btn.textContent = 'Frissítés telepítése'; btn.textContent = '{{T "settings_system.frissites_telepitese"}}';
if (checkBtn) checkBtn.disabled = false; if (checkBtn) checkBtn.disabled = false;
} }
}) })
.catch(function() { .catch(function() {
msg.textContent = 'Kapcsolódási hiba'; msg.textContent = '{{T "settings_system.kapcsolodasi_hiba"}}';
pollUntilBack(); pollUntilBack();
}); });
} }
@@ -291,12 +291,12 @@ function resizeMemory() {
var target = parseInt(input.value, 10); var target = parseInt(input.value, 10);
var current = parseInt(input.getAttribute('data-current'), 10); var current = parseInt(input.getAttribute('data-current'), 10);
if (isNaN(target) || target <= 0) { if (isNaN(target) || target <= 0) {
showMemMsg('Érvénytelen memória érték', true); showMemMsg('{{T "settings_system.ervenytelen_memoria_ertek"}}', true);
return; return;
} }
if (target < current) { if (target < current) {
openDialog({title:'Memória csökkentése', confirmLabel:'Csökkentés', openDialog({title:'{{T "settings_system.memoria_csokkentese"}}', confirmLabel:'{{T "settings_system.csokkentes"}}',
message:'A memória csökkentése hatással lehet a futó alkalmazások teljesítményére. Biztosan folytatod?', message:'{{T "settings_system.a_memoria_csokkentese_hatassal_lehet"}}',
onConfirm:function(){ doResizeMemory(target); }}); onConfirm:function(){ doResizeMemory(target); }});
return; return;
} }
@@ -305,7 +305,7 @@ function resizeMemory() {
function doResizeMemory(target) { function doResizeMemory(target) {
var btn = document.getElementById('btn-mem-resize'); var btn = document.getElementById('btn-mem-resize');
btn.disabled = true; btn.disabled = true;
btn.textContent = 'Átméretezés...'; btn.textContent = '{{T "settings_system.atmeretezes_2"}}';
fetch('/api/system/memory/resize', { fetch('/api/system/memory/resize', {
method:'POST', headers: Object.assign({'Content-Type':'application/json'}, csrfHeaders()), method:'POST', headers: Object.assign({'Content-Type':'application/json'}, csrfHeaders()),
body: JSON.stringify({memory_mb: target}) body: JSON.stringify({memory_mb: target})
@@ -313,18 +313,18 @@ function doResizeMemory(target) {
.then(function(r) { return r.json(); }) .then(function(r) { return r.json(); })
.then(function(data) { .then(function(data) {
if (data.ok) { if (data.ok) {
showMemMsg(data.error || data.message || 'Kész.', false); showMemMsg(data.error || data.message || '{{T "settings_system.kesz"}}', false);
setTimeout(function(){ location.reload(); }, 1200); setTimeout(function(){ location.reload(); }, 1200);
} else { } else {
showMemMsg(data.error || 'A művelet nem sikerült', true); showMemMsg(data.error || '{{T "settings_system.a_muvelet_nem_sikerult"}}', true);
btn.disabled = false; btn.disabled = false;
btn.textContent = 'Átméretezés'; btn.textContent = '{{T "settings_system.atmeretezes"}}';
} }
}) })
.catch(function() { .catch(function() {
showMemMsg('Kapcsolódási hiba', true); showMemMsg('{{T "settings_system.kapcsolodasi_hiba"}}', true);
btn.disabled = false; btn.disabled = false;
btn.textContent = 'Átméretezés'; btn.textContent = '{{T "settings_system.atmeretezes"}}';
}); });
} }
function showMemMsg(text, isErr) { function showMemMsg(text, isErr) {
@@ -357,50 +357,49 @@ pollUntilBack();
<!-- Section: Controller restart (self-serve) — always available (not gated on RetrievalPassword) --> <!-- Section: Controller restart (self-serve) — always available (not gated on RetrievalPassword) -->
<div class="settings-card"> <div class="settings-card">
<h3>Vezérlő újraindítása</h3> <h3>{{T "settings_system.vezerlo_ujrainditasa"}}</h3>
<p class="settings-card-desc"> <p class="settings-card-desc">
Ha a vezérlő hibásan működik, itt biztonságosan újraindíthatja — nem kell az egész szervert újraindítani. {{T "settings_system.ha_a_vezerlo_hibasan_mukodik"}}
Az alkalmazásai futnak tovább; csak a vezérlő indul újra (néhány másodperc).
</p> </p>
<div id="restart-status"></div> <div id="restart-status"></div>
<button type="button" class="btn btn-outline" id="btn-restart-controller" onclick="restartController()">Vezérlő újraindítása</button> <button type="button" class="btn btn-outline" id="btn-restart-controller" onclick="restartController()">{{T "settings_system.vezerlo_ujrainditasa"}}</button>
</div> </div>
<!-- Section: Full server (guest) restart — a deliberate maintenance affordance, sibling to the controller restart --> <!-- Section: Full server (guest) restart — a deliberate maintenance affordance, sibling to the controller restart -->
<div class="settings-card"> <div class="settings-card">
<h3>Kiszolgáló újraindítása</h3> <h3>{{T "settings_system.kiszolgalo_ujrainditasa"}}</h3>
<p class="settings-card-desc"> <p class="settings-card-desc">
Az egész kiszolgáló (szerver) újraindítása. Minden alkalmazás rövid időre leáll, és a vezérlőpult kb. 30 másodpercig nem elérhető. Akkor használja, ha a teljes rendszer újraindítására van szükség — egyébként a fenti „Vezérlő újraindítása” elegendő. {{T "settings_system.az_egesz_kiszolgalo_szerver_ujrainditasa"}}
</p> </p>
<div id="server-restart-status"></div> <div id="server-restart-status"></div>
<button type="button" class="btn btn-outline" id="btn-restart-server" onclick="restartServer()">Kiszolgáló újraindítása</button> <button type="button" class="btn btn-outline" id="btn-restart-server" onclick="restartServer()">{{T "settings_system.kiszolgalo_ujrainditasa"}}</button>
</div> </div>
<script> <script>
function restartController() { function restartController() {
openDialog({title:'Vezérlő újraindítása', confirmLabel:'Újraindítás', openDialog({title:'{{T "settings_system.vezerlo_ujrainditasa"}}', confirmLabel:'{{T "common.ujrainditas"}}',
message:'Biztosan újraindítja a vezérlőt? A művelet néhány másodpercig tart, és a felület rövid időre elérhetetlen lesz.', message:'{{T "settings_system.biztosan_ujrainditja_a_vezerlot_a"}}',
onConfirm:doRestartController}); onConfirm:doRestartController});
} }
function doRestartController() { function doRestartController() {
var btn = document.getElementById('btn-restart-controller'); var btn = document.getElementById('btn-restart-controller');
var status = document.getElementById('restart-status'); var status = document.getElementById('restart-status');
btn.disabled = true; btn.disabled = true;
status.innerHTML = '<div class="alert alert-info">Újraindítás folyamatban… újracsatlakozás…</div>'; status.innerHTML = '<div class="alert alert-info">{{T "settings_system.ujrainditas_folyamatban_ujracsatlakozas"}}</div>';
fetch('/api/selfrestart', { method: 'POST', headers: csrfHeaders() }) fetch('/api/selfrestart', { method: 'POST', headers: csrfHeaders() })
.then(function(){ pollRestart(0); }) .then(function(){ pollRestart(0); })
.catch(function(){ pollRestart(0); }); // connection may drop as the process exits — poll regardless .catch(function(){ pollRestart(0); }); // connection may drop as the process exits — poll regardless
} }
function restartServer() { function restartServer() {
openDialog({title:'Kiszolgáló újraindítása', confirmLabel:'Újraindítás', openDialog({title:'{{T "settings_system.kiszolgalo_ujrainditasa"}}', confirmLabel:'{{T "common.ujrainditas"}}',
message:'Biztosan újraindítja a kiszolgálót? Az alkalmazások és a vezérlőpult kb. 30 másodpercre elérhetetlenné válnak.', message:'{{T "settings_system.biztosan_ujrainditja_a_kiszolgalot_az"}}',
onConfirm:doRestartServer}); onConfirm:doRestartServer});
} }
function doRestartServer() { function doRestartServer() {
var btn = document.getElementById('btn-restart-server'); var btn = document.getElementById('btn-restart-server');
var status = document.getElementById('server-restart-status'); var status = document.getElementById('server-restart-status');
if (btn) btn.disabled = true; if (btn) btn.disabled = true;
if (status) status.innerHTML = '<div class="alert alert-info">Újraindítás folyamatban… a vezérlőpult néhány másodperc múlva újratölt.</div>'; if (status) status.innerHTML = '<div class="alert alert-info">{{T "settings_system.ujrainditas_folyamatban_a_vezerlopult_ne"}}</div>';
fetch('/api/server/reboot', { method: 'POST', headers: csrfHeaders() }) fetch('/api/server/reboot', { method: 'POST', headers: csrfHeaders() })
.then(function(){ pollRestart(0); }) .then(function(){ pollRestart(0); })
.catch(function(){ pollRestart(0); }); // the guest reboot drops the connection — poll regardless .catch(function(){ pollRestart(0); }); // the guest reboot drops the connection — poll regardless
@@ -408,7 +407,7 @@ function doRestartServer() {
function pollRestart(attempt) { function pollRestart(attempt) {
if (attempt > 60) { // ~2 min cap — never leave the user on a dead page silently if (attempt > 60) { // ~2 min cap — never leave the user on a dead page silently
document.getElementById('restart-status').innerHTML = document.getElementById('restart-status').innerHTML =
'<div class="alert alert-error">Az újraindítás a vártnál tovább tart. Töltse újra az oldalt kézzel.</div>'; '<div class="alert alert-error">{{T "settings_system.az_ujrainditas_a_vartnal_tovabb"}}</div>';
return; return;
} }
setTimeout(function(){ setTimeout(function(){
+31 -31
View File
@@ -2,18 +2,18 @@
{{template "layout_start" .}} {{template "layout_start" .}}
<div class="page-header"> <div class="page-header">
<h2>Alkalmazások</h2> <h2>{{T "stacks.alkalmazasok"}}</h2>
<span class="domain-badge">{{.Domain}}</span> <span class="domain-badge">{{.Domain}}</span>
<a href="/import" class="btn btn-sm btn-outline" title="Alkalmazás visszaállítása exportált csomagból">Importálás</a> <a href="/import" class="btn btn-sm btn-outline" title="{{T "stacks.alkalmazas_visszaallitasa_exportalt_csom"}}">{{T "stacks.importalas"}}</a>
<button class="btn btn-sm btn-outline" id="sync-btn" onclick="syncTemplates()" title="Sablonok frissítése a központi katalógusból">↻ Sablonok frissítése</button> <button class="btn btn-sm btn-outline" id="sync-btn" onclick="syncTemplates()" title="{{T "stacks.sablonok_frissitese_a_kozponti_katalogus"}}">{{T "stacks.sablonok_frissitese"}}</button>
</div> </div>
<div id="sync-toast" class="sync-toast" style="display:none"></div> <div id="sync-toast" class="sync-toast" style="display:none"></div>
<div class="filter-bar" id="filter-bar"> <div class="filter-bar" id="filter-bar">
<button class="filter-btn active" data-filter="all">Mind <span class="filter-count" id="count-all"></span></button> <button class="filter-btn active" data-filter="all">{{T "stacks.mind"}} <span class="filter-count" id="count-all"></span></button>
<button class="filter-btn" data-filter="running">Futó <span class="filter-count" id="count-running"></span></button> <button class="filter-btn" data-filter="running">{{T "stacks.futo"}} <span class="filter-count" id="count-running"></span></button>
<button class="filter-btn" data-filter="stopped">Leállítva <span class="filter-count" id="count-stopped"></span></button> <button class="filter-btn" data-filter="stopped">{{T "stacks.leallitva"}} <span class="filter-count" id="count-stopped"></span></button>
<button class="filter-btn" data-filter="available">Telepíthető <span class="filter-count" id="count-available"></span></button> <button class="filter-btn" data-filter="available">{{T "stacks.telepitheto"}} <span class="filter-count" id="count-available"></span></button>
</div> </div>
<div class="stack-grid"> <div class="stack-grid">
@@ -32,18 +32,18 @@
{{$subdomain}}.{{$.Domain}} ↗ {{$subdomain}}.{{$.Domain}} ↗
</a> </a>
{{if and .Deployed (routeUnpublished .State)}} {{if and .Deployed (routeUnpublished .State)}}
<span class="route-unpublished" title="A proxy (Traefik) csak egészséges konténerhez publikál nyilvános útvonalat. Amíg az alkalmazás nem egészséges, az URL nem érhető el (404), pedig a konténer fut."><svg class="ico ico-sm"><use href="#i-triangle-alert"/></svg> URL nem elérhető – útvonal nincs publikálva</span> <span class="route-unpublished" title="{{T "stacks.a_proxy_traefik_csak_egeszseges"}}"><svg class="ico ico-sm"><use href="#i-triangle-alert"/></svg> {{T "stacks.url_nem_elerheto_utvonal_nincs"}}</span>
{{end}} {{end}}
{{end}} {{end}}
</div> </div>
</div> </div>
<span class="tag tag-{{stateColor .State}}"><span class="dot"></span>{{stateLabel .State}}</span> <span class="tag tag-{{stateColor .State}}"><span class="dot"></span>{{stateLabel .State}}</span>
{{if .Orphaned}}<span class="tag tag-warn">Elavult</span>{{end}} {{if .Orphaned}}<span class="tag tag-warn">{{T "common.elavult"}}</span>{{end}}
{{template "meta_badge" (lifecycleBadge .Meta)}} {{template "meta_badge" (lifecycleBadge .Meta)}}
{{template "meta_badge" (updateBadge .)}} {{template "meta_badge" (updateBadge .)}}
{{$ms := index $.MissingStorage .Name}}{{if $ms}}<span class="tag tag-warn" title="Az alkalmazás adattárolója nem elérhető. Csatlakoztasd újra a meghajtót, vagy helyezd át az adatokat."><svg class="ico ico-sm"><use href="#i-triangle-alert"/></svg>Hiányzó tárhely: {{$ms}}</span>{{end}} {{$ms := index $.MissingStorage .Name}}{{if $ms}}<span class="tag tag-warn" title="{{T "stacks.az_alkalmazas_adattaroloja_nem_elerheto"}}"><svg class="ico ico-sm"><use href="#i-triangle-alert"/></svg>{{T "stacks.hianyzo_tarhely"}}</span>{{end}}
{{$ns := index $.NetworkStubs .Name}}{{if $ns}}<span class="tag tag-warn" title="Az alkalmazás környezetében a hálózati tárhely helyén üres helyi könyvtár van — az adatok nem a NAS-ra kerülnek. Jelezze az üzemeltetőnek."><svg class="ico ico-sm"><use href="#i-triangle-alert"/></svg>Hálózati tárhely hibás — az alkalmazás nem a NAS-t látja</span>{{end}} {{$ns := index $.NetworkStubs .Name}}{{if $ns}}<span class="tag tag-warn" title="{{T "stacks.az_alkalmazas_kornyezeteben_a_halozati"}}"><svg class="ico ico-sm"><use href="#i-triangle-alert"/></svg>{{T "stacks.halozati_tarhely_hibas_az_alkalmazas"}}</span>{{end}}
{{$nw := index $.NetworkWarnings .Name}}{{if $nw}}<span class="tag tag-warn" title="A hálózati tárhely (NAS) jelenleg nem érhető el. Az alkalmazás fut; az adatok elérése a NAS visszatértével helyreáll."><svg class="ico ico-sm"><use href="#i-triangle-alert"/></svg>Hálózati tárhely nem elérhető: {{$nw}}</span>{{end}} {{$nw := index $.NetworkWarnings .Name}}{{if $nw}}<span class="tag tag-warn" title="{{T "stacks.a_halozati_tarhely_nas_jelenleg"}}"><svg class="ico ico-sm"><use href="#i-triangle-alert"/></svg>{{T "stacks.halozati_tarhely_nem_elerheto"}}</span>{{end}}
</div> </div>
{{if $im}} {{if $im}}
@@ -54,10 +54,10 @@
<div class="metarows"> <div class="metarows">
{{if .Meta.Resources.MemRequest}}<span class="metarow"><svg class="ico"><use href="#i-memory-stick"/></svg><span class="mono">~{{.Meta.Resources.MemRequest}}</span></span>{{end}} {{if .Meta.Resources.MemRequest}}<span class="metarow"><svg class="ico"><use href="#i-memory-stick"/></svg><span class="mono">~{{.Meta.Resources.MemRequest}}</span></span>{{end}}
{{if .Meta.Resources.PiCompatible}}<span class="metarow"><svg class="ico"><use href="#i-check"/></svg>Pi kompatibilis</span>{{end}} {{if .Meta.Resources.PiCompatible}}<span class="metarow"><svg class="ico"><use href="#i-check"/></svg>{{T "common.pi_kompatibilis"}}</span>{{end}}
{{if .Meta.Resources.NeedsHDD}}<span class="metarow"><svg class="ico"><use href="#i-hard-drive"/></svg>HDD szükséges</span>{{end}} {{if .Meta.Resources.NeedsHDD}}<span class="metarow"><svg class="ico"><use href="#i-hard-drive"/></svg>{{T "common.hdd_szukseges"}}</span>{{end}}
{{if .Meta.Resources.HungarianUI}}<span class="metarow"><svg class="ico"><use href="#i-check"/></svg>Magyar felület</span>{{end}} {{if .Meta.Resources.HungarianUI}}<span class="metarow"><svg class="ico"><use href="#i-check"/></svg>{{T "common.magyar_felulet"}}</span>{{end}}
{{if and .Deployed (index $.StorageLabels .Name)}}<span class="metarow" title="Adattároló: {{index $.StorageLabels .Name}}"><svg class="ico"><use href="#i-hard-drive"/></svg>{{index $.StorageLabels .Name}}</span>{{end}} {{if and .Deployed (index $.StorageLabels .Name)}}<span class="metarow" title="{{T "stacks.adattarolo"}}"><svg class="ico"><use href="#i-hard-drive"/></svg>{{index $.StorageLabels .Name}}</span>{{end}}
</div> </div>
{{if .Containers}} {{if .Containers}}
@@ -73,18 +73,18 @@
<div class="stack-detail-actions"> <div class="stack-detail-actions">
{{if .Protected}} {{if .Protected}}
<span class="tag"><svg class="ico ico-sm"><use href="#i-lock"/></svg>Védett rendszerkomponens</span> <span class="tag"><svg class="ico ico-sm"><use href="#i-lock"/></svg>{{T "stacks.vedett_rendszerkomponens"}}</span>
{{if isOperational .State}} {{if isOperational .State}}
<button class="btn btn-warning" onclick="stackAction(event, '{{.Name}}', 'restart')">Újraindítás</button> <button class="btn btn-warning" onclick="stackAction(event, '{{.Name}}', 'restart')">{{T "common.ujrainditas"}}</button>
{{end}} {{end}}
{{if .Meta.Slug}} {{if .Meta.Slug}}
<a href="/apps/{{.Meta.Slug}}" class="btn btn-outline">Részletek</a> <a href="/apps/{{.Meta.Slug}}" class="btn btn-outline">{{T "stacks.reszletek"}}</a>
{{end}} {{end}}
{{else if not .Deployed}} {{else if not .Deployed}}
{{/* The endpoint refuses a non-installable template server-side; hiding the button {{/* The endpoint refuses a non-installable template server-side; hiding the button
here keeps the two consistent rather than offering an action that will fail. */}} here keeps the two consistent rather than offering an action that will fail. */}}
{{if canInstall .Meta}}<a href="/stacks/{{.Name}}/deploy" class="btn btn-primary" onclick="return checkBeforeDeploy(event, '{{.Name}}')">Telepítés</a>{{end}} {{if canInstall .Meta}}<a href="/stacks/{{.Name}}/deploy" class="btn btn-primary" onclick="return checkBeforeDeploy(event, '{{.Name}}')">{{T "stacks.telepites"}}</a>{{end}}
<a href="{{appPageURL .Meta.Slug}}" class="btn btn-outline">Részletek</a> <a href="{{appPageURL .Meta.Slug}}" class="btn btn-outline">{{T "stacks.reszletek"}}</a>
{{else}} {{else}}
{{/* Slice 4 (v0.238.0): the UPDATING and HELD checks come BEFORE isOperational. That {{/* Slice 4 (v0.238.0): the UPDATING and HELD checks come BEFORE isOperational. That
predicate counts `restarting` as operational, which is how the 2026-09-01 spike saw predicate counts `restarting` as operational, which is how the 2026-09-01 spike saw
@@ -93,21 +93,21 @@
{{if .Updating}} {{if .Updating}}
<span class="tag tag-progress" data-update-phase="{{.UpdatePhase}}"><span class="dot"></span>{{.UpdatePhaseLabel}}</span> <span class="tag tag-progress" data-update-phase="{{.UpdatePhase}}"><span class="dot"></span>{{.UpdatePhaseLabel}}</span>
{{else if .HoldReason}} {{else if .HoldReason}}
<div class="alert alert-error" data-held="true">{{.HoldReason}} <a href="/backups/apps" class="btn btn-sm btn-outline">Mentések</a></div> <div class="alert alert-error" data-held="true">{{.HoldReason}} <a href="/backups/apps" class="btn btn-sm btn-outline">{{T "stacks.mentesek"}}</a></div>
{{if not .Orphaned}}<button class="btn btn-danger" onclick="removeStack('{{.Name}}')">Eltávolítás</button>{{end}} {{if not .Orphaned}}<button class="btn btn-danger" onclick="removeStack('{{.Name}}')">{{T "common.eltavolitas"}}</button>{{end}}
{{else if isOperational .State}} {{else if isOperational .State}}
{{if .UpdateError}}<div class="alert alert-warning" data-update-error="true">{{.UpdateError}}</div>{{end}} {{if .UpdateError}}<div class="alert alert-warning" data-update-error="true">{{.UpdateError}}</div>{{end}}
{{if not .Orphaned}}<button class="btn btn-success" onclick="stackAction(event, '{{.Name}}', 'update')">Frissítés</button>{{end}} {{if not .Orphaned}}<button class="btn btn-success" onclick="stackAction(event, '{{.Name}}', 'update')">{{T "stacks.frissites"}}</button>{{end}}
<button class="btn btn-warning" onclick="stackAction(event, '{{.Name}}', 'restart')">Újraindítás</button> <button class="btn btn-warning" onclick="stackAction(event, '{{.Name}}', 'restart')">{{T "common.ujrainditas"}}</button>
<button class="btn btn-danger" onclick="stackAction(event, '{{.Name}}', 'stop')">Leállítás</button> <button class="btn btn-danger" onclick="stackAction(event, '{{.Name}}', 'stop')">{{T "stacks.leallitas"}}</button>
{{else}} {{else}}
{{if .UpdateError}}<div class="alert alert-warning" data-update-error="true">{{.UpdateError}}</div>{{end}} {{if .UpdateError}}<div class="alert alert-warning" data-update-error="true">{{.UpdateError}}</div>{{end}}
<button class="btn btn-success" onclick="stackAction(event, '{{.Name}}', 'start')">Indítás</button> <button class="btn btn-success" onclick="stackAction(event, '{{.Name}}', 'start')">{{T "stacks.inditas"}}</button>
{{if not .Orphaned}}<button class="btn btn-danger" onclick="removeStack('{{.Name}}')">Eltávolítás</button>{{end}} {{if not .Orphaned}}<button class="btn btn-danger" onclick="removeStack('{{.Name}}')">{{T "common.eltavolitas"}}</button>{{end}}
{{end}} {{end}}
<a href="/stacks/{{.Name}}/logs" class="btn btn-outline">Naplók</a> <a href="/stacks/{{.Name}}/logs" class="btn btn-outline">{{T "stacks.naplok"}}</a>
{{if not .Orphaned}}<a href="{{appPageURL .Meta.Slug}}" class="btn btn-outline">Részletek</a>{{end}} {{if not .Orphaned}}<a href="{{appPageURL .Meta.Slug}}" class="btn btn-outline">{{T "stacks.reszletek"}}</a>{{end}}
{{if .Orphaned}}<button class="btn btn-danger" onclick="deleteOrphanStack('{{.Name}}')">Törlés</button>{{end}} {{if .Orphaned}}<button class="btn btn-danger" onclick="deleteOrphanStack('{{.Name}}')">{{T "common.torles"}}</button>{{end}}
{{end}} {{end}}
</div> </div>
</div> </div>
+11 -1
View File
@@ -41,7 +41,9 @@ ASCII_HU = re.compile(r"\b(Fut|Nincs|Igen|Nem|Hiba|Mentve|Rendben|Adatok|Napi|He
r"Perc|Letiltva|Bekapcsolva|Kikapcsolva|Folyamatban|Sikertelen|Sikeres|Tartalom|" r"Perc|Letiltva|Bekapcsolva|Kikapcsolva|Folyamatban|Sikertelen|Sikeres|Tartalom|"
r"Kapcsolat|Vissza|Megosztva|Kezdeti|Rendszer|Csatlakozva|Kulcs|Megnyit|Elrejt|" r"Kapcsolat|Vissza|Megosztva|Kezdeti|Rendszer|Csatlakozva|Kulcs|Megnyit|Elrejt|"
r"Ismeretlen|Szabad|Foglalt|Kijelentkezes|Napok|nap|perc|ora|" r"Ismeretlen|Szabad|Foglalt|Kijelentkezes|Napok|nap|perc|ora|"
r"Megszakadt|Elavult|Csak|kompatibilis|Rendszermonitor|Most nem)\b", re.I) r"Megszakadt|Elavult|Csak|kompatibilis|Rendszermonitor|Most nem|"
# slice 1 release A review: left behind on dashboard..settings_notifications
r"Folyamat|Titkos|Processzor|Hamarosan|aldomain|adatai|Figyelem|pelda)\b", re.I)
INLINE = {"strong", "em", "b", "i", "br", "code", "small", "a", "kbd", "u"} # NOT span: it is layout here INLINE = {"strong", "em", "b", "i", "br", "code", "small", "a", "kbd", "u"} # NOT span: it is layout here
VOID = {"br"} VOID = {"br"}
@@ -328,6 +330,14 @@ def js_edits(body, base, ctx):
gt, lt = content.find(">"), content.find("<") gt, lt = content.find(">"), content.find("<")
if gt >= 0 and (lt < 0 or gt < lt): if gt >= 0 and (lt < 0 or gt < lt):
skip = gt + 1 skip = gt + 1
# ...or it starts inside an attribute value: '%;opacity:.5" title="Rendszer: ' — only the
# text after the last attribute opening is copy (slice 1 release A, monitoring.html).
elif lt < 0:
last = None
for am in re.finditer(r'=\s*"', content):
last = am
if last is not None:
skip = last.end()
sub = edits_for_fragment(content[skip:], base + i + 1 + skip, ctx, allow_attrs=False) sub = edits_for_fragment(content[skip:], base + i + 1 + skip, ctx, allow_attrs=False)
ctx.stats["js"] += len(sub) ctx.stats["js"] += len(sub)
edits.extend(sub) edits.extend(sub)
+1 -1
View File
@@ -38,7 +38,7 @@ TEMPLATE_ROOTS = [os.path.join(CTRL, "internal", "web", "templates")]
MARKER = re.compile(r'\{\{\s*T\s+"([A-Za-z0-9_.\-]+)"\s*\}\}') MARKER = re.compile(r'\{\{\s*T\s+"([A-Za-z0-9_.\-]+)"\s*\}\}')
EN_MISSING_CEILING = 0 EN_MISSING_CEILING = 0
HU_FORMAL_CEILING = 6 HU_FORMAL_CEILING = 10 # raised 6 -> 10 in slice 1 release A: the converted pages carry 4 more (R-516), not changed (parity)
EN_FORBIDDEN = re.compile(r"\b(please|kindly)\b", re.I) EN_FORBIDDEN = re.compile(r"\b(please|kindly)\b", re.I)