package notify import ( "fmt" "strings" "time" ) // budapest timezone for formatting. var budapest *time.Location func init() { var err error budapest, err = time.LoadLocation("Europe/Budapest") if err != nil { budapest = time.FixedZone("CET", 3600) } } // ────────────────────────────────────────────────────────────────────── // Operator email — concise, English // ────────────────────────────────────────────────────────────────────── // FormatOperatorEmail returns (subject, textBody) for the operator channel. func FormatOperatorEmail(customerID, eventType, severity, message, detailsJSON string) (string, string) { // Icon is eventType-aware for recovery (v0.71.0, audit F11); severity stays the fallback. icon := "⚠️" switch { case strings.HasSuffix(eventType, "_recovered"): icon = "✅" case severity == "error" || severity == "critical": icon = "🔴" } subject := fmt.Sprintf("[Felhom] %s %s: %s", icon, customerID, eventType) now := time.Now().In(budapest).Format("2006-01-02 15:04 MST") body := fmt.Sprintf(`Customer: %s Event: %s Severity: %s Time: %s Message: %s`, customerID, eventType, severity, now, message) if detailsJSON != "" && detailsJSON != "{}" { body += fmt.Sprintf("\nDetails: %s", detailsJSON) } body += fmt.Sprintf("\n\nDashboard: https://hub.felhom.eu/customers/%s", customerID) return subject, body } // ────────────────────────────────────────────────────────────────────── // Customer email — Hungarian, friendly // ────────────────────────────────────────────────────────────────────── // customerMessages maps event_type → Hungarian customer message. var customerMessages = map[string]string{ // Customer-claim arc (v0.50.0) "claim_lockout": "Túl sok hibás beállító/visszaállító kód próbálkozás történt — a beállító oldal 15 percre zárolva lett. Ha nem te próbálkoztál, jelezd az üzemeltetőnek.", // Backup events "backup_completed": "A biztonsági mentés sikeresen elkészült.", "backup_failed": "A biztonsági mentés sikertelen! Kérjük, ellenőrizd a rendszert.", "db_dump_completed": "Az adatbázis mentés sikeresen elkészült.", "db_dump_failed": "Az adatbázis mentés sikertelen!", "backup_integrity_ok": "A mentés integritás ellenőrzés sikeres.", "backup_integrity_failed": "A mentés integritás ellenőrzés hibát talált!", "crossdrive_completed": "A másodlagos mentés sikeresen elkészült.", "crossdrive_failed": "A másodlagos mentés sikertelen!", // Offsite-repo continuity (controller v0.142.0) "offbox_repo_orphaned": "A távoli mentési tároló elárvult: a benne lévő mentések egy korábbi, már nem elérhető kulccsal készültek (jellemzően újratelepítés után). Új mentés a tároló visszaállításáig nem készül — nyisd meg a Távoli mentés oldalt.", "offbox_repo_reset": "A távoli mentési tároló visszaállítva: a régi előzmény félretéve (nem törölve), és egy üres, új tároló jött létre a mostani kulccsal.", // Disk events (GUEST — the controller's own cgroup view) "disk_warning": "A lemezterület 90% felett van — kérjük, szabadíts fel helyet.", "disk_critical": "A lemezterület kritikusan magas (95%+) — azonnali beavatkozás szükséges!", // Host disk events (the Proxmox HOST root filesystem — distinct from the guest disk above) "host_disk_warning": "A házszerver alaprendszerének (Proxmox-gazda) gyökérlemeze 90% felett van — kérjük, szabadíts fel helyet (pl. régi biztonsági mentések).", "host_disk_critical": "A házszerver alaprendszerének gyökérlemeze kritikusan tele van (95%+) — azonnali beavatkozás szükséges, különben a rendszer hibázhat!", // Per-storage fill events (a SPECIFIC tároló — mentési kötet, adatmeghajtó, tároló-készlet — telik be) "storage_fill_warning": "Egy tároló a házszerveren 90% felett telt — kérjük, szabadíts fel helyet, mielőtt megtelik.", "storage_fill_critical": "Egy tároló a házszerveren kritikusan tele van (95%+) — a rá készülő mentések/írások meghiúsulhatnak; azonnali beavatkozás szükséges!", // Storage events "storage_disconnected": "Egy meghajtó leválasztva — a mentések szünetelhetnek.", "storage_reconnected": "A meghajtó újra csatlakoztatva.", // Staleness events (Hub-generated) "node_stale": "A szerver nem küldött jelentést az elmúlt időszakban.", "node_down": "A szerver nem elérhető!", "node_recovered": "A szerver újra elérhető.", "host_recovered": "A házszerver alaprendszere (Proxmox-gazda) újra elérhető.", // Health events "health_degraded": "A rendszer állapota romlott.", "health_critical": "A rendszer állapota kritikus!", "health_recovered": "A rendszer állapota helyreállt.", // Controller events "controller_started": "A vezérlő elindult.", "controller_updated": "A vezérlő frissítve lett.", // Deadline events (Hub-generated) "expected_backup_missed": "A mai biztonsági mentés nem készült el a határidőig!", "expected_dbdump_missed": "A mai adatbázis mentés nem készült el a határidőig!", // App lifecycle events "app_deployed": "Alkalmazás telepítve.", "app_removed": "Alkalmazás eltávolítva.", "app_start_failed": "Egy telepített alkalmazás nem fut — ellenőrizze a rendszermonitort.", // Disaster recovery events "disaster_recovery_started": "Katasztrófa helyreállítás elindítva.", "disaster_recovery_completed": "Katasztrófa helyreállítás befejezve.", // Test "test": "Ez egy teszt értesítés.", } // severityLabels maps severity to Hungarian labels. var severityLabels = map[string]string{ "info": "Információ", "warning": "Figyelmeztetés", "error": "Hiba", "critical": "Kritikus hiba", } // FormatCustomerEmail returns (subject, textBody) for the customer channel. func FormatCustomerEmail(customerID, eventType, severity, message, detailsJSON string) (string, string) { label := severityLabels[severity] if label == "" { label = severity } // Use the per-event-type Hungarian message if available, otherwise fall back to message hunMessage := customerMessages[eventType] if hunMessage == "" { hunMessage = message } subject := fmt.Sprintf("[Felhom] %s: %s", label, hunMessage) now := time.Now().In(budapest).Format("2006-01-02 15:04") body := fmt.Sprintf(`Kedves Ügyfél! A Felhom rendszered a következő értesítést küldte: %s Részletek: - Szerver: %s - Időpont: %s - Szint: %s - Típus: %s`, hunMessage, customerID, now, label, eventType) if message != "" && message != hunMessage { body += fmt.Sprintf("\n- Üzenet: %s", message) } if detailsJSON != "" && detailsJSON != "{}" { body += fmt.Sprintf("\n- Megjegyzés: %s", detailsJSON) } body += ` Ha kérdésed van, vedd fel a kapcsolatot az üzemeltetővel. Üdvözlettel, Felhom.eu monitoring` return subject, body } // ────────────────────────────────────────────────────────────────────── // Customer-claim password arc (v0.50.0) — Hungarian code-delivery emails // ────────────────────────────────────────────────────────────────────── // FormatClaimEmail returns (subject, textBody) for a claim-arc email. kind is one of // "claim" | "reset" | "claimed" (claim.EmailKind values). The code appears ONLY in the // returned body — callers must never log it. func FormatClaimEmail(kind, customerID, domain, code string) (string, string) { dashboardURL := "https://felhom." + domain switch kind { case "reset": subject := "[Felhom] Jelszó-visszaállítási kód" body := fmt.Sprintf(`Kedves Ügyfél! Jelszó-visszaállítást kértél a Felhom vezérlőpultodhoz. Visszaállító kód: %s A kód 72 óráig érvényes, és egyszer használható fel. Add meg a vezérlőpult "Elfelejtett jelszó" oldalán, majd válassz új jelszót: %s Ha nem te kérted, hagyd figyelmen kívül — a jelenlegi jelszavad változatlan. Üdvözlettel, Felhom.eu`, code, dashboardURL) return subject, body case "claimed": subject := "[Felhom] A vezérlőpultod mostantól jelszóval védett" body := fmt.Sprintf(`Kedves Ügyfél! A Felhom vezérlőpultod beállítása elkészült — a vezérlőpultod mostantól jelszóval védett. A megadott jelszóval tudsz bejelentkezni: %s Ha nem te végezted a beállítást, azonnal vedd fel a kapcsolatot az üzemeltetővel. Üdvözlettel, Felhom.eu`, dashboardURL) return subject, body default: // "claim" subject := "[Felhom] Elindult a Felhom szervered — beállító kód" body := fmt.Sprintf(`Kedves Ügyfél! Elindult a Felhom szervered. A vezérlőpult első használatához add meg az alábbi beállító kódot, majd válassz saját jelszót: Beállító kód: %s A kód 72 óráig érvényes, és egyszer használható fel. A vezérlőpultot itt éred el: %s Ha nem kaptad volna meg időben, a vezérlőpult "Új kód kérése" gombjával kérhetsz frisset — az mindig erre az e-mail címre érkezik. Üdvözlettel, Felhom.eu`, code, dashboardURL) return subject, body } } // FormatSelfBindEmail builds the customer-facing Hungarian email carrying the self-bind capability // link (v0.66.0, R-27 slice 1). The link is the ONLY secret here — the passphrase is never in the // mail (the customer already holds it), and the console pairing code is read off the box screen. The // copy tells the customer they will need both factors on the page. Adult tone, no emoji. func FormatSelfBindEmail(customerID, link string) (string, string) { subject := "[Felhom] Kösd össze a Felhom dobozodat" body := fmt.Sprintf(`Kedves Ügyfél! Elkészült a Felhom dobozod, és készen áll az összekötésre. Az alábbi hivatkozáson tudod te magad összekötni a fiókoddal — nincs szükség bejelentkezésre: %s A hivatkozás megnyitása után két adatot kell megadnod: 1. A párosító kódot, amely a doboz képernyőjén (a monitoron) látható. 2. A visszaállító jelszavadat (az 5 szóból álló kifejezést), amelyet a beállításkor kaptál. A hivatkozás 7 napig érvényes. Biztonsági okból 5 sikertelen próbálkozás után zárolódik — ilyenkor vedd fel a kapcsolatot az ügyfélszolgálattal. Ha nem te kérted ezt, hagyd figyelmen kívül ezt az e-mailt. Üdvözlettel, Felhom.eu`, link) return subject, body }