3x-ui/frontend
MHSanaei 7fbaf5fe2d
fix(clients): expose Attached inbounds in edit mode
The multi-select was gated on add-only, so editing a client had no way
to change which inbounds it belonged to. The picker now shows in both
modes, and on submit the modal diffs the picked set against the
original attachedIds — additions go through the /attach endpoint,
removals through /detach, both after the field update lands so the
new attachments get the latest values.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-17 07:52:24 +02:00
..
src fix(clients): expose Attached inbounds in edit mode 2026-05-17 07:52:24 +02:00
.gitignore Vue3 migration (#4198) 2026-05-09 17:47:35 +02:00
api-docs.html feat(ui): use the host as the browser tab title prefix 2026-05-13 14:23:57 +02:00
clients.html feat(clients): add top-level Clients tab and CRUD API 2026-05-17 07:28:55 +02:00
eslint.config.js fix: reality random target/sni buttons not working (#4337) (#4340) 2026-05-13 14:42:20 +02:00
inbounds.html feat(ui): use the host as the browser tab title prefix 2026-05-13 14:23:57 +02:00
index.html feat(ui): use the host as the browser tab title prefix 2026-05-13 14:23:57 +02:00
login.html feat(ui): use the host as the browser tab title prefix 2026-05-13 14:23:57 +02:00
nodes.html feat(ui): use the host as the browser tab title prefix 2026-05-13 14:23:57 +02:00
package-lock.json feat(clients): add shadow tables for first-class client promotion 2026-05-17 07:03:14 +02:00
package.json v3.0.2 2026-05-14 10:27:33 +02:00
README.md Vue3 migration (#4198) 2026-05-09 17:47:35 +02:00
settings.html feat(ui): use the host as the browser tab title prefix 2026-05-13 14:23:57 +02:00
subpage.html Vue3 migration (#4198) 2026-05-09 17:47:35 +02:00
vite.config.js feat(clients): add top-level Clients tab and CRUD API 2026-05-17 07:28:55 +02:00
xray.html feat(ui): use the host as the browser tab title prefix 2026-05-13 14:23:57 +02:00

3x-ui frontend

Vue 3 + Ant Design Vue 4 + Vite. Multi-page app — one HTML entry per panel route — built into ../web/dist/ and embedded into the Go binary via embed.FS.

Dev

npm install
npm run dev

Vite serves on http://localhost:5173/. API calls and /panel/* routes proxy to the Go panel at http://localhost:2053/, so start the Go panel first (go run main.go) and then Vite.

The proxy auto-rewrites /panel, /panel/settings, /panel/inbounds, /panel/xray to the matching Vite-served HTML in dev mode (see MIGRATED_ROUTES in vite.config.js), so the sidebar's production-style links work without round-tripping through Go.

Production build

npm run build

Outputs to ../web/dist/ (HTML at the root, hashed JS/CSS under assets/). The Go binary embeds this directory at compile time and web/controller/dist.go serves the per-page HTML.

Lint

npm run lint

ESLint 10 with eslint.config.js (flat config) — vue3-recommended plus a few rule overrides for the project's formatting style.

Layout

frontend/
├── *.html                 # Vite entry HTML, one per panel route
├── eslint.config.js
├── vite.config.js
└── src/
    ├── entries/           # Per-page bootstrap (createApp + mount)
    ├── pages/             # One folder per route, each with the page
    │   ├── index/         # component + helpers + sub-components
    │   ├── login/
    │   ├── inbounds/
    │   ├── xray/
    │   ├── settings/
    │   └── sub/
    ├── components/        # Cross-page Vue components
    ├── composables/       # Reusable reactive logic (useTheme, …)
    ├── api/               # Axios setup, CSRF interceptor
    ├── i18n/              # vue-i18n init (locales live in web/translation/)
    ├── models/            # Inbound, Outbound, Status, … domain classes
    └── utils/             # HttpUtil, ObjectUtil, LanguageManager, …

Adding a new page

  1. Add frontend/<page>.html referencing /src/entries/<page>.js.
  2. Add src/entries/<page>.js that imports the page component and mounts it.
  3. Add the page component under src/pages/<page>/.
  4. Register the entry in rollupOptions.input in vite.config.js.
  5. If the page is reachable from the sidebar at /panel/<route>, add it to MIGRATED_ROUTES so the dev proxy serves the Vite HTML.
  6. Wire the Go controller to serveDistPage(c, "<page>.html").