feat(web): localize and redesign operations console
This commit is contained in:
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user