diff --git a/src/components/layout/MainLayout.tsx b/src/components/layout/MainLayout.tsx index 09c80eb7..1293a290 100644 --- a/src/components/layout/MainLayout.tsx +++ b/src/components/layout/MainLayout.tsx @@ -384,18 +384,85 @@ export function MainLayout() { }); }, [fetchConfig]); - const navItems = [ - { path: '/', label: t('nav.dashboard'), icon: sidebarIcons.dashboard }, - { path: '/config', label: t('nav.config_management'), icon: sidebarIcons.config }, - { path: '/ai-providers', label: t('nav.ai_providers'), icon: sidebarIcons.aiProviders }, - { path: '/auth-files', label: t('nav.auth_files'), icon: sidebarIcons.authFiles }, - { path: '/oauth', label: t('nav.oauth', { defaultValue: 'OAuth' }), icon: sidebarIcons.oauth }, - { path: '/quota', label: t('nav.quota_management'), icon: sidebarIcons.quota }, - ...(config?.loggingToFile - ? [{ path: '/logs', label: t('nav.logs'), icon: sidebarIcons.logs }] - : []), - { path: '/system', label: t('nav.system_info'), icon: sidebarIcons.system }, + const navGroups = [ + { + id: 'operate', + labelKey: 'nav_groups.operate', + items: [ + { + path: '/', + labelKey: 'nav.dashboard', + metaKey: 'nav_meta.dashboard', + icon: sidebarIcons.dashboard, + }, + ], + }, + { + id: 'gateway', + labelKey: 'nav_groups.gateway', + items: [ + { + path: '/ai-providers', + labelKey: 'nav.ai_providers', + metaKey: 'nav_meta.ai_providers', + icon: sidebarIcons.aiProviders, + }, + { + path: '/auth-files', + labelKey: 'nav.auth_files', + metaKey: 'nav_meta.auth_files', + icon: sidebarIcons.authFiles, + }, + { + path: '/oauth', + labelKey: 'nav.oauth', + metaKey: 'nav_meta.oauth', + icon: sidebarIcons.oauth, + }, + ], + }, + { + id: 'observe', + labelKey: 'nav_groups.observe', + items: [ + { + path: '/quota', + labelKey: 'nav.quota_management', + metaKey: 'nav_meta.quota_management', + icon: sidebarIcons.quota, + }, + ...(config?.loggingToFile + ? [ + { + path: '/logs', + labelKey: 'nav.logs', + metaKey: 'nav_meta.logs', + icon: sidebarIcons.logs, + }, + ] + : []), + ], + }, + { + id: 'control', + labelKey: 'nav_groups.control', + items: [ + { + path: '/config', + labelKey: 'nav.config_management', + metaKey: 'nav_meta.config_management', + icon: sidebarIcons.config, + }, + { + path: '/system', + labelKey: 'nav.system_info', + metaKey: 'nav_meta.system_info', + icon: sidebarIcons.system, + }, + ], + }, ]; + const navItems = navGroups.flatMap((group) => group.items); const navOrder = navItems.map((item) => item.path); const getRouteOrder = (pathname: string) => { const trimmedPath = @@ -652,17 +719,32 @@ export function MainLayout() {
- {navItems.map((item) => ( - `nav-item ${isActive ? 'active' : ''}`} - onClick={() => setSidebarOpen(false)} - title={showSidebarLabels ? undefined : item.label} - > - {item.icon} - {showSidebarLabels && {item.label}} - + {navGroups.map((group, idx) => ( +
+ {showSidebarLabels + ?
{t(group.labelKey)}
+ : idx > 0 && ))}
diff --git a/src/i18n/locales/en.json b/src/i18n/locales/en.json index bb0df03b..9774c6a6 100644 --- a/src/i18n/locales/en.json +++ b/src/i18n/locales/en.json @@ -120,6 +120,22 @@ "logs": "Logs Viewer", "system_info": "Management Center Info" }, + "nav_groups": { + "operate": "Operate", + "gateway": "Gateway", + "observe": "Observe", + "control": "Control" + }, + "nav_meta": { + "dashboard": "Operations overview", + "ai_providers": "Upstream model routing", + "auth_files": "Auth files & credentials", + "oauth": "OAuth authorization", + "quota_management": "API keys & limits", + "logs": "Request tracing", + "config_management": "Gateway configuration", + "system_info": "Runtime & diagnostics" + }, "dashboard": { "title": "Dashboard", "subtitle": "Welcome to CLI Proxy API Management Center", diff --git a/src/i18n/locales/ru.json b/src/i18n/locales/ru.json index 2fcf340f..56390273 100644 --- a/src/i18n/locales/ru.json +++ b/src/i18n/locales/ru.json @@ -120,6 +120,22 @@ "logs": "Просмотр логов", "system_info": "Информация системы" }, + "nav_groups": { + "operate": "Работа", + "gateway": "Шлюз", + "observe": "Наблюдение", + "control": "Управление" + }, + "nav_meta": { + "dashboard": "Обзор работы", + "ai_providers": "Маршрутизация моделей", + "auth_files": "Файлы и учётные данные", + "oauth": "Авторизация OAuth", + "quota_management": "API ключи и лимиты", + "logs": "Трассировка запросов", + "config_management": "Базовая конфигурация шлюза", + "system_info": "Состояние и диагностика" + }, "dashboard": { "title": "Панель управления", "subtitle": "Добро пожаловать в Центр управления CLI Proxy API", diff --git a/src/i18n/locales/zh-CN.json b/src/i18n/locales/zh-CN.json index 56a1331d..af945c89 100644 --- a/src/i18n/locales/zh-CN.json +++ b/src/i18n/locales/zh-CN.json @@ -120,6 +120,22 @@ "logs": "日志查看", "system_info": "中心信息" }, + "nav_groups": { + "operate": "运行", + "gateway": "网关", + "observe": "观测", + "control": "控制" + }, + "nav_meta": { + "dashboard": "运行总览", + "ai_providers": "上游模型路由", + "auth_files": "Auth 文件与凭证", + "oauth": "OAuth 授权登录", + "quota_management": "API Key 与限额", + "logs": "请求追踪与排查", + "config_management": "网关基础配置", + "system_info": "运行与诊断信息" + }, "dashboard": { "title": "仪表盘", "subtitle": "欢迎使用 CLI Proxy API 管理中心", diff --git a/src/i18n/locales/zh-TW.json b/src/i18n/locales/zh-TW.json index dfcc07b7..2a96cccc 100644 --- a/src/i18n/locales/zh-TW.json +++ b/src/i18n/locales/zh-TW.json @@ -120,6 +120,22 @@ "logs": "記錄檢視", "system_info": "中心資訊" }, + "nav_groups": { + "operate": "運行", + "gateway": "閘道", + "observe": "觀測", + "control": "控制" + }, + "nav_meta": { + "dashboard": "運行總覽", + "ai_providers": "上游模型路由", + "auth_files": "Auth 檔案與憑證", + "oauth": "OAuth 授權登入", + "quota_management": "API Key 與限額", + "logs": "請求追蹤與排查", + "config_management": "閘道基礎設定", + "system_info": "運行與診斷資訊" + }, "dashboard": { "title": "儀表板", "subtitle": "歡迎使用 CLI Proxy API 管理中心", diff --git a/src/styles/layout.scss b/src/styles/layout.scss index b7c5a29f..5eba872e 100644 --- a/src/styles/layout.scss +++ b/src/styles/layout.scss @@ -3,7 +3,7 @@ :root { --header-height: 80px; --shell-gutter: 24px; - --sidebar-panel-width: 240px; + --sidebar-panel-width: 256px; --sidebar-collapsed-width: 68px; --sidebar-active-width: var(--sidebar-panel-width); --sidebar-gap: 32px; @@ -428,6 +428,14 @@ justify-content: center; padding: 10px; } + + .nav-group-label { + display: none; + } + + .nav-group-divider { + display: block; + } } .sidebar-brand { @@ -462,12 +470,36 @@ .nav-section { display: flex; flex-direction: column; - gap: $spacing-sm; + gap: 14px; flex: 1; } + .nav-group { + display: flex; + flex-direction: column; + gap: $spacing-xs; + } + + .nav-group-label { + padding: 0 12px 4px; + font-size: 10.5px; + font-weight: 700; + letter-spacing: 0.08em; + text-transform: uppercase; + color: color-mix(in srgb, var(--text-primary) 42%, transparent); + user-select: none; + } + + .nav-group-divider { + display: none; + height: 1px; + width: 24px; + margin: 4px auto; + background: color-mix(in srgb, var(--border-color) 70%, transparent); + } + .nav-item { - padding: 10px 12px; + padding: 9px 12px; border-radius: 11px; border: 1px solid transparent; color: var(--text-primary); @@ -509,6 +541,13 @@ } } + .nav-text { + display: flex; + flex-direction: column; + min-width: 0; + line-height: 1.2; + } + .nav-label { min-width: 0; overflow: hidden; @@ -516,6 +555,16 @@ white-space: nowrap; } + .nav-meta { + margin-top: 2px; + font-size: 11.5px; + font-weight: 500; + color: color-mix(in srgb, var(--text-primary) 50%, transparent); + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + } + &:hover { background: color-mix(in srgb, var(--text-primary) 7%, transparent); border-color: color-mix(in srgb, var(--border-color) 58%, transparent); @@ -527,6 +576,10 @@ color-mix(in srgb, var(--bg-primary) 88%, transparent); box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--border-hover) 86%, transparent); } + + .nav-meta { + color: color-mix(in srgb, var(--text-primary) 62%, transparent); + } } &.active { @@ -542,6 +595,10 @@ color-mix(in srgb, var(--primary-color) 14%, var(--bg-primary)); box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--primary-color) 24%, transparent); } + + .nav-meta { + color: color-mix(in srgb, var(--text-primary) 68%, transparent); + } } } }