fix(frontend): real dark mode + silence dev proxy ECONNREFUSED noise

Two issues from running login.html against no Go backend:

1. Dark mode toggled the body class but didn't actually re-theme any
   AD-Vue components. The legacy panel relied on custom.min.css which
   we haven't ported. AD-Vue 4 ships its own dark algorithm — wrap
   LoginPage in <a-config-provider :theme="{ algorithm }"> driven by
   our useTheme state, and AD-Vue restyles every component for free.
   Page chrome (background, card, title) gets explicit .is-dark CSS
   since the algorithm only covers AD-Vue components.

2. Vite logged every failed proxy attempt loudly. When the Go panel
   isn't running locally that's pure noise. Added a configure()
   callback that swallows ECONNREFUSED specifically; real errors
   (timeouts, 5xx, anything else) still surface.

Both fixes are dev-experience only — production build is unchanged.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
MHSanaei 2026-05-08 11:59:02 +02:00
parent 1faecbe1dd
commit 6056fda518
No known key found for this signature in database
GPG key ID: 7E4060F2FBE5AB7A
2 changed files with 48 additions and 8 deletions

View file

@ -1,11 +1,19 @@
<script setup> <script setup>
import { onMounted, reactive, ref } from 'vue'; import { computed, onMounted, reactive, ref } from 'vue';
import { UserOutlined, LockOutlined, KeyOutlined, SettingOutlined } from '@ant-design/icons-vue'; import { UserOutlined, LockOutlined, KeyOutlined, SettingOutlined } from '@ant-design/icons-vue';
import { theme as antdTheme } from 'ant-design-vue';
import { HttpUtil } from '@/utils'; import { HttpUtil } from '@/utils';
import { currentTheme } from '@/composables/useTheme.js'; import { currentTheme, theme as themeState } from '@/composables/useTheme.js';
import ThemeSwitchLogin from '@/components/ThemeSwitchLogin.vue'; import ThemeSwitchLogin from '@/components/ThemeSwitchLogin.vue';
// Drive AD-Vue 4's built-in dark algorithm from our useTheme state.
// This re-themes every AD-Vue component without depending on the
// legacy panel's custom.min.css.
const antdThemeConfig = computed(() => ({
algorithm: themeState.isDark ? antdTheme.darkAlgorithm : antdTheme.defaultAlgorithm,
}));
// Phase 4 ships this page in English only. Translations come back in // Phase 4 ships this page in English only. Translations come back in
// Phase 7 (vue-i18n) once we decide how the new build pipeline reads // Phase 7 (vue-i18n) once we decide how the new build pipeline reads
// the existing TOML translation files. // the existing TOML translation files.
@ -47,7 +55,8 @@ async function login() {
</script> </script>
<template> <template>
<a-layout class="login-app"> <a-config-provider :theme="antdThemeConfig">
<a-layout class="login-app" :class="{ 'is-dark': themeState.isDark }">
<a-layout-content class="login-content"> <a-layout-content class="login-content">
<div class="waves-header"> <div class="waves-header">
<svg class="waves" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" <svg class="waves" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
@ -139,6 +148,7 @@ async function login() {
</a-row> </a-row>
</a-layout-content> </a-layout-content>
</a-layout> </a-layout>
</a-config-provider>
</template> </template>
<style scoped> <style scoped>
@ -146,6 +156,16 @@ async function login() {
min-height: 100vh; min-height: 100vh;
background: #f0f2f5; background: #f0f2f5;
} }
.login-app.is-dark {
background: #141a26;
}
.login-app.is-dark :deep(.login-card) {
background: #1f2937;
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.45);
}
.login-app.is-dark :deep(.login-title) {
color: #2dd4bf;
}
.login-settings { .login-settings {
display: flex; display: flex;

View file

@ -6,6 +6,27 @@ import path from 'node:path';
// via embed.FS without reaching outside the web/ tree. // via embed.FS without reaching outside the web/ tree.
const outDir = path.resolve(__dirname, '../web/dist'); const outDir = path.resolve(__dirname, '../web/dist');
// 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,
configure(proxy) {
proxy.on('error', (err) => {
if (err.code === 'ECONNREFUSED') return;
// eslint-disable-next-line no-console
console.error('[proxy]', err);
});
},
};
}
return config;
}
export default defineConfig({ export default defineConfig({
plugins: [vue()], plugins: [vue()],
resolve: { resolve: {
@ -30,13 +51,12 @@ export default defineConfig({
server: { server: {
port: 5173, port: 5173,
strictPort: true, strictPort: true,
proxy: { proxy: makeBackendProxy('http://localhost:2053', [
// Proxy API calls during `npm run dev` to the local Go panel.
// Patterns are anchored regex so /login.html and /index.html // Patterns are anchored regex so /login.html and /index.html
// (which Vite serves itself) are NOT forwarded — only the bare // (which Vite serves itself) are NOT forwarded — only the bare
// backend paths and their sub-routes. // backend paths and their sub-routes.
'^/(login|logout|getTwoFactorEnable)$': 'http://localhost:2053', '^/(login|logout|getTwoFactorEnable)$',
'^/(panel|server)(/|$)': 'http://localhost:2053', '^/(panel|server)(/|$)',
}, ]),
}, },
}); });