feat(web): localize and redesign operations console

This commit is contained in:
chick
2026-07-18 22:22:41 +08:00
parent d575ab4d9d
commit abfd07f8a3
34 changed files with 1536 additions and 1058 deletions
@@ -49,7 +49,7 @@ const snapshot: FleetSnapshot = {
authenticated: true,
latencyMs: 12,
summary: {
capabilities: ['messages', 'calls', 7],
capabilities: ['messages', 'calls', 'vendor-capability', 7],
freshness: 'fresh',
password: 'never-render',
credentialConfigured: true,
@@ -79,11 +79,11 @@ describe('Settings instances page', () => {
it('is explicitly unavailable without an injected fleet source', () => {
render(<InstanceSettingsPage />);
expect(screen.getByRole('heading', { name: 'Instances' })).toBeTruthy();
expect(screen.getByRole('status', { name: 'Instances unavailable' }).textContent).toMatch(
/no fleet data source/i,
expect(screen.getByRole('heading', { name: '实例' })).toBeTruthy();
expect(screen.getByRole('status', { name: '实例不可用' }).textContent).toMatch(
/未提供实例总览数据源/i,
);
expect(screen.getByRole('link', { name: 'Add instance' }).getAttribute('href')).toBe(
expect(screen.getByRole('link', { name: '添加实例' }).getAttribute('href')).toBe(
'/instances/new',
);
});
@@ -91,24 +91,25 @@ describe('Settings instances page', () => {
it('loads and renders an accessible, safe settings list using only fleet fields', async () => {
const pending = deferredSource();
render(<InstanceSettingsPage dataSource={pending.source} />);
expect(screen.getByRole('status', { name: 'Instances loading status' })).toBeTruthy();
expect(screen.getByRole('status', { name: '实例加载状态' })).toBeTruthy();
expect(pending.load).toHaveBeenCalledWith(expect.any(AbortSignal));
pending.resolve(snapshot);
const list = await screen.findByRole('list', { name: 'Configured instances' });
const list = await screen.findByRole('list', { name: '已配置实例' });
const west = within(list).getByRole('listitem', { name: 'West modem' });
expect(within(west).getByRole('link', { name: 'West modem' }).getAttribute('href')).toBe(
'/settings/instances/west%2Fone',
);
const origin = within(west).getByRole('link', { name: 'Open West modem origin' });
const origin = within(west).getByRole('link', { name: '打开 West modem 的源站' });
expect(origin.getAttribute('href')).toBe('https://west.example:8443');
expect(origin.getAttribute('rel')).toBe('noopener noreferrer');
for (const value of ['Online', 'Authenticated', 'Fresh', 'messages, calls'])
for (const value of ['在线', '已认证', '最新', '消息, 通话, vendor-capability'])
expect(within(west).getByText(value)).toBeTruthy();
expect(within(west).queryByText('messages, calls')).toBeNull();
const offline = within(list).getByRole('listitem', { name: 'offline' });
expect(within(offline).getByText('Invalid origin')).toBeTruthy();
expect(within(offline).getByText('Offline')).toBeTruthy();
expect(within(offline).getByText('源地址无效')).toBeTruthy();
expect(within(offline).getByText('离线')).toBeTruthy();
expect(within(offline).queryByText(/authentication|freshness|capabilities/i)).toBeNull();
expect(document.body.textContent).not.toMatch(
@@ -124,10 +125,10 @@ describe('Settings instances page', () => {
.mockResolvedValueOnce({ instances: [], statuses: new Map() });
render(<InstanceSettingsPage dataSource={{ load }} />);
const alert = await screen.findByRole('alert');
expect(alert.textContent).toBe('Instances could not be loaded.Retry loading instances');
expect(alert.textContent).toBe('无法加载实例。重试加载实例');
expect(document.body.textContent).not.toContain('top-secret');
await user.click(screen.getByRole('button', { name: 'Retry loading instances' }));
expect(await screen.findByText('No instances are configured.')).toBeTruthy();
await user.click(screen.getByRole('button', { name: '重试加载实例' }));
expect(await screen.findByText('尚未配置实例。')).toBeTruthy();
expect(load).toHaveBeenCalledTimes(2);
});
@@ -114,19 +114,39 @@ export function sanitizeFleetSnapshot(value: unknown): SafeSnapshot | null {
function statusLabel(status: SafeStatus | undefined): string | null {
if (!status) return null;
if (!status.reachable) return 'Offline';
if (status.authenticated === false) return 'Authentication required';
return 'Online';
if (!status.reachable) return '离线';
if (status.authenticated === false) return '需要认证';
return '在线';
}
function authLabel(status: SafeStatus | undefined): string | null {
if (status?.authenticated === true) return 'Authenticated';
if (status?.authenticated === false) return 'Authentication required';
if (status?.authenticated === true) return '已认证';
if (status?.authenticated === false) return '需要认证';
return null;
}
function titleCase(value: string): string {
return value[0]!.toUpperCase() + value.slice(1);
function freshnessLabel(value: string): string {
return (
({ fresh: '最新', stale: '可能过期', unknown: '未知' } as Readonly<Record<string, string>>)[
value
] ?? value
);
}
const CAPABILITY_LABELS: Readonly<Record<string, string>> = {
overview: '概览',
cellular: '蜂窝网络',
'device-network': '设备网络',
messages: '消息',
calls: '通话',
esim: 'eSIM',
notifications: '通知',
automation: '自动化',
ota: 'OTA',
};
function capabilityLabel(value: string): string {
return CAPABILITY_LABELS[value] ?? value;
}
export function InstanceSettingsPage({
@@ -179,37 +199,35 @@ export function InstanceSettingsPage({
<section aria-labelledby="settings-instances-title">
<header>
<div>
<h1 id="settings-instances-title">Instances</h1>
<p>Configure the SimAdmin instances available to this workspace.</p>
<h1 id="settings-instances-title"></h1>
<p> SimAdmin </p>
</div>
<a href="/instances/new">Add instance</a>
<a href="/instances/new"></a>
</header>
{unavailable ? (
<p role="status" aria-label="Instances unavailable">
Instances are runtime-unavailable because no fleet data source was provided.
<p role="status" aria-label="实例不可用">
</p>
) : null}
{loading ? (
<p role="status" aria-label="Instances loading status">
Loading instances
<p role="status" aria-label="实例加载状态">
</p>
) : null}
{failed ? (
<div role="alert">
<p>Instances could not be loaded.</p>
<p></p>
{dataSource ? (
<button type="button" onClick={() => setAttempt((value) => value + 1)}>
Retry loading instances
</button>
) : null}
</div>
) : null}
{!loading && !failed && snapshot?.instances.length === 0 ? (
<p>No instances are configured.</p>
) : null}
{!loading && !failed && snapshot?.instances.length === 0 ? <p></p> : null}
{!loading && !failed && snapshot && snapshot.instances.length > 0 ? (
<ul aria-label="Configured instances">
<ul aria-label="已配置实例">
{snapshot.instances.map((instance) => {
const displayName = instance.name ?? instance.id;
const origin = canonicalHttpOrigin(instance.url);
@@ -226,44 +244,44 @@ export function InstanceSettingsPage({
{instance.name ? <p>{instance.id}</p> : null}
<dl>
<div>
<dt>Origin</dt>
<dt></dt>
<dd>
{origin ? (
<a
href={origin}
target="_blank"
rel="noopener noreferrer"
aria-label={`Open ${displayName} origin`}
aria-label={`打开 ${displayName} 的源站`}
>
{origin}
</a>
) : (
'Invalid origin'
'源地址无效'
)}
</dd>
</div>
{state ? (
<div>
<dt>Status</dt>
<dt></dt>
<dd>{state}</dd>
</div>
) : null}
{authentication ? (
<div>
<dt>Authentication</dt>
<dt></dt>
<dd>{authentication}</dd>
</div>
) : null}
{status?.freshness ? (
<div>
<dt>Freshness</dt>
<dd>{titleCase(status.freshness)}</dd>
<dt></dt>
<dd>{freshnessLabel(status.freshness)}</dd>
</div>
) : null}
{status?.capabilities?.length ? (
<div>
<dt>Capabilities</dt>
<dd>{status.capabilities.join(', ')}</dd>
<dt></dt>
<dd>{status.capabilities.map(capabilityLabel).join(', ')}</dd>
</div>
) : null}
</dl>