n8n-editor-ui
Version:
Workflow Editor UI for n8n
2,151 lines • 83.7 kB
JavaScript
import { o as __toESM } from "./chunk-6z4oVpB-.js";
import { $t as hasOwn, C as computed, Cn as toDisplayString, D as createElementBlock, E as createCommentVNode, G as nextTick, Gt as unref, It as ref, J as onBeforeUnmount, L as h, M as createVNode, P as defineComponent, T as createBlock, Z as onMounted, _ as Fragment, _t as watch, at as renderSlot, bn as normalizeStyle, bt as withCtx, c as useCssModule, en as hyphenate, et as openBlock, it as renderList, j as createTextVNode, mt as useTemplateRef, ot as resolveComponent, p as vShow, q as onBeforeMount, tn as init_shared_esm_bundler, vn as normalizeClass, w as createBaseVNode, xt as withDirectives } from "./vue.runtime.esm-bundler-tP5dCd7J.js";
import { M as useLocalStorage, _t as useI18n, vt as I18nT, w as useDocumentVisibility } from "./_MapCache-ficiegRb.js";
import { Sn as N8nButton_default, T as N8nRadioButtons_default, b as SuggestedActions_default, c as N8nInlineTextEdit_default, h as N8nUserStack_default, ht as N8nTooltip_default, lt as N8nBadge_default, ot as N8nActionDropdown_default, wn as N8nIcon_default, x as N8nTag_default, xt as N8nIconButton_default } from "./src-DgvumQTi.js";
import "./en-CF30SCh2.js";
import { t as __vitePreload } from "./preload-helper-CR0ecmWK.js";
import { t as __plugin_vue_export_helper_default } from "./_plugin-vue_export-helper-BwBpWJRZ.js";
import { T as createEventBus, v as useRoute, y as useRouter } from "./truncate-B1HVeveJ.js";
import { $i as nodeViewEventBus, A as telemetry, Dr as useUsersStore, Ec as STORES, Ft as useEvaluationStore, M as useNDVStore, N as useWorkflowsStore, Nt as useNodeTypesStore, O as useTelemetry, Qn as ProjectTypes, Rn as useWorkflowHelpers, S as getBannerRowHeight, Vn as useTagsStore, Xn as useProjectsStore, Yn as useDocumentTitle, Zn as useSourceControlStore, _ as require_FileSaver_min, _a as PROJECT_MOVE_RESOURCE_MODAL, gs as useSettingsStore, h as useNpsSurveyStore, hi as getActivatableTriggerNodes, li as ResourceType, m as useMessage, n as useWorkflowSaving, oa as useUIStore, v as useToast, vr as hasPermission } from "./builder.store-sBTWwxRU.js";
import "./empty-BuGRxzl4.js";
import "./sanitize-html-Cc45ZKm8.js";
import "./CalendarDate-zWqgZMlk.js";
import "./path-browserify-BtCDmZ3_.js";
import { Cs as WORKFLOW_SHARE_MODAL_KEY, Go as DUPLICATE_MODAL_KEY, Lo as MAIN_HEADER_TABS, Ms as LOCAL_STORAGE_HIDE_GITHUB_STAR_BUTTON, Oc as TIME, Oo as getResourcePermissions, Qo as IMPORT_WORKFLOW_URL_MODAL_KEY, Ro as VIEWS, Ss as WORKFLOW_SETTINGS_MODAL_KEY, Uc as WORKFLOW_MENU_ACTIONS, a as IS_DRAFT_PUBLISH_ENABLED, bc as EnterpriseEditionFeature, bs as WORKFLOW_HISTORY_VERSION_UNPUBLISH, fr as TIME_SAVED_NODE_TYPE, g as EVALUATIONS_DOCS_URL, h as ERROR_WORKFLOW_DOCS_URL, hs as WORKFLOW_DESCRIPTION_MODAL_KEY, ms as WORKFLOW_ACTIVE_MODAL_KEY, o as MAX_WORKFLOW_NAME_LENGTH, rs as MODAL_CONFIRM, sr as STICKY_NODE_TYPE, v as N8N_MAIN_GITHUB_REPO_URL, w as TIME_SAVED_DOCS_URL, xs as WORKFLOW_PUBLISH_MODAL_KEY } from "./constants-BbpucWL4.js";
import "./merge-k8vSyyXH.js";
import { k as defineStore, r as useRootStore } from "./_baseOrderBy-B_6CV8x6.js";
import "./dateformat-CM9k0--B.js";
import { n as require_debounce, t as useDebounce } from "./useDebounce-Bnb8W2LR.js";
import "./assistant.store-bS9AHOoo.js";
import "./retry-dP46utx2.js";
import { t as useExecutionsStore } from "./executions.store-CXjGPxs0.js";
import { i as usePushConnectionStore } from "./useRunWorkflow-BQJbxiOd.js";
import "./usePinnedData-CL2qWhwp.js";
import "./nodeCreator.store-FJrC_Pu6.js";
import "./nodeIcon-DI2Acud1.js";
import "./useCanvasOperations-CAHezq7d.js";
import { t as useFoldersStore } from "./folders.store-BjAwtSH4.js";
import "./NodeIcon-BVhrZNYI.js";
import "./KeyboardShortcutTooltip-B4E72qs1.js";
import "./versions.store-BwdT833-.js";
import { t as usePageRedirectionHelper } from "./usePageRedirectionHelper-fGNB6ZKK.js";
import { t as useBannersStore } from "./banners.store-C9sgKjcT.js";
import "./dataTable.store-B4A4ieSD.js";
import "./ProjectIcon-A2WPT9dM.js";
import "./schemaPreview.store-BFprwUEe.js";
import { t as EnterpriseEdition_ee_default } from "./EnterpriseEdition.ee-UkQT-fFo.js";
import { t as TimeAgo_default } from "./TimeAgo-CWvYoS88.js";
import "./roles.store-DcLW4g80.js";
import { t as SaveButton_default } from "./SaveButton-DJxU0D7G.js";
import { t as router_default } from "./router-BuH0cEsQ.js";
import "./TagsDropdown-LwHy1RX4.js";
import { t as WorkflowTagsDropdown_default } from "./WorkflowTagsDropdown-DEysJMrX.js";
import "./sso.store-Ba2eVE3K.js";
import "./insights.store-BBo4cRMD.js";
import "./insights.constants-C8GITe6H.js";
import "./insights.utils-B7OXsqrQ.js";
import { a as MCP_DOCS_PAGE_URL, s as MCP_SETTINGS_VIEW } from "./mcp.constants-DQPD3Ntp.js";
import "./constants-DLTaaipl.js";
import "./useRecentResources-BAGmp8Wq.js";
import { t as useMcp } from "./useMcp-DzTrtFvb.js";
import { t as useWorkflowActivate } from "./useWorkflowActivate-BhSS_xpK.js";
import "./WorkflowActivationErrorMessage-v37Lr_Ej.js";
import { i as getLastPublishedVersion, r as generateVersionName } from "./utils-C2GPI2gK.js";
import "./ProjectBreadcrumb-C7tWU5jd.js";
import { n as FolderBreadcrumbs_default, t as WorkflowActivator_default } from "./WorkflowActivator-D3SAdw0t.js";
import { t as PushConnectionTracker_default } from "./PushConnectionTracker-TnOaTBdr.js";
import { t as useWorkflowSettingsCache } from "./useWorkflowsCache-K84an3SQ.js";
import { t as useBeforeUnload } from "./useBeforeUnload-Dmmheunz.js";
import { t as usePushConnection } from "./usePushConnection-DTJahVkB.js";
import "./aiTemplatesStarterCollection.store-DkHa_nSs.js";
import "./readyToRun.store-CsvyRJzn.js";
var TabBar_vue_vue_type_script_setup_true_lang_default = /* @__PURE__ */ defineComponent({
__name: "TabBar",
props: {
items: {},
modelValue: { default: MAIN_HEADER_TABS.WORKFLOW }
},
emits: ["update:modelValue"],
setup(__props, { emit: __emit }) {
const emit = __emit;
function onUpdateModelValue(tab, event) {
emit("update:modelValue", tab, event);
}
return (_ctx, _cache) => {
return _ctx.items ? (openBlock(), createElementBlock("div", {
key: 0,
class: normalizeClass({
[_ctx.$style.container]: true,
["tab-bar-container"]: true
})
}, [createVNode(unref(N8nRadioButtons_default), {
"model-value": _ctx.modelValue,
options: _ctx.items,
"onUpdate:modelValue": onUpdateModelValue
}, null, 8, ["model-value", "options"])], 2)) : createCommentVNode("", true);
};
}
});
var TabBar_vue_vue_type_style_index_0_lang_module_default = { container: "_container_cznfu_123" };
var TabBar_default = /* @__PURE__ */ __plugin_vue_export_helper_default(TabBar_vue_vue_type_script_setup_true_lang_default, [["__cssModules", { "$style": TabBar_vue_vue_type_style_index_0_lang_module_default }]]);
var BreakpointsObserver_default = /* @__PURE__ */ defineComponent({
__name: "BreakpointsObserver",
props: {
valueXS: {},
valueXL: {},
valueLG: {},
valueMD: {},
valueSM: {},
valueDefault: {}
},
setup(__props) {
const props = __props;
const { callDebounced } = useDebounce();
const bannersStore = useBannersStore();
const width = ref(window.innerWidth);
const bp = computed(() => {
if (width.value < 768) return "XS";
if (width.value >= 1920) return "XL";
if (width.value >= 1200) return "LG";
if (width.value >= 992) return "MD";
return "SM";
});
const value = computed(() => {
if (props.valueXS && width.value < 768) return props.valueXS;
if (props.valueXL && width.value >= 1920) return props.valueXL;
if (props.valueLG && width.value >= 1200) return props.valueLG;
if (props.valueMD && width.value >= 992) return props.valueMD;
if (props.valueSM) return props.valueSM;
return props.valueDefault;
});
const onResize = () => {
callDebounced(onResizeEnd, { debounceTime: 50 });
};
const onResizeEnd = async () => {
width.value = window.innerWidth;
await nextTick();
const bannerHeight = await getBannerRowHeight();
bannersStore.updateBannersHeight(bannerHeight);
};
onMounted(() => {
window.addEventListener("resize", onResize);
});
onBeforeUnmount(() => {
window.removeEventListener("resize", onResize);
});
return (_ctx, _cache) => {
return openBlock(), createElementBlock("span", null, [renderSlot(_ctx.$slots, "default", {
bp: bp.value,
value: value.value
})]);
};
}
});
var WorkflowProductionChecklist_default = /* @__PURE__ */ defineComponent({
__name: "WorkflowProductionChecklist",
props: { workflow: {} },
setup(__props) {
const props = __props;
const i18n = useI18n();
const router = useRouter();
const evaluationStore = useEvaluationStore();
const nodeTypesStore = useNodeTypesStore();
const workflowsCache = useWorkflowSettingsCache();
const uiStore = useUIStore();
const message = useMessage();
const telemetry$1 = useTelemetry();
const sourceControlStore = useSourceControlStore();
const settingsStore = useSettingsStore();
const { isEligibleForMcpAccess } = useMcp();
const usersStore = useUsersStore();
const isPopoverOpen = ref(false);
const cachedSettings = ref(null);
const hasAINode = computed(() => {
return props.workflow.nodes.some((node) => {
return nodeTypesStore.getNodeType(node.type, node.typeVersion)?.codex?.categories?.includes("AI");
});
});
const hasEvaluationSetOutputsNode = computed(() => {
return evaluationStore.evaluationSetOutputsNodeExist;
});
const hasErrorWorkflow = computed(() => {
return !!props.workflow.settings?.errorWorkflow;
});
const hasSavedTimeNodes = computed(() => {
if (!props.workflow?.nodes) return false;
return props.workflow.nodes.some((node) => node.type === "n8n-nodes-base.timeSaved" && node.disabled !== true);
});
const hasTimeSaved = computed(() => {
return props.workflow.settings?.timeSavedPerExecution !== void 0 || hasSavedTimeNodes.value;
});
const isActivationModalOpen = computed(() => {
return uiStore.isModalActiveById[WORKFLOW_ACTIVE_MODAL_KEY];
});
const isProtectedEnvironment = computed(() => {
return sourceControlStore.preferences.branchReadOnly;
});
const isOwner = computed(() => usersStore.isInstanceOwner);
const isAdmin = computed(() => usersStore.isAdmin);
const isMcpModuleEnabled = computed(() => {
return settingsStore.isModuleActive("mcp");
});
const isMcpAccessEnabled = computed(() => {
return settingsStore.moduleSettings.mcp?.mcpAccessEnabled ?? false;
});
const isWorkflowEligibleForMcpAccess = computed(() => {
return isEligibleForMcpAccess(props.workflow);
});
const canToggleInstanceMCPAccess = computed(() => isOwner.value || isAdmin.value);
const availableActions = computed(() => {
if (props.workflow.activeVersionId === null || workflowsCache.isCacheLoading.value) return [];
const actions = [];
const suggestedActionSettings = cachedSettings.value?.suggestedActions ?? {};
if (!suggestedActionSettings.errorWorkflow?.ignored) actions.push({
id: "errorWorkflow",
title: i18n.baseText("workflowProductionChecklist.errorWorkflow.title"),
description: i18n.baseText("workflowProductionChecklist.errorWorkflow.description"),
moreInfoLink: ERROR_WORKFLOW_DOCS_URL,
completed: hasErrorWorkflow.value
});
if (hasAINode.value && evaluationStore.isEvaluationEnabled && !suggestedActionSettings.evaluations?.ignored) actions.push({
id: "evaluations",
title: i18n.baseText("workflowProductionChecklist.evaluations.title"),
description: i18n.baseText("workflowProductionChecklist.evaluations.description"),
moreInfoLink: EVALUATIONS_DOCS_URL,
completed: hasEvaluationSetOutputsNode.value
});
if (!suggestedActionSettings.timeSaved?.ignored) actions.push({
id: "timeSaved",
title: i18n.baseText("workflowProductionChecklist.timeSaved.title"),
description: i18n.baseText("workflowProductionChecklist.timeSaved.description"),
moreInfoLink: TIME_SAVED_DOCS_URL,
completed: hasTimeSaved.value
});
const mcpAction = getMcpAction();
if (mcpAction) actions.push(mcpAction);
return actions;
function getMcpAction() {
if (!isMcpModuleEnabled.value || !isWorkflowEligibleForMcpAccess.value) return null;
const baseAction = {
title: i18n.baseText("mcp.productionChecklist.title"),
moreInfoLink: MCP_DOCS_PAGE_URL
};
if (!isMcpAccessEnabled.value) {
if (!canToggleInstanceMCPAccess.value || suggestedActionSettings["instance-mcp-access"]?.ignored) return null;
return {
...baseAction,
id: "instance-mcp-access",
description: i18n.baseText("mcp.productionChecklist.instance.description"),
completed: false
};
}
if (suggestedActionSettings["workflow-mcp-access"]?.ignored) return null;
return {
...baseAction,
id: "workflow-mcp-access",
description: i18n.baseText("mcp.productionChecklist.workflow.description"),
completed: props.workflow.settings?.availableInMCP ?? false
};
}
});
async function loadWorkflowSettings() {
if (props.workflow.id) cachedSettings.value = await workflowsCache.getMergedWorkflowSettings(props.workflow.id);
}
async function handleActionClick(actionId) {
switch (actionId) {
case "evaluations":
await router.push({
name: VIEWS.EVALUATION_EDIT,
params: { name: props.workflow.id }
});
break;
case "errorWorkflow":
case "timeSaved":
case "workflow-mcp-access":
uiStore.openModal(WORKFLOW_SETTINGS_MODAL_KEY);
break;
case "instance-mcp-access":
await router.push({ name: MCP_SETTINGS_VIEW });
break;
default: break;
}
isPopoverOpen.value = false;
}
function isValidAction(action) {
return [
"evaluations",
"errorWorkflow",
"timeSaved",
"workflow-mcp-access",
"instance-mcp-access"
].includes(action);
}
async function handleIgnoreClick(actionId) {
if (!isValidAction(actionId)) return;
await workflowsCache.ignoreSuggestedAction(props.workflow.id, actionId);
await loadWorkflowSettings();
telemetry$1.track("user clicked ignore suggested action", { actionId });
}
async function handleIgnoreAll() {
if (await message.confirm(i18n.baseText("workflowProductionChecklist.ignoreAllConfirmation.description"), i18n.baseText("workflowProductionChecklist.ignoreAllConfirmation.title"), { confirmButtonText: i18n.baseText("workflowProductionChecklist.ignoreAllConfirmation.confirm") }) === "confirm") {
await workflowsCache.ignoreAllSuggestedActionsForAllWorkflows(availableActions.value.map((action) => action.id));
await loadWorkflowSettings();
telemetry$1.track("user clicked ignore suggested actions for all workflows");
}
}
function openSuggestedActions() {
isPopoverOpen.value = true;
}
function onPopoverOpened() {
telemetry$1.track("user opened suggested actions checklist");
}
function handlePopoverOpenChange(open) {
if (open) {
isPopoverOpen.value = true;
onPopoverOpened();
} else if (!isActivationModalOpen.value) isPopoverOpen.value = false;
}
watch(() => !!props.workflow.activeVersionId, async (isActive, wasActive) => {
if (isActive && !wasActive) {
if (!cachedSettings.value?.firstActivatedAt) setTimeout(() => {
openSuggestedActions();
}, 0);
await workflowsCache.updateFirstActivatedAt(props.workflow.id);
}
});
onMounted(async () => {
await loadWorkflowSettings();
});
return (_ctx, _cache) => {
return availableActions.value.length > 0 ? (openBlock(), createBlock(unref(SuggestedActions_default), {
key: 0,
open: isPopoverOpen.value,
title: unref(i18n).baseText("workflowProductionChecklist.title"),
actions: availableActions.value,
"ignore-all-label": unref(i18n).baseText("workflowProductionChecklist.turnOffWorkflowSuggestions"),
notice: isProtectedEnvironment.value ? unref(i18n).baseText("workflowProductionChecklist.readOnlyNotice") : "",
"popover-alignment": "end",
onActionClick: handleActionClick,
onIgnoreClick: handleIgnoreClick,
onIgnoreAll: handleIgnoreAll,
"onUpdate:open": handlePopoverOpenChange
}, null, 8, [
"open",
"title",
"actions",
"ignore-all-label",
"notice"
])) : createCommentVNode("", true);
};
}
});
var IntersectionObserver_default = /* @__PURE__ */ defineComponent({
__name: "IntersectionObserver",
props: {
threshold: { default: 0 },
enabled: {
type: Boolean,
default: false
},
eventBus: {}
},
emits: ["observed"],
setup(__props, { emit: __emit }) {
const props = __props;
const emit = __emit;
const observer = ref(null);
const root = ref(null);
onBeforeUnmount(() => {
if (props.enabled && observer.value) observer.value.disconnect();
});
onMounted(() => {
if (!props.enabled) return;
const options = {
root: root.value,
rootMargin: "0px",
threshold: props.threshold
};
const intersectionObserver = new IntersectionObserver((entries) => {
entries.forEach(({ target, isIntersecting }) => {
emit("observed", {
el: target,
isIntersecting
});
});
}, options);
observer.value = intersectionObserver;
props.eventBus.on("observe", (observed) => {
if (observed) intersectionObserver.observe(observed);
});
props.eventBus.on("unobserve", (observed) => {
intersectionObserver.unobserve(observed);
});
});
return (_ctx, _cache) => {
return openBlock(), createElementBlock("div", {
ref_key: "root",
ref: root
}, [renderSlot(_ctx.$slots, "default")], 512);
};
}
});
var IntersectionObserved_default = /* @__PURE__ */ defineComponent({
__name: "IntersectionObserved",
props: {
enabled: {
type: Boolean,
default: false
},
eventBus: {}
},
setup(__props) {
const props = __props;
const observed = ref(null);
onMounted(async () => {
if (!props.enabled) return;
await nextTick();
props.eventBus.emit("observe", observed.value);
});
onBeforeUnmount(() => {
if (props.enabled) props.eventBus.emit("unobserve", observed.value);
});
return (_ctx, _cache) => {
return openBlock(), createElementBlock("span", {
ref_key: "observed",
ref: observed
}, [renderSlot(_ctx.$slots, "default")], 512);
};
}
});
var import_debounce = /* @__PURE__ */ __toESM(require_debounce());
var _hoisted_1$2 = { class: "tags" };
var _hoisted_2$2 = ["onClick"];
var TagsContainer_default = /* @__PURE__ */ __plugin_vue_export_helper_default(/* @__PURE__ */ defineComponent({
__name: "TagsContainer",
props: {
tagIds: {},
tagsById: {},
limit: { default: 20 },
clickable: {
type: Boolean,
default: false
},
responsive: {
type: Boolean,
default: false
}
},
emits: ["click"],
setup(__props, { emit: __emit }) {
const props = __props;
const emit = __emit;
const maxWidth = ref(320);
const intersectionEventBus = createEventBus();
const visibility = ref({});
const tagsContainer = ref();
const style = computed(() => ({ "max-width": `${maxWidth.value}px` }));
const tags = computed(() => {
const allTags = props.tagIds.map((tagId) => props.tagsById[tagId]).filter(Boolean);
let toDisplay = props.limit ? allTags.slice(0, props.limit) : allTags;
toDisplay = toDisplay.map((tag) => ({
...tag,
hidden: props.responsive && !visibility.value[tag.id]
}));
let visibleCount = toDisplay.length;
if (props.responsive) visibleCount = Object.values(visibility.value).reduce((accu, val) => val ? accu + 1 : accu, 0);
if (visibleCount < allTags.length) {
const hidden = allTags.slice(visibleCount);
const hiddenTitle = hidden.reduce((accu, tag) => accu ? `${accu}, ${tag.name}` : tag.name, "");
const countTag = {
id: "count",
name: `+${hidden.length}`,
title: hiddenTitle,
isCount: true
};
toDisplay.splice(visibleCount, 0, countTag);
}
return toDisplay;
});
const setMaxWidth = () => {
const parent = (tagsContainer.value?.$el)?.parentNode;
if (parent) {
maxWidth.value = 0;
nextTick(() => {
maxWidth.value = parent.clientWidth;
});
}
};
const debouncedSetMaxWidth = (0, import_debounce.default)(setMaxWidth, 100);
const onObserved = ({ el, isIntersecting }) => {
if (el.dataset.id) visibility.value = {
...visibility.value,
[el.dataset.id]: isIntersecting
};
};
const onClick = (e, tag) => {
if (props.clickable) e.stopPropagation();
if (!tag.hidden) emit("click", tag.id);
};
onMounted(() => {
setMaxWidth();
window.addEventListener("resize", debouncedSetMaxWidth);
});
onBeforeUnmount(() => {
window.removeEventListener("resize", debouncedSetMaxWidth);
});
return (_ctx, _cache) => {
return openBlock(), createBlock(IntersectionObserver_default, {
ref_key: "tagsContainer",
ref: tagsContainer,
threshold: 1,
class: "tags-container",
style: normalizeStyle(style.value),
enabled: _ctx.responsive,
"event-bus": unref(intersectionEventBus),
onObserved
}, {
default: withCtx(() => [createBaseVNode("span", _hoisted_1$2, [(openBlock(true), createElementBlock(Fragment, null, renderList(tags.value, (tag) => {
return openBlock(), createElementBlock("span", {
key: tag.id,
class: normalizeClass({ clickable: !tag.hidden }),
onClick: (e) => onClick(e, tag)
}, [tag.isCount ? (openBlock(), createBlock(unref(N8nTag_default), {
key: 0,
title: tag.title,
text: tag.name,
clickable: false,
class: "count-container"
}, null, 8, ["title", "text"])) : (openBlock(), createBlock(IntersectionObserved_default, {
key: 1,
class: normalizeClass({ hideTag: tag.hidden }),
"data-id": tag.id,
enabled: _ctx.responsive,
"event-bus": unref(intersectionEventBus)
}, {
default: withCtx(() => [createVNode(unref(N8nTag_default), {
title: tag.name,
text: tag.name,
clickable: _ctx.clickable
}, null, 8, [
"title",
"text",
"clickable"
])]),
_: 2
}, 1032, [
"class",
"data-id",
"enabled",
"event-bus"
]))], 10, _hoisted_2$2);
}), 128))])]),
_: 1
}, 8, [
"style",
"enabled",
"event-bus"
]);
};
}
}), [["__scopeId", "data-v-2e7ba899"]]);
var WorkflowTagsContainer_default = /* @__PURE__ */ defineComponent({
__name: "WorkflowTagsContainer",
props: {
tagIds: {},
limit: {},
clickable: { type: Boolean },
responsive: { type: Boolean }
},
emits: ["click"],
setup(__props, { emit: __emit }) {
const emit = __emit;
const annotationTagsStore = useTagsStore();
const tagsById = computed(() => annotationTagsStore.tagsById);
function onClick(tagId) {
emit("click", tagId);
}
return (_ctx, _cache) => {
return openBlock(), createBlock(TagsContainer_default, {
"tag-ids": _ctx.tagIds,
"tags-by-id": tagsById.value,
limit: _ctx.limit,
clickable: _ctx.clickable,
responsive: _ctx.responsive,
onClick
}, null, 8, [
"tag-ids",
"tags-by-id",
"limit",
"clickable",
"responsive"
]);
};
}
});
var HEARTBEAT_INTERVAL = 5 * TIME.MINUTE;
const useCollaborationStore = defineStore(STORES.COLLABORATION, () => {
const pushStore = usePushConnectionStore();
const workflowsStore = useWorkflowsStore();
const usersStore = useUsersStore();
const uiStore = useUIStore();
const { addBeforeUnloadEventBindings, removeBeforeUnloadEventBindings, addBeforeUnloadHandler } = useBeforeUnload({ route: useRoute() });
const unloadTimeout = ref(null);
addBeforeUnloadHandler(() => {
notifyWorkflowClosed();
if (uiStore.stateIsDirty) unloadTimeout.value = setTimeout(() => notifyWorkflowOpened, 5 * TIME.SECOND);
});
const collaborators = ref([]);
const heartbeatTimer = ref(null);
const startHeartbeat = () => {
stopHeartbeat();
heartbeatTimer.value = window.setInterval(notifyWorkflowOpened, HEARTBEAT_INTERVAL);
};
const stopHeartbeat = () => {
if (heartbeatTimer.value !== null) {
clearInterval(heartbeatTimer.value);
heartbeatTimer.value = null;
}
};
const pushStoreEventListenerRemovalFn = ref(null);
function initialize() {
if (pushStoreEventListenerRemovalFn.value) return;
pushStoreEventListenerRemovalFn.value = pushStore.addEventListener((event) => {
if (event.type === "collaboratorsChanged" && event.data.workflowId === workflowsStore.workflowId) collaborators.value = event.data.collaborators;
});
addBeforeUnloadEventBindings();
notifyWorkflowOpened();
startHeartbeat();
}
function terminate() {
if (typeof pushStoreEventListenerRemovalFn.value === "function") {
pushStoreEventListenerRemovalFn.value();
pushStoreEventListenerRemovalFn.value = null;
}
notifyWorkflowClosed();
stopHeartbeat();
pushStore.clearQueue();
removeBeforeUnloadEventBindings();
if (unloadTimeout.value) clearTimeout(unloadTimeout.value);
}
function notifyWorkflowOpened() {
if (!workflowsStore.isWorkflowSaved[workflowsStore.workflowId]) return;
pushStore.send({
type: "workflowOpened",
workflowId: workflowsStore.workflowId
});
}
function notifyWorkflowClosed() {
if (!workflowsStore.isWorkflowSaved[workflowsStore.workflowId]) return;
pushStore.send({
type: "workflowClosed",
workflowId: workflowsStore.workflowId
});
collaborators.value = collaborators.value.filter(({ user }) => user.id !== usersStore.currentUserId);
}
return {
collaborators,
initialize,
terminate,
startHeartbeat,
stopHeartbeat
};
});
var CollaborationPane_vue_vue_type_script_setup_true_lang_default = /* @__PURE__ */ defineComponent({
__name: "CollaborationPane",
setup(__props) {
const collaborationStore = useCollaborationStore();
const usersStore = useUsersStore();
watch(useDocumentVisibility(), (visibilityState) => {
if (visibilityState === "hidden") collaborationStore.stopHeartbeat();
else collaborationStore.startHeartbeat();
});
const showUserStack = computed(() => collaborationStore.collaborators.length > 1);
const collaboratorsSorted = computed(() => {
const users = collaborationStore.collaborators.map(({ user }) => user);
const index = users.findIndex((user) => user.id === usersStore.currentUser?.id);
if (index < 1) return { defaultGroup: users };
const [currentUser] = users.splice(index, 1);
return { defaultGroup: [currentUser, ...users] };
});
const currentUserEmail = computed(() => usersStore.currentUser?.email);
onMounted(() => {
collaborationStore.initialize();
});
onBeforeUnmount(() => {
collaborationStore.terminate();
});
return (_ctx, _cache) => {
return showUserStack.value ? (openBlock(), createElementBlock("div", {
key: 0,
class: normalizeClass(`collaboration-pane-container ${_ctx.$style.container}`),
"data-test-id": "collaboration-pane"
}, [createVNode(unref(N8nUserStack_default), {
users: collaboratorsSorted.value,
"current-user-email": currentUserEmail.value
}, null, 8, ["users", "current-user-email"])], 2)) : createCommentVNode("", true);
};
}
});
var CollaborationPane_vue_vue_type_style_index_0_lang_module_default = { container: "_container_dsr9f_123" };
var CollaborationPane_default = /* @__PURE__ */ __plugin_vue_export_helper_default(CollaborationPane_vue_vue_type_script_setup_true_lang_default, [["__cssModules", { "$style": CollaborationPane_vue_vue_type_style_index_0_lang_module_default }]]);
var _hoisted_1$1 = { key: 0 };
var _hoisted_2$1 = { key: 1 };
var WorkflowHistoryButton_default = /* @__PURE__ */ defineComponent({
__name: "WorkflowHistoryButton",
props: {
workflowId: {},
isNewWorkflow: { type: Boolean }
},
setup(__props) {
const locale = useI18n();
const props = __props;
const workflowHistoryRoute = computed(() => ({
name: VIEWS.WORKFLOW_HISTORY,
params: { workflowId: props.workflowId }
}));
return (_ctx, _cache) => {
const _component_RouterLink = resolveComponent("RouterLink");
return _ctx.workflowId ? (openBlock(), createBlock(unref(N8nTooltip_default), {
key: 0,
placement: "bottom"
}, {
content: withCtx(() => [_ctx.isNewWorkflow ? (openBlock(), createElementBlock("span", _hoisted_1$1, toDisplayString(unref(locale).baseText("workflowHistory.button.tooltip.empty")), 1)) : (openBlock(), createElementBlock("span", _hoisted_2$1, toDisplayString(unref(locale).baseText("workflowHistory.button.tooltip")), 1))]),
default: withCtx(() => [createVNode(_component_RouterLink, { to: workflowHistoryRoute.value }, {
default: withCtx(() => [createVNode(unref(N8nIconButton_default), {
disabled: _ctx.isNewWorkflow,
"data-test-id": "workflow-history-button",
type: "highlight",
icon: "history",
size: "medium"
}, null, 8, ["disabled"])]),
_: 1
}, 8, ["to"])]),
_: 1
})) : createCommentVNode("", true);
};
}
});
var INVALID_CHARS_REGEX = /[<>:"/\\|?*\u0000-\u001F\u007F-\u009F]/g;
var ZERO_WIDTH_CHARS_REGEX = /[\u200B-\u200D\u2060\uFEFF]/g;
var UNICODE_SPACES_REGEX = /[\u00A0\u2000-\u200A]/g;
var LEADING_TRAILING_DOTS_SPACES_REGEX = /^[\s.]+|[\s.]+$/g;
var WINDOWS_RESERVED_NAMES = new Set([
"CON",
"PRN",
"AUX",
"NUL",
"COM1",
"COM2",
"COM3",
"COM4",
"COM5",
"COM6",
"COM7",
"COM8",
"COM9",
"LPT1",
"LPT2",
"LPT3",
"LPT4",
"LPT5",
"LPT6",
"LPT7",
"LPT8",
"LPT9"
]);
var DEFAULT_FALLBACK_NAME = "untitled";
var MAX_FILENAME_LENGTH = 200;
var sanitizeFilename = (filename, maxLength = MAX_FILENAME_LENGTH) => {
if (!filename) return DEFAULT_FALLBACK_NAME;
let baseName = filename.trim().replace(INVALID_CHARS_REGEX, "_").replace(ZERO_WIDTH_CHARS_REGEX, "").replace(UNICODE_SPACES_REGEX, " ").replace(LEADING_TRAILING_DOTS_SPACES_REGEX, "");
if (!baseName) baseName = DEFAULT_FALLBACK_NAME;
if (WINDOWS_RESERVED_NAMES.has(baseName.toUpperCase())) baseName = `_${baseName}`;
if (baseName.length > maxLength) baseName = baseName.slice(0, maxLength);
return baseName;
};
var import_FileSaver_min = /* @__PURE__ */ __toESM(require_FileSaver_min());
const getWorkflowId = (propId, routeName) => {
return propId ?? (typeof routeName === "string" ? routeName : void 0);
};
var ActionsDropdownMenu_vue_vue_type_script_setup_true_lang_default = /* @__PURE__ */ defineComponent({
__name: "ActionsDropdownMenu",
props: {
workflowPermissions: {},
isNewWorkflow: { type: Boolean },
readOnly: { type: Boolean },
isArchived: { type: Boolean },
id: {},
name: {},
tags: {},
currentFolder: {},
meta: {}
},
emits: ["workflow:saved"],
setup(__props, { expose: __expose, emit: __emit }) {
const props = __props;
const emit = __emit;
const importFileRef = ref();
const toast = useToast();
const locale = useI18n();
const route = useRoute();
const projectsStore = useProjectsStore();
const sourceControlStore = useSourceControlStore();
const workflowsStore = useWorkflowsStore();
const uiStore = useUIStore();
const $style = useCssModule();
const rootStore = useRootStore();
const tagsStore = useTagsStore();
const settingsStore = useSettingsStore();
const usersStore = useUsersStore();
const workflowHelpers = useWorkflowHelpers();
const workflowActivate = useWorkflowActivate();
const changeOwnerEventBus = createEventBus();
const workflowTelemetry = useTelemetry();
const onWorkflowPage = computed(() => {
return route.meta && (route.meta.nodeView || route.meta.keepWorkflowAlive === true);
});
const onExecutionsTab = computed(() => {
return [
VIEWS.EXECUTION_HOME.toString(),
VIEWS.WORKFLOW_EXECUTIONS.toString(),
VIEWS.EXECUTION_PREVIEW
].includes(route.name || "");
});
const activeVersion = computed(() => workflowsStore.workflow.activeVersion);
const isSharingEnabled = computed(() => settingsStore.isEnterpriseFeatureEnabled[EnterpriseEditionFeature.Sharing]);
function handleFileImport() {
const inputRef = importFileRef.value;
if (inputRef?.files && inputRef.files.length !== 0) {
const reader = new FileReader();
reader.onload = () => {
let workflowData;
try {
workflowData = JSON.parse(reader.result);
} catch (error) {
toast.showMessage({
title: locale.baseText("mainSidebar.showMessage.handleFileImport.title"),
message: locale.baseText("mainSidebar.showMessage.handleFileImport.message"),
type: "error"
});
return;
} finally {
reader.onload = null;
inputRef.value = "";
}
nodeViewEventBus.emit("importWorkflowData", { data: workflowData });
};
reader.readAsText(inputRef.files[0]);
}
}
const workflowMenuItems = computed(() => {
const actions = [{
id: WORKFLOW_MENU_ACTIONS.DOWNLOAD,
label: locale.baseText("menuActions.download"),
disabled: !onWorkflowPage.value
}];
if (isSharingEnabled.value) actions.push({
id: WORKFLOW_MENU_ACTIONS.SHARE,
label: locale.baseText("workflowDetails.share"),
disabled: !onWorkflowPage.value
});
if (props.workflowPermissions.move && projectsStore.isTeamProjectFeatureEnabled) actions.push({
id: WORKFLOW_MENU_ACTIONS.CHANGE_OWNER,
label: locale.baseText("workflows.item.changeOwner"),
disabled: props.isNewWorkflow
});
if (!props.readOnly && !props.isArchived) actions.push({
id: WORKFLOW_MENU_ACTIONS.RENAME,
label: locale.baseText("generic.rename"),
disabled: !onWorkflowPage.value || props.workflowPermissions.update !== true
});
if (props.workflowPermissions.update === true && !props.readOnly && !props.isArchived || props.isNewWorkflow) {
actions.unshift({
id: WORKFLOW_MENU_ACTIONS.DUPLICATE,
label: locale.baseText("menuActions.duplicate"),
disabled: !onWorkflowPage.value || !props.id
});
actions.unshift({
id: WORKFLOW_MENU_ACTIONS.EDIT_DESCRIPTION,
label: locale.baseText("menuActions.editDescription"),
disabled: !onWorkflowPage.value || !props.id
});
actions.push({
id: WORKFLOW_MENU_ACTIONS.IMPORT_FROM_URL,
label: locale.baseText("menuActions.importFromUrl"),
disabled: !onWorkflowPage.value || onExecutionsTab.value
}, {
id: WORKFLOW_MENU_ACTIONS.IMPORT_FROM_FILE,
label: locale.baseText("menuActions.importFromFile"),
disabled: !onWorkflowPage.value || onExecutionsTab.value
});
}
if (hasPermission(["rbac"], { rbac: { scope: "sourceControl:push" } })) actions.push({
id: WORKFLOW_MENU_ACTIONS.PUSH,
label: locale.baseText("menuActions.push"),
disabled: !sourceControlStore.isEnterpriseSourceControlEnabled || !onWorkflowPage.value || onExecutionsTab.value || sourceControlStore.preferences.branchReadOnly
});
actions.push({
id: WORKFLOW_MENU_ACTIONS.SETTINGS,
label: locale.baseText("generic.settings"),
disabled: !onWorkflowPage.value || props.isNewWorkflow
});
if (activeVersion.value && props.workflowPermissions.publish && !props.readOnly) actions.push({
id: WORKFLOW_MENU_ACTIONS.UNPUBLISH,
label: locale.baseText("menuActions.unpublish"),
disabled: !onWorkflowPage.value
});
if (props.workflowPermissions.delete === true && !props.readOnly || props.isNewWorkflow) if (props.isArchived) {
actions.push({
id: WORKFLOW_MENU_ACTIONS.UNARCHIVE,
label: locale.baseText("menuActions.unarchive"),
disabled: !onWorkflowPage.value || props.isNewWorkflow
});
actions.push({
id: WORKFLOW_MENU_ACTIONS.DELETE,
label: locale.baseText("menuActions.delete"),
disabled: !onWorkflowPage.value || props.isNewWorkflow,
customClass: $style.deleteItem,
divided: true
});
} else actions.push({
id: WORKFLOW_MENU_ACTIONS.ARCHIVE,
label: locale.baseText("menuActions.archive"),
disabled: !onWorkflowPage.value || props.isNewWorkflow,
customClass: $style.deleteItem,
divided: true
});
return actions;
});
function onUnpublishWorkflow() {
const workflowId = getWorkflowId(props.id, route.params.name);
if (!workflowId || !activeVersion.value) {
toast.showMessage({
title: locale.baseText("workflowHistory.action.unpublish.notAvailable"),
type: "warning"
});
return;
}
const unpublishEventBus = createEventBus();
unpublishEventBus.once("unpublish", async () => {
const success = await workflowActivate.unpublishWorkflowFromHistory(workflowId);
uiStore.closeModal(WORKFLOW_HISTORY_VERSION_UNPUBLISH);
if (success) toast.showMessage({
title: locale.baseText("workflowHistory.action.unpublish.success.title"),
type: "success"
});
});
uiStore.openModalWithData({
name: WORKFLOW_HISTORY_VERSION_UNPUBLISH,
data: {
versionName: activeVersion.value.name,
eventBus: unpublishEventBus
}
});
}
async function onWorkflowMenuSelect(action) {
switch (action) {
case WORKFLOW_MENU_ACTIONS.EDIT_DESCRIPTION: {
const workflowId = getWorkflowId(props.id, route.params.name);
if (!workflowId) return;
const workflowDescription = workflowsStore.getWorkflowById(workflowId).description;
uiStore.openModalWithData({
name: WORKFLOW_DESCRIPTION_MODAL_KEY,
data: {
workflowId,
workflowDescription
}
});
break;
}
case WORKFLOW_MENU_ACTIONS.DUPLICATE:
uiStore.openModalWithData({
name: DUPLICATE_MODAL_KEY,
data: {
id: props.id,
name: props.name,
tags: props.tags,
parentFolderId: props.currentFolder?.id
}
});
break;
case WORKFLOW_MENU_ACTIONS.RENAME:
nodeViewEventBus.emit("renameWorkflow");
break;
case WORKFLOW_MENU_ACTIONS.DOWNLOAD: {
const workflowData = await workflowHelpers.getWorkflowDataToSave();
const { tags,...data } = workflowData;
const exportData = {
...data,
meta: {
...props.meta,
instanceId: rootStore.instanceId
},
tags: (tags ?? []).map((tagId) => {
const { usageCount,...tag } = tagsStore.tagsById[tagId];
return tag;
})
};
const blob = new Blob([JSON.stringify(exportData, null, 2)], { type: "application/json;charset=utf-8" });
let name = props.name || "unsaved_workflow";
name = sanitizeFilename(name);
telemetry.track("User exported workflow", { workflow_id: workflowData.id });
(0, import_FileSaver_min.default)(blob, name + ".json");
break;
}
case WORKFLOW_MENU_ACTIONS.IMPORT_FROM_URL:
uiStore.openModal(IMPORT_WORKFLOW_URL_MODAL_KEY);
break;
case WORKFLOW_MENU_ACTIONS.IMPORT_FROM_FILE:
nodeViewEventBus.emit("importWorkflowFromFile");
break;
case WORKFLOW_MENU_ACTIONS.PUSH:
try {
emit("workflow:saved");
router_default.push({ query: {
...route.query,
sourceControl: "push"
} });
} catch (error) {
switch (error.message) {
case "source_control_not_connected":
toast.showError({
...error,
message: ""
}, locale.baseText("settings.sourceControl.error.not.connected.title"), locale.baseText("settings.sourceControl.error.not.connected.message"));
break;
default: toast.showError(error, locale.baseText("error"));
}
}
break;
case WORKFLOW_MENU_ACTIONS.SETTINGS:
uiStore.openModal(WORKFLOW_SETTINGS_MODAL_KEY);
break;
case WORKFLOW_MENU_ACTIONS.SHARE:
uiStore.openModalWithData({
name: WORKFLOW_SHARE_MODAL_KEY,
data: { id: props.id }
});
workflowTelemetry.track("User opened sharing modal", {
workflow_id: props.id,
user_id_sharer: usersStore.currentUser?.id,
sub_view: route.name === VIEWS.WORKFLOWS ? "Workflows listing" : "Workflow editor"
});
break;
case WORKFLOW_MENU_ACTIONS.ARCHIVE:
nodeViewEventBus.emit("archiveWorkflow");
break;
case WORKFLOW_MENU_ACTIONS.UNARCHIVE:
nodeViewEventBus.emit("unarchiveWorkflow");
break;
case WORKFLOW_MENU_ACTIONS.DELETE:
nodeViewEventBus.emit("deleteWorkflow");
break;
case WORKFLOW_MENU_ACTIONS.CHANGE_OWNER: {
const workflowId = getWorkflowId(props.id, route.params.name);
if (!workflowId) return;
changeOwnerEventBus.once("resource-moved", async () => await router_default.push({ name: VIEWS.WORKFLOWS }));
uiStore.openModalWithData({
name: PROJECT_MOVE_RESOURCE_MODAL,
data: {
resource: workflowsStore.workflowsById[workflowId],
resourceType: ResourceType.Workflow,
resourceTypeLabel: locale.baseText("generic.workflow").toLowerCase(),
eventBus: changeOwnerEventBus
}
});
break;
}
case WORKFLOW_MENU_ACTIONS.UNPUBLISH:
onUnpublishWorkflow();
break;
default: break;
}
}
onMounted(() => {
nodeViewEventBus.on("unpublishWorkflow", onUnpublishWorkflow);
});
onBeforeUnmount(() => {
nodeViewEventBus.off("unpublishWorkflow", onUnpublishWorkflow);
});
__expose({ importFileRef });
return (_ctx, _cache) => {
return openBlock(), createElementBlock("div", { class: normalizeClass([unref($style).group]) }, [createBaseVNode("input", {
ref_key: "importFileRef",
ref: importFileRef,
class: normalizeClass(unref($style).hiddenInput),
type: "file",
"data-test-id": "workflow-import-input",
onChange: _cache[0] || (_cache[0] = ($event) => handleFileImport())
}, null, 34), createVNode(unref(N8nActionDropdown_default), {
items: workflowMenuItems.value,
"data-test-id": "workflow-menu",
onSelect: onWorkflowMenuSelect
}, null, 8, ["items"])], 2);
};
}
});
var ActionsDropdownMenu_vue_vue_type_style_index_0_lang_module_default = {
deleteItem: "_deleteItem_ntqfl_123",
group: "_group_ntqfl_127",
hiddenInput: "_hiddenInput_ntqfl_132"
};
var ActionsDropdownMenu_default = /* @__PURE__ */ __plugin_vue_export_helper_default(ActionsDropdownMenu_vue_vue_type_script_setup_true_lang_default, [["__cssModules", { "$style": ActionsDropdownMenu_vue_vue_type_style_index_0_lang_module_default }]]);
var WorkflowHeaderActions_vue_vue_type_script_setup_true_lang_default = /* @__PURE__ */ defineComponent({
__name: "WorkflowHeaderActions",
props: {
readOnly: { type: Boolean },
id: {},
tags: {},
name: {},
meta: {},
active: { type: Boolean },
currentFolder: {},
isArchived: { type: Boolean },
isNewWorkflow: { type: Boolean },
workflowPermissions: {}
},
emits: ["workflow:deactivated", "workflow:saved"],
setup(__props, { expose: __expose, emit: __emit }) {
const i18n = useI18n();
const uiStore = useUIStore();
const telemetry$1 = useTelemetry();
const usersStore = useUsersStore();
const route = useRoute();
const pageRedirectionHelper = usePageRedirectionHelper();
const $style = useCssModule();
const props = __props;
const emit = __emit;
const isWorkflowSaving = computed(() => {
return uiStore.isActionActive.workflowSaving;
});
const actionsMenuRef = useTemplateRef("actionsMenu");
const importFileRef = computed(() => actionsMenuRef.value?.importFileRef);
const onWorkflowActiveToggle = async (value) => {
if (!value.active) emit("workflow:deactivated");
};
function onShareButtonClick() {
uiStore.openModalWithData({
name: WORKFLOW_SHARE_MODAL_KEY,
data: { id: props.id }
});
telemetry$1.track("User opened sharing modal", {
workflow_id: props.id,
user_id_sharer: usersStore.currentUser?.id,
sub_view: route.name === VIEWS.WORKFLOWS ? "Workflows listing" : "Workflow editor"
});
}
function goToUpgrade() {
pageRedirectionHelper.goToUpgrade("workflow_sharing", "upgrade-workflow-sharing");
}
__expose({ importFileRef });
return (_ctx, _cache) => {
return openBlock(), createElementBlock("div", { class: normalizeClass(unref($style).container) }, [
createBaseVNode("span", { class: normalizeClass([unref($style).activator, unref($style).group]) }, [createVNode(WorkflowActivator_default, {
"is-archived": _ctx.isArchived,
"workflow-active": _ctx.active,
"workflow-id": _ctx.id,
"workflow-permissions": _ctx.workflowPermissions,
"onUpdate:workflowActive": onWorkflowActiveToggle
}, null, 8, [
"is-archived",
"workflow-active",
"workflow-id",
"workflow-permissions"
])], 2),
createVNode(EnterpriseEdition_ee_default, { features: [unref(EnterpriseEditionFeature).Sharing] }, {
fallback: withCtx(() => [createVNode(unref(N8nTooltip_default), null, {
content: withCtx(() => [createVNode(unref(I18nT), {
keypath: unref(uiStore).contextBasedTranslationKeys.workflows.sharing.unavailable.description.tooltip,
tag: "span",
scope: "global"
}, {
action: withCtx(() => [createBaseVNode("a", { onClick: goToUpgrade }, toDisplayString(unref(i18n).baseText(unref(uiStore).contextBasedTranslationKeys.workflows.sharing.unavailable.button)), 1)]),
_: 1
}, 8, ["keypath"])]),
default: withCtx(() => [createVNode(unref(N8nButton_default), {
type: "secondary",
class: normalizeClass(["mr-2xs", unref($style).disabledShareButton])
}, {
default: withCtx(() => [createTextVNode(toDisplayString(unref(i18n).baseText("workflowDetails.share")), 1)]),
_: 1
}, 8, ["class"])]),
_: 1
})]),
default: withCtx(() => [createBaseVNode("div", { class: normalizeClass(unref($style).group) }, [!_ctx.isNewWorkflow ? (openBlock(), createBlock(CollaborationPane_default, { key: 0 })) : createCommentVNode("", true), createVNode(unref(N8nButton_default), {
type: "secondary",
"data-test-id": "workflow-share-button",
onClick: onShareButtonClick
}, {
default: withCtx(() => [createTextVNode(toDisplayString(unref(i18n).baseText("workflowDetails.share")), 1)]),
_: 1
})], 2)]),
_: 1
}, 8, ["features"]),
createBaseVNode("div", { class: normalizeClass(unref($style).group) }, [
createVNode(SaveButton_default, {
type: "primary",
saved: !unref(uiStore).stateIsDirty && !_ctx.isNewWorkflow,
disabled: isWorkflowSaving.value || _ctx.readOnly || _ctx.isArchived || !_ctx.isNewWorkflow && !_ctx.workflowPermissions.update,
"is-saving": isWorkflowSaving.value,
"with-shortcut": !_ctx.readOnly && !_ctx.isArchived && _ctx.workflowPermissions.update,
"shortcut-tooltip": unref(i18n).baseText("saveWorkflowButton.hint"),
"data-test-id": "workflow-save-button",
onClick: _cache[0] || (_cache[0] = ($event) => _ctx.$emit("workflow:saved"))
}, null, 8, [
"saved",
"disabled",
"is-saving",
"with-shortcut",
"shortcut-tooltip"
]),
createVNode(WorkflowHistoryButton_default, {
"workflow-id": props.id,
"is-new-workflow": _ctx.isNewWorkflow
}, null, 8, ["workflow-id", "is-new-workflow"]),
createVNode(ActionsDropdownMenu_default, {
id: _ctx.id,
ref: "actionsMenu",
"workflow-permissions": _ctx.workflowPermissions,
"is-new-workflow": _ctx.isNewWorkflow,
"read-only": _ctx.readOnly,
"is-archived": _ctx.isArchived,
name: _ctx.name,
tags: _ctx.tags,
"current-folder": _ctx.currentFolder,
meta: _ctx.meta,
"onWorkflow:saved": _cache[1] || (_cache[1] = ($event) => _ctx.$emit("workflow:saved"))
}, null, 8, [
"id",
"workflow-permissions",
"is-new-workflow",
"read-only",
"is-archived",
"name",
"tags",
"current-folder",
"meta"
])
], 2)
], 2);
};
}
});
var WorkflowHeaderActions_vue_vue_type_style_index_0_lang_module_default = {
container: "_container_uqc1z_123",
activator: "_activator_uqc1z_127",
group: "_group_uqc1z_138",
disabledShareButton: "_disabledShareButton_uqc1z_143"
};
var WorkflowHeaderActions_default = /* @__PURE__ */ __plugin_vue_export_helper_default(WorkflowHeaderActions_vue_vue_type_script_setup_true_lang_default, [["__cssModules", { "$style": WorkflowHeaderActions_vue_vue_type_style_index_0_lang_module_default }]]);
var WorkflowHeaderDraftPublishActions_vue_vue_type_script_setup_true_lang_default = /* @__PURE__ */ defineComponent({
__name: "WorkflowHeaderDraftPublishActions",
props: {
readOnly: { type: Boolean },
id: {},
tags: {},
name: {},
meta: {},
currentFolder: {},
isArchived: { type: Boolean },
isNewWorkflow: { type: Boolean },
workflowPermissions: {}
},
emits: ["workflow:saved"],
setup(__props, { expose: __expose }) {
const props = __props;
const actionsMenuRef = useTemplateRef("actionsMenu");
const locale = useI18n();
const uiStore = useUIStore();
const workflowsStore = useWorkflowsStore();
const i18n = useI18n();
const { saveCurrentWorkflow } = useWorkflowSaving({ router: useRouter() });
const autoSaveForPublish = ref(false);
const isWorkflowSaving = computed(() => {
return uiStore.isActionActive.workflowSaving && !autoSaveForPublish.value;
});
const importFileRef = computed(() => actionsMenuRef.value?.importFileRef);
const onPublishButtonClick = async () => {
if (uiStore.stateIsDirty || props.isNewWorkflow) {
autoSaveForPublish.value = true;
const saved = await saveCurrentWorkflow({}, true);
autoSaveForPublish.value = false;
if (!saved) return;
}
uiStore.openModalWithData({
name: WORKFLOW_PUBLISH_MODAL_KEY,
data: {}
});
};
const foundTriggers = computed(() => getActivatableTriggerNodes(workflowsStore.workflowTriggerNodes));
const containsTrigger = computed(() => {
return foundTriggers.value.length > 0;
});
const isWorkflowSaved = computed(() => {
return !uiStore.stateIsDirty && !props.isNewWorkflow;
});
const publishButtonEnabled = computed(() => {
if (!props.workflowPermissions.publish) return false;
if (!containsTrigger.value) return false;
if (workflowsStore.nodesIssuesExist) return false;
return workflowsStore.workflow.versionId && workflowsStore.workflow.versionId !== workflowsStore.workflow.activeVersion?.versionId || uiStore.stateIsDirty;
});
const publishTooltipText = computed(() => {
if (!props.workflowPermissions.publish) return i18n.baseText("workflows.publish.permissionDenied");
const wfHasAnyChanges = workflowsStore.workflow.versionId !== workflowsStore.workflow.activeVersion?.versionId;
if (!containsTrigger.value) return i18n.baseText("workflows.publishModal.noTriggerMessage");
if (workflowsStore.nodesIssuesExist) return i18n.baseText("workflowActivator.showMessage.activeChangedNodesIssuesExistTrue.title", {
interpolate: { count: workflowsStore.nodesWithIssues.length },
adjustToNumber: workflowsStore.nodesWithIssues.length
});
if (!wfHasAnyChanges && !uiStore.stateIsDirty) return i18n.baseText("workflows.publishModal.noChanges");
return "";
});
const activeVersion = computed(() => workflowsStore.workflow.activeVersion);
const activeVersionName = computed(() => {
if (!activeVersion.value) return "";
return activeVersion.value.name || generateVersionName(activeVersion.value.versionId);
});
const latestPublishDate = computed(() => {
return getLastPublishedVersion(activeVersion.value?.workflowPublishHistory ?? [])?.createdAt;
});
onMounted(() => {
nodeViewEventBus.on("publishWorkflow", onPublishButtonClick);
});
onBeforeUnmount(() => {
nodeViewEventBus.off("publishWorkflow", onPublishButtonClick);
});
__expose({ importFileRef });
return (_ctx, _cache) => {
return openBlock(), createElementBlock("div", { class: normalizeClass(_ctx.$style.container) }, [
!_ctx.isNewWorkflow ? (openBlock(), createBlock(CollaborationPane_default, { key: 0 })) : createCommentVNode("", true),
activeVersion.value ? (openBlock(), createElementBlock("div", {
key: 1,
class: normalizeClass(_ctx.$style.activeVersionIndicator),
"data-test-id": "workflow-active-version-indicator"
}, [createVNode(unref(N8nTooltip_default), null, {
content: withCtx(() => [
createTextVNode(toDisplayString(activeVersionName.value), 1),
_cache[2] || (_cache[2] = createBaseVNode("br", null, null, -1)),
createTextVNode(toDisplayString(unref(i18n).baseText("workflowHistory.item.active")) + " ", 1),
latestPublishDate.value ? (openBlock(), createBlock(TimeAgo_default, {
key: 0,
date: latestPublishDate.value
}, null, 8, ["date"])) : createCommentVNode("", true)
]),
default: withCtx(() => [createVNode(unref(N8nIcon_default), {
icon: "circle-check",
color: "success",
size: "xlarge",
class: normalizeClass(_ctx.$style.icon)
}, null, 8, ["class"])]),
_: 1
})], 2)) : createCommentVNode("", true),
!_ctx.isArchived && (_ctx.workflowPermissions.update || _ctx.workflowPermissions.publish) ? (openBlock(), createElementBlock("div", {
key: 2,
class: normalizeClass(_ctx.$style.publishButtonWrapper)
}, [createVNode(unref(N8nTooltip_default), { disabled: !publishTooltipText.value }, {
content: withCtx(() => [createTextVNode(toDisplayString(publishTooltipText.value), 1)]),
default: withCtx(() => [createVNode(unref(N8nButton_default), {
loading: autoSaveForPublish.value,
disabled: !publishButtonEnabled.value || isWorkflowSaving.value,
type: "secondary",
"data-test-id": "workflow-open-publish-modal-button",
onClick: onPublishButtonClick
}, {
default: withCtx(() => [createTextVNode(toDisplayString(unref(locale).baseText("workflows.publish")), 1)]),
_: 1
}, 8, ["loading", "disabled"])]),
_: 1
}, 8, ["disabled"])], 2)) : createCommentVNode("", true),
createVNode(SaveButton_default, {
type: "primary",
saved: isWorkflowSaved.value,
disabled: isWorkflowSaving.value || _ctx.readOnly || _ctx.isArchived || !_ctx.isNewWorkflow && !_ctx.workflowPermissions.update,
"is-saving": isWorkflowSaving.value,
"with-shortcut": !_ctx.readOnly && !_ctx.isArchived && _ctx.workflowPermissions.update,
"shortcut-tooltip": unref(i18n).baseText("saveWorkflowButton.hint"),
"data-test-id": "workflow-save-button",
onClick: _cache[0] || (_cache[0] = ($event) => _ctx.$emit("workflow:saved"))
}, null, 8, [
"saved",
"disabled",
"is-saving",
"with-shortcut",
"shortcut-tooltip"
]),
createVNode(WorkflowHistoryButton_default, {
"workflow-id": props.id,
"is-new-workflow": _ctx.isNewWorkflow
}, null, 8, ["workflow-id", "is-new-workflow"]),
createVNode(ActionsDropdownMenu_default, {
id: _ctx.id,
ref: "actionsMenu",
"workflow-permissions": _ctx.workflowPermissions,
"is-new-workflow": _ctx.isNewWorkflow,
"read-only": _ctx.readOnly,
"is-archived": _ctx.isArchived,
name: _ctx.name,
tags: _ctx.tags,
"current-folder": _ctx.currentFolder,
meta: _ctx.meta,
"onWorkflow:saved": _cache[1] || (_cache[1] = ($event) => _ctx.$emit("workflow:saved"))
}, null, 8, [
"id",
"workflow-permissions",
"is-new-workflow",
"read-only",
"is-archived",
"name",
"tags",
"current-folder",
"meta"
])
], 2);
};
}
});
var WorkflowHeaderDraftPublishActions_vue_vue_type_style_index_0_lang_module_default = {
container: "_container_p6bf9_123",
activeVersionIndicator: "_activeVersionIndicator_p6bf9_127",
icon: "_icon_p6bf9_131",
publishButtonWrapper: "_publishButtonWrapper_p6bf9_135"
};
var WorkflowHeaderDraftPublishActions_default = /* @__PURE__ */ __plugin_vue_export_helper_default(WorkflowHeaderDraftPublishActions_vue_vue_type_script_setup_true_lang_default, [["__cssModules", { "$style": WorkflowHeaderDraftPublishActions_vue_vue_type_style_index_0_lang_module_default }]]);
var _hoisted_1 = {
class: "tags",
"data-test-id": "workflow-tags-container"
};
var _hoisted_2 = { key: 1 };
var WorkflowDetails_vue_vue_type_script_setup_true_lang_default = /* @__PURE__ */ defineComponent({
__name: "WorkflowDetails",
props: {
readOnly: { type: Boolean },
id: {},
tags: {},
name: {},
meta: {},
scopes: {},
active: { type: Boolean },
currentFolder: {},
isArchived: { type: Boolean },
description: {}
},
emits: ["workflow:deactivated"],
setup(__props, { emit: __emit }) {
const WORKFLOW_NAME_BP_TO_WIDTH = {
XS: 150,
SM: 200,
MD: 250,
LG: 500,
XL: 1e3
};
const props = __props;
const emit = __emit;
const $style = useCssModule();
const settingsStore = useSettingsStore();
const uiStore = useUIStore();
const workflowsStore = useWorkflowsStore();
const projectsStore = useProjectsStore();
const foldersStore = useFoldersStore();
const npsSurveyStore = useNpsSurveyStore();
const i18n = useI18n();
const router = useRouter();
const route = useRoute();
const locale = useI18n();
const telemetry$1 = useTelemetry();
const message = useMessage();
const toast = useToast();
const documentTitle = useDocumentTitle();
const workflowSaving = useWorkflowSaving({ router });
const isTagsEditEnabled = ref(false);
const appliedTagIds = ref([]);
const tagsSaving = ref(false);
const workflowHeaderActionsRef = useTemplateRef("workflowHeaderActions");
const tagsEventBus = createEventBus();
const hasChanged = (prev, curr) => {
if (prev.length !== curr.length) return true;
const set = new Set(prev);
return curr.reduce((acc, val) => acc || !set.has(val), false);
};
const isNewWorkflow = computed(() => {
return !workflowsStore.isWorkflowSaved[props.id];
});
const isWorkflowSaving = computed(() => {
return uiStore.isActionActive.workflowSaving;
});
const workflowPermissions = computed(() => getResourcePermissions(props.scopes).workflow);
const workflowTagIds = computed(() => {
return (props.tags ?? []).map((tag) => typeof tag === "string" ? tag : tag.id);
});
const currentProjectName = computed(() => {
if (projectsStore.currentProject?.type === ProjectTypes.Personal) return locale.baseText("projects.menu.personal");
return projectsStore.currentProject?.name;
});
const currentFolderForBreadcrumbs = computed(() => {
if (!isNewWorkflow.value && props.currentFolder) return props.currentFolder;
const folderId = route.query.parentFolderId;
if (folderId) return foldersStore.getCachedFolder(folderId);
return null;
});
watch(() => props.id, () => {
isTagsEditEnabled.value = false;
renameInput.value?.forceCancel();
});
async function onSaveButtonClick() {
if (isWorkflowSaving.value) return;
const id = getWorkflowId(props.id, route.params.name);
const name = props.name;
const tags = props.tags;
const wasNewWorkflow = !workflowsStore.isWorkflowSaved[props.id];
if (await workflowSaving.saveCurrentWorkflow({
id,
name,
tags
})) {
showCreateWorkflowSuccessToast(id, wasNewWorkflow);
await npsSurveyStore.fetchPromptsData();
if (route.name === VIEWS.EXECUTION_DEBUG) await router.replace({
name: VIEWS.WORKFLOW,
params: { name: props.id }
});
}
}
function onTagsEditEnable() {
appliedTagIds.value = props.tags ?? [];
isTagsEditEnabled.value = true;
setTimeout(() => {
renameInput.value?.forceCancel();
tagsEventBus.emit("focus");
}, 0);
}
async function onTagsBlur() {
const current = props.tags ?? [];
const tags = appliedTagIds.value;
if (!hasChanged(current, tags)) {
isTagsEditEnabled.value = false;
return;
}
if (tagsSaving.value) return;
tagsSaving.value = true;
const saved = await workflowSaving.saveCurrentWorkflow({ tags });
telemetry$1.track("User edited workflow tags", {
workflow_id: props.id,
new_tag_count: tags.length
});
tagsSaving.value = false;
if (saved) isTagsEditEnabled.value = false;
}
function onTagsEditEsc() {
isTagsEditEnabled.value = false;
}
const renameInput = useTemplateRef("renameInput");
function onNameToggle() {
if (renameInput.value?.forceFocus) renameInput.value.forceFocus();
}
async function onNameSubmit(name) {
const newName = name.trim();
if (!newName) {
toast.showMessage({
title: locale.baseText("renameAction.emptyName.title"),
message: locale.baseText("renameAction.emptyName.message"),
type: "error"
});
renameInput.value?.forceCancel();
return;
}
if (newName === props.name) {
renameInput.value?.forceCancel();
return;
}
uiStore.addActiveAction("workflowSaving");
const id = getWorkflowId(props.id, route.params.name);
const wasNewWorkflow = !workflowsStore.isWorkflowSaved[props.id];
if (await workflowSaving.saveCurrentWorkflow({ name })) {
showCreateWorkflowSuccessToast(id, wasNewWorkflow);
documentTitle.setDocumentTitle(newName, "IDLE");
}
uiStore.removeActiveAction("workflowSaving");
renameInput.value?.forceCancel();
}
async function handleArchiveWorkflow() {
if (props.active) {
if (await message.confirm(locale.baseText("mainSidebar.confirmMessage.workflowArchive.message", { interpolate: { workflowName: props.name } }), locale.baseText("mainSidebar.confirmMessage.workflowArchive.headline"), {
type: "warning",
confirmButtonText: locale.baseText("mainSidebar.confirmMessage.workflowArchive.confirmButtonText"),
cancelButtonText: locale.baseText("mainSidebar.confirmMessage.workflowArchive.cancelButtonText")
}) !== "confirm") return;
}
try {
await workflowsStore.archiveWorkflow(props.id);
} catch (error) {
toast.showError(error, locale.baseText("generic.archiveWorkflowError"));
return;
}
uiStore.stateIsDirty = false;
toast.showMessage({
title: locale.baseText("mainSidebar.showMessage.handleArchive.title", { interpolate: { workflowName: props.name } }),
type: "success"
});
const workflow = workflowsStore.getWorkflowById(props.id);
if (workflow?.homeProject?.type === ProjectTypes.Team) await router.push({
name: VIEWS.PROJECTS_WORKFLOWS,
params: { projectId: workflow.homeProject.id }
});
else await router.push({ name: VIEWS.WORKFLOWS });
}
async function handleUnarchiveWorkflow() {
await workflowsStore.unarchiveWorkflow(props.id);
toast.showMessage({
title: locale.baseText("mainSidebar.showMessage.handleUnarchive.title", { interpolate: { workflowName: props.name } }),
type: "success"
});
}
async function handleDeleteWorkflow() {
if (await message.confirm(locale.baseText("mainSidebar.confirmMessage.workflowDelete.message", { interpolate: { workflowName: props.name } }), locale.baseText("mainSidebar.confirmMessage.workflowDelete.headline"), {
type: "warning",
confirmButtonText: locale.baseText("mainSidebar.confirmMessage.workflowDelete.confirmButtonText"),
cancelButtonText: locale.baseText("mainSidebar.confirmMessage.workflowDelete.cancelButtonText")
}) !== "confirm") return;
const workflow = workflowsStore.getWorkflowById(props.id);
const isTeamProject = workflow?.homeProject?.type === ProjectTypes.Team;
try {
await workflowsStore.deleteWorkflow(props.id);
} catch (error) {
toast.showError(error, locale.baseText("generic.deleteWorkflowError"));
return;
}
uiStore.stateIsDirty = false;
documentTitle.reset();
toast.showMessage({
title: locale.baseText("mainSidebar.showMessage.handleSelect1.title", { interpolate: { workflowName: props.name } }),
type: "success"
});
if (isTeamProject && workflow?.homeProject) await router.push({
name: VIEWS.PROJECTS_WORKFLOWS,
params: { projectId: workflow.homeProject.id }
});
else await router.push({ name: VIEWS.WORKFLOWS });
}
function getPersonalProjectToastContent() {
const title = locale.baseText("workflows.create.personal.toast.title");
if (!props.currentFolder) return { title };
return {
title,
toastMessage: locale.baseText("workflows.create.folder.toast.title", { interpolate: {
projectName: "Personal",
folderName: props.currentFolder.name
} })
};
}
function getToastContent() {
const currentProject = projectsStore.currentProject;
const isPersonalProject = !projectsStore.currentProject || currentProject?.id === projectsStore.personalProject?.id;
const projectName = currentProjectName.value ?? "";
if (isPersonalProject) return getPersonalProjectToastContent();
const titleKey = props.currentFolder ? "workflows.create.folder.toast.title" : "workflows.create.project.toast.title";
const interpolateData = props.currentFolder ? {
projectName,
folderName: props.currentFolder.name ?? ""
} : { projectName };
return {
title: locale.baseText(titleKey, { interpolate: interpolateData }),
toastMessage: locale.baseText("workflows.create.project.toast.text", { interpolate: { projectName } })
};
}
function showCreateWorkflowSuccessToast(id, wasNewWorkflow) {
if (!id) return;
if (!(wasNewWorkflow ?? false)) return;
const { title, toastMessage } = getToastContent();
toast.showMessage({
title,
message: toastMessage,
type: "success"
});
}
const onBreadcrumbsItemSelected = (item) => {
if (item.href) router.push(item.href).catch((error) => {
toast.showError(error, i18n.baseText("folders.open.error.title"));
});
};
const handleImportWorkflowFromFile = () => {
if (workflowHeaderActionsRef.value?.importFileRef) workflowHeaderActionsRef.value.importFileRef.click();
};
onMounted(() => {
nodeViewEventBus.on("importWorkflowFromFile", handleImportWorkflowFromFile);
nodeViewEventBus.on("archiveWorkflow", handleArchiveWorkflow);
nodeViewEventBus.on("unarchiveWorkflow", handleUnarchiveWorkflow);
nodeViewEventBus.on("deleteWorkflow", handleDeleteWorkflow);
nodeViewEventBus.on("renameWorkflow", onNameToggle);
nodeViewEventBus.on("addTag", onTagsEditEnable);
});
onBeforeUnmount(() => {
nodeViewEventBus.off("importWorkflowFromFile", handleImportWorkflowFromFile);
nodeViewEventBus.off("archiveWorkflow", handleArchiveWorkflow);
nodeViewEventBus.off("unarchiveWorkflow", handleUnarchiveWorkflow);
nodeViewEventBus.off("deleteWorkflow", handleDeleteWorkflow);
nodeViewEventBus.off("renameWorkflow", onNameToggle);
nodeViewEventBus.off("addTag", onTagsEditEnable);
});
return (_ctx, _cache) => {
return openBlock(), createElementBlock("div", { class: normalizeClass(unref($style).container) }, [
createVNode(BreakpointsObserver_default, {
"value-x-s": 15,
"value-s-m": 25,
"value-m-d": 50,
class: "name-container",
"data-test-id": "canvas-breadcrumbs"
}, {
default: withCtx(({ bp }) => [createVNode(FolderBreadcrumbs_default, {
"current-folder": currentFolderForBreadcrumbs.value,
"current-folder-as-link": true,
onItemSelected: onBreadcrumbsItemSelected
}, {
append: withCtx(() => [unref(projectsStore).currentProject ?? unref(projectsStore).personalProject ? (openBlock(), createElementBlock("span", {
key: 0,
class: normalizeClass(unref($style)["path-separator"])
}, "/", 2)) : createCommentVNode("", true), (openBlock(), createBlock(unref(N8nInlineTextEdit_default), {
ref_key: "renameInput",
ref: renameInput,
key: _ctx.id,
placeholder: "Workflow name",
"data-test-id": "workflow-name-input",
class: "name",
"model-value": _ctx.name,
"max-length": unref(128),
"max-width": WORKFLOW_NAME_BP_TO_WIDTH[bp],
"read-only": _ctx.readOnly || _ctx.isArchived || !isNewWorkflow.value && !workflowPermissions.value.update,
disabled: _ctx.readOnly || _ctx.isArchived || !isNewWorkflow.value && !workflowPermissions.value.update,
"onUpdate:modelValue": onNameSubmit
}, null, 8, [
"model-value",
"max-length",
"max-width",
"read-only",
"disabled"
]))]),
_: 2
}, 1032, ["current-folder"])]),
_: 1
}),
createBaseVNode("span", _hoisted_1, [unref(settingsStore).areTagsEnabled ? (openBlock(), createElementBlock(Fragment, { key: 0 }, [isTagsEditEnabled.value && !(_ctx.readOnly || _ctx.isArchived) && (isNewWorkflow.value || workflowPermissions.value.update) ? (openBlock(), createBlock(WorkflowTagsDropdown_default, {
key: 0,
ref: "dropdown",
modelValue: appliedTagIds.value,
"onUpdate:modelValue": _cache[0] || (_cache[0] = ($event) => appliedTagIds.value = $event),
"event-bus": unref(tagsEventBus),
placeholder: unref(i18n).baseText("workflowDetails.chooseOrCreateATag"),
class: "tags-edit",
"data-test-id": "workflow-tags-dropdown",
onBlur: onTagsBlur,
onEsc: onTagsEditEsc
}, null, 8, [
"modelValue",
"event-bus",
"placeholder"
])) : (_ctx.tags ?? []).length === 0 && !(_ctx.readOnly || _ctx.isArchived) && (isNewWorkflow.value || workflowPermissions.value.update) ? (openBlock(), createElementBlock("div", _hoisted_2, [createBaseVNode("span", {
class: "add-tag clickable",
"data-test-id": "new-tag-link",
onClick: onTagsEditEnable
}, " + " + toDisplayString(unref(i18n).baseText("workflowDetails.addTag")), 1)])) : (openBlock(), createBlock(WorkflowTagsContainer_default, {
key: _ctx.id,
"tag-ids": workflowTagIds.value,
clickable: true,
responsive: true,
"data-test-id": "workflow-tags",
onClick: onTagsEditEnable
}, null, 8, ["tag-ids"]))], 64)) : createCommentVNode("", true), createBaseVNode("span", { class: normalizeClass(unref($style)["header-controls"]) }, [_ctx.isArchived ? (openBlock(), createBlock(unref(N8nBadge_default), {
key: 0,
class: "ml-3xs",
theme: "tertiary",
bold: "",
"data-test-id": "workflow-archived-tag"
}, {
default: withCtx(() => [createTextVNode(toDisplayString(unref(locale).baseText("workflows.item.archived")), 1)]),
_: 1
})) : createCommentVNode("", true)], 2)]),
createVNode(PushConnectionTracker_default, { class: "actions" }, {
default: withCtx(() => [!isNewWorkflow.value ? (openBlock(), createBlock(WorkflowProductionChecklist_default, {
key: 0,
workflow: unref(workflowsStore).workflow
}, null, 8, ["workflow"])) : createCommentVNode("", true), unref(true) ? (openBlock(), createBlock(WorkflowHeaderDraftPublishActions_default, {
key: 1,
id: _ctx.id,
ref: "workflowHeaderActions",
tags: _ctx.tags,
name: _ctx.name,
meta: _ctx.meta,
"read-only": _ctx.readOnly,
"is-archived": _ctx.isArchived,
"is-new-workflow": isNewWorkflow.value,
"workflow-permissions": workflowPermissions.value,
"onWorkflow:saved": onSaveButtonClick
}, null, 8, [
"id",
"tags",
"name",
"meta",
"read-only",
"is-archived",
"is-new-workflow",
"workflow-permissions"
])) : (openBlock(), createBlock(WorkflowHeaderActions_default, {
key: 2,
id: _ctx.id,
ref: "workflowHeaderActions",
name: _ctx.name,
tags: _ctx.tags,
"current-folder": _ctx.currentFolder,
meta: _ctx.meta,
"read-only": _ctx.readOnly,
"is-archived": _ctx.isArchived,
active: _ctx.active,
"is-new-workflow": isNewWorkflow.value,
"workflow-permissions": workflowPermissions.value,
"onWorkflow:saved": onSaveButtonClick,
"onWorkflow:deactivated": _cache[1] || (_cache[1] = ($event) => emit("workflow:deactivated"))
}, null, 8, [
"id",
"name",
"tags",
"current-folder",
"meta",
"read-only",
"is-archived",
"active",
"is-new-workflow",
"workflow-permissions"
]))]),
_: 1
})
], 2);
};
}
});
const container$1 = "_container_7bao3_123";
const closeNodeViewDiscovery = "_closeNodeViewDiscovery_7bao3_138";
var WorkflowDetails_vue_vue_type_style_index_1_lang_module_default = {
container: container$1,
"path-separator": "_path-separator_7bao3_132",
closeNodeViewDiscovery,
"header-controls": "_header-controls_7bao3_145"
};
var WorkflowDetails_default = /* @__PURE__ */ __plugin_vue_export_helper_default(WorkflowDetails_vue_vue_type_script_setup_true_lang_default, [["__cssModules", { "$style": WorkflowDetails_vue_vue_type_style_index_1_lang_module_default }], ["__scopeId", "data-v-22fed76c"]]);
init_shared_esm_bundler();
var vue_github_button_default = defineComponent({
name: "github-button",
props: {
href: String,
ariaLabel: String,
title: String,
dataIcon: String,
dataColorScheme: String,
dataSize: String,
dataShowCount: String,
dataText: String
},
render: function() {
const props = { ref: "_" };
for (const key in this.$props) props[hyphenate(key)] = this.$props[key];
return h("span", [hasOwn(this.$slots, "default") ? h("a", props, this.$slots.default()) : h("a", props)]);
},
mounted: function() {
this.paint();
},
beforeUpdate: function() {
this.reset();
},
updated: function() {
this.paint();
},
beforeUnmount: function() {
this.reset();
},
methods: {
paint: function() {
if (this.$el.lastChild !== this.$refs._) return;
const _ = this.$el.appendChild(document.createElement("span"));
const _this = this;
__vitePreload(() => import(
/* webpackMode: "eager" */
"./buttons.esm-CT4flsfB.js"
), []).then(function(module) {
if (_this.$el.lastChild !== _) return;
module.render(_.appendChild(_this.$refs._), function(el) {
if (_this.$el.lastChild !== _) return;
_.parentNode.replaceChild(el, _);
});
});
},
reset: function() {
if (this.$refs._ == null) return;
this.$el.replaceChild(this.$refs._, this.$el.lastChild);
}
}
});
var MainHeader_vue_vue_type_script_setup_true_lang_default = /* @__PURE__ */ defineComponent({
__name: "MainHeader",
setup(__props) {
const router = useRouter();
const route = useRoute();
const locale = useI18n();
const pushConnection = usePushConnection({ router });
const toast = useToast();
const ndvStore = useNDVStore();
const uiStore = useUIStore();
const sourceControlStore = useSourceControlStore();
const workflowsStore = useWorkflowsStore();
const executionsStore = useExecutionsStore();
const settingsStore = useSettingsStore();
const activeHeaderTab = ref(MAIN_HEADER_TABS.WORKFLOW);
const workflowToReturnTo = ref("");
const executionToReturnTo = ref("");
const dirtyState = ref(false);
const githubButtonHidden = useLocalStorage(LOCAL_STORAGE_HIDE_GITHUB_STAR_BUTTON, false);
const evaluationRoutes = [VIEWS.EVALUATION_EDIT, VIEWS.EVALUATION_RUNS_DETAIL];
const workflowRoutes = [
VIEWS.WORKFLOW,
VIEWS.NEW_WORKFLOW,
VIEWS.EXECUTION_DEBUG
];
const executionRoutes = [
VIEWS.EXECUTION_HOME,
VIEWS.WORKFLOW_EXECUTIONS,
VIEWS.EXECUTION_PREVIEW
];
const tabBarItems = computed(() => {
return [
{
value: MAIN_HEADER_TABS.WORKFLOW,
label: locale.baseText("generic.editor")
},
{
value: MAIN_HEADER_TABS.EXECUTIONS,
label: locale.baseText("generic.executions")
},
{
value: MAIN_HEADER_TABS.EVALUATION,
label: locale.baseText("generic.tests")
}
];
});
const activeNode = computed(() => ndvStore.activeNode);
const hideMenuBar = computed(() => Boolean(activeNode.value && activeNode.value.type !== "n8n-nodes-base.stickyNote"));
const workflow = computed(() => workflowsStore.workflow);
const workflowId = computed(() => String(route.params.name || workflowsStore.workflowId));
const onWorkflowPage = computed(() => !!(route.meta.nodeView || route.meta.keepWorkflowAlive));
const readOnly = computed(() => sourceControlStore.preferences.branchReadOnly);
const isEnterprise = computed(() => settingsStore.isQueueModeEnabled && settingsStore.isWorkerViewAvailable);
const isTelemetryEnabled = computed(() => {
return settingsStore.isTelemetryEnabled;
});
const showGitHubButton = computed(() => !isEnterprise.value && !settingsStore.settings.inE2ETests && !githubButtonHidden.value && isTelemetryEnabled.value);
const parentFolderForBreadcrumbs = computed(() => {
if (!workflow.value.parentFolder) return;
return {
id: workflow.value.parentFolder.id,
name: workflow.value.parentFolder.name,
parentFolder: workflow.value.parentFolder.parentFolderId ?? void 0
};
});
watch(route, (to, from) => {
syncTabsWithRoute(to, from);
});
onBeforeMount(() => {
pushConnection.initialize();
});
onBeforeUnmount(() => {
pushConnection.terminate();
});
onMounted(async () => {
dirtyState.value = uiStore.stateIsDirty;
syncTabsWithRoute(route);
});
function isViewRoute(name) {
return typeof name === "string" && [
evaluationRoutes,
workflowRoutes,
executionRoutes
].flat().includes(name);
}
function syncTabsWithRoute(to, from) {
const routeTabMapping = [
{
routes: evaluationRoutes,
tab: MAIN_HEADER_TABS.EVALUATION
},
{
routes: executionRoutes,
tab: MAIN_HEADER_TABS.EXECUTIONS
},
{
routes: workflowRoutes,
tab: MAIN_HEADER_TABS.WORKFLOW
}
];
if (to.name && isViewRoute(to.name)) {
const matchingTab = routeTabMapping.find(({ routes }) => routes.includes(to.name));
if (matchingTab) activeHeaderTab.value = matchingTab.tab;
}
if (typeof to.params.name === "string") workflowToReturnTo.value = to.params.name;
if (from?.name === VIEWS.EXECUTION_PREVIEW && to.params.name === from.params.name && typeof from.params.executionId === "string") executionToReturnTo.value = from.params.executionId;
}
function onTabSelected(tab, event) {
const openInNewTab = event.ctrlKey || event.metaKey;
switch (tab) {
case MAIN_HEADER_TABS.WORKFLOW:
navigateToWorkflowView(openInNewTab);
break;
case MAIN_HEADER_TABS.EXECUTIONS:
navigateToExecutionsView(openInNewTab);
break;
case MAIN_HEADER_TABS.EVALUATION:
navigateToEvaluationsView(openInNewTab);
break;
default: break;
}
}
async function navigateToWorkflowView(openInNewTab) {
let routeToNavigateTo;
if (workflowToReturnTo.value && workflowToReturnTo.value !== "") routeToNavigateTo = {
name: VIEWS.WORKFLOW,
params: { name: workflowToReturnTo.value },
query: route.query
};
else routeToNavigateTo = {
name: VIEWS.NEW_WORKFLOW,
query: route.query
};
if (openInNewTab) {
const { href } = router.resolve(routeToNavigateTo);
window.open(href, "_blank");
} else if (route.name !== routeToNavigateTo.name) {
if (route.name === VIEWS.NEW_WORKFLOW) uiStore.stateIsDirty = dirtyState.value;
activeHeaderTab.value = MAIN_HEADER_TABS.WORKFLOW;
await router.push(routeToNavigateTo);
}
}
async function navigateToExecutionsView(openInNewTab) {
const executionToReturnToValue = executionsStore.activeExecution?.id || executionToReturnTo.value;
const routeToNavigateTo = executionToReturnToValue ? {
name: VIEWS.EXECUTION_PREVIEW,
params: {
name: workflowId.value,
executionId: executionToReturnToValue
},
query: route.query
} : {
name: VIEWS.EXECUTION_HOME,
params: { name: workflowId.value },
query: route.query
};
if (openInNewTab) {
const { href } = router.resolve(routeToNavigateTo);
window.open(href, "_blank");
} else if (route.name !== routeToNavigateTo.name) {
dirtyState.value = uiStore.stateIsDirty;
workflowToReturnTo.value = workflowId.value;
activeHeaderTab.value = MAIN_HEADER_TABS.EXECUTIONS;
await router.push(routeToNavigateTo);
}
}
async function navigateToEvaluationsView(openInNewTab) {
const routeToNavigateTo = {
name: VIEWS.EVALUATION_EDIT,
params: { name: workflowId.value },
query: route.query
};
if (openInNewTab) {
const { href } = router.resolve(routeToNavigateTo);
window.open(href, "_blank");
} else if (route.name !== routeToNavigateTo.name) {
dirtyState.value = uiStore.stateIsDirty;
workflowToReturnTo.value = workflowId.value;
activeHeaderTab.value = MAIN_HEADER_TABS.EXECUTIONS;
await router.push(routeToNavigateTo);
}
}
function hideGithubButton() {
githubButtonHidden.value = true;
}
async function onWorkflowDeactivated() {
if (settingsStore.isModuleActive("mcp") && workflow.value.settings?.availableInMCP) try {
const updatedWorkflow = await workflowsStore.fetchWorkflow(workflow.value.id);
workflowsStore.setWorkflow(updatedWorkflow);
toast.showToast({
title: locale.baseText("mcp.workflowDeactivated.title"),
message: locale.baseText("mcp.workflowDeactivated.message"),
type: "info"
});
} catch (error) {
toast.showError(error, locale.baseText("workflowSettings.showError.fetchSettings.title"));
}
}
return (_ctx, _cache) => {
return openBlock(), createElementBlock("div", { class: normalizeClass(_ctx.$style.container) }, [createBaseVNode("div", { class: normalizeClass({
[_ctx.$style["main-header"]]: true,
[_ctx.$style.expanded]: !unref(uiStore).sidebarMenuCollapsed
}) }, [withDirectives(createBaseVNode("div", { class: normalizeClass(_ctx.$style["top-menu"]) }, [workflow.value?.name ? (openBlock(), createBlock(WorkflowDetails_default, {
key: 0,
id: workflow.value.id,
tags: workflow.value.tags,
name: workflow.value.name,
meta: workflow.value.meta,
scopes: workflow.value.scopes,
active: workflow.value.active,
"read-only": readOnly.value,
"current-folder": parentFolderForBreadcrumbs.value,
"is-archived": workflow.value.isArchived,
description: workflow.value.description,
"onWorkflow:deactivated": onWorkflowDeactivated
}, null, 8, [
"id",
"tags",
"name",
"meta",
"scopes",
"active",
"read-only",
"current-folder",
"is-archived",
"description"
])) : createCommentVNode("", true), showGitHubButton.value ? (openBlock(), createElementBlock("div", {
key: 1,
class: normalizeClass([_ctx.$style["github-button"], "hidden-sm-and-down"])
}, [createBaseVNode("div", { class: normalizeClass(_ctx.$style["github-button-container"]) }, [createVNode(unref(vue_github_button_default), {
href: unref(N8N_MAIN_GITHUB_REPO_URL),
"data-color-scheme": unref(uiStore).appliedTheme,
"data-size": "large",
"data-show-count": "true",
"aria-label": unref(locale).baseText("editor.mainHeader.githubButton.label")
}, {
default: withCtx(() => [createTextVNode(toDisplayString(unref(locale).baseText("generic.star")), 1)]),
_: 1
}, 8, [
"href",
"data-color-scheme",
"aria-label"
]), createVNode(unref(N8nIcon_default), {
class: normalizeClass(_ctx.$style["close-github-button"]),
icon: "circle-x",
size: "medium",
onClick: hideGithubButton
}, null, 8, ["class"])], 2)], 2)) : createCommentVNode("", true)], 2), [[vShow, !hideMenuBar.value]]), onWorkflowPage.value ? (openBlock(), createBlock(TabBar_default, {
key: 0,
items: tabBarItems.value,
"model-value": activeHeaderTab.value,
"onUpdate:modelValue": onTabSelected
}, null, 8, ["items", "model-value"])) : createCommentVNode("", true)], 2)], 2);
};
}
});
var MainHeader_vue_vue_type_style_index_0_lang_module_default = {
container: "_container_4ekmy_123",
"main-header": "_main-header_4ekmy_130",
"top-menu": "_top-menu_4ekmy_138",
"github-button": "_github-button_4ekmy_149",
"close-github-button": "_close-github-button_4ekmy_158",
"github-button-container": "_github-button-container_4ekmy_173"
};
var MainHeader_default = /* @__PURE__ */ __plugin_vue_export_helper_default(MainHeader_vue_vue_type_script_setup_true_lang_default, [["__cssModules", { "$style": MainHeader_vue_vue_type_style_index_0_lang_module_default }]]);
export { MainHeader_default as default };