D1 Part 2: settings.html split into four pages + sidebar restructure

- settings.html (1451 lines) deleted; sections moved verbatim into
  settings_system.html (Rendszer konfiguráció, Verzió és frissítés,
  Vezérlő/Kiszolgáló újraindítása + update/restart JS),
  settings_notifications.html (Értesítések, Alkalmazás-email),
  settings_security.html (Jelszó módosítás, Földrajzi korlátozás + geo
  JS, Vészhelyzeti információk — heading + section copy accents fixed),
  storage.html (Adattárolók, NAS, migrate progress, agent view + all
  storage JS; wizard entry links now /storage/init|attach with sprite
  icons instead of emoji). The NAS + migrate sections were nested inside
  {{if .StoragePaths}} in the monolith and vanished with zero drives —
  now unconditional on /storage.
- layout.html: Tárhely main-nav item (hard-drive icon) + the
  'Beállítások' sidebar group with Rendszer / Értesítések / Biztonság és
  hozzáférés sub-links (active-state per page key); orphaned
  .sidebar-settings-link CSS deleted (grep-zero), .nav-group-label /
  .nav-links-sub added.
- Handlers wired to their own builders + templates; the legacy
  settingsData() merge deleted.
- scripts/template_id_gate.py: the §10 JS element-ID integrity gate
  (getElementById/querySelector('#…') must resolve in the SAME template;
  JS-created + template-parameterized IDs handled; layout modal IDs
  allowlisted). Red-proven: a storage function planted in the
  notifications template failed the gate with 'static #migrate-progress
  not defined'.
- Tests: per-page section markers + cross-leak assertions, h3 section
  inventory (all 11 old headings accounted for; typo rename asserted).
