feat(web): redesign fleet cards and instance workspace

This commit is contained in:
chick
2026-07-19 18:54:26 +08:00
parent f2803896a8
commit 49ee4e6570
9 changed files with 458 additions and 74 deletions
+68 -26
View File
@@ -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}>