UNPKG

n8n-editor-ui

Version:

Workflow Editor UI for n8n

322 lines (312 loc) 11.9 kB
import { C as computed, It as ref } from "./vue.runtime.esm-bundler-tP5dCd7J.js"; import { gt as useI18n } from "./_MapCache-B159LOu5.js"; import { Gn as updatedIconSet, it as useDeviceSupport } from "./src-BW7zuOU1.js"; import { y as useRouter } from "./truncate-Dc5upPom.js"; import { Dn as useSourceControlStore, En as useProjectsStore, Ho as useSettingsStore } from "./useTelemetry-C797TaYa.js"; import { t as useToast } from "./useToast-ZaUhbG9w.js"; import { Do as getResourcePermissions, Io as VIEWS } from "./constants-CX1ozgFz.js"; import { r as useRootStore } from "./_baseOrderBy-DL6Sy5xK.js"; import { t as useCloudPlanStore } from "./cloudPlan.store-nppjm48S.js"; import { t as usePageRedirectionHelper } from "./usePageRedirectionHelper-DY-ECTME.js"; import { t as sortByProperty } from "./sortByProperty-DeupcrDk.js"; function useDebugInfo() { const settingsStore = useSettingsStore(); const rootStore = useRootStore(); const { isTouchDevice, userAgent } = useDeviceSupport(); const coreInfo = (skipSensitive) => { const info = { n8nVersion: rootStore.versionCli, platform: settingsStore.isDocker && settingsStore.deploymentType === "cloud" ? "docker (cloud)" : settingsStore.isDocker ? "docker (self-hosted)" : "npm", nodeJsVersion: settingsStore.nodeJsVersion, nodeEnv: settingsStore.nodeEnv, database: settingsStore.databaseType === "postgresdb" ? "postgres" : settingsStore.databaseType === "mysqldb" ? "mysql" : settingsStore.databaseType, executionMode: settingsStore.isQueueModeEnabled ? settingsStore.isMultiMain ? "scaling (multi-main)" : "scaling (single-main)" : "regular", concurrency: settingsStore.settings.concurrency, license: settingsStore.isCommunityPlan || !settingsStore.settings.license ? "community" : settingsStore.settings.license.environment === "production" ? "enterprise (production)" : "enterprise (sandbox)" }; if (!skipSensitive) return { ...info, consumerId: !skipSensitive ? settingsStore.consumerId : void 0 }; return info; }; const storageInfo = () => { return { success: settingsStore.saveDataSuccessExecution, error: settingsStore.saveDataErrorExecution, progress: settingsStore.saveDataProgressExecution, manual: settingsStore.saveManualExecutions, binaryMode: settingsStore.binaryDataMode === "default" ? "memory" : settingsStore.binaryDataMode }; }; const pruningInfo = () => { if (!settingsStore.pruning?.isEnabled) return { enabled: false }; return { enabled: true, maxAge: `${settingsStore.pruning?.maxAge} hours`, maxCount: `${settingsStore.pruning?.maxCount} executions` }; }; const securityInfo = () => { const info = {}; if (!settingsStore.security.blockFileAccessToN8nFiles) info.blockFileAccessToN8nFiles = false; if (!settingsStore.security.secureCookie) info.secureCookie = false; if (Object.keys(info).length === 0) return; return info; }; const client = () => { return { userAgent, isTouchDevice }; }; const gatherDebugInfo = (skipSensitive) => { const debugInfo = { core: coreInfo(skipSensitive), storage: storageInfo(), pruning: pruningInfo(), client: client() }; const security = securityInfo(); if (security) debugInfo.security = security; return debugInfo; }; const toMarkdown = (debugInfo, { secondaryHeader }) => { const extraLevel = secondaryHeader ? "#" : ""; let markdown = `${extraLevel}# Debug info\n\n`; for (const sectionKey in debugInfo) { markdown += `${extraLevel}## ${sectionKey}\n\n`; const section = debugInfo[sectionKey]; if (!section) continue; for (const itemKey in section) { const itemValue = section[itemKey]; markdown += `- ${itemKey}: ${itemValue}\n`; } markdown += "\n"; } return markdown; }; const appendTimestamp = (markdown) => { return `${markdown}Generated at: ${(/* @__PURE__ */ new Date()).toISOString()}`; }; const generateDebugInfo = ({ skipSensitive, secondaryHeader } = {}) => { return appendTimestamp(toMarkdown(gatherDebugInfo(skipSensitive), { secondaryHeader })); }; return { generateDebugInfo }; } var isIconName = (icon) => typeof icon === "string" && Object.keys(updatedIconSet).includes(icon); var isProjectIcon = (icon) => isIconName(icon) || typeof icon === "object" && icon !== null && "value" in icon && typeof icon.value === "string" && "type" in icon && (icon.type === "emoji" || icon.type === "icon" && isIconName(icon.value)); const useGlobalEntityCreation = () => { const CREATE_PROJECT_ID = "create-project"; const WORKFLOWS_MENU_ID = "workflow"; const CREDENTIALS_MENU_ID = "credential"; const DEFAULT_ICON = "layers"; const settingsStore = useSettingsStore(); const cloudPlanStore = useCloudPlanStore(); const projectsStore = useProjectsStore(); const sourceControlStore = useSourceControlStore(); const router = useRouter(); const i18n = useI18n(); const toast = useToast(); const isCreatingProject = ref(false); const displayProjects = computed(() => sortByProperty("name", projectsStore.myProjects.filter((p) => p.type === "team"))); const disabledWorkflow = (scopes = []) => sourceControlStore.preferences.branchReadOnly || !getResourcePermissions(scopes).workflow.create; const disabledCredential = (scopes = []) => sourceControlStore.preferences.branchReadOnly || !getResourcePermissions(scopes).credential.create; const menu = computed(() => { if (!projectsStore.isTeamProjectFeatureEnabled) return [ { id: "workflow", title: "Workflow", route: { name: VIEWS.NEW_WORKFLOW, query: { projectId: projectsStore.personalProject?.id } } }, { id: "credential", title: "Credential", route: { name: VIEWS.CREDENTIALS, params: { projectId: projectsStore.personalProject?.id, credentialId: "create" } } }, { id: CREATE_PROJECT_ID, title: "Project", disabled: true } ]; return [ { id: WORKFLOWS_MENU_ID, title: "Workflow", disabled: sourceControlStore.preferences.branchReadOnly, ...!sourceControlStore.preferences.branchReadOnly && { submenu: [ { id: "workflow-title", title: "Create in", disabled: true }, { id: "workflow-personal", title: i18n.baseText("projects.menu.personal"), icon: "user", disabled: disabledWorkflow(projectsStore.personalProject?.scopes), route: { name: VIEWS.NEW_WORKFLOW, query: { projectId: projectsStore.personalProject?.id } } }, ...displayProjects.value.map((project) => ({ id: `workflow-${project.id}`, title: project.name, icon: isProjectIcon(project.icon) ? project.icon : DEFAULT_ICON, disabled: disabledWorkflow(project.scopes), route: { name: VIEWS.NEW_WORKFLOW, query: { projectId: project.id } } })) ] } }, { id: CREDENTIALS_MENU_ID, title: "Credential", disabled: sourceControlStore.preferences.branchReadOnly, ...!sourceControlStore.preferences.branchReadOnly && { submenu: [ { id: "credential-title", title: "Create in", disabled: true }, { id: "credential-personal", title: i18n.baseText("projects.menu.personal"), icon: "user", disabled: disabledCredential(projectsStore.personalProject?.scopes), route: { name: VIEWS.PROJECTS_CREDENTIALS, params: { projectId: projectsStore.personalProject?.id, credentialId: "create" } } }, ...displayProjects.value.map((project) => ({ id: `credential-${project.id}`, title: project.name, icon: isProjectIcon(project.icon) ? project.icon : DEFAULT_ICON, disabled: disabledCredential(project.scopes), route: { name: VIEWS.PROJECTS_CREDENTIALS, params: { projectId: project.id, credentialId: "create" } } })) ] } }, { id: CREATE_PROJECT_ID, title: "Project", disabled: !projectsStore.canCreateProjects || !projectsStore.hasPermissionToCreateProjects } ]; }); const createProject = async (uiContext) => { isCreatingProject.value = true; try { const newProject = await projectsStore.createProject({ name: i18n.baseText("projects.settings.newProjectName"), icon: { type: "icon", value: DEFAULT_ICON }, uiContext }); await router.push({ name: VIEWS.PROJECT_SETTINGS, params: { projectId: newProject.id } }); toast.showMessage({ title: i18n.baseText("projects.settings.save.successful.title", { interpolate: { projectName: newProject.name } }), type: "success" }); } catch (error) { toast.showError(error, i18n.baseText("projects.error.title")); } finally { isCreatingProject.value = false; } }; const handleSelect = (id) => { if (id !== CREATE_PROJECT_ID) return; if (projectsStore.canCreateProjects && projectsStore.hasPermissionToCreateProjects) { createProject("universal_button"); return; } usePageRedirectionHelper().goToUpgrade("rbac", "upgrade-rbac"); }; const projectsLimitReachedMessage = computed(() => { if (settingsStore.isCloudDeployment) return i18n.baseText("projects.create.limitReached.cloud", { interpolate: { planName: cloudPlanStore.currentPlanData?.displayName ?? "", limit: projectsStore.teamProjectsLimit } }); if (!projectsStore.isTeamProjectFeatureEnabled) return i18n.baseText("projects.create.limitReached.self"); if (!projectsStore.hasPermissionToCreateProjects) return i18n.baseText("projects.create.permissionDenied"); return i18n.baseText("projects.create.limitReached", { interpolate: { limit: projectsStore.teamProjectsLimit } }); }); return { menu, handleSelect, createProjectAppendSlotName: computed(() => `item.append.${CREATE_PROJECT_ID}`), createWorkflowsAppendSlotName: computed(() => `item.append.${WORKFLOWS_MENU_ID}`), createCredentialsAppendSlotName: computed(() => `item.append.${CREDENTIALS_MENU_ID}`), projectsLimitReachedMessage, hasPermissionToCreateProjects: projectsStore.hasPermissionToCreateProjects, upgradeLabel: computed(() => { if (settingsStore.isCloudDeployment) return i18n.baseText("generic.upgrade"); if (!projectsStore.isTeamProjectFeatureEnabled) return i18n.baseText("generic.enterprise"); return i18n.baseText("generic.upgrade"); }), createProject, isCreatingProject, displayProjects }; }; const EXTERNAL_LINKS = { QUICKSTART_VIDEO: "https://www.youtube.com/watch?v=4cQWJViybAQ", DOCUMENTATION: "https://docs.n8n.io?utm_source=n8n_app&utm_medium=app_sidebar", FORUM: "https://community.n8n.io?utm_source=n8n_app&utm_medium=app_sidebar", COURSES: "https://docs.n8n.io/courses/" }; var BASE_FORUM_URL = "https://github.com/n8n-io/n8n/issues/new?labels=bug-report"; var REPORT_TEMPLATE = ` <!-- Please follow the template below. Skip the questions that are not relevant to you. --> ## Describe the problem/error/question ## What is the error message (if any)? ## Please share your workflow/screenshots/recording \`\`\` (Select the nodes on your canvas and use the keyboard shortcuts CMD+C/CTRL+C and CMD+V/CTRL+V to copy and paste the workflow.) ⚠️ WARNING ⚠️ If you have sensitive data in your workflow (like API keys), please remove it before sharing. \`\`\` ## Share the output returned by the last node <!-- If you need help with data transformations, please also share your expected output. --> `; function useBugReporting() { const debugInfo = useDebugInfo(); const getReportingURL = () => { const url = new URL(BASE_FORUM_URL); const report = `${REPORT_TEMPLATE}\n${debugInfo.generateDebugInfo({ skipSensitive: true, secondaryHeader: true })}}`; url.searchParams.append("body", report); return url.toString(); }; return { getReportingURL }; } export { useDebugInfo as i, EXTERNAL_LINKS as n, useGlobalEntityCreation as r, useBugReporting as t };