feat(web): redesign fleet cards and instance workspace
This commit is contained in:
@@ -82,8 +82,7 @@ const messageDirection = (value: string): string =>
|
||||
: value === 'outgoing' || value === 'sent'
|
||||
? '发送'
|
||||
: '未知方向';
|
||||
const messageExcerpt = (value: string): string =>
|
||||
value.length > 36 ? `${value.slice(0, 36)}…` : value;
|
||||
|
||||
const messageTime = (value: string): string => {
|
||||
const date = new Date(value);
|
||||
return Number.isNaN(date.getTime()) ? value : date.toLocaleString('zh-CN');
|
||||
@@ -198,9 +197,22 @@ export function FleetPage({
|
||||
[auth, capability, filter, page, query, selectedIds, snapshot, sort, tag, version],
|
||||
);
|
||||
|
||||
const fleetSummary = useMemo(() => {
|
||||
const statuses = [...(snapshot?.statuses.values() ?? [])];
|
||||
const online = statuses.filter(
|
||||
(status) => status.reachable && status.authenticated !== false,
|
||||
).length;
|
||||
return {
|
||||
total: snapshot?.instances.length ?? 0,
|
||||
online,
|
||||
attention: (snapshot?.instances.length ?? 0) - online,
|
||||
};
|
||||
}, [snapshot]);
|
||||
|
||||
useEffect(() => {
|
||||
if (selectAllRef.current)
|
||||
if (selectAllRef.current) {
|
||||
selectAllRef.current.indeterminate = model.visibleSelection.indeterminate;
|
||||
}
|
||||
}, [model.visibleSelection.indeterminate]);
|
||||
useEffect(() => {
|
||||
if (page !== model.page) setPage(model.page);
|
||||
@@ -332,6 +344,22 @@ export function FleetPage({
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
{snapshot ? (
|
||||
<section className="fleet-status-summary" aria-label="实例状态摘要">
|
||||
<div>
|
||||
<span>实例总数</span>
|
||||
<strong>{fleetSummary.total}</strong>
|
||||
</div>
|
||||
<div>
|
||||
<span>在线</span>
|
||||
<strong>{fleetSummary.online}</strong>
|
||||
</div>
|
||||
<div>
|
||||
<span>需处理</span>
|
||||
<strong>{fleetSummary.attention}</strong>
|
||||
</div>
|
||||
</section>
|
||||
) : null}
|
||||
{batchOpen ? (
|
||||
<div className="batch-entry" role="region" aria-label="批量操作入口">
|
||||
请为已选择的 {model.selectedIds.length} 项选择操作。
|
||||
@@ -465,6 +493,7 @@ export function FleetPage({
|
||||
>
|
||||
<h2>{row.displayName}</h2>
|
||||
<code>{row.id}</code>
|
||||
<span className="fleet-card-entry-label">进入仪表盘</span>
|
||||
</a>
|
||||
<span className={`status status-${row.statusKind}`}>
|
||||
{STATUS_LABELS[row.statusKind]}
|
||||
@@ -479,11 +508,39 @@ export function FleetPage({
|
||||
</div>
|
||||
<div>
|
||||
<dt>CPU</dt>
|
||||
<dd>{percent(row.status?.summary?.resources?.cpuPercent)}</dd>
|
||||
<dd>
|
||||
{row.status?.summary?.resources?.cpuPercent === undefined ? (
|
||||
'暂未获取'
|
||||
) : (
|
||||
<>
|
||||
<meter
|
||||
min="0"
|
||||
max="100"
|
||||
value={row.status.summary.resources.cpuPercent}
|
||||
aria-label="CPU 使用率"
|
||||
/>
|
||||
{percent(row.status.summary.resources.cpuPercent)}
|
||||
</>
|
||||
)}
|
||||
</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt>内存</dt>
|
||||
<dd>{percent(row.status?.summary?.resources?.memoryPercent)}</dd>
|
||||
<dd>
|
||||
{row.status?.summary?.resources?.memoryPercent === undefined ? (
|
||||
'暂未获取'
|
||||
) : (
|
||||
<>
|
||||
<meter
|
||||
min="0"
|
||||
max="100"
|
||||
value={row.status.summary.resources.memoryPercent}
|
||||
aria-label="内存使用率"
|
||||
/>
|
||||
{percent(row.status.summary.resources.memoryPercent)}
|
||||
</>
|
||||
)}
|
||||
</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt>最高温度</dt>
|
||||
@@ -511,10 +568,6 @@ export function FleetPage({
|
||||
<dt>号码</dt>
|
||||
<dd>{messageStates.get(row.id)!.latest!.phoneNumber}</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt>内容</dt>
|
||||
<dd>{messageExcerpt(messageStates.get(row.id)!.latest!.content)}</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt>时间</dt>
|
||||
<dd>{messageTime(messageStates.get(row.id)!.latest!.timestamp)}</dd>
|
||||
@@ -524,7 +577,11 @@ export function FleetPage({
|
||||
<p className="fleet-card-sms-empty">暂无短信</p>
|
||||
)}
|
||||
</section>
|
||||
<div className="fleet-card-actions fleet-card-admin-actions">
|
||||
<div
|
||||
className="fleet-card-actions fleet-card-admin-actions"
|
||||
role="group"
|
||||
aria-label="实例管理操作"
|
||||
>
|
||||
<a
|
||||
href={`/settings/instances/${encodeURIComponent(row.id)}`}
|
||||
aria-label={`编辑 ${row.displayName}`}
|
||||
@@ -665,22 +722,7 @@ export function FleetPage({
|
||||
tags: <td>{row.tags.join(', ') || '—'}</td>,
|
||||
freshness: <td>{FRESHNESS_LABELS[row.freshness] ?? row.freshness}</td>,
|
||||
anomalies: <td>{row.anomalies.join(', ') || '—'}</td>,
|
||||
origin: (
|
||||
<td>
|
||||
{origin ? (
|
||||
<a
|
||||
href={origin}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
aria-label={`打开 ${row.displayName} 的源站`}
|
||||
>
|
||||
{origin}
|
||||
</a>
|
||||
) : (
|
||||
<span>源地址无效</span>
|
||||
)}
|
||||
</td>
|
||||
),
|
||||
origin: <td>{origin ? <span>{origin}</span> : <span>源地址无效</span>}</td>,
|
||||
};
|
||||
return (
|
||||
<tr key={row.id} aria-selected={row.selected}>
|
||||
|
||||
Reference in New Issue
Block a user