Show full copyable batch ids in biz-state/cutover UI and document batch list tools.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
oliver 2026-09-20 15:26:46 +08:00
parent 04850b942c
commit 83cf086d8c
6 changed files with 123 additions and 22 deletions

View file

@ -2,10 +2,10 @@
name: netx-biz-monitor
description: >-
Overnight cutover / biz_state monitor analysis via netxops host tools
(netx__listBizMonitors / netx__getBizMonitor*). List projects/tasks first
when no id is given; then read definitions, board, reds/diffs with evidence
(device-raw A/B, show commands), and judge tool false-positive vs real
business fault. Does not create templates/tasks.
(netx__listBizMonitors / listBizMonitorBatches / getBizMonitor*). List
projects/tasks then batches when no id is given; read definitions, board,
reds/diffs with evidence (device-raw A/B, show commands), and judge tool
false-positive vs real business fault. Does not create templates/tasks.
---
# netx-biz-monitor(割接 / 业务监控只读分析)
@ -18,6 +18,7 @@ description: >-
| 工具 | 用途 |
|------|------|
| `netx__listBizMonitors` | **入口目录**:列出割接/监控对比项目 + 业务监控任务(**无需** project_id) |
| `netx__listBizMonitorBatches` | **批次目录**:`project_id`→割接批次;`task_id`→采集批次;`kind=runs`→evaluate run |
| `netx__getBizMonitorContext` | 任务定义:项目、监控/对比模板、归一化规则、端口映射、关联任务与命令项 |
| `netx__getBizMonitorBoard` | 批次进度、evaluate run、sheet 卡、verdict 统计 |
| `netx__listBizMonitorReds` | 红单 + **evidence** |
@ -37,12 +38,13 @@ description: >-
## 推荐顺序
1. 无 id → `listBizMonitors`(`kind=projects|tasks|all`,可加 `q` / `purpose`)
2. `getBizMonitorContext(project_id)` — 弄清盯什么
3. `getBizMonitorBoard(batch_id)` — 进度 / missing / skipped
4. `listBizMonitorReds` 或 `getBizMonitorDiffs(color=red)`
5. 看 evidence:parse 失败?current_missing?映射 miss?归一化误伤?
6. 需要原文 → `getBizCollectCommandRaw`
7. 像真障 → `execManagedNe` 现场核对
2. `listBizMonitorBatches(project_id)` 或 `(task_id)` — 拿到 **完整 batch_id**;需要 run 时再 `kind=runs` / `include_runs`
3. `getBizMonitorContext(project_id)` — 弄清盯什么
4. `getBizMonitorBoard(batch_id)` — 进度 / missing / skipped
5. `listBizMonitorReds` 或 `getBizMonitorDiffs(color=red)`
6. 看 evidence:parse 失败?current_missing?映射 miss?归一化误伤?
7. 需要原文 → `getBizCollectCommandRaw`
8. 像真障 → `execManagedNe` 现场核对
## Verdict 速查(skill 解释,工具不硬编码)

View file

