mirror of
https://github.com/hansjone/netx.git
synced 2026-10-09 02:00:46 +08:00
Fix vpnv6 ECMP under-parse and persist full CLI line counts.
RR vpnv6 wrap lines with next-hop+metrics were collapsing ECMP legs; store raw_line_count/declared_total before DB truncate so collect stats stay accurate. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
parent
86fcb98218
commit
ac53af4a5d
16 changed files with 531 additions and 35 deletions
|
|
@ -285,7 +285,8 @@ const en = {
|
|||
batchId: "Batch ID",
|
||||
copyBatchId: "Copy batch ID",
|
||||
batchMessageEmpty: "No message or error",
|
||||
batchCmdSummary: "{{ok}} ok / {{fail}} failed / {{other}} other · {{total}} commands",
|
||||
batchCmdSummary:
|
||||
"{{ok}} ok / {{fail}} failed / {{skipped}} skipped / {{other}} other · {{total}} counted (batch {{batchCmds}} cmds)",
|
||||
sheetCommands: "Commands",
|
||||
sheetLldp: "LLDP neighbors",
|
||||
sheetVrfRoute: "VRF route summary",
|
||||
|
|
@ -305,6 +306,7 @@ const en = {
|
|||
rawLogTitle: "Raw collect log",
|
||||
rawLogEmpty: "No raw output",
|
||||
rawLogStats: "{{lines}} lines collected · {{rows}} rows stored",
|
||||
rawLogDeclared: "declared {{declared}}",
|
||||
openWorkbook: "Open workbook",
|
||||
filterColumn: "Column",
|
||||
filterAllCols: "All columns",
|
||||
|
|
@ -322,6 +324,7 @@ const en = {
|
|||
colTime: "Time",
|
||||
colRows: "DB rows",
|
||||
colRawLines: "CLI lines",
|
||||
colDeclared: "Declared",
|
||||
colActions: "Actions",
|
||||
colCommand: "Command",
|
||||
colMessage: "Message",
|
||||
|
|
|
|||
|
|
@ -285,7 +285,8 @@ const zh = {
|
|||
batchId: "批次 ID",
|
||||
copyBatchId: "复制批次 ID",
|
||||
batchMessageEmpty: "无额外说明或报错",
|
||||
batchCmdSummary: "命令 {{ok}} 成功 / {{fail}} 失败 / {{other}} 其他 · 共 {{total}} 条",
|
||||
batchCmdSummary:
|
||||
"命令 {{ok}} 成功 / {{fail}} 失败 / {{skipped}} 跳过 / {{other}} 其他 · 统计 {{total}} 条(批次计数 {{batchCmds}})",
|
||||
sheetCommands: "命令",
|
||||
sheetLldp: "LLDP 邻居",
|
||||
sheetVrfRoute: "VRF 路由摘要",
|
||||
|
|
@ -305,6 +306,7 @@ const zh = {
|
|||
rawLogTitle: "原始采集日志",
|
||||
rawLogEmpty: "无原始输出",
|
||||
rawLogStats: "采集 {{lines}} 行 · 入库 {{rows}} 条",
|
||||
rawLogDeclared: "声明 {{declared}}",
|
||||
openWorkbook: "打开工作簿",
|
||||
filterColumn: "筛选列",
|
||||
filterAllCols: "全部列",
|
||||
|
|
@ -322,6 +324,7 @@ const zh = {
|
|||
colTime: "时间",
|
||||
colRows: "入库条数",
|
||||
colRawLines: "采集行数",
|
||||
colDeclared: "设备声明",
|
||||
colActions: "操作",
|
||||
colCommand: "命令",
|
||||
colMessage: "说明",
|
||||
|
|
|
|||
|
|
@ -10181,6 +10181,20 @@ html.login-page--paused .login-page__flare {
|
|||
|
||||
.bs-collect-detail-modal .bs-sheet-table {
|
||||
max-height: min(48vh, 480px);
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.bs-collect-detail-modal .bs-collect-detail-table table {
|
||||
border-collapse: separate;
|
||||
border-spacing: 0;
|
||||
}
|
||||
|
||||
.bs-collect-detail-modal .bs-collect-detail-table thead th {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 3;
|
||||
background: rgba(15, 23, 42, 0.97);
|
||||
box-shadow: 0 1px 0 rgba(148, 163, 184, 0.25);
|
||||
}
|
||||
|
||||
.bs-rawlog-pre {
|
||||
|
|
|
|||
|
|
@ -111,6 +111,7 @@ type SheetCmd = {
|
|||
parse_status?: string;
|
||||
row_count?: number;
|
||||
raw_line_count?: number;
|
||||
declared_total?: number;
|
||||
message?: string;
|
||||
has_raw?: boolean;
|
||||
profile_id?: string;
|
||||
|
|
@ -162,6 +163,20 @@ function intervalUnitMax(unit: "days" | "hours" | "seconds") {
|
|||
return 604800; // up to 7 days in seconds
|
||||
}
|
||||
|
||||
function formatCmdCollectStats(
|
||||
t: (key: string, vars?: Record<string, unknown>) => string,
|
||||
lines: number,
|
||||
rows: number,
|
||||
declared?: number,
|
||||
) {
|
||||
const base = t("bizState.rawLogStats", { lines, rows });
|
||||
const d = Number(declared || 0);
|
||||
if (d > 0) {
|
||||
return `${base} · ${t("bizState.rawLogDeclared", { declared: d })}`;
|
||||
}
|
||||
return base;
|
||||
}
|
||||
|
||||
function convertIntervalValue(
|
||||
value: number,
|
||||
from: "days" | "hours" | "seconds",
|
||||
|
|
@ -311,6 +326,7 @@ export function BizStatePage() {
|
|||
const [rawLogCommandId, setRawLogCommandId] = useState("");
|
||||
const [rawLogLines, setRawLogLines] = useState(0);
|
||||
const [rawLogRows, setRawLogRows] = useState(0);
|
||||
const [rawLogDeclared, setRawLogDeclared] = useState(0);
|
||||
const [rawLogMessage, setRawLogMessage] = useState("");
|
||||
/** Collect status / errors detail (lighter than workbook). */
|
||||
const [collectDetail, setCollectDetail] = useState<any>(null);
|
||||
|
|
@ -550,17 +566,33 @@ export function BizStatePage() {
|
|||
}, [activeSheet, batchDetail, debouncedSheetKw, sheetColumn, sheetTotal]);
|
||||
|
||||
const collectDetailCmdSummary = useMemo(() => {
|
||||
const stats = collectDetail?.command_stats as
|
||||
| { ok?: number; failed?: number; skipped?: number; other?: number; total?: number; aux_failed?: number }
|
||||
| undefined;
|
||||
if (stats && typeof stats.total === "number") {
|
||||
return {
|
||||
ok: Number(stats.ok || 0),
|
||||
fail: Number(stats.failed || 0),
|
||||
skipped: Number(stats.skipped || 0),
|
||||
other: Number(stats.other || 0),
|
||||
total: Number(stats.total || 0),
|
||||
auxFailed: Number(stats.aux_failed || 0),
|
||||
};
|
||||
}
|
||||
const cmds = (collectDetail?.commands || []) as SheetCmd[];
|
||||
let ok = 0;
|
||||
let fail = 0;
|
||||
let skipped = 0;
|
||||
let other = 0;
|
||||
for (const c of cmds) {
|
||||
const st = String(c.parse_status || "").toLowerCase();
|
||||
if (st === "ok" || st === "success" || st === "aux" || st === "aux_ok" || st === "aux_cached") ok += 1;
|
||||
else if (st === "failed" || st === "error" || st === "fail") fail += 1;
|
||||
else if (st === "failed" || st === "error" || st === "fail" || st === "aux_failed" || st.endsWith("_failed"))
|
||||
fail += 1;
|
||||
else if (st.startsWith("skipped")) skipped += 1;
|
||||
else other += 1;
|
||||
}
|
||||
return { ok, fail, other, total: cmds.length };
|
||||
return { ok, fail, skipped, other, total: cmds.length, auxFailed: 0 };
|
||||
}, [collectDetail]);
|
||||
|
||||
const loadSheetPage = useCallback(
|
||||
|
|
@ -1192,6 +1224,7 @@ export function BizStatePage() {
|
|||
setRawLogCommandId(commandId);
|
||||
setRawLogLines(0);
|
||||
setRawLogRows(0);
|
||||
setRawLogDeclared(0);
|
||||
setRawLogMessage("");
|
||||
try {
|
||||
const d = await bizStateGetBatchCommand(bid, commandId);
|
||||
|
|
@ -1199,13 +1232,15 @@ export function BizStatePage() {
|
|||
setRawLogText(String(d.raw_text || ""));
|
||||
const lines = Number(d.raw_line_count ?? 0);
|
||||
const rows = Number(d.row_count ?? 0);
|
||||
const declared = Number(d.declared_total ?? 0);
|
||||
setRawLogLines(lines);
|
||||
setRawLogRows(rows);
|
||||
setRawLogDeclared(declared);
|
||||
setRawLogMessage(String(d.message || ""));
|
||||
const bits = [
|
||||
d.parse_status,
|
||||
d.metric_id,
|
||||
t("bizState.rawLogStats", { lines, rows }),
|
||||
formatCmdCollectStats(t, lines, rows, declared),
|
||||
d.collected_at ? fmtTime(d.collected_at) : "",
|
||||
].filter(Boolean);
|
||||
setRawLogMeta(bits.join(" · "));
|
||||
|
|
@ -2336,10 +2371,12 @@ export function BizStatePage() {
|
|||
{t("bizState.exportRawLog")}
|
||||
</Button>
|
||||
<span className="muted bs-cmd-stat">
|
||||
{t("bizState.rawLogStats", {
|
||||
lines: Number(c.raw_line_count ?? 0),
|
||||
rows: Number(c.row_count ?? 0),
|
||||
})}
|
||||
{formatCmdCollectStats(
|
||||
t,
|
||||
Number(c.raw_line_count ?? 0),
|
||||
Number(c.row_count ?? 0),
|
||||
Number(c.declared_total ?? 0),
|
||||
)}
|
||||
</span>
|
||||
{c.parse_status ? (
|
||||
<NmStatusChip color={jobChipColor(String(c.parse_status))}>
|
||||
|
|
@ -2383,10 +2420,12 @@ export function BizStatePage() {
|
|||
{t("bizState.exportRawLog")}
|
||||
</Button>
|
||||
<span className="muted bs-cmd-stat">
|
||||
{t("bizState.rawLogStats", {
|
||||
lines: Number(row.raw_line_count ?? 0),
|
||||
rows: Number(row.row_count ?? 0),
|
||||
})}
|
||||
{formatCmdCollectStats(
|
||||
t,
|
||||
Number(row.raw_line_count ?? 0),
|
||||
Number(row.row_count ?? 0),
|
||||
Number(row.declared_total ?? 0),
|
||||
)}
|
||||
</span>
|
||||
{cellText(row.parse_status) ? (
|
||||
<NmStatusChip color={jobChipColor(cellText(row.parse_status))}>
|
||||
|
|
@ -2573,7 +2612,7 @@ export function BizStatePage() {
|
|||
{rawLogCmd ? <code className="bs-sheet-cmd-code bs-sheet-cmd-code--block">{rawLogCmd}</code> : null}
|
||||
<div className="bs-rawlog-stats">
|
||||
<NmStatusChip color="accent">
|
||||
{t("bizState.rawLogStats", { lines: rawLogLines, rows: rawLogRows })}
|
||||
{formatCmdCollectStats(t, rawLogLines, rawLogRows, rawLogDeclared)}
|
||||
</NmStatusChip>
|
||||
{rawLogMeta ? <span className="muted">{rawLogMeta}</span> : null}
|
||||
</div>
|
||||
|
|
@ -2655,11 +2694,13 @@ export function BizStatePage() {
|
|||
{t("bizState.batchCmdSummary", {
|
||||
ok: collectDetailCmdSummary.ok,
|
||||
fail: collectDetailCmdSummary.fail,
|
||||
skipped: collectDetailCmdSummary.skipped,
|
||||
other: collectDetailCmdSummary.other,
|
||||
total: collectDetailCmdSummary.total,
|
||||
batchCmds: Number(collectDetail.command_count ?? collectDetailCmdSummary.total),
|
||||
})}
|
||||
</p>
|
||||
<div className="pt-list-table-wrap bs-sheet-table">
|
||||
<div className="pt-list-table-wrap bs-sheet-table bs-collect-detail-table">
|
||||
<table className="data-table pt-list-table">
|
||||
<thead>
|
||||
<tr>
|
||||
|
|
@ -2667,6 +2708,7 @@ export function BizStatePage() {
|
|||
<th>{t("bizState.colStatus")}</th>
|
||||
<th>{t("bizState.colRawLines")}</th>
|
||||
<th>{t("bizState.colRows")}</th>
|
||||
<th>{t("bizState.colDeclared")}</th>
|
||||
<th>{t("bizState.colMessage")}</th>
|
||||
<th>{t("bizState.colActions")}</th>
|
||||
</tr>
|
||||
|
|
@ -2684,6 +2726,9 @@ export function BizStatePage() {
|
|||
</td>
|
||||
<td className="pt-list-num">{Number(c.raw_line_count ?? 0)}</td>
|
||||
<td className="pt-list-num">{Number(c.row_count ?? 0)}</td>
|
||||
<td className="pt-list-num">
|
||||
{Number(c.declared_total ?? 0) > 0 ? Number(c.declared_total) : "—"}
|
||||
</td>
|
||||
<td className="bs-msg-cell" title={c.message || ""}>
|
||||
{c.message?.trim() ? c.message : "—"}
|
||||
</td>
|
||||
|
|
|
|||
|
|
@ -1915,6 +1915,7 @@ export const bizStateGetBatchCommand = (batchId: string, commandId: string) =>
|
|||
parse_status: string;
|
||||
row_count: number;
|
||||
raw_line_count?: number;
|
||||
declared_total?: number;
|
||||
message: string;
|
||||
raw_text: string;
|
||||
collected_at?: string | null;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue