Commit Graph

51 Commits

Author SHA1 Message Date
Abdalrahman b47f794ed0 fix: reality random target/sni buttons not working (#4337) (#4340) 2026-05-13 14:42:20 +02:00
MHSanaei 4e1b597914 feat(ui): use the host as the browser tab title prefix 2026-05-13 14:23:57 +02:00
MHSanaei bbefe91011 fix(auth): invalidate sessions when 2FA is enabled, fix dev 401 loop
Add UserService.BumpLoginEpoch and call it from updateSetting when
TwoFactorEnable flips false → true. Existing cookies (issued under
the looser no-2FA policy) get a 401 on their next request and are
forced through the login flow. Disabling 2FA is a relaxation and
does not bump the epoch — sessions stay valid.

Also fix the dev-mode 401 redirect: targeting `${basePath}login.html`
breaks when basePath isn't "/" (Vite has no file at e.g.
"/test/login.html"; the SPA fallback loops the 401). Navigate to
basePath instead — Vite's bypassMigratedRoute and Go's index
handler both serve login.html for that path.

Strip stale doc-comment from netsafe and IndexController.logout
in line with the project's no-inline-comments convention.
2026-05-13 14:08:16 +02:00
Farhad H. P. Shirvan 428f1333ac Security hardening: sessions, SSRF, CSP nonce, CSRF logout, trusted proxies (#4275)
* refactor(session): store user ID in session instead of full struct

Replaces storing the full User object in the session cookie with just
the user ID. GetLoginUser now re-fetches the user from the database on
every request so credential/permission changes take effect immediately
without requiring a re-login. Includes a backward-compatible migration
path for existing sessions that still carry the old struct payload.

* feat(auth): block panel with default admin/admin credentials and guide credential change

checkLogin middleware now detects default admin/admin credentials and
redirects every panel route to /panel/settings until they are changed.
The settings page auto-opens the Authentication tab, shows a
non-dismissible error banner, and lists 'Default credentials' first in
the security checklist. Login response includes mustChangeCredentials
so the login page can redirect directly. Logout is now POST-only.
Password must be at least 10 characters and cannot be admin/admin.

* feat(settings): redact secrets in AllSettingView and add TrustedProxyCIDRs

Introduces AllSettingView which strips tgBotToken, twoFactorToken,
ldapPassword, apiToken and warp/nord secrets before sending them to
the browser, replacing them with boolean hasFoo presence flags. A new
/panel/setting/secret endpoint allows updating individual secrets by
key. Secrets that arrive blank on a save are preserved from the DB
rather than overwritten. Adds TrustedProxyCIDRs as a configurable
setting (defaults to localhost CIDRs). URL fields are validated before
save.

* fix(security): SSRF prevention, trusted-proxy header gating, CSP nonce, HTTP timeouts

Adds SanitizeHTTPURL / SanitizePublicHTTPURL to reject private-range
and loopback targets before any outbound HTTP request (node probe,
xray download, outbound test, external traffic inform, tgbot API
server, panel updater). Forwarded headers (X-Real-IP, X-Forwarded-For,
X-Forwarded-Host) are now only trusted when the direct connection
arrives from a CIDR in TrustedProxyCIDRs. CSP policy is tightened with
a per-request nonce. HTTP server gains read/write/idle timeouts. Panel
updater downloads the script to a temp file instead of piping curl into
shell. Xray archive download adds a size cap and response-code check.
backuptotgbot is changed from GET to POST.

* feat(nodes): add allow-private-address toggle per node

Adds AllowPrivateAddress to the Node model (DB default false). When
enabled it bypasses the SSRF private-range check for that node's probe
URL, allowing nodes hosted on RFC-1918 or loopback addresses (e.g.
a private VPN or LAN setup).

* chore: frontend UX improvements, CI pipeline, and dev tooling

- AppSidebar: logout via POST /logout instead of navigating to GET
- InboundList: persist filter state (search, protocol, node) to
  localStorage across page reloads; add protocol and node filter dropdowns
- IndexPage: add health status strip (Xray, CPU, Memory, Update) with
  quick-action buttons
- dependabot: weekly go mod and npm update schedule
- ci.yml: add GitHub Actions workflow for build and vet
- .nvmrc: pin Node 22 for local development
- frontend: bump package.json and package-lock.json
- SubPage, DnsPresetsModal, api-docs: minor fixes

* fix(ci): stub web/dist before go list to satisfy go:embed at compile time

* chore(ui): remove health-strip bar from dashboard top

* Revert "feat(auth): block panel with default admin/admin credentials and guide credential change"

This reverts commit 56ce6073ce09f08147f989858e0e88b3a4359546.

* fix(auth): make logout POST+CSRF and propagate session loss to other tabs

- Switch /logout from GET to POST with CSRFMiddleware so it matches the
  SPA's existing HttpUtil.post('/logout') call (previously 404'd silently)
  and blocks GET-based logout via image tags or link prefetchers. Handler
  now returns JSON; the SPA already navigates client-side.
- Return 401 (instead of 404) from /panel/api/* when the caller is a
  browser XHR (X-Requested-With: XMLHttpRequest) so the axios interceptor
  redirects to the login page on logout-in-another-tab, cookie expiry,
  and server restart. Anonymous callers still get 404 to keep endpoints
  hidden from casual scanners.
- One-shot the 401 redirect in axios-init.js and hang the rejected
  promise so queued polls don't stack reloads or surface error toasts
  while the browser is navigating away.
- Add the CSP nonce to the runtime-injected <script> in dist.go so the
  panel loads under the existing script-src 'nonce-...' policy.
- Update api-docs endpoints.js: GET /logout doc entry was missing.

* fix(settings): POST /logout after credential change

* fix(auth): invalidate other sessions when credentials change

When the admin changes username/password from one machine, sessions
on every other machine kept working until they manually logged out
because session storage is a signed client-side cookie — there is
no server-side session list to revoke.

Add a per-user LoginEpoch counter stamped into the session at login
and re-verified on every authenticated request. UpdateUser and
UpdateFirstUser bump the epoch (UpdateUser via gorm.Expr so a single
update statement is atomic), so any cookie issued before the change
no longer matches the user's current epoch and GetLoginUser returns
nil — the SPA's 401 interceptor then redirects to the login page.

Backward compatible: the column defaults to 0 and missing cookie
values are treated as 0, so sessions issued before this change
remain valid until the first credential update.

---------

Co-authored-by: Sanaei <ho3ein.sanaei@gmail.com>
2026-05-13 12:52:52 +02:00
MHSanaei 406cb6dbc0 fix(api-docs): resolve no-useless-escape lint errors
- endpoints.js: replace `\"` with `\\"` in xray response example so the
  rendered docs actually show escaped JSON-in-JSON (the original
  single-quoted `\"` collapsed to a bare `"` and produced malformed output).
- CodeBlock.vue: drop the unnecessary `\[` inside the regex character
  class `[{}\[\]]`; `[` does not need escaping inside `[...]`.
2026-05-13 11:31:34 +02:00
Abdalrahman 4884a2972a fix(graphs): increase y-axis paddingLeft from 32 to 56 to prevent clipped labels (#4309) 2026-05-13 01:47:54 +02:00
Abdalrahman 6e12329d9d feat(api-docs): enhance in-panel API documentation (#4312)
* feat(api-docs): enhance API documentation with missing endpoints, search, collapse, and route sync test

- Add 29 undocumented routes across 4 new sections (Settings, Xray Settings,
  Subscription Server, WebSocket) plus 4 missing Server API endpoints
- Fix inaccuracies: history metric keys, node metric keys, VLESS enc description
- Add response schemas to 15+ key endpoints
- Add search bar and expand/collapse all controls to the docs page
- Add collapsible endpoint sections with endpoint count
- Add Go test (TestAPIRoutesDocumented) to verify all Go routes are documented

* feat(api-docs): add JSON syntax highlighting and top-right copy button to code blocks

* fix(api-docs): use distinct colors for JSON syntax highlighting (green strings, amber numbers)

* feat(api-docs): add request body examples, error responses, WebSocket message types, and subscription response headers

* fix(api-docs): use ClipboardManager.copyText instead of copy to fix API token copy button
2026-05-13 01:47:09 +02:00
Abdalrahman 9f7e8178d4 fix: delete button missing after searching for a user (#4315)
When searching for a user, the projected DBInbound only contains the
matching clients, so isRemovable evaluated to alse (since a single
match made clients.value.length === 1), hiding the Delete button.

Pass the original total client count from the parent's clientCount
prop and use it in the isRemovable check instead of the projected
clients array length.
2026-05-13 01:27:10 +02:00
Abdalrahman 60e6b12f4c fix(hysteria2): restore missing masquerade config in inbound form (#4316)
* fix(hysteria2): restore missing masquerade config in inbound form

Fixes #4303

The Hysteria2 Masquerade option was missing from the Stream settings
tab after the v3.0.0 form rewrite. Added the UI form and ensured the
masquerade block is passed through in subscription JSON generation.
2026-05-13 01:25:00 +02:00
Abdalrahman 48e90bba51 fix: show UDP tag for Hysteria and fix client count spacing (#4318) 2026-05-13 01:12:25 +02:00
Abdalrahman 6de9b24229 fix: preserve space between date and time in log modal (#4326)
Vue 3's whitespace: condense strips bare whitespace text nodes and
trailing whitespace inside elements, causing the &lt;template&gt; trick
to fail. Use mustache interpolations (which compile to _createTextVNode)
for all spacing between fields so they survive compilation.
2026-05-13 01:02:48 +02:00
MHSanaei 07bc74a521 feat(nodes): blur address column with eye-toggle, mirroring IndexPage IP card 2026-05-12 12:38:38 +02:00
MHSanaei f570b991e7 fix(api-docs): copy API token button 2026-05-12 12:34:22 +02:00
MHSanaei 80031e67cc feat(inbounds): restore copy-clients-between-inbounds modal
The menu item, backend endpoint (POST /panel/api/inbounds/:id/copyClients),
and i18n keys were already in place after the Vue3 migration, but the modal
itself was never ported — clicking the menu just toasted "coming soon".

Adds CopyClientsModal.vue: source inbound dropdown (multi-user inbounds
except the target), per-client checkbox selection via a-table row-selection,
optional Flow override when the target supports TLS flow, and result toasts
for added/skipped/errors.
2026-05-12 12:30:07 +02:00
Farhad H. P. Shirvan fdaa65ad7e Feat: clarify VLESS encryption auth selection (#4271)
* feat(traffic_writer): enhance traffic writer with concurrency safety and state management

* Revert "feat(traffic_writer): enhance traffic writer with concurrency safety and state management"

This reverts commit e6760ae39629a592dec293197768f27ff0f5a578.

* feat(vless): clarify VLESS encryption auth selection and enhance parsing logic
2026-05-12 11:39:28 +02:00
Abdalrahman 89a8f549f2 feat: sortable inbounds table columns (#4300) 2026-05-12 11:29:32 +02:00
MHSanaei 355bb4c9c0 feat(panel): xray metrics dashboard with observatory probe history
Polls xray's /debug/vars on the 2s status tick, stores memstats and per-outbound observatory delay in the metric history ring buffer, and exposes them through a new XrayMetricsModal opened from the Charts card. Restructures the dashboard to consolidate uptime, usage, version, and Telegram link into stat-style or action-style cards consistent with the existing AntD aesthetic.
2026-05-12 02:17:45 +02:00
MHSanaei 6a90f98412 feat(inbounds): add sub/client link endpoints; hide panel version on login
- New GET /panel/api/inbounds/getSubLinks/:subId and /getClientLinks/:id/:email
  return the same protocol URLs the panel UI's Copy button emits, honouring
  X-Forwarded-Host / X-Forwarded-Proto. Documented in the API docs page.
- Refactor: sub package no longer imports web. The embedded dist FS is
  injected via sub.SetDistFS, and the link generator is registered with the
  service layer via service.RegisterSubLinkProvider, avoiding the circular
  import the new endpoints would otherwise introduce.
- Security: stop emitting window.X_UI_CUR_VER on login.html and drop the
  visible version chip from the login page, so the panel version is no
  longer pre-auth info disclosure. Authenticated pages still receive it.
- Bump config/version.
2026-05-11 15:03:47 +02:00
MHSanaei e642f7324e feat(panel): in-panel API documentation page
New /panel/api-docs route with a one-page reference covering every
/panel/api/* endpoint (Auth, Inbounds, Server, Nodes, Custom Geo,
Backup) plus a Bearer-token primer that reads the current token and
exposes Show/Copy/Regenerate inline. Sidebar gets an API Docs entry
right after Xray; the menu label is shared via menu.apiDocs across all
13 locales.
2026-05-11 13:57:42 +02:00
MHSanaei 88061bac10 fix(theme): default to dark, polish theme cycle visibility and hover
New installs land on plain dark instead of ultra-dark. The cycle button
icon now has an explicit colour so it stays visible inside the mobile
drawer (the previous color:inherit didn't cascade through the teleported
node), and hover/focus matches the menu's blue across sidebar, login,
and sub pages.
2026-05-11 12:51:37 +02:00
MHSanaei b5479f3f30 feat(sidebar): pin Logout above trigger, inline 3-state theme cycle
The desktop sider stretched to match the page height, so below lg
(992px) where dashboard cards stack into one column the collapse
trigger plus Logout slid off-screen. Pin the sider with
`position: sticky; height: 100vh; align-self: flex-start` so the chrome
stays viewport-tall. Split the menu into `.sider-nav` (flex: 1,
scrollable) and `.sider-utility` so Logout sits directly above the
48px trigger reserved by padding-bottom.

Replace the `<ThemeSwitch>` a-sub-menu with a single inline icon
button next to the '3X-UI' brand (sun / moon / moon+star SVG). One
click cycles Light -> Dark -> Ultra Dark -> Light. ThemeSwitch.vue
removed since it is now inlined.

Override AD-Vue dark Menu selected + hover/active state on the
sider-nav, sider-utility, and drawer menus to use the same light-blue
tint AD-Vue's light theme uses (rgba(64,150,255,0.2) / #4096ff). The
default dark variant was too subtle against #252526, so the current
page and Logout-on-hover barely distinguished themselves.
2026-05-11 12:05:45 +02:00
MHSanaei d8aedcdde4 fix(inbounds): bulk-delete keeps last client to satisfy backend constraint
DelClient rejects the removal that would leave an inbound with zero
clients (the constraint exists because Xray protocols need at least
one client to keep the inbound functional). The bulk-delete flow
fired one DelClient call per picked client in a loop, so picking
every client meant the final iteration always errored out with
"no client remained in Inbound" and surfaced as a red toast even
though N-1 deletions had already gone through.

Now confirmBulkDelete detects the "all selected" case up front,
drops the last client from the request, and surfaces the partial
operation in the confirm dialog ("N-1 / N — last selected will
remain. Delete the inbound to remove all."). The pre-existing
single-row delete path and partial-selection bulk delete paths are
untouched. If the only client in the inbound is selected, a
Modal.warning explains the constraint instead of asking for confirm.
2026-05-11 10:22:52 +02:00
MHSanaei 5f3e9ed0ea feat(xray/nord): searchable server list + colored load tag, surface API errors
Frontend (NordModal.vue):
- Server selector gets show-search with the option label set to
  `${cityName} ${name} ${hostname}` so admins can find a specific
  server inside a 100+ entry country list by typing.
- Each option renders the load as a colored a-tag (green <30%,
  orange 30-70%, red >70%) instead of plain text — quicker visual
  scan when sorting through servers in the dropdown.

Backend (nord.go):
- GetCountries / GetServers now check resp.StatusCode and return
  "NordVPN API error: <status>" on non-200, matching the pattern
  GetCredentials already used. Previously a 4xx/5xx body was
  returned as a "success" string and the frontend silently failed
  to parse it, surfacing only as an empty "No servers found".
- GetCredentials drops its own ad-hoc 10s http.Client and reuses
  the shared nordHTTPClient (15s) — one client, one timeout.
2026-05-11 10:06:01 +02:00
MHSanaei 3e8a0eb93e fix(inbounds): paginate expanded client list, restore ID column, hide empty Remark
- ClientRowTable now applies the General-Settings pageSize to its
  expanded client list. The 3.0 rewrite dropped pagination, so users
  with thousands of clients per inbound hit a 30-60s browser hang on
  expand (#4233).
- ID column was marked responsive: ['xs'] so it was hidden on desktop;
  removed the restriction so it shows as the first column everywhere.
- Remark column is now omitted entirely when no inbound has a non-empty
  remark, matching the existing Node-column pattern.
2026-05-11 09:05:47 +02:00
Harry NG 9f06bffbea chore: fix remarks shadowrocket subscription (#4247) 2026-05-11 08:24:22 +02:00
Amirmohammad Sadat Shokouhi e20d73ba7e add loopback and dns servers tag to inbound lists in RuleFormModal (#4244)
* add loopback and dns servers tag to inbound lists in RuleFormModal

* fix: remove clientIp from dns section when its empty
2026-05-11 08:23:30 +02:00
MHSanaei 8834e5fbbe feat(xray/outbounds): TCP probe mode + Test All + timing breakdown
- service.TestOutbound now dispatches on `mode`:
  - "tcp": parallel net.DialTimeout to every server/peer endpoint
    (vmess/vless/trojan/ss/socks/http/wireguard). No xray spin-up,
    no semaphore — safe to run concurrently across outbounds.
  - "http" (default): existing temp-xray + SOCKS path, now with an
    httptrace.ClientTrace breakdown (DNS / Connect / TLS / TTFB)
    alongside the total delay and status code.
- testSemaphore renamed to httpTestSemaphore — only HTTP probes
  serialise, TCP runs free.
- TestOutboundResult carries the per-mode extras: timing fields for
  HTTP, per-endpoint dial list for TCP, plus a `mode` echo.
- Controller reads `mode` from the form and passes it through.
- useXraySetting: testOutbound accepts mode (default "tcp"); new
  testAllOutbounds(mode) runs a worker pool (concurrency 8 for TCP,
  1 for HTTP) and skips blackhole / loopback / blocked outbounds —
  also skips freedom / dns under TCP since they have no endpoint.
- OutboundsTab: TCP/HTTP radio toggle and a Test All button land in
  the toolbar; the per-row  now uses the selected mode. Results
  surface in a popover with the full timing breakdown plus the
  endpoint list for TCP probes. Latency header replaces the duplicate
  "check" column title.

Practical effect: testing ten outbounds in TCP mode drops from ~50–100s
(serial HTTP) to ~1–2s (parallel dial × 8). HTTP mode stays as the
authoritative probe and now shows where the latency actually lives.
2026-05-11 04:17:23 +02:00
MHSanaei 6d732d8d32 feat(inbounds): bulk-select clients + UX polish
- ClientBulkModal: add `comment` and VLESS `reverseTag` fields so the
  bulk-add modal can set them on every generated client (matching the
  single-client form)
- ClientRowTable: add multi-select checkboxes (desktop + mobile) with a
  tri-state select-all and a sticky bulk-action bar; emits a new
  `delete-clients` event so the parent can wipe the picked clients in
  one go. Hidden entirely when the inbound has only one client (the
  last one must stay)
- ClientRowTable: new "Remained" column shows live remaining quota
  per client (∞ for unlimited, red when depleted)
- InboundInfoModal: Remained cell now shows the ∞ tag when the client
  has no totalGB limit, matching how Total Usage already renders it
- InboundsPage: add Online tag (+ per-bucket popovers listing client
  emails) to the summary card so it mirrors the per-inbound row, and
  wire an `onDeleteClients` handler that loops the existing single-
  delete endpoint then refreshes once
- InboundList: forward the `delete-clients` event; hide empty remarks
  on both the desktop table (custom #bodyCell) and the mobile card
- useInbounds: aggregate an `online` email list across all inbounds
  so the summary popover has data to render
2026-05-11 03:50:28 +02:00
MHSanaei 04828246fc feat(frontend): swap QRious for ant-design-vue's a-qrcode
- Migrate SubPage, QrPanel and TwoFactorModal from a QRious canvas to
  <a-qrcode type="svg">, which renders the QR matrix as crispEdges
  SVG rectangles — pixel-perfect at any display size or DPR, no more
  white scan-line artifacts from non-integer canvas scaling
- Drop the now-unused qrious dependency and its manualChunks entry
- Default the panel to ultra-dark on first load (existing user
  preferences in localStorage are preserved)
- Let the sub controller read subpage.html from web/dist/ first and
  fall back to the embedded copy, so Vite rebuilds in dev no longer
  require a Go recompile to refresh the asset hashes
2026-05-11 02:07:47 +02:00
MHSanaei c1efc48694 feat(frontend): refresh dark theme + redesign login page
- Swap navy dark palette for VS Code Dark+ neutrals (#1e1e1e/#252526/
  #2d2d30) across theme tokens, page backgrounds and DateTimePicker
- Add brand header to the mobile drawer and desktop sider, and recolor
  the drawer body so it reads as one panel with the menu
- Redesign login page with a centered card, cycling Hello/Welcome
  headline and per-theme animated gradient-blob backgrounds
2026-05-11 01:10:05 +02:00
MHSanaei f1760b0a28 feat(xray/balancer): restore observatory editor + auto-sync selectors
The Vue3 migration dropped the Observatory / Burst Observatory section
that used to sit under the balancer table. Without it, leastPing /
leastLoad strategies had nowhere to populate Xray's required
subjectSelector, so balancers that depended on probe data silently
ran with an empty observer config.

- Auto-seed and sync `observatory` for leastPing balancers and
  `burstObservatory` for leastLoad balancers (subjectSelector
  recomputed from every matching balancer's selector list). Drops
  the observatory when no matching strategy remains.
- Defaults (probeURL, interval, connectivity, sampling) match the
  values the legacy panel shipped, themselves taken from the Xray
  docs at xtls.github.io/config/{observatory,burstobservatory}.html.
- Surface both observatories under the table as a radio-switched
  JSON textarea so admins can tune probe settings inline without
  dropping into the full xray template tab.
2026-05-11 00:11:09 +02:00
MHSanaei 745e394c74 refactor(panel): rename injected globals + collapse QR modal entries
Rename the SPA globals injected by Go to drop the ad-hoc dunder shape
and free up the bare `webBasePath` name (still the DB setting key)
from colliding with the JS global it used to share:
  window.__X_UI_BASE_PATH__ -> window.X_UI_BASE_PATH
  window.__X_UI_CUR_VER__   -> window.X_UI_CUR_VER

Also rework the QR-Code modal to fold every QR (subscription + JSON
sub URL, share links, WireGuard config/peer links) into a single
a-collapse with one panel per QR. Subscription panels are listed
first and open by default; everything else stays collapsed so a
multi-link inbound no longer scrolls forever.
2026-05-10 23:40:39 +02:00
MHSanaei 737300b14b fix(outbound): default VLESS encryption to "none"
A blank encryption field caused Xray to reject the outbound config with
'VLESS users: please add/set "encryption":"none"'. Default the
constructor parameter, coerce empty values, and final-guard toJson so
every code path emits a valid encryption value.
2026-05-10 23:06:28 +02:00
GRCR13 30469fcd10 fix: backup path with webbasepath (#4223)
* Update BackupModal.vue

add base path to importDB API call

* fix

add base path to importDB API call
2026-05-10 22:48:35 +02:00
MHSanaei 6efc4b0665 Revert "perf(frontend): code-split heavy components to improve LCP"
This reverts commit 444b05cac9.
2026-05-10 17:45:05 +02:00
MHSanaei a96612f595 feat(xray/dns): align DNS settings with Xray docs + UI polish
- DNS server modal: rename expectIPs -> expectedIPs (per docs); add
  per-server tag, clientIP, serveStale, serveExpiredTTL, timeoutMs;
  flip skipFallback default to false; hydration still accepts legacy
  expectIPs for back-compat.
- DNS tab: add hosts editor (domain -> IP/array), serveStale +
  serveExpiredTTL controls, "Use Preset" button bringing back the
  legacy preset gallery (Google / Cloudflare / AdGuard + Family
  variants — fixed AdGuard Family IPs that were wrong in legacy),
  and a "Delete All" button to wipe the server list at once.
- i18n: add 15 new dns.* keys across all 13 locales.
- Frontend-wide formatter pass on Vue components (whitespace and
  attribute layout only, no behavior changes).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-10 17:03:11 +02:00
MHSanaei 8e7d215b4a feat(nodes): traffic-writer queue, full-mirror sync, WS event fixes
- Traffic-writer single-consumer queue (web/service/traffic_writer.go)
  serialises every DB write that touches up/down/all_time/last_online
  (AddTraffic, SetRemoteTraffic, Reset*, UpdateClientTrafficByEmail) so
  overlapping goroutines can no longer clobber each other's column-scoped
  Updates with a stale tx.Save.

- DB pool: WAL + busy_timeout=10s + synchronous=NORMAL + _txlock=
  immediate, MaxOpenConns=8 / MaxIdleConns=4. The immediate-tx PRAGMA
  fixes residual "database is locked [0ms]" cases where deferred-tx
  writer-upgrade conflicts bypass busy_timeout.

- SetRemoteTraffic full-mirrors node-authoritative state into central:
  settings JSON, remark, listen, port, total, expiry, all_time, enable,
  plus per-client total/expiry/reset/all_time. Inbounds and
  client_traffics rows present on node but missing from central are
  created; rows missing from snap are deleted (with cascading
  client_traffics removal).

- NodeTrafficSyncJob detects structural changes from the mirror and
  broadcasts invalidate(inbounds) so open central UIs re-fetch via REST
  on node-side add/del/edit without manual refresh.

- XrayTrafficJob broadcasts invalidate(inbounds) when auto-disable flips
  client_traffics.enable so the per-client toggle reflects depletion
  without manual refresh.

- Frontend: inbounds page now subscribes to the BroadcastInbounds 'inbounds'
  WS event (full-list pushes from add/del/update controllers were silently
  dropped). Fixes invalidate payload field (dataType -> type). Restart-
  panel modal switched from Promise-wrap to onOk-only so Cancel actually
  cancels.

- Node files trimmed of stale prose-comments; cron cadence dropped
  10s -> 5s to match the inbounds page UX.

- README badges and Go module path bumped v2 -> v3 to match module rename.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-10 16:25:23 +02:00
Ali Fotouhi 9cbba130ab fix(xray): clear outbound test state on delete to prevent result bleed (#4205) 2026-05-10 12:03:00 +02:00
MHSanaei cf5767acd1 i18n: localize sidebar theme toggle, xray-status badge, and nodes menu
The sidebar theme submenu (Theme / Dark / Ultra dark) and the dashboard's
Xray status badge ("Xray is running" etc.) were hardcoded English strings.
Wire them through vue-i18n: ThemeSwitch.vue uses menu.theme/dark/ultraDark,
and XrayStatusCard.vue derives the badge text from the existing
pages.index.xrayStatus{Running,Stop,Error,Unknown} keys (status.js no
longer carries an English stateMsg field).

The "Nodes" menu item was already keyed as menu.nodes but only en-US and
fa-IR had a translation; add it to the other 11 languages, matching the
wording each file already uses for pages.nodes.title.
#4201
2026-05-10 11:56:30 +02:00
MHSanaei 444b05cac9 perf(frontend): code-split heavy components to improve LCP
Switch the inbounds-page modals, login page's theme switch, and the
Persian date picker to defineAsyncComponent. They're not needed on
first paint, so deferring them shrinks the initial bundle and lets
the LCP element render sooner.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-10 11:33:46 +02:00
MHSanaei 267fb1c866 refactor(inbounds): reorder Inbound's Data tabs (client first, sub inline)
Show the per-client pane as the default tab and fold the subscription
URLs into its bottom (under a divider) so the modal has two tabs
instead of three. Inbound details move to the second tab and remain
the default fallback for protocol-only entries (HTTP/Mixed/Tunnel/
WireGuard) that have no per-client view.
2026-05-10 01:59:02 +02:00
MHSanaei 5ac88271af feat(inbounds): mobile card layout for inbounds and clients
Replace the cramped <a-table> on <768px with a stacked card list for
both inbounds and the per-client expanded rows. Each card surfaces
protocol, port, node, traffic, all-time traffic, client count and
expiry inline as labeled rows instead of hiding them behind popovers,
fixes the 0px gutter that made cards visually merge, and softens the
in-quota green from #52c41a to #389e0a (Ant green-7) so traffic tags
are no longer blinding on dark themes.
2026-05-10 01:46:48 +02:00
MHSanaei b776b33497 fix(ui): correct responsive breakpoints for add client form and bulk 2026-05-10 00:52:22 +02:00
MHSanaei 1478124712 fix(ui): correct responsive breakpoints for inbound form and settings
InboundFormModal forms specified label/wrapper cols only at md
(>=768), leaving 576-767 unset and breaking the grid in that range.
Move the breakpoint down to sm so the desktop 8/14 split applies
from 576 upward.

SettingListItem had its breakpoints inverted: at <992 no span was
set so the meta and control cols squeezed side-by-side, and at lg
(992-1199) they stacked. Switch to xs/lg so input stacks below the
text under 992 and sits beside it from 992 upward.
2026-05-10 00:43:25 +02:00
MHSanaei 113a29733e feat(logs): mobile-friendly log modals with theme-aware colors
Both index-page log modals (panel logs and xray access logs) now
adapt to narrow viewports and dark / ultra-dark themes:

- Render through Vue templates instead of v-html — drops the manual
  escapeHtml helper and the regex-based string formatting; each line
  is parsed once into structured fields (date, time, level, body for
  panel logs; from / to / inbound / outbound / email for xray logs).
- Mobile: stacked cards per entry. Panel-log cards show time + a
  level badge above the wrapped message; xray-log cards show time
  and event tag above the From → To pair, with inbound / outbound /
  email as small meta pairs below. Long IPv6 / hostnames wrap
  instead of overflowing.
- Modal goes full-bleed on mobile (100vw, no rounded corners,
  pinned to viewport height) so cards get full width.
- Toolbar wraps cleanly when the row-count, level, syslog checkbox,
  and download button can't fit on one line.
- Theme-aware colour palette via CSS variables on .log-container —
  brighter shades on body.dark and [data-theme="ultra-dark"] so
  level text and blocked / proxy rows keep AA contrast against the
  navy and near-black surfaces.
- Cards render flush on the container surface (no separate card bg)
  so the colour story is identical to the desktop view.
2026-05-10 00:13:20 +02:00
MHSanaei f68a14a3ca fix(xray): align DNS outbound to spec and repair item-list rules UI
DNS outbound now mirrors xray-core's documented shape: rewriteNetwork
/ rewriteAddress / rewritePort / userLevel replace the legacy network
/ address / port keys, and unset values are dropped on the wire. Old
configs are still accepted on read so saved configs migrate cleanly.

While there, fix two latent bugs in repeat-item editors (DNS rules,
Freedom noise, WireGuard peers):
- The "+" buttons pushed plain objects into arrays of class instances,
  so toJson() crashed on the next read and the JSON tab silently went
  blank. Push proper class instances instead.
- Each item heading lived outside any a-form-item, so the delete icon
  ignored the form's column grid and slumped left. Wrap the heading
  in a form-item with the standard offset wrapper-col and switch the
  flex to space-between so the icon sits at the right of the input
  column, in line with the fields below it.
2026-05-09 23:17:31 +02:00
MHSanaei 60e2af088d feat(xray): add loopback outbound protocol
#4185
Surface xray-core's loopback outbound in the Outbounds form so users
can re-route already-processed traffic back into a named inbound for
secondary routing (e.g. splitting TCP/UDP from one ingress). The
inboundTag field is an autocomplete over existing inbound tags, with
free-text fallback for inbounds defined outside the panel. Loopback
outbounds are excluded from the connectivity test since they have no
network endpoint.
2026-05-09 22:49:49 +02:00
MHSanaei 917f9b307e fix(xray): surface reverse tags in routing and balancer dropdowns
Outbound reverse tags now appear as inbound options in routing rules
(#4199), and inbound-client reverse tags appear as outbounds in the
balancer selector (#4187). Both represent virtual endpoints created by
xray-core that the dropdowns previously missed.
2026-05-09 22:03:01 +02:00
MHSanaei 61c84e8223 fix(panel): make webBasePath work end-to-end in dev and prod
- Vite dev server reads webBasePath from x-ui.db via node:sqlite and
  injects __X_UI_BASE_PATH__ on every HTML serve, mirroring dist.go.
  Single broad proxy regex catches backend routes whether the URL is
  prefixed or not, and the bypass serves login.html for the bare
  basePath URL so post-logout navigation lands on Vite's own page
  instead of the production dist HTML's hashed asset URLs.
- axios.defaults.baseURL is set from __X_UI_BASE_PATH__ at startup so
  HttpUtil calls reach the backend's basePath group instead of 404ing
  on every prefixed install. fetch() for the public CSRF endpoint
  prepends the prefix manually since it doesn't honor axios defaults.
- Logout/redirect responses set Cache-Control: no-store and the index
  handler's logged-in redirect uses an absolute base_path+panel/ URL,
  preventing browsers from replaying a stale cached 307 that bounced
  the user back to /panel/ after logout.
- ClearSession also issues a Path=/ deletion cookie when basePath is
  not "/", so a legacy cookie from an earlier basePath setting can't
  keep IsLogin returning true after logout.
- getPanelUpdateInfo no longer returns a translated error message on
  GitHub fetch failures, so HttpUtil's auto-popup stays quiet on
  offline / blocked environments.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-09 21:47:37 +02:00
MHSanaei b885a1f8a6 fix(index): improve mobile dashboard layout
- Move System History action from the 3X-UI card into the System Load
  card's #extra slot so the chart opener sits next to live load values.
- Fix card widths on mobile by switching :sm="24" to :xs="24"; the sm
  breakpoint only kicks in at >=576px, so phones in portrait had no
  span set and cards shrank to content width.
- Restore vertical spacing between cards (vertical gutter was 0 on
  mobile) and reduce content padding on small screens, reserving 64px
  top so the sidebar drawer handle no longer overlaps the StatusCard.
- Wrap the 3X-UI link tags in a flex container so version/Telegram/docs
  chips wrap with consistent spacing on narrow widths.
- Make Sparkline's viewBox track its actual rendered pixel width via
  ResizeObserver so X-axis time labels stop being squashed horizontally
  by preserveAspectRatio="none" on narrow containers.
- Make the SystemHistory modal width responsive (95vw on mobile, was a
  fixed 900px that overflowed phone viewports).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-09 19:03:09 +02:00