mirror of
https://github.com/hansjone/netx.git
synced 2026-10-09 05:30:46 +08:00
Add LLDP collect retry for failed and weak-evidence NEs.
Mirror config-sync retry_failed so operators can re-collect hard failures and empty/stub LLDP results without re-running the full scope. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
parent
527472976a
commit
dcdf4147a6
10 changed files with 423 additions and 23 deletions
|
|
@ -319,6 +319,7 @@ const en = {
|
|||
lldpLinks: {
|
||||
title: "LLDP links",
|
||||
collectNow: "Collect now",
|
||||
retryFailed: "Retry failed",
|
||||
started: "LLDP collect started",
|
||||
pause: "Pause",
|
||||
resume: "Resume",
|
||||
|
|
@ -396,6 +397,7 @@ const en = {
|
|||
manual: "Manual",
|
||||
schedule: "Scheduled",
|
||||
topology: "Topology canvas",
|
||||
retry_failed: "Retry failed",
|
||||
},
|
||||
jobScope: {
|
||||
all: "All NEs",
|
||||
|
|
|
|||
|
|
@ -316,6 +316,7 @@ const zh = {
|
|||
lldpLinks: {
|
||||
title: "LLDP 链路",
|
||||
collectNow: "立即采集",
|
||||
retryFailed: "一键重试失败",
|
||||
started: "已启动 LLDP 采集",
|
||||
pause: "暂停",
|
||||
resume: "继续",
|
||||
|
|
@ -392,6 +393,7 @@ const zh = {
|
|||
manual: "手动",
|
||||
schedule: "周期",
|
||||
topology: "拓扑画布",
|
||||
retry_failed: "重试失败",
|
||||
},
|
||||
jobScope: {
|
||||
all: "全部网元",
|
||||
|
|
|
|||
|
|
@ -48,11 +48,23 @@ function lldpScopeLabel(t: (k: string) => string, scope: string): string {
|
|||
|
||||
function lldpItemResultLabel(
|
||||
t: (k: string) => string,
|
||||
it: { ok?: boolean; parser_stub?: boolean | string | null; unmatched_count?: number; unmatched?: unknown[] },
|
||||
it: {
|
||||
ok?: boolean;
|
||||
parser_stub?: boolean | string | null;
|
||||
unmatched_count?: number;
|
||||
unmatched?: unknown[];
|
||||
error?: string | null;
|
||||
},
|
||||
): string {
|
||||
if (!it.ok) return t("lldpLinks.itemResult.fail");
|
||||
const err = String(it.error || "").trim();
|
||||
const weak =
|
||||
Boolean(it.parser_stub) ||
|
||||
err === "parser_stub" ||
|
||||
err === "empty_cli_output" ||
|
||||
err === "vendor_or_device_type_required";
|
||||
if (!it.ok || weak) return t("lldpLinks.itemResult.fail");
|
||||
const unmatchedCount = it.unmatched_count ?? (it.unmatched?.length || 0);
|
||||
if (it.parser_stub || unmatchedCount > 0) return t("lldpLinks.itemResult.warn");
|
||||
if (unmatchedCount > 0) return t("lldpLinks.itemResult.warn");
|
||||
return t("lldpLinks.itemResult.ok");
|
||||
}
|
||||
|
||||
|
|
@ -250,7 +262,7 @@ export function LldpLinksPage() {
|
|||
});
|
||||
|
||||
const startMut = useMutation({
|
||||
mutationFn: () => startLldpCollect(),
|
||||
mutationFn: (mode: "full" | "retry_failed" = "full") => startLldpCollect({ mode }),
|
||||
onSuccess: async () => {
|
||||
showOk(t("lldpLinks.started"));
|
||||
await refresh();
|
||||
|
|
@ -322,10 +334,17 @@ export function LldpLinksPage() {
|
|||
type="button"
|
||||
className="btn-primary"
|
||||
disabled={Boolean(running) || startMut.isPending}
|
||||
onClick={() => startMut.mutate()}
|
||||
onClick={() => startMut.mutate("full")}
|
||||
>
|
||||
{t("lldpLinks.collectNow")}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
disabled={Boolean(running) || startMut.isPending || !(last && (last.fail_count || 0) > 0)}
|
||||
onClick={() => startMut.mutate("retry_failed")}
|
||||
>
|
||||
{t("lldpLinks.retryFailed")}
|
||||
</button>
|
||||
{running?.status === "running" || running?.status === "pending" ? (
|
||||
<button type="button" onClick={() => pauseMut.mutate(running.id)} disabled={pauseMut.isPending}>
|
||||
{t("lldpLinks.pause")}
|
||||
|
|
@ -379,7 +398,7 @@ export function LldpLinksPage() {
|
|||
<div className="pt-list-kpi__label">{t("lldpLinks.kpi.last")}</div>
|
||||
<div className="pt-list-kpi__value" style={{ fontSize: 15 }}>
|
||||
{last
|
||||
? `${last.status} · +${last.edges_added} / ~${last.edges_updated}`
|
||||
? `${last.status} · ok ${last.success_count ?? "—"} / fail ${last.fail_count ?? 0}`
|
||||
: t("common.empty")}
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -697,6 +716,11 @@ export function LldpLinksPage() {
|
|||
</td>
|
||||
<td>
|
||||
{job.done}/{job.total}
|
||||
{(job.fail_count || 0) > 0 ? (
|
||||
<div className="muted">
|
||||
ok {job.success_count ?? 0} / fail {job.fail_count}
|
||||
</div>
|
||||
) : null}
|
||||
</td>
|
||||
<td>
|
||||
+{job.edges_added} / ~{job.edges_updated}
|
||||
|
|
|
|||
|
|
@ -1384,8 +1384,11 @@ export const fetchLldpCollectDashboard = () =>
|
|||
export const updateLldpCollectPolicy = (body: Partial<LldpCollectPolicy>) =>
|
||||
apiPut<LldpCollectPolicy>("/v1/topology/lldp-collect/policy", body);
|
||||
|
||||
export const startLldpCollect = () =>
|
||||
apiPost<{ ok: boolean; job: TopologyDiscoverJob }>("/v1/topology/lldp-collect/start", {});
|
||||
export const startLldpCollect = (body?: { mode?: "full" | "retry_failed"; job_id?: string }) =>
|
||||
apiPost<{ ok: boolean; job: TopologyDiscoverJob }>("/v1/topology/lldp-collect/start", {
|
||||
mode: body?.mode || "full",
|
||||
...(body?.job_id ? { job_id: body.job_id } : {}),
|
||||
});
|
||||
|
||||
export const pauseLldpCollectJob = (jobId: string) =>
|
||||
apiPost<TopologyDiscoverJob>(`/v1/topology/lldp-collect/jobs/${encodeURIComponent(jobId)}/pause`, {});
|
||||
|
|
|
|||
|
|
@ -715,6 +715,8 @@ export type LldpCollectJobSummary = {
|
|||
status: string;
|
||||
total: number;
|
||||
done: number;
|
||||
success_count?: number;
|
||||
fail_count?: number;
|
||||
edges_added: number;
|
||||
edges_updated: number;
|
||||
edges_stale: number;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue