import { Fragment, useState } from "react"; import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; import { apiPost, disconnectUmeToken, fetchUmeCurrentAlarms, fetchUmeNe, cancelUmeAlarmSubscription, clearLocalUmeAlarmSubscription, establishUmeAlarmSubscription, fetchUmeAlarmSubscriptionStatus, fetchUmeSyncStatus, fetchUmeTokenStatus, refreshUmeToken, } from "../services/api"; import { HelpHint } from "../components/HelpHint"; import { queryKeys } from "../constants/queryKeys"; import { useI18n } from "../i18n"; import { useToast } from "../hooks/useToast"; import type { UmeAlarmSubscriptionStatus } from "../types"; import { pageCount, runtimeIntervalLabel } from "../utils/display"; import { formatSystemTime } from "../utils/time"; export function UmePage() { const { t } = useI18n(); const { showOk, showError } = useToast(); const queryClient = useQueryClient(); const [tokenOpError, setTokenOpError] = useState(""); const [runtimeTaskError, setRuntimeTaskError] = useState(""); const [subscriptionOpError, setSubscriptionOpError] = useState(""); const [syncPage, setSyncPage] = useState(1); const [syncPageSize, setSyncPageSize] = useState(20); const [neKeyword, setNeKeyword] = useState(""); const [nePage, setNePage] = useState(1); const [nePageSize, setNePageSize] = useState(50); const [expandedNeId, setExpandedNeId] = useState(""); const [nePanelOpen, setNePanelOpen] = useState(false); const [curSeverity, setCurSeverity] = useState(""); const [curCleared, setCurCleared] = useState(""); const [curHostName, setCurHostName] = useState(""); const [curKeyword, setCurKeyword] = useState(""); const [curPage, setCurPage] = useState(1); const [curPageSize, setCurPageSize] = useState(50); const [alarmsPanelOpen, setAlarmsPanelOpen] = useState(false); const syncMutation = useMutation({ mutationFn: async (domains: string[]) => apiPost<{ ok: boolean; jobs: unknown[] }>("/v1/ume/sync", { domains }), onSuccess: async () => { await queryClient.invalidateQueries({ queryKey: queryKeys.umeSyncStatusAll }); await queryClient.invalidateQueries({ queryKey: queryKeys.umeNEAll }); await queryClient.invalidateQueries({ queryKey: queryKeys.umeCurrentAlarmsAll }); }, }); const syncStatusQuery = useQuery({ queryKey: queryKeys.umeSyncStatus(syncPage, syncPageSize), queryFn: () => fetchUmeSyncStatus({ page: syncPage, pageSize: syncPageSize }), staleTime: 5000, refetchInterval: 5000, }); const tokenStatusQuery = useQuery({ queryKey: queryKeys.umeTokenStatus, queryFn: fetchUmeTokenStatus, staleTime: 3000, refetchInterval: 5000, }); const tokenRefreshMutation = useMutation({ mutationFn: refreshUmeToken, onMutate: () => setTokenOpError(""), onSuccess: async (res) => { if (!res?.ok) { const msg = String(res.error || res.error_kind || "token_refresh_failed"); setTokenOpError(msg); showError(msg); } else { setTokenOpError(""); showOk(res.changed ? t("ume.token.renewOkChanged") : t("ume.token.renewOkSame")); } await queryClient.invalidateQueries({ queryKey: queryKeys.umeTokenStatus }); await queryClient.invalidateQueries({ queryKey: queryKeys.umeSyncStatusAll }); }, onError: (err) => { const msg = String(err); setTokenOpError(msg); showError(msg); }, }); const subscriptionStatusQuery = useQuery({ queryKey: queryKeys.umeAlarmSubscription, queryFn: fetchUmeAlarmSubscriptionStatus, staleTime: 3000, refetchInterval: 5000, }); const confirmClearLocalSubscription = (hint?: string) => window.confirm(hint || t("ume.subscription.confirmClearDefault")); const subscriptionEstablishMutation = useMutation({ mutationFn: (opts?: { forceReestablish?: boolean }) => establishUmeAlarmSubscription(opts), onMutate: () => setSubscriptionOpError(""), onSuccess: async (res) => { if (!res?.active) { const msg = t("ume.subscription.establishInvalid"); setSubscriptionOpError(msg); showError(msg); } else { setSubscriptionOpError(""); showOk(res.already_exists ? t("ume.subscription.establishExists") : t("ume.subscription.establishOk")); } await queryClient.invalidateQueries({ queryKey: queryKeys.umeAlarmSubscription }); await queryClient.invalidateQueries({ queryKey: queryKeys.umeSyncStatusAll }); }, onError: (err) => { const msg = String(err); setSubscriptionOpError(msg); showError(msg); }, }); const subscriptionClearLocalMutation = useMutation({ mutationFn: clearLocalUmeAlarmSubscription, onMutate: () => setSubscriptionOpError(""), onSuccess: async () => { setSubscriptionOpError(""); showOk(t("ume.subscription.clearLocalOk")); await queryClient.invalidateQueries({ queryKey: queryKeys.umeAlarmSubscription }); await queryClient.invalidateQueries({ queryKey: queryKeys.umeSyncStatusAll }); }, onError: (err) => { const msg = String(err); setSubscriptionOpError(msg); showError(msg); }, }); const subscriptionCancelMutation = useMutation({ mutationFn: (opts?: { forceClearLocal?: boolean }) => cancelUmeAlarmSubscription(opts), onMutate: () => setSubscriptionOpError(""), onSuccess: async (res) => { if (res?.needs_local_cleanup) { const msg = res.message || t("ume.subscription.serverLostMsg"); if (confirmClearLocalSubscription(msg)) { subscriptionCancelMutation.mutate({ forceClearLocal: true }); return; } setSubscriptionOpError(msg); showError(msg); await queryClient.invalidateQueries({ queryKey: queryKeys.umeAlarmSubscription }); await queryClient.invalidateQueries({ queryKey: queryKeys.umeSyncStatusAll }); return; } if (res?.active) { const msg = t("ume.subscription.cancelFailed"); setSubscriptionOpError(msg); showError(msg); } else { setSubscriptionOpError(""); showOk(res?.ume_already_missing ? t("ume.subscription.cancelOkMissing") : t("ume.subscription.cancelOk")); } await queryClient.invalidateQueries({ queryKey: queryKeys.umeAlarmSubscription }); await queryClient.invalidateQueries({ queryKey: queryKeys.umeSyncStatusAll }); }, onError: (err) => { const msg = String(err); setSubscriptionOpError(msg); showError(msg); }, }); const tokenDisconnectMutation = useMutation({ mutationFn: disconnectUmeToken, onMutate: () => setTokenOpError(""), onSuccess: async (res) => { if (!res?.ok) { const msg = String(res.error || res.error_kind || "token_disconnect_failed"); setTokenOpError(msg); showError(msg); } else { setTokenOpError(""); showOk(t("ume.token.disconnectOk")); } await queryClient.invalidateQueries({ queryKey: queryKeys.umeTokenStatus }); await queryClient.invalidateQueries({ queryKey: queryKeys.umeSyncStatusAll }); }, onError: (err) => { const msg = String(err); setTokenOpError(msg); showError(msg); }, }); const tokenExpiresIn = Number(tokenStatusQuery.data?.expires_in_s || 0); const hasToken = Boolean(tokenStatusQuery.data?.has_token); const tokenNeedsRenewal = hasToken && tokenExpiresIn <= 0; const tokenLevel = !hasToken ? "down" : tokenNeedsRenewal ? "warn" : tokenExpiresIn < 15 ? "down" : tokenExpiresIn < 60 ? "unknown" : "up"; const neQuery = useQuery({ queryKey: queryKeys.umeNE(neKeyword, nePage, nePageSize), queryFn: () => fetchUmeNe({ keyword: neKeyword, page: nePage, pageSize: nePageSize }), staleTime: 5000, }); const currentQuery = useQuery({ queryKey: queryKeys.umeCurrentAlarms(curSeverity, curCleared, curHostName, curKeyword, curPage, curPageSize), queryFn: () => fetchUmeCurrentAlarms({ severity: curSeverity, isCleared: curCleared, hostName: curHostName, keyword: curKeyword, page: curPage, pageSize: curPageSize, }), staleTime: 5000, }); const runningTasks = (syncStatusQuery.data?.items || []).filter((x) => String(x.status || "").toLowerCase() === "running"); const runtimeTasks = syncStatusQuery.data?.runtime_tasks || []; const alarmSub: UmeAlarmSubscriptionStatus = subscriptionStatusQuery.data ?? syncStatusQuery.data?.alarm_subscription ?? { active: false }; const wsConsumer = runtimeTasks.find((x) => x.task === "alarms_current_ws_consumer"); const wsConn = subscriptionStatusQuery.data?.ws_connection; const wsState = String(wsConn?.state || ""); const wsPaused = Boolean(wsConsumer?.paused); const wsLabel = wsConn?.label || (wsPaused ? t("ume.subscription.wssPaused") : wsConsumer?.last_error || wsConsumer?.status || t("common.empty")); const subscriptionActive = Boolean(alarmSub.active); const serverSubLost = Boolean( subscriptionStatusQuery.data?.server_subscription_lost ?? alarmSub.server_subscription_lost, ); const serverSubLostReason = String( subscriptionStatusQuery.data?.server_subscription_lost_reason ?? alarmSub.server_subscription_lost_reason ?? "", ); const currentAlarmsMode = subscriptionStatusQuery.data?.current_alarms_mode ?? alarmSub.current_alarms_mode ?? "rest"; const scheduledSyncSkipped = Boolean( subscriptionStatusQuery.data?.scheduled_sync_skipped ?? alarmSub.scheduled_sync_skipped, ); const wsPillLevel = serverSubLost || wsState === "subscription_lost" ? "warn" : wsPaused || wsState === "paused" ? "warn" : wsState === "connected" ? "up" : wsState === "connecting" || wsState === "reconnecting" || wsState === "waiting_token" ? "warn" : wsState === "no_subscription" || wsState === "disconnected" || wsState === "error" || wsState === "init" ? "down" : String(wsConsumer?.last_error || "").includes("connected") ? "up" : "unknown"; const wsLogs = [...(subscriptionStatusQuery.data?.ws_logs ?? alarmSub.ws_logs ?? [])].reverse(); const subPending = subscriptionEstablishMutation.isPending || subscriptionCancelMutation.isPending || subscriptionClearLocalMutation.isPending; const runtimeTaskMutation = useMutation({ mutationFn: async (vars: { task: string; action: "pause" | "resume" }) => apiPost<{ ok: boolean }>(`/v1/ume/runtime/tasks/${encodeURIComponent(vars.task)}/${vars.action}`, {}), onMutate: () => setRuntimeTaskError(""), onSuccess: async () => { await queryClient.invalidateQueries({ queryKey: queryKeys.umeSyncStatusAll }); }, onError: (err) => setRuntimeTaskError(String(err)), }); const syncTotal = Number(syncStatusQuery.data?.total || 0); const syncPages = pageCount(syncTotal, syncPageSize); const neTotal = Number(neQuery.data?.total || 0); const nePages = pageCount(neTotal, nePageSize); const curTotal = Number(currentQuery.data?.total || 0); const curPages = pageCount(curTotal, curPageSize); const perPage = (n: number) => t("common.perPage", { n: String(n) }); return ( <>

{t("ume.token.title")}

token:{" "} {!hasToken ? t("ume.token.disconnected") : tokenNeedsRenewal ? t("ume.token.connectedRenew") : t("ume.token.connected")} {t("ume.token.expiresIn")}:{" "} {typeof tokenStatusQuery.data?.expires_in_s === "number" ? tokenNeedsRenewal ? t("ume.token.needsRenew") : `${tokenStatusQuery.data.expires_in_s}s` : t("common.empty")} {tokenStatusQuery.data?.token_preview ? ( {t("ume.token.preview")}: {tokenStatusQuery.data.token_preview} ) : null}
{(tokenOpError || tokenRefreshMutation.error || tokenDisconnectMutation.error) && (
{t("common.opFailed")}: {tokenOpError || String(tokenRefreshMutation.error || tokenDisconnectMutation.error)}
)}

{t("ume.subscription.title")}

{t("ume.subscription.sub")}: {subscriptionActive ? t("ume.subscription.subActive") : t("ume.subscription.subInactive")} {subscriptionActive && alarmSub.subscription_id ? ( id: {String(alarmSub.subscription_id).slice(0, 12)}… ) : null} {subscriptionActive || wsConsumer ? ( WSS: {wsLabel} ) : null}
{scheduledSyncSkipped && !serverSubLost ? (
{t("ume.subscription.wssModeBanner", { mode: currentAlarmsMode })}
) : null} {serverSubLost ? (
{t("ume.subscription.serverLostBanner")} {serverSubLostReason ? (
{serverSubLostReason.slice(0, 280)}
) : null}
) : null}
{serverSubLost && subscriptionActive ? ( ) : null}
{subscriptionOpError ? (
{t("ume.subscription.opFailed")}: {subscriptionOpError}
) : null}
{t("ume.subscription.wsLogs")} {wsConsumer?.last_run_at ? t("ume.subscription.wsActivity", { time: formatSystemTime(String(wsConsumer.last_run_at)) }) : t("ume.subscription.wsAutoRefresh")}
{wsLogs.length === 0 ? (
{t("ume.subscription.wsLogsEmpty")}
) : ( 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 (
{formatSystemTime(line.ts)} [{lvl}] {line.message} {line.subscription_id ? ( · {String(line.subscription_id).slice(0, 8)}… ) : null}
); }) )}

