feat(monitor): NATS 集群 Raft 副本健康 + 基建 ping 延迟(监测组完善)
此前 /status 把 NATS 当一盏二元灯(连不上就 fatal 故恒真),看不出 3 节点集群里 哪个节点掉了、JetStream 持久流的 Raft 副本是否还齐(计费/状态/评测流不丢的关键)。 DB/Redis/MinIO 也只二元 ping、无延迟。 - bus.ClusterStatus:连的节点名 + 集群发现节点数(nc.Servers) + RTT(nc.RTT) + 6 条关键 持久流(tasks/status/usage/eval/ingest/approvals)的 Raft 副本健康(leader + healthy/total, 单节点部署记 1/1;某节点掉队 → healthy<total 记降级)。 - /status 新增 nats 集群对象 + NATS 灯改为'连接且无副本降级才绿'、detail 显示'N 节点·连 X· 流副本齐全/降级'、latency=RTT;PG/Redis/MinIO 加 ping 往返耗时。 - admin StatusPage:新增 NATS 集群面板(节点/RTT/连接 + 各流 leader/副本健康/消息数表), 基建行显示延迟。 sundynix-shared/gateway build+vet+test 绿;admin tsc 绿。 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -670,11 +670,27 @@ export interface ToolGroup {
|
||||
up: boolean;
|
||||
tools: ToolInfo[] | null;
|
||||
}
|
||||
export interface NatsStreamHealth {
|
||||
name: string;
|
||||
leader: string;
|
||||
replicas_healthy: number;
|
||||
replicas_total: number;
|
||||
messages: number;
|
||||
}
|
||||
export interface NatsClusterStatus {
|
||||
connected: boolean;
|
||||
connected_to: string;
|
||||
known_servers: number;
|
||||
rtt_ms: number;
|
||||
streams: NatsStreamHealth[] | null;
|
||||
degraded: number;
|
||||
}
|
||||
export interface SystemStatus {
|
||||
checked_at: string;
|
||||
infra: StatusItem[];
|
||||
services: StatusItem[];
|
||||
tools: ToolGroup[];
|
||||
nats?: NatsClusterStatus;
|
||||
}
|
||||
|
||||
export async function getStatus(): Promise<SystemStatus> {
|
||||
|
||||
@@ -330,6 +330,9 @@ export function StatusPage() {
|
||||
)}
|
||||
</div>
|
||||
<div className="flex items-center gap-3">
|
||||
{item.up && item.latency_ms != null && item.latency_ms > 0 && (
|
||||
<span className="font-mono text-[10px] text-slate-400 tabular-nums">{item.latency_ms}ms</span>
|
||||
)}
|
||||
<code className="text-[10px] text-slate-400 font-mono bg-slate-100 px-1.5 py-0.5 rounded">
|
||||
:{meta?.port || "—"}
|
||||
</code>
|
||||
@@ -344,6 +347,68 @@ export function StatusPage() {
|
||||
</section>
|
||||
</div>
|
||||
|
||||
{/* NATS 集群 · JetStream Raft 副本健康 */}
|
||||
{data.nats && (
|
||||
<section className="rounded-2xl border border-gray-200 bg-white p-5">
|
||||
<div className="border-b border-slate-100 pb-3.5 mb-4 flex flex-wrap items-center justify-between gap-3">
|
||||
<div>
|
||||
<h3 className="text-sm font-bold text-slate-800 flex items-center gap-1.5">
|
||||
<span className="h-2 w-2 rounded-full bg-indigo-500"></span>
|
||||
NATS 消息骨干 · 集群
|
||||
</h3>
|
||||
<p className="text-[10px] text-slate-400 mt-0.5">JetStream Raft 副本健康——计费/状态/评测等持久流不丢的保证</p>
|
||||
</div>
|
||||
<div className="flex items-center gap-5 text-xs">
|
||||
{[
|
||||
{ k: "节点", v: String(data.nats.known_servers) },
|
||||
{ k: "RTT", v: `${data.nats.rtt_ms}ms` },
|
||||
{ k: "连接", v: data.nats.connected_to || "—" },
|
||||
].map((m) => (
|
||||
<div key={m.k} className="text-right">
|
||||
<div className="text-[9px] uppercase text-slate-400 tracking-wider font-semibold">{m.k}</div>
|
||||
<div className="font-bold text-slate-700 font-mono truncate max-w-[140px]" title={m.v}>{m.v}</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
<div className="overflow-x-auto">
|
||||
<table className="w-full text-xs">
|
||||
<thead>
|
||||
<tr className="text-left text-[10px] uppercase tracking-wide text-slate-400">
|
||||
<th className="pb-2 font-medium">持久流</th>
|
||||
<th className="pb-2 font-medium">Leader</th>
|
||||
<th className="pb-2 font-medium text-right">副本健康</th>
|
||||
<th className="pb-2 font-medium text-right">消息数</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{(data.nats.streams ?? []).map((s) => {
|
||||
const ok = s.replicas_healthy >= s.replicas_total;
|
||||
return (
|
||||
<tr key={s.name} className="border-t border-slate-50">
|
||||
<td className="py-2 font-mono text-slate-700">{s.name}</td>
|
||||
<td className="py-2 font-mono text-[10px] text-slate-500 truncate max-w-[160px]" title={s.leader}>{s.leader || "—"}</td>
|
||||
<td className="py-2 text-right">
|
||||
<span className={`font-bold font-mono ${ok ? "text-emerald-600" : "text-amber-600"}`}>
|
||||
{s.replicas_healthy}/{s.replicas_total}
|
||||
</span>
|
||||
{!ok && <span className="ml-1 text-[9px] text-amber-600">降级</span>}
|
||||
</td>
|
||||
<td className="py-2 text-right font-mono text-slate-500 tabular-nums">{s.messages.toLocaleString()}</td>
|
||||
</tr>
|
||||
);
|
||||
})}
|
||||
{(data.nats.streams ?? []).length === 0 && (
|
||||
<tr>
|
||||
<td colSpan={4} className="py-4 text-center text-[10px] text-slate-400">暂无流信息</td>
|
||||
</tr>
|
||||
)}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</section>
|
||||
)}
|
||||
|
||||
{/* MCP 工具注册箱 */}
|
||||
<section className="rounded-2xl border border-gray-200 bg-white p-6 space-y-4">
|
||||
<div className="border-b border-slate-100 pb-3 flex flex-wrap items-center justify-between gap-3">
|
||||
|
||||
Reference in New Issue
Block a user