mirror of
https://github.com/hansjone/netxops.git
synced 2026-10-20 11:10:44 +08:00
Netx tools: load from netx remote /mcp, add per-tool toggles
- Drop the TypeScript netx tool implementations and the hard-coded TOOLS_BY_GROUP list; register netx__* tools from the server's tools/list (group from _meta.netx.group) and forward tools/call. - Thin Streamable HTTP client on standard fetch (fetchImpl injectable); cached catalog when netx is unreachable, refresh to reconnect. - disabledTools (deny list; new server tools follow their group) and readOnlyOnly (only readOnlyHint=true tools), re-registered live. - Settings card: per-group expandable tool rows with switches and a write marker; skills append a disabled-tools note. - Toggles only control model visibility; netx enforces security. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
2def53e7c7
commit
2ef3a43c63
38 changed files with 5049 additions and 17029 deletions
486
lib/client.js
486
lib/client.js
|
|
@ -682,7 +682,10 @@ var EMPTY2 = {
|
|||
groups: [],
|
||||
plane: "",
|
||||
tokenConfigured: null,
|
||||
apiUrl: ""
|
||||
apiUrl: "",
|
||||
mcpPhase: "idle",
|
||||
mcpError: "",
|
||||
mcpFromCache: false
|
||||
};
|
||||
function asToolsMountView(value) {
|
||||
if (value === null || typeof value !== "object" || Array.isArray(value))
|
||||
|
|
@ -741,7 +744,29 @@ function asToolsMountView(value) {
|
|||
groups = [...new Set(mounts.flatMap((m) => m.groups))];
|
||||
}
|
||||
const toolsEnabled = !(connection && typeof connection === "object" && connection.toolsEnabled === false);
|
||||
return { count, version, groups, plane, error, tokenConfigured, apiUrl, mounts, toolsEnabled };
|
||||
let mcpPhase = "idle";
|
||||
let mcpError = "";
|
||||
let mcpFromCache = false;
|
||||
if (row.mcp !== null && typeof row.mcp === "object" && !Array.isArray(row.mcp)) {
|
||||
const mcp = row.mcp;
|
||||
mcpPhase = typeof mcp.phase === "string" ? mcp.phase : "idle";
|
||||
mcpError = typeof mcp.error === "string" ? mcp.error : "";
|
||||
mcpFromCache = mcp.fromCache === true;
|
||||
}
|
||||
return {
|
||||
count,
|
||||
version,
|
||||
groups,
|
||||
plane,
|
||||
error,
|
||||
tokenConfigured,
|
||||
apiUrl,
|
||||
mounts,
|
||||
toolsEnabled,
|
||||
mcpPhase,
|
||||
mcpError,
|
||||
mcpFromCache
|
||||
};
|
||||
}
|
||||
async function fetchToolsStatus(call, signal) {
|
||||
const result = await call(NETXOPS_RPC_CHANNEL3, TOOLS_STATUS_ENDPOINT, {}, signal);
|
||||
|
|
@ -753,8 +778,10 @@ async function fetchToolsStatus(call, signal) {
|
|||
function toolsMountTone(view) {
|
||||
if (!view)
|
||||
return "neutral";
|
||||
if (view.error)
|
||||
if (view.error || view.mcpPhase === "error")
|
||||
return "err";
|
||||
if (view.mcpPhase === "offline" || view.mcpFromCache)
|
||||
return "warn";
|
||||
if (!view.toolsEnabled)
|
||||
return "neutral";
|
||||
if (view.count > 0)
|
||||
|
|
@ -810,6 +837,12 @@ var CSS = `
|
|||
.dsh-nx-groupBlock+.dsh-nx-groupBlock{border-top:1px solid var(--dsw-alias-border-l1,#eef0f3)}
|
||||
.dsh-nx-groupTitle{display:flex;align-items:center;gap:8px;font-size:13px;font-weight:600;line-height:1.5;color:var(--dsw-alias-label-primary,#1f2329)}
|
||||
.dsh-nx-groupChecks{display:flex;flex-wrap:wrap;gap:8px 24px}
|
||||
.dsh-nx-toolList{margin:0}
|
||||
.dsh-nx-toolList>summary{font-size:12px;color:var(--dsw-alias-label-secondary,#646a73)}
|
||||
.dsh-nx-toolRows{display:flex;flex-direction:column;gap:6px;padding:8px 0 0}
|
||||
.dsh-nx-toolRow{align-items:flex-start}
|
||||
.dsh-nx-toolMeta{display:flex;flex-direction:column;gap:2px;min-width:0}
|
||||
.dsh-nx-toolName{display:inline-flex;align-items:center;gap:6px;font-size:13px;font-weight:500;overflow-wrap:anywhere}
|
||||
.dsh-nx-targets{margin-top:0}
|
||||
.dsh-nx-diagnostics{padding-top:12px;border-top:1px solid var(--dsw-alias-border-l1,#eef0f3)}
|
||||
.dsh-nx-diagnostics[open] summary{margin-bottom:8px}
|
||||
|
|
@ -996,52 +1029,6 @@ var DEFAULT_CAPABILITY_GROUPS = Object.freeze({
|
|||
topology: Object.freeze({ inPreset: false, public: false }),
|
||||
bizMonitor: Object.freeze({ inPreset: true, public: false })
|
||||
});
|
||||
var TOOLS_BY_GROUP = Object.freeze({
|
||||
ops: Object.freeze([
|
||||
"netx__queryNmsAlarms",
|
||||
"netx__aggregateNmsAlarms",
|
||||
"netx__runNmsDiagnostics",
|
||||
"netx__queryNmsNeInventory",
|
||||
"netx__getNmsNe",
|
||||
"netx__queryNmsAlarmsRaw",
|
||||
"netx__aggregateNmsAlarmsRaw",
|
||||
"netx__listNmsAlarmFields",
|
||||
"netx__sqlQueryNms",
|
||||
"netx__listManagedNe",
|
||||
"netx__getManagedNe",
|
||||
"netx__execManagedNe",
|
||||
"netx__getNeExecJob",
|
||||
"netx__listCliTargets",
|
||||
"netx__findTopologyPaths"
|
||||
]),
|
||||
topology: Object.freeze([
|
||||
"netx__getTopologyTree",
|
||||
"netx__getTopologyView",
|
||||
"netx__createTopologyFolder",
|
||||
"netx__addTopologyViewNodes",
|
||||
"netx__removeTopologyViewNodes",
|
||||
"netx__copyTopologyViewNodes",
|
||||
"netx__updateTopologyViewPositions",
|
||||
"netx__projectTopologyNeighbors",
|
||||
"netx__queryTopologyFabricNodes",
|
||||
"netx__classifyTopologyFabricNodes",
|
||||
"netx__queryTopologyNeighborhood",
|
||||
"netx__queryTopologyEdges",
|
||||
"netx__layoutTopologyView",
|
||||
"netx__suggestSinkHubs",
|
||||
"netx__analyzeTopologyViewLayout"
|
||||
]),
|
||||
bizMonitor: Object.freeze([
|
||||
"netx__listBizMonitors",
|
||||
"netx__listBizMonitorBatches",
|
||||
"netx__getBizMonitorContext",
|
||||
"netx__getBizMonitorBoard",
|
||||
"netx__listBizMonitorReds",
|
||||
"netx__getBizMonitorDiffs",
|
||||
"netx__getBizCollectBatch",
|
||||
"netx__getBizCollectCommandRaw"
|
||||
])
|
||||
});
|
||||
var SKILL_DIR_BY_GROUP = Object.freeze({
|
||||
ops: "ops",
|
||||
topology: "topology",
|
||||
|
|
@ -1059,6 +1046,30 @@ function parsePresetIds(value) {
|
|||
return [...new Set(current.split(/[\s,,]+/).filter(Boolean))];
|
||||
}
|
||||
|
||||
// src/netx/mcp-catalog.ts
|
||||
function parseDisabledTools(value) {
|
||||
if (Array.isArray(value)) {
|
||||
return [...new Set(value.filter((item) => typeof item === "string" && item.trim().length > 0).map((s) => s.trim()))];
|
||||
}
|
||||
if (typeof value !== "string")
|
||||
return [];
|
||||
const trimmed = value.trim();
|
||||
if (!trimmed)
|
||||
return [];
|
||||
if (trimmed.startsWith("[")) {
|
||||
try {
|
||||
const parsed = JSON.parse(trimmed);
|
||||
if (Array.isArray(parsed)) {
|
||||
return [...new Set(parsed.filter((item) => typeof item === "string" && item.trim().length > 0).map((s) => s.trim()))];
|
||||
}
|
||||
} catch {}
|
||||
}
|
||||
return [...new Set(trimmed.split(/[\s,,]+/).filter(Boolean))];
|
||||
}
|
||||
function serializeDisabledTools(names) {
|
||||
return JSON.stringify([...new Set(names.map((n) => n.trim()).filter(Boolean))].sort());
|
||||
}
|
||||
|
||||
// src/client/NetxopsCard.tsx
|
||||
var jsx_runtime = require("react/jsx-runtime");
|
||||
function phaseLocaleKey(phase) {
|
||||
|
|
@ -1112,9 +1123,12 @@ function toolsBadgeLabel(t, view) {
|
|||
return t("toolsStatusUnknown");
|
||||
if (!view.toolsEnabled)
|
||||
return t("toolsDisabled");
|
||||
if (view.error) {
|
||||
if (view.error || view.mcpPhase === "error") {
|
||||
return t("toolsStatusError");
|
||||
}
|
||||
if (view.mcpPhase === "offline" || view.mcpFromCache) {
|
||||
return t("mcpDisconnected");
|
||||
}
|
||||
if (view.count > 0) {
|
||||
return fillTemplate(t("toolsStatusMounted"), {
|
||||
count: String(view.count)
|
||||
|
|
@ -1420,7 +1434,58 @@ function CapabilityGroupBlock(props) {
|
|||
})
|
||||
]
|
||||
}),
|
||||
props.targets
|
||||
props.targets,
|
||||
props.tools ? /* @__PURE__ */ jsx_runtime.jsxs("details", {
|
||||
className: "dsh-nx-toolList",
|
||||
children: [
|
||||
/* @__PURE__ */ jsx_runtime.jsx("summary", {
|
||||
children: props.toolCount ?? props.tools.length
|
||||
}),
|
||||
props.tools.length === 0 ? /* @__PURE__ */ jsx_runtime.jsx("p", {
|
||||
className: "dsh-nx-hint",
|
||||
children: props.emptyLabel ?? ""
|
||||
}) : /* @__PURE__ */ jsx_runtime.jsx("div", {
|
||||
className: "dsh-nx-toolRows",
|
||||
children: props.tools.map((tool) => {
|
||||
const enabled = !(props.disabledTools?.has(tool.name) ?? false);
|
||||
const desc = tool.description.trim();
|
||||
const short = desc.length > 72 ? `${desc.slice(0, 72)}…` : desc;
|
||||
return /* @__PURE__ */ jsx_runtime.jsxs("label", {
|
||||
className: "dsh-nx-checkRow dsh-nx-toolRow",
|
||||
children: [
|
||||
/* @__PURE__ */ jsx_runtime.jsx("input", {
|
||||
type: "checkbox",
|
||||
checked: enabled,
|
||||
disabled: props.disabled,
|
||||
onChange: (event) => {
|
||||
props.onToggleTool?.(tool.name, event.target.checked);
|
||||
}
|
||||
}),
|
||||
/* @__PURE__ */ jsx_runtime.jsxs("span", {
|
||||
className: "dsh-nx-toolMeta",
|
||||
children: [
|
||||
/* @__PURE__ */ jsx_runtime.jsxs("span", {
|
||||
className: "dsh-nx-toolName",
|
||||
children: [
|
||||
tool.name,
|
||||
tool.readOnlyHint !== true ? /* @__PURE__ */ jsx_runtime.jsx("span", {
|
||||
className: "dsh-nx-badgeMuted",
|
||||
children: props.writeBadge ?? ""
|
||||
}) : null
|
||||
]
|
||||
}),
|
||||
short ? /* @__PURE__ */ jsx_runtime.jsx("span", {
|
||||
className: "dsh-nx-hint",
|
||||
children: short
|
||||
}) : null
|
||||
]
|
||||
})
|
||||
]
|
||||
}, tool.name);
|
||||
})
|
||||
})
|
||||
]
|
||||
}) : null
|
||||
]
|
||||
});
|
||||
}
|
||||
|
|
@ -1677,9 +1742,23 @@ function NetxopsCard(props) {
|
|||
/* @__PURE__ */ jsx_runtime.jsx("h3", {
|
||||
children: t("sectionCapabilities")
|
||||
}),
|
||||
/* @__PURE__ */ jsx_runtime.jsx(StatusBadge, {
|
||||
tone: toolsMountTone(state.toolsStatus),
|
||||
label: toolsBadgeLabel(t, state.toolsStatus)
|
||||
/* @__PURE__ */ jsx_runtime.jsxs("span", {
|
||||
className: "dsh-nx-badges",
|
||||
children: [
|
||||
/* @__PURE__ */ jsx_runtime.jsx(StatusBadge, {
|
||||
tone: toolsMountTone(state.toolsStatus),
|
||||
label: toolsBadgeLabel(t, state.toolsStatus)
|
||||
}),
|
||||
/* @__PURE__ */ jsx_runtime.jsx("button", {
|
||||
type: "button",
|
||||
className: "dsh-nx-btn",
|
||||
disabled: disabled || state.mcpRefreshBusy,
|
||||
onClick: () => {
|
||||
props.refreshMcpCatalog();
|
||||
},
|
||||
children: state.mcpRefreshBusy ? t("mcpRefreshing") : t("mcpRefresh")
|
||||
})
|
||||
]
|
||||
})
|
||||
]
|
||||
}),
|
||||
|
|
@ -1696,60 +1775,104 @@ function NetxopsCard(props) {
|
|||
onReset: () => {
|
||||
props.resetField("toolsEnabled");
|
||||
},
|
||||
error: state.toolsStatus?.error ? /* @__PURE__ */ jsx_runtime.jsx("p", {
|
||||
error: state.toolsStatus?.error || state.toolsStatus?.mcpError || state.mcpCatalog?.error ? /* @__PURE__ */ jsx_runtime.jsxs("p", {
|
||||
className: "dsh-nx-invalid",
|
||||
role: "status",
|
||||
children: state.toolsStatus.error
|
||||
children: [
|
||||
state.toolsStatus?.error || state.toolsStatus?.mcpError || state.mcpCatalog?.error,
|
||||
state.toolsStatus?.mcpFromCache || state.mcpCatalog?.fromCache ? ` · ${t("mcpDisconnected")}` : ""
|
||||
]
|
||||
}) : null
|
||||
}),
|
||||
/* @__PURE__ */ jsx_runtime.jsx(CapabilityGroupBlock, {
|
||||
title: t("groupOps"),
|
||||
toolCount: TOOLS_BY_GROUP.ops.length,
|
||||
targets: targets("groupOpsPresets", state.groupOpsPresets),
|
||||
inPresetLabel: t("groupInPreset"),
|
||||
publicLabel: t("groupPublic"),
|
||||
inPreset: state.groupOpsInPreset,
|
||||
published: state.groupOpsPublic,
|
||||
/* @__PURE__ */ jsx_runtime.jsx(ToggleField, {
|
||||
id: "netxops-readonly-only",
|
||||
label: t("readOnlyOnly"),
|
||||
checked: state.readOnlyOnly.text === "true",
|
||||
disabled: publicationDisabled,
|
||||
onEditInPreset: (checked) => {
|
||||
props.edit("groupOpsInPreset", checked ? "true" : "false");
|
||||
overridden: state.readOnlyOnly.overridden,
|
||||
resetLabel: t("reset"),
|
||||
onToggle: (checked) => {
|
||||
props.edit("readOnlyOnly", String(checked));
|
||||
},
|
||||
onEditPublic: (checked) => {
|
||||
props.edit("groupOpsPublic", checked ? "true" : "false");
|
||||
}
|
||||
}),
|
||||
/* @__PURE__ */ jsx_runtime.jsx(CapabilityGroupBlock, {
|
||||
title: t("groupTopology"),
|
||||
toolCount: TOOLS_BY_GROUP.topology.length,
|
||||
targets: targets("groupTopologyPresets", state.groupTopologyPresets),
|
||||
inPresetLabel: t("groupInPreset"),
|
||||
publicLabel: t("groupPublic"),
|
||||
inPreset: state.groupTopologyInPreset,
|
||||
published: state.groupTopologyPublic,
|
||||
disabled: publicationDisabled,
|
||||
onEditInPreset: (checked) => {
|
||||
props.edit("groupTopologyInPreset", checked ? "true" : "false");
|
||||
},
|
||||
onEditPublic: (checked) => {
|
||||
props.edit("groupTopologyPublic", checked ? "true" : "false");
|
||||
}
|
||||
}),
|
||||
/* @__PURE__ */ jsx_runtime.jsx(CapabilityGroupBlock, {
|
||||
title: t("groupBizMonitor"),
|
||||
toolCount: TOOLS_BY_GROUP.bizMonitor.length,
|
||||
targets: targets("groupBizMonitorPresets", state.groupBizMonitorPresets),
|
||||
inPresetLabel: t("groupInPreset"),
|
||||
publicLabel: t("groupPublic"),
|
||||
inPreset: state.groupBizMonitorInPreset,
|
||||
published: state.groupBizMonitorPublic,
|
||||
disabled: publicationDisabled,
|
||||
onEditInPreset: (checked) => {
|
||||
props.edit("groupBizMonitorInPreset", checked ? "true" : "false");
|
||||
},
|
||||
onEditPublic: (checked) => {
|
||||
props.edit("groupBizMonitorPublic", checked ? "true" : "false");
|
||||
onReset: () => {
|
||||
props.resetField("readOnlyOnly");
|
||||
}
|
||||
}),
|
||||
(() => {
|
||||
const catalogTools = state.mcpCatalog?.tools ?? [];
|
||||
const byGroup = {
|
||||
ops: catalogTools.filter((tool) => tool.group === "ops"),
|
||||
topology: catalogTools.filter((tool) => tool.group === "topology"),
|
||||
bizMonitor: catalogTools.filter((tool) => tool.group === "bizMonitor")
|
||||
};
|
||||
const disabledSet = new Set(parseDisabledTools(state.disabledTools.text));
|
||||
const toolProps = {
|
||||
disabledTools: disabledSet,
|
||||
writeBadge: t("toolWriteBadge"),
|
||||
emptyLabel: t("toolListEmpty"),
|
||||
onToggleTool: (name, enabled) => {
|
||||
props.setToolEnabled(name, enabled);
|
||||
}
|
||||
};
|
||||
return /* @__PURE__ */ jsx_runtime.jsxs(jsx_runtime.Fragment, {
|
||||
children: [
|
||||
/* @__PURE__ */ jsx_runtime.jsx(CapabilityGroupBlock, {
|
||||
title: t("groupOps"),
|
||||
toolCount: byGroup.ops.length,
|
||||
tools: byGroup.ops,
|
||||
targets: targets("groupOpsPresets", state.groupOpsPresets),
|
||||
inPresetLabel: t("groupInPreset"),
|
||||
publicLabel: t("groupPublic"),
|
||||
inPreset: state.groupOpsInPreset,
|
||||
published: state.groupOpsPublic,
|
||||
disabled: publicationDisabled,
|
||||
onEditInPreset: (checked) => {
|
||||
props.edit("groupOpsInPreset", checked ? "true" : "false");
|
||||
},
|
||||
onEditPublic: (checked) => {
|
||||
props.edit("groupOpsPublic", checked ? "true" : "false");
|
||||
},
|
||||
...toolProps
|
||||
}),
|
||||
/* @__PURE__ */ jsx_runtime.jsx(CapabilityGroupBlock, {
|
||||
title: t("groupTopology"),
|
||||
toolCount: byGroup.topology.length,
|
||||
tools: byGroup.topology,
|
||||
targets: targets("groupTopologyPresets", state.groupTopologyPresets),
|
||||
inPresetLabel: t("groupInPreset"),
|
||||
publicLabel: t("groupPublic"),
|
||||
inPreset: state.groupTopologyInPreset,
|
||||
published: state.groupTopologyPublic,
|
||||
disabled: publicationDisabled,
|
||||
onEditInPreset: (checked) => {
|
||||
props.edit("groupTopologyInPreset", checked ? "true" : "false");
|
||||
},
|
||||
onEditPublic: (checked) => {
|
||||
props.edit("groupTopologyPublic", checked ? "true" : "false");
|
||||
},
|
||||
...toolProps
|
||||
}),
|
||||
/* @__PURE__ */ jsx_runtime.jsx(CapabilityGroupBlock, {
|
||||
title: t("groupBizMonitor"),
|
||||
toolCount: byGroup.bizMonitor.length,
|
||||
tools: byGroup.bizMonitor,
|
||||
targets: targets("groupBizMonitorPresets", state.groupBizMonitorPresets),
|
||||
inPresetLabel: t("groupInPreset"),
|
||||
publicLabel: t("groupPublic"),
|
||||
inPreset: state.groupBizMonitorInPreset,
|
||||
published: state.groupBizMonitorPublic,
|
||||
disabled: publicationDisabled,
|
||||
onEditInPreset: (checked) => {
|
||||
props.edit("groupBizMonitorInPreset", checked ? "true" : "false");
|
||||
},
|
||||
onEditPublic: (checked) => {
|
||||
props.edit("groupBizMonitorPublic", checked ? "true" : "false");
|
||||
},
|
||||
...toolProps
|
||||
})
|
||||
]
|
||||
});
|
||||
})(),
|
||||
state.toolsStatus && state.toolsStatus.mounts.length > 0 ? /* @__PURE__ */ jsx_runtime.jsxs("details", {
|
||||
className: "dsh-nx-diagnostics",
|
||||
children: [
|
||||
|
|
@ -2097,6 +2220,22 @@ function apiUrlField(field) {
|
|||
}
|
||||
};
|
||||
}
|
||||
function booleanField(field) {
|
||||
return {
|
||||
field,
|
||||
format: (value) => value === true ? "true" : "false",
|
||||
parse: (text) => {
|
||||
const normalized = text.trim().toLowerCase();
|
||||
if (normalized === "true" || normalized === "1" || normalized === "yes") {
|
||||
return { kind: "set", value: true };
|
||||
}
|
||||
if (normalized === "false" || normalized === "0" || normalized === "no" || normalized === "") {
|
||||
return { kind: "clear" };
|
||||
}
|
||||
return;
|
||||
}
|
||||
};
|
||||
}
|
||||
function booleanFieldPersistFalse(field) {
|
||||
return {
|
||||
field,
|
||||
|
|
@ -2306,6 +2445,68 @@ class CardForm {
|
|||
}
|
||||
}
|
||||
|
||||
// src/client/mcp-catalog-view.ts
|
||||
var TOOLS_CATALOG_ENDPOINT = "tools.catalog";
|
||||
var TOOLS_REFRESH_ENDPOINT = "tools.refresh";
|
||||
var EMPTY3 = {
|
||||
phase: "idle",
|
||||
mcpUrl: "",
|
||||
error: "",
|
||||
listedAt: null,
|
||||
fromCache: false,
|
||||
tools: []
|
||||
};
|
||||
function asMcpCatalogView(value) {
|
||||
if (value === null || typeof value !== "object" || Array.isArray(value))
|
||||
return { ...EMPTY3 };
|
||||
const row = value;
|
||||
const tools = [];
|
||||
if (Array.isArray(row.tools)) {
|
||||
for (const raw of row.tools) {
|
||||
if (!raw || typeof raw !== "object" || Array.isArray(raw))
|
||||
continue;
|
||||
const t = raw;
|
||||
const name = typeof t.name === "string" ? t.name : "";
|
||||
if (!name)
|
||||
continue;
|
||||
const group = t.group === "ops" || t.group === "topology" || t.group === "bizMonitor" ? t.group : null;
|
||||
tools.push({
|
||||
name,
|
||||
description: typeof t.description === "string" ? t.description : "",
|
||||
readOnlyHint: t.readOnlyHint === true,
|
||||
group
|
||||
});
|
||||
}
|
||||
}
|
||||
return {
|
||||
phase: typeof row.phase === "string" ? row.phase : "idle",
|
||||
mcpUrl: typeof row.mcpUrl === "string" ? row.mcpUrl : "",
|
||||
error: typeof row.error === "string" ? row.error : "",
|
||||
listedAt: typeof row.listedAt === "string" ? row.listedAt : null,
|
||||
fromCache: row.fromCache === true,
|
||||
tools
|
||||
};
|
||||
}
|
||||
async function fetchMcpCatalog(call, signal) {
|
||||
const result = await call(NETXOPS_RPC_CHANNEL3, TOOLS_CATALOG_ENDPOINT, {}, signal);
|
||||
if (result !== null && typeof result === "object" && result.ok === true) {
|
||||
return asMcpCatalogView(result.value);
|
||||
}
|
||||
return { ...EMPTY3 };
|
||||
}
|
||||
async function refreshMcpCatalogRemote(call, signal) {
|
||||
const result = await call(NETXOPS_RPC_CHANNEL3, TOOLS_REFRESH_ENDPOINT, {}, signal);
|
||||
if (result !== null && typeof result === "object") {
|
||||
const row = result;
|
||||
const view = asMcpCatalogView(row.value);
|
||||
if (typeof row.error === "string" && row.error && !view.error) {
|
||||
return { ...view, error: row.error, phase: view.phase === "connected" ? view.phase : "error" };
|
||||
}
|
||||
return view;
|
||||
}
|
||||
return { ...EMPTY3, phase: "error", error: "refresh_failed" };
|
||||
}
|
||||
|
||||
// src/client/controller.ts
|
||||
var NETXOPS_NS = "netxops";
|
||||
var NETXOPS_SECTION_ID = "dsh-netxops";
|
||||
|
|
@ -2328,6 +2529,9 @@ class NetxopsCardController {
|
|||
alarmPushStatus = null;
|
||||
toolsStatus = null;
|
||||
toolsStatusInFlight = false;
|
||||
mcpCatalog = null;
|
||||
mcpCatalogInFlight = false;
|
||||
mcpRefreshBusy = false;
|
||||
presetCatalog = [];
|
||||
presetCatalogError = false;
|
||||
presetCatalogInFlight = false;
|
||||
|
|
@ -2375,6 +2579,8 @@ class NetxopsCardController {
|
|||
booleanFieldPersistFalse("alarmPushEnabled"),
|
||||
booleanFieldPersistFalse("alarmDeliverDsh"),
|
||||
booleanFieldPersistFalse("alarmDeliverIm"),
|
||||
booleanField("readOnlyOnly"),
|
||||
textField("disabledTools"),
|
||||
textField("imTargets"),
|
||||
textField("imBotId"),
|
||||
textField("imTargetId")
|
||||
|
|
@ -2412,6 +2618,10 @@ class NetxopsCardController {
|
|||
this.toolsStatus = null;
|
||||
changed = true;
|
||||
}
|
||||
if (this.mcpCatalog !== null) {
|
||||
this.mcpCatalog = null;
|
||||
changed = true;
|
||||
}
|
||||
this.presetCatalog = [];
|
||||
this.presetCatalogError = true;
|
||||
changed = true;
|
||||
|
|
@ -2435,6 +2645,7 @@ class NetxopsCardController {
|
|||
this.startStatusPoll();
|
||||
this.refreshAlarmPushStatus();
|
||||
this.refreshToolsStatus();
|
||||
this.refreshMcpCatalogStatus();
|
||||
this.refreshPresetCatalog();
|
||||
this.reloadKbFromHost();
|
||||
this.refreshKbPreview();
|
||||
|
|
@ -2455,6 +2666,7 @@ class NetxopsCardController {
|
|||
this.pollTimer = setInterval(() => {
|
||||
this.refreshAlarmPushStatus();
|
||||
this.refreshToolsStatus();
|
||||
this.refreshMcpCatalogStatus();
|
||||
this.refreshPresetCatalog();
|
||||
this.refreshKbStatus();
|
||||
this.refreshImDeliveryCatalog();
|
||||
|
|
@ -2499,6 +2711,50 @@ class NetxopsCardController {
|
|||
this.toolsStatusInFlight = false;
|
||||
}
|
||||
}
|
||||
async refreshMcpCatalogStatus() {
|
||||
const call = this.rpcCall;
|
||||
if (call === undefined || this.mcpCatalogInFlight)
|
||||
return;
|
||||
this.mcpCatalogInFlight = true;
|
||||
try {
|
||||
const next = await fetchMcpCatalog(call);
|
||||
const prev = this.mcpCatalog;
|
||||
if (prev && JSON.stringify(prev) === JSON.stringify(next))
|
||||
return;
|
||||
this.mcpCatalog = next;
|
||||
this.store.set(this.projection());
|
||||
} catch {} finally {
|
||||
this.mcpCatalogInFlight = false;
|
||||
}
|
||||
}
|
||||
async runMcpRefresh() {
|
||||
const call = this.rpcCall;
|
||||
if (call === undefined || this.mcpRefreshBusy)
|
||||
return;
|
||||
this.mcpRefreshBusy = true;
|
||||
this.store.set(this.projection());
|
||||
try {
|
||||
const next = await refreshMcpCatalogRemote(call);
|
||||
this.mcpCatalog = next;
|
||||
await this.refreshToolsStatus();
|
||||
} catch {} finally {
|
||||
this.mcpRefreshBusy = false;
|
||||
this.store.set(this.projection());
|
||||
}
|
||||
}
|
||||
setToolEnabled(serverName, enabled) {
|
||||
const name = serverName.trim();
|
||||
if (!name)
|
||||
return;
|
||||
const current = new Set(parseDisabledTools(this.form.field("disabledTools").text));
|
||||
if (enabled)
|
||||
current.delete(name);
|
||||
else
|
||||
current.add(name);
|
||||
const next = serializeDisabledTools([...current]);
|
||||
this.form.actions().edit("disabledTools", next);
|
||||
this.form.save(["disabledTools"]);
|
||||
}
|
||||
async refreshPresetCatalog() {
|
||||
const call = this.rpcCall;
|
||||
if (!call || this.presetCatalogInFlight)
|
||||
|
|
@ -2759,6 +3015,10 @@ class NetxopsCardController {
|
|||
apiTokenRemoteReady: this.credential.remoteReady,
|
||||
alarmPushStatus: this.alarmPushStatus,
|
||||
toolsStatus: this.toolsStatus,
|
||||
mcpCatalog: this.mcpCatalog,
|
||||
mcpRefreshBusy: this.mcpRefreshBusy,
|
||||
disabledTools: this.form.field("disabledTools"),
|
||||
readOnlyOnly: this.form.field("readOnlyOnly"),
|
||||
kbStatus: this.kbPreview ?? this.kbStatus,
|
||||
kbDirectoryPickerReady: this.kbDirectoryPicker !== undefined || this.rpcCall !== undefined,
|
||||
kbUiError: this.kbUiError,
|
||||
|
|
@ -2826,7 +3086,9 @@ class NetxopsCardController {
|
|||
"groupBizMonitorInPreset",
|
||||
"groupBizMonitorPublic",
|
||||
"groupKbInPreset",
|
||||
"groupKbPublic"
|
||||
"groupKbPublic",
|
||||
"disabledTools",
|
||||
"readOnlyOnly"
|
||||
]);
|
||||
return {
|
||||
hooks: { netxopsCard: this.store },
|
||||
|
|
@ -2854,6 +3116,12 @@ class NetxopsCardController {
|
|||
},
|
||||
browseKbRoot: () => {
|
||||
this.browseKbRoot();
|
||||
},
|
||||
refreshMcpCatalog: () => {
|
||||
this.runMcpRefresh();
|
||||
},
|
||||
setToolEnabled: (serverName, enabled) => {
|
||||
this.setToolEnabled(serverName, enabled);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
@ -2969,6 +3237,12 @@ var en = {
|
|||
toolsStatusEmpty: "No active tools",
|
||||
toolsStatusError: "Publication failed",
|
||||
toolsStatusUnknown: "Status unavailable",
|
||||
mcpDisconnected: "Disconnected",
|
||||
mcpRefresh: "Refresh",
|
||||
mcpRefreshing: "Refreshing…",
|
||||
readOnlyOnly: "Read-only tools only",
|
||||
toolWriteBadge: "write",
|
||||
toolListEmpty: "No tools yet",
|
||||
sessionsExportCount: "{count} sessions",
|
||||
sessionsExportUnavailable: "Export unavailable",
|
||||
sessionsExportNoPersistence: "Session store unavailable.",
|
||||
|
|
@ -3056,6 +3330,12 @@ var zh = {
|
|||
toolsStatusEmpty: "暂无活跃工具",
|
||||
toolsStatusError: "发布异常",
|
||||
toolsStatusUnknown: "状态不可用",
|
||||
mcpDisconnected: "未连接",
|
||||
mcpRefresh: "刷新",
|
||||
mcpRefreshing: "刷新中…",
|
||||
readOnlyOnly: "只允许只读工具",
|
||||
toolWriteBadge: "写",
|
||||
toolListEmpty: "暂无工具清单",
|
||||
sessionsExportCount: "{count} 个会话",
|
||||
sessionsExportUnavailable: "无法导出",
|
||||
sessionsExportNoPersistence: "会话存储不可用。",
|
||||
|
|
@ -3196,7 +3476,9 @@ var MEMORY_DEFAULTS = {
|
|||
groupBizMonitorPublic: false,
|
||||
kbRoot: "",
|
||||
groupKbInPreset: true,
|
||||
groupKbPublic: false
|
||||
groupKbPublic: false,
|
||||
disabledTools: "[]",
|
||||
readOnlyOnly: false
|
||||
};
|
||||
function apply(ctx) {
|
||||
ctx.effect(() => {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue