feat(web): localize and redesign operations console
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
import { useEffect, useRef, useState, type ReactNode } from 'react';
|
||||
|
||||
import type { InstanceContext } from '../app-shell.js';
|
||||
import { displayValue } from '../ui/locale.js';
|
||||
|
||||
/** Bounded primitives permitted at the Messages presentation boundary. */
|
||||
export type MessageMetadataValue = string | number | boolean | null;
|
||||
@@ -47,33 +48,37 @@ type ReadState =
|
||||
| { kind: 'ready'; ownerId: string; snapshot: MessagesSnapshot }
|
||||
| { kind: 'error'; ownerId: string; snapshot?: MessagesSnapshot };
|
||||
|
||||
const SAFE_LOAD_ERROR = 'Messages data could not be loaded.';
|
||||
const SAFE_LOAD_ERROR = '无法加载消息数据。';
|
||||
|
||||
const AGGREGATE_FIELDS = [
|
||||
['Total', 'total'],
|
||||
['Inbound', 'inbound'],
|
||||
['Outbound', 'outbound'],
|
||||
['Unread', 'unread'],
|
||||
['Failed', 'failed'],
|
||||
['Queued', 'queued'],
|
||||
['Last activity', 'lastActivityAt'],
|
||||
['总计', 'total'],
|
||||
['接收', 'inbound'],
|
||||
['发送', 'outbound'],
|
||||
['未读', 'unread'],
|
||||
['失败', 'failed'],
|
||||
['排队中', 'queued'],
|
||||
['最近活动', 'lastActivityAt'],
|
||||
] as const;
|
||||
|
||||
function display(value: MessageMetadataValue | undefined): string {
|
||||
return value == null ? 'Unavailable' : String(value);
|
||||
function displayRaw(value: MessageMetadataValue | undefined): string {
|
||||
return value == null ? '不可用' : String(value);
|
||||
}
|
||||
|
||||
function Fields({
|
||||
values,
|
||||
}: {
|
||||
values: readonly (readonly [label: string, value: MessageMetadataValue | undefined])[];
|
||||
values: readonly (readonly [
|
||||
label: string,
|
||||
value: MessageMetadataValue | undefined,
|
||||
localized?: boolean,
|
||||
])[];
|
||||
}) {
|
||||
return (
|
||||
<dl>
|
||||
{values.map(([label, value]) => (
|
||||
{values.map(([label, value, localized]) => (
|
||||
<div key={label}>
|
||||
<dt>{label}</dt>
|
||||
<dd>{display(value)}</dd>
|
||||
<dd>{localized ? displayValue(value) : displayRaw(value)}</dd>
|
||||
</div>
|
||||
))}
|
||||
</dl>
|
||||
@@ -99,34 +104,34 @@ function aggregateFields(
|
||||
function SnapshotView({ snapshot }: { snapshot: MessagesSnapshot }) {
|
||||
return (
|
||||
<>
|
||||
{snapshot.observedAt ? <p>Observed {snapshot.observedAt}</p> : null}
|
||||
<p>Aggregate metadata only; sensitive payload and addressing details are excluded.</p>
|
||||
{snapshot.observedAt ? <p>观测时间:{snapshot.observedAt}</p> : null}
|
||||
<p>仅显示汇总元数据;敏感内容和寻址详情已排除。</p>
|
||||
<div className="messages-grid">
|
||||
<Section label="SMS aggregate">
|
||||
<Section label="短信汇总">
|
||||
<Fields values={aggregateFields(snapshot.sms)} />
|
||||
</Section>
|
||||
<Section label="Device message aggregates">
|
||||
<Section label="设备消息汇总">
|
||||
{snapshot.devices.length ? (
|
||||
snapshot.devices.map((device, index) => (
|
||||
<article key={`${device.deviceId ?? device.label ?? 'device'}-${index}`}>
|
||||
<Fields
|
||||
values={[
|
||||
['Device ID', device.deviceId],
|
||||
['Label', device.label],
|
||||
['State', device.state],
|
||||
['设备 ID', device.deviceId],
|
||||
['标签', device.label],
|
||||
['状态', device.state, true],
|
||||
...aggregateFields(device),
|
||||
]}
|
||||
/>
|
||||
</article>
|
||||
))
|
||||
) : (
|
||||
<p>No device message aggregates were supplied.</p>
|
||||
<p>未提供设备消息汇总。</p>
|
||||
)}
|
||||
</Section>
|
||||
</div>
|
||||
<section className="state-panel" aria-label="Messages actions">
|
||||
<h2>Messages actions</h2>
|
||||
<p>Sending, deleting, and changing messages are unavailable in this read-only module.</p>
|
||||
<section className="state-panel" aria-label="消息操作">
|
||||
<h2>消息操作</h2>
|
||||
<p>此只读模块不支持发送、删除或修改消息。</p>
|
||||
</section>
|
||||
</>
|
||||
);
|
||||
@@ -183,16 +188,15 @@ export function MessagesModule({ instance, dataSource, refreshSignal }: Messages
|
||||
if (instance.authentication !== 'authenticated') {
|
||||
return (
|
||||
<div className="state-panel state-error" role="alert">
|
||||
Authentication is required before Messages data can be read for this instance.
|
||||
需要先完成认证,才能读取消息数据。
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (!dataSource) {
|
||||
return (
|
||||
<div className="state-panel" role="status" aria-label="Messages unavailable">
|
||||
No safe Messages 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>
|
||||
);
|
||||
}
|
||||
@@ -204,8 +208,8 @@ export function MessagesModule({ instance, dataSource, refreshSignal }: Messages
|
||||
|
||||
if ((state.kind === 'idle' || state.kind === 'loading') && !retainedSnapshot) {
|
||||
return (
|
||||
<p role="status" aria-label="Messages loading status">
|
||||
Loading Messages…
|
||||
<p role="status" aria-label="消息加载状态">
|
||||
正在加载消息…
|
||||
</p>
|
||||
);
|
||||
}
|
||||
@@ -213,9 +217,9 @@ export function MessagesModule({ instance, dataSource, refreshSignal }: Messages
|
||||
if (state.kind === 'error' && !retainedSnapshot) {
|
||||
return (
|
||||
<div className="state-panel state-error" role="alert">
|
||||
<p>Unable to load Messages: {SAFE_LOAD_ERROR}</p>
|
||||
<p>{SAFE_LOAD_ERROR}</p>
|
||||
<button type="button" onClick={() => setRetry((value) => value + 1)}>
|
||||
Retry loading Messages
|
||||
重试加载消息
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
@@ -227,18 +231,18 @@ export function MessagesModule({ instance, dataSource, refreshSignal }: Messages
|
||||
|
||||
return (
|
||||
<div className="messages-module">
|
||||
{state.kind === 'loading' ? <p role="status">Refreshing Messages…</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 Messages data.</p>
|
||||
<p>刷新失败,正在显示上次已知的消息数据。</p>
|
||||
<button type="button" onClick={() => setRetry((value) => value + 1)}>
|
||||
Retry loading Messages
|
||||
重试加载消息
|
||||
</button>
|
||||
</div>
|
||||
) : null}
|
||||
{instance.freshness !== 'fresh' ? (
|
||||
<p className="state-panel" role="status" aria-label="Messages freshness">
|
||||
Messages data is {instance.freshness}; verify freshness before relying on these values.
|
||||
<p className="state-panel" role="status" aria-label="消息数据新鲜度">
|
||||
消息数据可能已过期;使用这些值前请核实数据新鲜度。
|
||||
</p>
|
||||
) : null}
|
||||
<SnapshotView snapshot={currentSnapshot} />
|
||||
|
||||
Reference in New Issue
Block a user