Commit Graph

2771 Commits

Author SHA1 Message Date
ssrlive 63a6d40457 Update ExecReload command in x-ui.service.debian (#5219)
* Update ExecReload command in x-ui.service.debian

* fix(systemd): use absolute path in ExecReload for arch and rhel units

systemd requires absolute executable paths; kill -USR1 $MAINPID fails
with 'Executable path is not absolute'. Matches the debian unit fix.

---------

Co-authored-by: MHSanaei <ho3ein.sanaei@gmail.com>
2026-06-12 12:09:48 +02:00
MHSanaei 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.
2026-06-12 12:03:22 +02:00
MHSanaei 7ae3ea66d1 feat(ui): improve client form modal UX
- Rename tabs: "Basic" → "Basics", "Config" → "Credentials"
- Move reverseTag field from Credentials tab to Basics tab
- Move IP log button inline with limitIp field (tooltip button, edit mode only)
- Hide random email button when editing an existing client
- Add tooltips to totalGB and limitIp fields with descriptive hints
- Rename labels: "Total Sent/Received (GB)" → "Traffic Limit (GB)", "Duration" → "Duration (days)"
- Add renewDays translation key for auto-renew label with unit hint
- Remove redundant filterOption and width style from AutoComplete group selectors
- Update all 15 locale files with new and renamed translation keys
2026-06-12 10:38:26 +02:00
MHSanaei 253063b785 feat: filter inbounds and clients by node (#4997)
Multi-node panels had no way to narrow the inbounds or clients lists to
a single node. Add a node filter to both pages:

- Inbounds: a toolbar select (All / Local / each node) that filters the
  list client-side; shown only when the panel has nodes or node-attached
  inbounds.
- Clients: a Nodes multi-select in the filter drawer. Node selections
  are mapped onto inbound IDs client-side and fed through the existing
  inbound CSV paging parameter, so the paging backend is untouched; an
  impossible id (-1) is sent when no inbound matches so the filter
  yields an honest empty result. InboundOption now carries nodeId to
  make the mapping possible.

The local panel is selectable via a 0 sentinel (inbounds without a
nodeId). New i18n keys in all 13 locales.
2026-06-12 09:33:35 +02:00
MHSanaei d04cb10971 feat(wireguard): per-peer comments for identifying devices (#5168)
WG peers were only identifiable by their keys. Add an optional panel-side
comment per peer: editable in the inbound form (echoed next to "Peer N"
in the section header), stored in the settings JSON alongside the
panel-only privateKey (xray-core ignores unknown peer fields), and
appended to the share link / .conf remark so the device is identifiable
in client apps too.
2026-06-12 09:10:57 +02:00
MHSanaei d1a13844b2 feat(api): include consumed traffic in the client-get response (#4973)
GET /panel/api/clients/get/:email returned the quota (totalGB) but not
the bytes the client has actually used, forcing API consumers to scrape
it elsewhere. Add a sibling "usedTraffic" field (up+down, including the
cross-node global overlay) next to "client" and "inboundIds".
2026-06-12 09:06:35 +02:00
MHSanaei 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.
2026-06-12 09:04:17 +02:00
MHSanaei 0e0e41197f fix(settings): normalize tgCpu on load so a bad value can't block saving (#5091)
The settings page validates the whole AllSetting object before saving, so a
tgCpu value that isn't an integer in 0-100 (left over from an older or corrupt
setting) failed validation with "tgCpu: Invalid input" and blocked saving every
other setting too. Clamp/round tgCpu to a valid integer in the model
constructor, defaulting to 80 when it isn't a finite number.
2026-06-12 03:17:32 +02:00
MHSanaei 5c29851be1 fix(nodes): "Invalid input" when saving a node with inbound sync mode "all"
NodeFormSchema required inboundTags, but the inboundTags Form.Item is only
mounted when inboundSyncMode is "selected" - antd onFinish omits unmounted
fields, so saving with the default "all" mode failed schema validation with
Zod generic "Invalid input" (regression from #5178; same class as the
earlier pinnedCertSha256 fix).

Also tolerate null inboundTags (Go nil slice) for nodes saved before #5178,
both in the form schema and NodeRecordSchema, and normalize edit-mode values.
2026-06-12 02:29:46 +02:00
MHSanaei 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.
2026-06-12 01:50:37 +02:00
MHSanaei 7e87b7dc60 i18n: point API token hint at the Authentication page in all locales
The remote panel's API token moved from Settings to the Authentication
page; update the node form hint accordingly.
2026-06-12 01:32:00 +02:00
MHSanaei dbee150b33 fix(script): SSL management fixes (#4994, #5010, #5070)
- Issue acme.sh HTTP-01 over IPv4 unless the host has no global IPv4
  address: the hardcoded --listen-v6 started a v6-only standalone
  listener, so validation of a domain whose A record points at this
  host always failed (#4994).
- Add a custom cert/key path option to the "Set Cert paths" menu so
  certificates living outside /root/cert (e.g. certbot under
  /etc/letsencrypt) can be wired to the panel from the CLI (#5010).
- Derive the displayed Access URL from the certificate's actual SAN
  list instead of the cert folder name, list the other covered names,
  and show the panel's custom-path certificate in "Show Existing
  Domains" (#5070). Also silence find when /root/cert doesn't exist.
2026-06-12 01:22:30 +02:00
MHSanaei 1a525b4cb4 fix(client): apply per-field client edits to every inbound of the email (#5039)
applyClientFieldByEmail patched only the first inbound that the
client_traffics row pointed at. For a multi-inbound client the sibling
inbounds kept the old expiryTime/totalGB/limitIp in their settings JSON,
and the next SyncInbound over a stale sibling reverted the edit in the
normalized records — the Telegram bot's expiry change appeared to apply
and then sprang back. Patch the field on every inbound linked to the
email, falling back to the legacy single-inbound lookup for clients that
were never normalized.
2026-06-12 01:22:15 +02:00
MHSanaei b062cb5a14 fix(sub): tag node-hosted entries with the node name in remarks (#5035)
An inbound pushed to nodes keeps the same remark on every copy, so a
multi-node subscription (and the panel's per-client link view) listed
several identically-named entries differing only by address. Append the
node name to the remark of node-hosted inbounds unless the admin already
included it.
2026-06-12 01:22:15 +02:00
MHSanaei a27d57b2ff fix(ui): keep dropdown action menus inside the viewport (#5133)
The inbound/client context menus hold a dozen items; when antd flips a
tall menu upward near the screen edge it overflowed the top of the
viewport, hiding the first entries. Cap the overlay height and scroll.
2026-06-12 01:21:54 +02:00
MHSanaei 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.
2026-06-12 01:21:54 +02:00
MHSanaei 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.
2026-06-12 01:21:38 +02:00
MHSanaei 0711d3077b chore: pin generated files to LF to avoid phantom CRLF diffs on Windows 2026-06-11 23:41:01 +02:00
MHSanaei 8578b229ce feat(settings): allow a balancer as the panel traffic outbound
The panel egress is injected as a routing rule, so a routing balancer is
a valid target for it (unlike the geodata download, which dials a forced
outbound tag and bypasses the router). Surface routing balancers in the
panel outbound picker as a separate group, and emit balancerTag instead
of outboundTag in the injected egress rule when the configured tag names
a balancer, so the panel's own traffic load-balances across its members.
2026-06-11 23:32:58 +02:00
MHSanaei 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.
2026-06-11 23:32:47 +02:00
MHSanaei 825778144c fix(outbound): widen probe timeout and surface failure reason in outbound test (#5152)
The v3 outbound test spins up a temp xray that probes the outbound via
burstObservatory. Two regressions made it report "Failed" for healthy
outbounds on high-latency / tunnel-routed boxes (e.g. default route over
an OpenVPN tun device to a remote proxy), even though client traffic over
the same outbound works:

- Each probe disables keep-alive, so every attempt is a cold round-trip
  (redial + re-handshake). The 5s per-probe timeout was too tight for such
  paths and every probe timed out. Restore the ~10s budget the pre-v3
  SOCKS-based test gave a cold connection (timeout 5s -> 10s) and widen the
  poll window 12s -> 15s so one full probe can complete and surface alive.

- The temp config set log error to "none", discarding the real failure
  reason, so "Failed" was undiagnosable. Route error logs to stderr ("")
  like the production template does, so the probe error (DNS lookup
  failure, connection refused, deadline exceeded, TLS error, ...) is
  captured into the panel/Xray log, and point the operator there in the
  generic timeout messages.
2026-06-11 22:49:22 +02:00
MHSanaei 1b0dbf8e6d fix(sub): deduplicate settings.clients entries per inbound in subscription output (#5134)
Multi-node sync/import drift can leave the same client twice inside an
inbound's legacy settings.clients JSON while the normalized
client_inbounds table stays clean (SyncInbound dedupes the rows it
writes but never rewrites the JSON). All three subscription builders
iterated that JSON verbatim, so every duplicate entry became a
duplicate profile in the raw, Clash, and JSON output.

Filter and dedupe by email in one shared helper (link generation keys
purely on inbound + email, so same-email entries are pure duplicates
and dropping them is lossless). The clash/json services' own
inboundService copies became unused and are removed.
2026-06-11 22:19:14 +02:00
MHSanaei 09a887f95c fix(warp): prefer IPv4 with v6 fallback and userspace TUN in generated WireGuard outbounds (#5205)
The generated WARP outbound used domainStrategy ForceIP, which may pick
the AAAA record for engage.cloudflareclient.com; on a host with
half-configured IPv6 the handshake then blackholes with nothing in the
logs. ForceIPv4v6 prefers IPv4 and still falls back to IPv6 on
v6-only hosts, matching the official WARP client's behavior.

It also set noKernelTun: false, so with root privileges the real
outbound used kernel TUN — a path that needs CAP_NET_ADMIN plus fwmark
routing and fails silently on many VPS setups — while the panel's
connectivity probe always tests with noKernelTun: true. The status
check and real traffic exercised different data paths and could
disagree. Generate WARP and NordVPN outbounds with the userspace TUN
so both follow the path the probe validates.

Only affects newly added/reset outbounds; existing templates keep
their saved settings.
2026-06-11 21:49:45 +02:00
MHSanaei cc65f37164 fix(sub): honor per-inbound share address strategy in subscription output (#5208)
Subscriptions resolved a node-managed inbound's address to the node's
panel address unconditionally, so an inbound bound to a specific public
IP advertised an endpoint clients could not reach. The shareAddrStrategy
field added in #5162 only applied to panel share/QR links by design.

resolveInboundAddress now follows the same order as the panel's link
builder: 'listen' prefers a routable bind, 'custom' prefers shareAddr,
and the default 'node' keeps the existing node-first behavior, so output
is unchanged for inbounds that never set the field. Applies to raw,
JSON, and Clash subscriptions, which all resolve through this path.
Help text in all locales updated to drop the 'subscriptions are not
affected' caveat.
2026-06-11 21:31:27 +02:00
MHSanaei 21143a6d72 fix(node-sync): keep node baseline while a sibling inbound still reports the email (#5202)
The orphan sweeps in setRemoteTrafficLocked deleted the (node, email)
baseline row unconditionally whenever an email was missing from one
inbound's snapshot stats — even though baselines are keyed per node, not
per inbound. For a client attached to two inbounds of the same node whose
stats the node reports under only one of them, the sweep for the other
inbound deleted the baseline at the end of every sync cycle. Depending on
inbound order, the baseline written earlier in the same transaction was
wiped each time, so the next cycle computed delta against a missing
baseline (zero) and the client's traffic froze permanently.

Scope both sweeps to the union of emails across the whole snapshot: a
baseline is only dropped when the email left the node entirely.
2026-06-11 21:20:38 +02:00
MHSanaei 1508666e52 fix: DNS server edit modal showing defaults instead of saved values (#5155)
The DNS server table columns were memoized with only [t] as deps, so
they permanently captured the first render's openEditServer callback,
which closed over the initial (null) dns settings. Clicking Edit then
resolved the server to null and the modal fell back to default values.

Stabilize openEditServer/deleteServer (and the fakedns equivalents)
with useCallback and include them in the column memo deps so the
columns refresh whenever the servers list changes.
2026-06-11 20:58:23 +02:00
MHSanaei 2db48174b0 fix: apply only the x-ui sysctl config when toggling BBR (#5160)
sysctl --system re-applies every sysctl file on the host, surfacing
unrelated "Invalid argument" errors from the distro's own defaults
(e.g. Ubuntu 22.04's 50-default.conf on kernels 5.14+). Apply only
/etc/sysctl.d/99-bbr-x-ui.conf on enable, and drop the redundant
re-apply on disable since sysctl -w already restores the live values.
2026-06-11 20:53:05 +02:00
animesha3 554d85c2f7 feat: allow selecting inbounds synchronized from nodes (#5178)
* feat: select node inbounds for synchronization

Allow node owners to import either all remote inbounds or an explicit tag-based selection. Add remote inbound discovery, persistence, snapshot filtering, API documentation, tests, and localized UI labels.

* fix

* fix: scope node reconcile and orphan sweep to selected inbound tags

In 'selected' sync mode unselected inbounds never enter the panel DB, so
ReconcileNode treated them as undesired and deleted them from the node the
first time it went config-dirty. Reconcile now only sweeps remote tags that
are part of the selection; everything else on the node is unmanaged.

Panel-created or renamed inbounds on a selected-mode node also vanished:
their tag was outside the selection, so the next traffic pull filtered them
out of the snapshot and the orphan sweep silently dropped the central row.
AddInbound/UpdateInbound now allow the tag on the node before committing.

---------

Co-authored-by: Sanaei <ho3ein.sanaei@gmail.com>
2026-06-11 20:48:26 +02:00
iYuan 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>
2026-06-11 20:24:15 +02:00
w3struk ec45d3491a fix: derive JSON/Clash subscription URLs from configured subURI (#5203)
* fix: derive JSON/Clash subscription URLs from configured subURI

When subURI is explicitly configured (reverse-proxy setup) but subJsonURI
or subClashURI are not, BuildSubURIBase generates URLs with the raw sub-
server port (2096) and the wrong scheme (http), producing broken links
on the subscription page (e.g. http://domain:2096/json/SUB_ID).

Fix: in BuildURLs, when subURI is set, extract its scheme+host and use
that as the base for all unconfigured sibling URLs instead of calling
BuildSubURIBase. This ensures JSON and Clash Copy URLs match the reverse-
proxy endpoint.

Fixes: JSON/Clash subscription URLs shown on the subscription info page
now correctly inherit the configured subURI's scheme and host.

* fix(sub): fall back to request base when configured subURI is unparseable

Harden the JSON/Clash URL derivation added for the reverse-proxy fix:
extractBaseFromURI now returns "" when the configured subURI has no
scheme/host, and BuildURLs falls back to the request-derived base in
that case instead of emitting a broken value (e.g. ":///json/ABC").

Add a regression test covering a scheme-less subURI.

---------

Co-authored-by: w3struk <w3struk@gmail.com>
Co-authored-by: Sanaei <ho3ein.sanaei@gmail.com>
2026-06-11 20:05:38 +02:00
MHSanaei 7bcc5830c6 feat(online): use xray online-stats API for onlines and access-log-free IP limit
Adopt xray-core's statsUserOnline policy and GetUsersStats RPC so online
detection is connection-based and IP limiting no longer requires an access
log. Falls back to the legacy traffic-delta onlines and access-log parsing
when the running core lacks the RPCs (Unimplemented), probed lazily per
process so a panel-driven version switch re-evaluates automatically.

Backend:
- xray/api.go: GetOnlineUsers (one GetUsersStats call returns all online
  users and their source IPs) and IsUnimplementedErr.
- xray/process.go: per-process OnlineAPISupport tri-state capability cache.
- service/xray.go: ensureStatsPolicy injects statsUserOnline into every
  policy level of the generated config; XrayService.GetOnlineUsers probes
  and falls back.
- job/xray_traffic_job.go: union API onlines into the delta-derived active
  set; bump last_online for idle-but-connected clients.
- job/check_client_ip_job.go: API-first IP source with shared enforcement;
  live observations bypass the 30-min stale cutoff; access-log path
  unchanged for older cores.
- service/setting.go: GetIpLimitEnable always true; new accessLogEnable
  default for features that genuinely read the access log.

Frontend:
- Client form split into Basic and Config tabs; IP Limit and IP Log no
  longer gated on access log; compact Auto Renew next to Start After First
  Use; tabBasic/tabConfig added to all 13 locales.
- Xray logs button on the dashboard now gated on accessLogEnable.
2026-06-11 19:42:03 +02:00
MHSanaei 58905d81a4 feat(node-sync): push global client usage to nodes for display and local enforcement
A client attached to several panels has one aggregated row on each
master, but a node only ever saw its local share: the node UI
under-reported usage, and the node kept serving a client whose
cross-panel total had already exceeded its quota — the master's disable
push doesn't kill established connections unless the node restarts xray
itself.

Masters now push their aggregated per-client counters to each node from
NodeTrafficSyncJob (throttled, scoped to the clients that node hosts).
The node stores them in the new client_global_traffics side table keyed
by (masterGuid, email), overwritten on every push so a master-side
reset propagates, and:

- overlays max(local, pushed) onto UI read paths (slim inbound list,
  inbound detail, clients list, WS stats, per-email lookups). The full
  /panel/api/inbounds/list stays un-overlaid on purpose: it doubles as
  the traffic snapshot masters poll, and overlaying it would corrupt
  every master's delta accounting;
- trips disableInvalidClients when any master's pushed total exceeds
  the client's quota, so the existing RestartXrayOnClientDisable flow
  disconnects the client locally;
- clears the side rows on traffic reset, auto-renew, and client
  delete, keeping a renewed quota window clean.

Supersedes #5204, which folded pushed globals into client_traffics and
compensated with read-back baselines — that double-counted first-sight
emails and could not work with several masters sharing one node.
2026-06-11 15:14:08 +02:00
MHSanaei 8258a26fbf fix(node-sync): keep shared client traffic row when email still lives on other inbounds
client_traffics is the per-email accumulator shared across every inbound
and node the client is attached to. setRemoteTrafficLocked deleted it
unguarded in two sweeps — when a node inbound vanished from the snapshot
(node reinstall, tag change, another master's reconcile on a shared
node) and when an email left one inbound's stats — even though the
email was still attached elsewhere. The next sync then re-seeded the
row with that node's counter alone, so the panel showed the last
changed panel's number instead of the summed total.

Guard both sweeps with emailUsedByOtherInbounds, matching what the
manual-edit path (updateClientTraffics) already does. Truly removed
clients are still cleaned up by the zero-attachment sweep.
2026-06-11 14:28:09 +02:00
MHSanaei dc52e725b6 fix(ui): blink the online dot in mobile client cards like desktop
The mobile card rendered a static antd Badge for every bucket. When the
client is enabled, online, and not depleted, render the same animated
online-dot span the desktop Online column and the nodes list use.
2026-06-11 14:05:10 +02:00
MHSanaei aeb2217ae5 fix(ui): classify ended clients as depleted, not disabled, on inbounds page
The auto-disable job flips client.enable off in the settings JSON when a
client expires or exhausts its traffic, so the inbounds-page rollup filed
every ended client under the gray Disabled badge (and double-counted it
in Depleted when stats were present). Classify with depleted-first
priority, matching computeClientsSummary and the client info modal.

Also backfill cross-inbound client_traffics rows in GetInboundsSlim:
the row is keyed on email and only preloads on the inbound the client
was created on, so on every other attached inbound the depleted/expiring
checks could never fire.
2026-06-11 14:05:02 +02:00
MHSanaei 9730561f20 ci(bot): update issue-bot repo map and tighten reply style
- Refresh repository map: add internal/mtproto, web/entity, web/global,
  web/session, web/locale, frontend/, tools/openapigen, docs/, windows_files
- Correct stack facts: Xray-core runs as a managed child process; full env
  var list incl. XUI_INIT_WEB_BASE_PATH, XUI_LOG_FOLDER, XUI_BIN_FOLDER,
  XUI_SKIP_HSTS; protocol list matches the model.go enum incl. MTProto
- Add COMMENT STYLE section for professional, precise, answer-first replies
- Raise --max-turns for both jobs
2026-06-11 13:28:35 +02:00
Nikan Zeyaei 07e5e8498e feat(ui): add select all / clear all shortcuts for inbound multi-select (#5175)
* feat(ui): add select all / clear all shortcuts for inbound multi-select

Adds 'Select all' and 'Clear all' buttons above the inbound multi-select in:
- ClientFormModal (add/edit client)
- BulkAttachInboundsModal (bulk attach clients to inbounds)
- BulkDetachInboundsModal (bulk detach clients from inbounds)
- ClientBulkAddModal (add bulk clients)

Extracts the repeated button logic into a reusable SelectAllClearButtons component.

Includes i18n keys for all 13 supported languages with proper translations.

Closes #5144

* refactor(form): decouple SelectAllClearButtons labels and harden select-all

Accept optional selectAllLabel/clearLabel props so the generic form component is not tied to the client-inbound i18n keys (defaults unchanged). Compute the all-selected state by checking every option is present and union the current value on select-all, so it stays correct if value holds ids outside options.

---------

Co-authored-by: Sanaei <ho3ein.sanaei@gmail.com>
2026-06-11 13:09:58 +02:00
Nikan Zeyaei ffde2f7ebf feat(sub): add Copy All Configs button to subscription page (#5163)
* feat(sub): add Copy All Configs button to subscription page

* fix(sub): include links in copyAll dependency array

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

* chore: fmt

* fix(sub): drop module-level links from copyAll deps to satisfy exhaustive-deps

links is derived from window.__SUB_PAGE_DATA__ at module scope, so listing it in the useCallback dependency array triggers a react-hooks/exhaustive-deps warning (outer-scope value). Matches the existing single-link copy callback's deps.

---------

Co-authored-by: nikan <nikan>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Sanaei <ho3ein.sanaei@gmail.com>
2026-06-11 13:00:37 +02:00
Vladimir Avtsenov 89b1137b00 feat(env): allow setting the initial URI path for the web panel (#5149)
* feat(env): allow setting the initial URI path for the web panel

* fix(setting): normalize and guard XUI_INIT_WEB_BASE_PATH default

Address Copilot review on PR #5149: an env value that is empty, whitespace, or lacks slashes (e.g. `panel`) could produce an invalid webBasePath such as `/ /` and reach the frontend un-normalized.

getEnv now trims whitespace and falls back when the value is empty; the env-derived default is passed through the existing normalizeBasePath helper (reused from node.go) so it always carries a leading and trailing slash. GetBasePath reuses the same helper instead of duplicating the slash logic.

---------

Co-authored-by: Sanaei <ho3ein.sanaei@gmail.com>
2026-06-11 12:51:54 +02:00
aleskxyz 8f408d2d6a feat(routing): show tag (remark) in routing rules list (#5151)
* feat(routing): show tag (remark) in routing rules list

Rules table and mobile cards showed raw inboundTag while the form already
used remarks. Display "tag (remark)" when a remark exists; saved rules
still store tags only.

Signed-off-by: aleskxyz <39186039+aleskxyz@users.noreply.github.com>

* feat(inbounds): show "tag (remark)" consistently wherever an inbound is listed

Add a shared formatInboundLabel/formatInboundTag helper and apply the "tag (remark)" format across the routing rules table, mobile cards, the rule form and route tester, plus the client attach/detach/filter modals and the attached-inbounds column. Falls back to the bare tag when no distinct remark exists.

Also fix the routing rules list mis-rendering inbounds whose remark contains a comma: formatted entries are now carried as an array end to end instead of being joined and re-split on commas.

---------

Signed-off-by: aleskxyz <39186039+aleskxyz@users.noreply.github.com>
Co-authored-by: Sanaei <ho3ein.sanaei@gmail.com>
2026-06-11 12:46:24 +02:00
nima1024m 941eba546d feat(clients): restore traffic usage progress bars on Clients page (#5150)
Bring back the v2.9.x traffic column UX: used amount, color-coded progress bar, limit/infinity label, and hover popover with upload/download/remaining breakdown. Adds a shared ClientTrafficCell component, traffic display helpers, and unit tests.
2026-06-11 12:10:49 +02:00
Rouzbeh† c7a76e9626 fix: enable XTLS vision flow for VLESS+XHTTP+vlessenc in UI and share links (#5157) (#5185)
* fix: enable XTLS vision flow for VLESS+XHTTP+vlessenc in UI and share links (#5157)

* fix: enable xtls-rprx-vision flow for VLESS XHTTP with vlessenc encryption (#5157)

The flow selector was hidden and the vless:// link omitted flow= because:
1. The backend gate (inboundCanEnableTlsFlow) only accepted tcp+tls/reality.
2. The PR #5185 frontend check used `encryption === 'vlessenc'`, which never
   matches — the stored value is a generated ML-KEM dotted string, not the CLI
   subcommand name.

Fix: extend inboundCanEnableTlsFlow to also return true for XHTTP when a
non-none vlessenc encryption/decryption value is present. Update all three
call-sites (inbound.go TlsFlowCapable field, client_crud.go clientWithInboundFlow,
inbound_clients.go copy-flow path) and the sub/service.go link generator.
Scope is XHTTP-only: TCP without tls/reality is intentionally excluded.

Add inbound_protocol_test.go covering the new and existing gate combinations,
extend client_flow_isolation_test.go with xhttp+vlessenc cases, and add
frontend tests for canEnableTlsFlow with real ML-KEM key values.

---------

Co-authored-by: rqzbeh <rqzbeh@users.noreply.github.com>
Co-authored-by: Sanaei <ho3ein.sanaei@gmail.com>
2026-06-11 12:04:02 +02:00
dependabot[bot] eee652c4a5 chore(deps): bump golang.org/x/net from 0.55.0 to 0.56.0 (#5199)
Bumps [golang.org/x/net](https://github.com/golang/net) from 0.55.0 to 0.56.0.
- [Commits](https://github.com/golang/net/compare/v0.55.0...v0.56.0)

---
updated-dependencies:
- dependency-name: golang.org/x/net
  dependency-version: 0.56.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-06-11 11:06:03 +02:00
Rouzbeh† 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>
2026-06-11 11:05:42 +02:00
Rouzbeh† 57e9661758 fix: properly configure fail2ban backend and dependencies on Ubuntu 22.04+ (#5159) (#5184)
Co-authored-by: rqzbeh <rqzbeh@users.noreply.github.com>
2026-06-11 01:27:39 +02:00
Rouzbeh† 65fa40b819 fix: accurately retrieve and generate API tokens via CLI with hashed storage (#5145) (#5183)
Co-authored-by: rqzbeh <rqzbeh@users.noreply.github.com>
2026-06-11 01:25:23 +02:00
MHSanaei f88f53cd7b fix(update): restart panel after regenerating webBasePath to fix login desync
When update.sh regenerates a short webBasePath, it writes the new path to the
database after the panel is already running with the old path loaded in memory.
Without a restart the server keeps serving the old path while the UI shows the new
one, making the new path unreachable.
2026-06-11 00:17:55 +02:00
MHSanaei ca4f32e3da feat: replace panel proxy URL with outbound-based egress bridge
Instead of requiring a manual SOCKS5/HTTP URL, the panel now lets the
admin pick an Xray outbound from a dropdown (same UX as Geodata
Auto-Update). At runtime, injectPanelEgress appends a loopback SOCKS
inbound (tag: panel-egress) and prepends a routing rule so the panel's
own HTTP traffic — version checks, Telegram, normal geo-file updates —
is routed through the chosen outbound. Xray-native Geodata Auto-Update
is unaffected (it uses its own geodata.outbound inside Xray). Blackhole
outbounds are excluded from both picker dropdowns since routing any
download through one just drops it. Translations updated for all 13
locales.
2026-06-10 23:52:20 +02:00
MHSanaei 6b16d8c37a feat: apply inbound/outbound/routing changes live via Xray gRPC API
Add a hot-apply layer that computes a diff between the old and new
generated config and applies only the changed parts through the Xray
gRPC HandlerService and RoutingService, avoiding a full process restart
whenever possible. A restart is still performed when sections that have
no reload API (log, dns, policy, observatory, ...) actually change.

Key additions:
- internal/xray/hot_diff.go: ComputeHotDiff with canonical-JSON
  comparison (sorted keys, null=absent, full number precision) so UI
  reformatting never triggers a spurious restart
- internal/xray/api.go: AddOutbound/DelOutbound, ApplyRoutingConfig,
  GetBalancerInfo, SetBalancerTarget, TestRoute gRPC wrappers
- internal/web/service/xray.go: tryHotApply, ensureAPIServices,
  GetBalancersStatus, OverrideBalancer, TestRoute service methods
- internal/web/controller/xray_setting.go: balancerStatus,
  balancerOverride, routeTest API endpoints
- frontend: BalancersTab live-status/override columns, RouteTester
  component, Restart button removed (Save now hot-applies)
- balancer-helpers.ts: syncObservatories never creates observatory
  sections for random/roundRobin balancers (no reload API → restart)
- i18n: balancerLive/Override/routeTester keys added to all 13 locales
2026-06-10 23:01:33 +02:00
MHSanaei 3092326d9e refactor: replace custom geo manager with Xray-core native geodata auto-update
Remove the panel-side custom geo download feature (service, controller,
/panel/api/custom-geo/* endpoints, CustomGeoResource model, UI tab) in
favor of Xray-core's native geodata section
(https://xtls.github.io/config/geodata.html).

- pass the top-level "geodata" key through xray.Config so it survives
  the template round-trip into the generated config
- add a Geodata Auto-Update section to the Xray Updates modal that
  edits geodata (cron schedule, download outbound, asset list) in the
  config template and restarts Xray on save
- previously downloaded geo files in the bin folder keep working in
  ext: routing rules; the orphaned custom_geo_resources table is left
  in place so existing source URLs stay recoverable
2026-06-10 18:27:12 +02:00