mirror of
https://github.com/n8n-io/n8n.git
synced 2025-03-05 20:50:17 -08:00
feat(editor): Prevent saving of workflow when canvas is loading (#6497)
* feat(editor): Prevent saving of pristine workflow Signed-off-by: Oleg Ivaniv <me@olegivaniv.com> * Prevent saving if loading Signed-off-by: Oleg Ivaniv <me@olegivaniv.com> * Fix 7-workflow-actions spec Signed-off-by: Oleg Ivaniv <me@olegivaniv.com> * Restrict delay intercept to GET only Signed-off-by: Oleg Ivaniv <me@olegivaniv.com> * Wait for WF patch Signed-off-by: Oleg Ivaniv <me@olegivaniv.com> * Add helper to remove all active WFs in e2e Signed-off-by: Oleg Ivaniv <me@olegivaniv.com> * Use META_KEY env var * Remove cy.wait * Delete debugging DB reset console log Signed-off-by: Oleg Ivaniv <me@olegivaniv.com> * Fix clashin mixins `isReadOnly` property Signed-off-by: Oleg Ivaniv <me@olegivaniv.com> --------- Signed-off-by: Oleg Ivaniv <me@olegivaniv.com>
This commit is contained in:
parent
d70a1cb0c8
commit
f89ef83c76
|
@ -5,6 +5,7 @@ import {
|
||||||
SCHEDULE_TRIGGER_NODE_NAME,
|
SCHEDULE_TRIGGER_NODE_NAME,
|
||||||
} from '../constants';
|
} from '../constants';
|
||||||
import { WorkflowPage as WorkflowPageClass } from '../pages/workflow';
|
import { WorkflowPage as WorkflowPageClass } from '../pages/workflow';
|
||||||
|
import { WorkflowsPage as WorkflowsPageClass } from '../pages/workflows';
|
||||||
|
|
||||||
const NEW_WORKFLOW_NAME = 'Something else';
|
const NEW_WORKFLOW_NAME = 'Something else';
|
||||||
const IMPORT_WORKFLOW_URL = 'https://gist.githubusercontent.com/OlegIvaniv/010bd3f45c8a94f8eb7012e663a8b671/raw/3afea1aec15573cc168d9af7e79395bd76082906/test-workflow.json';
|
const IMPORT_WORKFLOW_URL = 'https://gist.githubusercontent.com/OlegIvaniv/010bd3f45c8a94f8eb7012e663a8b671/raw/3afea1aec15573cc168d9af7e79395bd76082906/test-workflow.json';
|
||||||
|
@ -12,6 +13,7 @@ const DUPLICATE_WORKFLOW_NAME = 'Duplicated workflow';
|
||||||
const DUPLICATE_WORKFLOW_TAG = 'Duplicate';
|
const DUPLICATE_WORKFLOW_TAG = 'Duplicate';
|
||||||
|
|
||||||
const WorkflowPage = new WorkflowPageClass();
|
const WorkflowPage = new WorkflowPageClass();
|
||||||
|
const WorkflowPages = new WorkflowsPageClass();
|
||||||
|
|
||||||
describe('Workflow Actions', () => {
|
describe('Workflow Actions', () => {
|
||||||
before(() => {
|
before(() => {
|
||||||
|
@ -66,6 +68,42 @@ describe('Workflow Actions', () => {
|
||||||
.should('eq', NEW_WORKFLOW_NAME);
|
.should('eq', NEW_WORKFLOW_NAME);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should not save workflow if canvas is loading', () => {
|
||||||
|
let interceptCalledCount = 0;
|
||||||
|
|
||||||
|
// There's no way in Cypress to check if intercept was not called
|
||||||
|
// so we'll count the number of times it was called
|
||||||
|
cy.intercept('PATCH', '/rest/workflows/*', () => {
|
||||||
|
interceptCalledCount++;
|
||||||
|
}).as('saveWorkflow');
|
||||||
|
|
||||||
|
WorkflowPage.actions.addNodeToCanvas(MANUAL_TRIGGER_NODE_NAME);
|
||||||
|
WorkflowPage.actions.saveWorkflowOnButtonClick();
|
||||||
|
cy.intercept(
|
||||||
|
{
|
||||||
|
url: '/rest/workflows/*',
|
||||||
|
method: 'GET',
|
||||||
|
middleware: true,
|
||||||
|
},
|
||||||
|
(req) => {
|
||||||
|
// Delay the response to give time for the save to be triggered
|
||||||
|
req.on('response', async (res) => {
|
||||||
|
await new Promise((resolve) => setTimeout(resolve, 2000))
|
||||||
|
res.send();
|
||||||
|
})
|
||||||
|
}
|
||||||
|
)
|
||||||
|
cy.reload();
|
||||||
|
cy.get('.el-loading-mask').should('exist');
|
||||||
|
cy.get('body').type(META_KEY, { release: false }).type('s');
|
||||||
|
cy.get('body').type(META_KEY, { release: false }).type('s');
|
||||||
|
cy.get('body').type(META_KEY, { release: false }).type('s');
|
||||||
|
cy.wrap(null).then(() => expect(interceptCalledCount).to.eq(0));
|
||||||
|
WorkflowPage.actions.addNodeToCanvas(SCHEDULE_TRIGGER_NODE_NAME);
|
||||||
|
cy.get('body').type(META_KEY, { release: false }).type('s');
|
||||||
|
cy.wait('@saveWorkflow');
|
||||||
|
cy.wrap(null).then(() => expect(interceptCalledCount).to.eq(1));
|
||||||
|
})
|
||||||
it('should copy nodes', () => {
|
it('should copy nodes', () => {
|
||||||
WorkflowPage.actions.addNodeToCanvas(SCHEDULE_TRIGGER_NODE_NAME);
|
WorkflowPage.actions.addNodeToCanvas(SCHEDULE_TRIGGER_NODE_NAME);
|
||||||
WorkflowPage.actions.addNodeToCanvas(CODE_NODE_NAME);
|
WorkflowPage.actions.addNodeToCanvas(CODE_NODE_NAME);
|
||||||
|
@ -110,64 +148,70 @@ describe('Workflow Actions', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should update workflow settings', () => {
|
it('should update workflow settings', () => {
|
||||||
WorkflowPage.actions.visit();
|
cy.visit(WorkflowPages.url);
|
||||||
// Open settings dialog
|
WorkflowPages.getters.workflowCards().then((cards) => {
|
||||||
WorkflowPage.actions.saveWorkflowOnButtonClick();
|
const totalWorkflows = cards.length;
|
||||||
WorkflowPage.getters.workflowMenu().should('be.visible');
|
|
||||||
WorkflowPage.getters.workflowMenu().click();
|
WorkflowPage.actions.visit();
|
||||||
WorkflowPage.getters.workflowMenuItemSettings().should('be.visible');
|
// Open settings dialog
|
||||||
WorkflowPage.getters.workflowMenuItemSettings().click();
|
WorkflowPage.actions.saveWorkflowOnButtonClick();
|
||||||
// Change all settings
|
WorkflowPage.getters.workflowMenu().should('be.visible');
|
||||||
WorkflowPage.getters.workflowSettingsErrorWorkflowSelect().find('li').should('have.length', 7);
|
WorkflowPage.getters.workflowMenu().click();
|
||||||
WorkflowPage.getters
|
WorkflowPage.getters.workflowMenuItemSettings().should('be.visible');
|
||||||
.workflowSettingsErrorWorkflowSelect()
|
WorkflowPage.getters.workflowMenuItemSettings().click();
|
||||||
.find('li')
|
// Change all settings
|
||||||
.last()
|
// totalWorkflows + 1 (current workflow) + 1 (no workflow option)
|
||||||
.click({ force: true });
|
WorkflowPage.getters.workflowSettingsErrorWorkflowSelect().find('li').should('have.length', totalWorkflows + 2);
|
||||||
WorkflowPage.getters.workflowSettingsTimezoneSelect().find('li').should('exist');
|
WorkflowPage.getters
|
||||||
WorkflowPage.getters.workflowSettingsTimezoneSelect().find('li').eq(1).click({ force: true });
|
.workflowSettingsErrorWorkflowSelect()
|
||||||
WorkflowPage.getters
|
.find('li')
|
||||||
.workflowSettingsSaveFiledExecutionsSelect()
|
.last()
|
||||||
.find('li')
|
.click({ force: true });
|
||||||
.should('have.length', 3);
|
WorkflowPage.getters.workflowSettingsTimezoneSelect().find('li').should('exist');
|
||||||
WorkflowPage.getters
|
WorkflowPage.getters.workflowSettingsTimezoneSelect().find('li').eq(1).click({ force: true });
|
||||||
.workflowSettingsSaveFiledExecutionsSelect()
|
WorkflowPage.getters
|
||||||
.find('li')
|
.workflowSettingsSaveFiledExecutionsSelect()
|
||||||
.last()
|
.find('li')
|
||||||
.click({ force: true });
|
.should('have.length', 3);
|
||||||
WorkflowPage.getters
|
WorkflowPage.getters
|
||||||
.workflowSettingsSaveSuccessExecutionsSelect()
|
.workflowSettingsSaveFiledExecutionsSelect()
|
||||||
.find('li')
|
.find('li')
|
||||||
.should('have.length', 3);
|
.last()
|
||||||
WorkflowPage.getters
|
.click({ force: true });
|
||||||
.workflowSettingsSaveSuccessExecutionsSelect()
|
WorkflowPage.getters
|
||||||
.find('li')
|
.workflowSettingsSaveSuccessExecutionsSelect()
|
||||||
.last()
|
.find('li')
|
||||||
.click({ force: true });
|
.should('have.length', 3);
|
||||||
WorkflowPage.getters
|
WorkflowPage.getters
|
||||||
.workflowSettingsSaveManualExecutionsSelect()
|
.workflowSettingsSaveSuccessExecutionsSelect()
|
||||||
.find('li')
|
.find('li')
|
||||||
.should('have.length', 3);
|
.last()
|
||||||
WorkflowPage.getters
|
.click({ force: true });
|
||||||
.workflowSettingsSaveManualExecutionsSelect()
|
WorkflowPage.getters
|
||||||
.find('li')
|
.workflowSettingsSaveManualExecutionsSelect()
|
||||||
.last()
|
.find('li')
|
||||||
.click({ force: true });
|
.should('have.length', 3);
|
||||||
WorkflowPage.getters
|
WorkflowPage.getters
|
||||||
.workflowSettingsSaveExecutionProgressSelect()
|
.workflowSettingsSaveManualExecutionsSelect()
|
||||||
.find('li')
|
.find('li')
|
||||||
.should('have.length', 3);
|
.last()
|
||||||
WorkflowPage.getters
|
.click({ force: true });
|
||||||
.workflowSettingsSaveExecutionProgressSelect()
|
WorkflowPage.getters
|
||||||
.find('li')
|
.workflowSettingsSaveExecutionProgressSelect()
|
||||||
.last()
|
.find('li')
|
||||||
.click({ force: true });
|
.should('have.length', 3);
|
||||||
WorkflowPage.getters.workflowSettingsTimeoutWorkflowSwitch().click();
|
WorkflowPage.getters
|
||||||
WorkflowPage.getters.workflowSettingsTimeoutForm().find('input').first().type('1');
|
.workflowSettingsSaveExecutionProgressSelect()
|
||||||
// Save settings
|
.find('li')
|
||||||
WorkflowPage.getters.workflowSettingsSaveButton().click();
|
.last()
|
||||||
WorkflowPage.getters.workflowSettingsModal().should('not.exist');
|
.click({ force: true });
|
||||||
WorkflowPage.getters.successToast().should('exist');
|
WorkflowPage.getters.workflowSettingsTimeoutWorkflowSwitch().click();
|
||||||
|
WorkflowPage.getters.workflowSettingsTimeoutForm().find('input').first().type('1');
|
||||||
|
// Save settings
|
||||||
|
WorkflowPage.getters.workflowSettingsSaveButton().click();
|
||||||
|
WorkflowPage.getters.workflowSettingsModal().should('not.exist');
|
||||||
|
WorkflowPage.getters.successToast().should('exist');
|
||||||
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should not be able to delete unsaved workflow', () => {
|
it('should not be able to delete unsaved workflow', () => {
|
||||||
|
|
|
@ -177,7 +177,7 @@ export class WorkflowPage extends BasePage {
|
||||||
},
|
},
|
||||||
saveWorkflowUsingKeyboardShortcut: () => {
|
saveWorkflowUsingKeyboardShortcut: () => {
|
||||||
cy.intercept('POST', '/rest/workflows').as('createWorkflow');
|
cy.intercept('POST', '/rest/workflows').as('createWorkflow');
|
||||||
cy.get('body').type('{meta}', { release: false }).type('s');
|
cy.get('body').type(META_KEY, { release: false }).type('s');
|
||||||
},
|
},
|
||||||
deleteNode: (name: string) => {
|
deleteNode: (name: string) => {
|
||||||
this.getters.canvasNodeByName(name).first().click();
|
this.getters.canvasNodeByName(name).first().click();
|
||||||
|
|
|
@ -36,8 +36,10 @@ export class WorkflowsPage extends BasePage {
|
||||||
cy.visit(this.url);
|
cy.visit(this.url);
|
||||||
this.getters.workflowCardActions(name).click();
|
this.getters.workflowCardActions(name).click();
|
||||||
this.getters.workflowDeleteButton().click();
|
this.getters.workflowDeleteButton().click();
|
||||||
|
cy.intercept('DELETE', '/rest/workflows/*').as('deleteWorkflow');
|
||||||
|
|
||||||
cy.get('button').contains('delete').click();
|
cy.get('button').contains('delete').click();
|
||||||
|
cy.wait('@deleteWorkflow');
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,6 +12,7 @@ import { Container } from 'typedi';
|
||||||
import config from '@/config';
|
import config from '@/config';
|
||||||
import * as Db from '@/Db';
|
import * as Db from '@/Db';
|
||||||
import type { Role } from '@db/entities/Role';
|
import type { Role } from '@db/entities/Role';
|
||||||
|
import { ActiveWorkflowRunner } from '@/ActiveWorkflowRunner';
|
||||||
import { RoleRepository } from '@db/repositories';
|
import { RoleRepository } from '@db/repositories';
|
||||||
import { hashPassword } from '@/UserManagement/UserManagementHelper';
|
import { hashPassword } from '@/UserManagement/UserManagementHelper';
|
||||||
import { eventBus } from '@/eventbus/MessageEventBus/MessageEventBus';
|
import { eventBus } from '@/eventbus/MessageEventBus/MessageEventBus';
|
||||||
|
@ -108,10 +109,18 @@ const resetLogStreaming = async () => {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const removeActiveWorkflows = async () => {
|
||||||
|
const workflowRunner = Container.get(ActiveWorkflowRunner);
|
||||||
|
|
||||||
|
workflowRunner.removeAllQueuedWorkflowActivations();
|
||||||
|
await workflowRunner.removeAll();
|
||||||
|
};
|
||||||
|
|
||||||
export const e2eController = Router();
|
export const e2eController = Router();
|
||||||
|
|
||||||
e2eController.post('/db/reset', async (req, res) => {
|
e2eController.post('/db/reset', async (req, res) => {
|
||||||
await resetLogStreaming();
|
await resetLogStreaming();
|
||||||
|
await removeActiveWorkflows();
|
||||||
await truncateAll();
|
await truncateAll();
|
||||||
await setupUserManagement();
|
await setupUserManagement();
|
||||||
|
|
||||||
|
|
|
@ -45,7 +45,7 @@
|
||||||
<div class="expression-editor ph-no-capture">
|
<div class="expression-editor ph-no-capture">
|
||||||
<ExpressionEditorModalInput
|
<ExpressionEditorModalInput
|
||||||
:value="value"
|
:value="value"
|
||||||
:isReadOnly="isReadOnly"
|
:isReadOnly="isReadOnlyRoute"
|
||||||
:path="path"
|
:path="path"
|
||||||
@change="valueChanged"
|
@change="valueChanged"
|
||||||
@close="closeDialog"
|
@close="closeDialog"
|
||||||
|
|
|
@ -15,7 +15,7 @@
|
||||||
color="text-dark"
|
color="text-dark"
|
||||||
data-test-id="credentials-label"
|
data-test-id="credentials-label"
|
||||||
>
|
>
|
||||||
<div v-if="readonly || isReadOnly">
|
<div v-if="readonly || isReadOnlyRoute">
|
||||||
<n8n-input
|
<n8n-input
|
||||||
:value="getSelectedName(credentialTypeDescription.name)"
|
:value="getSelectedName(credentialTypeDescription.name)"
|
||||||
disabled
|
disabled
|
||||||
|
|
|
@ -7,7 +7,7 @@
|
||||||
:class="$style.pinnedDataCallout"
|
:class="$style.pinnedDataCallout"
|
||||||
>
|
>
|
||||||
{{ $locale.baseText('runData.pindata.thisDataIsPinned') }}
|
{{ $locale.baseText('runData.pindata.thisDataIsPinned') }}
|
||||||
<span class="ml-4xs" v-if="!isReadOnly">
|
<span class="ml-4xs" v-if="!isReadOnlyRoute">
|
||||||
<n8n-link
|
<n8n-link
|
||||||
theme="secondary"
|
theme="secondary"
|
||||||
size="small"
|
size="small"
|
||||||
|
@ -59,7 +59,7 @@
|
||||||
data-test-id="ndv-run-data-display-mode"
|
data-test-id="ndv-run-data-display-mode"
|
||||||
/>
|
/>
|
||||||
<n8n-icon-button
|
<n8n-icon-button
|
||||||
v-if="canPinData && !isReadOnly"
|
v-if="canPinData && !isReadOnlyRoute"
|
||||||
v-show="!editMode.enabled"
|
v-show="!editMode.enabled"
|
||||||
:title="$locale.baseText('runData.editOutput')"
|
:title="$locale.baseText('runData.editOutput')"
|
||||||
:circle="false"
|
:circle="false"
|
||||||
|
@ -99,7 +99,9 @@
|
||||||
type="tertiary"
|
type="tertiary"
|
||||||
:active="hasPinData"
|
:active="hasPinData"
|
||||||
icon="thumbtack"
|
icon="thumbtack"
|
||||||
:disabled="editMode.enabled || (inputData.length === 0 && !hasPinData) || isReadOnly"
|
:disabled="
|
||||||
|
editMode.enabled || (inputData.length === 0 && !hasPinData) || isReadOnlyRoute
|
||||||
|
"
|
||||||
@click="onTogglePinData({ source: 'pin-icon-click' })"
|
@click="onTogglePinData({ source: 'pin-icon-click' })"
|
||||||
data-test-id="ndv-pin-data"
|
data-test-id="ndv-pin-data"
|
||||||
/>
|
/>
|
||||||
|
@ -917,7 +919,7 @@ export default defineComponent({
|
||||||
if (
|
if (
|
||||||
value &&
|
value &&
|
||||||
value.length > 0 &&
|
value.length > 0 &&
|
||||||
!this.isReadOnly &&
|
!this.isReadOnlyRoute &&
|
||||||
!localStorage.getItem(LOCAL_STORAGE_PIN_DATA_DISCOVERY_NDV_FLAG)
|
!localStorage.getItem(LOCAL_STORAGE_PIN_DATA_DISCOVERY_NDV_FLAG)
|
||||||
) {
|
) {
|
||||||
this.pinDataDiscoveryComplete();
|
this.pinDataDiscoveryComplete();
|
||||||
|
|
|
@ -212,7 +212,7 @@ export default defineComponent({
|
||||||
copy_type: copyType,
|
copy_type: copyType,
|
||||||
workflow_id: this.workflowsStore.workflowId,
|
workflow_id: this.workflowsStore.workflowId,
|
||||||
pane: this.paneType,
|
pane: this.paneType,
|
||||||
in_execution_log: this.isReadOnly,
|
in_execution_log: this.isReadOnlyRoute,
|
||||||
});
|
});
|
||||||
|
|
||||||
this.copyToClipboard(value);
|
this.copyToClipboard(value);
|
||||||
|
|
|
@ -17,7 +17,7 @@ export const genericHelpers = defineComponent({
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
isReadOnly(): boolean {
|
isReadOnlyRoute(): boolean {
|
||||||
return ![VIEWS.WORKFLOW, VIEWS.NEW_WORKFLOW, VIEWS.LOG_STREAMING_SETTINGS].includes(
|
return ![VIEWS.WORKFLOW, VIEWS.NEW_WORKFLOW, VIEWS.LOG_STREAMING_SETTINGS].includes(
|
||||||
this.$route.name as VIEWS,
|
this.$route.name as VIEWS,
|
||||||
);
|
);
|
||||||
|
@ -50,7 +50,7 @@ export const genericHelpers = defineComponent({
|
||||||
return { date, time };
|
return { date, time };
|
||||||
},
|
},
|
||||||
editAllowedCheck(): boolean {
|
editAllowedCheck(): boolean {
|
||||||
if (this.isReadOnly) {
|
if (this.isReadOnlyRoute) {
|
||||||
this.showMessage({
|
this.showMessage({
|
||||||
// title: 'Workflow can not be changed!',
|
// title: 'Workflow can not be changed!',
|
||||||
title: this.$locale.baseText('genericHelpers.showMessage.title'),
|
title: this.$locale.baseText('genericHelpers.showMessage.title'),
|
||||||
|
|
|
@ -43,6 +43,7 @@ import type {
|
||||||
|
|
||||||
import { externalHooks } from '@/mixins/externalHooks';
|
import { externalHooks } from '@/mixins/externalHooks';
|
||||||
import { nodeHelpers } from '@/mixins/nodeHelpers';
|
import { nodeHelpers } from '@/mixins/nodeHelpers';
|
||||||
|
import { genericHelpers } from '@/mixins/genericHelpers';
|
||||||
import { useToast, useMessage } from '@/composables';
|
import { useToast, useMessage } from '@/composables';
|
||||||
|
|
||||||
import { isEqual } from 'lodash-es';
|
import { isEqual } from 'lodash-es';
|
||||||
|
@ -329,7 +330,7 @@ function executeData(
|
||||||
}
|
}
|
||||||
|
|
||||||
export const workflowHelpers = defineComponent({
|
export const workflowHelpers = defineComponent({
|
||||||
mixins: [externalHooks, nodeHelpers],
|
mixins: [externalHooks, nodeHelpers, genericHelpers],
|
||||||
setup() {
|
setup() {
|
||||||
return {
|
return {
|
||||||
...useToast(),
|
...useToast(),
|
||||||
|
@ -699,6 +700,7 @@ export const workflowHelpers = defineComponent({
|
||||||
forceSave = false,
|
forceSave = false,
|
||||||
): Promise<boolean> {
|
): Promise<boolean> {
|
||||||
const currentWorkflow = id || this.$route.params.name;
|
const currentWorkflow = id || this.$route.params.name;
|
||||||
|
const isLoading = this.loadingService !== null;
|
||||||
|
|
||||||
if (!currentWorkflow || ['new', PLACEHOLDER_EMPTY_WORKFLOW_ID].includes(currentWorkflow)) {
|
if (!currentWorkflow || ['new', PLACEHOLDER_EMPTY_WORKFLOW_ID].includes(currentWorkflow)) {
|
||||||
return this.saveAsNewWorkflow({ name, tags }, redirect);
|
return this.saveAsNewWorkflow({ name, tags }, redirect);
|
||||||
|
@ -706,6 +708,9 @@ export const workflowHelpers = defineComponent({
|
||||||
|
|
||||||
// Workflow exists already so update it
|
// Workflow exists already so update it
|
||||||
try {
|
try {
|
||||||
|
if (!forceSave && isLoading) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
this.uiStore.addActiveAction('workflowSaving');
|
this.uiStore.addActiveAction('workflowSaving');
|
||||||
|
|
||||||
const workflowDataRequest: IWorkflowDataUpdate = await this.getWorkflowDataToSave();
|
const workflowDataRequest: IWorkflowDataUpdate = await this.getWorkflowDataToSave();
|
||||||
|
|
|
@ -54,7 +54,7 @@
|
||||||
@run="onNodeRun"
|
@run="onNodeRun"
|
||||||
:key="`${nodeData.id}_node`"
|
:key="`${nodeData.id}_node`"
|
||||||
:name="nodeData.name"
|
:name="nodeData.name"
|
||||||
:isReadOnly="isReadOnly || readOnlyEnv"
|
:isReadOnly="isReadOnlyRoute || readOnlyEnv"
|
||||||
:instance="instance"
|
:instance="instance"
|
||||||
:isActive="!!activeNode && activeNode.name === nodeData.name"
|
:isActive="!!activeNode && activeNode.name === nodeData.name"
|
||||||
:hideActions="pullConnActive"
|
:hideActions="pullConnActive"
|
||||||
|
@ -76,7 +76,7 @@
|
||||||
@removeNode="(name) => removeNode(name, true)"
|
@removeNode="(name) => removeNode(name, true)"
|
||||||
:key="`${nodeData.id}_sticky`"
|
:key="`${nodeData.id}_sticky`"
|
||||||
:name="nodeData.name"
|
:name="nodeData.name"
|
||||||
:isReadOnly="isReadOnly || readOnlyEnv"
|
:isReadOnly="isReadOnlyRoute || readOnlyEnv"
|
||||||
:instance="instance"
|
:instance="instance"
|
||||||
:isActive="!!activeNode && activeNode.name === nodeData.name"
|
:isActive="!!activeNode && activeNode.name === nodeData.name"
|
||||||
:nodeViewScale="nodeViewScale"
|
:nodeViewScale="nodeViewScale"
|
||||||
|
@ -87,7 +87,7 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<node-details-view
|
<node-details-view
|
||||||
:readOnly="isReadOnly || readOnlyEnv"
|
:readOnly="isReadOnlyRoute || readOnlyEnv"
|
||||||
:renaming="renamingActive"
|
:renaming="renamingActive"
|
||||||
:isProductionExecutionPreview="isProductionExecutionPreview"
|
:isProductionExecutionPreview="isProductionExecutionPreview"
|
||||||
@valueChanged="valueChanged"
|
@valueChanged="valueChanged"
|
||||||
|
@ -95,14 +95,14 @@
|
||||||
@saveKeyboardShortcut="onSaveKeyboardShortcut"
|
@saveKeyboardShortcut="onSaveKeyboardShortcut"
|
||||||
/>
|
/>
|
||||||
<node-creation
|
<node-creation
|
||||||
v-if="!isReadOnly && !readOnlyEnv"
|
v-if="!isReadOnlyRoute && !readOnlyEnv"
|
||||||
:create-node-active="createNodeActive"
|
:create-node-active="createNodeActive"
|
||||||
:node-view-scale="nodeViewScale"
|
:node-view-scale="nodeViewScale"
|
||||||
@toggleNodeCreator="onToggleNodeCreator"
|
@toggleNodeCreator="onToggleNodeCreator"
|
||||||
@addNode="onAddNode"
|
@addNode="onAddNode"
|
||||||
/>
|
/>
|
||||||
<canvas-controls />
|
<canvas-controls />
|
||||||
<div class="workflow-execute-wrapper" v-if="!isReadOnly">
|
<div class="workflow-execute-wrapper" v-if="!isReadOnlyRoute">
|
||||||
<span
|
<span
|
||||||
@mouseenter="showTriggerMissingToltip(true)"
|
@mouseenter="showTriggerMissingToltip(true)"
|
||||||
@mouseleave="showTriggerMissingToltip(false)"
|
@mouseleave="showTriggerMissingToltip(false)"
|
||||||
|
@ -149,7 +149,7 @@
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<n8n-icon-button
|
<n8n-icon-button
|
||||||
v-if="!isReadOnly && workflowExecution && !workflowRunning && !allTriggersDisabled"
|
v-if="!isReadOnlyRoute && workflowExecution && !workflowRunning && !allTriggersDisabled"
|
||||||
:title="$locale.baseText('nodeView.deletesTheCurrentExecutionData')"
|
:title="$locale.baseText('nodeView.deletesTheCurrentExecutionData')"
|
||||||
icon="trash"
|
icon="trash"
|
||||||
size="large"
|
size="large"
|
||||||
|
@ -961,7 +961,7 @@ export default defineComponent({
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|
||||||
if (this.isReadOnly) {
|
if (this.isReadOnlyRoute) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1015,13 +1015,13 @@ export default defineComponent({
|
||||||
} else if (e.key === 'Tab') {
|
} else if (e.key === 'Tab') {
|
||||||
this.onToggleNodeCreator({
|
this.onToggleNodeCreator({
|
||||||
source: NODE_CREATOR_OPEN_SOURCES.TAB,
|
source: NODE_CREATOR_OPEN_SOURCES.TAB,
|
||||||
createNodeActive: !this.createNodeActive && !this.isReadOnly,
|
createNodeActive: !this.createNodeActive && !this.isReadOnlyRoute,
|
||||||
});
|
});
|
||||||
} else if (e.key === this.controlKeyCode) {
|
} else if (e.key === this.controlKeyCode) {
|
||||||
this.ctrlKeyPressed = true;
|
this.ctrlKeyPressed = true;
|
||||||
} else if (e.key === ' ') {
|
} else if (e.key === ' ') {
|
||||||
this.moveCanvasKeyPressed = true;
|
this.moveCanvasKeyPressed = true;
|
||||||
} else if (e.key === 'F2' && !this.isReadOnly) {
|
} else if (e.key === 'F2' && !this.isReadOnlyRoute) {
|
||||||
const lastSelectedNode = this.lastSelectedNode;
|
const lastSelectedNode = this.lastSelectedNode;
|
||||||
if (lastSelectedNode !== null && lastSelectedNode.type !== STICKY_NODE_TYPE) {
|
if (lastSelectedNode !== null && lastSelectedNode.type !== STICKY_NODE_TYPE) {
|
||||||
void this.callDebounced(
|
void this.callDebounced(
|
||||||
|
@ -1067,7 +1067,7 @@ export default defineComponent({
|
||||||
const lastSelectedNode = this.lastSelectedNode;
|
const lastSelectedNode = this.lastSelectedNode;
|
||||||
|
|
||||||
if (lastSelectedNode !== null) {
|
if (lastSelectedNode !== null) {
|
||||||
if (lastSelectedNode.type === STICKY_NODE_TYPE && this.isReadOnly) {
|
if (lastSelectedNode.type === STICKY_NODE_TYPE && this.isReadOnlyRoute) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.ndvStore.activeNodeName = lastSelectedNode.name;
|
this.ndvStore.activeNodeName = lastSelectedNode.name;
|
||||||
|
@ -1307,7 +1307,7 @@ export default defineComponent({
|
||||||
},
|
},
|
||||||
|
|
||||||
cutSelectedNodes() {
|
cutSelectedNodes() {
|
||||||
const deleteCopiedNodes = !this.isReadOnly;
|
const deleteCopiedNodes = !this.isReadOnlyRoute;
|
||||||
this.copySelectedNodes(deleteCopiedNodes);
|
this.copySelectedNodes(deleteCopiedNodes);
|
||||||
if (deleteCopiedNodes) {
|
if (deleteCopiedNodes) {
|
||||||
this.deleteSelectedNodes();
|
this.deleteSelectedNodes();
|
||||||
|
@ -2162,7 +2162,7 @@ export default defineComponent({
|
||||||
if (!this.suspendRecordingDetachedConnections) {
|
if (!this.suspendRecordingDetachedConnections) {
|
||||||
this.historyStore.pushCommandToUndo(new AddConnectionCommand(connectionData));
|
this.historyStore.pushCommandToUndo(new AddConnectionCommand(connectionData));
|
||||||
}
|
}
|
||||||
if (!this.isReadOnly) {
|
if (!this.isReadOnlyRoute) {
|
||||||
NodeViewUtils.addConnectionActionsOverlay(
|
NodeViewUtils.addConnectionActionsOverlay(
|
||||||
info.connection,
|
info.connection,
|
||||||
() => {
|
() => {
|
||||||
|
@ -2211,7 +2211,7 @@ export default defineComponent({
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
this.isReadOnly ||
|
this.isReadOnlyRoute ||
|
||||||
this.readOnlyEnv ||
|
this.readOnlyEnv ||
|
||||||
this.enterTimer ||
|
this.enterTimer ||
|
||||||
!connection ||
|
!connection ||
|
||||||
|
@ -2242,7 +2242,7 @@ export default defineComponent({
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
this.isReadOnly ||
|
this.isReadOnlyRoute ||
|
||||||
this.readOnlyEnv ||
|
this.readOnlyEnv ||
|
||||||
!connection ||
|
!connection ||
|
||||||
this.activeConnection?.id !== connection.id
|
this.activeConnection?.id !== connection.id
|
||||||
|
@ -2609,7 +2609,7 @@ export default defineComponent({
|
||||||
// Create connections in DOM
|
// Create connections in DOM
|
||||||
this.instance?.connect({
|
this.instance?.connect({
|
||||||
uuids: uuid,
|
uuids: uuid,
|
||||||
detachable: !this.isReadOnly,
|
detachable: !this.isReadOnlyRoute,
|
||||||
});
|
});
|
||||||
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
|
|
Loading…
Reference in a new issue