feat: unify operations health rail

This commit is contained in:
lingniu
2026-07-19 09:41:36 +08:00
parent 39c828ed32
commit a6f6ce260f
4 changed files with 100 additions and 291 deletions

View File

@@ -223,8 +223,9 @@ test('reconciles service identities with bound and identity-required vehicles',
expect(screen.getByText('全部协议达到当前口径')).toBeInTheDocument();
expect(screen.getByText('已绑定 1024 · 待绑定 11')).toBeInTheDocument();
expect(screen.getByRole('heading', { name: '运行概览', level: 5 })).toBeInTheDocument();
expect(screen.getByText('车辆总数 / 在线').closest('.v2-ops-metric')).toBeInTheDocument();
expect(screen.getByRole('list', { name: '运行健康指标' })).toHaveClass('v2-ops-metric-rail');
expect(screen.getByText('车辆总数 / 在线').closest('[role="listitem"]')).toHaveClass('is-secondary');
expect(screen.getByRole('list', { name: '运行健康指标' })).toHaveClass('v2-workspace-metric-list');
expect(screen.getByText('Kafka Lag').closest('.v2-workspace-metric-context')).toHaveClass('v2-workspace-metric-context');
expect(screen.getByText('全部正常')).toBeInTheDocument();
expect(screen.getByText('运行时安全').closest('.semi-card')).toHaveClass('v2-ops-runtime');
expect(screen.getByText('协议来源就绪度').closest('.semi-card')).toHaveClass('v2-ops-sources');

View File

@@ -13,6 +13,7 @@ import { SegmentedTabs } from '../shared/SegmentedTabs';
import { VehicleCandidateList } from '../shared/VehicleCandidateList';
import { WorkspaceCommandBar } from '../shared/WorkspaceCommandBar';
import { WorkspaceFilterPanel } from '../shared/WorkspaceFilterPanel';
import { WorkspaceMetricRail, type WorkspaceQueueMetricRailItem } from '../shared/WorkspaceMetricRail';
import { WorkspacePanelHeader } from '../shared/WorkspacePanelHeader';
import { WorkspaceSideSheet } from '../shared/WorkspaceSideSheet';
import { LIVE_QUERY_POLICY, QUERY_MEMORY } from '../queryPolicy';
@@ -49,19 +50,6 @@ function number(value?: number, digits = 1) {
return value == null || !Number.isFinite(value) ? '—' : value.toLocaleString('zh-CN', { maximumFractionDigits: digits });
}
function OpsMetric({ label, value, detail, tone = 'neutral' }: {
label: string;
value: string;
detail: string;
tone?: 'neutral' | 'success' | 'warning' | 'danger';
}) {
return <article className={`v2-ops-metric is-${tone}`} role="listitem">
<small>{label}</small>
<strong>{value}</strong>
<span>{detail}</span>
</article>;
}
function sourceOnlineRate(online: number, total: number, onlineRate?: number) {
if (onlineRate != null && Number.isFinite(onlineRate)) return Math.max(0, Math.min(100, onlineRate));
return total > 0 ? Math.max(0, Math.min(100, (online / total) * 100)) : 0;
@@ -457,6 +445,34 @@ export default function OperationsPage() {
: healthIssueCount > 0
? `协议覆盖 ${sourceIssueCount} 项 · 链路运行 ${infrastructureIssueCount}`
: '链路、写入与协议覆盖均正常';
const healthMetrics: WorkspaceQueueMetricRailItem[] = [
{
label: '整体状态',
value: overallValue,
note: overallDetail,
tone: healthEvidencePending ? 'neutral' : readinessUnavailable || sourceErrorCount > 0 ? 'danger' : healthIssueCount > 0 ? 'warning' : 'success',
emphasis: 'primary'
},
{
label: '协议就绪 / 总数',
value: sources?.sources.length ? `${sourceHealthyCount} / ${sources.sources.length}` : readinessUnavailable ? '不可用' : '—',
note: readinessUnavailable ? '来源证据读取失败' : sourceIssueCount > 0 ? `${sourceIssueCount} 个协议需要处置` : sources?.sources.length ? '全部协议达到当前口径' : '等待协议覆盖证据',
tone: readinessUnavailable || sourceErrorCount > 0 ? 'danger' : sourceIssueCount > 0 ? 'warning' : sources?.sources.length ? 'success' : 'neutral',
emphasis: 'primary'
},
{
label: '车辆总数 / 在线',
value: sources ? `${sources.totalVehicles} / ${sources.onlineVehicles}` : '—',
note: sources ? `已绑定 ${sources.boundVehicles} · 待绑定 ${sources.identityRequiredVehicles}` : '档案与快照并集',
emphasis: 'secondary'
},
{
label: '活跃连接',
value: data?.activeConnections?.toLocaleString('zh-CN') ?? '—',
note: '网关实时连接',
emphasis: 'secondary'
}
];
const sortedLinks = useMemo(() => [...(data?.linkHealth ?? [])].sort((left, right) => {
const priority = { error: 0, warning: 1, ok: 2 } as Record<string, number>;
return (priority[left.status] ?? 3) - (priority[right.status] ?? 3);
@@ -519,18 +535,17 @@ export default function OperationsPage() {
description="关键链路、基础设施与车辆覆盖的同一健康快照"
meta={<span className="v2-ops-overview-meta"><HealthTag status={overallStatus}>{!data ? '正在读取' : healthIssueCount > 0 ? `${healthIssueCount} 项需关注` : '全部正常'}</HealthTag><Typography.Text type="tertiary">15 </Typography.Text></span>}
/>
<section className="v2-ops-metric-rail" role="list" aria-label="运行健康指标">
<OpsMetric label="整体状态" value={overallValue} detail={overallDetail} tone={healthEvidencePending ? 'neutral' : readinessUnavailable || sourceErrorCount > 0 ? 'danger' : healthIssueCount > 0 ? 'warning' : 'success'} />
<OpsMetric
label="协议就绪 / 总数"
value={sources?.sources.length ? `${sourceHealthyCount} / ${sources.sources.length}` : readinessUnavailable ? '不可用' : '—'}
detail={readinessUnavailable ? '来源证据读取失败' : sourceIssueCount > 0 ? `${sourceIssueCount} 个协议需要处置` : sources?.sources.length ? '全部协议达到当前口径' : '等待协议覆盖证据'}
tone={readinessUnavailable || sourceErrorCount > 0 ? 'danger' : sourceIssueCount > 0 ? 'warning' : sources?.sources.length ? 'success' : 'neutral'}
/>
<OpsMetric label="车辆总数 / 在线" value={sources ? `${sources.totalVehicles} / ${sources.onlineVehicles}` : '—'} detail={sources ? `已绑定 ${sources.boundVehicles} · 待绑定 ${sources.identityRequiredVehicles}` : '档案与快照并集'} />
<OpsMetric label="活跃连接" value={data?.activeConnections?.toLocaleString('zh-CN') ?? '—'} detail="网关实时连接" />
<OpsMetric label="Kafka Lag" value={data?.kafkaLag?.toLocaleString('zh-CN') ?? '—'} detail={data?.kafkaLag === 0 ? '消费积压已回零' : data ? '存在待消费消息' : '等待消费证据'} tone={data?.kafkaLag === 0 ? 'success' : data ? 'warning' : 'neutral'} />
</section>
<WorkspaceMetricRail
variant="queue"
className="v2-ops-health-metric-rail"
ariaLabel="运行健康指标"
items={healthMetrics}
context={<span className={`v2-ops-health-kafka${data?.kafkaLag === 0 ? ' is-ok' : data ? ' is-warning' : ''}`}>
<small>Kafka Lag</small>
<strong>{data?.kafkaLag?.toLocaleString('zh-CN') ?? '—'}</strong>
<em>{data?.kafkaLag === 0 ? '消费积压已回零' : data ? '存在待消费消息' : '等待消费证据'}</em>
</span>}
/>
</Card>
<Card className={`v2-ops-panel v2-ops-sources${sourceIssueCount || readinessUnavailable ? ' has-attention' : ''}`} bodyStyle={{ padding: 0 }}><WorkspacePanelHeader title="协议来源就绪度" description="优先展示覆盖不足的协议,并给出处置动作与验收口径" meta={<HealthTag status={readinessUnavailable ? 'error' : sourceIssueCount > 0 ? 'warning' : sources ? 'ok' : 'unknown'}>{readinessUnavailable ? '证据不可用' : sources ? sourceIssueCount > 0 ? `${sourceIssueCount} 个协议需关注` : `${sources.sources.length} 个协议正常` : '正在读取'}</HealthTag>} />{sources ? sortedSources.length ? <CardGroup className="v2-ops-source-list" type="grid" spacing={0}>{sortedSources.map((source) => {
const onlineRate = sourceOnlineRate(source.online, source.total, source.onlineRate);