feat(ume): expose WSS runtime logs on subscription status UI

Ring-buffer ws_logs API, alarm raise/clear labels with alarmkey, and scrollable log panel on UME page.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
oliver 2026-05-22 21:15:15 +08:00
parent 635cd52d4f
commit 5bd83c58dc
6 changed files with 203 additions and 31 deletions

View file

@ -189,6 +189,46 @@ pre {
padding: 10px;
}
.ws-log-panel {
margin-top: 12px;
max-height: 240px;
overflow-y: auto;
background: #091425;
border: 1px solid #223651;
border-radius: 8px;
padding: 8px 10px;
font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
font-size: 12px;
line-height: 1.45;
}
.ws-log-line {
padding: 3px 0;
border-bottom: 1px solid #152238;
word-break: break-word;
}
.ws-log-line:last-child {
border-bottom: none;
}
.ws-log-line--error {
color: #ff8a9a;
}
.ws-log-line--warning {
color: #f0c060;
}
.ws-log-line--info {
color: #b8c9e0;
}
.ws-log-ts {
color: #6b8299;
margin-right: 8px;
}
.pill {
margin-top: 8px;
padding: 4px 8px;

View file

@ -197,6 +197,7 @@ export function UmePage({ toastOk, toastError }: UmePageProps) {
syncStatusQuery.data?.alarm_subscription ??
({ active: false } as const);
const wsConsumer = runtimeTasks.find((t) => t.task === "alarms_current_ws_consumer");
const wsLogs = [...(subscriptionStatusQuery.data?.ws_logs ?? alarmSub.ws_logs ?? [])].reverse();
const subscriptionActive = Boolean(alarmSub.active);
const subPending =
subscriptionEstablishMutation.isPending || subscriptionCancelMutation.isPending;
@ -364,6 +365,37 @@ export function UmePage({ toastOk, toastError }: UmePageProps) {
{subscriptionOpError ? (
<div className="pill pill--high">订阅操作失败: {subscriptionOpError}</div>
) : null}
<div style={{ marginTop: 12 }}>
<div className="actions-row actions-row--inline" style={{ marginTop: 0 }}>
<strong style={{ fontSize: 13 }}>WSS 运行日志</strong>
<span className="muted" style={{ fontSize: 12 }}>
{wsConsumer?.last_run_at
? `最近活动 ${formatSystemTime(String(wsConsumer.last_run_at))}`
: "自动刷新"}
</span>
</div>
<div className="ws-log-panel" role="log" aria-live="polite">
{wsLogs.length === 0 ? (
<div className="ws-log-line ws-log-line--info">暂无日志(建立订阅并连接 WSS 后会出现连接、收包、重连等记录)</div>
) : (
wsLogs.map((line, idx) => {
const lvl = String(line.level || "info").toLowerCase();
const cls =
lvl === "error" ? "ws-log-line--error" : lvl === "warning" ? "ws-log-line--warning" : "ws-log-line--info";
return (
<div key={`${line.ts}-${idx}`} className={`ws-log-line ${cls}`}>
<span className="ws-log-ts">{formatSystemTime(line.ts)}</span>
<span>[{lvl}] </span>
<span>{line.message}</span>
{line.subscription_id ? (
<span className="muted"> · {String(line.subscription_id).slice(0, 8)}…</span>
) : null}
</div>
);
})
)}
</div>
</div>
</article>
<article className="card card--full">
<h3>UME 同步</h3>

View file

@ -89,6 +89,13 @@ export type UmeSyncJobItem = {
ended_at?: string | null;
};
export type UmeWsLogEntry = {
ts: string;
level: string;
message: string;
subscription_id?: string;
};
export type UmeAlarmSubscriptionStatus = {
ok?: boolean;
created?: boolean;
@ -100,6 +107,7 @@ export type UmeAlarmSubscriptionStatus = {
ws_consumer_status?: string;
ws_consumer_last_error?: string;
ws_consumer_last_run_at?: string | null;
ws_logs?: UmeWsLogEntry[];
};
export type UmeSyncStatusResponse = {