feat(web): localize and redesign operations console
This commit is contained in:
@@ -34,14 +34,53 @@ type ReadState =
|
||||
| { kind: 'error'; message: string; snapshot?: OverviewSnapshot };
|
||||
|
||||
const SECTIONS = [
|
||||
['device', 'Device'],
|
||||
['device', '设备'],
|
||||
['sim', 'SIM'],
|
||||
['network', 'Network'],
|
||||
['stats', 'Statistics'],
|
||||
['network', '网络'],
|
||||
['stats', '统计'],
|
||||
['cpu', 'CPU'],
|
||||
['connectivity', 'Connectivity'],
|
||||
['connectivity', '连接状态'],
|
||||
] as const;
|
||||
|
||||
const FIELD_LABELS: Readonly<Record<string, string>> = {
|
||||
Model: '型号',
|
||||
Uptime: '运行时间',
|
||||
Slots: '卡槽数',
|
||||
Active: '活跃数',
|
||||
Operator: '运营商',
|
||||
Registration: '注册状态',
|
||||
'Messages today': '今日消息数',
|
||||
Calls: '通话数',
|
||||
Usage: '使用率',
|
||||
Temperature: '温度',
|
||||
State: '状态',
|
||||
Latency: '延迟',
|
||||
};
|
||||
|
||||
const ENUM_LABELS: Readonly<Record<string, string>> = {
|
||||
connected: '已连接',
|
||||
disconnected: '未连接',
|
||||
connecting: '正在连接',
|
||||
registered: '已注册',
|
||||
unregistered: '未注册',
|
||||
searching: '正在搜索',
|
||||
roaming: '漫游中',
|
||||
online: '在线',
|
||||
offline: '离线',
|
||||
unknown: '未知',
|
||||
};
|
||||
|
||||
function displayFieldLabel(key: string): string {
|
||||
return FIELD_LABELS[key] ?? key;
|
||||
}
|
||||
|
||||
function displayFieldValue(value: OverviewFieldValue): string {
|
||||
if (value == null) return '不可用';
|
||||
if (typeof value === 'boolean') return value ? '是' : '否';
|
||||
if (typeof value === 'string') return ENUM_LABELS[value.toLocaleLowerCase()] ?? value;
|
||||
return String(value);
|
||||
}
|
||||
|
||||
function StructuredSection({ label, values }: { label: string; values: OverviewSection }) {
|
||||
const entries = Object.entries(values);
|
||||
return (
|
||||
@@ -51,13 +90,13 @@ function StructuredSection({ label, values }: { label: string; values: OverviewS
|
||||
<dl>
|
||||
{entries.map(([key, value]) => (
|
||||
<div key={key}>
|
||||
<dt>{key}</dt>
|
||||
<dd>{value == null ? 'Unavailable' : String(value)}</dd>
|
||||
<dt>{displayFieldLabel(key)}</dt>
|
||||
<dd>{displayFieldValue(value)}</dd>
|
||||
</div>
|
||||
))}
|
||||
</dl>
|
||||
) : (
|
||||
<p>No {label.toLocaleLowerCase()} data was supplied.</p>
|
||||
<p>未提供{label}数据。</p>
|
||||
)}
|
||||
</section>
|
||||
);
|
||||
@@ -103,7 +142,7 @@ export function OverviewSystemPage({
|
||||
if (request === requestOwner.current && !controller.signal.aborted)
|
||||
setState((current) => ({
|
||||
kind: 'error',
|
||||
message: 'Overview data could not be loaded.',
|
||||
message: '无法加载概览数据。',
|
||||
...(current.kind === 'loading' && current.snapshot
|
||||
? { snapshot: current.snapshot }
|
||||
: {}),
|
||||
@@ -116,15 +155,14 @@ export function OverviewSystemPage({
|
||||
if (instance.authentication === 'auth-required')
|
||||
return (
|
||||
<div className="state-panel state-error" role="alert">
|
||||
Authentication is required before overview data can be read for this instance.
|
||||
需要先完成认证,才能读取概览数据。
|
||||
</div>
|
||||
);
|
||||
|
||||
if (!dataSource)
|
||||
return (
|
||||
<div className="state-panel" role="status" aria-label="Overview unavailable">
|
||||
No safe overview read data source is available. This console will not invent or call an
|
||||
uncontracted production endpoint.
|
||||
<div className="state-panel" role="status" aria-label="概览不可用">
|
||||
没有可用的安全概览只读数据源。此控制台不会臆造或调用未签订契约的生产端点。
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -133,17 +171,17 @@ export function OverviewSystemPage({
|
||||
|
||||
if ((state.kind === 'loading' || state.kind === 'idle') && !retainedSnapshot)
|
||||
return (
|
||||
<p role="status" aria-label="Overview loading status">
|
||||
Loading overview…
|
||||
<p role="status" aria-label="概览加载状态">
|
||||
正在加载概览…
|
||||
</p>
|
||||
);
|
||||
|
||||
if (state.kind === 'error' && !state.snapshot)
|
||||
return (
|
||||
<div className="state-panel state-error" role="alert">
|
||||
<p>Unable to load overview: {state.message}</p>
|
||||
<p>无法加载概览: {state.message}</p>
|
||||
<button type="button" onClick={() => setRetry((value) => value + 1)}>
|
||||
Retry loading overview
|
||||
重试加载概览
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
@@ -153,32 +191,29 @@ export function OverviewSystemPage({
|
||||
|
||||
return (
|
||||
<div className="overview-system">
|
||||
{state.kind === 'loading' ? <p role="status">Refreshing overview…</p> : null}
|
||||
{state.kind === 'loading' ? <p role="status">正在刷新概览…</p> : null}
|
||||
{state.kind === 'error' ? (
|
||||
<div className="state-panel state-error" role="alert">
|
||||
<p>Refresh failed; showing the last known overview.</p>
|
||||
<p>刷新失败,正在显示上次已知的概览数据。</p>
|
||||
<button type="button" onClick={() => setRetry((value) => value + 1)}>
|
||||
Retry loading overview
|
||||
重试加载概览
|
||||
</button>
|
||||
</div>
|
||||
) : null}
|
||||
{instance.freshness !== 'fresh' ? (
|
||||
<p className="state-panel" role="status" aria-label="Overview freshness">
|
||||
Overview data is {instance.freshness}; verify freshness before relying on these values.
|
||||
<p className="state-panel" role="status" aria-label="概览数据新鲜度">
|
||||
概览数据可能已过期;使用这些值前请先核实数据新鲜度。
|
||||
</p>
|
||||
) : null}
|
||||
{snapshot.observedAt ? <p>Observed {snapshot.observedAt}</p> : null}
|
||||
{snapshot.observedAt ? <p>观测时间:{snapshot.observedAt}</p> : null}
|
||||
<div className="overview-grid">
|
||||
{SECTIONS.map(([key, label]) => (
|
||||
<StructuredSection key={key} label={label} values={snapshot[key]} />
|
||||
))}
|
||||
</div>
|
||||
<section className="state-panel" aria-label="System actions">
|
||||
<h2>System actions</h2>
|
||||
<p>
|
||||
Restart is unavailable because the backend R3 restart operation is not implemented. No
|
||||
action is offered.
|
||||
</p>
|
||||
<section className="state-panel" aria-label="系统操作">
|
||||
<h2>系统操作</h2>
|
||||
<p>重启不可用,因为后端尚未实现 R3 重启操作,因此不提供任何操作。</p>
|
||||
</section>
|
||||
</div>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user