import { Button, Input } from "@heroui/react"; import { useCallback, useEffect, useState } from "react"; import { FieldSelect } from "../../components/ui/FieldSelect"; import { useToast } from "../../hooks/useToast"; import { useI18n } from "../../i18n"; import { bizCompareCreateJob, bizCompareCreateMapping, bizCompareGetRun, bizCompareListJobs, bizCompareListMappings, bizCompareListRuns, bizCompareListTemplates, bizCompareRunJob, bizCompareUpdateJob, bizCompareUpdateMapping, bizCompareValidateMapping, bizStateListBatches, bizStateListTasks, formatErr, } from "../../services/api"; import { formatSystemTime } from "../../utils/time"; import { jobChipColor, NmStatusChip } from "./nmChips"; type TaskOpt = { id: string; ne_name: string; ne_ip: string; vendor: string }; type BatchOpt = { id: string; status: string; row_count: number; started_at?: string | null }; type Template = { id: string; name: string; metric_id: string }; type Mapping = { id: string; name: string; rows: { before_if: string; after_if: string }[] }; type Job = { id: string; name: string; template_id: string; mapping_id: string; before_task_id: string; after_task_id: string; before_batch_id: string; after_batch_id: string; mode: string; status: string; }; function fmtTime(v?: string | null) { if (!v) return "—"; return formatSystemTime(v) || v; } export function BizComparePage() { const { t } = useI18n(); const { showOk, showError } = useToast(); const [tasks, setTasks] = useState([]); const [templates, setTemplates] = useState([]); const [mappings, setMappings] = useState([]); const [jobs, setJobs] = useState([]); const [selectedJobId, setSelectedJobId] = useState(""); const [runs, setRuns] = useState([]); const [runDetail, setRunDetail] = useState(null); const [busy, setBusy] = useState(false); const [name, setName] = useState("割接比对"); const [templateId, setTemplateId] = useState(""); const [mappingId, setMappingId] = useState(""); const [beforeTaskId, setBeforeTaskId] = useState(""); const [afterTaskId, setAfterTaskId] = useState(""); const [beforeBatches, setBeforeBatches] = useState([]); const [afterBatches, setAfterBatches] = useState([]); const [beforeBatchId, setBeforeBatchId] = useState(""); const [afterBatchId, setAfterBatchId] = useState(""); const [mode, setMode] = useState<"manual" | "auto">("manual"); const [mapName, setMapName] = useState("端口映射"); const [mapText, setMapText] = useState("before_if,after_if\n"); const [validateOut, setValidateOut] = useState(null); const refresh = useCallback(async () => { const [taskRes, tpl, maps, j] = await Promise.all([ bizStateListTasks(), bizCompareListTemplates(), bizCompareListMappings(), bizCompareListJobs(), ]); setTasks((taskRes.items || []) as TaskOpt[]); setTemplates((tpl.items || []) as Template[]); setMappings((maps.items || []) as Mapping[]); setJobs((j.items || []) as Job[]); const items = (tpl.items || []) as Template[]; if (!templateId && items[0]) setTemplateId(items[0].id); }, [templateId]); useEffect(() => { void (async () => { try { await refresh(); } catch (e) { showError(formatErr(e)); } })(); // eslint-disable-next-line react-hooks/exhaustive-deps -- mount once }, [refresh]); useEffect(() => { void (async () => { if (!beforeTaskId) { setBeforeBatches([]); return; } const b = await bizStateListBatches(beforeTaskId); setBeforeBatches((b.items || []) as BatchOpt[]); })(); }, [beforeTaskId]); useEffect(() => { void (async () => { if (!afterTaskId) { setAfterBatches([]); return; } const b = await bizStateListBatches(afterTaskId); setAfterBatches((b.items || []) as BatchOpt[]); })(); }, [afterTaskId]); const parseMapRows = () => { const rows: { before_if: string; after_if: string }[] = []; for (const line of mapText.split(/\r?\n/)) { const s = line.trim(); if (!s || s.startsWith("#") || s.toLowerCase().startsWith("old") || s.toLowerCase().startsWith("before")) { continue; } const parts = s.split(/[,|\t]+/).map((x) => x.trim()); if (parts.length >= 2 && parts[0] && parts[1]) { rows.push({ before_if: parts[0], after_if: parts[1] }); } } return rows; }; const updateExistingMapping = async () => { setBusy(true); try { const rows = parseMapRows(); if (mappingId) { await bizCompareUpdateMapping(mappingId, { name: mapName, rows }); } else { const m = await bizCompareCreateMapping({ name: mapName, rows }); setMappingId(String(m.id)); } showOk(t("bizCompare.mappingSaved")); await refresh(); } catch (e) { showError(formatErr(e)); } finally { setBusy(false); } }; const doValidate = async () => { if (!mappingId || !beforeBatchId || !afterBatchId) { showError(`${t("bizCompare.validateMapping")}: mapping / batches`); return; } setBusy(true); try { const v = await bizCompareValidateMapping({ mapping_id: mappingId, before_batch_id: beforeBatchId, after_batch_id: afterBatchId, template_id: templateId, }); setValidateOut(v); } catch (e) { showError(formatErr(e)); } finally { setBusy(false); } }; const createJob = async () => { setBusy(true); try { const j = await bizCompareCreateJob({ name, template_id: templateId, mapping_id: mappingId, before_task_id: beforeTaskId, after_task_id: afterTaskId || beforeTaskId, before_batch_id: beforeBatchId, after_batch_id: mode === "manual" ? afterBatchId : "", mode, }); setSelectedJobId(String(j.id)); showOk(t("bizCompare.created")); await refresh(); const r = await bizCompareListRuns(String(j.id)); setRuns(r.items || []); } catch (e) { showError(formatErr(e)); } finally { setBusy(false); } }; const openJob = async (id: string) => { setSelectedJobId(id); setRunDetail(null); const r = await bizCompareListRuns(id); setRuns(r.items || []); const job = jobs.find((x) => x.id === id); if (job) { setName(job.name); setTemplateId(job.template_id); setMappingId(job.mapping_id); setBeforeTaskId(job.before_task_id); setAfterTaskId(job.after_task_id); setBeforeBatchId(job.before_batch_id); setAfterBatchId(job.after_batch_id); setMode(job.mode === "auto" ? "auto" : "manual"); if (job.mapping_id) loadMappingText(job.mapping_id); } }; const runNow = async () => { if (!selectedJobId) return; setBusy(true); try { await bizCompareUpdateJob(selectedJobId, { before_batch_id: beforeBatchId, after_batch_id: mode === "manual" ? afterBatchId : "", mode, mapping_id: mappingId, template_id: templateId, }); const run = await bizCompareRunJob(selectedJobId); setRunDetail(run); showOk(t("bizCompare.ran")); const r = await bizCompareListRuns(selectedJobId); setRuns(r.items || []); } catch (e) { showError(formatErr(e)); } finally { setBusy(false); } }; const loadRun = async (runId: string) => { try { const d = await bizCompareGetRun(runId); setRunDetail(d); } catch (e) { showError(formatErr(e)); } }; const loadMappingText = (id: string) => { const m = mappings.find((x) => x.id === id); if (!m) return; setMappingId(id); setMapName(m.name); setMapText(["before_if,after_if", ...m.rows.map((r) => `${r.before_if},${r.after_if}`)].join("\n")); }; const taskLabel = (row: TaskOpt) => `${row.ne_name || row.ne_ip || row.id} (${row.vendor || "-"})`; return (

{t("bizCompare.title")}

{t("bizCompare.mapping")}

{ const id = e.target.value; if (id) loadMappingText(id); else { setMappingId(""); setMapText("before_if,after_if\n"); } }} > {mappings.map((m) => ( ))} setMapName(e.target.value)} />