This commit is contained in:
2026-07-02 19:07:32 +02:00
parent d50a919404
commit f8e18a9ec9
9 changed files with 1062 additions and 890 deletions
+40 -55
View File
@@ -1048,43 +1048,28 @@ func (s *Server) securityPageData() map[string]interface{} {
return data
}
// settingsData merges every subpage builder — legacy glue kept ONLY while the monolithic
// settings.html exists (deleted with the D1 Part 2 template split).
func (s *Server) settingsData() map[string]interface{} {
data := s.systemPageData()
for _, m := range []map[string]interface{}{s.storagePageData(), s.notificationsPageData(), s.securityPageData()} {
for k, v := range m {
if k == "Page" || k == "Title" {
continue
}
data[k] = v
}
}
return data
}
func (s *Server) settingsHandler(w http.ResponseWriter, r *http.Request) {
s.executeTemplate(w, r, "settings", s.settingsData())
s.executeTemplate(w, r, "settings_system", s.systemPageData())
}
// storagePageHandler serves the Tárhely main-nav page (D1). Storage action flashes land here.
func (s *Server) storagePageHandler(w http.ResponseWriter, r *http.Request) {
data := s.settingsData()
data := s.storagePageData()
if msg := r.URL.Query().Get("storage_msg"); msg == "success" {
data["StorageSuccess"] = r.URL.Query().Get("storage_detail")
}
s.executeTemplate(w, r, "settings", data)
s.executeTemplate(w, r, "storage", data)
}
// settingsNotificationsPageHandler serves GET /settings/notifications (the POST on the same
// path is the save handler — dispatch is split in the router).
func (s *Server) settingsNotificationsPageHandler(w http.ResponseWriter, r *http.Request) {
s.executeTemplate(w, r, "settings", s.settingsData())
s.executeTemplate(w, r, "settings_notifications", s.notificationsPageData())
}
// settingsSecurityPageHandler serves GET /settings/security.
func (s *Server) settingsSecurityPageHandler(w http.ResponseWriter, r *http.Request) {
s.executeTemplate(w, r, "settings", s.settingsData())
s.executeTemplate(w, r, "settings_security", s.securityPageData())
}
func (s *Server) settingsPasswordHandler(w http.ResponseWriter, r *http.Request) {
@@ -1097,7 +1082,7 @@ func (s *Server) settingsPasswordHandler(w http.ResponseWriter, r *http.Request)
s.logger.Printf("[DEBUG] [web] settingsPasswordHandler: password change attempt from %s", r.RemoteAddr)
}
data := s.settingsData()
data := s.securityPageData()
// Validate current password
effectiveHash := s.effectivePasswordHash()
@@ -1106,21 +1091,21 @@ func (s *Server) settingsPasswordHandler(w http.ResponseWriter, r *http.Request)
s.logger.Printf("[DEBUG] [web] settingsPasswordHandler: current password mismatch from %s", r.RemoteAddr)
}
data["PasswordError"] = "Hibás jelenlegi jelszó"
s.executeTemplate(w, r, "settings", data)
s.executeTemplate(w, r, "settings_security", data)
return
}
// Validate new password length
if len(newPassword) < 8 {
data["PasswordError"] = "A jelszónak legalább 8 karakter hosszúnak kell lennie"
s.executeTemplate(w, r, "settings", data)
s.executeTemplate(w, r, "settings_security", data)
return
}
// Validate passwords match
if newPassword != confirmPassword {
data["PasswordError"] = "A két jelszó nem egyezik"
s.executeTemplate(w, r, "settings", data)
s.executeTemplate(w, r, "settings_security", data)
return
}
@@ -1129,7 +1114,7 @@ func (s *Server) settingsPasswordHandler(w http.ResponseWriter, r *http.Request)
if err != nil {
s.logger.Printf("[ERROR] [web] Failed to hash new password: %v", err)
data["PasswordError"] = "Belső hiba a jelszó mentésekor"
s.executeTemplate(w, r, "settings", data)
s.executeTemplate(w, r, "settings_security", data)
return
}
@@ -1137,7 +1122,7 @@ func (s *Server) settingsPasswordHandler(w http.ResponseWriter, r *http.Request)
if err := s.settings.SetPasswordHash(string(hash)); err != nil {
s.logger.Printf("[ERROR] [web] Failed to save password to settings.json: %v", err)
data["PasswordError"] = "Belső hiba a jelszó mentésekor"
s.executeTemplate(w, r, "settings", data)
s.executeTemplate(w, r, "settings_security", data)
return
}
@@ -1202,16 +1187,16 @@ func (s *Server) settingsNotificationsHandler(w http.ResponseWriter, r *http.Req
if err := s.settings.SetNotificationPrefs(prefs); err != nil {
s.logger.Printf("[ERROR] [web] Failed to save notification prefs: %v", err)
data := s.settingsData()
data := s.notificationsPageData()
data["NotificationError"] = "Hiba a beállítások mentésekor"
s.executeTemplate(w, r, "settings", data)
s.executeTemplate(w, r, "settings_notifications", data)
return
}
s.logger.Printf("[INFO] [web] Notification preferences updated: email=%s, events=%v", email, enabledEvents)
// Sync preferences to hub
data := s.settingsData()
data := s.notificationsPageData()
if s.notifier != nil && s.notifier.IsEnabled() {
if err := s.notifier.SyncPreferences(email, enabledEvents, cooldownHours); err != nil {
s.logger.Printf("[WARN] [web] Failed to sync preferences to hub: %v", err)
@@ -1222,7 +1207,7 @@ func (s *Server) settingsNotificationsHandler(w http.ResponseWriter, r *http.Req
} else {
data["NotificationSuccess"] = "Értesítési beállítások mentve."
}
s.executeTemplate(w, r, "settings", data)
s.executeTemplate(w, r, "settings_notifications", data)
}
// settingsAppEmailHandler saves the global app-email toggle and starts/stops the on-box
@@ -1232,39 +1217,39 @@ func (s *Server) settingsAppEmailHandler(w http.ResponseWriter, r *http.Request)
enabled := r.FormValue("app_email_enabled") == "on" || r.FormValue("app_email_enabled") == "true"
fromName := strings.TrimSpace(r.FormValue("app_email_from_name"))
data := s.settingsData()
data := s.notificationsPageData()
if err := s.settings.SetAppEmail(enabled, fromName); err != nil {
s.logger.Printf("[ERROR] [web] Failed to save app-email toggle: %v", err)
data["AppEmailError"] = "Hiba az alkalmazás-email beállítás mentésekor"
s.executeTemplate(w, r, "settings", data)
s.executeTemplate(w, r, "settings_notifications", data)
return
}
// Reconcile the shim's running state with the new toggle.
if s.mailShim != nil {
if err := s.mailShim.Apply(enabled); err != nil {
s.logger.Printf("[ERROR] [web] app-email shim could not be %s: %v", map[bool]string{true: "started", false: "stopped"}[enabled], err)
data = s.settingsData()
data = s.notificationsPageData()
data["AppEmailError"] = "A beállítás elmentve, de az email-szolgáltatás indítása nem sikerült."
s.executeTemplate(w, r, "settings", data)
s.executeTemplate(w, r, "settings_notifications", data)
return
}
}
s.logger.Printf("[INFO] [web] App-email globally %s (from_name=%q)", map[bool]string{true: "enabled", false: "disabled"}[enabled], fromName)
data = s.settingsData()
data = s.notificationsPageData()
if enabled {
data["AppEmailSuccess"] = "Alkalmazás-email bekapcsolva. Kapcsold be az egyes alkalmazásoknál is, ahol email-küldést szeretnél."
} else {
data["AppEmailSuccess"] = "Alkalmazás-email kikapcsolva."
}
s.executeTemplate(w, r, "settings", data)
s.executeTemplate(w, r, "settings_notifications", data)
}
func (s *Server) settingsNotificationsTestHandler(w http.ResponseWriter, r *http.Request) {
data := s.settingsData()
data := s.notificationsPageData()
if s.notifier == nil {
data["NotificationError"] = "Az értesítések nincsenek bekapcsolva"
s.executeTemplate(w, r, "settings", data)
s.executeTemplate(w, r, "settings_notifications", data)
return
}
@@ -1272,12 +1257,12 @@ func (s *Server) settingsNotificationsTestHandler(w http.ResponseWriter, r *http
if err != nil {
s.logger.Printf("[ERROR] [web] Test notification failed: %v", err)
data["NotificationError"] = fmt.Sprintf("Teszt email küldése sikertelen: %v", err)
s.executeTemplate(w, r, "settings", data)
s.executeTemplate(w, r, "settings_notifications", data)
return
}
data["NotificationSuccess"] = "Teszt email elküldve."
s.executeTemplate(w, r, "settings", data)
s.executeTemplate(w, r, "settings_notifications", data)
}
// --- Storage path management handlers ---
@@ -1496,27 +1481,27 @@ func (s *Server) settingsStorageAddHandler(w http.ResponseWriter, r *http.Reques
label = settings.InferStorageLabel(path)
}
data := s.settingsData()
data := s.storagePageData()
// 1. Exists and is directory
fi, err := os.Stat(path)
if err != nil || !fi.IsDir() {
data["StorageError"] = "Az útvonal nem létezik vagy nem mappa."
s.executeTemplate(w, r, "settings", data)
s.executeTemplate(w, r, "storage", data)
return
}
// 2. Is mount point
if !system.IsMountPoint(path) {
data["StorageError"] = "Ez az útvonal nem külön csatlakoztatott meghajtó. Adatok az SSD-re kerülnének!"
s.executeTemplate(w, r, "settings", data)
s.executeTemplate(w, r, "storage", data)
return
}
// 3. Writable
if !system.IsWritable(path) {
data["StorageError"] = "Az útvonal nem írható."
s.executeTemplate(w, r, "settings", data)
s.executeTemplate(w, r, "storage", data)
return
}
@@ -1524,7 +1509,7 @@ func (s *Server) settingsStorageAddHandler(w http.ResponseWriter, r *http.Reques
for _, existing := range s.settings.GetStoragePaths() {
if system.PathsOverlap(path, existing.Path) {
data["StorageError"] = fmt.Sprintf("Az útvonal átfedi a már regisztrált %s útvonalat.", existing.Path)
s.executeTemplate(w, r, "settings", data)
s.executeTemplate(w, r, "storage", data)
return
}
}
@@ -1545,7 +1530,7 @@ func (s *Server) settingsStorageAddHandler(w http.ResponseWriter, r *http.Reques
if err := s.settings.AddStoragePath(sp); err != nil {
s.logger.Printf("[ERROR] [web] Failed to add storage path: %v", err)
data["StorageError"] = "Hiba a mentés során."
s.executeTemplate(w, r, "settings", data)
s.executeTemplate(w, r, "storage", data)
return
}
@@ -1562,13 +1547,13 @@ func (s *Server) settingsStorageRemoveHandler(w http.ResponseWriter, r *http.Req
s.logger.Printf("[DEBUG] [web] settingsStorageRemoveHandler: path=%s from %s", path, r.RemoteAddr)
}
data := s.settingsData()
data := s.storagePageData()
// Check: apps using this path
apps := s.appsUsingPath(path)
if len(apps) > 0 {
data["StorageError"] = fmt.Sprintf("Nem törölhető: az alábbi alkalmazások használják: %s", strings.Join(apps, ", "))
s.executeTemplate(w, r, "settings", data)
s.executeTemplate(w, r, "storage", data)
return
}
@@ -1576,7 +1561,7 @@ func (s *Server) settingsStorageRemoveHandler(w http.ResponseWriter, r *http.Req
for _, sp := range s.settings.GetStoragePaths() {
if sp.Path == path && sp.IsDefault {
data["StorageError"] = "Az alapértelmezett adattároló nem törölhető."
s.executeTemplate(w, r, "settings", data)
s.executeTemplate(w, r, "storage", data)
return
}
}
@@ -1584,13 +1569,13 @@ func (s *Server) settingsStorageRemoveHandler(w http.ResponseWriter, r *http.Req
// Check: last path
if len(s.settings.GetStoragePaths()) <= 1 {
data["StorageError"] = "Az utolsó adattároló nem törölhető."
s.executeTemplate(w, r, "settings", data)
s.executeTemplate(w, r, "storage", data)
return
}
if err := s.settings.RemoveStoragePath(path); err != nil {
data["StorageError"] = "Hiba a törlés során."
s.executeTemplate(w, r, "settings", data)
s.executeTemplate(w, r, "storage", data)
return
}
@@ -1645,17 +1630,17 @@ func (s *Server) settingsStorageLabelHandler(w http.ResponseWriter, r *http.Requ
}
if label == "" || len(label) > 50 {
data := s.settingsData()
data := s.storagePageData()
data["StorageError"] = "A megnevezés nem lehet üres és legfeljebb 50 karakter."
s.executeTemplate(w, r, "settings", data)
s.executeTemplate(w, r, "storage", data)
return
}
if err := s.settings.SetStorageLabel(path, label); err != nil {
s.logger.Printf("[ERROR] [web] Failed to set storage label: %v", err)
data := s.settingsData()
data := s.storagePageData()
data["StorageError"] = "Hiba a megnevezés mentésekor."
s.executeTemplate(w, r, "settings", data)
s.executeTemplate(w, r, "storage", data)
return
}