feat(platform): add ops quality workspace

This commit is contained in:
lingniu
2026-07-04 16:31:51 +08:00
parent c060317fb0
commit 318beccaaf
7 changed files with 221 additions and 8 deletions

View File

@@ -0,0 +1,137 @@
import { Button, Card, Space, Table, Tag, Toast } from '@douyinfe/semi-ui';
import { useEffect, useState } from 'react';
import { api } from '../api/client';
import type { LinkHealth, OpsHealth } from '../api/types';
import { PageHeader } from '../components/PageHeader';
const statusColor: Record<string, 'green' | 'orange' | 'red' | 'grey'> = {
ok: 'green',
warning: 'orange',
error: 'red'
};
function formatNumber(value?: number | null) {
return value == null ? '-' : value.toLocaleString();
}
function statusText(ok?: boolean) {
return ok ? '正常' : '异常';
}
function boolColor(ok?: boolean): 'green' | 'red' {
return ok ? 'green' : 'red';
}
function amapSecurityText(health: OpsHealth | null) {
const runtime = health?.runtime;
if (!runtime?.amapWebJsConfigured) return '地图未配置';
if (runtime.amapSecurityCodeExposed) return '安全码已暴露';
if (runtime.amapSecurityProxyEnabled) return '安全码未暴露';
return '安全代理未启用';
}
function amapSecurityColor(health: OpsHealth | null): 'green' | 'orange' | 'red' {
const runtime = health?.runtime;
if (runtime?.amapSecurityCodeExposed) return 'red';
if (runtime?.amapWebJsConfigured && runtime?.amapSecurityProxyEnabled) return 'green';
return 'orange';
}
export function OpsQuality() {
const [health, setHealth] = useState<OpsHealth | null>(null);
const [loading, setLoading] = useState(true);
const load = () => {
setLoading(true);
api.opsHealth()
.then(setHealth)
.catch((error: Error) => Toast.error(error.message))
.finally(() => setLoading(false));
};
useEffect(() => {
load();
}, []);
const runtime = health?.runtime;
return (
<div className="vp-page">
<PageHeader
title="运维质量"
description="集中查看网关、消息队列、缓存、时序库、关系库、地图配置和运行版本状态"
actions={<Button size="small" loading={loading} onClick={load}></Button>}
/>
<div className="vp-kpi-grid">
<Card bordered loading={loading}>
<div className="vp-kpi-value">{formatNumber(health?.kafkaLag)}</div>
<div className="vp-kpi-label">Kafka Lag</div>
</Card>
<Card bordered loading={loading}>
<div className="vp-kpi-value">{formatNumber(health?.activeConnections)}</div>
<div className="vp-kpi-label"></div>
</Card>
<Card bordered loading={loading}>
<div className="vp-kpi-value">{formatNumber(health?.redisOnlineKeys)}</div>
<div className="vp-kpi-label">Redis 线 Key</div>
</Card>
<Card bordered loading={loading}>
<div className="vp-kpi-value">{runtime?.platformRelease || '-'}</div>
<div className="vp-kpi-label"></div>
</Card>
</div>
<Card bordered title="存储与运行时" loading={loading} style={{ marginTop: 16 }}>
<div className="vp-alert-flow">
<div className="vp-alert-flow-item">
<Tag color={boolColor(health?.tdengineWritable)}>TDengine </Tag>
<div className="vp-alert-flow-value">{statusText(health?.tdengineWritable)}</div>
<div>RAW TDengine </div>
</div>
<div className="vp-alert-flow-item">
<Tag color={boolColor(health?.mysqlWritable)}>MySQL </Tag>
<div className="vp-alert-flow-value">{statusText(health?.mysqlWritable)}</div>
<div> MySQL</div>
</div>
<div className="vp-alert-flow-item">
<Tag color="blue"></Tag>
<div className="vp-alert-flow-value">{runtime?.requestTimeoutMs ? `${runtime.requestTimeoutMs.toLocaleString()} ms` : '-'}</div>
<div> RedisTDengineMySQL </div>
</div>
</div>
</Card>
<Card bordered title="地图安全配置" loading={loading} style={{ marginTop: 16 }}>
<Space wrap>
<Tag color={runtime?.amapWebJsConfigured ? 'green' : 'orange'}>{runtime?.amapWebJsConfigured ? 'Web JS Key 已配置' : 'Web JS Key 未配置'}</Tag>
<Tag color={amapSecurityColor(health)}>{amapSecurityText(health)}</Tag>
<Tag color={runtime?.amapSecurityProxyEnabled ? 'green' : 'grey'}>{runtime?.amapSecurityServiceHost || '代理未启用'}</Tag>
</Space>
</Card>
{health?.capacityFindings?.length ? (
<Card bordered title="容量与风险发现" style={{ marginTop: 16 }}>
<Space wrap>
{health.capacityFindings.map((item) => (
<Tag key={item} color="orange">{item}</Tag>
))}
</Space>
</Card>
) : null}
<Card bordered title="链路健康" loading={loading} style={{ marginTop: 16 }}>
<Table<LinkHealth>
pagination={false}
dataSource={health?.linkHealth ?? []}
rowKey={(row?: LinkHealth) => `${row?.name ?? ''}-${row?.status ?? ''}`}
columns={[
{ title: '链路', dataIndex: 'name' },
{ title: '状态', width: 120, render: (_: unknown, row: LinkHealth) => <Tag color={statusColor[row.status] ?? 'grey'}>{row.status}</Tag> },
{ title: '说明', dataIndex: 'detail' }
]}
/>
</Card>
</div>
);
}