Show DSH alarm hub multi-subscriber status instead of OClaw single link.

Track per-connection metadata on the hub and surface it in the UME key-alert UI so operators can see which Netx Ops clients are subscribed.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
oliver 2026-09-10 16:57:42 +08:00
parent 0f8a06bfba
commit 5096117ad4
8 changed files with 328 additions and 46 deletions

View file

@ -432,11 +432,15 @@ export function UmePage() {
[neQuery.data, expandedNeId],
);
const keyAlertHub = keyAlertMonitorQuery.data?.dsh_alarm_hub;
const keyAlertHubConnections = keyAlertHub?.connections || [];
const keyAlertHubSubscribers = Number(keyAlertHub?.subscribers || keyAlertHubConnections.length || 0);
const keyAlertForwarder = keyAlertMonitorQuery.data?.forwarder;
const keyAlertRules = keyAlertMonitorQuery.data?.rules || [];
const keyAlertTotal = Number(keyAlertMonitorQuery.data?.total || keyAlertRules.length);
const keyAlertPages = pageCount(keyAlertTotal, keyAlertPageSize);
const keyAlertForwardOnClear = Boolean(keyAlertMonitorQuery.data?.config?.forward_on_clear);
const hubWsPill = keyAlertHubSubscribers > 0 ? "up" : "down";
const oclawWsPill =
!keyAlertForwarder?.enabled
? "unknown"
@ -445,6 +449,7 @@ export function UmePage() {
: keyAlertForwarder.connected
? "up"
: "down";
const showLegacyOclaw = Boolean(keyAlertForwarder?.enabled);
const runtimeTaskLabel = (task: string) => {
const key = `ume.tasks.runtimeTask.${task}`;
@ -852,25 +857,30 @@ export function UmePage() {
</button>
</div>
<div className="ume-entry__pills actions-row actions-row--inline">
<span className={`conn-pill conn-pill--${oclawWsPill}`}>
{t("ume.keyAlert.ws")}:{" "}
{!keyAlertForwarder?.enabled
? t("ume.keyAlert.wsDisabled")
: keyAlertForwarder.paused
<span className={`conn-pill conn-pill--${hubWsPill}`}>
{t("ume.keyAlert.hub")}:{" "}
{keyAlertHubSubscribers > 0
? t("ume.keyAlert.hubConnected")
: t("ume.keyAlert.hubEmpty")}
</span>
<span className="conn-pill">
{t("ume.keyAlert.hubSubscribers")}: {keyAlertHubSubscribers}
</span>
<span className="conn-pill">
{t("ume.keyAlert.hubPublished")}: {Number(keyAlertHub?.published || 0)}
</span>
<span className="conn-pill">
{t("ume.keyAlert.hubDeliverOk")}: {Number(keyAlertHub?.deliver_ok || 0)}
</span>
{showLegacyOclaw ? (
<span className={`conn-pill conn-pill--${oclawWsPill}`} title={t("ume.keyAlert.legacyOclaw")}>
{t("ume.keyAlert.ws")}:{" "}
{keyAlertForwarder?.paused
? t("ume.keyAlert.wsPaused")
: keyAlertForwarder.connected
: keyAlertForwarder?.connected
? t("ume.keyAlert.wsConnected")
: t("ume.keyAlert.wsDisconnected")}
</span>
{keyAlertForwarder?.enabled ? (
<>
<span className="conn-pill">
{t("ume.keyAlert.publishedOk")}: {Number(keyAlertForwarder.published_ok || 0)}
</span>
<span className="conn-pill">
{t("ume.keyAlert.queue")}: {Number(keyAlertForwarder.queue_size || 0)}
</span>
</>
</span>
) : null}
</div>
</article>
@ -964,16 +974,22 @@ export function UmePage() {
<div className="ops-detail-modal__title">
<h3>{t("ume.keyAlert.title")}</h3>
<p className="muted">
{t("ume.keyAlert.ws")}:{" "}
{!keyAlertForwarder?.enabled
? t("ume.keyAlert.wsDisabled")
: keyAlertForwarder.paused
? t("ume.keyAlert.wsPaused")
: keyAlertForwarder.connected
? t("ume.keyAlert.wsConnected")
: t("ume.keyAlert.wsDisconnected")}
{keyAlertForwarder?.enabled
? ` · ${t("ume.keyAlert.publishedOk")} ${Number(keyAlertForwarder.published_ok || 0)} · ${t("ume.keyAlert.queue")} ${Number(keyAlertForwarder.queue_size || 0)}`
{t("ume.keyAlert.hub")}:{" "}
{keyAlertHubSubscribers > 0
? t("ume.keyAlert.hubConnected")
: t("ume.keyAlert.hubEmpty")}
{` · ${t("ume.keyAlert.hubSubscribers")} ${keyAlertHubSubscribers}`}
{` · ${t("ume.keyAlert.hubPublished")} ${Number(keyAlertHub?.published || 0)}`}
{` · ${t("ume.keyAlert.hubDeliverOk")} ${Number(keyAlertHub?.deliver_ok || 0)}`}
{keyAlertHub?.path ? ` · ${t("ume.keyAlert.hubPath")} ${keyAlertHub.path}` : ""}
{showLegacyOclaw
? ` · ${t("ume.keyAlert.legacyOclaw")}: ${
keyAlertForwarder?.paused
? t("ume.keyAlert.wsPaused")
: keyAlertForwarder?.connected
? t("ume.keyAlert.wsConnected")
: t("ume.keyAlert.wsDisconnected")
}`
: ""}
</p>
</div>
@ -992,6 +1008,62 @@ export function UmePage() {
</div>
<div className="ops-detail-modal__scroll ops-detail-modal__scroll--flow">
<div className="ume-modal-form">
<div className="muted ume-modal-form__label">{t("ume.keyAlert.hubConnections")}</div>
{keyAlertHubConnections.length === 0 ? (
<p className="muted">{t("ume.keyAlert.hubEmptyConnections")}</p>
) : (
<div className="table-wrap">
<table className="data-table">
<thead>
<tr>
<th>{t("ume.keyAlert.hubColId")}</th>
<th>{t("ume.keyAlert.hubColUser")}</th>
<th>{t("ume.keyAlert.hubColRemote")}</th>
<th>{t("ume.keyAlert.hubColClient")}</th>
<th>{t("ume.keyAlert.hubColConnected")}</th>
<th>{t("ume.keyAlert.hubColLastSeen")}</th>
</tr>
</thead>
<tbody>
{keyAlertHubConnections.map((conn) => (
<tr key={conn.id}>
<td>
<code>{conn.id}</code>
</td>
<td>{conn.user || "-"}</td>
<td>{conn.remote || "-"}</td>
<td>{conn.client || "-"}</td>
<td>
{conn.connected_at
? formatSystemTime(conn.connected_at)
: "-"}
</td>
<td>
{conn.last_seen_at
? formatSystemTime(conn.last_seen_at)
: "-"}
</td>
</tr>
))}
</tbody>
</table>
</div>
)}
{showLegacyOclaw ? (
<p className="muted ume-modal-form__hint">
{t("ume.keyAlert.legacyOclaw")}:{" "}
{keyAlertForwarder?.paused
? t("ume.keyAlert.wsPaused")
: keyAlertForwarder?.connected
? t("ume.keyAlert.wsConnected")
: t("ume.keyAlert.wsDisconnected")}
{` · ${t("ume.keyAlert.publishedOk")} ${Number(keyAlertForwarder?.published_ok || 0)}`}
{` · ${t("ume.keyAlert.queue")} ${Number(keyAlertForwarder?.queue_size || 0)}`}
</p>
) : null}
</div>
<div className="ume-modal-form">
<div className="ops-detail-modal__toolbar filter-inline">
<label className="muted">{t("ume.keyAlert.matchType")}</label>