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>
1165 lines
56 KiB
JSON
1165 lines
56 KiB
JSON
{
|
||
"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": "0–256. 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}}"
|
||
}
|
||
}
|
||
}
|