{ "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", "users": "Users", "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?", "namePlaceholder": "e.g. Allow SSH from office", "cidr": "CIDR", "actions": { "accept": "ACCEPT", "drop": "DROP", "reject": "REJECT" }, "kinds": { "any": "Any", "object": "Object", "group": "Group", "cidr": "CIDR" }, "duplicate": "Duplicate rule", "duplicated": "Rule duplicated (disabled, priority +1).", "cidrInvalid": "Enter a valid CIDR (e.g. 10.0.0.0/24)", "moveUp": "Move up (higher priority)", "moveDown": "Move down (lower priority)", "emptyTitle": "No custom firewall rules yet.", "emptyDesc": "The system rules above keep SSH (rate-limited), HTTPS :443 and the mgmt UI :3443 open (anti-lockout). Add custom rules for app-specific inbound ports or cross-zone forwards.", "logEnabled": "Logging active — matched packets are written to the firewall log", "ruleDisabled": "Rule disabled" }, "kpi": { "policyRules": "Policy Rules", "active": "active", "disabled": "disabled", "natRules": "NAT Rules", "natHint": "DNAT / SNAT / Masquerade", "zones": "Zones", "zonesHint": "Network segments", "defaultPolicy": "Default Policy", "nftActive": "nftables active", "nftInactive": "nftables inactive" }, "filter": { "search": "Search name / comment…", "allActions": "All actions", "allZones": "All zones", "noResults": "No rules match the filter", "noResultsHint": "Clear the filter to see all rules." }, "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)", "portRangeError": "End port must be ≥ start port", "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?", "namePlaceholder": "e.g. Forward HTTP to web backend", "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?", "viewerBadge": "Read-only", "viewerHint": "Your account has viewer role — all changes are blocked. Contact an admin to change your role." }, "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.", "back": "Back", "modeSelect": "How would you like to set up this box?", "modeStandalone": "Fresh installation", "modeStandaloneDesc": "Set up as a standalone EdgeGuard or the first node of a new cluster. Creates an admin account on this box.", "modeNode": "Join existing cluster", "modeNodeDesc": "Add this box as a second cluster node. Admin credentials are replicated from the primary automatically — no separate account needed.", "nodeTitle": "Cluster Node Setup", "nodeIntro": "Enter the FQDN of this node. No admin account is created here — login credentials come from the primary via database replication after you run the join command.", "nodePreflightTitle": "Before you continue", "nodePreflightDesc": "Make sure EdgeGuard is already running on your primary node and you can generate a join token there (Cluster page). You will need it in the next step.", "nodeSubmit": "Configure as cluster node", "nodeSuccessTitle": "Successfully joined!", "nodeJoinTitle": "Next: join the cluster", "nodeJoinDesc": "This box ({{fqdn}}) is configured. Now run the join command below to connect it to the primary.", "nodeStep1Title": "Generate a join token on the primary", "nodeStep1Desc": "Open the Cluster page on your primary EdgeGuard → Add node → Generate join token. Copy the token.", "nodeStep2Title": "Run the join command on this box", "nodeStep2Desc": "Replace with your primary's FQDN and with the token from step 1:", "nodeStep3Title": "Restart the API", "nodeStep3Desc": "After the join command completes, restart the API to pick up the replicated configuration:", "nodeLoginNote": "Login uses the admin credentials from the primary. This will work once database replication is established (the join command sets this up).", "primaryFqdn": "Primary FQDN", "primaryFqdnHint": "FQDN of the existing primary EdgeGuard node (e.g. eg1.example.com).", "joinToken": "Join token", "joinTokenHint": "Generated on the primary: Cluster page → Add node → Generate join token.", "joinInsecure": "Skip TLS verification (use if the primary has a self-signed certificate)", "nodeSuccessDesc": "Cluster certs have been written. One last step:", "nodeRestartTitle": "Restart required", "nodeRestartDesc": "Run the following command on this box to load the new cluster certificates:" }, "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.", "peersOnline": "{{online}} / {{total}} peers online" }, "firewallCard": { "title": "Firewall", "zones": "Zones", "activeRules": "{{rules}} active rules · {{nat}} NAT" }, "sslCard": { "title": "SSL certificates", "total": "Managed certificates", "certExpired": "{{count}} certificate(s) expired — renew immediately", "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" }, "downBackendsAlert": "{{count}} backend(s) completely down — no server UP", "maintenanceAlert": "{{count}} domain(s) in maintenance mode", "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).", "frontends": "Listeners" }, "resCard": { "load": "Load", "memory": "Memory", "disk": "Disk /", "free": "free", "conntrack": "Conntrack", "uptime": "Uptime" }, "licenseOk": "License OK", "licenseTrial": "Trial", "licenseTrialDays": "Trial · {{days}}d" }, "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. Open the domain and click \"Issue certificate\".", "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.", "disableH3": "Disable HTTP/3 (QUIC)", "disableH3Hint": "Suppresses the Alt-Svc response header for this domain. Browsers won't receive an h3/QUIC upgrade hint and stay on h2/http1.1. Useful when clients report issues with QUIC connections.", "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}}?", "maintenanceTag": "Maintenance", "rateLimitTag": "Rate limited", "routingRulesTitle": "Routing rules — {{name}}", "routingRulesHint": "Path-prefix → backend mappings for this domain. Lowest priority number wins; unmatched requests go to the primary backend.", "routingRulesEmpty": "No routing rules — all requests go to the primary backend.", "backendUp": "backend UP", "backendDown": "backend DOWN" }, "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", "attachedToOther": "currently: #{{id}}", "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.", "forceHttp1": "Force HTTP/1.1", "forceHttp1Hint": "Disables HTTP/2 (h2) on the backend connection — HAProxy negotiates HTTP/1.1 only. Required for backends that don't support h2 (e.g. older nginx configs without the h2 module, legacy apps).", "servers": "Servers", "noServers": "no server", "noServersWarning": "One or more active backends have no servers configured.", "noServersWarningDesc": "HAProxy will return 503 for any traffic routed to these backends. Open the backend (orange tag) and add at least one 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.", "deleteInUse": "Used by {{n}} routing rule(s) — remove those first", "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", "addressInvalid": "Enter a valid IPv4/IPv6 address or hostname", "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}}?", "live": "Live" } }, "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", "pathPrefixHint": "Must start with /. HAProxy uses prefix matching.", "pathPrefixInvalid": "Path must start with / and contain only valid URL characters", "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. Follow the steps below to join a second node.", "addNodeTitle": "Add second node to cluster", "step1Title": "1 — Install EdgeGuard on the new host", "step1Desc": "Run as root on the new host (Debian 13, amd64):", "step2Title": "2 — Generate a join token here (on this primary node)", "step2Desc": "Enter the FQDN of the new node first. This pre-registers it and opens port 8443 for the join.", "step3Title": "3 — Run on the new node", "step3NoToken": "Click \"Generate join token\" above — the full command will appear here.", "step4Title": "4 — Restart edgeguard-api on the new node", "step4Desc": "After the setup wizard completes on the new node, restart edgeguard-api there to load the cluster certificates:", "apiUnreachable": "Cluster API not reachable", "tokenLabel": "Token", "caFingerprintLabel": "CA fingerprint", "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", "pgRole": "PG 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" }, "pgRole": { "standalone": "standalone", "primary": "primary", "standby": "standby" }, "vipCard": { "title": "High Availability (VIP / Keepalived)", "vipAddress": "VIP address", "vipAddressHelp": "Virtual IP address that moves between nodes (e.g. 89.163.205.10)", "vipInterface": "Network interface", "vipInterfaceHelp": "Interface to bind the VIP on (e.g. eth0)", "vipAuthPass": "VRRP auth password", "vipAuthPassHelp": "Max. 8 characters — Keepalived limit. Same on all nodes.", "vrrpRouterId": "VRRP router ID", "vrrpRouterIdHelp": "Must be unique in the subnet (1–255). Default: 51.", "saveBtn": "Save & reconfigure Keepalived", "saved": "VIP settings saved", "saveFailed": "Failed to save", "hintTitle": "Next steps after saving", "hintPrimary": "On primary: edgeguard-ctl cluster-init-replication", "hintStandby": "On secondary: edgeguard-ctl cluster-setup-standby ", "hintKeepalived": "Keepalived on both nodes: sudo systemctl enable --now keepalived", "hintFailover": "On failover: edgeguard-ctl promote (on the secondary node)" }, "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", "certDaysRemaining": "{{n}} days", "certExpiredDaysAgo": "expired {{n}} d ago", "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:", "status": { "online": "online", "offline": "offline", "joining": "joining", "leaving": "leaving", "unknown": "unknown" }, "step3SetupDesc": "Open the setup wizard on the new node (https://:3443/setup), choose \"Join existing cluster\", enter the primary FQDN ({{primaryFqdn}}) and paste the token above.", "generateNewToken": "Generate new token", "setupWizardHint": "Open the setup wizard on the new node", "newNodeFqdnLabel": "New node FQDN" }, "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", "hostname": "Hostname", "os": "OS", "kernel": "Kernel", "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 all configs", "renderConfigsOk": "All configs re-rendered and reloaded.", "renderConfigsFailed": "Config render failed", "renderConfigsPartial": "Partial render — some services failed: {{failed}}", "backupNowBtn": "Backup now", "serviceRestartCardTitle": "Restart services", "serviceRestartBtn": "Restart", "serviceRestartConfirmTitle": "Restart {{service}}?", "serviceRestartConfirmDesc": "The service will be briefly unavailable while restarting.", "serviceRestartConfirmOk": "Yes, 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", "ipv6CardTitle": "IPv6", "ipv6On": "Enabled — HAProxy binds on [::]:80, [::]:443 and [::]:3443 in addition to IPv4.", "ipv6Off": "Disabled — HAProxy listens on IPv4 only.", "ipv6Hint": "Requires IPv6 to be configured on this server's network interfaces. HAProxy reloads automatically after changing this setting.", "ipv6Toggled": "IPv6 setting saved.", "ipv6Failed": "IPv6 toggle failed", "configPreviewCardTitle": "Config preview", "configPreviewBtn": "Load preview", "configPreviewHint": "Renders the selected service config from the current DB state. Read-only — nothing is written to disk or reloaded.", "configCopied": "Config copied to clipboard", "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", "rollingUpdate": "Rolling Update (Cluster)", "rollingConfirmTitle": "Start Rolling Update?", "rollingConfirmDesc": "The secondary node ({{secondary}}) is updated first, then this primary. No downtime for proxied traffic during the secondary phase.", "rollingRunning": "Rolling update in progress…", "rollingStepSecondary": "Updating secondary ({{fqdn}})", "rollingStepWaiting": "Waiting for secondary restart", "rollingStepPrimary": "Updating primary (this node)" }, "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", "qr": "QR", "conf": ".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" }, "import": { "btn": "Import .conf", "modalTitle": "Import WireGuard configs from /etc/wireguard", "hint": "Select which .conf files to import. Already-imported interfaces are greyed out.", "selectAll": "Select all new", "alreadyIn": "already imported", "noFiles": "No .conf files found in /etc/wireguard.", "okBtn": "Import selected", "ok": "Imported {{ifaces}} interface(s), {{peers}} peer(s)", "failed": "Import failed" } }, "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", "peers": "Peer status" }, "sourcesCard": { "title": "Live peer status (chronyc sources)", "name": "Name / IP", "stratum": "Stratum", "poll": "Poll", "reach": "Reach", "lastRx": "Last rx", "sample": "Offset ± error", "reachHint": "8-bit shift register (octal) — 377 = all 8 recent polls replied. 0 = unreachable.", "empty": "No peer data — is chrony running?", "state_synced": "Synced", "state_combined": "Combined", "state_not_combined": "Not combined", "state_unreachable": "Unreachable", "state_error": "Error", "state_variable": "Variable" }, "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.", "freqPpm": "Freq. error", "freqPpmHint": "Frequency error of the local clock vs. reference. Values > ±100 ppm indicate a drifting clock or hardware issue.", "rmsOffset": "RMS offset", "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.", "addressPlaceholder": "0.de.pool.ntp.org or 10.0.0.10", "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.", "forceSyncBtn": "Force clock step", "forceSyncTooltip": "Immediately step the system clock to the NTP reference (chronyc makestep). Use after VM migration or long outage.", "forceSyncOk": "Clock stepped successfully", "forceSyncFailed": "Force sync failed" } }, "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", "stats": "Resolver stats" }, "statsCard": { "title": "Resolver statistics (unbound-control stats_noreset)", "totalQueries": "Total queries", "cacheHits": "Cache hits", "cacheMiss": "Cache miss", "cacheHitPct": "Cache hit rate", "recursiveReplies": "Recursive replies", "prefetch": "Prefetch", "rateLimited": "Rate-limited", "rrsetCacheBytes": "RRset cache", "msgCacheBytes": "Msg cache", "tcpUsage": "TCP connections", "unwanted": "Unwanted queries", "empty": "No stats available — is unbound running?", "sinceRestart": "Since last restart" }, "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", "allIPv4": "all IPv4 interfaces", "allIPv6": "all IPv6 interfaces", "loopback": "Loopback", "flushCacheBtn": "Flush DNS cache", "flushCacheTooltip": "Discard all cached records (unbound-control flush_zone .). Use after DNS changes have propagated.", "flushCacheOk": "DNS cache flushed", "flushCacheFailed": "Flush failed", "upstreamForwardsInvalid": "Each forwarder must be a valid IP (e.g. 1.1.1.1 or 9.9.9.9)", "accessACLInvalid": "Each entry must be a valid IP or CIDR (e.g. 10.0.0.0/8 or 192.168.1.0/24)", "cacheTTLError": "Cache max-TTL must be ≥ cache min-TTL" } }, "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", "valuePlaceholder": ".example.com or 10.10.20.0/24 or 443", "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.", "statsCard": { "title": "Squid cache statistics", "clientRequests": "Client requests", "cacheHits": "Cache hits", "cacheHitPct": "Hit rate", "clientErrors": "Client errors", "bytesIn": "Bytes received", "bytesOut": "Bytes sent", "serverRequests": "Backend requests", "serverErrors": "Backend errors", "sinceRestart": "Since last restart" }, "actions": { "allow": "allow — permit access", "deny": "deny — block access" }, "aclTypes": { "src": "src — source IP/CIDR", "dst": "dst — destination IP/CIDR", "dstdomain": "dstdomain — destination domain (exact)", "srcdomain": "srcdomain — source domain (rDNS)", "port": "port — destination port", "proto": "proto — http/https/ftp/...", "method": "method — GET/POST/CONNECT/...", "time": "time — weekday/time range", "url_regex": "url_regex — full URL regex", "urlpath_regex": "urlpath_regex — URL path regex", "dstdom_regex": "dstdom_regex — destination domain regex", "srcdom_regex": "srcdom_regex — source domain regex", "browser": "browser — User-Agent regex" } }, "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", "retry": "Retry", "close": "Close", "refresh": "Refresh", "up": "UP", "down": "DOWN", "relTime": { "Xs": "{{n}}s ago", "Xm": "{{n}}m ago", "Xh": "{{n}}h ago", "Xd": "{{n}}d ago", "never": "never" } }, "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", "serverRejected": "License rejected by server", "trialExpiring": "Trial expires in {{days}} day(s)", "trialExpiringHint": "Activate a license before the trial period ends.", "tagActive": "Active", "tagTrial": "Trial", "tagExpired": "Expired", "tagInvalid": "Invalid" }, "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. backend.down — all servers in a backend pool are DOWN (2 min check, 12 h dedupe). disk.full — root filesystem ≥80% warning, ≥90% critical (hourly check, 12 h dedupe). mem.high — RAM usage ≥85% warning, ≥95% critical (5 min check, 12 h dedupe). conntrack.high — conntrack table ≥80% warning, ≥90% critical; at 100% all new connections are silently dropped (2 min check, 12 h dedupe). ntp.unsync — chrony has no synchronized time source; clock drift causes TLS and JWT failures (10 min check, 12 h dedupe). wg.tunnel.down — WireGuard client tunnel has no handshake for >5 min; remote traffic is silently dropped (5 min check, 12 h dedupe).", "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}}?", "kindWebhook": "Webhook (Slack/Discord/Teams/HTTP endpoint)", "kindEmail": "Email (SMTP)", "smtp": { "host": "SMTP host", "port": "SMTP port", "from": "From", "username": "Username", "password": "Password", "starttls": "STARTTLS" }, "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).", "namePlaceholder": "e.g. MinIO offsite / Hetzner Storage Box", "kindS3": "S3 (AWS / MinIO / R2 / B2 / Hetzner Object)", "kindSftp": "SFTP (SSH)", "s3": { "endpoint": "Endpoint", "endpointExtra": "s3.amazonaws.com / minio.example.com:9000 / fsn1.your-objectstorage.com", "region": "Region", "regionPlaceholder": "eu-central-1 / auto (R2)", "bucket": "Bucket", "accessKey": "Access Key", "secretKey": "Secret Key", "pathPrefix": "Path Prefix", "pathPrefixExtra": "e.g. edgeguard/utm-1 — prepended to every filename", "useSsl": "HTTPS (use_ssl)" }, "sftp": { "host": "Host", "port": "Port", "username": "Username", "password": "Password", "passwordExtra": "Either password OR private key.", "privateKey": "Private Key (OpenSSH, base64)", "privateKeyPlaceholder": "base64-encoded OpenSSH private key", "remoteDir": "Remote Dir", "remoteDirExtra": "e.g. /backups/edgeguard", "hostKeyFp": "Host Key Fingerprint", "hostKeyFpExtra": "Optional. SHA256:... — leave empty for TOFU (insecure against MitM)" }, "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://.", "noOutput": "(no output)" }, "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", "okTag": "OK", "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" } }, "users": { "title": "User Management", "intro": "Manage login accounts. Every account listed here can authenticate via the login page.", "addUser": "Add User", "editUser": "Edit User", "email": "Email", "role": "Role", "active": "Active", "lastLogin": "Last Login", "created": "Created", "setPassword": "Set Password", "setPasswordTitle": "Set New Password", "newPassword": "New Password", "newPasswordHint": "Minimum 12 characters.", "roleAdmin": "Admin", "roleViewer": "Viewer (read-only)", "deleteConfirm": "Delete user {{email}}? This cannot be undone.", "emptyTitle": "No users yet", "emptyDesc": "Add the first additional user above.", "errorEmailTaken": "This email address is already in use.", "cannotDeleteSelf": "You cannot delete your own account.", "you": "You", "never": "Never" }, "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}}" } } }