- service.TestOutbound now dispatches on `mode`:
- "tcp": parallel net.DialTimeout to every server/peer endpoint
(vmess/vless/trojan/ss/socks/http/wireguard). No xray spin-up,
no semaphore — safe to run concurrently across outbounds.
- "http" (default): existing temp-xray + SOCKS path, now with an
httptrace.ClientTrace breakdown (DNS / Connect / TLS / TTFB)
alongside the total delay and status code.
- testSemaphore renamed to httpTestSemaphore — only HTTP probes
serialise, TCP runs free.
- TestOutboundResult carries the per-mode extras: timing fields for
HTTP, per-endpoint dial list for TCP, plus a `mode` echo.
- Controller reads `mode` from the form and passes it through.
- useXraySetting: testOutbound accepts mode (default "tcp"); new
testAllOutbounds(mode) runs a worker pool (concurrency 8 for TCP,
1 for HTTP) and skips blackhole / loopback / blocked outbounds —
also skips freedom / dns under TCP since they have no endpoint.
- OutboundsTab: TCP/HTTP radio toggle and a Test All button land in
the toolbar; the per-row ⚡ now uses the selected mode. Results
surface in a popover with the full timing breakdown plus the
endpoint list for TCP probes. Latency header replaces the duplicate
"check" column title.
Practical effect: testing ten outbounds in TCP mode drops from ~50–100s
(serial HTTP) to ~1–2s (parallel dial × 8). HTTP mode stays as the
authoritative probe and now shows where the latency actually lives.
In the Xray settings update handler, the error from
SetXrayOutboundTestUrl was silently discarded. If the database write
failed, the user received a success toast ("Settings updated
successfully") but the outbound test URL was not actually saved.
Now properly checks the error and returns a failure response to the
user, consistent with how the preceding SaveXraySetting call is
handled.
`getXraySetting` builds its response as
{ "xraySetting": <db value>, "inboundTags": ..., "outboundTestUrl": ... }
and embeds the raw DB value as the `xraySetting` field without
checking whether the stored value already has that exact shape.
The frontend pulls the textarea content from `result.xraySetting`
and saves it back verbatim. If the DB ever ends up holding the
response-shaped wrapper instead of a real xray config (older
installs where this happened at least once, users who imported a
copy-pasted response into the textarea, a botched migration, etc.),
the next save nests another layer, the one after that nests a
third, and the Vue-side JSON.parse of the resulting blob silently
fails — the Xray Settings page goes blank.
Fix both ends of the round-trip:
* Add `service.UnwrapXrayTemplateConfig`. It peels off any number of
`xraySetting`-keyed layers, leaving a real xray config behind.
The check is conservative: if the outer object already contains
any top-level xray key (`inbounds`, `outbounds`, `routing`, `api`,
`dns`, `log`, `policy`, `stats`), it is returned unchanged, and
there is a depth cap to avoid pathological inputs.
* `SaveXraySetting` unwraps before validation so a round-tripped
wrapper from an already-corrupted page can no longer re-poison
the DB on save.
* `getXraySetting` unwraps on read and, when it finds a wrapper,
rewrites the DB with the corrected value. Existing broken installs
heal themselves on the next visit to the page.
Includes unit tests for the passthrough, single-wrap, multi-wrap,
string-encoded-inner, and false-positive cases.
Co-authored-by: pwnnex <eternxles@gmail.com>
* feat: Add NordVPN NordLynx (WireGuard) integration with dedicated UI and backend services.
* remove limit=10 to get all servers
* feat: add city selector to NordVPN modal
* feat: auto-select best server on country/city change
* feat: simplify filter logic and enforce > 7% load
* fix
---------
Co-authored-by: Sanaei <ho3ein.sanaei@gmail.com>