Commit graph

1362 commits

Author SHA1 Message Date
MHSanaei
f4f0af576a
feat(ws): live updates on inbounds/xray/nodes pages, drop polling + manual refresh
Replaces the legacy polling + manual-refresh model with WebSocket pushes
across the three live-data pages. The hub already broadcast traffic /
client_stats / outbounds; this wires the frontend to consume them and
adds a new `nodes` channel for the heartbeat job's snapshot.

Frontend
- new useWebSocket composable: page-scoped singleton WebSocketClient,
  lifecycle-managed on/off, leaves disconnect to page-unload
- inbounds: useInbounds gains applyTrafficEvent / applyClientStatsEvent
  / applyInvalidate that merge counters and online/lastOnline in place;
  InboundsPage subscribes; InboundList drops the auto-refresh popover,
  the refresh button, and the now-unused refreshing prop
- xray outbounds: useXraySetting gains applyOutboundsEvent; XrayPage
  subscribes; OutboundsTab drops the refresh button + emit
- nodes: useNodes gains applyNodesEvent and stops the 5s
  setInterval/visibilitychange polling; NodesPage subscribes;
  NodeList drops the refresh button and ReloadOutlined import

Backend
- web/websocket: new MessageTypeNodes + BroadcastNodes notifier
- node_heartbeat_job: after wg.Wait(), reload the table once and
  BroadcastNodes(updated). Gated on websocket.HasClients() so a panel
  with no open browser doesn't spend the DB read

Bug fixes spotted in this pass
- websocket.js #buildUrl defaulted basePath to '' when the global was
  missing (dev mode), producing `ws://host:portws` and a SyntaxError
  on the WebSocket constructor. Fall back to '/' and ensure leading
  slash.
- vite.config.js: forward /ws to ws://localhost:2053 with ws:true so
  dev (5173) reaches the Go backend's WebSocket
- NodeFormModal: a-input-password's visibilityToggle is Boolean in
  AntD Vue 4; the v3-era object form (`{ visible, 'onUpdate:visible' }`)
  triggered a Vue prop-type warning. Drop the override (default true
  shows the eye icon and toggles internally) and remove the orphaned
  tokenVisible ref

Translations
- pages.inbounds.autoRefresh / autoRefreshInterval: removed from all
  13 locales (UI gone)
- pages.nodes.refresh: removed from all 13 locales (UI gone)

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-09 17:30:31 +02:00
MHSanaei
6bdf4bb4a0
fix(embed): include underscore-prefixed Vite chunks in dist FS
go:embed silently excludes files whose names start with `_` or `.`,
so the `_plugin-vue_export-helper-<hash>.js` chunk that Vite/rolldown
emits for @vitejs/plugin-vue was missing from the production binary.
First import at runtime hit a 404 and the SPA failed to mount — blank
page on every page load, no error in the server logs because the
asset 404 was just a static-handler miss.

Switched the directive to `//go:embed all:dist` which keeps the same
root layout but disables the underscore/dot exclusion rule. Dev mode
was unaffected (it serves dist/assets/ from disk, not the embedded FS).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-09 16:44:15 +02:00
MHSanaei
8cd97654f2
feat(stats): system history modal + per-node CPU/Mem trends across all locales
Backend
- web/service/metric_history.go: generic in-memory ring buffer with two
  singletons — system-wide (cpu/mem/netUp/netDown/online/load1/5/15)
  and per-node (cpu/mem) keyed by node id
- ServerService.AppendStatusSample writes all 8 metrics every 2s on the
  same tick; AppendCpuSample/AggregateCpuHistory kept for back-compat
- NodeService.UpdateHeartbeat appends cpu/mem only on online ticks so
  offline gaps render as missing data, not phantom dips
- New routes: GET /panel/api/server/history/:metric/:bucket and
  GET /panel/api/nodes/history/:id/:metric/:bucket, both whitelisted

Frontend
- Sparkline component generalized: arbitrary value range (auto-scale
  when valueMax=null), pluggable yFormatter/tooltipFormatter for B/s,
  client counts, load averages
- SystemHistoryModal replaces CpuHistoryModal with tabs for every
  metric; opened from a tag on the 3X-UI card next to Documentation
- NodeHistoryPanel: expandable row on the Nodes table showing per-node
  CPU and Mem trends, refreshed every 15s

