Files
edgeguard-native/management-ui/src/i18n/locales/en/common.json
Debian bc5d81d966 feat(wg): Push-Routes (Client-Routes) für WireGuard-Server-Interfaces
Neues Feld 'client_routes' auf wireguard_interfaces: der Operator
trägt dort kommagetrennte Netzwerke ein (z. B. 10.0.10.0/24 für ein
LAN hinter der Box). Der Peer-Config-Download fügt diese automatisch
als zusätzliche AllowedIPs in den [Peer]-Block der Client-Config ein.

Bisher wurde nur das Server-Tunnel-Subnetz (ifc.address_cidr) als
AllowedIPs exportiert — Peers konnten so keine anderen Netze über
den Tunnel erreichen ohne die Config manuell anzupassen.

Migration: 0026_wg_client_routes.sql

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-21 13:09:51 +02:00

1165 lines
56 KiB
JSON
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
{
"app": {
"title": "EdgeGuard",
"subtitle": "Native reverse-proxy / VPN / firewall"
},
"nav": {
"dashboard": "Dashboard",
"domains": "Domains",
"backends": "Backends",
"routing": "Routing",
"networks": "Network interfaces",
"ipAddresses": "IP addresses",
"ssl": "SSL certificates",
"vpn": "VPN",
"wireguard": "WireGuard",
"forwardProxy": "Forward proxy",
"dns": "DNS",
"ntp": "Time (NTP)",
"firewall": "Firewall",
"firewallLive": "Firewall log",
"cluster": "Cluster",
"logs": "Logs",
"audit": "Audit log",
"backups": "Backups",
"diagnostics": "Diagnostics",
"alerts": "Alerts",
"license": "License",
"settings": "Settings",
"section": {
"overview": "Overview",
"routing": "Routing",
"network": "Network",
"security": "Security",
"system": "System"
}
},
"fw": {
"title": "Firewall",
"intro": "Fortigate-style: rules built from zones × address objects/groups × services/service groups × action. NAT is separate. Top-down, first-match.",
"tabs": {
"rules": "Rules",
"nat": "NAT",
"live": "Live log",
"zones": "Zones",
"addrObj": "Address objects",
"addrGrp": "Address groups",
"services": "Services",
"svcGrp": "Service groups",
"system": "System rules"
},
"zone": {
"name": "Name",
"description": "Description",
"builtin": "built-in",
"builtinHint": "Built-in zones cannot be deleted — the renderer and anti-lockout rules depend on them.",
"builtinNameLocked": "Name is built-in — cannot be changed because existing rules and interfaces reference it.",
"namePattern": "Lowercase letters, digits, _ and -; must start with a letter, up to 32 chars.",
"add": "Add zone",
"edit": "Edit zone",
"deleteConfirm": "Really delete zone {{name}}?",
"emptyTitle": "No custom firewall zones yet.",
"emptyDesc": "Zones group interfaces (lan, wan, dmz). Built-in zones already exist; add custom ones for e.g. a separate dmz or a wg zone for VPN."
},
"ao": {
"name": "Name", "kind": "Kind", "value": "Value", "description": "Description",
"add": "Add address object", "edit": "Edit address object",
"deleteConfirm": "Really delete address object {{name}}?",
"emptyTitle": "No address objects yet.",
"emptyDesc": "Reusable named IPs/networks/ranges for firewall + NAT rules (e.g. office-net = 10.0.0.0/24, mailbox-1 = 10.0.1.42)."
},
"ag": {
"name": "Name", "members": "Members", "description": "Description",
"add": "Add address group", "edit": "Edit address group",
"selectMembers": "Select address objects",
"deleteConfirm": "Really delete address group {{name}}?",
"emptyTitle": "No address groups yet.",
"emptyDesc": "Bundle multiple address objects into a group (e.g. office-locations = [hq-net, branch-1-net, branch-2-net]) — rules then reference one group."
},
"svc": {
"name": "Name", "proto": "Protocol", "ports": "Ports",
"portStart": "Port (start)", "portEnd": "Port (end)",
"description": "Description", "builtinHint": "Built-in — not editable",
"add": "Add service", "edit": "Edit service",
"deleteConfirm": "Really delete service {{name}}?",
"emptyTitle": "No custom services yet.",
"emptyDesc": "Built-ins (HTTP, HTTPS, SSH, …) already exist. Add app-specific ports (e.g. mailcow-imaps tcp/993) to make rules easier to read."
},
"sg": {
"name": "Name", "members": "Members", "description": "Description",
"add": "Add service group", "edit": "Edit service group",
"selectMembers": "Select services",
"deleteConfirm": "Really delete service group {{name}}?",
"emptyTitle": "No service groups yet.",
"emptyDesc": "Bundle multiple services into a group (e.g. web-stack = [HTTP, HTTPS, HTTP/3]) — one rule with the group replaces three rules per service."
},
"rule": {
"name": "Name", "priority": "Priority", "enabled": "Enabled", "log": "Log",
"action": "Action", "src": "Source", "dst": "Destination", "service": "Service",
"srcZone": "Source zone", "dstZone": "Dest. zone",
"srcKind": "Source kind", "dstKind": "Dest. kind",
"object": "Address object", "group": "Address group",
"serviceKind": "Service kind", "serviceGroup": "Service group",
"comment": "Comment",
"hits": "Hits",
"add": "Add rule", "edit": "Edit rule",
"deleteConfirm": "Really delete this rule?",
"emptyTitle": "No custom firewall rules yet.",
"emptyDesc": "The system rules above keep SSH (rate-limited), HTTPS :443 and the mgmt UI :3443 open (anti-lockout). Add custom rules for app-specific inbound ports or cross-zone forwards."
},
"nat": {
"name": "Name", "priority": "Priority", "kind": "Kind", "enabled": "Enabled",
"match": "Match", "target": "Target",
"inZone": "Ingress zone", "outZone": "Egress zone", "proto": "Protocol",
"matchSrcCidr": "Source CIDR (match)", "matchDstCidr": "Dest. CIDR (match)",
"matchDstCidrHint": "empty = any dest IP (e.g. box's public IP)",
"dportStart": "Port (start)", "dportEnd": "Port (end)",
"targetAddr": "Target address", "targetPortStart": "Target port (start)", "targetPortEnd": "Target port (end)",
"comment": "Comment",
"add": "Add NAT rule", "edit": "Edit NAT rule",
"deleteConfirm": "Really delete this NAT rule?",
"emptyTitle": "No NAT rules yet.",
"emptyDesc": "DNAT (e.g. external :2030 → internal 10.10.20.12:22 for SSH to an internal host) or SNAT/MASQUERADE (internet access for an internal subnet via the box IP)."
},
"sys": {
"title": "System rules (always active)",
"chain": "Chain", "match": "Match", "action": "Action", "note": "Note",
"policy": "Default policy",
"policyValue": "Input DROP — everything must be explicitly allowed.",
"order": "Evaluation",
"orderValue": "System rules first, then operator rules top-down (priority asc, first-match).",
"lockout": "Anti-lockout",
"lockoutValue": "SSH (22) and the management UI (443) are always reachable — even the operator can't accidentally lock themselves out."
}
},
"networks": {
"title": "Network",
"intro": "Interfaces (Ethernet / VLAN / bond / bridge) and static routes. Live kernel discovery + declared configuration from the DB.",
"tabs": {
"interfaces": "Interfaces",
"routes": "Routes"
},
"systemDiscovered": "System interfaces — live traffic counters (since last HAProxy start)",
"addresses": "Addresses",
"linkType": "Type",
"systemEmpty": "No kernel interfaces found",
"addInterface": "Add interface",
"editInterface": "Edit interface",
"emptyTitle": "No managed interfaces yet.",
"emptyDesc": "System interfaces (above) are detected read-only. Managed interfaces are what EdgeGuard creates itself — VLANs, bridges, bonds, GRE tunnels — and renders into /etc/network/interfaces.d.",
"name": "Name",
"type": "Type",
"parent": "Parent interface",
"selectParent": "Select parent",
"vlan": "VLAN",
"vlanId": "VLAN ID",
"composition": "Composition",
"members": "Member interfaces",
"selectMembers": "Select physical interfaces",
"membersRequired": "At least one member interface is required",
"membersHintBridge": "A bridge joins multiple physical ports at L2 — typically two ports for a software switch.",
"membersHintBond": "A bond aggregates multiple physical ports into one logical link (LACP / active-backup).",
"role": "Zone",
"roleHint": "Zones are managed in Firewall → Zones. Custom zones (e.g. iot, guest) can be added there.",
"mtu": "MTU",
"active": "Active",
"description": "Description",
"actions": "Actions",
"deleteConfirm": "Really delete interface {{name}}?"
},
"ips": {
"title": "IP addresses",
"intro": "Addresses the kernel currently has (read-only above) plus addresses EdgeGuard additionally manages — including VIPs that follow the active cluster node on failover.",
"systemDiscovered": "Kernel addresses (read-only)",
"managedTitle": "Managed addresses",
"family": "Family",
"addAddress": "Add address",
"emptyTitle": "No managed IP addresses yet.",
"emptyDesc": "Add your first IP — floating VIPs for HA failover or extra listen addresses for HAProxy/Squid/Unbound. Distro-owned IPs are shown above under \"Detected IPs\".",
"editAddress": "Edit address",
"interface": "Interface",
"selectInterface": "Select interface",
"address": "Address",
"prefix": "Prefix",
"vip": "VIP",
"vipFlag": "Mark as VIP",
"vipPriority": "VIP priority (cluster failover)",
"active": "Active",
"description": "Description",
"actions": "Actions",
"deleteConfirm": "Really delete address {{addr}}?"
},
"auth": {
"loginTitle": "Sign in",
"email": "Email",
"password": "Password",
"login": "Sign in",
"logout": "Sign out",
"loginFailed": "Sign-in failed",
"loggedInAs": "Signed in as",
"forgotPassword": "Forgot your password?"
},
"reset": {
"title": "Reset admin password",
"intro": "Self-service reset via CLI token. You need SSH access to the box.",
"cliTitle": "1. Run on the box:",
"token": "Reset token",
"tokenExtra": "32 hex chars from the CLI output. Token is valid for 30 minutes.",
"newPassword": "New password (min. 12 chars)",
"confirmPassword": "Confirm password",
"submit": "Set password",
"backToLogin": "← Back to sign-in",
"mismatch": "Passwords don't match",
"success": "Password set. Redirecting…",
"failed": "Reset failed"
},
"setup": {
"title": "First-time setup",
"intro": "Create the admin account, declare the public FQDN, and — optionally — paste a license key. Without one, a 30-day trial starts.",
"preflightTitle": "Before you hit \"Finish setup\"",
"preflightDesc": "The FQDN must already resolve to this box via DNS (A/AAAA record). Without DNS resolution the later ACME HTTP-01 challenge fails — your browser URL should already be this FQDN, otherwise things break at first Let's Encrypt issue.",
"adminEmail": "Admin email",
"adminEmailHint": "Login identifier for the management UI. NOT used for outbound mail (use the ACME email below or the SMTP settings inside the Alerts channel).",
"adminPassword": "Admin password",
"passwordRule": "At least 12 characters.",
"fqdn": "Public FQDN",
"fqdnHint": "Fully-qualified hostname of this box (e.g. eg.example.com). Becomes the Subject-CN of the self-signed bootstrap cert AND ends up in the ACME cert once Let's Encrypt has issued. Must resolve to the public IP via DNS.",
"fqdnInvalid": "Doesn't look like a valid FQDN (at least label.tld).",
"acmeEmail": "ACME / Let's Encrypt email",
"acmeEmailHint": "Sent to Let's Encrypt as account contact (expiry warnings + LE compliance mail). Can be the same as the admin email, but doesn't have to be.",
"licenseKey": "License key (optional)",
"licenseKeyHint": "If present: 30-day trial is skipped, features unlock immediately. Can also be added later under License.",
"submit": "Finish setup",
"successTitle": "Setup complete",
"successHint": "Redirecting you to sign-in."
},
"dashboard": {
"title": "Dashboard",
"welcomeHint": "Overview of all running EdgeGuard components."
},
"domains": {
"title": "Domains",
"intro": "Manage FQDNs that HAProxy terminates. Optional primary backend as catch-all; path-based routing via routing rules.",
"addDomain": "Add domain",
"editDomain": "Edit domain",
"backToList": "Back to domains",
"settingsCard": "Domain settings",
"moreOnDetailPage": "HSTS, rate-limit, maintenance and more on the domain detail page after saving.",
"emptyTitle": "No domains yet.",
"emptyDesc": "Add your first domain — HAProxy will then terminate TLS for that hostname and route to the chosen backend.",
"name": "Name",
"active": "Active",
"primaryBackend": "Primary backend",
"primaryBackendHint": "Catch-all backend for requests with no matching routing rule. Optional — leave empty if all traffic is routed via routing rules.",
"selectBackend": "Select backend",
"noBackend": "no backend",
"quickBackendBtn": "+ new",
"quickBackendBtnHint": "Create backend + first upstream server in one click — skips the trip to the Backends page.",
"quickBackendTitle": "Quick-add backend",
"quickBackendName": "Backend name",
"quickBackendScheme": "Scheme",
"quickBackendAddress": "Server address (first upstream)",
"quickBackendPort": "Port",
"quickBackendCreated": "Backend + server created and selected.",
"quickBackendFailed": "Quick-add backend failed",
"httpToHttps": "HTTP→HTTPS",
"hsts": "HSTS",
"tlsCert": "TLS cert",
"tlsCertValid": "valid",
"tlsCertExpiring": "{{days}}d left",
"tlsCertExpired": "expired",
"tlsCertError": "error",
"tlsCertNone": "missing",
"tlsCertNoneHint": "No own TLS cert for this domain — HAProxy serves the bootstrap self-signed cert. Issue one in the SSL tab.",
"hstsMaxAge": "HSTS max-age (sec)",
"hstsMaxAgeHint": "How long browsers cache the HTTPS-only mandate. Recommended: 31536000 (1 year).",
"hstsSubdomains": "includeSubDomains",
"hstsSubdomainsHint": "Extends HSTS to all subdomains. Enable only if every subdomain speaks HTTPS exclusively.",
"hstsPreload": "preload",
"hstsPreloadHint": "Sets the preload flag so the domain may be added to hstspreload.org. Requires max-age ≥ 31536000 and includeSubDomains enabled.",
"maintenance": "Maintenance mode",
"maintenanceHint": "On: every request to this domain receives 503 directly from HAProxy. Backends are not contacted.",
"maintenanceMessage": "Maintenance message",
"maintenanceMessagePlaceholder": "Service temporarily unavailable.",
"wwwRedirect": "www redirect",
"wwwRedirectHint": "Canonicalises the domain. \"to naked\": Name=example.com → www.example.com redirects to example.com. \"to www\": Name=www.example.com → example.com redirects to www.example.com.",
"wwwRedirectNone": "No redirect",
"wwwRedirectToNaked": "→ naked (no www)",
"wwwRedirectToWWW": "→ www",
"rateLimit": "Rate limit (per client IP)",
"rateLimitHint": "Max requests per second per client IP. HAProxy counts over a 10-second window per stick table (max. 100k IPs). 0 = off.",
"maxBody": "Max request body",
"maxBodyHint": "Cap on the Content-Length header. Larger requests get 413. Chunked bodies are not detected (HAProxy doesn't buffer by default). 0 = off.",
"headersBtn": "Headers",
"headersTitle": "Response headers — {{name}}",
"headersHint": "These headers are set by HAProxy on every response for this domain (http-response set-header). Ordering via position.",
"headersEmpty": "No custom headers configured yet.",
"addHeader": "Add header",
"editHeader": "Edit header",
"headerName": "Name",
"headerNameHint": "Letters, digits, hyphens only. Case-insensitive unique per domain.",
"headerNamePattern": "Only a-z, A-Z, 0-9, '-'",
"headerValue": "Value",
"headerPosition": "Position",
"headerDeleteConfirm": "Really delete header \"{{name}}\"?",
"settingsSection": "HAProxy settings",
"notes": "Notes",
"actions": "Actions",
"edit": "Edit",
"delete": "Delete",
"deleteConfirm": "Really delete domain {{name}}?"
},
"backends": {
"title": "Backends",
"intro": "Upstream pools (one backend = N servers). HAProxy balances load by the chosen algorithm; health-check path enables HTTP probes every 5s per server.",
"addBackend": "Add backend pool",
"emptyTitle": "No backend pools yet.",
"emptyDesc": "Add your first pool — define upstream servers (e.g. app servers / mailbox nodes) so domains can route to them.",
"editBackend": "Edit backend pool",
"name": "Name",
"scheme": "Scheme",
"target": "Target",
"healthCheck": "Health check path",
"liveStatus": "HAProxy status",
"active": "Active",
"usedBy": "Used by",
"noDomain": "no domain",
"attachedDomains": "Domains",
"attachedDomainsHint": "Domains that use this backend as their primary. Selecting domains here reconfigures them directly — same source of truth as the Domain modal's backend picker.",
"selectDomains": "Select domains",
"lbAlgo": "Load balancing",
"lbAlgoHint": "roundrobin = evenly, leastconn = pick the server with fewest active connections, source = sticky per client-IP hash (for stateful apps without shared session).",
"websocket": "WebSocket support",
"websocketHint": "On: allow long-lived WebSocket / long-poll connections (Proxmox console, SSH-over-WS, AsyncAPI) — tunnel idle 1h instead of 60s. Off: strict HTTP timeouts.",
"servers": "Servers",
"noServers": "no server",
"nServers": "{{n}} servers",
"serversIn": "Servers in “{{name}}”",
"serverHintCreate": "Saving creates the pool only. Add servers in the next step — open the pool and click “Add server”.",
"actions": "Actions",
"deleteConfirm": "Really delete backend pool {{name}}? All servers in the pool will be removed too.",
"backToList": "Back to backends",
"settingsCard": "Pool settings",
"healthCheckHint": "HTTP path HAProxy probes every 5s per server. Leave empty to disable health checks.",
"server": {
"intro": "Upstream servers in this pool. Order doesn't matter — the LB algorithm decides.",
"add": "Add server",
"edit": "Edit server",
"name": "Server name",
"address": "Address",
"port": "Port",
"target": "Endpoint",
"weight": "Weight",
"weightHint": "0256. Higher = more traffic. 100 = default.",
"backup": "Backup",
"backupHint": "Backup servers receive traffic only when every primary (non-backup) server is down.",
"empty": "No servers in the pool yet. Click “Add server” to get started.",
"deleteConfirm": "Really delete server {{name}}?"
}
},
"routing": {
"title": "Routing rules",
"intro": "Path-prefix → backend mapping per domain. Lowest priority wins; catch-all via domain.primary_backend.",
"addRule": "Add rule",
"emptyTitle": "No routing rules yet.",
"emptyDesc": "Path-specific routing — e.g. domain.tld/api → Backend A, domain.tld/* → Backend B. Optional. Without rules HAProxy uses the primary backend from the domain.",
"editRule": "Edit rule",
"domain": "Domain",
"pathPrefix": "Path prefix",
"backend": "Backend",
"priority": "Priority",
"active": "Active",
"actions": "Actions",
"selectDomain": "Select domain",
"selectBackend": "Select backend",
"deleteConfirm": "Really delete this routing rule?"
},
"cluster": {
"title": "Cluster",
"intro": "{{count}} node(s) registered. Multi-node (KeyDB-AA + PG streaming replication + leader election) lands in stages.",
"id": "Node ID",
"fqdn": "FQDN",
"role": "Role",
"joinedAt": "Joined",
"self": "this node",
"drift": "Drift",
"modeSingle": "Single node",
"modeCluster": "Cluster",
"health": {
"ok": "OK",
"degraded": "degraded",
"split-brain": "split-brain"
},
"selfTitle": "This node",
"noSelf": "Self-registration in ha_nodes failed — run the setup wizard.",
"peersTitle": "Peers ({{count}})",
"singleNodeTitle": "Single-node mode",
"singleNodeDesc": "Only this box is known. To add another node: `edgeguard-ctl cluster-join` (coming in phase 3.2).",
"driftBanner": "Config drift detected",
"driftBannerDesc": "One or more peers have a different config hash than this node. Either changes are still in the outbox or a peer was edited directly in the DB. Wait for the outbox to drain or run diagnostics.",
"col": {
"node": "Node",
"status": "Status",
"role": "Role",
"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"
},
"loadTitle": "Per-node resources (mTLS aggregator)",
"loadEmpty": "No node resources available — agent listener unreachable?",
"certCardTitle": "Cluster TLS certificates",
"certCALabel": "Cluster CA",
"certPeerLabel": "Peer cert (this node)",
"certExpiry": "Expires in",
"renewSelfBtn": "Renew peer cert",
"renewSelfConfirm": "Re-sign peer cert with the local CA (1 year)? edgeguard-api restart required after.",
"certRenewedRestartHint": "Cert renewed — please run sudo systemctl restart edgeguard-api.",
"certRenewFailed": "Cert renewal failed",
"removePeerBtn": "Remove",
"removePeerConfirmTitle": "Really remove peer from the cluster?",
"removePeerConfirmDesc": "{{fqdn}} is deleted from ha_nodes. Firewall renderer drops its IP from the peer_ipv4 set. On the peer side edgeguard-api keeps running; manually stop it + delete cluster-tls for a full decommission.",
"removePeerOk": "Peer removed.",
"removePeerFailed": "Peer removal failed",
"generateJoinToken": "Generate join token",
"joinTokenTitle": "Cluster join token",
"joinTokenOneShot": "Shown once, usable once",
"joinTokenOneShotDesc": "Transfer the token securely now — it won't be shown again. Valid until {{expires}}. Server marks it as consumed when redeemed via cluster-join.",
"joinTokenFailed": "Token generation failed",
"joinCmdLabel": "Run on the new node:"
},
"ssl": {
"title": "SSL certificates",
"intro": "Manage TLS certs — let EdgeGuard issue them via Let's Encrypt or upload your own PEM. HAProxy reloads automatically after each change.",
"tabLE": "Let's Encrypt",
"tabUpload": "Custom certificate",
"leIntro": "Pick a domain, click Issue — EdgeGuard solves HTTP-01 over the ACME webroot, writes the PEM into /etc/edgeguard/tls/, and reloads HAProxy.",
"uploadIntro": "Upload your own certificate. Format: PEM-encoded. Cert + optional chain + private key. EdgeGuard validates cert/key match before writing.",
"uploadHint": "Tip: for Let's Encrypt renewals don't upload here — use the LE tab.",
"domain": "Domain",
"selectDomain": "Pick or type a domain",
"domainExtra": "Includes the management FQDN (from Setup), cluster nodes and operator domains. You can also type any other domain — as long as DNS resolves to this box.",
"fqdnHintMgmt": "management FQDN",
"fqdnHintCluster": "cluster · {{role}}",
"issuer": "Issuer",
"status": "Status",
"expiresIn": "Expires in",
"expiredAgo": "expired {{days}} days ago",
"actions": "Actions",
"issueButton": "Issue certificate",
"uploadButton": "Upload",
"issueSuccess": "Certificate issued + installed.",
"renewBtn": "Renew",
"renewConfirmTitle": "Renew certificate now?",
"renewConfirmDesc": "Triggers an ACME HTTP-01 challenge for {{domain}}. Let's Encrypt has rate limits (50 issues/domain/week) — only use when needed.",
"renewSuccess": "Certificate renewed + installed.",
"renewFailed": "Renewal failed",
"uploadSuccess": "Certificate uploaded + installed.",
"deleteConfirm": "Delete certificate for {{domain}}? HAProxy falls back to the default cert for this domain.",
"installedTitle": "Installed certificates",
"lastRenewed": "Last renewed",
"statTotal": "Certificates total",
"statExpiring": "Expiring < 30 days",
"statExpired": "Expired",
"statErrors": "With errors",
"relAgo": {
"justNow": "just now",
"minutes": "{{n}} min ago",
"hours": "{{n}} h ago",
"days": "{{n}} d ago"
},
"emptyTitle": "No certificates installed yet.",
"emptyDesc": "Use the tabs above — Let's Encrypt issues automatically via HTTP-01 challenge, or upload your own PEM. Until then HAProxy serves the bootstrap self-signed cert for all domains.",
"certPem": "Certificate (PEM)",
"chainPem": "Chain (PEM, optional)",
"keyPem": "Private key (PEM)"
},
"settings": {
"title": "Settings",
"intro": "System information, setup status and admin account.",
"systemInfo": "System",
"version": "Version",
"status": "Status",
"dbSize": "PostgreSQL DB size",
"dbSizeTop": "Top tables",
"upgradeStatusCardTitle": "Last upgrade attempt",
"upgradeStatusStarted": "Started",
"upgradeStatusFinished": "Finished",
"upgradeStatusResult": "Result",
"upgradeStatusState": "State",
"upgradeStatusOk": "Successful",
"upgradeStatusShowLog": "Show full log ({{n}} lines)",
"actionsCardTitle": "System actions",
"actionsHint": "Manual triggers for operator tasks that normally happen automatically on save in the respective pages. Useful after SSH interventions (e.g. /etc/edgeguard/tls/ filled manually).",
"haproxyReloadBtn": "HAProxy reload",
"haproxyReloadOk": "HAProxy reloaded.",
"haproxyReloadFailed": "HAProxy reload failed",
"renderConfigsBtn": "Re-render configs (HAProxy)",
"renderConfigsOk": "Configs re-rendered + reloaded.",
"renderConfigsFailed": "Config render failed",
"backupNowBtn": "Backup now",
"serviceRestartCardTitle": "Restart services",
"serviceRestartBtn": "Restart",
"serviceRestartOk": "{{service}} restarted successfully.",
"serviceRestartFailed": "Restart of {{service}} failed",
"serviceRestartHint": "Restarts the service via systemctl restart. edgeguard-api and PostgreSQL are intentionally excluded.",
"backupNowOk": "Backup triggered — watch status on the Backups page.",
"backupNowFailed": "Backup trigger failed",
"setupInfo": "Setup",
"adminEmail": "Admin email",
"fqdn": "FQDN",
"setupCompleted": "Setup completed",
"emailsCardTitle": "Contact emails",
"adminEmailHint": "Used as the admin login identifier. Change requires a fresh login with the new address.",
"acmeEmail": "ACME email",
"acmeEmailHint": "Sent to Let's Encrypt as account contact. Existing certs are not affected — the next renew op registers the new address.",
"emailsSaved": "Emails updated.",
"emailsFailed": "Email update failed",
"maintenanceCardTitle": "Maintenance mode (whole box)",
"maintenanceOn": "Active — all customer domains return 503. Mgmt UI stays reachable.",
"maintenanceOff": "Inactive — customer traffic is routed normally to backends.",
"maintenanceMessage": "Maintenance message",
"maintenanceMessagePlaceholder": "Service temporarily unavailable — we'll be back shortly.",
"maintenanceMessageHint": "Returned in the body of 503 responses to end users. Plain text, max 500 chars.",
"maintenanceHint": "Switches HAProxy on :443 into default-503 mode. Per-domain maintenance (Domains page) is overridden by this. Mgmt UI on :3443 is NOT affected.",
"maintenanceSaved": "Maintenance mode updated.",
"maintenanceFailed": "Maintenance toggle failed",
"maintenanceActiveTitle": "Maintenance mode active",
"maintenanceActiveDesc": "All customer domains currently return 503. Mgmt UI is reachable — customer traffic is NOT. Settings → Maintenance mode to disable.",
"backupRetentionCardTitle": "Backup retention",
"backupRetentionUnit": "backups",
"backupRetentionDefault": "Default ({{n}} backups) — with daily schedule = {{n}} days of history.",
"backupRetentionCustom": "Custom — the last {{n}} successful backups are kept; older ones are pruned after each backup run.",
"backupRetentionHint": "0 = default (14). 1-365 = custom limit. Each backup is a full pg_dump + files tar (typically 50-500 MB). Reduce when /var disk gets tight.",
"backupRetentionSaved": "Backup retention updated.",
"backupRetentionFailed": "Backup retention update failed",
"auditRetentionCardTitle": "Audit log retention",
"auditRetentionUnit": "days",
"auditRetentionDefault": "Default ({{n}} days) — audit entries older than {{n}} days are pruned daily.",
"auditRetentionCustom": "Custom — audit entries are kept for {{n}} days.",
"auditRetentionHint": "0 = default (90). 1-3650 (= 10 years) for compliance (SOX 7y = 2555, GDPR usually <= 365). Cleanup runs daily in the scheduler.",
"auditRetentionSaved": "Audit retention updated.",
"auditRetentionFailed": "Audit retention update failed",
"autoUpdateCardTitle": "Automatic updates",
"autoUpdateOn": "Enabled — edgeguard packages install automatically every day.",
"autoUpdateOff": "Disabled — install updates manually via the banner.",
"autoUpdateHint": "Whitelist covers edgeguard, edgeguard-api, edgeguard-ui only. Other packages stay under manual control. Requires unattended-upgrades (Trixie distro default). Conf file: /etc/apt/apt.conf.d/52edgeguard-auto-updates.",
"autoUpdateToggled": "Auto-update setting saved.",
"autoUpdateFailed": "Auto-update toggle failed",
"passwordCardTitle": "Change admin password",
"currentPassword": "Current password",
"newPassword": "New password",
"newPasswordHint": "At least 12 characters. Stored as bcrypt hash.",
"confirmPassword": "Confirm new password",
"changePasswordBtn": "Change password",
"passwordChanged": "Password changed.",
"passwordChangeFailed": "Password change failed",
"passwordMismatch": "Passwords do not match.",
"passwordMinLen": "Minimum 12 characters required."
},
"update": {
"available": "Update available: version {{version}}",
"multiPackageHint": "{{count}} packages will be updated.",
"applyNow": "Install now",
"confirmTitle": "Install update now?",
"confirmDesc": "Packages will be updated to version {{version}}. edgeguard-api + scheduler restart (~2-5 s), HAProxy/nft/WG/Squid/Unbound/Chrony stay running.",
"checkNowHint": "Refresh the server-side apt cache now and look for newer versions.",
"updateReady": "Update ready: v{{version}}",
"checkNow": "Check for updates",
"checkDone": "Update available",
"noUpdate": "No new updates",
"checkFailed": "Update check failed",
"running": "Update in progress …",
"waitHint": "Please wait — the page will reload automatically once the new version is live.",
"success": "Updated to {{version}}.",
"failed": "Update failed",
"stepDownload": "Download packages",
"stepInstall": "Install",
"stepRestart": "Service restart",
"stepVerify": "Verification"
},
"wg": {
"title": "WireGuard",
"intro": "WireGuard VPN tunnels. Server mode = we listen for peers; client mode = we dial out to a fixed upstream. Private keys are encrypted at rest.",
"tabs": { "servers": "Server tunnels", "clients": "Client tunnels" },
"serverIntro": "Server tunnels host a peer roster — typically employee devices or branch sites. Each peer can be downloaded as a wg-quick.conf or scanned as a QR code.",
"clientIntro": "Client tunnels connect EdgeGuard to a remote WireGuard server (e.g. HQ datacenter). Allowed IPs control which traffic is routed through the tunnel.",
"iface": {
"name": "Name",
"namePattern": "wg followed by lowercase letters/digits/-, max 15 chars",
"nameExtra": "Suggested: wg0, wg1, wg-hq …",
"address": "Address (CIDR)",
"addressExtra": "Box's tunnel IP, e.g. 10.99.0.1/24 for a /24 pool",
"listenPort": "Listen port",
"publicKey": "Public key",
"privateKey": "Private key (paste)",
"privateKeyExtra": "Fill in only if not auto-generating — base64 32 bytes. Stored encrypted.",
"peerEndpoint": "Peer endpoint",
"peerPublicKey": "Peer public key",
"peerPSK": "Pre-shared key (PSK)",
"peerPSKExtra": "Optional extra layer",
"allowedIPs": "Allowed IPs",
"allowedIPsExtra": "What gets routed through the tunnel. Default = full tunnel.",
"keepalive": "Persistent keepalive (sec)",
"mtu": "MTU",
"zone": "Firewall zone",
"description": "Description",
"addServer": "Add server tunnel",
"editServer": "Edit server tunnel",
"addClient": "Add client tunnel",
"editClient": "Edit client tunnel",
"emptyServerTitle": "No WireGuard server tunnels yet.",
"emptyServerDesc": "Server mode: this box listens on a UDP port and accepts peer connections (e.g. roadwarrior users, site-to-site branches).",
"emptyClientTitle": "No WireGuard client tunnels yet.",
"emptyClientDesc": "Client mode: this box connects to an external WireGuard server (e.g. HQ datacenter, cloud uplink).",
"upstream": "Upstream peer",
"deleteConfirm": "Really delete tunnel {{name}}? wg-quick will be stopped.",
"keys": "Keys",
"generateExtra": "If on: server generates a fresh Curve25519 keypair on save.",
"generateOn": "Server-generated",
"generateOff": "Manual paste",
"editKeyWarning": "Warning: new keys invalidate all existing peer configs. Only change if intentional.",
"clientRoutes": "Push routes (client)",
"clientRoutesExtra": "Extra networks the peer should reach through the tunnel, e.g. 10.0.10.0/24 for a LAN behind the box. Comma-separated. Automatically included in the peer config download."
},
"peers": {
"button": "Peers",
"drawerTitle": "Peer roster"
},
"peer": {
"name": "Name",
"publicKey": "Public key",
"publicKeyExtra": "Generated by the peer device; only paste here if the peer already has a keypair.",
"allowedIPs": "Allowed IPs",
"allowedIPsExtra": "The IP this peer gets in the tunnel — must be /32 (e.g. 10.0.10.2/32), not the whole subnet (/24). Using /24 would route all subnet traffic to this one peer, making other peers unreachable.",
"keepalive": "Keepalive (sec)",
"keepaliveExtra": "0 = off. Recommended 25 behind NAT.",
"lastHandshake": "Last handshake",
"never": "never",
"description": "Description",
"add": "Add peer",
"edit": "Edit peer",
"deleteConfirm": "Really remove peer {{name}}?",
"emptyTitle": "No peers in this tunnel yet.",
"emptyDesc": "Add peers — each is a WireGuard identity (public key + allowed IPs). Server-generated keys give you config download / QR code for mobile clients in one go.",
"keys": "Keys",
"generateExtra": "If on: server generates a keypair for this peer and can hand out the config / QR. If off: paste the peer's public key only — no config download.",
"pskExtra": "If on: server generates a 32-byte PSK for this peer.",
"pskOn": "Generate PSK",
"pskOff": "no PSK",
"downloadConf": "Download wg-quick.conf",
"qrTitle": "WireGuard QR",
"qrHint": "Scan with the WireGuard app (iOS/Android): \"Add tunnel\" → \"Scan QR code\". Replace the Endpoint placeholder in the downloaded conf before use.",
"online": "Online",
"offline": "Offline",
"traffic": "Traffic"
}
},
"dashboard": {
"title": "Dashboard",
"welcomeHint": "EdgeGuard overview — health, counts, live status of the major services.",
"kpi": {
"domains": "Domains",
"backends": "Backends",
"ifaces": "Interfaces",
"fwRules": "FW rules",
"natRules": "NAT rules",
"wg": "WG connections"
},
"wgCard": {
"title": "WireGuard",
"empty": "No WG tunnel configured yet."
},
"firewallCard": {
"title": "Firewall",
"zones": "Zones",
"activeRules": "{{rules}} active rules · {{nat}} NAT"
},
"sslCard": {
"title": "SSL certificates",
"total": "Managed certificates",
"expiringSoon": "{{count}} expiring soon (< 30 days)",
"allFresh": "All certs have > 30 days remaining."
},
"clusterCard": {
"title": "Cluster",
"nodes": "Nodes",
"modeSingle": "Single-Node",
"modeCluster": "Cluster",
"drift": "Config drift detected",
"health": {
"ok": "OK",
"degraded": "degraded",
"split-brain": "split-brain"
}
},
"routingCard": {
"title": "Routing",
"domains": "Domains",
"backends": "Backends",
"attached": "{{count}}/{{total}} domains have a primary backend"
},
"systemCard": {
"title": "System",
"version": "Version",
"api": "API",
"ifaces": "Interfaces",
"wg": "WireGuard"
},
"alertsCard": {
"title": "Recent alerts",
"viewAll": "View all"
},
"onboardingTitle": "Welcome to EdgeGuard",
"onboardingIntro": "Fresh box — here are the next steps to route customer traffic:",
"onboardingStep1": "Create a backend pool (app servers behind HAProxy)",
"onboardingStep2": "Add a domain (FQDN, assign primary backend)",
"onboardingStep3": "Issue TLS certificate (Let's Encrypt HTTP-01)",
"servicesCard": {
"title": "Service status (live, 10s)"
},
"activityCard": {
"title": "Recent activity (audit log)",
"empty": "No activity yet — mutations are logged here."
},
"haproxyCard": {
"title": "HAProxy backends (live)",
"empty": "No backend stats reachable (HAProxy down or admin.sock permission)."
},
"resCard": {
"load": "Load",
"memory": "Memory",
"disk": "Disk /",
"free": "free",
"conntrack": "Conntrack",
"uptime": "Uptime"
}
},
"ntp": {
"title": "Time server (Chrony)",
"intro": "Chrony as time-sync daemon (NTP). Sources on top, listen/serve config on the settings tab. With 'serve_clients' on and LAN-IPs bound, the box itself becomes an NTP server for the LAN.",
"tabs": { "pools": "Sources", "settings": "Settings" },
"statusCard": {
"title": "Sync status (chronyc tracking)",
"sync": "Synchronized",
"synced": "Yes",
"notSynced": "No",
"source": "Source",
"stratum": "Stratum",
"offset": "Offset",
"offsetHint": "Time difference to reference source. Values > 100 ms are unusual — check network or misconfigured source.",
"loading": "Loading…"
},
"pool": {
"kind": "Type",
"kindPool": "pool — DNS round-robin (multiple servers from A records)",
"kindServer": "server — single host",
"address": "Address / host",
"addressExtra": "FQDN (for pool: 0.de.pool.ntp.org) or IP.",
"iburst": "iburst",
"prefer": "prefer",
"minpoll": "min-poll",
"maxpoll": "max-poll",
"options": "Options",
"description": "Description",
"add": "Add source",
"edit": "Edit source",
"deleteConfirm": "Really delete NTP source {{addr}}?",
"emptyTitle": "No NTP sources yet.",
"emptyDesc": "Without configured pool/server entries chrony falls back to its compiled-in default pool (debian.pool.ntp.org). Set custom pools for better time sync or internal stratum servers."
},
"settings": {
"intro": "Global chrony settings. Saves reload chrony automatically.",
"serveClients": "Act as NTP server for clients",
"serveClientsExtra": "If off: chrony acts as client only (port 0). If on + listen IP: binds UDP/123.",
"listenAddresses": "Listen addresses",
"listenAddressesPlaceholder": "Pick IPs (or type)",
"listenAddressesExtra": "Which IPs chrony binds :123/UDP on. 127.0.0.1+::1 = local only; LAN IPs open for LAN clients (FW rule auto-generated).",
"allowACL": "Allow ACL (CIDRs)",
"allowACLExtra": "Who is allowed to ask for NTP time.",
"makestepSecs": "makestep secs",
"makestepSecsExtra": "Allow step (vs. slew) when offset > N seconds.",
"makestepLimit": "makestep limit",
"rtcsync": "Sync RTC with system time",
"rtcsyncExtra": "Keep hardware clock in sync every 11 min — after reboot time is roughly correct.",
"leapsectz": "Leap-sec TZ",
"leapsectzExtra": "Optional, e.g. 'right/UTC' for leap-sec via tzdata."
}
},
"dns": {
"title": "DNS (Unbound)",
"intro": "Unbound resolver on :53. Local zones (authoritative from DNS records) and forward zones (stub-zone to remote resolvers). Default forwarders catch everything else.",
"tabs": { "zones": "Zones", "settings": "Resolver settings" },
"zone": {
"name": "Zone name",
"nameExtra": "FQDN without leading/trailing dot — e.g. internal.netcell-it.de",
"type": "Type",
"typeLocal": "local — authoritative (records here)",
"typeForward": "forward — stub-zone to remote resolver",
"forwardTo": "Upstream resolvers",
"forwardToExtra": "Comma-separated IP list — e.g. '10.0.0.53, 8.8.8.8'",
"description": "Description",
"records": "Records …",
"add": "Add zone",
"edit": "Edit zone",
"deleteConfirm": "Really delete zone {{name}} and all its records?",
"emptyTitle": "No DNS zones yet.",
"emptyDesc": "Unbound forwards everything to upstream resolvers by default. Add a zone to host internal FQDNs (internal.example.com) locally or set up an upstream stub for a foreign domain."
},
"record": {
"name": "Name",
"nameExtra": "Relative to zone (e.g. 'mailcow') or FQDN with trailing dot.",
"type": "Type",
"value": "Value",
"valueExtra": "RDATA in text form: A → IP, CNAME → FQDN, MX → 'priority host', TXT → 'string'.",
"ttl": "TTL (sec)",
"drawerTitle": "DNS records",
"add": "Add record",
"edit": "Edit record",
"deleteConfirm": "Really delete record {{name}}?",
"emptyTitle": "No records in this zone yet.",
"emptyDesc": "A/AAAA/CNAME/MX/TXT entries. Authoritative on local zones; on forward zones records here have no effect (upstream wins)."
},
"settings": {
"intro": "Global resolver settings. Saves reload Unbound automatically.",
"listenAddresses": "Listen addresses",
"listenAddressesPlaceholder": "Pick IPs (or type)",
"listenAddressesRequired": "At least one address required.",
"listenAddressesExtra": "Multi-select from kernel-discovered IPs. 127.0.0.1 + ::1 = local only; LAN iface IPs (e.g. 10.10.20.3) open the resolver to LAN clients. You can also type custom IPs (Enter).",
"listenPort": "Port",
"upstreamForwards": "Default forwarders",
"upstreamForwardsExtra": "Where everything not local goes. Default 1.1.1.1 + 9.9.9.9.",
"accessACL": "Access ACL (CIDRs)",
"accessACLExtra": "Who is allowed to use this resolver.",
"dnssec": "DNSSEC validation",
"qnameMin": "QName minimisation (privacy)",
"cacheMin": "Cache min-TTL",
"cacheMax": "Cache max-TTL"
}
},
"fwd": {
"title": "Forward proxy (Squid)",
"intro": "Squid-based forward proxy on :3128. ACLs are evaluated top-down by priority — first match wins. If no rule matches, the default permits only localnet (10/8, 172.16/12, 192.168/16).",
"helpTitle": "ACL ordering tip",
"helpBody": "Higher priority = evaluated first. Example: 'deny .badsite.com' (priority 200) before 'allow .com' (priority 100). Values can be lists (multiple lines), regex depending on acl_type.",
"name": "Name",
"nameExtra": "Squid-conformant identifier — lowercase + _, no spaces.",
"aclType": "Type",
"aclTypeExtra": "What Squid matches (source, domain, port, …).",
"value": "Value",
"valueExtra": "Format depends on type — IPs/CIDRs for src/dst, domain with leading dot for dstdomain (.example.com also matches sub.example.com), regex for *_regex types.",
"action": "Action",
"priority": "Priority",
"priorityExtra": "Higher = evaluated first.",
"comment": "Comment",
"add": "Add ACL",
"edit": "Edit ACL",
"deleteConfirm": "Really delete ACL {{name}}?",
"emptyTitle": "No forward-proxy ACLs yet.",
"emptyDesc": "Default with no ACLs: only localnet (10/8, 172.16/12, 192.168/16) is allowed out. Add an ACL to selectively allow or block specific domains/IPs/ports."
},
"common": {
"yes": "Yes",
"no": "No",
"save": "Save",
"cancel": "Cancel",
"loading": "Loading …",
"error": "Error",
"edit": "Edit",
"delete": "Delete",
"deleteConfirm": "Really delete?",
"search": "Search …",
"totalRows": "{{count}} rows",
"active": "Active",
"inactive": "Inactive",
"noData": "No data",
"actions": "Actions",
"add": "Add",
"download": "Download",
"copy": "Copy",
"copied": "Copied",
"close": "Close",
"refresh": "Refresh"
},
"license": {
"title": "License",
"status": "Status",
"product": "Product",
"key": "License key",
"noKey": "No key configured",
"validUntil": "Valid until",
"expired": "Expired",
"daysLeft": "{{days}} days left",
"lastVerifiedAt": "Last verified",
"verifiedBy": "Verified by",
"limits": "Limits",
"unlimited": "Unlimited",
"features": "Features",
"reverify": "Re-verify",
"reverified": "License re-verified successfully",
"enterKey": "Enter key",
"replaceKey": "Replace key",
"enterKeyHint": "Paste your license key from the self-service portal at license.netcell-it.com.",
"activate": "Activate",
"saved": "License saved and verified",
"savedButVerifyFailed": "Key saved but server-verify failed",
"clearKey": "Remove key",
"cleared": "License removed — system falls back to trial",
"confirmClear": "Really remove the license key?",
"confirmClearHint": "The system will fall back to trial-mode once the key is deleted.",
"lastVerifyFailed": "Last server verify failed",
"trialExpiring": "Trial expires in {{days}} day(s)",
"trialExpiringHint": "Activate a license before the trial period ends."
},
"licenseBanner": {
"expired": "License expired or invalid.",
"trialExpiring": "Trial expires in {{days}} day(s).",
"verifyFailed": "License verification failed",
"cta": "Activate now →",
"openPage": "Open license page →"
},
"routes": {
"liveTitle": "Live routes (kernel)",
"liveIntro": "Current state from `ip -j route show table all`. proto edgeguard marks routes managed by EdgeGuard — other sources (kernel/static/dhcp) are left untouched.",
"liveEmpty": "No routes in kernel.",
"managedTitle": "Managed routes",
"managedIntro": "Static routes EdgeGuard installs via `ip route … proto edgeguard` on boot. Changes here trigger `systemctl restart edgeguard-routes.service` immediately.",
"add": "Add route",
"addTitle": "Add static route",
"editTitle": "Edit static route",
"empty": "No managed routes yet.",
"emptyTitle": "No managed routes yet.",
"emptyDesc": "Static routes EdgeGuard installs on boot (e.g. to 10.0.5.0/24 via VPN gateway). Live routes above are read-only — what you add here persists.",
"confirmDelete": "Really delete route to {{dest}}?",
"refreshTooltip": "Reload live routes",
"destExtra": "CIDR — e.g. 10.0.5.0/24 or 0.0.0.0/0 for the default route.",
"gatewayExtra": "Optional. Empty = on-link via dev.",
"devExtra": "Output interface. Optional if gateway resolves; explicit is more stable.",
"metricExtra": "Lower wins. Default 100. Linux defaults: dhcp 1024, kernel-link 0.",
"tableExtra": "Routing table. main = default. Custom tables via /etc/iproute2/rt_tables.",
"col": {
"destination": "Destination",
"gateway": "Gateway",
"dev": "Interface",
"metric": "Metric",
"table": "Table",
"active": "Active",
"comment": "Comment",
"proto": "Protocol",
"scope": "Scope",
"src": "Source IP"
}
},
"alerts": {
"title": "Health alerts",
"intro": "Notification channels for critical events. Webhook (Slack/Discord/Teams/generic-HTTP) or email (SMTP). Triggers: cert.expiring (<14 d), cert.renew_failed, backup.failed, license.invalid.",
"scopeTitle": "What triggers alerts?",
"scopeDesc": "cert.expiring — TLS cert <14 days remaining (12 h dedupe). cert.renew_failed — ACME renewer cycle had failures. backup.failed — scheduled backup couldn't run. license.invalid — License server returns valid=false. More triggers coming (backend-down, disk usage).",
"tabs": { "channels": "Channels", "events": "History" },
"add": "Add channel",
"addTitle": "Add notification channel",
"editTitle": "Edit channel",
"test": "Test alert",
"testDone": "Test sent — {{ok}}/{{total}} channels OK",
"emptyChannels": "No channels. Add a webhook or an email.",
"emptyEvents": "No alerts yet — triggers haven't fired any events.",
"emptyChannelsTitle": "No alert channels yet.",
"emptyChannelsDesc": "Without channels, fired events are only written to the database (Events tab) — nobody is notified. Add a webhook (Mattermost/Slack/Discord/custom) or an SMTP email.",
"emptyEventsTitle": "No alert events yet.",
"emptyEventsDesc": "Triggers (cert expiry, backup failure, cluster drift, license invalid, etc.) haven't fired any events yet. When they do, they land here and get delivered to the configured channels.",
"noChannels": "no active channel",
"confirmDelete": "Really delete channel {{name}}?",
"col": {
"name": "Name",
"kind": "Kind",
"target": "Target",
"targetWebhook": "Webhook URL",
"targetEmail": "Recipient email",
"active": "Active",
"time": "Time",
"severity": "Severity",
"subject": "Subject",
"delivered": "Delivered"
}
},
"remotes": {
"scopeTitle": "Off-site backup targets",
"scopeDesc": "After every successful local backup, the tar.gz is uploaded to all active targets. S3 endpoints (AWS, MinIO, Backblaze B2, Cloudflare R2, Hetzner Object Storage) and SFTP/SSH. Protects against box loss.",
"add": "Add target",
"addTitle": "Add off-site target",
"editTitle": "Edit off-site target",
"empty": "No off-site targets. Local backups do NOT protect against disk loss or full box failure.",
"test": "Test",
"testOk": "Test OK — connect, upload + cleanup all worked.",
"testFailed": "Test failed",
"confirmDelete": "Really delete target {{name}}?",
"targetExtra": "Free-text label for the overview. Use bucket/path hint (s3://my-bucket or sftp://backup@host).",
"col": {
"name": "Name",
"kind": "Kind",
"target": "Target",
"lastUpload": "Last upload",
"active": "Active"
}
},
"diag": {
"title": "Diagnostics",
"intro": "Operator tools straight from the UI: ping, traceroute, DNS, HTTP probe, TCP connect. All calls run authenticated on this box (not in the browser).",
"run": "Run",
"runFromBoxTitle": "Tools run on the EdgeGuard box",
"runFromBoxDesc": "These calls use the EdgeGuard's outbound connectivity — not your laptop's. If ping/curl fails here, the box's network layer is the issue (firewall rules, default route, DNS).",
"ping": { "intro": "ICMP echo-request × 4, 2 s timeout per packet. Measures loss + RTT." },
"trace": { "intro": "Hop-by-hop path with UDP probes (max 20 hops, no reverse-DNS)." },
"dig": { "intro": "DNS lookup via unbound (box resolver). Shows answer + AUTHORITY/ADDITIONAL section." },
"curl": { "intro": "HTTPS probe with -IsSv: TLS handshake details + headers. Does NOT follow redirects." },
"tcp": { "intro": "Pure TCP connect (no I/O). Ideal to verify a backend port is reachable." },
"securityTitle": "Security note",
"securityDesc": "Endpoints are behind admin auth. Targets are validated against a strict allow-list (no shell metachars). curl is restricted to http(s) — no file:// / smb:// / data://."
},
"backups": {
"title": "Backups",
"intro": "Snapshots of the PostgreSQL database + /var/lib/edgeguard (setup, license, JWT, ACME account). Daily auto job + manual trigger.",
"scopeTitle": "What is backed up?",
"scopeDesc": "DB dump (pg_dump --clean), setup.json, license_key, license.cache, .jwt_fingerprint, acme-account/. Generated configs (haproxy.cfg, nft, …) are reproducible from the DB and are NOT included.",
"tabs": { "history": "Backups", "remotes": "Off-site targets" },
"statLastSuccess": "Last successful backup",
"statTotal": "Successful backups",
"statSize": "Total size",
"statFails24h": "Failures (24 h)",
"statHoursAgo": "{{n}} h ago",
"statDaysAgo": "{{n}} days ago",
"runNow": "Run backup now",
"created": "Backup created: {{file}}",
"failed": "Backup failed",
"deleted": "Backup deleted",
"download": "Download",
"restore": "Restore",
"restoreOk": "Start restore",
"restoreDone": "Restore complete — page reloading.",
"restoreFailed": "Restore failed",
"restoreRunning": "Restore in progress …",
"restoreHint": "edgeguard-api will restart; the UI reloads after the restart.",
"confirmRestoreTitle": "Restore this backup?",
"confirmRestoreDesc": "Database content will be replaced with {{file}} + state files. edgeguard-api + scheduler will restart. Changes made since the backup are lost.",
"step": {
"extract": "Extract tar",
"psql": "DB restore (psql)",
"render": "Re-render configs",
"restart": "Restart services"
},
"downloadTooltip": "Download tar.gz",
"refreshTooltip": "Reload list",
"confirmDelete": "Really delete backup {{file}}?",
"empty": "No backups yet. Click “Run backup now” or wait for the next scheduled tick.",
"failedTag": "FAILED",
"col": {
"time": "Time",
"file": "File",
"kind": "Kind",
"status": "Status",
"size": "Size",
"duration": "Duration"
}
},
"logs": {
"title": "System logs",
"intro": "Aggregated view across all service journals + audit_log. Multi-source selection, level filter, free-text search, time range, auto-refresh (5s).",
"autoOn": "Auto",
"autoOff": "Manual",
"refresh": "Refresh",
"refreshTooltip": "Reload once",
"export": "CSV",
"exportTooltip": "Export current table as CSV",
"exportEmpty": "No entries to export",
"found": "{{n}} entries",
"limit": "limit",
"empty": "No entries found. Change source selection or widen the time range.",
"col": {
"time": "Time",
"source": "Source",
"level": "Level",
"message": "Message"
},
"filter": {
"sources": "Select sources (all if empty)",
"levels": "Filter levels",
"grep": "Full-text search",
"reset": "Reset filters"
}
},
"fwlog": {
"title": "Firewall log (live)",
"intro": "Packets matching nft rules with the log flag enabled flow via NFLOG → ulogd2 → JSONL into this view. WebSocket stream shows live events; ring buffer (1000) keeps recent hits across reconnects.",
"start": "Start live log",
"stop": "Stop",
"notStartedTitle": "Live log is off",
"notStartedDesc": "Paused by default — click to connect and see events flowing in.",
"live": "Live",
"disconnected": "connecting …",
"pause": "Pause",
"resume": "Resume",
"queued": "queued",
"clear": "Clear",
"clearTooltip": "Clear the table (server ring buffer is unaffected)",
"export": "CSV",
"exportTooltip": "Export current table as CSV",
"exportEmpty": "No events to export",
"connError": "WebSocket error — retrying",
"empty": "No events yet. Enable the log toggle on a firewall rule — matches will show up here.",
"connecting": "Connecting …",
"col": {
"time": "Time",
"action": "Action",
"rule": "Rule",
"proto": "Proto",
"src": "Source",
"dst": "Destination",
"iface": "Interface",
"size": "Size"
},
"filter": {
"action": "Filter action",
"proto": "Filter proto",
"src": "Source IP",
"dst": "Dest IP",
"rule": "Rule ID"
}
},
"audit": {
"title": "Audit log",
"intro": "Who changed what, when. Every mutation through the API (domain create, backend disable, cert issue, …) is recorded here.",
"filter": {
"actor": "Actor",
"action": "Action",
"subject": "Subject",
"range": "Time range",
"search": "Search",
"reset": "Reset"
},
"col": {
"time": "Time",
"actor": "Actor",
"action": "Action",
"subject": "Subject",
"detail": "Details"
},
"detailShow": "Show details",
"empty": {
"title": "No matches",
"desc": "No entries matched these filters. Adjust or reset them."
},
"page": {
"prev": "Prev",
"next": "Next",
"showing": "Row {{from}}{{to}}"
}
}
}