mirror of
https://github.com/hansjone/netxops.git
synced 2026-10-10 17:35:11 +08:00
Add bizMonitor host tools for cutover analysis via netx REST (0.1.40).
Expose getBizMonitor* / getBizCollect* on the Ops preset with skill netx-biz-monitor; keep tooling in netxops instead of netx-mcp. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
parent
f7801db296
commit
0934e10409
23 changed files with 1022 additions and 62 deletions
|
|
@ -5,10 +5,11 @@
|
|||
*
|
||||
* - `ops` → skill `netx-ops` — NMS alarms/inventory/SQL + managed CLI + findTopologyPaths
|
||||
* - `topology` → skill `netx-topology` — canvas / fabric / dual_unit / layout
|
||||
* - `bizMonitor` → skill `netx-biz-monitor` — cutover / biz_state read + overnight analysis
|
||||
*/
|
||||
|
||||
/** Stable group ids used in settings, skill dirs, and registration filters. */
|
||||
export type NetxCapabilityGroupId = 'ops' | 'topology'
|
||||
export type NetxCapabilityGroupId = 'ops' | 'topology' | 'bizMonitor'
|
||||
|
||||
/** Per-group exposure knobs. */
|
||||
export interface NetxGroupExposure {
|
||||
|
|
@ -27,14 +28,17 @@ export interface NetxCapabilityGroupSettingsFields {
|
|||
groupOpsPublic: boolean
|
||||
groupTopologyInPreset: boolean
|
||||
groupTopologyPublic: boolean
|
||||
groupBizMonitorInPreset: boolean
|
||||
groupBizMonitorPublic: boolean
|
||||
}
|
||||
|
||||
/**
|
||||
* Default: ops in Ops preset; topology and all public off.
|
||||
* Default: ops in Ops preset; topology / bizMonitor and all public off.
|
||||
*/
|
||||
export const DEFAULT_CAPABILITY_GROUPS: NetxCapabilityGroups = Object.freeze({
|
||||
ops: Object.freeze({ inPreset: true, public: false }),
|
||||
topology: Object.freeze({ inPreset: false, public: false }),
|
||||
bizMonitor: Object.freeze({ inPreset: true, public: false }),
|
||||
})
|
||||
|
||||
/**
|
||||
|
|
@ -76,17 +80,27 @@ export const TOOLS_BY_GROUP: Readonly<Record<NetxCapabilityGroupId, readonly str
|
|||
'netx__analyzeTopologyViewLayout',
|
||||
'netx__sinkTopologyDualUnits',
|
||||
]),
|
||||
bizMonitor: Object.freeze([
|
||||
'netx__getBizMonitorContext',
|
||||
'netx__getBizMonitorBoard',
|
||||
'netx__listBizMonitorReds',
|
||||
'netx__getBizMonitorDiffs',
|
||||
'netx__getBizCollectBatch',
|
||||
'netx__getBizCollectCommandRaw',
|
||||
]),
|
||||
})
|
||||
|
||||
/** Skill directory name under skills root / `presets/netxops/skills/<group>/`. */
|
||||
export const SKILL_DIR_BY_GROUP: Readonly<Record<NetxCapabilityGroupId, string>> = Object.freeze({
|
||||
ops: 'ops',
|
||||
topology: 'topology',
|
||||
bizMonitor: 'biz-monitor',
|
||||
})
|
||||
|
||||
export const CAPABILITY_GROUP_IDS: readonly NetxCapabilityGroupId[] = Object.freeze([
|
||||
'ops',
|
||||
'topology',
|
||||
'bizMonitor',
|
||||
])
|
||||
|
||||
/**
|
||||
|
|
@ -126,6 +140,11 @@ export function capabilityGroupsFromSettings(
|
|||
}
|
||||
}
|
||||
|
||||
let bizMonitorInPreset = true
|
||||
if (src.groupBizMonitorInPreset !== undefined) {
|
||||
bizMonitorInPreset = src.groupBizMonitorInPreset !== false
|
||||
}
|
||||
|
||||
return {
|
||||
ops: {
|
||||
inPreset: opsInPreset,
|
||||
|
|
@ -140,6 +159,10 @@ export function capabilityGroupsFromSettings(
|
|||
public: src.groupTopologyPublic === true
|
||||
|| src.groupTopologyLayoutPublic === true,
|
||||
},
|
||||
bizMonitor: {
|
||||
inPreset: bizMonitorInPreset,
|
||||
public: src.groupBizMonitorPublic === true,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -177,9 +200,9 @@ export function groupsForced(
|
|||
* Tool name allow-list for the given groups.
|
||||
*/
|
||||
export function toolNamesForGroups(groupIds: readonly NetxCapabilityGroupId[]): Set<string> {
|
||||
const names = new Set<string>()
|
||||
const out = new Set<string>()
|
||||
for (const id of groupIds) {
|
||||
for (const tool of TOOLS_BY_GROUP[id]) names.add(tool)
|
||||
for (const name of TOOLS_BY_GROUP[id] ?? []) out.add(name)
|
||||
}
|
||||
return names
|
||||
return out
|
||||
}
|
||||
|
|
|
|||
|
|
@ -330,3 +330,101 @@ export async function findTopologyPaths(client: NetxClient, args: NetxJson, sign
|
|||
else body.to_managed_ne_id = toMid
|
||||
return client.post('/v1/topology/fabric/paths', body, signal, 30_000)
|
||||
}
|
||||
|
||||
// ── bizMonitor group (cutover / biz_state read) ─────────────────────────────
|
||||
|
||||
/** Fat cutover / biz_state definition bundle (templates, mapping, tasks). */
|
||||
export async function getBizMonitorContext(
|
||||
client: NetxClient,
|
||||
args: NetxJson,
|
||||
signal?: AbortSignal,
|
||||
): Promise<NetxJson> {
|
||||
const projectId = str(args, 'project_id').trim()
|
||||
const taskId = str(args, 'task_id').trim()
|
||||
if (!projectId && !taskId) {
|
||||
return { ok: false, error: 'project_id_or_task_id_required' }
|
||||
}
|
||||
const params: Record<string, string | number | boolean> = {}
|
||||
if (projectId) params.project_id = projectId
|
||||
if (taskId) params.task_id = taskId
|
||||
return client.get('/v1/biz-migration/monitor-context', params, signal)
|
||||
}
|
||||
|
||||
/** Cutover batch board + latest evaluate run summary. */
|
||||
export async function getBizMonitorBoard(
|
||||
client: NetxClient,
|
||||
args: NetxJson,
|
||||
signal?: AbortSignal,
|
||||
): Promise<NetxJson> {
|
||||
const batchId = str(args, 'batch_id').trim()
|
||||
if (!batchId) return { ok: false, error: 'batch_id_required' }
|
||||
const params: Record<string, string | number | boolean> = {}
|
||||
const runId = str(args, 'run_id').trim()
|
||||
if (runId) params.run_id = runId
|
||||
return client.get(`/v1/biz-migration/batches/${encodeURIComponent(batchId)}/board`, params, signal)
|
||||
}
|
||||
|
||||
/** Red tickets with evidence (raw A/B + show command). */
|
||||
export async function listBizMonitorReds(
|
||||
client: NetxClient,
|
||||
args: NetxJson,
|
||||
signal?: AbortSignal,
|
||||
): Promise<NetxJson> {
|
||||
const projectId = str(args, 'project_id').trim()
|
||||
if (!projectId) return { ok: false, error: 'project_id_required' }
|
||||
const params: Record<string, string | number | boolean> = {}
|
||||
putStr(params, args, ['status'])
|
||||
const limit = num(args, 'limit')
|
||||
if (limit !== undefined) params.limit = clampInt(limit, 200, 1, 500)
|
||||
return client.get(
|
||||
`/v1/biz-migration/projects/${encodeURIComponent(projectId)}/red-tickets`,
|
||||
params,
|
||||
signal,
|
||||
)
|
||||
}
|
||||
|
||||
/** Paged evaluate diffs (old_key/new_key = device-raw A/B). */
|
||||
export async function getBizMonitorDiffs(
|
||||
client: NetxClient,
|
||||
args: NetxJson,
|
||||
signal?: AbortSignal,
|
||||
): Promise<NetxJson> {
|
||||
const runId = str(args, 'run_id').trim()
|
||||
if (!runId) return { ok: false, error: 'run_id_required' }
|
||||
const params: Record<string, string | number | boolean> = {}
|
||||
putStr(params, args, ['metric_id', 'sheet_id', 'verdict', 'color', 'q'])
|
||||
const limit = num(args, 'limit')
|
||||
if (limit !== undefined) params.limit = clampInt(limit, 200, 1, 500)
|
||||
const offset = num(args, 'offset')
|
||||
if (offset !== undefined) params.offset = clampInt(offset, 0, 0, Number.MAX_SAFE_INTEGER)
|
||||
return client.get(`/v1/biz-migration/runs/${encodeURIComponent(runId)}/diffs`, params, signal)
|
||||
}
|
||||
|
||||
/** One biz_state collect batch (commands + metrics). */
|
||||
export async function getBizCollectBatch(
|
||||
client: NetxClient,
|
||||
args: NetxJson,
|
||||
signal?: AbortSignal,
|
||||
): Promise<NetxJson> {
|
||||
const batchId = str(args, 'batch_id').trim()
|
||||
if (!batchId) return { ok: false, error: 'batch_id_required' }
|
||||
return client.get(`/v1/biz-state/batches/${encodeURIComponent(batchId)}`, {}, signal)
|
||||
}
|
||||
|
||||
/** Full CLI raw_text for one collect command. */
|
||||
export async function getBizCollectCommandRaw(
|
||||
client: NetxClient,
|
||||
args: NetxJson,
|
||||
signal?: AbortSignal,
|
||||
): Promise<NetxJson> {
|
||||
const batchId = str(args, 'batch_id').trim()
|
||||
const commandId = str(args, 'command_id').trim()
|
||||
if (!batchId || !commandId) {
|
||||
return { ok: false, error: 'batch_id_and_command_id_required' }
|
||||
}
|
||||
return client.get(
|
||||
`/v1/biz-state/batches/${encodeURIComponent(batchId)}/commands/${encodeURIComponent(commandId)}`,
|
||||
{},
|
||||
signal,
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -316,6 +316,67 @@ export function registerNetxTools(
|
|||
},
|
||||
H.findTopologyPaths, getClient, t,
|
||||
),
|
||||
// ── bizMonitor group (cutover / biz_state read via netx REST) ─────────
|
||||
tool(
|
||||
'netx__getBizMonitorContext',
|
||||
'Biz-monitor definition bundle: cutover project + monitor/compare templates + iface normalize + port mapping + related tasks/commands. Prefer project_id. Display keys are device-raw A/B.',
|
||||
{
|
||||
project_id: str('Cutover / biz-migration project id'),
|
||||
task_id: str('biz_state task id (optional if project_id set)'),
|
||||
},
|
||||
H.getBizMonitorContext, getClient, t,
|
||||
),
|
||||
tool(
|
||||
'netx__getBizMonitorBoard',
|
||||
'Cutover batch board: project/batch status, evaluate run summary, sheet cards, verdict counts, metric_batches.',
|
||||
{
|
||||
batch_id: { type: 'string' as const, required: true as const },
|
||||
run_id: str('Optional evaluate run id'),
|
||||
},
|
||||
H.getBizMonitorBoard, getClient, t,
|
||||
),
|
||||
tool(
|
||||
'netx__listBizMonitorReds',
|
||||
'Open/carried red tickets. Each item has old_key/new_key (raw A/B) and evidence (device, collect time, show command, iface raw→norm→map).',
|
||||
{
|
||||
project_id: { type: 'string' as const, required: true as const },
|
||||
status: str('open|carried|resolved or empty=all'),
|
||||
limit: num(),
|
||||
},
|
||||
H.listBizMonitorReds, getClient, t,
|
||||
),
|
||||
tool(
|
||||
'netx__getBizMonitorDiffs',
|
||||
'Paged evaluate diffs. Rows expose old_key/new_key (raw A/B), match_* keys, status, evidence with show commands.',
|
||||
{
|
||||
run_id: { type: 'string' as const, required: true as const },
|
||||
metric_id: str(),
|
||||
sheet_id: str(),
|
||||
verdict: str(),
|
||||
color: str('e.g. red'),
|
||||
q: str(),
|
||||
limit: num(),
|
||||
offset: num(),
|
||||
},
|
||||
H.getBizMonitorDiffs, getClient, t,
|
||||
),
|
||||
tool(
|
||||
'netx__getBizCollectBatch',
|
||||
'One biz_state collect batch: commands (raw_command, parse_status, preview), parsed metrics.',
|
||||
{
|
||||
batch_id: { type: 'string' as const, required: true as const },
|
||||
},
|
||||
H.getBizCollectBatch, getClient, t,
|
||||
),
|
||||
tool(
|
||||
'netx__getBizCollectCommandRaw',
|
||||
'Full CLI raw_text for one collect command inside a batch (deep dive).',
|
||||
{
|
||||
batch_id: { type: 'string' as const, required: true as const },
|
||||
command_id: { type: 'string' as const, required: true as const },
|
||||
},
|
||||
H.getBizCollectCommandRaw, getClient, t,
|
||||
),
|
||||
// ── topology group (netx-topology canvas / fabric) ─────────────────────
|
||||
tool(
|
||||
'netx__getTopologyTree',
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue