feat: HA-Cluster v1.2.x — Split-Brain, TOTP, Enterprise-FW, Drift-Fix, VIP-Recovery
- keepalived: pg_role='standby' hat Vorrang vor role für BACKUP-Bestimmung - keepalived-master.sh: gecrasht Dienste beim MASTER-Übergang starten (nicht nur reload) - confighash: ip_addresses per Interface-Name hashen statt per FK (Cross-Node-Drift-Fix) - TOTP/2FA: RFC 6238 — Setup-Flow, QR-Code, Admin-Disable; two-step Login - Firewall-UI: Enterprise-Design — auto-Beschreibung, icon-only Actions, zero-hit Indikator - fe80-Filter: Link-local IPv6 aus NTP/DNS Listen-Dropdowns entfernen - VIP-Dashboard, Dual-Path VRRP, GW-Tracking (Migrations 0033/0034) - Forward Proxy + DNS erweiterte Einstellungen (Migrations 0031/0032) - unbound-control: edgeguard in unbound-Gruppe via postinst Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -152,7 +152,10 @@
|
||||
"emptyTitle": "Noch keine eigenen Firewall-Regeln.",
|
||||
"emptyDesc": "Die System-Regeln oben halten SSH (rate-limited), HTTPS :443 und Mgmt-UI :3443 immer offen (Anti-Lockout). Eigene Regeln für app-spezifische Inbound-Ports oder zonenübergreifende Forwards anlegen.",
|
||||
"logEnabled": "Logging aktiv — gematchte Pakete werden ins Firewall-Log geschrieben",
|
||||
"ruleDisabled": "Regel deaktiviert"
|
||||
"ruleDisabled": "Regel deaktiviert",
|
||||
"enabled": "Aktiv",
|
||||
"unnamed": "(kein Name)",
|
||||
"zeroHitHint": "Keine Treffer seit dem letzten Neustart — möglicherweise ungenutzte oder überlagerte Regel"
|
||||
},
|
||||
"kpi": {
|
||||
"policyRules": "Policy-Regeln",
|
||||
@@ -281,7 +284,12 @@
|
||||
"loggedInAs": "Angemeldet als",
|
||||
"forgotPassword": "Passwort vergessen?",
|
||||
"viewerBadge": "Nur lesen",
|
||||
"viewerHint": "Dieser Account hat die Rolle Betrachter — Änderungen sind gesperrt. Ein Admin kann die Rolle anpassen."
|
||||
"viewerHint": "Dieser Account hat die Rolle Betrachter — Änderungen sind gesperrt. Ein Admin kann die Rolle anpassen.",
|
||||
"totp": {
|
||||
"prompt": "Bitte gib den 6-stelligen Code aus deiner Authenticator-App ein.",
|
||||
"verify": "Code bestätigen",
|
||||
"invalidCode": "Ungültiger Code"
|
||||
}
|
||||
},
|
||||
"reset": {
|
||||
"title": "Admin-Passwort zurücksetzen",
|
||||
@@ -392,6 +400,17 @@
|
||||
"backends": "Backends",
|
||||
"attached": "{{count}}/{{total}} Domains haben einen Primary-Backend"
|
||||
},
|
||||
"vipCard": {
|
||||
"title": "VIP / VRRP",
|
||||
"noVips": "Keine VIPs konfiguriert",
|
||||
"keepalivedInactive": "keepalived läuft nicht",
|
||||
"state": {
|
||||
"MASTER": "MASTER",
|
||||
"BACKUP": "BACKUP",
|
||||
"FAULT": "FAULT",
|
||||
"UNKNOWN": "Unbekannt"
|
||||
}
|
||||
},
|
||||
"systemCard": {
|
||||
"title": "System",
|
||||
"version": "Version",
|
||||
@@ -399,9 +418,16 @@
|
||||
"ifaces": "Interfaces",
|
||||
"wg": "WireGuard"
|
||||
},
|
||||
"networkServicesCard": {
|
||||
"title": "Netzwerk-Dienste",
|
||||
"configure": "Konfigurieren"
|
||||
},
|
||||
"alertsCard": {
|
||||
"title": "Aktuelle Alerts",
|
||||
"viewAll": "Alle anzeigen"
|
||||
"viewAll": "Alle anzeigen",
|
||||
"summary": "{{critical}} kritisch · {{warning}} Warnung",
|
||||
"summaryWarning": "{{warning}} Warnung",
|
||||
"summaryCritical": "{{critical}} kritisch"
|
||||
},
|
||||
"downBackendsAlert": "{{count}} Backend(s) komplett ausgefallen — kein Server UP",
|
||||
"maintenanceAlert": "{{count}} Domain(s) im Wartungs-Modus",
|
||||
@@ -664,7 +690,18 @@
|
||||
"hintPrimary": "Auf dem Primary: edgeguard-ctl cluster-init-replication",
|
||||
"hintStandby": "Auf dem Secondary: edgeguard-ctl cluster-setup-standby <primary-ip>",
|
||||
"hintKeepalived": "Keepalived auf beiden Nodes: sudo systemctl enable --now keepalived",
|
||||
"hintFailover": "Bei Failover: edgeguard-ctl promote (auf dem Secondary)"
|
||||
"hintFailover": "Bei Failover: edgeguard-ctl promote (auf dem Secondary)",
|
||||
"splitBrainSection": "Split-Brain-Schutz (Dual-Path VRRP + Gateway-Tracking)",
|
||||
"hbInterface": "Heartbeat-Interface",
|
||||
"hbInterfaceHelp": "Zweites Interface für die VI_HB-Instanz — VRRP-Advertisements laufen hier unabhängig von VI_1. Leer lassen um zu deaktivieren.",
|
||||
"hbSrcIp": "Heartbeat-Quell-IP",
|
||||
"hbSrcIpHelp": "Eigene IP auf dem Heartbeat-Interface (unicast_src_ip für VI_HB).",
|
||||
"hbPeerIp": "Heartbeat-Peer-IP",
|
||||
"hbPeerIpHelp": "Peer-IP auf dem Heartbeat-Interface (unicast_peer für VI_HB).",
|
||||
"hbRouterId": "Heartbeat Router-ID",
|
||||
"hbRouterIdHelp": "VRRP virtual_router_id für VI_HB — muss sich von der Haupt-Router-ID unterscheiden. Standard: 52.",
|
||||
"gwCheckIp": "Gateway-Check-IP",
|
||||
"gwCheckIpHelp": "Upstream-Gateway-IP die alle 5 s angepingt wird. Nicht erreichbar → Priorität sinkt um 110 → Failover wird ausgelöst. Leer lassen um zu deaktivieren."
|
||||
},
|
||||
"loadTitle": "Per-Node Resources (mTLS-Aggregator)",
|
||||
"loadEmpty": "Keine Node-Resources verfügbar — Agent-Listener nicht erreichbar?",
|
||||
@@ -699,7 +736,27 @@
|
||||
"step3SetupDesc": "Setup-Wizard auf dem neuen Knoten öffnen (https://<node-fqdn>:3443/setup), \"Vorhandenem Cluster beitreten\" wählen, Primary-FQDN ({{primaryFqdn}}) eingeben und den Token oben einfügen.",
|
||||
"generateNewToken": "Neuen Token generieren",
|
||||
"setupWizardHint": "Setup-Wizard auf dem neuen Knoten öffnen",
|
||||
"newNodeFqdnLabel": "FQDN des neuen Knotens"
|
||||
"newNodeFqdnLabel": "FQDN des neuen Knotens",
|
||||
"vipTest": {
|
||||
"cardTitle": "VIP-Schwenk Test",
|
||||
"cardDesc": "Verschiebt einen VIP temporär auf den Secondary um zu prüfen ob die Dienste korrekt antworten. Keepalived ist nicht beteiligt — reiner ip addr add/del Test.",
|
||||
"colAddress": "VIP-Adresse",
|
||||
"colInterface": "Interface",
|
||||
"colActiveOn": "Aktiv auf",
|
||||
"swingBtn": "→ Secondary",
|
||||
"restoreBtn": "← Primary",
|
||||
"swingOk": "VIP erfolgreich auf Secondary geschwenkt",
|
||||
"restoreOk": "VIP zurück auf Primary",
|
||||
"swingFailed": "VIP-Schwenk fehlgeschlagen",
|
||||
"restoreFailed": "VIP-Rückschwenk fehlgeschlagen",
|
||||
"noVips": "Keine VIPs konfiguriert (ip_addresses mit is_vip=true)",
|
||||
"steps": "Schritte",
|
||||
"stepOk": "OK",
|
||||
"stepFail": "Fehler",
|
||||
"confirmSwing": "{{addr}} auf Secondary verschieben?",
|
||||
"confirmRestore": "{{addr}} zurück auf Primary?",
|
||||
"unknown": "unbekannt"
|
||||
}
|
||||
},
|
||||
"ssl": {
|
||||
"title": "SSL-Zertifikate",
|
||||
@@ -1126,7 +1183,16 @@
|
||||
"flushCacheFailed": "Cache-Flush fehlgeschlagen",
|
||||
"upstreamForwardsInvalid": "Jeder Forwarder muss eine gültige IP sein (z.B. 1.1.1.1 oder 9.9.9.9)",
|
||||
"accessACLInvalid": "Jeder Eintrag muss eine gültige IP oder CIDR sein (z.B. 10.0.0.0/8)",
|
||||
"cacheTTLError": "Cache-Max-TTL muss ≥ Cache-Min-TTL sein"
|
||||
"cacheTTLError": "Cache-Max-TTL muss ≥ Cache-Min-TTL sein",
|
||||
"cacheSection": "Cache",
|
||||
"prefetch": "Häufige Records vorausladen",
|
||||
"prefetchExtra": "Häufig abgefragte Records werden vor Ablauf der TTL neu aufgelöst — reduziert Latenz für bekannte Namen.",
|
||||
"serveExpired": "Abgelaufene Records ausliefern",
|
||||
"serveExpiredExtra": "Stale Cache-Einträge zurückgeben wenn Upstream-Resolver nicht erreichbar ist. Reduziert SERVFAIL bei Ausfällen.",
|
||||
"msgCacheSizeMB": "Message-Cache (MB)",
|
||||
"msgCacheSizeMBExtra": "RAM für DNS-Antwort-Cache (msg-cache-size). Standard 64 MB.",
|
||||
"rrsetCacheSizeMB": "RRset-Cache (MB)",
|
||||
"rrsetCacheSizeMBExtra": "RAM für Resource-Record-Cache (rrset-cache-size). Sollte ~2x Message-Cache sein. Standard 128 MB."
|
||||
}
|
||||
},
|
||||
"fwd": {
|
||||
@@ -1180,6 +1246,28 @@
|
||||
"dstdom_regex": "dstdom_regex — Domain-Regex",
|
||||
"srcdom_regex": "srcdom_regex — Quell-Domain-Regex",
|
||||
"browser": "browser — User-Agent-Regex"
|
||||
},
|
||||
"settings": {
|
||||
"title": "Proxy-Einstellungen",
|
||||
"listenAddresses": "Listen-Adressen",
|
||||
"listenAddressesExtra": "Komma-getrennte IPs auf denen Squid lauscht (z.B. 10.0.5.1, 10.0.20.1). Leer = alle Interfaces.",
|
||||
"listenPort": "Port",
|
||||
"listenPortExtra": "Standard: 3128.",
|
||||
"saveFailed": "Einstellungen konnten nicht gespeichert werden.",
|
||||
"cacheSection": "Cache",
|
||||
"cacheMemMB": "RAM-Cache (MB)",
|
||||
"cacheMemMBExtra": "RAM den Squid für Caching nutzt (cache_mem). Standard 64 MB.",
|
||||
"cacheDirMB": "Disk-Cache (MB)",
|
||||
"cacheDirMBExtra": "Speicherplatz für den UFS-Cache. Standard 100 MB.",
|
||||
"maxObjSizeMB": "Max. Objektgröße (MB)",
|
||||
"maxObjSizeMBExtra": "Größtes Objekt das Squid cached. Größere Objekte werden direkt durchgeleitet. Standard 4 MB.",
|
||||
"timeoutSection": "Timeouts",
|
||||
"connectTimeout": "Verbindungs-Timeout (s)",
|
||||
"connectTimeoutExtra": "Sekunden, die Squid beim Verbindungsaufbau zum Upstream wartet.",
|
||||
"readTimeout": "Lese-Timeout (s)",
|
||||
"readTimeoutExtra": "Sekunden zwischen aufeinanderfolgenden Lesevorgängen vom Upstream.",
|
||||
"requestTimeout": "Anfrage-Timeout (s)",
|
||||
"requestTimeoutExtra": "Maximale Zeit für einen vollständigen Request/Response-Zyklus."
|
||||
}
|
||||
},
|
||||
"common": {
|
||||
@@ -1205,6 +1293,8 @@
|
||||
"retry": "Erneut versuchen",
|
||||
"close": "Schließen",
|
||||
"refresh": "Aktualisieren",
|
||||
"back": "Zurück",
|
||||
"next": "Weiter",
|
||||
"up": "UP",
|
||||
"down": "DOWN",
|
||||
"relTime": {
|
||||
@@ -1542,7 +1632,24 @@
|
||||
"errorEmailTaken": "Diese E-Mail-Adresse wird bereits verwendet.",
|
||||
"cannotDeleteSelf": "Das eigene Konto kann nicht gelöscht werden.",
|
||||
"you": "Ich",
|
||||
"never": "Nie"
|
||||
"never": "Nie",
|
||||
"totp": {
|
||||
"on": "2FA",
|
||||
"off": "–",
|
||||
"setup": "2FA einrichten",
|
||||
"manage": "2FA verwalten",
|
||||
"disable": "2FA deaktivieren",
|
||||
"disableFor": "2FA für {{email}} deaktivieren",
|
||||
"enabled": "2FA wurde aktiviert",
|
||||
"disabled": "2FA wurde deaktiviert",
|
||||
"setupTitle": "Zwei-Faktor-Authentifizierung einrichten",
|
||||
"manageTitle": "Zwei-Faktor-Authentifizierung",
|
||||
"scanHint": "Scanne den QR-Code mit Google Authenticator, Authy oder einer kompatiblen App.",
|
||||
"enterCode": "Gib den 6-stelligen Code aus deiner Authenticator-App ein:",
|
||||
"confirm": "Bestätigen & aktivieren",
|
||||
"alreadyEnabled": "2FA ist für diesen Account aktiv.",
|
||||
"disableHint": "Klicke auf 'Deaktivieren' um 2FA für diesen Account zu entfernen."
|
||||
}
|
||||
},
|
||||
"audit": {
|
||||
"title": "Audit-Log",
|
||||
|
||||
@@ -152,7 +152,10 @@
|
||||
"emptyTitle": "No custom firewall rules yet.",
|
||||
"emptyDesc": "The system rules above keep SSH (rate-limited), HTTPS :443 and the mgmt UI :3443 open (anti-lockout). Add custom rules for app-specific inbound ports or cross-zone forwards.",
|
||||
"logEnabled": "Logging active — matched packets are written to the firewall log",
|
||||
"ruleDisabled": "Rule disabled"
|
||||
"ruleDisabled": "Rule disabled",
|
||||
"enabled": "Active",
|
||||
"unnamed": "(unnamed)",
|
||||
"zeroHitHint": "No hits since last restart — possibly unused or shadowed rule"
|
||||
},
|
||||
"kpi": {
|
||||
"policyRules": "Policy Rules",
|
||||
@@ -281,7 +284,12 @@
|
||||
"loggedInAs": "Signed in as",
|
||||
"forgotPassword": "Forgot your password?",
|
||||
"viewerBadge": "Read-only",
|
||||
"viewerHint": "Your account has viewer role — all changes are blocked. Contact an admin to change your role."
|
||||
"viewerHint": "Your account has viewer role — all changes are blocked. Contact an admin to change your role.",
|
||||
"totp": {
|
||||
"prompt": "Enter the 6-digit code from your authenticator app.",
|
||||
"verify": "Verify code",
|
||||
"invalidCode": "Invalid code"
|
||||
}
|
||||
},
|
||||
"reset": {
|
||||
"title": "Reset admin password",
|
||||
@@ -392,6 +400,17 @@
|
||||
"backends": "Backends",
|
||||
"attached": "{{count}}/{{total}} domains have a primary backend"
|
||||
},
|
||||
"vipCard": {
|
||||
"title": "VIP / VRRP",
|
||||
"noVips": "No VIPs configured",
|
||||
"keepalivedInactive": "keepalived not running",
|
||||
"state": {
|
||||
"MASTER": "MASTER",
|
||||
"BACKUP": "BACKUP",
|
||||
"FAULT": "FAULT",
|
||||
"UNKNOWN": "Unknown"
|
||||
}
|
||||
},
|
||||
"systemCard": {
|
||||
"title": "System",
|
||||
"version": "Version",
|
||||
@@ -399,9 +418,16 @@
|
||||
"ifaces": "Interfaces",
|
||||
"wg": "WireGuard"
|
||||
},
|
||||
"networkServicesCard": {
|
||||
"title": "Network services",
|
||||
"configure": "Configure"
|
||||
},
|
||||
"alertsCard": {
|
||||
"title": "Recent alerts",
|
||||
"viewAll": "View all"
|
||||
"title": "Active alerts",
|
||||
"viewAll": "View all",
|
||||
"summary": "{{critical}} critical · {{warning}} warning",
|
||||
"summaryWarning": "{{warning}} warning",
|
||||
"summaryCritical": "{{critical}} critical"
|
||||
},
|
||||
"downBackendsAlert": "{{count}} backend(s) completely down — no server UP",
|
||||
"maintenanceAlert": "{{count}} domain(s) in maintenance mode",
|
||||
@@ -664,7 +690,18 @@
|
||||
"hintPrimary": "On primary: edgeguard-ctl cluster-init-replication",
|
||||
"hintStandby": "On secondary: edgeguard-ctl cluster-setup-standby <primary-ip>",
|
||||
"hintKeepalived": "Keepalived on both nodes: sudo systemctl enable --now keepalived",
|
||||
"hintFailover": "On failover: edgeguard-ctl promote (on the secondary node)"
|
||||
"hintFailover": "On failover: edgeguard-ctl promote (on the secondary node)",
|
||||
"splitBrainSection": "Split-brain protection (dual-path VRRP + gateway tracking)",
|
||||
"hbInterface": "Heartbeat interface",
|
||||
"hbInterfaceHelp": "Second interface for VI_HB instance — VRRP advertisements run here independently of VI_1. Leave empty to disable.",
|
||||
"hbSrcIp": "Heartbeat source IP",
|
||||
"hbSrcIpHelp": "Own IP on the heartbeat interface (unicast_src_ip for VI_HB).",
|
||||
"hbPeerIp": "Heartbeat peer IP",
|
||||
"hbPeerIpHelp": "Peer IP on the heartbeat interface (unicast_peer for VI_HB).",
|
||||
"hbRouterId": "Heartbeat router ID",
|
||||
"hbRouterIdHelp": "VRRP virtual_router_id for VI_HB — must differ from main Router ID. Default: 52.",
|
||||
"gwCheckIp": "Gateway check IP",
|
||||
"gwCheckIpHelp": "Upstream gateway IP to ping every 5 s. If unreachable: priority drops by 110 → failover triggers. Leave empty to disable."
|
||||
},
|
||||
"loadTitle": "Per-node resources (mTLS aggregator)",
|
||||
"loadEmpty": "No node resources available — agent listener unreachable?",
|
||||
@@ -699,7 +736,27 @@
|
||||
"step3SetupDesc": "Open the setup wizard on the new node (https://<node-fqdn>:3443/setup), choose \"Join existing cluster\", enter the primary FQDN ({{primaryFqdn}}) and paste the token above.",
|
||||
"generateNewToken": "Generate new token",
|
||||
"setupWizardHint": "Open the setup wizard on the new node",
|
||||
"newNodeFqdnLabel": "New node FQDN"
|
||||
"newNodeFqdnLabel": "New node FQDN",
|
||||
"vipTest": {
|
||||
"cardTitle": "VIP failover test",
|
||||
"cardDesc": "Temporarily move a VIP to the secondary to test that services respond correctly. Keepalived is not involved — this is a raw ip addr add/del test.",
|
||||
"colAddress": "VIP address",
|
||||
"colInterface": "Interface",
|
||||
"colActiveOn": "Active on",
|
||||
"swingBtn": "→ Secondary",
|
||||
"restoreBtn": "← Primary",
|
||||
"swingOk": "VIP successfully moved to secondary",
|
||||
"restoreOk": "VIP restored to primary",
|
||||
"swingFailed": "VIP swing failed",
|
||||
"restoreFailed": "VIP restore failed",
|
||||
"noVips": "No VIPs configured (ip_addresses with is_vip=true)",
|
||||
"steps": "Steps",
|
||||
"stepOk": "OK",
|
||||
"stepFail": "Failed",
|
||||
"confirmSwing": "Move {{addr}} to secondary?",
|
||||
"confirmRestore": "Restore {{addr}} to primary?",
|
||||
"unknown": "unknown"
|
||||
}
|
||||
},
|
||||
"ssl": {
|
||||
"title": "SSL certificates",
|
||||
@@ -1126,7 +1183,16 @@
|
||||
"flushCacheFailed": "Flush failed",
|
||||
"upstreamForwardsInvalid": "Each forwarder must be a valid IP (e.g. 1.1.1.1 or 9.9.9.9)",
|
||||
"accessACLInvalid": "Each entry must be a valid IP or CIDR (e.g. 10.0.0.0/8 or 192.168.1.0/24)",
|
||||
"cacheTTLError": "Cache max-TTL must be ≥ cache min-TTL"
|
||||
"cacheTTLError": "Cache max-TTL must be ≥ cache min-TTL",
|
||||
"cacheSection": "Cache",
|
||||
"prefetch": "Prefetch popular records",
|
||||
"prefetchExtra": "Re-fetch records before TTL expires if queried frequently — reduces latency for hot names.",
|
||||
"serveExpired": "Serve expired records",
|
||||
"serveExpiredExtra": "Return stale cache entries when upstream resolvers are unreachable. Reduces SERVFAIL during outages.",
|
||||
"msgCacheSizeMB": "Message cache (MB)",
|
||||
"msgCacheSizeMBExtra": "RAM for DNS response cache (msg-cache-size). Default 64 MB.",
|
||||
"rrsetCacheSizeMB": "RRset cache (MB)",
|
||||
"rrsetCacheSizeMBExtra": "RAM for resource-record cache (rrset-cache-size). Should be ~2x message cache. Default 128 MB."
|
||||
}
|
||||
},
|
||||
"fwd": {
|
||||
@@ -1180,6 +1246,28 @@
|
||||
"dstdom_regex": "dstdom_regex — destination domain regex",
|
||||
"srcdom_regex": "srcdom_regex — source domain regex",
|
||||
"browser": "browser — User-Agent regex"
|
||||
},
|
||||
"settings": {
|
||||
"title": "Proxy settings",
|
||||
"listenAddresses": "Listen addresses",
|
||||
"listenAddressesExtra": "Comma-separated IPs Squid listens on (e.g. 10.0.5.1, 10.0.20.1). Leave empty to listen on all interfaces.",
|
||||
"listenPort": "Port",
|
||||
"listenPortExtra": "Default: 3128.",
|
||||
"saveFailed": "Settings could not be saved.",
|
||||
"cacheSection": "Cache",
|
||||
"cacheMemMB": "In-memory cache (MB)",
|
||||
"cacheMemMBExtra": "RAM used by Squid for caching (cache_mem). Default 64 MB.",
|
||||
"cacheDirMB": "Disk cache (MB)",
|
||||
"cacheDirMBExtra": "Disk space for the UFS cache. Default 100 MB.",
|
||||
"maxObjSizeMB": "Max. object size (MB)",
|
||||
"maxObjSizeMBExtra": "Largest object Squid will cache. Objects above this are fetched fresh. Default 4 MB.",
|
||||
"timeoutSection": "Timeouts",
|
||||
"connectTimeout": "Connect timeout (s)",
|
||||
"connectTimeoutExtra": "Seconds to wait when opening a connection to the upstream server.",
|
||||
"readTimeout": "Read timeout (s)",
|
||||
"readTimeoutExtra": "Seconds Squid waits between consecutive reads from the upstream.",
|
||||
"requestTimeout": "Request timeout (s)",
|
||||
"requestTimeoutExtra": "Maximum time for a complete request/response cycle."
|
||||
}
|
||||
},
|
||||
"common": {
|
||||
@@ -1205,6 +1293,8 @@
|
||||
"retry": "Retry",
|
||||
"close": "Close",
|
||||
"refresh": "Refresh",
|
||||
"back": "Back",
|
||||
"next": "Next",
|
||||
"up": "UP",
|
||||
"down": "DOWN",
|
||||
"relTime": {
|
||||
@@ -1542,7 +1632,24 @@
|
||||
"errorEmailTaken": "This email address is already in use.",
|
||||
"cannotDeleteSelf": "You cannot delete your own account.",
|
||||
"you": "You",
|
||||
"never": "Never"
|
||||
"never": "Never",
|
||||
"totp": {
|
||||
"on": "2FA",
|
||||
"off": "–",
|
||||
"setup": "Set up 2FA",
|
||||
"manage": "Manage 2FA",
|
||||
"disable": "Disable 2FA",
|
||||
"disableFor": "Disable 2FA for {{email}}",
|
||||
"enabled": "2FA has been enabled",
|
||||
"disabled": "2FA has been disabled",
|
||||
"setupTitle": "Set up two-factor authentication",
|
||||
"manageTitle": "Two-factor authentication",
|
||||
"scanHint": "Scan the QR code with Google Authenticator, Authy, or any compatible app.",
|
||||
"enterCode": "Enter the 6-digit code from your authenticator app:",
|
||||
"confirm": "Confirm & activate",
|
||||
"alreadyEnabled": "2FA is active for this account.",
|
||||
"disableHint": "Click 'Disable 2FA' to remove two-factor authentication from this account."
|
||||
}
|
||||
},
|
||||
"audit": {
|
||||
"title": "Audit log",
|
||||
|
||||
Reference in New Issue
Block a user