mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-08-23 11:27:13 +00:00
f4bbaf40f04e00e23a64bff1c05efb06b39932cf
19 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
1c0fdb4527 |
fix(outbounds): test subscriptions in Test All, skip direct/dns
Test All only iterated the editable template outbounds, so subscription
outbounds (the read-only "from subscriptions" table) were never probed in
bulk. They are now queued too, keyed by tag in subscriptionTestStates so
their rows light up live; the template and subscription HTTP lanes run
serially to respect the backend's single-batch lock (TCP runs alongside).
Also stop testing freedom ("direct") and dns outbounds: they aren't
proxies, so an HTTP probe through them only measures the host's own
reachability, not a tunnel. They are now untestable in every mode -- the
per-row button is disabled and Test All skips them -- with a matching
backend guard so a direct API caller can't HTTP-test them either.
|
||
|
|
5716ae5987 |
feat(outbound): batched connection tester with direct timed HTTP probes
Replace the per-outbound burstObservatory polling (one temp xray spawn + up to 15s of /debug/vars polling per outbound, serialised) with one shared temp xray instance per batch: every tested outbound gets its own loopback SOCKS inbound plus an inboundTag->outboundTag routing rule, and the panel times a real HTTP request through each one in parallel. The probe returns as soon as the response lands and records the HTTP status plus an httptrace breakdown (proxy connect / TLS via outbound / first byte) shown in the result popover. New POST /panel/api/xray/testOutbounds endpoint (array in, results in input order, max 50); the legacy /testOutbound endpoint now delegates to the same engine. Test All chunks HTTP probes 16 per request, and a batch whose shared process never comes up (one structurally-broken outbound poisons the config) retries each item in an isolated instance so the broken outbound reports xray's real error while the rest still test. |
||
|
|
bade1fcef6 |
feat(ui): allow custom fragment packets ranges, not just presets (#5075)
The fragment "packets" field was a locked dropdown (tlshello / 1-3 / 1-5) in both the finalmask TCP-mask form and the Freedom outbound form, while xray-core accepts any "n-m" packet range. Replace both with an AutoComplete that keeps the presets as suggestions and validates free input as "tlshello" or a numeric range. |
||
|
|
60da6bed15 |
fix(xhttp): stop injecting scMaxEachPostBytes/scMinPostsIntervalMs defaults (#5141)
The panel seeded xhttp configs with scMaxEachPostBytes=1000000 and scMinPostsIntervalMs=30 — xray-core''s own defaults — and emitted them into every generated config and share link. The literal scMinPostsIntervalMs=30 is a stable DPI fingerprint that Russia''s TSPU keys on to block connections on mobile networks. New configs no longer seed these values (empty schema/template defaults, so xray-core applies its internal defaults). For configs already stored with the old defaults, the link/subscription builders now drop values equal to xray-core''s defaults instead of advertising them — covering panel share links, the raw subscription, and the JSON subscription without requiring every inbound to be re-saved. Non-default values the user set deliberately are still emitted. |
||
|
|
10a0c9131c |
fix(hysteria): clamp udpIdleTimeout to xray-core's accepted 2-600s range (#5117)
The schema and form inputs allowed any value >= 1, but xray-core rejects UdpIdleTimeout outside 2-600 seconds at startup, so an out-of-range value silently killed the whole config. |
||
|
|
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> |
||
|
|
e8171ab4f7 |
fix(xray): sync routing rules when outbound tag is renamed (#5006)
* chore: ignore local .cursor directory * fix(xray): sync routing rules when outbound tag is renamed Renaming an outbound in the Outbounds tab only updated the outbound list, leaving routing rules pointing at the old tag. Propagate tag changes to routing rules, balancer selectors, and sockopt dialerProxy references, matching the behavior already used for balancer and WARP/Nord renames. * test: mock HttpUtil to fix unhandled vitest rejections * test(frontend): mock axios globally to prevent flaky network errors on CI * test(frontend): fix eslint any errors in component test setup --------- Co-authored-by: Rqzbeh <rqzbeh@users.noreply.github.com> |
||
|
|
0daedd3db9 |
feat: add support for subscription-based outbounds with auto-update (#5037)
* feat: add support for subscription-based outbounds with auto-update
- New OutboundSubscription model (full support on both SQLite and PostgreSQL)
- Go subscription link parser (vmess/vless/trojan/ss/hysteria2/wireguard) matching frontend behavior
- Stable tag assignment across refreshes (designed for balancer + routing use)
- Runtime merge of subscription outbounds into Xray config (additive only)
- Full CRUD + manual refresh + preview API
- Background auto-update job (per-subscription interval)
- Frontend management UI in Outbounds tab (Subscriptions drawer) + tag integration in balancers/routing rules
- Proper dual-database support including CLI migration path
Review & hardening notes:
- Fixed merge logic bug that could drop manual outbounds
- Added SSRF/private-IP protection on subscription URLs using SanitizePublicHTTPURL
- Improved update interval UX (hours + minutes)
- Auto-fetch on first subscription creation
- Added detailed comments on tag stability strategy and balancer implications when servers are added/removed/rotated
- Updated migrationModels() for CLI migrate-db support
* fix: resolve frontend lint/type errors and Go build break
Frontend (eslint + tsc clean):
- Destructure subscriptionOutboundTags prop in RoutingTab and
BalancersTab. It was declared in the interface and used in useMemo
but never destructured, so it resolved as an unresolved global
(react-hooks warning + tsc "Cannot find name"). The prop is passed
by XrayPage, so the feature was silently inert.
- OutboundsTab: remove unused useEffect import, add an OutboundSub
type to replace any[] state and the any/any table render signature,
type the subscriptionOutbounds cast, and replace unused catch (e)
bindings with parameter-less catch. Also type HttpUtil.post as
OutboundSub so r.obj?.id type-checks.
Backend (go build clean):
- outbound_subscription_job: websocket.MessageTypeXray is undefined;
use the existing MessageTypeOutbounds since the job refreshes
outbound subscriptions.
* fix(xray): make outbound subscription creation work end-to-end
- Correct API paths from /panel/xray/outbound-subs to
/panel/api/xray/outbound-subs. The controller is mounted under
/panel/api, so the old paths hit the SPA page route (GET-only)
and 404'd on POST.
- Send the create-subscription body as a plain object instead of
URLSearchParams. The axios request interceptor serializes bodies
with qs.stringify, which can't read URLSearchParams' internal
storage and produced an empty body, so the backend rejected it
with "subscription URL is required".
- Use message.useMessage() + context holder instead of the static
antd message API (resolves the "Static function can not consume
context" warning), matching XrayPage's pattern.
- Migrate the subscriptions Drawer to antd v6 props: width -> size,
destroyOnClose -> destroyOnHidden, and Space direction -> orientation.
* feat(xray): show traffic/test for subscription outbounds; harden + test the feature
Display (the reported issue):
- Replace the flat read-only pills with a proper read-only table (desktop)
and cards (mobile) in a new SubscriptionOutbounds component, showing
Address, Protocol, Traffic (matched by tag — already collected by Xray),
and a Test button with Latency. No edit/delete/move (read-only).
- Test subscription outbounds via the existing /testOutbound endpoint, with
results keyed by tag (subscriptionTestStates + testSubscriptionOutbound in
useXraySetting, wired through XrayPage). Generalize isTesting/testResult to
a string|number key so the same helpers serve index- and tag-keyed states.
i18n:
- Replace all hardcoded English subscription strings with t() calls and add
pages.xray.outboundSub.* keys to en-US.json (other locales fall back).
Backend hardening + tests:
- xray.go: drop the tautological `subSvc != nil` check.
- outbound_subscription: re-validate every redirect hop against private/
internal addresses (CheckRedirect) and cap the redirect chain, closing an
SSRF gap where only the initial host was checked.
- Extract assignStableTags as a pure function and add unit tests for tag
stability and SSRF rejection (the feature previously had no tests).
Misc:
- gofmt util/link/outbound.go (it was not gofmt-clean).
* fix(xray): make outbound-subs feature pass CI (test compile, route docs, openapi)
- outbound_test.go: remove unused `inner`/`lines` variables that broke the
`util/link` test build (declared and not used).
- Document the 7 outbound-subscription routes in endpoints.ts (list, create,
update, delete, del alias, refresh, parse) so TestAPIRoutesDocumented passes.
- Regenerate frontend/public/openapi.json (npm run gen) to include the new
endpoints, satisfying the codegen freshness check.
* feat(xray): per-subscription allow-private, gap-filled tags, UI tweaks, delete refresh
Backend:
- Add a per-subscription AllowPrivate flag (default off). Create/Update/refresh
and the redirect check sanitize the URL with it, so localhost/LAN sources work
only when explicitly opted in; the SSRF guard still blocks private targets by
default. Controller reads the allowPrivate form field on create/update/parse.
- Default outbound tag prefix now uses the smallest free "subN-" number instead
of the auto-increment id, so deleting a subscription frees its number for reuse
(a fresh start gives sub1) while staying stable per subscription. Extracted a
pure defaultPrefixNumber() with unit tests.
- deleteOutboundSub now signals SetToNeedRestart so xray drops the outbounds.
Frontend:
- "Allow private address" toggle in the add form (sends allowPrivate).
- Delete now refreshes the xray view immediately (no manual page reload).
- Subscriptions manager opens as a centered Modal instead of a right-side Drawer.
- Move Outbounds to a top-level sidebar item under Nodes (out of Xray Configs).
- Collapse WARP/NordVPN into a "more" dropdown.
- Document the allowPrivate param in endpoints.ts.
* i18n(xray): translate outbound-subscription UI into all locales
- Translate the pages.xray.outboundSub.* strings (and allowPrivate label/hint)
into all 12 non-English locales, matching each file's existing terminology.
- Remove the unused outboundSub.add ("Add subscription") key from every locale.
* feat(xray): subscription manager — edit, reorder/priority, status, preview, refresh-all
Backend:
- Per-subscription Priority + Prepend: subscriptions are ordered by Priority and
placed before (Prepend) or after the manual template outbounds in the merge, so
a subscription server can become the default. New Move(up/down) endpoint
re-normalizes priorities; merge split into prepend/template/append.
- List now returns a derived OutboundCount and orders by priority, and strips the
heavy LastFetchedOutbounds/LinkIdentities blobs from the list payload.
- Create/Update accept the prepend flag; new subs append at the end of priority.
Frontend (Outbound Subscriptions modal):
- Edit existing subscriptions (reuses the form + Update endpoint).
- Inline enable/disable Switch, Status column (OK / error tooltip), Outbounds
count column, per-row refresh spinner, "Refresh all" button.
- Reorder (move up/down) controls + a "Before manual outbounds" toggle.
- Preview button: fetch+parse a URL via /parse without saving.
- Document the move route + prepend param in endpoints.ts; regenerate openapi.json.
* i18n(xray): translate new subscription-manager strings into all locales
Add the prepend/prependHint, preview/previewEmpty, refreshAll, statusOk and
toastUpdated keys to all 12 non-English locales, matching each file's terminology.
---------
Co-authored-by: MHSanaei <ho3ein.sanaei@gmail.com>
|
||
|
|
6ed6f57b5c |
fix(panel): normalize XHTTP/sockopt/Reality wire output and validate REALITY target (#4988)
* fix(panel): normalize XHTTP/sockopt/Reality wire output and validate REALITY target Strip mode-specific XHTTP fields for stream-one, reset harmful sockopt defaults to 0, split server/client Reality fields on save, validate target host:port in the inbound form, and expose Happy Eyeballs for the direct freedom outbound. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(panel): keep REALITY public key on the wire, guard freedom noises The REALITY server/client wire split deleted realitySettings.settings on save, but the panel stores the REALITY public key there and every share-link / subscription generator reads it back from that path (frontend inbound-link.ts, Go subService/subJsonService/subClashService). Stripping it produced empty pbk= links, breaking client connectivity after save+reload. Revert the reality normalization (drop normalizeRealityForWire and the key sets), restore the inbound REALITY form fields (uTLS, spiderX, publicKey, mldsa65Verify) while keeping the new validated target field, and restore the mldsa65Verify clear handler. Also guard freedomToWire against undefined noises/finalRules (same defensive treatment as the existing fragment guard, issue #4686) which the new freedom-outbound test surfaced as a crash. Tests now assert the public key is preserved. --------- Co-authored-by: Cursor <cursoragent@cursor.com> Co-authored-by: MHSanaei <ho3ein.sanaei@gmail.com> |
||
|
|
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 |
||
|
|
5fb18b8819 |
fix(outbounds): preserve SNI/TLS settings on transport change
Changing the transport in the outbound edit modal rebuilt streamSettings from scratch, dropping tlsSettings (and its serverName) while keeping security: 'tls'. On save xray received TLS with an empty SNI, so SNI-spoof tunnels connected but passed no traffic. Carry over tlsSettings/ realitySettings when the new network still supports the security mode, via a new applyNetworkChange helper. Fixes #4791. |
||
|
|
7bc31dd194 |
feat(outbounds): pick dialerProxy from other outbound tags for proxy chaining
Turn the outbound sockopt dialerProxy free-text input into a searchable Select populated with the other outbound tags, so users can build a proxy chain (route one outbound through another) without typing tags by hand. The list excludes the current outbound, so self-reference cycles cannot be selected. A tooltip and placeholder explain the chaining concept. Adds dialerProxyPlaceholder and dialerProxyHint to all 13 locales. Closes #4446 |
||
|
|
5b6e05a0fc |
fix(raw): complete the HTTP header section for inbound and outbound
Align both raw (TCP) transport forms with the Xray docs: request {version, method, path, headers} + response {version, status, reason, headers}. The outbound form was missing the request.path input, so panel-created outbounds were stuck on GET / and could not match a custom inbound path; add it with the same comma-separated array handling as the inbound. Also drop a stale inbound comment that claimed xray-core ignores the inbound request object, which contradicts both the code and the docs (request and response must match on both sides).
|
||
|
|
6ae1b38607 |
fix(outbound): add None option to uTLS fingerprint in TLS form (#4760)
Hysteria doesn't use uTLS, but the outbound TLS form's uTLS dropdown only listed concrete fingerprints (chrome, firefox, ...) with no explicit empty entry. Add a None option, matching the inbound TLS form, so the fingerprint can be left empty. |
||
|
|
b6641439d4 |
fix(sockopt): rename interfaceName to interface so xray honors it
xray-core reads the bind-interface sockopt as json:"interface", but the schema and forms used interfaceName. Go's JSON unmarshal is case-insensitive, yet interfacename != interface, so the value never reached xray and interface binding silently did nothing. Rename the field across the schema, the inbound/outbound forms, and the golden fixture to match xray-core and the official docs. |
||
|
|
2bb9ed1cda |
feat(outbound): sync DNS outbound config with Xray core changes
Rename the DNS rule wire key qtype to qType (reading the legacy qtype on parse for back-compat), add the new rCode response-code field for the return action (omitted when zero), and rename the reject action to return. Align the DNS rule action set across the form dropdown, schema, and adapter to the core's valid values (direct/drop/return/hijack), dropping the never-valid rejectIPv4/rejectIPv6 entries. |
||
|
|
49c30d6baf |
fix(frontend): add missing react-hooks/exhaustive-deps
ESLint failed the frontend build on four react-hooks/exhaustive-deps errors. Add the missing dependencies: the hysteria streamSettings effect now lists form, and the inbounds page prompt/import/general-action callbacks now list t. Both form (Form.useForm) and t (useTranslation) are stable references, so no extra re-renders or loops. |
||
|
|
f02018cfb7 |
fix(outbounds): prevent freedom save crash, complete its fields (#4686)
freedomToWire called Object.entries(s.fragment), but getFieldsValue(true) returns freedom settings without a fragment object when the Fragment switch is off (its sub-fields never register). That threw 'Cannot convert undefined or null to object' and silently killed the save. Guard fragment with a fallback so an unset value is treated as empty. While verifying against xray-core's freedom config, also: - add the missing userLevel field (schema, form schema, adapter, UI) - fix noise applyTo enum to ip/ipv4/ipv6 (xray rejects the old host/all) Closes #4686 |
||
|
|
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).
|