mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-07-12 07:36:07 +00:00
97e2c9e7ba8d3d80b095edc3f2dbeb7399b56dca
34 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
97e2c9e7ba |
fix(web): sync the VLESS generate-key dropdown with the encryption field
The auth-kind dropdown in the VLESS "Generate Key" block was hardcoded to x25519 on mount, while the "Already selected" text next to it was derived independently from settings.encryption. Editing an inbound whose encryption uses another kind (e.g. ML-KEM-768) showed a mismatched dropdown, and clicking Generate without noticing would produce a keypair of the wrong kind for the inbound. Extract the encryption-string parsing into a shared pure helper (lib/xray/vless-encryption), use it both for the selected-auth label and to initialize/sync the dropdown, so the two can no longer diverge. When the encryption is none or unparseable the dropdown keeps its x25519 default. Closes #5744 |
||
|
|
c8ef1b1f68 |
feat(reality): derive a stable per-client spiderX for shared links
The inbound's spiderX now acts as a per-client seed: exports emit sha256(seed|subKey) truncated to a 15-hex "/path", so a client's spx no longer changes on every subscription fetch (#5718) while different clients stop sharing one fingerprintable value. The form gains a regenerate button that rotates every client's path at once. The frontend link builders derive through the same function (lib/xray/spider-x.ts, @noble/hashes) keyed on subId-then-email like the Go subKey, so panel QR/copy links and subscription output agree — cross-language vector tests lock both sides byte-for-byte. streamData now tolerates malformed stored stream settings (unparseable JSON, null tls/reality settings) instead of panicking the subscription request. |
||
|
|
9c8cd08f90 |
feat(wireguard): multi-client support
WireGuard inbounds now manage per-client peers using xray-core's native WireGuard users (AddUser/RemoveUser). Each client lives in settings.clients (canonical, like every other protocol) and is projected to peers[] only when emitting the xray config, at level 0 so the dispatcher's per-user traffic/online counters work with no extra plumbing. Backend: internal/util/wireguard gains KeyToHex (base64 to hex for the gRPC path), PublicKeyFromPrivate and GenerateWireguardPSK; xray/api.go builds a wireguard account in AddUser with hex keys (RemoveUser already worked); client CRUD generates a keypair and allocates a unique tunnel address per client and never rotates keys on edit; an idempotent migration converts legacy settings.peers into managed clients; WireGuard is included in the raw subscription. Frontend: WireGuard in the add-client modal with keys on the credential tab, client schema, per-client QR/link/.conf, inbound form reduced to server settings; i18n added across 13 locales. Fix: guard the settings[clients] assertion in add/update so a legacy WireGuard inbound stored without a clients key no longer panics. |
||
|
|
6964d84742 |
feat(reality): add live REALITY target scanner with IP/CIDR discovery
Replace the static reality-targets list with a server-side TLS 1.3 probe that checks TLS 1.3 + HTTP/2 + X25519 + a trusted certificate. - Single-domain validate auto-fills target and serverNames from the cert SAN - Discovery scans an IP/CIDR without SNI to find new targets from their certificates, deduped and ranked by feasibility then latency, private-IP guarded via netsafe - New endpoints scanRealityTarget and scanRealityTargets with RealityScanResult, plus openapigen and api-docs entries - Add scanner strings to all 13 locales - Replace deprecated AntD Alert message prop with title across the panel |
||
|
|
3ba43bd86d |
feat(web): vless encryption new modes (#5517)
* feat(web): add vless encryption new modes * feat(web): add translations for vless encryption modes * feat(translation): bring "vlessAuthX25519" and "vlessAuthMlkem768" to general form |
||
|
|
fea3c94b11 |
feat(xhttp): support sessionID* rename + sessionIDTable/Length (xray v26.6.22) (#5506)
* feat(xhttp): support sessionID* rename + sessionIDTable/Length (xray v26.6.22) xray-core v26.6.22 (PR #6258) renamed the XHTTP session config keys sessionPlacement/sessionKey to sessionIDPlacement/sessionIDKey (no fallback kept in core) and added sessionIDTable (predefined charset name or literal ASCII) and sessionIDLength (range, e.g. 16-32, lower bound > 0). Panel changes: - Schema (xhttp.ts): rename the two keys, add sessionIDTable/sessionIDLength, and a z.preprocess that lifts legacy keys off stored configs so an upgraded panel never silently drops a saved session setting. - Wire normalize + share-link build/parse: rename keys, emit the two new fields, and accept legacy sessionPlacement/sessionKey from old share links. - Inbound + outbound XHTTP forms: rename field paths, add a sessionIDTable autocomplete (9 predefined tables + free ASCII) and a sessionIDLength range input shown only when a table is set, with light client validation (ASCII table, length min > 0; xray enforces the room-size minimum server-side). - Subscription (service.go) and Clash (clash_service.go) builders: emit the renamed + new keys, with a legacy fallback for not-yet-resaved inbounds. - Locales: add sessionIDTable/sessionIDLength labels + hints in all 13 files. Two sibling v26.6.22 XHTTP commits need no panel change and are covered by the core bump alone: #6332 (XHTTP/3 closes QUIC/UDP) and #6320 (udpHop honors the existing dialerProxy). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * test(xhttp): add Session ID Table to inbound form-blocks snapshot The new sessionIDTable input renders by default in the inbound XHTTP form, so its label joins the field-structure snapshot. sessionIDLength stays conditional (only shown when a table is set), so it does not appear here. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(xhttp): migrate legacy session keys in the running xray config The Zod preprocess plus the subscription/Clash fallbacks only covered the panel UI and share-link output. The config handed to the running xray-core process is built from the raw stored streamSettings in GetXrayConfig, which did not rewrite the renamed XHTTP session keys — so a pre-upgrade inbound (or template outbound) stored with a non-default sessionPlacement was emitted unchanged and dropped by xray-core v26.6.22, until the admin re-saved it. Lift sessionPlacement/sessionKey onto sessionIDPlacement/sessionIDKey at config-generation time, in the existing inbound stream-rewrite block (next to the tls/reality/externalProxy handling) and across template outbounds. The lift is idempotent and leaves unchanged configs byte-identical so the hot-reload diff never sees a spurious change. Also tighten validateSessionIDLength to reject an inverted range (e.g. 32-16) in addition to the existing lower-bound > 0 check. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(xray): avoid summed-capacity allocation in mergeSubscriptionOutbounds CodeQL go/allocation-size-overflow flagged the pre-sized make() whose capacity was a sum of three slice lengths. Grow the slice via append on a nil slice instead; same result, no overflow-prone capacity expression. |
||
|
|
852b53db79 |
feat(xray): add loopback sniffing and per-segment fragment masks
- Loopback outbound: add sniffing support (xray-core #6320) - FinalMask fragment: support per-segment lengths/delays arrays with legacy length/delay migration (xray-core #6334) - Consolidate sniffing into a shared SniffingFields component and the canonical SniffingSchema across inbound, VLESS reverse, and loopback |
||
|
|
679d2e1cca |
fix: resolve a batch of open bug-tagged issues (traffic accounting, share strategy, sub address, CPU) (#5477)
* fix(node): never re-add a node's full counter on reset/restart (#5456, #5476, #5390) When a node's per-client counter dips below the master's stored baseline (node reboot, xray restart, or a reset propagated to the node), the delta accounting clamped delta to the node's whole current counter and re-added it to the master total — double-counting a client's lifetime usage in a single sync and often pushing them over quota. Treat a backward-moving counter as a reset: add 0 and rebaseline to the reported value, so only genuine post-reset usage accrues. Resets also now clear the per-node NodeClientTraffic baseline (ResetClient TrafficByEmail, resetClientTrafficLocked, BulkResetTraffic, resetAllClient TrafficsLocked), mirroring the delete paths. Without this the node's pre-reset cumulative — including traffic it had counted but not yet synced — leaks back onto the master after a reset, which is the 'reset reverts after a while' report. The next sync then takes the clean delta=0 + rebaseline path regardless of node state. Updates TestNodeCounterReset (was _Clamped, now _NoReAdd) to assert rebaseline instead of re-add, and adds TestCentralResetClearsNodeBaseline_NoLeak. * fix(inbound): keep persisted node share strategy on edit (#5375) Opening the edit modal silently reverted shareAddrStrategy from 'node' to 'listen'. The downgrade effect fires before the form settles: availableNodes is an empty placeholder until /nodes/list resolves, and Form.useWatch('protocol') is briefly empty on the first edit render — both transiently make the node option look unavailable, so the effect clobbered the saved value. Gate the downgrade on availableNodesFetched (threaded from useNodesQuery through InboundsPage) and on the protocol watch being settled, so a persisted strategy is only downgraded when the node option is genuinely unavailable. Adds a rerender-based regression test covering the nodes-loading race. * <3 * perf(traffic): skip cross-panel quota subquery when no globals exist (#5392, #5389) disableInvalidClients ran a correlated EXISTS against client_global_traffics on the full client_traffics table every 5s. On a panel no master pushes to, that table is empty so the subquery can never match — yet it forced a full scan that pegged Postgres at 100% CPU on large client counts. Probe the table first and drop the EXISTS branch when it's empty (the common case), and add an idx_client_global_email index so the subquery is an index lookup when globals are present. Cross-panel enforcement is unchanged (TestGlobalUsage_DisablesClient). This also relieves #5389 ('traffic writer queue full' / panel freeze): the heavy query runs inside the serialized traffic write, so a slow DB backs the shared writer queue up until request handlers block. * fix(sub): don't advertise a leaked client IP for local wildcard inbounds (#5425) For a local inbound with no node, no custom share address, and a wildcard/blank listen, resolveInboundAddress fell straight through to the subscriber's request host. Behind NAT/proxy/CDN that Host can be the requesting client's own IP, so the subscription wrote the client's address into the inbound instead of the server's — while the panel's own share link (which doesn't use the request host) stayed correct. Prefer the admin's configured public host (Sub/Web domain) over the raw request host for this last-resort fallback. With no configured host the request host still stands, so existing single-domain setups are unaffected. |
||
|
|
7c8889466b |
feat(tls,reality): port xray TLS/REALITY fields, cert-hash helpers, fallback UX
TLS: add verifyPeerCertByName (vcn) to inbound settings + emit in both share-link generators (frontend + Go sub) and outbound parser; the allowInsecure replacement xray removed after 2026-06-01. Add server-side curvePreferences, masterKeyLog, echSockopt (passthrough + form) at tlsSettings top-level so they survive the panel-only settings strip. REALITY: add limitFallbackUpload/Download (afterBytes/bytesPerSec/burstBytesPerSec) with per-field tooltips, plus masterKeyLog. Verified field names/semantics against pinned xray v1.260327.1 (bytesPerSec=0 disables). Hosts: fix verify_peer_cert_by_name column bool->string (xray expects comma-separated names) with an idempotent, history-gate-free migration (SQLite typeof blank; Postgres ALTER once); emit vcn for hosts/external proxies. Server: add getCertHash (local cert DER SHA-256) and getRemoteCertHash (xray tls ping) endpoints + api-docs; wire pinned-cert field buttons. Drop the meaningless random-hash button. Xray UI: metrics endpoint (listen/tag) config in Basics; import/export for routing rules and outbounds. Fallbacks card: compact empty state, header-aligned actions, responsive labeled grid rows. i18n: add all new keys to every locale; drop unused generateRandomPin. |
||
|
|
709b332d17 |
feat(hosts): managed Hosts for per-host subscription link overrides (#5409)
* test(sub): characterize current link output (externalProxy + single-link baselines)
Phase 0 of the Hosts feature. Locks current subscription-link output for the
externalProxy paths (vless/vmess/trojan/ss exact, reality/hysteria by Contains)
so the upcoming ShareEndpoint refactor can be proven behavior-preserving. These
must stay green and unedited through every later phase.
* refactor(sub): unify external-proxy link building behind ShareEndpoint (TDD, snapshot-locked)
Phase 1 of the Hosts feature. Collapse the duplicated externalProxy link
builders (param-form for vless/trojan/ss, object-form for vmess) onto a single
ShareEndpoint abstraction so Phase 4 can add Host-driven links with ~zero new
branching.
Design: an externalProxy-derived endpoint carries the original entry map and
applies it through the UNCHANGED applyExternalProxyTLS{Params,Obj} helpers, so
output is provably byte-identical. buildExternalProxyURLLinks /
buildVmessExternalProxyLinks become thin adapters; the genVless/Trojan/SS/Vmess
call sites are untouched. genHysteriaLink is deliberately left on its own path
(hex pinSHA256, not pcs). The no-externalProxy default tails are unchanged.
TDD: N1-N4 (externalProxyToEndpoint, inboundDefaultEndpoint, buildEndpointLinks,
buildEndpointVmessLinks) written failing-first against stubs, then implemented.
Mutation sanity (performed + reverted): dropping the ep-carry in
externalProxyToEndpoint makes the Phase-0 C1/C2 characterization snapshots go
red (TLS overrides vanish), proving the snapshots guard the emitted output.
Gate: go test ./internal/sub/... and go test ./... green with ZERO edits to the
Phase-0 snapshots; go build ./... green on linux and windows; go vet clean.
* feat(model): Host entity + automigrate + openapi codegen (TDD)
Phase 2 of the Hosts feature. Adds the Host GORM model: an override endpoint
attached to an inbound (address/port + TLS/transport/clash overrides + sub
scoping), superseding the legacy externalProxy array functionally while leaving
it intact.
- model.Host with snake_case column tags, json serializer for slices, text for
free-JSON (mux/sockopt/xhttp), validate tags (remark 1-40, port 0-65535,
security + mihomoIpVersion enums); TableName "hosts". NodeGuids column is added
now but unused (host->node scoping deferred to v2).
- Registered in BOTH initModels() (db.go) and migrationModels() (migrate_data.go);
the latter is required for cross-DB migration and is easy to miss. PG sequence
resync iterates the initModels slice, so it is covered automatically.
- pruneOrphanedHosts() deletes hosts whose inbound_id has no inbound, called
alongside pruneOrphanedClientInbounds().
- openapigen manifest: Host added to StructAllow with MuxParams/SockoptParams/
XhttpExtraParams -> KindAny; regenerated frontend/src/generated/* + openapi.json.
TDD: TestHostTableName, TestHostValidation, TestHostAutoMigrateCreatesColumns
(+ _Postgres), TestPruneOrphanedHosts written failing-first against a wrong-name,
untagged, unregistered stub, then implemented.
Gate: go test ./... green on SQLite AND a real Postgres DSN (local container);
go build/vet/gofmt clean; npm run gen succeeds with the new Host type/schema/
example/zod; npm run typecheck + npm run test (542) green.
* feat(api): Host CRUD service + controller + routes (TDD)
Phase 3 of the Hosts feature.
- service/host.go (HostService, empty struct + database.GetDB() like
ClientService): GetHosts, GetHostsByInbound, GetHost, AddHost (verifies the
inbound exists — no hard FK), UpdateHost (inbound + sort order immutable here),
DeleteHost, SetHostEnable, SetHostsEnable, DeleteHosts, ReorderHosts (single
driver-safe transaction), GetAllTags.
- controller/host.go mirrors NodeController: routes under /panel/api/hosts
(list/get/byInbound/tags + add/update/del/setEnable/reorder + bulk/setEnable,
bulk/del), binds via middleware.BindAndValidate so the model validate tags are
enforced, {success,msg,obj} envelopes.
- Wired the hosts group into api.go after nodes (inherits checkAPIAuth + CSRF).
- DelInbound now cascades: deleting an inbound deletes its hosts.
- Documented all 11 routes in api-docs endpoints.ts (referencing the generated
Host schema) and regenerated openapi.json; extended TestAPIRoutesDocumented's
controller->basePath switch for host.go. Backend en toast keys added.
TDD: service tests (Add/GetByInbound, RejectsUnknownInbound, Reorder, Set/Bulk
enable, DeleteHosts, DeleteInboundCascadesHosts, GetAllTags) written failing-
first against a nil-returning stub; controller test (AddListGetDelete envelope
round-trip + AuthInherited 401) added.
Gate: go test ./internal/web/... + go test ./... green; npm run gen + typecheck
+ lint + test (542) + build green.
* feat(sub): render subscription links from hosts; legacy fallback when none (TDD, mutation-checked)
Phase 4 of the Hosts feature. Inserts host resolution between inbound and link
across all three subscription formats.
Mechanism: hostEndpoints(inbound, format) loads the inbound's enabled hosts
(filtered by ExcludeFromSubTypes, ordered by sort_order then id) and projects
each onto the externalProxy entry shape the raw/json/clash renderers already
consume. So a host fans out one link/proxy reusing the exact existing rendering
(address/port/security/sni/fp/alpn/pins/ech) with zero new TLS code. Host header
and path overrides are applied additively in the raw builders (no-op for legacy
externalProxy, which never carries those keys — characterization snapshots stay
green). Clash ip-version (MihomoIpVersion) is set last on the proxy.
Integration points:
- getSubs (raw): per inbound, hostEndpoints AFTER projectThroughFallbackMaster;
len>0 -> linkFromHosts (renders only the hosts), else legacy GetLink.
- GetJson/GetClash: inject the host endpoints into the inbound's externalProxy
before the existing getConfig/getProxies loop.
- Precedence: hosts win over any legacy externalProxy (injection replaces it).
Backward compat: a zero-host inbound takes the legacy path -> byte-identical
output (all Phase-0 characterization snapshots unchanged).
TDD: 9 cycles (zero-hosts identical, N-links-ordered with host/path override,
disabled skipped, host-vs-externalProxy precedence, no-dedup, sort composes with
SubSortIndex, host-over-fallback, resolve-via-client-inbounds, ExcludeFromSubTypes
per format) written failing-first against unwired helpers, then wired green.
Mutation sanity (performed + reverted, documented here):
- zero-hosts fallback: flipping the len(hostEps)>0 guard to >=0 makes
TestSub_ZeroHosts_IdenticalOutput go red (host path yields "" for no hosts).
- no-dedup: adding a remark-dedup in hostEndpoints makes TestSub_NHosts_NoDedup
go red (two distinct hosts collapse to one link).
Gate: go test ./internal/sub/... + go test ./... green with ZERO edits to the
Phase-0 snapshots; go build green on linux and windows; go vet + gofmt clean.
* feat(migration): seed hosts from inbound externalProxy (TDD, idempotent, dual-driver)
Phase 5 of the Hosts feature. One-time migration so existing installs surface
their legacy externalProxy entries as first-class Host rows.
- seedHostsFromExternalProxy() is self-gated on a HistoryOfSeeders
"HostsFromExternalProxy" row (run-once) and wired into runSeeders. For each
inbound it parses StreamSettings, reads externalProxy[], and creates one Host
per entry: forceTls->Security (unknown->same), dest->Address, port->Port,
remark->Remark (generated when blank, capped at 40), sni/fingerprint/alpn/
pinnedPeerCertSha256/echConfigList copied; SortOrder=index; InboundId set.
- Additive: externalProxy is left intact in StreamSettings (rollback-safe; the
sub layer prefers hosts when present, §Phase 4).
- Postgres: GORM db.Create advances hosts_id_seq via the sequence, so no extra
resync is needed beyond the existing startup resync.
TDD: field-mapping, idempotency (second run no-op), no-externalProxy->no-hosts,
externalProxy-kept-intact written failing-first against a stub; plus a
Postgres counterpart that skips without XUI_DB_DSN.
Gate: go test ./internal/web/service/... ./internal/database/... green on SQLite;
the *_Postgres tests green against a real Postgres container; go build green on
linux and windows; go vet + gofmt clean. (Running the whole database package
under XUI_DB_TYPE=postgres is not supported — the SQLite-path tests share the one
DSN — so only the t.Skip-gated *_Postgres tests run with the env set.)
* feat(ui): Hosts page + schema + query hooks + link preview helper (TDD on schema/helpers)
Phase 6 of the Hosts feature — the admin UI.
- schemas/api/host.ts: HostFormSchema (validation: remark 1-40, tags ^[A-Z0-9_:]+$
≤10×≤36, port 0-65535, security/mihomoIpVersion enums, alpn/fingerprint reused
from the shared primitives) + a loose HostRecordSchema/HostListSchema for reads.
- lib/hosts/host-link.ts: hostToExternalProxyEntry — the frontend mirror of the
backend hostToExternalProxyMap (security->forceTls, sni override rules, port
inherit), for share-link previews.
- api/queries/useHostsQuery.ts + useHostMutations.ts (mirror the node hooks):
list/get + add/update/del/setEnable/reorder/bulk; queryKeys.hosts.* added;
mutations invalidate keys.hosts.root().
- pages/hosts/{HostsPage,HostList,HostFormModal}.tsx (+CSS) mirroring pages/nodes:
list with remark · address:port · inbound · security · tags · enable Switch ·
per-inbound move up/down (reorder) · bulk enable/disable/delete; form grouped
into Basic / Advanced / Clash / Subscription-scope sections.
- Route '/hosts' + sidebar item (Global icon); menu.hosts + pages.hosts.* added to
the en-US bundle (other locales fall back to English until translated).
TDD: HostFormSchema (10 cases) and hostToExternalProxyEntry (6 cases) written
failing-first, then implemented. UI verified by lint/typecheck/test/build.
Deferred (documented enhancement): the live in-form share-link preview (needs
inbound+client context) and a per-host host/path override in JSON/Clash output
(raw already overrides; JSON/Clash inherit the inbound's host/path).
Gate: cd frontend && npm run lint && npm run typecheck && npm run test (557) &&
npm run build all green; go build ./... + go test ./... still green.
* refactor(ui): remove the External Proxy form from the inbound stream settings
Hosts supersede the legacy externalProxy: the subscription renders from hosts
(hosts win when both exist) and the migration converts existing externalProxy
entries to hosts. externalProxy's only real consumers were the subscription
(now covered) and this form's preview — the backend per-client copy-link never
used it — so removing the editor has no functional regression.
- Drop ExternalProxyForm + toggleExternalProxy from InboundFormModal and delete
the orphaned form component + its export; remove its block test + snapshot.
- KEEP the externalProxy schema field and backend parsing/link-generation: an
existing inbound's externalProxy still round-trips through the form (not
silently destroyed on edit) and still renders if a host was removed.
Gate: cd frontend && npm run typecheck + lint + test (556) + build green.
* fix(ui): use Alert `title` instead of deprecated `message` (antd 6)
Ant Design 6 deprecated <Alert message=> in favor of <Alert title=>; the panel
was mid-migration (21 Alerts already on title). Renamed the 7 remaining stragglers
across 5 files (SubLinksModal, InboundFormModal, sockopt, EmailTab, TelegramTab),
silencing the runtime deprecation warning. description= is unchanged.
Pre-existing warning, surfaced while testing Hosts — not introduced by it.
Gate: npm run typecheck + lint + test (556) + build green.
* style(ui): align Hosts page with Clients/Inbounds cards + reorder columns
- page-shell.css never listed .hosts-page, so the Hosts page got no content
padding / transparent-layout / summary-card spacing. Add a .hosts-page shell
block (background, dark/ultra vars, content-area + summary-card padding). This
is the actual "card spacing" bug.
- HostList: match the Clients/Inbounds list card — hoverable + the toolbar moved
into the card title as a .card-toolbar (Add when nothing selected; selected
count + bulk enable/disable/delete on selection). Re-declare .card-toolbar in
HostList.css since the shared rule lives in a lazily-loaded page stylesheet.
- Reorder table columns as requested: Actions, Enable, then Remark, Endpoint,
Inbound, Security, Tags. Added scroll x for narrow screens.
- HostsPage: add a summary card (Total / Enabled / Disabled) like the other
pages. New i18n keys: pages.hosts.selectedCount + pages.hosts.summary.*.
Gate: npm run typecheck + lint + test (556) + build green.
* style(ui): use Tabs instead of Collapse in the Add/Edit Host form
The Basic / Advanced / Clash / Subscription-scope sections are now tabs. Each
pane sets forceRender so all fields stay mounted — required because the form
uses preserve=false, so an unmounted tab's values would otherwise be dropped on
submit (and a required field on a hidden tab still blocks submit).
Gate: npm run typecheck + lint + test (556) + build green.
* style(ui): split Host form into Security + Advanced tabs; drop unused JSON fields
- Remove the Mux/Sockopt/XHTTP raw-JSON fields from the Host form: they were not
wired into link generation and the inbound's structured editors are inbound-
specific (not reusable). The DB columns + read schema + generated type stay, so
they can get proper editors later. (HostFormSchema drops them; HostRecordSchema
keeps them.)
- Reorganize tabs to Basic / Security / Advanced / Clash / Subscription scope:
Security holds the TLS/cert fields (security, sni, sni-overrides, alpn,
fingerprint, pins, verify-by-name, ech); Advanced now holds the transport
overrides (host header, path).
- i18n: add pages.hosts.sections.security; drop the 3 unused field labels.
Gate: npm run typecheck + lint + test (556) + build green.
* style(ui): restore Mux/Sockopt/XHTTP fields in the Host Advanced tab
Put the three free-JSON override fields back, in the Advanced tab next to host
header / path (as JSON inputs — the inbound's structured editors aren't reusable
here). Re-added to HostFormSchema + defaults + the i18n labels.
Gate: npm run typecheck + lint + test (556) + build green.
* feat(hosts): add allowInsecure (rendered) + serverDescription/mihomoX25519/vlessRouteId fields
Closes most of the Remnawave-host gap analysis.
- model.Host: + allowInsecure, serverDescription (≤64), vlessRouteId (0-65535),
mihomoX25519. Auto-migrated (SQLite + Postgres verified); openapi regenerated.
- allowInsecure is fully RENDERED into subscription output (TDD):
- raw link: allowInsecure=1 (TLS/Reality, skipped for none) via the endpoint
builder;
- JSON/Clash: applyExternalProxyTLSToStream writes tlsSettings.settings.
allowInsecure, and clash applySecurity now emits skip-cert-verify for the tls
case (it previously only did so for Hysteria — a pre-existing gap, so inbound
allowInsecure now renders for vless/trojan/ss clash too).
- Frontend: the four fields added to the Host form (allowInsecure → Security,
serverDescription → Basic, vlessRouteId → Advanced, mihomoX25519 → Clash);
serverDescription shown under the remark in the list. Schema + i18n updated.
serverDescription / vlessRouteId / mihomoX25519 are stored + editable; their
deeper rendering (and per-host mux/sockopt/xhttp into JSON/Clash, plus a per-host
xray JSON template) are tracked as follow-ups.
Gate: go test ./... green (SQLite + Postgres for the host schema/migration);
go build linux+windows; go vet + gofmt clean; npm run gen + typecheck + lint +
test (556) + build green; generated files in sync.
* feat(sub): render host sockopt + xhttp-extra params into JSON/Clash output (TDD)
A host's sockoptParams and xhttpExtraParams (free-JSON) now take effect:
applyHostStreamOverrides injects sockopt into the per-host stream (re-added since
the base stream strips it) and merges xhttpExtraParams into xhttpSettings, called
in both getConfig (JSON) and getProxies (Clash) right after the per-host TLS
apply. No-op for legacy externalProxy entries (keys absent) — characterization
snapshots unchanged.
mux rendering is outbound-level (overrides outbound.Mux) and needs a genVless/
genVnext/genServer signature change — deferred, along with the per-host xray
JSON template.
Gate: go test ./internal/sub/... + go test ./... green (snapshots unchanged);
go build + vet + gofmt clean.
* feat(sub): render host muxParams as a per-host JSON outbound mux override (TDD)
genVnext/genVless/genServer take a muxOverride: a host's muxParams (when valid
JSON) overrides the global mux on its JSON outbound; empty falls back to the
panel mux (behavior unchanged for non-host configs). Completes the host
mux/sockopt/xhttp trio. Test call sites updated for the new signature.
Gate: go test ./internal/sub/... + go test ./... green (snapshots unchanged);
go build + gofmt clean.
* style(ui): show Host security fields conditionally per security (like externalProxy)
* feat(sub): apply host SNI + fingerprint override for reality (TDD)
A reality host now overrides SNI and fingerprint while inheriting publicKey/
shortId from the inbound (reality keys can't be host-supplied). Previously the
reality link kept the inbound's serverName because the TLS appliers are gated to
security=="tls".
- raw: applyEndpointRealityParams sets sni/fp on the params for reality;
- JSON/Clash: applyHostStreamOverrides sets realitySettings.serverName +
serverNames from the host SNI.
Gated to host endpoints via an isHost marker on the synthesized ep, so the legacy
externalProxy path stays byte-identical (characterization snapshots unchanged).
The marker is internal and never emitted.
Gate: go test ./internal/sub/... + go test ./... green; go build + vet + gofmt clean.
* fix(ui): start the Host inbound select unselected instead of showing 0
A new host left inboundId defaulting to 0, so the Select rendered "0". inboundId
is now optional in the form (undefined until chosen), so it shows its
placeholder ("Select an inbound"); the required rule still enforces a choice on
save. Port keeps 0 (means "inherit the inbound's port").
Gate: npm run typecheck + lint + build green.
* fix(ui): drop redundant :port suffix from the Host inbound select label
The inbound tag (e.g. in-59303-tcp) already carries the port, so the appended
":59303" was duplicated. Show just the remark/tag.
Gate: npm run typecheck + lint + build green.
* style(ui): apply the shared card hover shadows to the Hosts page
page-cards.css scoped its card styling + hover shadows to each page class but
not .hosts-page, so Hosts fell back to antd's default hoverable (a larger/blurry
shadow + pointer cursor). Add a .hosts-page block matching the other pages.
Gate: npm run build green.
* feat(hosts): move Tags to Basic tab, add Nodes field, accept VLESS route ranges
- Move the Tags field into the Host form's Basic tab and add a Nodes
multi-select (visual-only assignment, backed by the existing node_guids
column) so the Basic tab matches the reference layout.
- Replace the single-port vlessRouteId integer with a free-form vlessRoute
string that accepts comma-separated ports/ranges (e.g. 53,443,1000-2000);
format-validated on the frontend, stored verbatim on the backend.
- Regenerated frontend types/openapi from the changed model.
* feat(hosts): structured editors for Mux/Sockopt/XHTTP + new Final Mask
Replace the raw JSON textareas in the Host form's Advanced tab with the same
structured editors used elsewhere, under a nested tabbed layout (General / Mux /
Sockopt / XHTTP / Final Mask), mirroring the Sub-JSON settings tab:
- Mux: the Sub-JSON mux editor (enable + concurrency/xudpConcurrency/xudp443).
- Sockopt + XHTTP: reuse the outbound SockoptForm / XhttpForm, wrapped in an
isolated form that serializes the edited subtree back to the host's JSON
string (pruned so the override stays sparse).
- Final Mask: new host field (model + column + JSON-render wiring that merges
the masks into the host's JSON-subscription stream), edited via the shared
FinalMaskForm like the Sub-JSON Final Mask editor.
Each editor stays a controlled value/onChange component bound to its existing
host JSON string field; backend rendering of mux/sockopt/xhttp is unchanged.
* feat(hosts): drop XHTTP + Xray-JSON-template overrides; fix mobile form layout
Remove the host's XHTTP extra-params and Xray-JSON-template overrides entirely
(model fields + columns, JSON-subscription render paths incl. hostTemplateOutbound,
schema, form tab/field, i18n, openapi codegen, and their tests) — they did not
fit the host model. Mux, Sockopt and Final Mask stay as structured editors.
Mobile fixes for the Edit Host modal:
- responsive width (95vw on mobile, was a fixed 760px that overflowed the
viewport and clipped the tabs/labels) + a scrollable body so the footer stays
on screen;
- Mux fields use responsive Row/Col (stack on mobile) instead of a fixed-width
label grid.
* fix(hosts): hide the spurious horizontal scrollbar in the Edit Host modal
Setting overflowY:auto on the modal body forced overflow-x to auto too (CSS
rule), so antd Row's negative gutter margins triggered a horizontal scrollbar.
Pin overflowX:hidden.
* feat(hosts): inbound-style responsive field layout + icon empty state
- Host form (main form + Mux/Sockopt/Final Mask editors) now use the inbound
form's label layout: label beside the input on desktop (labelCol sm span 8 /
wrapperCol sm span 14, right-aligned), stacked label-above-input on mobile.
Rewrote HostMuxForm onto an internal antd Form so it follows the same layout
instead of a manual grid.
- Empty hosts table now shows the host icon + the shared 'Nothing here yet'
(noData) text, matching Nodes/Inbounds/Clients, replacing the bespoke
'No hosts yet…' string.
* fix(hosts): avoid nested <form> in the Edit Host modal
The Mux/Sockopt/Final Mask editors each render their own antd Form inside the
host's main Form, producing an invalid nested <form> DOM node (hydration
warning). Render those inner forms with component={false} so they keep the form
instance/context but emit no <form> element.
* fix(hosts): make the Mux enable toggle work
The Switch's checked state came from Form.useWatch('mux'), but the mux object
field had no registered Form.Item while disabled, so setFieldValue never
notified the watcher and the toggle stayed off. Bind the Switch to a real
name='enabled' field (antd drives its checked state directly) and keep the
sub-fields registered via hidden={!enabled}, serialized to the flat mux JSON.
* refactor(hosts): reuse the outbound MuxForm instead of a bespoke Mux editor
The Mux fields duplicated the outbound MuxForm. Reuse it through the same
wrapper as Sockopt: generalize OutboundSubtreeJsonForm with defaultSubtree
(pre-fill on enable) and a serialize hook, and have HostMuxForm render MuxForm
at the ['mux'] path. The host keeps its inherit-when-off semantics by storing ''
unless mux.enabled. Also drops the now-unused enableSwitch path from the
wrapper (only the removed XHTTP editor used it).
* style(hosts): use default-width Port input like the inbound form
The host Port used width:100% (full width); the inbound's numeric inputs use
antd's default width. Drop the override so Port matches. The Mux number inputs
already use the default width via the reused MuxForm.
* refactor(sockopt): readable customSockopt editor as a shared component
The customSockopt rows were a single cramped Space.Compact line and duplicated
verbatim in the inbound and outbound sockopt forms. Extract a shared
CustomSockoptList that renders each entry as a titled group of labeled fields
(System / Level / Opt / Type / Value), matching the rest of the form, and use it
in both (and thus the host Sockopt editor).
* fix(finalmask): drop the empty Custom Tables tag on a new sudoku mask
The sudoku TCP-mask default seeded customTables: [''] (one empty string), which
rendered as a blank removable tag. Seed [] instead.
* fix(sockopt): make the outbound (and host) Sockopt client-only
Per the XTLS sockopt docs, tproxy / acceptProxyProtocol / V6Only /
trustedXForwardedFor only apply to an inbound (listening socket); they are
meaningless on an outbound/dialer. Drop them from the outbound SockoptForm
(which the host reuses). The Sockopt default object still seeds those keys, so
the host also strips them on serialize, keeping its override honest to the
server/client split. The inbound SockoptForm is left unchanged.
* fix(sockopt): make the inbound Sockopt server-only
Complete the server/client split: drop the outbound/dialer-only fields from the
inbound SockoptForm — dialerProxy, domainStrategy, interface, addressPortStrategy,
happyEyeballs, tcpMptcp (client-only since Go 1.24 auto-enables MPTCP on listen).
mark stays (xray applies SO_MARK on inbound sockets too). Update the form-blocks
snapshot to the server-side field set (intentional spec change).
* feat(hosts): populate Sockopt dialerProxy with the panel's outbound tags
The host Sockopt editor reused the outbound SockoptForm with outboundTags=[],
so the dialerProxy dropdown was empty. Feed it the panel's outbound tags via
the existing useOutboundTags hook (shares the cached xray-config query;
blackhole excluded), so a host can chain through a subscription outbound by tag.
* fix(hosts): empty-state styling on direct load + exclude balancers from dialerProxy
- .card-empty was only defined in lazily-loaded Clients/Inbounds/Nodes
stylesheets, so a direct /hosts refresh rendered the empty table state
unstyled (faint + uncentered) until another page was visited. Re-declare it
in HostList.css so it's correct on first load.
- The Sockopt dialerProxy dropdown listed balancer tags (useOutboundTags merges
them in for mtproto egress). dialerProxy chains a single outbound, so balancers
aren't valid — switch to useOutboundTagGroups and use only the outbound group.
* fix(outbounds): icon + 'Nothing here yet' empty state; stop fading other pages
The Outbounds empty state was a faint '—', and OutboundsTab.css set the global
.card-empty to opacity:0.4 — which leaked onto whichever page's empty state was
shown after the Outbounds CSS had loaded (e.g. Hosts went faint after visiting
Outbounds). Render the icon + noData ('Nothing here yet') like the other lists,
and align .card-empty to the shared centered/secondary style (no opacity).
* fix(outbounds): custom empty state on the desktop table too
The desktop Outbounds Table had no locale.emptyText, so it showed antd's
default 'No data' box. Add the same ExportOutlined + noData empty state as the
card (mobile) view.
* style(sidebar): use ExportOutlined for the Outbounds nav item
The Outbounds sidebar item used UploadOutlined (an upload tray). Switch to
ExportOutlined, matching the outbound icon now used in the routing target and
the outbounds empty states.
* feat(hosts): icons on the form tabs (icon-only on mobile)
Wrap every Host form tab label (Basic/Security/Advanced/Clash/Subscription
scope and the nested General/Mux/Sockopt/Final Mask) with catTabLabel, so the
tabs show icon + text on desktop and just the icon (with a tooltip) on mobile,
matching the Settings/Xray tab bars.
* refactor(hosts): fold Exclude-from-formats into Advanced, drop the one-field tab
The Subscription scope tab held only excludeFromSubTypes after Tags moved to
Basic — a niche per-format scoping knob. Move it into the Advanced > General
sub-tab and remove the standalone tab (and its now-unused subScope label/icon).
* feat(sub): per-client remark template variables; drop the remark model & Show Usage Info
* fix(migration): cap seeded host remark at the model's 256-char limit, not 40
|
||
|
|
d882d6aa74 |
feat(inbounds): add Real client IP presets to capture visitor IP behind CDN/relay
Surface the existing sockopt knobs (acceptProxyProtocol, trustedXForwardedFor) as a guided 'Real client IP' preset selector in the inbound form, so the real visitor IP is recovered behind Cloudflare CDN or an L4 tunnel/relay instead of recording the intermediary address. Presets are mutually exclusive, warn on incompatible transports, and add tooltips, docs, and translations for all locales. |
||
|
|
bbab83db17 |
refactor(frontend): stack client credential fields and use label hints on inbound form
Stack UUID/password/subId/auth/flow/security fields vertically in the client modal instead of two-column rows, and replace the inbound form's 'extra' help lines with hover tooltip hints on field labels. |
||
|
|
90e6217749 | fix(inbound): preserve custom share strategy on edit (#5225) | ||
|
|
5af02265ec |
fix(inbound): remove stale mkcp-legacy finalmask when switching away from mKCP
Switching the transport to mKCP auto-seeds a mkcp-legacy entry into finalmask.udp, but switching back to another transport only dropped the kcpSettings blob and left the mask behind. It survived downstream pruning (finalmask.udp was non-empty) and bled into every client share link. Strip auto-seeded mkcp-legacy entries from finalmask.udp whenever the network changes away from kcp, leaving user-authored masks intact. Fixes #5221 |
||
|
|
7c698c4bcf |
feat(inbound): support abstract unix sockets (@ prefix) in Address field
Accept the @-prefixed abstract socket form (e.g. @xray/in.sock) for an inbound listen address, not just path-based sockets. The form now allows Port 0 for both, and the Address help text documents the @ form across all locales. The backend already treated both prefixes as unix sockets. @ |
||
|
|
f1a4286e2f |
feat(sub): per-inbound sort order for subscription links
Add a subSortIndex field to inbounds that controls the order of links in subscription output only: the raw sub body, the HTML sub page, and the JSON/Clash formats (all served from the same query). Lower values come first; ties keep id order. The panel inbound list is unaffected. The value is editable in the inbound form next to the share-address fields, propagates to nodes via wireInbound, and follows the usual node-sync rules (copied on import, mirrored while not dirty, never a structural change). Rescoped from #5214 by @Ponywka. |
||
|
|
a5e5640804 |
fix(inbound): explain how to unlock fallbacks on the inbound form (#5014)
The fallbacks card only renders for VLESS/Trojan over RAW with TLS or Reality security, and a new inbound starts at security=none — so the Add Inbound page looked like it had lost fallback support entirely. Show an inline hint in that state pointing at the Security tab. |
||
|
|
c47a905ad2 |
fix(inbound): offer node share-address strategy only when a node exists
The `node` share-address strategy resolves to an address only when the inbound can live on a node; for a local inbound it is always empty and behaves like `listen`. Drop the `node` option from the picker unless an enabled, node-eligible node exists, and coerce the value to `listen` otherwise so the Select never shows an option that does nothing. |
||
|
|
2a7342baa9 |
feat: add inbound share address strategy (#5162)
* feat: add inbound share address strategy Allow node-managed inbounds to choose whether exported share links use the node address, routable listen address, or a custom endpoint. Preserve locally configured share address fields during remote node traffic sync. Refs #5161 Refs #4891 * fix: preserve inbound share address settings Forward share address fields to remote nodes, keep existing values when older update payloads omit them, align localhost handling between frontend and subscriptions, and preserve share address settings when cloning inbounds. * fix: keep share address strategy out of subscriptions Limit the new share address strategy to direct exported share links and QR codes. Restore subscription address resolution to the existing panel-owned behavior and update the UI help text accordingly. * fix: address share address review feedback * fix: validate custom share address * fix --------- Co-authored-by: Sanaei <ho3ein.sanaei@gmail.com> |
||
|
|
1ad483ede6 |
fix: expose streamSettings for Tunnel inbounds to support TProxy (#5171)
* fix: expose streamSettings for Tunnel inbounds to support TProxy * fix(ui): hide security tab for tunnel inbounds when stream is enabled tunnel (dokodemo-door) does not support TLS or Reality, so showing the security tab only results in a fully-disabled radio group. Exclude tunnel alongside wireguard from the security tab. * fix(tunnel): restrict stream tab to sockopt-only and fix transportless schema Tunnel (dokodemo-door) only needs sockopt.tproxy for TProxy mode — no user-selectable transport. Add hasSelectableTransport flag to hide the network picker, per-network sub-forms, ExternalProxy, and FinalMask for both tunnel and wireguard, matching the pattern already used for Hysteria. Fix a pre-existing Zod schema bug where NetworkSettingsSchema was a bare discriminatedUnion requiring `network` to be present. Wireguard and tunnel submit streamSettings without a `network` key, causing "Invalid discriminator value. Expected 'tcp' | ..." on every save. Fix by adding a transportless union branch (z.never().optional()) alongside the transport DU; also add ?? 'tcp' fallback in inbound-link.ts where stream.network is now string | undefined. Three regression tests added. --------- Co-authored-by: rqzbeh <rqzbeh@users.noreply.github.com> Co-authored-by: MHSanaei <ho3ein.sanaei@gmail.com> |
||
|
|
4002be4ade |
feat: support latest Wireguard features from Xray-core (PRs #5643, #5833, #5850) (#5131)
* feat: support latest Wireguard features from Xray-core Implements support for Xray-core PRs #5833, #5643, and #5850 for Wireguard Inbounds: - Adds 'domainStrategy' and 'workers' to Wireguard inbound configuration. - Enables the Stream Settings tab for Wireguard inbounds to configure 'sockopt' and 'finalmask', hiding the irrelevant 'network' transmission dropdown. - Adds the 'randRange' field to the 'noise' UDP Finalmask obfuscation settings. * fix --------- Co-authored-by: Rqzbeh <Rqzbeh@example.com> Co-authored-by: Sanaei <ho3ein.sanaei@gmail.com> |
||
|
|
6c1594693d |
feat(mtproto): add domain-fronting and essential mtg options
Expose mtg's [domain-fronting] section (ip/port/proxy-protocol) plus proxy-protocol-listener, prefer-ip, and debug on MTProto inbounds. Each key is written to the generated mtg-<id>.toml only when set, so mtg's own defaults apply otherwise. The instance fingerprint now covers these fields, so editing an option restarts the sidecar. Since MTProto is mtg-served (not Xray), sniffing does not apply: hide the Sniffing tab and the Advanced sniffing sub-editor, drop it from the Advanced "All" JSON view, and emit empty sniffing in the wire payload, all gated by a new canEnableSniffing predicate. |
||
|
|
1ca5924a44 |
feat(mtproto): add MTProto (FakeTLS) protocol via managed mtg sidecar (#5076)
* feat(mtproto): add MTProto (FakeTLS) protocol via managed mtg sidecar Xray-core has no mtproto proxy, so mtproto inbounds run as standalone mtg (9seconds/mtg) sidecar processes managed by the panel — one per inbound — and are excluded from the generated Xray config entirely. - model: MTProto protocol constant, validator, and FakeTLS secret helpers (GenerateFakeTLSSecret/HealMtprotoSecret) - mtproto package: per-inbound mtg process manager with reconcile, graceful stop, and best-effort Prometheus traffic scraping - runtime: delegate mtproto inbounds to the mtg manager instead of the Xray gRPC API; skip mtproto when building the Xray config - web: boot reconcile + StopAll wiring, periodic reconcile/traffic job, port-conflict transport, secret healing on inbound add/update - sub: tg:// proxy share-link generation - frontend: protocol option, Zod schema, Protocol tab (FakeTLS domain + regenerable secret), info-modal link, and i18n - provisioning: fetch mtg v2.2.8 in install.sh, DockerInit.sh, and the Linux + Windows release workflows * fix * fix * fix: address Copilot review comments on mtproto PR - web/web.go: create NewMtprotoJob once and reuse for cron + initial run - mtproto/manager.go: StopAll cleans up per-inbound config files on shutdown - mtproto/manager.go: CollectTraffic releases mutex before HTTP scrapes to avoid blocking Ensure/Reconcile/Remove during network I/O - database/model/model.go: panic on crypto/rand failure in mtprotoRandomMiddle instead of silently producing a weak all-zero secret - install.sh: fix chmod to handle renamed bin/mtg-linux-arm on armv5/v6/v7 |
||
|
|
af3c808444 | fix: default hysteria tls to no utls fingerprint | ||
|
|
75bc6e8076 |
fix(inbound-form): wrap long labels and shorten RU pinned-cert label
Long TLS-tab labels overflowed their field in locales with wider strings (e.g. Russian 'Pinned Peer Cert SHA-256'). Add AntD labelWrap to the inbound and outbound form modals so any over-long label wraps onto a second line instead of overflowing, and shorten the Russian pinnedPeerCertSha256 label to fit. Closes #4986 |
||
|
|
55d6729955 |
fix(nodes): Set Cert from Panel uses the node's own web cert for node inbounds
For an inbound deployed to a node, the button read the central panel's webCertFile/webKeyFile and inserted paths that don't exist on the node, crashing the node's Xray on startup. Add a token-accessible GET /panel/api/server/getWebCertFiles that returns a panel's own web cert/key paths, Remote.GetWebCertFiles to fetch it from a node, and GET /panel/api/nodes/webCert/:id to proxy it. setCertFromPanel now calls the node endpoint for a node-assigned inbound and the local settings otherwise, warning instead of inserting wrong paths on error/empty. Fixes #4854 |
||
|
|
e7c11c913a |
feat(inbounds): per-proxy Pinned Peer Cert SHA-256 + labeled External Proxy form
Redesign the Add Inbound -> Stream External Proxy section into labeled per-entry cards (Force TLS / Host / Port / Remark and, under TLS, SNI / Fingerprint / ALPN) and add a Pinned Peer Cert SHA-256 field with a generate-random-hash button to each entry. The pin flows end to end into share links: pcs for vmess/vless/trojan/ss (stripped when a proxy forces security off) and the hex-normalized pinSHA256 for Hysteria. JSON and Clash subscriptions emit the native pinnedPeerCertSha256 / pin-sha256 via the cloned stream. Adds the forceTls label across all 13 locales plus frontend and Go tests. |
||
|
|
b2e2120eb3 |
feat(inbounds): support Unix domain socket path in Listen field (#4429)
UDS listen already worked for proxying (the listen string is passed to xray verbatim and port 0 is accepted), and the Go sub/link layer already ignores the bind listen. The only gap was the frontend resolveAddr, which would put a socket-path listen into share/sub links (e.g. vless://uuid@/run/xray/x.sock:0). resolveAddr now treats a path-style listen (starting with / or @) as having no client-reachable address and falls back to hostOverride/hostname. Adds a test and a Listen-field help hint across all locales. |
||
|
|
ccd0853b6c |
fix(inbounds): allow port 0 for UDS inbounds (#4783)
Unix Domain Socket inbounds (listen path starting with /) use port 0, which xray-core ignores. Validation was hard-locked to a minimum of 1 in three places: the shared Zod PortSchema, the AntD InputNumber, and the Go Inbound model tag. Adds an InboundPortSchema (min 0) for the inbound form/API schemas, makes the port InputNumber min UDS-aware, and relaxes the Inbound model validate tag to gte=0. PortSchema and the Node model stay min 1. |
||
|
|
32f96298f8 |
feat(finalmask): sync transport with upstream Xray core changes
Consolidate the eight legacy mKCP/header UDP mask types into a single mkcp-legacy type ({header, value}), simplify xicmp to {dgram, ips}, and add the new realm UDP mask type, matching the updated Xray-core wire format. Update the FinalMask schema enum, the transport form, the mKCP seeding default, and the backend KCP share-link translation. Refresh golden fixtures/snapshots and add backend coverage for the mapping.
|
||
|
|
a3dca4b82d |
fix(inbounds): drop listen address from auto-generated inbound tag
A non-empty, non-any Address (listen) leaked into the tag as in-<listen>:<port>-<transport> (e.g. in-127.0.0.1:443-tcp). The tag is now always in-<port>-<transport>, with the node prefix and numeric dedup suffix still handling uniqueness across nodes and same-port/different-listen inbounds. Mirrored in the Go authority and the TS form preview, kept in parity by tests. Existing colon-form tags are now treated as custom, so editing such an inbound preserves its tag rather than rewriting it; new inbounds (or a cleared tag field) get the clean form. |
||
|
|
eb78b8666f |
fix(inbound): re-derive auto tags on edit and keep node tags consistent
Auto-generated inbound tags (in-<port>-<l4>, n<id>- prefixed for node inbounds) now re-derive when port/listen/transport change on update instead of keeping the stale round-tripped value. The resolved tag is mirrored onto the API response, and NodeID is pinned to the stored row so a node inbound never loses its n<id>- prefix on edit. The edit form recomputes the tag live via a Go-parity helper so the JSON preview matches what gets saved. Make node/central tag matching prefix-agnostic in all three places (traffic attribution, remote-id resolution, and the orphan sweep) so an n<id>- prefix present on only one side can no longer spawn duplicate inbounds or drop traffic on sync. Force LF on shell scripts via .gitattributes (CRLF broke the Docker build shebang when the repo is checked out on Windows) and add a .dockerignore to keep node_modules/.git out of the build context. Adds Go and frontend tests covering tag re-derivation, prefix-agnostic matching, and node-snapshot prefix mismatch. |
||
|
|
76dbbfc1f8 |
feat(inbounds): clearer client validation errors on save
When an inbound save fails Zod validation, the toast previously showed a raw path like `settings.clients.494.tgId: Invalid input`, which gave no hint which of hundreds of clients was at fault. Resolve the client array index back to the client email, name the field, and append a "(+N more)" count when several fields fail. console.error now logs a readable list of every issue instead of dumping the whole form. Adds the invalidClientField/invalidField/moreIssues toast strings across all 13 translations. |
||
|
|
d1882c7f29 |
refactor(frontend): reorganize source tree & break down oversized modals/tabs (#4698)
* refactor(frontend): reorganize components & pages into feature folders
No behavior change; pure file relocation + import path updates.
* refactor(frontend): move shared protocol enums to schemas/protocols/shared
Decouple Outbound from Inbound schemas: SSMethodSchema and VmessSecuritySchema (shared between inbound & outbound) now live in a neutral schemas/protocols/shared/ module. Outbound no longer reaches into schemas/protocols/inbound/*. Pure relocation + import rewiring; schema values identical, snapshots & golden tests unchanged.
* refactor(frontend): break InboundList into helpers/types/RowActions/columns hook/stats modal
InboundList.tsx 781 -> 203 lines. Extracted pure helpers (network labels, sort fns, isInboundMultiUser), shared types, the row-actions menu/cell, the table columns hook, and the mobile stats modal into the list/ folder. Code moved verbatim; no behavior change. typecheck/lint/test/build green, 337 tests pass.
* refactor(frontend): extract InboundInfoModal helpers, types & buildInboundInfo
InboundInfoModal.tsx 1081 -> 836 lines. Moved the pure data helpers (network host/path readers, link-protocol check, copy/download/statsColor/IP formatting) plus all shared types and the buildInboundInfo data builder into info/helpers.ts and info/types.ts. The state-coupled render body is left intact (no React render tests to guard a deeper split). Code moved verbatim; no behavior change. All gates green, 337 tests pass.
* test(frontend): add React Testing Library + jsdom render-test harness
- vitest projects: node unit tests stay lean; new jsdom 'components' project runs *.test.tsx
- component setup: matchMedia/ResizeObserver/localStorage polyfills, react-i18next init, persian-calendar-suite stub (only used under jalali locale)
- smoke + field-label structure snapshots for Inbound & Outbound form modals
- establishes the regression net required before decomposing the oversized form modals
- 341 tests pass (337 unit + 4 component); typecheck/lint/build green
* test(frontend): per-protocol field-structure coverage for both form modals
- drive the protocol Select in jsdom and snapshot rendered Form.Item labels for every protocol
- 10 outbound + 10 inbound protocol states captured as the regression net for protocol-core extraction
- add robust select-driving helpers (test-utils) + post-test body cleanup (setup.components)
- 341 tests pass; typecheck/lint green
* refactor(frontend): extract OutboundFormModal constants & stream helpers
OutboundFormModal.tsx 2238 -> 2080. Moved the pure option arrays/sets and the stream-slice helpers (newStreamSlice, hysteriaStreamSlice, isMuxAllowed, buildAddModeValues) into outbound-form-constants.ts and outbound-form-helpers.ts. Per-protocol render snapshots unchanged -> verified no behavior change. typecheck/lint/build green.
* refactor(frontend): extract InboundFormModal advanced JSON editors
InboundFormModal.tsx 3129 -> 2863. Moved AdvancedSliceEditor and AdvancedAllEditor (the in-modal JSON slice/all editors) into advanced-editors.tsx along with their adapter-helper imports. Per-protocol render snapshots unchanged -> verified no behavior change. typecheck/lint/build green.
* refactor(frontend): extract OutboundFormModal loopback/blackhole/dns field blocks
Moved the outbound-only protocol field blocks (loopback, blackhole, dns) out of the modal render body into outbound-only-fields.tsx. First render-body extraction behind the per-protocol snapshot net: loopback/blackhole/dns snapshots unchanged -> verified no behavior change. typecheck/lint/build green.
* refactor(frontend): extract OutboundFormModal freedom field block
OutboundFormModal.tsx 2063 -> 1753. Moved the freedom protocol field block (domainStrategy, fragment, noises, finalRules) into outbound-freedom-fields.tsx. Verbatim relocation; freedom per-protocol snapshot unchanged -> no behavior change. typecheck/lint/build green.
* refactor(frontend): extract OutboundFormModal wireguard field block
OutboundFormModal.tsx 1753 -> 1622. Moved the wireguard protocol field block (address, keypair gen, domainStrategy, peers + allowedIPs) into outbound-wireguard-fields.tsx; dropped now-unused icon/InputAddon/WireguardDomainStrategy imports. Verbatim relocation; wireguard snapshot unchanged -> no behavior change. typecheck/lint/build green.
* refactor(frontend): extract OutboundFormModal core protocol fields
OutboundFormModal.tsx 1622 -> 1538. Moved the shared protocol core field blocks (vmess/vless ID, vmess security, vless encryption/reverseTag, trojan/ss password, ss method/uot, socks/http user/pass) into outbound-core-fields.tsx; dropped now-unused schema/option imports. Per-protocol snapshots unchanged -> no behavior change. typecheck/lint/build green.
* refactor(frontend): fold OutboundFormModal server address/port block into core fields
OutboundFormModal.tsx 1538 -> 1516. Moved the shared connect-target (address/port) block into OutboundCoreProtocolFields at the same render position; dropped the now-unused SERVER_PROTOCOLS import. Snapshots unchanged -> no behavior change. typecheck/lint/build green.
* refactor(frontend): split outbound-only protocol forms into per-protocol files
Replace the grouped outbound-only-fields.tsx + outbound-freedom-fields.tsx with one file per protocol under outbounds/protocols/: freedom.tsx, blackhole.tsx, dns.tsx, loopback.tsx (+ barrel). Matches the prompt's 1-file-per-protocol structure. Outbound snapshots unchanged -> no behavior change. typecheck/lint/build green.
* refactor(frontend): split outbound protocol forms into per-protocol files
Replace the grouped outbound-core-fields / outbound-wireguard-fields with one file per protocol under outbounds/protocols/: vmess, vless, trojan, shadowsocks, http, socks, wireguard, freedom, blackhole, dns, loopback (+ shared server-target). Matches the prompt's 1-file-per-protocol structure (per-modal). Outbound snapshots unchanged -> no behavior change. typecheck/lint/build green.
* refactor(frontend): split outbound transport forms into per-transport files
Extract the tcp(raw)/kcp/ws/grpc/httpupgrade transport blocks into outbounds/transport/ per-file components (RawForm, KcpForm, WsForm, GrpcForm, HttpUpgradeForm). xhttp + hysteria transport remain inline for a follow-up. Verbatim relocation; outbound snapshots unchanged -> no behavior change. typecheck/lint/build green.
* refactor(frontend): extract OutboundFormModal xhttp transport form
Move the xhttp transport block into transport/xhttp.tsx (takes form + onXmuxToggle prop); drop now-unused HeaderMapEditor and MODE_OPTIONS imports from the modal. OutboundFormModal.tsx down to ~1001 lines (from 2238 originally). Verbatim relocation; outbound snapshots unchanged -> no behavior change. typecheck/lint/build green.
* refactor(frontend): extract OutboundFormModal tls/reality security forms
Move the TLS and Reality field blocks into outbounds/security/{tls,reality}.tsx; the none/TLS/Reality Radio.Group selector stays in the modal. Drop now-unused ALPN_OPTIONS/UTLS_OPTIONS imports. OutboundFormModal.tsx down to ~918 lines (from 2238 originally). Verbatim relocation; outbound snapshots unchanged -> no behavior change. typecheck/lint/build green.
* refactor(frontend): split inbound-only protocol forms (tun, tunnel) into per-file
Extract the tun and tunnel protocol blocks from InboundFormModal into inbounds/form/protocols/{tun,tunnel}.tsx (presentational, declarative). First inbound-side per-protocol split. Verbatim relocation; inbound snapshots unchanged -> no behavior change. typecheck/lint/build green.
* refactor(frontend): split inbound wireguard & shadowsocks protocol forms
Extract the wireguard and shadowsocks protocol blocks from InboundFormModal into inbounds/form/protocols/{wireguard,shadowsocks}.tsx (presentational; form + regen handlers / isSSWith2022 passed as props). Drop now-unused Divider + SSMethodSchema imports. Verbatim relocation; inbound snapshots unchanged -> no behavior change. typecheck/lint/build green.
* refactor(frontend): split inbound vless/http/mixed/hysteria protocol forms
Extract the remaining inbound protocol blocks into inbounds/form/protocols/: vless (auth handlers/state as props), http + mixed (shared accounts-list), hysteria. Drop now-unused HysteriaMasqueradeForm/Typography/Text imports from the modal. InboundFormModal.tsx 2841 -> 2478. Inbound snapshots unchanged -> no behavior change. typecheck/lint/build green.
* refactor(frontend): move HysteriaMasqueradeForm to lib/xray/forms/transport
The hysteria masquerade form edits streamSettings.hysteriaSettings.masquerade (a transport/stream concept) and is rendered identically by both modals, so it belongs next to FinalMaskForm in lib/xray/forms/transport/ rather than protocols/shared/. Moved the file, updated the transport barrel + both consumers (inbound hysteria protocol form, outbound modal), and removed the now-empty protocols/shared/ folder. Pure relocation; snapshots unchanged, typecheck/lint/build green.
* refactor(frontend): extract inbound transport forms into transport/ folder
Move the six inbound stream-transport blocks (tcp/raw, ws, grpc, xhttp,
httpupgrade, kcp) out of InboundFormModal into presentational components
under inbounds/form/transport/. XhttpForm takes the form instance and
re-derives its mode/obfs/placement watches internally; the rest are
declarative. InboundFormModal drops from 2566 to 2105 lines. No behavior
change — per-protocol field-label snapshots unchanged.
* refactor(frontend): extract inbound security forms into security/ folder
Move the inbound TLS and Reality stream-security blocks out of
InboundFormModal into presentational components under
inbounds/form/security/. The Radio.Group security selector stays in the
modal; TlsForm and RealityForm receive their cert/key/ECH generation
handlers and the saving flag as props. InboundFormModal drops from 2105
to 1708 lines.
Add inbound-form-blocks.test.tsx: render-snapshot coverage for each
extracted transport (raw/ws/grpc/kcp/httpupgrade/xhttp) and security
(tls/reality) component in isolation inside a minimal Form. The full
modal cannot exercise the stream/security tabs in jsdom because they are
gated behind Form.useWatch values that do not propagate in the test
harness, so component-level snapshots are the regression net for these
blocks. No behavior change.
* refactor(frontend): extract outbound sockopt/mux/hysteria transport blocks
Move the last three oversized inline stream blocks out of
OutboundFormModal into presentational components under
xray/outbounds/transport/: SockoptForm (~260 lines, the worst offender),
MuxForm, and HysteriaForm. Each takes the form instance; MuxForm also
takes protocol/network and keeps its isMuxAllowed gate. OutboundFormModal
drops from 962 to 621 lines and no inline section now exceeds the
250-line guideline. Existing outbound-form-modal snapshots already cover
sockopt/mux and stay byte-identical, confirming no behavior change.
* refactor(frontend): extract inbound sockopt + external-proxy blocks
Move the inbound Sockopt (~250 lines) and External Proxy stream blocks
out of InboundFormModal into presentational components under
inbounds/form/transport/, mirroring the outbound extraction. Each takes
its toggle handler (toggleSockopt / toggleExternalProxy) as a prop and
keeps its render-prop getFieldValue gate. InboundFormModal drops from
1708 to 1332 lines.
Extend inbound-form-blocks.test.tsx with isolated render-snapshot
coverage for both (SockoptForm seeded enabled + happyEyeballs;
ExternalProxyForm seeded with one TLS entry). No behavior change.
* refactor(frontend): break down RoutingTab into sections
Extract RoutingTab's presentational pieces into the routing/ folder:
helpers.ts (arrJoin/csv/chipPreview/ruleCriteriaChips), types.ts
(RuleRow), CriterionRow.tsx, RuleCardList.tsx (mobile card view), and
useRoutingColumns.tsx (desktop table columns hook). RoutingTab stays the
orchestrator holding rule state, mutate, tag-option memos and the
pointer-drag reorder logic, and drops from 550 to 291 lines. No behavior
change.
* refactor(frontend): extract BasicsTab constants and rule helpers
Move BasicsTab's geo option arrays + freedom/ipv4 outbound presets into
basics/constants.ts and the routing-rule get/set/sync helpers into
basics/helpers.ts. BasicsTab drops from 550 to 447 lines and keeps its
Collapse-of-settings panels (which stay coupled to mutate + derived
state, so splitting them into components would only add prop-drilling).
No behavior change.
* refactor(frontend): break down DnsTab columns/helpers/types
Extract DnsTab's pure pieces into the dns/ folder: helpers.ts
(STRATEGIES/DEFAULT_FAKEDNS + addr/domains/expectedIPs accessors),
types.ts (DnsConfig/HostRow/FakednsRow), and useDnsColumns.tsx
(useDnsServerColumns + useFakednsColumns table-column hooks taking their
row handlers as params). DnsTab stays the orchestrator for dns state,
mutate, hosts sync and the Collapse panels, and drops from 539 to 424
lines. No behavior change.
* refactor(frontend): break down OutboundsTab into sections
Extract OutboundsTab's pieces: outbounds-tab-types.ts (OutboundRow),
outbounds-tab-helpers.ts (address/untestable/security/breakdown +
traffic/testing/result accessors), useOutboundColumns.tsx (desktop table
columns hook) and OutboundCardList.tsx (mobile card view). OutboundsTab
stays the orchestrator for outbound state, mutate, reorder and the
toolbar, and drops from 516 to 238 lines. No behavior change.
This completes plan section 2.4.5 — all four oversized Xray tabs
(Basics/Routing/Dns/Outbounds) are now broken into sections + hooks.
* refactor(frontend): fold HysteriaMasqueradeForm into the hysteria forms
Inline the masquerade fields directly into both hysteria transport forms
(inbounds/form/protocols/hysteria + xray/outbounds/transport/hysteria)
and delete the shared lib/xray/forms/transport/HysteriaMasqueradeForm so
each hysteria form is self-contained. The masquerade JSX is unchanged;
form is typed as the untyped FormInstance (as the shared component was)
so the masquerade name paths still resolve. No behavior change.
* refactor(frontend): slim InboundFormModal by extracting hooks + sections
Pull the modal's non-layout logic into focused files at the form root:
- useSecurityActions.ts: TLS/Reality key + cert generation handlers and
onSecurityChange (consumed by the security tab)
- useInboundFallbacks.ts: fallback row state + load/save/derive/add/
update/remove/move handlers + eligible-child options
- FallbacksCard.tsx: the fallbacks card UI (presentational)
- SniffingTab.tsx: the sniffing tab UI (presentational)
Also drop the stale "Pattern A rewrite / sibling file" header comment and
the imports the extractions made unused. InboundFormModal goes from 1332
to 868 lines with no behavior change (351 tests green, snapshots
unchanged).
|