mirror of
https://github.com/hansjone/netx.git
synced 2026-10-09 03:10:46 +08:00
Heal UME dock-to-Fabric gaps: partial jobs, startup apply, and one-click UI.
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
parent
74672b733f
commit
8444d85005
11 changed files with 280 additions and 5 deletions
|
|
@ -1103,6 +1103,12 @@ const en = {
|
|||
hidePanel: "Close",
|
||||
empty: "No sync records",
|
||||
summary: "{{total}} record(s), {{running}} running",
|
||||
fabricGapTitle: "Topology dock is newer than Fabric / UME World",
|
||||
fabricGapHint:
|
||||
"Dock ME {{dock}}, Fabric UME {{fabric}}. Apply without pulling UME. Last status: {{status}}",
|
||||
applyFabric: "Apply to topology",
|
||||
applyingFabric: "Applying…",
|
||||
applyFabricOk: "Applied to topology",
|
||||
},
|
||||
ne: {
|
||||
title: "Network elements",
|
||||
|
|
|
|||
|
|
@ -1096,6 +1096,12 @@ const zh = {
|
|||
hidePanel: "关闭",
|
||||
empty: "暂无同步记录",
|
||||
summary: "共 {{total}} 条记录,进行中 {{running}}",
|
||||
fabricGapTitle: "拓扑码头已更新,但尚未灌入拓扑管理(Fabric / UME World)",
|
||||
fabricGapHint:
|
||||
"码头 ME {{dock}},Fabric UME {{fabric}};可一键应用(不拉 UME)。上次状态:{{status}}",
|
||||
applyFabric: "应用到拓扑管理",
|
||||
applyingFabric: "正在应用…",
|
||||
applyFabricOk: "已应用到拓扑管理",
|
||||
},
|
||||
ne: {
|
||||
title: "网元清单",
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import { useMemo, useState } from "react";
|
|||
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
||||
import {
|
||||
apiPost,
|
||||
applyUmeTopologyToFabric,
|
||||
disconnectUmeToken,
|
||||
fetchUmeNe,
|
||||
cancelUmeAlarmSubscription,
|
||||
|
|
@ -260,6 +261,17 @@ export function UmePage() {
|
|||
|
||||
const runningTasks = (syncStatusQuery.data?.items || []).filter((x) => String(x.status || "").toLowerCase() === "running");
|
||||
const runtimeTasks = syncStatusQuery.data?.runtime_tasks || [];
|
||||
const topologyFabric = syncStatusQuery.data?.topology_fabric;
|
||||
const needsFabricApply = Boolean(topologyFabric?.needs_apply);
|
||||
const applyFabricMut = useMutation({
|
||||
mutationFn: () => applyUmeTopologyToFabric(),
|
||||
onSuccess: async () => {
|
||||
showOk(t("ume.syncStatus.applyFabricOk"));
|
||||
await queryClient.invalidateQueries({ queryKey: queryKeys.umeSyncStatusAll });
|
||||
await queryClient.invalidateQueries({ queryKey: queryKeys.topologyTree });
|
||||
},
|
||||
onError: (err) => showError(String(err)),
|
||||
});
|
||||
const alarmSub: UmeAlarmSubscriptionStatus =
|
||||
subscriptionStatusQuery.data ??
|
||||
syncStatusQuery.data?.alarm_subscription ??
|
||||
|
|
@ -532,6 +544,32 @@ export function UmePage() {
|
|||
</button>
|
||||
</div>
|
||||
</div>
|
||||
{needsFabricApply ? (
|
||||
<div className="pill pill--high" style={{ marginBottom: 10 }} role="status">
|
||||
<div style={{ fontWeight: 600 }}>{t("ume.syncStatus.fabricGapTitle")}</div>
|
||||
<div className="muted" style={{ marginTop: 4 }}>
|
||||
{t("ume.syncStatus.fabricGapHint")
|
||||
.replace("{{dock}}", String(topologyFabric?.dock_me_count ?? 0))
|
||||
.replace("{{fabric}}", String(topologyFabric?.fabric_ume_count ?? 0))
|
||||
.replace(
|
||||
"{{status}}",
|
||||
String(topologyFabric?.latest_topology_status || topologyFabric?.latest_topology_error || "—"),
|
||||
)}
|
||||
</div>
|
||||
<div className="btn-row" style={{ marginTop: 8 }}>
|
||||
<button
|
||||
type="button"
|
||||
className="btn btn--sm"
|
||||
disabled={applyFabricMut.isPending}
|
||||
onClick={() => applyFabricMut.mutate()}
|
||||
>
|
||||
{applyFabricMut.isPending
|
||||
? t("ume.syncStatus.applyingFabric")
|
||||
: t("ume.syncStatus.applyFabric")}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
) : null}
|
||||
<div className="pt-list">
|
||||
<div className="pt-list-table-wrap">
|
||||
<table className="data-table pt-list-table">
|
||||
|
|
|
|||
|
|
@ -449,6 +449,14 @@ export const fetchUmeSyncStatus = (params: { page: number; pageSize: number }) =
|
|||
return apiGet<UmeSyncStatusResponse>(`/v1/ume/sync/status?${p.toString()}`);
|
||||
};
|
||||
|
||||
/** Dock tables → Fabric + UME World (does not pull from UME). */
|
||||
export const applyUmeTopologyToFabric = () =>
|
||||
apiPost<{
|
||||
ok: boolean;
|
||||
fabric_apply?: Record<string, unknown>;
|
||||
world?: Record<string, unknown>;
|
||||
}>("/v1/topology/world/apply-ume", {});
|
||||
|
||||
export const fetchUmeTokenStatus = () => apiGet<UmeTokenStatus>("/v1/ume/token/status");
|
||||
export const refreshUmeToken = () => apiPost<UmeTokenStatus>("/v1/ume/token/refresh", {});
|
||||
export const disconnectUmeToken = () => apiPost<UmeTokenStatus>("/v1/ume/token/disconnect", {});
|
||||
|
|
|
|||
|
|
@ -112,6 +112,18 @@ export type UmeAlarmSubscriptionStatus = {
|
|||
ws_logs?: UmeWsLogEntry[];
|
||||
};
|
||||
|
||||
export type UmeTopologyFabricGap = {
|
||||
dock_me_count?: number;
|
||||
fabric_ume_count?: number;
|
||||
world_exists?: boolean;
|
||||
world_folder_id?: string;
|
||||
latest_topology_status?: string;
|
||||
latest_topology_error?: string;
|
||||
partial_apply?: boolean;
|
||||
needs_apply?: boolean;
|
||||
error?: string;
|
||||
};
|
||||
|
||||
export type UmeSyncStatusResponse = {
|
||||
total?: number;
|
||||
page?: number;
|
||||
|
|
@ -119,6 +131,7 @@ export type UmeSyncStatusResponse = {
|
|||
items: UmeSyncJobItem[];
|
||||
latest_by_domain?: Record<string, UmeSyncJobItem>;
|
||||
alarm_subscription?: UmeAlarmSubscriptionStatus;
|
||||
topology_fabric?: UmeTopologyFabricGap;
|
||||
runtime_tasks?: Array<{
|
||||
task: string;
|
||||
status: string;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue