Commit Graph

3309 Commits

Author SHA1 Message Date
isultanov99 be70535b94 feat(inbounds): improve multi-node online attribution (#6164) dev-latest 2026-08-15 17:40:35 +02:00
isultanov99 2d669fa4b7 feat(sub): add template variables to subscription metadata (#6163) 2026-08-15 17:38:29 +02:00
Lex Rivera 8c8556ab32 feat(frontend): multi-node cloning initial implementation (#6216)
* feat(frontend): multinode cloning initial implementation

* fix(frontend): harden live node detection in multinode cloning

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>

* fix(frontend): add aria label to clone inbound modal

* fix(frontend): shallow copy inbound settings during cloning

* fix(frontend): avoid potential port conflict during testing

* fix(frontend): selection buttons and websocket selection reset fix

---------

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
2026-08-15 17:36:25 +02:00
Sanaei 03950b1295 fix(frontend): disable table virtualization
Removes the `virtual` table option from Clients, Inbounds, and Nodes list pages. This aligns table behavior across key admin views and avoids virtualization-related rendering/interaction issues with the existing scroll and pagination setup.
2026-08-15 17:33:56 +02:00
Grigoriy d7698ec7aa feat(xray): browse geosite/geoip categories from routing rules (#6165)
* feat(xray): browse geosite/geoip categories from routing rules

Routing rules made you type category names from memory: nothing showed which
categories a database actually contains, what is inside one, or whether a
name resolves at all — a typo only surfaced when Xray refused the config.

The panel now reads Xray's .dat databases itself and exposes them over four
endpoints: databases in the asset folder, a database's categories, one page
of a category's rules, and validation of the tokens already in a rule. The
reader walks the protobuf wire format directly rather than decoding into Go
structs, because a 10 MB geosite.dat holds well over a million domains and
materialising them costs ~284 MB where streaming costs ~19 MB. Only the
category index is cached, entry pages are scanned on demand, and scans are
serialised, so twenty concurrent requests peak at 87 MB instead of 1 GB.
A database's type is decided by its contents, not its file name, since
custom .dat files are named freely.

In the rule form, the source-IP, IP and domain fields gain a database button
opening the browser: search over categories, a preview of what a category
holds, and a multi-select that merges into the field. Plain domains, CIDRs
and categories the panel does not know are left untouched; categories already
present come back ticked, and unticking one removes it from the rule.

* fix(xray): read geo databases through os.Root and match codes verbatim

CodeQL flagged the database read as a path built from a user-supplied value,
and it was right about the shape of it. The file name arrives in a request;
resolve() rejects traversal and stats the file through an os.Root, but the
read itself went through a joined path with os.ReadFile. That left the
symlink defence incomplete: the stat could pass while the read followed a
link planted — or swapped in — afterwards.

Reads now go through the same root, so a request-supplied name never becomes
a path this code resolves on its own, and the size limit is applied to the
opened file rather than to a separate stat of it.

Lookup no longer trims the category code either. It backs the routing-token
validator, and the core matches codes verbatim: "geosite: cn" will not start
Xray, so repairing that space here hid exactly the typo the validator exists
to report.

* fix(xray): address review findings on the geo category browser

Asset folder. The browser read config.GetBinFolderPath() unconditionally,
but the core honours a preset XRAY_LOCATION_ASSET and only falls back to the
bin folder (ensureXrayAssetLocation). On an install pointing at a shared
asset directory the panel listed an empty folder and reported perfectly
valid geosite:/geoip: tokens as missing — the validator warning about a
correct config. The directory is now resolved with the core's precedence.

Paging. Serving one page read and rescanned the whole database, so walking
category-ads-all re-read it per page. The index now records each category's
byte range and a page reads only that record through the os.Root handle,
with the current category's records held for the duration of a paging
session. Profiling that also showed the real cost was not the read but the
slice of payload pointers built per call — a category holds a hundred
thousand of them — so records are now walked with a callback instead.
Ten pages over category-ads-all: 239 MB allocated, now 4.3 MB.

Cached failures. Any error from reading a file was latched under the file's
size+mtime, so a transient ENOMEM or EMFILE marked a healthy database as
damaged until it changed on disk. Only deterministic failures are cached.

Wrong kind. A geoip: token typed into a domain field parsed as a plain
domain and was waved through, though the core cannot resolve it as one. It
is now reported, with its own reason and wording.

Frontend. The category filter fed the query key on every keystroke, so each
character triggered a request that re-scanned the database; it is debounced
now. GeoTokenInput accepts and forwards a ref, so React Hook Form can focus
these three fields on a validation error again. A failed validation shows
that it failed instead of rendering the same empty state as "no issues".

Also drops an unreachable branch in the token-count guard and corrects the
categories endpoint docs, where limit is unbounded by default.

---------

Co-authored-by: STRENCH0 <17428017+STRENCH0@users.noreply.github.com>
2026-08-15 17:12:59 +02:00
Kobi Hikri 7c8a9a6909 ci: attach provenance and SBOM attestations to the published images (#6130) 2026-08-15 16:59:11 +02:00
Rouzbeh† 694ad6deae feat(sub): add per-client subscription HWID limits (#5802)
* feat(sub): add per-client subscription HWID limits

* fix(sub): address HWID review on shared subId and bulk create

* fix(sub): store HWID devices by sub_id and drop anchor client workaround

* fix(sub): restore UA auto-detect and HTML page routing in subs()

The cherry-pick of the HWID gate onto main's refactored SUBController
had dropped main's UA-based format auto-detection and sub-page handling
from subs(). Restore those branches, slotting enforceHwid after the
HTML page and before format detection so the gate only applies to
machine-readable subscription bodies.

Also adapt tests to main's options-struct constructor and to the
ClientService.Update signature extended with limitHwid.

* fix(frontend): drop axios from HttpUtil.delete

The bulk-delete rework's committed version still referenced axios,
which this file no longer imports, breaking typecheck in CI. Use the
httpRequest wrapper like the other verbs.

---------

Co-authored-by: Sanaei <ho3ein.sanaei@gmail.com>
2026-08-15 16:50:20 +02:00
n0ctal 1793a9b8b4 feat(nodes): opt-in encryption at rest for the outbound node API token (#6186)
* node: encrypt outbound bearer token at rest

* fix(nodes): keep bearer tokens encrypted throughout

---------

Co-authored-by: n0ctal <293235942+n0ctal@users.noreply.github.com>
2026-08-15 16:48:50 +02:00
PathGao 8e7fb144ee perf(frontend): replace blank Suspense fallbacks with Spin, switch to matchMedia hook, add virtual table scrolling (#6187)
- routes.tsx, LazyMount.tsx: replace Suspense fallback={null} with Spin
  loader so page transitions and lazy modals never show blank content
- useMediaQuery.ts: switch from resize event to matchMedia change event,
  eliminating state updates on every pixel drag; export MOBILE_BREAKPOINT_PX
- SubPage.tsx: drop duplicate inline isMobile logic (7 lines), use shared
  useMediaQuery(576) (2 lines)
- ClientsPage, InboundList, HostList, NodeList: add virtual + scroll.y to
  Table for viewport-only DOM rendering of large datasets
2026-08-15 16:44:08 +02:00
Dan Liutko 0f14ce7551 fix(web): fallback to default secret when database setting is empty (#6189)
* fix(web): fallback to default secret when database setting is empty

* style(web): format setting_security_test.go with gofumpt
2026-08-15 16:40:22 +02:00
n0ctal 7ecd88b9e3 fix(nodes): apply a rotated master mTLS certificate without restarting the panel (#6194)
* fix(mtls): invalidate pooled clients after credential rotation

* fix(mtls): make connection reload read-only

---------

Co-authored-by: n0ctal <293235942+n0ctal@users.noreply.github.com>
2026-08-15 16:03:42 +02:00
n0ctal 1230559e69 feat(api): scoped, optionally expiring API tokens (#6201)
* security(api): add scoped expiring API tokens

* security(api): make scoped token lifecycle enforceable

---------

Co-authored-by: n0ctal <293235942+n0ctal@users.noreply.github.com>
2026-08-15 15:31:49 +02:00
n0ctal aecbad3ab1 test(tgbot): detect open-coded keypad transitions (#6214)
Co-authored-by: n0ctal <293235942+n0ctal@users.noreply.github.com>
2026-08-15 15:27:02 +02:00
Maxim Myalin 2217213e9f feat(sub): expose last subscription fetch time (#6217)
* feat(sub): expose last subscription fetch time

Record successful GET subscription fetches per client and surface the timestamp in the client API and UI.

* fix(frontend): include last subscription fetch in client traffic

* Update sub_fetch_test.go

---------

Co-authored-by: Hermes Agent <hermes-agent@localhost>
2026-08-15 15:22:11 +02:00
n0ctal ad32144c42 fix(sub): use a fullwidth percent in USAGE_PERCENTAGE (#6174)
* fix(sub): use a fullwidth percent in USAGE_PERCENTAGE

A remark is placed in the share link fragment, so an ASCII percent is
percent-encoded to %25. Happ treats such a fragment as malformed, discards the
whole remark and falls back to showing the server hostname, which defeats the
point of a remark template and leaks the host into the client's server list.

Emit U+FF05 FULLWIDTH PERCENT SIGN instead. It renders the same to a reader,
never produces %25, and round-trips through url.Parse unchanged.

* test(sub): exercise production fragment encoding

---------

Co-authored-by: n0ctal <293235942+n0ctal@users.noreply.github.com>
2026-08-14 20:13:15 +02:00
n0ctal 34c248bb79 fix(cli): stop -getApiToken accumulating admin tokens (#6175)
* fix(cli): stop -getApiToken accumulating admin tokens

`x-ui setting -getApiToken` reads like a getter, but when tokens already exist
it minted a brand-new one named `cli-fallback-<unix>` on every invocation. The
plaintext is printed once and the row stays enabled forever, so an operator who
runs the command a few times while debugging silently leaves several
admin-equivalent credentials behind that nobody can tell apart or revoke
knowingly.

Keep the convenience the fallback was added for, but rotate a single
`cli-fallback` token instead: RecreateByName drops any existing row with that
name before issuing a new one, so at most one CLI-issued token exists at a time
and the previous plaintext stops working.

* fix(api-token): preserve token on failed replacement

---------

Co-authored-by: n0ctal <293235942+n0ctal@users.noreply.github.com>
2026-08-14 20:12:43 +02:00
n0ctal 17fea2f656 fix(database): keep IP limits when the fail2ban probe is inconclusive (#6176)
* fix(database): keep IP limits when the fail2ban probe is inconclusive

ResetIpLimitNoFail2ban clears limitIp on every client — inbound settings JSON
and the clients table — whenever fail2banCanEnforce() returns false, then
records itself in the seeder history so it never re-evaluates. The probe was a
single `fail2ban-client -h` run, so it answered false both when fail2ban is
genuinely absent and when the command merely failed that once: a panel that
starts before fail2ban is up, or in a container where it is installed a moment
later, permanently loses every configured limit with no log line and no way
back.

Separate the two. A missing binary still means "absent" and the cleanup runs as
before; a binary that exists but will not run is reported as unknown, leaves the
configured values untouched, logs why, and does not record the seeder, so the
next start decides again.

* test(database): cover fail2ban reset safeguards

---------

Co-authored-by: n0ctal <293235942+n0ctal@users.noreply.github.com>
2026-08-14 20:12:07 +02:00
n0ctal c5dec64d36 fix(clients): push bulk client changes to nodes only after the commit lands (#6181)
* fix(clients): apply bulk mutations after durable commit

* test(clients): guard bulk pushes behind commit

* fix(clients): fully delete remote bulk clients

---------

Co-authored-by: n0ctal <293235942+n0ctal@users.noreply.github.com>
2026-08-14 20:11:19 +02:00
n0ctal b56b087254 fix(migration): stop a half-applied startup migration from committing silently (#6182)
* fix(traffic): check maintenance commits and IP-limit errors

* fix(migrations): propagate transactional failures

---------

Co-authored-by: n0ctal <293235942+n0ctal@users.noreply.github.com>
2026-08-14 20:10:39 +02:00
n0ctal 3bb87e80aa fix(db): harden unrestricted freedom outbounds (#6184) 2026-08-14 20:07:30 +02:00
n0ctal 60453bf523 fix(nodes): persist the master mTLS credential atomically and stop silent reissue (#6195)
* fix: harden master mTLS credential persistence

* fix(mtls): validate and serialize credential persistence

---------

Co-authored-by: n0ctal <293235942+n0ctal@users.noreply.github.com>
2026-08-14 20:03:54 +02:00
n0ctal bb29b6afec fix(node): adopt a matching deployed inbound instead of recreating it (#6197)
* fix(node): adopt compatible origin inbounds without mutation

* fix(nodes): preserve ambiguous and adopted aliases

---------

Co-authored-by: n0ctal <293235942+n0ctal@users.noreply.github.com>
2026-08-14 20:01:35 +02:00
n0ctal 3b19091547 fix(sub): render the full remark once per subscription, not once per credential (#6198)
* fix(sub): scope full remarks to subscription identity

* fix(sub): preserve configured remark whitespace

---------

Co-authored-by: n0ctal <293235942+n0ctal@users.noreply.github.com>
2026-08-14 20:00:51 +02:00
n0ctal 0496c23a26 fix(groups): report changed bulk moves without restarting xray (#6199) 2026-08-14 19:50:06 +02:00
n0ctal 1396005082 fix(traffic): apply maintenance side effects only after the commit lands (#6200)
* fix(traffic): apply maintenance after durable commit

* fix(traffic): apply all runtime maintenance after commit

---------

Co-authored-by: n0ctal <293235942+n0ctal@users.noreply.github.com>
2026-08-14 19:42:09 +02:00
n0ctal b70c5abce8 fix(outbounds): propagate allocation query failures (#6208)
* fix(outbounds): propagate allocation query failures

* test(outbounds): cover update allocation failure

---------

Co-authored-by: n0ctal <293235942+n0ctal@users.noreply.github.com>
2026-08-14 19:41:24 +02:00
n0ctal 20b3f84f77 fix(web): report unexpected HTTP serve failures (#6210)
* fix(web): report unexpected HTTP serve failures

* test(web): cover normal close and all HTTP servers

---------

Co-authored-by: n0ctal <293235942+n0ctal@users.noreply.github.com>
2026-08-14 19:40:53 +02:00
Sanaei d291e1c5ee Bump Go toolchain and x dependencies
Refresh the Go toolchain from 1.26.5 to 1.26.6 and update the related x/* and protobuf dependency set in go.mod/go.sum. This keeps the project aligned with the current patch releases and ensures the module graph matches the expected transitive versions.
2026-08-14 17:02:17 +02:00
Mr. Nickson ecadfd0e60 fix(clients): stop recomputing the summary badges from the client_stats snapshot (#6169)
* fix(clients): stop recomputing the summary badges from the client_stats snapshot

pickClientsSummary's coverage guard (serverSummary.total >
allClientStats.length) only catches a net shortfall: an orphaned
client_traffics row and a client still missing one can cancel out, or an
orphan surplus alone can pass uncaught, and either way the guard fails to
fall back (#6116).

client_paging.go's q.summary() already derives the same bucket counts with
clients as the driving table (LEFT JOIN client_traffics), so it cannot
miscount either shape regardless of how the row got there, and listQuery
already polls it every 5s — the same cadence client_stats ticks on. The
client-side recompute bought no fresher a number than the server already
provides on its own poll, only a window to get one wrong, so this drops it:
the summary badges now always read serverSummary directly. allClientStats,
computeClientsSummary, pickClientsSummary and sameSummaryInputs are removed
as dead code along with it; the per-row live traffic patch in
applyClientStatsEvent is untouched, since it reads the same snapshot by
email match rather than by count and was never exposed to this class of bug.

* fix(clients): force a refetch on window focus and drop a stale comment

Review feedback on PR #6169:

listQuery combines staleTime: Infinity with refetchInterval: 5000, which
pauses while the tab is hidden. The WS-driven per-row traffic patch in
applyClientStatsEvent has no such visibility gating, so on a background tab
a row's live numbers keep moving while the summary badges above them freeze
at whatever they were before the tab was hidden, and staleTime: Infinity
blocks refetchOnWindowFocus from closing that gap on return. Before this
PR the client-side recompute this branch removed happened to paper over the
same underlying gap; now that it's gone, the gap is directly visible.
refetchOnWindowFocus: 'always' forces exactly one refetch on refocus,
ignoring staleTime, without touching the interval/staleTime pairing that
governs the rest of this query's behavior.

Separately, useInbounds.ts still referenced computeClientsSummary by name
in a comment explaining bucket priority; that function no longer exists
after this PR. Dropped the comment rather than repoint it, per the repo's
no-//-comment convention.
2026-08-14 16:45:31 +02:00
MMX d05e44e401 fix(outbound): import Hysteria2 salamander properly from standard obfs params (#6166)
* fix(outbound): import Hysteria2 salamander from standard obfs params

The outbound share-link importers only reconstructed salamander from the
private fm=<json> finalmask dump. Every standard Hysteria2 link — and this
panel's own generator (internal/sub) since it stopped emitting fm= — carries
the obfuscation as the standard obfs=salamander & obfs-password=<pw> pair,
which the importers ignored. As a result, importing a normal Hysteria2 link
(pasted into the outbound form or pulled from a subscription) silently dropped
the salamander config and produced an outbound that negotiates plain QUIC
against a server expecting obfuscation.

Parse the standard obfs/obfs-password pair in both the Go importer
(internal/util/link, used by subscription + JSON import) and the frontend
form parser (outbound-link-parser.ts), folding it into finalmask.udp. A
salamander mask already supplied via fm= still wins, so 3x-ui→3x-ui links
are unchanged.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix(outbound): address review — mport hop, password-less fm mask, tests

Follow-up to the automated PR review on #6166:

- Import the Hysteria2 UDP port-hopping range from the standard `mport`
  param (finalmask.quicParams.udpHop.ports) in both importers — the same
  class of gap as salamander: the subscription generator emits `mport`
  standalone and no `fm=`, so port hopping was silently lost on import.
  An `fm=`-supplied udpHop still wins.
- When `fm=` carries a salamander mask without a usable password, fill it
  in from the obfs pair instead of treating the empty mask as authoritative
  (would otherwise enable obfuscation with an empty password).
- Trim the duplicated rationale comments to two lines each.
- Tests: collapse the four per-case Go functions into table-driven
  subtests; cover the obfs_password/obfsPassword aliases, case-insensitive
  obfs value, append-onto-non-salamander-udp, password-less-fm fill, and the
  mport paths; assert the fm-wins masks stay length 1 in both suites.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Co-authored-by: Sanaei <ho3ein.sanaei@gmail.com>
2026-08-14 16:43:29 +02:00
Kuzz007 9165ab67eb fix(install): preserve custom bin/ files (e.g. hand-added geoip) across updates (#6152)
* fix(install): preserve custom bin/ files (e.g. hand-added geoip) across updates

Every reinstall/update wipes /usr/local/x-ui/ wholesale and re-extracts
the release tarball, which only ships known assets (xray/mtg binaries,
the bundled geoip*/geosite*.dat sets). A user-reported real incident:
a hand-placed custom geoip file referenced from a routing rule via
"ext:<file>:<code>" got silently deleted on update, and Xray refused
to start at all afterward ("failed to open <file>: no such file or
directory"), taking down every inbound until the file was manually
restored from the user's own backup.

install_x-ui now backs up the old bin/ before the wipe and restores,
after extraction, only the files the fresh release doesn't provide --
bundled assets still get the newer per-release copy, nothing custom
silently disappears. Verified in isolation: standard files (geoip.dat,
the xray binary) end up as the fresh release's copy; a custom file
absent from the release survives untouched.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* fix: harden the bin/ snapshot-and-restore against the review round on #6152

- Replace the mktemp+cp snapshot with a same-filesystem mv of bin/ aside:
  an unchecked mktemp failure previously made the very next line copy
  bin/'s contents into "/" (empty custom_bin_backup + trailing slash),
  and a silently-ignored cp failure (stderr redirected, exit code never
  checked) could leave a truncated custom geo file that gets "restored"
  as if it were intact. A rename is atomic and needs no extra disk space,
  removing both failure modes at once; if it fails, back off cleanly and
  say so instead of proceeding as if a backup exists.
- Add a trap so an interrupted update (Ctrl-C, signal) between the
  backup and the restore doesn't leave the snapshot (which contains
  bin/config.json and every mtproto client's FakeTLS secret) sitting
  around indefinitely; the two exit-path cleanups this replaces are gone
  since the trap now covers those exits too.
- Move the restore below the arm arch-rename/chmod block instead of
  before it, so xray-linux-arm32/mtg-linux-arm already exist under their
  final names and don't get needlessly restored-then-overwritten and
  misreported as "custom".
- Exclude bin/config.json and bin/mtproto/*.toml from the restore: those
  are the panel's own generated runtime state (internal/xray/process.go,
  internal/mtproto/manager.go), not admin-placed files, and restoring a
  stale one only resurrects dead state or recreates bin/mtproto/ with the
  wrong (more permissive) directory mode.
- Match symlinks in the restore's find, not just plain files -- cp -a
  already preserves them in the snapshot, but the restore loop was
  silently dropping them, which is exactly the failure mode (a geo file
  symlinked in from elsewhere) this PR set out to fix.
- Quote the two new xui_folder expansions.
- Extend the non-interactive smoke test to reinstall over an existing
  install with a sentinel file in bin/, asserting it survives and that
  the bundled geoip.dat is still the release's own copy -- the update
  path this PR touches had no CI coverage at all before this.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-14 16:41:45 +02:00
n0ctal 0a30a03cb7 refactor(frontend): remove unused response envelope schema (#6204)
Co-authored-by: n0ctal <293235942+n0ctal@users.noreply.github.com>
2026-08-13 12:46:00 +02:00
n0ctal 286a93474d refactor(frontend): remove unreachable barrel modules (#6205)
Co-authored-by: n0ctal <293235942+n0ctal@users.noreply.github.com>
2026-08-13 12:45:26 +02:00
n0ctal 238e4bb314 refactor(tgbot): share numeric keypad transitions (#6211)
Co-authored-by: n0ctal <293235942+n0ctal@users.noreply.github.com>
2026-08-13 12:41:47 +02:00
n0ctal 4a5f6771b3 fix(nodes): report probe heartbeat persistence failures (#6207)
Co-authored-by: n0ctal <293235942+n0ctal@users.noreply.github.com>
2026-08-13 12:34:37 +02:00
n0ctal 64f4f0746c fix(warp): surface update-clock persistence failures (#6209)
Co-authored-by: n0ctal <293235942+n0ctal@users.noreply.github.com>
2026-08-13 12:31:05 +02:00
n0ctal 79ef85b59f refactor(frontend): remove unused legacy utilities (#6206)
Co-authored-by: n0ctal <293235942+n0ctal@users.noreply.github.com>
2026-08-13 12:27:40 +02:00
Sanaei 5b80d4562d chore(docs): bump docs dependencies
Update fumadocs-core/mdx/ui to 16.14.3, lucide-react to 1.31.0, @types/node to 26.2.0, typescript-eslint to 8.67.0, esbuild to 0.28.2, shiki to 4.4.3, and various other transitive dependencies.
2026-08-12 19:59:41 +02:00
dependabot[bot] e2f75acad2 chore(deps): bump github.com/klauspost/compress from 1.19.1 to 1.19.2 (#6212)
Bumps [github.com/klauspost/compress](https://github.com/klauspost/compress) from 1.19.1 to 1.19.2.
- [Release notes](https://github.com/klauspost/compress/releases)
- [Commits](https://github.com/klauspost/compress/compare/v1.19.1...v1.19.2)

---
updated-dependencies:
- dependency-name: github.com/klauspost/compress
  dependency-version: 1.19.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-08-12 16:57:37 +02:00
Chen, Ting-An 69a8237581 fix(i18n): localize Chinese Xray labels (#6202)
* fix(i18n): localize Traditional Chinese Xray labels

Several navigation, outbound, balancer, VPN, and DNS labels still displayed their English source text in the zh-TW interface. Translate the non-protocol labels while retaining established Xray terminology.

* fix(i18n): localize Simplified Chinese Xray labels

Mirror the reviewed Xray UI coverage in zh-CN so the same labels no longer fall back to English there.

Signed-off-by: 陳廷安 <73953029+nrps9909@users.noreply.github.com>

---------

Signed-off-by: 陳廷安 <73953029+nrps9909@users.noreply.github.com>
Co-authored-by: Sanaei <ho3ein.sanaei@gmail.com>
2026-08-12 16:56:11 +02:00
Sanaei f3f57e66f5 fix(frontend): wait out Collapse fade before a11y scan in ConfigBlock story
Collapse animates opacity in over motionDurationMid; the Collapsed story's
play function only waited for visibility, so the addon-a11y color-contrast
check could sample a mid-fade, lower-contrast frame and fail flakily in CI.
Wait for the panel's opacity to settle to 1 first.
2026-08-12 16:43:57 +02:00
Sanaei 8a8da88548 fix(frontend): isolate swagger deps from main vendor chunk
Keep swagger-ui-react and its transitive dependencies in the lazy swagger chunk so the initial panel bundle stays smaller. This avoids eager loading the OpenAPI UI on first paint while keeping the API docs route unchanged.
2026-08-12 16:11:47 +02:00
Sanaei 1f846c3cb2 fix(frontend): clean test validation output 2026-08-12 15:35:20 +02:00
Sanaei 1c255fc00c chore(frontend): bump npm dependencies
Refresh frontend package versions and regenerate the lockfile. This updates core UI and tooling packages including Ant Design, React Hook Form, Storybook, Vite, eslint/typescript-eslint, @noble/hashes, persian-calendar-suite, and swagger-ui-react to pick up the latest fixes and minor improvements.
2026-08-12 14:06:18 +02:00
dependabot[bot] 75032fd498 chore(deps): bump dompurify (#6193)
Bumps the npm_and_yarn group with 1 update in the /frontend directory: [dompurify](https://github.com/cure53/DOMPurify).


Updates `dompurify` from 3.4.12 to 3.4.13
- [Release notes](https://github.com/cure53/DOMPurify/releases)
- [Commits](https://github.com/cure53/DOMPurify/compare/3.4.12...3.4.13)

---
updated-dependencies:
- dependency-name: dompurify
  dependency-version: 3.4.13
  dependency-type: indirect
  dependency-group: npm_and_yarn
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-08-12 13:40:13 +02:00
Sanaei ece1655939 fix(docs): prevent theme switch hydration mismatch 2026-08-06 20:29:00 +02:00
Sanaei cb902314db fix(docs): restore theme switch without runtime warnings
Move html/body shell and global css to root app layout to avoid hydration/script warnings from nested document nodes. Disable provider theme injection and add a custom script-free theme switch in shared layout slots.

Also migrate docs search static client initializer to ZBSearch (initDB), add zbsearch dependency, and align docs lint tooling with ESLint 9 compatibility so npm run lint passes.
2026-08-06 17:59:23 +02:00
n0ctal 7eacce6a46 chore(frontend): resolve the high-severity brace-expansion advisory (#6180)
npm audit --omit=dev --audit-level=high is a CI gate and it currently fails on
main: swagger-ui-react pulls @swagger-api/apidom-reference, which pins
minimatch, which resolves brace-expansion to 5.0.8 — the range covered by
GHSA-rgw5-rvv9-x895.

Pin the patched 5.0.9 through the existing swagger-ui-react overrides block
rather than globally: minimatch@3 under eslint-plugin-jsx-a11y still needs the
1.x line, and a blanket override would force v5 there too.
2026-08-06 16:29:03 +02:00
dependabot[bot] 199ddaf485 chore(deps-dev): bump brace-expansion (#6172)
Bumps the npm_and_yarn group with 1 update in the /frontend directory: [brace-expansion](https://github.com/juliangruber/brace-expansion).


Updates `brace-expansion` from 1.1.16 to 1.1.18
- [Release notes](https://github.com/juliangruber/brace-expansion/releases)
- [Commits](https://github.com/juliangruber/brace-expansion/compare/v1.1.16...v1.1.18)

---
updated-dependencies:
- dependency-name: brace-expansion
  dependency-version: 1.1.18
  dependency-type: indirect
  dependency-group: npm_and_yarn
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-08-06 16:27:30 +02:00
dependabot[bot] d142307366 chore(deps-dev): bump postcss (#6173)
Bumps the npm_and_yarn group with 1 update in the /docs directory: [postcss](https://github.com/postcss/postcss).


Updates `postcss` from 8.5.21 to 8.5.23
- [Release notes](https://github.com/postcss/postcss/releases)
- [Changelog](https://github.com/postcss/postcss/blob/main/CHANGELOG.md)
- [Commits](https://github.com/postcss/postcss/compare/8.5.21...8.5.23)

---
updated-dependencies:
- dependency-name: postcss
  dependency-version: 8.5.23
  dependency-type: direct:development
  dependency-group: npm_and_yarn
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-08-06 16:27:13 +02:00