mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-08-25 12:27:13 +00:00
effcccceac
* feat(amneziawg): add native AmneziaWG protocol backend AmneziaWG (WireGuard plus DPI-resistant obfuscation) needs no Docker here — it runs as a genuine kernel interface via awg-quick/awg, managed the same way internal/mtproto manages mtg: one Inbound row is one desired Instance, and a Manager reconciles running interfaces toward the database every 10s (internal/web/job/amneziawg_job.go) plus immediately after a client edit (applyLocalAmneziaWG). Clients reuse model.Client verbatim (the same PrivateKey/PublicKey/ PreSharedKey/AllowedIPs fields WireGuard already uses), so bulk operations, the QR/share-link modal and subscriptions come from the shared inbound infrastructure instead of a parallel implementation. internal/amneziawg owns the obfuscation param generator/validator (ported from coinman-dev/3ax-ui, upgraded to AmneziaWG 2.0's S3/S4 padding and I1 signature packet) and the exec wrapper around awg-quick/awg, with fingerprint-based reconcile (noop / reload-via- syncconf / full restart) mirroring mtproto.Manager so a same-protocol edit doesn't force an unnecessary interface bounce that would drop every peer's connection. Frontend and install.sh's DKMS/awg-tools setup are tracked separately; this is backend-only. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> * feat(amneziawg): add frontend support and fix a Go->Zod generator gap Wires the amneziawg protocol through the panel UI the same way every other protocol is registered: a Zod settings schema (nested {server, clients}, matching the Go JSON exactly), the protocol enum, the inbound-form's per-protocol fields component and its tab-visibility allowlist, the default-settings factory, the client schema dispatcher, and the sniffing-capability exclusion (no Xray inbound exists for amneziawg, same as mtproto). Client key/allowedIPs fields are reused rather than duplicated: since AmneziaWG clients are wire-identical to WireGuard clients (same model.Client fields), ClientFormModal renders one shared field block for both, switching only the visible label by which protocol is active. The private-key input also gets a live public-key sync via a new useEffect, because unlike WireGuard's Xray-native inbound (which re-derives its public key at runtime and never stores one), AmneziaWG's server.publicKey is a real persisted field the Go backend reads directly — free-typing a new private key without this would silently save a mismatched keypair. Adds a downloadable per-client .conf (amneziawgConfig.ts, mirroring wireguardConfig.ts) with the obfuscation lines, and an InboundOption.AwgServer field on the Go side so the config builder gets the full server block in one round trip. Along the way, running tools/openapigen surfaced a real bug: it doesn't flatten anonymously-embedded Go structs the way encoding/json does, so ServerSettings embedding Obfuscation20 produced a Zod schema with a nested `obfuscation20` key that never matches the real wire JSON. Fixed by un-embedding (flat fields + an accessor method) and registering internal/amneziawg in the generator's own package list, which had been silently emitting a dangling schema reference. English and Russian translations are complete; the other 10 locale files still fall back to English for the new keys. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> * feat(amneziawg): complete frontend parity for the Inbounds list page The Clients page (form, CRUD, QR/config) already worked from the prior commit; this closes the remaining gap on the Inbounds side and in a couple of protocol allowlists that a plain search for existing wireguard/mtproto handling turned up. lib/xray/inbound-link.ts gets amneziawg-specific link/config builders (genAmneziaWGLink/genAmneziaWGConfig, plus the *s fan-out variants) mirroring the wireguard ones — AmneziaWG has no legacy peers-array to fall back to, so these read settings.clients directly and add the obfuscation lines every client must share with the server. Wired into genInboundLinks generically, and into three consumers that call the wireguard builders directly rather than through that dispatcher: QrCodeModal, InboundInfoModal, and InboundsPage's bulk export. ClientInfoModal, ClientBulkAddModal, and the bulk attach/detach modals each had their own protocol allowlist that needed amneziawg added alongside wireguard/mtproto. Two real gaps surfaced by grepping every remaining 'wireguard' / Protocols.WIREGUARD hit in frontend/src rather than trusting the checklist was exhaustive: - useInbounds.ts's TRACKED_PROTOCOLS gates the deactive/depleted/ expiring/online client counts shown per inbound on the list page; without amneziawg those counts would silently read zero. - inbound-tag.ts is an explicit client-side mirror of the Go backend's port_conflict.go (the file says so itself: "Keep in sync"). It still only special-cased wireguard for UDP, so an amneziawg inbound would have fallen through to the TCP default and disagreed with the backend's own port-conflict math. Also finishes translating the AmneziaWG UI strings into the 11 locale files that were still falling back to English (ar-EG, es-ES, fa-IR, id-ID, ja-JP, pt-BR, tr-TR, uk-UA, vi-VN, zh-CN, zh-TW), matching en-US/ru-RU key-for-key (26 new keys, verified by count in every file). Not run anywhere: npm run typecheck / build. This machine has neither Node nor npm, so nothing here has compiled — reviewed by hand plus brace/paren balance checks and cross-referencing the generated Zod/TS types. Treat this as needing a real typecheck before shipping. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> * docs(install): note that AmneziaWG kernel module install is still manual Tracked separately (not yet ported into this script) — see coinman-dev/3ax-ui's install_amneziawg for the reference approach (ppa:amnezia/ppa). Also serves as a real, path-filter-matching change to get the previous empty commit's CI trigger to actually fire — release.yml's push trigger is paths-scoped and an empty commit changes no files, so it never matched. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> * feat(amneziawg): add a button to randomize obfuscation parameters Mirrors the existing key-regenerate button next to the private key field. Client-side randomization matches the ranges/constraints of GenerateObfuscation20's "default" preset (internal/amneziawg/params.go) closely enough for a form suggestion — the user can still hand-edit any field afterward. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> * feat(install): auto-install the AmneziaWG DKMS module + amneziawg-tools Ports install_amneziawg from coinman-dev/3ax-ui's install.sh, adapted to this script's broader distro coverage and NONINTERACTIVE convention: - Ubuntu/Debian/Armbian: ppa:amnezia/ppa (primary, tested path), with a reachability pre-check for the Launchpad PPA host — often blocked by hosting providers, especially Russian VPS — so a flaky network skips the feature instead of hanging apt through several retries. - Fedora/RHEL-family, Arch/Manjaro/Parch: best-effort fallback to plain wireguard-tools (+ AUR amneziawg-dkms via yay/paru when available), with a manual-install pointer. - Everything else: manual-install pointer only. Also installs ndppd and persists IPv4/IPv6 forwarding (for the future IPv6/NDP phase, not yet wired into the panel) and adds a Secure Boot warning at the end of the run, since a DKMS-built module is unsigned and won't load while it's enabled — a common trap on cloud VPS images. Never fatal: the panel installs and runs fine either way, an AmneziaWG inbound just won't bring up its tunnel until the module is present. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> * fix(amneziawg): resolve all 3 real CI failures (typecheck/lint/codegen) Found by checking the fork's Actions tab after the last two pushes — the release build passed (it doesn't run these checks) but the separate CI workflow caught three real issues: - golangci-lint (noctx): every internal/amneziawg/manager.go exec.Command call is now exec.CommandContext with a 30s timeout, so a hung awg-quick/awg invocation can't block the reconcile job indefinitely (mirrors internal/mtproto/process.go's own CommandContext usage). - tsc --noEmit: frontend/src/schemas/client.ts's hand-maintained InboundOptionSchema (used by the useClients hook, separate from the auto-generated one in generated/) never got an awgServer field added when the AmneziaWG frontend work was done — every read of inbound.awgServer.* in amneziawgConfig.ts was typing as {}. Added AwgServerOptionSchema, nested (not flattened like wg*) to match what amneziawgConfig.ts already expects. Also guarded server.publicKey in inbound-link.ts's genAmneziaWGLink against the schema's optional type. - codegen staleness: frontend/public/openapi.json is produced by a Node script (gen:api) this machine can't run; hand-applied the exact diff the CI failure log already showed (amneziawg protocol enum entry, ServerSettings schema, InboundOption.awgServer, one example payload), verified as valid JSON. Also confirmed independently by this run: install_amneziawg (previous commit) installed and loaded the DKMS module successfully on both amd64 and arm64 CI runners. The two "Deploy Smoke Tests" failures are unrelated to this change — this fork has only ever published the dev-latest pre-release, and GitHub's /releases/latest API deliberately excludes pre-releases, so the smoke test's no-argument install path (which resolves "latest") has nothing to find. Not a regression; needs an actual tagged release whenever that's wanted. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> * feat(amneziawg): Phase 2a — IPv6 support + NDP proxy Adds native dual-stack IPv6 to AmneziaWG inbounds, ported from coinman-dev/3ax-ui's approach: - ServerSettings gets ipv6Enabled/ipv6Subnet/ipv6ExternalInterface; Instance carries the server's own IPv6 address (first host of the subnet) alongside its IPv4 one. - defaultAmneziaWGClients allocates an IPv6 host address per client (second AllowedIPs entry) when the server has IPv6 enabled, reusing allocateWireguardAddress — which needed a real fix along the way: it always suffixed "/32" regardless of address family, which is wrong for an IPv6 host address (needs /128). Now family-aware. - generateServerConfig's PostUp/PostDown gains IPv6 forward-accept rules, proxy_ndp sysctl, and one `ip -6 neigh add/del proxy` entry per enabled peer with an IPv6 address — the lightweight per-client method, not the ndppd-daemon whole-subnet method (not worth the config-file-management complexity at this scale; ndppd itself is still installed by install.sh in case that changes later). - ValidateIPv6Subnet rejects a malformed subnet before save. - Frontend: ipv6Enabled/ipv6Subnet/ipv6ExternalInterface fields on the AmneziaWG inbound form, EN+RU translations, openapi.json/generated/* regenerated (the latter via `go run ./tools/openapigen`, pure Go). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> * fix(amneziawg): fill in IPv6 fields missed by the Phase 2a commit Two real gaps the CI caught (both new fields, both my miss): - inbound-defaults.ts's createDefaultAmneziawgInboundSettings() built a server object literal predating ipv6Enabled/ipv6Subnet/ ipv6ExternalInterface — AmneziawgServer's inferred type now requires them (zod .default() fields are non-optional post-parse), so this didn't typecheck at all. - openapi.json's ipv6Enabled property was missing the description the real generator attaches (the Go doc comment covering all three IPv6 fields is attached to the first one) — a one-line diff, but git diff --exit-code doesn't care how small. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> * feat(amneziawg): Phase 2b — per-client port-forwarding Admins can now set a per-client ForwardedPorts string (e.g. "80, 443, 8000-8100") that gets DNAT'd + FORWARD'd to that peer's tunnel address via iptables rules in PostUp/PostDown, ported and simplified from coinman-dev/3ax-ui's shared/portfwd. Two decisions worth flagging for future readers: - The iptables --comment tag on each rule is awg-fwd-<fnv32a(email)>, not the raw client email. Email is admin/API-supplied free text that ends up embedded in a shell-executed PostUp/PostDown line; a hash can never carry a shell metacharacter through where raw interpolation could. - The reconcile manager gained a third fingerprint (portFwdFP, next to the existing structural/peers ones). `awg syncconf` only touches the WireGuard peer table — it never re-applies PostUp/PostDown iptables rules — so a port-forward-only change has to force a full awg-quick down+up bounce, same as a structural change, rather than the lighter sync a plain peer add/remove can use. Also fixes a real pre-existing bug found while wiring up IPv6 client allocation in the previous commit's spirit: allocateWireguardAddress always suffixed "/32" regardless of address family, which produced invalid host bits for IPv6 (needs "/128"). ForwardedPorts flows through model.Client -> model.ClientRecord (gorm column wg_forwarded_ports, auto-migrated) -> ToRecord/ToClient/ MergeClientRecord, mirroring the awgServer field's earlier lesson that new fields need checking against a second, hand-maintained persistence-layer struct. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> * feat(amneziawg): route a client's traffic through Xray via the Routing page Every enabled AmneziaWG inbound gets its own Xray TPROXY bridge automatically, with no toggle to enable first: a loopback dokodemo-door inbound (sockopt.tproxy) tagged with the AmneziaWG inbound's own real tag, so it's already selectable in the existing Routing page's inbound-tag picker — the same trick the mtproto sidecar's own bridge already relies on (InboundService.GetInboundTags is a plain, protocol-blind SELECT over every inbound row's tag, no dedicated UI plumbing needed). internal/amneziawg's defaultPostUpDown TPROXYs every peer's traffic into that bridge unconditionally; the bridge's port is derived deterministically from the inbound's id (EgressPortForInbound) so the kernel-side reconcile loop and the Xray-config generator never need to negotiate a runtime value between them. injectAmneziawgEgress never generates a routing rule itself — whether a client's traffic goes anywhere beyond Xray's default routing is entirely up to whatever rules the admin adds through the existing Routing UI (pick the AmneziaWG inbound's tag as source, optionally a specific peer's IP via that page's own Source-IP field, and an outbound), exactly the same workflow as routing any other protocol. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> * fix(amneziawg): recover orphaned interfaces after an ungraceful exit Two gaps left an AmneziaWG interface stuck outside the manager's control after a crash (kill -9/OOM/panic skips StopAll): - ensureRestart's teardown was gated on the in-memory `exists` map, which is always empty on a fresh process, so a survived interface never got interfaceDown before interfaceUp tried `ip link add` against a name the kernel already had — failing forever and never populating m.ifaces, so traffic accounting silently stopped and the inbound could never be removed. Gate on isInterfaceUp instead, which checks real kernel state rather than this process's own bookkeeping. - An inbound deleted from the database entirely while the panel was down has no entry in `desired` ever again, so it never reaches the per-id cleanup loop in Reconcile (which only walks m.ifaces). Add a one-time sweepOrphansLocked scan of configDir, mirroring mtproto.Manager.sweepOrphansLocked, that tears down and removes any leftover interface/config not in the current desired set. Found by the automated review on MHSanaei/3x-ui#6105 (Finding 1). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> * i18n(amneziawg): backfill IPv6/obfuscation/port-forwarding keys in 11 locales Only en-US/ru-RU ever got these 9 keys as each AmneziaWG feature landed (the regenerate-obfuscation button, then Phase 2a's IPv6 fields, then Phase 2b's per-client ForwardedPorts) — the other 11 locale files were never backfilled, so i18next has been silently falling back to English for all of them since Phase 1. Cosmetic-only (never broke anything), but now closed for every shipped locale. * fix(amneziawg): resolve 7 Medium findings from the automated PR review Each is independently reproducible; fixed together since one review pass found all of them. - manager.go: the shared "ip rule add fwmark" policy route had no existence check, so it duplicated in "ip rule show" on every interface bounce (which hostRulesFingerprint forces on any client add/remove/ re-IP). Now checked via "ip rule list | grep -q ..." first. (Finding 2) - params.go: ExternalInterface, IPv6ExternalInterface, and subnetIp/ subnetCidr are interpolated unescaped into a shell-executed PostUp/ PostDown line, but only obfuscation and the IPv6 subnet were validated before save. Added ValidateInterfaceName (a strict charset+length pattern) and ValidateSubnetIPv4 (netip.ParsePrefix), wired into normalizeAmneziaWGSettings. (Finding 3) - amneziawg_job.go: IsAwgInstalled() existed but nothing ever called it, so a host without awg/awg-quick (the Docker image, RHEL, Arch, a failed install.sh PPA step) logged a reconcile failure every 10s forever. Now checked once an inbound actually needs it, warning once instead of spamming. (Finding 4) - client_inbound_apply.go: the WireGuard/AmneziaWG credential carry-forward (added so a metadata-only client edit doesn't rotate keys) never covered ForwardedPorts, so a partial edit -- an API call or Telegram-bot toggle that omits the field -- silently wiped a client's port-forwarding spec. Carried forward and written back the same way the key fields already are. (Finding 5) - manager.go: hostRulesFingerprint keyed each peer on its IPv4 address only, and structuralFingerprint omitted IPv6Enabled/IPv6ExternalInterface entirely, so an IPv6-only change could pick the syncconf reload path (which never re-runs PostUp, leaving a stale NDP-proxy entry) or be a complete no-op. Both fingerprints now cover the IPv6 fields. (Finding 6) - port_conflict.go: the AmneziaWG egress bridge (injectAmneziawgEgress) binds 127.0.0.1:63100+id with no collision check anywhere, since it isn't a database row the ordinary port-conflict query can see -- same blind spot the reserved Xray API port already has its own check for. Added the equivalent check for the AmneziaWG bridge port. (Finding 7) - install.sh: install_amneziawg ran unconditionally for every install/ update, building a DKMS kernel module and enabling host-wide IPv4/IPv6 forwarding whether or not the feature is ever used. Gated behind a new should_install_amneziawg (XUI_INSTALL_AMNEZIAWG=true/false, or an interactive y/N prompt defaulting to no). Also replaced the deprecated apt-key adv with a dedicated keyring + signed-by= on the Debian branch, and guarded its sources.list appends against duplication on a retried install. (Finding 8) Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> * feat(amneziawg): make the Xray TPROXY bridge a per-inbound opt-in Addresses Finding 10 from the automated PR review: an always-on TPROXY bridge makes every AmneziaWG tunnel hard-depend on Xray being up (all traffic, including DNS, drops whenever Xray restarts), and forces a full awg-quick down+up bounce on any client add/remove/re-IP, permanently losing the syncconf fast path. Adds ServerSettings.RouteThroughXray (off by default): - defaultPostUpDown only emits the TPROXY/policy-route rules when it's on; a plain AmneziaWG tunnel now has zero Xray dependency out of the box. - structuralFingerprint covers it (toggling it changes whether PostUp/ PostDown contain any TPROXY rules at all -- structural, not a per-peer host-rule). hostRulesFingerprint's IPv4 tracking is now itself conditional on RouteThroughXray (and IPv6 tracking on IPv6Enabled), so an instance that never uses either keeps the syncconf fast path for a plain peer re-IP. - injectAmneziawgEgress only creates a bridge for inbounds that opted in; checkAmneziawgEgressConflict (the Finding-7 fix) now parses each candidate through InstanceFromInbound so a non-routed inbound's port is correctly never treated as reserved. - New inbound-level Switch in the AmneziaWG form; the actual outbound decision is still made entirely through the panel's stock Routing page, same as before -- only whether the bridge exists at all is now a choice. Translation keys added to all 13 locales in the same commit this time, not backfilled later (see Finding 9's lesson). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> * fix(amneziawg): resolve 4 Low findings from the automated PR review - manager.go: serverAddress assumed subnetIp always ends in ".0"; a base like "10.8.1.5" was used verbatim as the server's own address, eventually colliding with peer allocation (which starts at .2 upward). Now derives the first host of the actual subnetIp/subnetCidr network via netip, matching serverAddressV6's own approach. A /32 base (no host bits at all) is still used as-is. (Finding 12, partial -- the /16 pool-widening half of this finding only exists on the upstream-pr/amneziawg branch's merged client_wireguard.go, not here; handled separately on that branch.) - manager.go: ensureLocked carried the previous per-peer traffic counters (`last`) forward even through a full restart, but awg-quick down+up resets the kernel's own counters to zero -- the next CollectTraffic computed a large negative delta (clamped to 0), silently discarding real traffic. Extracted the decision into nextTrafficBaseline: only a reload (syncconf) preserves the baseline. (Finding 13) - portfwd.go: exported ForwardedPortsInclude; inbound_amneziawg.go's new checkForwardedPortsConflict uses it to reject, at save time, a client's forwardedPorts that would DNAT the panel's own port or another enabled inbound's port to the tunnel client -- portForwardLines has no destination restriction, so this collision was previously silent. Wired into both the single-client update path and the add-client path (client_inbound_apply.go), plus normalizeAmneziaWGSettings for the whole-inbound save path. (Finding 14) - inbound.go: InboundOption.AwgServer sent the whole ServerSettings struct including PrivateKey to GetInboundOptions callers -- a shared, admin-wide dropdown-filling endpoint the frontend's own AwgServerOptionSchema never reads that field from. Redacted it before assigning. (Finding 11) Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> * fix(amneziawg): don't widen the peer address pool past AmneziaWG's own subnet Completes Finding 12 from the automated PR review (the serverAddress half of this finding was already fixed on main and cherry-picked here). This half is specific to this branch: allocateWireguardAddress's /16 pool-widening fallback is an independent addition from upstream's own main that this branch inherited during the cherry-pick rebase -- it doesn't exist on the fork's own main at all, so this fix can't be cherry-picked the normal way and is committed directly here. Widening is safe for WireGuard's own Xray-native inbound (AllowedIPs isn't tied to a strict kernel interface subnet), but AmneziaWG's kernel interface Address is exactly the configured subnet -- an address allocated from the containing /16 once the /24 fills up would be silently unroutable. allocateWireguardAddress now takes an explicit allowWidening bool: WireGuard's own caller passes true (unchanged behavior), AmneziaWG's passes false (fails loudly on exhaustion instead). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> * docs(docker): note that AmneziaWG doesn't work in this image Investigated: the image is Alpine-based, and AmneziaWG's own packaging (DKMS module + amneziawg-tools) doesn't target Alpine/musl at all -- unlike the Debian/Ubuntu/Fedora/Arch paths install.sh already handles, there's no package to apk add even with full host network/capabilities. The panel already degrades gracefully (IsAwgInstalled() logs one warning instead of retrying forever), so no code change is needed -- just made the reason explicit at the point where a user would reach for cap_add/ network_mode to try to work around it. * fix(sub): include amneziawg inbounds in subscription links getInboundsBySubId's SQL protocol allowlist never had 'amneziawg' added, so every AmneziaWG client was silently excluded from all three subscription formats (plain/individual links, JSON, Clash) and from the Telegram bot's QR/individual-link buttons, which fetch through the same path. genAmneziaWGLink itself was already fully implemented and already wired into GetLink's dispatch switch -- it just never got a chance to run. Same bug shape as the earlier TRACKED_PROTOCOLS frontend gap: a hardcoded protocol list one entry short. Found while investigating whether the Telegram bot needed AmneziaWG- specific client-management code -- it doesn't (the bot itself is fully protocol-agnostic), but this is the actual root cause of "can't share an AmneziaWG client's config via the bot." Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> * fix(inbound): enforce node-eligibility server-side, not just in the UI Investigated multi-node interaction with AmneziaWG: the master's own reconcile (DesiredAmneziaWGInstances) and Xray config generation (injectAmneziawgEgress, the GenXrayInboundConfig protocol skip) all correctly filter on NodeID IS NULL, so a node-assigned AmneziaWG (or MTProto) inbound would never be managed by the master. But nothing stopped one from being created that way: NODE_ELIGIBLE_PROTOCOLS (frontend/src/pages/inbounds/form/InboundFormModal.tsx) only hides the node picker client-side -- a direct API call could set nodeId on an AmneziaWG inbound, which every node then reconciles as an ordinary local inbound (nodes run the identical binary, full cron suite included), leaving it running unmanaged and untracked by the master's own AmneziaWG bookkeeping. Added isNodeEligibleProtocol (inbound_protocol.go), mirroring the frontend's allowlist, and enforced it in both AddInbound (the actually exploitable path -- nodeId comes straight from the request) and UpdateInbound (defense in depth; NodeID is already restored from the stored row there before this check, so it mainly guards against a protocol change on an existing node-hosted inbound). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> * fix(amneziawg): allow TPROXY-marked traffic through a default-deny INPUT chain TPROXY never rewrites a packet's own destination address, only the routing decision. A default-deny firewall whose INPUT chain sanity-checks "is this destination actually local" (UFW's ufw-not-local, via addrtype --dst-type LOCAL, is a concrete example) silently drops the redirected packet before Xray's socket ever sees it -- RouteThroughXray looked fully configured (TPROXY rule present and counting, Xray listening with IP_TRANSPARENT set) yet every peer's traffic vanished with no trace on either side. Adds an idempotent, never-torn-down "iptables -I INPUT 1 -m mark --mark <fwmark> -j ACCEPT" alongside the existing shared policy route, so this works regardless of which firewall manager owns the rest of the INPUT chain. * fix(frontend): give AmneziaWG the same UDP tag and its own tag color The Inbounds list only special-cased isWireguard/isHysteria for the "UDP" network badge, so an AmneziaWG row showed just the bare protocol tag with no transport badge next to it. Added the missing isAmneziawg flag (mirrors isWireguard exactly) and wired it into the same branch. Client-row protocol-color maps in ClientsPage/HostList had no amneziawg entry, silently falling back to grey -- ClientInfoModal already had amneziawg: 'yellow' from earlier work, these two just never got it. * feat(logs): show which AmneziaWG client an access-log line belongs to The dokodemo-door TPROXY bridge every AmneziaWG peer's traffic is routed through has no per-user identity, so Xray's own access log never carries an "email:" token for these lines -- the Access Logs modal showed a blank Email column for every in-*-udp row, even though every other protocol's rows show the client normally. The peer's decapsulated tunnel IP does survive as the log's "from" address, and that IP deterministically maps to exactly one configured peer. Builds a "<inbound tag>|<ip>" -> email index from the same AmneziaWG inbounds already parsed elsewhere (amneziawg.InstanceFromInbound), and fills in Email from it whenever the raw log line didn't have one. * fix(amneziawg): enable sniffing on the TPROXY bridge Domain-based Routing rules could never match RouteThroughXray traffic: an AmneziaWG peer resolves DNS itself, through the tunnel, before ever sending a packet, so the decapsulated traffic TPROXY hands to the bridge is already a bare destination IP with no domain name attached at the network layer. Every other inbound recovers this via sniffing (confirmed working for the stock wireguard inbound, which does have it configured); the bridge never got a sniffing block at all, so only tag/IP/network-based rules could ever match it -- any domain rule above it in the list was silently unreachable. * docs: add an AmneziaWG config page and list it as a supported protocol Closes the PR checklist gap: the feature shipped with zero mention on the docs site. Mirrors reality.mdx's structure (key settings, setup steps, config excerpt) and notes the Docker/multi-node/Telegram-bot caveats the PR itself is honest about not having confirmed. * fix: address the fresh review round on PR #6105 (8 findings) 1. hostRulesFingerprint didn't account for ForwardedPorts when RouteThroughXray was off, so re-IPing a peer with port-forwarding configured left stale DNAT rules pointing at an address the next peer could be handed. 2. Server/client config values (keys, email, I1) were never validated for control characters before being written into the generated .conf; a newline could smuggle a PostUp hook into awg-quick's parser. Added ValidateConfigValue at save time and a sanitizeConfigValue backstop at render time. 3. checkForwardedPortsConflict didn't scope to node_id IS NULL, so a port used only on a different node produced a false collision; also hoisted the panel-port/inbounds lookup out of the per-client loop (portConflictContext) so N clients cost one query, not N. 4. PostDown commands were ";"-joined and abort on the first failure; appendOrTrue makes teardown best-effort so an external firewall flush can't leave DNAT rules to accumulate across bounces. 5. The "ip rule list | grep -q" existence check could SIGPIPE under pipefail and re-add a duplicate rule; switched to grep -c >/dev/null. 6. Ported the vpn:// share-link format (base64url of the plain .conf text, matching the real AmneziaVPN app) onto this branch -- it had only ever landed on our own fork's main, so this PR branch was still on the old amneziawg://+query-params scheme our own docs no longer described. Also corrected the docs' install.sh claim (opt-in/ interactive, not automatic) and stale pre-opt-in comments in route_egress.go. 7. install.sh: Arch's ndppd install used pacman -Syu (full system upgrade) instead of -Sy like every other call in the script; and should_install_amneziawg re-prompted on every `x-ui update` even when awg was already installed. 8. CollectTraffic could clobber a concurrent restart's freshly-reset (empty) traffic baseline with stale pre-restart counters, since getPeerStats runs lock-free; now checks pointer identity before writing back. sweepOrphansLocked permanently disabled itself on a transient os.ReadDir failure instead of allowing a retry. go build/vet/test and frontend typecheck/lint/build/vitest all pass. * fix(install.sh): check the live sysctl value, not sysctl.conf text Reviewer feedback (cherts, PR #6105): grepping /etc/sysctl.conf for the setting name is unreliable -- many distros split sysctl config across /etc/sysctl.d/*.conf, and /etc/sysctl.conf can be a symlink into that directory, so the check can miss an already-active setting (harmless duplicate append) or match a disabled/commented line (forwarding silently stays off). Query the live value via `sysctl -n` instead, which is accurate regardless of which file set it. Applied the same fix to both the IPv6 and IPv4 checks for consistency. * fix: update inbound_amneziawg.go to the split buildInboundForLocalRuntime Same fork-only-file blind spot as the one caught on our own main after the 3.6.0 sync: upstream split buildRuntimeInboundForAPI into buildInboundForNodePush / buildInboundForLocalRuntime (part of the node-sync client-deletion fix,5bc81dfd), updating every call site it could see. This file doesn't exist upstream, so it kept calling the old name even after the branch merged in that commit. * fix(frontend): recognize AmneziaWG's vpn:// scheme in share-link labels The shared link-tag/label helper (used by the client info modal, QR modal, and subscription page) had no entry for the vpn:// scheme AmneziaWG links use, so it fell through to the generic fallback: a plain "Vpn" tag with no color, and an empty remark/port that made the row's title fall back to "Link N" instead of the inbound's actual name:port — unlike every other protocol, which shows its real tag and label. vpn:// links are base64url of a plain .conf text (matching the real AmneziaVPN app's own share-link format), not a structured URL, so there's no query string or #hash to read a remark/port from. Decode the payload and pull the remark/endpoint back out of the .conf text directly instead. * fix(xray): force a full restart for TPROXY inbounds, never hot-add them Real incident: an AmneziaWG inbound with RouteThroughXray enabled lost all internet on that connection after a migration. Root-caused on the live box -- iptables TPROXY counters were incrementing (packets correctly redirected to 127.0.0.1:63110), but nothing was actually listening there (ss showed nothing on that port) until a full `systemctl restart x-ui`, after which the bridge came up immediately. Xray-core's gRPC AddInbound reports success for a new sockopt.tproxy inbound (internal/amneziawg's own Xray egress bridge is the only kind this fork ever generates) but doesn't reliably bind a working listener for it outside of process startup -- the bridge silently never comes up, and RouteThroughXray traffic goes nowhere until the next full restart happens to occur for an unrelated reason. diffInbounds already has this exact defensive pattern for REALITY inbounds ("a gRPC remove+add does not reliably rebuild the REALITY authenticator"), just never extended to TPROXY, and only in the already-existing-then-changed branch -- the "brand new inbound" branch had no such guard at all, which is exactly the path a freshly-enabled RouteThroughXray bridge takes. Added inboundUsesTproxy and wired it into both branches. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> * fix(amneziawg): flag Xray for resync when a peer edit changes qualifying state Real production bug, root-caused on iiadmin-vps: updateAmneziaWGInbound/ AddInbound/DelInbound only ever updated the kernel interface via amneziawg.GetManager() -- they never called SetNeedRestart the way every other protocol's mutation path does (client_crud.go, inbound.go, etc. all do). injectAmneziawgEgress's TPROXY bridge inbound depends on InstanceFromInbound finding at least one qualifying peer plus RouteThroughXray, so an edit that flips that (first peer added, last one removed, RouteThroughXray toggled on) previously required a full panel restart before the bridge actually got created, with no error anywhere: the kernel interface would handshake fine, but traffic redirected into the bridge's TPROXY port went nowhere because nothing was listening there. diffInbounds/inboundUsesTproxy already correctly force a full restart for a brand new TPROXY inbound (bdee0a20) -- that part was never the bug. The gap was entirely upstream: nothing ever told Xray a resync was even needed. * fix(clients): reject AllowedIPs already used on another WireGuard/AmneziaWG inbound defaultWireguardClients/defaultAmneziaWGClients only ever checked uniqueness against their own inbound's client list, so two inbounds sharing a subnet (same protocol or not) could silently hand out or accept the same address -- the exact scenario behind a real duplicate-IP incident where a WireGuard and an AmneziaWG client both ended up on the same address. otherTunnelAllowedIPs now collects every address already claimed on every other tunnel inbound and folds it into both the auto-allocation pool and the manual-entry collision check, naming the other inbound in the error when it fires. * fix(frontend): add the missing AmneziaWG config download on the sub page The subscription page already gave WireGuard links their own "Config" block (copy/download/QR of the actual .conf, via wireguardConfigFromLink reversing the wireguard:// query params) but had no equivalent for AmneziaWG's vpn:// links -- its isWireguardLink gate never matched them, and no reverse-parse helper existed for this page specifically. Every other surface (InboundInfoModal, ClientInfoModal, ClientQrModal) already had this parity; this was the one page that didn't. Fixed by adding amneziawgConfigFromLink (inbound-link.ts), simpler than its WireGuard counterpart since a vpn:// payload already *is* the plain .conf text -- just base64url-decode it, no query-param reconstruction needed -- and wiring it into SubPage.tsx alongside the existing WireGuard block, reusing the same pages.clients.amneziaWgConfig label the other three surfaces already use. * fix(xray): force a full restart for password-auth SOCKS5 hot-apply Real production incident: editing a client under an AmneziaWG inbound left its embedded SOCKS5 relay's settings byte-different (a new account list), and Xray's gRPC remove+add hot swap silently dropped the account for a peer whose email contained non-ASCII characters -- its tunnel kept handshaking fine but all its traffic got rejected at the SOCKS5 layer, while every other peer on the same relay was unaffected. A full restart (reading the same JSON straight from disk) always produced the correct account list. socks isn't in userDiffableProtocols (that only covers vless/vmess/trojan's clients+email shape, not accounts+user), so any settings drift on this inbound fell through to the generic remove+add path. Forces a restart instead, the same defensive choice already made for REALITY and TPROXY -- scoped to auth:"password" specifically so the other, noauth SOCKS5 bridges (panel/node/mtproto egress) keep the cheaper hot path. * Fix Attach reusing one identity's address across wg/awg inbounds ClientService.Attach deliberately copies one identity's stored AllowedIPs into every WireGuard/AmneziaWG inbound it's attached to in the same call, so the same person gets the same tunnel address on every protocol they use. Its loop calls addInboundClient once per inbound, and each of those independently computes otherTunnelAllowedIPs -- so by the second inbound in the batch, the first inbound's just-written copy of this identity's own address looked like a cross-inbound collision against itself. Real production symptom this caused: detaching then re-attaching a client to both wg and awg failed with "wireguard: allowedIPs entry X is already used by a client on inbound 'awg' (#N)" -- the exact address the identity is supposed to keep, rejected as if it belonged to someone else. Add a selfEmails exclusion to otherTunnelAllowedIPs and populate it from the client(s) being processed at the one real call site. Safe unconditionally: ClientRecord.Email is globally unique, so a match can only ever be this same identity's own entry on a sibling inbound, never a genuine different client's address. Reproduced the underlying mechanism live (manual entry correctly rejected as a cross-inbound collision; fresh auto-allocation correctly avoided a used address) before writing the fix, to confirm the guard itself works and the bug is specifically in how Attach's per-inbound calls interact with it. * Attach: allocate fresh when re-attaching with no active tunnel The previous fix (82cc69f5) made Attach's own address-reuse correctly not collide with itself across inbounds -- but it still always reused an identity's stored AllowedIPs verbatim, even when that identity currently has zero WireGuard/AmneziaWG attachments at all. A real report from testing this live: an identity fully detached from both its wg and awg inbounds, then re-attached, got its old address back even though several lower addresses were free -- because nothing about being fully detached ever cleared the stored value Attach copies from. Add hasTunnelAttachment, checked once against the identity's CURRENT inbound set before Attach's loop runs: if none of its current inbounds is WireGuard/AmneziaWG, clear the stored AllowedIPs so this attach allocates fresh (matching what a brand-new client would get) instead of resurrecting an address nothing reserves anymore. Left alone when the identity already has an active tunnel elsewhere, so extending it to a second protocol still keeps a consistent address. * Fix TestOtherTunnelAllowedIPsExcludesSelfEmail's own test setup CI caught this: the "genuinely different client" (other@wg) was seeded onto the SAME inbound passed as excludeID, which otherTunnelAllowedIPs already excludes entirely regardless of the selfEmails fix -- so the assertion that its address is still reported could never have passed, proving nothing either way. Move it onto the sibling inbound alongside shared@id, which is what the test actually needs to exercise (two clients on one sibling, one excluded by email, one not). * Attach: never inherit an address that doesn't fit the target inbound hasTunnelAttachment (from the earlier fix, commit 51067f16) only asked "does this identity have ANY tunnel attachment", treating that as license to reuse its stored address verbatim on every inbound being attached. Real production case this missed: an identity's stored address came from WireGuard's own fallback subnet (10.0.0.0/24, used when that inbound has no other clients to infer a base from), then got attached to a second, AmneziaWG inbound configured for a completely different subnet (10.8.1.0/24). defaultAmneziaWGClients's already-set-AllowedIPs branch only checks for collisions, never subnet membership, so the mismatched address was accepted silently -- producing a peer that can never actually connect, since an AmneziaWG address must fall inside the kernel interface's own configured subnet to be routable at all. Add addressesFitAmneziaWGInbound, checked per inbound inside Attach's loop: if the inherited address doesn't fit the SPECIFIC inbound being attached, clear it just for that one so it gets a fresh, valid allocation instead, while other already-attached inbounds keep their existing values. WireGuard has no equivalent strict subnet requirement (allocateWireguardAddress can widen to a fallback pool for it), so this only ever constrains AmneziaWG targets. * Give WireGuard an explicit, admin-configurable subnet field WireGuard previously had no configurable subnet at all -- only an implicit one, either inferred from existing clients' own addresses (wireguardAllocationBase) or a hardcoded 10.0.0.0/24 fallback when none exist yet. AmneziaWG, by contrast, has always had a real server.subnetIp/subnetCidr field in its settings, editable in the UI. User request: give WireGuard the same treatment. Backend: explicitWireguardSubnetBase reads an optional subnetIp/ subnetCidr pair from the inbound's own settings JSON (mirroring AmneziaWG's defaultAmneziaWGSubnetBases). defaultWireguardClients checks it first; only when unset does it fall back to today's inference-from-existing-clients behavior, so an inbound saved before this field existed keeps working exactly as it always has. Frontend: subnetIp/subnetCidr added to WireguardInboundSettingsSchema and the inbound form (mirroring AmneziaWG's own field layout/labels), with a real default (10.0.0.0/24, the same value the backend already fell back to) seeded for newly created inbounds so the field starts populated and editable rather than blank. Translated across all 13 locales. This also structurally closes the class of bug fixed in 82cc69f5/291c47b3: with wg and awg subnets explicit and independently controllable, an admin who wants matching addresses across both protocols can configure them to actually agree, instead of one silently inheriting the other's incompatible range. * Split the client edit form's AllowedIPs into per-protocol fields A client attached to both WireGuard and AmneziaWG shared one AllowedIPs form field with a dynamically-switching label, so its two genuinely different addresses could never both be shown or edited correctly. Worse, Update/Create broadcast that one shared value to every attached wg/awg inbound with no subnet-fit check, so an ordinary edit save could silently overwrite one protocol's address with the other's -- the same bug class already fixed for Attach, but reachable from any client edit. model.Client gains an optional AllowedIPsByInbound map so a caller can send distinct values per inbound; Update/Create honor it and, when it's absent, clear a shared value that doesn't fit an AmneziaWG inbound's own subnet instead of writing it through. A new TunnelAllowedIPsByInbound read path feeds the real per-inbound address to the client edit form via GET, which now renders two separate, correctly-labeled fields whenever both protocols are attached (unchanged single dynamic field otherwise). * Regenerate openapi.json for the new allowedIPsByInbound field Follow-up to 878ee839: gen:zod (frontend/src/generated) was already regenerated and committed, but gen:api (frontend/public/openapi.json) wasn't, so CI's codegen drift check failed. * Fix build breakage from merging upstream main: Update() gained a limitHwid param Two of our own AllowedIPs tests (not present upstream, so the merge never flagged them as conflicting) still called the old 3-arg Update(inboundSvc, id, client) -- upstream's hardware-ID-limit feature added a required limitHwid parameter that every other caller in this package already passes. Also drop createDefaultInboundSettings from InboundsPage.tsx: the merge conflict resolution kept the import, but upstream's clone-payload refactor (buildClonePayload, inbound-clone.ts) already calls it internally now -- this file doesn't need it directly anymore. * Fix real bug: AmneziaWG clients rejected as "empty client ID" in 3 places Three switch statements on inbound.Protocol handle "wireguard" explicitly (checking client.PublicKey) but fall through to the default case for "amneziawg" (checking client.ID, which AmneziaWG clients never set -- they use PublicKey/Email like WireGuard, not the VMess/VLESS UUID field). This is what the 4 AllowedIPs tests were actually catching: UpdateInboundClient's newClientId derivation hit this same default branch, so every Update() on an AmneziaWG client returned "empty client ID" before ever reaching the AllowedIPs logic being tested. Fixed by adding "amneziawg" alongside "wireguard" in each switch: addInboundClient's per-client validation, UpdateInboundClient's newClientId derivation, and AddInbound's per-client validation (the third one wasn't hit by these tests, but has the identical bug -- creating a brand-new AmneziaWG inbound with a client attached would fail the same way). * refactor(amneziawg): rename Obfuscation20 to Obfuscation31, drop the dead mobile preset Mechanical rename ahead of the AmneziaWG 3.1 parameter work: the type, generator and prose all said 2.0, and the "mobile" generator preset was reachable only from its own test. No behavior change. * feat(amneziawg): AmneziaWG 3.1 obfuscation parameters (backend + generated schemas) Adds the 3.1 parameter surface to the inbound settings and both Go config emitters: I2-I5 signature packets, HeaderProtectionKey (base64 32-byte, shared server<->client), ContentPaddingAddition, the five handshake-timing randomization ranges (RekeyAfterTime/RekeyTimeout/RejectAfterTime/ KeepaliveTimeout/MaxHandshakeAttempts), and the RandomTrailers/ DisableCookies switches. Freshly generated sets fill everything except I2-I5 (matching Amnezia's own generator) with jittered ranges bracketing WireGuard's stock timing constants; every reject window starts >= 30s above the rekey window by construction. Empty fields stay off the wire, so blanking a field disables just that feature. Validation generalizes the H1-H4 range checker for the new uint32-range fields, requires min 1 on timers, cross-checks rekey-vs-reject, and demands a real 32-byte base64 header-protection key. The manager warns once per process when the installed awg tools predate 3.1 but an inbound uses 3.1 parameters (awg-quick rejects unknown keys with a generic error otherwise); apply still proceeds. Requires amneziawg-tools v3.1.20260812+ / module or amneziawg-go v3.1.20260814+ on the host. * feat(amneziawg): emit and randomize 3.1 parameters in the frontend Both client-config emitters (the vpn:// link builder and the clients-page .conf builder) now carry the 3.1 [Interface] lines in the same order as the Go emitters. The obfuscation randomizer moves out of InboundFormModal into a shared lib/xray/amneziawg-obfuscation.ts that also fills the new fields, and createDefaultAmneziawgInboundSettings switches from static values to that generator — a fresh inbound now really gets the unique fingerprint the docs promise instead of the same jc=5/jmin=10 set on every install. Schema parse-time defaults for the new fields stay ''/false on purpose: real values come only from the generator, so resaving an inbound never mutates its stored parameters. A new parity test pins the hand-written AmneziawgServerSchema to the generated ServerSettings key set, so a field added on one side can no longer silently vanish from configs. * feat(amneziawg): 3.1 form fields and translations Inbound form gains inputs for I2-I5, HeaderProtectionKey (filled by the existing obfuscation Regenerate button), ContentPaddingAddition, the five timing ranges, and the RandomTrailers/DisableCookies switches; the MTU input picks up the min=1 its schema already enforced. All 13 locales get the 19 new keys and drop the "2.0" branding from the s3/s4/i1 labels. * docs(amneziawg): document 3.1 parameters; install.sh kernel/version notes The AmneziaWG page's obfuscation section moves from the 2.0 to the 3.1 parameter set: table rows for I2-I5, HeaderProtectionKey, ContentPaddingAddition, the timing-randomization ranges and the RandomTrailers/DisableCookies switches, a requirements callout (tools v3.1.20260812+, module/awg-go v3.1.20260814+, Linux 6.7+ for the DKMS path), and a sample client .conf that matches what the panel actually emits (including the DNS defaults and PersistentKeepalive it always had). install.sh warns before a DKMS build on a pre-6.7 kernel and after any install that left pre-3.1 amneziawg-tools on PATH. Also updates the hosts API operation paths ({id} -> {groupId}) in the stale ru/zh/fa reference pages: syncing docs/public/openapi.json for the new AmneziaWG schema fields surfaced that rename, which had never been copied over, and the docs build fails on paths missing from the spec. * fix(amneziawg): reject control characters and canonicalize 3.1 range values Adversarial review of the 3.1 work surfaced a validation gap: base64.DecodeString silently ignores CR/LF, so a header-protection key that picked up a line wrap in transit decoded to a valid 32 bytes, passed validation, and was emitted verbatim into every client config — where the orphan second line breaks the import while the server (whose emitter strips control chars) keeps running with the correct key. The key and range validators now reject control characters outright. Also from the same review: range values are canonicalized on save ("110 - 140" -> "110-140", whitespace-only collapses to feature-off, closing a case where the server conf rendered an invalid blank-value line the client emitters omitted); the rekey/reject invariant is now enforced against WireGuard's 120s/180s defaults when only one side is set; and the structural fingerprint joins on "\n" instead of "|", which is a legal I1-I5 character and made adjacent free-text fields join-ambiguous. * fix(install): resolve latest release tag via web redirect to dodge API rate limits The non-interactive install smoke test resolved the release version through the unauthenticated GitHub API (api.github.com/.../releases/latest), which allows only 60 requests/hour per IP. The test installs twice in one run, and on shared CI runner IPs the second call gets rate-limited, returns no tag_name, and install.sh treats an empty version as fatal (exit 1) — the same "Failed to fetch x-ui version" real users hit behind CGNAT/shared addresses. resolve_latest_tag() now reads the tag from the github.com releases/latest web redirect (not subject to the API rate limit), falling back to the API only if the redirect yields nothing. Verified with the real deploy/test/smoke-noninteractive.sh (two installs, both green). * fix(amneziawg): three review findings on #6105, plus a comment trim 1. A peer's allowedIPs reached the generated .conf unvalidated and unsanitized, unlike email/publicKey/preSharedKey which normalizeAmneziaWGSettings already guards. A newline in an entry let a following "[Interface]" re-open the interface section, whose "PostUp = ..." awg-quick then runs as root on the next apply. Reproduced end to end against generateServerConfig. The save path now rejects and canonicalizes through normalizeWireguardAllowedIPs, and the render path sanitizes as a backstop for rows predating the validation (an upgrade, a restored backup, a direct DB edit). H1-H4 get the same render-time sanitize, and the two NIC name fields a plausibility check, since stripping control characters alone would still let a shell metacharacter into a root-executed PostUp line. 2. EgressPortForInbound is 63100 + inbound id, so an id past 2435 derives a port above 65535 -- and Xray rejects the whole generated config over one invalid port, taking every other protocol down with it. It now reports ok=false past the range, and both the Xray bridge and its TPROXY rules are skipped instead of emitting an impossible port. 3. The downloadable AmneziaWG .conf read ClientRecord.allowedIPs, a single shared column that holds the WireGuard address for an identity attached to both protocols -- the exact ambiguity tunnelAllowedIPs was added to resolve for the edit form. The info and QR modals already hydrate that field, so they now pass this inbound's own address to the builder. Also trims the comment blocks in the files touched here to the 2-line guidance in CLAUDE.md: internal/amneziawg alone carried 423 comment lines in over-long blocks against 118 for the comparable internal/mtproto, and is now at 110. Every non-obvious constraint is kept (the kernel S1/S2 rule, why PostDown is best-effort, why grep -c and not -q, why the fingerprints split three ways); the narration is gone. Two hot_diff.go comments pointed at an internal/amneziawgnet package and an injectAmneziawgnetSocks function that exist nowhere in the tree; the checks themselves are unchanged. * feat(logs): add an AmneziaWG log view to the overview The overview has an access-log view for Xray but nothing for AmneziaWG, so when a tunnel misbehaves there is no way to see it from the panel at all. A kernel tunnel logs no per-request lines, so the equivalent view is built from the two things it does expose: - Live per-peer activity from `awg show <iface> dump`, joined to the client email through the desired peer set: last handshake, endpoint, allowed IPs, cumulative transfer and online state, newest handshake first. - The panel's own AmneziaWG event lines (interface up/down, awg-quick failures, the pre-3.1 tools warning), which are what actually explain a peer being absent from the table. POST /panel/api/server/amneziawglogs/:count serves both, with the same count + filter contract GetXrayLogs uses, and the modal mirrors XrayLogModal's toolbar, auto-update, mobile cards and download. The action-bar button is gated on a new status.amneziawg.configured, which stays true while an inbound exists but its interface is down -- exactly when the event lines matter. Verified against a running panel: the endpoint returns the peer table and real event lines ("awg/awg-quick not found on PATH", "create config dir: permission denied"), and count and filter both narrow as documented. One of those lines surfaced a Debugf that had been rendering as "for inbound1:amneziawg:"; fixed here since it is now user-visible. * fix(amneziawg): stop double-counting a routed inbound's traffic injectAmneziawgEgress tags its Xray bridge with the AmneziaWG inbound's own tag, so the stock Routing page can target it. Xray therefore reports that bridge's bytes under the inbound's tag, and XrayTrafficJob feeds them to AddTraffic -- which accumulates -- on top of the same bytes AmneziaWGJob already reported from `awg show dump`. An inbound with routeThroughXray on counted roughly twice its real traffic, which also inflates the quota checks that read the same counters. The awg counters are the complete measure: every peer, whether or not TPROXY routed it, and the same wire bytes the per-client totals are built from, so they stay and the Xray rows are dropped. Per-client stats were never affected -- a dokodemo-door bridge has no per-user identity, so Xray emits no user>>>email rows for it. Filtering happens before every consumer, so the DB totals, the external traffic inform and the dashboard's live speed all read one source per inbound. The set of bridge tags now comes from a predicate shared with injectAmneziawgEgress itself, with a test that pins the two together -- naming one tag too few doubles the traffic again, one too many makes real traffic vanish. * fix(amneziawg): align the three .conf emitters on one peer field order The panel builds an AmneziaWG client .conf in three independent places, and they disagreed: buildAmneziaWGClientConfig put PresharedKey right after PublicKey (wg-quick(8)'s own order, and what both WireGuard emitters on the clients side already use), while genAmneziaWGConfig and the Go amneziaWGConfigText put it after Endpoint. A user comparing a subscription link against a downloaded .conf sees the difference immediately, and the generators are exactly the kind of parallel implementation CLAUDE.md warns about drifting. Moves the two outliers onto the wg-quick order. Also drops the stray trailing newline that only appeared when PersistentKeepalive was set, so a config now always ends on its last set field whichever that is -- the same shape all three emitters produce for the same client. Parsing is unaffected either way (the format is order-insensitive, and the AmneziaVPN app reads it as a flat key-value bag), so this changes only the rendered text. Adds a test on each side that pins the peer block's field order, since nothing previously asserted it. * refactor(amneziawg): switch to the embedded amneziawg-go/gVisor architecture Replaces the kernel-module (DKMS) + awg-quick + TPROXY backend with the fork's own embedded design: amneziawg-go runs in-process over a userspace gVisor netstack, and each peer's decapsulated traffic relays into its own loopback Xray SOCKS5 inbound, so Xray's native stats/sniffing/routing work for free instead of through hand-rolled bridges. No kernel module, no DKMS, no Secure Boot conflicts, works the same in a container as on bare metal. - internal/amneziawgnet: new package (Device/UAPI, gVisor netstack, TCP/UDP forwarding, SOCKS5 relay, peer identity, IPv6 host-alias egress identity, per-client port-forwarding) - amneziawg-go v3.1.20260814 + gvisor. - internal/amneziawg: keep the reusable protocol-shape types/validation (Instance/Peer/Obfuscation, InstanceFromInbound); drop the OS-shellout half (awg-quick, TPROXY policy routing, NDP proxy, peer-stats parsing). - internal/web/service: rewire the 5 integration points (job, runtime, client-apply, web shutdown, xray config) from the old manager to the new one; the AmneziaWG log view is rebuilt on the embedded Device's own UAPI dump (extended to carry endpoint/AllowedIPs) instead of `awg show dump`. - install.sh: drop DKMS/ndppd/TPROXY/Secure-Boot installer code (~250 lines) - an entire recurring class of installer fragility goes away. - frontend: drop the now-meaningless routeThroughXray toggle (the relay is always on); keep the field in the Zod schema, unexposed, so it isn't silently stripped from stored settings on next save - two regression tests deliberately depend on the Go struct still carrying it. - docs/i18n: rewrite amneziawg.mdx for the new architecture; drop the dead routeThroughXray translation keys across all 13 locales. Real production throughput (embedded core datapath, isolated bench, same box the kernel-module path was measured on): ~296 Mbit/s up, ~640 Mbit/s down, vs. 414.69 MB/s (~3.3 Gbit/s) for the kernel module on the same hardware - a real gap, tempered by this being single-stream/no-SOCKS5-hop and most VPN traffic being latency-bound rather than throughput-saturating. * fix(amneziawg): restore the branch's own Obfuscation31 shape + 2 CodeQL findings The previous push's wholesale-copy of types.go/params.go from the fork's main branch pulled in that branch's own independent (and incompatible) naming for the same AWG 3.1 feature set: Obfuscation20/GenerateObfuscation20 instead of this branch's already-shipped Obfuscation31/GenerateObfuscation31, and a missing CanonicalizeUintRange -- broke every Go CI job (the whole matrix fails to compile when any one package doesn't, which is why govulncheck/ golangci/postgres-durable-first/race all failed identically, not just go-test). Restores params.go/params_test.go verbatim from this branch's own last commit (a strict superset of validation: it already cross-checks rekey vs. reject timing windows, which the copied version never did) and folds the 3.0/3.1 fields (HeaderProtectionKey, ContentPaddingAddition, the 5 timing fields, RandomTrailers/DisableCookies) into Obfuscation31 itself, matching the original struct exactly instead of as separate top-level Instance fields. instance.go, the two amneziawgnet call sites, and 7 amneziawgnet test files updated to match. Also drops the one test (sanitizeConfigValue) that only ever served the retired kernel-module .conf writer -- correctly not ported, so the test testing it shouldn't have been copied either. Also fixes 2 CodeQL findings the same push surfaced: a clamped uint64->int64 conversion for the new log view's live byte counters (server.go), and an unneeded len+len sum feeding a slice pre-size in the v6-egress outbound merge (xray.go) -- append already grows correctly without it. * chore(amneziawg): regenerate frontend schemas for updated doc comments npm run gen was missed after the previous commit's types.go doc-comment edits (Obfuscation20 -> Obfuscation31, ValidateHeaderProtection -> ValidateObfuscation in the prose) -- openapigen bakes those comments into the generated schema's description field, so the committed frontend/src/generated/schemas.ts and openapi.json still had the old wording. codegen's git-diff-exit-code check caught it correctly. * fix(amneziawg): narrow 2 test fixtures that collided with MaxForwardedPorts TestCheckForwardedPortsConflict_CollidesWithEnabledInboundPort and ..._NoCollisionWhenPortsDontOverlap used "8000-8100"/"9000-9100" as their ForwardedPorts fixture -- 101 ports each, one over MaxForwardedPorts (100). The cap check (checkForwardedPortsConflict, added this session alongside the SOCKS-phantom-port check) fires first, so both tests got "more than 100 forwarded ports" instead of ever reaching the collision logic they're actually testing. The cap itself has its own dedicated boundary test already; these two just needed a narrower range that still covers/misses port 8080 as intended -- 8075-8085 and 9075-9085, 11 ports each. * fix(amneziawg): checkAmneziawgnetSocksConflict had no receiver in its new home My merge-conflict resolution kept this as a method call (s.checkAmneziawgnetSocksConflict) inside checkPortConflictTx, a plain function with no *InboundService receiver -- upstream's #6225 fix moved the port-conflict check out of the (s *InboundService) method and into this new tx-scoped free function, and I didn't notice the call site needed to change shape too. CI caught it immediately (undefined: s); nothing in this specific package can be locally verified past internal/database's own unrelated, pre-existing CGO build issue on this dev machine. Since the signature had to change either way, folded in the fix already flagged as a separate follow-up: checkAmneziawgnetSocksConflict now takes the caller's db handle instead of fetching its own via database.GetDB(), so it actually runs inside the same serialized transaction #6225 introduced -- previously it sat right next to that race fix without benefiting from it. * fix: address the review findings on the embedded AmneziaWG PR 5 blocking findings: - Floor S3/S4 at 12 in both obfuscation generators (Go and frontend) and reject a hand-edited value below that when HeaderProtectionKey is set -- IpcSet requires it, and ~39% of previously-generated sets violated it silently. - Guard PrivateKey/PrimaryDNS/SecondaryDNS/remark against newline injection in the AmneziaWG .conf builder (both the Go subscription-link path and the frontend downloadable-config path) -- unguarded, any of them could inject an arbitrary config line into a subscriber's client. - Bound the derived AmneziaWG SOCKS relay port to <= 65535 once an inbound's id is known, and check the reverse direction (does the relay port collide with an existing inbound's port) on both create and update -- previously only port -> relay collisions were checked, not relay -> port. - Gate injectAmneziawgV6Egress on the same V6AliasesActive predicate desiredV6Aliases already uses, so the two can't disagree about whether a peer's IPv6 identity is actually active at the OS level. 2 minor findings: - Fix the forwarded-ports cap check's off-by-one (a spec covering exactly the cap was rejected as if it were over it). - Correct docker-compose.yml's stale comment describing the retired DKMS/kernel-module architecture. * chore: retrigger CI build (armv5) failed on a transient Go module proxy network error (INTERNAL_ERROR stream reset on sagernet/sing), unrelated to this PR's changes. * docs: fix doc comments still describing the retired DKMS/awg-quick design A few doc comments (and one illustrative test log line) survived the embedded-architecture cutover unchanged and now contradict the code they sit next to: - internal/amneziawg/types.go's package comment claimed this package still owns a Manager that reconciles OS-level interfaces via awg-quick/DKMS -- that Manager was removed; the reconcile loop lives in internal/amneziawgnet now, and this package is protocol-shape-only. - internal/amneziawg/params.go's ValidateObfuscation/ValidateConfigValue comments cited "awg-quick up" / "awg-quick executes as root" as the reason to validate -- the server itself never calls awg-quick in this architecture; the same value still reaches a real rendered .conf that a client app or an admin's own awg-quick CLI applies downstream, so the validation is still warranted, just for a different consumer. Mirrored the same fix in inbound_amneziawg.go's matching comment and its test's comment. - internal/amneziawgnet/manager.go's Manager doc comments (x3) pointed readers at "internal/amneziawg.Manager" for comparison -- that type no longer exists in this diff at all. Repointed at internal/mtproto.Manager, the pattern this was actually modeled on and the one that's still real. - Swapped one test's illustrative "awg-quick up awg2 failed" log line for a message shaped like this architecture's actual amneziawgnet logging, so a reader skimming the test doesn't wonder whether the server still shells out to awg-quick. No behavior change. * fix(docs): re-run codegen for xray-settings.mdx after conflict merge The automated conflict-resolution hand-merge for this generated file was content-correct but didn't byte-match a real regen (different YAML long-string folding style). Re-ran npm run gen + docs' gen:api and kept that canonical output instead. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> * fix(amneziawg): drop the dead access-log email backfill amneziawgEmailIndex keyed peers by "<tag>|<tunnel IP>", a scheme built for the retired TPROXY bridge where the peer's decapsulated tunnel address survived as the access log's from-address. The embedded architecture relays through a loopback SOCKS5 dial, so every AmneziaWG log line's from-address is 127.0.0.1:<ephemeral> and the lookup could never match: the index was rebuilt on every log view just to miss. Remove the index, its GetXrayLogs wiring and its test. If per-line emails are wanted back, the relay would have to publish a local-port->email registry for the viewer to resolve loopback sources. * fix(api): generate AmneziaWGLogs/PeerActivity schemas instead of hand-writing them The amneziawglogs endpoint's response structs were missing from openapigen's StructAllow, so they were silently absent from every generated schema/example, the endpoints.ts entry carried a hand-written response, and AmneziaWGLogModal.tsx duplicated the shapes as local interfaces - the exact drift the allowlist rule exists to prevent. Allowlist both structs with example tags, point the endpoint at the generated schema, import the generated types in the modal, and sync docs/public/openapi.json. * chore(amneziawg): drop the unreferenced quiccapture package Nothing imports internal/amneziawg/quiccapture and no route exposes it; its package doc justifies the code as a port of frontend/src/lib/xray/i1Generators.ts, which does not exist in this repository, and promises an API round-trip that also does not exist. 1,110 lines of unreachable code with misleading provenance claims. Revert this commit to bring the package back when the live-capture I1 feature and its frontend counterpart actually land. * fix(clients): re-run cross-inbound conflict checks on the serialized writer The new client-level checks - cross-inbound AllowedIPs collisions and AmneziaWG forwardedPorts conflicts - read a fresh DB snapshot, decide, and only then enter runSerializedTx, while lockInbound only serializes writers on the SAME inbound. Two concurrent client creates on two different tunnel inbounds both passed the read and both committed, yielding two peers with one address: the exact check-then-claim race81cfd857(#6225) closed for AddInbound, which this PR's own checkAmneziawgnetSocksReverseConflict already cites. Keep the pre-tx pass for fail-fast UX and re-validate inside the transaction, where the single writer makes the answer authoritative. The race test drives two goroutines at two inbounds and demands exactly one winner; it fails with committed=2 when the in-tx re-check is removed. * fix(amneziawg): hot-apply depletion disables like mtproto does applyTrafficMutationBatch special-cases MTProto so a quota/expiry depletion cuts the sidecar immediately, but AmneziaWG fell through to runtime AddUser/RemoveUser - explicit no-ops for this protocol - so a depleted peer kept tunneling until the next 10s reconcile tick. Route it through applyLocalAmneziaWG, whose own contract (re-read committed settings, filter depleted clients, push to the interface) is exactly this case; the comment claiming it mirrors applyLocalMtproto is now true for the depletion path too. * fix(amneziawg): persist cleared DNS fields instead of resurrecting defaults PrimaryDNS/SecondaryDNS marshaled with omitempty, so clearing them persisted settings with no key at all - and the frontend re-parses stored settings through a Zod schema whose .default('8.8.8.8') / .default('8.8.4.4') fire on missing keys, silently repopulating the form on every load and re-persisting the defaults on the next save. Blank is a documented, meaningful state (no DNS line in client configs); drop omitempty so a cleared value survives the round-trip. The regression test normalizes a server block with cleared DNS and fails when the keys are dropped. * fix(amneziawg): accept cleared numeric obfuscation/subnet fields in the form AntD InputNumber emits null when cleared, Zod .default() only replaces undefined, and unlike wireguard.ts - whose optionalClearedInt comment documents exactly this failure mode - the AmneziaWG schema declared subnetCidr and jc/jmin/jmax/s1-s4 as bare z.number() defaults. Clearing any of the eight fields made safeParse reject the null and block the save until the user retyped a value. Absorb null into undefined while keeping each field's schema default, so a cleared field refills its documented default and legacy blobs with absent keys behave as before. * fix(amneziawg): guard the third .conf emitter against newline injection The review-round fix added the newline guard to amneziaWGConfigText (Go) and buildAmneziaWGClientConfig, but genAmneziaWGConfig in inbound-link.ts - the third of the three emitters its own comment says must not drift - still rendered privateKey/primaryDns/secondaryDns/remark unescaped, so a newline there injected a config line (e.g. a rogue PostUp) into the inbound form's downloaded .conf. Add the same guard, plus the regression tests the original fix shipped without: all four fields on the Go and both frontend emitters go red if any guard is removed. * test(amneziawg): pin the S3/S4 floors the TS drift guard claims to mirror The test's docstring says it mirrors internal/amneziawg/params_test.go, but it asserted S3>=8/S4>=4 while the Go test and both generators pin 12/12 - the floor ValidateObfuscation enforces whenever a header protection key is set, which this generator always sets. A regression narrowing the TS floors into 8-11/4-11 would have passed the drift guard and produced configs the backend rejects on save. * docs: restore the pia repo-map entry and document the AmneziaWG subsystem Merging main dropped CLAUDE.md's internal/pia/ bullet (added by #6272) while resolving the repo-map conflict - the package itself is untouched. Restore it, add the missing map entries for the two packages this branch introduces (internal/amneziawg/, internal/amneziawgnet/), bump the cron count, and give amneziawg_job its row in architecture.md's 5.4 table. * chore(amneziawg): correct comments stranded by the architecture pivot ae77c7e9's cutover to the embedded gVisor path deleted the kernel-module code but left several comments describing it in the present tense: hot_diff.go cited the removed service.amneziawgEgressStreamSettings and wrongly claimed AmneziaWG is the only sockopt.tproxy source (tunnel's TProxy mode is the live one the guard protects), socks_config.go pointed at the deleted EgressBasePort/EgressPortForInbound, manager.go referred to the deleted Manager and its fingerprinting as live code, web.go's cron registration claimed the job scrapes traffic (its own doc says it does not), and types.go capped ContentPaddingAddition at uint16 when validation and upstream both use uint32. * style(lint): satisfy gofumpt/goimports so make verify is green json_service.go's two 'Tag: "proxy"}' literals came in with main's owncc245a90formatting commit and fail the repo's gofumpt gate for everyone; the import grouping in inbound_amneziawg.go is from the serialized-writer fix on this branch. --------- Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com> Co-authored-by: Sanaei <ho3ein.sanaei@gmail.com> Co-authored-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com>
2253 lines
120 KiB
JSON
2253 lines
120 KiB
JSON
{
|
||
"username": "用户名",
|
||
"password": "密码",
|
||
"login": "登录",
|
||
"confirm": "确定",
|
||
"cancel": "取消",
|
||
"close": "关闭",
|
||
"save": "保存",
|
||
"logout": "登出",
|
||
"create": "创建",
|
||
"add": "添加",
|
||
"remove": "移除",
|
||
"update": "更新",
|
||
"copy": "复制",
|
||
"copied": "已复制",
|
||
"more": "更多",
|
||
"download": "下载",
|
||
"regenerate": "重新生成",
|
||
"jsonEditor": "JSON 编辑器",
|
||
"downloadImage": "下载图片",
|
||
"sort": "排序",
|
||
"remark": "备注",
|
||
"enable": "启用",
|
||
"protocol": "协议",
|
||
"search": "搜索",
|
||
"filter": "筛选",
|
||
"all": "全部",
|
||
"from": "从",
|
||
"to": "到",
|
||
"done": "完成",
|
||
"loading": "加载中...",
|
||
"refresh": "刷新",
|
||
"clear": "清除",
|
||
"second": "秒",
|
||
"minute": "分钟",
|
||
"hour": "小时",
|
||
"day": "天",
|
||
"check": "查看",
|
||
"indefinite": "无限期",
|
||
"unlimited": "无限制",
|
||
"none": "无",
|
||
"qrCode": "二维码",
|
||
"info": "更多信息",
|
||
"edit": "编辑",
|
||
"delete": "删除",
|
||
"reset": "重置",
|
||
"noData": "无数据。",
|
||
"copySuccess": "复制成功",
|
||
"sure": "确定",
|
||
"encryption": "加密",
|
||
"transmission": "传输",
|
||
"host": "主机",
|
||
"path": "路径",
|
||
"camouflage": "混淆",
|
||
"status": "状态",
|
||
"enabled": "开启",
|
||
"disabled": "关闭",
|
||
"depleted": "耗尽",
|
||
"depletingSoon": "即将耗尽",
|
||
"offline": "离线",
|
||
"online": "在线",
|
||
"domainName": "域名",
|
||
"monitor": "监听",
|
||
"certificate": "数字证书",
|
||
"fail": "失败",
|
||
"comment": "评论",
|
||
"success": "成功",
|
||
"lastOnline": "上次在线",
|
||
"lastSubFetch": "上次获取订阅",
|
||
"getVersion": "获取版本",
|
||
"install": "安装",
|
||
"clients": "客户端",
|
||
"usage": "使用情况",
|
||
"twoFactorCode": "代码",
|
||
"remained": "剩余",
|
||
"security": "安全",
|
||
"emptyDnsDesc": "未添加 DNS 服务器。",
|
||
"emptyFakeDnsDesc": "未添加 Fake DNS 服务器。",
|
||
"emptyBalancersDesc": "未添加负载均衡器。",
|
||
"somethingWentWrong": "出了点问题",
|
||
"subscription": {
|
||
"title": "订阅信息",
|
||
"subId": "订阅 ID",
|
||
"status": "状态",
|
||
"downloaded": "已下载",
|
||
"uploaded": "已上传",
|
||
"expiry": "到期",
|
||
"totalQuota": "总配额",
|
||
"individualLinks": "单独链接",
|
||
"active": "启用",
|
||
"inactive": "停用",
|
||
"unlimited": "无限制",
|
||
"noExpiry": "无到期",
|
||
"copyAllConfigs": "复制全部配置",
|
||
"copyAllConfigsCopied": "已复制全部配置",
|
||
"email": "邮箱"
|
||
},
|
||
"menu": {
|
||
"theme": "主题",
|
||
"dashboard": "系统状态",
|
||
"inbounds": "入站",
|
||
"clients": "客户端",
|
||
"groups": "分组",
|
||
"nodes": "节点",
|
||
"settings": "面板设置",
|
||
"xray": "Xray 配置",
|
||
"routing": "路由",
|
||
"outbounds": "出站",
|
||
"apiDocs": "API 文档",
|
||
"donate": "捐赠",
|
||
"hosts": "主机",
|
||
"docs": "文档",
|
||
"openMenu": "打开菜单",
|
||
"pinSidebar": "固定侧边栏",
|
||
"unpinSidebar": "取消固定侧边栏",
|
||
"subFormats": "Sub Formats"
|
||
},
|
||
"pages": {
|
||
"login": {
|
||
"hello": "你好",
|
||
"title": "欢迎",
|
||
"loginAgain": "登录时效已过,请重新登录",
|
||
"toasts": {
|
||
"invalidFormData": "数据格式错误",
|
||
"emptyUsername": "请输入用户名",
|
||
"emptyPassword": "请输入密码",
|
||
"wrongUsernameOrPassword": "用户名、密码或双重验证码无效。",
|
||
"successLogin": "您已成功登录您的账户。"
|
||
}
|
||
},
|
||
"index": {
|
||
"cpu": "CPU",
|
||
"swap": "交换空间",
|
||
"storage": "存储",
|
||
"memory": "内存",
|
||
"xrayStatus": "Xray",
|
||
"stopXray": "停止",
|
||
"restartXray": "重启",
|
||
"xraySwitch": "版本",
|
||
"xrayUpdates": "Xray 更新",
|
||
"xraySwitchClickDesk": "请谨慎选择,因为较旧版本可能与当前配置不兼容",
|
||
"updatePanel": "更新面板",
|
||
"panelUpdateDesc": "这将把 3X-UI 更新到最新版本并重启面板服务。",
|
||
"currentPanelVersion": "当前面板版本",
|
||
"latestPanelVersion": "最新面板版本",
|
||
"panelUpToDate": "面板已是最新",
|
||
"devChannel": "开发通道",
|
||
"devChannelWarning": "开发版会跟踪 main 的每次提交,并非稳定版本,且无法自动降级。",
|
||
"currentCommit": "当前提交",
|
||
"latestCommit": "最新提交",
|
||
"updateChannelChanged": "更新通道已切换",
|
||
"xrayStatusUnknown": "未知",
|
||
"xrayStatusRunning": "运行中",
|
||
"xrayStatusStop": "停止",
|
||
"xrayStatusError": "错误",
|
||
"systemHistoryTitle": "系统历史",
|
||
"historyTitleCpu": "CPU 使用率",
|
||
"historyTitleMem": "内存使用率",
|
||
"historyTitleNetwork": "网络带宽",
|
||
"historyTitlePackets": "网络数据包",
|
||
"historyTitleDisk": "磁盘 I/O",
|
||
"historyTitleOnline": "在线客户端",
|
||
"historyTitleLoad": "系统平均负载(1 分钟 / 5 分钟 / 15 分钟)",
|
||
"historyTitleConnections": "活动连接 (TCP / UDP)",
|
||
"historyTitleDiskUsage": "磁盘空间使用率",
|
||
"historyTabBandwidth": "带宽",
|
||
"historyTabPackets": "数据包",
|
||
"historyTabDisk": "磁盘 I/O",
|
||
"historyTabOnline": "在线",
|
||
"historyTabLoad": "负载",
|
||
"historyTabConnections": "连接数",
|
||
"historyTabDiskUsage": "磁盘使用量",
|
||
"xrayMetricsTitle": "Xray 指标",
|
||
"xrayTitleHeap": "已分配的堆内存",
|
||
"xrayTitleSys": "向操作系统保留的内存",
|
||
"xrayTitleObjects": "存活的堆对象",
|
||
"xrayTitleGcCount": "已完成的 GC 周期",
|
||
"xrayTitleGcPause": "GC 暂停时间",
|
||
"xrayTitleObservatory": "出站连接健康状态",
|
||
"xrayTabHeap": "堆",
|
||
"xrayTabSys": "系统",
|
||
"xrayTabObjects": "对象",
|
||
"xrayTabGcCount": "GC 次数",
|
||
"xrayTabGcPause": "GC 暂停",
|
||
"xrayTabObservatory": "观测站",
|
||
"xrayMetricsDisabled": "未配置 Xray 指标端点",
|
||
"xrayMetricsHint": "在 xray 配置中添加顶级 metrics 块,tag 为 metrics_out,listen 为 127.0.0.1:11111,然后重启 xray。",
|
||
"xrayObservatoryEmpty": "暂无 Observatory 数据",
|
||
"xrayObservatoryHint": "在 xray 配置中添加 observatory 块,列出要探测的出站 tag,然后重启 xray。",
|
||
"xrayObservatoryTagPlaceholder": "选择出站",
|
||
"xrayObservatoryAlive": "在线",
|
||
"xrayObservatoryDead": "离线",
|
||
"xrayObservatoryLastSeen": "最后在线",
|
||
"xrayObservatoryLastTry": "最后尝试",
|
||
"connectionCount": "连接数",
|
||
"ipAddresses": "IP 地址",
|
||
"toggleIpVisibility": "切换 IP 可见性",
|
||
"overallSpeed": "整体速度",
|
||
"upload": "上传",
|
||
"download": "下载",
|
||
"sent": "已发送",
|
||
"received": "已接收",
|
||
"xraySwitchVersionDialog": "您确定要更改 Xray 版本吗?",
|
||
"xraySwitchVersionDialogDesc": "这将把 Xray 版本更改为 #version#。",
|
||
"xraySwitchVersionPopover": "Xray 更新成功",
|
||
"panelUpdateDialog": "您确定要更新面板吗?",
|
||
"panelUpdateDialogDesc": "这将把 3X-UI 更新到 #version# 并重启面板服务。",
|
||
"panelUpdateStartedPopover": "已开始更新面板",
|
||
"panelUpdateFailedTitle": "面板更新失败",
|
||
"panelUpdateFailedDesc": "更新未成功完成。请检查服务器日志,或在命令行运行「x-ui update」。",
|
||
"panelUpdateUnknownTitle": "无法确认更新是否已完成",
|
||
"panelUpdateUnknownDesc": "面板未能及时返回结果。请刷新页面查看当前版本,或检查服务器日志。",
|
||
"geofileUpdateDialog": "您确定要更新地理文件吗?",
|
||
"geofileUpdateDialogDesc": "这将更新 #filename# 文件。",
|
||
"geofilesUpdateDialogDesc": "这将更新所有文件。",
|
||
"geofilesUpdateAll": "全部更新",
|
||
"geofileUpdatePopover": "地理文件更新成功",
|
||
"geodataTitle": "Geodata 自动更新",
|
||
"geodataHint": "Xray 会按计划下载这些文件并热重载,无需重启。URL 必须为 HTTPS。文件必须已存在于 bin 目录中,Xray 才能对其更新。",
|
||
"geodataCron": "更新计划 (cron)",
|
||
"geodataOutbound": "通过出站下载(可选)",
|
||
"geodataFile": "文件名",
|
||
"geodataAddFile": "添加文件",
|
||
"geodataSaveRestart": "保存并重启 Xray",
|
||
"geodataConfirmTitle": "保存 geodata 设置?",
|
||
"geodataConfirmContent": "将更新 Xray 配置模板并重启 Xray。",
|
||
"geodataInvalidUrl": "每个文件都需要 HTTPS 地址。",
|
||
"geodataInvalidFile": "文件名必须是纯文件名,例如 geosite_custom.dat(不能包含路径)。",
|
||
"geodataInvalidCron": "Cron 必须为 5 段,例如 0 4 * * *",
|
||
"geodataEmpty": "尚未配置文件。路由规则中可通过 ext:geosite_custom.dat:category 引用文件。",
|
||
"dontRefresh": "安装中,请勿刷新此页面",
|
||
"logs": "日志",
|
||
"accessLogs": "访问日志",
|
||
"autoUpdate": "自动更新",
|
||
"amneziawgLogs": "AmneziaWG 日志",
|
||
"amneziawgHandshake": "最后握手",
|
||
"amneziawgInterface": "网络接口",
|
||
"amneziawgInbound": "入站",
|
||
"amneziawgEndpoint": "端点",
|
||
"amneziawgIdle": "空闲",
|
||
"amneziawgEvents": "事件",
|
||
"amneziawgNoPeers": "当前没有活动的 AmneziaWG 对端",
|
||
"amneziawgNoEvents": "尚未记录 AmneziaWG 事件",
|
||
"config": "配置",
|
||
"backupTitle": "备份和恢复",
|
||
"exportDatabase": "备份",
|
||
"exportDatabaseDesc": "点击下载包含当前数据库备份的 .db 文件到您的设备。同一文件也可以恢复到运行 PostgreSQL 的面板中。",
|
||
"importDatabase": "恢复",
|
||
"importDatabaseDesc": "点击选择并上传设备中的 .db 备份或迁移导出文件(.dump)以恢复数据库。",
|
||
"importDatabaseSuccess": "数据库导入成功",
|
||
"importDatabaseError": "导入数据库时出错",
|
||
"readDatabaseError": "读取数据库时出错",
|
||
"getDatabaseError": "检索数据库时出错",
|
||
"getConfigError": "检索配置文件时出错",
|
||
"backupPostgresNote": "此面板运行在 PostgreSQL 上。「备份」会下载一个 pg_dump 归档(.dump),「恢复」会通过 pg_restore 重新载入。「恢复」也接受 SQLite 数据库(.db)或 SQLite 迁移导出文件,并将其数据导入 PostgreSQL。服务器需要安装 PostgreSQL 客户端工具(pg_dump 和 pg_restore)。",
|
||
"exportDatabasePgDesc": "点击将当前数据库的 PostgreSQL 转储(.dump)下载到您的设备。",
|
||
"importDatabasePgDesc": "点击选择并上传 PostgreSQL 备份(.dump)、SQLite 数据库(.db)或 SQLite 迁移导出文件以恢复您的数据库。此操作将替换所有当前数据。",
|
||
"migrationDownload": "下载迁移文件",
|
||
"migrationDownloadPgDesc": "点击下载由 PostgreSQL 数据构建的 .db SQLite 数据库,可用于在 SQLite 上运行本面板。",
|
||
"avg": "均值",
|
||
"peak": "峰值",
|
||
"free": "剩余",
|
||
"openSockets": "打开的套接字",
|
||
"throughputSub": "网卡总计",
|
||
"avgWindow": "窗口均值",
|
||
"healthWarm": "{list} — 偏高",
|
||
"healthCritical": "{list} — 危险",
|
||
"panel": "面板",
|
||
"threads": "线程",
|
||
"uptime": "运行时间",
|
||
"logLevelDebug": "Debug",
|
||
"logLevelInfo": "Info",
|
||
"logLevelNotice": "Notice",
|
||
"logLevelWarning": "Warning",
|
||
"logLevelError": "Error",
|
||
"accessDirect": "DIRECT",
|
||
"accessBlocked": "BLOCKED",
|
||
"accessProxy": "PROXY",
|
||
"importKeepHostSettings": "保留本机设置",
|
||
"importKeepHostSettingsDesc": "保留本面板的监听地址、端口、基础路径、证书和节点身份,而不是使用上传文件中的值。"
|
||
},
|
||
"inbounds": {
|
||
"totalDownUp": "总上传 / 下载",
|
||
"totalUsage": "总用量",
|
||
"inboundCount": "入站数量",
|
||
"operate": "菜单",
|
||
"enable": "启用",
|
||
"remark": "备注",
|
||
"node": "节点",
|
||
"deployTo": "部署到",
|
||
"localPanel": "本地面板",
|
||
"fallbacks": {
|
||
"title": "Fallbacks",
|
||
"empty": "暂无回落",
|
||
"add": "添加回落",
|
||
"pickInbound": "选择一个入站",
|
||
"matchAny": "任意",
|
||
"destPlaceholder": "自动(子入站 listen:port)",
|
||
"needsTls": "在“安全”标签页选择 TLS 或 Reality 后即可配置回落(仅限 RAW 上的 VLESS/Trojan)。"
|
||
},
|
||
"protocol": "协议",
|
||
"port": "端口",
|
||
"portMap": "端口映射",
|
||
"traffic": "流量",
|
||
"speed": "速度",
|
||
"expireDate": "到期时间",
|
||
"createdAt": "创建时间",
|
||
"updatedAt": "更新时间",
|
||
"resetTraffic": "重置流量",
|
||
"addInbound": "添加入站",
|
||
"generalActions": "通用操作",
|
||
"modifyInbound": "修改入站",
|
||
"deleteConfirmTitle": "删除入站 \"{remark}\"?",
|
||
"deleteConfirmContent": "将删除此入站及其所有客户端。该操作不可撤销。",
|
||
"resetConfirmTitle": "重置 \"{remark}\" 的流量?",
|
||
"resetConfirmContent": "将此入站的上/下行计数器清零。",
|
||
"selectedCount": "已选 {count} 项",
|
||
"selectAll": "全选",
|
||
"bulkDeleteConfirmTitle": "删除 {count} 个入站?",
|
||
"bulkDeleteConfirmContent": "将删除所选入站及其所有客户端。该操作不可撤销。",
|
||
"cloneConfirmTitle": "克隆入站 \"{remark}\"?",
|
||
"cloneConfirmContent": "使用新端口和空客户端列表创建副本。",
|
||
"delAllClients": "删除所有客户端",
|
||
"delAllClientsConfirmTitle": "从 \"{remark}\" 中删除全部 {count} 个客户端?",
|
||
"delAllClientsConfirmContent": "从此入站中移除每个客户端并丢弃其流量记录。入站本身将保留。此操作无法撤销。",
|
||
"attachClients": "附加客户端到…",
|
||
"addClientsToGroup": "将客户端添加到分组…",
|
||
"attachClientsTitle": "从 “{remark}” 附加客户端",
|
||
"attachClientsDesc": "将相同的 {count} 个客户端(相同 UUID/密码和共享流量)附加到选定的入站。它们仍保留在此入站中。",
|
||
"attachClientsTargets": "目标入站",
|
||
"attachClientsNoTargets": "没有可附加的其他兼容入站。",
|
||
"attachClientsResult": "已附加 {attached},已跳过 {skipped}。",
|
||
"attachClientsResultMixed": "已附加 {attached},已跳过 {skipped},错误 {errors}。",
|
||
"attachClientsSelectLabel": "要附加的客户端",
|
||
"attachClientsSearchPlaceholder": "搜索邮箱或备注",
|
||
"attachClientsStatusDisabled": "已禁用",
|
||
"attachClientsSelectedCount": "已选 {selected}/{total}",
|
||
"attachExistingClients": "附加现有客户端…",
|
||
"attachExistingTitle": "将现有客户端附加到 “{remark}”",
|
||
"attachExistingDesc": "将现有客户端(可用 {count} 个)附加到此入站 — 相同 UUID/密码和共享流量。已在此入站的客户端将被跳过。",
|
||
"attachExistingNoClients": "尚无客户端。请先创建客户端,然后在此附加。",
|
||
"attachExistingStatusAttached": "已附加",
|
||
"detachClients": "分离客户端",
|
||
"detachClientsTitle": "从 “{remark}” 分离客户端",
|
||
"detachClientsDesc": "仅从此入站移除选中的客户端。客户端记录保留(使用 Delete 完全移除)。源共有 {count} 个客户端。",
|
||
"detachClientsResult": "已分离 {detached},已跳过 {skipped}。",
|
||
"detachClientsResultMixed": "已分离 {detached},已跳过 {skipped},错误 {errors}。",
|
||
"detachClientsSelectLabel": "要分离的客户端",
|
||
"exportLinksTitle": "导出入站链接",
|
||
"exportSubsTitle": "导出订阅链接",
|
||
"exportAllLinksTitle": "导出所有入站链接",
|
||
"exportAllSubsTitle": "导出所有订阅链接",
|
||
"exportAllLinksFileName": "所有入站",
|
||
"exportAllSubsFileName": "所有入站-Subs",
|
||
"inboundJsonTitle": "入站 JSON",
|
||
"resetTrafficContent": "确定要重置流量吗?",
|
||
"copyLink": "复制链接",
|
||
"address": "地址",
|
||
"network": "网络",
|
||
"destinationPort": "目标端口",
|
||
"targetAddress": "目标地址",
|
||
"monitorDesc": "留空表示监听所有 IP",
|
||
"meansNoLimit": "= 无限制。(单位: GB)",
|
||
"totalFlow": "总流量",
|
||
"leaveBlankToNeverExpire": "留空表示永不过期",
|
||
"certificatePath": "文件路径",
|
||
"certificateContent": "文件内容",
|
||
"publicKey": "公钥",
|
||
"privatekey": "私钥",
|
||
"client": "客户",
|
||
"export": "导出链接",
|
||
"clone": "克隆",
|
||
"resetAllTraffic": "重置所有入站流量",
|
||
"resetAllTrafficTitle": "重置所有入站流量",
|
||
"resetAllTrafficContent": "确定要重置所有入站流量吗?",
|
||
"email": "邮箱",
|
||
"IPLimit": "IP 限制",
|
||
"IPLimitlog": "IP 日志",
|
||
"IPLimitlogclear": "清除日志",
|
||
"setDefaultCert": "从面板设置证书",
|
||
"setDefaultCertEmpty": "面板尚未配置证书。请先在“设置”中设置。",
|
||
"streamTab": "传输",
|
||
"securityTab": "安全",
|
||
"sniffingTab": "嗅探",
|
||
"sniffingMetadataOnly": "仅元数据",
|
||
"sniffingRouteOnly": "仅路由",
|
||
"sniffingIpsExcluded": "排除的 IP",
|
||
"sniffingDomainsExcluded": "排除的域名",
|
||
"decryption": "解密",
|
||
"encryption": "加密",
|
||
"vlessAuthX25519": "X25519 (native)",
|
||
"vlessAuthMlkem768": "ML-KEM-768 (native)",
|
||
"vlessAuthX25519Xorpub": "X25519 (xorpub)",
|
||
"vlessAuthX25519Random": "X25519 (random)",
|
||
"vlessAuthMlkem768Xorpub": "ML-KEM-768 (xorpub)",
|
||
"vlessAuthMlkem768Random": "ML-KEM-768 (random)",
|
||
"vlessAuthCustom": "自定义",
|
||
"vlessAuthSelected": "已选择:{auth}",
|
||
"vlessAuthGenerate": "生成密钥",
|
||
"vlessAuthGenerateButton": "生成",
|
||
"advanced": {
|
||
"title": "入站 JSON 部分",
|
||
"subtitle": "完整入站 JSON 以及针对 settings、sniffing 和 streamSettings 的专用编辑器。",
|
||
"all": "全部",
|
||
"allHelp": "在单个编辑器中编辑包含所有字段的完整入站对象。",
|
||
"settings": "设置",
|
||
"settingsHelp": "Xray settings 块包装:",
|
||
"sniffing": "Sniffing",
|
||
"sniffingHelp": "Xray sniffing 块包装:",
|
||
"stream": "Stream",
|
||
"streamHelp": "Xray stream 块包装:"
|
||
},
|
||
"subSortIndex": "订阅排序",
|
||
"inboundInfo": "入站信息",
|
||
"exportInbound": "导出入站规则",
|
||
"import": "导入",
|
||
"importInbound": "导入入站规则",
|
||
"periodicTrafficResetTitle": "流量重置",
|
||
"periodicTrafficResetDay": "每月重置日",
|
||
"periodicTrafficReset": {
|
||
"never": "从不",
|
||
"daily": "每日",
|
||
"weekly": "每周",
|
||
"monthly": "每月",
|
||
"hourly": "每小时"
|
||
},
|
||
"toasts": {
|
||
"obtain": "获取",
|
||
"updateSuccess": "更新成功",
|
||
"logCleanSuccess": "日志已清除",
|
||
"inboundUpdateSuccess": "入站连接已成功更新",
|
||
"inboundCreateSuccess": "入站连接已成功创建",
|
||
"bulkDeleted": "已删除 {count} 个入站",
|
||
"bulkDeletedMixed": "已删除 {ok} 个,失败 {failed} 个",
|
||
"clonedMany": "已克隆 {count} 个入站",
|
||
"clonedMixed": "已克隆 {ok} 个,失败 {failed} 个",
|
||
"inboundDeleteSuccess": "入站连接已成功删除",
|
||
"inboundClientAddSuccess": "已添加入站客户端",
|
||
"inboundClientDeleteSuccess": "入站客户端已删除",
|
||
"inboundClientUpdateSuccess": "入站客户端已更新",
|
||
"savedNodeOfflineWillSync": "已在本地保存。某个支撑节点离线或已禁用——重新连接后将同步此更改。",
|
||
"resetAllClientTrafficSuccess": "客户端所有流量已重置",
|
||
"resetAllTrafficSuccess": "所有流量已重置",
|
||
"resetInboundClientTrafficSuccess": "流量已重置",
|
||
"resetInboundTrafficSuccess": "入站流量已重置",
|
||
"trafficGetError": "获取流量数据时出错",
|
||
"getNewX25519CertError": "获取 X25519 证书时出错。",
|
||
"getNewmldsa65Error": "获取 mldsa65 证书时出错。",
|
||
"getNewVlessEncError": "获取 VlessEnc 证书时出错。",
|
||
"scanRealityTargetError": "扫描 REALITY 目标失败。",
|
||
"scanRealityTargetFeasible": "目标可用 — 已填入目标和 SNI。",
|
||
"scanRealityTargetNotFeasible": "目标可达,但不适用于 REALITY。",
|
||
"scanRealityTargetPrivate": "目标可用,但位于内网/本地网络中。",
|
||
"invalidClientField": "客户端 {client}:字段 {field} — {reason}",
|
||
"invalidField": "{field} — {reason}",
|
||
"moreIssues": "{message} (另有 {count} 项)"
|
||
},
|
||
"form": {
|
||
"echSockopt": "ECH Sockopt",
|
||
"echSockoptTip": "Xray 获取 ECH 配置列表时所用连接的 Socket 选项(例如让该查询通过 dialerProxy 出站)。保持禁用则使用默认值。",
|
||
"curvePreferences": "曲线偏好",
|
||
"curvePreferencesTip": "限制服务器提供的 TLS 密钥交换曲线,并按偏好顺序排列(例如 X25519MLKEM768、X25519)。留空则使用 Xray-core 默认值。",
|
||
"masterKeyLog": "主密钥日志",
|
||
"masterKeyLogTip": "写入 TLS 主密钥的路径(SSLKEYLOGFILE 格式),用于配合 Wireshark 调试。生产环境请留空——任何拥有该文件的人都能解密流量。",
|
||
"verifyPeerCertByNameTip": "让客户端以此名称(而非 SNI)验证服务器证书。多个名称用逗号分隔。仅面板使用——会包含在分享链接中(vcn)。这是 allowInsecure 的现代替代方案,Xray 已在 2026-06-01 之后将其移除。",
|
||
"pinFromCert": "从此入站的证书填充",
|
||
"pinFromRemote": "通过 ping SNI 获取哈希(xray tls ping)",
|
||
"pinFromRemoteNoSni": "请先设置 SNI(serverName)才能 ping 远端证书。",
|
||
"pinFromRemoteFailed": "无法获取远端证书哈希。",
|
||
"limitFallback": "限制 Fallback",
|
||
"limitFallbackUpload": "限制 Fallback 上传",
|
||
"limitFallbackDownload": "限制 Fallback 下载",
|
||
"afterBytes": "起始字节数",
|
||
"afterBytesTip": "允许 fallback 以全速运行此字节数,之后开始限速。0 = 从第一个字节起就限速。",
|
||
"bytesPerSec": "每秒字节数",
|
||
"bytesPerSecTip": "在达到阈值后对 fallback 流量施加的速度上限(字节/秒),以防探测者把你的服务器当作通往目标的免费带宽。0 = 无限制(禁用此方向)。",
|
||
"burstBytesPerSec": "突发每秒字节数",
|
||
"burstBytesPerSecTip": "允许在稳定速率之上的短时突发额度(token-bucket 容量)。若低于“每秒字节数”,则会被提升至与之相同。",
|
||
"moveUp": "上移",
|
||
"moveDown": "下移",
|
||
"addAll": "全部添加",
|
||
"addAllFallbackTooltip": "为尚未连接的每个符合条件的入站添加一个 fallback 行",
|
||
"peers": "Peers",
|
||
"addPeer": "添加 peer",
|
||
"keepAlive": "Keep-alive",
|
||
"autoSystemRoutesTooltip": "仅 Windows。CIDR 会自动添加到系统路由表,以便匹配的流量通过 TUN。",
|
||
"autoOutboundsInterface": "自动出站接口",
|
||
"autoOutboundsInterfaceTooltip": "出站流量的物理接口。使用 'auto' 进行检测;设置 Auto system routes 时自动启用。",
|
||
"rewriteAddress": "重写地址",
|
||
"rewritePort": "重写端口",
|
||
"allowedNetwork": "允许的网络",
|
||
"followRedirect": "跟随重定向",
|
||
"accounts": "账户",
|
||
"allowTransparent": "允许透明",
|
||
"encryptionMethod": "加密方法",
|
||
"fakeTlsDomain": "FakeTLS 域名 (SNI)",
|
||
"mtprotoSecret": "密钥",
|
||
"mtgDomainFrontingIp": "域前置 IP",
|
||
"mtgDomainFrontingPort": "域前置端口",
|
||
"mtgDomainFrontingProxyProtocol": "域前置 PROXY 协议",
|
||
"mtgDomainFrontingHint": "mtg 转发非 Telegram 流量的目标——例如你的 NGINX 伪装站点。留空 IP 则通过 DNS 解析 FakeTLS 域名;默认端口为 443。",
|
||
"mtgProxyProtocolListener": "接受 PROXY 协议(监听器)",
|
||
"mtgPreferIp": "IP 优先级",
|
||
"mtgDebug": "调试日志",
|
||
"mtgRouteThroughXray": "通过 Xray 路由",
|
||
"mtgRouteThroughXrayHint": "让此代理的 Telegram 流量经过 Xray,以应用您的路由规则。mtg 附属进程会通过带有此入站标签的本地 SOCKS 桥接出站;在路由选项卡中引用该标签即可设置高级规则。",
|
||
"mtgRouteOutbound": "出站",
|
||
"mtgRouteOutboundHint": "可选。强制 Telegram 流量经由此出站(或负载均衡器)发出。留空则由您的路由规则决定。",
|
||
"mtgRouteOutboundPlaceholder": "使用路由规则",
|
||
"mtprotoFakeTlsDomainHint": "生成新客户端密钥时使用的默认 FakeTLS 域名。每个客户端可使用各自的域名。",
|
||
"mtgThrottleMaxConnections": "最大连接数",
|
||
"mtgThrottleMaxConnectionsHint": "按公平分配限制所有用户的并发连接数。0 表示不限制。",
|
||
"mtgAdTagInvalid": "广告标签必须为 32 位十六进制字符。",
|
||
"mtgPublicIpv4": "公网 IPv4",
|
||
"mtgPublicIpv6": "公网 IPv6",
|
||
"mtgPublicIpHint": "本服务器可访问的公网地址,供广告标签的中间代理使用。留空则由 mtg 自动检测。",
|
||
"visionTestseed": "Vision testseed",
|
||
"version": "版本",
|
||
"udpIdleTimeout": "UDP 空闲超时 (s)",
|
||
"masquerade": "伪装",
|
||
"type": "类型",
|
||
"upstreamUrl": "Upstream URL",
|
||
"rewriteHost": "重写 Host",
|
||
"skipTlsVerify": "跳过 TLS 验证",
|
||
"directory": "目录",
|
||
"statusCode": "状态码",
|
||
"body": "Body",
|
||
"headers": "请求头",
|
||
"proxyProtocol": "Proxy Protocol",
|
||
"requestVersion": "请求版本",
|
||
"requestMethod": "请求方法",
|
||
"requestPath": "请求路径",
|
||
"requestHeaders": "请求头",
|
||
"responseVersion": "响应版本",
|
||
"responseStatus": "响应状态",
|
||
"responseReason": "响应原因",
|
||
"responseHeaders": "响应头",
|
||
"heartbeatPeriod": "心跳周期",
|
||
"serviceName": "服务名",
|
||
"authority": "Authority",
|
||
"multiMode": "多模式",
|
||
"maxBufferedUpload": "最大缓冲上传",
|
||
"maxUploadSize": "最大上传大小 (字节)",
|
||
"streamUpServer": "Stream-Up 服务器",
|
||
"serverMaxHeaderBytes": "服务器最大头字节",
|
||
"paddingBytes": "Padding 字节",
|
||
"uplinkHttpMethod": "Uplink HTTP 方法",
|
||
"paddingObfsMode": "Padding 混淆模式",
|
||
"paddingKey": "Padding Key",
|
||
"paddingHeader": "Padding Header",
|
||
"paddingPlacement": "Padding 位置",
|
||
"paddingMethod": "Padding 方法",
|
||
"sessionPlacement": "Session 位置",
|
||
"sessionKey": "Session Key",
|
||
"sessionIDTable": "会话 ID 字符表",
|
||
"sessionIDTableHint": "生成会话 ID 使用的字符集:预定义名称(ALPHABET、Base62、hex、number 等)或字面 ASCII 字符串。留空则使用 xray-core 默认值。",
|
||
"sessionIDLength": "会话 ID 长度",
|
||
"sessionIDLengthHint": "生成会话 ID 的长度或范围(如 8-16)。仅在设置了会话 ID 字符表时生效;最小值必须大于 0。",
|
||
"sequencePlacement": "Sequence 位置",
|
||
"sequenceKey": "Sequence Key",
|
||
"uplinkDataPlacement": "Uplink 数据位置",
|
||
"uplinkDataKey": "Uplink 数据 Key",
|
||
"noSseHeader": "无 SSE 头",
|
||
"ttiMs": "TTI (ms)",
|
||
"uplinkMbps": "上行 (MB/s)",
|
||
"downlinkMbps": "下行 (MB/s)",
|
||
"cwndMultiplier": "CWND 倍数",
|
||
"maxSendingWindow": "最大发送窗口",
|
||
"externalProxy": "外部代理",
|
||
"forceTls": "强制 TLS",
|
||
"fingerprint": "指纹",
|
||
"defaultOption": "默认",
|
||
"routeMark": "Route Mark",
|
||
"tcpKeepAliveInterval": "TCP Keep Alive 间隔",
|
||
"tcpKeepAliveIdle": "TCP Keep Alive Idle",
|
||
"tcpMaxSeg": "TCP Max Seg",
|
||
"tcpUserTimeout": "TCP User Timeout",
|
||
"tcpWindowClamp": "TCP Window Clamp",
|
||
"tcpWindowClampHint": "留 0 使用操作系统默认值。非零值会限制通告的 TCP 接收窗口;像 600 这样的值(来自 Xray 文档示例)在高延迟链路上可能导致吞吐量骤降。",
|
||
"tcpFastOpen": "TCP Fast Open",
|
||
"multipathTcp": "Multipath TCP",
|
||
"penetrate": "Penetrate",
|
||
"v6Only": "仅 V6",
|
||
"tcpCongestion": "TCP Congestion",
|
||
"dialerProxy": "Dialer Proxy",
|
||
"trustedXForwardedFor": "可信 X-Forwarded-For",
|
||
"trustedXForwardedForHint": "信任此请求头来获取真实客户端 IP(例如 Cloudflare CDN 后的 CF-Connecting-IP)。仅在 WebSocket、HTTPUpgrade、XHTTP 和 gRPC 传输上生效。留空则忽略转发头。",
|
||
"proxyProtocolHint": "接受 PROXY protocol 头,从上游 L4 隧道或中继(HAProxy、gost、nginx-stream、Xray dokodemo-door)或 Cloudflare Spectrum 获取真实客户端 IP。上游必须发送 PROXY protocol。适用于 TCP、WebSocket、HTTPUpgrade 和 gRPC;不适用于 mKCP。",
|
||
"realClientIp": "真实客户端 IP",
|
||
"realClientIpHint": "当流量通过 CDN 或中继到达此入站时,获取访客的真实 IP,而不是记录中间节点的地址。选择一个预设以自动填写下方对应的 sockopt 字段。这些字段绝不会在订阅中发送给客户端。",
|
||
"realClientIpPresetOff": "关闭 / 直连",
|
||
"realClientIpPresetCloudflare": "Cloudflare CDN",
|
||
"realClientIpPresetProxyProtocol": "L4 中继 / Spectrum (PROXY)",
|
||
"realClientIpTrustedHeaderTransportWarn": "Trusted X-Forwarded-For 仅在 WebSocket、HTTPUpgrade 和 XHTTP 上生效。在当前传输上此请求头将被忽略。",
|
||
"realClientIpProxyProtocolTransportWarn": "PROXY protocol 不支持此传输(mKCP)。请使用 TCP/RAW、WebSocket、HTTPUpgrade、gRPC 或 XHTTP。",
|
||
"addressPortStrategy": "地址+端口策略",
|
||
"tryDelayMs": "尝试延迟 (ms)",
|
||
"prioritizeIPv6": "IPv6 优先",
|
||
"interleave": "Interleave",
|
||
"maxConcurrentTry": "最大并发尝试",
|
||
"customSockopt": "自定义 sockopt",
|
||
"addCustomOption": "添加自定义选项",
|
||
"serverNameIndication": "SNI",
|
||
"cipherSuites": "Cipher Suites",
|
||
"autoOption": "自动",
|
||
"minMaxVersion": "最小/最大版本",
|
||
"rejectUnknownSni": "拒绝未知 SNI",
|
||
"disableSystemRoot": "禁用系统根",
|
||
"sessionResumption": "会话恢复",
|
||
"oneTimeLoading": "一次性加载",
|
||
"usageOption": "使用选项",
|
||
"buildChain": "构建证书链",
|
||
"echKey": "ECH key",
|
||
"echConfig": "ECH 配置",
|
||
"pinnedPeerCertSha256": "固定对端证书 SHA-256",
|
||
"pinnedPeerCertSha256Tip": "对端证书的 SHA-256 哈希(十六进制字符串,如 e8e2d3…),逗号分隔。仅面板使用 — 不写入服务器的 xray 配置,但会包含在分享链接中,以便客户端固定证书。",
|
||
"pinnedPeerCertSha256Placeholder": "十六进制哈希,逗号分隔",
|
||
"getNewEchCert": "获取新 ECH 证书",
|
||
"show": "显示",
|
||
"xver": "Xver",
|
||
"target": "目标",
|
||
"maxTimeDiff": "最大时间差 (ms)",
|
||
"minClientVer": "最小客户端版本",
|
||
"maxClientVer": "最大客户端版本",
|
||
"minClientVerHint": "留空不等于不限制:Xray-core 会改用所运行内核版本的内置最低值(当前版本为 26.3.27),拒绝自报版本更低的客户端——包括 Mihomo、sing-box 等第三方内核。填 1.0.0 可放行它们,代价是允许过时的 TLS 指纹。",
|
||
"maxClientVerHint": "留空表示无上限。若填写,不得低于实际生效的下限——最小客户端版本,该字段留空时则为 Xray-core 的内置最低值——否则所有客户端都会被拒绝。",
|
||
"clientVerInvalid": "客户端版本须为最多三段以点分隔的数字,每段 0-255(例如 26.3.27)",
|
||
"maxClientVerBelowMin": "最大客户端版本不得低于最小客户端版本",
|
||
"shortIds": "Short IDs",
|
||
"realityTargetHint": "必填。必须包含端口(例如 example.com:443)。没有端口时 Xray-core 将无法启动。",
|
||
"realityTargetRequired": "REALITY 目标为必填项",
|
||
"realityTargetNeedsPort": "REALITY 目标必须包含端口(例如 example.com:443)",
|
||
"realityTargetInvalidPort": "REALITY 目标的端口无效",
|
||
"scan": "扫描",
|
||
"findTargets": "查找目标",
|
||
"scanModalTitle": "REALITY 目标扫描器",
|
||
"scanModalDesc": "验证某个域名,或扫描 IP / CIDR 范围,从证书中发现新的 REALITY 目标。留空则探测常用候选。",
|
||
"scanDiscoverPlaceholder": "IP、CIDR 或域名 — 留空使用常用候选",
|
||
"scanStatus": "状态",
|
||
"scanFeasible": "可用",
|
||
"scanNotFeasible": "不可用",
|
||
"scanCurve": "密钥交换",
|
||
"scanCert": "证书",
|
||
"scanCertInvalid": "不受信任",
|
||
"scanCertExpiry": "证书有效期至",
|
||
"scanSniUsed": "使用的 SNI",
|
||
"scanPrivateNote": "已通过内网/本地网络检测 — 该地址无法从互联网访问。",
|
||
"scanPrivateConfirmTitle": "目标位于本地网络",
|
||
"scanPrivateConfirmContent": "“{target}”解析到内网或回环地址。本次检测将仅为此探测跳过面板的 SSRF 防护。是否继续?",
|
||
"scanLatency": "延迟",
|
||
"scanUse": "使用",
|
||
"scanRescan": "重新扫描",
|
||
"spiderX": "SpiderX",
|
||
"spiderXHint": "按客户端的种子——面板据此为每个客户端派生唯一的 spx 路径;重新生成可轮换所有客户端的路径",
|
||
"getNewCert": "获取新证书",
|
||
"mldsa65Seed": "mldsa65 Seed",
|
||
"mldsa65Verify": "mldsa65 Verify",
|
||
"getNewSeed": "获取新 Seed",
|
||
"listenHelp": "也可以填写 Unix socket 路径(例如 /run/xray/in.sock),或以 @ 为前缀的抽象套接字名称(例如 @xray/in.sock),以使用套接字而非 TCP 端口监听——此时请将端口设为 0。",
|
||
"shareAddrStrategy": "分享地址策略",
|
||
"shareAddrStrategyHelp": "控制导出分享链接、二维码和订阅输出时写入哪个地址。",
|
||
"shareAddr": "自定义分享地址",
|
||
"shareAddrHelp": "仅在分享地址策略为自定义时使用。填写不带协议和端口的域名或 IP。",
|
||
"subSortIndex": "订阅排序",
|
||
"subSortIndexHelp": "此入站的链接在订阅输出(订阅页面和客户端应用)中的位置。数值越小越靠前;数值相同时保持创建顺序。不影响面板中的入站列表。",
|
||
"disableFlow": "禁用 XTLS flow",
|
||
"disableFlowHelp": "让此入站跳过自动注入 xtls-rprx-vision,即使其传输支持 flow(例如启用 VLESS 加密的隧道化 XHTTP 入站)。客户端在同一订阅中的其他可用入站上仍保留 Vision。仅限 VLESS。",
|
||
"shareAddrStrategyOptions": {
|
||
"node": "节点地址",
|
||
"listen": "入站监听地址",
|
||
"custom": "自定义"
|
||
},
|
||
"verifyPeerCertByName": "按名称验证对端证书"
|
||
},
|
||
"info": {
|
||
"mode": "模式",
|
||
"grpcServiceName": "grpc serviceName",
|
||
"grpcMultiMode": "grpc multiMode",
|
||
"interfaceName": "接口名称",
|
||
"mtu": "MTU",
|
||
"gateway": "Gateway",
|
||
"dns": "DNS",
|
||
"outboundsInterface": "出站接口",
|
||
"autoSystemRoutes": "自动系统路由",
|
||
"followRedirect": "FollowRedirect",
|
||
"auth": "认证",
|
||
"noKernelTun": "非内核 TUN",
|
||
"keepAlive": "Keep alive",
|
||
"peerNumber": "Peer {n}",
|
||
"peerNumberConfig": "Peer {n} 配置"
|
||
},
|
||
"sniffingDestOverride": "目标覆盖"
|
||
},
|
||
"clients": {
|
||
"tabBasics": "基本",
|
||
"tabCredentials": "凭据",
|
||
"tabLinks": "链接",
|
||
"wireguardConfig": "WireGuard 配置",
|
||
"config": "配置",
|
||
"linksHint": "添加第三方分享链接和远程订阅地址,将其包含在该客户端的订阅中。",
|
||
"addExternalLink": "添加外部链接",
|
||
"addExternalSubscription": "添加外部订阅",
|
||
"noExternalLinks": "暂无外部链接。",
|
||
"noExternalSubscriptions": "暂无外部订阅。",
|
||
"namePrefix": "名称前缀",
|
||
"lastFetchAt": "最后拉取",
|
||
"lastFetchError": "拉取失败",
|
||
"neverFetched": "尚未拉取",
|
||
"submitEdit": "保存更改",
|
||
"clientCount": "客户端数量",
|
||
"bulk": "批量添加",
|
||
"selectAll": "全选",
|
||
"clearAll": "全部清除",
|
||
"method": "方式",
|
||
"first": "首个",
|
||
"last": "末位",
|
||
"ipLog": "IP 日志",
|
||
"prefix": "前缀",
|
||
"postfix": "后缀",
|
||
"delayedStart": "首次使用后开始",
|
||
"expireDays": "时长 (天)",
|
||
"renew": "自动续期",
|
||
"renewDesc": "到期后自动续期。(0 = 禁用) (单位: 天)",
|
||
"renewDays": "自动续期 (天)",
|
||
"searchPlaceholder": "搜索邮箱、备注、sub ID、UUID、密码、auth、Telegram ID…",
|
||
"filterTitle": "筛选客户端",
|
||
"clearAllFilters": "清除全部",
|
||
"filters": {
|
||
"nodes": "节点",
|
||
"localPanel": "本机(此面板)"
|
||
},
|
||
"showingCount": "显示 {shown} / {total}",
|
||
"sortOldest": "最旧优先",
|
||
"sortNewest": "最新优先",
|
||
"sortRecentlyUpdated": "最近更新",
|
||
"sortRecentlyOnline": "最近在线",
|
||
"sortEmailAZ": "邮箱 A→Z",
|
||
"sortEmailZA": "邮箱 Z→A",
|
||
"sortMostTraffic": "流量最多",
|
||
"sortHighestRemaining": "剩余最多",
|
||
"sortExpiringSoonest": "即将过期",
|
||
"has": "拥有",
|
||
"hasNot": "不拥有",
|
||
"actions": "操作",
|
||
"totalGB": "流量上限 (GB)",
|
||
"totalGBDesc": "该客户端的流量配额。0 = 不限制。",
|
||
"expiryTime": "过期时间",
|
||
"addClients": "添加客户端",
|
||
"limitIp": "IP 限制",
|
||
"limitIpDesc": "最大同时连接 IP 数。0 = 不限制。",
|
||
"limitHwid": "HWID 限制",
|
||
"limitHwidDesc": "订阅请求最多可注册的设备数。0 = 不限制。",
|
||
"hwidLog": "HWID 设备",
|
||
"hwidDevice": "已注册设备",
|
||
"noHwids": "暂无 HWID 设备",
|
||
"firstSeen": "首次出现",
|
||
"lastSeen": "最后出现",
|
||
"deleteHwid": "移除设备",
|
||
"deleteHwidConfirm": "移除此设备?下次获取订阅时它将需要重新注册。",
|
||
"hwidDeleted": "设备已移除。",
|
||
"clearHwidsConfirm": "移除所有已注册的设备?每台设备在下次获取订阅时都需要重新注册。",
|
||
"limitIpFail2banMissing": "未安装 Fail2ban,无法实施 IP 限制。请从 x-ui 命令行菜单安装 Fail2ban 以启用此选项。",
|
||
"limitIpFail2banWindows": "Windows 上不支持 Fail2ban,无法实施 IP 限制。",
|
||
"limitIpDisabled": "此服务器已禁用 IP 限制功能。",
|
||
"password": "密码",
|
||
"passwordDesc": "仅 Trojan 和 Shadowsocks 客户端使用;VLESS、VMess、Hysteria 和 WireGuard 会忽略此项。",
|
||
"subId": "订阅 ID",
|
||
"online": "在线",
|
||
"email": "邮箱",
|
||
"emailInvalidChars": "邮箱不能包含空格、'/'、'\\' 或控制字符",
|
||
"subIdInvalidChars": "订阅 ID 不能包含空格、'/'、'\\' 或控制字符",
|
||
"group": "分组",
|
||
"groupDesc": "用于对相关客户端进行分桶的逻辑标签(如团队、客户、地区)。可从工具栏筛选。",
|
||
"groupPlaceholder": "如 customer-a",
|
||
"comment": "备注",
|
||
"traffic": "流量",
|
||
"speed": "速度",
|
||
"offline": "离线",
|
||
"addClient": "添加客户端",
|
||
"qrCode": "二维码",
|
||
"clientInfo": "客户端信息",
|
||
"editClient": "编辑客户端",
|
||
"client": "客户端",
|
||
"enabled": "已启用",
|
||
"remaining": "剩余",
|
||
"duration": "时长",
|
||
"attachedInbounds": "关联入站",
|
||
"selectInbound": "选择一个或多个入站",
|
||
"selectAllInbounds": "全选",
|
||
"clearAllInbounds": "全部清除",
|
||
"noSubId": "该客户端没有 subId,无法生成共享链接。",
|
||
"noLinks": "没有可共享的链接 — 请先将此客户端关联到支持协议的入站。",
|
||
"link": "链接",
|
||
"resetNotPossible": "请先将此客户端关联到入站。",
|
||
"resetAllTraffics": "重置所有客户端流量",
|
||
"resetAllTrafficsTitle": "重置所有客户端流量?",
|
||
"resetAllTrafficsContent": "所有客户端的上下行计数器将归零。配额与过期时间不受影响。该操作不可撤销。",
|
||
"deleteConfirmTitle": "删除客户端 {email}?",
|
||
"deleteConfirmContent": "将从所有关联入站中移除该客户端并删除其流量记录。该操作不可撤销。",
|
||
"adjustSelected": "调整 ({count})",
|
||
"subLinksSelected": "订阅链接 ({count})",
|
||
"addToGroupTitle": "将 {count} 个客户端添加到分组",
|
||
"addToGroupTooltip": "选择现有分组或输入新名称。使用 Ungroup 操作从当前分组移除客户端。",
|
||
"groupName": "分组名称",
|
||
"addToGroupSuccessToast": "已将 {count} 个客户端添加到 {group}",
|
||
"ungroupSuccessToast": "已清除 {count} 个客户端的分组",
|
||
"ungroup": "取消分组",
|
||
"ungroupConfirmTitle": "将 {count} 个客户端从其分组中移除?",
|
||
"ungroupConfirmContent": "清除每个选中客户端的分组标签。客户端本身保留(使用 Delete 完全移除)。",
|
||
"addToGroup": "添加到分组",
|
||
"attach": "附加",
|
||
"adjust": "调整",
|
||
"subLinks": "订阅链接",
|
||
"enable": "启用",
|
||
"disable": "禁用",
|
||
"bulkEnableConfirmTitle": "启用 {count} 个客户端?",
|
||
"bulkEnableConfirmContent": "在每个已附加的入站上启用所选的客户端。配额已用尽或已过期的客户端将被自动重新禁用。",
|
||
"bulkDisableConfirmTitle": "禁用 {count} 个客户端?",
|
||
"bulkDisableConfirmContent": "在每个已附加的入站上禁用所选的客户端。他们将立即失去访问权限,但其记录和流量将被保留。",
|
||
"selectedCount": "已选 {count} 项",
|
||
"attachToInboundsTitle": "将 {count} 个客户端附加到入站",
|
||
"attachToInboundsDesc": "将选中的 {count} 个客户端(相同 UUID/密码和共享流量)附加到选定的入站。它们保留现有的附加关系。",
|
||
"attachToInboundsTargets": "目标入站",
|
||
"attachToInboundsNoTargets": "没有可用于附加的多用户入站。",
|
||
"detach": "分离",
|
||
"detachFromInboundsTitle": "从入站分离 {count} 个客户端",
|
||
"detachFromInboundsDesc": "从选定的入站中移除选中的 {count} 个客户端。客户端未附加的配对将被静默跳过。客户端记录保留(使用 Delete 完全移除)。",
|
||
"detachFromInboundsTargets": "要分离的入站",
|
||
"detachFromInboundsNoTargets": "没有可用的多用户入站。",
|
||
"detachFromInboundsResult": "已分离 {detached},已跳过 {skipped}。",
|
||
"detachFromInboundsResultMixed": "已分离 {detached},已跳过 {skipped},错误 {errors}。",
|
||
"subLinksTitle": "订阅链接 ({count})",
|
||
"subLinkColumn": "订阅 URL",
|
||
"subJsonLinkColumn": "订阅 JSON URL",
|
||
"subLinksCopyAll": "全部复制",
|
||
"subLinksCopiedAll": "已复制 {count} 条链接",
|
||
"subLinksEmpty": "选中的客户端均无订阅 ID。",
|
||
"subLinksDisabled": "订阅服务已禁用。",
|
||
"subLinksDisabledHint": "在面板设置 → 订阅中启用订阅以生成链接。",
|
||
"bulkDeleteConfirmTitle": "删除 {count} 个客户端?",
|
||
"bulkDeleteConfirmContent": "每个所选客户端都会从关联的入站中被移除,其流量记录也会被删除。该操作不可撤销。",
|
||
"bulkAdjustTitle": "调整 {count} 个客户端",
|
||
"bulkAdjustHint": "正值延长,负值减少。具有无限期限或流量的客户端将跳过该字段。",
|
||
"bulkAdjustNothing": "应用前请设置天数或流量。",
|
||
"addDays": "添加天数",
|
||
"addTrafficGB": "添加流量 (GB)",
|
||
"bulkFlow": "设置 flow",
|
||
"bulkFlowNoChange": "不更改",
|
||
"bulkFlowDisable": "禁用(清除 flow)",
|
||
"delDepleted": "删除已耗尽",
|
||
"delDepletedConfirmTitle": "删除已耗尽的客户端?",
|
||
"delDepletedConfirmContent": "删除所有流量配额已用尽或已过期的客户端。该操作不可撤销。",
|
||
"exportClients": "导出客户端",
|
||
"importClients": "导入客户端",
|
||
"import": "导入",
|
||
"delOrphans": "删除未关联的客户端",
|
||
"delOrphansConfirmTitle": "删除没有入站的客户端?",
|
||
"delOrphansConfirmContent": "删除所有未关联到任何入站的客户端及其流量记录。该操作不可撤销。",
|
||
"auth": "认证",
|
||
"hysteriaAuth": "Hysteria 认证",
|
||
"hysteriaAuthDesc": "仅 Hysteria 客户端使用的凭据。Trojan 和 Shadowsocks 请改用上方的“密码”字段。",
|
||
"uuid": "UUID",
|
||
"flow": "Flow",
|
||
"vmessSecurity": "VMess 加密",
|
||
"wireguardPrivateKey": "WireGuard 私钥",
|
||
"wireguardPublicKey": "WireGuard 公钥",
|
||
"wireguardPreSharedKey": "WireGuard 预共享密钥",
|
||
"wireguardAllowedIPs": "WireGuard 允许的 IP",
|
||
"wireguardAllowedIPsHint": "留空则自动分配;多个条目用逗号分隔",
|
||
"amneziaWgPrivateKey": "AmneziaWG 私钥",
|
||
"amneziaWgPublicKey": "AmneziaWG 公钥",
|
||
"amneziaWgPreSharedKey": "AmneziaWG 预共享密钥",
|
||
"amneziaWgAllowedIPs": "AmneziaWG 允许的 IP",
|
||
"amneziaWgAllowedIPsHint": "留空则自动分配;多个条目用逗号分隔",
|
||
"amneziaWgForwardedPorts": "转发端口",
|
||
"amneziaWgForwardedPortsHint": "转发到此客户端的端口/范围,例如 80, 443, 8000-8100。留空则不转发。",
|
||
"amneziaWgConfig": "AmneziaWG 配置",
|
||
"mtprotoSecret": "MTProto 密钥",
|
||
"mtprotoSecretHint": "该客户端的 FakeTLS 密钥。重新生成即可更换。",
|
||
"mtprotoAdTag": "广告标签(赞助频道)",
|
||
"mtprotoAdTagHint": "可选的 32 位十六进制标签,从 Telegram 代理注册处获取。设置后,该客户端将通过 Telegram 中间代理路由,赞助频道会显示在其聊天列表顶部。",
|
||
"reverseTag": "反向标签",
|
||
"reverseTagPlaceholder": "可选 Reverse tag",
|
||
"telegramId": "Telegram 用户 ID",
|
||
"telegramIdPlaceholder": "数字形式的 Telegram 用户 ID (0 = 无)",
|
||
"ipLimit": "IP 限制",
|
||
"toasts": {
|
||
"deleted": "客户端已删除",
|
||
"trafficReset": "流量已重置",
|
||
"allTrafficsReset": "所有客户端流量已重置",
|
||
"bulkDeleted": "已删除 {count} 个客户端",
|
||
"bulkDeletedMixed": "已删除 {ok} 个,失败 {failed} 个",
|
||
"bulkEnabled": "已启用 {count} 个客户端",
|
||
"bulkEnabledMixed": "已启用 {ok} 个,失败 {failed} 个",
|
||
"bulkDisabled": "已禁用 {count} 个客户端",
|
||
"bulkDisabledMixed": "已禁用 {ok} 个,失败 {failed} 个",
|
||
"bulkCreated": "已创建 {count} 个客户端",
|
||
"bulkCreatedMixed": "已创建 {ok} 个,失败 {failed} 个",
|
||
"bulkAdjusted": "已调整 {count} 个客户端",
|
||
"bulkAdjustedMixed": "已调整 {ok} 个,跳过 {skipped} 个",
|
||
"delDepleted": "已删除 {count} 个已耗尽的客户端",
|
||
"delOrphans": "已删除 {count} 个未关联的客户端",
|
||
"imported": "已导入 {count} 个客户端",
|
||
"importedMixed": "已导入 {ok} 个,跳过 {failed} 个"
|
||
},
|
||
"renewMax": "最大续期次数",
|
||
"renewMaxDesc": "自动续期最多可触发的次数,达到后客户端将自然到期。填 0 表示不限制。补齐多个错过的周期时,每个周期消耗一次续期。",
|
||
"renewOnDay": "按日期续期",
|
||
"renewOnDayDesc": "每个自然月的这一天午夜(按面板时区)续期,而不是每 N 天续期一次。若当月没有该日期,则在当月最后一天续期。填 0 保持按天间隔模式。",
|
||
"renewsUsed": "已用续期次数"
|
||
},
|
||
"groups": {
|
||
"name": "名称",
|
||
"clientCount": "客户端",
|
||
"totalGroups": "分组总数",
|
||
"totalGroupedClients": "有分组的客户端",
|
||
"trafficUsed": "已用流量",
|
||
"upload": "上传",
|
||
"download": "下载",
|
||
"totalTraffic": "总流量",
|
||
"totalUpDown": "总上传 / 下载",
|
||
"addGroup": "添加分组",
|
||
"createSuccess": "已创建分组 “{name}”。",
|
||
"rename": "重命名",
|
||
"renameTitle": "重命名 {name}",
|
||
"renameCollision": "已存在名为 “{name}” 的分组。",
|
||
"renameSuccess": "已为 {count} 个客户端重命名分组。",
|
||
"deleteConfirmTitle": "删除分组 {name}?",
|
||
"deleteConfirmContent": "这将删除分组并清除 {count} 个客户端的标签。客户端本身不会被删除。",
|
||
"deleteSuccess": "已清除 {count} 个客户端的分组。",
|
||
"resetTraffic": "重置流量",
|
||
"resetConfirmTitle": "重置分组 {name} 的流量?",
|
||
"resetConfirmContent": "这只会清零该分组的流量计数器,不影响各个客户端的计数器。",
|
||
"resetSuccess": "已重置分组 {name} 的流量。",
|
||
"adjustSuccess": "已调整 {name} 中的 {count} 个客户端。",
|
||
"emptyForAction": "此分组尚无客户端。",
|
||
"deleteGroupOnly": "删除分组(保留客户端)",
|
||
"deleteClients": "删除分组中的客户端",
|
||
"deleteClientsConfirmTitle": "删除 {name} 中的所有客户端?",
|
||
"deleteClientsConfirmContent": "这将永久删除 {count} 个客户端及其流量记录。分组标签也会被清除。此操作无法撤销。",
|
||
"deleteClientsSuccess": "已删除 {count} 个客户端。",
|
||
"deleteClientsMixed": "已删除 {ok},已跳过 {failed}",
|
||
"addToGroup": "添加客户端…",
|
||
"addToGroupTitle": "添加客户端到分组 “{name}”",
|
||
"addToGroupDesc": "选择要添加到此分组的客户端。保留其现有入站附加;仅更改分组标签。已在此分组中的客户端不会列出。",
|
||
"addToGroupEmpty": "没有其他可添加的客户端。",
|
||
"addToGroupResult": "已将 {count} 个客户端添加到 {name}。",
|
||
"removeFromGroup": "移除客户端…",
|
||
"removeFromGroupTitle": "从分组 “{name}” 移除客户端",
|
||
"removeFromGroupDesc": "选择要从此分组中移除的成员。客户端本身保留(使用 “删除分组中的客户端” 完全移除)。",
|
||
"removeFromGroupResult": "已从 {name} 移除 {count} 个客户端。"
|
||
},
|
||
"nodes": {
|
||
"addNode": "添加节点",
|
||
"editNode": "编辑节点",
|
||
"totalNodes": "节点总数",
|
||
"onlineNodes": "在线",
|
||
"offlineNodes": "离线",
|
||
"avgLatency": "平均延迟",
|
||
"name": "名称",
|
||
"namePlaceholder": "例如:de-frankfurt-1",
|
||
"addressPlaceholder": "panel.example.com 或 1.2.3.4",
|
||
"remark": "备注",
|
||
"scheme": "协议",
|
||
"address": "地址",
|
||
"port": "端口",
|
||
"basePath": "基础路径",
|
||
"apiToken": "API 令牌",
|
||
"apiTokenPlaceholder": "远程面板设置页中的令牌",
|
||
"apiTokenHint": "远程面板在 安全设定 → API 令牌 中显示其 API 令牌。",
|
||
"apiTokenKeepHint": "留空以保留当前令牌",
|
||
"allowPrivateAddress": "允许私有地址",
|
||
"allowPrivateAddressHint": "仅对私有网络或 VPN 上的节点启用。",
|
||
"outboundTag": "连接出站",
|
||
"outboundTagHint": "通过选定的 Xray 出站路由此节点的面板 API 流量。系统会自动将回环桥接入站添加到运行配置并实时应用。留空表示直接连接。",
|
||
"outboundTagPlaceholder": "直接连接",
|
||
"inboundSyncMode": "入站导入",
|
||
"inboundSyncModeHint": "选择要从此节点导入的入站。现有节点默认导入全部入站。",
|
||
"allInbounds": "全部入站",
|
||
"selectedInbounds": "选定的入站",
|
||
"inboundTags": "入站",
|
||
"inboundTagsHint": "按入站标签匹配。空选择不会导入任何入站。",
|
||
"inboundTagsPlaceholder": "加载并选择入站",
|
||
"loadInbounds": "从节点加载入站",
|
||
"inboundsLoaded": "已加载 {{count}} 个入站",
|
||
"inboundsLoadFailed": "加载入站失败",
|
||
"enable": "已启用",
|
||
"status": "状态",
|
||
"cpu": "CPU",
|
||
"mem": "内存",
|
||
"netUp": "网络上行 (KB/s)",
|
||
"netDown": "网络下行 (KB/s)",
|
||
"uptime": "运行时长",
|
||
"latency": "延迟",
|
||
"lastHeartbeat": "上次心跳",
|
||
"xrayVersion": "Xray 版本",
|
||
"panelVersion": "面板版本",
|
||
"actions": "操作",
|
||
"probe": "立即探测",
|
||
"updatePanel": "更新面板",
|
||
"updateSelected": "更新所选 ({count})",
|
||
"updateAvailable": "有可用更新",
|
||
"updateConfirmTitle": "将 {count} 个节点更新到最新版本?",
|
||
"updateConfirmContent": "每个所选节点会下载最新版本并重启。仅更新已启用且在线的节点。",
|
||
"updateDevChannel": "更新到开发通道(最新提交)",
|
||
"testConnection": "测试连接",
|
||
"connectionOk": "连接正常 ({ms} ms)",
|
||
"connectionFailed": "连接失败",
|
||
"never": "从未",
|
||
"justNow": "刚刚",
|
||
"subNode": "子节点",
|
||
"subNodeTip": "只读:通过 {parent} 接入的下游节点。请在 {parent} 自己的面板中管理。",
|
||
"deleteConfirmTitle": "删除节点 \"{name}\"?",
|
||
"deleteConfirmContent": "这将停止监控该节点。远程面板本身不受影响。",
|
||
"statusValues": {
|
||
"online": "在线",
|
||
"offline": "离线",
|
||
"unknown": "未知",
|
||
"xrayError": "Xray 错误",
|
||
"xrayStopped": "已停止"
|
||
},
|
||
"toasts": {
|
||
"list": "加载节点失败",
|
||
"obtain": "加载节点失败",
|
||
"add": "添加节点",
|
||
"update": "更新节点",
|
||
"delete": "删除节点",
|
||
"deleted": "节点已删除",
|
||
"test": "测试连接",
|
||
"fillRequired": "名称、地址、端口和 API 令牌为必填项",
|
||
"probeFailed": "探测失败",
|
||
"updateStarted": "已开始更新面板",
|
||
"updateResult": "已在 {ok} 个节点上触发更新,{failed} 个失败",
|
||
"updateNoneEligible": "请至少选择一个在线且已启用的节点",
|
||
"saveMtls": "保存节点 mTLS",
|
||
"reloadMtls": "Reload master mTLS credential"
|
||
},
|
||
"tlsVerifyMode": "TLS 校验",
|
||
"tlsVerifyModeHint": "面板如何校验节点的 HTTPS 证书。固定或跳过用于自签名证书(仅 https 节点)。",
|
||
"tlsVerify": "校验(默认 CA)",
|
||
"tlsPin": "固定证书(SHA-256)",
|
||
"tlsSkip": "跳过校验",
|
||
"tlsMtls": "双向 TLS(客户端证书)",
|
||
"mtlsFormHint": "此节点使用客户端证书对面板进行认证。请从“节点 mTLS”区域复制本面板的 CA 到该节点,设置其受信任的 CA,然后重启该节点。",
|
||
"mtls": {
|
||
"title": "节点 mTLS",
|
||
"intro": "双向 TLS 在节点间调用的 API 令牌之上增加客户端证书认证。此为可选项:留空则仅使用令牌认证。",
|
||
"copyCa": "复制此面板的 CA",
|
||
"copyCaHint": "将此 CA 提供给本面板管理的节点,然后将它们的 TLS 校验设置为双向 TLS。",
|
||
"caCopied": "CA 证书已复制到剪贴板",
|
||
"caFailed": "获取 CA 证书失败",
|
||
"trustLabel": "受信任的上级 CA",
|
||
"trustHint": "当本面板自身作为节点时,将管理它的面板的 CA 粘贴到此处以要求其客户端证书。重启面板后生效。",
|
||
"trustPlaceholder": "-----BEGIN CERTIFICATE-----",
|
||
"save": "保存受信任的 CA",
|
||
"saved": "受信任的 CA 已保存 — 重启面板后生效"
|
||
},
|
||
"tlsSkipWarning": "跳过校验会失去对中间人攻击的防护,API 令牌可能被截获。建议改用固定证书。",
|
||
"pinnedCert": "固定证书的 SHA-256",
|
||
"pinnedCertHint": "节点证书的 SHA-256(base64 或 hex)。点击“获取”可立即从节点读取。",
|
||
"pinnedCertPlaceholder": "base64 或 hex 的 SHA-256",
|
||
"fetchPin": "获取",
|
||
"pinFetched": "已获取节点当前证书",
|
||
"pinFetchFailed": "无法获取证书"
|
||
},
|
||
"settings": {
|
||
"defaultTag": "默认",
|
||
"title": "面板设置",
|
||
"save": "保存",
|
||
"infoDesc": "此处的所有更改都需要保存并重启面板才能生效",
|
||
"restartPanel": "重启面板",
|
||
"restartPanelDesc": "确定要重启面板吗?若重启后无法访问面板,请前往服务器查看面板日志信息",
|
||
"restartPanelSuccess": "面板已成功重启",
|
||
"actions": "操作",
|
||
"resetDefaultConfig": "重置为默认配置",
|
||
"panelSettings": "常规",
|
||
"securitySettings": "安全设定",
|
||
"securityWarnings": "安全警告",
|
||
"panelExposed": "您的面板可能已暴露:",
|
||
"warnHttp": "面板通过明文 HTTP 提供服务 — 生产环境请配置 TLS。",
|
||
"warnDefaultPort": "默认端口 2053 众所周知 — 请更改为随机端口。",
|
||
"warnDefaultBasePath": "默认根路径 \"/\" 众所周知 — 请更改为随机路径。",
|
||
"warnDefaultSubPath": "默认订阅路径 \"/sub/\" 众所周知 — 请更改。",
|
||
"warnDefaultJsonPath": "默认 JSON 订阅路径 \"/json/\" 众所周知 — 请更改。",
|
||
"TGBotSettings": "Telegram 机器人",
|
||
"panelListeningIP": "面板监听 IP",
|
||
"panelListeningIPDesc": "默认留空监听所有 IP",
|
||
"panelListeningDomain": "面板监听域名",
|
||
"panelListeningDomainDesc": "默认情况下留空以监视所有域名和 IP 地址",
|
||
"panelPort": "面板监听端口",
|
||
"panelPortDesc": "重启面板生效",
|
||
"publicKeyPath": "面板证书公钥文件路径",
|
||
"publicKeyPathDesc": "填写一个 '/' 开头的绝对路径",
|
||
"privateKeyPath": "面板证书密钥文件路径",
|
||
"privateKeyPathDesc": "填写一个 '/' 开头的绝对路径",
|
||
"panelUrlPath": "URI 路径",
|
||
"panelUrlPathDesc": "必须以 '/' 开头,以 '/' 结尾",
|
||
"pageSize": "分页大小",
|
||
"pageSizeDesc": "定义入站表的页面大小。设置 0 表示禁用",
|
||
"panelOutbound": "面板流量出站",
|
||
"panelOutboundDesc": "通过此 Xray 出站路由面板自身的请求(面板/Xray 版本检查与下载、Telegram、普通 geo 文件更新),以绕过服务端对 GitHub/Telegram 的过滤。本地桥接入站会自动添加到运行中的配置并实时生效。Xray 原生的 Geodata 自动更新不受影响,它有自己的下载出站。留空表示直连。",
|
||
"panelOutboundPh": "直连",
|
||
"datepicker": "日期选择器",
|
||
"datepickerPlaceholder": "选择日期",
|
||
"datepickerDescription": "选择器日历类型指定到期日期",
|
||
"oldUsername": "原用户名",
|
||
"currentPassword": "原密码",
|
||
"newUsername": "新用户名",
|
||
"newPassword": "新密码",
|
||
"telegramBotEnable": "启用 Telegram 机器人",
|
||
"telegramBotEnableDesc": "启用 Telegram 机器人功能",
|
||
"telegramToken": "Telegram Token",
|
||
"telegramTokenDesc": "从 '{'@'}BotFather' 获取的 Telegram 机器人令牌",
|
||
"telegramProxy": "SOCKS 代理",
|
||
"telegramProxyDesc": "启用 SOCKS5 代理连接到 Telegram(根据指南调整设置)",
|
||
"telegramAPIServer": "Telegram API 服务器",
|
||
"telegramAPIServerDesc": "要使用的 Telegram API 服务器。留空以使用默认服务器。",
|
||
"telegramChatId": "管理员聊天 ID",
|
||
"telegramChatIdDesc": "Telegram 管理员聊天 ID (多个以逗号分隔)(可通过 {'@'}userinfobot 获取,或在机器人中使用 '/id' 命令获取)",
|
||
"telegramNotifyTime": "通知时间",
|
||
"telegramNotifyTimeDesc": "Telegram 机器人发送周期性报告的频率。选择预设间隔,或选择“自定义”以输入 crontab 表达式。",
|
||
"notifyTime": {
|
||
"every": "@every — 按间隔重复",
|
||
"hourly": "@hourly — 每小时",
|
||
"daily": "@daily — 每天 00:00",
|
||
"weekly": "@weekly — 每周",
|
||
"monthly": "@monthly — 每月",
|
||
"custom": "自定义 (crontab)",
|
||
"seconds": "秒",
|
||
"minutes": "分钟",
|
||
"hours": "小时",
|
||
"interval": "间隔",
|
||
"unit": "单位"
|
||
},
|
||
"tgNotifyBackup": "数据库备份",
|
||
"tgNotifyBackupDesc": "发送带有报告的数据库备份文件",
|
||
"tgNotifyLogin": "登录通知",
|
||
"tgNotifyLoginDesc": "当有人试图登录你的面板时显示用户名、IP 地址和时间",
|
||
"sessionMaxAge": "会话时长",
|
||
"sessionMaxAgeDesc": "保持登录状态的时长(单位:分钟)",
|
||
"expireTimeDiff": "到期通知阈值",
|
||
"expireTimeDiffDesc": "达到此阈值时,将收到有关到期时间的通知(单位:天)",
|
||
"trafficDiff": "流量耗尽阈值",
|
||
"trafficDiffDesc": "达到此阈值时,将收到有关流量耗尽的通知(单位:GB)",
|
||
"tgNotifyCpu": "CPU 负载通知阈值",
|
||
"tgNotifyCpuDesc": "CPU 负载超过此阈值时,将收到通知(单位:%)",
|
||
"timeZone": "时区",
|
||
"timeZoneDesc": "定时任务将按照该时区的时间运行",
|
||
"subSettings": "订阅设置",
|
||
"subEnable": "启用订阅服务",
|
||
"subEnableDesc": "启用订阅服务功能",
|
||
"subJsonEnable": "单独启用/禁用 JSON 订阅端点。",
|
||
"subJsonEnableTitle": "JSON 订阅",
|
||
"subClashEnableTitle": "Clash / Mihomo 订阅",
|
||
"subFormatsTipTitle": "特定格式的订阅设置",
|
||
"subFormatsTipDesc": "分别配置 JSON 和 Clash / Mihomo 的 URL 路径、反向代理 URL 和客户端自动识别。",
|
||
"subFormatsTipAction": "打开订阅格式",
|
||
"subJsonAutoDetect": "自动识别 Xray JSON 客户端",
|
||
"subJsonAutoDetectDesc": "启用后,使用标准订阅 URL 的已识别兼容客户端将自动获得 Xray JSON 配置数组。其他客户端继续获得原始/Base64 响应。需要启用 JSON 订阅并重启面板才能生效。",
|
||
"subJsonAlwaysArray": "始终返回 JSON 数组",
|
||
"subJsonAlwaysArrayDesc": "即使只有一个配置,也将显式 JSON 订阅端点返回为数组,以符合 XTLS 订阅标准。自动识别的 JSON 响应始终使用数组。禁用时保留旧版单对象响应。",
|
||
"subJsonUserAgentRegex": "Xray JSON User-Agent 正则表达式",
|
||
"subJsonUserAgentRegexDesc": "用于与客户端 User-Agent 进行匹配,从而在标准订阅 URL 上自动选择 Xray JSON 格式的 Go RE2 正则表达式。默认留空,因此在为需要服务的客户端设置规则之前,自动识别保持关闭。其他客户端继续获得原始/Base64 响应。更改后请重启面板。",
|
||
"subClashAutoDetect": "自动识别 Clash/Mihomo 客户端",
|
||
"subClashAutoDetectDesc": "启用后,使用标准订阅 URL 的已识别 Clash/Mihomo 客户端将自动获得 Clash YAML。浏览器仍显示订阅页面,其他客户端继续获得原始/Base64 响应,独立的 JSON 和 Clash URL 仍然可用。需要启用 Clash/Mihomo 订阅并重启面板才能生效。",
|
||
"subClashUserAgentRegex": "Clash/Mihomo User-Agent 正则表达式",
|
||
"subClashUserAgentRegexDesc": "用于与客户端 User-Agent 进行匹配,从而在标准订阅 URL 上识别 Clash/Mihomo 客户端的 Go RE2 正则表达式。留空则使用默认规则。更改后请重启面板。",
|
||
"subTitle": "订阅标题",
|
||
"subTitleDesc": "在 VPN 客户端中显示的标题。支持客户端身份令牌:{{EMAIL}}、{{ID}}、{{SHORT_ID}}、{{SUB_ID}}、{{TELEGRAM_ID}}。",
|
||
"subSupportUrl": "支持链接",
|
||
"subSupportUrlDesc": "VPN 客户端中显示的技术支持链接。支持客户端身份令牌:{{EMAIL}}、{{ID}}、{{SHORT_ID}}、{{SUB_ID}}、{{TELEGRAM_ID}}。",
|
||
"subProfileUrl": "个人资料链接",
|
||
"subProfileUrlDesc": "VPN 客户端中显示的网站链接。支持客户端身份令牌:{{EMAIL}}、{{ID}}、{{SHORT_ID}}、{{SUB_ID}}、{{TELEGRAM_ID}}。",
|
||
"subAnnounce": "公告",
|
||
"subAnnounceDesc": "VPN 客户端中显示的公告文本。支持客户端身份令牌:{{EMAIL}}、{{ID}}、{{SHORT_ID}}、{{SUB_ID}}、{{TELEGRAM_ID}}。",
|
||
"subThemeDir": "订阅主题目录",
|
||
"subThemeDirDesc": "包含自定义订阅页面模板 (index.html/sub.html) 的文件夹的绝对路径(例如 /etc/3x-ui/sub_templates/my-theme/)。留空则使用默认页面。",
|
||
"subThemeDirDocs": "模板指南 ↗",
|
||
"subEnableRouting": "启用路由",
|
||
"subEnableRoutingDesc": "在 VPN 客户端中启用路由的全局设置。(仅限 Happ)",
|
||
"subRoutingRules": "路由规则",
|
||
"subRoutingRulesDesc": "粘贴现成的 happ:// 深层链接或一个固定 HTTPS URL。面板会在后台更新远程规则并保留最后一个有效值,因此订阅请求无需等待远程源。(仅限 Happ)",
|
||
"subHideSettings": "隐藏服务器设置",
|
||
"subHideSettingsDesc": "在 VPN 客户端中隐藏查看和编辑服务器配置的功能。(仅限 Happ)",
|
||
"subIncyEnableRouting": "启用路由",
|
||
"subIncyEnableRoutingDesc": "为 Incy 客户端将路由配置注入订阅内容中。(仅限 Incy)",
|
||
"subIncyRoutingRules": "路由规则",
|
||
"subIncyRoutingRulesDesc": "粘贴现成的 incy:// 深层链接或指向 JSON 的固定 HTTPS URL。Incy 会创建 autorouting 配置并自动更新。(仅限 Incy)",
|
||
"subClashEnableRouting": "启用路由",
|
||
"subClashEnableRoutingDesc": "在生成的 YAML 订阅中包含 Clash/Mihomo 全局路由规则。",
|
||
"subClashRoutingRules": "全局路由规则",
|
||
"subClashRoutingRulesDesc": "粘贴规则/YAML 或一个固定 HTTPS URL。面板会在后台更新,仅导入代理组、规则提供者和规则,并保留面板生成的 VPN 节点及最后一个有效值。",
|
||
"subListen": "监听 IP",
|
||
"subListenDesc": "订阅服务监听的 IP 地址(留空表示监听所有 IP)",
|
||
"subPort": "监听端口",
|
||
"subPortDesc": "订阅服务监听的端口号(必须是未使用的端口)。当下方「反向代理 URI」为空时,也会用来生成面板中显示的订阅链接/二维码——如果订阅是通过反向代理的其他端口访问的,请改为设置「反向代理 URI」。",
|
||
"subCertPath": "公钥路径",
|
||
"subCertPathDesc": "订阅服务使用的公钥文件路径(以 '/' 开头)",
|
||
"subKeyPath": "私钥路径",
|
||
"subKeyPathDesc": "订阅服务使用的私钥文件路径(以 '/' 开头)",
|
||
"subPath": "URI 路径",
|
||
"subPathDesc": "订阅服务使用的 URI 路径(以 '/' 开头,以 '/' 结尾)",
|
||
"subDomain": "监听域名",
|
||
"subDomainDesc": "订阅服务监听的域名(留空表示监听所有域名和 IP)。当「反向代理 URI」为空时,也会作为显示的订阅链接的回退域名——如果面板和订阅通过不同的域名访问(例如位于反向代理之后),请设置「反向代理 URI」。",
|
||
"subUpdates": "更新间隔",
|
||
"subUpdatesDesc": "客户端应用中订阅 URL 的更新间隔(单位:小时)",
|
||
"subEncrypt": "编码",
|
||
"subEncryptDesc": "订阅服务返回的内容将采用 Base64 编码",
|
||
"subURI": "反向代理 URI",
|
||
"subURIDesc": "用于订阅链接和二维码的完整基础 URL(scheme://域名[:端口]/路径/),会替代监听域名/监听端口使用。当订阅通过反向代理访问,或使用与上面不同的域名/端口访问时,请设置此项。",
|
||
"externalTrafficInformEnable": "外部交通通知",
|
||
"externalTrafficInformEnableDesc": "每次流量更新时通知外部 API。",
|
||
"externalTrafficInformURI": "外部流量通知 URI",
|
||
"externalTrafficInformURIDesc": "流量更新将发送到此 URI",
|
||
"restartXrayOnClientDisable": "客户端自动禁用后重启 Xray",
|
||
"restartXrayOnClientDisableDesc": "当客户端因到期或流量超限被自动禁用时,重启 Xray。",
|
||
"fragment": "分片",
|
||
"fragmentDesc": "启用 TLS hello 数据包分片",
|
||
"fragmentSett": "设置",
|
||
"noisesDesc": "启用 Noises.",
|
||
"noisesSett": "Noises 设置",
|
||
"trustedProxyCidrs": "可信代理 CIDR",
|
||
"trustedProxyCidrsDesc": "允许设置转发 host、proto 和客户端 IP 标头的 IP/CIDR(逗号分隔)。",
|
||
"ldap": {
|
||
"enable": "启用 LDAP 同步",
|
||
"host": "LDAP host",
|
||
"port": "LDAP 端口",
|
||
"useTls": "使用 TLS (LDAPS)",
|
||
"skipTlsVerify": "跳过 TLS 证书验证",
|
||
"skipTlsVerifyDesc": "不安全 — 禁用服务器证书验证。仅用于内部/不受信任的 CA。",
|
||
"bindDn": "Bind DN",
|
||
"passwordConfigured": "已配置;留空以保留当前密码。",
|
||
"passwordUnconfigured": "未配置。",
|
||
"passwordPlaceholder": "已配置 - 输入新值以替换",
|
||
"baseDn": "Base DN",
|
||
"userFilter": "用户筛选",
|
||
"userAttr": "用户属性 (username/email)",
|
||
"vlessField": "VLESS flag 属性",
|
||
"flagField": "通用 flag 属性 (可选)",
|
||
"flagFieldDesc": "如设置,将覆盖 VLESS flag — 如 shadowInactive。",
|
||
"truthyValues": "Truthy 值",
|
||
"truthyValuesDesc": "逗号分隔;默认: true,1,yes,on",
|
||
"invertFlag": "反转 flag",
|
||
"invertFlagDesc": "当属性表示已禁用时启用 (如 shadowInactive)。",
|
||
"syncSchedule": "同步计划",
|
||
"syncScheduleDesc": "类 cron 字符串,如 @every 1m",
|
||
"inboundTags": "入站标签",
|
||
"inboundTagsDesc": "允许 LDAP 同步自动创建或删除客户端的入站。",
|
||
"noInbounds": "未找到入站。请先在“入站”中创建。",
|
||
"autoCreate": "自动创建客户端",
|
||
"autoDelete": "自动删除客户端",
|
||
"defaultTotalGb": "默认总流量 (GB)",
|
||
"defaultExpiryDays": "默认到期 (天)",
|
||
"defaultIpLimit": "默认 IP 限制"
|
||
},
|
||
"subFormats": {
|
||
"finalMask": "Final Mask",
|
||
"finalMaskDesc": "将 Xray finalmask TCP/UDP 掩码和 QUIC 参数注入每个生成的 Xray JSON 配置。需要支持 Xray JSON 订阅的客户端应用和较新的 Xray 核心。",
|
||
"packets": "数据包",
|
||
"length": "长度",
|
||
"interval": "间隔",
|
||
"maxSplit": "最大分割",
|
||
"noises": "噪声",
|
||
"noiseItem": "噪声 №{n}",
|
||
"type": "类型",
|
||
"packet": "数据包",
|
||
"delayMs": "延迟 (ms)",
|
||
"applyTo": "应用于",
|
||
"addNoise": "+ 噪声",
|
||
"concurrency": "并发",
|
||
"xudpConcurrency": "xudp 并发",
|
||
"xudpUdp443": "xudp UDP 443"
|
||
},
|
||
"mux": "Mux",
|
||
"muxDesc": "在已建立的数据流内传输多个独立的数据流",
|
||
"muxSett": "复用器设置",
|
||
"direct": "直接连接",
|
||
"directDesc": "直接与特定国家的域或 IP 范围建立连接",
|
||
"notifications": "通知",
|
||
"certs": "证书",
|
||
"externalTraffic": "外部流量",
|
||
"dateAndTime": "日期和时间",
|
||
"proxyAndServer": "代理和服务器",
|
||
"intervals": "间隔",
|
||
"information": "信息",
|
||
"profile": "资料",
|
||
"language": "语言",
|
||
"telegramBotLanguage": "Telegram 机器人语言",
|
||
"security": {
|
||
"admin": "管理员凭据",
|
||
"twoFactor": "双重验证",
|
||
"twoFactorEnable": "启用 2FA",
|
||
"twoFactorEnableDesc": "增加额外的验证层以提高安全性。",
|
||
"twoFactorModalSetTitle": "启用双重认证",
|
||
"twoFactorModalDeleteTitle": "停用双重认证",
|
||
"twoFactorModalSteps": "要设定双重认证,请执行以下步骤:",
|
||
"twoFactorModalFirstStep": "1. 在认证应用程序中扫描此 QR 码,或复制 QR 码附近的令牌并粘贴到应用程序中",
|
||
"twoFactorModalSecondStep": "2. 输入应用程序中的验证码",
|
||
"twoFactorModalRemoveStep": "输入应用程序中的验证码以移除双重认证。",
|
||
"twoFactorModalChangeCredentialsTitle": "更改凭据",
|
||
"twoFactorModalChangeCredentialsStep": "输入应用程序中的代码以更改管理员凭据。",
|
||
"twoFactorModalSetSuccess": "双因素认证已成功建立",
|
||
"twoFactorModalDeleteSuccess": "双因素认证已成功删除",
|
||
"twoFactorModalError": "验证码错误",
|
||
"show": "显示",
|
||
"hide": "隐藏",
|
||
"apiTokenNew": "新建令牌",
|
||
"apiTokenName": "名称",
|
||
"apiTokenNamePlaceholder": "例如 central-panel-a",
|
||
"apiTokenNameRequired": "名称必填",
|
||
"apiTokenEmpty": "暂无令牌 — 创建一个用于认证机器人或远程面板。",
|
||
"apiTokenDeleteWarning": "使用此令牌的任何调用方将立即无法认证。",
|
||
"apiTokenCreatedTitle": "令牌已创建",
|
||
"apiTokenCreatedNotice": "请立即复制此令牌。出于安全考虑,它不会以可读形式存储,也不会再次显示。"
|
||
},
|
||
"toasts": {
|
||
"modifySettings": "参数已更改。",
|
||
"getSettings": "获取参数时发生错误",
|
||
"modifyUserError": "更改管理员凭据时发生错误。",
|
||
"modifyUser": "您已成功更改管理员凭据。",
|
||
"originalUserPassIncorrect": "原用户名或原密码错误",
|
||
"userPassMustBeNotEmpty": "新用户名和新密码不能为空",
|
||
"getOutboundTrafficError": "获取出站流量错误",
|
||
"resetOutboundTrafficError": "重置出站流量错误"
|
||
},
|
||
"smtpSettings": "SMTP 设置",
|
||
"smtpEnable": "启用邮件通知",
|
||
"smtpEnableDesc": "通过 SMTP 启用邮件通知",
|
||
"smtpHost": "SMTP 主机",
|
||
"smtpHostDesc": "SMTP 服务器主机名(例如 smtp.gmail.com)",
|
||
"smtpPort": "SMTP 端口",
|
||
"smtpPortDesc": "SMTP 服务器端口(默认:587)",
|
||
"smtpUsername": "SMTP 用户名",
|
||
"smtpUsernameDesc": "SMTP 认证用户名",
|
||
"smtpFrom": "发件人地址 (From)",
|
||
"smtpFromDesc": "邮件 From 头使用的地址。留空则使用用户名。",
|
||
"smtpFromName": "发件人名称 (From)",
|
||
"smtpFromNameDesc": "From 头中显示在地址前的可选显示名称。",
|
||
"smtpPassword": "SMTP 密码",
|
||
"smtpPasswordDesc": "SMTP 认证密码",
|
||
"smtpTo": "收件人",
|
||
"smtpToDesc": "以逗号分隔的收件人邮箱地址",
|
||
"emailSettings": "邮件",
|
||
"emailNotifications": "通知",
|
||
"smtpEventBusNotify": "邮件事件通知",
|
||
"smtpEventBusNotifyDesc": "选择触发邮件通知的事件",
|
||
"tgEventBusNotify": "Telegram 事件通知",
|
||
"tgEventBusNotifyDesc": "选择触发 Telegram 通知的事件",
|
||
"testSmtp": "发送测试邮件",
|
||
"testTgBot": "发送测试消息",
|
||
"eventGroupOutbound": "出站",
|
||
"eventGroupXray": "Xray 核心",
|
||
"eventGroupSystem": "系统",
|
||
"eventGroupSecurity": "安全",
|
||
"eventGroupNode": "节点",
|
||
"eventOutboundDown": "断开",
|
||
"eventOutboundUp": "恢复",
|
||
"eventXrayCrash": "崩溃",
|
||
"eventNodeDown": "离线",
|
||
"eventNodeUp": "上线",
|
||
"eventCPUHigh": "CPU 占用过高(%)",
|
||
"requestFailed": "请求失败",
|
||
"smtpEncryption": "加密",
|
||
"smtpEncryptionDesc": "SMTP 连接加密方式",
|
||
"smtpEncryptionNone": "无(明文)",
|
||
"smtpEncryptionStartTLS": "STARTTLS",
|
||
"smtpEncryptionTLS": "TLS(隐式)",
|
||
"smtpStageConnect": "连接",
|
||
"smtpStageAuth": "认证",
|
||
"smtpStageSend": "发送",
|
||
"smtpTestSuccess": "测试邮件发送成功",
|
||
"smtpHostNotConfigured": "尚未配置 SMTP 主机",
|
||
"smtpNoRecipients": "尚未配置收件人",
|
||
"smtpFromNotConfigured": "未配置 SMTP 发件人地址",
|
||
"eventLoginAttempt": "登录尝试",
|
||
"telegramTokenConfigured": "已配置;留空则保留当前令牌。",
|
||
"telegramTokenPlaceholder": "已配置——输入新令牌以替换",
|
||
"smtpPasswordConfigured": "已配置;留空则保留当前密码。",
|
||
"smtpPasswordPlaceholder": "已配置——输入新密码以替换",
|
||
"smtpNotInitialized": "SMTP 尚未初始化",
|
||
"tgBotNotEnabled": "Telegram 机器人未启用",
|
||
"tgTestFailed": "Telegram 测试失败",
|
||
"tgTestSuccess": "测试消息已发送至 Telegram",
|
||
"tgBotNotRunning": "Telegram 机器人未运行",
|
||
"smtpErrorAuth": "认证失败——请检查用户名和密码",
|
||
"smtpErrorStarttls": "服务器要求 STARTTLS——请更改加密类型",
|
||
"smtpErrorTls": "服务器要求 TLS——请更改加密类型",
|
||
"smtpErrorRefused": "连接被拒绝——请检查主机和端口",
|
||
"smtpErrorTimeout": "连接超时——主机无法访问",
|
||
"smtpErrorRelay": "服务器拒绝从此地址发送",
|
||
"smtpErrorEof": "连接被服务器关闭",
|
||
"smtpErrorUnknown": "SMTP 错误:{{ .Error }}",
|
||
"eventMemoryHigh": "内存使用率高 (%)",
|
||
"remarkTemplate": "备注模板",
|
||
"remarkTemplateDesc": "设置后,将替换每个订阅链接的备注模型 — 使用变量标记编写您自己的格式(用按钮插入它们)。留空则使用上方的模型。",
|
||
"subShowIdentityOnAllLinks": "在每个链接上显示身份",
|
||
"subShowIdentityOnAllLinksDesc": "启用后,{{EMAIL}} 和 {{USERNAME}} 会保留在每条订阅正文备注中。用量相关变量仍仅出现在第一条链接。",
|
||
"validation": {
|
||
"pathLeadingSlash": "路径必须以 / 开头"
|
||
},
|
||
"secretClear": "清除",
|
||
"secretClearUndo": "撤销清除",
|
||
"calendarGregorian": "Gregorian (Standard)",
|
||
"calendarJalalian": "Jalalian (شمسی)",
|
||
"ipLimitAllowlist": "IP 限制白名单",
|
||
"ipLimitAllowlistDesc": "IP 限制永远不会计入也不会封禁的地址和网段,避免办公室或校园的共享地址耗尽客户端的限额。IP/CIDR(逗号分隔)。",
|
||
"subBalancers": {
|
||
"menu": "订阅均衡器",
|
||
"title": "订阅均衡器",
|
||
"add": "添加均衡器",
|
||
"desc": "每个启用的均衡器会作为额外配置加入 JSON 订阅,自动在所选入站的端点中选择最优节点(客户端配置中的 routing.balancers + burstObservatory)。",
|
||
"remark": "备注",
|
||
"remarkPlaceholder": "自动 · 最快",
|
||
"strategy": "策略",
|
||
"strategyLeastLoad": "最小负载",
|
||
"strategyLeastPing": "最低延迟",
|
||
"strategyRandom": "随机",
|
||
"strategyRoundRobin": "轮询",
|
||
"sortOrder": "顺序",
|
||
"sortOrderHelp": "在订阅列表中的位置,与入站顺序交错排列;序号相同时排在入站之后。",
|
||
"inbounds": "入站",
|
||
"inboundsCount": "{count} 入站",
|
||
"enabled": "启用",
|
||
"empty": "暂无均衡器",
|
||
"deleteConfirm": "确定删除此均衡器?",
|
||
"errRemarkRequired": "请填写备注",
|
||
"errInboundsRequired": "请至少选择一个入站",
|
||
"errSortOrder": "顺序必须为不小于 1 的整数",
|
||
"toasts": {
|
||
"list": "列出订阅均衡器失败",
|
||
"create": "创建订阅均衡器失败",
|
||
"update": "更新订阅均衡器失败",
|
||
"delete": "删除订阅均衡器失败",
|
||
"invalidId": "无效的 id"
|
||
},
|
||
"tabBalancers": "负载均衡",
|
||
"tabObservatory": "观测器",
|
||
"observatory": {
|
||
"title": "均衡器探活",
|
||
"desc": "写入每个 leastPing/leastLoad 均衡器配置的 burstObservatory 探活参数。random/roundRobin 不生成探活。作为面板级 JSON 订阅设置保存。",
|
||
"destination": "探活 URL",
|
||
"destinationDesc": "客户端探测每个成员出站的地址。",
|
||
"connectivity": "连通性 URL",
|
||
"connectivityDesc": "可选地址,检查成员能否到达探活目标。留空则跳过。",
|
||
"interval": "探活间隔",
|
||
"intervalDesc": "探活轮次之间的时间,例如 1m。",
|
||
"timeout": "探活超时",
|
||
"timeoutDesc": "单次探活超时,例如 5s。",
|
||
"sampling": "采样",
|
||
"samplingDesc": "用于稳定度平均的连续探活次数。",
|
||
"httpMethod": "HTTP 方法",
|
||
"httpMethodDesc": "探活请求使用的 HTTP 方法。",
|
||
"note": "leastPing/leastLoad 均衡器始终带有 burstObservatory。此开关自定义其探活参数 — 关闭以使用内置默认值。更改在面板重启后生效。"
|
||
}
|
||
}
|
||
},
|
||
"xray": {
|
||
"importRules": "导入规则",
|
||
"exportRules": "导出规则",
|
||
"importOutbounds": "导入出站",
|
||
"exportOutbounds": "导出出站",
|
||
"importInvalidJson": "无效的 JSON——应为数组或包含匹配键的对象。",
|
||
"metricsListen": "指标端点",
|
||
"metricsListenDesc": "在此 address:port 上暴露 Xray 的 Prometheus 风格指标(例如 127.0.0.1:11111)。留空则禁用。请绑定到本地回环并通过反向代理转发——它没有身份验证。",
|
||
"metricsTag": "指标标签",
|
||
"save": "保存",
|
||
"restartSuccess": "Xray 已成功重新启动",
|
||
"stopSuccess": "Xray 已成功停止",
|
||
"restartError": "重启 Xray 时发生错误。",
|
||
"stopError": "停止 Xray 时发生错误。",
|
||
"basicTemplate": "基础配置",
|
||
"advancedTemplate": "高级配置",
|
||
"generalConfigs": "常规配置",
|
||
"generalConfigsDesc": "这些选项将决定常规配置",
|
||
"logConfigs": "日志",
|
||
"logConfigsDesc": "日志可能会影响服务器的性能,建议仅在需要时启用",
|
||
"basicRouting": "基本路由",
|
||
"blockConnectionsConfigsDesc": "这些选项将根据特定的请求国家阻止流量。",
|
||
"directConnectionsConfigsDesc": "直接连接确保特定的流量不会通过其他服务器路由。",
|
||
"blockips": "阻止 IP",
|
||
"blockdomains": "阻止域名",
|
||
"directips": "直接 IP",
|
||
"directdomains": "直接域名",
|
||
"ipv4Routing": "IPv4 路由",
|
||
"ipv4RoutingDesc": "此选项将仅通过 IPv4 路由到目标域",
|
||
"Template": "高级 Xray 配置模板",
|
||
"TemplateDesc": "最终的 Xray 配置文件将基于此模板生成",
|
||
"FreedomStrategy": "Freedom 协议策略",
|
||
"FreedomStrategyDesc": "设置 Freedom 协议中网络的输出策略",
|
||
"FreedomHappyEyeballs": "Freedom Happy Eyeballs (IPv4/IPv6)",
|
||
"FreedomHappyEyeballsDesc": "为直连(freedom)出站启用双栈拨号——在同时具备 IPv4 和 IPv6 的出口服务器上很有用。",
|
||
"FreedomHappyEyeballsTryDelayDesc": "尝试备用地址族之前等待的毫秒数。150–250 毫秒是不错的起点。",
|
||
"RoutingStrategy": "配置路由域策略",
|
||
"RoutingStrategyDesc": "设置 DNS 解析的整体路由策略",
|
||
"outboundTestUrl": "出站测试 URL",
|
||
"outboundTestUrlDesc": "测试出站连接时使用的 URL",
|
||
"Torrent": "屏蔽 BitTorrent 协议",
|
||
"Inbounds": "入站",
|
||
"Outbounds": "出站",
|
||
"Balancers": "负载均衡",
|
||
"balancerTagRequired": "标签为必填项",
|
||
"balancerSelectorRequired": "至少选择一个出站",
|
||
"balancerLive": "当前目标",
|
||
"balancerOverride": "强制指定",
|
||
"balancerOverridePh": "自动(策略)",
|
||
"balancerLiveRefresh": "刷新负载均衡器实时状态",
|
||
"balancerNotRunning": "此负载均衡器在运行中的 Xray 未激活 — 请先保存更改或启动 Xray",
|
||
"routeTester": "路由测试",
|
||
"routeTesterDesc": "向运行中的 Xray 查询某个连接将使用哪个出站。不会发送真实流量 — 结果直接来自实时路由引擎。",
|
||
"routeTesterDest": "域名或 IP",
|
||
"routeTesterPort": "端口",
|
||
"routeTesterInbound": "入站",
|
||
"routeTesterProtocol": "嗅探协议",
|
||
"routeTesterTest": "测试路由",
|
||
"routeTesterMatchedOutbound": "匹配出站",
|
||
"routeTesterViaBalancer": "经由负载均衡器",
|
||
"routeTesterDefaultOutbound": "无路由规则匹配 — 流量将发往默认(第一个)出站。",
|
||
"Routings": "路由规则",
|
||
"completeTemplate": "全部",
|
||
"logLevel": "日志级别",
|
||
"logLevelDesc": "错误日志的日志级别,用于指示需要记录的信息",
|
||
"accessLog": "访问日志",
|
||
"accessLogDesc": "访问日志的文件路径。特殊值 'none' 禁用访问日志",
|
||
"errorLog": "错误日志",
|
||
"errorLogDesc": "错误日志的文件路径。特殊值 'none' 禁用错误日志",
|
||
"dnsLog": "DNS 日志",
|
||
"dnsLogDesc": "是否启用 DNS 查询日志",
|
||
"maskAddress": "隐藏地址",
|
||
"maskAddressDesc": "IP 地址掩码,启用时会自动替换日志中出现的 IP 地址。",
|
||
"statistics": "统计",
|
||
"statsInboundUplink": "入站上传统计",
|
||
"statsInboundDownlink": "入站下载统计",
|
||
"statsOutboundUplink": "出站上传统计",
|
||
"statsOutboundDownlink": "出站下载统计",
|
||
"connectionLimits": "连接限制",
|
||
"connectionLimitsDesc": "用户等级 0 的连接级策略。留空则使用 Xray 的默认值。",
|
||
"connIdle": "空闲超时",
|
||
"connIdleDesc": "连接空闲达到该秒数后将被关闭。在繁忙的服务器上调低此值可更快释放内存和文件描述符(Xray 默认值:300)。",
|
||
"bufferSize": "缓冲区大小",
|
||
"bufferSizeDesc": "每个连接的内部缓冲区大小(KB)。在低内存服务器上设为 0 可最大限度减少内存占用(Xray 默认值取决于平台)。",
|
||
"bufferSizePlaceholder": "自动",
|
||
"seconds": "秒",
|
||
"rules": {
|
||
"source": "来源",
|
||
"dest": "目的地址",
|
||
"inbound": "入站",
|
||
"balancer": "负载均衡",
|
||
"useComma": "逗号分隔的项目"
|
||
},
|
||
"routing": {
|
||
"dragToReorder": "拖动以重新排序"
|
||
},
|
||
"geoBrowser": {
|
||
"title": "geo 分类",
|
||
"openTooltip": "浏览 geo 分类",
|
||
"database": "数据库",
|
||
"searchCategory": "搜索分类",
|
||
"searchEntries": "在分类内筛选",
|
||
"selectFound": "选中搜索结果",
|
||
"selected": "已选 {count} 项",
|
||
"clearAll": "全部清除",
|
||
"apply": "应用",
|
||
"emptySelection": "勾选分类,它们将成为规则中的条目",
|
||
"pickCategory": "在左侧选择一个分类以查看其内容",
|
||
"noMatches": "未找到任何内容",
|
||
"noFiles": "Xray 目录中没有 geo 数据库",
|
||
"noFilesHint": "Xray 下载 geosite.dat 和 geoip.dat 后即会出现",
|
||
"fileMeta": "{count} 个分类 · {size} · 更新于 {date}",
|
||
"entriesCount": "{count} 条记录",
|
||
"subnetsCount": "{count} 个网段",
|
||
"shownRange": "显示第 {from}–{to} 项,共 {total} 项",
|
||
"loadFailed": "无法加载 geo 数据库",
|
||
"checkFailed": "无法根据 geo 数据库校验这些值",
|
||
"parseFailed": "文件已损坏或不是 geosite/geoip 数据库",
|
||
"tooLarge": "文件过大,无法浏览",
|
||
"unknownCategories": "数据库中不存在:{tokens}",
|
||
"missingDatabase": "未找到数据库文件:{tokens} — 请在 Geodata 中添加",
|
||
"unknownAttribute": "未找到该属性,规则不会匹配任何内容:{tokens}",
|
||
"invalidToken": "Xray 无法接受此写法:{tokens}",
|
||
"wrongKind": "该字段的数据库类型不匹配:{tokens}"
|
||
},
|
||
"ruleForm": {
|
||
"sourceIps": "源 IP",
|
||
"sourcePort": "源端口",
|
||
"vlessRoute": "VLESS 路由",
|
||
"attributes": "属性",
|
||
"value": "值",
|
||
"user": "用户",
|
||
"userPlaceholder": "选择用户",
|
||
"userEmpty": "暂无可用用户",
|
||
"userLoadError": "无法加载用户",
|
||
"inboundTags": "入站标签",
|
||
"outboundTag": "出站标签",
|
||
"balancerTag": "均衡器标签",
|
||
"balancerTagTooltip": "通过其中一个已配置的负载均衡器路由流量"
|
||
},
|
||
"outboundForm": {
|
||
"tagDuplicate": "该标签已被其他出站使用",
|
||
"tagRequired": "标签为必填项",
|
||
"tagPlaceholder": "唯一标签",
|
||
"localIpPlaceholder": "本地 IP",
|
||
"dialerProxyPlaceholder": "选择要串联的出站",
|
||
"dialerProxyHint": "让此出站通过另一个出站(按标签)拨号,以建立代理链。留空则直接连接。",
|
||
"targetStrategyHint": "连接前如何解析目标域名:AsIs(默认)原样发送,UseIP… 解析失败时回退,ForceIP… 必须解析成功。",
|
||
"addressRequired": "地址为必填项",
|
||
"portRequired": "端口为必填项",
|
||
"optional": "可选",
|
||
"udpOverTcp": "UDP over TCP",
|
||
"uotVersion": "UoT 版本",
|
||
"inboundTag": "入站标签",
|
||
"inboundTagPlaceholder": "用于路由规则的入站标签",
|
||
"responseType": "响应类型",
|
||
"rewriteNetwork": "重写网络",
|
||
"unchanged": "(未更改)",
|
||
"unchangedAddress": "(未更改) 如 1.1.1.1",
|
||
"rules": "规则",
|
||
"ruleN": "规则 {n}",
|
||
"action": "操作",
|
||
"redirect": "Redirect",
|
||
"finalRules": "最终规则",
|
||
"overrideXrayPrivateIp": "覆盖 Xray 默认的私有 IP 阻止",
|
||
"blockDelay": "阻塞延迟 (ms)",
|
||
"reverseSniffing": "反向 sniffing",
|
||
"reserved": "保留",
|
||
"minUploadInterval": "最小上传间隔 (ms)",
|
||
"maxUploadSizeBytes": "最大上传大小 (字节)",
|
||
"uplinkChunkSize": "Uplink chunk 大小",
|
||
"noGrpcHeader": "无 gRPC 头",
|
||
"maxConcurrency": "最大并发",
|
||
"maxConnections": "最大连接",
|
||
"maxReuseTimes": "最大复用次数",
|
||
"maxRequestTimes": "最大请求次数",
|
||
"maxReusableSecs": "最大可复用秒数",
|
||
"keepAlivePeriod": "keep alive 周期",
|
||
"authPassword": "认证密码",
|
||
"visionTestpre": "Vision testpre",
|
||
"serverNamePlaceholder": "服务器名",
|
||
"verifyPeerName": "验证 peer 名称",
|
||
"pinnedSha256": "Pinned SHA256",
|
||
"shortId": "Short ID",
|
||
"sockopts": "Sockopts",
|
||
"keepAliveInterval": "keep alive 间隔",
|
||
"markFwmark": "Mark (fwmark)",
|
||
"interface": "接口",
|
||
"proxyProtocol": "Proxy protocol",
|
||
"tcpUserTimeoutMs": "TCP user timeout (ms)",
|
||
"tcpKeepAliveIdleS": "TCP keep-alive idle (s)"
|
||
},
|
||
"outbound": {
|
||
"tag": "标签",
|
||
"egress": "出口",
|
||
"egressHint": "执行 HTTP 测试以显示出口 IP 和所在国家/地区。",
|
||
"outboundStatus": "出站状态",
|
||
"sendThrough": "发送通过",
|
||
"targetStrategy": "目标解析策略",
|
||
"modeRealDelay": "真实延迟",
|
||
"testModeTooltip": "TCP: 快速 dial-only 探测。HTTP: 通过 xray 的完整请求。真实延迟: 含建立连接的总耗时。",
|
||
"testAll": "全部测试",
|
||
"httpStatus": "HTTP 状态",
|
||
"breakdownConnect": "代理连接",
|
||
"breakdownTls": "经由出站的 TLS",
|
||
"breakdownTtfb": "首字节",
|
||
"country": "国家",
|
||
"server": "服务器",
|
||
"city": "城市",
|
||
"allCities": "所有城市",
|
||
"moveToTop": "移到顶部"
|
||
},
|
||
"outboundSub": {
|
||
"manage": "订阅",
|
||
"title": "出站订阅",
|
||
"remark": "备注(可选)",
|
||
"remarkPlaceholder": "如:香港节点",
|
||
"url": "订阅 URL",
|
||
"urlPlaceholder": "https://...(base64 编码的链接列表)",
|
||
"tagPrefix": "标签前缀",
|
||
"tagPrefixPlaceholder": "hk-",
|
||
"interval": "更新间隔",
|
||
"hours": "时",
|
||
"minutes": "分",
|
||
"intervalHint": "默认 10 分钟。后台任务会频繁检查;每个订阅仅在自身间隔到期后才重新拉取。",
|
||
"enabled": "启用",
|
||
"allowPrivate": "允许私有地址",
|
||
"allowPrivateHint": "允许此订阅 URL 使用 localhost / 局域网(LAN)/ 私有 IP 地址。出于安全考虑默认关闭,仅在使用可信的本地来源时才开启。",
|
||
"prepend": "置于手动出站之前",
|
||
"prependHint": "将此订阅的出站排在手动配置的出站之前,使其中之一可成为默认出站。",
|
||
"preview": "预览",
|
||
"previewEmpty": "在该 URL 未找到任何出站。",
|
||
"refreshAll": "全部刷新",
|
||
"statusOk": "正常",
|
||
"toastUpdated": "订阅已更新",
|
||
"addButton": "添加",
|
||
"active": "已启用的订阅",
|
||
"empty": "暂无订阅。请在上方添加。",
|
||
"colRemark": "备注",
|
||
"colLastFetch": "上次拉取",
|
||
"colEnabled": "启用",
|
||
"auto": "自动",
|
||
"never": "从未",
|
||
"refreshNow": "立即刷新",
|
||
"deleteConfirm": "删除此订阅?",
|
||
"restartHint": "添加或刷新后,请重启 Xray(或等待下一次自动重载)以使出站生效。",
|
||
"fromSubsTitle": "来自出站订阅(只读)",
|
||
"fromSubsDesc": "从已启用的订阅中导入。请在上方的订阅面板中管理它们。",
|
||
"toastLoadFailed": "加载订阅失败",
|
||
"toastUrlRequired": "订阅 URL 为必填项",
|
||
"toastAdded": "订阅已添加",
|
||
"toastAddFailed": "添加订阅失败",
|
||
"toastRefreshed": "已刷新",
|
||
"toastRefreshFailed": "刷新失败",
|
||
"toastDeleted": "已删除",
|
||
"toastDeleteFailed": "删除失败"
|
||
},
|
||
"pia": {
|
||
"menu": "PIA",
|
||
"username": "PIA 用户名",
|
||
"password": "PIA 密码",
|
||
"account": "账号",
|
||
"region": "区域",
|
||
"allRegions": "全部区域",
|
||
"noServers": "所选国家没有可用服务器",
|
||
"outboundAdded": "已添加 PIA 出站",
|
||
"outboundUpdated": "已更新 PIA 出站",
|
||
"addedServers": "已添加的服务器",
|
||
"alreadyAdded": "该服务器已在出站列表中。要用新密钥请点 {reset}。",
|
||
"provisionFailed": "无法生成 PIA 出站,请重试。"
|
||
},
|
||
"tabBalancerSettings": "负载均衡设置",
|
||
"tabObservatory": "观测器",
|
||
"observatory": {
|
||
"autoManaged": "观测器会根据你的负载均衡器自动管理。可在下方调整探测方式;被观测的出站会跟随负载均衡器的选择器。",
|
||
"emptyHint": "当前没有活动的连接观测器。当你创建 Least Ping 或 Least Load 负载均衡器,或带有 fallback 的 Random / Round-robin 负载均衡器时,会自动添加一个,以便依赖观测器的负载均衡器在选择目标前检查出站健康状态。",
|
||
"mixedLegacy": "此配置同时包含 Observatory 和 Burst Observatory。Xray 只使用一个全局观测器,因此不支持这种旧式混合状态;保存负载均衡器时会将其规范化为单个观测器。",
|
||
"subjectSelector": "被观测的出站",
|
||
"subjectSelectorDesc": "该观测器探测的出站标签。根据你的负载均衡器自动管理。",
|
||
"probeURL": "探测 URL",
|
||
"probeURLDesc": "用于测量每个出站而请求的 URL,应返回 HTTP 204。",
|
||
"probeInterval": "探测间隔",
|
||
"probeIntervalDesc": "每个出站的探测频率,例如 30s、1m、2h45m。",
|
||
"enableConcurrency": "并发探测",
|
||
"enableConcurrencyDesc": "一次性探测所有被观测的出站,而不是逐个探测。更快,但在网络上更明显。",
|
||
"destination": "探测目标",
|
||
"destinationDesc": "用于测量每个出站而请求的 URL,应返回 HTTP 204。",
|
||
"connectivity": "连通性检查",
|
||
"connectivityDesc": "可选的本地网络检查 URL,仅在目标失败后才尝试。留空则跳过。",
|
||
"interval": "探测间隔",
|
||
"intervalDesc": "每个出站两次探测之间的平均时间,例如 1m。最小 10s。",
|
||
"timeout": "探测超时",
|
||
"timeoutDesc": "判定探测失败前的等待时长,例如 5s。",
|
||
"sampling": "采样数量",
|
||
"samplingDesc": "为每个出站评分而保留的最近探测结果数量。",
|
||
"httpMethod": "HTTP 方法",
|
||
"httpMethodDesc": "探测所用的 HTTP 方法。",
|
||
"deleteAlsoObservatory": "这是最后一个使用 Observatory 的负载均衡器,因此它也会被一并移除。",
|
||
"deleteAlsoBurst": "这是最后一个使用 Burst Observatory 的负载均衡器,因此它也会被一并移除。"
|
||
},
|
||
"refCleanup": {
|
||
"header": "删除此项还会更新你的路由:",
|
||
"ruleRemoved": "规则 {label} — 已移除(没有剩余出口)",
|
||
"ruleModified": "规则 {label} — 已保留(现使用 {keeps})",
|
||
"balancerRemoved": "负载均衡器 {tag} — 已移除(没有剩余目标)"
|
||
},
|
||
"balancer": {
|
||
"balancerStrategy": "策略",
|
||
"tag": "标签",
|
||
"tagDuplicate": "该标签已被其他均衡器使用",
|
||
"tagPlaceholder": "唯一均衡器标签",
|
||
"selector": "选择器",
|
||
"fallback": "备用负载均衡器",
|
||
"cycleTooltip": "循环: {path} → (回到 {start})",
|
||
"expected": "期望",
|
||
"expectedPlaceholder": "最佳节点数",
|
||
"maxRtt": "最大 RTT",
|
||
"tolerance": "容差",
|
||
"baselines": "基准值",
|
||
"costs": "成本设置",
|
||
"costMatch": "标签匹配模式",
|
||
"costValue": "权重",
|
||
"costRegexp": "正则表达式匹配",
|
||
"balancerDeleteInUse": "无法删除此负载均衡器 — 它被用作以下负载均衡器的备用:{names}",
|
||
"balancerFallbackCycle": "无法将此负载均衡器设置为备用 — 这会创建循环依赖。",
|
||
"balancerFallbackInfo": "流量将通过以下路径路由:负载均衡器 → Loopback → 服务器 → 目标负载均衡器 → 出站连接。这会增加一个经过服务器的额外跳转,可能会引入轻微延迟。",
|
||
"fallbackBalancerHint": "选择另一个负载均衡器作为备用",
|
||
"reservedPrefix": "_bl_ 前缀保留给内部负载均衡器回环对象"
|
||
},
|
||
"wireguard": {
|
||
"secretKey": "密钥",
|
||
"publicKey": "公钥",
|
||
"subnetIp": "子网",
|
||
"subnetCidr": "子网 CIDR",
|
||
"allowedIPs": "允许的 IP",
|
||
"endpoint": "端点",
|
||
"domainStrategy": "域策略"
|
||
},
|
||
"amneziawg": {
|
||
"privateKey": "私钥",
|
||
"publicKey": "公钥",
|
||
"subnetIp": "子网",
|
||
"subnetCidr": "子网 CIDR",
|
||
"mtu": "MTU",
|
||
"primaryDns": "主 DNS",
|
||
"secondaryDns": "备用 DNS",
|
||
"externalInterface": "外部网卡",
|
||
"externalInterfaceHint": "用于 NAT(PostUp/PostDown)的主机网卡。留空则自动检测。",
|
||
"ipv6Enabled": "启用 IPv6",
|
||
"ipv6Subnet": "IPv6 子网",
|
||
"ipv6SubnetHint": "例如 fd86:ea04:1115::/64。启用 IPv6 时必填。",
|
||
"ipv6ExternalInterface": "IPv6 外部网卡",
|
||
"ipv6ExternalInterfaceHint": "用于 NDP 代理条目的主机网卡。留空则使用外部网卡。",
|
||
"obfuscation": "混淆参数",
|
||
"regenerateObfuscation": "重新生成",
|
||
"jc": "Jc(垃圾包数量)",
|
||
"jmin": "Jmin(垃圾包最小大小)",
|
||
"jmax": "Jmax(垃圾包最大大小)",
|
||
"s1": "S1(init 包填充大小)",
|
||
"s2": "S2(response 包填充大小)",
|
||
"s3": "S3(cookie reply 填充)",
|
||
"s4": "S4(传输包填充)",
|
||
"h1": "H1(魔术头)",
|
||
"h2": "H2(魔术头)",
|
||
"h3": "H3(魔术头)",
|
||
"h4": "H4(魔术头)",
|
||
"hHint": "单个整数或范围。留空则使用经典默认值 1/2/3/4。",
|
||
"i1": "I1(签名包)",
|
||
"i1Hint": "可选的签名包。留空则不发送。",
|
||
"i2": "I2(签名包)",
|
||
"i3": "I3(签名包)",
|
||
"i4": "I4(签名包)",
|
||
"i5": "I5(签名包)",
|
||
"headerProtectionKey": "HeaderProtectionKey(头部保护)",
|
||
"headerProtectionKeyHint": "Base64 编码的 32 字节密钥;必须与每个客户端配置一致。留空则禁用头部保护。",
|
||
"contentPaddingAddition": "ContentPaddingAddition(内容填充)",
|
||
"contentPaddingAdditionHint": "单个整数或 low-high 字节范围,附加到内容数据包。留空则禁用。",
|
||
"rekeyAfterTime": "RekeyAfterTime(秒)",
|
||
"rekeyTimeout": "RekeyTimeout(秒)",
|
||
"rejectAfterTime": "RejectAfterTime(秒)",
|
||
"keepaliveTimeout": "KeepaliveTimeout(秒)",
|
||
"maxHandshakeAttempts": "MaxHandshakeAttempts",
|
||
"timingRangeHint": "单个整数或范围。留空则使用 WireGuard 默认值。",
|
||
"maxHandshakeAttemptsHint": "放弃前的握手重试次数。留空则使用默认值。",
|
||
"randomTrailers": "RandomTrailers",
|
||
"randomTrailersHint": "在每个数据包末尾追加随机字节。两端都需要 AmneziaWG 3.1+。",
|
||
"disableCookies": "DisableCookies",
|
||
"disableCookiesHint": "从不发送 cookie reply——消除一个 DPI 指纹,但会削弱抗洪泛能力。"
|
||
},
|
||
"tun": {
|
||
"userLevel": "用户级别"
|
||
},
|
||
"nord": {
|
||
"accessToken": "访问令牌",
|
||
"privateKey": "私钥",
|
||
"noServers": "未找到选定国家/地区的服务器",
|
||
"noPublicKey": "选定的服务器未公布 NordLynx 公钥。",
|
||
"outboundAdded": "NordVPN 出站已添加",
|
||
"outboundUpdated": "NordVPN 出站已更新"
|
||
},
|
||
"warp": {
|
||
"changeIp": "更换 IP",
|
||
"changeIpSuccess": "WARP IP 更换成功!",
|
||
"autoUpdateIp": "自动更新 IP 地址",
|
||
"intervalDays": "间隔(天)",
|
||
"intervalDesc": "设为 0 禁用。自动更换 IP 地址。",
|
||
"licenseError": "设置 WARP 许可证失败。",
|
||
"fetchFirst": "请先获取 WARP 配置。",
|
||
"createAccount": "创建 WARP 账户",
|
||
"accessToken": "访问令牌",
|
||
"deviceId": "设备 ID",
|
||
"licenseKey": "许可证密钥",
|
||
"privateKey": "私钥",
|
||
"deleteAccount": "删除账户",
|
||
"settings": "设置",
|
||
"licenseKeyLabel": "WARP / WARP+ 许可证密钥",
|
||
"key": "密钥",
|
||
"keyPlaceholder": "26 位 WARP+ 密钥",
|
||
"accountInfo": "账户信息",
|
||
"deviceName": "设备名称",
|
||
"deviceModel": "设备型号",
|
||
"deviceEnabled": "设备已启用",
|
||
"accountType": "账户类型",
|
||
"role": "角色",
|
||
"warpPlusData": "WARP+ 数据",
|
||
"quota": "配额",
|
||
"usage": "使用",
|
||
"addOutbound": "添加出站"
|
||
},
|
||
"dns": {
|
||
"enable": "启用 DNS",
|
||
"enableDesc": "启用内置 DNS 服务器",
|
||
"tag": "DNS 入站标签",
|
||
"tagDesc": "此标签将在路由规则中可用作入站标签",
|
||
"clientIp": "客户端 IP",
|
||
"clientIpDesc": "用于在 DNS 查询期间通知服务器指定的 IP 位置",
|
||
"disableCache": "禁用缓存",
|
||
"disableCacheDesc": "禁用 DNS 缓存",
|
||
"disableFallback": "禁用回退",
|
||
"disableFallbackDesc": "禁用回退 DNS 查询",
|
||
"disableFallbackIfMatch": "匹配时禁用回退",
|
||
"disableFallbackIfMatchDesc": "当 DNS 服务器的匹配域名列表命中时,禁用回退 DNS 查询",
|
||
"enableParallelQuery": "启用并行查询",
|
||
"enableParallelQueryDesc": "启用并行 DNS 查询到多个服务器以实现更快的解析",
|
||
"strategy": "查询策略",
|
||
"strategyDesc": "解析域名的总体策略",
|
||
"add": "添加服务器",
|
||
"edit": "编辑服务器",
|
||
"domains": "域名",
|
||
"expectIPs": "预期 IP",
|
||
"unexpectIPs": "意外 IP",
|
||
"useSystemHosts": "使用系统 Hosts",
|
||
"useSystemHostsDesc": "使用已安装系统的 hosts 文件",
|
||
"serveStale": "提供过期结果",
|
||
"serveStaleDesc": "在后台刷新时返回过期的缓存结果",
|
||
"serveExpiredTTL": "过期 TTL",
|
||
"serveExpiredTTLDesc": "过期缓存条目的有效期(秒);0 = 永不过期",
|
||
"timeoutMs": "超时 (毫秒)",
|
||
"skipFallback": "跳过回退",
|
||
"finalQuery": "最终查询",
|
||
"hosts": "主机映射",
|
||
"hostsAdd": "添加 Host",
|
||
"hostsEmpty": "未定义任何 Host",
|
||
"hostsDomain": "域名 (例如 domain:example.com)",
|
||
"hostsValues": "IP 或域名 — 输入后按 Enter",
|
||
"usePreset": "使用模板",
|
||
"dnsPresetTitle": "DNS 模板",
|
||
"dnsPresetFamily": "家庭",
|
||
"clearAll": "删除全部",
|
||
"clearAllTitle": "删除所有 DNS 服务器?",
|
||
"clearAllConfirm": "此操作将从列表中删除所有 DNS 服务器,且无法撤销。",
|
||
"dnsLeakWarning": "DNS 可能通过 localhost、明文 UDP/TCP、本地模式 DoH/DoQ、回退查询或 EDNS client IP 泄漏。重视隐私时请使用经路由的 DoH、在 hosts 中固定解析器,并禁用回退。"
|
||
},
|
||
"fakedns": {
|
||
"add": "添加假 DNS",
|
||
"ipPool": "IP 池子网",
|
||
"poolSize": "池大小"
|
||
},
|
||
"defaultOutbound": "默认出站",
|
||
"defaultOutboundDesc": "未匹配任何路由规则的流量走此出站(列表中的第一个出站)。"
|
||
},
|
||
"hosts": {
|
||
"addHost": "添加主机",
|
||
"editHost": "编辑主机",
|
||
"selectInbound": "选择一个入站",
|
||
"selectedCount": "已选 {count} 项",
|
||
"summary": {
|
||
"total": "总计",
|
||
"enabled": "已启用",
|
||
"disabled": "已禁用"
|
||
},
|
||
"moveUp": "上移",
|
||
"moveDown": "下移",
|
||
"bulkEnable": "启用",
|
||
"bulkDisable": "禁用",
|
||
"bulkDelete": "删除",
|
||
"bulkDeleteConfirm": "删除选中的 {count} 个主机?",
|
||
"deleteConfirmTitle": "删除主机 \"{name}\"?",
|
||
"sections": {
|
||
"basic": "基本",
|
||
"security": "安全",
|
||
"advanced": "高级",
|
||
"general": "常规",
|
||
"clash": "Clash (mihomo)"
|
||
},
|
||
"fields": {
|
||
"remark": "备注",
|
||
"serverDescription": "描述",
|
||
"inbound": "入站",
|
||
"address": "地址",
|
||
"port": "端口",
|
||
"endpoint": "端点",
|
||
"enable": "启用",
|
||
"actions": "操作",
|
||
"security": "安全",
|
||
"sni": "SNI",
|
||
"overrideSniFromAddress": "使用地址作为 SNI",
|
||
"keepSniBlank": "保持 SNI 为空",
|
||
"hostHeader": "Host 头",
|
||
"path": "路径",
|
||
"alpn": "ALPN",
|
||
"fingerprint": "指纹",
|
||
"pins": "固定证书 SHA-256",
|
||
"verifyPeerCertByName": "按名称验证对端证书",
|
||
"allowInsecure": "允许不安全连接",
|
||
"echConfigList": "ECH 配置列表",
|
||
"muxParams": "Mux",
|
||
"sockoptParams": "Sockopt",
|
||
"finalMask": "Final Mask",
|
||
"vlessRoute": "VLESS 路由",
|
||
"mihomoIpVersion": "IP 版本",
|
||
"mihomoX25519": "Mihomo X25519",
|
||
"shuffleHost": "随机打乱 Host",
|
||
"tags": "标签",
|
||
"nodeGuids": "节点",
|
||
"excludeFromSubTypes": "从格式中排除",
|
||
"inheritAddress": "继承地址"
|
||
},
|
||
"hints": {
|
||
"address": "留空则继承入站自身的地址。",
|
||
"port": "填 0 则继承入站的端口。",
|
||
"tags": "对终端用户不可见;仅随 RAW 订阅发送。只能包含大写字母、数字、_ 和 :。",
|
||
"nodeGuids": "选择从此主机解析的节点。仅用于可视化关联。",
|
||
"serverDescription": "可选备注,显示在备注下方。",
|
||
"allowInsecure": "跳过 TLS 证书验证(allowInsecure / skip-cert-verify)。",
|
||
"vlessRoute": "嵌入 UUID 的单个 VLESS 路由值(0-65535),例如 443。留空表示不路由。",
|
||
"remark": "此主机的纯文本标签。仅当入站没有自己的备注时,才作为配置名称显示。"
|
||
},
|
||
"remarkVars": {
|
||
"title": "模板变量",
|
||
"intro": "点击变量即可添加。生成订阅时会按客户端逐一替换。",
|
||
"preview": "预览",
|
||
"groups": {
|
||
"client": "客户端",
|
||
"traffic": "流量",
|
||
"time": "时间与状态",
|
||
"connection": "连接"
|
||
},
|
||
"descEMAIL": "客户端邮箱",
|
||
"descINBOUND": "入站本身的备注(配置名称)",
|
||
"descHOST": "主机备注",
|
||
"descID": "客户端 UUID",
|
||
"descSHORT_ID": "UUID 的前 8 个字符",
|
||
"descTELEGRAM_ID": "客户端的 Telegram ID(未设置则为空)",
|
||
"descSUB_ID": "订阅 ID",
|
||
"descCOMMENT": "客户端备注",
|
||
"descTRAFFIC_USED": "已用流量(易读格式)",
|
||
"descTRAFFIC_LEFT": "剩余流量(无限制时隐藏)",
|
||
"descTRAFFIC_TOTAL": "总流量(无限制时隐藏)",
|
||
"descTRAFFIC_USED_BYTES": "已用流量(字节)",
|
||
"descTRAFFIC_LEFT_BYTES": "剩余流量(字节)",
|
||
"descTRAFFIC_TOTAL_BYTES": "总流量(字节)",
|
||
"descUP": "上传流量",
|
||
"descDOWN": "下载流量",
|
||
"descSTATUS": "active / expired / disabled / depleted",
|
||
"descSTATUS_EMOJI": "以表情符号显示状态(✅ ⏳ 🚫)",
|
||
"descDAYS_LEFT": "距到期天数(无限制时隐藏)",
|
||
"descTIME_LEFT": "剩余时间(例如 12d 4h 30m)",
|
||
"descUSAGE_PERCENTAGE": "已用流量百分比(无限制时隐藏)",
|
||
"descEXPIRE_DATE": "到期日期(YYYY-MM-DD)",
|
||
"descJALALI_EXPIRE_DATE": "Jalali(波斯)历的到期日期(YYYY/MM/DD)",
|
||
"descEXPIRE_UNIX": "到期时间的 Unix 时间戳(秒)",
|
||
"descCREATED_UNIX": "创建时间的 Unix 时间戳(秒)",
|
||
"descRESET_DAYS": "流量重置周期(天)",
|
||
"descRESET_DAY": "按月续期的日期",
|
||
"descPROTOCOL": "入站协议(VLESS、VMess、Trojan……)",
|
||
"descTRANSPORT": "传输网络(tcp、ws、grpc……)",
|
||
"descSECURITY": "传输安全(TLS、REALITY、NONE)"
|
||
},
|
||
"toasts": {
|
||
"list": "加载主机失败",
|
||
"obtain": "加载主机失败",
|
||
"add": "添加主机",
|
||
"update": "更新主机",
|
||
"delete": "删除主机",
|
||
"badTag": "无效的标签",
|
||
"badVlessRoute": "请输入 0 到 65535 之间的单个数字"
|
||
}
|
||
}
|
||
},
|
||
"tgbot": {
|
||
"keyboardClosed": "❌ 自定义键盘已关闭!",
|
||
"noResult": "❗ 没有结果!",
|
||
"noQuery": "❌ 未找到查询!请再次使用该命令!",
|
||
"wentWrong": "❌ 出了点问题!",
|
||
"noIpRecord": "❗ 没有 IP 记录!",
|
||
"noInbounds": "❗ 未找到入站!",
|
||
"unlimited": "♾ 无限(重置)",
|
||
"add": "添加",
|
||
"month": "月",
|
||
"months": "月",
|
||
"days": "天",
|
||
"hours": "小时",
|
||
"minutes": "分钟",
|
||
"unknown": "未知",
|
||
"inbounds": "入站",
|
||
"clients": "客户端",
|
||
"offline": "🔴 离线",
|
||
"online": "🟢 在线",
|
||
"commands": {
|
||
"unknown": "❗ 未知命令",
|
||
"pleaseChoose": "👇 请选择:\r\n",
|
||
"help": "🤖 欢迎使用本机器人!它旨在为您提供来自服务器的特定数据,并允许您进行必要的修改。\r\n\r\n",
|
||
"start": "👋 你好,<i>{{ .Firstname }}</i>。\r\n",
|
||
"welcome": "🤖 欢迎来到 <b>{{ .Hostname }}</b> 管理机器人。\r\n",
|
||
"status": "✅ 机器人正常运行!",
|
||
"usage": "❗ 请输入要搜索的文本!",
|
||
"getID": "🆔 您的 ID 为:<code>{{ .ID }}</code>",
|
||
"helpAdminCommands": "要重新启动 Xray Core:\r\n<code>/restart</code>\r\n\r\n要搜索客户电子邮件:\r\n<code>/usage [电子邮件]</code>\r\n\r\n要搜索入站(带有客户统计数据):\r\n<code>/inbound [备注]</code>\r\n\r\nTelegram 聊天 ID:\r\n<code>/id</code>",
|
||
"helpClientCommands": "要搜索统计数据,请使用以下命令:\r\n<code>/usage [电子邮件]</code>\r\n\r\nTelegram 聊天 ID:\r\n<code>/id</code>",
|
||
"restartUsage": "\r\n\r\n<code>/restart</code>",
|
||
"restartSuccess": "✅ 操作成功!",
|
||
"restartFailed": "❗ 操作错误。\r\n\r\n<code>错误: {{ .Error }}</code>.",
|
||
"xrayNotRunning": "❗ Xray Core 未运行。",
|
||
"startDesc": "显示主菜单",
|
||
"helpDesc": "机器人帮助",
|
||
"statusDesc": "检查机器人状态",
|
||
"idDesc": "显示您的 Telegram ID",
|
||
"usageDesc": "查看客户端用量:/usage 邮箱",
|
||
"inboundDesc": "搜索入站:/inbound 备注(管理员)",
|
||
"restartDesc": "重启 Xray 内核(管理员)",
|
||
"clearallDesc": "重置所有客户端流量(管理员)"
|
||
},
|
||
"messages": {
|
||
"cpuThreshold": "CPU 使用率为 {{ .Percent }}%,超过阈值 {{ .Threshold }}%",
|
||
"selectUserFailed": "❌ 用户选择错误!",
|
||
"userSaved": "✅ 电报用户已保存。",
|
||
"loginSuccess": "✅ 成功登录到面板。\r\n",
|
||
"loginFailed": "❗️ 面板登录失败。\r\n",
|
||
"report": "🕰 定时报告:{{ .RunTime }}\r\n",
|
||
"datetime": "⏰ 日期时间:{{ .DateTime }}\r\n",
|
||
"hostname": "💻 主机: {{ .Hostname }}\r\n",
|
||
"version": "🚀 X-UI 版本:{{ .Version }}\r\n",
|
||
"xrayVersion": "📡 Xray 版本: {{ .XrayVersion }}\r\n",
|
||
"ipv6": "🌐 IPv6: {{ .IPv6 }}\r\n",
|
||
"ipv4": "🌐 IPv4: {{ .IPv4 }}\r\n",
|
||
"ip": "🌐 IP: {{ .IP }}\r\n",
|
||
"ips": "🔢 IPs:\r\n{{ .IPs }}\r\n",
|
||
"serverUpTime": "⏳ 服务器运行时间:{{ .UpTime }} {{ .Unit }}\r\n",
|
||
"serverLoad": "📈 服务器负载:{{ .Load1 }}, {{ .Load2 }}, {{ .Load3 }}\r\n",
|
||
"serverMemory": "📋 RAM: {{ .Current }}/{{ .Total }}\r\n",
|
||
"tcpCount": "🔹 TCP: {{ .Count }}\r\n",
|
||
"udpCount": "🔸 UDP: {{ .Count }}\r\n",
|
||
"traffic": "🚦 流量:{{ .Total }} (↑{{ .Upload }},↓{{ .Download }})\r\n",
|
||
"xrayStatus": "ℹ️ 状态: {{ .State }}\r\n",
|
||
"username": "👤 用户名:{{ .Username }}\r\n",
|
||
"reason": "❗️ 原因:{{ .Reason }}\r\n",
|
||
"time": "⏰ 时间:{{ .Time }}\r\n",
|
||
"inbound": "📍 入站: {{ .Remark }}\r\n",
|
||
"port": "🔌 端口: {{ .Port }}\r\n",
|
||
"expire": "📅 过期日期:{{ .Time }}\r\n",
|
||
"expireIn": "📅 剩余时间:{{ .Time }}\r\n",
|
||
"active": "💡 激活:{{ .Enable }}\r\n",
|
||
"enabled": "🚨 已启用:{{ .Enable }}\r\n",
|
||
"online": "🌐 连接状态:{{ .Status }}\r\n",
|
||
"lastOnline": "🔙 上次在线: {{ .Time }}\r\n",
|
||
"email": "📧 邮箱: {{ .Email }}\r\n",
|
||
"upload": "🔼 上传: ↑{{ .Upload }}\r\n",
|
||
"download": "🔽 下载: ↓{{ .Download }}\r\n",
|
||
"total": "📊 总计: ↑↓{{ .UpDown }} / {{ .Total }}\r\n",
|
||
"TGUser": "👤 电报用户:{{ .TelegramID }}\r\n",
|
||
"exhaustedCount": "🚨 耗尽的 {{ .Type }} 数量:\r\n",
|
||
"onlinesCount": "🌐 在线客户:{{ .Count }}\r\n",
|
||
"disabled": "🛑 禁用:{{ .Disabled }}\r\n",
|
||
"depleteSoon": "🔜 即将耗尽:{{ .Deplete }}\r\n\r\n",
|
||
"backupTime": "🗄 备份时间:{{ .Time }}\r\n",
|
||
"refreshedOn": "\r\n📋🔄 刷新时间:{{ .Time }}\r\n\r\n",
|
||
"yes": "✅ 是的",
|
||
"no": "❌ 否",
|
||
"received_email": "📧📥 邮箱已更新。",
|
||
"received_comment": "💬📥 评论已更新。",
|
||
"email_prompt": "📧 默认邮箱: {{ .ClientEmail }}\n\n请输入您的邮箱。",
|
||
"comment_prompt": "💬 默认评论: {{ .ClientComment }}\n\n请输入您的评论。",
|
||
"cancel": "❌ 进程已取消!\n\n您可以随时使用 /start 重新开始。 🔄",
|
||
"error_add_client": "⚠️ 错误:\n\n {{ .error }}",
|
||
"using_default_value": "好的,我会使用默认值。 😊",
|
||
"incorrect_input": "您的输入无效。\n短语应连续输入,不能有空格。\n正确示例: aaaaaa\n错误示例: aaa aaa 🚫",
|
||
"AreYouSure": "你确定吗?🤔",
|
||
"SuccessResetTraffic": "📧 邮箱: {{ .ClientEmail }}\n🏁 结果: ✅ 成功",
|
||
"FailedResetTraffic": "📧 邮箱: {{ .ClientEmail }}\n🏁 结果: ❌ 失败 \n\n🛠️ 错误: [ {{ .ErrorMessage }} ]",
|
||
"FinishProcess": "🔚 所有客户的流量重置已完成。",
|
||
"eventOutboundDown": "出站 {{ .Tag }} 已断开",
|
||
"eventOutboundUp": "出站 {{ .Tag }} 已恢复",
|
||
"eventErrorDetail": "错误:{{ .Error }}",
|
||
"eventDelayDetail": "延迟:{{ .Delay }} 毫秒",
|
||
"eventXrayCrash": "Xray 已崩溃",
|
||
"eventXrayCrashError": "错误:{{ .Error }}",
|
||
"eventNodeDown": "节点 {{ .Name }} 已离线",
|
||
"eventNodeUp": "节点 {{ .Name }} 已上线",
|
||
"eventLoginFallback": "来自 {{ .Source }} 的登录失败",
|
||
"memoryThreshold": "内存使用率 {{ .Percent }}% 超过阈值 {{ .Threshold }}%"
|
||
},
|
||
"buttons": {
|
||
"closeKeyboard": "❌ 关闭键盘",
|
||
"cancel": "❌ 取消",
|
||
"cancelReset": "❌ 取消重置",
|
||
"cancelIpLimit": "❌ 取消 IP 限制",
|
||
"confirmResetTraffic": "✅ 确认重置流量?",
|
||
"confirmClearIps": "✅ 确认清除 IP?",
|
||
"confirmRemoveTGUser": "✅ 确认移除 Telegram 用户?",
|
||
"confirmToggle": "✅ 确认启用/禁用用户?",
|
||
"dbBackup": "获取数据库备份",
|
||
"serverUsage": "服务器使用情况",
|
||
"getInbounds": "获取入站信息",
|
||
"depleteSoon": "即将耗尽",
|
||
"clientUsage": "获取使用情况",
|
||
"onlines": "在线客户端",
|
||
"commands": "命令",
|
||
"refresh": "🔄 刷新",
|
||
"clearIPs": "❌ 清除 IP",
|
||
"removeTGUser": "❌ 移除 Telegram 用户",
|
||
"selectTGUser": "👤 选择 Telegram 用户",
|
||
"selectOneTGUser": "👤 选择一个 Telegram 用户:",
|
||
"resetTraffic": "📈 重置流量",
|
||
"resetExpire": "📅 更改到期日期",
|
||
"ipLog": "🔢 IP 日志",
|
||
"ipLimit": "🔢 IP 限制",
|
||
"setTGUser": "👤 设置 Telegram 用户",
|
||
"toggle": "🔘 启用/禁用",
|
||
"custom": "🔢 自定义",
|
||
"confirmNumber": "✅ 确认: {{ .Num }}",
|
||
"confirmNumberAdd": "✅ 确认添加:{{ .Num }}",
|
||
"limitTraffic": "🚧 流量限制",
|
||
"getBanLogs": "禁止日志",
|
||
"allClients": "所有客户",
|
||
"addClient": "添加客户",
|
||
"submitDisable": "提交为禁用 ☑️",
|
||
"submitEnable": "提交为启用 ✅",
|
||
"use_default": "🏷️ 使用默认",
|
||
"change_email": "⚙️📧 邮箱",
|
||
"change_comment": "⚙️💬 评论",
|
||
"ResetAllTraffics": "重置所有流量",
|
||
"SortedTrafficUsageReport": "排序的流量使用报告"
|
||
},
|
||
"answers": {
|
||
"successfulOperation": "✅ 成功!",
|
||
"errorOperation": "❗ 操作错误。",
|
||
"getInboundsFailed": "❌ 获取入站信息失败。",
|
||
"getClientsFailed": "❌ 获取客户失败。",
|
||
"canceled": "❌ {{ .Email }}:操作已取消。",
|
||
"clientRefreshSuccess": "✅ {{ .Email }}:客户端刷新成功。",
|
||
"IpRefreshSuccess": "✅ {{ .Email }}:IP 刷新成功。",
|
||
"TGIdRefreshSuccess": "✅ {{ .Email }}:客户端的 Telegram 用户刷新成功。",
|
||
"resetTrafficSuccess": "✅ {{ .Email }}:流量已重置成功。",
|
||
"setTrafficLimitSuccess": "✅ {{ .Email }}: 流量限制保存成功。",
|
||
"expireResetSuccess": "✅ {{ .Email }}:过期天数已重置成功。",
|
||
"resetIpSuccess": "✅ {{ .Email }}:成功保存 IP 限制数量为 {{ .Count }}。",
|
||
"clearIpSuccess": "✅ {{ .Email }}:IP 已成功清除。",
|
||
"getIpLog": "✅ {{ .Email }}:获取 IP 日志。",
|
||
"getUserInfo": "✅ {{ .Email }}:获取 Telegram 用户信息。",
|
||
"removedTGUserSuccess": "✅ {{ .Email }}:Telegram 用户已成功移除。",
|
||
"enableSuccess": "✅ {{ .Email }}:已成功启用。",
|
||
"disableSuccess": "✅ {{ .Email }}:已成功禁用。",
|
||
"askToAddUserId": "未找到您的配置!\r\n请向管理员询问,在您的配置中使用您的 Telegram 用户 ChatID。\r\n\r\n您的用户 ChatID:<code>{{ .TgUserID }}</code>",
|
||
"chooseClient": "为入站 {{ .Inbound }} 选择一个客户",
|
||
"chooseInbound": "选择一个入站"
|
||
}
|
||
},
|
||
"email": {
|
||
"labelStatus": "状态",
|
||
"labelOutbound": "出站",
|
||
"labelNode": "节点",
|
||
"labelError": "错误",
|
||
"labelDelay": "延迟",
|
||
"labelUsername": "用户名",
|
||
"labelIP": "IP",
|
||
"labelReason": "原因",
|
||
"labelSource": "来源",
|
||
"statusCrashed": "已崩溃",
|
||
"statusHigh": "过高",
|
||
"statusSuccess": "成功",
|
||
"statusFailed": "失败",
|
||
"statusDown": "断开",
|
||
"statusUp": "恢复"
|
||
}
|
||
}
|