The dot/badge/pill styles still hardcoded AntD's default palette values (#52c41a, #1677ff, #ff4d4f, #fa8c16, #ff4d4f). Replace each with its semantic --ant-color-* equivalent so they auto-adapt to any theme customization through ConfigProvider. - ClientsPage: .dot-green/.dot-blue/.dot-red/.dot-orange/.dot-gray now use --ant-color-success / -primary / -error / -warning / -text-quaternary. .bulk-count / .client-card / .client-card.is-selected backgrounds use color-mix on --ant-color-primary and --ant-color-fill-quaternary, which also let the body-dark .client-card fork go away. - XrayMetricsModal: .obs-dot is-alive/is-dead and its pulse keyframe now build their box-shadow tint via color-mix on --ant-color-success and --ant-color-error instead of rgba literals. - IndexPage: .action-update warning color uses --ant-color-warning. - OutboundsTab: .outbound-card border, .address-pill background, and .mode-badge tint now use AntD CSS variables; the .xray-page.is-dark .address-pill fork is gone. - InboundFormModal/InboundsPage/ClientBulkAddModal: drop the stale `, #1677ff`/`, #1890ff` fallbacks on var(--ant-color-primary), and switch .danger-icon to --ant-color-error. The teal/cyan brand colors (#008771, #3c89e8, #e04141) used by traffic and pill rows are intentionally kept hardcoded — they are brand-specific shades, not AntD palette colors. |
||
|---|---|---|
| .. | ||
| public | ||
| scripts | ||
| src | ||
| .gitignore | ||
| eslint.config.js | ||
| index.html | ||
| login.html | ||
| package-lock.json | ||
| package.json | ||
| README.md | ||
| subpage.html | ||
| tsconfig.json | ||
| vite.config.js | ||
3x-ui frontend
React 19 + Ant Design 6 + TypeScript + Vite 8. 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.
Type check and lint
npm run typecheck
npm run lint
tsc --noEmit against tsconfig.json (strict mode, jsx: "react-jsx",
@/* → src/* alias). ESLint 10 with eslint.config.js (flat config)
— @eslint/js recommended plus typescript-eslint and
eslint-plugin-react-hooks rules.
Layout
frontend/
├── *.html # Vite entry HTML, one per panel route
├── tsconfig.json
├── eslint.config.js
├── vite.config.js
└── src/
├── entries/ # Per-page bootstrap (createRoot + render)
├── pages/ # One folder per route, each with the page
│ ├── index/ # component + helpers + sub-components
│ ├── login/
│ ├── inbounds/
│ ├── clients/
│ ├── xray/
│ ├── nodes/
│ ├── settings/
│ ├── api-docs/
│ └── sub/
├── components/ # Cross-page React components
├── hooks/ # Reusable hooks (useTheme, useWebSocket, …)
├── api/ # Axios setup, CSRF interceptor, WebSocket
├── i18n/ # react-i18next init (locales live in web/translation/)
├── models/ # Inbound, Outbound, Status, … domain classes
├── styles/ # Shared CSS modules (page-cards, …)
└── utils/ # HttpUtil, ObjectUtil, LanguageManager, …
Adding a new page
- Add
frontend/<page>.htmlreferencing/src/entries/<page>.tsx. - Add
src/entries/<page>.tsxthat imports the page component and mounts it withcreateRoot(...).render(...). - 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").