diff --git a/frontend/src/composables/useTheme.js b/frontend/src/composables/useTheme.js index 1d6ff2ce..bac910fd 100644 --- a/frontend/src/composables/useTheme.js +++ b/frontend/src/composables/useTheme.js @@ -27,8 +27,15 @@ export const currentTheme = computed(() => (theme.isDark ? 'dark' : 'light')); // AD-Vue 4 theme config consumed by every page's . // Three modes — light / dark / ultra-dark — all share AD-Vue's vanilla -// blue primary. Ultra-dark layers deeper background tokens on top of -// darkAlgorithm so layouts/cards/popups all darken together. +// blue primary. Dark uses a navy palette across page/cards/modals so +// the sidebar blends with the rest of the surface; ultra-dark stays +// neutral black on top of darkAlgorithm. +const DARK_TOKENS = { + colorBgBase: '#0a1426', + colorBgLayout: '#0a1426', + colorBgContainer: '#142340', + colorBgElevated: '#1a2c4d', +}; const ULTRA_DARK_TOKENS = { colorBgBase: '#000', colorBgLayout: '#000', @@ -36,13 +43,45 @@ const ULTRA_DARK_TOKENS = { colorBgElevated: '#141414', }; +// AD-Vue 4 hardcodes navy `#001529` / `#002140` as the Layout sider +// + trigger backgrounds and `#001529` / `#000c17` as the dark Menu item +// backgrounds (see node_modules/ant-design-vue/es/{layout,menu}/style/ +// index.js). Override at the component-token level so the sider blends +// with darkAlgorithm's neutral surfaces. +// Dark theme uses a refined navy for the sidebar — distinct from the +// neutral ultra-dark and warmer than AD-Vue's stock #001529. +const DARK_LAYOUT_TOKENS = { + colorBgHeader: '#0d1d33', + colorBgTrigger: '#15294a', + colorBgBody: '#000', +}; +const ULTRA_DARK_LAYOUT_TOKENS = { + colorBgHeader: '#0a0a0a', + colorBgTrigger: '#141414', + colorBgBody: '#000', +}; +const DARK_MENU_TOKENS = { + colorItemBg: '#0d1d33', + colorSubItemBg: '#08142a', + menuSubMenuBg: '#0d1d33', +}; +const ULTRA_DARK_MENU_TOKENS = { + colorItemBg: '#0a0a0a', + colorSubItemBg: '#000', + menuSubMenuBg: '#0a0a0a', +}; + export const antdThemeConfig = computed(() => { if (!theme.isDark) { return { algorithm: antdTheme.defaultAlgorithm }; } return { algorithm: antdTheme.darkAlgorithm, - token: theme.isUltra ? ULTRA_DARK_TOKENS : undefined, + token: theme.isUltra ? ULTRA_DARK_TOKENS : DARK_TOKENS, + components: { + Layout: theme.isUltra ? ULTRA_DARK_LAYOUT_TOKENS : DARK_LAYOUT_TOKENS, + Menu: theme.isUltra ? ULTRA_DARK_MENU_TOKENS : DARK_MENU_TOKENS, + }, }; }); diff --git a/frontend/src/pages/inbounds/ClientBulkModal.vue b/frontend/src/pages/inbounds/ClientBulkModal.vue index a4009eac..8c94c849 100644 --- a/frontend/src/pages/inbounds/ClientBulkModal.vue +++ b/frontend/src/pages/inbounds/ClientBulkModal.vue @@ -172,22 +172,9 @@ async function submit() {
{{ IntlUtil.formatRelativeTime(client.expiryTime) }} - + {{ client.reset }}d
@@ -331,19 +314,13 @@ function rowKey(client) { {{ t('pages.client.delayedStart') }} {{ IntlUtil.formatDate(client.expiryTime) }} - + {{ IntlUtil.formatRelativeTime(client.expiryTime) }} - + @@ -378,7 +355,9 @@ function rowKey(client) { {{ -client.expiryTime / 86400000 }}d ({{ t('pages.client.delayedStart') }}) - + + + @@ -402,21 +381,30 @@ function rowKey(client) { .client-row { display: grid; grid-template-columns: - 140px /* actions */ - 60px /* enable */ - 80px /* online */ - minmax(160px, 2fr) /* client identity */ - minmax(160px, 2fr) /* traffic */ - 130px /* all-time */ - 140px; /* expiry */ + 140px + /* actions */ + 60px + /* enable */ + 80px + /* online */ + minmax(160px, 2fr) + /* client identity */ + minmax(160px, 2fr) + /* traffic */ + 130px + /* all-time */ + 140px; + /* expiry */ gap: 12px; align-items: center; padding: 8px 16px; border-top: 1px solid rgba(128, 128, 128, 0.12); } + .client-row:last-child { border-bottom: 1px solid rgba(128, 128, 128, 0.12); } + .client-list-header { font-weight: 500; font-size: 12px; @@ -435,8 +423,10 @@ function rowKey(client) { } .cell { - min-width: 0; /* allow grid children to shrink instead of overflowing */ + min-width: 0; + /* allow grid children to shrink instead of overflowing */ } + .cell-actions, .cell-enable, .cell-online, @@ -449,22 +439,27 @@ function rowKey(client) { gap: 6px; flex-wrap: wrap; } + .cell-actions { justify-content: flex-start; } + .cell-client { display: inline-flex; align-items: center; gap: 6px; min-width: 0; } + .cell-traffic, .cell-expiry { text-align: center; } + .client-list-header .cell { text-align: center; } + .client-list-header .cell-actions, .client-list-header .cell-client { text-align: left; @@ -478,13 +473,18 @@ function rowKey(client) { color: inherit; transition: color 120ms ease; } + .row-icon:hover { color: var(--ant-color-primary, #1677ff); } + .row-icon.danger { color: #ff4d4f; } -.danger { color: #ff4d4f; } + +.danger { + color: #ff4d4f; +} /* Client identity stack (badge + email + comment) */ .client-id-stack { @@ -494,6 +494,7 @@ function rowKey(client) { min-width: 0; overflow: hidden; } + .client-email { font-weight: 500; white-space: nowrap; @@ -501,6 +502,7 @@ function rowKey(client) { text-overflow: ellipsis; display: inline-block; } + .client-comment { font-size: 11px; opacity: 0.7; @@ -517,10 +519,12 @@ function rowKey(client) { align-items: center; gap: 6px; } + .usage-text { font-size: 12px; white-space: nowrap; } + .usage-bar :deep(.ant-progress) { margin: 0; line-height: 1; @@ -534,8 +538,15 @@ function rowKey(client) { } /* Mobile popover content table */ -.text-center { text-align: center; } -.num-cell { text-align: right; font-size: 12px; padding: 2px 6px; } +.text-center { + text-align: center; +} + +.num-cell { + text-align: right; + font-size: 12px; + padding: 2px 6px; +} /* Strip AD-Vue's default expanded-cell padding so the grid sits * flush against the inbound row's left/right edges. */ diff --git a/frontend/src/pages/inbounds/InboundFormModal.vue b/frontend/src/pages/inbounds/InboundFormModal.vue index 133e0767..49e9bb84 100644 --- a/frontend/src/pages/inbounds/InboundFormModal.vue +++ b/frontend/src/pages/inbounds/InboundFormModal.vue @@ -502,17 +502,8 @@ watch( - + @@ -708,13 +683,8 @@ watch( - + {{ k }} @@ -740,21 +710,15 @@ watch( - + - - + + Add @@ -765,7 +729,9 @@ watch( - + @@ -789,13 +755,8 @@ watch( - + @@ -815,16 +776,12 @@ watch( - + @@ -839,22 +796,22 @@ watch( - + Add peer
Peer {{ idx + 1 }} - + @@ -866,17 +823,16 @@ watch( - + - + @@ -892,25 +848,21 @@ watch(
+ title="Route incoming TLS traffic to a backend when it doesn't match a valid VLESS/Trojan handshake. Match by SNI, ALPN, and HTTP path; the most precise rule wins. Fallbacks require TCP+TLS transport."> Fallbacks ({{ inbound.settings.fallbacks.length }}) - + Add
- + Fallback {{ idx + 1 }} @@ -928,8 +880,7 @@ watch( @@ -940,42 +891,32 @@ watch( - + - + - + @@ -990,8 +931,8 @@ watch( - + @@ -1010,10 +951,8 @@ watch( - + diff --git a/frontend/src/pages/inbounds/InboundsPage.vue b/frontend/src/pages/inbounds/InboundsPage.vue index e5a063d6..5c1611d9 100644 --- a/frontend/src/pages/inbounds/InboundsPage.vue +++ b/frontend/src/pages/inbounds/InboundsPage.vue @@ -512,10 +512,7 @@ function onRowAction({ key, dbInbound }) { @@ -692,10 +632,17 @@ function onRowAction({ key, dbInbound }) { background: transparent; } -.content-shell { background: transparent; } -.content-area { padding: 24px; } +.content-shell { + background: transparent; +} -.loading-spacer { min-height: calc(100vh - 120px); } +.content-area { + padding: 24px; +} + +.loading-spacer { + min-height: calc(100vh - 120px); +} .summary-card { padding: 16px; diff --git a/frontend/src/pages/inbounds/QrCodeModal.vue b/frontend/src/pages/inbounds/QrCodeModal.vue index cb6cd215..64a82759 100644 --- a/frontend/src/pages/inbounds/QrCodeModal.vue +++ b/frontend/src/pages/inbounds/QrCodeModal.vue @@ -52,23 +52,11 @@ function close() {