From dba5e6e9e99efe869a625851360986c979af37a5 Mon Sep 17 00:00:00 2001 From: elky Date: Sun, 6 Sep 2026 00:13:57 +0800 Subject: [PATCH] feat: expose remote control as admin module --- aether-vscodex/README.md | 2 +- .../handlers/admin/system/shared/modules.rs | 12 ++++ frontend/src/i18n/messages.ts | 12 ++-- .../main-layout/__tests__/navigation.spec.ts | 58 +++++++++++++++---- .../src/layouts/main-layout/navigation.ts | 3 +- frontend/src/mocks/data.ts | 14 +++++ 6 files changed, 82 insertions(+), 19 deletions(-) diff --git a/aether-vscodex/README.md b/aether-vscodex/README.md index 035e4309a..2af6cf66f 100644 --- a/aether-vscodex/README.md +++ b/aether-vscodex/README.md @@ -92,7 +92,7 @@ npm --prefix aether-vscodex/web run build `vscodex_data`,短期配对码、60 秒一次性浏览器票据和在线房间保存在内存。不要在未引入 共享连接目录前横向扩容 sidecar。 -登录 Aether 后打开“Codex 远程控制”,生成一次性配对码。然后在 VS Code 命令面板执行 +登录 Aether 后打开“远程控制”,生成一次性配对码。然后在 VS Code 命令面板执行 **Codex Remote: Pair with Aether**,填写 Aether 地址和配对码。插件会把设备凭据写入 VS Code SecretStorage,并同时保持本机控制台连接。 diff --git a/apps/aether-gateway/src/handlers/admin/system/shared/modules.rs b/apps/aether-gateway/src/handlers/admin/system/shared/modules.rs index a5eb4b28b..2463611ee 100644 --- a/apps/aether-gateway/src/handlers/admin/system/shared/modules.rs +++ b/apps/aether-gateway/src/handlers/admin/system/shared/modules.rs @@ -122,6 +122,18 @@ pub(crate) const ADMIN_MODULE_DEFINITIONS: &[AdminModuleDefinition] = &[ admin_menu_group: None, admin_menu_order: 59, }, + AdminModuleDefinition { + name: "vscodex", + display_name: "远程控制", + description: "通过 aether-vscodex 插件远程控制本地 Codex 工作区", + category: "integration", + env_key: "VSCODEX_AVAILABLE", + default_available: true, + admin_route: Some("/dashboard/vscodex"), + admin_menu_icon: Some("SquareTerminal"), + admin_menu_group: Some("overview"), + admin_menu_order: 80, + }, AdminModuleDefinition { name: "s3_backup", display_name: "S3 备份", diff --git a/frontend/src/i18n/messages.ts b/frontend/src/i18n/messages.ts index 1f9236ea9..75a620c11 100644 --- a/frontend/src/i18n/messages.ts +++ b/frontend/src/i18n/messages.ts @@ -275,8 +275,8 @@ export const messages = { 'nav.healthMonitor': '健康监控', 'nav.modelCatalog': '模型目录', 'nav.apiKeys': 'API 密钥', - 'nav.vscodex': 'Codex 远程控制', - 'vscodex.title': 'Codex 远程控制', + 'nav.vscodex': '远程控制', + 'vscodex.title': '远程控制', 'vscodex.devices.label': '控制设备', 'vscodex.devices.refresh': '刷新设备', 'vscodex.devices.loading': '正在加载设备...', @@ -305,7 +305,7 @@ export const messages = { 'vscodex.connection.connecting': '正在建立安全连接...', 'vscodex.connection.ticketFailed': '安全连接凭证获取失败。', 'vscodex.connection.retry': '重试', - 'vscodex.frame.title': 'Codex 远程控制台', + 'vscodex.frame.title': '远程控制台', 'nav.walletCenter': '钱包中心', 'nav.billingCenter': '套餐中心', 'nav.myReferral': '我的邀请', @@ -692,8 +692,8 @@ export const messages = { 'nav.healthMonitor': 'Health monitor', 'nav.modelCatalog': 'Model catalog', 'nav.apiKeys': 'API keys', - 'nav.vscodex': 'Codex remote control', - 'vscodex.title': 'Codex remote control', + 'nav.vscodex': 'Remote control', + 'vscodex.title': 'Remote control', 'vscodex.devices.label': 'Control device', 'vscodex.devices.refresh': 'Refresh devices', 'vscodex.devices.loading': 'Loading devices...', @@ -722,7 +722,7 @@ export const messages = { 'vscodex.connection.connecting': 'Establishing a secure connection...', 'vscodex.connection.ticketFailed': 'A secure connection ticket could not be issued.', 'vscodex.connection.retry': 'Retry', - 'vscodex.frame.title': 'Codex remote control surface', + 'vscodex.frame.title': 'Remote control surface', 'nav.walletCenter': 'Wallet', 'nav.billingCenter': 'Plans', 'nav.myReferral': 'Referrals', diff --git a/frontend/src/layouts/main-layout/__tests__/navigation.spec.ts b/frontend/src/layouts/main-layout/__tests__/navigation.spec.ts index e2ef71fb9..0f4bd9511 100644 --- a/frontend/src/layouts/main-layout/__tests__/navigation.spec.ts +++ b/frontend/src/layouts/main-layout/__tests__/navigation.spec.ts @@ -37,19 +37,27 @@ describe('main layout navigation builder', () => { expect(navigation.flatMap(group => group.items.map(item => item.name))).toContain('tx:nav.myReferral') }) - it('exposes the same VS Code control destination to users and administrators', () => { - const commonOptions = { + it('exposes remote control to users and active administrators', () => { + const userNavigation = buildNavigation({ + canAccessAdmin: false, modules: {}, isModuleActive: () => false, t: translate, - } - const userNavigation = buildNavigation({ - ...commonOptions, - canAccessAdmin: false, }) const adminNavigation = buildNavigation({ - ...commonOptions, canAccessAdmin: true, + modules: { + vscodex: { + active: true, + admin_route: '/dashboard/vscodex', + admin_menu_group: 'overview', + admin_menu_order: 80, + admin_menu_icon: 'SquareTerminal', + display_name: '远程控制', + }, + }, + isModuleActive: () => false, + t: translate, }) const findVscodeControl = (navigation: ReturnType) => ( @@ -63,9 +71,14 @@ describe('main layout navigation builder', () => { href: '/dashboard/vscodex', }) expect(findVscodeControl(adminNavigation)).toMatchObject({ - name: 'tx:nav.vscodex', + name: '远程控制', href: '/dashboard/vscodex', }) + + const overviewItems = adminNavigation.find(group => group.title === 'tx:nav.group.overview')?.items ?? [] + expect(overviewItems.findIndex(item => item.name === '远程控制')).toBe( + overviewItems.findIndex(item => item.name === 'tx:nav.performanceAnalysis') + 1, + ) }) it('builds admin navigation with dynamic module menu items sorted by menu order', () => { @@ -133,13 +146,36 @@ describe('main layout navigation builder', () => { expect(buildBreadcrumbs({ route: route('/dashboard/vscodex'), - navigation, - modules: {}, + navigation: buildNavigation({ + canAccessAdmin: true, + modules: { + vscodex: { + active: true, + admin_route: '/dashboard/vscodex', + admin_menu_group: 'overview', + admin_menu_order: 80, + admin_menu_icon: 'SquareTerminal', + display_name: '远程控制', + }, + }, + isModuleActive: () => false, + t: translate, + }), + modules: { + vscodex: { + active: true, + admin_route: '/dashboard/vscodex', + admin_menu_group: 'overview', + admin_menu_order: 80, + admin_menu_icon: 'SquareTerminal', + display_name: '远程控制', + }, + }, isNavActive: href => href === '/dashboard/vscodex', t: translate, })).toEqual([ expect.objectContaining({ label: expect.any(String) }), - { label: 'tx:nav.vscodex' }, + { label: '远程控制' }, ]) }) }) diff --git a/frontend/src/layouts/main-layout/navigation.ts b/frontend/src/layouts/main-layout/navigation.ts index 5027e8abd..82846d1c8 100644 --- a/frontend/src/layouts/main-layout/navigation.ts +++ b/frontend/src/layouts/main-layout/navigation.ts @@ -51,6 +51,7 @@ const moduleIconMap: Record = { Server, Send, SlidersHorizontal, + SquareTerminal, CreditCard, Gift, } @@ -117,12 +118,12 @@ export function buildNavigation(options: { title: t('nav.group.overview'), items: [ { name: t('nav.dashboard'), href: '/admin/dashboard', icon: Home }, - { name: t('nav.vscodex'), href: '/dashboard/vscodex', icon: SquareTerminal }, { name: t('nav.operations'), href: '/admin/operations', icon: Activity }, { name: t('nav.healthMonitor'), href: '/admin/health-monitor', icon: Activity }, { name: t('nav.userStats'), href: '/admin/user-stats', icon: BarChart3 }, { name: t('nav.costAnalysis'), href: '/admin/cost-analysis', icon: Gauge }, { name: t('nav.performanceAnalysis'), href: '/admin/performance-analysis', icon: Activity }, + ...activeModuleItems(modules, 'overview'), ] }, { diff --git a/frontend/src/mocks/data.ts b/frontend/src/mocks/data.ts index 7bb51fbf2..e232bd43b 100644 --- a/frontend/src/mocks/data.ts +++ b/frontend/src/mocks/data.ts @@ -1138,6 +1138,20 @@ const MOCK_MODULE_DEFINITIONS: Array & { admin_menu_group: null, admin_menu_order: 59, }, + { + name: 'vscodex', + display_name: '远程控制', + description: '通过 aether-vscodex 插件远程控制本地 Codex 工作区', + category: 'integration', + available: true, + enabled: false, + config_validated: true, + config_error: null, + admin_route: '/dashboard/vscodex', + admin_menu_icon: 'SquareTerminal', + admin_menu_group: 'overview', + admin_menu_order: 80, + }, { name: 's3_backup', display_name: 'S3 备份',