- 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>
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
- Add
frontend/<page>.htmlreferencing/src/entries/<page>.js. - Add
src/entries/<page>.jsthat imports the page component and mounts it. - Add the page component under
src/pages/<page>/. - Register the entry in
rollupOptions.inputinvite.config.js. - If the page is reachable from the sidebar at
/panel/<route>, add it toMIGRATED_ROUTESso the dev proxy serves the Vite HTML. - Wire the Go controller to
serveDistPage(c, "<page>.html").