feat(platform): streamline dashboard for vehicle service
This commit is contained in:
@@ -1,8 +1,7 @@
|
||||
import { Button, Card, Col, Form, Row, Select, Space, Spin, Table, Tag, Toast, Typography } from '@douyinfe/semi-ui';
|
||||
import { IconSearch } from '@douyinfe/semi-icons';
|
||||
import { useEffect, useMemo, useState } from 'react';
|
||||
import { api } from '../api/client';
|
||||
import type { DashboardSummary, LinkHealth, OpsHealth, ProtocolStat, QualityIssueRow, ServiceStatusStat, VehicleCoverageRow, VehicleRealtimeRow, VehicleServiceSummary } from '../api/types';
|
||||
import type { DashboardSummary, OpsHealth, QualityIssueRow, VehicleCoverageRow, VehicleRealtimeRow, VehicleServiceSummary } from '../api/types';
|
||||
import { PageHeader } from '../components/PageHeader';
|
||||
import { SourceStatusTags } from '../components/SourceStatusTags';
|
||||
import { StatusTag } from '../components/StatusTag';
|
||||
@@ -13,20 +12,6 @@ import { buildCsv, downloadCsv, type CsvColumn } from '../domain/csvExport';
|
||||
import { qualityIssueLabel, qualityProtocolLabel } from '../domain/qualityIssue';
|
||||
import { qualityIssueVehicleLookup } from '../domain/vehicleLookup';
|
||||
|
||||
const statusColor: Record<string, 'green' | 'orange' | 'red' | 'grey'> = {
|
||||
ok: 'green',
|
||||
warning: 'orange',
|
||||
error: 'red'
|
||||
};
|
||||
|
||||
const serviceStatusColor: Record<string, 'green' | 'orange' | 'red' | 'grey'> = {
|
||||
healthy: 'green',
|
||||
degraded: 'orange',
|
||||
offline: 'red',
|
||||
no_data: 'orange',
|
||||
identity_required: 'orange'
|
||||
};
|
||||
|
||||
const serviceStatusTitle: Record<string, string> = {
|
||||
healthy: '服务正常',
|
||||
degraded: '来源不完整',
|
||||
@@ -86,13 +71,6 @@ function vehicleServiceOnlineText(serviceSummary: VehicleServiceSummary | null,
|
||||
return `${onlineVehicles.toLocaleString()} / ${totalVehicles.toLocaleString()} 在线`;
|
||||
}
|
||||
|
||||
function formatProtocolRate(row: ProtocolStat) {
|
||||
if (!Number.isFinite(row.total) || row.total <= 0) {
|
||||
return '0%';
|
||||
}
|
||||
return `${Math.round((row.online / row.total) * 100)}%`;
|
||||
}
|
||||
|
||||
function rowServiceStatus(row: { serviceStatus?: { title: string; severity: string }; onlineSourceCount: number; sourceCount: number }) {
|
||||
if (row.serviceStatus) {
|
||||
return {
|
||||
@@ -311,16 +289,6 @@ export function Dashboard({
|
||||
.finally(() => setLoading(false));
|
||||
}, []);
|
||||
|
||||
const kpis: Array<{ label: string; value: string; filters: Record<string, string> }> = [
|
||||
{ label: '总车辆', value: formatCount(serviceSummary?.totalVehicles), filters: {} },
|
||||
{ label: '在线车辆', value: formatCount(serviceSummary?.onlineVehicles ?? summary?.onlineVehicles), filters: { online: 'online' } },
|
||||
{ label: '单源车辆', value: formatCount(serviceSummary?.singleSourceVehicles), filters: { coverage: 'single' } },
|
||||
{ label: '多源车辆', value: formatCount(serviceSummary?.multiSourceVehicles), filters: { coverage: 'multi' } },
|
||||
{ label: '暂无来源车辆', value: formatCount(serviceSummary?.noDataVehicles), filters: { serviceStatus: 'no_data' } },
|
||||
{ label: '身份未绑定', value: formatCount(serviceSummary?.identityRequiredVehicles), filters: { serviceStatus: 'identity_required' } },
|
||||
{ label: '档案不完整', value: formatCount(serviceSummary?.archiveIncompleteVehicles), filters: { archiveStatus: 'incomplete' } }
|
||||
];
|
||||
const missingSourceCounts = new Map((serviceSummary?.missingSources ?? []).map((item) => [item.protocol, item.count]));
|
||||
const serviceActionQueue = useMemo<Array<{ label: string; count: number; filters: Record<string, string>; detail: string }>>(() => {
|
||||
const items: Array<{ label: string; count: number; filters: Record<string, string>; detail: string }> = [];
|
||||
if ((serviceSummary?.noDataVehicles ?? 0) > 0) {
|
||||
@@ -370,6 +338,15 @@ export function Dashboard({
|
||||
const commandOnlineCount = locations.filter((row) => row.online).length;
|
||||
const commandLocatedCount = locations.filter(hasValidCoordinate).length;
|
||||
const commandDegradedCount = locations.filter((row) => row.onlineSourceCount <= 0 || row.onlineSourceCount < row.sourceCount).length;
|
||||
const kpis: Array<{ label: string; value: string; filters: Record<string, string> }> = [
|
||||
{ label: '总车辆', value: formatCount(serviceSummary?.totalVehicles), filters: {} },
|
||||
{ label: '在线车辆', value: formatCount(serviceSummary?.onlineVehicles ?? summary?.onlineVehicles), filters: { online: 'online' } },
|
||||
{ label: '今日活跃', value: formatCount(summary?.activeToday), filters: { online: 'online' } },
|
||||
{ label: '有效定位', value: commandLocatedCount.toLocaleString(), filters: { online: 'online' } },
|
||||
{ label: '告警车辆', value: formatCount(summary?.issueVehicles), filters: { serviceStatus: 'degraded' } },
|
||||
{ label: '今日数据', value: formatCount(summary?.frameToday), filters: {} },
|
||||
{ label: '档案不完整', value: formatCount(serviceSummary?.archiveIncompleteVehicles), filters: { archiveStatus: 'incomplete' } }
|
||||
];
|
||||
const highPriorityIssue = qualityIssues.find((item) => item.severity === 'error') ?? qualityIssues[0];
|
||||
const highPriorityLookup = highPriorityIssue ? qualityIssueVehicleLookup(highPriorityIssue) : undefined;
|
||||
const highPriorityEvidenceFilters = highPriorityIssue ? priorityIssueEvidenceFilters(highPriorityIssue) : undefined;
|
||||
@@ -647,8 +624,8 @@ export function Dashboard({
|
||||
{
|
||||
title: '历史数据查询',
|
||||
value: `${formatCount(summary?.frameToday)} 今日帧`,
|
||||
meta: `Kafka Lag ${formatLag(summary?.kafkaLag)}`,
|
||||
color: (summary?.kafkaLag ?? 0) > 0 ? 'orange' as const : 'blue' as const,
|
||||
meta: '字段裁剪与导出',
|
||||
color: 'blue' as const,
|
||||
detail: '查询位置历史、RAW 帧和解析字段,为车辆服务提供可追溯证据。',
|
||||
actions: [
|
||||
{ label: '查询历史数据', onClick: () => onOpenHistory({ tab: 'location' }) },
|
||||
@@ -758,7 +735,7 @@ export function Dashboard({
|
||||
{
|
||||
title: '历史数据查询',
|
||||
objective: '围绕车辆查询位置、RAW、解析字段,给 BI、运维和业务复盘提供证据。',
|
||||
evidence: `TDengine / 今日帧 ${formatCount(summary?.frameToday)} / Kafka Lag ${formatLag(summary?.kafkaLag)}`,
|
||||
evidence: `今日数据 ${formatCount(summary?.frameToday)} / 支持字段裁剪与导出`,
|
||||
sla: '优先返回必要字段,避免大 JSON 拖慢查询',
|
||||
primaryAction: '历史查询',
|
||||
secondaryAction: '字段证据',
|
||||
@@ -778,7 +755,7 @@ export function Dashboard({
|
||||
{
|
||||
title: '统计查询',
|
||||
objective: '按车辆口径查询里程等指标,保证区间统计和日统计可闭合。',
|
||||
evidence: `车辆口径 ${formatCount(serviceSummary?.totalVehicles)} / 多源覆盖 ${formatCount(serviceSummary?.multiSourceVehicles)}`,
|
||||
evidence: `车辆口径 ${formatCount(serviceSummary?.totalVehicles)} / 区间统计可追溯轨迹证据`,
|
||||
sla: '统计值必须能追溯轨迹和 RAW 证据',
|
||||
primaryAction: '统计查询',
|
||||
secondaryAction: '车辆中心',
|
||||
@@ -798,9 +775,7 @@ export function Dashboard({
|
||||
`今日帧量:${formatCount(summary?.frameToday)}`,
|
||||
`实时地图有效定位:${commandLocatedCount.toLocaleString()}`,
|
||||
`告警车辆:${formatCount(summary?.issueVehicles)}`,
|
||||
`Kafka Lag:${formatLag(summary?.kafkaLag)}`,
|
||||
`高德地图:Web JS ${amapConfigured ? '已配置' : '待配置'} / 服务端 API ${amapApiConfigured ? '已配置' : '待配置'} / 安全代理 ${amapSecurityProxyEnabled ? '已启用' : '未启用'}`,
|
||||
`链路健康:${unhealthyLinks.length > 0 ? unhealthyLinks.map((item) => `${item.name}=${item.status}`).join(';') : '正常'}`,
|
||||
`优先动作:${priorityAction ? `${priorityAction.label} ${priorityAction.count.toLocaleString()}辆 - ${priorityAction.detail}` : '暂无待办'}`,
|
||||
highPriorityIssue ? `最高告警:${highPriorityIssue.severity === 'error' ? 'P0' : 'P1'} ${qualityIssueLabel(highPriorityIssue.issueType)} / ${priorityIssueVehicleLabel(highPriorityIssue)} / ${highPriorityIssue.lastSeen || '-'}` : '最高告警:暂无',
|
||||
highPriorityIssue ? `告警详情:${highPriorityIssue.detail || '-'}` : '',
|
||||
@@ -890,21 +865,9 @@ export function Dashboard({
|
||||
},
|
||||
{
|
||||
section: '车辆服务',
|
||||
item: '多源覆盖车辆',
|
||||
value: formatCount(serviceSummary?.multiSourceVehicles),
|
||||
detail: '同一车辆可由多个协议交叉验证'
|
||||
},
|
||||
{
|
||||
section: '车辆服务',
|
||||
item: '单源车辆',
|
||||
value: formatCount(serviceSummary?.singleSourceVehicles),
|
||||
detail: '仅有一个协议来源,需要持续补齐'
|
||||
},
|
||||
{
|
||||
section: '车辆服务',
|
||||
item: '暂无来源车辆',
|
||||
value: formatCount(serviceSummary?.noDataVehicles),
|
||||
detail: '车辆档案存在但暂无实时来源'
|
||||
item: '今日活跃',
|
||||
value: formatCount(summary?.activeToday),
|
||||
detail: '当天有有效上报或历史记录的车辆服务对象'
|
||||
},
|
||||
{
|
||||
section: '实时监控',
|
||||
@@ -922,7 +885,7 @@ export function Dashboard({
|
||||
section: '历史数据',
|
||||
item: '今日帧量',
|
||||
value: formatCount(summary?.frameToday),
|
||||
detail: `Kafka Lag ${formatLag(summary?.kafkaLag)}`
|
||||
detail: '历史查询和导出可按车辆、时间、字段裁剪'
|
||||
},
|
||||
{
|
||||
section: '告警事件',
|
||||
@@ -930,12 +893,6 @@ export function Dashboard({
|
||||
value: formatCount(summary?.issueVehicles),
|
||||
detail: highPriorityIssue ? `${qualityIssueLabel(highPriorityIssue.issueType)} / ${priorityIssueVehicleLabel(highPriorityIssue)}` : '暂无最高优先级告警'
|
||||
},
|
||||
{
|
||||
section: '链路健康',
|
||||
item: '异常链路',
|
||||
value: unhealthyLinks.length.toLocaleString(),
|
||||
detail: unhealthyLinks.length > 0 ? unhealthyLinks.map((item) => `${item.name}=${item.status}`).join(';') : '正常'
|
||||
},
|
||||
{
|
||||
section: '地图能力',
|
||||
item: '高德地图',
|
||||
@@ -949,14 +906,6 @@ export function Dashboard({
|
||||
detail: priorityAction?.detail ?? '当前没有必须立即处理的车辆服务事项'
|
||||
}
|
||||
];
|
||||
(serviceSummary?.protocols ?? summary?.protocols ?? []).forEach((item) => {
|
||||
rows.push({
|
||||
section: '协议来源',
|
||||
item: item.protocol,
|
||||
value: `${item.online.toLocaleString()} / ${item.total.toLocaleString()}`,
|
||||
detail: `在线率 ${formatProtocolRate(item)}`
|
||||
});
|
||||
});
|
||||
(serviceSummary?.serviceStatuses ?? summary?.serviceStatuses ?? []).forEach((item) => {
|
||||
rows.push({
|
||||
section: '服务状态',
|
||||
@@ -965,14 +914,6 @@ export function Dashboard({
|
||||
detail: serviceStatusTitle[item.status] || item.status
|
||||
});
|
||||
});
|
||||
(summary?.linkHealth ?? []).forEach((item) => {
|
||||
rows.push({
|
||||
section: '链路健康',
|
||||
item: item.name,
|
||||
value: item.status,
|
||||
detail: item.detail ?? ''
|
||||
});
|
||||
});
|
||||
return rows;
|
||||
};
|
||||
const exportDashboardSnapshot = () => {
|
||||
@@ -992,9 +933,9 @@ export function Dashboard({
|
||||
<Card bordered className="vp-customer-hero" bodyStyle={{ padding: 0 }}>
|
||||
<div className="vp-customer-hero-map">
|
||||
<div className="vp-customer-hero-copy">
|
||||
<Typography.Title heading={3} style={{ margin: 0 }}>先看车,再看数据来源</Typography.Title>
|
||||
<Typography.Title heading={3} style={{ margin: 0 }}>先看车辆服务</Typography.Title>
|
||||
<Typography.Text type="secondary">
|
||||
车辆是主对象。32960、808 和宇通 MQTT 只是证据来源,客户每天需要先确认车辆在哪里、是否在线、今天跑了多少、异常能否追溯和导出。
|
||||
车辆是主对象。32960、808 和宇通 MQTT 只是数据来源,客户每天需要先确认车辆在哪里、是否在线、今天跑了多少、异常能否追溯和导出。
|
||||
</Typography.Text>
|
||||
<Space wrap>
|
||||
<Button theme="solid" type="primary" onClick={() => onOpenMap({ online: 'online' })}>打开实时地图</Button>
|
||||
@@ -1050,21 +991,42 @@ export function Dashboard({
|
||||
</Card>
|
||||
))}
|
||||
</div>
|
||||
<Card bordered title="统一车辆服务入口" style={{ marginBottom: 16 }}>
|
||||
<Card bordered title="车辆服务入口" style={{ marginBottom: 16 }}>
|
||||
<Space wrap>
|
||||
<Tag color="blue">{vehicleServiceOnlineText(serviceSummary, summary)}</Tag>
|
||||
<Button size="small" theme="light" type="primary" onClick={() => onOpenVehicles({ online: 'online' })}>查看在线车辆</Button>
|
||||
<Tag color="green">{formatCount(serviceSummary?.multiSourceVehicles)} 多源覆盖</Tag>
|
||||
<Button size="small" theme="light" type="primary" onClick={() => onOpenVehicles({ coverage: 'multi' })}>查看多源车辆</Button>
|
||||
<Tag color="blue">有效定位 {commandLocatedCount.toLocaleString()}</Tag>
|
||||
<Button size="small" theme="light" type="primary" onClick={() => onOpenMap({ online: 'online' })}>实时地图</Button>
|
||||
<Tag color="blue">今日活跃 {formatCount(summary?.activeToday)}</Tag>
|
||||
<Button size="small" theme="light" type="primary" onClick={() => onOpenHistory()}>轨迹回放</Button>
|
||||
<Tag color="blue">今日数据 {formatCount(summary?.frameToday)}</Tag>
|
||||
<Button size="small" theme="light" type="primary" onClick={() => onOpenHistory({ tab: 'raw', includeFields: 'true' })}>历史查询导出</Button>
|
||||
<Tag color={(summary?.issueVehicles ?? 0) > 0 ? 'orange' : 'green'}>
|
||||
{formatCount(summary?.issueVehicles)} 告警事件
|
||||
</Tag>
|
||||
<Button size="small" theme="light" type={(summary?.issueVehicles ?? 0) > 0 ? 'warning' : 'tertiary'} onClick={() => onOpenQuality()}>查看告警事件</Button>
|
||||
<Tag color={(summary?.kafkaLag ?? 0) > 0 ? 'orange' : 'green'}>Kafka Lag {formatLag(summary?.kafkaLag)}</Tag>
|
||||
<Button size="small" theme="solid" type="primary" onClick={copyOperationsHandoff}>复制运营交接摘要</Button>
|
||||
<Button size="small" theme="light" type="primary" onClick={exportDashboardSnapshot}>导出驾驶舱 CSV</Button>
|
||||
</Space>
|
||||
</Card>
|
||||
<Card bordered title="自定义时间监控" style={{ marginBottom: 16 }}>
|
||||
<div className="vp-time-monitor-grid">
|
||||
{[
|
||||
{ title: '今天车辆轨迹', detail: '查看当天活跃车辆的轨迹、速度和里程断点。', action: '查今天', onClick: () => onOpenHistory() },
|
||||
{ title: '历史时间窗', detail: '按任意起止时间查询位置历史、RAW 字段和证据。', action: '自定义查询', onClick: () => onOpenHistory({ tab: 'raw', includeFields: 'true' }) },
|
||||
{ title: '区间里程', detail: '按车辆和时间范围核对区间里程、日统计和异常点。', action: '统计查询', onClick: () => onOpenMileage() },
|
||||
{ title: '告警复盘', detail: '按车辆或事件回放告警发生前后的轨迹和数据证据。', action: '告警事件', onClick: () => onOpenQuality() }
|
||||
].map((item) => (
|
||||
<button key={item.title} type="button" className="vp-time-monitor-item" onClick={item.onClick} aria-label={`时间监控 ${item.title}`}>
|
||||
<div>
|
||||
<Typography.Title heading={6} style={{ margin: 0 }}>{item.title}</Typography.Title>
|
||||
<Typography.Text type="secondary">{item.detail}</Typography.Text>
|
||||
</div>
|
||||
<Tag color="blue">{item.action}</Tag>
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</Card>
|
||||
<Card bordered title="地图运营能力" style={{ marginBottom: 16 }}>
|
||||
<div className="vp-map-ops-board">
|
||||
<div className="vp-map-ops-readiness">
|
||||
@@ -1100,7 +1062,7 @@ export function Dashboard({
|
||||
<div className="vp-focus-service-main">
|
||||
<Tag color="blue">{focusVehicle.reason}</Tag>
|
||||
<Typography.Title heading={5} style={{ margin: '8px 0 4px' }}>{focusVehicle.label}</Typography.Title>
|
||||
<Typography.Text type="secondary">把协议来源作为证据,把实时、轨迹、RAW、统计和告警集中到同一辆车处理。</Typography.Text>
|
||||
<Typography.Text type="secondary">把数据来源作为证据,把实时、轨迹、RAW、统计和告警集中到同一辆车处理。</Typography.Text>
|
||||
</div>
|
||||
<div className="vp-focus-service-evidence">
|
||||
{[
|
||||
@@ -1165,26 +1127,6 @@ export function Dashboard({
|
||||
))}
|
||||
</div>
|
||||
</Card>
|
||||
<Card
|
||||
bordered
|
||||
title={<Space><span>数据流转作业链</span><Button size="small" onClick={copyDataFlowBlueprint}>复制流转图</Button></Space>}
|
||||
style={{ marginBottom: 16 }}
|
||||
>
|
||||
<div className="vp-data-flow-grid">
|
||||
{dataFlowStages.map((item) => (
|
||||
<button key={item.stage} className="vp-data-flow-item" type="button" onClick={item.onClick} aria-label={`数据流转 ${item.title}`}>
|
||||
<div className="vp-data-flow-head">
|
||||
<span>{item.stage}</span>
|
||||
<Tag color="blue">{item.owner}</Tag>
|
||||
</div>
|
||||
<Typography.Title heading={6} style={{ margin: 0 }}>{item.title}</Typography.Title>
|
||||
<div className="vp-data-flow-evidence">{item.evidence}</div>
|
||||
<Typography.Text type="secondary">{item.detail}</Typography.Text>
|
||||
<Tag color="grey">{item.action}</Tag>
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</Card>
|
||||
<Card
|
||||
bordered
|
||||
title={<Space><span>车联网场景导航</span><Button size="small" onClick={copyScenarioBlueprint}>复制功能蓝图</Button></Space>}
|
||||
@@ -1211,31 +1153,6 @@ export function Dashboard({
|
||||
))}
|
||||
</div>
|
||||
</Card>
|
||||
<Card bordered title="全链路值班闭环" style={{ marginBottom: 16 }}>
|
||||
<div className="vp-operation-flow">
|
||||
{workflowSteps.map((item, index) => (
|
||||
<div key={item.title} className="vp-operation-step">
|
||||
<div className="vp-operation-index">{index + 1}</div>
|
||||
<div className="vp-operation-content">
|
||||
<Space spacing={8} align="center">
|
||||
<Typography.Title heading={6} style={{ margin: 0 }}>{item.title}</Typography.Title>
|
||||
<Tag color={item.color}>{item.value}</Tag>
|
||||
</Space>
|
||||
<Typography.Text type="secondary">{item.detail}</Typography.Text>
|
||||
<Button
|
||||
size="small"
|
||||
theme="light"
|
||||
type="primary"
|
||||
aria-label={`闭环入口 ${item.title}`}
|
||||
onClick={item.onClick}
|
||||
>
|
||||
{item.action}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</Card>
|
||||
{highPriorityIssue ? (
|
||||
<Card bordered title="最高优先级告警" style={{ marginBottom: 16 }}>
|
||||
<div className="vp-priority-alert">
|
||||
@@ -1377,89 +1294,6 @@ export function Dashboard({
|
||||
</div>
|
||||
</Card>
|
||||
) : null}
|
||||
<Row gutter={16}>
|
||||
<Col span={8}>
|
||||
<Card title="车辆服务状态" bordered>
|
||||
<Table
|
||||
pagination={false}
|
||||
dataSource={serviceSummary?.serviceStatuses ?? summary?.serviceStatuses ?? []}
|
||||
columns={[
|
||||
{
|
||||
title: '状态',
|
||||
render: (_: unknown, row: ServiceStatusStat) => <Tag color={serviceStatusColor[row.status] ?? 'grey'}>{row.title}</Tag>
|
||||
},
|
||||
{ title: '车辆数', dataIndex: 'count' },
|
||||
{
|
||||
title: '操作',
|
||||
width: 90,
|
||||
render: (_: unknown, row: ServiceStatusStat) => (
|
||||
<Button
|
||||
aria-label={`查看${row.title}`}
|
||||
icon={<IconSearch />}
|
||||
size="small"
|
||||
onClick={() => loadCoverage({ serviceStatus: row.status })}
|
||||
/>
|
||||
)
|
||||
}
|
||||
]}
|
||||
/>
|
||||
</Card>
|
||||
</Col>
|
||||
<Col span={8}>
|
||||
<Card title="来源证据在线分布" bordered>
|
||||
<Table
|
||||
pagination={false}
|
||||
dataSource={serviceSummary?.protocols ?? summary?.protocols ?? []}
|
||||
columns={[
|
||||
{ title: '来源证据', dataIndex: 'protocol' },
|
||||
{ title: '在线', dataIndex: 'online' },
|
||||
{ title: '总数', dataIndex: 'total' },
|
||||
{
|
||||
title: '在线率',
|
||||
render: (_: unknown, row: ProtocolStat) => formatProtocolRate(row)
|
||||
},
|
||||
{
|
||||
title: '缺失车辆',
|
||||
render: (_: unknown, row: ProtocolStat) => {
|
||||
const missingCount = missingSourceCounts.get(row.protocol);
|
||||
if (missingCount == null) {
|
||||
return '-';
|
||||
}
|
||||
return (
|
||||
<Button
|
||||
aria-label={`查看缺 ${row.protocol}`}
|
||||
size="small"
|
||||
onClick={() => loadCoverage({ missingProtocol: row.protocol })}
|
||||
>
|
||||
{formatCount(missingCount)}
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
}
|
||||
]}
|
||||
/>
|
||||
</Card>
|
||||
</Col>
|
||||
<Col span={8}>
|
||||
<Card title="链路健康" bordered>
|
||||
<Table
|
||||
pagination={false}
|
||||
dataSource={summary?.linkHealth ?? []}
|
||||
columns={[
|
||||
{ title: '链路', dataIndex: 'name' },
|
||||
{
|
||||
title: '状态',
|
||||
render: (_: unknown, row: LinkHealth) => <Tag color={statusColor[row.status] ?? 'grey'}>{row.status}</Tag>
|
||||
},
|
||||
{ title: '说明', dataIndex: 'detail' }
|
||||
]}
|
||||
/>
|
||||
</Card>
|
||||
</Col>
|
||||
</Row>
|
||||
<Card title="实时积压" bordered style={{ marginTop: 16 }}>
|
||||
<Typography.Text>Kafka 当前消费积压:{formatLag(summary?.kafkaLag)}</Typography.Text>
|
||||
</Card>
|
||||
<Card
|
||||
title={<Space><span>质量问题预览</span><Button size="small" onClick={() => onOpenQuality()}>查看全部</Button></Space>}
|
||||
bordered
|
||||
|
||||
Reference in New Issue
Block a user