n8n-editor-ui
Version:
Workflow Editor UI for n8n
1,023 lines (1,022 loc) • 42.2 kB
JavaScript
import { C as computed, Cn as toDisplayString, D as createElementBlock, E as createCommentVNode, Gt as unref, It as ref, M as createVNode, P as defineComponent, T as createBlock, Z as onMounted, at as renderSlot, bn as normalizeStyle, bt as withCtx, et as openBlock, j as createTextVNode, p as vShow, vn as normalizeClass, w as createBaseVNode, xt as withDirectives } from "./vue.runtime.esm-bundler-tP5dCd7J.js";
import { gt as useI18n } from "./_MapCache-B159LOu5.js";
import { Ct as N8nActionBox_default, D as N8nNotice_default, F as N8nInfoTip_default, Hn as N8nButton_default, I as N8nInfoAccordion_default, M as N8nMarkdown_default, Tt as ElSwitch, Vn as N8nText_default, Wn as N8nIcon_default, et as N8nActionToggle_default, g as N8nTabs_default, mt as N8nTooltip_default, s as N8nDataTableServer_default, tt as N8nLoading_default, ut as N8nLink_default, zn as N8nHeading_default } from "./src-BW7zuOU1.js";
import "./en-b3uD8cvU.js";
import "./preload-helper-CR0ecmWK.js";
import { t as __plugin_vue_export_helper_default } from "./_plugin-vue_export-helper-BwBpWJRZ.js";
import "./truncate-Dc5upPom.js";
import "./icon-MZ_3fB_h.js";
import "./empty-4-N75rxI.js";
import { Tn as useDocumentTitle, nr as useUsersStore, o as useWorkflowsStore, t as useTelemetry } from "./useTelemetry-C797TaYa.js";
import { t as useToast } from "./useToast-ZaUhbG9w.js";
import "./sanitize-html-CgpOSgtK.js";
import "./CalendarDate-DxkU3CHY.js";
import "./path-browserify-B5h_dP_u.js";
import { Io as VIEWS } from "./constants-CX1ozgFz.js";
import "./merge-BArJwbQo.js";
import { r as useRootStore } from "./_baseOrderBy-DL6Sy5xK.js";
import "./dateformat-CMLc6OKJ.js";
import "./useDebounce-RgPaX171.js";
import "./useExternalHooks-CYMS6JpD.js";
import "./useStyles-DhM0cpS7.js";
import "./npsSurvey.store-BuKohzKX.js";
import "./cloudPlan.store-nppjm48S.js";
import "./templates.store-CSx5YxpE.js";
import "./focusPanel.store-Dys3muoU.js";
import "./retry-CalJieID.js";
import "./executions.store-Cu3L65gQ.js";
import "./usePinnedData-BPdJqXOY.js";
import "./nodeCreator.store-DfjyHcRR.js";
import "./nodeIcon-CdzjV7EE.js";
import { t as useClipboard } from "./useClipboard-B4OWL4sL.js";
import "./useCanvasOperations-B_GKebCJ.js";
import "./folders.store-zKqujIOj.js";
import "./NodeIcon-DywH900v.js";
import "./versions.store-BYh7N-zv.js";
import "./banners.store-Bucfp6Le.js";
import "./dataTable.store-DbXbbBi8.js";
import { n as isIconOrEmoji, t as ProjectIcon_default } from "./ProjectIcon-C-YEsNSL.js";
import "./CredentialIcon-DJSoDYWS.js";
import { t as TimeAgo_default } from "./TimeAgo-B1zSnI1Z.js";
import "./roles.store-BDtQtMoj.js";
import "./sso.store-B_c2j1dV.js";
import { t as router_default } from "./router-B0pHLk6x.js";
import "./insights.store-4r_2y-MI.js";
import "./insights.constants-BoP_B6zb.js";
import "./insights.utils-DKMDXq77.js";
import { n as MCP_DOCS_PAGE_URL, t as LOADING_INDICATOR_TIMEOUT } from "./mcp.constants-BEZmLv14.js";
import "./constants-D2fYduVh.js";
import "./chat.store-VUDdXt4K.js";
import "./fileUtils-BIVczUdz.js";
import "./useRecentResources-kKbG_n-r.js";
import { t as useMcp } from "./useMcp-Bf6ZiLU4.js";
import { t as useMCPStore } from "./mcp.store-0cRlt4hy.js";
var ConnectionParameter_vue_vue_type_script_setup_true_lang_default = /* @__PURE__ */ defineComponent({
__name: "ConnectionParameter",
props: {
value: {},
allowCopy: {
type: Boolean,
default: true
},
maxWidth: { default: void 0 }
},
emits: ["copy"],
setup(__props, { emit: __emit }) {
const { copy, copied, isSupported } = useClipboard();
const i18n = useI18n();
const props = __props;
const emit = __emit;
const handleCopy = async (value) => {
await copy(value);
emit("copy", value);
};
return (_ctx, _cache) => {
return openBlock(), createElementBlock("div", {
class: normalizeClass(_ctx.$style.container),
style: normalizeStyle({ maxWidth: props.maxWidth ? props.maxWidth + "px" : "none" })
}, [createBaseVNode("code", null, toDisplayString(props.value), 1), createBaseVNode("div", { class: normalizeClass(_ctx.$style["copy-button-wrapper"]) }, [renderSlot(_ctx.$slots, "customActions"), createVNode(unref(N8nTooltip_default), {
disables: !unref(isSupported),
content: unref(copied) ? unref(i18n).baseText("generic.copied") : unref(i18n).baseText("generic.copy"),
placement: "right"
}, {
default: withCtx(() => [props.allowCopy && unref(isSupported) ? (openBlock(), createBlock(unref(N8nButton_default), {
key: 0,
type: "tertiary",
icon: unref(copied) ? "clipboard-check" : "clipboard",
square: true,
class: normalizeClass(_ctx.$style["copy-button"]),
onClick: _cache[0] || (_cache[0] = ($event) => handleCopy(props.value))
}, null, 8, ["icon", "class"])) : createCommentVNode("", true)]),
_: 1
}, 8, ["disables", "content"])], 2)], 6);
};
}
});
var ConnectionParameter_vue_vue_type_style_index_0_lang_module_default = {
container: "_container_9i963_123",
"copy-button-wrapper": "_copy-button-wrapper_9i963_157",
"copy-button": "_copy-button_9i963_157"
};
var ConnectionParameter_default = /* @__PURE__ */ __plugin_vue_export_helper_default(ConnectionParameter_vue_vue_type_script_setup_true_lang_default, [["__cssModules", { "$style": ConnectionParameter_vue_vue_type_style_index_0_lang_module_default }]]);
var _hoisted_1$2 = { "data-test-id": "oauth-clients-table" };
var _hoisted_2$1 = { key: 0 };
var _hoisted_3$1 = {
key: 1,
class: "mt-s mb-xl"
};
var OAuthClientsTable_vue_vue_type_script_setup_true_lang_default = /* @__PURE__ */ defineComponent({
__name: "OAuthClientsTable",
props: {
clients: {},
loading: { type: Boolean }
},
emits: ["revokeClient", "refresh"],
setup(__props, { emit: __emit }) {
const i18n = useI18n();
const props = __props;
const emit = __emit;
const tableHeaders = ref([
{
title: i18n.baseText("settings.mcp.oAuthClients.table.clientName"),
key: "name",
width: 250,
disableSort: true,
value(client) {
return client.name;
}
},
{
title: i18n.baseText("settings.mcp.oAuthClients.table.connectedAt"),
key: "createdAt",
width: 250,
disableSort: true,
value() {}
},
{
title: "",
key: "actions",
align: "end",
width: 50,
disableSort: true,
value() {}
}
]);
const tableActions = ref([{
label: i18n.baseText("settings.mcp.oAuthClients.table.action.revokeAccess"),
value: "revokeClient"
}]);
const onTableAction = (action, item$2) => {
if (action === "revokeClient") emit("revokeClient", item$2);
};
return (_ctx, _cache) => {
return openBlock(), createElementBlock("div", _hoisted_1$2, [props.loading ? (openBlock(), createElementBlock("div", _hoisted_2$1, [createVNode(unref(N8nLoading_default), {
loading: props.loading,
variant: "h1",
class: "mb-l"
}, null, 8, ["loading"]), createVNode(unref(N8nLoading_default), {
loading: props.loading,
variant: "p",
rows: 5,
"shrink-last": false
}, null, 8, ["loading"])])) : (openBlock(), createElementBlock("div", _hoisted_3$1, [createBaseVNode("div", { class: normalizeClass([_ctx.$style.header, "mb-s"]) }, [createVNode(unref(N8nHeading_default), {
size: "medium",
bold: true
}, {
default: withCtx(() => [createTextVNode(toDisplayString(unref(i18n).baseText("settings.mcp.oAuthClients.heading")) + " (" + toDisplayString(props.clients.length) + ") ", 1)]),
_: 1
}), createVNode(unref(N8nTooltip_default), { content: unref(i18n).baseText("settings.mcp.refresh.tooltip") }, {
default: withCtx(() => [createVNode(unref(N8nButton_default), {
"data-test-id": "mcp-oauth-clients-refresh-button",
size: "small",
type: "tertiary",
icon: "refresh-cw",
square: true,
onClick: _cache[0] || (_cache[0] = ($event) => _ctx.$emit("refresh"))
})]),
_: 1
}, 8, ["content"])], 2), props.clients.length === 0 ? (openBlock(), createBlock(unref(N8nActionBox_default), {
key: 0,
"data-test-id": "empty-oauth-clients-list-box",
heading: unref(i18n).baseText("settings.mcp.oAuthClients.table.empty.title")
}, null, 8, ["heading"])) : (openBlock(), createBlock(unref(N8nDataTableServer_default), {
key: 1,
"data-test-id": "oauth-clients-data-table",
headers: tableHeaders.value,
items: props.clients,
"items-length": props.clients.length
}, {
[`item.createdAt`]: withCtx(({ item: item$2 }) => [createVNode(unref(N8nText_default), { "data-test-id": "mcp-client-created-at" }, {
default: withCtx(() => [createVNode(TimeAgo_default, { date: String(item$2.createdAt) }, null, 8, ["date"])]),
_: 2
}, 1024)]),
[`item.actions`]: withCtx(({ item: item$2 }) => [createVNode(unref(N8nActionToggle_default), {
"data-test-id": "mcp-oauth-client-action-toggle",
placement: "bottom",
actions: tableActions.value,
theme: "dark",
onAction: ($event) => onTableAction($event, item$2)
}, null, 8, ["actions", "onAction"])]),
_: 2
}, 1032, [
"headers",
"items",
"items-length"
]))]))]);
};
}
});
var OAuthClientsTable_vue_vue_type_style_index_0_lang_module_default = { header: "_header_yr8i2_123" };
var OAuthClientsTable_default = /* @__PURE__ */ __plugin_vue_export_helper_default(OAuthClientsTable_vue_vue_type_script_setup_true_lang_default, [["__cssModules", { "$style": OAuthClientsTable_vue_vue_type_style_index_0_lang_module_default }]]);
var OAuthConnectionInstructions_vue_vue_type_script_setup_true_lang_default = /* @__PURE__ */ defineComponent({
__name: "OAuthConnectionInstructions",
props: {
serverUrl: {},
clients: {},
clientsLoading: { type: Boolean }
},
emits: [
"revokeClient",
"refresh",
"urlCopied"
],
setup(__props, { emit: __emit }) {
const props = __props;
const emit = __emit;
const i18n = useI18n();
const onRefreshOAuthClients = () => {
emit("refresh");
};
const onRevokeClientAccess = (client) => {
emit("revokeClient", client);
};
const onUrlCopied = (url$2) => {
emit("urlCopied", url$2);
};
return (_ctx, _cache) => {
return openBlock(), createElementBlock("div", { class: normalizeClass(_ctx.$style.container) }, [createBaseVNode("ol", { class: normalizeClass(_ctx.$style.instructions) }, [createBaseVNode("li", null, [createBaseVNode("div", { class: normalizeClass(_ctx.$style.item) }, [createBaseVNode("span", { class: normalizeClass(_ctx.$style.label) }, toDisplayString(unref(i18n).baseText("settings.mcp.instructions.enableAccess")), 3)], 2)]), createBaseVNode("li", null, [createBaseVNode("div", { class: normalizeClass(_ctx.$style.item) }, [createBaseVNode("span", { class: normalizeClass(_ctx.$style.label) }, toDisplayString(unref(i18n).baseText("settings.mcp.instructions.serverUrl")) + ": ", 3), createVNode(ConnectionParameter_default, {
value: props.serverUrl,
onCopy: onUrlCopied
}, null, 8, ["value"])], 2)])], 2), createBaseVNode("div", { class: normalizeClass(_ctx.$style["clients-table"]) }, [createVNode(OAuthClientsTable_default, {
"data-test-id": "mcp-oauth-clients-table",
clients: props.clients,
loading: props.clientsLoading,
onRevokeClient: onRevokeClientAccess,
onRefresh: onRefreshOAuthClients
}, null, 8, ["clients", "loading"])], 2)], 2);
};
}
});
var OAuthConnectionInstructions_vue_vue_type_style_index_0_lang_module_default = {
container: "_container_k3h69_123",
instructions: "_instructions_k3h69_129",
item: "_item_k3h69_139",
label: "_label_k3h69_149",
url: "_url_k3h69_153",
"copy-url-wrapper": "_copy-url-wrapper_k3h69_169",
"copy-url-button": "_copy-url-button_k3h69_174",
"clients-table": "_clients-table_k3h69_185"
};
var OAuthConnectionInstructions_default = /* @__PURE__ */ __plugin_vue_export_helper_default(OAuthConnectionInstructions_vue_vue_type_script_setup_true_lang_default, [["__cssModules", { "$style": OAuthConnectionInstructions_vue_vue_type_style_index_0_lang_module_default }]]);
var AccessTokenConnectionInstructions_vue_vue_type_script_setup_true_lang_default = /* @__PURE__ */ defineComponent({
__name: "AccessTokenConnectionInstructions",
props: {
serverUrl: {},
apiKey: {},
loadingApiKey: { type: Boolean }
},
emits: [
"rotateKey",
"urlCopied",
"accessTokenCopied",
"connectionStringCopied"
],
setup(__props, { emit: __emit }) {
const props = __props;
const emit = __emit;
const { copy, copied, isSupported } = useClipboard();
const i18n = useI18n();
const connectionString$1 = computed(() => {
return `
{
"mcpServers": {
"n8n-mcp": {
"command": "npx",
"args": [
"-y",
"supergateway",
"--streamableHttp",
"${props.serverUrl}",
"--header",
"authorization:Bearer ${apiKeyText.value}"
]
}
}
}
`;
});
const isKeyRedacted = computed(() => {
return props.apiKey.apiKey.includes("******");
});
const connectionCode = computed(() => {
return `\`\`\`json${connectionString$1.value}\`\`\``;
});
const apiKeyText = computed(() => {
if (props.loadingApiKey) return `<${i18n.baseText("generic.loading")}...>`;
return isKeyRedacted.value ? "<YOUR_ACCESS_TOKEN_HERE>" : props.apiKey.apiKey;
});
const handleConnectionStringCopy = async () => {
await copy(connectionString$1.value);
emit("connectionStringCopied");
};
const handleUrlCopy = (url$2) => {
emit("urlCopied", url$2);
};
const handleAccessTokenCopy = () => {
emit("accessTokenCopied");
};
return (_ctx, _cache) => {
return openBlock(), createElementBlock("div", { class: normalizeClass(_ctx.$style.container) }, [createBaseVNode("div", { class: normalizeClass(_ctx.$style["instructions-container"]) }, [createBaseVNode("ol", { class: normalizeClass(_ctx.$style.instructions) }, [
createBaseVNode("li", null, [createBaseVNode("div", { class: normalizeClass(_ctx.$style.item) }, [createBaseVNode("span", { class: normalizeClass(_ctx.$style.label) }, toDisplayString(unref(i18n).baseText("settings.mcp.instructions.enableAccess")), 3)], 2)]),
createBaseVNode("li", null, [createBaseVNode("div", { class: normalizeClass(_ctx.$style.item) }, [createBaseVNode("span", { class: normalizeClass(_ctx.$style.label) }, toDisplayString(unref(i18n).baseText("settings.mcp.instructions.serverUrl")) + ": ", 3), createVNode(ConnectionParameter_default, {
value: props.serverUrl,
onCopy: handleUrlCopy
}, null, 8, ["value"])], 2)]),
createBaseVNode("li", null, [createBaseVNode("div", { class: normalizeClass(_ctx.$style.item) }, [
createBaseVNode("span", { class: normalizeClass(_ctx.$style.label) }, toDisplayString(unref(i18n).baseText("settings.mcp.instructions.apiKey.label")) + ": ", 3),
props.loadingApiKey ? (openBlock(), createBlock(unref(N8nLoading_default), {
key: 0,
loading: props.loadingApiKey,
class: normalizeClass(_ctx.$style["api-key-loader"])
}, null, 8, ["loading", "class"])) : (openBlock(), createBlock(ConnectionParameter_default, {
key: 1,
value: props.apiKey.apiKey,
"max-width": 400,
"allow-copy": !isKeyRedacted.value,
onCopy: handleAccessTokenCopy
}, {
customActions: withCtx(() => [createVNode(unref(N8nTooltip_default), { content: unref(i18n).baseText("settings.mcp.instructions.rotateKey.tooltip") }, {
default: withCtx(() => [createVNode(unref(N8nButton_default), {
type: "tertiary",
icon: "refresh-cw",
square: true,
onClick: _cache[0] || (_cache[0] = ($event) => emit("rotateKey"))
})]),
_: 1
}, 8, ["content"])]),
_: 1
}, 8, ["value", "allow-copy"])),
!props.loadingApiKey ? (openBlock(), createBlock(unref(N8nInfoTip_default), {
key: 2,
type: "tooltip",
"tooltip-placement": "right"
}, {
default: withCtx(() => [createTextVNode(toDisplayString(unref(i18n).baseText("settings.mcp.instructions.apiKey.tip")), 1)]),
_: 1
})) : createCommentVNode("", true)
], 2)])
], 2), !isKeyRedacted.value && !props.loadingApiKey ? (openBlock(), createBlock(unref(N8nNotice_default), {
key: 0,
theme: "warning",
class: normalizeClass(_ctx.$style["copy-key-notice"]),
content: unref(i18n).baseText("settings.mcp.newKey.notice")
}, null, 8, ["class", "content"])) : createCommentVNode("", true)], 2), createBaseVNode("div", { class: normalizeClass(_ctx.$style.connectionString) }, [createVNode(unref(N8nInfoAccordion_default), { title: unref(i18n).baseText("settings.mcp.instructions.json") }, {
customContent: withCtx(() => [createVNode(unref(N8nMarkdown_default), { content: connectionCode.value }, null, 8, ["content"]), createVNode(unref(N8nTooltip_default), {
disabled: !unref(isSupported),
content: unref(copied) ? unref(i18n).baseText("generic.copied") : unref(i18n).baseText("generic.copy")
}, {
default: withCtx(() => [unref(isSupported) && !props.loadingApiKey ? (openBlock(), createBlock(unref(N8nButton_default), {
key: 0,
type: "tertiary",
icon: unref(copied) ? "clipboard-check" : "clipboard",
square: true,
class: normalizeClass(_ctx.$style["copy-json-button"]),
onClick: handleConnectionStringCopy
}, null, 8, ["icon", "class"])) : createCommentVNode("", true)]),
_: 1
}, 8, ["disabled", "content"])]),
_: 1
}, 8, ["title"])], 2)], 2);
};
}
});
const container$2 = "_container_1r8bk_123";
const instructions = "_instructions_1r8bk_129";
const item = "_item_1r8bk_143";
const label = "_label_1r8bk_153";
const url = "_url_1r8bk_157";
const connectionString = "_connectionString_1r8bk_189";
var AccessTokenConnectionInstructions_vue_vue_type_style_index_0_lang_module_default = {
container: container$2,
"instructions-container": "_instructions-container_1r8bk_129",
instructions,
item,
label,
url,
"copy-url-wrapper": "_copy-url-wrapper_1r8bk_173",
"copy-url-button": "_copy-url-button_1r8bk_178",
connectionString,
"copy-json-button": "_copy-json-button_1r8bk_202"
};
var AccessTokenConnectionInstructions_default = /* @__PURE__ */ __plugin_vue_export_helper_default(AccessTokenConnectionInstructions_vue_vue_type_script_setup_true_lang_default, [["__cssModules", { "$style": AccessTokenConnectionInstructions_vue_vue_type_style_index_0_lang_module_default }]]);
var _hoisted_1$1 = ["href"];
var MCP_ENDPOINT = "mcp-server/http";
var MCPConnectionInstructions_vue_vue_type_script_setup_true_lang_default = /* @__PURE__ */ defineComponent({
__name: "MCPConnectionInstructions",
props: {
baseUrl: {},
apiKey: {},
loadingApiKey: { type: Boolean },
oAuthClients: {},
loadingOAuthClients: { type: Boolean }
},
emits: [
"rotateKey",
"revokeClient",
"refreshClientList"
],
setup(__props, { emit: __emit }) {
const props = __props;
const emit = __emit;
const i18n = useI18n();
const telemetry = useTelemetry();
const selectedTab = ref("oauth");
const tabs = ref([{
label: i18n.baseText("settings.mcp.instructions.tabs.oauth"),
value: "oauth"
}, {
label: i18n.baseText("settings.mcp.instructions.tabs.apiKey"),
value: "token"
}]);
const onTabSelected = (tab) => {
selectedTab.value = tab;
};
const onClientRevoked = (client) => {
emit("revokeClient", client);
telemetry.track("User revoked access for MCP OAuth client", { client_name: client.name });
};
const trackCopyEvent = (payload) => {
telemetry.track("User copied MCP connection parameter", {
parameter: payload.item,
source: payload.source
});
};
return (_ctx, _cache) => {
return openBlock(), createElementBlock("div", { class: normalizeClass(_ctx.$style.container) }, [createVNode(unref(N8nHeading_default), {
size: "medium",
bold: true
}, {
default: withCtx(() => [createTextVNode(toDisplayString(unref(i18n).baseText("settings.mcp.connection.info.heading")), 1)]),
_: 1
}), createBaseVNode("div", null, [
createVNode(unref(N8nTabs_default), {
"model-value": selectedTab.value,
options: tabs.value,
"onUpdate:modelValue": onTabSelected
}, null, 8, ["model-value", "options"]),
withDirectives(createVNode(OAuthConnectionInstructions_default, {
"server-url": `${props.baseUrl}${MCP_ENDPOINT}`,
clients: props.oAuthClients,
"clients-loading": props.loadingOAuthClients,
onRevokeClient: onClientRevoked,
onRefresh: _cache[0] || (_cache[0] = ($event) => emit("refreshClientList")),
onUrlCopied: _cache[1] || (_cache[1] = ($event) => trackCopyEvent({
item: "server-url",
source: "oauth-tab"
}))
}, null, 8, [
"server-url",
"clients",
"clients-loading"
]), [[vShow, selectedTab.value === "oauth"]]),
withDirectives(createVNode(AccessTokenConnectionInstructions_default, {
"server-url": `${props.baseUrl}${MCP_ENDPOINT}`,
"api-key": props.apiKey,
"loading-api-key": props.loadingApiKey,
onRotateKey: _cache[2] || (_cache[2] = ($event) => emit("rotateKey")),
onConnectionStringCopied: _cache[3] || (_cache[3] = ($event) => trackCopyEvent({
item: "mcp-json",
source: "token-tab"
})),
onAccessTokenCopied: _cache[4] || (_cache[4] = ($event) => trackCopyEvent({
item: "access-token",
source: "token-tab"
})),
onUrlCopied: _cache[5] || (_cache[5] = ($event) => trackCopyEvent({
item: "server-url",
source: "token-tab"
}))
}, null, 8, [
"server-url",
"api-key",
"loading-api-key"
]), [[vShow, selectedTab.value === "token"]]),
createVNode(unref(N8nText_default), {
size: "small",
"data-test-id": "mcp-connection-instructions-docs-text"
}, {
default: withCtx(() => [createTextVNode(toDisplayString(unref(i18n).baseText("settings.mcp.instructions.docs.part1")) + " ", 1), createBaseVNode("a", {
href: unref(MCP_DOCS_PAGE_URL),
target: "_blank"
}, toDisplayString(unref(i18n).baseText("settings.mcp.instructions.docs.part2")), 9, _hoisted_1$1)]),
_: 1
})
])], 2);
};
}
});
var MCPConnectionInstructions_vue_vue_type_style_index_0_lang_module_default = { container: "_container_1n2r0_123" };
var MCPConnectionInstructions_default = /* @__PURE__ */ __plugin_vue_export_helper_default(MCPConnectionInstructions_vue_vue_type_script_setup_true_lang_default, [["__cssModules", { "$style": MCPConnectionInstructions_vue_vue_type_style_index_0_lang_module_default }]]);
var _hoisted_1 = { key: 0 };
var _hoisted_2 = {
key: 1,
class: "mt-s mb-xl"
};
var _hoisted_3 = { key: 1 };
var _hoisted_4 = { key: 0 };
var WorkflowsTable_vue_vue_type_script_setup_true_lang_default = /* @__PURE__ */ defineComponent({
__name: "WorkflowsTable",
props: {
workflows: {},
loading: { type: Boolean }
},
emits: ["removeMcpAccess", "refresh"],
setup(__props, { emit: __emit }) {
const props = __props;
const emit = __emit;
const i18n = useI18n();
const tableHeaders = ref([
{
title: i18n.baseText("generic.project"),
key: "homeProject",
width: 150,
disableSort: true,
value() {}
},
{
title: i18n.baseText("settings.mcp.workflowsTable.workflow"),
key: "workflow",
width: 200,
disableSort: true,
value() {}
},
{
title: i18n.baseText("generic.description"),
key: "description",
width: 350,
disableSort: true,
value() {}
},
{
title: "",
key: "actions",
align: "end",
width: 50,
disableSort: true,
value() {}
}
]);
const tableActions = ref([{
label: i18n.baseText("settings.mcp.workflows.table.action.removeMCPAccess"),
value: "removeFromMCP"
}]);
const getProjectIcon = (workflow) => {
if (workflow.homeProject?.type === "personal") return {
type: "icon",
value: "user"
};
else if (workflow.homeProject?.name) return isIconOrEmoji(workflow.homeProject.icon) ? workflow.homeProject.icon : {
type: "icon",
value: "layers"
};
else return {
type: "icon",
value: "house"
};
};
const getProjectName = (workflow) => {
if (workflow.homeProject?.type === "personal") return i18n.baseText("projects.menu.personal");
return workflow.homeProject?.name ?? "";
};
const onWorkflowAction = (action, workflow) => {
switch (action) {
case "removeFromMCP":
emit("removeMcpAccess", workflow);
break;
default: break;
}
};
return (_ctx, _cache) => {
return openBlock(), createElementBlock("div", { class: normalizeClass(_ctx.$style["workflow-table-container"]) }, [props.loading ? (openBlock(), createElementBlock("div", _hoisted_1, [createVNode(unref(N8nLoading_default), {
loading: props.loading,
variant: "h1",
class: "mb-l"
}, null, 8, ["loading"]), createVNode(unref(N8nLoading_default), {
loading: props.loading,
variant: "p",
rows: 5,
"shrink-last": false
}, null, 8, ["loading"])])) : (openBlock(), createElementBlock("div", _hoisted_2, [createBaseVNode("div", { class: normalizeClass([_ctx.$style.header, "mb-s"]) }, [createVNode(unref(N8nHeading_default), {
size: "medium",
bold: true
}, {
default: withCtx(() => [createTextVNode(toDisplayString(unref(i18n).baseText("settings.mcp.available.workflows.heading")) + " (" + toDisplayString(props.workflows.length) + ") ", 1)]),
_: 1
}), createVNode(unref(N8nTooltip_default), { content: unref(i18n).baseText("settings.mcp.refresh.tooltip") }, {
default: withCtx(() => [createVNode(unref(N8nButton_default), {
"data-test-id": "mcp-workflows-refresh-button",
size: "small",
type: "tertiary",
icon: "refresh-cw",
square: true,
onClick: _cache[0] || (_cache[0] = ($event) => _ctx.$emit("refresh"))
})]),
_: 1
}, 8, ["content"])], 2), props.workflows.length === 0 ? (openBlock(), createBlock(unref(N8nActionBox_default), {
key: 0,
"data-test-id": "empty-workflow-list-box",
heading: unref(i18n).baseText("settings.mcp.empty.title"),
description: unref(i18n).baseText("settings.mcp.empty.description")
}, null, 8, ["heading", "description"])) : (openBlock(), createBlock(unref(N8nDataTableServer_default), {
key: 1,
class: normalizeClass(_ctx.$style["workflow-table"]),
"data-test-id": "mcp-workflow-table",
headers: tableHeaders.value,
items: props.workflows,
"items-length": props.workflows.length
}, {
[`item.workflow`]: withCtx(({ item: item$2 }) => [createBaseVNode("div", {
class: normalizeClass(_ctx.$style["workflow-cell"]),
"data-test-id": "mcp-workflow-cell"
}, [
item$2.parentFolder?.parentFolderId ? (openBlock(), createElementBlock("span", {
key: 0,
class: normalizeClass(_ctx.$style["parent-folder"]),
"data-test-id": "mcp-workflow-grandparent-folder"
}, [createBaseVNode("span", { class: normalizeClass(_ctx.$style.ellipsis) }, "...", 2), createBaseVNode("span", {
class: normalizeClass(_ctx.$style.separator),
"data-test-id": "mcp-workflow-ellipsis-separator"
}, "/", 2)], 2)) : createCommentVNode("", true),
item$2.parentFolder ? (openBlock(), createElementBlock("span", {
key: 1,
class: normalizeClass(_ctx.$style["parent-folder"])
}, [item$2.homeProject ? (openBlock(), createBlock(unref(N8nLink_default), {
key: 0,
"data-test-id": "mcp-workflow-folder-link",
to: `/projects/${item$2.homeProject.id}/folders/${item$2.parentFolder.id}/workflows`,
theme: "text",
class: normalizeClass(_ctx.$style["table-link"]),
"new-window": true
}, {
default: withCtx(() => [createVNode(unref(N8nText_default), { "data-test-id": "mcp-workflow-folder-name" }, {
default: withCtx(() => [createTextVNode(toDisplayString(item$2.parentFolder.name), 1)]),
_: 2
}, 1024)]),
_: 2
}, 1032, ["to", "class"])) : (openBlock(), createElementBlock("span", _hoisted_3, [item$2.parentFolder ? (openBlock(), createBlock(unref(N8nIcon_default), {
key: 0,
icon: "folder",
size: 16,
color: "text-light"
})) : createCommentVNode("", true), createVNode(unref(N8nText_default), { "data-test-id": "mcp-workflow-folder-name" }, {
default: withCtx(() => [createTextVNode(toDisplayString(item$2.parentFolder.name), 1)]),
_: 2
}, 1024)]))], 2)) : createCommentVNode("", true),
item$2.parentFolder ? (openBlock(), createElementBlock("span", {
key: 2,
class: normalizeClass(_ctx.$style["separator"]),
"data-test-id": "mcp-workflow-folder-separator"
}, "/", 2)) : createCommentVNode("", true),
createVNode(unref(N8nLink_default), {
"data-test-id": "mcp-workflow-name-link",
"new-window": true,
to: unref(router_default).resolve({
name: unref(VIEWS).WORKFLOW,
params: { name: item$2.id }
}).fullPath,
theme: "text",
class: normalizeClass(_ctx.$style["table-link"])
}, {
default: withCtx(() => [createVNode(unref(N8nText_default), { "data-test-id": "mcp-workflow-name" }, {
default: withCtx(() => [createTextVNode(toDisplayString(item$2.name), 1)]),
_: 2
}, 1024)]),
_: 2
}, 1032, ["to", "class"])
], 2)]),
[`item.description`]: withCtx(({ item: item$2 }) => [createVNode(unref(N8nTooltip_default), {
content: item$2.description,
disabled: !item$2.description,
"popper-class": _ctx.$style["description-popper"]
}, {
default: withCtx(() => [createBaseVNode("div", { class: normalizeClass(_ctx.$style["description-cell"]) }, [createVNode(unref(N8nText_default), { "data-test-id": "mcp-workflow-description" }, {
default: withCtx(() => [createTextVNode(toDisplayString(item$2.description || ""), 1)]),
_: 2
}, 1024)], 2)]),
_: 2
}, 1032, [
"content",
"disabled",
"popper-class"
])]),
[`item.homeProject`]: withCtx(({ item: item$2 }) => [item$2.homeProject ? (openBlock(), createElementBlock("span", _hoisted_4, [createVNode(unref(N8nLink_default), {
"data-test-id": "mcp-workflow-project-link",
to: unref(router_default).resolve({
name: unref(VIEWS).PROJECTS_WORKFLOWS,
params: { projectId: item$2.homeProject.id }
}).fullPath,
theme: "text",
class: normalizeClass([_ctx.$style["table-link"], _ctx.$style["project-link"]]),
"new-window": true
}, {
default: withCtx(() => [
item$2.homeProject ? (openBlock(), createBlock(ProjectIcon_default, {
key: 0,
icon: getProjectIcon(item$2),
"border-less": true
}, null, 8, ["icon"])) : createCommentVNode("", true),
createVNode(unref(N8nText_default), { "data-test-id": "mcp-workflow-project-name" }, {
default: withCtx(() => [createTextVNode(toDisplayString(getProjectName(item$2)), 1)]),
_: 2
}, 1024),
createVNode(unref(N8nIcon_default), {
icon: "external-link",
class: normalizeClass(_ctx.$style["link-icon"]),
color: "text-light"
}, null, 8, ["class"])
]),
_: 2
}, 1032, ["to", "class"])])) : (openBlock(), createBlock(unref(N8nText_default), {
key: 1,
"data-test-id": "mcp-workflow-no-project"
}, {
default: withCtx(() => _cache[1] || (_cache[1] = [createTextVNode("-")])),
_: 1
}))]),
[`item.actions`]: withCtx(({ item: item$2 }) => [createVNode(unref(N8nActionToggle_default), {
"data-test-id": "mcp-workflow-action-toggle",
placement: "bottom",
actions: tableActions.value,
theme: "dark",
onAction: ($event) => onWorkflowAction($event, item$2)
}, null, 8, ["actions", "onAction"])]),
_: 2
}, 1032, [
"class",
"headers",
"items",
"items-length"
]))]))], 2);
};
}
});
const header = "_header_1c109_127";
const separator = "_separator_1c109_143";
const ellipsis = "_ellipsis_1c109_144";
var WorkflowsTable_vue_vue_type_style_index_0_lang_module_default = {
"workflow-table-container": "_workflow-table-container_1c109_123",
header,
"workflow-table": "_workflow-table_1c109_123",
"workflow-cell": "_workflow-cell_1c109_137",
"parent-folder": "_parent-folder_1c109_138",
separator,
ellipsis,
"description-cell": "_description-cell_1c109_151",
"description-popper": "_description-popper_1c109_160",
"table-link": "_table-link_1c109_164",
"link-icon": "_link-icon_1c109_172",
"project-link": "_project-link_1c109_178"
};
var WorkflowsTable_default = /* @__PURE__ */ __plugin_vue_export_helper_default(WorkflowsTable_vue_vue_type_script_setup_true_lang_default, [["__cssModules", { "$style": WorkflowsTable_vue_vue_type_style_index_0_lang_module_default }]]);
var McpAccessToggle_vue_vue_type_script_setup_true_lang_default = /* @__PURE__ */ defineComponent({
__name: "McpAccessToggle",
props: {
modelValue: { type: Boolean },
disabled: {
type: Boolean,
default: false
},
loading: {
type: Boolean,
default: false
}
},
emits: ["toggleMcpAccess"],
setup(__props, { emit: __emit }) {
const props = __props;
const emit = __emit;
const i18n = useI18n();
const onUpdateMCPEnabled = (value) => {
emit("toggleMcpAccess", typeof value === "boolean" ? value : Boolean(value));
};
return (_ctx, _cache) => {
return openBlock(), createElementBlock("div", { class: normalizeClass(_ctx.$style["mcp-access-toggle"]) }, [createBaseVNode("div", { class: normalizeClass(_ctx.$style["main-toggle-container"]) }, [createBaseVNode("div", { class: normalizeClass(_ctx.$style["main-toggle-info"]) }, [createVNode(unref(N8nText_default), { bold: true }, {
default: withCtx(() => [createTextVNode(toDisplayString(unref(i18n).baseText("settings.mcp.toggle.label")), 1)]),
_: 1
}), createVNode(unref(N8nText_default), {
size: "small",
color: "text-light"
}, {
default: withCtx(() => [createTextVNode(toDisplayString(unref(i18n).baseText("settings.mcp.toggle.description")), 1)]),
_: 1
})], 2), createBaseVNode("div", {
class: normalizeClass(_ctx.$style["main-toggle"]),
"data-test-id": "mcp-toggle-container"
}, [createVNode(unref(N8nTooltip_default), {
content: unref(i18n).baseText("settings.mcp.toggle.disabled.tooltip"),
disabled: !props.disabled,
placement: "top"
}, {
default: withCtx(() => [createVNode(unref(ElSwitch), {
size: "large",
"data-test-id": "mcp-access-toggle",
"model-value": props.modelValue,
disabled: props.disabled,
loading: props.loading,
"onUpdate:modelValue": onUpdateMCPEnabled
}, null, 8, [
"model-value",
"disabled",
"loading"
])]),
_: 1
}, 8, ["content", "disabled"])], 2)], 2), !props.modelValue ? (openBlock(), createElementBlock("div", {
key: 0,
class: normalizeClass(_ctx.$style["toggle-notice"])
}, [createVNode(unref(N8nText_default), {
color: "text-base",
"data-test-id": "mcp-toggle-disabled-notice"
}, {
default: withCtx(() => [createTextVNode(toDisplayString(unref(i18n).baseText("settings.mcp.toggle.disabled.notice")), 1)]),
_: 1
}), createVNode(unref(N8nLink_default), {
to: unref(MCP_DOCS_PAGE_URL),
"new-window": true
}, {
default: withCtx(() => [createTextVNode(toDisplayString(unref(i18n).baseText("generic.learnMore")), 1)]),
_: 1
}, 8, ["to"])], 2)) : createCommentVNode("", true)], 2);
};
}
});
var McpAccessToggle_vue_vue_type_style_index_0_lang_module_default = {
"mcp-access-toggle": "_mcp-access-toggle_p4msq_123",
"main-toggle-container": "_main-toggle-container_p4msq_130",
"main-toggle-info": "_main-toggle-info_p4msq_140",
"main-toggle": "_main-toggle_p4msq_130",
"toggle-notice": "_toggle-notice_p4msq_154"
};
var McpAccessToggle_default = /* @__PURE__ */ __plugin_vue_export_helper_default(McpAccessToggle_vue_vue_type_script_setup_true_lang_default, [["__cssModules", { "$style": McpAccessToggle_vue_vue_type_style_index_0_lang_module_default }]]);
var SettingsMCPView_vue_vue_type_script_setup_true_lang_default = /* @__PURE__ */ defineComponent({
__name: "SettingsMCPView",
setup(__props) {
const i18n = useI18n();
const toast = useToast();
const documentTitle = useDocumentTitle();
const mcp = useMcp();
const workflowsStore = useWorkflowsStore();
const mcpStore = useMCPStore();
const usersStore = useUsersStore();
const rootStore = useRootStore();
const mcpStatusLoading = ref(false);
const workflowsLoading = ref(false);
const mcpKeyLoading = ref(false);
const oAuthClientsLoading = ref(false);
const availableWorkflows = ref([]);
const apiKey = computed(() => mcpStore.currentUserMCPKey);
const connectedOAuthClients = ref([]);
const isOwner = computed(() => usersStore.isInstanceOwner);
const isAdmin = computed(() => usersStore.isAdmin);
const canToggleMCP = computed(() => isOwner.value || isAdmin.value);
const onToggleMCPAccess = async (enabled) => {
try {
mcpStatusLoading.value = true;
if (await mcpStore.setMcpAccessEnabled(enabled)) {
await fetchAvailableWorkflows();
await fetchApiKey();
await fetchoAuthCLients();
} else workflowsLoading.value = false;
mcp.trackUserToggledMcpAccess(enabled);
} catch (error) {
toast.showError(error, i18n.baseText("settings.mcp.toggle.error"));
} finally {
mcpStatusLoading.value = false;
workflowsLoading.value = false;
}
};
const onRemoveMCPAccess = async (workflow) => {
try {
await workflowsStore.updateWorkflowSetting(workflow.id, "availableInMCP", false);
availableWorkflows.value = availableWorkflows.value.filter((w) => w.id !== workflow.id);
} catch (error) {
toast.showError(error, i18n.baseText("workflowSettings.toggleMCP.error.title"));
}
};
const fetchAvailableWorkflows = async () => {
workflowsLoading.value = true;
try {
availableWorkflows.value = await mcpStore.fetchWorkflowsAvailableForMCP(1, 200);
} catch (error) {
toast.showError(error, i18n.baseText("workflows.list.error.fetching"));
} finally {
setTimeout(() => {
workflowsLoading.value = false;
}, 200);
}
};
const fetchApiKey = async () => {
try {
mcpKeyLoading.value = true;
await mcpStore.getOrCreateApiKey();
} catch (error) {
toast.showError(error, i18n.baseText("settings.mcp.error.fetching.apiKey"));
} finally {
setTimeout(() => {
mcpKeyLoading.value = false;
}, 200);
}
};
const rotateKey = async () => {
try {
mcpKeyLoading.value = true;
await mcpStore.generateNewApiKey();
} catch (error) {
toast.showError(error, i18n.baseText("settings.mcp.error.rotating.apiKey"));
} finally {
setTimeout(() => {
mcpKeyLoading.value = false;
}, 200);
}
};
const fetchoAuthCLients = async () => {
try {
oAuthClientsLoading.value = true;
connectedOAuthClients.value = await mcpStore.getAllOAuthClients();
} catch (error) {
toast.showError(error, i18n.baseText("settings.mcp.error.fetching.oAuthClients"));
} finally {
setTimeout(() => {
oAuthClientsLoading.value = false;
}, 200);
}
};
const revokeClientAccess = async (client) => {
try {
await mcpStore.removeOAuthClient(client.id);
connectedOAuthClients.value = connectedOAuthClients.value.filter((c) => c.id !== client.id);
toast.showMessage({
type: "success",
title: i18n.baseText("settings.mcp.oAuthClients.revoke.success.title"),
message: i18n.baseText("settings.mcp.oAuthClients.revoke.success.message", { interpolate: { name: client.name } })
});
} catch (error) {
toast.showError(error, i18n.baseText("settings.mcp.oAuthClients.revoke.error"));
}
};
const onRefreshOAuthClients = async () => {
await fetchoAuthCLients();
};
const onRefreshWorkflows = async () => {
await fetchAvailableWorkflows();
};
onMounted(async () => {
documentTitle.set(i18n.baseText("settings.mcp"));
if (!mcpStore.mcpAccessEnabled) return;
await fetchAvailableWorkflows();
await fetchApiKey();
await fetchoAuthCLients();
});
return (_ctx, _cache) => {
return openBlock(), createElementBlock("div", { class: normalizeClass(_ctx.$style.container) }, [
createVNode(unref(N8nHeading_default), {
size: "2xlarge",
class: "mb-2xs"
}, {
default: withCtx(() => [createTextVNode(toDisplayString(unref(i18n).baseText("settings.mcp")), 1)]),
_: 1
}),
createVNode(McpAccessToggle_default, {
"data-test-id": "mcp-toggle-section",
"model-value": unref(mcpStore).mcpAccessEnabled,
disabled: !canToggleMCP.value,
loading: mcpStatusLoading.value,
onToggleMcpAccess: onToggleMCPAccess
}, null, 8, [
"model-value",
"disabled",
"loading"
]),
unref(mcpStore).mcpAccessEnabled ? (openBlock(), createElementBlock("div", {
key: 0,
class: normalizeClass(_ctx.$style.container),
"data-test-id": "mcp-enabled-section"
}, [apiKey.value ? (openBlock(), createBlock(MCPConnectionInstructions_default, {
key: 0,
"loading-api-key": mcpKeyLoading.value,
"base-url": unref(rootStore).urlBaseEditor,
"api-key": apiKey.value,
"o-auth-clients": connectedOAuthClients.value,
"loading-o-auth-clients": oAuthClientsLoading.value,
onRotateKey: rotateKey,
onRevokeClient: revokeClientAccess,
onRefreshClientList: onRefreshOAuthClients
}, null, 8, [
"loading-api-key",
"base-url",
"api-key",
"o-auth-clients",
"loading-o-auth-clients"
])) : createCommentVNode("", true), createVNode(WorkflowsTable_default, {
"data-test-id": "mcp-workflow-table",
workflows: availableWorkflows.value,
loading: workflowsLoading.value,
onRemoveMcpAccess: onRemoveMCPAccess,
onRefresh: onRefreshWorkflows
}, null, 8, ["workflows", "loading"])], 2)) : createCommentVNode("", true)
], 2);
};
}
});
var SettingsMCPView_vue_vue_type_style_index_0_lang_module_default = { container: "_container_727ie_123" };
var SettingsMCPView_default = /* @__PURE__ */ __plugin_vue_export_helper_default(SettingsMCPView_vue_vue_type_script_setup_true_lang_default, [["__cssModules", { "$style": SettingsMCPView_vue_vue_type_style_index_0_lang_module_default }]]);
export { SettingsMCPView_default as default };