mirror of
https://github.com/n8n-io/n8n.git
synced 2024-12-25 04:34:06 -08:00
feat(editor): add readonly state for nodes (#4299)
* fix(editor): add types to Node.vue component props * fix(editor): some cleanup in NodeView * fix(editor): fix some boolean usage * feat(editor): check foreign credentials * fix(editor): passing readOnly to inputs * fix(editor): add types to component and solve property mutation * fix(editor): add types to component and solve property mutation * fix(editor): component property type * fix(editor): component property type * fix(editor): default prop values * fix(editor): fix FixedCollectionParameter.vue
This commit is contained in:
parent
1f4eaeb3ae
commit
408bd96815
|
@ -1,3 +1,14 @@
|
||||||
|
import {
|
||||||
|
jsPlumbInstance,
|
||||||
|
DragOptions,
|
||||||
|
DropOptions,
|
||||||
|
ElementGroupRef,
|
||||||
|
Endpoint,
|
||||||
|
EndpointOptions,
|
||||||
|
EndpointRectangle,
|
||||||
|
EndpointRectangleOptions,
|
||||||
|
EndpointSpec,
|
||||||
|
} from "jsplumb";
|
||||||
import {
|
import {
|
||||||
GenericValue,
|
GenericValue,
|
||||||
IConnections,
|
IConnections,
|
||||||
|
@ -104,24 +115,40 @@ declare module 'jsplumb' {
|
||||||
}
|
}
|
||||||
|
|
||||||
// EndpointOptions from jsplumb seems incomplete and wrong so we define an own one
|
// EndpointOptions from jsplumb seems incomplete and wrong so we define an own one
|
||||||
export interface IEndpointOptions {
|
export type IEndpointOptions = Omit<EndpointOptions, 'endpoint' | 'dragProxy'> & {
|
||||||
anchor?: any; // tslint:disable-line:no-any
|
endpointStyle: EndpointStyle
|
||||||
createEndpoint?: boolean;
|
endpointHoverStyle: EndpointStyle
|
||||||
dragAllowedWhenFull?: boolean;
|
endpoint?: EndpointSpec | string
|
||||||
dropOptions?: any; // tslint:disable-line:no-any
|
dragAllowedWhenFull?: boolean
|
||||||
dragProxy?: any; // tslint:disable-line:no-any
|
dropOptions?: DropOptions & {
|
||||||
endpoint?: string;
|
tolerance: string
|
||||||
endpointStyle?: object;
|
};
|
||||||
endpointHoverStyle?: object;
|
dragProxy?: string | string[] | EndpointSpec | [ EndpointRectangle, EndpointRectangleOptions & { strokeWidth: number } ]
|
||||||
isSource?: boolean;
|
};
|
||||||
isTarget?: boolean;
|
|
||||||
maxConnections?: number;
|
export type EndpointStyle = {
|
||||||
overlays?: any; // tslint:disable-line:no-any
|
width?: number
|
||||||
parameters?: any; // tslint:disable-line:no-any
|
height?: number
|
||||||
uuid?: string;
|
fill?: string
|
||||||
enabled?: boolean;
|
stroke?: string
|
||||||
cssClass?: string;
|
outlineStroke?:string
|
||||||
}
|
lineWidth?: number
|
||||||
|
hover?: boolean
|
||||||
|
showOutputLabel?: boolean
|
||||||
|
size?: string
|
||||||
|
hoverMessage?: string
|
||||||
|
};
|
||||||
|
|
||||||
|
export type IDragOptions = DragOptions & {
|
||||||
|
grid: [number, number]
|
||||||
|
filter: string
|
||||||
|
};
|
||||||
|
|
||||||
|
export type IJsPlumbInstance = Omit<jsPlumbInstance, 'addEndpoint' | 'draggable'> & {
|
||||||
|
clearDragSelection: () => void
|
||||||
|
addEndpoint(el: ElementGroupRef, params?: IEndpointOptions, referenceParams?: IEndpointOptions): Endpoint | Endpoint[]
|
||||||
|
draggable(el: {}, options?: IDragOptions): IJsPlumbInstance
|
||||||
|
};
|
||||||
|
|
||||||
export interface IUpdateInformation {
|
export interface IUpdateInformation {
|
||||||
name: string;
|
name: string;
|
||||||
|
@ -908,6 +935,7 @@ export interface ICredentialMap {
|
||||||
export interface ICredentialsState {
|
export interface ICredentialsState {
|
||||||
credentialTypes: ICredentialTypeMap;
|
credentialTypes: ICredentialTypeMap;
|
||||||
credentials: ICredentialMap;
|
credentials: ICredentialMap;
|
||||||
|
foreignCredentials?: ICredentialMap;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ITagsState {
|
export interface ITagsState {
|
||||||
|
|
|
@ -51,3 +51,9 @@ export async function oAuth2CredentialAuthorize(context: IRestApiContext, data:
|
||||||
export async function testCredential(context: IRestApiContext, data: INodeCredentialTestRequest): Promise<INodeCredentialTestResult> {
|
export async function testCredential(context: IRestApiContext, data: INodeCredentialTestRequest): Promise<INodeCredentialTestResult> {
|
||||||
return makeRestApiRequest(context, 'POST', '/credentials/test', data as unknown as IDataObject);
|
return makeRestApiRequest(context, 'POST', '/credentials/test', data as unknown as IDataObject);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function getForeignCredentials(context: IRestApiContext): Promise<ICredentialsResponse[]> {
|
||||||
|
// TODO: Get foreign credentials
|
||||||
|
//return await makeRestApiRequest(context, 'GET', '/foreign-credentials');
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
<n8n-text size="small">{{ $locale.baseText('collectionParameter.noProperties') }}</n8n-text>
|
<n8n-text size="small">{{ $locale.baseText('collectionParameter.noProperties') }}</n8n-text>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<parameter-input-list :parameters="getProperties" :nodeValues="nodeValues" :path="path" :hideDelete="hideDelete" :indent="true" @valueChanged="valueChanged" />
|
<parameter-input-list :parameters="getProperties" :nodeValues="nodeValues" :path="path" :hideDelete="hideDelete" :indent="true" :isReadOnly="isReadOnly" @valueChanged="valueChanged" />
|
||||||
|
|
||||||
<div v-if="parameterOptions.length > 0 && !isReadOnly" class="param-options">
|
<div v-if="parameterOptions.length > 0 && !isReadOnly" class="param-options">
|
||||||
<n8n-button
|
<n8n-button
|
||||||
|
@ -43,7 +43,6 @@ import {
|
||||||
INodePropertyOptions,
|
INodePropertyOptions,
|
||||||
} from 'n8n-workflow';
|
} from 'n8n-workflow';
|
||||||
|
|
||||||
import { genericHelpers } from '@/components/mixins/genericHelpers';
|
|
||||||
import { nodeHelpers } from '@/components/mixins/nodeHelpers';
|
import { nodeHelpers } from '@/components/mixins/nodeHelpers';
|
||||||
|
|
||||||
import { get } from 'lodash';
|
import { get } from 'lodash';
|
||||||
|
@ -52,7 +51,6 @@ import mixins from 'vue-typed-mixins';
|
||||||
import {Component} from "vue";
|
import {Component} from "vue";
|
||||||
|
|
||||||
export default mixins(
|
export default mixins(
|
||||||
genericHelpers,
|
|
||||||
nodeHelpers,
|
nodeHelpers,
|
||||||
)
|
)
|
||||||
.extend({
|
.extend({
|
||||||
|
@ -63,6 +61,7 @@ export default mixins(
|
||||||
'parameter', // INodeProperties
|
'parameter', // INodeProperties
|
||||||
'path', // string
|
'path', // string
|
||||||
'values', // NodeParameters
|
'values', // NodeParameters
|
||||||
|
'isReadOnly', // boolean
|
||||||
],
|
],
|
||||||
components: {
|
components: {
|
||||||
ParameterInputList: () => import('./ParameterInputList.vue') as Promise<Component>,
|
ParameterInputList: () => import('./ParameterInputList.vue') as Promise<Component>,
|
||||||
|
|
|
@ -7,6 +7,7 @@
|
||||||
:value="displayValue"
|
:value="displayValue"
|
||||||
:placeholder="parameter.placeholder ? getPlaceholder() : $locale.baseText('parameterInput.select')"
|
:placeholder="parameter.placeholder ? getPlaceholder() : $locale.baseText('parameterInput.select')"
|
||||||
:title="displayTitle"
|
:title="displayTitle"
|
||||||
|
:disabled="isReadOnly"
|
||||||
ref="innerSelect"
|
ref="innerSelect"
|
||||||
@change="(value) => $emit('valueChanged', value)"
|
@change="(value) => $emit('valueChanged', value)"
|
||||||
@keydown.stop
|
@keydown.stop
|
||||||
|
|
|
@ -16,9 +16,9 @@
|
||||||
size="small"
|
size="small"
|
||||||
color="text-dark"
|
color="text-dark"
|
||||||
/>
|
/>
|
||||||
<div v-if="multipleValues === true">
|
<div v-if="multipleValues">
|
||||||
<div
|
<div
|
||||||
v-for="(value, index) in values[property.name]"
|
v-for="(value, index) in mutableValues[property.name]"
|
||||||
:key="property.name + index"
|
:key="property.name + index"
|
||||||
class="parameter-item"
|
class="parameter-item"
|
||||||
>
|
>
|
||||||
|
@ -39,7 +39,7 @@
|
||||||
@click="moveOptionUp(property.name, index)"
|
@click="moveOptionUp(property.name, index)"
|
||||||
/>
|
/>
|
||||||
<font-awesome-icon
|
<font-awesome-icon
|
||||||
v-if="index !== (values[property.name].length - 1)"
|
v-if="index !== (mutableValues[property.name].length - 1)"
|
||||||
icon="angle-down"
|
icon="angle-down"
|
||||||
class="clickable"
|
class="clickable"
|
||||||
:title="$locale.baseText('fixedCollectionParameter.moveDown')"
|
:title="$locale.baseText('fixedCollectionParameter.moveDown')"
|
||||||
|
@ -52,6 +52,7 @@
|
||||||
:nodeValues="nodeValues"
|
:nodeValues="nodeValues"
|
||||||
:path="getPropertyPath(property.name, index)"
|
:path="getPropertyPath(property.name, index)"
|
||||||
:hideDelete="true"
|
:hideDelete="true"
|
||||||
|
:isReadOnly="isReadOnly"
|
||||||
@valueChanged="valueChanged"
|
@valueChanged="valueChanged"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
@ -71,6 +72,7 @@
|
||||||
:parameters="property.values"
|
:parameters="property.values"
|
||||||
:nodeValues="nodeValues"
|
:nodeValues="nodeValues"
|
||||||
:path="getPropertyPath(property.name)"
|
:path="getPropertyPath(property.name)"
|
||||||
|
:isReadOnly="isReadOnly"
|
||||||
class="parameter-item"
|
class="parameter-item"
|
||||||
@valueChanged="valueChanged"
|
@valueChanged="valueChanged"
|
||||||
:hideDelete="true"
|
:hideDelete="true"
|
||||||
|
@ -108,42 +110,66 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
import Vue, { Component, PropType } from "vue";
|
||||||
import {
|
import {
|
||||||
IUpdateInformation,
|
IUpdateInformation,
|
||||||
} from '@/Interface';
|
} from '@/Interface';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
deepCopy,
|
|
||||||
INodeParameters,
|
INodeParameters,
|
||||||
|
INodeProperties,
|
||||||
INodePropertyCollection,
|
INodePropertyCollection,
|
||||||
NodeParameterValue,
|
NodeParameterValue,
|
||||||
|
deepCopy,
|
||||||
|
isINodePropertyCollectionList,
|
||||||
} from 'n8n-workflow';
|
} from 'n8n-workflow';
|
||||||
|
|
||||||
import { get } from 'lodash';
|
import { get } from 'lodash';
|
||||||
|
|
||||||
import { genericHelpers } from '@/components/mixins/genericHelpers';
|
export default Vue.extend({
|
||||||
|
|
||||||
import mixins from 'vue-typed-mixins';
|
|
||||||
import {Component} from "vue";
|
|
||||||
|
|
||||||
export default mixins(genericHelpers)
|
|
||||||
.extend({
|
|
||||||
name: 'FixedCollectionParameter',
|
name: 'FixedCollectionParameter',
|
||||||
props: [
|
props: {
|
||||||
'nodeValues', // INodeParameters
|
nodeValues: {
|
||||||
'parameter', // INodeProperties
|
type: Object as PropType<Record<string, INodeParameters[]>>,
|
||||||
'path', // string
|
required: true,
|
||||||
'values', // INodeParameters
|
},
|
||||||
],
|
parameter: {
|
||||||
|
type: Object as PropType<INodeProperties>,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
path: {
|
||||||
|
type: String,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
values: {
|
||||||
|
type: Object as PropType<Record<string, INodeParameters[]>>,
|
||||||
|
default: () => ({}),
|
||||||
|
},
|
||||||
|
isReadOnly: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
components: {
|
components: {
|
||||||
ParameterInputList: () => import('./ParameterInputList.vue') as Promise<Component>,
|
ParameterInputList: () => import('./ParameterInputList.vue') as Promise<Component>,
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
selectedOption: undefined,
|
selectedOption: undefined,
|
||||||
|
mutableValues: {} as Record<string, INodeParameters[]>,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
watch: {
|
||||||
|
values: {
|
||||||
|
handler(newValues: Record<string, INodeParameters[]>) {
|
||||||
|
this.mutableValues = deepCopy(newValues);
|
||||||
|
},
|
||||||
|
deep: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
created(){
|
||||||
|
this.mutableValues = deepCopy(this.values);
|
||||||
|
},
|
||||||
computed: {
|
computed: {
|
||||||
getPlaceholderText(): string {
|
getPlaceholderText(): string {
|
||||||
const placeholder = this.$locale.nodeText().placeholder(this.parameter, this.path);
|
const placeholder = this.$locale.nodeText().placeholder(this.parameter, this.path);
|
||||||
|
@ -161,14 +187,11 @@ export default mixins(genericHelpers)
|
||||||
return returnProperties;
|
return returnProperties;
|
||||||
},
|
},
|
||||||
multipleValues(): boolean {
|
multipleValues(): boolean {
|
||||||
if (this.parameter.typeOptions !== undefined && this.parameter.typeOptions.multipleValues === true) {
|
return !!this.parameter.typeOptions?.multipleValues;
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
},
|
},
|
||||||
|
|
||||||
parameterOptions(): INodePropertyCollection[] {
|
parameterOptions(): INodePropertyCollection[] {
|
||||||
if (this.multipleValues === true) {
|
if (this.multipleValues && isINodePropertyCollectionList(this.parameter.options)) {
|
||||||
return this.parameter.options;
|
return this.parameter.options;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -177,18 +200,15 @@ export default mixins(genericHelpers)
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
propertyNames(): string[] {
|
propertyNames(): string[] {
|
||||||
if (this.values) {
|
return Object.keys(this.mutableValues || {});
|
||||||
return Object.keys(this.values);
|
|
||||||
}
|
|
||||||
return [];
|
|
||||||
},
|
},
|
||||||
sortable(): string {
|
sortable(): boolean {
|
||||||
return this.parameter.typeOptions && this.parameter.typeOptions.sortable;
|
return !!this.parameter.typeOptions?.sortable;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
deleteOption(optionName: string, index?: number) {
|
deleteOption(optionName: string, index?: number) {
|
||||||
const currentOptionsOfSameType = this.values[optionName];
|
const currentOptionsOfSameType = this.mutableValues[optionName];
|
||||||
if (!currentOptionsOfSameType || currentOptionsOfSameType.length > 1) {
|
if (!currentOptionsOfSameType || currentOptionsOfSameType.length > 1) {
|
||||||
// it's not the only option of this type, so just remove it.
|
// it's not the only option of this type, so just remove it.
|
||||||
this.$emit('valueChanged', {
|
this.$emit('valueChanged', {
|
||||||
|
@ -207,30 +227,35 @@ export default mixins(genericHelpers)
|
||||||
return `${this.path}.${name}` + (index !== undefined ? `[${index}]` : '');
|
return `${this.path}.${name}` + (index !== undefined ? `[${index}]` : '');
|
||||||
},
|
},
|
||||||
getOptionProperties(optionName: string): INodePropertyCollection | undefined {
|
getOptionProperties(optionName: string): INodePropertyCollection | undefined {
|
||||||
for (const option of this.parameter.options) {
|
if(isINodePropertyCollectionList(this.parameter.options)){
|
||||||
if (option.name === optionName) {
|
for (const option of this.parameter.options) {
|
||||||
return option;
|
if (option.name === optionName) {
|
||||||
|
return option;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return undefined;
|
return undefined;
|
||||||
},
|
},
|
||||||
moveOptionDown(optionName: string, index: number) {
|
moveOptionDown(optionName: string, index: number) {
|
||||||
this.values[optionName].splice(index + 1, 0, this.values[optionName].splice(index, 1)[0]);
|
if(Array.isArray(this.mutableValues[optionName])){
|
||||||
|
this.mutableValues[optionName].splice(index + 1, 0, this.mutableValues[optionName].splice(index, 1)[0]);
|
||||||
|
}
|
||||||
|
|
||||||
const parameterData = {
|
const parameterData = {
|
||||||
name: this.getPropertyPath(optionName),
|
name: this.getPropertyPath(optionName),
|
||||||
value: this.values[optionName],
|
value: this.mutableValues[optionName],
|
||||||
};
|
};
|
||||||
|
|
||||||
this.$emit('valueChanged', parameterData);
|
this.$emit('valueChanged', parameterData);
|
||||||
},
|
},
|
||||||
moveOptionUp(optionName: string, index: number) {
|
moveOptionUp(optionName: string, index: number) {
|
||||||
this.values[optionName].splice(index - 1, 0, this.values[optionName].splice(index, 1)[0]);
|
if(Array.isArray(this.mutableValues[optionName])) {
|
||||||
|
this.mutableValues?.[optionName].splice(index - 1, 0, this.mutableValues[optionName].splice(index, 1)[0]);
|
||||||
|
}
|
||||||
|
|
||||||
const parameterData = {
|
const parameterData = {
|
||||||
name: this.getPropertyPath(optionName),
|
name: this.getPropertyPath(optionName),
|
||||||
value: this.values[optionName],
|
value: this.mutableValues[optionName],
|
||||||
};
|
};
|
||||||
|
|
||||||
this.$emit('valueChanged', parameterData);
|
this.$emit('valueChanged', parameterData);
|
||||||
|
@ -262,8 +287,8 @@ export default mixins(genericHelpers)
|
||||||
}
|
}
|
||||||
|
|
||||||
let newValue;
|
let newValue;
|
||||||
if (this.multipleValues === true) {
|
if (this.multipleValues) {
|
||||||
newValue = get(this.nodeValues, name, []);
|
newValue = get(this.nodeValues, name, [] as INodeParameters[]);
|
||||||
|
|
||||||
newValue.push(newParameterValue);
|
newValue.push(newParameterValue);
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
<n8n-button
|
<n8n-button
|
||||||
type="secondary"
|
type="secondary"
|
||||||
:label="$locale.baseText('importParameter.label')"
|
:label="$locale.baseText('importParameter.label')"
|
||||||
|
:disabled="isReadOnly"
|
||||||
size="mini"
|
size="mini"
|
||||||
@click="onImportCurlClicked"
|
@click="onImportCurlClicked"
|
||||||
/>
|
/>
|
||||||
|
@ -16,6 +17,12 @@ import { showMessage } from './mixins/showMessage';
|
||||||
|
|
||||||
export default mixins(showMessage).extend({
|
export default mixins(showMessage).extend({
|
||||||
name: 'import-parameter',
|
name: 'import-parameter',
|
||||||
|
props: {
|
||||||
|
isReadOnly: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
methods: {
|
methods: {
|
||||||
onImportCurlClicked() {
|
onImportCurlClicked() {
|
||||||
this.$store.dispatch('ui/openModal', IMPORT_CURL_MODAL_KEY);
|
this.$store.dispatch('ui/openModal', IMPORT_CURL_MODAL_KEY);
|
||||||
|
|
|
@ -8,16 +8,16 @@
|
||||||
color="text-dark"
|
color="text-dark"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div v-for="(value, index) in values" :key="index" class="duplicate-parameter-item" :class="parameter.type">
|
<div v-for="(value, index) in mutableValues" :key="index" class="duplicate-parameter-item" :class="parameter.type">
|
||||||
<div class="delete-item clickable" v-if="!isReadOnly">
|
<div class="delete-item clickable" v-if="!isReadOnly">
|
||||||
<font-awesome-icon icon="trash" :title="$locale.baseText('multipleParameter.deleteItem')" @click="deleteItem(index)" />
|
<font-awesome-icon icon="trash" :title="$locale.baseText('multipleParameter.deleteItem')" @click="deleteItem(index)" />
|
||||||
<div v-if="sortable">
|
<div v-if="sortable">
|
||||||
<font-awesome-icon v-if="index !== 0" icon="angle-up" class="clickable" :title="$locale.baseText('multipleParameter.moveUp')" @click="moveOptionUp(index)" />
|
<font-awesome-icon v-if="index !== 0" icon="angle-up" class="clickable" :title="$locale.baseText('multipleParameter.moveUp')" @click="moveOptionUp(index)" />
|
||||||
<font-awesome-icon v-if="index !== (values.length -1)" icon="angle-down" class="clickable" :title="$locale.baseText('multipleParameter.moveDown')" @click="moveOptionDown(index)" />
|
<font-awesome-icon v-if="index !== (mutableValues.length - 1)" icon="angle-down" class="clickable" :title="$locale.baseText('multipleParameter.moveDown')" @click="moveOptionDown(index)" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="parameter.type === 'collection'">
|
<div v-if="parameter.type === 'collection'">
|
||||||
<collection-parameter :parameter="parameter" :values="value" :nodeValues="nodeValues" :path="getPath(index)" :hideDelete="hideDelete" @valueChanged="valueChanged" />
|
<collection-parameter :parameter="parameter" :values="value" :nodeValues="nodeValues" :path="getPath(index)" :hideDelete="hideDelete" :isReadOnly="isReadOnly" @valueChanged="valueChanged" />
|
||||||
</div>
|
</div>
|
||||||
<div v-else>
|
<div v-else>
|
||||||
<parameter-input-full class="duplicate-parameter-input-item" :parameter="parameter" :value="value" :displayOptions="true" :hideLabel="true" :path="getPath(index)" @valueChanged="valueChanged" inputSize="small" :isReadOnly="isReadOnly" />
|
<parameter-input-full class="duplicate-parameter-input-item" :parameter="parameter" :value="value" :displayOptions="true" :hideLabel="true" :path="getPath(index)" @valueChanged="valueChanged" inputSize="small" :isReadOnly="isReadOnly" />
|
||||||
|
@ -25,7 +25,7 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="add-item-wrapper">
|
<div class="add-item-wrapper">
|
||||||
<div v-if="values && Object.keys(values).length === 0 || isReadOnly" class="no-items-exist">
|
<div v-if="mutableValues && mutableValues.length === 0 || isReadOnly" class="no-items-exist">
|
||||||
<n8n-text size="small">{{ $locale.baseText('multipleParameter.currentlyNoItemsExist') }}</n8n-text>
|
<n8n-text size="small">{{ $locale.baseText('multipleParameter.currentlyNoItemsExist') }}</n8n-text>
|
||||||
</div>
|
</div>
|
||||||
<n8n-button v-if="!isReadOnly" type="tertiary" block @click="addItem()" :label="addButtonText" />
|
<n8n-button v-if="!isReadOnly" type="tertiary" block @click="addItem()" :label="addButtonText" />
|
||||||
|
@ -34,33 +34,60 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
import Vue, { PropType } from "vue";
|
||||||
import {
|
import {
|
||||||
IUpdateInformation,
|
IUpdateInformation,
|
||||||
} from '@/Interface';
|
} from '@/Interface';
|
||||||
|
import { deepCopy, INodeParameters, INodeProperties } from "n8n-workflow";
|
||||||
import CollectionParameter from '@/components/CollectionParameter.vue';
|
import CollectionParameter from '@/components/CollectionParameter.vue';
|
||||||
import ParameterInputFull from '@/components/ParameterInputFull.vue';
|
import ParameterInputFull from '@/components/ParameterInputFull.vue';
|
||||||
|
|
||||||
import { get } from 'lodash';
|
import { get } from 'lodash';
|
||||||
|
|
||||||
import { genericHelpers } from '@/components/mixins/genericHelpers';
|
export default Vue.extend({
|
||||||
|
|
||||||
import mixins from 'vue-typed-mixins';
|
|
||||||
import { deepCopy } from "n8n-workflow";
|
|
||||||
|
|
||||||
export default mixins(genericHelpers)
|
|
||||||
.extend({
|
|
||||||
name: 'MultipleParameter',
|
name: 'MultipleParameter',
|
||||||
components: {
|
components: {
|
||||||
CollectionParameter,
|
CollectionParameter,
|
||||||
ParameterInputFull,
|
ParameterInputFull,
|
||||||
},
|
},
|
||||||
props: [
|
props: {
|
||||||
'nodeValues', // NodeParameters
|
nodeValues: {
|
||||||
'parameter', // NodeProperties
|
type: Object as PropType<Record<string, INodeParameters[]>>,
|
||||||
'path', // string
|
required: true,
|
||||||
'values', // NodeParameters[]
|
},
|
||||||
],
|
parameter: {
|
||||||
|
type: Object as PropType<INodeProperties>,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
path: {
|
||||||
|
type: String,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
values: {
|
||||||
|
type: Array as PropType<INodeParameters[]>,
|
||||||
|
default: () => [],
|
||||||
|
},
|
||||||
|
isReadOnly: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
mutableValues: [] as INodeParameters[],
|
||||||
|
};
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
values: {
|
||||||
|
handler(newValues: INodeParameters[]) {
|
||||||
|
this.mutableValues = deepCopy(newValues);
|
||||||
|
},
|
||||||
|
deep: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
created(){
|
||||||
|
this.mutableValues = deepCopy(this.values);
|
||||||
|
},
|
||||||
computed: {
|
computed: {
|
||||||
addButtonText (): string {
|
addButtonText (): string {
|
||||||
if (
|
if (
|
||||||
|
@ -73,22 +100,18 @@ export default mixins(genericHelpers)
|
||||||
return this.$locale.nodeText().multipleValueButtonText(this.parameter);
|
return this.$locale.nodeText().multipleValueButtonText(this.parameter);
|
||||||
},
|
},
|
||||||
hideDelete (): boolean {
|
hideDelete (): boolean {
|
||||||
return this.parameter.options.length === 1;
|
return this.parameter.options?.length === 1;
|
||||||
},
|
},
|
||||||
sortable (): string {
|
sortable (): boolean {
|
||||||
return this.parameter.typeOptions && this.parameter.typeOptions.sortable;
|
return !!this.parameter.typeOptions?.sortable;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
addItem () {
|
addItem () {
|
||||||
const name = this.getPath();
|
const name = this.getPath();
|
||||||
let currentValue = get(this.nodeValues, name);
|
const currentValue = get(this.nodeValues, name, [] as INodeParameters[]);
|
||||||
|
|
||||||
if (currentValue === undefined) {
|
currentValue.push(deepCopy(this.parameter.default as INodeParameters));
|
||||||
currentValue = [];
|
|
||||||
}
|
|
||||||
|
|
||||||
currentValue.push(deepCopy(this.parameter.default));
|
|
||||||
|
|
||||||
const parameterData = {
|
const parameterData = {
|
||||||
name,
|
name,
|
||||||
|
@ -109,21 +132,21 @@ export default mixins(genericHelpers)
|
||||||
return this.path + (index !== undefined ? `[${index}]` : '');
|
return this.path + (index !== undefined ? `[${index}]` : '');
|
||||||
},
|
},
|
||||||
moveOptionDown (index: number) {
|
moveOptionDown (index: number) {
|
||||||
this.values.splice(index + 1, 0, this.values.splice(index, 1)[0]);
|
this.mutableValues.splice(index + 1, 0, this.mutableValues.splice(index, 1)[0]);
|
||||||
|
|
||||||
const parameterData = {
|
const parameterData = {
|
||||||
name: this.path,
|
name: this.path,
|
||||||
value: this.values,
|
value: this.mutableValues,
|
||||||
};
|
};
|
||||||
|
|
||||||
this.$emit('valueChanged', parameterData);
|
this.$emit('valueChanged', parameterData);
|
||||||
},
|
},
|
||||||
moveOptionUp (index: number) {
|
moveOptionUp (index: number) {
|
||||||
this.values.splice(index - 1, 0, this.values.splice(index, 1)[0]);
|
this.mutableValues.splice(index - 1, 0, this.mutableValues.splice(index, 1)[0]);
|
||||||
|
|
||||||
const parameterData = {
|
const parameterData = {
|
||||||
name: this.path,
|
name: this.path,
|
||||||
value: this.values,
|
value: this.mutableValues,
|
||||||
};
|
};
|
||||||
|
|
||||||
this.$emit('valueChanged', parameterData);
|
this.$emit('valueChanged', parameterData);
|
||||||
|
|
|
@ -63,10 +63,10 @@
|
||||||
<div v-touch:tap="duplicateNode" class="option" :title="$locale.baseText('node.duplicateNode')" v-if="isDuplicatable">
|
<div v-touch:tap="duplicateNode" class="option" :title="$locale.baseText('node.duplicateNode')" v-if="isDuplicatable">
|
||||||
<font-awesome-icon icon="clone" />
|
<font-awesome-icon icon="clone" />
|
||||||
</div>
|
</div>
|
||||||
<div v-touch:tap="setNodeActive" class="option touch" :title="$locale.baseText('node.editNode')" v-if="!isReadOnly">
|
<div v-touch:tap="setNodeActive" class="option touch" :title="$locale.baseText('node.editNode')">
|
||||||
<font-awesome-icon class="execute-icon" icon="cog" />
|
<font-awesome-icon class="execute-icon" icon="cog" />
|
||||||
</div>
|
</div>
|
||||||
<div v-touch:tap="executeNode" class="option" :title="$locale.baseText('node.executeNode')" v-if="!isReadOnly && !workflowRunning">
|
<div v-touch:tap="executeNode" class="option" :title="$locale.baseText('node.executeNode')" v-if="!workflowRunning">
|
||||||
<font-awesome-icon class="execute-icon" icon="play-circle" />
|
<font-awesome-icon class="execute-icon" icon="play-circle" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -54,7 +54,7 @@
|
||||||
:linkedRuns="linked"
|
:linkedRuns="linked"
|
||||||
:currentNodeName="inputNodeName"
|
:currentNodeName="inputNodeName"
|
||||||
:sessionId="sessionId"
|
:sessionId="sessionId"
|
||||||
:readOnly="readOnly"
|
:readOnly="readOnly || hasForeignCredential"
|
||||||
@linkRun="onLinkRunToInput"
|
@linkRun="onLinkRunToInput"
|
||||||
@unlinkRun="() => onUnlinkRun('input')"
|
@unlinkRun="() => onUnlinkRun('input')"
|
||||||
@runChange="onRunInputIndexChange"
|
@runChange="onRunInputIndexChange"
|
||||||
|
@ -71,7 +71,7 @@
|
||||||
:runIndex="outputRun"
|
:runIndex="outputRun"
|
||||||
:linkedRuns="linked"
|
:linkedRuns="linked"
|
||||||
:sessionId="sessionId"
|
:sessionId="sessionId"
|
||||||
:isReadOnly="readOnly"
|
:isReadOnly="readOnly || hasForeignCredential"
|
||||||
@linkRun="onLinkRunToOutput"
|
@linkRun="onLinkRunToOutput"
|
||||||
@unlinkRun="() => onUnlinkRun('output')"
|
@unlinkRun="() => onUnlinkRun('output')"
|
||||||
@runChange="onRunOutputIndexChange"
|
@runChange="onRunOutputIndexChange"
|
||||||
|
@ -86,6 +86,7 @@
|
||||||
:dragging="isDragging"
|
:dragging="isDragging"
|
||||||
:sessionId="sessionId"
|
:sessionId="sessionId"
|
||||||
:nodeType="activeNodeType"
|
:nodeType="activeNodeType"
|
||||||
|
:isReadOnly="readOnly || hasForeignCredential"
|
||||||
@valueChanged="valueChanged"
|
@valueChanged="valueChanged"
|
||||||
@execute="onNodeExecute"
|
@execute="onNodeExecute"
|
||||||
@activate="onWorkflowActivate"
|
@activate="onWorkflowActivate"
|
||||||
|
@ -114,7 +115,7 @@ import {
|
||||||
Workflow,
|
Workflow,
|
||||||
jsonParse,
|
jsonParse,
|
||||||
} from 'n8n-workflow';
|
} from 'n8n-workflow';
|
||||||
import { IExecutionResponse, INodeUi, IUpdateInformation, TargetItem } from '../Interface';
|
import { IExecutionResponse, INodeUi, IUpdateInformation, TargetItem } from '@/Interface';
|
||||||
|
|
||||||
import { externalHooks } from '@/components/mixins/externalHooks';
|
import { externalHooks } from '@/components/mixins/externalHooks';
|
||||||
import { nodeHelpers } from '@/components/mixins/nodeHelpers';
|
import { nodeHelpers } from '@/components/mixins/nodeHelpers';
|
||||||
|
@ -136,7 +137,7 @@ import {
|
||||||
} from '@/constants';
|
} from '@/constants';
|
||||||
import { workflowActivate } from './mixins/workflowActivate';
|
import { workflowActivate } from './mixins/workflowActivate';
|
||||||
import { pinData } from "@/components/mixins/pinData";
|
import { pinData } from "@/components/mixins/pinData";
|
||||||
import { dataPinningEventBus } from '../event-bus/data-pinning-event-bus';
|
import { dataPinningEventBus } from '@/event-bus/data-pinning-event-bus';
|
||||||
|
|
||||||
export default mixins(
|
export default mixins(
|
||||||
externalHooks,
|
externalHooks,
|
||||||
|
@ -174,6 +175,7 @@ export default mixins(
|
||||||
pinDataDiscoveryTooltipVisible: false,
|
pinDataDiscoveryTooltipVisible: false,
|
||||||
avgInputRowHeight: 0,
|
avgInputRowHeight: 0,
|
||||||
avgOutputRowHeight: 0,
|
avgOutputRowHeight: 0,
|
||||||
|
hasForeignCredential: false,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
|
@ -360,6 +362,8 @@ export default mixins(
|
||||||
nodeSubtitle: this.getNodeSubtitle(node, this.activeNodeType, this.getCurrentWorkflow()),
|
nodeSubtitle: this.getNodeSubtitle(node, this.activeNodeType, this.getCurrentWorkflow()),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
this.checkForeignCredentials();
|
||||||
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
if (this.activeNode) {
|
if (this.activeNode) {
|
||||||
const outogingConnections = this.$store.getters.outgoingConnectionsByNodeName(
|
const outogingConnections = this.$store.getters.outgoingConnectionsByNodeName(
|
||||||
|
@ -603,6 +607,10 @@ export default mixins(
|
||||||
input_node_type: this.inputNode ? this.inputNode.type : '',
|
input_node_type: this.inputNode ? this.inputNode.type : '',
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
checkForeignCredentials() {
|
||||||
|
const issues = this.getNodeCredentialIssues(this.activeNode);
|
||||||
|
this.hasForeignCredential = !!issues?.credentials?.foreign;
|
||||||
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -7,8 +7,8 @@
|
||||||
class="node-name"
|
class="node-name"
|
||||||
:value="node && node.name"
|
:value="node && node.name"
|
||||||
:nodeType="nodeType"
|
:nodeType="nodeType"
|
||||||
|
:isReadOnly="isReadOnly"
|
||||||
@input="nameChanged"
|
@input="nameChanged"
|
||||||
:readOnly="isReadOnly"
|
|
||||||
></NodeTitle>
|
></NodeTitle>
|
||||||
<div v-if="!isReadOnly">
|
<div v-if="!isReadOnly">
|
||||||
<NodeExecuteButton
|
<NodeExecuteButton
|
||||||
|
@ -72,11 +72,11 @@
|
||||||
:parameters="parametersNoneSetting"
|
:parameters="parametersNoneSetting"
|
||||||
:hideDelete="true"
|
:hideDelete="true"
|
||||||
:nodeValues="nodeValues"
|
:nodeValues="nodeValues"
|
||||||
|
:isReadOnly="isReadOnly"
|
||||||
path="parameters"
|
path="parameters"
|
||||||
@valueChanged="valueChanged"
|
@valueChanged="valueChanged"
|
||||||
@activate="onWorkflowActivate"
|
@activate="onWorkflowActivate"
|
||||||
>
|
>
|
||||||
|
|
||||||
<node-credentials :node="node" @credentialSelected="credentialSelected" />
|
<node-credentials :node="node" @credentialSelected="credentialSelected" />
|
||||||
</parameter-input-list>
|
</parameter-input-list>
|
||||||
<div v-if="parametersNoneSetting.length === 0" class="no-parameters">
|
<div v-if="parametersNoneSetting.length === 0" class="no-parameters">
|
||||||
|
@ -99,6 +99,7 @@
|
||||||
<parameter-input-list
|
<parameter-input-list
|
||||||
:parameters="parametersSetting"
|
:parameters="parametersSetting"
|
||||||
:nodeValues="nodeValues"
|
:nodeValues="nodeValues"
|
||||||
|
:isReadOnly="isReadOnly"
|
||||||
path="parameters"
|
path="parameters"
|
||||||
@valueChanged="valueChanged"
|
@valueChanged="valueChanged"
|
||||||
/>
|
/>
|
||||||
|
@ -106,6 +107,7 @@
|
||||||
:parameters="nodeSettings"
|
:parameters="nodeSettings"
|
||||||
:hideDelete="true"
|
:hideDelete="true"
|
||||||
:nodeValues="nodeValues"
|
:nodeValues="nodeValues"
|
||||||
|
:isReadOnly="isReadOnly"
|
||||||
path=""
|
path=""
|
||||||
@valueChanged="valueChanged"
|
@valueChanged="valueChanged"
|
||||||
/>
|
/>
|
||||||
|
@ -142,14 +144,13 @@ import NodeWebhooks from '@/components/NodeWebhooks.vue';
|
||||||
import { get, set, unset } from 'lodash';
|
import { get, set, unset } from 'lodash';
|
||||||
|
|
||||||
import { externalHooks } from '@/components/mixins/externalHooks';
|
import { externalHooks } from '@/components/mixins/externalHooks';
|
||||||
import { genericHelpers } from '@/components/mixins/genericHelpers';
|
|
||||||
import { nodeHelpers } from '@/components/mixins/nodeHelpers';
|
import { nodeHelpers } from '@/components/mixins/nodeHelpers';
|
||||||
|
|
||||||
import mixins from 'vue-typed-mixins';
|
import mixins from 'vue-typed-mixins';
|
||||||
import NodeExecuteButton from './NodeExecuteButton.vue';
|
import NodeExecuteButton from './NodeExecuteButton.vue';
|
||||||
import { isCommunityPackageName } from './helpers';
|
import { isCommunityPackageName } from './helpers';
|
||||||
|
|
||||||
export default mixins(externalHooks, genericHelpers, nodeHelpers).extend({
|
export default mixins(externalHooks, nodeHelpers).extend({
|
||||||
name: 'NodeSettings',
|
name: 'NodeSettings',
|
||||||
components: {
|
components: {
|
||||||
NodeTitle,
|
NodeTitle,
|
||||||
|
@ -235,6 +236,9 @@ export default mixins(externalHooks, genericHelpers, nodeHelpers).extend({
|
||||||
nodeType: {
|
nodeType: {
|
||||||
type: Object as PropType<INodeTypeDescription>,
|
type: Object as PropType<INodeTypeDescription>,
|
||||||
},
|
},
|
||||||
|
isReadOnly: {
|
||||||
|
type: Boolean,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
|
|
|
@ -40,6 +40,7 @@
|
||||||
:rows="getArgument('rows')"
|
:rows="getArgument('rows')"
|
||||||
:value="expressionDisplayValue"
|
:value="expressionDisplayValue"
|
||||||
:title="displayTitle"
|
:title="displayTitle"
|
||||||
|
:readOnly="isReadOnly"
|
||||||
@keydown.stop
|
@keydown.stop
|
||||||
/>
|
/>
|
||||||
<div
|
<div
|
||||||
|
@ -64,6 +65,7 @@
|
||||||
:value="value"
|
:value="value"
|
||||||
:parameter="parameter"
|
:parameter="parameter"
|
||||||
:path="path"
|
:path="path"
|
||||||
|
:isReadOnly="isReadOnly"
|
||||||
@closeDialog="closeTextEditDialog"
|
@closeDialog="closeTextEditDialog"
|
||||||
@valueChanged="expressionUpdated"
|
@valueChanged="expressionUpdated"
|
||||||
></text-edit>
|
></text-edit>
|
||||||
|
|
|
@ -12,12 +12,14 @@
|
||||||
:values="getParameterValue(nodeValues, parameter.name, path)"
|
:values="getParameterValue(nodeValues, parameter.name, path)"
|
||||||
:nodeValues="nodeValues"
|
:nodeValues="nodeValues"
|
||||||
:path="getPath(parameter.name)"
|
:path="getPath(parameter.name)"
|
||||||
|
:isReadOnly="isReadOnly"
|
||||||
@valueChanged="valueChanged"
|
@valueChanged="valueChanged"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<import-parameter
|
<import-parameter
|
||||||
v-else-if="parameter.type === 'curlImport' && nodeTypeName === 'n8n-nodes-base.httpRequest' && nodeTypeVersion >= 3"
|
v-else-if="parameter.type === 'curlImport' && nodeTypeName === 'n8n-nodes-base.httpRequest' && nodeTypeVersion >= 3"
|
||||||
|
:isReadOnly="isReadOnly"
|
||||||
@valueChanged="valueChanged"
|
@valueChanged="valueChanged"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
@ -53,6 +55,7 @@
|
||||||
:values="getParameterValue(nodeValues, parameter.name, path)"
|
:values="getParameterValue(nodeValues, parameter.name, path)"
|
||||||
:nodeValues="nodeValues"
|
:nodeValues="nodeValues"
|
||||||
:path="getPath(parameter.name)"
|
:path="getPath(parameter.name)"
|
||||||
|
:isReadOnly="isReadOnly"
|
||||||
@valueChanged="valueChanged"
|
@valueChanged="valueChanged"
|
||||||
/>
|
/>
|
||||||
<fixed-collection-parameter
|
<fixed-collection-parameter
|
||||||
|
@ -61,6 +64,7 @@
|
||||||
:values="getParameterValue(nodeValues, parameter.name, path)"
|
:values="getParameterValue(nodeValues, parameter.name, path)"
|
||||||
:nodeValues="nodeValues"
|
:nodeValues="nodeValues"
|
||||||
:path="getPath(parameter.name)"
|
:path="getPath(parameter.name)"
|
||||||
|
:isReadOnly="isReadOnly"
|
||||||
@valueChanged="valueChanged"
|
@valueChanged="valueChanged"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
@ -107,7 +111,6 @@ import {
|
||||||
import { INodeUi, IUpdateInformation } from '@/Interface';
|
import { INodeUi, IUpdateInformation } from '@/Interface';
|
||||||
|
|
||||||
import MultipleParameter from '@/components/MultipleParameter.vue';
|
import MultipleParameter from '@/components/MultipleParameter.vue';
|
||||||
import { genericHelpers } from '@/components/mixins/genericHelpers';
|
|
||||||
import { workflowHelpers } from '@/components/mixins/workflowHelpers';
|
import { workflowHelpers } from '@/components/mixins/workflowHelpers';
|
||||||
import ParameterInputFull from '@/components/ParameterInputFull.vue';
|
import ParameterInputFull from '@/components/ParameterInputFull.vue';
|
||||||
import ImportParameter from '@/components/ImportParameter.vue';
|
import ImportParameter from '@/components/ImportParameter.vue';
|
||||||
|
@ -118,7 +121,6 @@ import mixins from 'vue-typed-mixins';
|
||||||
import {Component} from "vue";
|
import {Component} from "vue";
|
||||||
|
|
||||||
export default mixins(
|
export default mixins(
|
||||||
genericHelpers,
|
|
||||||
workflowHelpers,
|
workflowHelpers,
|
||||||
)
|
)
|
||||||
.extend({
|
.extend({
|
||||||
|
@ -136,6 +138,7 @@ export default mixins(
|
||||||
'path', // string
|
'path', // string
|
||||||
'hideDelete', // boolean
|
'hideDelete', // boolean
|
||||||
'indent',
|
'indent',
|
||||||
|
'isReadOnly',
|
||||||
],
|
],
|
||||||
computed: {
|
computed: {
|
||||||
nodeTypeVersion(): number | null {
|
nodeTypeVersion(): number | null {
|
||||||
|
|
|
@ -84,6 +84,7 @@
|
||||||
:size="inputSize"
|
:size="inputSize"
|
||||||
:value="expressionDisplayValue"
|
:value="expressionDisplayValue"
|
||||||
:title="displayTitle"
|
:title="displayTitle"
|
||||||
|
:disabled="isReadOnly"
|
||||||
@keydown.stop
|
@keydown.stop
|
||||||
ref="input"
|
ref="input"
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
<div class="ignore-key-press">
|
<div class="ignore-key-press">
|
||||||
<n8n-input-label :label="$locale.nodeText().inputLabelDisplayName(parameter, path)">
|
<n8n-input-label :label="$locale.nodeText().inputLabelDisplayName(parameter, path)">
|
||||||
<div @keydown.stop @keydown.esc="onKeyDownEsc()">
|
<div @keydown.stop @keydown.esc="onKeyDownEsc()">
|
||||||
<n8n-input v-model="tempValue" type="textarea" ref="inputField" :value="value" :placeholder="$locale.nodeText().placeholder(parameter, path)" @change="valueChanged" @keydown.stop="noOp" :rows="15" />
|
<n8n-input v-model="tempValue" type="textarea" ref="inputField" :value="value" :placeholder="$locale.nodeText().placeholder(parameter, path)" :readOnly="isReadOnly" @change="valueChanged" :rows="15" />
|
||||||
</div>
|
</div>
|
||||||
</n8n-input-label>
|
</n8n-input-label>
|
||||||
</div>
|
</div>
|
||||||
|
@ -24,6 +24,7 @@ export default Vue.extend({
|
||||||
'parameter',
|
'parameter',
|
||||||
'path',
|
'path',
|
||||||
'value',
|
'value',
|
||||||
|
'isReadOnly',
|
||||||
],
|
],
|
||||||
data () {
|
data () {
|
||||||
return {
|
return {
|
||||||
|
|
|
@ -17,7 +17,7 @@ export const genericHelpers = mixins(showMessage).extend({
|
||||||
methods: {
|
methods: {
|
||||||
displayTimer (msPassed: number, showMs = false): string {
|
displayTimer (msPassed: number, showMs = false): string {
|
||||||
if (msPassed < 60000) {
|
if (msPassed < 60000) {
|
||||||
if (showMs === false) {
|
if (!showMs) {
|
||||||
return `${Math.floor(msPassed / 1000)} ${this.$locale.baseText('genericHelpers.sec')}`;
|
return `${Math.floor(msPassed / 1000)} ${this.$locale.baseText('genericHelpers.sec')}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,11 +1,9 @@
|
||||||
import { IEndpointOptions, INodeUi, XYPosition } from '@/Interface';
|
import { PropType } from "vue";
|
||||||
|
|
||||||
import mixins from 'vue-typed-mixins';
|
import mixins from 'vue-typed-mixins';
|
||||||
|
import { IJsPlumbInstance, IEndpointOptions, INodeUi, XYPosition } from '@/Interface';
|
||||||
import { deviceSupportHelpers } from '@/components/mixins/deviceSupportHelpers';
|
import { deviceSupportHelpers } from '@/components/mixins/deviceSupportHelpers';
|
||||||
import { NO_OP_NODE_TYPE, STICKY_NODE_TYPE } from '@/constants';
|
import { NO_OP_NODE_TYPE, STICKY_NODE_TYPE } from '@/constants';
|
||||||
import * as CanvasHelpers from '@/views/canvasHelpers';
|
import * as CanvasHelpers from '@/views/canvasHelpers';
|
||||||
import { Endpoint } from 'jsplumb';
|
|
||||||
|
|
||||||
import {
|
import {
|
||||||
INodeTypeDescription,
|
INodeTypeDescription,
|
||||||
|
@ -34,9 +32,7 @@ export const nodeBase = mixins(
|
||||||
type: String,
|
type: String,
|
||||||
},
|
},
|
||||||
instance: {
|
instance: {
|
||||||
// We can't use PropType<jsPlumbInstance> here because the version of jsplumb doesn't
|
type: Object as PropType<IJsPlumbInstance>,
|
||||||
// include correct typing for draggable instance(`clearDragSelection`, `destroyDraggable`, etc.)
|
|
||||||
type: Object,
|
|
||||||
},
|
},
|
||||||
isReadOnly: {
|
isReadOnly: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
|
@ -104,13 +100,15 @@ export const nodeBase = mixins(
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
const endpoint: Endpoint = this.instance.addEndpoint(this.nodeId, newEndpointData);
|
const endpoint = this.instance.addEndpoint(this.nodeId, newEndpointData);
|
||||||
endpoint.__meta = {
|
if(!Array.isArray(endpoint)) {
|
||||||
nodeName: node.name,
|
endpoint.__meta = {
|
||||||
nodeId: this.nodeId,
|
nodeName: node.name,
|
||||||
index: i,
|
nodeId: this.nodeId,
|
||||||
totalEndpoints: nodeTypeData.inputs.length,
|
index: i,
|
||||||
};
|
totalEndpoints: nodeTypeData.inputs.length,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
// TODO: Activate again if it makes sense. Currently makes problems when removing
|
// TODO: Activate again if it makes sense. Currently makes problems when removing
|
||||||
// connection on which the input has a name. It does not get hidden because
|
// connection on which the input has a name. It does not get hidden because
|
||||||
|
@ -159,7 +157,7 @@ export const nodeBase = mixins(
|
||||||
},
|
},
|
||||||
cssClass: 'dot-output-endpoint',
|
cssClass: 'dot-output-endpoint',
|
||||||
dragAllowedWhenFull: false,
|
dragAllowedWhenFull: false,
|
||||||
dragProxy: ['Rectangle', { width: 1, height: 1, strokeWidth: 0 }],
|
dragProxy: ['Rectangle', {width: 1, height: 1, strokeWidth: 0}],
|
||||||
};
|
};
|
||||||
|
|
||||||
if (nodeTypeData.outputNames) {
|
if (nodeTypeData.outputNames) {
|
||||||
|
@ -169,13 +167,15 @@ export const nodeBase = mixins(
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
const endpoint: Endpoint = this.instance.addEndpoint(this.nodeId, {...newEndpointData});
|
const endpoint = this.instance.addEndpoint(this.nodeId, {...newEndpointData});
|
||||||
endpoint.__meta = {
|
if(!Array.isArray(endpoint)) {
|
||||||
nodeName: node.name,
|
endpoint.__meta = {
|
||||||
nodeId: this.nodeId,
|
nodeName: node.name,
|
||||||
index: i,
|
nodeId: this.nodeId,
|
||||||
totalEndpoints: nodeTypeData.outputs.length,
|
index: i,
|
||||||
};
|
totalEndpoints: nodeTypeData.outputs.length,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
if (!this.isReadOnly) {
|
if (!this.isReadOnly) {
|
||||||
const plusEndpointData: IEndpointOptions = {
|
const plusEndpointData: IEndpointOptions = {
|
||||||
|
@ -206,16 +206,18 @@ export const nodeBase = mixins(
|
||||||
},
|
},
|
||||||
cssClass: 'plus-draggable-endpoint',
|
cssClass: 'plus-draggable-endpoint',
|
||||||
dragAllowedWhenFull: false,
|
dragAllowedWhenFull: false,
|
||||||
dragProxy: ['Rectangle', { width: 1, height: 1, strokeWidth: 0 }],
|
dragProxy: ['Rectangle', {width: 1, height: 1, strokeWidth: 0}],
|
||||||
};
|
};
|
||||||
|
|
||||||
const plusEndpoint: Endpoint = this.instance.addEndpoint(this.nodeId, plusEndpointData);
|
const plusEndpoint = this.instance.addEndpoint(this.nodeId, plusEndpointData);
|
||||||
plusEndpoint.__meta = {
|
if(!Array.isArray(plusEndpoint)) {
|
||||||
nodeName: node.name,
|
plusEndpoint.__meta = {
|
||||||
nodeId: this.nodeId,
|
nodeName: node.name,
|
||||||
index: i,
|
nodeId: this.nodeId,
|
||||||
totalEndpoints: nodeTypeData.outputs.length,
|
index: i,
|
||||||
};
|
totalEndpoints: nodeTypeData.outputs.length,
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
|
@ -27,7 +27,7 @@ import {
|
||||||
import {
|
import {
|
||||||
ICredentialsResponse,
|
ICredentialsResponse,
|
||||||
INodeUi,
|
INodeUi,
|
||||||
} from '../../Interface';
|
} from '@/Interface';
|
||||||
|
|
||||||
import { restApi } from '@/components/mixins/restApi';
|
import { restApi } from '@/components/mixins/restApi';
|
||||||
|
|
||||||
|
@ -214,31 +214,36 @@ export const nodeHelpers = mixins(
|
||||||
|
|
||||||
// Returns all the credential-issues of the node
|
// Returns all the credential-issues of the node
|
||||||
getNodeCredentialIssues (node: INodeUi, nodeType?: INodeTypeDescription): INodeIssues | null {
|
getNodeCredentialIssues (node: INodeUi, nodeType?: INodeTypeDescription): INodeIssues | null {
|
||||||
if (node.disabled === true) {
|
if (node.disabled) {
|
||||||
// Node is disabled
|
// Node is disabled
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (nodeType === undefined) {
|
if (!nodeType) {
|
||||||
nodeType = this.$store.getters['nodeTypes/getNodeType'](node.type, node.typeVersion);
|
nodeType = this.$store.getters['nodeTypes/getNodeType'](node.type, node.typeVersion);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (nodeType === null || nodeType!.credentials === undefined) {
|
if (!nodeType?.credentials) {
|
||||||
// Node does not need any credentials or nodeType could not be found
|
// Node does not need any credentials or nodeType could not be found
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (nodeType!.credentials === undefined) {
|
|
||||||
// No credentials defined for node type
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
const foundIssues: INodeIssueObjectProperty = {};
|
const foundIssues: INodeIssueObjectProperty = {};
|
||||||
|
|
||||||
let userCredentials: ICredentialsResponse[] | null;
|
let userCredentials: ICredentialsResponse[] | null;
|
||||||
let credentialType: ICredentialType | null;
|
let credentialType: ICredentialType | null;
|
||||||
let credentialDisplayName: string;
|
let credentialDisplayName: string;
|
||||||
let selectedCredentials: INodeCredentialsDetails;
|
let selectedCredentials: INodeCredentialsDetails;
|
||||||
|
const foreignCredentials = this.$store.getters['credentials/allForeignCredentials'];
|
||||||
|
|
||||||
|
// TODO: Check if any of the node credentials is found in foreign credentials
|
||||||
|
if(foreignCredentials?.some(() => true)){
|
||||||
|
return {
|
||||||
|
credentials: {
|
||||||
|
foreign: [],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
const {
|
const {
|
||||||
authentication,
|
authentication,
|
||||||
|
@ -279,9 +284,9 @@ export const nodeHelpers = mixins(
|
||||||
return this.reportUnsetCredential(credential);
|
return this.reportUnsetCredential(credential);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const credentialTypeDescription of nodeType!.credentials!) {
|
for (const credentialTypeDescription of nodeType.credentials) {
|
||||||
// Check if credentials should be displayed else ignore
|
// Check if credentials should be displayed else ignore
|
||||||
if (this.displayParameter(node.parameters, credentialTypeDescription, '', node) !== true) {
|
if (!this.displayParameter(node.parameters, credentialTypeDescription, '', node)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -293,9 +298,9 @@ export const nodeHelpers = mixins(
|
||||||
credentialDisplayName = credentialType.displayName;
|
credentialDisplayName = credentialType.displayName;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (node.credentials === undefined || node.credentials[credentialTypeDescription.name] === undefined) {
|
if (!node.credentials || !node.credentials?.[credentialTypeDescription.name]) {
|
||||||
// Credentials are not set
|
// Credentials are not set
|
||||||
if (credentialTypeDescription.required === true) {
|
if (credentialTypeDescription.required) {
|
||||||
foundIssues[credentialTypeDescription.name] = [this.$locale.baseText('nodeIssues.credentials.notSet', { interpolate: { type: credentialDisplayName } })];
|
foundIssues[credentialTypeDescription.name] = [this.$locale.baseText('nodeIssues.credentials.notSet', { interpolate: { type: credentialDisplayName } })];
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
@ -493,7 +498,7 @@ export const nodeHelpers = mixins(
|
||||||
* selected credentials are of the specified type.
|
* selected credentials are of the specified type.
|
||||||
*/
|
*/
|
||||||
function selectedCredsAreUnusable(node: INodeUi, credentialType: string) {
|
function selectedCredsAreUnusable(node: INodeUi, credentialType: string) {
|
||||||
return node.credentials === undefined || Object.keys(node.credentials).includes(credentialType) === false;
|
return !node.credentials || !Object.keys(node.credentials).includes(credentialType);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import { getCredentialTypes,
|
import {
|
||||||
|
getCredentialTypes,
|
||||||
getCredentialsNewName,
|
getCredentialsNewName,
|
||||||
getAllCredentials,
|
getAllCredentials,
|
||||||
deleteCredential,
|
deleteCredential,
|
||||||
|
@ -8,6 +9,7 @@ import { getCredentialTypes,
|
||||||
oAuth2CredentialAuthorize,
|
oAuth2CredentialAuthorize,
|
||||||
oAuth1CredentialAuthorize,
|
oAuth1CredentialAuthorize,
|
||||||
testCredential,
|
testCredential,
|
||||||
|
getForeignCredentials,
|
||||||
} from '@/api/credentials';
|
} from '@/api/credentials';
|
||||||
import Vue from 'vue';
|
import Vue from 'vue';
|
||||||
import { ActionContext, Module } from 'vuex';
|
import { ActionContext, Module } from 'vuex';
|
||||||
|
@ -17,7 +19,7 @@ import {
|
||||||
ICredentialsState,
|
ICredentialsState,
|
||||||
ICredentialTypeMap,
|
ICredentialTypeMap,
|
||||||
IRootState,
|
IRootState,
|
||||||
} from '../Interface';
|
} from '@/Interface';
|
||||||
import {
|
import {
|
||||||
ICredentialType,
|
ICredentialType,
|
||||||
ICredentialsDecrypted,
|
ICredentialsDecrypted,
|
||||||
|
@ -58,6 +60,15 @@ const module: Module<ICredentialsState, IRootState> = {
|
||||||
return accu;
|
return accu;
|
||||||
}, {});
|
}, {});
|
||||||
},
|
},
|
||||||
|
setForeignCredentials: (state: ICredentialsState, credentials: ICredentialsResponse[]) => {
|
||||||
|
state.foreignCredentials = credentials.reduce((accu: ICredentialMap, cred: ICredentialsResponse) => {
|
||||||
|
if (cred.id) {
|
||||||
|
accu[cred.id] = cred;
|
||||||
|
}
|
||||||
|
|
||||||
|
return accu;
|
||||||
|
}, {});
|
||||||
|
},
|
||||||
upsertCredential(state: ICredentialsState, credential: ICredentialsResponse) {
|
upsertCredential(state: ICredentialsState, credential: ICredentialsResponse) {
|
||||||
if (credential.id) {
|
if (credential.id) {
|
||||||
Vue.set(state.credentials, credential.id, { ...state.credentials[credential.id], ...credential });
|
Vue.set(state.credentials, credential.id, { ...state.credentials[credential.id], ...credential });
|
||||||
|
@ -83,6 +94,10 @@ const module: Module<ICredentialsState, IRootState> = {
|
||||||
return Object.values(state.credentials)
|
return Object.values(state.credentials)
|
||||||
.sort((a, b) => a.name.localeCompare(b.name));
|
.sort((a, b) => a.name.localeCompare(b.name));
|
||||||
},
|
},
|
||||||
|
allForeignCredentials(state: ICredentialsState): ICredentialsResponse[] {
|
||||||
|
return Object.values(state.foreignCredentials || {})
|
||||||
|
.sort((a, b) => a.name.localeCompare(b.name));
|
||||||
|
},
|
||||||
allCredentialsByType(state: ICredentialsState, getters: any): {[type: string]: ICredentialsResponse[]} { // tslint:disable-line:no-any
|
allCredentialsByType(state: ICredentialsState, getters: any): {[type: string]: ICredentialsResponse[]} { // tslint:disable-line:no-any
|
||||||
const credentials = getters.allCredentials as ICredentialsResponse[];
|
const credentials = getters.allCredentials as ICredentialsResponse[];
|
||||||
const types = getters.allCredentialTypes as ICredentialType[];
|
const types = getters.allCredentialTypes as ICredentialType[];
|
||||||
|
@ -181,6 +196,12 @@ const module: Module<ICredentialsState, IRootState> = {
|
||||||
|
|
||||||
return credentials;
|
return credentials;
|
||||||
},
|
},
|
||||||
|
fetchForeignCredentials: async (context: ActionContext<ICredentialsState, IRootState>): Promise<ICredentialsResponse[]> => {
|
||||||
|
const credentials = await getForeignCredentials(context.rootGetters.getRestApiContext);
|
||||||
|
context.commit('setForeignCredentials', credentials);
|
||||||
|
|
||||||
|
return credentials;
|
||||||
|
},
|
||||||
getCredentialData: async (context: ActionContext<ICredentialsState, IRootState>, { id }: {id: string}) => {
|
getCredentialData: async (context: ActionContext<ICredentialsState, IRootState>, { id }: {id: string}) => {
|
||||||
return await getCredentialData(context.rootGetters.getRestApiContext, id);
|
return await getCredentialData(context.rootGetters.getRestApiContext, id);
|
||||||
},
|
},
|
||||||
|
|
|
@ -15,6 +15,7 @@ import {
|
||||||
} from 'n8n-design-system';
|
} from 'n8n-design-system';
|
||||||
|
|
||||||
import englishBaseText from './locales/en.json';
|
import englishBaseText from './locales/en.json';
|
||||||
|
import { INodeProperties } from "n8n-workflow";
|
||||||
|
|
||||||
Vue.use(VueI18n);
|
Vue.use(VueI18n);
|
||||||
locale.use('en');
|
locale.use('en');
|
||||||
|
@ -335,12 +336,11 @@ export class I18nClass {
|
||||||
* `fixedCollection` param having `multipleValues: true`.
|
* `fixedCollection` param having `multipleValues: true`.
|
||||||
*/
|
*/
|
||||||
multipleValueButtonText(
|
multipleValueButtonText(
|
||||||
{ name: parameterName, typeOptions: { multipleValueButtonText } }:
|
{ name: parameterName, typeOptions}: INodeProperties,
|
||||||
{ name: string; typeOptions: { multipleValueButtonText: string; } },
|
|
||||||
) {
|
) {
|
||||||
return context.dynamicRender({
|
return context.dynamicRender({
|
||||||
key: `${initialKey}.${parameterName}.multipleValueButtonText`,
|
key: `${initialKey}.${parameterName}.multipleValueButtonText`,
|
||||||
fallback: multipleValueButtonText,
|
fallback: typeOptions!.multipleValueButtonText!,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
|
@ -254,26 +254,6 @@ export default mixins(
|
||||||
// When a node gets set as active deactivate the create-menu
|
// When a node gets set as active deactivate the create-menu
|
||||||
this.createNodeActive = false;
|
this.createNodeActive = false;
|
||||||
},
|
},
|
||||||
nodes: {
|
|
||||||
async handler () {
|
|
||||||
// Load a workflow
|
|
||||||
let workflowId = null as string | null;
|
|
||||||
if (this.$route && this.$route.params.name) {
|
|
||||||
workflowId = this.$route.params.name;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
deep: true,
|
|
||||||
},
|
|
||||||
connections: {
|
|
||||||
async handler(value, oldValue) {
|
|
||||||
// Load a workflow
|
|
||||||
let workflowId = null as string | null;
|
|
||||||
if (this.$route && this.$route.params.name) {
|
|
||||||
workflowId = this.$route.params.name;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
deep: true,
|
|
||||||
},
|
|
||||||
containsTrigger(containsTrigger) {
|
containsTrigger(containsTrigger) {
|
||||||
// Re-center CanvasAddButton if there's no triggers
|
// Re-center CanvasAddButton if there's no triggers
|
||||||
if (containsTrigger === false) this.setRecenteredCanvasAddButtonPosition(this.getNodeViewOffsetPosition);
|
if (containsTrigger === false) this.setRecenteredCanvasAddButtonPosition(this.getNodeViewOffsetPosition);
|
||||||
|
@ -348,11 +328,11 @@ export default mixins(
|
||||||
return this.$store.getters.allNodes;
|
return this.$store.getters.allNodes;
|
||||||
},
|
},
|
||||||
runButtonText(): string {
|
runButtonText(): string {
|
||||||
if (this.workflowRunning === false) {
|
if (!this.workflowRunning) {
|
||||||
return this.$locale.baseText('nodeView.runButtonText.executeWorkflow');
|
return this.$locale.baseText('nodeView.runButtonText.executeWorkflow');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.executionWaitingForWebhook === true) {
|
if (this.executionWaitingForWebhook) {
|
||||||
return this.$locale.baseText('nodeView.runButtonText.waitingForTriggerEvent');
|
return this.$locale.baseText('nodeView.runButtonText.waitingForTriggerEvent');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -375,14 +355,14 @@ export default mixins(
|
||||||
},
|
},
|
||||||
workflowClasses() {
|
workflowClasses() {
|
||||||
const returnClasses = [];
|
const returnClasses = [];
|
||||||
if (this.ctrlKeyPressed === true) {
|
if (this.ctrlKeyPressed) {
|
||||||
if (this.$store.getters.isNodeViewMoveInProgress === true) {
|
if (this.$store.getters.isNodeViewMoveInProgress === true) {
|
||||||
returnClasses.push('move-in-process');
|
returnClasses.push('move-in-process');
|
||||||
} else {
|
} else {
|
||||||
returnClasses.push('move-active');
|
returnClasses.push('move-active');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (this.selectActive || this.ctrlKeyPressed === true) {
|
if (this.selectActive || this.ctrlKeyPressed) {
|
||||||
// Makes sure that nothing gets selected while select or move is active
|
// Makes sure that nothing gets selected while select or move is active
|
||||||
returnClasses.push('do-not-select');
|
returnClasses.push('do-not-select');
|
||||||
}
|
}
|
||||||
|
@ -612,7 +592,7 @@ export default mixins(
|
||||||
this.$externalHooks().run('execution.open', { workflowId: data.workflowData.id, workflowName: data.workflowData.name, executionId });
|
this.$externalHooks().run('execution.open', { workflowId: data.workflowData.id, workflowName: data.workflowData.name, executionId });
|
||||||
this.$telemetry.track('User opened read-only execution', { workflow_id: data.workflowData.id, execution_mode: data.mode, execution_finished: data.finished });
|
this.$telemetry.track('User opened read-only execution', { workflow_id: data.workflowData.id, execution_mode: data.mode, execution_finished: data.finished });
|
||||||
|
|
||||||
if (data.finished !== true && data && data.data && data.data.resultData && data.data.resultData.error) {
|
if (!data.finished && data.data?.resultData?.error) {
|
||||||
// Check if any node contains an error
|
// Check if any node contains an error
|
||||||
let nodeErrorFound = false;
|
let nodeErrorFound = false;
|
||||||
if (data.data.resultData.runData) {
|
if (data.data.resultData.runData) {
|
||||||
|
@ -628,7 +608,7 @@ export default mixins(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (nodeErrorFound === false) {
|
if (!nodeErrorFound) {
|
||||||
const resultError = data.data.resultData.error;
|
const resultError = data.data.resultData.error;
|
||||||
const errorMessage = this.$getExecutionError(data.data);
|
const errorMessage = this.$getExecutionError(data.data);
|
||||||
const shouldTrack = resultError && 'node' in resultError && resultError.node!.type.startsWith('n8n-nodes-base');
|
const shouldTrack = resultError && 'node' in resultError && resultError.node!.type.startsWith('n8n-nodes-base');
|
||||||
|
@ -724,7 +704,7 @@ export default mixins(
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (data === undefined) {
|
if (!data) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
this.$locale.baseText(
|
this.$locale.baseText(
|
||||||
'nodeView.workflowWithIdCouldNotBeFound',
|
'nodeView.workflowWithIdCouldNotBeFound',
|
||||||
|
@ -800,9 +780,9 @@ export default mixins(
|
||||||
|
|
||||||
// Check if the keys got emitted from a message box or from something
|
// Check if the keys got emitted from a message box or from something
|
||||||
// else which should ignore the default keybindings
|
// else which should ignore the default keybindings
|
||||||
for (let index = 0; index < path.length; index++) {
|
for (const element of path) {
|
||||||
if (path[index].className && typeof path[index].className === 'string' && (
|
if (element.className && typeof element.className === 'string' && (
|
||||||
path[index].className.includes('ignore-key-press')
|
element.className.includes('ignore-key-press')
|
||||||
)) {
|
)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -854,21 +834,21 @@ export default mixins(
|
||||||
this.resetZoom();
|
this.resetZoom();
|
||||||
} else if ((e.key === '1') && !this.isCtrlKeyPressed(e)) {
|
} else if ((e.key === '1') && !this.isCtrlKeyPressed(e)) {
|
||||||
this.zoomToFit();
|
this.zoomToFit();
|
||||||
} else if ((e.key === 'a') && (this.isCtrlKeyPressed(e) === true)) {
|
} else if ((e.key === 'a') && this.isCtrlKeyPressed(e)) {
|
||||||
// Select all nodes
|
// Select all nodes
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|
||||||
this.callDebounced('selectAllNodes', { debounceTime: 1000 });
|
this.callDebounced('selectAllNodes', { debounceTime: 1000 });
|
||||||
} else if ((e.key === 'c') && (this.isCtrlKeyPressed(e) === true)) {
|
} else if ((e.key === 'c') && this.isCtrlKeyPressed(e)) {
|
||||||
this.callDebounced('copySelectedNodes', { debounceTime: 1000 });
|
this.callDebounced('copySelectedNodes', { debounceTime: 1000 });
|
||||||
} else if ((e.key === 'x') && (this.isCtrlKeyPressed(e) === true)) {
|
} else if ((e.key === 'x') && this.isCtrlKeyPressed(e)) {
|
||||||
// Cut nodes
|
// Cut nodes
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|
||||||
this.callDebounced('cutSelectedNodes', { debounceTime: 1000 });
|
this.callDebounced('cutSelectedNodes', { debounceTime: 1000 });
|
||||||
} else if (e.key === 'n' && this.isCtrlKeyPressed(e) === true && e.altKey === true) {
|
} else if (e.key === 'n' && this.isCtrlKeyPressed(e) && e.altKey) {
|
||||||
// Create a new workflow
|
// Create a new workflow
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
@ -886,7 +866,7 @@ export default mixins(
|
||||||
title: this.$locale.baseText('nodeView.showMessage.keyDown.title'),
|
title: this.$locale.baseText('nodeView.showMessage.keyDown.title'),
|
||||||
type: 'success',
|
type: 'success',
|
||||||
});
|
});
|
||||||
} else if ((e.key === 's') && (this.isCtrlKeyPressed(e) === true)) {
|
} else if ((e.key === 's') && this.isCtrlKeyPressed(e)) {
|
||||||
// Save workflow
|
// Save workflow
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
@ -906,7 +886,7 @@ export default mixins(
|
||||||
}
|
}
|
||||||
this.$store.commit('ndv/setActiveNodeName', lastSelectedNode.name);
|
this.$store.commit('ndv/setActiveNodeName', lastSelectedNode.name);
|
||||||
}
|
}
|
||||||
} else if (e.key === 'ArrowRight' && e.shiftKey === true) {
|
} else if (e.key === 'ArrowRight' && e.shiftKey) {
|
||||||
// Select all downstream nodes
|
// Select all downstream nodes
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
@ -926,7 +906,7 @@ export default mixins(
|
||||||
}
|
}
|
||||||
|
|
||||||
this.callDebounced('nodeSelectedByName', { debounceTime: 100 }, connections.main[0][0].node, false, true);
|
this.callDebounced('nodeSelectedByName', { debounceTime: 100 }, connections.main[0][0].node, false, true);
|
||||||
} else if (e.key === 'ArrowLeft' && e.shiftKey === true) {
|
} else if (e.key === 'ArrowLeft' && e.shiftKey) {
|
||||||
// Select all downstream nodes
|
// Select all downstream nodes
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
@ -969,14 +949,14 @@ export default mixins(
|
||||||
|
|
||||||
const connections = workflow.connectionsByDestinationNode[lastSelectedNode.name];
|
const connections = workflow.connectionsByDestinationNode[lastSelectedNode.name];
|
||||||
|
|
||||||
if (connections.main === undefined || connections.main.length === 0) {
|
if (!Array.isArray(connections.main) || !connections.main.length) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const parentNode = connections.main[0][0].node;
|
const parentNode = connections.main[0][0].node;
|
||||||
const connectionsParent = this.$store.getters.outgoingConnectionsByNodeName(parentNode);
|
const connectionsParent = this.$store.getters.outgoingConnectionsByNodeName(parentNode);
|
||||||
|
|
||||||
if (connectionsParent.main === undefined || connectionsParent.main.length === 0) {
|
if (!Array.isArray(connectionsParent.main) || !connectionsParent.main.length) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1015,7 +995,7 @@ export default mixins(
|
||||||
},
|
},
|
||||||
|
|
||||||
deactivateSelectedNode() {
|
deactivateSelectedNode() {
|
||||||
if (this.editAllowedCheck() === false) {
|
if (!this.editAllowedCheck()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.disableNodes(this.$store.getters.getSelectedNodes);
|
this.disableNodes(this.$store.getters.getSelectedNodes);
|
||||||
|
@ -1160,13 +1140,12 @@ export default mixins(
|
||||||
}
|
}
|
||||||
|
|
||||||
// https://docs.jsplumbtoolkit.com/community/current/articles/zooming.html
|
// https://docs.jsplumbtoolkit.com/community/current/articles/zooming.html
|
||||||
const prependProperties = ['webkit', 'moz', 'ms', 'o'];
|
|
||||||
const scaleString = 'scale(' + zoomLevel + ')';
|
const scaleString = 'scale(' + zoomLevel + ')';
|
||||||
|
|
||||||
for (let i = 0; i < prependProperties.length; i++) {
|
['webkit', 'moz', 'ms', 'o'].forEach((prefix) => {
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
element.style[prependProperties[i] + 'Transform'] = scaleString;
|
element.style[prefix + 'Transform'] = scaleString;
|
||||||
}
|
});
|
||||||
element.style['transform'] = scaleString;
|
element.style['transform'] = scaleString;
|
||||||
|
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
|
@ -1295,7 +1274,7 @@ export default mixins(
|
||||||
if (plainTextData.match(/^http[s]?:\/\/.*\.json$/i)) {
|
if (plainTextData.match(/^http[s]?:\/\/.*\.json$/i)) {
|
||||||
// Pasted data points to a possible workflow JSON file
|
// Pasted data points to a possible workflow JSON file
|
||||||
|
|
||||||
if (this.editAllowedCheck() === false) {
|
if (!this.editAllowedCheck()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1310,7 +1289,7 @@ export default mixins(
|
||||||
this.$locale.baseText('nodeView.confirmMessage.receivedCopyPasteData.cancelButtonText'),
|
this.$locale.baseText('nodeView.confirmMessage.receivedCopyPasteData.cancelButtonText'),
|
||||||
);
|
);
|
||||||
|
|
||||||
if (importConfirm === false) {
|
if (!importConfirm) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1324,7 +1303,7 @@ export default mixins(
|
||||||
// Check first if it is valid JSON
|
// Check first if it is valid JSON
|
||||||
workflowData = JSON.parse(plainTextData);
|
workflowData = JSON.parse(plainTextData);
|
||||||
|
|
||||||
if (this.editAllowedCheck() === false) {
|
if (!this.editAllowedCheck()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
@ -1509,7 +1488,7 @@ export default mixins(
|
||||||
this.lastSelectedConnection = null;
|
this.lastSelectedConnection = null;
|
||||||
this.newNodeInsertPosition = null;
|
this.newNodeInsertPosition = null;
|
||||||
|
|
||||||
if (setActive === true) {
|
if (setActive) {
|
||||||
this.$store.commit('ndv/setActiveNodeName', node.name);
|
this.$store.commit('ndv/setActiveNodeName', node.name);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -1744,7 +1723,7 @@ export default mixins(
|
||||||
this.__addConnection(connectionData, true);
|
this.__addConnection(connectionData, true);
|
||||||
},
|
},
|
||||||
async addNode(nodeTypeName: string, options: AddNodeOptions = {}) {
|
async addNode(nodeTypeName: string, options: AddNodeOptions = {}) {
|
||||||
if (this.editAllowedCheck() === false) {
|
if (!this.editAllowedCheck()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1876,7 +1855,7 @@ export default mixins(
|
||||||
|
|
||||||
CanvasHelpers.resetConnection(info.connection);
|
CanvasHelpers.resetConnection(info.connection);
|
||||||
|
|
||||||
if (this.isReadOnly === false) {
|
if (!this.isReadOnly) {
|
||||||
let exitTimer: NodeJS.Timeout | undefined;
|
let exitTimer: NodeJS.Timeout | undefined;
|
||||||
let enterTimer: NodeJS.Timeout | undefined;
|
let enterTimer: NodeJS.Timeout | undefined;
|
||||||
info.connection.bind('mouseover', (connection: Connection) => {
|
info.connection.bind('mouseover', (connection: Connection) => {
|
||||||
|
@ -2119,8 +2098,7 @@ export default mixins(
|
||||||
},
|
},
|
||||||
tryToAddWelcomeSticky: once(async function(this: any) {
|
tryToAddWelcomeSticky: once(async function(this: any) {
|
||||||
const newWorkflow = this.workflowData;
|
const newWorkflow = this.workflowData;
|
||||||
const flagAvailable = window.posthog !== undefined && window.posthog.getFeatureFlag !== undefined;
|
if (window.posthog?.getFeatureFlag?.('welcome-note') === 'test') {
|
||||||
if (flagAvailable && window.posthog.getFeatureFlag('welcome-note') === 'test') {
|
|
||||||
// For novice users (onboardingFlowEnabled == true)
|
// For novice users (onboardingFlowEnabled == true)
|
||||||
// Inject welcome sticky note and zoom to fit
|
// Inject welcome sticky note and zoom to fit
|
||||||
|
|
||||||
|
@ -2250,7 +2228,7 @@ export default mixins(
|
||||||
return CanvasHelpers.getInputEndpointUUID(node.id, index);
|
return CanvasHelpers.getInputEndpointUUID(node.id, index);
|
||||||
},
|
},
|
||||||
__addConnection(connection: [IConnection, IConnection], addVisualConnection = false) {
|
__addConnection(connection: [IConnection, IConnection], addVisualConnection = false) {
|
||||||
if (addVisualConnection === true) {
|
if (addVisualConnection) {
|
||||||
const outputUuid = this.getOutputEndpointUUID(connection[0].node, connection[0].index);
|
const outputUuid = this.getOutputEndpointUUID(connection[0].node, connection[0].index);
|
||||||
const inputUuid = this.getInputEndpointUUID(connection[1].node, connection[1].index);
|
const inputUuid = this.getInputEndpointUUID(connection[1].node, connection[1].index);
|
||||||
if (!outputUuid || !inputUuid) {
|
if (!outputUuid || !inputUuid) {
|
||||||
|
@ -2280,7 +2258,7 @@ export default mixins(
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
__removeConnection(connection: [IConnection, IConnection], removeVisualConnection = false) {
|
__removeConnection(connection: [IConnection, IConnection], removeVisualConnection = false) {
|
||||||
if (removeVisualConnection === true) {
|
if (removeVisualConnection) {
|
||||||
const sourceId = this.$store.getters.getNodeByName(connection[0].node);
|
const sourceId = this.$store.getters.getNodeByName(connection[0].node);
|
||||||
const targetId = this.$store.getters.getNodeByName(connection[1].node);
|
const targetId = this.$store.getters.getNodeByName(connection[1].node);
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
|
@ -2340,7 +2318,7 @@ export default mixins(
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
async duplicateNode(nodeName: string) {
|
async duplicateNode(nodeName: string) {
|
||||||
if (this.editAllowedCheck() === false) {
|
if (!this.editAllowedCheck()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2518,7 +2496,7 @@ export default mixins(
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
removeNode(nodeName: string) {
|
removeNode(nodeName: string) {
|
||||||
if (this.editAllowedCheck() === false) {
|
if (!this.editAllowedCheck()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2545,7 +2523,7 @@ export default mixins(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (deleteAllowed === false) {
|
if (!deleteAllowed) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3042,7 +3020,7 @@ export default mixins(
|
||||||
// Reset nodes
|
// Reset nodes
|
||||||
this.deleteEveryEndpoint();
|
this.deleteEveryEndpoint();
|
||||||
|
|
||||||
if (this.executionWaitingForWebhook === true) {
|
if (this.executionWaitingForWebhook) {
|
||||||
// Make sure that if there is a waiting test-webhook that
|
// Make sure that if there is a waiting test-webhook that
|
||||||
// it gets removed
|
// it gets removed
|
||||||
this.restApi().removeTestWebhook(this.$store.getters.workflowId)
|
this.restApi().removeTestWebhook(this.$store.getters.workflowId)
|
||||||
|
@ -3088,6 +3066,7 @@ export default mixins(
|
||||||
},
|
},
|
||||||
async loadCredentials(): Promise<void> {
|
async loadCredentials(): Promise<void> {
|
||||||
await this.$store.dispatch('credentials/fetchAllCredentials');
|
await this.$store.dispatch('credentials/fetchAllCredentials');
|
||||||
|
await this.$store.dispatch('credentials/fetchForeignCredentials');
|
||||||
},
|
},
|
||||||
async loadNodesProperties(nodeInfos: INodeTypeNameVersion[]): Promise<void> {
|
async loadNodesProperties(nodeInfos: INodeTypeNameVersion[]): Promise<void> {
|
||||||
const allNodes: INodeTypeDescription[] = this.$store.getters['nodeTypes/allNodeTypes'];
|
const allNodes: INodeTypeDescription[] = this.$store.getters['nodeTypes/allNodeTypes'];
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import { getStyleTokenValue, isNumber } from "@/components/helpers";
|
import { getStyleTokenValue, isNumber } from "@/components/helpers";
|
||||||
import { NODE_OUTPUT_DEFAULT_KEY, START_NODE_TYPE, STICKY_NODE_TYPE, QUICKSTART_NOTE_NAME } from "@/constants";
|
import { NODE_OUTPUT_DEFAULT_KEY, START_NODE_TYPE, STICKY_NODE_TYPE, QUICKSTART_NOTE_NAME } from "@/constants";
|
||||||
import { IBounds, INodeUi, IZoomConfig, XYPosition } from "@/Interface";
|
import { EndpointStyle, IBounds, INodeUi, IZoomConfig, XYPosition } from "@/Interface";
|
||||||
import { Connection, Endpoint, Overlay, OverlaySpec, PaintStyle } from "jsplumb";
|
import { AnchorArraySpec, Connection, Endpoint, Overlay, OverlaySpec, PaintStyle } from "jsplumb";
|
||||||
import {
|
import {
|
||||||
IConnection,
|
IConnection,
|
||||||
INode,
|
INode,
|
||||||
|
@ -146,7 +146,7 @@ export const CONNECTOR_ARROW_OVERLAYS: OverlaySpec[] = [
|
||||||
|
|
||||||
export const ANCHOR_POSITIONS: {
|
export const ANCHOR_POSITIONS: {
|
||||||
[key: string]: {
|
[key: string]: {
|
||||||
[key: number]: number[][];
|
[key: number]: AnchorArraySpec[];
|
||||||
}
|
}
|
||||||
} = {
|
} = {
|
||||||
input: {
|
input: {
|
||||||
|
@ -192,7 +192,7 @@ export const ANCHOR_POSITIONS: {
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
export const getInputEndpointStyle = (nodeTypeData: INodeTypeDescription, color: string) => ({
|
export const getInputEndpointStyle = (nodeTypeData: INodeTypeDescription, color: string): EndpointStyle => ({
|
||||||
width: 8,
|
width: 8,
|
||||||
height: nodeTypeData && nodeTypeData.outputs.length > 2 ? 18 : 20,
|
height: nodeTypeData && nodeTypeData.outputs.length > 2 ? 18 : 20,
|
||||||
fill: getStyleTokenValue(color),
|
fill: getStyleTokenValue(color),
|
||||||
|
@ -200,7 +200,7 @@ export const getInputEndpointStyle = (nodeTypeData: INodeTypeDescription, color:
|
||||||
lineWidth: 0,
|
lineWidth: 0,
|
||||||
});
|
});
|
||||||
|
|
||||||
export const getInputNameOverlay = (label: string) => ([
|
export const getInputNameOverlay = (label: string): OverlaySpec => ([
|
||||||
'Label',
|
'Label',
|
||||||
{
|
{
|
||||||
id: OVERLAY_INPUT_NAME_LABEL,
|
id: OVERLAY_INPUT_NAME_LABEL,
|
||||||
|
@ -217,7 +217,7 @@ export const getOutputEndpointStyle = (nodeTypeData: INodeTypeDescription, color
|
||||||
outlineStroke: 'none',
|
outlineStroke: 'none',
|
||||||
});
|
});
|
||||||
|
|
||||||
export const getOutputNameOverlay = (label: string) => ([
|
export const getOutputNameOverlay = (label: string): OverlaySpec => ([
|
||||||
'Label',
|
'Label',
|
||||||
{
|
{
|
||||||
id: OVERLAY_OUTPUT_NAME_LABEL,
|
id: OVERLAY_OUTPUT_NAME_LABEL,
|
||||||
|
|
|
@ -17,3 +17,11 @@ export * from './WorkflowErrors';
|
||||||
export * from './WorkflowHooks';
|
export * from './WorkflowHooks';
|
||||||
export { LoggerProxy, NodeHelpers, ObservableObject, TelemetryHelpers };
|
export { LoggerProxy, NodeHelpers, ObservableObject, TelemetryHelpers };
|
||||||
export { deepCopy, jsonParse } from './utils';
|
export { deepCopy, jsonParse } from './utils';
|
||||||
|
export {
|
||||||
|
isINodeProperties,
|
||||||
|
isINodePropertyOptions,
|
||||||
|
isINodePropertyCollection,
|
||||||
|
isINodePropertiesList,
|
||||||
|
isINodePropertyCollectionList,
|
||||||
|
isINodePropertyOptionsList,
|
||||||
|
} from './type-guards';
|
||||||
|
|
27
packages/workflow/src/type-guards.ts
Normal file
27
packages/workflow/src/type-guards.ts
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
import { INodeProperties, INodePropertyOptions, INodePropertyCollection } from './Interfaces';
|
||||||
|
|
||||||
|
export const isINodeProperties = (
|
||||||
|
item: INodePropertyOptions | INodeProperties | INodePropertyCollection,
|
||||||
|
): item is INodeProperties => 'name' in item && 'type' in item && !('value' in item);
|
||||||
|
|
||||||
|
export const isINodePropertyOptions = (
|
||||||
|
item: INodePropertyOptions | INodeProperties | INodePropertyCollection,
|
||||||
|
): item is INodePropertyOptions => 'value' in item && 'name' in item && !('displayName' in item);
|
||||||
|
|
||||||
|
export const isINodePropertyCollection = (
|
||||||
|
item: INodePropertyOptions | INodeProperties | INodePropertyCollection,
|
||||||
|
): item is INodePropertyCollection => 'values' in item && 'name' in item && 'displayName' in item;
|
||||||
|
|
||||||
|
export const isINodePropertiesList = (
|
||||||
|
items: INodeProperties['options'],
|
||||||
|
): items is INodeProperties[] => Array.isArray(items) && items.every(isINodeProperties);
|
||||||
|
|
||||||
|
export const isINodePropertyOptionsList = (
|
||||||
|
items: INodeProperties['options'],
|
||||||
|
): items is INodePropertyOptions[] => Array.isArray(items) && items.every(isINodePropertyOptions);
|
||||||
|
|
||||||
|
export const isINodePropertyCollectionList = (
|
||||||
|
items: INodeProperties['options'],
|
||||||
|
): items is INodePropertyCollection[] => {
|
||||||
|
return Array.isArray(items) && items.every(isINodePropertyCollection);
|
||||||
|
};
|
Loading…
Reference in a new issue