feat: expose remote control as admin module

This commit is contained in:
elky
2026-09-06 00:13:57 +08:00
parent c125e78c5f
commit dba5e6e9e9
6 changed files with 82 additions and 19 deletions
+1 -1
View File
@@ -92,7 +92,7 @@ npm --prefix aether-vscodex/web run build
`vscodex_data`,短期配对码、60 秒一次性浏览器票据和在线房间保存在内存。不要在未引入 `vscodex_data`,短期配对码、60 秒一次性浏览器票据和在线房间保存在内存。不要在未引入
共享连接目录前横向扩容 sidecar。 共享连接目录前横向扩容 sidecar。
登录 Aether 后打开“Codex 远程控制”,生成一次性配对码。然后在 VS Code 命令面板执行 登录 Aether 后打开“远程控制”,生成一次性配对码。然后在 VS Code 命令面板执行
**Codex Remote: Pair with Aether**,填写 Aether 地址和配对码。插件会把设备凭据写入 **Codex Remote: Pair with Aether**,填写 Aether 地址和配对码。插件会把设备凭据写入
VS Code SecretStorage,并同时保持本机控制台连接。 VS Code SecretStorage,并同时保持本机控制台连接。
@@ -122,6 +122,18 @@ pub(crate) const ADMIN_MODULE_DEFINITIONS: &[AdminModuleDefinition] = &[
admin_menu_group: None, admin_menu_group: None,
admin_menu_order: 59, 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 { AdminModuleDefinition {
name: "s3_backup", name: "s3_backup",
display_name: "S3 备份", display_name: "S3 备份",
+6 -6
View File
@@ -275,8 +275,8 @@ export const messages = {
'nav.healthMonitor': '健康监控', 'nav.healthMonitor': '健康监控',
'nav.modelCatalog': '模型目录', 'nav.modelCatalog': '模型目录',
'nav.apiKeys': 'API 密钥', 'nav.apiKeys': 'API 密钥',
'nav.vscodex': 'Codex 远程控制', 'nav.vscodex': '远程控制',
'vscodex.title': 'Codex 远程控制', 'vscodex.title': '远程控制',
'vscodex.devices.label': '控制设备', 'vscodex.devices.label': '控制设备',
'vscodex.devices.refresh': '刷新设备', 'vscodex.devices.refresh': '刷新设备',
'vscodex.devices.loading': '正在加载设备...', 'vscodex.devices.loading': '正在加载设备...',
@@ -305,7 +305,7 @@ export const messages = {
'vscodex.connection.connecting': '正在建立安全连接...', 'vscodex.connection.connecting': '正在建立安全连接...',
'vscodex.connection.ticketFailed': '安全连接凭证获取失败。', 'vscodex.connection.ticketFailed': '安全连接凭证获取失败。',
'vscodex.connection.retry': '重试', 'vscodex.connection.retry': '重试',
'vscodex.frame.title': 'Codex 远程控制台', 'vscodex.frame.title': '远程控制台',
'nav.walletCenter': '钱包中心', 'nav.walletCenter': '钱包中心',
'nav.billingCenter': '套餐中心', 'nav.billingCenter': '套餐中心',
'nav.myReferral': '我的邀请', 'nav.myReferral': '我的邀请',
@@ -692,8 +692,8 @@ export const messages = {
'nav.healthMonitor': 'Health monitor', 'nav.healthMonitor': 'Health monitor',
'nav.modelCatalog': 'Model catalog', 'nav.modelCatalog': 'Model catalog',
'nav.apiKeys': 'API keys', 'nav.apiKeys': 'API keys',
'nav.vscodex': 'Codex remote control', 'nav.vscodex': 'Remote control',
'vscodex.title': 'Codex remote control', 'vscodex.title': 'Remote control',
'vscodex.devices.label': 'Control device', 'vscodex.devices.label': 'Control device',
'vscodex.devices.refresh': 'Refresh devices', 'vscodex.devices.refresh': 'Refresh devices',
'vscodex.devices.loading': 'Loading devices...', 'vscodex.devices.loading': 'Loading devices...',
@@ -722,7 +722,7 @@ export const messages = {
'vscodex.connection.connecting': 'Establishing a secure connection...', 'vscodex.connection.connecting': 'Establishing a secure connection...',
'vscodex.connection.ticketFailed': 'A secure connection ticket could not be issued.', 'vscodex.connection.ticketFailed': 'A secure connection ticket could not be issued.',
'vscodex.connection.retry': 'Retry', 'vscodex.connection.retry': 'Retry',
'vscodex.frame.title': 'Codex remote control surface', 'vscodex.frame.title': 'Remote control surface',
'nav.walletCenter': 'Wallet', 'nav.walletCenter': 'Wallet',
'nav.billingCenter': 'Plans', 'nav.billingCenter': 'Plans',
'nav.myReferral': 'Referrals', 'nav.myReferral': 'Referrals',
@@ -37,19 +37,27 @@ describe('main layout navigation builder', () => {
expect(navigation.flatMap(group => group.items.map(item => item.name))).toContain('tx:nav.myReferral') 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', () => { it('exposes remote control to users and active administrators', () => {
const commonOptions = { const userNavigation = buildNavigation({
canAccessAdmin: false,
modules: {}, modules: {},
isModuleActive: () => false, isModuleActive: () => false,
t: translate, t: translate,
}
const userNavigation = buildNavigation({
...commonOptions,
canAccessAdmin: false,
}) })
const adminNavigation = buildNavigation({ const adminNavigation = buildNavigation({
...commonOptions,
canAccessAdmin: true, 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<typeof buildNavigation>) => ( const findVscodeControl = (navigation: ReturnType<typeof buildNavigation>) => (
@@ -63,9 +71,14 @@ describe('main layout navigation builder', () => {
href: '/dashboard/vscodex', href: '/dashboard/vscodex',
}) })
expect(findVscodeControl(adminNavigation)).toMatchObject({ expect(findVscodeControl(adminNavigation)).toMatchObject({
name: 'tx:nav.vscodex', name: '远程控制',
href: '/dashboard/vscodex', 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', () => { it('builds admin navigation with dynamic module menu items sorted by menu order', () => {
@@ -133,13 +146,36 @@ describe('main layout navigation builder', () => {
expect(buildBreadcrumbs({ expect(buildBreadcrumbs({
route: route('/dashboard/vscodex'), route: route('/dashboard/vscodex'),
navigation, navigation: buildNavigation({
modules: {}, 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', isNavActive: href => href === '/dashboard/vscodex',
t: translate, t: translate,
})).toEqual([ })).toEqual([
expect.objectContaining({ label: expect.any(String) }), expect.objectContaining({ label: expect.any(String) }),
{ label: 'tx:nav.vscodex' }, { label: '远程控制' },
]) ])
}) })
}) })
@@ -51,6 +51,7 @@ const moduleIconMap: Record<string, LucideIcon> = {
Server, Server,
Send, Send,
SlidersHorizontal, SlidersHorizontal,
SquareTerminal,
CreditCard, CreditCard,
Gift, Gift,
} }
@@ -117,12 +118,12 @@ export function buildNavigation(options: {
title: t('nav.group.overview'), title: t('nav.group.overview'),
items: [ items: [
{ name: t('nav.dashboard'), href: '/admin/dashboard', icon: Home }, { 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.operations'), href: '/admin/operations', icon: Activity },
{ name: t('nav.healthMonitor'), href: '/admin/health-monitor', 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.userStats'), href: '/admin/user-stats', icon: BarChart3 },
{ name: t('nav.costAnalysis'), href: '/admin/cost-analysis', icon: Gauge }, { name: t('nav.costAnalysis'), href: '/admin/cost-analysis', icon: Gauge },
{ name: t('nav.performanceAnalysis'), href: '/admin/performance-analysis', icon: Activity }, { name: t('nav.performanceAnalysis'), href: '/admin/performance-analysis', icon: Activity },
...activeModuleItems(modules, 'overview'),
] ]
}, },
{ {
+14
View File
@@ -1138,6 +1138,20 @@ const MOCK_MODULE_DEFINITIONS: Array<Omit<ModuleStatus, 'active' | 'health'> & {
admin_menu_group: null, admin_menu_group: null,
admin_menu_order: 59, 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', name: 's3_backup',
display_name: 'S3 备份', display_name: 'S3 备份',