{t("ume.sync.title")}

{syncMutation.error && (
{t("ume.sync.failed")}: {String(syncMutation.error)}
)}

{t("ume.tasks.currentTitle")}

0 ? "unknown" : "up"}`}> {t("ume.tasks.running")}: {runningTasks.length}
{runningTasks.map((x) => ( ))} {!syncStatusQuery.isLoading && runningTasks.length === 0 && ( )}
ID domain trigger_mode status started_at error
{x.id} {x.domain} {x.trigger_mode} {x.status} {formatSystemTime(x.started_at)} {x.error_message || t("common.empty")}
{t("ume.tasks.noRunning")}

{t("ume.tasks.runtimeTitle")}

{runtimeTaskError ? (
{t("ume.tasks.runtimeOpFailed")}: {runtimeTaskError}
) : null} {runtimeTasks.map((x) => ( ))} {!syncStatusQuery.isLoading && runtimeTasks.length === 0 && ( )}
task interval status last_run_at last_error actions
{x.task} {runtimeIntervalLabel(x.interval_label)} {x.status} {x.last_run_at ? formatSystemTime(x.last_run_at) : t("common.empty")} {x.last_error || t("common.empty")} {Boolean(x.paused) ? ( ) : ( )}
{t("ume.tasks.noRuntime")}

{t("ume.syncStatus.title")}

{(syncStatusQuery.data?.items || []).map((x) => ( ))} {!syncStatusQuery.isLoading && (syncStatusQuery.data?.items || []).length === 0 && ( )}
ID domain status pulled inserted updated deleted started_at ended_at error
{x.id} {x.domain} {x.status} {x.pulled_count} {x.inserted_count} {x.updated_count} {Number(x.deleted ?? 0)} {formatSystemTime(x.started_at)} {x.ended_at ? formatSystemTime(x.ended_at) : t("common.empty")} {x.error_message || t("common.empty")}
{t("ume.syncStatus.empty")}
{t("common.pagerMeta", { total: syncTotal, page: syncPage, pages: syncPages })}

{t("ume.ne.title")}

{nePanelOpen ? ( <>
setNeKeyword(e.target.value)} />
{(neQuery.data?.items || []).map((x) => ( {expandedNeId === x.ne_id ? ( ) : null} ))}
ne_id user_label ip type device_level host_name hw_ver last_seen
{x.user_label} {x.ip_address} {x.ne_type} {x.device_level || t("common.empty")} {x.host_name || t("common.empty")} {x.hardware_version || t("common.empty")} {x.last_seen_at ? formatSystemTime(x.last_seen_at) : t("common.empty")}
consistent_state: {x.consistent_state || t("common.empty")}
admin_status: {x.admin_status || t("common.empty")}
connection_status: {x.connection_status || t("common.empty")}
maintain_status: {x.maintain_status || t("common.empty")}
address_type: {x.address_type || t("common.empty")}
location: {x.location || t("common.empty")}
loopback: {x.loopback || t("common.empty")}
net_mask: {x.net_mask || t("common.empty")}
mac: {x.mac || t("common.empty")}
interface_version: {x.interface_version || t("common.empty")}
create_time: {x.create_time || t("common.empty")}
creator: {x.creator || t("common.empty")}
{t("common.pagerMeta", { total: neTotal, page: nePage, pages: nePages })}
) : null}

{t("ume.alarms.title")}

{alarmsPanelOpen ? ( <>
setCurKeyword(e.target.value)} /> setCurHostName(e.target.value)} />
{(currentQuery.data?.items || []).map((x) => ( ))}
time_created severity ne_id host_name ne_type cause
{formatSystemTime(x.time_created)} {x.perceived_severity} {x.ne_id} {(x.host_name || "").trim() ? ( ) : ( {t("common.empty")} )} {x.ne_type ?? ""} {x.native_probable_cause}
{t("common.pagerMeta", { total: curTotal, page: curPage, pages: curPages })}
) : null}
); }