mirror of
https://github.com/n8n-io/n8n.git
synced 2024-12-24 04:04:06 -08:00
feat(editor): Add HTTP request nodes for credentials without a node (#7157)
Github issue / Community forum post (link here to close automatically): --------- Co-authored-by: Giulio Andreini <g.andreini@gmail.com> Co-authored-by: कारतोफ्फेलस्क्रिप्ट™ <aditya@netroy.in>
This commit is contained in:
parent
460ac85fda
commit
14035e1244
|
@ -478,11 +478,9 @@ describe('Node Creator', () => {
|
||||||
|
|
||||||
nodeCreatorFeature.getters.searchBar().find('input').clear().type('wa');
|
nodeCreatorFeature.getters.searchBar().find('input').clear().type('wa');
|
||||||
nodeCreatorFeature.getters.nodeItemName().first().should('have.text', 'Wait');
|
nodeCreatorFeature.getters.nodeItemName().first().should('have.text', 'Wait');
|
||||||
nodeCreatorFeature.getters.nodeItemName().eq(1).should('have.text', 'Merge');
|
|
||||||
|
|
||||||
nodeCreatorFeature.getters.searchBar().find('input').clear().type('wait');
|
nodeCreatorFeature.getters.searchBar().find('input').clear().type('wait');
|
||||||
nodeCreatorFeature.getters.nodeItemName().first().should('have.text', 'Wait');
|
nodeCreatorFeature.getters.nodeItemName().first().should('have.text', 'Wait');
|
||||||
nodeCreatorFeature.getters.nodeItemName().eq(1).should('have.text', 'Merge');
|
|
||||||
|
|
||||||
nodeCreatorFeature.getters.searchBar().find('input').clear().type('spreadsheet');
|
nodeCreatorFeature.getters.searchBar().find('input').clear().type('spreadsheet');
|
||||||
nodeCreatorFeature.getters.nodeItemName().first().should('have.text', 'Spreadsheet File');
|
nodeCreatorFeature.getters.nodeItemName().first().should('have.text', 'Spreadsheet File');
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
import { WorkflowPage, NDV } from '../pages';
|
import { WorkflowPage, NDV } from '../pages';
|
||||||
|
import { NodeCreator } from '../pages/features/node-creator';
|
||||||
|
|
||||||
const workflowPage = new WorkflowPage();
|
const workflowPage = new WorkflowPage();
|
||||||
|
const nodeCreatorFeature = new NodeCreator();
|
||||||
const ndv = new NDV();
|
const ndv = new NDV();
|
||||||
|
|
||||||
describe('HTTP Request node', () => {
|
describe('HTTP Request node', () => {
|
||||||
|
@ -18,4 +20,40 @@ describe('HTTP Request node', () => {
|
||||||
|
|
||||||
ndv.getters.outputPanel().contains('fact');
|
ndv.getters.outputPanel().contains('fact');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('Credential-only HTTP Request Node variants', () => {
|
||||||
|
it('should render a modified HTTP Request Node', () => {
|
||||||
|
workflowPage.actions.addInitialNodeToCanvas('Manual');
|
||||||
|
|
||||||
|
workflowPage.getters.nodeCreatorPlusButton().click();
|
||||||
|
workflowPage.getters.nodeCreatorSearchBar().type('VirusTotal');
|
||||||
|
|
||||||
|
expect(nodeCreatorFeature.getters.nodeItemName().first().should('have.text', 'VirusTotal'));
|
||||||
|
expect(
|
||||||
|
nodeCreatorFeature.getters
|
||||||
|
.nodeItemDescription()
|
||||||
|
.first()
|
||||||
|
.should('have.text', 'HTTP request'),
|
||||||
|
);
|
||||||
|
|
||||||
|
nodeCreatorFeature.actions.selectNode('VirusTotal');
|
||||||
|
expect(ndv.getters.nodeNameContainer().should('contain.text', 'VirusTotal HTTP Request'));
|
||||||
|
expect(
|
||||||
|
ndv.getters
|
||||||
|
.parameterInput('url')
|
||||||
|
.find('input')
|
||||||
|
.should('contain.value', 'https://www.virustotal.com/api/v3/'),
|
||||||
|
);
|
||||||
|
|
||||||
|
// These parameters exist for normal HTTP Request Node, but are hidden for credential-only variants
|
||||||
|
expect(ndv.getters.parameterInput('authentication').should('not.exist'));
|
||||||
|
expect(ndv.getters.parameterInput('nodeCredentialType').should('not.exist'));
|
||||||
|
|
||||||
|
expect(
|
||||||
|
workflowPage.getters
|
||||||
|
.nodeCredentialsLabel()
|
||||||
|
.should('contain.text', 'Credential for VirusTotal'),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -20,6 +20,7 @@ export class NodeCreator extends BasePage {
|
||||||
communityNodeTooltip: () => cy.getByTestId('node-item-community-tooltip'),
|
communityNodeTooltip: () => cy.getByTestId('node-item-community-tooltip'),
|
||||||
noResults: () => cy.getByTestId('node-creator-no-results'),
|
noResults: () => cy.getByTestId('node-creator-no-results'),
|
||||||
nodeItemName: () => cy.getByTestId('node-creator-item-name'),
|
nodeItemName: () => cy.getByTestId('node-creator-item-name'),
|
||||||
|
nodeItemDescription: () => cy.getByTestId('node-creator-item-description'),
|
||||||
activeSubcategory: () => cy.getByTestId('nodes-list-header'),
|
activeSubcategory: () => cy.getByTestId('nodes-list-header'),
|
||||||
expandedCategories: () =>
|
expandedCategories: () =>
|
||||||
this.getters.creatorItem().find('>div').filter('.active').invoke('text'),
|
this.getters.creatorItem().find('>div').filter('.active').invoke('text'),
|
||||||
|
|
|
@ -17,8 +17,8 @@ export class CredentialTypes implements ICredentialTypes {
|
||||||
return this.getCredential(credentialType).type;
|
return this.getCredential(credentialType).type;
|
||||||
}
|
}
|
||||||
|
|
||||||
getNodeTypesToTestWith(type: string): string[] {
|
getSupportedNodes(type: string): string[] {
|
||||||
return this.loadNodesAndCredentials.knownCredentials[type]?.nodesToTestWith ?? [];
|
return this.loadNodesAndCredentials.knownCredentials[type]?.supportedNodes ?? [];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -490,8 +490,8 @@ export class CredentialsHelper extends ICredentialsHelper {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const nodeTypesToTestWith = this.credentialTypes.getNodeTypesToTestWith(credentialType);
|
const supportedNodes = this.credentialTypes.getSupportedNodes(credentialType);
|
||||||
for (const nodeName of nodeTypesToTestWith) {
|
for (const nodeName of supportedNodes) {
|
||||||
const node = this.nodeTypes.getByName(nodeName);
|
const node = this.nodeTypes.getByName(nodeName);
|
||||||
|
|
||||||
// Always set to an array even if node is not versioned to not having
|
// Always set to an array even if node is not versioned to not having
|
||||||
|
|
|
@ -290,15 +290,15 @@ export class LoadNodesAndCredentials {
|
||||||
const {
|
const {
|
||||||
className,
|
className,
|
||||||
sourcePath,
|
sourcePath,
|
||||||
nodesToTestWith,
|
supportedNodes,
|
||||||
extends: extendsArr,
|
extends: extendsArr,
|
||||||
} = known.credentials[type];
|
} = known.credentials[type];
|
||||||
this.known.credentials[type] = {
|
this.known.credentials[type] = {
|
||||||
className,
|
className,
|
||||||
sourcePath: path.join(directory, sourcePath),
|
sourcePath: path.join(directory, sourcePath),
|
||||||
nodesToTestWith:
|
supportedNodes:
|
||||||
loader instanceof PackageDirectoryLoader
|
loader instanceof PackageDirectoryLoader
|
||||||
? nodesToTestWith?.map((nodeName) => `${loader.packageName}.${nodeName}`)
|
? supportedNodes?.map((nodeName) => `${loader.packageName}.${nodeName}`)
|
||||||
: undefined,
|
: undefined,
|
||||||
extends: extendsArr,
|
extends: extendsArr,
|
||||||
};
|
};
|
||||||
|
|
|
@ -2,7 +2,8 @@
|
||||||
|
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
const glob = require('fast-glob');
|
const glob = require('fast-glob');
|
||||||
const { LoggerProxy } = require('n8n-workflow');
|
const uniq = require('lodash/uniq');
|
||||||
|
const { LoggerProxy, getCredentialsForNode } = require('n8n-workflow');
|
||||||
const { packageDir, writeJSON } = require('./common');
|
const { packageDir, writeJSON } = require('./common');
|
||||||
const { loadClassInIsolation } = require('../dist/ClassLoader');
|
const { loadClassInIsolation } = require('../dist/ClassLoader');
|
||||||
|
|
||||||
|
@ -19,48 +20,80 @@ const loadClass = (sourcePath) => {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const nodesToTestWith = {};
|
const generateKnownNodes = async () => {
|
||||||
|
const nodeClasses = glob
|
||||||
const generate = async (kind) => {
|
.sync('dist/nodes/**/*.node.js', { cwd: packageDir })
|
||||||
const data = glob
|
|
||||||
.sync(`dist/${kind}/**/*.${kind === 'nodes' ? 'node' : kind}.js`, {
|
|
||||||
cwd: packageDir,
|
|
||||||
})
|
|
||||||
.map(loadClass)
|
.map(loadClass)
|
||||||
.filter((data) => !!data)
|
// Ignore node versions
|
||||||
.reduce((obj, { className, sourcePath, instance }) => {
|
.filter((nodeClass) => nodeClass && !/[vV]\d.node\.js$/.test(nodeClass.sourcePath));
|
||||||
const name = kind === 'nodes' ? instance.description.name : instance.name;
|
|
||||||
if (!/[vV]\d.node\.js$/.test(sourcePath)) {
|
const nodes = {};
|
||||||
if (name in obj) console.error('already loaded', kind, name, sourcePath);
|
const nodesByCredential = {};
|
||||||
else obj[name] = { className, sourcePath };
|
|
||||||
|
for (const { className, sourcePath, instance } of nodeClasses) {
|
||||||
|
const nodeName = instance.description.name;
|
||||||
|
nodes[nodeName] = { className, sourcePath };
|
||||||
|
|
||||||
|
for (const credential of getCredentialsForNode(instance)) {
|
||||||
|
if (!nodesByCredential[credential.name]) {
|
||||||
|
nodesByCredential[credential.name] = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
if (kind === 'credentials' && Array.isArray(instance.extends)) {
|
nodesByCredential[credential.name].push(nodeName);
|
||||||
obj[name].extends = instance.extends;
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
LoggerProxy.info(`Detected ${Object.keys(nodes).length} nodes`);
|
||||||
|
await writeJSON('known/nodes.json', nodes);
|
||||||
|
return { nodes, nodesByCredential };
|
||||||
|
};
|
||||||
|
|
||||||
|
const generateKnownCredentials = async (nodesByCredential) => {
|
||||||
|
const credentialClasses = glob
|
||||||
|
.sync(`dist/credentials/**/*.credentials.js`, { cwd: packageDir })
|
||||||
|
.map(loadClass)
|
||||||
|
.filter((data) => !!data);
|
||||||
|
|
||||||
|
for (const { instance } of credentialClasses) {
|
||||||
|
if (Array.isArray(instance.extends)) {
|
||||||
|
for (const extendedCredential of instance.extends) {
|
||||||
|
nodesByCredential[extendedCredential] = [
|
||||||
|
...(nodesByCredential[extendedCredential] ?? []),
|
||||||
|
...(nodesByCredential[instance.name] ?? []),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const credentials = credentialClasses.reduce(
|
||||||
|
(credentials, { className, sourcePath, instance }) => {
|
||||||
|
const credentialName = instance.name;
|
||||||
|
const credential = {
|
||||||
|
className,
|
||||||
|
sourcePath,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (Array.isArray(instance.extends)) {
|
||||||
|
credential.extends = instance.extends;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (kind === 'nodes') {
|
if (nodesByCredential[credentialName]) {
|
||||||
const { credentials } = instance.description;
|
credential.supportedNodes = Array.from(new Set(nodesByCredential[credentialName]));
|
||||||
if (credentials && credentials.length) {
|
|
||||||
for (const credential of credentials) {
|
|
||||||
nodesToTestWith[credential.name] = nodesToTestWith[credential.name] || [];
|
|
||||||
nodesToTestWith[credential.name].push(name);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (name in nodesToTestWith) {
|
|
||||||
obj[name].nodesToTestWith = nodesToTestWith[name];
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return obj;
|
|
||||||
}, {});
|
|
||||||
|
|
||||||
LoggerProxy.info(`Detected ${Object.keys(data).length} ${kind}`);
|
credentials[credentialName] = credential;
|
||||||
await writeJSON(`known/${kind}.json`, data);
|
|
||||||
return data;
|
return credentials;
|
||||||
|
},
|
||||||
|
{},
|
||||||
|
);
|
||||||
|
|
||||||
|
LoggerProxy.info(`Detected ${Object.keys(credentials).length} credentials`);
|
||||||
|
await writeJSON('known/credentials.json', credentials);
|
||||||
|
return credentials;
|
||||||
};
|
};
|
||||||
|
|
||||||
(async () => {
|
(async () => {
|
||||||
await generate('nodes');
|
const { nodesByCredential } = await generateKnownNodes();
|
||||||
await generate('credentials');
|
await generateKnownCredentials(nodesByCredential);
|
||||||
})();
|
})();
|
||||||
|
|
|
@ -45,7 +45,14 @@ function addWebhookLifecycle(nodeType) {
|
||||||
const loader = new PackageDirectoryLoader(packageDir);
|
const loader = new PackageDirectoryLoader(packageDir);
|
||||||
await loader.loadAll();
|
await loader.loadAll();
|
||||||
|
|
||||||
const credentialTypes = Object.values(loader.credentialTypes).map((data) => data.type);
|
const knownCredentials = loader.known.credentials;
|
||||||
|
const credentialTypes = Object.values(loader.credentialTypes).map((data) => {
|
||||||
|
const credentialType = data.type;
|
||||||
|
if (knownCredentials[credentialType.name].supportedNodes?.length > 0) {
|
||||||
|
delete credentialType.httpRequestNode;
|
||||||
|
}
|
||||||
|
return credentialType;
|
||||||
|
});
|
||||||
|
|
||||||
const loaderNodeTypes = Object.values(loader.nodeTypes);
|
const loaderNodeTypes = Object.values(loader.nodeTypes);
|
||||||
|
|
||||||
|
@ -75,13 +82,12 @@ function addWebhookLifecycle(nodeType) {
|
||||||
addWebhookLifecycle(nodeType);
|
addWebhookLifecycle(nodeType);
|
||||||
return data.type;
|
return data.type;
|
||||||
})
|
})
|
||||||
.flatMap((nodeData) => {
|
.flatMap((nodeType) =>
|
||||||
return NodeHelpers.getVersionedNodeTypeAll(nodeData).map((item) => {
|
NodeHelpers.getVersionedNodeTypeAll(nodeType).map((item) => {
|
||||||
const { __loadOptionsMethods, ...rest } = item.description;
|
const { __loadOptionsMethods, ...rest } = item.description;
|
||||||
|
|
||||||
return rest;
|
return rest;
|
||||||
});
|
}),
|
||||||
});
|
);
|
||||||
|
|
||||||
const referencedMethods = findReferencedMethods(nodeTypes);
|
const referencedMethods = findReferencedMethods(nodeTypes);
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,5 @@
|
||||||
import * as path from 'path';
|
|
||||||
import { readFile } from 'fs/promises';
|
|
||||||
import glob from 'fast-glob';
|
import glob from 'fast-glob';
|
||||||
import { jsonParse, getVersionedNodeTypeAll, LoggerProxy as Logger } from 'n8n-workflow';
|
import { readFile } from 'fs/promises';
|
||||||
import type {
|
import type {
|
||||||
CodexData,
|
CodexData,
|
||||||
DocumentationLink,
|
DocumentationLink,
|
||||||
|
@ -9,15 +7,22 @@ import type {
|
||||||
ICredentialTypeData,
|
ICredentialTypeData,
|
||||||
INodeType,
|
INodeType,
|
||||||
INodeTypeBaseDescription,
|
INodeTypeBaseDescription,
|
||||||
INodeTypeDescription,
|
|
||||||
INodeTypeData,
|
INodeTypeData,
|
||||||
|
INodeTypeDescription,
|
||||||
INodeTypeNameVersion,
|
INodeTypeNameVersion,
|
||||||
IVersionedNodeType,
|
IVersionedNodeType,
|
||||||
KnownNodesAndCredentials,
|
KnownNodesAndCredentials,
|
||||||
} from 'n8n-workflow';
|
} from 'n8n-workflow';
|
||||||
|
import {
|
||||||
|
LoggerProxy as Logger,
|
||||||
|
getCredentialsForNode,
|
||||||
|
getVersionedNodeTypeAll,
|
||||||
|
jsonParse,
|
||||||
|
} from 'n8n-workflow';
|
||||||
|
import * as path from 'path';
|
||||||
|
import { loadClassInIsolation } from './ClassLoader';
|
||||||
import { CUSTOM_NODES_CATEGORY } from './Constants';
|
import { CUSTOM_NODES_CATEGORY } from './Constants';
|
||||||
import type { n8n } from './Interfaces';
|
import type { n8n } from './Interfaces';
|
||||||
import { loadClassInIsolation } from './ClassLoader';
|
|
||||||
|
|
||||||
function toJSON(this: ICredentialType) {
|
function toJSON(this: ICredentialType) {
|
||||||
return {
|
return {
|
||||||
|
@ -44,6 +49,8 @@ export abstract class DirectoryLoader {
|
||||||
|
|
||||||
types: Types = { nodes: [], credentials: [] };
|
types: Types = { nodes: [], credentials: [] };
|
||||||
|
|
||||||
|
protected nodesByCredential: Record<string, string[]> = {};
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
readonly directory: string,
|
readonly directory: string,
|
||||||
protected readonly excludeNodes: string[] = [],
|
protected readonly excludeNodes: string[] = [],
|
||||||
|
@ -140,6 +147,13 @@ export abstract class DirectoryLoader {
|
||||||
getVersionedNodeTypeAll(tempNode).forEach(({ description }) => {
|
getVersionedNodeTypeAll(tempNode).forEach(({ description }) => {
|
||||||
this.types.nodes.push(description);
|
this.types.nodes.push(description);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
for (const credential of getCredentialsForNode(tempNode)) {
|
||||||
|
if (!this.nodesByCredential[credential.name]) {
|
||||||
|
this.nodesByCredential[credential.name] = [];
|
||||||
|
}
|
||||||
|
this.nodesByCredential[credential.name].push(fullNodeName);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected loadCredentialFromFile(credentialName: string, filePath: string): void {
|
protected loadCredentialFromFile(credentialName: string, filePath: string): void {
|
||||||
|
@ -168,6 +182,7 @@ export abstract class DirectoryLoader {
|
||||||
className: credentialName,
|
className: credentialName,
|
||||||
sourcePath: filePath,
|
sourcePath: filePath,
|
||||||
extends: tempCredential.extends,
|
extends: tempCredential.extends,
|
||||||
|
supportedNodes: this.nodesByCredential[tempCredential.name],
|
||||||
};
|
};
|
||||||
|
|
||||||
this.credentialTypes[tempCredential.name] = {
|
this.credentialTypes[tempCredential.name] = {
|
||||||
|
@ -276,19 +291,24 @@ export class CustomDirectoryLoader extends DirectoryLoader {
|
||||||
packageName = 'CUSTOM';
|
packageName = 'CUSTOM';
|
||||||
|
|
||||||
override async loadAll() {
|
override async loadAll() {
|
||||||
const filePaths = await glob('**/*.@(node|credentials).js', {
|
const nodes = await glob('**/*.node.js', {
|
||||||
cwd: this.directory,
|
cwd: this.directory,
|
||||||
absolute: true,
|
absolute: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
for (const filePath of filePaths) {
|
for (const nodePath of nodes) {
|
||||||
const [fileName, type] = path.parse(filePath).name.split('.');
|
const [fileName] = path.parse(nodePath).name.split('.');
|
||||||
|
this.loadNodeFromFile(fileName, nodePath);
|
||||||
|
}
|
||||||
|
|
||||||
if (type === 'node') {
|
const credentials = await glob('**/*.credentials.js', {
|
||||||
this.loadNodeFromFile(fileName, filePath);
|
cwd: this.directory,
|
||||||
} else if (type === 'credentials') {
|
absolute: true,
|
||||||
this.loadCredentialFromFile(fileName, filePath);
|
});
|
||||||
}
|
|
||||||
|
for (const credentialPath of credentials) {
|
||||||
|
const [fileName] = path.parse(credentialPath).name.split('.');
|
||||||
|
this.loadCredentialFromFile(fileName, credentialPath);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -315,15 +335,6 @@ export class PackageDirectoryLoader extends DirectoryLoader {
|
||||||
|
|
||||||
const { nodes, credentials } = n8n;
|
const { nodes, credentials } = n8n;
|
||||||
|
|
||||||
if (Array.isArray(credentials)) {
|
|
||||||
for (const credential of credentials) {
|
|
||||||
const filePath = this.resolvePath(credential);
|
|
||||||
const [credentialName] = path.parse(credential).name.split('.');
|
|
||||||
|
|
||||||
this.loadCredentialFromFile(credentialName, filePath);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Array.isArray(nodes)) {
|
if (Array.isArray(nodes)) {
|
||||||
for (const node of nodes) {
|
for (const node of nodes) {
|
||||||
const filePath = this.resolvePath(node);
|
const filePath = this.resolvePath(node);
|
||||||
|
@ -333,6 +344,15 @@ export class PackageDirectoryLoader extends DirectoryLoader {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (Array.isArray(credentials)) {
|
||||||
|
for (const credential of credentials) {
|
||||||
|
const filePath = this.resolvePath(credential);
|
||||||
|
const [credentialName] = path.parse(credential).name.split('.');
|
||||||
|
|
||||||
|
this.loadCredentialFromFile(credentialName, filePath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Logger.debug(`Loaded all credentials and nodes from ${this.packageName}`, {
|
Logger.debug(`Loaded all credentials and nodes from ${this.packageName}`, {
|
||||||
credentials: credentials?.length ?? 0,
|
credentials: credentials?.length ?? 0,
|
||||||
nodes: nodes?.length ?? 0,
|
nodes: nodes?.length ?? 0,
|
||||||
|
|
|
@ -53,7 +53,12 @@ const i18n = useI18n();
|
||||||
<n8n-icon :class="$style.tooltipIcon" icon="cube" />
|
<n8n-icon :class="$style.tooltipIcon" icon="cube" />
|
||||||
</n8n-tooltip>
|
</n8n-tooltip>
|
||||||
</div>
|
</div>
|
||||||
<p :class="$style.description" v-if="description" v-text="description" />
|
<p
|
||||||
|
v-if="description"
|
||||||
|
data-test-id="node-creator-item-description"
|
||||||
|
:class="$style.description"
|
||||||
|
v-text="description"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<slot name="dragContent" />
|
<slot name="dragContent" />
|
||||||
<button :class="$style.panelIcon" v-if="showActionArrow">
|
<button :class="$style.panelIcon" v-if="showActionArrow">
|
||||||
|
|
|
@ -24,6 +24,9 @@
|
||||||
<div v-if="type !== 'unknown'" :class="$style.icon">
|
<div v-if="type !== 'unknown'" :class="$style.icon">
|
||||||
<img v-if="type === 'file'" :src="src" :class="$style.nodeIconImage" />
|
<img v-if="type === 'file'" :src="src" :class="$style.nodeIconImage" />
|
||||||
<font-awesome-icon v-else :icon="name" :style="fontStyleData" />
|
<font-awesome-icon v-else :icon="name" :style="fontStyleData" />
|
||||||
|
<div v-if="badge" :class="$style.badge" :style="badgeStyleData">
|
||||||
|
<n8n-node-icon :type="badge.type" :src="badge.src" :size="badgeSize"></n8n-node-icon>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div v-else :class="$style.nodeIconPlaceholder">
|
<div v-else :class="$style.nodeIconPlaceholder">
|
||||||
{{ nodeTypeName ? nodeTypeName.charAt(0) : '?' }}
|
{{ nodeTypeName ? nodeTypeName.charAt(0) : '?' }}
|
||||||
|
@ -35,9 +38,9 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { defineComponent } from 'vue';
|
|
||||||
import N8nTooltip from '../N8nTooltip';
|
|
||||||
import { FontAwesomeIcon } from '@fortawesome/vue-fontawesome';
|
import { FontAwesomeIcon } from '@fortawesome/vue-fontawesome';
|
||||||
|
import { defineComponent, type PropType } from 'vue';
|
||||||
|
import N8nTooltip from '../N8nTooltip';
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
name: 'n8n-node-icon',
|
name: 'n8n-node-icon',
|
||||||
|
@ -75,6 +78,7 @@ export default defineComponent({
|
||||||
showTooltip: {
|
showTooltip: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
},
|
},
|
||||||
|
badge: { type: Object as PropType<{ src: string; type: string }> },
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
iconStyleData(): Record<string, string> {
|
iconStyleData(): Record<string, string> {
|
||||||
|
@ -92,6 +96,25 @@ export default defineComponent({
|
||||||
'line-height': `${this.size}px`,
|
'line-height': `${this.size}px`,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
badgeSize(): number {
|
||||||
|
switch (this.size) {
|
||||||
|
case 40:
|
||||||
|
return 18;
|
||||||
|
case 24:
|
||||||
|
return 10;
|
||||||
|
case 18:
|
||||||
|
default:
|
||||||
|
return 8;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
badgeStyleData(): Record<string, string> {
|
||||||
|
const size = this.badgeSize;
|
||||||
|
return {
|
||||||
|
padding: `${Math.floor(size / 4)}px`,
|
||||||
|
right: `-${Math.floor(size / 2)}px`,
|
||||||
|
bottom: `-${Math.floor(size / 2)}px`,
|
||||||
|
};
|
||||||
|
},
|
||||||
fontStyleData(): Record<string, string> {
|
fontStyleData(): Record<string, string> {
|
||||||
if (!this.size) {
|
if (!this.size) {
|
||||||
return {};
|
return {};
|
||||||
|
@ -113,7 +136,6 @@ export default defineComponent({
|
||||||
color: var(--node-icon-color, #444);
|
color: var(--node-icon-color, #444);
|
||||||
line-height: var(--node-icon-size, 26px);
|
line-height: var(--node-icon-size, 26px);
|
||||||
font-size: 1.1em;
|
font-size: 1.1em;
|
||||||
overflow: hidden;
|
|
||||||
text-align: center;
|
text-align: center;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
font-size: 20px;
|
font-size: 20px;
|
||||||
|
@ -125,6 +147,7 @@ export default defineComponent({
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
svg {
|
svg {
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
|
@ -145,6 +168,12 @@ export default defineComponent({
|
||||||
max-height: 100%;
|
max-height: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.badge {
|
||||||
|
position: absolute;
|
||||||
|
background: var(--color-background-node-icon-badge, var(--color-background-base));
|
||||||
|
border-radius: 50%;
|
||||||
|
}
|
||||||
|
|
||||||
.circle {
|
.circle {
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
}
|
}
|
||||||
|
|
|
@ -809,6 +809,7 @@ export type SimplifiedNodeType = Pick<
|
||||||
| 'group'
|
| 'group'
|
||||||
| 'icon'
|
| 'icon'
|
||||||
| 'iconUrl'
|
| 'iconUrl'
|
||||||
|
| 'badgeIconUrl'
|
||||||
| 'codex'
|
| 'codex'
|
||||||
| 'defaults'
|
| 'defaults'
|
||||||
| 'outputs'
|
| 'outputs'
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
<template>
|
<template>
|
||||||
<div :class="$style.container">
|
<div :class="$style.container">
|
||||||
<el-row>
|
<el-row v-if="nodesWithAccess.length > 0">
|
||||||
<el-col :span="8" :class="$style.accessLabel">
|
<el-col :span="8" :class="$style.accessLabel">
|
||||||
<n8n-text :compact="true" :bold="true">
|
<n8n-text :compact="true" :bold="true">
|
||||||
{{ $locale.baseText('credentialEdit.credentialInfo.allowUseBy') }}
|
{{ $locale.baseText('credentialEdit.credentialInfo.allowUseBy') }}
|
||||||
|
|
|
@ -781,6 +781,7 @@ export default defineComponent({
|
||||||
border: 2px solid var(--color-foreground-xdark);
|
border: 2px solid var(--color-foreground-xdark);
|
||||||
border-radius: var(--border-radius-large);
|
border-radius: var(--border-radius-large);
|
||||||
background-color: var(--color-canvas-node-background);
|
background-color: var(--color-canvas-node-background);
|
||||||
|
--color-background-node-icon-badge: var(--color-canvas-node-background);
|
||||||
&.executing {
|
&.executing {
|
||||||
background-color: $node-background-executing !important;
|
background-color: $node-background-executing !important;
|
||||||
|
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
@dragstart="onDragStart"
|
@dragstart="onDragStart"
|
||||||
@dragend="onDragEnd"
|
@dragend="onDragEnd"
|
||||||
:class="$style.nodeItem"
|
:class="$style.nodeItem"
|
||||||
:description="subcategory !== DEFAULT_SUBCATEGORY ? description : ''"
|
:description="description"
|
||||||
:title="displayName"
|
:title="displayName"
|
||||||
:show-action-arrow="showActionArrow"
|
:show-action-arrow="showActionArrow"
|
||||||
:is-trigger="isTrigger"
|
:is-trigger="isTrigger"
|
||||||
|
@ -44,6 +44,7 @@ import { computed, ref } from 'vue';
|
||||||
import type { SimplifiedNodeType } from '@/Interface';
|
import type { SimplifiedNodeType } from '@/Interface';
|
||||||
import {
|
import {
|
||||||
COMMUNITY_NODES_INSTALLATION_DOCS_URL,
|
COMMUNITY_NODES_INSTALLATION_DOCS_URL,
|
||||||
|
CREDENTIAL_ONLY_NODE_PREFIX,
|
||||||
DEFAULT_SUBCATEGORY,
|
DEFAULT_SUBCATEGORY,
|
||||||
DRAG_EVENT_DATA_KEY,
|
DRAG_EVENT_DATA_KEY,
|
||||||
} from '@/constants';
|
} from '@/constants';
|
||||||
|
@ -78,6 +79,13 @@ const draggablePosition = ref({ x: -100, y: -100 });
|
||||||
const draggableDataTransfer = ref(null as Element | null);
|
const draggableDataTransfer = ref(null as Element | null);
|
||||||
|
|
||||||
const description = computed<string>(() => {
|
const description = computed<string>(() => {
|
||||||
|
if (
|
||||||
|
props.subcategory === DEFAULT_SUBCATEGORY &&
|
||||||
|
!props.nodeType.name.startsWith(CREDENTIAL_ONLY_NODE_PREFIX)
|
||||||
|
) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
return i18n.headerText({
|
return i18n.headerText({
|
||||||
key: `headers.${shortNodeType.value}.description`,
|
key: `headers.${shortNodeType.value}.description`,
|
||||||
fallback: props.nodeType.description,
|
fallback: props.nodeType.description,
|
||||||
|
|
|
@ -30,7 +30,7 @@ import { useViewStacks } from './composables/useViewStacks';
|
||||||
import { useKeyboardNavigation } from './composables/useKeyboardNavigation';
|
import { useKeyboardNavigation } from './composables/useKeyboardNavigation';
|
||||||
import { useActionsGenerator } from './composables/useActionsGeneration';
|
import { useActionsGenerator } from './composables/useActionsGeneration';
|
||||||
import NodesListPanel from './Panel/NodesListPanel.vue';
|
import NodesListPanel from './Panel/NodesListPanel.vue';
|
||||||
import { useUIStore } from '@/stores';
|
import { useCredentialsStore, useUIStore } from '@/stores';
|
||||||
import { DRAG_EVENT_DATA_KEY } from '@/constants';
|
import { DRAG_EVENT_DATA_KEY } from '@/constants';
|
||||||
|
|
||||||
export interface Props {
|
export interface Props {
|
||||||
|
@ -135,9 +135,12 @@ registerKeyHook('NodeCreatorCloseTab', {
|
||||||
});
|
});
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
() => useNodeTypesStore().visibleNodeTypes,
|
() => ({
|
||||||
(nodeTypes) => {
|
httpOnlyCredentials: useCredentialsStore().httpOnlyCredentialTypes,
|
||||||
const { actions, mergedNodes } = generateMergedNodesAndActions(nodeTypes);
|
nodeTypes: useNodeTypesStore().visibleNodeTypes,
|
||||||
|
}),
|
||||||
|
({ nodeTypes, httpOnlyCredentials }) => {
|
||||||
|
const { actions, mergedNodes } = generateMergedNodesAndActions(nodeTypes, httpOnlyCredentials);
|
||||||
|
|
||||||
setActions(actions);
|
setActions(actions);
|
||||||
setMergeNodes(mergedNodes);
|
setMergeNodes(mergedNodes);
|
||||||
|
|
|
@ -235,6 +235,7 @@ function onBackButton() {
|
||||||
background: var(--color-background-xlight);
|
background: var(--color-background-xlight);
|
||||||
height: 100%;
|
height: 100%;
|
||||||
background-color: $node-creator-background-color;
|
background-color: $node-creator-background-color;
|
||||||
|
--color-background-node-icon-badge: var(--color-background-xlight);
|
||||||
width: 385px;
|
width: 385px;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
|
|
@ -1,15 +1,18 @@
|
||||||
|
import type { ActionTypeDescription, ActionsRecord, SimplifiedNodeType } from '@/Interface';
|
||||||
|
import { CUSTOM_API_CALL_KEY, HTTP_REQUEST_NODE_TYPE } from '@/constants';
|
||||||
import { memoize, startCase } from 'lodash-es';
|
import { memoize, startCase } from 'lodash-es';
|
||||||
import type {
|
import type {
|
||||||
|
ICredentialType,
|
||||||
|
INodeProperties,
|
||||||
INodePropertyCollection,
|
INodePropertyCollection,
|
||||||
INodePropertyOptions,
|
INodePropertyOptions,
|
||||||
INodeProperties,
|
|
||||||
INodeTypeDescription,
|
INodeTypeDescription,
|
||||||
} from 'n8n-workflow';
|
} from 'n8n-workflow';
|
||||||
import { CUSTOM_API_CALL_KEY } from '@/constants';
|
|
||||||
import type { ActionTypeDescription, SimplifiedNodeType, ActionsRecord } from '@/Interface';
|
|
||||||
|
|
||||||
import { i18n } from '@/plugins/i18n';
|
import { i18n } from '@/plugins/i18n';
|
||||||
|
|
||||||
|
import { getCredentialOnlyNodeType } from '@/utils/credentialOnlyNodes';
|
||||||
|
|
||||||
const PLACEHOLDER_RECOMMENDED_ACTION_KEY = 'placeholder_recommended';
|
const PLACEHOLDER_RECOMMENDED_ACTION_KEY = 'placeholder_recommended';
|
||||||
|
|
||||||
function translate(...args: Parameters<typeof i18n.baseText>) {
|
function translate(...args: Parameters<typeof i18n.baseText>) {
|
||||||
|
@ -241,7 +244,18 @@ export function useActionsGenerator() {
|
||||||
}
|
}
|
||||||
|
|
||||||
function getSimplifiedNodeType(node: INodeTypeDescription): SimplifiedNodeType {
|
function getSimplifiedNodeType(node: INodeTypeDescription): SimplifiedNodeType {
|
||||||
const { displayName, defaults, description, name, group, icon, iconUrl, outputs, codex } = node;
|
const {
|
||||||
|
displayName,
|
||||||
|
defaults,
|
||||||
|
description,
|
||||||
|
name,
|
||||||
|
group,
|
||||||
|
icon,
|
||||||
|
iconUrl,
|
||||||
|
badgeIconUrl,
|
||||||
|
outputs,
|
||||||
|
codex,
|
||||||
|
} = node;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
displayName,
|
displayName,
|
||||||
|
@ -251,12 +265,16 @@ export function useActionsGenerator() {
|
||||||
group,
|
group,
|
||||||
icon,
|
icon,
|
||||||
iconUrl,
|
iconUrl,
|
||||||
|
badgeIconUrl,
|
||||||
outputs,
|
outputs,
|
||||||
codex,
|
codex,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function generateMergedNodesAndActions(nodeTypes: INodeTypeDescription[]) {
|
function generateMergedNodesAndActions(
|
||||||
|
nodeTypes: INodeTypeDescription[],
|
||||||
|
httpOnlyCredentials: ICredentialType[],
|
||||||
|
) {
|
||||||
const visibleNodeTypes = [...nodeTypes];
|
const visibleNodeTypes = [...nodeTypes];
|
||||||
const actions: ActionsRecord<typeof mergedNodes> = {};
|
const actions: ActionsRecord<typeof mergedNodes> = {};
|
||||||
const mergedNodes: SimplifiedNodeType[] = [];
|
const mergedNodes: SimplifiedNodeType[] = [];
|
||||||
|
@ -267,6 +285,13 @@ export function useActionsGenerator() {
|
||||||
const appActions = generateNodeActions(app);
|
const appActions = generateNodeActions(app);
|
||||||
actions[app.name] = appActions;
|
actions[app.name] = appActions;
|
||||||
|
|
||||||
|
if (app.name === HTTP_REQUEST_NODE_TYPE) {
|
||||||
|
const credentialOnlyNodes = httpOnlyCredentials.map((credentialType) =>
|
||||||
|
getSimplifiedNodeType(getCredentialOnlyNodeType(app, credentialType)),
|
||||||
|
);
|
||||||
|
mergedNodes.push(...credentialOnlyNodes);
|
||||||
|
}
|
||||||
|
|
||||||
mergedNodes.push(getSimplifiedNodeType(app));
|
mergedNodes.push(getSimplifiedNodeType(app));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -128,7 +128,7 @@ import { useWorkflowsStore } from '@/stores/workflows.store';
|
||||||
import { useNodeTypesStore } from '@/stores/nodeTypes.store';
|
import { useNodeTypesStore } from '@/stores/nodeTypes.store';
|
||||||
import { useCredentialsStore } from '@/stores/credentials.store';
|
import { useCredentialsStore } from '@/stores/credentials.store';
|
||||||
import { useNDVStore } from '@/stores/ndv.store';
|
import { useNDVStore } from '@/stores/ndv.store';
|
||||||
import { KEEP_AUTH_IN_NDV_FOR_NODES } from '@/constants';
|
import { CREDENTIAL_ONLY_NODE_PREFIX, KEEP_AUTH_IN_NDV_FOR_NODES } from '@/constants';
|
||||||
import {
|
import {
|
||||||
getAuthTypeForNodeCredential,
|
getAuthTypeForNodeCredential,
|
||||||
getMainAuthField,
|
getMainAuthField,
|
||||||
|
@ -248,7 +248,7 @@ export default defineComponent({
|
||||||
// When active node parameters change, check if authentication type has been changed
|
// When active node parameters change, check if authentication type has been changed
|
||||||
// and set `subscribedToCredentialType` to corresponding credential type
|
// and set `subscribedToCredentialType` to corresponding credential type
|
||||||
const isActive = this.node.name === this.ndvStore.activeNode?.name;
|
const isActive = this.node.name === this.ndvStore.activeNode?.name;
|
||||||
const nodeType = this.nodeTypesStore.getNodeType(this.node.type, this.node.typeVersion);
|
const nodeType = this.nodeType;
|
||||||
// Only do this for active node and if it's listening for auth change
|
// Only do this for active node and if it's listening for auth change
|
||||||
if (isActive && nodeType && this.listeningForAuthChange) {
|
if (isActive && nodeType && this.listeningForAuthChange) {
|
||||||
if (this.mainNodeAuthField && oldValue && newValue) {
|
if (this.mainNodeAuthField && oldValue && newValue) {
|
||||||
|
@ -297,7 +297,7 @@ export default defineComponent({
|
||||||
|
|
||||||
if (credType) return [credType];
|
if (credType) return [credType];
|
||||||
|
|
||||||
const activeNodeType = this.nodeTypesStore.getNodeType(node.type, node.typeVersion);
|
const activeNodeType = this.nodeType;
|
||||||
if (activeNodeType?.credentials) {
|
if (activeNodeType?.credentials) {
|
||||||
return activeNodeType.credentials;
|
return activeNodeType.credentials;
|
||||||
}
|
}
|
||||||
|
@ -548,19 +548,24 @@ export default defineComponent({
|
||||||
this.subscribedToCredentialType = credentialType;
|
this.subscribedToCredentialType = credentialType;
|
||||||
},
|
},
|
||||||
showMixedCredentials(credentialType: INodeCredentialDescription): boolean {
|
showMixedCredentials(credentialType: INodeCredentialDescription): boolean {
|
||||||
const nodeType = this.nodeTypesStore.getNodeType(this.node.type, this.node.typeVersion);
|
const nodeType = this.nodeType;
|
||||||
const isRequired = isRequiredCredential(nodeType, credentialType);
|
const isRequired = isRequiredCredential(nodeType, credentialType);
|
||||||
|
|
||||||
return !KEEP_AUTH_IN_NDV_FOR_NODES.includes(this.node.type || '') && isRequired;
|
return !KEEP_AUTH_IN_NDV_FOR_NODES.includes(this.node.type || '') && isRequired;
|
||||||
},
|
},
|
||||||
getCredentialsFieldLabel(credentialType: INodeCredentialDescription): string {
|
getCredentialsFieldLabel(credentialType: INodeCredentialDescription): string {
|
||||||
const credentialTypeName = this.credentialTypeNames[credentialType.name];
|
const credentialTypeName = this.credentialTypeNames[credentialType.name];
|
||||||
|
const isCredentialOnlyNode = this.node.type.startsWith(CREDENTIAL_ONLY_NODE_PREFIX);
|
||||||
|
|
||||||
|
if (isCredentialOnlyNode) {
|
||||||
|
return this.$locale.baseText('nodeCredentials.credentialFor', {
|
||||||
|
interpolate: { credentialType: this.nodeType?.displayName ?? credentialTypeName },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
if (!this.showMixedCredentials(credentialType)) {
|
if (!this.showMixedCredentials(credentialType)) {
|
||||||
return this.$locale.baseText('nodeCredentials.credentialFor', {
|
return this.$locale.baseText('nodeCredentials.credentialFor', {
|
||||||
interpolate: {
|
interpolate: { credentialType: credentialTypeName },
|
||||||
credentialType: credentialTypeName,
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
return this.$locale.baseText('nodeCredentials.credentialsLabel');
|
return this.$locale.baseText('nodeCredentials.credentialsLabel');
|
||||||
|
|
|
@ -9,6 +9,7 @@
|
||||||
:circle="circle"
|
:circle="circle"
|
||||||
:nodeTypeName="nodeType ? nodeType.displayName : ''"
|
:nodeTypeName="nodeType ? nodeType.displayName : ''"
|
||||||
:showTooltip="showTooltip"
|
:showTooltip="showTooltip"
|
||||||
|
:badge="badge"
|
||||||
@click="(e) => $emit('click')"
|
@click="(e) => $emit('click')"
|
||||||
></n8n-node-icon>
|
></n8n-node-icon>
|
||||||
</template>
|
</template>
|
||||||
|
@ -18,7 +19,7 @@ import type { IVersionNode } from '@/Interface';
|
||||||
import { useRootStore } from '@/stores/n8nRoot.store';
|
import { useRootStore } from '@/stores/n8nRoot.store';
|
||||||
import type { INodeTypeDescription } from 'n8n-workflow';
|
import type { INodeTypeDescription } from 'n8n-workflow';
|
||||||
import { mapStores } from 'pinia';
|
import { mapStores } from 'pinia';
|
||||||
import { defineComponent } from 'vue';
|
import { defineComponent, type PropType } from 'vue';
|
||||||
|
|
||||||
interface NodeIconSource {
|
interface NodeIconSource {
|
||||||
path?: string;
|
path?: string;
|
||||||
|
@ -29,7 +30,9 @@ interface NodeIconSource {
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
name: 'NodeIcon',
|
name: 'NodeIcon',
|
||||||
props: {
|
props: {
|
||||||
nodeType: {},
|
nodeType: {
|
||||||
|
type: Object as PropType<INodeTypeDescription | IVersionNode | null>,
|
||||||
|
},
|
||||||
size: {
|
size: {
|
||||||
type: Number,
|
type: Number,
|
||||||
required: false,
|
required: false,
|
||||||
|
@ -54,7 +57,7 @@ export default defineComponent({
|
||||||
computed: {
|
computed: {
|
||||||
...mapStores(useRootStore),
|
...mapStores(useRootStore),
|
||||||
type(): string {
|
type(): string {
|
||||||
const nodeType = this.nodeType as INodeTypeDescription | IVersionNode | null;
|
const nodeType = this.nodeType;
|
||||||
let iconType = 'unknown';
|
let iconType = 'unknown';
|
||||||
if (nodeType) {
|
if (nodeType) {
|
||||||
if (nodeType.iconUrl) return 'file';
|
if (nodeType.iconUrl) return 'file';
|
||||||
|
@ -67,8 +70,8 @@ export default defineComponent({
|
||||||
return iconType;
|
return iconType;
|
||||||
},
|
},
|
||||||
color(): string {
|
color(): string {
|
||||||
const nodeType = this.nodeType as INodeTypeDescription | IVersionNode | null;
|
const nodeType = this.nodeType;
|
||||||
if (nodeType && nodeType.defaults && nodeType.defaults.color) {
|
if (nodeType?.defaults?.color) {
|
||||||
return nodeType.defaults.color.toString();
|
return nodeType.defaults.color.toString();
|
||||||
}
|
}
|
||||||
if (this.colorDefault) {
|
if (this.colorDefault) {
|
||||||
|
@ -77,7 +80,7 @@ export default defineComponent({
|
||||||
return '';
|
return '';
|
||||||
},
|
},
|
||||||
iconSource(): NodeIconSource {
|
iconSource(): NodeIconSource {
|
||||||
const nodeType = this.nodeType as INodeTypeDescription | IVersionNode | null;
|
const nodeType = this.nodeType;
|
||||||
const baseUrl = this.rootStore.getBaseUrl;
|
const baseUrl = this.rootStore.getBaseUrl;
|
||||||
const iconSource = {} as NodeIconSource;
|
const iconSource = {} as NodeIconSource;
|
||||||
|
|
||||||
|
@ -104,6 +107,14 @@ export default defineComponent({
|
||||||
}
|
}
|
||||||
return iconSource;
|
return iconSource;
|
||||||
},
|
},
|
||||||
|
badge(): { src: string; type: string } | undefined {
|
||||||
|
const nodeType = this.nodeType as INodeTypeDescription;
|
||||||
|
if (nodeType && 'badgeIconUrl' in nodeType && nodeType.badgeIconUrl) {
|
||||||
|
return { type: 'file', src: this.rootStore.getBaseUrl + nodeType.badgeIconUrl };
|
||||||
|
}
|
||||||
|
|
||||||
|
return undefined;
|
||||||
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -40,8 +40,8 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { defineComponent } from 'vue';
|
|
||||||
import NodeIcon from '@/components/NodeIcon.vue';
|
import NodeIcon from '@/components/NodeIcon.vue';
|
||||||
|
import { defineComponent } from 'vue';
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
name: 'NodeTitle',
|
name: 'NodeTitle',
|
||||||
|
|
|
@ -419,6 +419,7 @@ import type { EventBus } from 'n8n-design-system/utils';
|
||||||
import { createEventBus } from 'n8n-design-system/utils';
|
import { createEventBus } from 'n8n-design-system/utils';
|
||||||
import { useI18n } from '@/composables';
|
import { useI18n } from '@/composables';
|
||||||
import type { N8nInput } from 'n8n-design-system';
|
import type { N8nInput } from 'n8n-design-system';
|
||||||
|
import { isCredentialOnlyNodeType } from '@/utils/credentialOnlyNodes';
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
name: 'parameter-input',
|
name: 'parameter-input',
|
||||||
|
@ -961,7 +962,7 @@ export default defineComponent({
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.node.type.startsWith('n8n-nodes-base')) {
|
if (this.node.type.startsWith('n8n-nodes-base') || isCredentialOnlyNodeType(this.node.type)) {
|
||||||
this.$telemetry.track('User opened Expression Editor', {
|
this.$telemetry.track('User opened Expression Editor', {
|
||||||
node_type: this.node.type,
|
node_type: this.node.type,
|
||||||
parameter_name: this.parameter.displayName,
|
parameter_name: this.parameter.displayName,
|
||||||
|
|
|
@ -23,11 +23,7 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<import-parameter
|
<import-parameter
|
||||||
v-else-if="
|
v-else-if="parameter.type === 'curlImport'"
|
||||||
parameter.type === 'curlImport' &&
|
|
||||||
nodeTypeName === 'n8n-nodes-base.httpRequest' &&
|
|
||||||
nodeTypeVersion >= 3
|
|
||||||
"
|
|
||||||
:isReadOnly="isReadOnly"
|
:isReadOnly="isReadOnly"
|
||||||
@valueChanged="valueChanged"
|
@valueChanged="valueChanged"
|
||||||
/>
|
/>
|
||||||
|
@ -102,7 +98,10 @@
|
||||||
labelSize="small"
|
labelSize="small"
|
||||||
@valueChanged="valueChanged"
|
@valueChanged="valueChanged"
|
||||||
/>
|
/>
|
||||||
<div v-else-if="displayNodeParameter(parameter)" class="parameter-item">
|
<div
|
||||||
|
v-else-if="displayNodeParameter(parameter) && credentialsParameterIndex !== index"
|
||||||
|
class="parameter-item"
|
||||||
|
>
|
||||||
<div
|
<div
|
||||||
class="delete-option clickable"
|
class="delete-option clickable"
|
||||||
:title="$locale.baseText('parameterInputList.delete')"
|
:title="$locale.baseText('parameterInputList.delete')"
|
||||||
|
@ -137,9 +136,6 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { defineAsyncComponent, defineComponent } from 'vue';
|
|
||||||
import type { PropType } from 'vue';
|
|
||||||
import { mapStores } from 'pinia';
|
|
||||||
import type {
|
import type {
|
||||||
INodeParameters,
|
INodeParameters,
|
||||||
INodeProperties,
|
INodeProperties,
|
||||||
|
@ -147,19 +143,22 @@ import type {
|
||||||
NodeParameterValue,
|
NodeParameterValue,
|
||||||
} from 'n8n-workflow';
|
} from 'n8n-workflow';
|
||||||
import { deepCopy } from 'n8n-workflow';
|
import { deepCopy } from 'n8n-workflow';
|
||||||
|
import { mapStores } from 'pinia';
|
||||||
|
import type { PropType } from 'vue';
|
||||||
|
import { defineAsyncComponent, defineComponent } from 'vue';
|
||||||
|
|
||||||
import type { INodeUi, IUpdateInformation } from '@/Interface';
|
import type { INodeUi, IUpdateInformation } from '@/Interface';
|
||||||
|
|
||||||
import MultipleParameter from '@/components/MultipleParameter.vue';
|
|
||||||
import { workflowHelpers } from '@/mixins/workflowHelpers';
|
|
||||||
import ParameterInputFull from '@/components/ParameterInputFull.vue';
|
|
||||||
import ImportParameter from '@/components/ImportParameter.vue';
|
import ImportParameter from '@/components/ImportParameter.vue';
|
||||||
|
import MultipleParameter from '@/components/MultipleParameter.vue';
|
||||||
|
import ParameterInputFull from '@/components/ParameterInputFull.vue';
|
||||||
import ResourceMapper from '@/components/ResourceMapper/ResourceMapper.vue';
|
import ResourceMapper from '@/components/ResourceMapper/ResourceMapper.vue';
|
||||||
import { get, set } from 'lodash-es';
|
import { KEEP_AUTH_IN_NDV_FOR_NODES } from '@/constants';
|
||||||
|
import { workflowHelpers } from '@/mixins/workflowHelpers';
|
||||||
import { useNDVStore } from '@/stores/ndv.store';
|
import { useNDVStore } from '@/stores/ndv.store';
|
||||||
import { useNodeTypesStore } from '@/stores/nodeTypes.store';
|
import { useNodeTypesStore } from '@/stores/nodeTypes.store';
|
||||||
import { isAuthRelatedParameter, getNodeAuthFields, getMainAuthField } from '@/utils';
|
import { getMainAuthField, getNodeAuthFields, isAuthRelatedParameter } from '@/utils';
|
||||||
import { KEEP_AUTH_IN_NDV_FOR_NODES } from '@/constants';
|
import { get, set } from 'lodash-es';
|
||||||
import { nodeViewEventBus } from '@/event-bus';
|
import { nodeViewEventBus } from '@/event-bus';
|
||||||
|
|
||||||
const FixedCollectionParameter = defineAsyncComponent(
|
const FixedCollectionParameter = defineAsyncComponent(
|
||||||
|
@ -242,7 +241,16 @@ export default defineComponent({
|
||||||
nodeAuthFields(): INodeProperties[] {
|
nodeAuthFields(): INodeProperties[] {
|
||||||
return getNodeAuthFields(this.nodeType);
|
return getNodeAuthFields(this.nodeType);
|
||||||
},
|
},
|
||||||
|
credentialsParameterIndex(): number {
|
||||||
|
return this.filteredParameters.findIndex((parameter) => parameter.type === 'credentials');
|
||||||
|
},
|
||||||
indexToShowSlotAt(): number {
|
indexToShowSlotAt(): number {
|
||||||
|
const credentialsParameterIndex = this.credentialsParameterIndex;
|
||||||
|
|
||||||
|
if (credentialsParameterIndex !== -1) {
|
||||||
|
return credentialsParameterIndex;
|
||||||
|
}
|
||||||
|
|
||||||
let index = 0;
|
let index = 0;
|
||||||
// For nodes that use old credentials UI, keep credentials below authentication field in NDV
|
// For nodes that use old credentials UI, keep credentials below authentication field in NDV
|
||||||
// otherwise credentials will use auth filed position since the auth field is moved to credentials modal
|
// otherwise credentials will use auth filed position since the auth field is moved to credentials modal
|
||||||
|
@ -255,7 +263,7 @@ export default defineComponent({
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
return index < this.filteredParameters.length ? index : this.filteredParameters.length - 1;
|
return Math.min(index, this.filteredParameters.length - 1);
|
||||||
},
|
},
|
||||||
mainNodeAuthField(): INodeProperties | null {
|
mainNodeAuthField(): INodeProperties | null {
|
||||||
return getMainAuthField(this.nodeType || null);
|
return getMainAuthField(this.nodeType || null);
|
||||||
|
|
|
@ -157,6 +157,9 @@ export const ZENDESK_NODE_TYPE = 'n8n-nodes-base.zendesk';
|
||||||
export const ZENDESK_TRIGGER_NODE_TYPE = 'n8n-nodes-base.zendeskTrigger';
|
export const ZENDESK_TRIGGER_NODE_TYPE = 'n8n-nodes-base.zendeskTrigger';
|
||||||
export const DISCORD_NODE_TYPE = 'n8n-nodes-base.discord';
|
export const DISCORD_NODE_TYPE = 'n8n-nodes-base.discord';
|
||||||
|
|
||||||
|
export const CREDENTIAL_ONLY_NODE_PREFIX = 'n8n-creds-base';
|
||||||
|
export const CREDENTIAL_ONLY_HTTP_NODE_VERSION = 4.1;
|
||||||
|
|
||||||
export const EXECUTABLE_TRIGGER_NODE_TYPES = [
|
export const EXECUTABLE_TRIGGER_NODE_TYPES = [
|
||||||
START_NODE_TYPE,
|
START_NODE_TYPE,
|
||||||
MANUAL_TRIGGER_NODE_TYPE,
|
MANUAL_TRIGGER_NODE_TYPE,
|
||||||
|
|
|
@ -66,6 +66,7 @@ import { useWorkflowsStore } from '@/stores/workflows.store';
|
||||||
import { getSourceItems } from '@/utils';
|
import { getSourceItems } from '@/utils';
|
||||||
import { v4 as uuid } from 'uuid';
|
import { v4 as uuid } from 'uuid';
|
||||||
import { useSettingsStore } from '@/stores/settings.store';
|
import { useSettingsStore } from '@/stores/settings.store';
|
||||||
|
import { getCredentialTypeName, isCredentialOnlyNodeType } from '@/utils/credentialOnlyNodes';
|
||||||
|
|
||||||
export function getParentMainInputNode(workflow: Workflow, node: INode): INode {
|
export function getParentMainInputNode(workflow: Workflow, node: INode): INode {
|
||||||
const nodeType = useNodeTypesStore().getNodeType(node.type);
|
const nodeType = useNodeTypesStore().getNodeType(node.type);
|
||||||
|
@ -683,11 +684,18 @@ export const workflowHelpers = defineComponent({
|
||||||
const nodeType = this.nodeTypesStore.getNodeType(node.type, node.typeVersion);
|
const nodeType = this.nodeTypesStore.getNodeType(node.type, node.typeVersion);
|
||||||
|
|
||||||
if (nodeType !== null) {
|
if (nodeType !== null) {
|
||||||
|
const isCredentialOnly = isCredentialOnlyNodeType(nodeType.name);
|
||||||
|
|
||||||
|
if (isCredentialOnly) {
|
||||||
|
nodeData.type = HTTP_REQUEST_NODE_TYPE;
|
||||||
|
nodeData.extendsCredential = getCredentialTypeName(nodeType.name);
|
||||||
|
}
|
||||||
|
|
||||||
// Node-Type is known so we can save the parameters correctly
|
// Node-Type is known so we can save the parameters correctly
|
||||||
const nodeParameters = NodeHelpers.getNodeParameters(
|
const nodeParameters = NodeHelpers.getNodeParameters(
|
||||||
nodeType.properties,
|
nodeType.properties,
|
||||||
node.parameters,
|
node.parameters,
|
||||||
false,
|
isCredentialOnly,
|
||||||
false,
|
false,
|
||||||
node,
|
node,
|
||||||
);
|
);
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { defineComponent } from 'vue';
|
|
||||||
import { mapStores } from 'pinia';
|
|
||||||
import type { IExecutionPushResponse, IExecutionResponse, IStartRunData } from '@/Interface';
|
import type { IExecutionPushResponse, IExecutionResponse, IStartRunData } from '@/Interface';
|
||||||
|
import { mapStores } from 'pinia';
|
||||||
|
import { defineComponent } from 'vue';
|
||||||
|
|
||||||
import type { IRunData, IRunExecutionData, ITaskData, IWorkflowBase } from 'n8n-workflow';
|
import type { IRunData, IRunExecutionData, ITaskData, IWorkflowBase } from 'n8n-workflow';
|
||||||
import {
|
import {
|
||||||
|
@ -10,15 +10,15 @@ import {
|
||||||
FORM_TRIGGER_PATH_IDENTIFIER,
|
FORM_TRIGGER_PATH_IDENTIFIER,
|
||||||
} from 'n8n-workflow';
|
} from 'n8n-workflow';
|
||||||
|
|
||||||
|
import { useToast } from '@/composables';
|
||||||
import { externalHooks } from '@/mixins/externalHooks';
|
import { externalHooks } from '@/mixins/externalHooks';
|
||||||
import { workflowHelpers } from '@/mixins/workflowHelpers';
|
import { workflowHelpers } from '@/mixins/workflowHelpers';
|
||||||
import { useToast } from '@/composables';
|
|
||||||
|
|
||||||
import { useTitleChange } from '@/composables/useTitleChange';
|
import { useTitleChange } from '@/composables/useTitleChange';
|
||||||
|
import { useRootStore } from '@/stores/n8nRoot.store';
|
||||||
import { useUIStore } from '@/stores/ui.store';
|
import { useUIStore } from '@/stores/ui.store';
|
||||||
import { useWorkflowsStore } from '@/stores/workflows.store';
|
import { useWorkflowsStore } from '@/stores/workflows.store';
|
||||||
import { useRootStore } from '@/stores/n8nRoot.store';
|
import { FORM_TRIGGER_NODE_TYPE } from '@/constants';
|
||||||
import { FORM_TRIGGER_NODE_TYPE } from '../constants';
|
|
||||||
import { openPopUpWindow } from '@/utils/executionUtils';
|
import { openPopUpWindow } from '@/utils/executionUtils';
|
||||||
|
|
||||||
export const workflowRun = defineComponent({
|
export const workflowRun = defineComponent({
|
||||||
|
|
|
@ -814,6 +814,7 @@
|
||||||
"ndv.pinData.error.tooLarge.description": "Workflow has reached the maximum allowed pinned data size",
|
"ndv.pinData.error.tooLarge.description": "Workflow has reached the maximum allowed pinned data size",
|
||||||
"ndv.pinData.error.tooLargeWorkflow.title": "Pinned data too big",
|
"ndv.pinData.error.tooLargeWorkflow.title": "Pinned data too big",
|
||||||
"ndv.pinData.error.tooLargeWorkflow.description": "Workflow has reached the maximum allowed size",
|
"ndv.pinData.error.tooLargeWorkflow.description": "Workflow has reached the maximum allowed size",
|
||||||
|
"ndv.httpRequest.credentialOnly.docsNotice": "Use the <a target=\"_blank\" href=\"{docsUrl}\">{nodeName} docs</a> to construct your request. We'll take care of the authentication part if you add a {nodeName} credential below.",
|
||||||
"noTagsView.readyToOrganizeYourWorkflows": "Ready to organize your workflows?",
|
"noTagsView.readyToOrganizeYourWorkflows": "Ready to organize your workflows?",
|
||||||
"noTagsView.withWorkflowTagsYouReFree": "With workflow tags, you're free to create the perfect tagging system for your flows",
|
"noTagsView.withWorkflowTagsYouReFree": "With workflow tags, you're free to create the perfect tagging system for your flows",
|
||||||
"node.thisIsATriggerNode": "This is a Trigger node. <a target=\"_blank\" href=\"https://docs.n8n.io/workflows/components/nodes/\">Learn more</a>",
|
"node.thisIsATriggerNode": "This is a Trigger node. <a target=\"_blank\" href=\"https://docs.n8n.io/workflows/components/nodes/\">Learn more</a>",
|
||||||
|
|
|
@ -191,6 +191,9 @@ export const useCredentialsStore = defineStore(STORES.CREDENTIALS, {
|
||||||
return this.getCredentialOwnerName(credential);
|
return this.getCredentialOwnerName(credential);
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
httpOnlyCredentialTypes(): ICredentialType[] {
|
||||||
|
return this.allCredentialTypes.filter((credentialType) => credentialType.httpRequestNode);
|
||||||
|
},
|
||||||
},
|
},
|
||||||
actions: {
|
actions: {
|
||||||
setCredentialTypes(credentialTypes: ICredentialType[]): void {
|
setCredentialTypes(credentialTypes: ICredentialType[]): void {
|
||||||
|
|
|
@ -6,7 +6,12 @@ import {
|
||||||
getResourceLocatorResults,
|
getResourceLocatorResults,
|
||||||
getResourceMapperFields,
|
getResourceMapperFields,
|
||||||
} from '@/api/nodeTypes';
|
} from '@/api/nodeTypes';
|
||||||
import { DEFAULT_NODETYPE_VERSION, STORES } from '@/constants';
|
import {
|
||||||
|
DEFAULT_NODETYPE_VERSION,
|
||||||
|
HTTP_REQUEST_NODE_TYPE,
|
||||||
|
STORES,
|
||||||
|
CREDENTIAL_ONLY_HTTP_NODE_VERSION,
|
||||||
|
} from '@/constants';
|
||||||
import type {
|
import type {
|
||||||
INodeTypesState,
|
INodeTypesState,
|
||||||
IResourceLocatorReqParams,
|
IResourceLocatorReqParams,
|
||||||
|
@ -15,6 +20,7 @@ import type {
|
||||||
import { addHeaders, addNodeTranslation } from '@/plugins/i18n';
|
import { addHeaders, addNodeTranslation } from '@/plugins/i18n';
|
||||||
import { omit } from '@/utils';
|
import { omit } from '@/utils';
|
||||||
import type {
|
import type {
|
||||||
|
ConnectionTypes,
|
||||||
ILoadOptions,
|
ILoadOptions,
|
||||||
INode,
|
INode,
|
||||||
INodeCredentials,
|
INodeCredentials,
|
||||||
|
@ -26,12 +32,16 @@ import type {
|
||||||
INodeTypeNameVersion,
|
INodeTypeNameVersion,
|
||||||
ResourceMapperFields,
|
ResourceMapperFields,
|
||||||
Workflow,
|
Workflow,
|
||||||
ConnectionTypes,
|
|
||||||
} from 'n8n-workflow';
|
} from 'n8n-workflow';
|
||||||
|
import { NodeConnectionType, NodeHelpers } from 'n8n-workflow';
|
||||||
import { defineStore } from 'pinia';
|
import { defineStore } from 'pinia';
|
||||||
import { useCredentialsStore } from './credentials.store';
|
import { useCredentialsStore } from './credentials.store';
|
||||||
import { useRootStore } from './n8nRoot.store';
|
import { useRootStore } from './n8nRoot.store';
|
||||||
import { NodeHelpers, NodeConnectionType } from 'n8n-workflow';
|
import {
|
||||||
|
getCredentialOnlyNodeType,
|
||||||
|
getCredentialTypeName,
|
||||||
|
isCredentialOnlyNodeType,
|
||||||
|
} from '@/utils/credentialOnlyNodes';
|
||||||
|
|
||||||
function getNodeVersions(nodeType: INodeTypeDescription) {
|
function getNodeVersions(nodeType: INodeTypeDescription) {
|
||||||
return Array.isArray(nodeType.version) ? nodeType.version : [nodeType.version];
|
return Array.isArray(nodeType.version) ? nodeType.version : [nodeType.version];
|
||||||
|
@ -68,14 +78,28 @@ export const useNodeTypesStore = defineStore(STORES.NODE_TYPES, {
|
||||||
},
|
},
|
||||||
getNodeType() {
|
getNodeType() {
|
||||||
return (nodeTypeName: string, version?: number): INodeTypeDescription | null => {
|
return (nodeTypeName: string, version?: number): INodeTypeDescription | null => {
|
||||||
|
if (isCredentialOnlyNodeType(nodeTypeName)) {
|
||||||
|
return this.getCredentialOnlyNodeType(nodeTypeName, version);
|
||||||
|
}
|
||||||
|
|
||||||
const nodeVersions = this.nodeTypes[nodeTypeName];
|
const nodeVersions = this.nodeTypes[nodeTypeName];
|
||||||
|
|
||||||
if (!nodeVersions) return null;
|
if (!nodeVersions) return null;
|
||||||
|
|
||||||
const versionNumbers = Object.keys(nodeVersions).map(Number);
|
const versionNumbers = Object.keys(nodeVersions).map(Number);
|
||||||
const nodeType = nodeVersions[version || Math.max(...versionNumbers)];
|
const nodeType = nodeVersions[version ?? Math.max(...versionNumbers)];
|
||||||
|
return nodeType ?? null;
|
||||||
return nodeType || null;
|
};
|
||||||
|
},
|
||||||
|
getCredentialOnlyNodeType() {
|
||||||
|
return (nodeTypeName: string, version?: number): INodeTypeDescription | null => {
|
||||||
|
const credentialName = getCredentialTypeName(nodeTypeName);
|
||||||
|
const httpNode = this.getNodeType(
|
||||||
|
HTTP_REQUEST_NODE_TYPE,
|
||||||
|
version ?? CREDENTIAL_ONLY_HTTP_NODE_VERSION,
|
||||||
|
);
|
||||||
|
const credential = useCredentialsStore().getCredentialTypeByName(credentialName);
|
||||||
|
return getCredentialOnlyNodeType(httpNode, credential) ?? null;
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
isConfigNode() {
|
isConfigNode() {
|
||||||
|
|
|
@ -86,6 +86,7 @@ import { useNDVStore } from '@/stores/ndv.store';
|
||||||
import { useNodeTypesStore } from '@/stores/nodeTypes.store';
|
import { useNodeTypesStore } from '@/stores/nodeTypes.store';
|
||||||
import { useUsersStore } from '@/stores/users.store';
|
import { useUsersStore } from '@/stores/users.store';
|
||||||
import { useSettingsStore } from '@/stores/settings.store';
|
import { useSettingsStore } from '@/stores/settings.store';
|
||||||
|
import { getCredentialOnlyNodeTypeName } from '@/utils/credentialOnlyNodes';
|
||||||
|
|
||||||
const defaults: Omit<IWorkflowDb, 'id'> & { settings: NonNullable<IWorkflowDb['settings']> } = {
|
const defaults: Omit<IWorkflowDb, 'id'> & { settings: NonNullable<IWorkflowDb['settings']> } = {
|
||||||
name: '',
|
name: '',
|
||||||
|
@ -954,6 +955,10 @@ export const useWorkflowsStore = defineStore(STORES.WORKFLOWS, {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (nodeData.extendsCredential) {
|
||||||
|
nodeData.type = getCredentialOnlyNodeTypeName(nodeData.extendsCredential);
|
||||||
|
}
|
||||||
|
|
||||||
this.workflow.nodes.push(nodeData);
|
this.workflow.nodes.push(nodeData);
|
||||||
// Init node metadata
|
// Init node metadata
|
||||||
if (!this.nodeMetadata[nodeData.name]) {
|
if (!this.nodeMetadata[nodeData.name]) {
|
||||||
|
|
90
packages/editor-ui/src/utils/credentialOnlyNodes.ts
Normal file
90
packages/editor-ui/src/utils/credentialOnlyNodes.ts
Normal file
|
@ -0,0 +1,90 @@
|
||||||
|
import { deepCopy, type ICredentialType, type INodeTypeDescription } from 'n8n-workflow';
|
||||||
|
import { CREDENTIAL_ONLY_NODE_PREFIX } from '../constants';
|
||||||
|
import { i18n } from '@/plugins/i18n';
|
||||||
|
|
||||||
|
export function isCredentialOnlyNodeType(nodeTypeName: string): boolean {
|
||||||
|
return nodeTypeName?.startsWith(CREDENTIAL_ONLY_NODE_PREFIX) ?? false;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getCredentialTypeName(nodeTypeName: string): string {
|
||||||
|
return nodeTypeName.split('.')[1];
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getCredentialOnlyNodeTypeName(credentialTypeName: string): string {
|
||||||
|
return `${CREDENTIAL_ONLY_NODE_PREFIX}.${credentialTypeName}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getCredentialOnlyNodeType(
|
||||||
|
httpNode?: INodeTypeDescription | null,
|
||||||
|
credentialType?: ICredentialType,
|
||||||
|
): INodeTypeDescription | undefined {
|
||||||
|
const { httpRequestNode } = credentialType ?? {};
|
||||||
|
if (!httpNode || !credentialType || !httpRequestNode) return undefined;
|
||||||
|
|
||||||
|
const { docsUrl, name: displayName } = httpRequestNode;
|
||||||
|
|
||||||
|
const credentialOnlyNode = deepCopy(httpNode);
|
||||||
|
|
||||||
|
const httpIcon = httpNode.iconUrl;
|
||||||
|
|
||||||
|
credentialOnlyNode.name = getCredentialOnlyNodeTypeName(credentialType.name);
|
||||||
|
credentialOnlyNode.extendsCredential = credentialType.name;
|
||||||
|
credentialOnlyNode.displayName = displayName ?? credentialType.displayName;
|
||||||
|
credentialOnlyNode.description = 'HTTP request';
|
||||||
|
credentialOnlyNode.defaults.name = `${displayName} HTTP Request`;
|
||||||
|
credentialOnlyNode.codex = {
|
||||||
|
...credentialOnlyNode.codex,
|
||||||
|
alias: [],
|
||||||
|
categories: [],
|
||||||
|
subcategories: {},
|
||||||
|
};
|
||||||
|
|
||||||
|
credentialOnlyNode.credentials = [{ name: credentialType.name, required: true }];
|
||||||
|
|
||||||
|
if (credentialType.icon ?? credentialType.iconUrl) {
|
||||||
|
credentialOnlyNode.icon = credentialType.icon;
|
||||||
|
credentialOnlyNode.iconUrl = credentialType.iconUrl;
|
||||||
|
credentialOnlyNode.badgeIconUrl = httpIcon;
|
||||||
|
} else {
|
||||||
|
credentialOnlyNode.iconUrl = httpIcon;
|
||||||
|
}
|
||||||
|
|
||||||
|
credentialOnlyNode.properties = httpNode.properties.map((prop) => {
|
||||||
|
switch (prop.name) {
|
||||||
|
case 'authentication':
|
||||||
|
return { ...prop, type: 'hidden', default: 'predefinedCredentialType' };
|
||||||
|
case 'nodeCredentialType':
|
||||||
|
return { ...prop, type: 'hidden', default: credentialType.name };
|
||||||
|
case 'url':
|
||||||
|
const properties = { ...prop };
|
||||||
|
if ('apiBaseUrl' in httpRequestNode) {
|
||||||
|
const { apiBaseUrl } = httpRequestNode;
|
||||||
|
properties.default = apiBaseUrl;
|
||||||
|
properties.placeholder = apiBaseUrl ? `e.g. ${apiBaseUrl}` : prop.placeholder;
|
||||||
|
} else {
|
||||||
|
properties.placeholder = httpRequestNode.apiBaseUrlPlaceholder;
|
||||||
|
}
|
||||||
|
return properties;
|
||||||
|
default:
|
||||||
|
return prop;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
credentialOnlyNode.properties.splice(1, 0, {
|
||||||
|
type: 'notice',
|
||||||
|
displayName: i18n.baseText('ndv.httpRequest.credentialOnly.docsNotice', {
|
||||||
|
interpolate: { nodeName: displayName, docsUrl },
|
||||||
|
}),
|
||||||
|
name: 'httpVariantWarning',
|
||||||
|
default: '',
|
||||||
|
});
|
||||||
|
|
||||||
|
credentialOnlyNode.properties.splice(4, 0, {
|
||||||
|
type: 'credentials',
|
||||||
|
displayName: '',
|
||||||
|
name: '',
|
||||||
|
default: '',
|
||||||
|
});
|
||||||
|
|
||||||
|
return credentialOnlyNode;
|
||||||
|
}
|
|
@ -14,6 +14,12 @@ export class AlienVaultApi implements ICredentialType {
|
||||||
|
|
||||||
icon = 'file:icons/AlienVault.png';
|
icon = 'file:icons/AlienVault.png';
|
||||||
|
|
||||||
|
httpRequestNode = {
|
||||||
|
name: 'AlienVault',
|
||||||
|
docsUrl: 'https://otx.alienvault.com/api',
|
||||||
|
apiBaseUrl: 'https://otx.alienvault.com/api/v1/',
|
||||||
|
};
|
||||||
|
|
||||||
properties: INodeProperties[] = [
|
properties: INodeProperties[] = [
|
||||||
{
|
{
|
||||||
displayName: 'OTX Key',
|
displayName: 'OTX Key',
|
||||||
|
|
|
@ -16,6 +16,12 @@ export class Auth0ManagementApi implements ICredentialType {
|
||||||
|
|
||||||
icon = 'file:icons/Auth0.svg';
|
icon = 'file:icons/Auth0.svg';
|
||||||
|
|
||||||
|
httpRequestNode = {
|
||||||
|
name: 'Auth0',
|
||||||
|
docsUrl: 'https://auth0.com/docs/api/management/v2',
|
||||||
|
apiBaseUrlPlaceholder: 'https://your-tenant.auth0.com/api/v2/users/',
|
||||||
|
};
|
||||||
|
|
||||||
properties: INodeProperties[] = [
|
properties: INodeProperties[] = [
|
||||||
{
|
{
|
||||||
displayName: 'Session Token',
|
displayName: 'Session Token',
|
||||||
|
|
|
@ -9,6 +9,12 @@ export class CarbonBlackApi implements ICredentialType {
|
||||||
|
|
||||||
documentationUrl = 'carbonblack';
|
documentationUrl = 'carbonblack';
|
||||||
|
|
||||||
|
httpRequestNode = {
|
||||||
|
name: 'Carbon Black',
|
||||||
|
docsUrl: 'https://developer.carbonblack.com/reference',
|
||||||
|
apiBaseUrl: '',
|
||||||
|
};
|
||||||
|
|
||||||
properties: INodeProperties[] = [
|
properties: INodeProperties[] = [
|
||||||
{
|
{
|
||||||
displayName: 'URL',
|
displayName: 'URL',
|
||||||
|
|
|
@ -9,6 +9,12 @@ export class CiscoMerakiApi implements ICredentialType {
|
||||||
|
|
||||||
icon = 'file:icons/Cisco.svg';
|
icon = 'file:icons/Cisco.svg';
|
||||||
|
|
||||||
|
httpRequestNode = {
|
||||||
|
name: 'Cisco Meraki',
|
||||||
|
docsUrl: 'https://developer.cisco.com/meraki/api/',
|
||||||
|
apiBaseUrl: 'https://api.meraki.com/api/v1/',
|
||||||
|
};
|
||||||
|
|
||||||
properties: INodeProperties[] = [
|
properties: INodeProperties[] = [
|
||||||
{
|
{
|
||||||
displayName: 'API Key',
|
displayName: 'API Key',
|
||||||
|
|
|
@ -17,6 +17,12 @@ export class CiscoSecureEndpointApi implements ICredentialType {
|
||||||
|
|
||||||
icon = 'file:icons/Cisco.svg';
|
icon = 'file:icons/Cisco.svg';
|
||||||
|
|
||||||
|
httpRequestNode = {
|
||||||
|
name: 'Cisco Secure Endpoint',
|
||||||
|
docsUrl: 'https://developer.cisco.com/docs/secure-endpoint/',
|
||||||
|
apiBaseUrl: '',
|
||||||
|
};
|
||||||
|
|
||||||
properties: INodeProperties[] = [
|
properties: INodeProperties[] = [
|
||||||
{
|
{
|
||||||
displayName: 'Region',
|
displayName: 'Region',
|
||||||
|
|
|
@ -16,6 +16,12 @@ export class CiscoUmbrellaApi implements ICredentialType {
|
||||||
|
|
||||||
icon = 'file:icons/Cisco.svg';
|
icon = 'file:icons/Cisco.svg';
|
||||||
|
|
||||||
|
httpRequestNode = {
|
||||||
|
name: 'Cisco Umbrella',
|
||||||
|
docsUrl: 'https://developer.cisco.com/docs/cloud-security/',
|
||||||
|
apiBaseUrl: 'https://api.umbrella.com/',
|
||||||
|
};
|
||||||
|
|
||||||
properties: INodeProperties[] = [
|
properties: INodeProperties[] = [
|
||||||
{
|
{
|
||||||
displayName: 'Session Token',
|
displayName: 'Session Token',
|
||||||
|
|
|
@ -16,6 +16,12 @@ export class CrowdStrikeOAuth2Api implements ICredentialType {
|
||||||
|
|
||||||
icon = 'file:icons/CrowdStrike.svg';
|
icon = 'file:icons/CrowdStrike.svg';
|
||||||
|
|
||||||
|
httpRequestNode = {
|
||||||
|
name: 'CrowdStrike',
|
||||||
|
docsUrl: 'https://developer.crowdstrike.com/',
|
||||||
|
apiBaseUrl: '',
|
||||||
|
};
|
||||||
|
|
||||||
properties: INodeProperties[] = [
|
properties: INodeProperties[] = [
|
||||||
{
|
{
|
||||||
displayName: 'Session Token',
|
displayName: 'Session Token',
|
||||||
|
|
|
@ -9,6 +9,12 @@ export class F5BigIpApi implements ICredentialType {
|
||||||
|
|
||||||
icon = 'file:icons/F5.svg';
|
icon = 'file:icons/F5.svg';
|
||||||
|
|
||||||
|
httpRequestNode = {
|
||||||
|
name: 'F5 Big-IP',
|
||||||
|
docsUrl: 'https://clouddocs.f5.com/api/',
|
||||||
|
apiBaseUrl: '',
|
||||||
|
};
|
||||||
|
|
||||||
properties: INodeProperties[] = [
|
properties: INodeProperties[] = [
|
||||||
{
|
{
|
||||||
displayName: 'Username',
|
displayName: 'Username',
|
||||||
|
|
|
@ -9,6 +9,13 @@ export class FortiGateApi implements ICredentialType {
|
||||||
|
|
||||||
icon = 'file:icons/Fortinet.svg';
|
icon = 'file:icons/Fortinet.svg';
|
||||||
|
|
||||||
|
httpRequestNode = {
|
||||||
|
name: 'Fortinet FortiGate',
|
||||||
|
docsUrl:
|
||||||
|
'https://docs.fortinet.com/document/fortigate/7.4.1/administration-guide/940602/using-apis',
|
||||||
|
apiBaseUrl: '',
|
||||||
|
};
|
||||||
|
|
||||||
properties: INodeProperties[] = [
|
properties: INodeProperties[] = [
|
||||||
{
|
{
|
||||||
displayName: 'Access Token',
|
displayName: 'Access Token',
|
||||||
|
|
|
@ -9,6 +9,12 @@ export class HybridAnalysisApi implements ICredentialType {
|
||||||
|
|
||||||
icon = 'file:icons/Hybrid.png';
|
icon = 'file:icons/Hybrid.png';
|
||||||
|
|
||||||
|
httpRequestNode = {
|
||||||
|
name: 'Hybrid Analysis',
|
||||||
|
docsUrl: 'https://www.hybrid-analysis.com/docs/api/v2',
|
||||||
|
apiBaseUrl: 'https://www.hybrid-analysis.com/api/',
|
||||||
|
};
|
||||||
|
|
||||||
properties: INodeProperties[] = [
|
properties: INodeProperties[] = [
|
||||||
{
|
{
|
||||||
displayName: 'API Key',
|
displayName: 'API Key',
|
||||||
|
|
|
@ -9,6 +9,12 @@ export class ImpervaWafApi implements ICredentialType {
|
||||||
|
|
||||||
icon = 'file:icons/Imperva.svg';
|
icon = 'file:icons/Imperva.svg';
|
||||||
|
|
||||||
|
httpRequestNode = {
|
||||||
|
name: 'Imperva WAF',
|
||||||
|
docsUrl: 'https://docs.imperva.com/bundle/api-docs',
|
||||||
|
apiBaseUrl: 'https://api.imperva.com/',
|
||||||
|
};
|
||||||
|
|
||||||
properties: INodeProperties[] = [
|
properties: INodeProperties[] = [
|
||||||
{
|
{
|
||||||
displayName: 'API ID',
|
displayName: 'API ID',
|
||||||
|
|
|
@ -14,6 +14,12 @@ export class KibanaApi implements ICredentialType {
|
||||||
|
|
||||||
icon = 'file:icons/Kibana.svg';
|
icon = 'file:icons/Kibana.svg';
|
||||||
|
|
||||||
|
httpRequestNode = {
|
||||||
|
name: 'Kibana',
|
||||||
|
docsUrl: 'https://www.elastic.co/guide/en/kibana/current/api.html',
|
||||||
|
apiBaseUrl: '',
|
||||||
|
};
|
||||||
|
|
||||||
properties: INodeProperties[] = [
|
properties: INodeProperties[] = [
|
||||||
{
|
{
|
||||||
displayName: 'URL',
|
displayName: 'URL',
|
||||||
|
|
|
@ -14,6 +14,12 @@ export class MistApi implements ICredentialType {
|
||||||
|
|
||||||
documentationUrl = 'mist';
|
documentationUrl = 'mist';
|
||||||
|
|
||||||
|
httpRequestNode = {
|
||||||
|
name: 'Mist',
|
||||||
|
docsUrl: 'https://www.mist.com/documentation/mist-api-introduction/',
|
||||||
|
apiBaseUrl: '',
|
||||||
|
};
|
||||||
|
|
||||||
properties: INodeProperties[] = [
|
properties: INodeProperties[] = [
|
||||||
{
|
{
|
||||||
displayName: 'API Token',
|
displayName: 'API Token',
|
||||||
|
|
|
@ -14,6 +14,12 @@ export class OktaApi implements ICredentialType {
|
||||||
|
|
||||||
icon = 'file:icons/Okta.svg';
|
icon = 'file:icons/Okta.svg';
|
||||||
|
|
||||||
|
httpRequestNode = {
|
||||||
|
name: 'Okta',
|
||||||
|
docsUrl: 'https://developer.okta.com/docs/reference/',
|
||||||
|
apiBaseUrl: '',
|
||||||
|
};
|
||||||
|
|
||||||
properties: INodeProperties[] = [
|
properties: INodeProperties[] = [
|
||||||
{
|
{
|
||||||
displayName: 'URL',
|
displayName: 'URL',
|
||||||
|
|
|
@ -9,6 +9,12 @@ export class OpenCTIApi implements ICredentialType {
|
||||||
|
|
||||||
icon = 'file:icons/OpenCTI.png';
|
icon = 'file:icons/OpenCTI.png';
|
||||||
|
|
||||||
|
httpRequestNode = {
|
||||||
|
name: 'OpenCTI',
|
||||||
|
docsUrl: 'https://docs.opencti.io/latest/deployment/integrations/?h=api#graphql-api',
|
||||||
|
apiBaseUrl: '',
|
||||||
|
};
|
||||||
|
|
||||||
properties: INodeProperties[] = [
|
properties: INodeProperties[] = [
|
||||||
{
|
{
|
||||||
displayName: 'API Key',
|
displayName: 'API Key',
|
||||||
|
|
|
@ -9,6 +9,12 @@ export class QRadarApi implements ICredentialType {
|
||||||
|
|
||||||
documentationUrl = 'qradar';
|
documentationUrl = 'qradar';
|
||||||
|
|
||||||
|
httpRequestNode = {
|
||||||
|
name: 'QRadar',
|
||||||
|
docsUrl: 'https://www.ibm.com/docs/en/qradar-common',
|
||||||
|
apiBaseUrl: '',
|
||||||
|
};
|
||||||
|
|
||||||
properties: INodeProperties[] = [
|
properties: INodeProperties[] = [
|
||||||
{
|
{
|
||||||
displayName: 'API Key',
|
displayName: 'API Key',
|
||||||
|
|
|
@ -9,6 +9,12 @@ export class QualysApi implements ICredentialType {
|
||||||
|
|
||||||
documentationUrl = 'qualys';
|
documentationUrl = 'qualys';
|
||||||
|
|
||||||
|
httpRequestNode = {
|
||||||
|
name: 'Qualys',
|
||||||
|
docsUrl: 'https://qualysguard.qg2.apps.qualys.com/qwebhelp/fo_portal/api_doc/index.htm',
|
||||||
|
apiBaseUrl: 'https://qualysapi.qualys.com/api/',
|
||||||
|
};
|
||||||
|
|
||||||
properties: INodeProperties[] = [
|
properties: INodeProperties[] = [
|
||||||
{
|
{
|
||||||
displayName: 'Username',
|
displayName: 'Username',
|
||||||
|
|
|
@ -9,6 +9,12 @@ export class RecordedFutureApi implements ICredentialType {
|
||||||
|
|
||||||
icon = 'file:icons/RecordedFuture.svg';
|
icon = 'file:icons/RecordedFuture.svg';
|
||||||
|
|
||||||
|
httpRequestNode = {
|
||||||
|
name: 'Recorded Future',
|
||||||
|
docsUrl: 'https://api.recordedfuture.com',
|
||||||
|
apiBaseUrl: 'https://api.recordedfuture.com/v2/',
|
||||||
|
};
|
||||||
|
|
||||||
properties: INodeProperties[] = [
|
properties: INodeProperties[] = [
|
||||||
{
|
{
|
||||||
displayName: 'Access Token',
|
displayName: 'Access Token',
|
||||||
|
|
|
@ -9,6 +9,12 @@ export class SekoiaApi implements ICredentialType {
|
||||||
|
|
||||||
documentationUrl = 'sekoia';
|
documentationUrl = 'sekoia';
|
||||||
|
|
||||||
|
httpRequestNode = {
|
||||||
|
name: 'Sekoia',
|
||||||
|
docsUrl: 'https://docs.sekoia.io/cti/features/integrations/api/',
|
||||||
|
apiBaseUrl: 'https://api.sekoia.io/',
|
||||||
|
};
|
||||||
|
|
||||||
properties: INodeProperties[] = [
|
properties: INodeProperties[] = [
|
||||||
{
|
{
|
||||||
displayName: 'API Key',
|
displayName: 'API Key',
|
||||||
|
|
|
@ -14,6 +14,12 @@ export class ShufflerApi implements ICredentialType {
|
||||||
|
|
||||||
documentationUrl = 'shuffler';
|
documentationUrl = 'shuffler';
|
||||||
|
|
||||||
|
httpRequestNode = {
|
||||||
|
name: 'Shuffler',
|
||||||
|
docsUrl: 'https://shuffler.io/docs/API',
|
||||||
|
apiBaseUrl: 'https://shuffler.io/api/v1/',
|
||||||
|
};
|
||||||
|
|
||||||
properties: INodeProperties[] = [
|
properties: INodeProperties[] = [
|
||||||
{
|
{
|
||||||
displayName: 'API Key',
|
displayName: 'API Key',
|
||||||
|
|
|
@ -9,6 +9,12 @@ export class TrellixEpoApi implements ICredentialType {
|
||||||
|
|
||||||
icon = 'file:icons/Trellix.svg';
|
icon = 'file:icons/Trellix.svg';
|
||||||
|
|
||||||
|
httpRequestNode = {
|
||||||
|
name: 'Trellix (McAfee) ePolicy Orchestrator',
|
||||||
|
docsUrl: 'https://docs.trellix.com/en/bundle/epolicy-orchestrator-web-api-reference-guide',
|
||||||
|
apiBaseUrl: '',
|
||||||
|
};
|
||||||
|
|
||||||
properties: INodeProperties[] = [
|
properties: INodeProperties[] = [
|
||||||
{
|
{
|
||||||
displayName: 'Username',
|
displayName: 'Username',
|
||||||
|
|
|
@ -5,8 +5,16 @@ export class TwakeServerApi implements ICredentialType {
|
||||||
|
|
||||||
displayName = 'Twake Server API';
|
displayName = 'Twake Server API';
|
||||||
|
|
||||||
|
icon = 'file:icons/Twake.png';
|
||||||
|
|
||||||
documentationUrl = 'twake';
|
documentationUrl = 'twake';
|
||||||
|
|
||||||
|
httpRequestNode = {
|
||||||
|
name: 'Twake Server',
|
||||||
|
docsUrl: 'https://doc.twake.app/developers-api/home',
|
||||||
|
apiBaseUrl: '',
|
||||||
|
};
|
||||||
|
|
||||||
properties: INodeProperties[] = [
|
properties: INodeProperties[] = [
|
||||||
{
|
{
|
||||||
displayName: 'Host URL',
|
displayName: 'Host URL',
|
||||||
|
|
|
@ -8,12 +8,18 @@ import type {
|
||||||
export class VirusTotalApi implements ICredentialType {
|
export class VirusTotalApi implements ICredentialType {
|
||||||
name = 'virusTotalApi';
|
name = 'virusTotalApi';
|
||||||
|
|
||||||
displayName = 'Virus Total API';
|
displayName = 'VirusTotal API';
|
||||||
|
|
||||||
documentationUrl = 'virustotal';
|
documentationUrl = 'virustotal';
|
||||||
|
|
||||||
icon = 'file:icons/VirusTotal.svg';
|
icon = 'file:icons/VirusTotal.svg';
|
||||||
|
|
||||||
|
httpRequestNode = {
|
||||||
|
name: 'VirusTotal',
|
||||||
|
docsUrl: 'https://developers.virustotal.com/reference/overview',
|
||||||
|
apiBaseUrl: 'https://www.virustotal.com/api/v3/',
|
||||||
|
};
|
||||||
|
|
||||||
properties: INodeProperties[] = [
|
properties: INodeProperties[] = [
|
||||||
{
|
{
|
||||||
displayName: 'API Token',
|
displayName: 'API Token',
|
||||||
|
|
|
@ -16,6 +16,12 @@ export class ZscalerZiaApi implements ICredentialType {
|
||||||
|
|
||||||
icon = 'file:icons/Zscaler.svg';
|
icon = 'file:icons/Zscaler.svg';
|
||||||
|
|
||||||
|
httpRequestNode = {
|
||||||
|
name: 'Zscaler ZIA',
|
||||||
|
docsUrl: 'https://help.zscaler.com/zia/getting-started-zia-api',
|
||||||
|
apiBaseUrl: '',
|
||||||
|
};
|
||||||
|
|
||||||
properties: INodeProperties[] = [
|
properties: INodeProperties[] = [
|
||||||
{
|
{
|
||||||
displayName: 'Cookie',
|
displayName: 'Cookie',
|
||||||
|
|
BIN
packages/nodes-base/credentials/icons/Twake.png
Normal file
BIN
packages/nodes-base/credentials/icons/Twake.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 3.6 KiB |
|
@ -14,6 +14,9 @@ export class BrevoTrigger implements INodeType {
|
||||||
{
|
{
|
||||||
name: 'sendInBlueApi',
|
name: 'sendInBlueApi',
|
||||||
required: true,
|
required: true,
|
||||||
|
displayOptions: {
|
||||||
|
show: {},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
displayName: 'Brevo Trigger',
|
displayName: 'Brevo Trigger',
|
||||||
|
|
|
@ -14,5 +14,6 @@
|
||||||
"url": "https://docs.n8n.io/integrations/builtin/trigger-nodes/n8n-nodes-base.facebooktrigger/"
|
"url": "https://docs.n8n.io/integrations/builtin/trigger-nodes/n8n-nodes-base.facebooktrigger/"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
},
|
||||||
|
"alias": ["FB"]
|
||||||
}
|
}
|
||||||
|
|
|
@ -72,6 +72,12 @@ export const versionDescription: INodeTypeDescription = {
|
||||||
type: 'notice',
|
type: 'notice',
|
||||||
default: '',
|
default: '',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
displayName: '',
|
||||||
|
name: 'Credentials',
|
||||||
|
type: 'credentials',
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
{
|
{
|
||||||
displayName: 'Resource',
|
displayName: 'Resource',
|
||||||
name: 'resource',
|
name: 'resource',
|
||||||
|
|
|
@ -82,8 +82,8 @@ class CredentialType implements ICredentialTypes {
|
||||||
return this.credentialTypes[credentialType].type;
|
return this.credentialTypes[credentialType].type;
|
||||||
}
|
}
|
||||||
|
|
||||||
getNodeTypesToTestWith(type: string): string[] {
|
getSupportedNodes(type: string): string[] {
|
||||||
return knownCredentials[type]?.nodesToTestWith ?? [];
|
return knownCredentials[type]?.supportedNodes ?? [];
|
||||||
}
|
}
|
||||||
|
|
||||||
getParentTypes(typeName: string): string[] {
|
getParentTypes(typeName: string): string[] {
|
||||||
|
|
|
@ -308,6 +308,11 @@ export interface ICredentialTestRequestData {
|
||||||
testRequest: ICredentialTestRequest;
|
testRequest: ICredentialTestRequest;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type ICredentialHttpRequestNode = {
|
||||||
|
name: string;
|
||||||
|
docsUrl: string;
|
||||||
|
} & ({ apiBaseUrl: string } | { apiBaseUrlPlaceholder: string });
|
||||||
|
|
||||||
export interface ICredentialType {
|
export interface ICredentialType {
|
||||||
name: string;
|
name: string;
|
||||||
displayName: string;
|
displayName: string;
|
||||||
|
@ -324,12 +329,13 @@ export interface ICredentialType {
|
||||||
) => Promise<IDataObject>;
|
) => Promise<IDataObject>;
|
||||||
test?: ICredentialTestRequest;
|
test?: ICredentialTestRequest;
|
||||||
genericAuth?: boolean;
|
genericAuth?: boolean;
|
||||||
|
httpRequestNode?: ICredentialHttpRequestNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ICredentialTypes {
|
export interface ICredentialTypes {
|
||||||
recognizes(credentialType: string): boolean;
|
recognizes(credentialType: string): boolean;
|
||||||
getByName(credentialType: string): ICredentialType;
|
getByName(credentialType: string): ICredentialType;
|
||||||
getNodeTypesToTestWith(type: string): string[];
|
getSupportedNodes(type: string): string[];
|
||||||
getParentTypes(typeName: string): string[];
|
getParentTypes(typeName: string): string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -958,6 +964,7 @@ export interface INode {
|
||||||
parameters: INodeParameters;
|
parameters: INodeParameters;
|
||||||
credentials?: INodeCredentials;
|
credentials?: INodeCredentials;
|
||||||
webhookId?: string;
|
webhookId?: string;
|
||||||
|
extendsCredential?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IPinData {
|
export interface IPinData {
|
||||||
|
@ -1058,7 +1065,8 @@ export type NodePropertyTypes =
|
||||||
| 'credentialsSelect'
|
| 'credentialsSelect'
|
||||||
| 'resourceLocator'
|
| 'resourceLocator'
|
||||||
| 'curlImport'
|
| 'curlImport'
|
||||||
| 'resourceMapper';
|
| 'resourceMapper'
|
||||||
|
| 'credentials';
|
||||||
|
|
||||||
export type CodeAutocompleteTypes = 'function' | 'functionItem';
|
export type CodeAutocompleteTypes = 'function' | 'functionItem';
|
||||||
|
|
||||||
|
@ -1398,6 +1406,7 @@ export interface INodeTypeBaseDescription {
|
||||||
name: string;
|
name: string;
|
||||||
icon?: string;
|
icon?: string;
|
||||||
iconUrl?: string;
|
iconUrl?: string;
|
||||||
|
badgeIconUrl?: string;
|
||||||
group: string[];
|
group: string[];
|
||||||
description: string;
|
description: string;
|
||||||
documentationUrl?: string;
|
documentationUrl?: string;
|
||||||
|
@ -1611,6 +1620,7 @@ export interface INodeTypeDescription extends INodeTypeBaseDescription {
|
||||||
inactive: string;
|
inactive: string;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
extendsCredential?: string;
|
||||||
__loadOptionsMethods?: string[]; // only for validation during build
|
__loadOptionsMethods?: string[]; // only for validation during build
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1710,7 +1720,7 @@ export type LoadingDetails = {
|
||||||
};
|
};
|
||||||
|
|
||||||
export type CredentialLoadingDetails = LoadingDetails & {
|
export type CredentialLoadingDetails = LoadingDetails & {
|
||||||
nodesToTestWith?: string[];
|
supportedNodes?: string[];
|
||||||
extends?: string[];
|
extends?: string[];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -10,9 +10,12 @@
|
||||||
|
|
||||||
import get from 'lodash/get';
|
import get from 'lodash/get';
|
||||||
import isEqual from 'lodash/isEqual';
|
import isEqual from 'lodash/isEqual';
|
||||||
|
import uniqBy from 'lodash/uniqBy';
|
||||||
|
|
||||||
import type {
|
import type {
|
||||||
|
FieldType,
|
||||||
IContextObject,
|
IContextObject,
|
||||||
|
IHttpRequestMethods,
|
||||||
INode,
|
INode,
|
||||||
INodeCredentialDescription,
|
INodeCredentialDescription,
|
||||||
INodeIssueObjectProperty,
|
INodeIssueObjectProperty,
|
||||||
|
@ -23,17 +26,15 @@ import type {
|
||||||
INodePropertyCollection,
|
INodePropertyCollection,
|
||||||
INodePropertyMode,
|
INodePropertyMode,
|
||||||
INodePropertyModeValidation,
|
INodePropertyModeValidation,
|
||||||
|
INodePropertyOptions,
|
||||||
INodePropertyRegexValidation,
|
INodePropertyRegexValidation,
|
||||||
INodeType,
|
INodeType,
|
||||||
IVersionedNodeType,
|
|
||||||
IParameterDependencies,
|
IParameterDependencies,
|
||||||
IRunExecutionData,
|
IRunExecutionData,
|
||||||
|
IVersionedNodeType,
|
||||||
IWebhookData,
|
IWebhookData,
|
||||||
IWorkflowExecuteAdditionalData,
|
IWorkflowExecuteAdditionalData,
|
||||||
NodeParameterValue,
|
NodeParameterValue,
|
||||||
IHttpRequestMethods,
|
|
||||||
FieldType,
|
|
||||||
INodePropertyOptions,
|
|
||||||
ResourceMapperValue,
|
ResourceMapperValue,
|
||||||
ValidationResult,
|
ValidationResult,
|
||||||
ConnectionTypes,
|
ConnectionTypes,
|
||||||
|
@ -45,8 +46,8 @@ import type {
|
||||||
import { isResourceMapperValue, isValidResourceLocatorParameterValue } from './type-guards';
|
import { isResourceMapperValue, isValidResourceLocatorParameterValue } from './type-guards';
|
||||||
import { deepCopy } from './utils';
|
import { deepCopy } from './utils';
|
||||||
|
|
||||||
import type { Workflow } from './Workflow';
|
|
||||||
import { DateTime } from 'luxon';
|
import { DateTime } from 'luxon';
|
||||||
|
import type { Workflow } from './Workflow';
|
||||||
|
|
||||||
export const cronNodeOptions: INodePropertyCollection[] = [
|
export const cronNodeOptions: INodePropertyCollection[] = [
|
||||||
{
|
{
|
||||||
|
@ -1733,10 +1734,33 @@ export function getVersionedNodeType(
|
||||||
|
|
||||||
export function getVersionedNodeTypeAll(object: IVersionedNodeType | INodeType): INodeType[] {
|
export function getVersionedNodeTypeAll(object: IVersionedNodeType | INodeType): INodeType[] {
|
||||||
if ('nodeVersions' in object) {
|
if ('nodeVersions' in object) {
|
||||||
return Object.values(object.nodeVersions).map((element) => {
|
return uniqBy(
|
||||||
element.description.name = object.description.name;
|
Object.values(object.nodeVersions)
|
||||||
return element;
|
.map((element) => {
|
||||||
});
|
element.description.name = object.description.name;
|
||||||
|
return element;
|
||||||
|
})
|
||||||
|
.reverse(),
|
||||||
|
(node) => {
|
||||||
|
const { version } = node.description;
|
||||||
|
return Array.isArray(version) ? version.join(',') : version.toString();
|
||||||
|
},
|
||||||
|
);
|
||||||
}
|
}
|
||||||
return [object];
|
return [object];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getCredentialsForNode(
|
||||||
|
object: IVersionedNodeType | INodeType,
|
||||||
|
): INodeCredentialDescription[] {
|
||||||
|
if ('nodeVersions' in object) {
|
||||||
|
return uniqBy(
|
||||||
|
Object.values(object.nodeVersions).flatMap(
|
||||||
|
(version) => version.description.credentials ?? [],
|
||||||
|
),
|
||||||
|
'name',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return object.description.credentials ?? [];
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue