diff --git a/apps/web/index.html b/apps/web/index.html index d5a789d..3a84cd0 100644 --- a/apps/web/index.html +++ b/apps/web/index.html @@ -1,10 +1,10 @@ - + - - Multi SimAdmin + + 多实例 SimAdmin 管理台
diff --git a/apps/web/src/app-shell.integration.test.tsx b/apps/web/src/app-shell.integration.test.tsx index 57c8602..5f32a13 100644 --- a/apps/web/src/app-shell.integration.test.tsx +++ b/apps/web/src/app-shell.integration.test.tsx @@ -20,7 +20,7 @@ const snapshot: FleetSnapshot = { { id: 'bravo', name: 'Bravo', - url: 'http://user:secret@bravo.example:8080/private', + url: 'http://user:***@bravo.example:8080/private', tags: ['west'], }, { id: 'alpha', name: 'Alpha', url: 'https://alpha.example/admin' }, @@ -69,8 +69,8 @@ describe('React AppShell and Fleet vertical slice', () => { const dataSource = source(() => new Promise((done) => (resolve = done))); render(); - expect(screen.getByRole('status', { name: 'Fleet loading status' }).textContent).toContain( - 'Loading instances', + expect(screen.getByRole('status', { name: '实例加载状态' }).textContent).toContain( + '正在加载实例', ); resolve(snapshot); @@ -80,10 +80,10 @@ describe('React AppShell and Fleet vertical slice', () => { ); expect( within(screen.getByRole('row', { name: /Bravo/ })) - .getByRole('link', { name: 'Open Bravo origin' }) + .getByRole('link', { name: '打开 Bravo 的源站' }) .getAttribute('href'), ).toBe('http://bravo.example:8080'); - expect(screen.getByText('Version 0.1.0')).toBeTruthy(); + expect(screen.getByText('版本 0.1.0')).toBeTruthy(); expect( consoleError.mock.calls.some((call) => call.some((argument) => String(argument).includes('unique "key" prop')), @@ -96,23 +96,23 @@ describe('React AppShell and Fleet vertical slice', () => { render( snapshot)} />); await screen.findByRole('row', { name: /Alpha/ }); - await user.click(screen.getByRole('button', { name: /Sort by latency/i })); + await user.click(screen.getByRole('button', { name: /按延迟排序/i })); let rows = screen.getAllByRole('row').slice(1); expect(rows[0]?.textContent).toContain('Alpha'); - await user.click(screen.getByRole('button', { name: /Sort by latency/i })); + await user.click(screen.getByRole('button', { name: /按延迟排序/i })); rows = screen.getAllByRole('row').slice(1); expect(rows[0]?.textContent).toContain('Bravo'); - await user.selectOptions(screen.getByRole('combobox', { name: 'Status' }), 'auth'); + await user.selectOptions(screen.getByRole('combobox', { name: '状态' }), 'auth'); expect(screen.queryByRole('row', { name: /Alpha/ })).toBeNull(); - await user.click(screen.getByRole('checkbox', { name: 'Select all visible instances' })); - expect( - (screen.getByRole('checkbox', { name: 'Select Bravo' }) as HTMLInputElement).checked, - ).toBe(true); - expect(screen.getByText('1 selected')).toBeTruthy(); + await user.click(screen.getByRole('checkbox', { name: '选择当前页全部实例' })); + expect((screen.getByRole('checkbox', { name: '选择 Bravo' }) as HTMLInputElement).checked).toBe( + true, + ); + expect(screen.getByText('已选择 1 项')).toBeTruthy(); - await user.type(screen.getByRole('searchbox', { name: 'Search instances' }), 'missing'); - expect(screen.getByText(/No instances match your search/i)).toBeTruthy(); + await user.type(screen.getByRole('searchbox', { name: '搜索实例' }), 'missing'); + expect(screen.getByText(/没有实例符合搜索与筛选条件/i)).toBeTruthy(); }); it('exposes error, retry, configured-empty, and unsafe-origin states without claiming a backend', async () => { @@ -126,10 +126,10 @@ describe('React AppShell and Fleet vertical slice', () => { }); const first = render(); - expect((await screen.findByRole('alert')).textContent).toContain('collector unavailable'); - await user.click(screen.getByRole('button', { name: 'Retry loading instances' })); + expect((await screen.findByRole('alert')).textContent).toContain('实例加载失败'); + await user.click(screen.getByRole('button', { name: '重试加载实例' })); expect(await screen.findByRole('row', { name: /Unsafe/ })).toBeTruthy(); - expect(screen.queryByRole('link', { name: 'Open Unsafe origin' })).toBeNull(); + expect(screen.queryByRole('link', { name: '打开 Unsafe 的源站' })).toBeNull(); first.unmount(); const { unmount } = render( @@ -138,7 +138,7 @@ describe('React AppShell and Fleet vertical slice', () => { fleetDataSource={source(async () => ({ instances: [], statuses: new Map() }))} />, ); - expect(await screen.findByText(/No instances are configured/i)).toBeTruthy(); + expect(await screen.findByText(/尚未配置实例/i)).toBeTruthy(); unmount(); }); @@ -147,20 +147,20 @@ describe('React AppShell and Fleet vertical slice', () => { render( snapshot)} />); await screen.findByRole('row', { name: /Alpha/ }); - await user.selectOptions(screen.getByRole('combobox', { name: 'Capability' }), 'sms'); + await user.selectOptions(screen.getByRole('combobox', { name: '能力' }), 'sms'); expect(screen.queryByRole('row', { name: /Alpha/ })).toBeNull(); expect(screen.getByRole('row', { name: /Bravo/ }).textContent).toContain('clock drift'); - await user.click(screen.getByRole('button', { name: 'Choose columns' })); - await user.click(screen.getByRole('checkbox', { name: 'Show anomalies column' })); - expect(screen.queryByRole('columnheader', { name: 'Anomalies' })).toBeNull(); + await user.click(screen.getByRole('button', { name: '选择列' })); + await user.click(screen.getByRole('checkbox', { name: '显示异常列' })); + expect(screen.queryByRole('columnheader', { name: '异常' })).toBeNull(); - await user.click(screen.getByRole('checkbox', { name: 'Select Bravo' })); - expect( - (screen.getByRole('button', { name: 'Batch actions' }) as HTMLButtonElement).disabled, - ).toBe(false); - await user.click(screen.getByRole('button', { name: 'Batch actions' })); - expect(screen.getByText('Choose an action for 1 selected instance.')).toBeTruthy(); + await user.click(screen.getByRole('checkbox', { name: '选择 Bravo' })); + expect((screen.getByRole('button', { name: '批量操作' }) as HTMLButtonElement).disabled).toBe( + false, + ); + await user.click(screen.getByRole('button', { name: '批量操作' })); + expect(screen.getByText('请为已选择的 1 项选择操作。')).toBeTruthy(); }); it('paginates fleet rows and selects only the current page', async () => { @@ -177,12 +177,12 @@ describe('React AppShell and Fleet vertical slice', () => { await screen.findByRole('row', { name: /Instance 01/ }); expect(screen.queryByRole('row', { name: /Instance 11/ })).toBeNull(); - expect(screen.getByText('Page 1 of 2')).toBeTruthy(); - await user.click(screen.getByRole('checkbox', { name: 'Select all visible instances' })); - expect(screen.getByText('10 selected')).toBeTruthy(); - await user.click(screen.getByRole('button', { name: 'Next page' })); + expect(screen.getByText('第 1 页,共 2 页')).toBeTruthy(); + await user.click(screen.getByRole('checkbox', { name: '选择当前页全部实例' })); + expect(screen.getByText('已选择 10 项')).toBeTruthy(); + await user.click(screen.getByRole('button', { name: '下一页' })); expect(await screen.findByRole('row', { name: /Instance 11/ })).toBeTruthy(); - expect(screen.getByText('Page 2 of 2')).toBeTruthy(); + expect(screen.getByText('第 2 页,共 2 页')).toBeTruthy(); }); it('binds instance context to the route owner and never leaks mismatched context', () => { @@ -197,27 +197,27 @@ describe('React AppShell and Fleet vertical slice', () => { const { rerender } = render( , ); - expect(screen.getByRole('heading', { name: 'Messages' })).toBeTruthy(); + expect(screen.getByRole('heading', { name: '消息' })).toBeTruthy(); expect(screen.getByText('Owner modem')).toBeTruthy(); - expect(screen.getByRole('link', { name: 'Open original site' }).getAttribute('href')).toBe( + expect(screen.getByRole('link', { name: '打开源站' }).getAttribute('href')).toBe( 'https://owner.example', ); rerender(); expect(screen.queryByText('Owner modem')).toBeNull(); - expect(screen.getByText(/Instance context is unavailable/i)).toBeTruthy(); + expect(screen.getByText(/此路由缺少实例上下文/i)).toBeTruthy(); }); it.each([ { pathname: '/jobs', endpoint: '/api/v1/jobs?page=1&pageSize=25&sort=createdAt&direction=desc', - emptyMessage: /No jobs match the current query/i, + emptyMessage: /没有任务符合当前查询/i, }, { pathname: '/audit', endpoint: '/api/v1/audit?sort=occurredAt&direction=desc&page=1&pageSize=25', - emptyMessage: /No audit events match the current query/i, + emptyMessage: /没有审计事件符合当前查询/i, }, ])( 'uses the default HTTP data source for $pathname', @@ -247,12 +247,12 @@ describe('React AppShell and Fleet vertical slice', () => { { pathname: '/jobs', sourceProp: 'jobsDataSource' as const, - emptyMessage: /No jobs match the current query/i, + emptyMessage: /没有任务符合当前查询/i, }, { pathname: '/audit', sourceProp: 'auditDataSource' as const, - emptyMessage: /No audit events match the current query/i, + emptyMessage: /没有审计事件符合当前查询/i, }, ])( 'uses an explicitly injected data source instead of global fetch for $pathname', diff --git a/apps/web/src/app-shell.tsx b/apps/web/src/app-shell.tsx index 445e20e..8d7bc67 100644 --- a/apps/web/src/app-shell.tsx +++ b/apps/web/src/app-shell.tsx @@ -242,19 +242,16 @@ function Page({ if (route.kind === 'settings-system') return (
-

System settings

-

- System settings are unavailable because the control plane does not expose a settings - contract. -

+

系统设置

+

控制平面尚未提供系统设置契约,因此此功能暂不可用。

); if (route.kind === 'not-found') return (
-

Page not found

-

The requested console page does not exist.

- Return to Fleet +

页面未找到

+

请求的控制台页面不存在。

+ 返回实例总览
); if (route.kind.startsWith('instance-')) { @@ -369,19 +366,19 @@ function Page({ ); } const labels: Partial> = { - 'instance-new': 'Add instance', - jobs: 'Jobs', - 'job-detail': 'Job details', - audit: 'Audit', - 'audit-detail': 'Audit event', - 'settings-instances': 'Instance settings', - 'settings-instance-detail': 'Instance settings', - 'settings-system': 'System settings', + 'instance-new': '添加实例', + jobs: '任务', + 'job-detail': '任务详情', + audit: '审计', + 'audit-detail': '审计事件', + 'settings-instances': '实例设置', + 'settings-instance-detail': '实例设置', + 'settings-system': '系统设置', }; return (
-

{labels[route.kind] ?? 'Multi SimAdmin'}

-

This route is ready for structured data and actions.

+

{labels[route.kind] ?? '多实例 SimAdmin 管理台'}

+

此页面已准备好展示结构化数据与操作。

); } @@ -429,24 +426,24 @@ export function AppShell({ return (
- Skip to main content + 跳到主要内容
- Multi SimAdmin + 多实例 SimAdmin 管理台 - Console - Version {version} + 控制台 + 版本 {version}
-