@ -19,6 +19,8 @@ const en = {
opFailed: "Operation failed",
empty: "-",
cancel: "Cancel",
copy: "Copy",
copied: "Copied",
},
workbench: {
title: "Workbench",
@ -240,6 +242,8 @@ const en = {
noBatches: "No batches yet",
batchDetail: "Batch detail",
batchWorkbook: "Batch workbook",
batchId: "Batch ID",
copyBatchId: "Copy batch ID",
sheetCommands: "Commands",
sheetLldp: "LLDP neighbors",
sheetVrfRoute: "VRF route summary",
@ -593,6 +597,8 @@ const en = {
portFilterPh: "Filter",
emptyBaselinePorts: "No baseline data",
batchLabel: "Batch label",
batchId: "Batch ID",
copyBatchId: "Copy batch ID",
defaultBatchLabel: "Night N",
expectPorts: "Expected ports (old side)",
expectPortsPh: "Comma or newline separated",

View file

@ -19,6 +19,8 @@ const zh = {
opFailed: "操作失败",
empty: "-",
cancel: "取消",
copy: "复制",
copied: "已复制",
},
workbench: {
title: "工作台",
@ -240,6 +242,8 @@ const zh = {
noBatches: "尚无批次",
batchDetail: "批次详情",
batchWorkbook: "批次工作簿",
batchId: "批次 ID",
copyBatchId: "复制批次 ID",
sheetCommands: "命令",
sheetLldp: "LLDP 邻居",
sheetVrfRoute: "VRF 路由摘要",
@ -586,6 +590,8 @@ const zh = {
portFilterPh: "筛选",
emptyBaselinePorts: "基线无数据",
batchLabel: "批次标签",
batchId: "批次 ID",
copyBatchId: "复制批次 ID",
defaultBatchLabel: "第N晚",
expectPorts: "本批预期端口(老侧)",
expectPortsPh: "逗号或换行分隔",

View file

@ -9953,6 +9953,45 @@ html.login-page--paused .login-page__flare {
gap: 8px 12px;
}
.bs-id-row {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 8px;
width: 100%;
min-width: 0;
padding: 6px 8px;
border-radius: 6px;
border: 1px solid rgba(148, 163, 184, 0.2);
background: rgba(2, 6, 23, 0.45);
}
.bs-id-row--inline {
width: auto;
flex: 1 1 220px;
}
.bs-id-row__label {
flex: 0 0 auto;
font-size: 11px;
letter-spacing: 0.04em;
text-transform: uppercase;
color: #94a3b8;
}
.bs-id-row__value {
flex: 1 1 auto;
min-width: 0;
font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
font-size: 12px;
line-height: 1.4;
color: #e2e8f0;
white-space: nowrap;
overflow-x: auto;
overflow-y: hidden;
word-break: normal;
}
.bs-sheet-cmd-bar {
display: flex;
flex-direction: column;

View file

@ -37,6 +37,7 @@ import {
} from "../../services/api";
// bizStateListTasks not needed for create — NEs + auto HF
import type { CliTargetItem } from "../../types";
import { writeClipboardText } from "../../utils/clipboard";
import { pageCount } from "../../utils/display";
import { formatSystemTime, localDatetimeInputToUtcIso, utcIsoToLocalDatetimeInput } from "../../utils/time";
import { jobChipColor, NmStatusChip, sourceChipColor } from "./nmChips";
@ -1426,14 +1427,26 @@ export function BizMigrationPage() {
const batchOptions = (items: { id: string; started_at?: string | null }[]) => (
<>
<option value="">—</option>
{items.map((x) => (
<option key={x.id} value={x.id}>
{fmtTime(x.started_at) !== "—" ? fmtTime(x.started_at) : x.id.slice(0, 12)}
{items.map((x) => {
const when = fmtTime(x.started_at);
const label = when !== "—" ? `${when} · ${x.id}` : x.id;
return (
<option key={x.id} value={x.id} title={x.id}>
{label}
</option>
))}
);
})}
</>
);
const copyBatchId = (id: string) => {
void (async () => {
const ok = await writeClipboardText(id);
if (ok) showOk(t("common.copied"));
else showError(t("common.opFailed"));
})();
};
return (
<section className="panel nm-page-panel">
<div className="panel__toolbar">
@ -2340,8 +2353,8 @@ export function BizMigrationPage() {
aria-label={t("bizMigration.batches")}
>
{batches.map((b) => (
<option key={b.id} value={b.id}>
{b.batch_label} ({statusLabel(b.status)})
<option key={b.id} value={b.id} title={b.id}>
{b.batch_label} ({statusLabel(b.status)}) · {b.id}
</option>
))}
</FieldSelect>
@ -2397,6 +2410,15 @@ export function BizMigrationPage() {
) : null}
</div>
</div>
{batch ? (
<div className="bs-id-row" title={batch.id}>
<span className="bs-id-row__label">{t("bizMigration.batchId")}</span>
<code className="bs-id-row__value">{batch.id}</code>
<Button size="sm" variant="ghost" onPress={() => copyBatchId(batch.id)}>
{t("bizMigration.copyBatchId")}
</Button>
</div>
) : null}
<div className="ct-editor__main bm-batches__main">
<aside className="ct-editor__nav" aria-label={t("bizMigration.pickExpectPorts")}>
@ -2530,11 +2552,20 @@ export function BizMigrationPage() {
aria-label={t("bizMigration.batches")}
>
{batches.map((b) => (
<option key={b.id} value={b.id}>
{b.batch_label} ({statusLabel(b.status)})
<option key={b.id} value={b.id} title={b.id}>
{b.batch_label} ({statusLabel(b.status)}) · {b.id}
</option>
))}
</FieldSelect>
{batchId ? (
<div className="bs-id-row bs-id-row--inline" title={batchId}>
<span className="bs-id-row__label">{t("bizMigration.batchId")}</span>
<code className="bs-id-row__value">{batchId}</code>
<Button size="sm" variant="ghost" onPress={() => copyBatchId(batchId)}>
{t("bizMigration.copyBatchId")}
</Button>
</div>
) : null}
<FieldSelect
value={pinOldBatchId}
onChange={(e) => setPinOldBatchId(e.target.value)}

View file

@ -30,6 +30,7 @@ import {
} from "../../services/api";
import type { CliTargetItem } from "../../types";
import { pageCount } from "../../utils/display";
import { writeClipboardText } from "../../utils/clipboard";
import { formatSystemTime } from "../../utils/time";
import { jobChipColor, NmStatusChip, sourceChipColor } from "./nmChips";
@ -1502,9 +1503,7 @@ export function BizStatePage() {
className="app-heroui-modal--xl bs-workbook-modal"
>
<Modal.Header>
<Modal.Heading>
{t("bizState.batchWorkbook")} · {batchDetail?.id?.slice(0, 8)}…
</Modal.Heading>
<Modal.Heading>{t("bizState.batchWorkbook")}</Modal.Heading>
<Modal.CloseTrigger />
</Modal.Header>
<Modal.Body className="flex flex-col gap-2 bs-workbook-body">
@ -1517,6 +1516,24 @@ export function BizStatePage() {
{batchDetail.command_count} cmd · {batchDetail.row_count} rows ·{" "}
{fmtTime(batchDetail.started_at)}
</span>
<div className="bs-id-row" title={String(batchDetail.id || "")}>
<span className="bs-id-row__label">{t("bizState.batchId")}</span>
<code className="bs-id-row__value">{String(batchDetail.id || "")}</code>
<Button
size="sm"
variant="ghost"
isDisabled={!batchDetail.id}
onPress={() => {
void (async () => {
const ok = await writeClipboardText(String(batchDetail.id || ""));
if (ok) showOk(t("common.copied"));
else showError(t("common.opFailed"));
})();
}}
>
{t("bizState.copyBatchId")}
</Button>
</div>
</div>
) : null}