mirror of
https://github.com/hansjone/netx.git
synced 2026-10-09 05:30:46 +08:00
Move integration health into active tasks and fix dark topology labels.
Drop header netx-api/oclaw pills, restore UME/OClaw runtime rows in ops tasks, and raise node/edge label contrast on the dark canvas. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
parent
4dd121b42f
commit
83acb5eef1
10 changed files with 106 additions and 128 deletions
|
|
@ -332,13 +332,50 @@ def _ne_connect_items(db: Session, actors: dict[str, str]) -> list[dict[str, Any
|
||||||
|
|
||||||
def _ume_runtime_items() -> list[dict[str, Any]]:
|
def _ume_runtime_items() -> list[dict[str, Any]]:
|
||||||
try:
|
try:
|
||||||
from .main import _list_runtime_tasks
|
from .ume_support import _list_runtime_tasks
|
||||||
except Exception:
|
except Exception:
|
||||||
return []
|
return []
|
||||||
|
fwd: dict[str, Any] | None = None
|
||||||
|
try:
|
||||||
|
from .oclaw_alarm_forwarder import forwarder_status
|
||||||
|
|
||||||
|
fwd = forwarder_status()
|
||||||
|
except Exception:
|
||||||
|
fwd = None
|
||||||
|
ws_conn: dict[str, Any] | None = None
|
||||||
|
try:
|
||||||
|
from .ume_alarm_ws import get_ws_connection_status
|
||||||
|
|
||||||
|
ws_conn = get_ws_connection_status()
|
||||||
|
except Exception:
|
||||||
|
ws_conn = None
|
||||||
items: list[dict[str, Any]] = []
|
items: list[dict[str, Any]] = []
|
||||||
for row in _list_runtime_tasks():
|
for row in _list_runtime_tasks():
|
||||||
task = str(row.get("task") or "")
|
task = str(row.get("task") or "")
|
||||||
status = str(row.get("status") or "unknown")
|
status = str(row.get("status") or "unknown")
|
||||||
|
progress = str(row.get("interval_label") or "")
|
||||||
|
detail = str(row.get("last_error") or "")[:240]
|
||||||
|
if task == "oclaw_alarm_forwarder" and isinstance(fwd, dict):
|
||||||
|
bits: list[str] = []
|
||||||
|
if not bool(fwd.get("enabled")):
|
||||||
|
bits.append("disabled")
|
||||||
|
elif bool(fwd.get("paused")):
|
||||||
|
bits.append("paused")
|
||||||
|
elif bool(fwd.get("connected")):
|
||||||
|
bits.append("connected")
|
||||||
|
else:
|
||||||
|
bits.append("disconnected")
|
||||||
|
q = int(fwd.get("queue_size") or 0)
|
||||||
|
if q > 0:
|
||||||
|
bits.append(f"q={q}")
|
||||||
|
pub_ok = int(fwd.get("published_ok") or 0)
|
||||||
|
if pub_ok > 0:
|
||||||
|
bits.append(f"pub={pub_ok}")
|
||||||
|
progress = " · ".join([p for p in (progress, *bits) if p])
|
||||||
|
elif task == "alarms_current_ws_consumer" and isinstance(ws_conn, dict):
|
||||||
|
state = str(ws_conn.get("state") or ws_conn.get("status") or "").strip()
|
||||||
|
if state:
|
||||||
|
progress = " · ".join([p for p in (progress, state) if p])
|
||||||
# Always show UME background loops so ops can see paused/idle too.
|
# Always show UME background loops so ops can see paused/idle too.
|
||||||
items.append(
|
items.append(
|
||||||
_item(
|
_item(
|
||||||
|
|
@ -350,8 +387,8 @@ def _ume_runtime_items() -> list[dict[str, Any]]:
|
||||||
actor="system",
|
actor="system",
|
||||||
started_at=None,
|
started_at=None,
|
||||||
updated_at=None,
|
updated_at=None,
|
||||||
progress=str(row.get("interval_label") or ""),
|
progress=progress,
|
||||||
detail=str(row.get("last_error") or "")[:240],
|
detail=detail,
|
||||||
href="/ume",
|
href="/ume",
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
@ -437,6 +474,7 @@ def list_ops_tasks(db: Session) -> dict[str, Any]:
|
||||||
"collecting",
|
"collecting",
|
||||||
"pending",
|
"pending",
|
||||||
"paused",
|
"paused",
|
||||||
|
"idle",
|
||||||
"connecting",
|
"connecting",
|
||||||
"ready",
|
"ready",
|
||||||
"testing",
|
"testing",
|
||||||
|
|
@ -451,9 +489,10 @@ def list_ops_tasks(db: Session) -> dict[str, Any]:
|
||||||
"connecting": 3,
|
"connecting": 3,
|
||||||
"pending": 4,
|
"pending": 4,
|
||||||
"paused": 5,
|
"paused": 5,
|
||||||
"ready": 6,
|
"idle": 6,
|
||||||
"detached": 7,
|
"ready": 7,
|
||||||
"error": 8,
|
"detached": 8,
|
||||||
|
"error": 9,
|
||||||
}
|
}
|
||||||
items.sort(
|
items.sort(
|
||||||
key=lambda x: (
|
key=lambda x: (
|
||||||
|
|
|
||||||
|
|
@ -30,6 +30,7 @@ from .runtime_task_messages import (
|
||||||
RT_UME_WS_DISABLED_NO_BASE_URL,
|
RT_UME_WS_DISABLED_NO_BASE_URL,
|
||||||
RT_WSS_ACTIVE_SKIP_REST,
|
RT_WSS_ACTIVE_SKIP_REST,
|
||||||
)
|
)
|
||||||
|
from .oclaw_alarm_forwarder import is_forwarder_enabled
|
||||||
from .ume_alarm_ws import (
|
from .ume_alarm_ws import (
|
||||||
begin_startup_alarm_sync_gate,
|
begin_startup_alarm_sync_gate,
|
||||||
complete_startup_alarm_sync_gate,
|
complete_startup_alarm_sync_gate,
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,9 @@
|
||||||
import { lazy, Suspense } from "react";
|
import { lazy, Suspense } from "react";
|
||||||
import { Navigate, Route, Routes, useLocation } from "react-router-dom";
|
import { Navigate, Route, Routes, useLocation } from "react-router-dom";
|
||||||
import { useQuery } from "@tanstack/react-query";
|
|
||||||
import { AppLayout } from "./layout/AppLayout";
|
import { AppLayout } from "./layout/AppLayout";
|
||||||
import { queryKeys } from "./constants/queryKeys";
|
|
||||||
import { WorkbenchPage } from "./pages/WorkbenchPage";
|
import { WorkbenchPage } from "./pages/WorkbenchPage";
|
||||||
import { LoginPage } from "./pages/LoginPage";
|
import { LoginPage } from "./pages/LoginPage";
|
||||||
import { ForceChangePasswordPage } from "./pages/ForceChangePasswordPage";
|
import { ForceChangePasswordPage } from "./pages/ForceChangePasswordPage";
|
||||||
import { fetchIntegrationStatus } from "./services/api";
|
|
||||||
import { useAuth } from "./auth/AuthContext";
|
import { useAuth } from "./auth/AuthContext";
|
||||||
|
|
||||||
const CollectPage = lazy(() => import("./pages/CollectPage").then((m) => ({ default: m.CollectPage })));
|
const CollectPage = lazy(() => import("./pages/CollectPage").then((m) => ({ default: m.CollectPage })));
|
||||||
|
|
@ -84,13 +81,6 @@ function PageFallback() {
|
||||||
|
|
||||||
function ProtectedApp() {
|
function ProtectedApp() {
|
||||||
const { ready, user } = useAuth();
|
const { ready, user } = useAuth();
|
||||||
const integrationsQuery = useQuery({
|
|
||||||
queryKey: queryKeys.integrationsStatus,
|
|
||||||
queryFn: fetchIntegrationStatus,
|
|
||||||
refetchInterval: 10_000,
|
|
||||||
staleTime: 5_000,
|
|
||||||
enabled: ready && Boolean(user) && !user?.must_change_password,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!ready) {
|
if (!ready) {
|
||||||
return (
|
return (
|
||||||
|
|
@ -107,32 +97,7 @@ function ProtectedApp() {
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AppLayout
|
<AppLayout>
|
||||||
connections={{
|
|
||||||
netxApi: integrationsQuery.data?.netx_api?.status ?? (integrationsQuery.isError ? "down" : "unknown"),
|
|
||||||
netxApiLatencyMs:
|
|
||||||
typeof integrationsQuery.data?.db?.latency_ms === "number" ? integrationsQuery.data.db.latency_ms : undefined,
|
|
||||||
oclawBridge:
|
|
||||||
integrationsQuery.data?.oclaw_bridge?.status ?? (integrationsQuery.isError ? "down" : "unknown"),
|
|
||||||
oclawBridgeLatencyMs: undefined,
|
|
||||||
oclawBridgeErrorKind:
|
|
||||||
typeof integrationsQuery.data?.oclaw_bridge?.error_kind === "string"
|
|
||||||
? integrationsQuery.data.oclaw_bridge.error_kind
|
|
||||||
: undefined,
|
|
||||||
oclawBridgeError:
|
|
||||||
typeof integrationsQuery.data?.oclaw_bridge?.error === "string"
|
|
||||||
? integrationsQuery.data.oclaw_bridge.error
|
|
||||||
: undefined,
|
|
||||||
oclawBridgeQueueSize:
|
|
||||||
typeof integrationsQuery.data?.oclaw_bridge?.queue_size === "number"
|
|
||||||
? integrationsQuery.data.oclaw_bridge.queue_size
|
|
||||||
: undefined,
|
|
||||||
oclawBridgePublishedOk:
|
|
||||||
typeof integrationsQuery.data?.oclaw_bridge?.published_ok === "number"
|
|
||||||
? integrationsQuery.data.oclaw_bridge.published_ok
|
|
||||||
: undefined,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Suspense fallback={<PageFallback />}>
|
<Suspense fallback={<PageFallback />}>
|
||||||
<Routes>
|
<Routes>
|
||||||
<Route path="/" element={<WorkbenchPage />} />
|
<Route path="/" element={<WorkbenchPage />} />
|
||||||
|
|
|
||||||
|
|
@ -1654,6 +1654,7 @@ const en = {
|
||||||
detached: "Detached (grace)",
|
detached: "Detached (grace)",
|
||||||
pending: "Pending",
|
pending: "Pending",
|
||||||
paused: "Paused",
|
paused: "Paused",
|
||||||
|
idle: "Idle",
|
||||||
error: "Error",
|
error: "Error",
|
||||||
failed: "Failed",
|
failed: "Failed",
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -1645,6 +1645,7 @@ const zh = {
|
||||||
detached: "已断开(宽限)",
|
detached: "已断开(宽限)",
|
||||||
pending: "排队",
|
pending: "排队",
|
||||||
paused: "暂停",
|
paused: "暂停",
|
||||||
|
idle: "空闲",
|
||||||
error: "错误",
|
error: "错误",
|
||||||
failed: "失败",
|
failed: "失败",
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -7116,7 +7116,7 @@ pre {
|
||||||
}
|
}
|
||||||
|
|
||||||
.topo-node.is-selected .topo-node__glyph {
|
.topo-node.is-selected .topo-node__glyph {
|
||||||
outline: 2px solid #334155;
|
outline: 2px solid #60a5fa;
|
||||||
outline-offset: 3px;
|
outline-offset: 3px;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
}
|
}
|
||||||
|
|
@ -7127,10 +7127,14 @@ pre {
|
||||||
}
|
}
|
||||||
|
|
||||||
.topo-node.is-selected .topo-node__caption {
|
.topo-node.is-selected .topo-node__caption {
|
||||||
color: #0f172a;
|
color: #f8fafc;
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.topo-node.is-selected .topo-node__caption-name {
|
||||||
|
color: #93c5fd;
|
||||||
|
}
|
||||||
|
|
||||||
/* Selected link: light highlight only (do not alter geometry / arrows). */
|
/* Selected link: light highlight only (do not alter geometry / arrows). */
|
||||||
.topo-canvas .react-flow__edge.selected .react-flow__edge-path {
|
.topo-canvas .react-flow__edge.selected .react-flow__edge-path {
|
||||||
stroke: #2563eb !important;
|
stroke: #2563eb !important;
|
||||||
|
|
@ -7155,10 +7159,13 @@ pre {
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 1px;
|
gap: 1px;
|
||||||
margin-top: 3px;
|
margin-top: 3px;
|
||||||
color: #334155;
|
color: #e2e8f0;
|
||||||
line-height: 1.2;
|
line-height: 1.2;
|
||||||
max-width: 160px;
|
max-width: 160px;
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
|
text-shadow:
|
||||||
|
0 0 3px rgba(15, 23, 42, 0.95),
|
||||||
|
0 1px 2px rgba(15, 23, 42, 0.85);
|
||||||
}
|
}
|
||||||
|
|
||||||
.topo-node__caption-name {
|
.topo-node__caption-name {
|
||||||
|
|
@ -7166,7 +7173,7 @@ pre {
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
font-size: 11px;
|
font-size: 11px;
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
color: #0f172a;
|
color: #f1f5f9;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
|
|
@ -7176,7 +7183,7 @@ pre {
|
||||||
display: block;
|
display: block;
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
font-size: 10px;
|
font-size: 10px;
|
||||||
color: #64748b;
|
color: #94a3b8;
|
||||||
font-family: ui-monospace, SFMono-Regular, Consolas, "Liberation Mono", Menlo, monospace;
|
font-family: ui-monospace, SFMono-Regular, Consolas, "Liberation Mono", Menlo, monospace;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
|
@ -7194,17 +7201,19 @@ pre {
|
||||||
|
|
||||||
.topo-edge-label {
|
.topo-edge-label {
|
||||||
font-size: 11px;
|
font-size: 11px;
|
||||||
color: #cbd5e1;
|
color: #e2e8f0;
|
||||||
background: rgba(15, 23, 42, 0.92);
|
background: transparent;
|
||||||
border: 1px solid rgba(148, 163, 184, 0.28);
|
border: 0;
|
||||||
border-radius: 4px;
|
border-radius: 0;
|
||||||
padding: 1px 6px;
|
padding: 0;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
|
text-shadow:
|
||||||
|
0 0 3px rgba(15, 23, 42, 0.95),
|
||||||
|
0 1px 2px rgba(15, 23, 42, 0.85);
|
||||||
}
|
}
|
||||||
|
|
||||||
.topo-edge-label.is-selected {
|
.topo-edge-label.is-selected {
|
||||||
color: #93c5fd;
|
color: #93c5fd;
|
||||||
border-color: rgba(59, 130, 246, 0.55);
|
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -11,23 +11,11 @@ import { openOrFocusModule } from "../utils/moduleWindows";
|
||||||
import { returnToWorkbench } from "../utils/workbench";
|
import { returnToWorkbench } from "../utils/workbench";
|
||||||
import { useAuth } from "../auth/AuthContext";
|
import { useAuth } from "../auth/AuthContext";
|
||||||
|
|
||||||
type ConnLevel = "up" | "down" | "unknown";
|
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
connections: {
|
|
||||||
netxApi: ConnLevel;
|
|
||||||
netxApiLatencyMs?: number;
|
|
||||||
oclawBridge: ConnLevel;
|
|
||||||
oclawBridgeLatencyMs?: number;
|
|
||||||
oclawBridgeErrorKind?: string;
|
|
||||||
oclawBridgeError?: string;
|
|
||||||
oclawBridgeQueueSize?: number;
|
|
||||||
oclawBridgePublishedOk?: number;
|
|
||||||
};
|
|
||||||
children: ReactNode;
|
children: ReactNode;
|
||||||
};
|
};
|
||||||
|
|
||||||
export function AppLayout({ connections, children }: Props) {
|
export function AppLayout({ children }: Props) {
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
const { pathname } = useLocation();
|
const { pathname } = useLocation();
|
||||||
const { user, logout } = useAuth();
|
const { user, logout } = useAuth();
|
||||||
|
|
@ -41,22 +29,6 @@ export function AppLayout({ connections, children }: Props) {
|
||||||
staleTime: 1500,
|
staleTime: 1500,
|
||||||
});
|
});
|
||||||
const activeTasks = opsTasksQuery.data?.active ?? 0;
|
const activeTasks = opsTasksQuery.data?.active ?? 0;
|
||||||
const netxSuffix =
|
|
||||||
typeof connections.netxApiLatencyMs === "number" ? ` (${connections.netxApiLatencyMs}ms)` : "";
|
|
||||||
const oclawSuffix =
|
|
||||||
connections.oclawBridge === "up"
|
|
||||||
? typeof connections.oclawBridgePublishedOk === "number"
|
|
||||||
? ` (pub ${connections.oclawBridgePublishedOk})`
|
|
||||||
: ""
|
|
||||||
: connections.oclawBridgeErrorKind
|
|
||||||
? ` (${connections.oclawBridgeErrorKind})`
|
|
||||||
: typeof connections.oclawBridgeQueueSize === "number" && connections.oclawBridgeQueueSize > 0
|
|
||||||
? ` (q ${connections.oclawBridgeQueueSize})`
|
|
||||||
: "";
|
|
||||||
const oclawTitle =
|
|
||||||
connections.oclawBridge === "down" && connections.oclawBridgeError
|
|
||||||
? connections.oclawBridgeError
|
|
||||||
: undefined;
|
|
||||||
|
|
||||||
useAppWindowRegistration(pathname);
|
useAppWindowRegistration(pathname);
|
||||||
|
|
||||||
|
|
@ -88,47 +60,28 @@ export function AppLayout({ connections, children }: Props) {
|
||||||
) : null}
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
<div className="app-brand__actions">
|
<div className="app-brand__actions">
|
||||||
<div className="app-brand__actions-group">
|
|
||||||
<span className={`conn-pill conn-pill--on-brand conn-pill--${connections.netxApi}`}>
|
|
||||||
{t("layout.netxApi")}: {connections.netxApi}
|
|
||||||
{netxSuffix}
|
|
||||||
</span>
|
|
||||||
<span
|
|
||||||
className={`conn-pill conn-pill--on-brand conn-pill--${connections.oclawBridge}`}
|
|
||||||
title={oclawTitle}
|
|
||||||
>
|
|
||||||
{t("layout.oclawBridge")}: {connections.oclawBridge}
|
|
||||||
{oclawSuffix}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
{user ? (
|
{user ? (
|
||||||
<>
|
<div className="app-brand__actions-group">
|
||||||
<span className="app-brand__actions-sep" aria-hidden />
|
<button
|
||||||
<div className="app-brand__actions-group">
|
type="button"
|
||||||
<button
|
className={`conn-pill conn-pill--on-brand conn-pill--tasks conn-pill--${activeTasks > 0 ? "up" : "unknown"}`}
|
||||||
type="button"
|
title={t("layout.activeTasksHint")}
|
||||||
className={`conn-pill conn-pill--on-brand conn-pill--tasks conn-pill--${activeTasks > 0 ? "up" : "unknown"}`}
|
onClick={() => openOrFocusModule({ moduleId: "audit", path: "/audit/tasks" })}
|
||||||
title={t("layout.activeTasksHint")}
|
>
|
||||||
onClick={() => openOrFocusModule({ moduleId: "audit", path: "/audit/tasks" })}
|
{t("layout.activeTasks", { count: activeTasks })}
|
||||||
>
|
</button>
|
||||||
{t("layout.activeTasks", { count: activeTasks })}
|
<span className="conn-pill conn-pill--on-brand conn-pill--user" title={user.role}>
|
||||||
</button>
|
{user.username}
|
||||||
<span
|
</span>
|
||||||
className="conn-pill conn-pill--on-brand conn-pill--user"
|
<button
|
||||||
title={user.role}
|
type="button"
|
||||||
>
|
className="header-menu__trigger header-menu__trigger--on-brand"
|
||||||
{user.username}
|
onClick={() => void logout()}
|
||||||
</span>
|
>
|
||||||
<button
|
{t("auth.logout")}
|
||||||
type="button"
|
</button>
|
||||||
className="header-menu__trigger header-menu__trigger--on-brand"
|
<HeaderMenu />
|
||||||
onClick={() => void logout()}
|
</div>
|
||||||
>
|
|
||||||
{t("auth.logout")}
|
|
||||||
</button>
|
|
||||||
<HeaderMenu />
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
) : (
|
) : (
|
||||||
<HeaderMenu />
|
<HeaderMenu />
|
||||||
)}
|
)}
|
||||||
|
|
|
||||||
|
|
@ -334,7 +334,7 @@ export function UmePage() {
|
||||||
setKeyAlertNeTypes([]);
|
setKeyAlertNeTypes([]);
|
||||||
showOk(t("ume.keyAlert.addOk"));
|
showOk(t("ume.keyAlert.addOk"));
|
||||||
await queryClient.invalidateQueries({ queryKey: queryKeys.umeKeyAlertMonitorAll });
|
await queryClient.invalidateQueries({ queryKey: queryKeys.umeKeyAlertMonitorAll });
|
||||||
await queryClient.invalidateQueries({ queryKey: queryKeys.integrationsStatus });
|
await queryClient.invalidateQueries({ queryKey: ["opsTasks"] });
|
||||||
},
|
},
|
||||||
onError: (err) => {
|
onError: (err) => {
|
||||||
const msg = String(err);
|
const msg = String(err);
|
||||||
|
|
@ -349,7 +349,7 @@ export function UmePage() {
|
||||||
onSuccess: async () => {
|
onSuccess: async () => {
|
||||||
showOk(t("ume.keyAlert.deleteOk"));
|
showOk(t("ume.keyAlert.deleteOk"));
|
||||||
await queryClient.invalidateQueries({ queryKey: queryKeys.umeKeyAlertMonitorAll });
|
await queryClient.invalidateQueries({ queryKey: queryKeys.umeKeyAlertMonitorAll });
|
||||||
await queryClient.invalidateQueries({ queryKey: queryKeys.integrationsStatus });
|
await queryClient.invalidateQueries({ queryKey: ["opsTasks"] });
|
||||||
},
|
},
|
||||||
onError: (err) => {
|
onError: (err) => {
|
||||||
const msg = String(err);
|
const msg = String(err);
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@ const POLL_MS = 4000;
|
||||||
function statusTone(status: string): string {
|
function statusTone(status: string): string {
|
||||||
const s = status.toLowerCase();
|
const s = status.toLowerCase();
|
||||||
if (s === "collecting" || s === "running" || s === "connecting" || s === "testing") return "running";
|
if (s === "collecting" || s === "running" || s === "connecting" || s === "testing") return "running";
|
||||||
if (s === "ready") return "ok";
|
if (s === "ready" || s === "idle") return "ok";
|
||||||
if (s === "paused" || s === "pending" || s === "detached") return "paused";
|
if (s === "paused" || s === "pending" || s === "detached") return "paused";
|
||||||
if (s === "failed" || s === "error") return "stopped";
|
if (s === "failed" || s === "error") return "stopped";
|
||||||
return "other";
|
return "other";
|
||||||
|
|
@ -36,9 +36,20 @@ function triggerLabel(trigger: string, t: (k: string, vars?: Record<string, stri
|
||||||
return tr === key ? raw : tr;
|
return tr === key ? raw : tr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function runtimeTaskSubject(
|
||||||
|
taskId: string,
|
||||||
|
t: (k: string, vars?: Record<string, string | number>) => string,
|
||||||
|
): string {
|
||||||
|
const key = `ume.tasks.runtimeTask.${taskId}`;
|
||||||
|
const label = t(key);
|
||||||
|
return label === key ? taskId : label;
|
||||||
|
}
|
||||||
|
|
||||||
function taskTitle(row: OpsTaskItem, t: (k: string, vars?: Record<string, string | number>) => string): string {
|
function taskTitle(row: OpsTaskItem, t: (k: string, vars?: Record<string, string | number>) => string): string {
|
||||||
const kind = kindLabel(row.kind, t);
|
const kind = kindLabel(row.kind, t);
|
||||||
const subject = String(row.title || "").trim();
|
const subjectRaw = String(row.title || row.id || "").trim();
|
||||||
|
const subject =
|
||||||
|
row.kind === "ume_runtime" && subjectRaw ? runtimeTaskSubject(subjectRaw, t) : subjectRaw;
|
||||||
if (!subject) return kind;
|
if (!subject) return kind;
|
||||||
// Avoid "Kind · Kind · x" if an old backend still prefixed the localized kind.
|
// Avoid "Kind · Kind · x" if an old backend still prefixed the localized kind.
|
||||||
if (subject === kind || subject.startsWith(`${kind} · `) || subject.startsWith(`${kind}·`)) {
|
if (subject === kind || subject.startsWith(`${kind} · `) || subject.startsWith(`${kind}·`)) {
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,6 @@ export function ParallelEdge({
|
||||||
markerEnd,
|
markerEnd,
|
||||||
label,
|
label,
|
||||||
labelStyle,
|
labelStyle,
|
||||||
labelBgStyle,
|
|
||||||
data,
|
data,
|
||||||
selected,
|
selected,
|
||||||
}: EdgeProps) {
|
}: EdgeProps) {
|
||||||
|
|
@ -41,7 +40,6 @@ export function ParallelEdge({
|
||||||
transform: `translate(-50%, -50%) translate(${labelX}px,${labelY}px)`,
|
transform: `translate(-50%, -50%) translate(${labelX}px,${labelY}px)`,
|
||||||
pointerEvents: "none",
|
pointerEvents: "none",
|
||||||
...labelStyle,
|
...labelStyle,
|
||||||
...(labelBgStyle || {}),
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{label}
|
{label}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue