2026-05-08 08:36:03 +00:00
|
|
|
import { defineConfig } from 'vite';
|
|
|
|
|
import vue from '@vitejs/plugin-vue';
|
|
|
|
|
import path from 'node:path';
|
|
|
|
|
|
|
|
|
|
// Output goes to web/dist/ at the repo root so the Go binary can embed it
|
|
|
|
|
// via embed.FS without reaching outside the web/ tree.
|
|
|
|
|
const outDir = path.resolve(__dirname, '../web/dist');
|
|
|
|
|
|
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-08 23:52:57 +00:00
|
|
|
// In production the Go binary serves /panel/<route> from web/dist/html/<route>.html.
|
|
|
|
|
// In dev the Vue app lives at /html/index.html, /html/settings.html, ... while
|
|
|
|
|
// AppSidebar links use the production-style /panel/<route> URLs. Map each
|
|
|
|
|
// migrated route to its Vite entry so the sidebar works without relying on
|
|
|
|
|
// the Go backend for already-ported pages.
|
2026-05-08 11:16:16 +00:00
|
|
|
const MIGRATED_ROUTES = {
|
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-08 23:52:57 +00:00
|
|
|
'/panel': '/html/index.html',
|
|
|
|
|
'/panel/': '/html/index.html',
|
|
|
|
|
'/panel/settings': '/html/settings.html',
|
|
|
|
|
'/panel/settings/': '/html/settings.html',
|
|
|
|
|
'/panel/inbounds': '/html/inbounds.html',
|
|
|
|
|
'/panel/inbounds/': '/html/inbounds.html',
|
|
|
|
|
'/panel/xray': '/html/xray.html',
|
|
|
|
|
'/panel/xray/': '/html/xray.html',
|
2026-05-08 11:16:16 +00:00
|
|
|
};
|
|
|
|
|
|
2026-05-08 09:59:02 +00:00
|
|
|
// Build a proxy config that suppresses ECONNREFUSED noise when the Go
|
|
|
|
|
// backend isn't running locally. Real errors (timeouts, 5xx, etc.) still
|
|
|
|
|
// surface in the Vite log.
|
|
|
|
|
function makeBackendProxy(target, patterns) {
|
|
|
|
|
const config = {};
|
|
|
|
|
for (const pattern of patterns) {
|
|
|
|
|
config[pattern] = {
|
|
|
|
|
target,
|
|
|
|
|
changeOrigin: true,
|
2026-05-08 11:16:16 +00:00
|
|
|
// Returning a path from bypass tells Vite to serve that file from
|
|
|
|
|
// its own dev server instead of forwarding the request — used here
|
|
|
|
|
// to short-circuit /panel/<route> for pages we've already migrated.
|
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 15:21:03 +00:00
|
|
|
//
|
|
|
|
|
// Only GETs get bypassed: the xray page reuses its page URL
|
|
|
|
|
// (`POST /panel/xray/`) for data, so a method-blind bypass would
|
|
|
|
|
// hand HTML back to fetch calls and break the page in dev.
|
2026-05-08 11:16:16 +00:00
|
|
|
bypass(req) {
|
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 15:21:03 +00:00
|
|
|
if (req.method !== 'GET') return undefined;
|
2026-05-08 11:16:16 +00:00
|
|
|
const url = req.url.split('?')[0];
|
|
|
|
|
if (Object.prototype.hasOwnProperty.call(MIGRATED_ROUTES, url)) {
|
|
|
|
|
return MIGRATED_ROUTES[url];
|
|
|
|
|
}
|
|
|
|
|
return undefined;
|
|
|
|
|
},
|
2026-05-08 09:59:02 +00:00
|
|
|
configure(proxy) {
|
2026-05-08 20:51:42 +00:00
|
|
|
let warned = false;
|
|
|
|
|
proxy.on('error', (err, req) => {
|
|
|
|
|
// Node wraps connection failures in an AggregateError when DNS
|
|
|
|
|
// returns multiple addresses (e.g. ::1 + 127.0.0.1) and all
|
|
|
|
|
// refuse — the code lands on the inner errors, not the outer.
|
|
|
|
|
const codes = new Set();
|
|
|
|
|
if (err && err.code) codes.add(err.code);
|
|
|
|
|
if (err && Array.isArray(err.errors)) {
|
|
|
|
|
for (const inner of err.errors) {
|
|
|
|
|
if (inner && inner.code) codes.add(inner.code);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
const offline = codes.has('ECONNREFUSED') || codes.has('ECONNRESET');
|
|
|
|
|
if (offline) {
|
|
|
|
|
// Print a single friendly hint the first time, then stay quiet.
|
|
|
|
|
if (!warned) {
|
|
|
|
|
warned = true;
|
|
|
|
|
// eslint-disable-next-line no-console
|
|
|
|
|
console.warn(
|
|
|
|
|
`[proxy] backend ${target} is not reachable — start the Go server (e.g. \`go run main.go\`) to forward ${req?.url || 'requests'}.`,
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
return;
|
|
|
|
|
}
|
2026-05-08 09:59:02 +00:00
|
|
|
// eslint-disable-next-line no-console
|
|
|
|
|
console.error('[proxy]', err);
|
|
|
|
|
});
|
|
|
|
|
},
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
return config;
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-08 08:36:03 +00:00
|
|
|
export default defineConfig({
|
|
|
|
|
plugins: [vue()],
|
|
|
|
|
resolve: {
|
|
|
|
|
alias: {
|
|
|
|
|
'@': path.resolve(__dirname, 'src'),
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
build: {
|
|
|
|
|
outDir,
|
|
|
|
|
emptyOutDir: true,
|
|
|
|
|
sourcemap: true,
|
|
|
|
|
target: 'es2020',
|
2026-05-08 20:51:42 +00:00
|
|
|
// ant-design-vue is intentionally bundled as one chunk (its
|
|
|
|
|
// components share internals — splitting it breaks Modal/Form/
|
|
|
|
|
// Select interop). Minified it lands ~1.4MB but gzips to ~410kB,
|
|
|
|
|
// so the actual transfer is fine and caches across every page.
|
|
|
|
|
// Bump the warning past that ceiling so the build stays quiet.
|
|
|
|
|
chunkSizeWarningLimit: 1500,
|
2026-05-08 08:36:03 +00:00
|
|
|
// Multiple HTML entries — one per legacy page we migrate.
|
|
|
|
|
// As pages get ported in later phases, add their entrypoints here.
|
|
|
|
|
rollupOptions: {
|
|
|
|
|
input: {
|
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-08 23:52:57 +00:00
|
|
|
index: path.resolve(__dirname, 'html/index.html'),
|
|
|
|
|
login: path.resolve(__dirname, 'html/login.html'),
|
|
|
|
|
settings: path.resolve(__dirname, 'html/settings.html'),
|
|
|
|
|
inbounds: path.resolve(__dirname, 'html/inbounds.html'),
|
|
|
|
|
xray: path.resolve(__dirname, 'html/xray.html'),
|
|
|
|
|
subpage: path.resolve(__dirname, 'html/subpage.html'),
|
2026-05-08 08:36:03 +00:00
|
|
|
},
|
2026-05-08 20:51:42 +00:00
|
|
|
output: {
|
|
|
|
|
// Split vendor deps into stable chunks so each page only pulls
|
|
|
|
|
// what it needs and the browser caches them across versions.
|
|
|
|
|
// Without this, ant-design-vue + vue + icons all end up in one
|
|
|
|
|
// 1.6MB blob attached to whichever page consumed them first.
|
|
|
|
|
manualChunks(id) {
|
|
|
|
|
if (!id.includes('node_modules')) return undefined;
|
|
|
|
|
if (id.includes('ant-design-vue')) return 'vendor-antd';
|
|
|
|
|
if (id.includes('@ant-design/icons-vue')) return 'vendor-icons';
|
|
|
|
|
if (id.includes('vue-i18n')) return 'vendor-i18n';
|
|
|
|
|
if (
|
|
|
|
|
id.includes('/node_modules/vue/')
|
|
|
|
|
|| id.includes('/node_modules/@vue/')
|
|
|
|
|
) return 'vendor-vue';
|
|
|
|
|
if (id.includes('dayjs')) return 'vendor-dayjs';
|
|
|
|
|
if (id.includes('qrious')) return 'vendor-qrious';
|
|
|
|
|
if (id.includes('axios')) return 'vendor-axios';
|
2026-05-08 22:17:25 +00:00
|
|
|
// The persian datepicker pulls in moment + moment-jalaali; bundle
|
|
|
|
|
// the trio together so unrelated pages don't pay the cost.
|
|
|
|
|
if (
|
|
|
|
|
id.includes('vue3-persian-datetime-picker')
|
|
|
|
|
|| id.includes('moment-jalaali')
|
|
|
|
|
|| id.includes('jalaali-js')
|
|
|
|
|
|| id.includes('/node_modules/moment/')
|
|
|
|
|
) return 'vendor-jalali';
|
2026-05-08 20:51:42 +00:00
|
|
|
return 'vendor';
|
|
|
|
|
},
|
|
|
|
|
},
|
2026-05-08 08:36:03 +00:00
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
server: {
|
|
|
|
|
port: 5173,
|
|
|
|
|
strictPort: true,
|
2026-05-08 09:59:02 +00:00
|
|
|
proxy: makeBackendProxy('http://localhost:2053', [
|
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-08 23:52:57 +00:00
|
|
|
// Patterns are anchored regex so /html/login.html, /html/index.html
|
|
|
|
|
// etc. (which Vite serves itself) are NOT forwarded — only the
|
|
|
|
|
// bare backend paths and their sub-routes.
|
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 15:21:03 +00:00
|
|
|
'^/(login|logout|getTwoFactorEnable|csrf-token)$',
|
2026-05-08 09:59:02 +00:00
|
|
|
'^/(panel|server)(/|$)',
|
|
|
|
|
]),
|
2026-05-08 08:36:03 +00:00
|
|
|
},
|
|
|
|
|
});
|