SSO per OpenID Connect (Authorization Code + PKCE) zusätzlich zum lokalen Login.
- Regeln: kein Auto-Provisioning (E-Mail muss als User existieren), Rolle aus DB (nie aus Token), lokaler Login+TOTP unangetastet.
- Migration 0040: oidc_settings (Singleton, client_secret_enc via secrets.Box) + users.oidc_subject.
- internal/services/oidc: Settings-Repo (write-only Secret) + lazy go-oidc Client (testbarer Authenticator-Seam).
- internal/handlers/oidc.go: GET/PUT /oidc/settings (admin), GET /auth/oidc/{settings,login,callback}. Flow-State (state/PKCE/nonce) stateless im 5-min signierten HttpOnly-Cookie (SameSite=Lax). email_verified erzwungen, opportunistisches sub-Linking, Session via setSessionCookie+Signer.
- session.SignBlob/VerifyBlob; users.Get/SetOIDCSubject; main.go-Wiring.
- Frontend: App.tsx /auth/me-Bootstrap (für Cookie-Session nach Callback), Login-SSO-Button + sso_error, Settings OIDC-Card, i18n de/en.
- Tests (guarded EG_FWTEST_DSN): Secret-Roundtrip + Callback (Rolle-aus-DB, no_account, disabled, unverified, nonce, state).
Deps: go-oidc/v3, x/oauth2. Scope v1: nur Login (kein SLO/Refresh).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1887 lines
92 KiB
JSON
1887 lines
92 KiB
JSON
{
|
||
"app": {
|
||
"title": "EdgeGuard",
|
||
"subtitle": "Native Reverse-Proxy / VPN / Firewall"
|
||
},
|
||
"nav": {
|
||
"dashboard": "Dashboard",
|
||
"domains": "Domains",
|
||
"backends": "Backends",
|
||
"routing": "Routing",
|
||
"networks": "Netzwerk-Interfaces",
|
||
"ipAddresses": "IP-Adressen",
|
||
"ssl": "SSL-Zertifikate",
|
||
"vpn": "VPN",
|
||
"wireguard": "WireGuard",
|
||
"forwardProxy": "Forward-Proxy",
|
||
"dns": "DNS",
|
||
"ntp": "Zeit (NTP)",
|
||
"firewall": "Firewall",
|
||
"firewallLive": "Firewall-Log",
|
||
"cluster": "Cluster",
|
||
"logs": "Logs",
|
||
"audit": "Audit-Log",
|
||
"backups": "Backups",
|
||
"diagnostics": "Diagnose",
|
||
"users": "Benutzer",
|
||
"alerts": "Alarme",
|
||
"license": "Lizenz",
|
||
"settings": "Einstellungen",
|
||
"crowdsec": "CrowdSec IDS",
|
||
"section": {
|
||
"overview": "Übersicht",
|
||
"routing": "Routing",
|
||
"network": "Netzwerk",
|
||
"security": "Sicherheit",
|
||
"system": "System"
|
||
},
|
||
"waf": "WAF"
|
||
},
|
||
"fw": {
|
||
"title": "Firewall",
|
||
"intro": "Fortigate-Style: Regeln aus Zonen × Adress-Objekten/Gruppen × Services/Service-Gruppen × Action. NAT separat. Top-down, first-match.",
|
||
"tabs": {
|
||
"rules": "Regeln",
|
||
"nat": "NAT",
|
||
"live": "Live-Log",
|
||
"zones": "Zonen",
|
||
"addrObj": "Adress-Objekte",
|
||
"addrGrp": "Adress-Gruppen",
|
||
"services": "Services",
|
||
"svcGrp": "Service-Gruppen",
|
||
"system": "System-Regeln"
|
||
},
|
||
"zone": {
|
||
"name": "Name",
|
||
"description": "Beschreibung",
|
||
"builtin": "vordefiniert",
|
||
"builtinHint": "Vordefinierte Zonen können nicht gelöscht werden — Renderer und Anti-Lockout-Regeln verlassen sich darauf.",
|
||
"builtinNameLocked": "Name vordefiniert — kann nicht geändert werden, weil bestehende Regeln und Interfaces ihn referenzieren.",
|
||
"namePattern": "Nur Kleinbuchstaben, Ziffern, _ und -; muss mit Buchstaben beginnen, max. 32 Zeichen.",
|
||
"add": "Zone hinzufügen",
|
||
"edit": "Zone bearbeiten",
|
||
"deleteConfirm": "Zone {{name}} wirklich löschen?",
|
||
"emptyTitle": "Noch keine eigenen Firewall-Zonen.",
|
||
"emptyDesc": "Zonen gruppieren Interfaces (lan, wan, dmz). Vordefinierte Zonen sind bereits da; eigene anlegen wenn du z. B. eine separate dmz-Zone oder eine wg-Zone fürs VPN brauchst."
|
||
},
|
||
"ao": {
|
||
"name": "Name",
|
||
"kind": "Typ",
|
||
"value": "Wert",
|
||
"description": "Beschreibung",
|
||
"add": "Adress-Objekt hinzufügen",
|
||
"edit": "Adress-Objekt bearbeiten",
|
||
"deleteConfirm": "Adress-Objekt {{name}} wirklich löschen?",
|
||
"emptyTitle": "Noch keine Adress-Objekte.",
|
||
"emptyDesc": "Wiederverwendbare benannte IPs/Netze/Ranges für Firewall- und NAT-Regeln (z. B. office-net = 10.0.0.0/24, mailbox-1 = 10.0.1.42)."
|
||
},
|
||
"ag": {
|
||
"name": "Name",
|
||
"members": "Mitglieder",
|
||
"description": "Beschreibung",
|
||
"add": "Adress-Gruppe hinzufügen",
|
||
"edit": "Adress-Gruppe bearbeiten",
|
||
"selectMembers": "Adress-Objekte wählen",
|
||
"deleteConfirm": "Adress-Gruppe {{name}} wirklich löschen?",
|
||
"emptyTitle": "Noch keine Adress-Gruppen.",
|
||
"emptyDesc": "Bündele mehrere Adress-Objekte zu einer Gruppe (z. B. office-locations = [hq-net, branch-1-net, branch-2-net]) — Regeln müssen dann nur eine Gruppe referenzieren."
|
||
},
|
||
"svc": {
|
||
"name": "Name",
|
||
"proto": "Protokoll",
|
||
"ports": "Ports",
|
||
"portStart": "Port (Start)",
|
||
"portEnd": "Port (Ende)",
|
||
"description": "Beschreibung",
|
||
"builtinHint": "Vordefiniert — nicht editierbar",
|
||
"add": "Service hinzufügen",
|
||
"edit": "Service bearbeiten",
|
||
"deleteConfirm": "Service {{name}} wirklich löschen?",
|
||
"emptyTitle": "Noch keine eigenen Services.",
|
||
"emptyDesc": "Vordefinierte (HTTP, HTTPS, SSH, …) sind bereits da. Eigene Services für app-spezifische Ports (z. B. mailcow-imaps tcp/993) erleichtern Regeln deutlich."
|
||
},
|
||
"sg": {
|
||
"name": "Name",
|
||
"members": "Mitglieder",
|
||
"description": "Beschreibung",
|
||
"add": "Service-Gruppe hinzufügen",
|
||
"edit": "Service-Gruppe bearbeiten",
|
||
"selectMembers": "Services wählen",
|
||
"deleteConfirm": "Service-Gruppe {{name}} wirklich löschen?",
|
||
"emptyTitle": "Noch keine Service-Gruppen.",
|
||
"emptyDesc": "Bündele mehrere Services zu einer Gruppe (z. B. web-stack = [HTTP, HTTPS, HTTP/3]) — eine Regel mit der Gruppe ersetzt drei Regeln pro Service."
|
||
},
|
||
"rule": {
|
||
"name": "Name",
|
||
"priority": "Priority",
|
||
"enabled": "Aktiv",
|
||
"log": "Logging",
|
||
"action": "Aktion",
|
||
"src": "Quelle",
|
||
"dst": "Ziel",
|
||
"service": "Service",
|
||
"srcZone": "Quell-Zone",
|
||
"dstZone": "Ziel-Zone",
|
||
"srcKind": "Quell-Typ",
|
||
"dstKind": "Ziel-Typ",
|
||
"object": "Adress-Objekt",
|
||
"group": "Adress-Gruppe",
|
||
"serviceKind": "Service-Typ",
|
||
"serviceGroup": "Service-Gruppe",
|
||
"comment": "Kommentar",
|
||
"hits": "Hits",
|
||
"add": "Regel hinzufügen",
|
||
"edit": "Regel bearbeiten",
|
||
"deleteConfirm": "Diese Regel wirklich löschen?",
|
||
"namePlaceholder": "z.B. SSH von Office erlauben",
|
||
"cidr": "CIDR",
|
||
"actions": {
|
||
"accept": "ACCEPT",
|
||
"drop": "DROP",
|
||
"reject": "REJECT"
|
||
},
|
||
"kinds": {
|
||
"any": "Beliebig",
|
||
"object": "Objekt",
|
||
"group": "Gruppe",
|
||
"cidr": "CIDR"
|
||
},
|
||
"duplicate": "Regel duplizieren",
|
||
"duplicated": "Regel dupliziert (deaktiviert, Priorität +1).",
|
||
"cidrInvalid": "Gültiges CIDR eingeben (z.B. 10.0.0.0/24)",
|
||
"moveUp": "Nach oben (höhere Priorität)",
|
||
"moveDown": "Nach unten (niedrigere Priorität)",
|
||
"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",
|
||
"unnamed": "(kein Name)",
|
||
"zeroHitHint": "Keine Treffer seit dem letzten Neustart — möglicherweise ungenutzte oder überlagerte Regel"
|
||
},
|
||
"kpi": {
|
||
"policyRules": "Policy-Regeln",
|
||
"active": "aktiv",
|
||
"disabled": "deaktiviert",
|
||
"natRules": "NAT-Regeln",
|
||
"natHint": "DNAT / SNAT / Masquerade",
|
||
"zones": "Zonen",
|
||
"zonesHint": "Netzwerksegmente",
|
||
"defaultPolicy": "Default-Policy",
|
||
"nftActive": "nftables aktiv",
|
||
"nftInactive": "nftables inaktiv"
|
||
},
|
||
"filter": {
|
||
"search": "Name / Kommentar suchen…",
|
||
"allActions": "Alle Aktionen",
|
||
"allZones": "Alle Zonen",
|
||
"noResults": "Keine Regeln entsprechen dem Filter",
|
||
"noResultsHint": "Filter zurücksetzen um alle Regeln zu sehen.",
|
||
"groupView": "Nach Zone gruppieren",
|
||
"flatView": "Flache Liste",
|
||
"rules": "Regeln"
|
||
},
|
||
"nat": {
|
||
"name": "Name",
|
||
"priority": "Priority",
|
||
"kind": "Typ",
|
||
"enabled": "Aktiv",
|
||
"match": "Match",
|
||
"target": "Ziel",
|
||
"inZone": "Eingangs-Zone",
|
||
"outZone": "Ausgangs-Zone",
|
||
"proto": "Protokoll",
|
||
"matchSrcCidr": "Source-CIDR (Match)",
|
||
"matchDstCidr": "Dest-CIDR (Match)",
|
||
"matchDstCidrHint": "leer = jede dest-IP (z.B. öffentliche IP der Box)",
|
||
"dportStart": "Port (Start)",
|
||
"dportEnd": "Port (Ende)",
|
||
"portRangeError": "End-Port muss ≥ Start-Port sein",
|
||
"targetAddr": "Ziel-Adresse",
|
||
"targetPortStart": "Ziel-Port (Start)",
|
||
"targetPortEnd": "Ziel-Port (Ende)",
|
||
"comment": "Kommentar",
|
||
"add": "NAT-Regel hinzufügen",
|
||
"edit": "NAT-Regel bearbeiten",
|
||
"deleteConfirm": "Diese NAT-Regel wirklich löschen?",
|
||
"namePlaceholder": "z.B. HTTP zu Web-Backend weiterleiten",
|
||
"emptyTitle": "Noch keine NAT-Regeln.",
|
||
"emptyDesc": "DNAT (z. B. extern :2030 → intern 10.10.20.12:22 für SSH zu einem internen Host) oder SNAT/MASQUERADE (Internet-Zugang für ein internes Subnetz über die Box-IP)."
|
||
},
|
||
"sys": {
|
||
"title": "System-Regeln (immer aktiv)",
|
||
"chain": "Chain",
|
||
"match": "Match",
|
||
"action": "Aktion",
|
||
"note": "Hinweis",
|
||
"policy": "Default-Policy",
|
||
"policyValue": "Eingang DROP — alles muss explizit erlaubt werden.",
|
||
"order": "Auswertung",
|
||
"orderValue": "System-Regeln zuerst, danach Operator-Regeln top-down (priority asc, first-match).",
|
||
"lockout": "Anti-Lockout",
|
||
"lockoutValue": "SSH (22) und Management-UI (443) sind immer erreichbar — können auch vom Operator nicht versehentlich gesperrt werden."
|
||
}
|
||
},
|
||
"networks": {
|
||
"title": "Netzwerk",
|
||
"intro": "Interfaces (Ethernet/VLAN/Bond/Bridge) und Static-Routes. Live-Discovery + deklarative Konfiguration aus der DB.",
|
||
"tabs": {
|
||
"interfaces": "Interfaces",
|
||
"routes": "Routen"
|
||
},
|
||
"systemDiscovered": "System-Interfaces — Live-Traffic-Zähler (seit letztem HAProxy-Start)",
|
||
"addresses": "Adressen",
|
||
"linkType": "Typ",
|
||
"systemEmpty": "Keine Kernel-Interfaces gefunden",
|
||
"addInterface": "Interface hinzufügen",
|
||
"editInterface": "Interface bearbeiten",
|
||
"emptyTitle": "Noch keine verwalteten Interfaces.",
|
||
"emptyDesc": "System-Interfaces (siehe oben) werden read-only erkannt. Verwaltete Interfaces sind das was EdgeGuard selbst anlegt — VLANs, Bridges, Bond, GRE-Tunnels — und in /etc/network/interfaces.d rendert.",
|
||
"name": "Name",
|
||
"type": "Typ",
|
||
"parent": "Parent-Interface",
|
||
"selectParent": "Parent wählen",
|
||
"vlan": "VLAN",
|
||
"vlanId": "VLAN-ID",
|
||
"composition": "Zusammensetzung",
|
||
"members": "Member-Interfaces",
|
||
"selectMembers": "Physische Interfaces wählen",
|
||
"membersRequired": "Mindestens ein Member-Interface erforderlich",
|
||
"membersHintBridge": "Eine Bridge bündelt mehrere physische Ports auf L2 — typisch zwei Ports für einen Software-Switch.",
|
||
"membersHintBond": "Ein Bond aggregiert mehrere physische Ports zu einem logischen Link (LACP / active-backup).",
|
||
"role": "Zone",
|
||
"roleHint": "Zonen kommen aus Firewall → Zonen. Eigene Zonen (z.B. iot, guest) lassen sich dort anlegen.",
|
||
"mtu": "MTU",
|
||
"active": "Aktiv",
|
||
"description": "Beschreibung",
|
||
"actions": "Aktionen",
|
||
"deleteConfirm": "Interface {{name}} wirklich löschen?"
|
||
},
|
||
"ips": {
|
||
"title": "IP-Adressen",
|
||
"intro": "Adressen, die das Betriebssystem zeigt (Read-only oben) plus die Adressen, die EdgeGuard zusätzlich verwaltet — inklusive VIPs für Cluster-Failover.",
|
||
"systemDiscovered": "Adressen am Kernel (read-only)",
|
||
"managedTitle": "Verwaltete Adressen",
|
||
"family": "Familie",
|
||
"addAddress": "Adresse hinzufügen",
|
||
"emptyTitle": "Noch keine eigenen IP-Adressen.",
|
||
"emptyDesc": "Lege deine erste IP an — Floating-VIPs für HA-Failover oder zusätzliche Listen-Adressen für HAProxy/Squid/Unbound. Box-eigene Distro-IPs werden oben unter \"Erkannte IPs\" gelesen.",
|
||
"editAddress": "Adresse bearbeiten",
|
||
"interface": "Interface",
|
||
"selectInterface": "Interface wählen",
|
||
"address": "Adresse",
|
||
"prefix": "Prefix",
|
||
"vip": "VIP",
|
||
"vipFlag": "Als VIP markieren",
|
||
"vipPriority": "VIP-Priorität (Cluster-Failover)",
|
||
"active": "Aktiv",
|
||
"description": "Beschreibung",
|
||
"actions": "Aktionen",
|
||
"deleteConfirm": "Adresse {{addr}} wirklich löschen?"
|
||
},
|
||
"auth": {
|
||
"loginTitle": "Anmelden",
|
||
"email": "E-Mail",
|
||
"password": "Passwort",
|
||
"login": "Anmelden",
|
||
"logout": "Abmelden",
|
||
"loginFailed": "Anmeldung fehlgeschlagen",
|
||
"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.",
|
||
"sso": {
|
||
"login": "Mit SSO anmelden",
|
||
"err": {
|
||
"no_account": "Kein EdgeGuard-Konto für diese E-Mail. Bitte wende dich an einen Administrator.",
|
||
"disabled": "Dieses Konto ist deaktiviert.",
|
||
"unverified": "Die E-Mail-Adresse ist beim Identity-Provider nicht verifiziert.",
|
||
"state": "Sicherheitsprüfung fehlgeschlagen (State). Bitte erneut versuchen.",
|
||
"nonce": "Sicherheitsprüfung fehlgeschlagen (Nonce). Bitte erneut versuchen.",
|
||
"token": "Das Token vom Identity-Provider konnte nicht verifiziert werden.",
|
||
"exchange": "Code-Austausch mit dem Identity-Provider fehlgeschlagen.",
|
||
"expired": "Die Anmeldesitzung ist abgelaufen. Bitte erneut versuchen.",
|
||
"denied": "Anmeldung beim Identity-Provider abgebrochen.",
|
||
"subject_mismatch": "Die Identität passt nicht zum hinterlegten Konto.",
|
||
"config": "SSO ist nicht korrekt konfiguriert.",
|
||
"server": "Interner Fehler bei der SSO-Anmeldung.",
|
||
"generic": "SSO-Anmeldung fehlgeschlagen."
|
||
}
|
||
},
|
||
"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",
|
||
"intro": "Self-Service-Reset über CLI-Token. Du brauchst SSH-Zugang zur Box.",
|
||
"cliTitle": "1. Auf der Box ausführen:",
|
||
"token": "Reset-Token",
|
||
"tokenExtra": "32 hex-Zeichen aus der CLI-Ausgabe. Token ist 30 Minuten gültig.",
|
||
"newPassword": "Neues Passwort (min. 12 Zeichen)",
|
||
"confirmPassword": "Passwort bestätigen",
|
||
"submit": "Passwort setzen",
|
||
"backToLogin": "← Zurück zum Login",
|
||
"mismatch": "Passwörter stimmen nicht überein",
|
||
"success": "Passwort erfolgreich gesetzt. Du wirst weitergeleitet …",
|
||
"failed": "Reset fehlgeschlagen"
|
||
},
|
||
"setup": {
|
||
"title": "Erst-Einrichtung",
|
||
"intro": "Lege den Admin-Account an, gib die öffentliche FQDN an und – optional – einen Lizenzschlüssel. Ohne Lizenz startet eine 30-Tage-Trial.",
|
||
"preflightTitle": "Vor dem Klick auf \"Setup abschließen\"",
|
||
"preflightDesc": "Die FQDN muss bereits per DNS auf diese Box zeigen (A/AAAA-Record). Ohne DNS-Auflösung scheitert die spätere ACME-HTTP-01-Challenge — dein Browser-URL muss schon jetzt diese FQDN sein, sonst klemmt's spätestens beim ersten Let's-Encrypt-Issue.",
|
||
"adminEmail": "Admin-E-Mail",
|
||
"adminEmailHint": "Login-Identifier für die Management-UI. Wird NICHT für ausgehende Mails benutzt (das ist die ACME-E-Mail unten bzw. die SMTP-Settings im Alerts-Channel).",
|
||
"adminPassword": "Admin-Passwort",
|
||
"passwordRule": "Mindestens 12 Zeichen.",
|
||
"fqdn": "Öffentliche FQDN",
|
||
"fqdnHint": "Vollqualifizierter Hostname dieser Box (z. B. eg.example.com). Wird Subject-CN des Self-Signed-Bootstrap-Cert UND wandert in das ACME-Cert sobald Let's Encrypt ausgestellt hat. Muss per DNS auf die Public-IP zeigen.",
|
||
"fqdnInvalid": "Sieht nicht wie eine gültige FQDN aus (mindestens label.tld).",
|
||
"acmeEmail": "ACME-/Let's-Encrypt-E-Mail",
|
||
"acmeEmailHint": "Wird Let's Encrypt als Account-Contact mitgegeben (Expiry-Warnungen + Compliance-Mails von LE). Kann gleich der Admin-E-Mail sein, muss aber nicht.",
|
||
"licenseKey": "Lizenzschlüssel (optional)",
|
||
"licenseKeyHint": "Wenn vorhanden: 30-Tage-Trial wird übersprungen, Features unmittelbar freigeschaltet. Kann auch später unter License nachgereicht werden.",
|
||
"submit": "Setup abschließen",
|
||
"successTitle": "Setup abgeschlossen",
|
||
"successHint": "Du wirst zur Anmeldung weitergeleitet.",
|
||
"back": "Zurück",
|
||
"modeSelect": "Wie soll dieser Server eingerichtet werden?",
|
||
"modeStandalone": "Neuinstallation",
|
||
"modeStandaloneDesc": "Eigenständiger EdgeGuard oder erster Cluster-Knoten. Erstellt einen Admin-Account auf diesem Server.",
|
||
"modeNode": "Vorhandenem Cluster beitreten",
|
||
"modeNodeDesc": "Diesen Server als zweiten Cluster-Knoten hinzufügen. Admin-Zugangsdaten werden automatisch vom Primary repliziert.",
|
||
"nodeTitle": "Cluster-Knoten einrichten",
|
||
"nodeIntro": "Trage den FQDN dieses Knotens ein. Es wird kein Admin-Account erstellt — die Zugangsdaten kommen nach der Datenbankreplikation vom Primary.",
|
||
"nodePreflightTitle": "Vor dem Fortfahren",
|
||
"nodePreflightDesc": "Stelle sicher, dass EdgeGuard auf dem Primary bereits läuft und du dort einen Join-Token erstellen kannst (Cluster-Seite). Du benötigst ihn im nächsten Schritt.",
|
||
"nodeSubmit": "Als Cluster-Knoten einrichten",
|
||
"nodeSuccessTitle": "Erfolgreich beigetreten!",
|
||
"nodeJoinTitle": "Nächster Schritt: Cluster beitreten",
|
||
"nodeJoinDesc": "Dieser Server ({{fqdn}}) ist konfiguriert. Führe jetzt den Join-Befehl aus, um ihn mit dem Primary zu verbinden.",
|
||
"nodeStep1Title": "Join-Token auf dem Primary generieren",
|
||
"nodeStep1Desc": "Öffne die Cluster-Seite auf deinem Primary-EdgeGuard → Knoten hinzufügen → Join-Token generieren. Token kopieren.",
|
||
"nodeStep2Title": "Join-Befehl auf diesem Server ausführen",
|
||
"nodeStep2Desc": "Ersetze <primary-fqdn> mit dem FQDN deines Primarys und <token> mit dem Token aus Schritt 1:",
|
||
"nodeStep3Title": "API neu starten",
|
||
"nodeStep3Desc": "Nach erfolgreichem Join-Befehl API neu starten, damit die replizierte Konfiguration übernommen wird:",
|
||
"nodeLoginNote": "Der Login erfolgt mit den Admin-Zugangsdaten des Primarys. Das funktioniert sobald die Datenbankreplikation aufgebaut wurde (der Join-Befehl richtet das ein).",
|
||
"primaryFqdn": "Primary-FQDN",
|
||
"primaryFqdnHint": "FQDN des bestehenden Primary-EdgeGuard-Knotens (z.B. eg1.example.com).",
|
||
"joinToken": "Join-Token",
|
||
"joinTokenHint": "Auf dem Primary generieren: Cluster-Seite → Knoten hinzufügen → Join-Token generieren.",
|
||
"joinInsecure": "TLS-Prüfung überspringen (falls der Primary ein self-signed Zertifikat hat)",
|
||
"nodeSuccessDesc": "Cluster-Zertifikate wurden geschrieben. Noch ein letzter Schritt:",
|
||
"nodeRestartTitle": "Neustart erforderlich",
|
||
"nodeRestartDesc": "Führe folgenden Befehl auf diesem Server aus, um die neuen Cluster-Zertifikate zu laden:"
|
||
},
|
||
"dashboard": {
|
||
"title": "Dashboard",
|
||
"welcomeHint": "EdgeGuard-Übersicht — Health, Counts, Live-Status der wichtigsten Dienste.",
|
||
"kpi": {
|
||
"domains": "Domains",
|
||
"backends": "Backends",
|
||
"ifaces": "Interfaces",
|
||
"fwRules": "FW-Regeln",
|
||
"natRules": "NAT-Regeln",
|
||
"wg": "WG-Verbindungen"
|
||
},
|
||
"wgCard": {
|
||
"title": "WireGuard",
|
||
"empty": "Noch kein WG-Tunnel angelegt.",
|
||
"peersOnline": "{{online}} / {{total}} Peers verbunden"
|
||
},
|
||
"firewallCard": {
|
||
"title": "Firewall",
|
||
"zones": "Zonen",
|
||
"activeRules": "{{rules}} aktive Regeln · {{nat}} NAT"
|
||
},
|
||
"sslCard": {
|
||
"title": "SSL-Zertifikate",
|
||
"total": "Verwaltete Zertifikate",
|
||
"certExpired": "{{count}} Zertifikat(e) abgelaufen — sofort erneuern",
|
||
"expiringSoon": "{{count}} läuft bald ab (< 30 Tage)",
|
||
"allFresh": "Alle Zertifikate haben > 30 Tage Restlaufzeit."
|
||
},
|
||
"clusterCard": {
|
||
"title": "Cluster",
|
||
"nodes": "Knoten",
|
||
"modeSingle": "Single-Node",
|
||
"modeCluster": "Cluster",
|
||
"drift": "Config-Drift erkannt",
|
||
"health": {
|
||
"ok": "OK",
|
||
"degraded": "degraded",
|
||
"split-brain": "split-brain"
|
||
}
|
||
},
|
||
"routingCard": {
|
||
"title": "Routing",
|
||
"domains": "Domains",
|
||
"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",
|
||
"api": "API",
|
||
"ifaces": "Interfaces",
|
||
"wg": "WireGuard"
|
||
},
|
||
"networkServicesCard": {
|
||
"title": "Netzwerk-Dienste",
|
||
"configure": "Konfigurieren"
|
||
},
|
||
"alertsCard": {
|
||
"title": "Aktuelle Alerts",
|
||
"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",
|
||
"onboardingTitle": "Willkommen bei EdgeGuard",
|
||
"onboardingIntro": "Frische Box — hier die nächsten Schritte um Customer-Traffic zu routen:",
|
||
"onboardingStep1": "Backend-Pool anlegen (App-Server hinter HAProxy)",
|
||
"onboardingStep2": "Domain hinzufügen (FQDN, primary Backend zuweisen)",
|
||
"onboardingStep3": "TLS-Zertifikat ausstellen (Let's Encrypt HTTP-01)",
|
||
"servicesCard": {
|
||
"title": "Service-Status (live, 10s)"
|
||
},
|
||
"activityCard": {
|
||
"title": "Letzte Aktivität (Audit-Log)",
|
||
"empty": "Noch keine Aktivität — Mutationen werden hier protokolliert."
|
||
},
|
||
"haproxyCard": {
|
||
"title": "HAProxy-Backends (live)",
|
||
"empty": "Keine Backend-Stats erreichbar (HAProxy down oder admin.sock-permission).",
|
||
"frontends": "Listener"
|
||
},
|
||
"resCard": {
|
||
"load": "Load",
|
||
"memory": "Memory",
|
||
"disk": "Disk /",
|
||
"free": "frei",
|
||
"conntrack": "Conntrack",
|
||
"uptime": "Uptime"
|
||
},
|
||
"licenseOk": "Lizenz OK",
|
||
"licenseTrial": "Trial",
|
||
"licenseTrialDays": "Trial · {{days}}d"
|
||
},
|
||
"domains": {
|
||
"title": "Domains",
|
||
"intro": "Verwalte FQDNs, die HAProxy terminiert. Optionales Primary-Backend als Catch-all; Pfad-Routing via Routing-Regeln.",
|
||
"addDomain": "Domain hinzufügen",
|
||
"editDomain": "Domain bearbeiten",
|
||
"backToList": "Zurück zur Übersicht",
|
||
"settingsCard": "Domain-Einstellungen",
|
||
"moreOnDetailPage": "HSTS, Rate-Limit, Wartungsmodus und mehr auf der Domain-Detailseite nach dem Speichern.",
|
||
"emptyTitle": "Noch keine Domains.",
|
||
"emptyDesc": "Lege deine erste Domain an — HAProxy terminiert dann TLS für diesen Hostnamen und routet an das gewählte Backend.",
|
||
"name": "Name",
|
||
"active": "Aktiv",
|
||
"primaryBackend": "Primary-Backend",
|
||
"primaryBackendHint": "Catch-all-Backend für Requests, die kein Routing-Regel-Match haben. Optional — leer lassen, wenn alles über Routing-Regeln läuft.",
|
||
"selectBackend": "Backend wählen",
|
||
"noBackend": "kein Backend",
|
||
"quickBackendBtn": "+ neu",
|
||
"quickBackendBtnHint": "Backend + ersten Upstream-Server in einem Klick anlegen — spart den Umweg über die Backends-Seite.",
|
||
"quickBackendTitle": "Backend schnell anlegen",
|
||
"quickBackendName": "Backend-Name",
|
||
"quickBackendScheme": "Schema",
|
||
"quickBackendAddress": "Server-Adresse (Erster Upstream)",
|
||
"quickBackendPort": "Port",
|
||
"quickBackendCreated": "Backend + Server angelegt und ausgewählt.",
|
||
"quickBackendFailed": "Quick-Backend-Anlage fehlgeschlagen",
|
||
"httpToHttps": "HTTP→HTTPS",
|
||
"hsts": "HSTS",
|
||
"tlsCert": "TLS-Cert",
|
||
"tlsCertValid": "gültig",
|
||
"tlsCertExpiring": "noch {{days}}d",
|
||
"tlsCertExpired": "abgelaufen",
|
||
"tlsCertError": "Fehler",
|
||
"tlsCertNone": "fehlt",
|
||
"tlsCertNoneHint": "Kein eigener TLS-Cert für diese Domain — HAProxy serviert das Bootstrap-Self-Signed-Cert. Domain öffnen und \"Zertifikat ausstellen\" klicken.",
|
||
"hstsMaxAge": "HSTS max-age (Sek.)",
|
||
"hstsMaxAgeHint": "Wie lange Browser den HTTPS-Zwang cachen. Empfehlung 31536000 (1 Jahr).",
|
||
"hstsSubdomains": "includeSubDomains",
|
||
"hstsSubdomainsHint": "Erstreckt HSTS auf alle Sub-Domains. Nur aktivieren wenn alle Subs ausschließlich HTTPS sprechen.",
|
||
"hstsPreload": "preload",
|
||
"hstsPreloadHint": "Setzt den preload-Flag damit die Domain in die hstspreload.org-Liste aufgenommen werden kann. Voraussetzung: max-age ≥ 31536000 + includeSubDomains an.",
|
||
"maintenance": "Wartungs-Modus",
|
||
"maintenanceHint": "An: alle Requests auf diese Domain bekommen direkt 503 von HAProxy. Backends werden nicht kontaktiert.",
|
||
"maintenanceMessage": "Wartungs-Meldung",
|
||
"maintenanceMessagePlaceholder": "Service vorübergehend nicht verfügbar.",
|
||
"wwwRedirect": "www-Redirect",
|
||
"wwwRedirectHint": "Kanonisiert die Domain. „nach naked\": Name=example.com → www.example.com leitet auf example.com. „nach www\": Name=www.example.com → example.com leitet auf www.example.com.",
|
||
"wwwRedirectNone": "Kein Redirect",
|
||
"wwwRedirectToNaked": "→ naked (ohne www)",
|
||
"wwwRedirectToWWW": "→ www",
|
||
"rateLimit": "Rate-Limit (pro Client-IP)",
|
||
"rateLimitHint": "Max. Requests pro Sekunde je Client-IP. HAProxy zählt über ein 10-Sekunden-Fenster pro Stick-Table (max. 100k IPs). 0 = aus.",
|
||
"maxBody": "Max. Request-Body",
|
||
"maxBodyHint": "Cap auf Content-Length-Header. Größere Requests bekommen 413. Nicht erkannt: Chunked-Bodies (HAProxy bufferd nicht standardmäßig). 0 = aus.",
|
||
"disableH3": "HTTP/3 (QUIC) deaktivieren",
|
||
"disableH3Hint": "Unterdrückt den Alt-Svc-Response-Header für diese Domain. Browser erhalten keinen Hinweis auf h3/QUIC und bleiben auf h2/http1.1. Sinnvoll bei Clients die Probleme mit QUIC-Verbindungen melden.",
|
||
"headersBtn": "Headers",
|
||
"headersTitle": "Response-Headers — {{name}}",
|
||
"headersHint": "Diese Header werden von HAProxy auf jede Response für diese Domain gesetzt (http-response set-header). Reihenfolge via Position.",
|
||
"headersEmpty": "Noch keine Custom-Headers konfiguriert.",
|
||
"addHeader": "Header hinzufügen",
|
||
"editHeader": "Header bearbeiten",
|
||
"headerName": "Name",
|
||
"headerNameHint": "Nur Buchstaben, Zahlen, Bindestriche. Pro Domain case-insensitive eindeutig.",
|
||
"headerNamePattern": "Nur a-z, A-Z, 0-9, '-'",
|
||
"headerValue": "Wert",
|
||
"headerPosition": "Position",
|
||
"headerDeleteConfirm": "Header „{{name}}\" wirklich löschen?",
|
||
"settingsSection": "HAProxy-Einstellungen",
|
||
"notes": "Notizen",
|
||
"actions": "Aktionen",
|
||
"edit": "Bearbeiten",
|
||
"delete": "Löschen",
|
||
"deleteConfirm": "Domain {{name}} wirklich löschen?",
|
||
"maintenanceTag": "Wartung",
|
||
"rateLimitTag": "Rate-Limit",
|
||
"routingRulesTitle": "Routing-Regeln — {{name}}",
|
||
"routingRulesHint": "Pfad-Präfix → Backend-Zuordnungen für diese Domain. Niedrigste Prioritätszahl gewinnt; nicht gematchte Anfragen gehen an das Primary-Backend.",
|
||
"routingRulesEmpty": "Keine Routing-Regeln — alle Anfragen gehen an das Primary-Backend.",
|
||
"backendUp": "Backend UP",
|
||
"backendDown": "Backend DOWN"
|
||
},
|
||
"backends": {
|
||
"title": "Backends",
|
||
"intro": "Upstream-Pools (Backend = N Server). HAProxy verteilt laut LB-Algorithmus; Health-Check-Pfad aktiviert HTTP-Probes alle 5s pro Server.",
|
||
"addBackend": "Backend-Pool hinzufügen",
|
||
"emptyTitle": "Noch keine Backend-Pools.",
|
||
"emptyDesc": "Lege deinen ersten Pool an — definiere Upstream-Server (z. B. App-Server / Mailbox-Knoten) damit Domains darauf routen können.",
|
||
"editBackend": "Backend-Pool bearbeiten",
|
||
"name": "Name",
|
||
"scheme": "Schema",
|
||
"target": "Ziel",
|
||
"healthCheck": "Health-Check-Pfad",
|
||
"liveStatus": "HAProxy-Status",
|
||
"active": "Aktiv",
|
||
"usedBy": "Genutzt von",
|
||
"noDomain": "keine Domain",
|
||
"attachedDomains": "Domains",
|
||
"attachedToOther": "derzeit: #{{id}}",
|
||
"attachedDomainsHint": "Domains, die dieses Backend als Primary verwenden. Auswahl umkonfiguriert die Domains direkt — gleiche Quelle wie der Backend-Picker im Domain-Modal.",
|
||
"selectDomains": "Domains wählen",
|
||
"lbAlgo": "Load-Balancing",
|
||
"lbAlgoHint": "roundrobin = gleichmäßig, leastconn = an den Server mit wenigsten Verbindungen, source = sticky per Client-IP (für stateful Apps ohne shared session).",
|
||
"websocket": "WebSocket-Support",
|
||
"websocketHint": "An: erlaubt langlebige WebSocket-/Long-Poll-Verbindungen (z. B. Proxmox-Console, SSH-WS, AsyncAPI) — Tunnel-Idle 1h statt 60s. Aus: strikte HTTP-Timeouts.",
|
||
"forceHttp1": "HTTP/1.1 erzwingen",
|
||
"forceHttp1Hint": "Deaktiviert HTTP/2 (h2) auf der Backend-Verbindung — HAProxy handelt nur noch HTTP/1.1 aus. Nötig für Backends die kein h2 unterstützen (z. B. ältere nginx-Konfigurationen ohne h2-Modul, Legacy-Apps).",
|
||
"servers": "Server",
|
||
"noServers": "kein Server",
|
||
"noServersWarning": "Ein oder mehrere aktive Backends haben keine Server konfiguriert.",
|
||
"noServersWarningDesc": "HAProxy liefert für Traffic auf diese Backends 503. Backend öffnen (oranger Tag) und mindestens einen Server hinzufügen.",
|
||
"nServers": "{{n}} Server",
|
||
"serversIn": "Server in „{{name}}\"",
|
||
"serverHintCreate": "Speichern legt nur den Pool an. Server kommen im nächsten Schritt — Pool öffnen → „Server hinzufügen\".",
|
||
"actions": "Aktionen",
|
||
"deleteConfirm": "Backend-Pool {{name}} wirklich löschen? Alle Server im Pool werden mitentfernt.",
|
||
"deleteInUse": "Von {{n}} Routing-Regel(n) verwendet — zuerst diese löschen",
|
||
"backToList": "Zurück zur Übersicht",
|
||
"settingsCard": "Pool-Einstellungen",
|
||
"healthCheckHint": "HTTP-Pfad den HAProxy alle 5s pro Server prüft. Leer lassen = kein Health-Check.",
|
||
"server": {
|
||
"intro": "Upstream-Server im Pool. Reihenfolge in HAProxy egal — der LB-Algorithmus entscheidet.",
|
||
"add": "Server hinzufügen",
|
||
"edit": "Server bearbeiten",
|
||
"name": "Server-Name",
|
||
"address": "Adresse",
|
||
"addressInvalid": "Gültige IPv4/IPv6-Adresse oder Hostname eingeben",
|
||
"port": "Port",
|
||
"target": "Endpoint",
|
||
"weight": "Gewicht",
|
||
"weightHint": "0–256. Höher = mehr Traffic. 100 = Standard.",
|
||
"backup": "Backup",
|
||
"backupHint": "Backup-Server werden nur angesprochen, wenn alle primären Server (non-backup) down sind.",
|
||
"empty": "Noch keine Server im Pool. „Server hinzufügen\" startet damit.",
|
||
"deleteConfirm": "Server {{name}} wirklich löschen?",
|
||
"live": "Live"
|
||
}
|
||
},
|
||
"routing": {
|
||
"title": "Routing-Regeln",
|
||
"intro": "Pfad-Präfix → Backend-Mapping pro Domain. Niedrige Priority gewinnt; Catch-all per Domain.primary_backend.",
|
||
"addRule": "Regel hinzufügen",
|
||
"emptyTitle": "Noch keine Routing-Regeln.",
|
||
"emptyDesc": "Pfad-spezifisches Routing — z. B. domain.tld/api → Backend A, domain.tld/* → Backend B. Optional. Ohne Regeln nutzt HAProxy die Primary-Backend-Auswahl der Domain.",
|
||
"editRule": "Regel bearbeiten",
|
||
"domain": "Domain",
|
||
"pathPrefix": "Pfad-Präfix",
|
||
"pathPrefixHint": "Muss mit / beginnen. HAProxy matcht als Präfix.",
|
||
"pathPrefixInvalid": "Pfad muss mit / beginnen und darf nur gültige URL-Zeichen enthalten",
|
||
"backend": "Backend",
|
||
"priority": "Priorität",
|
||
"active": "Aktiv",
|
||
"actions": "Aktionen",
|
||
"selectDomain": "Domain wählen",
|
||
"selectBackend": "Backend wählen",
|
||
"deleteConfirm": "Diese Routing-Regel wirklich löschen?"
|
||
},
|
||
"cluster": {
|
||
"title": "Cluster",
|
||
"intro": "{{count}} Node(s) registriert. Multi-Node (KeyDB-AA + PG-Streaming-Replication + Leader-Election) folgt schrittweise.",
|
||
"id": "Node-ID",
|
||
"fqdn": "FQDN",
|
||
"role": "Rolle",
|
||
"joinedAt": "Beigetreten",
|
||
"self": "diese Node",
|
||
"drift": "Drift",
|
||
"modeSingle": "Single-Node",
|
||
"modeCluster": "Cluster",
|
||
"health": {
|
||
"ok": "OK",
|
||
"degraded": "degraded",
|
||
"split-brain": "split-brain"
|
||
},
|
||
"selfTitle": "Dieser Knoten",
|
||
"noSelf": "Selbst-Registrierung in ha_nodes fehlgeschlagen — Setup-Wizard durchlaufen.",
|
||
"peersTitle": "Peers ({{count}})",
|
||
"singleNodeTitle": "Single-Node-Modus",
|
||
"singleNodeDesc": "Nur diese Box bekannt. Folge den Schritten unten um einen zweiten Node beizutreten.",
|
||
"addNodeTitle": "Zweiten Node dem Cluster hinzufügen",
|
||
"step1Title": "1 — EdgeGuard auf dem neuen Host installieren",
|
||
"step1Desc": "Als root auf dem neuen Host ausführen (Debian 13, amd64):",
|
||
"step2Title": "2 — Join-Token hier generieren (auf diesem Primary-Node)",
|
||
"step2Desc": "Trage zuerst den FQDN des neuen Knotens ein. Er wird damit vorregistriert und Port 8443 wird für den Join geöffnet.",
|
||
"step3Title": "3 — Auf dem neuen Node ausführen",
|
||
"step3NoToken": "Zuerst oben auf \"Join-Token erzeugen\" klicken — der vollständige Befehl erscheint dann hier.",
|
||
"step4Title": "4 — edgeguard-api auf dem neuen Node neu starten",
|
||
"step4Desc": "Nachdem der Setup-Wizard auf dem neuen Knoten abgeschlossen ist, edgeguard-api dort neu starten um die Cluster-Zertifikate zu laden:",
|
||
"apiUnreachable": "Cluster-API nicht erreichbar",
|
||
"tokenLabel": "Token",
|
||
"caFingerprintLabel": "CA-Fingerabdruck",
|
||
"driftBanner": "Config-Drift erkannt",
|
||
"driftBannerDesc": "Ein oder mehrere Peers haben einen anderen Config-Hash als dieser Node. Kurz nach einer Änderung ist das normal (die Replikation hinkt nach) und verschwindet von selbst. Bleibt der Drift bestehen, ist die Replikation gestört oder es wurde direkt in die DB eines Peers geschrieben.",
|
||
"repair": {
|
||
"button": "Resync erzwingen",
|
||
"noStandbyHint": "Kein Standby-Node gefunden, an den der Resync delegiert werden könnte.",
|
||
"confirmTitle": "Replikation reparieren?",
|
||
"confirmDesc": "Baut die Replikations-Subscription auf dem Standby-Node neu auf und kopiert alle geteilten Config-Tabellen frisch vom Primary. Vom Primary aus geklickt wird der Resync per mTLS an den Standby delegiert. Lokale Direkt-Edits am Standby werden dabei überschrieben (Primary = Source of Truth).",
|
||
"confirmOk": "Resync starten",
|
||
"started": "Resync angestoßen — läuft im Hintergrund auf dem Standby.",
|
||
"ok": "Replikation repariert — Config wieder synchron.",
|
||
"failed": "Resync fehlgeschlagen"
|
||
},
|
||
"col": {
|
||
"node": "Knoten",
|
||
"status": "Status",
|
||
"role": "Rolle",
|
||
"pgRole": "PG-Rolle",
|
||
"apiUrl": "API-URL",
|
||
"configHash": "Config-Hash",
|
||
"version": "Version",
|
||
"lastSeen": "Last seen",
|
||
"mgmtIp": "MGMT-IP",
|
||
"load": "Load 1/5/15",
|
||
"mem": "Memory",
|
||
"disk": "Disk",
|
||
"conntrack": "Conntrack",
|
||
"uptime": "Uptime",
|
||
"fetchMs": "Fetch"
|
||
},
|
||
"pgRole": {
|
||
"standalone": "standalone",
|
||
"primary": "primary",
|
||
"standby": "standby"
|
||
},
|
||
"vipCard": {
|
||
"title": "Hochverfügbarkeit (VIP / Keepalived)",
|
||
"vipAddress": "VIP-Adresse",
|
||
"vipAddressHelp": "Virtuelle IP-Adresse die zwischen Nodes wandert (z.B. 89.163.205.10)",
|
||
"vipInterface": "Netzwerk-Interface",
|
||
"vipInterfaceHelp": "Interface auf dem die VIP gebunden wird (z.B. eth0)",
|
||
"vipAuthPass": "VRRP Auth-Passwort",
|
||
"vipAuthPassHelp": "Max. 8 Zeichen — Keepalived-Limit. Gleich auf allen Nodes.",
|
||
"vrrpRouterId": "VRRP Router-ID",
|
||
"vrrpRouterIdHelp": "Muss im Subnetz eindeutig sein (1–255). Standard: 51.",
|
||
"saveBtn": "Speichern & Keepalived neu konfigurieren",
|
||
"saved": "VIP-Einstellungen gespeichert",
|
||
"saveFailed": "Speichern fehlgeschlagen",
|
||
"hintTitle": "Nächste Schritte nach dem Speichern",
|
||
"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)",
|
||
"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?",
|
||
"certCardTitle": "Cluster-TLS-Zertifikate",
|
||
"certCALabel": "Cluster-CA",
|
||
"certPeerLabel": "Peer-Cert (diese Node)",
|
||
"certExpiry": "Läuft ab in",
|
||
"renewSelfBtn": "Peer-Cert erneuern",
|
||
"renewSelfConfirm": "Peer-Cert mit lokaler CA neu signieren (1 Jahr)? edgeguard-api Restart danach erforderlich.",
|
||
"certRenewedRestartHint": "Cert erneuert — bitte sudo systemctl restart edgeguard-api ausführen.",
|
||
"certRenewFailed": "Cert-Erneuerung fehlgeschlagen",
|
||
"certDaysRemaining": "{{n}} Tage",
|
||
"certExpiredDaysAgo": "abgelaufen vor {{n}} Tagen",
|
||
"removePeerBtn": "Entfernen",
|
||
"removePeerConfirmTitle": "Peer wirklich aus dem Cluster entfernen?",
|
||
"removePeerConfirmDesc": "{{fqdn}} wird aus ha_nodes gelöscht. Firewall-Renderer entfernt seine IP aus dem peer_ipv4-Set. Auf der Peer-Seite läuft edgeguard-api weiter; manuell stoppen + cluster-tls löschen für vollständigen Decommission.",
|
||
"removePeerOk": "Peer entfernt.",
|
||
"removePeerFailed": "Peer-Entfernung fehlgeschlagen",
|
||
"generateJoinToken": "Join-Token erzeugen",
|
||
"joinTokenTitle": "Cluster-Join-Token",
|
||
"joinTokenOneShot": "Einmalig anzeigen, einmalig nutzbar",
|
||
"joinTokenOneShotDesc": "Token jetzt sicher übertragen — er erscheint nie wieder. Gültig bis {{expires}}. Beim Einlösen via cluster-join wird er server-seitig als verbraucht markiert.",
|
||
"joinTokenFailed": "Token-Generierung fehlgeschlagen",
|
||
"joinCmdLabel": "Auf dem neuen Node ausführen:",
|
||
"status": {
|
||
"online": "online",
|
||
"offline": "offline",
|
||
"joining": "joining",
|
||
"leaving": "leaving",
|
||
"unknown": "unbekannt"
|
||
},
|
||
"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",
|
||
"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",
|
||
"intro": "TLS-Zertifikate verwalten — entweder per Let's Encrypt automatisch ausstellen oder eigene PEMs hochladen. HAProxy lädt nach jeder Änderung automatisch neu.",
|
||
"tabLE": "Let's Encrypt",
|
||
"tabUpload": "Eigenes Zertifikat",
|
||
"leIntro": "Domain wählen, Issue klicken — EdgeGuard löst HTTP-01 über die ACME-Webroot, schreibt das PEM nach /etc/edgeguard/tls/ und reloaded HAProxy.",
|
||
"uploadIntro": "Eigenes Zertifikat hochladen. Format: PEM-encoded. Cert + optional Chain + Private Key. EdgeGuard prüft die Cert/Key-Übereinstimmung vor dem Schreiben.",
|
||
"uploadHint": "Tipp: bei Let's-Encrypt-Renewals nicht hier hochladen — den LE-Tab nutzen.",
|
||
"domain": "Domain",
|
||
"selectDomain": "Domain wählen oder eintippen",
|
||
"domainExtra": "Inkludiert Management-FQDN (aus Setup), Cluster-Knoten und Operator-Domains. Du kannst auch eine andere Domain tippen, sofern DNS schon auf die Box zeigt.",
|
||
"fqdnHintMgmt": "Management-FQDN",
|
||
"fqdnHintCluster": "Cluster · {{role}}",
|
||
"issuer": "Issuer",
|
||
"status": "Status",
|
||
"expiresIn": "Gültig noch",
|
||
"expiredAgo": "abgelaufen vor {{days}} Tagen",
|
||
"actions": "Aktionen",
|
||
"issueButton": "Zertifikat anfordern",
|
||
"uploadButton": "Hochladen",
|
||
"issueSuccess": "Zertifikat ausgestellt + installiert.",
|
||
"uploadSuccess": "Zertifikat hochgeladen + installiert.",
|
||
"renewBtn": "Renew",
|
||
"renewConfirmTitle": "Zertifikat jetzt erneuern?",
|
||
"renewConfirmDesc": "Triggert eine ACME-HTTP-01-Challenge für {{domain}}. Let's Encrypt hat Rate-Limits (50 Issues/Domain/Woche) — nicht öfter als nötig nutzen.",
|
||
"renewSuccess": "Zertifikat erneuert + installiert.",
|
||
"renewFailed": "Renewal fehlgeschlagen",
|
||
"deleteConfirm": "Zertifikat für {{domain}} löschen? HAProxy fällt für diese Domain auf das Default-Cert zurück.",
|
||
"installedTitle": "Installierte Zertifikate",
|
||
"lastRenewed": "Zuletzt erneuert",
|
||
"statTotal": "Zertifikate gesamt",
|
||
"statExpiring": "< 30 Tage gültig",
|
||
"statExpired": "Abgelaufen",
|
||
"statErrors": "Mit Fehler",
|
||
"relAgo": {
|
||
"justNow": "gerade eben",
|
||
"minutes": "vor {{n}} min",
|
||
"hours": "vor {{n}} h",
|
||
"days": "vor {{n}} Tagen"
|
||
},
|
||
"emptyTitle": "Noch keine Zertifikate installiert.",
|
||
"emptyDesc": "Nutze die Tabs oben — Let's Encrypt issued vollautomatisch per HTTP-01-Challenge, oder lade ein eigenes PEM hoch. Bis dahin liefert HAProxy das Bootstrap-Self-Signed-Cert für alle Domains.",
|
||
"certPem": "Zertifikat (PEM)",
|
||
"chainPem": "Chain (PEM, optional)",
|
||
"keyPem": "Private Key (PEM)"
|
||
},
|
||
"settings": {
|
||
"title": "Einstellungen",
|
||
"intro": "System-Information, Setup-Status und Admin-Account.",
|
||
"systemInfo": "System",
|
||
"version": "Version",
|
||
"status": "Status",
|
||
"hostname": "Hostname",
|
||
"os": "Betriebssystem",
|
||
"kernel": "Kernel",
|
||
"dbSize": "PostgreSQL-DB-Größe",
|
||
"dbSizeTop": "Top-Tabellen",
|
||
"upgradeStatusCardTitle": "Letzter Update-Versuch",
|
||
"upgradeStatusStarted": "Gestartet",
|
||
"upgradeStatusFinished": "Beendet",
|
||
"upgradeStatusResult": "Ergebnis",
|
||
"upgradeStatusState": "Status",
|
||
"upgradeStatusOk": "Erfolgreich",
|
||
"upgradeStatusShowLog": "Vollständiges Log anzeigen ({{n}} Zeilen)",
|
||
"actionsCardTitle": "System-Aktionen",
|
||
"actionsHint": "Manuelle Trigger für Operator-Tasks die sonst nur automatisch beim Speichern in den jeweiligen Seiten passieren. Sinnvoll nach SSH-Eingriffen (z. B. /etc/edgeguard/tls/ manuell befüllt).",
|
||
"haproxyReloadBtn": "HAProxy reload",
|
||
"haproxyReloadOk": "HAProxy neu geladen.",
|
||
"haproxyReloadFailed": "HAProxy-Reload fehlgeschlagen",
|
||
"renderConfigsBtn": "Alle Configs neu rendern",
|
||
"renderConfigsOk": "Alle Configs neu gerendert und reloaded.",
|
||
"renderConfigsFailed": "Config-Render fehlgeschlagen",
|
||
"renderConfigsPartial": "Partieller Render — einige Dienste fehlgeschlagen: {{failed}}",
|
||
"backupNowBtn": "Backup jetzt erstellen",
|
||
"backupNowOk": "Backup ausgelöst — Status in der Backups-Seite verfolgen.",
|
||
"backupNowFailed": "Backup-Trigger fehlgeschlagen",
|
||
"serviceRestartCardTitle": "Dienste neu starten",
|
||
"serviceRestartBtn": "Restart",
|
||
"serviceRestartConfirmTitle": "{{service}} neu starten?",
|
||
"serviceRestartConfirmDesc": "Der Dienst ist während des Neustarts kurz nicht erreichbar.",
|
||
"serviceRestartConfirmOk": "Ja, neu starten",
|
||
"serviceRestartOk": "{{service}} wurde neu gestartet.",
|
||
"serviceRestartFailed": "Neustart von {{service}} fehlgeschlagen",
|
||
"serviceRestartHint": "Startet den Dienst via systemctl restart. edgeguard-api und PostgreSQL sind bewusst ausgeschlossen.",
|
||
"setupInfo": "Setup",
|
||
"adminEmail": "Admin-E-Mail",
|
||
"fqdn": "FQDN",
|
||
"setupCompleted": "Setup abgeschlossen",
|
||
"emailsCardTitle": "Kontakt-E-Mails",
|
||
"adminEmailHint": "Wird beim Login als Admin-Identifier genutzt. Änderung erfordert Re-Login mit der neuen Adresse.",
|
||
"acmeEmail": "ACME-E-Mail",
|
||
"acmeEmailHint": "Wird Let's Encrypt als Account-Contact mitgegeben. Bestehende Zertifikate sind nicht betroffen — die nächste Renew-Operation registriert die neue Adresse.",
|
||
"emailsSaved": "E-Mails aktualisiert.",
|
||
"emailsFailed": "E-Mail-Update fehlgeschlagen",
|
||
"maintenanceCardTitle": "Wartungs-Modus (gesamte Box)",
|
||
"maintenanceOn": "Aktiv — alle Customer-Domains liefern 503. Mgmt-UI bleibt erreichbar.",
|
||
"maintenanceOff": "Inaktiv — Customer-Traffic wird normal an die Backends geroutet.",
|
||
"maintenanceMessage": "Wartungs-Meldung",
|
||
"maintenanceMessagePlaceholder": "Service vorübergehend nicht verfügbar — wir sind in Kürze zurück.",
|
||
"maintenanceMessageHint": "Wird im Response-Body der 503-Antworten an Endkunden mitgeschickt. Plain text, max. 500 Zeichen.",
|
||
"maintenanceHint": "Schaltet HAProxy auf :443 in einen Default-503-Modus. Pro-Domain-Maintenance (Domains-Seite) wird hiervon übersteuert. Mgmt-UI auf :3443 ist NICHT betroffen.",
|
||
"maintenanceSaved": "Wartungs-Modus aktualisiert.",
|
||
"maintenanceFailed": "Wartungs-Modus-Toggle fehlgeschlagen",
|
||
"maintenanceActiveTitle": "Wartungs-Modus aktiv",
|
||
"maintenanceActiveDesc": "Alle Customer-Domains liefern aktuell 503. Mgmt-UI ist erreichbar — Customer-Traffic NICHT. Settings → Wartungs-Modus zum Deaktivieren.",
|
||
"backupRetentionCardTitle": "Backup-Aufbewahrung",
|
||
"backupRetentionUnit": "Backups",
|
||
"backupRetentionDefault": "Default ({{n}} Backups) — pro Tag bei täglichem Schedule = {{n}} Tage History.",
|
||
"backupRetentionCustom": "Custom — die letzten {{n}} erfolgreichen Backups werden behalten, ältere werden nach jedem Backup-Lauf gelöscht.",
|
||
"backupRetentionHint": "0 = Default (14). 1-365 = Custom-Limit. Bedenke: jedes Backup ist ein voller pg_dump + Files-tar (typisch 50-500 MB). Bei /var-Disk-Druck eher reduzieren.",
|
||
"backupRetentionSaved": "Backup-Retention aktualisiert.",
|
||
"backupRetentionFailed": "Backup-Retention-Update fehlgeschlagen",
|
||
"auditRetentionCardTitle": "Audit-Log-Aufbewahrung",
|
||
"auditRetentionUnit": "Tage",
|
||
"auditRetentionDefault": "Default ({{n}} Tage) — Audit-Einträge älter als {{n}} Tage werden täglich gepruned.",
|
||
"auditRetentionCustom": "Custom — Audit-Einträge werden für {{n}} Tage behalten.",
|
||
"auditRetentionHint": "0 = Default (90). 1-3650 (= 10 Jahre) für Compliance (SOX 7y = 2555, DSGVO meist <= 365). Cleanup läuft im Scheduler täglich.",
|
||
"auditRetentionSaved": "Audit-Retention aktualisiert.",
|
||
"auditRetentionFailed": "Audit-Retention-Update fehlgeschlagen",
|
||
"autoUpdateCardTitle": "Automatische Updates",
|
||
"autoUpdateOn": "Aktiviert — edgeguard-Pakete werden täglich automatisch installiert.",
|
||
"autoUpdateOff": "Deaktiviert — Updates müssen manuell über den Banner installiert werden.",
|
||
"autoUpdateHint": "Whitelist umfasst nur edgeguard, edgeguard-api, edgeguard-ui. Andere Pakete bleiben unter manueller Kontrolle. Verlangt unattended-upgrades (Distro-Standard auf Trixie). Conf-File: /etc/apt/apt.conf.d/52edgeguard-auto-updates.",
|
||
"autoUpdateToggled": "Auto-Update-Einstellung gespeichert.",
|
||
"autoUpdateFailed": "Auto-Update-Toggle fehlgeschlagen",
|
||
"ipv6CardTitle": "IPv6",
|
||
"ipv6On": "Aktiviert — HAProxy bindet zusätzlich zu IPv4 auf [::]:80, [::]:443 und [::]:3443.",
|
||
"ipv6Off": "Deaktiviert — HAProxy lauscht nur auf IPv4.",
|
||
"ipv6Hint": "Erfordert IPv6-Konfiguration auf den Netzwerk-Interfaces dieses Servers. HAProxy wird nach der Änderung automatisch neu geladen.",
|
||
"ipv6Toggled": "IPv6-Einstellung gespeichert.",
|
||
"ipv6Failed": "IPv6-Toggle fehlgeschlagen",
|
||
"configPreviewCardTitle": "Config-Vorschau",
|
||
"configPreviewBtn": "Vorschau laden",
|
||
"configPreviewHint": "Rendert die gewählte Service-Config aus dem aktuellen DB-State. Nur Lesezugriff — es wird nichts auf Disk geschrieben oder neu geladen.",
|
||
"configCopied": "Config in Zwischenablage kopiert",
|
||
"oidc": {
|
||
"title": "Single Sign-On (OIDC)",
|
||
"intro": "SSO-Login per OpenID Connect (z. B. Keycloak), zusätzlich zum lokalen Login. Anmeldung gelingt nur für bereits angelegte Benutzer; die Rolle wird in EdgeGuard verwaltet.",
|
||
"enabled": "SSO aktiviert",
|
||
"issuerUrl": "Issuer-URL",
|
||
"issuerHint": "Basis-URL des Realms, z. B. https://keycloak.example.com/realms/edgeguard",
|
||
"clientId": "Client-ID",
|
||
"clientSecret": "Client-Secret",
|
||
"secretSet": "Gespeichert — leer lassen, um es unverändert zu lassen.",
|
||
"secretUnset": "Noch kein Secret gespeichert.",
|
||
"scopes": "Scopes",
|
||
"emailClaim": "E-Mail-Claim",
|
||
"buttonLabel": "Button-Beschriftung",
|
||
"redirectUri": "Redirect-URI",
|
||
"redirectHint": "Diese URL im Keycloak-Client als gültige Redirect-URI eintragen (pro Cluster-Node die jeweilige FQDN).",
|
||
"saved": "OIDC-Einstellungen gespeichert",
|
||
"saveFailed": "Speichern der OIDC-Einstellungen fehlgeschlagen"
|
||
},
|
||
"passwordCardTitle": "Admin-Passwort ändern",
|
||
"currentPassword": "Aktuelles Passwort",
|
||
"newPassword": "Neues Passwort",
|
||
"newPasswordHint": "Mindestens 12 Zeichen. Wird mit bcrypt gehasht.",
|
||
"confirmPassword": "Neues Passwort bestätigen",
|
||
"changePasswordBtn": "Passwort ändern",
|
||
"passwordChanged": "Passwort geändert.",
|
||
"passwordChangeFailed": "Passwort-Änderung fehlgeschlagen",
|
||
"passwordMismatch": "Die Passwörter stimmen nicht überein.",
|
||
"passwordMinLen": "Mindestens 12 Zeichen erforderlich."
|
||
},
|
||
"update": {
|
||
"available": "Update verfügbar: Version {{version}}",
|
||
"multiPackageHint": "{{count}} Pakete werden aktualisiert.",
|
||
"applyNow": "Jetzt installieren",
|
||
"confirmTitle": "Update jetzt installieren?",
|
||
"confirmDesc": "Pakete werden auf Version {{version}} aktualisiert. edgeguard-api + scheduler restarten (~2-5s), HAProxy/nft/WG/Squid/Unbound/Chrony laufen durch.",
|
||
"checkNow": "Auf Updates prüfen",
|
||
"checkNowHint": "Server-seitigen apt-Cache jetzt aktualisieren und nach neueren Versionen schauen.",
|
||
"updateReady": "Update bereit: v{{version}}",
|
||
"checkDone": "Update verfügbar",
|
||
"noUpdate": "Keine neuen Updates",
|
||
"checkFailed": "Update-Check fehlgeschlagen",
|
||
"running": "Update läuft …",
|
||
"waitHint": "Bitte warten — die Seite lädt automatisch neu sobald die neue Version live ist.",
|
||
"success": "Update auf {{version}} abgeschlossen.",
|
||
"failed": "Update fehlgeschlagen",
|
||
"stepDownload": "Pakete laden",
|
||
"stepInstall": "Installation",
|
||
"stepRestart": "Service-Restart",
|
||
"stepVerify": "Verifizierung",
|
||
"rollingUpdate": "Rolling Update (Cluster)",
|
||
"rollingConfirmTitle": "Rolling Update starten?",
|
||
"rollingConfirmDesc": "Der Secondary-Node ({{secondary}}) wird zuerst aktualisiert, danach dieser Primary. Kein Ausfall für den Proxied-Traffic während der Secondary-Phase.",
|
||
"rollingRunning": "Rolling Update läuft…",
|
||
"rollingStepSecondary": "Secondary aktualisieren ({{fqdn}})",
|
||
"rollingStepWaiting": "Warte auf Neustart des Secondary",
|
||
"rollingStepPrimary": "Primary aktualisieren (dieser Node)"
|
||
},
|
||
"wg": {
|
||
"title": "WireGuard",
|
||
"intro": "VPN-Tunnel über WireGuard. Server-Modus = wir lauschen für Peers; Client-Modus = wir verbinden zu einem festen Upstream. Privater Schlüssel liegt verschlüsselt in der DB.",
|
||
"tabs": {
|
||
"servers": "Server-Tunnel",
|
||
"clients": "Client-Tunnel"
|
||
},
|
||
"serverIntro": "Server-Tunnel hosten ein Peer-Roster — z.B. Mitarbeiter-Geräte oder Niederlassungen. Pro Peer bekommt der Operator eine .conf zum Download (oder QR-Code für Mobile).",
|
||
"clientIntro": "Client-Tunnel verbinden EdgeGuard zu einem fremden WireGuard-Server (z.B. HQ-Datacenter). Allowed-IPs steuert, welcher Traffic durch den Tunnel geroutet wird.",
|
||
"iface": {
|
||
"name": "Name",
|
||
"namePattern": "wg gefolgt von Kleinbuchstaben/Ziffern/-, max. 15 Zeichen",
|
||
"nameExtra": "Empfehlung: wg0, wg1, wg-hq …",
|
||
"address": "Adresse (CIDR)",
|
||
"addressExtra": "Tunnel-IP der Box, z.B. 10.99.0.1/24 für /24-Pool",
|
||
"listenPort": "Listen-Port",
|
||
"publicKey": "Public-Key",
|
||
"privateKey": "Private-Key (paste)",
|
||
"privateKeyExtra": "Nur ausfüllen wenn nicht generieren — base64 32 Byte. Wird verschlüsselt gespeichert.",
|
||
"peerEndpoint": "Peer-Endpoint",
|
||
"peerPublicKey": "Peer Public-Key",
|
||
"peerPSK": "Pre-Shared-Key (PSK)",
|
||
"peerPSKExtra": "Optional, zusätzliche Schicht",
|
||
"allowedIPs": "Allowed IPs",
|
||
"allowedIPsExtra": "Was durch den Tunnel geroutet wird. Default = full-tunnel.",
|
||
"keepalive": "Persistent Keepalive (sec)",
|
||
"mtu": "MTU",
|
||
"zone": "Firewall-Zone",
|
||
"description": "Beschreibung",
|
||
"addServer": "Server-Tunnel hinzufügen",
|
||
"editServer": "Server-Tunnel bearbeiten",
|
||
"addClient": "Client-Tunnel hinzufügen",
|
||
"editClient": "Client-Tunnel bearbeiten",
|
||
"emptyServerTitle": "Noch keine WireGuard-Server-Tunnel.",
|
||
"emptyServerDesc": "Server-Modus: diese Box hört auf einem UDP-Port und akzeptiert Peer-Verbindungen (z. B. Roadwarrior-User, Site-to-Site-Niederlassungen).",
|
||
"emptyClientTitle": "Noch keine WireGuard-Client-Tunnel.",
|
||
"emptyClientDesc": "Client-Modus: diese Box verbindet sich zu einem externen WireGuard-Server (z. B. HQ-Datacenter, Cloud-Anbindung).",
|
||
"upstream": "Upstream-Peer",
|
||
"deleteConfirm": "Tunnel {{name}} wirklich löschen? wg-quick wird gestoppt.",
|
||
"keys": "Schlüssel",
|
||
"generateExtra": "Wenn an: Server erzeugt ein neues Curve25519-Keypair beim Speichern.",
|
||
"generateOn": "Server generiert",
|
||
"generateOff": "Manuell paste",
|
||
"editKeyWarning": "Achtung: neue Schlüssel = bestehende Peer-Configs ungültig. Nur ändern wenn explizit gewollt.",
|
||
"clientRoutes": "Push-Routes (Client)",
|
||
"clientRoutesExtra": "Zusätzliche Netzwerke die der Peer über den Tunnel erreichen soll, z. B. 10.0.10.0/24 für ein LAN hinter der Box. Kommagetrennt. Wird automatisch in den Peer-Config-Download eingetragen."
|
||
},
|
||
"peers": {
|
||
"button": "Peers",
|
||
"drawerTitle": "Peer-Roster"
|
||
},
|
||
"peer": {
|
||
"name": "Name",
|
||
"publicKey": "Public-Key",
|
||
"publicKeyExtra": "Wird vom Peer-Gerät erzeugt; hier nur paste-bar wenn der Peer schon ein Key-Pair hat.",
|
||
"allowedIPs": "Allowed IPs",
|
||
"allowedIPsExtra": "Die IP die dieser Peer im Tunnel bekommt — muss /32 sein (z. B. 10.0.10.2/32), nicht das ganze Subnetz (/24). Mit /24 würde der Server allen Verkehr zu diesem einen Peer routen und andere Peers wären nicht erreichbar.",
|
||
"keepalive": "Keepalive (sec)",
|
||
"keepaliveExtra": "0 = aus. Empfohlen 25 hinter NAT.",
|
||
"lastHandshake": "Letzter Handshake",
|
||
"never": "nie",
|
||
"description": "Beschreibung",
|
||
"add": "Peer hinzufügen",
|
||
"edit": "Peer bearbeiten",
|
||
"deleteConfirm": "Peer {{name}} wirklich entfernen?",
|
||
"emptyTitle": "Noch keine Peers in diesem Tunnel.",
|
||
"emptyDesc": "Lege Peers an — pro Peer eine WireGuard-Identität (Public-Key + Allowed-IPs). Server-generierte Keys liefern dir Config-Download bzw. QR-Code für Mobile-Clients direkt mit.",
|
||
"keys": "Schlüssel",
|
||
"generateExtra": "Wenn an: Server erzeugt für diesen Peer ein Keypair und kann die Config / QR-Code ausliefern. Wenn aus: nur den Public-Key paste-en — keine Config-Download möglich.",
|
||
"pskExtra": "Wenn an: Server generiert einen 32-Byte PSK für diesen Peer.",
|
||
"pskOn": "PSK generieren",
|
||
"pskOff": "kein PSK",
|
||
"downloadConf": "wg-quick.conf herunterladen",
|
||
"qr": "QR",
|
||
"conf": ".conf",
|
||
"qrTitle": "WireGuard-QR",
|
||
"qrHint": "Mit der WireGuard-App (iOS/Android) scannen: \"Tunnel hinzufügen\" → \"QR-Code scannen\". Endpoint im Download-Conf bitte vor Verwendung anpassen.",
|
||
"online": "Online",
|
||
"offline": "Offline",
|
||
"traffic": "Traffic"
|
||
},
|
||
"import": {
|
||
"btn": ".conf importieren",
|
||
"modalTitle": "WireGuard-Konfigurationen aus /etc/wireguard importieren",
|
||
"hint": "Wähle aus, welche .conf-Dateien importiert werden sollen. Bereits importierte Interfaces sind ausgegraut.",
|
||
"selectAll": "Alle neuen auswählen",
|
||
"alreadyIn": "bereits importiert",
|
||
"noFiles": "Keine .conf-Dateien in /etc/wireguard gefunden.",
|
||
"okBtn": "Ausgewählte importieren",
|
||
"ok": "{{ifaces}} Interface(s), {{peers}} Peer(s) importiert",
|
||
"failed": "Import fehlgeschlagen"
|
||
}
|
||
},
|
||
"ntp": {
|
||
"title": "Zeitserver (Chrony)",
|
||
"intro": "Chrony als Time-Sync-Daemon (NTP). Quellen oben, Listen-/Serve-Konfig im Settings-Tab. Wenn 'serve_clients' aktiv und LAN-IPs gebound sind, wird die Box selbst zum NTP-Server für das LAN.",
|
||
"tabs": {
|
||
"pools": "Quellen",
|
||
"settings": "Settings",
|
||
"peers": "Peer-Status"
|
||
},
|
||
"sourcesCard": {
|
||
"title": "Live Peer-Status (chronyc sources)",
|
||
"name": "Name / IP",
|
||
"stratum": "Stratum",
|
||
"poll": "Poll",
|
||
"reach": "Erreichbarkeit",
|
||
"lastRx": "Letzter Empfang",
|
||
"sample": "Offset ± Fehler",
|
||
"reachHint": "8-Bit-Schieberegister (oktal) — 377 = alle 8 letzten Abfragen beantwortet. 0 = nicht erreichbar.",
|
||
"empty": "Keine Peer-Daten — läuft chrony?",
|
||
"state_synced": "Synchronisiert",
|
||
"state_combined": "Kombiniert",
|
||
"state_not_combined": "Nicht kombiniert",
|
||
"state_unreachable": "Nicht erreichbar",
|
||
"state_error": "Fehler",
|
||
"state_variable": "Variabel"
|
||
},
|
||
"statusCard": {
|
||
"title": "Sync-Status (chronyc tracking)",
|
||
"sync": "Synchronisiert",
|
||
"synced": "Ja",
|
||
"notSynced": "Nein",
|
||
"source": "Quelle",
|
||
"stratum": "Stratum",
|
||
"offset": "Offset",
|
||
"offsetHint": "Zeitdifferenz zur Referenzquelle. Werte > 100 ms sind ungewöhnlich — Netzwerkprobleme oder fehlkonfigurierte Quelle prüfen.",
|
||
"freqPpm": "Frequenzfehler",
|
||
"freqPpmHint": "Frequenzabweichung der lokalen Uhr zur Referenz. Werte > ±100 ppm weisen auf eine driftende Uhr oder einen Hardware-Defekt hin.",
|
||
"rmsOffset": "RMS-Offset",
|
||
"loading": "Lade…"
|
||
},
|
||
"pool": {
|
||
"kind": "Typ",
|
||
"kindPool": "pool — DNS-Round-Robin (mehrere Server aus A-Records)",
|
||
"kindServer": "server — einzelner Host",
|
||
"address": "Adresse / Host",
|
||
"addressExtra": "FQDN (für pool: 0.de.pool.ntp.org) oder IP.",
|
||
"addressPlaceholder": "0.de.pool.ntp.org oder 10.0.0.10",
|
||
"iburst": "iburst",
|
||
"prefer": "prefer",
|
||
"minpoll": "min-poll",
|
||
"maxpoll": "max-poll",
|
||
"options": "Optionen",
|
||
"description": "Beschreibung",
|
||
"add": "Quelle hinzufügen",
|
||
"edit": "Quelle bearbeiten",
|
||
"deleteConfirm": "NTP-Quelle {{addr}} wirklich löschen?",
|
||
"emptyTitle": "Noch keine NTP-Quellen.",
|
||
"emptyDesc": "Ohne konfigurierte Pool/Server-Einträge nutzt chrony nur seine compiled-in Default-Pool (debian.pool.ntp.org). Setze eigene Pools für genauere Zeitabstimmung oder interne Stratum-Server."
|
||
},
|
||
"settings": {
|
||
"intro": "Globale Chrony-Settings. Save reloaded chrony automatisch.",
|
||
"serveClients": "Als NTP-Server für Clients arbeiten",
|
||
"serveClientsExtra": "Wenn aus: chrony agiert nur als Client (port 0). Wenn an + Listen-IP: bindet UDP/123.",
|
||
"listenAddresses": "Listen-Adressen",
|
||
"listenAddressesPlaceholder": "IPs wählen (oder eintippen)",
|
||
"listenAddressesExtra": "Auf welchen IPs chrony :123/UDP bindet. 127.0.0.1+::1 = nur lokal; LAN-IPs öffnen für LAN-Clients (FW-Rule wird automatisch generiert).",
|
||
"allowACL": "Allow-ACL (CIDRs)",
|
||
"allowACLExtra": "Wer darf NTP-Time anfragen.",
|
||
"makestepSecs": "makestep secs",
|
||
"makestepSecsExtra": "Erlaube step (statt slew) wenn offset > N sec.",
|
||
"makestepLimit": "makestep limit",
|
||
"rtcsync": "RTC mit System-Time syncen",
|
||
"rtcsyncExtra": "Hardware-Clock alle 11 min synchron halten — nach Reboot ist die Zeit grob korrekt.",
|
||
"leapsectz": "Leap-Sec TZ",
|
||
"leapsectzExtra": "Optional, z.B. 'right/UTC' für leap-sec über tzdata.",
|
||
"forceSyncBtn": "Uhr sofort stellen",
|
||
"forceSyncTooltip": "System-Uhr sofort auf den NTP-Referenzwert setzen (chronyc makestep). Nützlich nach VM-Migration oder längerem Ausfall.",
|
||
"forceSyncOk": "Uhr erfolgreich gestellt",
|
||
"forceSyncFailed": "Force-Sync fehlgeschlagen"
|
||
}
|
||
},
|
||
"dns": {
|
||
"title": "DNS (Unbound)",
|
||
"intro": "Unbound-Resolver auf :53. Lokale Zonen (authoritativ aus DNS-Records) und Forward-Zonen (per stub-zone weiter zu fremden Resolvern). Default-Forwarder für alles andere.",
|
||
"tabs": {
|
||
"zones": "Zonen",
|
||
"settings": "Resolver-Settings",
|
||
"stats": "Resolver-Stats"
|
||
},
|
||
"statsCard": {
|
||
"title": "Resolver-Statistiken (unbound-control stats_noreset)",
|
||
"totalQueries": "Abfragen gesamt",
|
||
"cacheHits": "Cache-Treffer",
|
||
"cacheMiss": "Cache-Miss",
|
||
"cacheHitPct": "Cache-Trefferrate",
|
||
"recursiveReplies": "Rekursive Antworten",
|
||
"prefetch": "Prefetch",
|
||
"rateLimited": "Rate-Limited",
|
||
"rrsetCacheBytes": "RRset-Cache",
|
||
"msgCacheBytes": "Msg-Cache",
|
||
"tcpUsage": "TCP-Verbindungen",
|
||
"unwanted": "Unerwünschte Anfragen",
|
||
"empty": "Keine Statistiken — läuft unbound?",
|
||
"sinceRestart": "Seit letztem Neustart"
|
||
},
|
||
"zone": {
|
||
"name": "Zone-Name",
|
||
"nameExtra": "FQDN ohne führenden/abschließenden Punkt — z.B. internal.netcell-it.de",
|
||
"type": "Typ",
|
||
"typeLocal": "local — authoritativ (records hier)",
|
||
"typeForward": "forward — stub-zone zu fremdem Resolver",
|
||
"forwardTo": "Upstream-Resolver",
|
||
"forwardToExtra": "Komma-separierte IP-Liste — z.B. '10.0.0.53, 8.8.8.8'",
|
||
"description": "Beschreibung",
|
||
"records": "Records …",
|
||
"add": "Zone hinzufügen",
|
||
"edit": "Zone bearbeiten",
|
||
"deleteConfirm": "Zone {{name}} mit allen Records wirklich löschen?",
|
||
"emptyTitle": "Noch keine DNS-Zonen.",
|
||
"emptyDesc": "Unbound forwarded standardmäßig alles an die Upstream-Resolver. Lege eine Zone an, um z. B. interne FQDNs (internal.example.com) lokal zu hosten oder einen Upstream-Stub für eine fremde Domain einzurichten."
|
||
},
|
||
"record": {
|
||
"name": "Name",
|
||
"nameExtra": "Relativ zur Zone (z.B. 'mailcow') oder FQDN mit abschließendem Punkt.",
|
||
"type": "Typ",
|
||
"value": "Wert",
|
||
"valueExtra": "RDATA in Textform: A → IP, CNAME → FQDN, MX → 'priority host', TXT → 'string'.",
|
||
"ttl": "TTL (sec)",
|
||
"drawerTitle": "DNS-Records",
|
||
"add": "Record hinzufügen",
|
||
"edit": "Record bearbeiten",
|
||
"deleteConfirm": "Record {{name}} wirklich löschen?",
|
||
"emptyTitle": "Noch keine Records in dieser Zone.",
|
||
"emptyDesc": "A/AAAA/CNAME/MX/TXT-Einträge. Bei local-Zone authoritativ; bei forward-Zone hat das hier keinen Effekt (Upstream gewinnt)."
|
||
},
|
||
"settings": {
|
||
"intro": "Globale Resolver-Settings. Änderungen hier reloaden Unbound automatisch.",
|
||
"listenAddresses": "Listen-Adressen",
|
||
"listenAddressesPlaceholder": "IPs wählen (oder eintippen)",
|
||
"listenAddressesRequired": "Mindestens eine Adresse erforderlich.",
|
||
"listenAddressesExtra": "Mehrfachauswahl aus den IPs die der Kernel kennt. 127.0.0.1 + ::1 = nur lokal; weitere LAN-Iface-IPs (z.B. 10.10.20.3) öffnen den Resolver für LAN-Clients. Eigene IPs lassen sich auch eintippen (Enter).",
|
||
"listenPort": "Port",
|
||
"upstreamForwards": "Default-Forwarders",
|
||
"upstreamForwardsExtra": "Wo geht alles hin was nicht lokal ist. Default 1.1.1.1 + 9.9.9.9.",
|
||
"accessACL": "Access-ACL (CIDRs)",
|
||
"accessACLExtra": "Wer darf diesen Resolver benutzen.",
|
||
"dnssec": "DNSSEC validieren",
|
||
"qnameMin": "QName-Minimisation (privacy)",
|
||
"cacheMin": "Cache min-TTL",
|
||
"cacheMax": "Cache max-TTL",
|
||
"allIPv4": "alle IPv4-Interfaces",
|
||
"allIPv6": "alle IPv6-Interfaces",
|
||
"loopback": "Loopback",
|
||
"flushCacheBtn": "DNS-Cache leeren",
|
||
"flushCacheTooltip": "Alle gecachten Einträge verwerfen (unbound-control flush_zone .). Verwenden wenn DNS-Änderungen sofort greifen sollen.",
|
||
"flushCacheOk": "DNS-Cache geleert",
|
||
"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",
|
||
"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": {
|
||
"title": "Forward-Proxy (Squid)",
|
||
"intro": "Squid-basierter Forward-Proxy auf :3128. ACLs werden top-down nach Priority ausgewertet — first-match wins. Wenn keine Regel passt, gewinnt der Default: nur localnet (10/8, 172.16/12, 192.168/16) darf raus.",
|
||
"helpTitle": "Tipp zur ACL-Reihenfolge",
|
||
"helpBody": "Höhere Priority = wird zuerst geprüft. Beispiel: 'deny .badsite.com' (priority 200) vor 'allow .com' (priority 100). Werte können Listen sein (mehrere Zeilen), Regex je nach acl_type.",
|
||
"name": "Name",
|
||
"nameExtra": "Squid-konformer Bezeichner — Kleinbuchstaben + _, kein Leerzeichen.",
|
||
"aclType": "Typ",
|
||
"aclTypeExtra": "Was Squid prüft (Quelle, Domain, Port, …).",
|
||
"value": "Wert",
|
||
"valuePlaceholder": ".example.com oder 10.10.20.0/24 oder 443",
|
||
"valueExtra": "Format hängt vom Typ ab — IPs/CIDRs für src/dst, Domain mit führendem . für dstdomain (.example.com matcht auch sub.example.com), Regex für *_regex-Typen.",
|
||
"action": "Aktion",
|
||
"priority": "Priority",
|
||
"priorityExtra": "Höher = wird zuerst geprüft.",
|
||
"comment": "Kommentar",
|
||
"add": "ACL hinzufügen",
|
||
"edit": "ACL bearbeiten",
|
||
"deleteConfirm": "ACL {{name}} wirklich löschen?",
|
||
"emptyTitle": "Noch keine Forward-Proxy-ACLs.",
|
||
"emptyDesc": "Default ohne ACLs: nur localnet (10/8, 172.16/12, 192.168/16) darf raus. Lege eine ACL an, um spezifische Domains/IPs/Ports gezielt zu erlauben oder zu blocken.",
|
||
"statsCard": {
|
||
"title": "Squid-Cache-Statistiken",
|
||
"clientRequests": "Client-Anfragen",
|
||
"cacheHits": "Cache-Treffer",
|
||
"cacheHitPct": "Trefferquote",
|
||
"clientErrors": "Client-Fehler",
|
||
"bytesIn": "Empfangen",
|
||
"bytesOut": "Gesendet",
|
||
"serverRequests": "Backend-Anfragen",
|
||
"serverErrors": "Backend-Fehler",
|
||
"sinceRestart": "Seit letztem Neustart"
|
||
},
|
||
"actions": {
|
||
"allow": "allow — Zugriff erlauben",
|
||
"deny": "deny — Zugriff blockieren"
|
||
},
|
||
"aclTypes": {
|
||
"src": "src — Quell-IP/CIDR",
|
||
"dst": "dst — Ziel-IP/CIDR",
|
||
"dstdomain": "dstdomain — Ziel-Domain (exact)",
|
||
"srcdomain": "srcdomain — Quell-Domain (rDNS)",
|
||
"port": "port — Ziel-Port",
|
||
"proto": "proto — http/https/ftp/...",
|
||
"method": "method — GET/POST/CONNECT/...",
|
||
"time": "time — Wochentag/Zeit",
|
||
"url_regex": "url_regex — kompletter URL-Match",
|
||
"urlpath_regex": "urlpath_regex — Path-Teil",
|
||
"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": {
|
||
"yes": "Ja",
|
||
"no": "Nein",
|
||
"or": "oder",
|
||
"save": "Speichern",
|
||
"cancel": "Abbrechen",
|
||
"loading": "Lädt …",
|
||
"error": "Fehler",
|
||
"edit": "Bearbeiten",
|
||
"delete": "Löschen",
|
||
"deleteConfirm": "Wirklich löschen?",
|
||
"search": "Suchen …",
|
||
"totalRows": "{{count}} Einträge",
|
||
"active": "Aktiv",
|
||
"inactive": "Inaktiv",
|
||
"noData": "Keine Einträge",
|
||
"actions": "Aktionen",
|
||
"add": "Hinzufügen",
|
||
"download": "Download",
|
||
"copy": "Kopieren",
|
||
"copied": "Kopiert",
|
||
"retry": "Erneut versuchen",
|
||
"close": "Schließen",
|
||
"refresh": "Aktualisieren",
|
||
"back": "Zurück",
|
||
"next": "Weiter",
|
||
"up": "UP",
|
||
"down": "DOWN",
|
||
"relTime": {
|
||
"Xs": "vor {{n}}s",
|
||
"Xm": "vor {{n}}m",
|
||
"Xh": "vor {{n}}h",
|
||
"Xd": "vor {{n}}d",
|
||
"never": "nie"
|
||
}
|
||
},
|
||
"license": {
|
||
"title": "Lizenz",
|
||
"status": "Status",
|
||
"product": "Produkt",
|
||
"key": "Lizenz-Schlüssel",
|
||
"noKey": "Kein Schlüssel hinterlegt",
|
||
"validUntil": "Gültig bis",
|
||
"expired": "Abgelaufen",
|
||
"daysLeft": "noch {{days}} Tage",
|
||
"lastVerifiedAt": "Letzte Verifizierung",
|
||
"verifiedBy": "Verifiziert von",
|
||
"limits": "Limits",
|
||
"unlimited": "Unbegrenzt",
|
||
"features": "Features",
|
||
"reverify": "Erneut prüfen",
|
||
"reverified": "Lizenz erfolgreich verifiziert",
|
||
"enterKey": "Schlüssel eingeben",
|
||
"replaceKey": "Schlüssel ersetzen",
|
||
"enterKeyHint": "Lizenz-Schlüssel aus dem Self-Service-Portal von license.netcell-it.com einfügen.",
|
||
"activate": "Aktivieren",
|
||
"saved": "Lizenz gespeichert und verifiziert",
|
||
"savedButVerifyFailed": "Schlüssel gespeichert, aber Server-Verifizierung fehlgeschlagen",
|
||
"clearKey": "Schlüssel entfernen",
|
||
"cleared": "Lizenz entfernt — System fällt auf Trial zurück",
|
||
"confirmClear": "Lizenz-Schlüssel wirklich entfernen?",
|
||
"confirmClearHint": "System fällt auf Trial-Modus zurück, sobald der Schlüssel gelöscht wird.",
|
||
"lastVerifyFailed": "Letzte Server-Verifizierung fehlgeschlagen",
|
||
"serverRejected": "Lizenz vom Server abgelehnt",
|
||
"trialExpiring": "Trial läuft in {{days}} Tag(en) ab",
|
||
"trialExpiringHint": "Lizenz aktivieren, bevor die Trial-Periode endet.",
|
||
"tagActive": "Aktiv",
|
||
"tagTrial": "Trial",
|
||
"tagExpired": "Abgelaufen",
|
||
"tagInvalid": "Ungültig"
|
||
},
|
||
"licenseBanner": {
|
||
"expired": "Lizenz abgelaufen oder ungültig.",
|
||
"trialExpiring": "Trial läuft in {{days}} Tag(en) ab.",
|
||
"verifyFailed": "Lizenz-Verifizierung fehlgeschlagen",
|
||
"cta": "Jetzt aktivieren →",
|
||
"openPage": "Lizenz-Seite öffnen →"
|
||
},
|
||
"routes": {
|
||
"liveTitle": "Live-Routen (Kernel)",
|
||
"liveIntro": "Aktueller Zustand aus `ip -j route show table all`. proto edgeguard markiert von EdgeGuard verwaltete Routen — andere Quellen (kernel/static/dhcp) bleiben unberührt.",
|
||
"liveEmpty": "Keine Routen im Kernel.",
|
||
"managedTitle": "Verwaltete Routen",
|
||
"managedIntro": "Statische Routen, die EdgeGuard via `ip route … proto edgeguard` beim Boot setzt. Änderung hier triggert sofort `systemctl restart edgeguard-routes.service`.",
|
||
"add": "Route hinzufügen",
|
||
"addTitle": "Statische Route anlegen",
|
||
"editTitle": "Statische Route bearbeiten",
|
||
"empty": "Keine verwalteten Routen.",
|
||
"emptyTitle": "Noch keine verwalteten Routen.",
|
||
"emptyDesc": "Statische Routen die EdgeGuard beim Boot setzt (z. B. nach 10.0.5.0/24 via VPN-Gateway). Live-Routen oben sind read-only — was hier eingetragen wird, persistiert.",
|
||
"confirmDelete": "Route nach {{dest}} wirklich löschen?",
|
||
"refreshTooltip": "Live-Routen neu laden",
|
||
"destExtra": "CIDR — z.B. 10.0.5.0/24 oder 0.0.0.0/0 für Default-Route.",
|
||
"gatewayExtra": "Optional. Leer = on-link via dev.",
|
||
"devExtra": "Output-Interface. Bei Gateway-only kann ip das auflösen, explizit ist aber stabiler.",
|
||
"metricExtra": "Niedriger gewinnt. Default 100. Standard-Linux-Defaults: dhcp 1024, kernel-link 0.",
|
||
"tableExtra": "Routing-Table. main = Standard. Custom-Tables via /etc/iproute2/rt_tables.",
|
||
"col": {
|
||
"destination": "Ziel",
|
||
"gateway": "Gateway",
|
||
"dev": "Interface",
|
||
"metric": "Metric",
|
||
"table": "Table",
|
||
"active": "Aktiv",
|
||
"comment": "Kommentar",
|
||
"proto": "Protokoll",
|
||
"scope": "Scope",
|
||
"src": "Quell-IP"
|
||
}
|
||
},
|
||
"alerts": {
|
||
"title": "Health-Alarme",
|
||
"intro": "Notification-Channels für kritische Events. Webhook (Slack/Discord/Teams/Generic-HTTP) oder Email (SMTP). Triggers: cert.expiring (<14 d), cert.renew_failed, backup.failed, license.invalid.",
|
||
"scopeTitle": "Was triggert Alarme?",
|
||
"scopeDesc": "cert.expiring — TLS-Zertifikat <14 Tage Restzeit (dedupe 12h). cert.renew_failed — ACME-Renewer hat Fails. backup.failed — Scheduled Backup konnte nicht erstellt werden. license.invalid — License-Server liefert valid=false. backend.down — alle Server eines Backend-Pools sind DOWN (2-Min-Check, dedupe 12h). disk.full — Root-Filesystem ≥80% Warnung, ≥90% Critical (stündlich, dedupe 12h). mem.high — RAM-Auslastung ≥85% Warnung, ≥95% Critical (5-Min-Check, dedupe 12h). conntrack.high — Conntrack-Tabelle ≥80% Warnung, ≥90% Critical; bei 100% werden alle neuen Verbindungen lautlos verworfen (2-Min-Check, dedupe 12h). ntp.unsync — chrony hat keine synchronisierte Zeitquelle; Drift führt zu TLS- und JWT-Fehlern (10-Min-Check, dedupe 12h). wg.tunnel.down — WireGuard-Client-Tunnel hat seit >5 Min keinen Handshake; Remote-Traffic wird lautlos verworfen (5-Min-Check, dedupe 12h).",
|
||
"tabs": {
|
||
"channels": "Channels",
|
||
"events": "History"
|
||
},
|
||
"add": "Channel hinzufügen",
|
||
"addTitle": "Notification-Channel anlegen",
|
||
"editTitle": "Channel bearbeiten",
|
||
"test": "Test-Alert",
|
||
"testDone": "Test gesendet — {{ok}}/{{total}} Channels erfolgreich",
|
||
"emptyChannels": "Keine Channels. Lege einen Webhook oder eine Email an.",
|
||
"emptyEvents": "Noch keine Alarme — Triggers haben noch keinen Event gefeuert.",
|
||
"emptyChannelsTitle": "Noch keine Alert-Channels.",
|
||
"emptyChannelsDesc": "Ohne Channels werden gefeuerte Events nur in die Datenbank (Tab \"Events\") geschrieben — niemand wird benachrichtigt. Lege einen Webhook (Mattermost/Slack/Discord/Custom) oder eine SMTP-Email an.",
|
||
"emptyEventsTitle": "Noch keine Alert-Events.",
|
||
"emptyEventsDesc": "Triggers (Cert-Expiry, Backup-Fail, Cluster-Drift, License-Invalid, etc.) haben noch keinen Event gefeuert. Wenn sie feuern, landen sie hier und werden an die konfigurierten Channels zugestellt.",
|
||
"noChannels": "kein Channel aktiv",
|
||
"confirmDelete": "Channel {{name}} wirklich löschen?",
|
||
"kindWebhook": "Webhook (Slack/Discord/Teams/HTTP-Endpoint)",
|
||
"kindEmail": "E-Mail (SMTP)",
|
||
"smtp": {
|
||
"host": "SMTP-Host",
|
||
"port": "SMTP-Port",
|
||
"from": "Absender (From)",
|
||
"username": "Benutzername",
|
||
"password": "Passwort",
|
||
"starttls": "STARTTLS"
|
||
},
|
||
"col": {
|
||
"name": "Name",
|
||
"kind": "Typ",
|
||
"target": "Ziel",
|
||
"targetWebhook": "Webhook-URL",
|
||
"targetEmail": "Empfänger-Email",
|
||
"active": "Aktiv",
|
||
"time": "Zeit",
|
||
"severity": "Severity",
|
||
"subject": "Betreff",
|
||
"delivered": "Gesendet"
|
||
}
|
||
},
|
||
"remotes": {
|
||
"scopeTitle": "Off-Site-Backup-Ziele",
|
||
"scopeDesc": "Nach jedem erfolgreichen lokalen Backup wird das tar.gz in alle aktiven Ziele hochgeladen. S3-Endpoints (AWS, MinIO, Backblaze B2, Cloudflare R2, Hetzner Object Storage) und SFTP/SSH. Schutz gegen Box-Total-Loss.",
|
||
"add": "Ziel hinzufügen",
|
||
"addTitle": "Off-Site-Ziel anlegen",
|
||
"editTitle": "Off-Site-Ziel bearbeiten",
|
||
"empty": "Keine Off-Site-Ziele. Lokale Backups schützen NICHT gegen Disk-Verlust oder Box-Total-Defekt.",
|
||
"test": "Test",
|
||
"testOk": "Test erfolgreich — Verbindung + Upload + Cleanup OK.",
|
||
"testFailed": "Test fehlgeschlagen",
|
||
"confirmDelete": "Ziel {{name}} wirklich löschen?",
|
||
"targetExtra": "Frei-Text-Label für die Übersicht. Setze hier den Bucket/Path-Hinweis (s3://my-bucket oder sftp://backup@host).",
|
||
"namePlaceholder": "z.B. MinIO offsite / Hetzner Storage Box",
|
||
"kindS3": "S3 (AWS / MinIO / R2 / B2 / Hetzner Object)",
|
||
"kindSftp": "SFTP (SSH)",
|
||
"s3": {
|
||
"endpoint": "Endpoint",
|
||
"endpointExtra": "s3.amazonaws.com / minio.example.com:9000 / fsn1.your-objectstorage.com",
|
||
"region": "Region",
|
||
"regionPlaceholder": "eu-central-1 / auto (R2)",
|
||
"bucket": "Bucket",
|
||
"accessKey": "Access Key",
|
||
"secretKey": "Secret Key",
|
||
"pathPrefix": "Path Prefix",
|
||
"pathPrefixExtra": "z.B. edgeguard/utm-1 — wird vor jedem Dateinamen gesetzt",
|
||
"useSsl": "HTTPS (use_ssl)"
|
||
},
|
||
"sftp": {
|
||
"host": "Host",
|
||
"port": "Port",
|
||
"username": "Benutzername",
|
||
"password": "Passwort",
|
||
"passwordExtra": "Entweder Passwort ODER Private Key.",
|
||
"privateKey": "Private Key (OpenSSH, base64)",
|
||
"privateKeyPlaceholder": "base64-kodierter OpenSSH Private Key",
|
||
"remoteDir": "Remote-Verzeichnis",
|
||
"remoteDirExtra": "z.B. /backups/edgeguard",
|
||
"hostKeyFp": "Host-Key-Fingerprint",
|
||
"hostKeyFpExtra": "Optional. SHA256:... — leer lassen für TOFU (unsicher gegen MitM)"
|
||
},
|
||
"col": {
|
||
"name": "Name",
|
||
"kind": "Typ",
|
||
"target": "Ziel",
|
||
"lastUpload": "Letzter Upload",
|
||
"active": "Aktiv"
|
||
}
|
||
},
|
||
"diag": {
|
||
"title": "Diagnose",
|
||
"intro": "Operator-Tools direkt aus dem UI: ping, traceroute, DNS, HTTP-Probe, TCP-Connect. Alle Calls laufen authentifiziert auf dieser Box (nicht im Browser).",
|
||
"run": "Ausführen",
|
||
"runFromBoxTitle": "Tools laufen auf der EdgeGuard-Box",
|
||
"runFromBoxDesc": "Diese Aufrufe nutzen die Outbound-Connectivity der EdgeGuard — nicht deinen lokalen Rechner. Wenn ping/curl auf der Box scheitert, ist das Box-Network-Layer das Problem (Firewall-Rules, Default-Route, DNS).",
|
||
"ping": {
|
||
"intro": "ICMP echo-request × 4, 2s Timeout pro Paket. Misst Loss + RTT."
|
||
},
|
||
"trace": {
|
||
"intro": "Hop-by-Hop-Pfad mit UDP-Probes (max 20 Hops, kein reverse-DNS)."
|
||
},
|
||
"dig": {
|
||
"intro": "DNS-Lookup über unbound (Box-Resolver). Zeigt Antwort + AUTHORITY/ADDITIONAL Section."
|
||
},
|
||
"curl": {
|
||
"intro": "HTTPS-Probe mit -IsSv: TLS-Handshake-Details + Header. Folgt KEINE Redirects."
|
||
},
|
||
"tcp": {
|
||
"intro": "Pure TCP-Connect ohne I/O. Ideal um zu prüfen ob ein Backend-Port erreichbar ist."
|
||
},
|
||
"securityTitle": "Sicherheits-Hinweis",
|
||
"securityDesc": "Endpoints sind hinter Admin-Auth. Targets werden gegen eine strikte Allow-Liste validiert (keine Shell-Metazeichen). curl ist auf http(s) beschränkt — kein file:// / smb:// / data://.",
|
||
"noOutput": "(kein Output)"
|
||
},
|
||
"backups": {
|
||
"title": "Backups",
|
||
"intro": "Sicherungen der PostgreSQL-Datenbank + /var/lib/edgeguard (Setup, License, JWT, ACME-Account). Täglicher Auto-Job + manueller Trigger.",
|
||
"scopeTitle": "Was wird gesichert?",
|
||
"scopeDesc": "DB-Dump (pg_dump --clean), setup.json, license_key, license.cache, .jwt_fingerprint, acme-account/. Konfig-Dateien (haproxy.cfg, nft, …) sind aus der DB regenerierbar und werden NICHT mitgesichert.",
|
||
"tabs": {
|
||
"history": "Sicherungen",
|
||
"remotes": "Off-Site-Ziele"
|
||
},
|
||
"statLastSuccess": "Letzte erfolgreiche Sicherung",
|
||
"statTotal": "Erfolgreiche Sicherungen",
|
||
"statSize": "Gesamtgröße",
|
||
"statFails24h": "Fehlschläge (24 h)",
|
||
"statHoursAgo": "vor {{n}} h",
|
||
"statDaysAgo": "vor {{n}} Tagen",
|
||
"runNow": "Backup jetzt erstellen",
|
||
"created": "Backup erstellt: {{file}}",
|
||
"failed": "Backup fehlgeschlagen",
|
||
"deleted": "Backup gelöscht",
|
||
"download": "Download",
|
||
"restore": "Wiederherstellen",
|
||
"restoreOk": "Restore starten",
|
||
"restoreDone": "Restore abgeschlossen — Seite wird neu geladen.",
|
||
"restoreFailed": "Restore fehlgeschlagen",
|
||
"restoreRunning": "Restore läuft …",
|
||
"restoreHint": "edgeguard-api startet automatisch neu; die UI lädt nach Restart neu.",
|
||
"confirmRestoreTitle": "Backup wiederherstellen?",
|
||
"confirmRestoreDesc": "DB-Inhalt wird durch {{file}} ersetzt + State-Dateien zurückgespielt. edgeguard-api + scheduler restarten. Aktuelle Änderungen seit dem Backup gehen verloren.",
|
||
"step": {
|
||
"extract": "Tar entpacken",
|
||
"psql": "DB-Restore (psql)",
|
||
"render": "Configs re-rendern",
|
||
"restart": "Services neu starten"
|
||
},
|
||
"downloadTooltip": "tar.gz herunterladen",
|
||
"refreshTooltip": "Liste neu laden",
|
||
"confirmDelete": "Backup {{file}} wirklich löschen?",
|
||
"empty": "Noch keine Backups. Klicke „Backup jetzt erstellen\" oder warte den nächsten Auto-Tick ab.",
|
||
"failedTag": "FEHLER",
|
||
"okTag": "OK",
|
||
"col": {
|
||
"time": "Zeit",
|
||
"file": "Datei",
|
||
"kind": "Typ",
|
||
"status": "Status",
|
||
"size": "Größe",
|
||
"duration": "Dauer"
|
||
}
|
||
},
|
||
"logs": {
|
||
"title": "System-Logs",
|
||
"intro": "Aggregierter Blick auf alle Service-Journals + audit_log. Multi-Source-Auswahl, Level-Filter, Freitext-Suche, Zeit-Range, Auto-Refresh (5s).",
|
||
"autoOn": "Auto",
|
||
"autoOff": "Manuell",
|
||
"refresh": "Aktualisieren",
|
||
"refreshTooltip": "Einmalig neu laden",
|
||
"export": "CSV",
|
||
"exportTooltip": "Aktuelle Tabelle als CSV exportieren",
|
||
"exportEmpty": "Keine Einträge zum Exportieren",
|
||
"found": "{{n}} Einträge",
|
||
"limit": "Limit",
|
||
"empty": "Keine Einträge gefunden. Quellen-Auswahl ändern oder Zeit-Range erweitern.",
|
||
"col": {
|
||
"time": "Zeit",
|
||
"source": "Quelle",
|
||
"level": "Level",
|
||
"message": "Nachricht"
|
||
},
|
||
"filter": {
|
||
"sources": "Quellen wählen (alle wenn leer)",
|
||
"levels": "Level filtern",
|
||
"grep": "Volltext-Suche",
|
||
"reset": "Filter zurücksetzen"
|
||
}
|
||
},
|
||
"fwlog": {
|
||
"title": "Firewall-Log (Live)",
|
||
"intro": "Pakete, die in nft-Regeln mit aktivem Log-Flag matchen, fließen via NFLOG → ulogd2 → JSONL hierher. WebSocket-Stream zeigt Live-Events; Ring-Buffer (1000) hält die letzten Treffer auch nach Reconnect.",
|
||
"start": "Live-Log starten",
|
||
"stop": "Stop",
|
||
"notStartedTitle": "Live-Log ist aus",
|
||
"notStartedDesc": "Standardmäßig pausiert — Klick zum Verbinden lässt Events live einfließen.",
|
||
"live": "Live",
|
||
"disconnected": "verbinde …",
|
||
"pause": "Pause",
|
||
"resume": "Fortsetzen",
|
||
"queued": "wartend",
|
||
"clear": "Leeren",
|
||
"clearTooltip": "Tabelle leeren (Server-Ringbuffer bleibt unverändert)",
|
||
"export": "CSV",
|
||
"exportTooltip": "Aktuelle Tabelle als CSV exportieren",
|
||
"exportEmpty": "Keine Events zum Exportieren",
|
||
"connError": "WebSocket-Fehler — versuche erneut",
|
||
"empty": "Noch keine Events. Aktiviere bei einer Firewall-Regel den Log-Schalter, dann fließen Treffer hier hinein.",
|
||
"connecting": "Verbinde …",
|
||
"col": {
|
||
"time": "Zeit",
|
||
"action": "Aktion",
|
||
"rule": "Rule",
|
||
"proto": "Proto",
|
||
"src": "Quelle",
|
||
"dst": "Ziel",
|
||
"iface": "Interface",
|
||
"size": "Größe"
|
||
},
|
||
"filter": {
|
||
"action": "Action filtern",
|
||
"proto": "Proto filtern",
|
||
"src": "Quell-IP",
|
||
"dst": "Ziel-IP",
|
||
"rule": "Rule-ID"
|
||
}
|
||
},
|
||
"users": {
|
||
"title": "Benutzerverwaltung",
|
||
"intro": "Login-Accounts verwalten. Alle hier gelisteten Accounts können sich über die Login-Seite authentifizieren.",
|
||
"addUser": "Benutzer anlegen",
|
||
"editUser": "Benutzer bearbeiten",
|
||
"email": "E-Mail",
|
||
"role": "Rolle",
|
||
"active": "Aktiv",
|
||
"lastLogin": "Letzter Login",
|
||
"created": "Angelegt",
|
||
"setPassword": "Passwort setzen",
|
||
"setPasswordTitle": "Neues Passwort setzen",
|
||
"newPassword": "Neues Passwort",
|
||
"newPasswordHint": "Mindestens 12 Zeichen.",
|
||
"roleAdmin": "Admin",
|
||
"roleViewer": "Betrachter (nur lesen)",
|
||
"deleteConfirm": "Benutzer {{email}} löschen? Dies kann nicht rückgängig gemacht werden.",
|
||
"emptyTitle": "Noch keine Benutzer",
|
||
"emptyDesc": "Oben den ersten zusätzlichen Benutzer anlegen.",
|
||
"errorEmailTaken": "Diese E-Mail-Adresse wird bereits verwendet.",
|
||
"cannotDeleteSelf": "Das eigene Konto kann nicht gelöscht werden.",
|
||
"you": "Ich",
|
||
"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",
|
||
"intro": "Wer hat was wann geändert. Jede Mutation in der API (Domain anlegen, Backend deaktivieren, Cert ausstellen, …) ist hier nachvollziehbar.",
|
||
"filter": {
|
||
"actor": "Actor",
|
||
"action": "Aktion",
|
||
"subject": "Subjekt",
|
||
"range": "Zeitraum",
|
||
"search": "Suchen",
|
||
"reset": "Zurücksetzen"
|
||
},
|
||
"col": {
|
||
"time": "Zeit",
|
||
"actor": "Actor",
|
||
"action": "Aktion",
|
||
"subject": "Subjekt",
|
||
"detail": "Details"
|
||
},
|
||
"detailShow": "Details anzeigen",
|
||
"empty": {
|
||
"title": "Keine Treffer",
|
||
"desc": "Mit diesen Filtern wurden keine Einträge gefunden. Filter ändern oder zurücksetzen."
|
||
},
|
||
"page": {
|
||
"prev": "Zurück",
|
||
"next": "Weiter",
|
||
"showing": "Zeile {{from}}–{{to}}"
|
||
}
|
||
},
|
||
"cs": {
|
||
"title": "CrowdSec IDS",
|
||
"intro": "Kollaborative Intrusion-Detection: Log-basierte Erkennung + Community-Blocklisten via nftables-Bouncer.",
|
||
"notInstalled": "CrowdSec ist nicht installiert. Bitte das Paket crowdsec + crowdsec-firewall-bouncer-nftables installieren.",
|
||
"status": {
|
||
"agent": "Agent",
|
||
"bouncer": "Bouncer",
|
||
"decisions": "Aktive Sperren",
|
||
"alerts": "Alarme",
|
||
"running": "Aktiv",
|
||
"stopped": "Gestoppt",
|
||
"notInstalled": "Nicht installiert"
|
||
},
|
||
"tabs": {
|
||
"decisions": "Entscheidungen",
|
||
"alerts": "Alarme",
|
||
"bouncers": "Bouncers",
|
||
"machines": "Maschinen",
|
||
"collections": "Collections"
|
||
},
|
||
"decision": {
|
||
"ip": "IP-Adresse",
|
||
"reason": "Grund",
|
||
"origin": "Herkunft",
|
||
"duration": "Dauer",
|
||
"country": "Land",
|
||
"as": "AS-Name",
|
||
"type": "Typ",
|
||
"unban": "Entsperren",
|
||
"banModal": "Manuell sperren",
|
||
"banBtn": "IP sperren",
|
||
"confirmUnban": "IP {{ip}} wirklich entsperren?",
|
||
"addSuccess": "IP {{ip}} wurde gesperrt.",
|
||
"deleteSuccess": "Sperre aufgehoben."
|
||
},
|
||
"alert": {
|
||
"id": "ID",
|
||
"scenario": "Szenario",
|
||
"events": "Events",
|
||
"sourceIP": "Quell-IP",
|
||
"country": "Land",
|
||
"start": "Beginn",
|
||
"stop": "Ende",
|
||
"delete": "Verwerfen",
|
||
"confirmDelete": "Alarm #{{id}} wirklich verwerfen?"
|
||
},
|
||
"bouncer": {
|
||
"name": "Name",
|
||
"ip": "IP",
|
||
"validKey": "Key gültig",
|
||
"version": "Version",
|
||
"lastPull": "Letzter Pull",
|
||
"type": "Typ",
|
||
"delete": "Entfernen",
|
||
"confirmDelete": "Bouncer {{name}} wirklich entfernen?"
|
||
},
|
||
"machine": {
|
||
"id": "Machine-ID",
|
||
"created": "Angelegt",
|
||
"lastPush": "Letzter Push",
|
||
"validated": "Validiert",
|
||
"version": "Version",
|
||
"delete": "Entfernen",
|
||
"confirmDelete": "Maschine {{id}} wirklich entfernen?"
|
||
},
|
||
"collection": {
|
||
"name": "Collection",
|
||
"status": "Status",
|
||
"version": "Version",
|
||
"author": "Author",
|
||
"install": "Installieren",
|
||
"remove": "Entfernen",
|
||
"enabled": "Installiert",
|
||
"disabled": "Nicht installiert",
|
||
"confirmRemove": "Collection {{name}} wirklich entfernen?"
|
||
}
|
||
},
|
||
"waf": {
|
||
"title": "Web Application Firewall",
|
||
"intro": "Domänen-spezifische HTTP-Request-Inspektion via Coraza/OWASP CRS. Standard: für alle Domains deaktiviert.",
|
||
"configure": "Konfigurieren",
|
||
"toggleFailed": "WAF-Status konnte nicht geändert werden.",
|
||
"defaultOffHint": "WAF ist standardmäßig für alle Domains deaktiviert. Unten pro Domain aktivieren und konfigurieren.",
|
||
"col": {
|
||
"domain": "Domain",
|
||
"status": "WAF",
|
||
"mode": "Modus",
|
||
"paranoia": "Paranoia"
|
||
},
|
||
"stat": {
|
||
"protected": "Geschützt",
|
||
"blocking": "Blocking",
|
||
"detection": "Nur Erkennung"
|
||
},
|
||
"mode": {
|
||
"detection": "Erkennung",
|
||
"blocking": "Blocking"
|
||
},
|
||
"pl": {
|
||
"1": "Basis (empfohlen)",
|
||
"2": "Standard",
|
||
"3": "Erweitert",
|
||
"4": "Maximum (kann Traffic brechen)"
|
||
},
|
||
"config": {
|
||
"enabled": "Aktiviert",
|
||
"mode": "Modus",
|
||
"paranoia": "Paranoia-Level",
|
||
"exclusions": "Regel-Ausnahmen",
|
||
"exclusionsHint": "Kommagetrennte Regel-IDs die deaktiviert werden (z.B. 920350, 941130).",
|
||
"trustedProxies": "Vertrauenswürdige Proxys",
|
||
"trustedProxiesHint": "IPs/CIDRs die die WAF-Inspektion umgehen (z.B. interne Load Balancer).",
|
||
"customRules": "Eigene SecRules",
|
||
"customRulesHint": "Rohe SecRule-Direktiven die nach dem CRS eingefügt werden. Können CRS-Regeln überschreiben.",
|
||
"defaultHint": "Standard: Nur-Erkennung, Paranoia-Level 1. Erst auf Blocking wechseln, nachdem Alerts geprüft wurden.",
|
||
"saveFailed": "WAF-Konfiguration konnte nicht gespeichert werden.",
|
||
"noExclusions": "Noch keine Regelausnahmen.",
|
||
"noNote": "Keine Notiz",
|
||
"exclusionsAddHint": "Ausnahmen über den Alarme-Tab hinzufügen — \"Als Ausnahme\" auf einem Alarm klicken."
|
||
},
|
||
"tabs": {
|
||
"domains": "Domains",
|
||
"alerts": "Alarme"
|
||
},
|
||
"alerts": {
|
||
"total": "Einträge",
|
||
"empty": "Noch keine WAF-Alarme. Regelübereinstimmungen erscheinen hier.",
|
||
"purge30d": "> 30 Tage löschen",
|
||
"purgeConfirm": "Alle Alarme älter als 30 Tage löschen?",
|
||
"purged": "Alarme gelöscht.",
|
||
"blocked": "Geblockt",
|
||
"detected": "Erkannt",
|
||
"col": {
|
||
"time": "Zeit",
|
||
"action": "Aktion",
|
||
"hostname": "Domain",
|
||
"clientIp": "Client-IP",
|
||
"method": "Methode",
|
||
"uri": "URI",
|
||
"ruleId": "Regel-ID",
|
||
"severity": "Schwere",
|
||
"msg": "Meldung"
|
||
},
|
||
"addException": "Als Ausnahme",
|
||
"exceptionAdded": "Regel als Ausnahme für diese Domain hinzugefügt.",
|
||
"exceptionFailed": "Ausnahme konnte nicht gespeichert werden.",
|
||
"noDomain": "Domain nicht gefunden — Ausnahme manuell konfigurieren.",
|
||
"exceptionModalTitle": "Ausnahme für Regel {{rule}} hinzufügen",
|
||
"exceptionModalHint": "Optional: Begründung warum diese Regel ein False Positive für diese Domain ist.",
|
||
"exceptionNotePlaceholder": "z.B. Unsere API verwendet nicht-standardisierte Header die diese Regel auslösen.",
|
||
"alreadyExcluded": "Bereits Ausnahme"
|
||
}
|
||
}
|
||
} |