2024-05-23 01:42:10 -07:00
|
|
|
<script setup lang="ts">
|
2024-07-08 03:25:18 -07:00
|
|
|
import {
|
|
|
|
computed,
|
|
|
|
defineAsyncComponent,
|
|
|
|
nextTick,
|
|
|
|
onBeforeMount,
|
|
|
|
onBeforeUnmount,
|
|
|
|
onMounted,
|
|
|
|
ref,
|
|
|
|
useCssModule,
|
|
|
|
} from 'vue';
|
2024-07-08 05:57:42 -07:00
|
|
|
import { onBeforeRouteLeave, useRoute, useRouter } from 'vue-router';
|
2024-05-23 01:42:10 -07:00
|
|
|
import WorkflowCanvas from '@/components/canvas/WorkflowCanvas.vue';
|
|
|
|
import { useNodeTypesStore } from '@/stores/nodeTypes.store';
|
|
|
|
import { useUIStore } from '@/stores/ui.store';
|
2024-07-11 03:03:46 -07:00
|
|
|
import CanvasRunWorkflowButton from '@/components/canvas/elements/buttons/CanvasRunWorkflowButton.vue';
|
2024-05-23 01:42:10 -07:00
|
|
|
import { useI18n } from '@/composables/useI18n';
|
|
|
|
import { useWorkflowsStore } from '@/stores/workflows.store';
|
|
|
|
import { useRunWorkflow } from '@/composables/useRunWorkflow';
|
|
|
|
import type {
|
|
|
|
AddedNodesAndConnections,
|
|
|
|
INodeUi,
|
2024-06-17 05:46:55 -07:00
|
|
|
IUpdateInformation,
|
|
|
|
IWorkflowDataUpdate,
|
2024-07-08 03:25:18 -07:00
|
|
|
IWorkflowDb,
|
2024-05-23 01:42:10 -07:00
|
|
|
ToggleNodeCreatorOptions,
|
|
|
|
XYPosition,
|
|
|
|
} from '@/Interface';
|
|
|
|
import type { Connection } from '@vue-flow/core';
|
2024-07-11 03:03:46 -07:00
|
|
|
import type { CanvasNode, ConnectStartEvent } from '@/types';
|
|
|
|
import { CanvasNodeRenderType } from '@/types';
|
2024-06-25 02:11:44 -07:00
|
|
|
import {
|
|
|
|
CANVAS_AUTO_ADD_MANUAL_TRIGGER_EXPERIMENT,
|
2024-07-11 03:03:46 -07:00
|
|
|
CHAT_TRIGGER_NODE_TYPE,
|
2024-06-25 02:11:44 -07:00
|
|
|
EnterpriseEditionFeature,
|
2024-07-08 05:57:42 -07:00
|
|
|
MAIN_HEADER_TABS,
|
2024-07-11 03:03:46 -07:00
|
|
|
MANUAL_CHAT_TRIGGER_NODE_TYPE,
|
2024-07-08 05:57:42 -07:00
|
|
|
MODAL_CANCEL,
|
2024-06-25 02:11:44 -07:00
|
|
|
MODAL_CONFIRM,
|
2024-07-10 02:22:31 -07:00
|
|
|
NODE_CREATOR_OPEN_SOURCES,
|
2024-07-08 05:57:42 -07:00
|
|
|
PLACEHOLDER_EMPTY_WORKFLOW_ID,
|
2024-07-11 03:03:46 -07:00
|
|
|
START_NODE_TYPE,
|
2024-06-25 02:11:44 -07:00
|
|
|
VIEWS,
|
|
|
|
} from '@/constants';
|
2024-05-23 01:42:10 -07:00
|
|
|
import { useSourceControlStore } from '@/stores/sourceControl.store';
|
|
|
|
import { useNodeCreatorStore } from '@/stores/nodeCreator.store';
|
|
|
|
import { useExternalHooks } from '@/composables/useExternalHooks';
|
2024-07-08 03:25:18 -07:00
|
|
|
import { TelemetryHelpers } from 'n8n-workflow';
|
|
|
|
import type {
|
|
|
|
NodeConnectionType,
|
|
|
|
ExecutionSummary,
|
|
|
|
IConnection,
|
|
|
|
IWorkflowBase,
|
|
|
|
} from 'n8n-workflow';
|
2024-05-23 01:42:10 -07:00
|
|
|
import { useToast } from '@/composables/useToast';
|
|
|
|
import { useSettingsStore } from '@/stores/settings.store';
|
|
|
|
import { useCredentialsStore } from '@/stores/credentials.store';
|
|
|
|
import useEnvironmentsStore from '@/stores/environments.ee.store';
|
|
|
|
import { useExternalSecretsStore } from '@/stores/externalSecrets.ee.store';
|
2024-06-18 10:15:12 -07:00
|
|
|
import { useRootStore } from '@/stores/root.store';
|
2024-06-04 05:36:27 -07:00
|
|
|
import { historyBus } from '@/models/history';
|
|
|
|
import { useCanvasOperations } from '@/composables/useCanvasOperations';
|
2024-06-17 05:46:55 -07:00
|
|
|
import { useExecutionsStore } from '@/stores/executions.store';
|
2024-06-25 02:11:44 -07:00
|
|
|
import { useCanvasStore } from '@/stores/canvas.store';
|
|
|
|
import { useMessage } from '@/composables/useMessage';
|
|
|
|
import { useTitleChange } from '@/composables/useTitleChange';
|
|
|
|
import { useNpsSurveyStore } from '@/stores/npsSurvey.store';
|
|
|
|
import { useWorkflowHelpers } from '@/composables/useWorkflowHelpers';
|
|
|
|
import { useTelemetry } from '@/composables/useTelemetry';
|
|
|
|
import { useHistoryStore } from '@/stores/history.store';
|
|
|
|
import { useProjectsStore } from '@/stores/projects.store';
|
|
|
|
import { usePostHog } from '@/stores/posthog.store';
|
2024-07-08 03:25:18 -07:00
|
|
|
import useWorkflowsEEStore from '@/stores/workflows.ee.store';
|
|
|
|
import { useNodeHelpers } from '@/composables/useNodeHelpers';
|
|
|
|
import { useExecutionDebugging } from '@/composables/useExecutionDebugging';
|
|
|
|
import type { ProjectSharingData } from '@/types/projects.types';
|
|
|
|
import { useUsersStore } from '@/stores/users.store';
|
|
|
|
import { sourceControlEventBus } from '@/event-bus/source-control';
|
|
|
|
import { useTagsStore } from '@/stores/tags.store';
|
|
|
|
import { usePushConnectionStore } from '@/stores/pushConnection.store';
|
2024-07-10 00:56:39 -07:00
|
|
|
import { useNDVStore } from '@/stores/ndv.store';
|
2024-07-10 01:53:27 -07:00
|
|
|
import { getNodeViewTab } from '@/utils/canvasUtils';
|
|
|
|
import CanvasStopCurrentExecutionButton from '@/components/canvas/elements/buttons/CanvasStopCurrentExecutionButton.vue';
|
|
|
|
import CanvasStopWaitingForWebhookButton from '@/components/canvas/elements/buttons/CanvasStopWaitingForWebhookButton.vue';
|
2024-07-11 06:37:01 -07:00
|
|
|
import CanvasClearExecutionDataButton from '@/components/canvas/elements/buttons/CanvasClearExecutionDataButton.vue';
|
2024-07-11 03:03:46 -07:00
|
|
|
import { nodeViewEventBus } from '@/event-bus';
|
2024-05-23 01:42:10 -07:00
|
|
|
|
|
|
|
const NodeCreation = defineAsyncComponent(
|
|
|
|
async () => await import('@/components/Node/NodeCreation.vue'),
|
|
|
|
);
|
|
|
|
|
2024-06-17 05:46:55 -07:00
|
|
|
const NodeDetailsView = defineAsyncComponent(
|
|
|
|
async () => await import('@/components/NodeDetailsView.vue'),
|
|
|
|
);
|
|
|
|
|
2024-05-23 01:42:10 -07:00
|
|
|
const $style = useCssModule();
|
|
|
|
const router = useRouter();
|
|
|
|
const route = useRoute();
|
|
|
|
const i18n = useI18n();
|
2024-06-25 02:11:44 -07:00
|
|
|
const telemetry = useTelemetry();
|
2024-05-23 01:42:10 -07:00
|
|
|
const externalHooks = useExternalHooks();
|
|
|
|
const toast = useToast();
|
2024-06-25 02:11:44 -07:00
|
|
|
const message = useMessage();
|
|
|
|
const titleChange = useTitleChange();
|
|
|
|
const workflowHelpers = useWorkflowHelpers({ router });
|
2024-07-08 03:25:18 -07:00
|
|
|
const nodeHelpers = useNodeHelpers();
|
|
|
|
const posthog = usePostHog();
|
2024-05-23 01:42:10 -07:00
|
|
|
|
|
|
|
const nodeTypesStore = useNodeTypesStore();
|
|
|
|
const uiStore = useUIStore();
|
|
|
|
const workflowsStore = useWorkflowsStore();
|
2024-07-08 03:25:18 -07:00
|
|
|
const workflowsEEStore = useWorkflowsEEStore();
|
2024-05-23 01:42:10 -07:00
|
|
|
const sourceControlStore = useSourceControlStore();
|
|
|
|
const nodeCreatorStore = useNodeCreatorStore();
|
|
|
|
const settingsStore = useSettingsStore();
|
|
|
|
const credentialsStore = useCredentialsStore();
|
|
|
|
const environmentsStore = useEnvironmentsStore();
|
|
|
|
const externalSecretsStore = useExternalSecretsStore();
|
|
|
|
const rootStore = useRootStore();
|
2024-06-17 05:46:55 -07:00
|
|
|
const executionsStore = useExecutionsStore();
|
2024-06-25 02:11:44 -07:00
|
|
|
const canvasStore = useCanvasStore();
|
|
|
|
const npsSurveyStore = useNpsSurveyStore();
|
|
|
|
const historyStore = useHistoryStore();
|
|
|
|
const projectsStore = useProjectsStore();
|
2024-07-08 03:25:18 -07:00
|
|
|
const usersStore = useUsersStore();
|
|
|
|
const tagsStore = useTagsStore();
|
|
|
|
const pushConnectionStore = usePushConnectionStore();
|
2024-07-10 00:56:39 -07:00
|
|
|
const ndvStore = useNDVStore();
|
2024-06-25 02:11:44 -07:00
|
|
|
|
|
|
|
const lastClickPosition = ref<XYPosition>([450, 450]);
|
2024-05-23 01:42:10 -07:00
|
|
|
|
2024-07-10 01:53:27 -07:00
|
|
|
const { runWorkflow, stopCurrentExecution, stopWaitingForWebhook } = useRunWorkflow({ router });
|
2024-06-04 05:36:27 -07:00
|
|
|
const {
|
|
|
|
updateNodePosition,
|
2024-06-17 05:46:55 -07:00
|
|
|
renameNode,
|
|
|
|
revertRenameNode,
|
|
|
|
setNodeActive,
|
2024-06-25 02:11:44 -07:00
|
|
|
setNodeSelected,
|
2024-06-26 06:56:58 -07:00
|
|
|
toggleNodeDisabled,
|
2024-06-04 05:36:27 -07:00
|
|
|
deleteNode,
|
|
|
|
revertDeleteNode,
|
2024-06-25 02:11:44 -07:00
|
|
|
addNodes,
|
2024-06-04 05:36:27 -07:00
|
|
|
createConnection,
|
|
|
|
deleteConnection,
|
|
|
|
revertDeleteConnection,
|
2024-06-17 05:46:55 -07:00
|
|
|
setNodeActiveByName,
|
2024-06-25 02:11:44 -07:00
|
|
|
addConnections,
|
|
|
|
editableWorkflow,
|
|
|
|
editableWorkflowObject,
|
|
|
|
} = useCanvasOperations({ router, lastClickPosition });
|
2024-07-08 03:25:18 -07:00
|
|
|
const { applyExecutionData } = useExecutionDebugging();
|
2024-05-23 01:42:10 -07:00
|
|
|
|
|
|
|
const isLoading = ref(true);
|
2024-06-25 02:11:44 -07:00
|
|
|
const isBlankRedirect = ref(false);
|
2024-05-23 01:42:10 -07:00
|
|
|
const readOnlyNotification = ref<null | { visible: boolean }>(null);
|
|
|
|
|
2024-06-17 05:46:55 -07:00
|
|
|
const isProductionExecutionPreview = ref(false);
|
|
|
|
const isExecutionPreview = ref(false);
|
|
|
|
|
|
|
|
const canOpenNDV = ref(true);
|
|
|
|
const hideNodeIssues = ref(false);
|
|
|
|
|
2024-07-08 05:57:42 -07:00
|
|
|
const workflowId = computed<string>(() => route.params.name as string);
|
2024-05-23 01:42:10 -07:00
|
|
|
const workflow = computed(() => workflowsStore.workflowsById[workflowId.value]);
|
|
|
|
|
2024-07-09 05:58:36 -07:00
|
|
|
const isNewWorkflowRoute = computed(() => route.name === VIEWS.NEW_WORKFLOW);
|
2024-05-23 01:42:10 -07:00
|
|
|
const isDemoRoute = computed(() => route.name === VIEWS.DEMO);
|
|
|
|
const isReadOnlyRoute = computed(() => route?.meta?.readOnlyCanvas === true);
|
|
|
|
const isReadOnlyEnvironment = computed(() => {
|
|
|
|
return sourceControlStore.preferences.branchReadOnly;
|
|
|
|
});
|
|
|
|
|
2024-07-11 03:03:46 -07:00
|
|
|
const isCanvasReadOnly = computed(() => {
|
|
|
|
return isLoading.value || isDemoRoute.value || isReadOnlyEnvironment.value;
|
|
|
|
});
|
|
|
|
|
|
|
|
const fallbackNodes = computed<INodeUi[]>(() =>
|
|
|
|
isCanvasReadOnly.value
|
|
|
|
? []
|
|
|
|
: [
|
|
|
|
{
|
|
|
|
id: CanvasNodeRenderType.AddNodes,
|
|
|
|
name: CanvasNodeRenderType.AddNodes,
|
|
|
|
type: CanvasNodeRenderType.AddNodes,
|
|
|
|
typeVersion: 1,
|
|
|
|
position: [0, 0],
|
|
|
|
parameters: {},
|
|
|
|
},
|
|
|
|
],
|
|
|
|
);
|
|
|
|
|
2024-06-25 02:11:44 -07:00
|
|
|
/**
|
|
|
|
* Initialization
|
|
|
|
*/
|
2024-05-23 01:42:10 -07:00
|
|
|
|
2024-06-25 02:11:44 -07:00
|
|
|
async function initializeData() {
|
2024-05-23 01:42:10 -07:00
|
|
|
isLoading.value = true;
|
2024-07-08 03:25:18 -07:00
|
|
|
canvasStore.startLoading();
|
2024-05-23 01:42:10 -07:00
|
|
|
|
2024-06-25 02:11:44 -07:00
|
|
|
resetWorkspace();
|
|
|
|
titleChange.titleReset();
|
|
|
|
|
2024-07-08 03:25:18 -07:00
|
|
|
const loadPromises = (() => {
|
|
|
|
if (settingsStore.isPreviewMode && isDemoRoute.value) return [];
|
2024-05-23 01:42:10 -07:00
|
|
|
|
2024-07-08 03:25:18 -07:00
|
|
|
const promises: Array<Promise<unknown>> = [
|
2024-05-23 01:42:10 -07:00
|
|
|
workflowsStore.fetchActiveWorkflows(),
|
|
|
|
credentialsStore.fetchAllCredentials(),
|
|
|
|
credentialsStore.fetchCredentialTypes(true),
|
2024-07-08 03:25:18 -07:00
|
|
|
];
|
2024-05-23 01:42:10 -07:00
|
|
|
|
|
|
|
if (settingsStore.isEnterpriseFeatureEnabled(EnterpriseEditionFeature.Variables)) {
|
2024-07-08 03:25:18 -07:00
|
|
|
promises.push(environmentsStore.fetchAllVariables());
|
2024-05-23 01:42:10 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
if (settingsStore.isEnterpriseFeatureEnabled(EnterpriseEditionFeature.ExternalSecrets)) {
|
2024-07-08 03:25:18 -07:00
|
|
|
promises.push(externalSecretsStore.fetchAllSecrets());
|
2024-05-23 01:42:10 -07:00
|
|
|
}
|
2024-07-08 03:25:18 -07:00
|
|
|
|
|
|
|
if (nodeTypesStore.allNodeTypes.length === 0) {
|
|
|
|
promises.push(nodeTypesStore.getNodeTypes());
|
|
|
|
}
|
|
|
|
|
|
|
|
return promises;
|
|
|
|
})();
|
|
|
|
|
|
|
|
// @TODO Implement this
|
|
|
|
// this.clipboard.onPaste.value = this.onClipboardPasteEvent;
|
2024-05-23 01:42:10 -07:00
|
|
|
|
|
|
|
try {
|
|
|
|
await Promise.all(loadPromises);
|
|
|
|
} catch (error) {
|
2024-07-08 03:25:18 -07:00
|
|
|
toast.showError(
|
2024-05-23 01:42:10 -07:00
|
|
|
error,
|
|
|
|
i18n.baseText('nodeView.showError.mounted1.title'),
|
|
|
|
i18n.baseText('nodeView.showError.mounted1.message') + ':',
|
|
|
|
);
|
2024-07-08 03:25:18 -07:00
|
|
|
return;
|
|
|
|
} finally {
|
|
|
|
canvasStore.stopLoading();
|
|
|
|
isLoading.value = false;
|
2024-05-23 01:42:10 -07:00
|
|
|
}
|
|
|
|
|
2024-07-08 03:25:18 -07:00
|
|
|
setTimeout(() => {
|
|
|
|
void usersStore.showPersonalizationSurvey();
|
|
|
|
}, 0);
|
2024-05-23 01:42:10 -07:00
|
|
|
|
2024-07-08 03:25:18 -07:00
|
|
|
// @TODO: This currently breaks since front-end hooks are still not updated to work with pinia store
|
|
|
|
void externalHooks.run('nodeView.mount').catch(() => {});
|
2024-06-25 02:11:44 -07:00
|
|
|
|
2024-07-08 03:25:18 -07:00
|
|
|
// @TODO maybe we can find a better way to handle this
|
|
|
|
canvasStore.isDemo = isDemoRoute.value;
|
2024-05-23 01:42:10 -07:00
|
|
|
}
|
|
|
|
|
2024-06-25 02:11:44 -07:00
|
|
|
async function initializeView() {
|
|
|
|
// In case the workflow got saved we do not have to run init
|
|
|
|
// as only the route changed but all the needed data is already loaded
|
|
|
|
if (route.params.action === 'workflowSave') {
|
|
|
|
uiStore.stateIsDirty = false;
|
|
|
|
return;
|
|
|
|
}
|
2024-06-04 05:36:27 -07:00
|
|
|
|
2024-06-25 02:11:44 -07:00
|
|
|
// This function is called on route change as well, so we need to do the following:
|
|
|
|
// - if the redirect is blank, then do nothing
|
|
|
|
// - if the route is the template import view, then open the template
|
|
|
|
// - if the user is leaving the current view without saving the changes, then show a confirmation modal
|
|
|
|
if (isBlankRedirect.value) {
|
|
|
|
isBlankRedirect.value = false;
|
|
|
|
} else if (route.name === VIEWS.TEMPLATE_IMPORT) {
|
|
|
|
// @TODO Implement template import
|
|
|
|
// const templateId = route.params.id;
|
|
|
|
// await openWorkflowTemplate(templateId.toString());
|
|
|
|
} else {
|
|
|
|
historyStore.reset();
|
|
|
|
|
|
|
|
// If there is no workflow id, treat it as a new workflow
|
2024-07-08 05:57:42 -07:00
|
|
|
if (!workflowId.value || isNewWorkflowRoute.value) {
|
2024-06-25 02:11:44 -07:00
|
|
|
if (route.meta?.nodeView === true) {
|
|
|
|
await initializeViewForNewWorkflow();
|
|
|
|
}
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Load workflow data
|
|
|
|
try {
|
2024-07-08 05:57:42 -07:00
|
|
|
await workflowsStore.fetchWorkflow(workflowId.value);
|
2024-06-25 02:11:44 -07:00
|
|
|
|
|
|
|
titleChange.titleSet(workflow.value.name, 'IDLE');
|
2024-07-08 03:25:18 -07:00
|
|
|
await openWorkflow(workflow.value);
|
|
|
|
await checkAndInitDebugMode();
|
2024-06-25 02:11:44 -07:00
|
|
|
|
|
|
|
trackOpenWorkflowFromOnboardingTemplate();
|
|
|
|
} catch (error) {
|
|
|
|
toast.showError(error, i18n.baseText('openWorkflow.workflowNotFoundError'));
|
|
|
|
|
|
|
|
void router.push({
|
2024-07-09 05:58:36 -07:00
|
|
|
name: VIEWS.NEW_WORKFLOW,
|
2024-06-25 02:11:44 -07:00
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-07-08 03:25:18 -07:00
|
|
|
nodeHelpers.updateNodesInputIssues();
|
|
|
|
nodeHelpers.updateNodesCredentialsIssues();
|
|
|
|
nodeHelpers.updateNodesParameterIssues();
|
|
|
|
|
2024-06-25 02:11:44 -07:00
|
|
|
await loadCredentials();
|
2024-07-08 03:25:18 -07:00
|
|
|
|
2024-06-25 02:11:44 -07:00
|
|
|
uiStore.nodeViewInitialized = true;
|
|
|
|
|
|
|
|
// Once view is initialized, pick up all toast notifications
|
|
|
|
// waiting in the store and display them
|
|
|
|
toast.showNotificationForViews([VIEWS.WORKFLOW, VIEWS.NEW_WORKFLOW]);
|
2024-06-17 05:46:55 -07:00
|
|
|
}
|
|
|
|
|
2024-06-25 02:11:44 -07:00
|
|
|
async function initializeViewForNewWorkflow() {
|
|
|
|
resetWorkspace();
|
|
|
|
|
|
|
|
await workflowsStore.getNewWorkflowData(undefined, projectsStore.currentProjectId);
|
|
|
|
|
|
|
|
workflowsStore.currentWorkflowExecutions = [];
|
|
|
|
executionsStore.activeExecution = null;
|
|
|
|
uiStore.stateIsDirty = false;
|
|
|
|
uiStore.nodeViewInitialized = true;
|
|
|
|
executionsStore.activeExecution = null;
|
|
|
|
|
2024-07-08 03:25:18 -07:00
|
|
|
makeNewWorkflowShareable();
|
|
|
|
await runAutoAddManualTriggerExperiment();
|
|
|
|
}
|
2024-06-25 02:11:44 -07:00
|
|
|
|
2024-07-08 03:25:18 -07:00
|
|
|
/**
|
|
|
|
* Pre-populate the canvas with the manual trigger node
|
|
|
|
* if the experiment is enabled and the user is in the variant group
|
|
|
|
*/
|
|
|
|
async function runAutoAddManualTriggerExperiment() {
|
2024-06-25 02:11:44 -07:00
|
|
|
if (
|
2024-07-08 03:25:18 -07:00
|
|
|
posthog.getVariant(CANVAS_AUTO_ADD_MANUAL_TRIGGER_EXPERIMENT.name) !==
|
2024-06-25 02:11:44 -07:00
|
|
|
CANVAS_AUTO_ADD_MANUAL_TRIGGER_EXPERIMENT.variant
|
|
|
|
) {
|
2024-07-08 03:25:18 -07:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
const manualTriggerNode = canvasStore.getAutoAddManualTriggerNode();
|
|
|
|
if (manualTriggerNode) {
|
|
|
|
await addNodes([manualTriggerNode]);
|
|
|
|
uiStore.lastSelectedNode = manualTriggerNode.name;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-06-25 02:11:44 -07:00
|
|
|
function resetWorkspace() {
|
2024-07-10 02:22:31 -07:00
|
|
|
onOpenNodeCreator({ createNodeActive: false });
|
2024-06-25 02:11:44 -07:00
|
|
|
nodeCreatorStore.setShowScrim(false);
|
2024-05-23 01:42:10 -07:00
|
|
|
|
2024-06-25 02:11:44 -07:00
|
|
|
// Make sure that if there is a waiting test-webhook that it gets removed
|
|
|
|
if (isExecutionWaitingForWebhook.value) {
|
|
|
|
try {
|
|
|
|
void workflowsStore.removeTestWebhook(workflowsStore.workflowId);
|
|
|
|
} catch (error) {}
|
|
|
|
}
|
2024-07-08 03:25:18 -07:00
|
|
|
workflowsStore.resetWorkflow();
|
2024-06-25 02:11:44 -07:00
|
|
|
workflowsStore.resetState();
|
2024-05-23 01:42:10 -07:00
|
|
|
|
2024-07-08 03:25:18 -07:00
|
|
|
uiStore.removeActiveAction('workflowRunning');
|
2024-06-25 02:11:44 -07:00
|
|
|
uiStore.resetSelectedNodes();
|
|
|
|
uiStore.nodeViewOffsetPosition = [0, 0]; // @TODO Not sure if needed
|
|
|
|
|
|
|
|
// this.credentialsUpdated = false;
|
2024-05-23 01:42:10 -07:00
|
|
|
}
|
|
|
|
|
2024-07-08 03:25:18 -07:00
|
|
|
/**
|
|
|
|
* Workflow
|
|
|
|
*/
|
|
|
|
|
|
|
|
async function openWorkflow(data: IWorkflowDb) {
|
|
|
|
const selectedExecution = executionsStore.activeExecution;
|
|
|
|
|
|
|
|
resetWorkspace();
|
|
|
|
|
2024-07-10 01:53:27 -07:00
|
|
|
await workflowHelpers.initState(data);
|
|
|
|
await addNodes(data.nodes);
|
|
|
|
workflowsStore.setConnections(data.connections);
|
2024-07-08 03:25:18 -07:00
|
|
|
|
|
|
|
if (data.sharedWithProjects) {
|
|
|
|
workflowsEEStore.setWorkflowSharedWith({
|
|
|
|
workflowId: data.id,
|
|
|
|
sharedWithProjects: data.sharedWithProjects,
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
if (data.usedCredentials) {
|
|
|
|
workflowsStore.setUsedCredentials(data.usedCredentials);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!nodeHelpers.credentialsUpdated.value) {
|
|
|
|
uiStore.stateIsDirty = false;
|
|
|
|
}
|
|
|
|
|
|
|
|
void externalHooks.run('workflow.open', {
|
|
|
|
workflowId: data.id,
|
|
|
|
workflowName: data.name,
|
|
|
|
});
|
|
|
|
|
|
|
|
if (selectedExecution?.workflowId !== data.id) {
|
|
|
|
executionsStore.activeExecution = null;
|
|
|
|
workflowsStore.currentWorkflowExecutions = [];
|
|
|
|
} else {
|
|
|
|
executionsStore.activeExecution = selectedExecution;
|
|
|
|
}
|
|
|
|
|
|
|
|
await projectsStore.setProjectNavActiveIdByWorkflowHomeProject(workflow.value.homeProject);
|
|
|
|
}
|
|
|
|
|
2024-06-25 02:11:44 -07:00
|
|
|
function trackOpenWorkflowFromOnboardingTemplate() {
|
|
|
|
if (workflow.value.meta?.onboardingId) {
|
|
|
|
telemetry.track(
|
|
|
|
`User opened workflow from onboarding template with ID ${workflow.value.meta.onboardingId}`,
|
|
|
|
{
|
|
|
|
workflow_id: workflowId.value,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
withPostHog: true,
|
|
|
|
},
|
|
|
|
);
|
|
|
|
}
|
2024-05-23 01:42:10 -07:00
|
|
|
}
|
|
|
|
|
2024-07-08 03:25:18 -07:00
|
|
|
function makeNewWorkflowShareable() {
|
|
|
|
const { currentProject, personalProject } = projectsStore;
|
|
|
|
const homeProject = currentProject ?? personalProject ?? {};
|
|
|
|
const scopes = currentProject?.scopes ?? personalProject?.scopes ?? [];
|
|
|
|
|
|
|
|
workflowsStore.workflow.homeProject = homeProject as ProjectSharingData;
|
|
|
|
workflowsStore.workflow.scopes = scopes;
|
|
|
|
}
|
|
|
|
|
2024-06-25 02:11:44 -07:00
|
|
|
/**
|
|
|
|
* Nodes
|
|
|
|
*/
|
|
|
|
|
2024-07-11 03:03:46 -07:00
|
|
|
const triggerNodes = computed(() => {
|
|
|
|
return editableWorkflow.value.nodes.filter(
|
|
|
|
(node) => node.type === START_NODE_TYPE || nodeTypesStore.isTriggerNode(node.type),
|
|
|
|
);
|
|
|
|
});
|
|
|
|
|
|
|
|
const containsTriggerNodes = computed(() => triggerNodes.value.length > 0);
|
|
|
|
|
|
|
|
const allTriggerNodesDisabled = computed(() => {
|
|
|
|
const disabledTriggerNodes = triggerNodes.value.filter((node) => node.disabled);
|
|
|
|
return disabledTriggerNodes.length === triggerNodes.value.length;
|
|
|
|
});
|
|
|
|
|
|
|
|
function onUpdateNodePosition(id: string, position: CanvasNode['position']) {
|
2024-06-04 05:36:27 -07:00
|
|
|
updateNodePosition(id, position, { trackHistory: true });
|
|
|
|
}
|
|
|
|
|
|
|
|
function onDeleteNode(id: string) {
|
|
|
|
deleteNode(id, { trackHistory: true });
|
|
|
|
}
|
|
|
|
|
|
|
|
function onRevertDeleteNode({ node }: { node: INodeUi }) {
|
|
|
|
revertDeleteNode(node);
|
2024-05-23 01:42:10 -07:00
|
|
|
}
|
|
|
|
|
2024-06-26 06:56:58 -07:00
|
|
|
function onToggleNodeDisabled(id: string) {
|
|
|
|
if (!checkIfEditingIsAllowed()) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
toggleNodeDisabled(id);
|
|
|
|
}
|
|
|
|
|
2024-06-17 05:46:55 -07:00
|
|
|
function onSetNodeActive(id: string) {
|
|
|
|
setNodeActive(id);
|
|
|
|
}
|
|
|
|
|
2024-06-25 02:11:44 -07:00
|
|
|
function onSetNodeSelected(id?: string) {
|
|
|
|
setNodeSelected(id);
|
|
|
|
}
|
|
|
|
|
|
|
|
function onRenameNode(parameterData: IUpdateInformation) {
|
|
|
|
// The name changed. Do not forget to change the connections as well
|
|
|
|
if (parameterData.name === 'name' && parameterData.oldValue) {
|
|
|
|
void renameNode(parameterData.oldValue as string, parameterData.value as string);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
async function onRevertRenameNode({
|
|
|
|
currentName,
|
|
|
|
newName,
|
|
|
|
}: {
|
|
|
|
currentName: string;
|
|
|
|
newName: string;
|
|
|
|
}) {
|
|
|
|
await revertRenameNode(currentName, newName);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Credentials
|
|
|
|
*/
|
|
|
|
|
|
|
|
async function loadCredentials() {
|
|
|
|
let options: { workflowId: string } | { projectId: string };
|
|
|
|
|
|
|
|
if (workflow.value) {
|
|
|
|
options = { workflowId: workflow.value.id };
|
|
|
|
} else {
|
|
|
|
const queryParam =
|
|
|
|
typeof route.query?.projectId === 'string' ? route.query?.projectId : undefined;
|
|
|
|
const projectId = queryParam ?? projectsStore.personalProject?.id;
|
|
|
|
if (projectId === undefined) {
|
|
|
|
throw new Error(
|
|
|
|
'Could not find projectId in the query nor could I find the personal project in the project store',
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
options = { projectId };
|
|
|
|
}
|
|
|
|
|
|
|
|
await credentialsStore.fetchAllCredentialsForWorkflow(options);
|
|
|
|
}
|
|
|
|
|
2024-05-23 01:42:10 -07:00
|
|
|
/**
|
2024-06-25 02:11:44 -07:00
|
|
|
* Connections
|
2024-05-23 01:42:10 -07:00
|
|
|
*/
|
2024-06-17 05:46:55 -07:00
|
|
|
|
2024-06-04 05:36:27 -07:00
|
|
|
function onCreateConnection(connection: Connection) {
|
|
|
|
createConnection(connection);
|
|
|
|
}
|
2024-05-23 01:42:10 -07:00
|
|
|
|
2024-07-10 02:22:31 -07:00
|
|
|
function onCreateConnectionCancelled(event: ConnectStartEvent) {
|
|
|
|
setTimeout(() => {
|
|
|
|
nodeCreatorStore.openNodeCreatorForConnectingNode({
|
2024-07-11 07:05:51 -07:00
|
|
|
connection: {
|
|
|
|
source: event.nodeId,
|
|
|
|
sourceHandle: event.handleId,
|
|
|
|
},
|
2024-07-10 02:22:31 -07:00
|
|
|
eventSource: NODE_CREATOR_OPEN_SOURCES.NODE_CONNECTION_DROP,
|
|
|
|
});
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2024-06-04 05:36:27 -07:00
|
|
|
function onDeleteConnection(connection: Connection) {
|
|
|
|
deleteConnection(connection, { trackHistory: true });
|
2024-05-23 01:42:10 -07:00
|
|
|
}
|
|
|
|
|
2024-06-04 05:36:27 -07:00
|
|
|
function onRevertDeleteConnection({ connection }: { connection: [IConnection, IConnection] }) {
|
|
|
|
revertDeleteConnection(connection);
|
2024-05-23 01:42:10 -07:00
|
|
|
}
|
|
|
|
|
2024-06-25 02:11:44 -07:00
|
|
|
/**
|
|
|
|
* Import / Export
|
|
|
|
*/
|
|
|
|
|
|
|
|
async function importWorkflowExact(_workflow: IWorkflowDataUpdate) {
|
|
|
|
// @TODO
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Node creator
|
|
|
|
*/
|
|
|
|
|
|
|
|
async function onAddNodesAndConnections(
|
2024-05-23 01:42:10 -07:00
|
|
|
{ nodes, connections }: AddedNodesAndConnections,
|
|
|
|
dragAndDrop = false,
|
|
|
|
position?: XYPosition,
|
|
|
|
) {
|
2024-06-25 02:11:44 -07:00
|
|
|
if (!checkIfEditingIsAllowed()) {
|
|
|
|
return;
|
2024-05-23 01:42:10 -07:00
|
|
|
}
|
|
|
|
|
2024-06-25 02:11:44 -07:00
|
|
|
await addNodes(nodes, { dragAndDrop, position });
|
|
|
|
await addConnections(connections, {
|
|
|
|
offsetIndex: editableWorkflow.value.nodes.length - nodes.length,
|
|
|
|
});
|
2024-07-11 07:05:51 -07:00
|
|
|
|
|
|
|
uiStore.lastSelectedNodeConnection = null;
|
2024-06-25 02:11:44 -07:00
|
|
|
}
|
2024-06-17 05:46:55 -07:00
|
|
|
|
2024-06-25 02:11:44 -07:00
|
|
|
async function onSwitchActiveNode(nodeName: string) {
|
|
|
|
setNodeActiveByName(nodeName);
|
2024-05-23 01:42:10 -07:00
|
|
|
}
|
|
|
|
|
2024-07-10 02:22:31 -07:00
|
|
|
async function onOpenSelectiveNodeCreator(node: string, connectionType: NodeConnectionType) {
|
2024-06-25 02:11:44 -07:00
|
|
|
nodeCreatorStore.openSelectiveNodeCreator({ node, connectionType });
|
|
|
|
}
|
2024-05-23 01:42:10 -07:00
|
|
|
|
2024-07-10 02:22:31 -07:00
|
|
|
function onOpenNodeCreator(options: ToggleNodeCreatorOptions) {
|
2024-06-25 02:11:44 -07:00
|
|
|
nodeCreatorStore.openNodeCreator(options);
|
|
|
|
}
|
2024-05-23 01:42:10 -07:00
|
|
|
|
2024-07-11 07:05:51 -07:00
|
|
|
function onClickConnectionAdd(connection: Connection) {
|
|
|
|
nodeCreatorStore.openNodeCreatorForConnectingNode({
|
|
|
|
connection,
|
|
|
|
eventSource: NODE_CREATOR_OPEN_SOURCES.NODE_CONNECTION_ACTION,
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2024-06-25 02:11:44 -07:00
|
|
|
/**
|
|
|
|
* Executions
|
|
|
|
*/
|
2024-05-23 01:42:10 -07:00
|
|
|
|
2024-07-10 01:53:27 -07:00
|
|
|
const isStoppingExecution = ref(false);
|
|
|
|
|
|
|
|
const isWorkflowRunning = computed(() => uiStore.isActionActive.workflowRunning);
|
|
|
|
const isExecutionWaitingForWebhook = computed(() => workflowsStore.executionWaitingForWebhook);
|
|
|
|
|
2024-07-11 03:03:46 -07:00
|
|
|
const isExecutionDisabled = computed(() => {
|
|
|
|
if (
|
|
|
|
containsChatTriggerNodes.value &&
|
|
|
|
isOnlyChatTriggerNodeActive.value &&
|
|
|
|
!chatTriggerNodePinnedData.value
|
|
|
|
) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
return !containsTriggerNodes.value || allTriggerNodesDisabled.value;
|
|
|
|
});
|
|
|
|
|
2024-07-10 01:53:27 -07:00
|
|
|
const isStopExecutionButtonVisible = computed(
|
|
|
|
() => isWorkflowRunning.value && !isExecutionWaitingForWebhook.value,
|
|
|
|
);
|
|
|
|
const isStopWaitingForWebhookButtonVisible = computed(
|
|
|
|
() => isWorkflowRunning.value && isExecutionWaitingForWebhook.value,
|
|
|
|
);
|
2024-07-11 06:37:01 -07:00
|
|
|
const isClearExecutionButtonVisible = computed(
|
|
|
|
() =>
|
|
|
|
!isReadOnlyRoute.value &&
|
|
|
|
!isReadOnlyEnvironment.value &&
|
|
|
|
!isWorkflowRunning.value &&
|
|
|
|
!allTriggerNodesDisabled.value &&
|
|
|
|
workflowExecutionData.value,
|
|
|
|
);
|
|
|
|
|
|
|
|
const workflowExecutionData = computed(() => workflowsStore.workflowExecutionData);
|
2024-07-10 01:53:27 -07:00
|
|
|
|
2024-06-25 02:11:44 -07:00
|
|
|
async function onRunWorkflow() {
|
2024-07-08 03:25:18 -07:00
|
|
|
trackRunWorkflow();
|
2024-05-23 01:42:10 -07:00
|
|
|
|
2024-07-08 03:25:18 -07:00
|
|
|
await runWorkflow({});
|
2024-05-23 01:42:10 -07:00
|
|
|
}
|
|
|
|
|
2024-07-08 03:25:18 -07:00
|
|
|
function trackRunWorkflow() {
|
|
|
|
void workflowHelpers.getWorkflowDataToSave().then((workflowData) => {
|
|
|
|
const telemetryPayload = {
|
|
|
|
workflow_id: workflowId.value,
|
|
|
|
node_graph_string: JSON.stringify(
|
|
|
|
TelemetryHelpers.generateNodesGraph(
|
|
|
|
workflowData as IWorkflowBase,
|
|
|
|
workflowHelpers.getNodeTypes(),
|
|
|
|
{ isCloudDeployment: settingsStore.isCloudDeployment },
|
|
|
|
).nodeGraph,
|
|
|
|
),
|
|
|
|
};
|
|
|
|
telemetry.track('User clicked execute workflow button', telemetryPayload);
|
|
|
|
void externalHooks.run('nodeView.onRunWorkflow', telemetryPayload);
|
|
|
|
});
|
2024-06-25 02:11:44 -07:00
|
|
|
}
|
2024-05-23 01:42:10 -07:00
|
|
|
|
2024-07-10 00:56:39 -07:00
|
|
|
async function onRunWorkflowToNode(id: string) {
|
|
|
|
const node = workflowsStore.getNodeById(id);
|
|
|
|
if (!node) return;
|
|
|
|
|
|
|
|
trackRunWorkflowToNode(node);
|
|
|
|
await runWorkflow({ destinationNode: node.name, source: 'Node.executeNode' });
|
|
|
|
}
|
|
|
|
|
|
|
|
function trackRunWorkflowToNode(node: INodeUi) {
|
|
|
|
const telemetryPayload = {
|
|
|
|
node_type: node.type,
|
|
|
|
workflow_id: workflowsStore.workflowId,
|
|
|
|
source: 'canvas',
|
|
|
|
push_ref: ndvStore.pushRef,
|
|
|
|
};
|
|
|
|
|
|
|
|
telemetry.track('User clicked execute node button', telemetryPayload);
|
|
|
|
void externalHooks.run('nodeView.onRunNode', telemetryPayload);
|
|
|
|
}
|
|
|
|
|
2024-07-08 03:25:18 -07:00
|
|
|
async function openExecution(_executionId: string) {
|
|
|
|
// @TODO
|
2024-05-23 01:42:10 -07:00
|
|
|
}
|
|
|
|
|
2024-07-10 01:53:27 -07:00
|
|
|
async function onStopExecution() {
|
|
|
|
isStoppingExecution.value = true;
|
|
|
|
await stopCurrentExecution();
|
|
|
|
isStoppingExecution.value = false;
|
|
|
|
}
|
|
|
|
|
|
|
|
async function onStopWaitingForWebhook() {
|
|
|
|
await stopWaitingForWebhook();
|
|
|
|
}
|
|
|
|
|
2024-07-11 06:37:01 -07:00
|
|
|
async function onClearExecutionData() {
|
|
|
|
workflowsStore.workflowExecutionData = null;
|
|
|
|
nodeHelpers.updateNodesExecutionIssues();
|
|
|
|
}
|
|
|
|
|
2024-07-11 03:03:46 -07:00
|
|
|
function onRunWorkflowButtonMouseEnter() {
|
|
|
|
nodeViewEventBus.emit('runWorkflowButton:mouseenter');
|
|
|
|
}
|
|
|
|
|
|
|
|
function onRunWorkflowButtonMouseLeave() {
|
|
|
|
nodeViewEventBus.emit('runWorkflowButton:mouseleave');
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Chat
|
|
|
|
*/
|
|
|
|
|
|
|
|
const chatTriggerNode = computed(() => {
|
|
|
|
return editableWorkflow.value.nodes.find((node) => node.type === CHAT_TRIGGER_NODE_TYPE);
|
|
|
|
});
|
|
|
|
|
|
|
|
const containsChatTriggerNodes = computed(() => {
|
|
|
|
return (
|
|
|
|
!isExecutionWaitingForWebhook.value &&
|
|
|
|
!!editableWorkflow.value.nodes.find(
|
|
|
|
(node) =>
|
|
|
|
[MANUAL_CHAT_TRIGGER_NODE_TYPE, CHAT_TRIGGER_NODE_TYPE].includes(node.type) &&
|
|
|
|
node.disabled !== true,
|
|
|
|
)
|
|
|
|
);
|
|
|
|
});
|
|
|
|
|
|
|
|
const isOnlyChatTriggerNodeActive = computed(() => {
|
|
|
|
return triggerNodes.value.every((node) => node.disabled || node.type === CHAT_TRIGGER_NODE_TYPE);
|
|
|
|
});
|
|
|
|
|
|
|
|
const chatTriggerNodePinnedData = computed(() => {
|
|
|
|
if (!chatTriggerNode.value) return null;
|
|
|
|
|
|
|
|
return workflowsStore.pinDataByNodeName(chatTriggerNode.value.name);
|
|
|
|
});
|
|
|
|
|
2024-05-23 01:42:10 -07:00
|
|
|
/**
|
2024-07-10 01:53:27 -07:00
|
|
|
* Keyboard
|
2024-05-23 01:42:10 -07:00
|
|
|
*/
|
2024-06-25 02:11:44 -07:00
|
|
|
|
|
|
|
function addKeyboardEventBindings() {
|
|
|
|
// document.addEventListener('keydown', this.keyDown);
|
|
|
|
// document.addEventListener('keyup', this.keyUp);
|
|
|
|
}
|
|
|
|
|
|
|
|
function removeKeyboardEventBindings() {
|
|
|
|
// document.removeEventListener('keydown', this.keyDown);
|
|
|
|
// document.removeEventListener('keyup', this.keyUp);
|
|
|
|
}
|
2024-05-23 01:42:10 -07:00
|
|
|
|
|
|
|
/**
|
2024-06-25 02:11:44 -07:00
|
|
|
* History events
|
2024-05-23 01:42:10 -07:00
|
|
|
*/
|
2024-06-25 02:11:44 -07:00
|
|
|
|
|
|
|
function addUndoRedoEventBindings() {
|
|
|
|
// historyBus.on('nodeMove', onMoveNode);
|
|
|
|
// historyBus.on('revertAddNode', onRevertAddNode);
|
|
|
|
historyBus.on('revertRemoveNode', onRevertDeleteNode);
|
|
|
|
// historyBus.on('revertAddConnection', onRevertAddConnection);
|
|
|
|
historyBus.on('revertRemoveConnection', onRevertDeleteConnection);
|
|
|
|
historyBus.on('revertRenameNode', onRevertRenameNode);
|
|
|
|
// historyBus.on('enableNodeToggle', onRevertEnableToggle);
|
2024-05-23 01:42:10 -07:00
|
|
|
}
|
|
|
|
|
2024-06-25 02:11:44 -07:00
|
|
|
function removeUndoRedoEventBindings() {
|
|
|
|
// historyBus.off('nodeMove', onMoveNode);
|
|
|
|
// historyBus.off('revertAddNode', onRevertAddNode);
|
|
|
|
historyBus.off('revertRemoveNode', onRevertDeleteNode);
|
|
|
|
// historyBus.off('revertAddConnection', onRevertAddConnection);
|
|
|
|
historyBus.off('revertRemoveConnection', onRevertDeleteConnection);
|
|
|
|
historyBus.off('revertRenameNode', onRevertRenameNode);
|
|
|
|
// historyBus.off('enableNodeToggle', onRevertEnableToggle);
|
|
|
|
}
|
2024-05-23 01:42:10 -07:00
|
|
|
|
2024-07-08 03:25:18 -07:00
|
|
|
/**
|
|
|
|
* Source control
|
|
|
|
*/
|
|
|
|
|
|
|
|
async function onSourceControlPull() {
|
|
|
|
try {
|
|
|
|
await Promise.all([
|
|
|
|
environmentsStore.fetchAllVariables(),
|
|
|
|
tagsStore.fetchAll(),
|
|
|
|
loadCredentials(),
|
|
|
|
]);
|
|
|
|
|
|
|
|
if (workflowId.value !== null && !uiStore.stateIsDirty) {
|
|
|
|
const workflowData = await workflowsStore.fetchWorkflow(workflowId.value);
|
|
|
|
if (workflowData) {
|
|
|
|
titleChange.titleSet(workflowData.name, 'IDLE');
|
|
|
|
await openWorkflow(workflowData);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} catch (error) {
|
|
|
|
console.error(error);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function addSourceControlEventBindings() {
|
|
|
|
sourceControlEventBus.on('pull', onSourceControlPull);
|
|
|
|
}
|
|
|
|
|
|
|
|
function removeSourceControlEventBindings() {
|
|
|
|
sourceControlEventBus.off('pull', onSourceControlPull);
|
|
|
|
}
|
|
|
|
|
2024-06-25 02:11:44 -07:00
|
|
|
/**
|
|
|
|
* Post message events
|
|
|
|
*/
|
2024-05-23 01:42:10 -07:00
|
|
|
|
2024-06-25 02:11:44 -07:00
|
|
|
function addPostMessageEventBindings() {
|
|
|
|
window.addEventListener('message', onPostMessageReceived);
|
|
|
|
|
|
|
|
if (window.parent) {
|
|
|
|
window.parent.postMessage(
|
|
|
|
JSON.stringify({ command: 'n8nReady', version: rootStore.versionCli }),
|
|
|
|
'*',
|
|
|
|
);
|
2024-05-23 01:42:10 -07:00
|
|
|
}
|
2024-06-25 02:11:44 -07:00
|
|
|
}
|
2024-05-23 01:42:10 -07:00
|
|
|
|
2024-06-25 02:11:44 -07:00
|
|
|
function removePostMessageEventBindings() {
|
|
|
|
window.removeEventListener('message', onPostMessageReceived);
|
2024-05-23 01:42:10 -07:00
|
|
|
}
|
2024-06-17 05:46:55 -07:00
|
|
|
|
|
|
|
async function onPostMessageReceived(message: MessageEvent) {
|
|
|
|
if (!message || typeof message.data !== 'string' || !message.data?.includes?.('"command"')) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
try {
|
|
|
|
const json = JSON.parse(message.data);
|
|
|
|
if (json && json.command === 'openWorkflow') {
|
|
|
|
try {
|
|
|
|
await importWorkflowExact(json.data);
|
|
|
|
canOpenNDV.value = json.canOpenNDV ?? true;
|
|
|
|
hideNodeIssues.value = json.hideNodeIssues ?? false;
|
|
|
|
isExecutionPreview.value = false;
|
|
|
|
} catch (e) {
|
|
|
|
if (window.top) {
|
|
|
|
window.top.postMessage(
|
|
|
|
JSON.stringify({
|
|
|
|
command: 'error',
|
|
|
|
message: i18n.baseText('openWorkflow.workflowImportError'),
|
|
|
|
}),
|
|
|
|
'*',
|
|
|
|
);
|
|
|
|
}
|
2024-06-25 02:11:44 -07:00
|
|
|
toast.showError(e, i18n.baseText('openWorkflow.workflowImportError'));
|
2024-06-17 05:46:55 -07:00
|
|
|
}
|
|
|
|
} else if (json && json.command === 'openExecution') {
|
|
|
|
try {
|
|
|
|
// If this NodeView is used in preview mode (in iframe) it will not have access to the main app store
|
|
|
|
// so everything it needs has to be sent using post messages and passed down to child components
|
|
|
|
isProductionExecutionPreview.value = json.executionMode !== 'manual';
|
|
|
|
|
|
|
|
await openExecution(json.executionId);
|
|
|
|
canOpenNDV.value = json.canOpenNDV ?? true;
|
|
|
|
hideNodeIssues.value = json.hideNodeIssues ?? false;
|
|
|
|
isExecutionPreview.value = true;
|
|
|
|
} catch (e) {
|
|
|
|
if (window.top) {
|
|
|
|
window.top.postMessage(
|
|
|
|
JSON.stringify({
|
|
|
|
command: 'error',
|
|
|
|
message: i18n.baseText('nodeView.showError.openExecution.title'),
|
|
|
|
}),
|
|
|
|
'*',
|
|
|
|
);
|
|
|
|
}
|
|
|
|
toast.showMessage({
|
|
|
|
title: i18n.baseText('nodeView.showError.openExecution.title'),
|
|
|
|
message: (e as Error).message,
|
|
|
|
type: 'error',
|
|
|
|
});
|
|
|
|
}
|
|
|
|
} else if (json?.command === 'setActiveExecution') {
|
|
|
|
executionsStore.activeExecution = (await executionsStore.fetchExecution(
|
|
|
|
json.executionId,
|
|
|
|
)) as ExecutionSummary;
|
|
|
|
}
|
|
|
|
} catch (e) {}
|
|
|
|
}
|
|
|
|
|
2024-06-25 02:11:44 -07:00
|
|
|
/**
|
|
|
|
* Permission checks
|
|
|
|
*/
|
2024-06-17 05:46:55 -07:00
|
|
|
|
2024-06-25 02:11:44 -07:00
|
|
|
function checkIfEditingIsAllowed(): boolean {
|
|
|
|
if (readOnlyNotification.value?.visible) {
|
|
|
|
return false;
|
|
|
|
}
|
2024-06-17 05:46:55 -07:00
|
|
|
|
2024-06-25 02:11:44 -07:00
|
|
|
if (isReadOnlyRoute.value || isReadOnlyEnvironment.value) {
|
|
|
|
const messageContext = isReadOnlyRoute.value ? 'executions' : 'workflows';
|
|
|
|
readOnlyNotification.value = toast.showMessage({
|
|
|
|
title: i18n.baseText(
|
|
|
|
isReadOnlyEnvironment.value
|
|
|
|
? `readOnlyEnv.showMessage.${messageContext}.title`
|
|
|
|
: 'readOnly.showMessage.executions.title',
|
|
|
|
),
|
|
|
|
message: i18n.baseText(
|
|
|
|
isReadOnlyEnvironment.value
|
|
|
|
? `readOnlyEnv.showMessage.${messageContext}.message`
|
|
|
|
: 'readOnly.showMessage.executions.message',
|
|
|
|
),
|
|
|
|
type: 'info',
|
|
|
|
dangerouslyUseHTMLString: true,
|
|
|
|
}) as unknown as { visible: boolean };
|
2024-06-17 05:46:55 -07:00
|
|
|
|
2024-06-25 02:11:44 -07:00
|
|
|
return false;
|
|
|
|
}
|
2024-06-17 05:46:55 -07:00
|
|
|
|
2024-06-25 02:11:44 -07:00
|
|
|
return true;
|
2024-06-17 05:46:55 -07:00
|
|
|
}
|
|
|
|
|
2024-07-08 03:25:18 -07:00
|
|
|
function checkIfRouteIsAllowed() {
|
|
|
|
if (
|
|
|
|
isReadOnlyEnvironment.value &&
|
|
|
|
[VIEWS.NEW_WORKFLOW, VIEWS.TEMPLATE_IMPORT].find((view) => view === route.name)
|
|
|
|
) {
|
|
|
|
void nextTick(async () => {
|
|
|
|
resetWorkspace();
|
|
|
|
uiStore.stateIsDirty = false;
|
|
|
|
|
|
|
|
await router.replace({ name: VIEWS.HOMEPAGE });
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Debug mode
|
|
|
|
*/
|
|
|
|
|
|
|
|
async function checkAndInitDebugMode() {
|
|
|
|
if (route.name === VIEWS.EXECUTION_DEBUG) {
|
|
|
|
titleChange.titleSet(workflowsStore.workflowName, 'DEBUG');
|
|
|
|
if (!workflowsStore.isInDebugMode) {
|
|
|
|
await applyExecutionData(route.params.executionId as string);
|
|
|
|
workflowsStore.isInDebugMode = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-06-25 02:11:44 -07:00
|
|
|
/**
|
|
|
|
* Mouse events
|
|
|
|
*/
|
2024-06-17 05:46:55 -07:00
|
|
|
|
2024-07-11 03:03:46 -07:00
|
|
|
function onClickPane(position: CanvasNode['position']) {
|
2024-06-25 02:11:44 -07:00
|
|
|
lastClickPosition.value = [position.x, position.y];
|
|
|
|
canvasStore.newNodeInsertPosition = [position.x, position.y];
|
2024-06-17 05:46:55 -07:00
|
|
|
}
|
2024-06-25 02:11:44 -07:00
|
|
|
|
2024-07-08 03:25:18 -07:00
|
|
|
/**
|
|
|
|
* Custom Actions
|
|
|
|
*/
|
|
|
|
|
|
|
|
function registerCustomActions() {
|
|
|
|
// @TODO Implement these
|
|
|
|
// this.registerCustomAction({
|
|
|
|
// key: 'openNodeDetail',
|
|
|
|
// action: ({ node }: { node: string }) => {
|
|
|
|
// this.nodeSelectedByName(node, true);
|
|
|
|
// },
|
|
|
|
// });
|
|
|
|
//
|
|
|
|
// this.registerCustomAction({
|
|
|
|
// key: 'openSelectiveNodeCreator',
|
|
|
|
// action: this.openSelectiveNodeCreator,
|
|
|
|
// });
|
|
|
|
//
|
|
|
|
// this.registerCustomAction({
|
|
|
|
// key: 'showNodeCreator',
|
|
|
|
// action: () => {
|
|
|
|
// this.ndvStore.activeNodeName = null;
|
|
|
|
//
|
|
|
|
// void this.$nextTick(() => {
|
|
|
|
// this.showTriggerCreator(NODE_CREATOR_OPEN_SOURCES.TAB);
|
|
|
|
// });
|
|
|
|
// },
|
|
|
|
// });
|
|
|
|
}
|
|
|
|
|
2024-07-08 05:57:42 -07:00
|
|
|
/**
|
|
|
|
* Routing
|
|
|
|
*/
|
|
|
|
|
|
|
|
onBeforeRouteLeave(async (to, from, next) => {
|
|
|
|
const toNodeViewTab = getNodeViewTab(to);
|
|
|
|
|
|
|
|
if (
|
|
|
|
toNodeViewTab === MAIN_HEADER_TABS.EXECUTIONS ||
|
|
|
|
from.name === VIEWS.TEMPLATE_IMPORT ||
|
|
|
|
(toNodeViewTab === MAIN_HEADER_TABS.WORKFLOW && from.name === VIEWS.EXECUTION_DEBUG)
|
|
|
|
) {
|
|
|
|
next();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (uiStore.stateIsDirty && !isReadOnlyEnvironment.value) {
|
|
|
|
const confirmModal = await message.confirm(
|
|
|
|
i18n.baseText('generic.unsavedWork.confirmMessage.message'),
|
|
|
|
{
|
|
|
|
title: i18n.baseText('generic.unsavedWork.confirmMessage.headline'),
|
|
|
|
type: 'warning',
|
|
|
|
confirmButtonText: i18n.baseText('generic.unsavedWork.confirmMessage.confirmButtonText'),
|
|
|
|
cancelButtonText: i18n.baseText('generic.unsavedWork.confirmMessage.cancelButtonText'),
|
|
|
|
showClose: true,
|
|
|
|
},
|
|
|
|
);
|
|
|
|
|
|
|
|
if (confirmModal === MODAL_CONFIRM) {
|
|
|
|
// Make sure workflow id is empty when leaving the editor
|
|
|
|
workflowsStore.setWorkflowId(PLACEHOLDER_EMPTY_WORKFLOW_ID);
|
|
|
|
const saved = await workflowHelpers.saveCurrentWorkflow({}, false);
|
|
|
|
if (saved) {
|
|
|
|
await npsSurveyStore.fetchPromptsData();
|
|
|
|
}
|
|
|
|
uiStore.stateIsDirty = false;
|
|
|
|
|
2024-07-09 05:58:36 -07:00
|
|
|
if (from.name === VIEWS.NEW_WORKFLOW) {
|
2024-07-08 05:57:42 -07:00
|
|
|
// Replace the current route with the new workflow route
|
|
|
|
// before navigating to the new route when saving new workflow.
|
|
|
|
await router.replace({
|
2024-07-09 05:58:36 -07:00
|
|
|
name: VIEWS.WORKFLOW,
|
2024-07-08 05:57:42 -07:00
|
|
|
params: { name: workflowId.value },
|
|
|
|
});
|
|
|
|
|
|
|
|
await router.push(to);
|
|
|
|
} else {
|
|
|
|
next();
|
|
|
|
}
|
|
|
|
} else if (confirmModal === MODAL_CANCEL) {
|
|
|
|
workflowsStore.setWorkflowId(PLACEHOLDER_EMPTY_WORKFLOW_ID);
|
|
|
|
resetWorkspace();
|
|
|
|
uiStore.stateIsDirty = false;
|
|
|
|
next();
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
next();
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
2024-06-25 02:11:44 -07:00
|
|
|
/**
|
|
|
|
* Lifecycle
|
|
|
|
*/
|
|
|
|
|
2024-07-08 03:25:18 -07:00
|
|
|
onBeforeMount(() => {
|
|
|
|
if (!isDemoRoute.value) {
|
|
|
|
pushConnectionStore.pushConnect();
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
2024-06-25 02:11:44 -07:00
|
|
|
onMounted(async () => {
|
2024-07-08 03:25:18 -07:00
|
|
|
void initializeData().then(() => {
|
|
|
|
void initializeView();
|
|
|
|
|
|
|
|
checkIfRouteIsAllowed();
|
|
|
|
});
|
2024-06-25 02:11:44 -07:00
|
|
|
|
|
|
|
addUndoRedoEventBindings();
|
|
|
|
addPostMessageEventBindings();
|
|
|
|
addKeyboardEventBindings();
|
2024-07-08 03:25:18 -07:00
|
|
|
addSourceControlEventBindings();
|
|
|
|
|
|
|
|
registerCustomActions();
|
2024-06-25 02:11:44 -07:00
|
|
|
});
|
|
|
|
|
|
|
|
onBeforeUnmount(() => {
|
|
|
|
removeKeyboardEventBindings();
|
|
|
|
removePostMessageEventBindings();
|
|
|
|
removeUndoRedoEventBindings();
|
2024-07-08 03:25:18 -07:00
|
|
|
removeSourceControlEventBindings();
|
2024-06-25 02:11:44 -07:00
|
|
|
});
|
2024-05-23 01:42:10 -07:00
|
|
|
</script>
|
|
|
|
|
|
|
|
<template>
|
|
|
|
<WorkflowCanvas
|
|
|
|
v-if="editableWorkflow && editableWorkflowObject"
|
|
|
|
:workflow="editableWorkflow"
|
|
|
|
:workflow-object="editableWorkflowObject"
|
2024-07-11 03:03:46 -07:00
|
|
|
:fallback-nodes="fallbackNodes"
|
2024-06-04 05:36:27 -07:00
|
|
|
@update:node:position="onUpdateNodePosition"
|
2024-06-17 05:46:55 -07:00
|
|
|
@update:node:active="onSetNodeActive"
|
2024-06-25 02:11:44 -07:00
|
|
|
@update:node:selected="onSetNodeSelected"
|
2024-06-26 06:56:58 -07:00
|
|
|
@update:node:enabled="onToggleNodeDisabled"
|
2024-07-10 00:56:39 -07:00
|
|
|
@run:node="onRunWorkflowToNode"
|
2024-06-04 05:36:27 -07:00
|
|
|
@delete:node="onDeleteNode"
|
|
|
|
@create:connection="onCreateConnection"
|
2024-07-10 02:22:31 -07:00
|
|
|
@create:connection:cancelled="onCreateConnectionCancelled"
|
2024-06-04 05:36:27 -07:00
|
|
|
@delete:connection="onDeleteConnection"
|
2024-07-11 07:05:51 -07:00
|
|
|
@click:connection:add="onClickConnectionAdd"
|
2024-06-25 02:11:44 -07:00
|
|
|
@click:pane="onClickPane"
|
2024-05-23 01:42:10 -07:00
|
|
|
>
|
|
|
|
<div :class="$style.executionButtons">
|
2024-07-11 03:03:46 -07:00
|
|
|
<CanvasRunWorkflowButton
|
2024-07-10 01:53:27 -07:00
|
|
|
:waiting-for-webhook="isExecutionWaitingForWebhook"
|
2024-07-11 03:03:46 -07:00
|
|
|
:disabled="isExecutionDisabled"
|
2024-07-10 01:53:27 -07:00
|
|
|
:executing="isWorkflowRunning"
|
2024-07-11 03:03:46 -07:00
|
|
|
@mouseenter="onRunWorkflowButtonMouseEnter"
|
|
|
|
@mouseleave="onRunWorkflowButtonMouseLeave"
|
2024-07-10 01:53:27 -07:00
|
|
|
@click="onRunWorkflow"
|
|
|
|
/>
|
|
|
|
<CanvasStopCurrentExecutionButton
|
|
|
|
v-if="isStopExecutionButtonVisible"
|
|
|
|
:stopping="isStoppingExecution"
|
|
|
|
@click="onStopExecution"
|
|
|
|
/>
|
|
|
|
<CanvasStopWaitingForWebhookButton
|
|
|
|
v-if="isStopWaitingForWebhookButtonVisible"
|
|
|
|
@click="onStopWaitingForWebhook"
|
|
|
|
/>
|
2024-07-11 06:37:01 -07:00
|
|
|
<CanvasClearExecutionDataButton
|
|
|
|
v-if="isClearExecutionButtonVisible"
|
|
|
|
@click="onClearExecutionData"
|
|
|
|
/>
|
2024-05-23 01:42:10 -07:00
|
|
|
</div>
|
|
|
|
<Suspense>
|
|
|
|
<NodeCreation
|
|
|
|
v-if="!isReadOnlyRoute && !isReadOnlyEnvironment"
|
|
|
|
:create-node-active="uiStore.isCreateNodeActive"
|
|
|
|
:node-view-scale="1"
|
2024-07-10 02:22:31 -07:00
|
|
|
@toggle-node-creator="onOpenNodeCreator"
|
2024-06-25 02:11:44 -07:00
|
|
|
@add-nodes="onAddNodesAndConnections"
|
2024-05-23 01:42:10 -07:00
|
|
|
/>
|
|
|
|
</Suspense>
|
2024-06-17 05:46:55 -07:00
|
|
|
<Suspense>
|
|
|
|
<NodeDetailsView
|
2024-06-27 09:50:09 -07:00
|
|
|
:workflow-object="editableWorkflowObject"
|
2024-06-17 05:46:55 -07:00
|
|
|
:read-only="isReadOnlyRoute || isReadOnlyEnvironment"
|
|
|
|
:is-production-execution-preview="isProductionExecutionPreview"
|
2024-06-18 10:15:12 -07:00
|
|
|
:renaming="false"
|
2024-06-25 02:11:44 -07:00
|
|
|
@value-changed="onRenameNode"
|
2024-07-10 01:53:27 -07:00
|
|
|
@stop-execution="onStopExecution"
|
2024-06-25 02:11:44 -07:00
|
|
|
@switch-selected-node="onSwitchActiveNode"
|
2024-07-10 02:22:31 -07:00
|
|
|
@open-connection-node-creator="onOpenSelectiveNodeCreator"
|
2024-06-17 05:46:55 -07:00
|
|
|
/>
|
|
|
|
<!--
|
|
|
|
:renaming="renamingActive"
|
|
|
|
@save-keyboard-shortcut="onSaveKeyboardShortcut"
|
|
|
|
-->
|
|
|
|
</Suspense>
|
2024-05-23 01:42:10 -07:00
|
|
|
</WorkflowCanvas>
|
|
|
|
</template>
|
|
|
|
|
|
|
|
<style lang="scss" module>
|
|
|
|
.executionButtons {
|
|
|
|
position: absolute;
|
|
|
|
display: flex;
|
|
|
|
justify-content: center;
|
|
|
|
align-items: center;
|
|
|
|
left: 50%;
|
|
|
|
transform: translateX(-50%);
|
|
|
|
bottom: var(--spacing-l);
|
|
|
|
width: auto;
|
|
|
|
|
|
|
|
@media (max-width: $breakpoint-2xs) {
|
|
|
|
bottom: 150px;
|
|
|
|
}
|
|
|
|
|
|
|
|
button {
|
|
|
|
display: flex;
|
|
|
|
justify-content: center;
|
|
|
|
align-items: center;
|
|
|
|
margin-left: 0.625rem;
|
|
|
|
|
|
|
|
&:first-child {
|
|
|
|
margin: 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
</style>
|