Localization
- Backfill systemHistoryTitle / trendLast2Min / pages.inbounds.{node,
  deployTo, localPanel} and the entire pages.nodes block (51 keys
  including statusValues + toasts) into all 11 non-en/fa locales:
  ar-EG, es-ES, id-ID, ja-JP, pt-BR, ru-RU, tr-TR, uk-UA, vi-VN,
  zh-CN, zh-TW

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-09 16:24:57 +02:00
MHSanaei
36114a2fcc
feat(nodes): multi-node panel orchestration (CRUD, deployment, traffic sync, sub per-node)
- Node model + service + controller (/panel/api/nodes/*) with bearer-token apiToken auth
- Heartbeat job @every 10s; status/latency/xrayVersion surfaced in Nodes UI
- Runtime abstraction (Local + Remote) so inbound/client mutations target the
  inbound's owning node instead of always hitting the local xray
- Inbounds gain optional NodeID; tag-based correlation with remote panel (no
  RemoteInboundID column needed)
- NodeTrafficSyncJob @every 10s pulls absolute counters + online/lastOnline
  from each enabled+online node and writes them into central DB; 30s reset
  grace window prevents post-reset overwrite
- Reset propagation to nodes (best-effort) on client/inbound/all reset paths
- Subscription server uses node.Address for inbounds with NodeID, falling back
  to existing host resolution for local inbounds
- Frontend: Nodes page, "Deploy to" select in inbound form, Node column on
  inbound list, hostOverride threaded through genAllLinks/QR/Info modals

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-09 15:25:29 +02:00
MHSanaei
ef066a19fc
feat(inbounds): one client identity across multiple inbounds via subId
Lets the operator add the same email under the same subId to several
inbounds. Xray reports traffic per email, so a single client_traffics
row acts as the shared accumulator — no aggregation overhead, quota and
expiry stay consistent.

- Email validation allows duplicates only when subId matches
- AddClientStat upserts via OnConflict DoNothing (idempotent on rerun)
- Stat/IP rows survive client deletion when a sibling inbound still
  references the email
- enrichClientStats tops up GORM-preloaded stats with rows whose
  inbound_id points at a sibling, so every panel view sees usage
- disableInvalidClients cascades enable=false and syncs the row's
  total/expiry into every sibling JSON when the shared identity expires
- DelDepletedClients removes the depleted client from all referencing
  inbounds, batched
- Subscription services dedupe traffic by email so shared quota is
  counted once

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-09 03:57:57 +02:00
MHSanaei
a5083f02e1
revert(frontend): keep entry HTML files at frontend/ root
The earlier move to frontend/html/ made dev-mode URLs ugly
(http://localhost:5173/html/index.html instead of plain /). The folder
didn't add real value — it just hid 6 files behind a non-conventional
layout. Reverting that piece while keeping src/entries/ (which is a
genuine separation between page bootstrap and the rest of src/).

- HTML files back at frontend/<page>.html
- Vite rollupOptions.input + MIGRATED_ROUTES restored to flat paths
- Build output is web/dist/<page>.html again
- web/controller/dist.go and sub/subController.go read from dist/<name>

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-09 02:36:26 +02:00
MHSanaei
2616f25638
refactor(frontend): organize entry HTML + bootstrap JS into folders
- Move entry HTML files: frontend/*.html -> frontend/html/*.html
- Move per-page bootstrap modules: src/{index,login,settings,inbounds,xray,subpage}.js -> src/entries/
- Update vite.config rollup inputs and dev-mode MIGRATED_ROUTES to /html/<page>.html
- Build output now lands at web/dist/html/<page>.html
- serveDistPage and subController updated to read from dist/html/

Cleans up the flat frontend/ root which previously interleaved 6 HTML
files with package.json, README, src/, etc. The src/ root similarly
gets rid of 6 entry .js files mixed in alongside api/, components/,
models/, etc.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-09 01:52:57 +02:00
MHSanaei
fb222a6622
chore: remove legacy template + asset trees and dead Go template engine
- Delete web/html/ entirely (page templates, form/, modals/, component/,
  common/, settings/) — every route is served from web/dist/ now via
  serveDistPage; nothing in the binary referenced these
- Delete web/assets/ entirely (jQuery-era ant-design-vue, axios, moment,
  codemirror, qrcode/qs/uri/vue/otpauth, custom CSS, Vazirmatn font);
  Vite bundles all of this into web/dist/assets
- Drop the Gin HTML template wiring: remove //go:embed assets +
  //go:embed html/*, the assetsFS/htmlFS vars, the wrapAssetsFS adapter,
  EmbeddedHTML / EmbeddedAssets exports, getHtmlFiles / getHtmlTemplate,
  the i18nWebFunc/funcMap and SetFuncMap call, and the dev/prod
  template-engine branch — only StaticFS for /assets/ is needed now
- Remove dead html()/getContext() helpers and unused imports from
  web/controller/util.go (no c.HTML(...) callers remain)

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-09 01:08:05 +02:00
MHSanaei
cbb35f73ed
feat(frontend): jalali calendar + drop legacy moment-jalali
- Wire Calendar Type setting to a real Jalali datepicker via
  vue3-persian-datetime-picker, gated by useDatepicker composable
- DateTimePicker wrapper swaps between AD-Vue and Persian picker; keeps
  dayjs v-model contract so existing forms/setters work unchanged
- Theme picker popup explicitly per body.dark / data-theme=ultra-dark
  (AD-Vue 4 doesn't expose CSS vars, so var() fallbacks defaulted to
  white); fix invisible disabled days, SVG arrow fills, popup clipping
  via append-to="body"
- Replace stray moment() calls in dbinbound/inbound models with dayjs;
  the legacy global was undefined under ESM and broke the inbounds list
  whenever any inbound had expiryTime > 0
- Remove legacy moment-jalali / persian-datepicker / aPersianDatepicker
  assets — replaced by the Vue 3 picker

Note: dark/ultra background of the date popup still renders white in
some cases — pending follow-up.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-09 00:17:25 +02:00
MHSanaei
aaaa1a015f
feat(frontend): migrate subpage.html to Vue 3 SPA
The subscription info page was the last page still rendered by Go
templates. Move it to the Vite multi-page setup so the whole panel
loads through one toolchain.

Frontend: SubPage.vue mounts at /sub/<id>?html=1 and reads window.__SUB_PAGE_DATA__
for the parsed view-model (traffic / quota / expiry + rendered share
links). Fix descriptions borders against the light-theme card by
painting the row divider on each cell's bottom edge — AD-Vue's <tr>
border doesn't render reliably under border-collapse:collapse.

Backend: serveSubPage reads dist/subpage.html, injects
window.__X_UI_BASE_PATH__ + window.__SUB_PAGE_DATA__ before </head>,
and rewrites Vite's absolute /assets/ URLs when the panel runs under
a URL prefix. Drop the legacy template-FuncMap wiring and switch the
sub server's static mount from web/assets to web/dist/assets.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-08 19:44:50 +02:00
MHSanaei
18658b7eaa
i18n: collapse two translation databases into a single web/translation/<lang>.json set
The Vue SPA had been reading from frontend/src/locales/*.json while the
Go binary still loaded web/translation/translate.*.toml — and a
sync-locales.mjs pre-build step kept the two in lockstep, with TOML as
the source of truth. Now that go-i18n v2.6.1 already flattens nested
JSON via recGetMessages/addChildMessages, both runtimes can share one
file per locale.

- Move the 13 nested-JSON locale files to web/translation/<lang>.json
  so they live alongside the Go //go:embed translation/* directive.
- Switch web/locale/locale.go from toml.Unmarshal to json.Unmarshal
  (and drop the pelletier/go-toml import — it's now indirect-only).
  Confirmed via a smoke test that pages.index.cpu, subscription.title,
  tgbot.commands.help, and menu.settings all resolve in en-US, fa-IR,
  ru-RU, and zh-CN.
- Repoint Vue's i18n loader at the new path (../../../web/translation/
  *.json glob) and drop the moved-here pathDelimiter comment that no
  longer applies.
- Delete the 13 legacy translate.*.toml files and the sync-locales.mjs
  script + its npm pre-script hooks (predev/prebuild/i18n:sync). The
  Telegram bot and subscription page still get their messages because
  they were reading the same MessageIDs the JSON files now produce.
- Update copilot-instructions.md so the next contributor knows where
  the canonical translation files live.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-08 18:05:56 +02:00
MHSanaei
bab41e31f8
i18n: shorten backupTitle to "Backup & Restore" across all 13 locales
The backup modal header was the second-longest title in the dashboard
on every locale ("Database Backup & Restore" / "Резервне копіювання
та відновлення бази даних" / etc). Drop the "Database / Veritabanı /
数据库" qualifier — the modal already lives under the "Database"
column, so the shorter form reads cleaner on narrow viewports.

Updated both the .toml source-of-truth files and the synced .json
locales (re-running scripts/sync-locales.mjs).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-08 17:53:45 +02:00
MHSanaei
36e75143fa
fix(frontend): Phase 9 — restore index dashboard, fix login/CSRF, port legacy styles
- Index dashboard regains the 8 cards that were lost in the SPA port
  (3X-UI panel info, Operation Hours, System Load, Usage, Overall Speed,
  Total Data, IP Addresses, Connection Stats), plus a Config button that
  shows the live xray config.json. Version display falls back through
  panelUpdateInfo → window.__X_UI_CUR_VER__ → '?' so dev mode isn't blank.
- Xray config no longer hangs on load: useXraySetting surfaces failures
  instead of leaving a perpetual spinner, and the Vite dev proxy stops
  hijacking POST requests to migrated routes (only GETs get bypassed).
- Inbound page no longer throws __asyncLoader/emitsOptions errors —
  inbound.js was missing imports (NumberFormatter, SizeFormatter,
  Wireguard) and InboundList kept emitting after unmount.
- Login round-trip works after logout: a public /csrf-token endpoint
  bootstraps the SPA before authentication, axios caches the token
  module-level, and the dev 401 handler navigates to /login.html
  instead of reloading the dashboard into a redirect loop.
- legacy.css mirrors the legacy panel's surface/text variables so dark
  and ultra-dark themes match main; every SPA entry imports it.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-08 17:21:03 +02:00
MHSanaei
8c8085f985
feat(server): Phase 8 — cut HTML routes over to web/dist/
Production cutover. Every user-facing HTML route now serves the
Vue-3-built bundle from web/dist/ instead of rendering the legacy
Go template; the long-hashed Vite assets are served at /assets/ from
the same embedded filesystem. The legacy templates in web/html/ and
the legacy static tree in web/assets/ are kept on disk for now in
case a quick revert is needed, but nothing the binary serves
references them.

What changed:
- web.go: a new //go:embed dist/* feeds the controller package via
  a SetDistFS hand-off before controller construction. The static
  /assets/ route is rebound: in dev to web/dist/assets/ on disk so
  Vite's incremental rebuilds show up live; in prod to the embedded
  dist via wrapDistFS (rooted one level deeper than wrapAssetsFS).
- controller/dist.go: serveDistPage helper used by every HTML
  handler. Reads dist/<name> from the embedded FS and applies two
  transforms before sending:
    1. injects <script>window.__X_UI_BASE_PATH__="..."</script>
       just before </head> so AppSidebar links resolve under the
       panel's basePath.
    2. when basePath != "/", rewrites Vite's absolute /assets/ URLs
       to <basePath>assets/ so installs running under a custom URL
       prefix load the bundle where the static handler lives.
  HTML responses go out with no-cache so panel upgrades reach
  users on the next refresh; hashed JS/CSS stays cacheable.
- controller/index.go: IndexController.index now serves
  dist/login.html for logged-out callers (the redirect for logged-in
  users is unchanged).
- controller/xui.go: XUIController.{index,inbounds,settings,xraySettings}
  each become a one-line wrapper around serveDistPage.

Smoke checklist for the maintainer:
- run `cd frontend && npm run build` to refresh web/dist/ before
  building the Go binary (the embed snapshot is taken at compile
  time);
- visit /panel/, /panel/inbounds, /panel/settings, /panel/xray and
  confirm each loads its Vue page;
- log out and log back in to verify the login flow;
- confirm the sidebar links navigate correctly under your install's
  basePath;
- POST flows (e.g. saving settings) still need the CSRF token —
  that endpoint (/panel/csrf-token, added earlier) is unchanged.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-08 14:39:55 +02:00
MHSanaei
3ecdae7c92
fix(csrf): expose token endpoint for SPA pages and fetch it from axios
The legacy panel pages got their CSRF token from a <meta name="csrf-token">
tag rendered by Go. SPA pages built by Vite don't have that, so every
unsafe (POST/PUT/DELETE) request from them was hitting CSRFMiddleware
with no token and getting 403 — visible as the settings page being
stuck on "Loading…" because POST /panel/setting/all failed.

- web/controller/xui.go: GET /panel/csrf-token returns the session
  token. Lives under the xui group so checkLogin still gates it; the
  CSRFMiddleware on the same group is a no-op for GET.
- frontend/src/api/axios-init.js: cache the token at module scope and
  lazy-fetch it when a non-safe request needs one. Seed from the meta
  tag first when present (legacy compat). On a 403 response, drop the
  cache and retry once — handles the case where a server restart
  rotated the token after the SPA loaded.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-08 13:20:26 +02:00
MHSanaei
12c10dbd98
feat(custom-geo): refresh index UI
Some checks are pending
Release 3X-UI / Analyze Go code (push) Waiting to run
Release 3X-UI / build (386) (push) Blocked by required conditions
Release 3X-UI / build (amd64) (push) Blocked by required conditions
Release 3X-UI / build (arm64) (push) Blocked by required conditions
Release 3X-UI / build (armv5) (push) Blocked by required conditions
Release 3X-UI / build (armv6) (push) Blocked by required conditions
Release 3X-UI / build (armv7) (push) Blocked by required conditions
Release 3X-UI / build (s390x) (push) Blocked by required conditions
Release 3X-UI / Build for Windows (push) Blocked by required conditions
Split the single ext-snippet column into Alias / URL / Routing /
Last-updated, with the alias surfaced next to a colored type tag,
the URL ellipsized with a tooltip + open-in-new-tab, and the
ext:file.dat:tag snippet click-to-copy via ClipboardManager.

Switch Last-updated to a relative time ("2 hours ago") with the
absolute timestamp on hover, add a friendly empty state, and show
a result toast when "Update All" finishes with partial failures.

customGeoEmpty translated for all 13 locales.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-08 10:09:33 +02:00
MHSanaei
2fd2cd0af1
fix(panel): silence update-check WARN spam when offline
The panel polls api.github.com on every page load. When the host has no
internet (DNS fails, GitHub blocked, etc.) jsonMsg's auto-WARN logging
floods the log with the same error every poll.

Bypass jsonMsg for getPanelUpdateInfo: log the error at Debug level and
return Success:false with the existing localized message so the frontend
popover behavior is unchanged.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-08 09:51:05 +02:00
MHSanaei
37fb48ffff
Axios v1.16.0 2026-05-08 09:41:56 +02:00
MHSanaei
d8198f543b
fix(warp): harden API client and frontend, bump to v0a4005
Backend:
- check HTTP status on every Cloudflare API call so error bodies don't
  get parsed as success
- replace unchecked type assertions with comma-ok form (no more panics
  when Cloudflare returns an error response)
- return real errors when license/id/token fields are missing instead
  of swallowing the failure
- guard SetWarpLicense against an empty errors array
- 15s timeout on the shared http.Client
- build all request bodies and persisted state with json.Marshal
- bump API path to v0a4005 and CF-Client-Version to a-6.30-3596 to
  match the current Cloudflare WARP client

Frontend (warp_modal.html):
- remove stray </a-form-item> closing tag
- declare config/peer with const and null-check before dereferencing
- guard addOutbound/resetOutbound against missing warpOutbound
- rename getResolved -> getReserved (the array it builds is "reserved")

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-08 09:29:42 +02:00
MHSanaei
f2bc4938b7
Reality: remove tesla.com because of blocking
Some checks are pending
Release 3X-UI / Analyze Go code (push) Waiting to run
Release 3X-UI / build (386) (push) Blocked by required conditions
Release 3X-UI / build (amd64) (push) Blocked by required conditions
Release 3X-UI / build (arm64) (push) Blocked by required conditions
Release 3X-UI / build (armv5) (push) Blocked by required conditions
Release 3X-UI / build (armv6) (push) Blocked by required conditions
Release 3X-UI / build (armv7) (push) Blocked by required conditions
Release 3X-UI / build (s390x) (push) Blocked by required conditions
Release 3X-UI / Build for Windows (push) Blocked by required conditions
#4175
2026-05-08 00:59:09 +02:00
MHSanaei
c394938f01
refactor(websocket): split controller into service + thin controller
Move per-connection lifecycle out of the controller and into a new
service.WebSocketService. The controller is now HTTP-layer only:
authenticate, validate origin, upgrade, and hand the connection off.

- web/service/websocket.go (new): owns the read/write pumps, hub
  registration, and connection lifetime. Pump constants are prefixed
  (wsWriteWait, wsPongWait, wsPingPeriod, wsClientReadLimit) to avoid
  collisions in the larger service package namespace.
- web/controller/websocket.go: trimmed to the upgrader, same-origin
  check, auth gate, and hand-off to the service.
- web/web.go: wires controller.NewWebSocketController(service.NewWebSocketService(hub)).

The hub package (web/websocket) stays as low-level fan-out
infrastructure. Behavior is unchanged — this is a structural cleanup
to align with the rest of the codebase's controller/service split.

Also includes a small range-int modernization in login_limiter_test.go
that gopls flagged.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-08 00:00:44 +02:00
MHSanaei
b84b58ef21
fix(websocket): guard stale events and disconnect race in JS client
Two subtle race conditions in the browser WebSocket client:

1. Stale-event clobber. When connect() is called while the old socket is
   in CLOSING state, the readyState guard falls through and a new socket
   is assigned to this.ws. The old socket's queued close event then
   nulls out this.ws, silently breaking send() until the next reconnect.
   Same risk for delayed open/error/message handlers.

2. Reconnect-after-disconnect. clearTimeout() does not cancel a callback
   that has already fired but whose macrotask has not yet run. If
   disconnect() lands in that window, the queued reconnect callback
   still calls #openSocket() and resurrects the connection.

Every event handler now bails out if this.ws no longer points at the
socket that fired the event, and the reconnect timer callback re-checks
shouldReconnect before opening a new socket.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-08 00:00:10 +02:00
Farhad H. P. Shirvan
10ebc6cbdc
Implement CSRF protection and security hardening across the application (#4179)
* Implement CSRF protection and security hardening across the application

- Added CSRF token handling in axios requests and HTML templates.
- Introduced CSRF middleware to validate tokens for unsafe HTTP methods.
- Implemented login limiter to prevent brute-force attacks.
- Enhanced security headers in middleware for improved response security.
- Updated login notification to include safe metadata without passwords.
- Added tests for CSRF middleware and login limiter functionality.

* fix
2026-05-07 23:36:11 +02:00
Harry NG
a1b2382877
chore: fix shadowrocketUrl client (#4183) 2026-05-07 20:59:10 +02:00
MHSanaei
59c55dfc92
fix(panel-update): poll for restart, fix dark-mode version label
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-07 20:55:22 +02:00
MHSanaei
28a3dddb60
refactor(fallbacks): share template, tighter UX, cleaner JSON
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-07 20:27:34 +02:00
MHSanaei
39bf31bd56
fix(tun): use single mtu number per Xray spec
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-07 19:50:47 +02:00
MHSanaei
42b2ebc00b
refactor(xhttp): split fields by direction, expand outbound coverage
Audit panel xhttp config against xray-core's runtime paths and split
fields per direction so each side carries only what it actually uses:

- Bidirectional (must match): host, path, mode, all xPadding*,
  session*/seq*, uplinkData*/Key, scMaxEachPostBytes
- Server-only (inbound): noSSEHeader, scMaxBufferedPosts,
  scStreamUpServerSecs, serverMaxHeaderBytes
- Client-only (outbound): uplinkHTTPMethod, uplinkChunkSize,
  noGRPCHeader, scMinPostsIntervalMs, xmux

The inbound previously held client-only fields and the outbound was
missing every must-match field beyond host/path/mode — meaning a
panel-built outbound couldn't connect to an inbound with a custom
xPaddingKey/sessionKey/etc.

Headers stay on the inbound for URL-share purposes only; xray's
listener ignores them at runtime, but they travel through the share
link's `extra` blob so the client picks them up.

Renames the URL helpers (applyXhttpPadding* -> applyXhttpExtra*) since
the blob now carries more than padding, and folds path/host/mode into
the helper so each link generator's xhttp branch is one line.

Adds two enforcement points for xray's "uplinkHTTPMethod=GET only in
packet-up" rule: the GET option is disabled when mode != packet-up,
and a watcher on the outbound modal auto-clears GET when the user
switches modes.

Hides the XMUX block behind an `enableXmux` switch on the outbound
form (mirrors the QUIC Params toggle) so the section doesn't clutter
the form by default; fromJson auto-flips it on for outbounds with
saved xmux config.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-07 19:26:40 +02:00
MHSanaei
3b64a62137
refactor(vless): drop selectedAuth, expose two explicit auth buttons
Some checks are pending
Release 3X-UI / Analyze Go code (push) Waiting to run
Release 3X-UI / build (386) (push) Blocked by required conditions
Release 3X-UI / build (amd64) (push) Blocked by required conditions
Release 3X-UI / build (arm64) (push) Blocked by required conditions
Release 3X-UI / build (armv5) (push) Blocked by required conditions
Release 3X-UI / build (armv6) (push) Blocked by required conditions
Release 3X-UI / build (armv7) (push) Blocked by required conditions
Release 3X-UI / build (s390x) (push) Blocked by required conditions
Release 3X-UI / Build for Windows (push) Blocked by required conditions
selectedAuth was UI-only metadata (Xray never reads it) and entirely
redundant with the encryption string itself — the dropdown only
controlled which block from `xray vlessenc` to apply. Replace it with
two explicit buttons ("X25519" and "ML-KEM-768") so the user picks
the auth mode in one click instead of dropdown + Get-New-Keys.

- VLESSSettings drops the field from constructor, fromJson, and toJson;
  legacy `selectedAuth` values still in DB will be silently shed on the
  next save.
- getNewVlessEnc(authLabel) now takes the label as a parameter; clear
  resets only decryption/encryption.
- Fallbacks visibility now keys on encryption === "none" (the same
  thing the dropdown was effectively gating on).
- Info modal drops the redundant Authentication tag and colours the
  encryption tag red when it's "none", green otherwise.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-07 15:08:06 +02:00
MHSanaei
79a7e7a5b5
fix(vless): scope testseed to xtls-rprx-vision flow
testseed is only meaningful for the exact xtls-rprx-vision flow, but the
panel was emitting it for any non-empty flow (including the UDP variant)
and keeping it on the inbound after the flow was cleared via the client
modal. Tighten the gate end-to-end:

- VLESSSettings.toJson (inbound + outbound) now only emits testseed when
  the flow is exactly xtls-rprx-vision and the array is 4 positive ints;
  default state is empty so unmodified inbounds omit the field entirely.
- canEnableVisionSeed drops the udp443 variant per spec.
- Form adds a tooltip + theme-aware help text and an inline error when
  the user partially fills the four inputs; submit is blocked in that
  state. Reset clears to empty (= use server defaults).
- UpdateInboundClient strips a now-orphaned testseed when the spliced
  client no longer leaves any XRV flow in the inbound.
- MigrationRequirements cleans up legacy rows where testseed lingered
  after flow changes or was saved for non-XRV flows by older versions.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-07 14:44:33 +02:00
MHSanaei
3349dcbc13
fix(fail2ban): fix banning regression and Docker zero-jail issue
- DockerEntrypoint.sh: create jail.d/filter.d/action.d config files
  before starting fail2ban so Docker containers no longer start with
  0 active jails (fixes #4134)

- x-ui.sh create_iplimit_jails: lower maxretry from 2 to 1 so
  fail2ban bans on the first log entry; with maxretry=2 and the
  partitionLiveIps logic the second occurrence could arrive after the
  32 s findtime window, silently preventing any ban (fixes #4163)

- x-ui.sh: fix datepattern (%%Y -> %Y) so fail2ban parses the Go
  log timestamp correctly instead of looking for a literal %%Y string

- x-ui.sh / DockerEntrypoint.sh: fix date command in actionban /
  actionunban echo (%%Y -> %Y) so the ban log records actual dates

- check_client_ip_job.go: replace log.SetOutput / log.SetFlags on
  the global standard-library logger with a local log.New instance,
  eliminating the dangling closed-file-handle between calls and
  stopping unrelated stdlib log output from polluting 3xipl.log

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-07 13:53:34 +02:00
MHSanaei
ad30298700
Exclude virtual interfaces from network stats
Some checks are pending
Release 3X-UI / Analyze Go code (push) Waiting to run
Release 3X-UI / build (386) (push) Blocked by required conditions
Release 3X-UI / build (amd64) (push) Blocked by required conditions
Release 3X-UI / build (arm64) (push) Blocked by required conditions
Release 3X-UI / build (armv5) (push) Blocked by required conditions
Release 3X-UI / build (armv6) (push) Blocked by required conditions
Release 3X-UI / build (armv7) (push) Blocked by required conditions
Release 3X-UI / build (s390x) (push) Blocked by required conditions
Release 3X-UI / Build for Windows (push) Blocked by required conditions
Switch net.IOCounters to per-interface mode and aggregate traffic while excluding loopback and common virtual/tunnel interfaces. Adds isVirtualInterface helper to filter interfaces by exact names and prefixes (docker, veth, tun, wg, tailscale, etc.), sums BytesSent/BytesRecv across valid interfaces, and assigns the totals to status.NetTraffic. Removes the previous warning branch when no counters were found and preserves NetIO rate calculations using lastStatus.
2026-05-06 17:28:41 +02:00
MHSanaei
9be11e109e
fix design 2026-05-06 17:12:08 +02:00
MHSanaei
7117d19fd1
fix: filter view in mobile
Some checks are pending
Release 3X-UI / Analyze Go code (push) Waiting to run
Release 3X-UI / build (386) (push) Blocked by required conditions
Release 3X-UI / build (amd64) (push) Blocked by required conditions
Release 3X-UI / build (arm64) (push) Blocked by required conditions
Release 3X-UI / build (armv5) (push) Blocked by required conditions
Release 3X-UI / build (armv6) (push) Blocked by required conditions
Release 3X-UI / build (armv7) (push) Blocked by required conditions
Release 3X-UI / build (s390x) (push) Blocked by required conditions
Release 3X-UI / Build for Windows (push) Blocked by required conditions
2026-05-06 14:45:46 +02:00
MHSanaei
c88627a839
outbound: mobile style 2026-05-06 13:27:40 +02:00
MHSanaei
c718e7ca5b
fix(inbounds): remove stale reverse outbound tags after client deletion 2026-05-06 11:43:21 +02:00
pwnnex
6a483fa987
inbound: check transport in port conflict, allow tcp and udp on same port (#4169)
the panel rejected configurations like vless reality on tcp/443 and
hysteria2 on udp/443 even though those are independent sockets in
linux. the old checkPortExist looked only at port + listen.

inboundTransports now classifies each inbound by L4 transport:
hysteria/hysteria2/wireguard are udp; streamSettings.network=kcp is
udp; shadowsocks reads settings.network ("tcp"/"udp"/"tcp,udp");
mixed (socks/http) adds udp when settings.udp is true; everything
else is tcp. checkPortConflict pulls every row on the same port and
only flags a conflict when transport masks overlap. the listen-
overlap rule (specific addr vs any-addr on the same port) is kept.

inbounds.tag has a unique DB constraint and the controller derives
tags from port ("inbound-443"). without disambiguation a second
inbound on the same port would still hit a unique-constraint error.
generateInboundTag keeps the historical "inbound-<port>" shape when
the base tag is free, so existing routing rules survive the upgrade
unchanged, and appends "-tcp"/"-udp" only when the base is already
taken.

closes #4103.
2026-05-06 11:41:21 +02:00
MHSanaei
47163c1418
Skip 26.5.3 and bump Xray version cutoff
In GetXrayVersions, explicitly ignore the tag "26.5.3" and raise the minimum accepted Xray release from 26.3.10 to 26.4.25. This excludes a specific problematic release and updates the version parsing logic to only include >26 or 26.4.25+ releases.
2026-05-06 10:13:55 +02:00
MHSanaei
09f4f09b84
fix design 2026-05-06 10:06:56 +02:00
MHSanaei
3313086071
fix: Swap left/right classes for client table cells
Swap tr-table-rt and tr-table-lt on the size and totalGB elements in aClientTable.html so the size display and the total GB display are positioned correctly (size on the left, total on the right). This is a UI alignment fix with no functional logic changes.
2026-05-06 09:12:25 +02:00
MHSanaei
a8dff126c7
outbound: reverse Sniffing 2026-05-06 08:17:27 +02:00
MHSanaei
50603fd430
fix: get client reverse tag in the outbound 2026-05-06 00:50:40 +02:00
MHSanaei
b2d32f588f
new: vless reverse
legacy reverse removed
2026-05-05 21:00:03 +02:00
lolka1333
8177f6dc66
ws/inbounds: realtime fixes + perf for 10k+ client inbounds (#4123)
* ws/inbounds: realtime fixes + perf for 10k+ client inbounds

- hub: dedup, throttle, panic-restart, deadlock fix, race tests
- client: backoff cap + slow-retry instead of giving up
- broadcast: delta-only payload, count-based invalidate fallback
- filter: fix empty online list (Inbound has no .id, use dbInbound.toInbound)
- perf: O(N²)→O(N) traffic merge, bulk delete, /setEnable endpoint
- traffic: monotonic all_time + UI clamp + propagate in delta handler
- session: persist on update/logout (fixes logout-after-password-change)
- ui: protocol tags flex, traffic bar normalize

* Remove hub_test.go file

* fix: ws hub, inbound service, and frontend correctness

- propagate DelInbound error on disable path in SetInboundEnable
- skip empty emails in updateClientTraffics to avoid constraint violations
- use consistent IN ? clause, drop redundant ErrRecordNotFound guards
- Hub.Unregister: direct removeClient fallback when channel is full
- applyClientStatsDelta: O(1) email lookup via per-inbound Map cache
- WS payload size check: Blob.size instead of .length for real byte count

* fix: chunk large IN ? queries and fix IPv6 same-origin check

* fix: chunk large IN ? queries and fix IPv6 same-origin check

* fix: unify clientStats cache, throttle clarity, hub constants

* fix(ui): align traffic/expiry cell columns across all rows

* style(ui): redesign outbounds table for visual consistency

* style(ui): redesign routing table for visual consistency

* fix:

* fix:

* fix:

* fix:

* fix:

* fix: font

* refactor: simplify outbound tone functions for consistency and maintainability

---------

Co-authored-by: lolka1333 <test123@gmail.com>
2026-05-05 17:27:49 +02:00
MHSanaei
77d94b25d0
Add 'active' filter option to inbounds
Some checks are pending
Release 3X-UI / Analyze Go code (push) Waiting to run
Release 3X-UI / build (386) (push) Blocked by required conditions
Release 3X-UI / build (amd64) (push) Blocked by required conditions
Release 3X-UI / build (arm64) (push) Blocked by required conditions
Release 3X-UI / build (armv5) (push) Blocked by required conditions
Release 3X-UI / build (armv6) (push) Blocked by required conditions
Release 3X-UI / build (armv7) (push) Blocked by required conditions
Release 3X-UI / build (s390x) (push) Blocked by required conditions
Release 3X-UI / Build for Windows (push) Blocked by required conditions
2026-05-04 23:33:48 +02:00
MHSanaei
32b7ada549
subpage: enabled state
Track and surface a subscription's enabled state from backend to frontend so the UI can show inactive subscriptions and use it in active-state logic.

Changes:
- sub/subService.go: track hasEnabledClient, set traffic.Enable, add Enabled to PageData and populate it in BuildPageData.
- sub/subController.go: include enabled in the page context.
- web/html/settings/panel/subscription/subpage.html: emit data-enabled attribute and render an "inactive" tag when disabled.
- web/assets/js/subscription.js: read data-enabled and include it in isActive() checks.

This ensures subscriptions with no enabled clients are marked inactive in the UI and excluded from being considered active.
2026-05-04 23:27:57 +02:00
MHSanaei
6099a07ff0
feat: add configurable auto-restart on client auto-disable
Add a configurable option to restart Xray when clients are auto-disabled and persist disable actions.

Changes include:
- New setting restartXrayOnClientDisable (default true), getters/setters in SettingService, UI toggle in general settings, and translations for multiple locales.
- AddTraffic signature updated to return a third bool (clientsDisabled). disableInvalidClients now calls Xray API to remove users, marks client_traffics.enable=false, updates inbound.Settings JSON so clients appear disabled in stored settings, and returns appropriate counts/errors.
- XrayTrafficJob now checks the clientsDisabled flag and restarts Xray when the setting is enabled (with fallback to mark Xray as needing restart on failure).
- XrayService.GetXrayConfig call adjusted to ignore AddTraffic returns.
- Subscription generation (subService/subJson/subClash) no longer filters clients by their enable flag when matching subId.
- Minor fixes: check_client_ip_job now checks scanner.Err and improved API error handling/logging.

These changes ensure auto-disabled clients are propagated to Xray and the stored inbound settings, and provide an option to restart Xray automatically after auto-disable events.
2026-05-04 23:19:25 +02:00
MHSanaei
e9806832ec
reality: remove apple, icloud 2026-05-04 19:49:28 +02:00
MHSanaei
15ebf3df10
fix: client count for Hysteria
#4143
2026-05-04 17:49:53 +02:00
MHSanaei
d44b70682c
Update QUIC params defaults and UI validations
#4142
Adjust QUIC parameter defaults and tighten form validation across inbound/outbound components.

- Set default brutalUp/brutalDown to 65537 and only include them in JSON when congestion is 'brutal' or 'force-brutal'.
- Change keepAlivePeriod defaults (inbound QUIC -> 5s, Hysteria stream -> 2s) and enforce minimums in the UI.
- Expose and serialize additional QUIC fields in outbound QuicParams: init/max stream windows, init/max connection windows, maxIdleTimeout, disablePathMTUDiscovery, maxIncomingStreams.
- Add UI min/placeholder constraints: stream/connection receive windows min=16384 and updated placeholders to show defaults, brutal fields min=65537, maxIncomingStreams min=8 (placeholders updated), keepAlive min adjusted.
- Add Wireguard and Hysteria entries to Protocols.

Touched files: web/assets/js/model/inbound.js, web/assets/js/model/outbound.js, web/html/form/outbound.html, web/html/form/stream/stream_finalmask.html.
2026-05-04 17:42:55 +02:00