mirror of
https://github.com/hansjone/netx.git
synced 2026-10-08 23:33:21 +08:00
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:
parent
04850b942c
commit
83cf086d8c
6 changed files with 123 additions and 22 deletions
|
|
@ -2,10 +2,10 @@
|
||||||
name: netx-biz-monitor
|
name: netx-biz-monitor
|
||||||
description: >-
|
description: >-
|
||||||
Overnight cutover / biz_state monitor analysis via netxops host tools
|
Overnight cutover / biz_state monitor analysis via netxops host tools
|
||||||
(netx__listBizMonitors / netx__getBizMonitor*). List projects/tasks first
|
(netx__listBizMonitors / listBizMonitorBatches / getBizMonitor*). List
|
||||||
when no id is given; then read definitions, board, reds/diffs with evidence
|
projects/tasks then batches when no id is given; read definitions, board,
|
||||||
(device-raw A/B, show commands), and judge tool false-positive vs real
|
reds/diffs with evidence (device-raw A/B, show commands), and judge tool
|
||||||
business fault. Does not create templates/tasks.
|
false-positive vs real business fault. Does not create templates/tasks.
|
||||||
---
|
---
|
||||||
|
|
||||||
# netx-biz-monitor(割接 / 业务监控只读分析)
|
# netx-biz-monitor(割接 / 业务监控只读分析)
|
||||||
|
|
@ -18,6 +18,7 @@ description: >-
|
||||||
| 工具 | 用途 |
|
| 工具 | 用途 |
|
||||||
|------|------|
|
|------|------|
|
||||||
| `netx__listBizMonitors` | **入口目录**:列出割接/监控对比项目 + 业务监控任务(**无需** project_id) |
|
| `netx__listBizMonitors` | **入口目录**:列出割接/监控对比项目 + 业务监控任务(**无需** project_id) |
|
||||||
|
| `netx__listBizMonitorBatches` | **批次目录**:`project_id`→割接批次;`task_id`→采集批次;`kind=runs`→evaluate run |
|
||||||
| `netx__getBizMonitorContext` | 任务定义:项目、监控/对比模板、归一化规则、端口映射、关联任务与命令项 |
|
| `netx__getBizMonitorContext` | 任务定义:项目、监控/对比模板、归一化规则、端口映射、关联任务与命令项 |
|
||||||
| `netx__getBizMonitorBoard` | 批次进度、evaluate run、sheet 卡、verdict 统计 |
|
| `netx__getBizMonitorBoard` | 批次进度、evaluate run、sheet 卡、verdict 统计 |
|
||||||
| `netx__listBizMonitorReds` | 红单 + **evidence** |
|
| `netx__listBizMonitorReds` | 红单 + **evidence** |
|
||||||
|
|
@ -37,12 +38,13 @@ description: >-
|
||||||
## 推荐顺序
|
## 推荐顺序
|
||||||
|
|
||||||
1. 无 id → `listBizMonitors`(`kind=projects|tasks|all`,可加 `q` / `purpose`)
|
1. 无 id → `listBizMonitors`(`kind=projects|tasks|all`,可加 `q` / `purpose`)
|
||||||
2. `getBizMonitorContext(project_id)` — 弄清盯什么
|
2. `listBizMonitorBatches(project_id)` 或 `(task_id)` — 拿到 **完整 batch_id**;需要 run 时再 `kind=runs` / `include_runs`
|
||||||
3. `getBizMonitorBoard(batch_id)` — 进度 / missing / skipped
|
3. `getBizMonitorContext(project_id)` — 弄清盯什么
|
||||||
4. `listBizMonitorReds` 或 `getBizMonitorDiffs(color=red)`
|
4. `getBizMonitorBoard(batch_id)` — 进度 / missing / skipped
|
||||||
5. 看 evidence:parse 失败?current_missing?映射 miss?归一化误伤?
|
5. `listBizMonitorReds` 或 `getBizMonitorDiffs(color=red)`
|
||||||
6. 需要原文 → `getBizCollectCommandRaw`
|
6. 看 evidence:parse 失败?current_missing?映射 miss?归一化误伤?
|
||||||
7. 像真障 → `execManagedNe` 现场核对
|
7. 需要原文 → `getBizCollectCommandRaw`
|
||||||
|
8. 像真障 → `execManagedNe` 现场核对
|
||||||
|
|
||||||
## Verdict 速查(skill 解释,工具不硬编码)
|
## Verdict 速查(skill 解释,工具不硬编码)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,8 @@ const en = {
|
||||||
opFailed: "Operation failed",
|
opFailed: "Operation failed",
|
||||||
empty: "-",
|
empty: "-",
|
||||||
cancel: "Cancel",
|
cancel: "Cancel",
|
||||||
|
copy: "Copy",
|
||||||
|
copied: "Copied",
|
||||||
},
|
},
|
||||||
workbench: {
|
workbench: {
|
||||||
title: "Workbench",
|
title: "Workbench",
|
||||||
|
|
@ -240,6 +242,8 @@ const en = {
|
||||||
noBatches: "No batches yet",
|
noBatches: "No batches yet",
|
||||||
batchDetail: "Batch detail",
|
batchDetail: "Batch detail",
|
||||||
batchWorkbook: "Batch workbook",
|
batchWorkbook: "Batch workbook",
|
||||||
|
batchId: "Batch ID",
|
||||||
|
copyBatchId: "Copy batch ID",
|
||||||
sheetCommands: "Commands",
|
sheetCommands: "Commands",
|
||||||
sheetLldp: "LLDP neighbors",
|
sheetLldp: "LLDP neighbors",
|
||||||
sheetVrfRoute: "VRF route summary",
|
sheetVrfRoute: "VRF route summary",
|
||||||
|
|
@ -593,6 +597,8 @@ const en = {
|
||||||
portFilterPh: "Filter",
|
portFilterPh: "Filter",
|
||||||
emptyBaselinePorts: "No baseline data",
|
emptyBaselinePorts: "No baseline data",
|
||||||
batchLabel: "Batch label",
|
batchLabel: "Batch label",
|
||||||
|
batchId: "Batch ID",
|
||||||
|
copyBatchId: "Copy batch ID",
|
||||||
defaultBatchLabel: "Night N",
|
defaultBatchLabel: "Night N",
|
||||||
expectPorts: "Expected ports (old side)",
|
expectPorts: "Expected ports (old side)",
|
||||||
expectPortsPh: "Comma or newline separated",
|
expectPortsPh: "Comma or newline separated",
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,8 @@ const zh = {
|
||||||
opFailed: "操作失败",
|
opFailed: "操作失败",
|
||||||
empty: "-",
|
empty: "-",
|
||||||
cancel: "取消",
|
cancel: "取消",
|
||||||
|
copy: "复制",
|
||||||
|
copied: "已复制",
|
||||||
},
|
},
|
||||||
workbench: {
|
workbench: {
|
||||||
title: "工作台",
|
title: "工作台",
|
||||||
|
|
@ -240,6 +242,8 @@ const zh = {
|
||||||
noBatches: "尚无批次",
|
noBatches: "尚无批次",
|
||||||
batchDetail: "批次详情",
|
batchDetail: "批次详情",
|
||||||
batchWorkbook: "批次工作簿",
|
batchWorkbook: "批次工作簿",
|
||||||
|
batchId: "批次 ID",
|
||||||
|
copyBatchId: "复制批次 ID",
|
||||||
sheetCommands: "命令",
|
sheetCommands: "命令",
|
||||||
sheetLldp: "LLDP 邻居",
|
sheetLldp: "LLDP 邻居",
|
||||||
sheetVrfRoute: "VRF 路由摘要",
|
sheetVrfRoute: "VRF 路由摘要",
|
||||||
|
|
@ -586,6 +590,8 @@ const zh = {
|
||||||
portFilterPh: "筛选",
|
portFilterPh: "筛选",
|
||||||
emptyBaselinePorts: "基线无数据",
|
emptyBaselinePorts: "基线无数据",
|
||||||
batchLabel: "批次标签",
|
batchLabel: "批次标签",
|
||||||
|
batchId: "批次 ID",
|
||||||
|
copyBatchId: "复制批次 ID",
|
||||||
defaultBatchLabel: "第N晚",
|
defaultBatchLabel: "第N晚",
|
||||||
expectPorts: "本批预期端口(老侧)",
|
expectPorts: "本批预期端口(老侧)",
|
||||||
expectPortsPh: "逗号或换行分隔",
|
expectPortsPh: "逗号或换行分隔",
|
||||||
|
|
|
||||||
|
|
@ -9953,6 +9953,45 @@ html.login-page--paused .login-page__flare {
|
||||||
gap: 8px 12px;
|
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 {
|
.bs-sheet-cmd-bar {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
|
|
||||||
|
|
@ -37,6 +37,7 @@ import {
|
||||||
} from "../../services/api";
|
} from "../../services/api";
|
||||||
// bizStateListTasks not needed for create — NEs + auto HF
|
// bizStateListTasks not needed for create — NEs + auto HF
|
||||||
import type { CliTargetItem } from "../../types";
|
import type { CliTargetItem } from "../../types";
|
||||||
|
import { writeClipboardText } from "../../utils/clipboard";
|
||||||
import { pageCount } from "../../utils/display";
|
import { pageCount } from "../../utils/display";
|
||||||
import { formatSystemTime, localDatetimeInputToUtcIso, utcIsoToLocalDatetimeInput } from "../../utils/time";
|
import { formatSystemTime, localDatetimeInputToUtcIso, utcIsoToLocalDatetimeInput } from "../../utils/time";
|
||||||
import { jobChipColor, NmStatusChip, sourceChipColor } from "./nmChips";
|
import { jobChipColor, NmStatusChip, sourceChipColor } from "./nmChips";
|
||||||
|
|
@ -1426,14 +1427,26 @@ export function BizMigrationPage() {
|
||||||
const batchOptions = (items: { id: string; started_at?: string | null }[]) => (
|
const batchOptions = (items: { id: string; started_at?: string | null }[]) => (
|
||||||
<>
|
<>
|
||||||
<option value="">—</option>
|
<option value="">—</option>
|
||||||
{items.map((x) => (
|
{items.map((x) => {
|
||||||
<option key={x.id} value={x.id}>
|
const when = fmtTime(x.started_at);
|
||||||
{fmtTime(x.started_at) !== "—" ? fmtTime(x.started_at) : x.id.slice(0, 12)}
|
const label = when !== "—" ? `${when} · ${x.id}` : x.id;
|
||||||
|
return (
|
||||||
|
<option key={x.id} value={x.id} title={x.id}>
|
||||||
|
{label}
|
||||||
</option>
|
</option>
|
||||||
))}
|
);
|
||||||
|
})}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const copyBatchId = (id: string) => {
|
||||||
|
void (async () => {
|
||||||
|
const ok = await writeClipboardText(id);
|
||||||
|
if (ok) showOk(t("common.copied"));
|
||||||
|
else showError(t("common.opFailed"));
|
||||||
|
})();
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<section className="panel nm-page-panel">
|
<section className="panel nm-page-panel">
|
||||||
<div className="panel__toolbar">
|
<div className="panel__toolbar">
|
||||||
|
|
@ -2340,8 +2353,8 @@ export function BizMigrationPage() {
|
||||||
aria-label={t("bizMigration.batches")}
|
aria-label={t("bizMigration.batches")}
|
||||||
>
|
>
|
||||||
{batches.map((b) => (
|
{batches.map((b) => (
|
||||||
<option key={b.id} value={b.id}>
|
<option key={b.id} value={b.id} title={b.id}>
|
||||||
{b.batch_label} ({statusLabel(b.status)})
|
{b.batch_label} ({statusLabel(b.status)}) · {b.id}
|
||||||
</option>
|
</option>
|
||||||
))}
|
))}
|
||||||
</FieldSelect>
|
</FieldSelect>
|
||||||
|
|
@ -2397,6 +2410,15 @@ export function BizMigrationPage() {
|
||||||
) : null}
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
</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">
|
<div className="ct-editor__main bm-batches__main">
|
||||||
<aside className="ct-editor__nav" aria-label={t("bizMigration.pickExpectPorts")}>
|
<aside className="ct-editor__nav" aria-label={t("bizMigration.pickExpectPorts")}>
|
||||||
|
|
@ -2530,11 +2552,20 @@ export function BizMigrationPage() {
|
||||||
aria-label={t("bizMigration.batches")}
|
aria-label={t("bizMigration.batches")}
|
||||||
>
|
>
|
||||||
{batches.map((b) => (
|
{batches.map((b) => (
|
||||||
<option key={b.id} value={b.id}>
|
<option key={b.id} value={b.id} title={b.id}>
|
||||||
{b.batch_label} ({statusLabel(b.status)})
|
{b.batch_label} ({statusLabel(b.status)}) · {b.id}
|
||||||
</option>
|
</option>
|
||||||
))}
|
))}
|
||||||
</FieldSelect>
|
</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
|
<FieldSelect
|
||||||
value={pinOldBatchId}
|
value={pinOldBatchId}
|
||||||
onChange={(e) => setPinOldBatchId(e.target.value)}
|
onChange={(e) => setPinOldBatchId(e.target.value)}
|
||||||
|
|
|
||||||
|
|
@ -30,6 +30,7 @@ import {
|
||||||
} from "../../services/api";
|
} from "../../services/api";
|
||||||
import type { CliTargetItem } from "../../types";
|
import type { CliTargetItem } from "../../types";
|
||||||
import { pageCount } from "../../utils/display";
|
import { pageCount } from "../../utils/display";
|
||||||
|
import { writeClipboardText } from "../../utils/clipboard";
|
||||||
import { formatSystemTime } from "../../utils/time";
|
import { formatSystemTime } from "../../utils/time";
|
||||||
import { jobChipColor, NmStatusChip, sourceChipColor } from "./nmChips";
|
import { jobChipColor, NmStatusChip, sourceChipColor } from "./nmChips";
|
||||||
|
|
||||||
|
|
@ -1502,9 +1503,7 @@ export function BizStatePage() {
|
||||||
className="app-heroui-modal--xl bs-workbook-modal"
|
className="app-heroui-modal--xl bs-workbook-modal"
|
||||||
>
|
>
|
||||||
<Modal.Header>
|
<Modal.Header>
|
||||||
<Modal.Heading>
|
<Modal.Heading>{t("bizState.batchWorkbook")}</Modal.Heading>
|
||||||
{t("bizState.batchWorkbook")} · {batchDetail?.id?.slice(0, 8)}…
|
|
||||||
</Modal.Heading>
|
|
||||||
<Modal.CloseTrigger />
|
<Modal.CloseTrigger />
|
||||||
</Modal.Header>
|
</Modal.Header>
|
||||||
<Modal.Body className="flex flex-col gap-2 bs-workbook-body">
|
<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 ·{" "}
|
{batchDetail.command_count} cmd · {batchDetail.row_count} rows ·{" "}
|
||||||
{fmtTime(batchDetail.started_at)}
|
{fmtTime(batchDetail.started_at)}
|
||||||
</span>
|
</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>
|
</div>
|
||||||
) : null}
|
) : null}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue