mirror of
https://github.com/Abdulazizzn/n8n-enterprise-unlocked.git
synced 2025-12-17 18:12:04 +00:00
Merge branch 'master' into save-changes-warning
This commit is contained in:
@@ -19,7 +19,7 @@ Condition notice.
|
||||
|
||||
Software: n8n
|
||||
|
||||
License: Apache 2.0
|
||||
License: Apache 2.0 with Commons Clause
|
||||
|
||||
Licensor: n8n GmbH
|
||||
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
MONGO_INITDB_ROOT_USERNAME=changeUser
|
||||
MONGO_INITDB_ROOT_PASSWORD=changePassword
|
||||
MONGO_INITDB_DATABASE=n8n
|
||||
|
||||
MONGO_NON_ROOT_USERNAME=changeUser
|
||||
MONGO_NON_ROOT_PASSWORD=changePassword
|
||||
|
||||
N8N_BASIC_AUTH_USER=changeUser
|
||||
N8N_BASIC_AUTH_PASSWORD=changePassword
|
||||
@@ -1,26 +0,0 @@
|
||||
# n8n with MongoDB
|
||||
|
||||
Starts n8n with MongoDB as database.
|
||||
|
||||
|
||||
## Start
|
||||
|
||||
To start n8n with MongoDB simply start docker-compose by executing the following
|
||||
command in the current folder.
|
||||
|
||||
|
||||
**IMPORTANT:** But before you do that change the default users and passwords in the `.env` file!
|
||||
|
||||
```
|
||||
docker-compose up -d
|
||||
```
|
||||
|
||||
To stop it execute:
|
||||
|
||||
```
|
||||
docker-compose stop
|
||||
```
|
||||
|
||||
## Configuration
|
||||
|
||||
The default name of the database, user and password for MongoDB can be changed in the `.env` file in the current directory.
|
||||
@@ -1,34 +0,0 @@
|
||||
version: '3.1'
|
||||
|
||||
services:
|
||||
|
||||
mongo:
|
||||
image: mongo:4.0
|
||||
restart: always
|
||||
environment:
|
||||
- MONGO_INITDB_ROOT_USERNAME
|
||||
- MONGO_INITDB_ROOT_PASSWORD
|
||||
- MONGO_INITDB_DATABASE
|
||||
- MONGO_NON_ROOT_USERNAME
|
||||
- MONGO_NON_ROOT_PASSWORD
|
||||
volumes:
|
||||
- ./init-data.sh:/docker-entrypoint-initdb.d/init-data.sh
|
||||
|
||||
n8n:
|
||||
image: n8nio/n8n
|
||||
restart: always
|
||||
environment:
|
||||
- DB_TYPE=mongodb
|
||||
- DB_MONGODB_CONNECTION_URL=mongodb://${MONGO_NON_ROOT_USERNAME}:${MONGO_NON_ROOT_PASSWORD}@mongo:27017/${MONGO_INITDB_DATABASE}
|
||||
- N8N_BASIC_AUTH_ACTIVE=true
|
||||
- N8N_BASIC_AUTH_USER
|
||||
- N8N_BASIC_AUTH_PASSWORD
|
||||
ports:
|
||||
- 5678:5678
|
||||
links:
|
||||
- mongo
|
||||
volumes:
|
||||
- ~/.n8n:/root/.n8n
|
||||
# Wait 5 seconds to start n8n to make sure that MongoDB is ready
|
||||
# when n8n tries to connect to it
|
||||
command: /bin/sh -c "sleep 5; n8n start"
|
||||
@@ -1,17 +0,0 @@
|
||||
#!/bin/bash
|
||||
set -e;
|
||||
|
||||
# Create a default non-root role
|
||||
MONGO_NON_ROOT_ROLE="${MONGO_NON_ROOT_ROLE:-readWrite}"
|
||||
|
||||
if [ -n "${MONGO_NON_ROOT_USERNAME:-}" ] && [ -n "${MONGO_NON_ROOT_PASSWORD:-}" ]; then
|
||||
"${mongo[@]}" "$MONGO_INITDB_DATABASE" <<-EOJS
|
||||
db.createUser({
|
||||
user: $(_js_escape "$MONGO_NON_ROOT_USERNAME"),
|
||||
pwd: $(_js_escape "$MONGO_NON_ROOT_PASSWORD"),
|
||||
roles: [ { role: $(_js_escape "$MONGO_NON_ROOT_ROLE"), db: $(_js_escape "$MONGO_INITDB_DATABASE") } ]
|
||||
})
|
||||
EOJS
|
||||
else
|
||||
echo "SETUP INFO: No Environment variables given!"
|
||||
fi
|
||||
@@ -2,6 +2,43 @@
|
||||
|
||||
This list shows all the versions which include breaking changes and how to upgrade.
|
||||
|
||||
## 0.87.0
|
||||
|
||||
### What changed?
|
||||
|
||||
The link.fish node got removed because the service is shutting down.
|
||||
|
||||
### When is action necessary?
|
||||
|
||||
If you are are actively using the link.fish node.
|
||||
|
||||
### How to upgrade:
|
||||
|
||||
Unfortunately, that's not possible. We'd recommend you to look for an alternative service.
|
||||
|
||||
|
||||
## 0.83.0
|
||||
|
||||
### What changed?
|
||||
|
||||
In the Active Campaign Node, we have changed how the `getAll` operation works with various resources for the sake of consistency. To achieve this, a new parameter called 'Simple' has been added.
|
||||
|
||||
### When is action necessary?
|
||||
|
||||
When one of the following resources/operations is used:
|
||||
|
||||
| Resource | Operation |
|
||||
|--|--|
|
||||
| Deal | Get All |
|
||||
| Connector | Get All |
|
||||
| E-commerce Order | Get All |
|
||||
| E-commerce Customer | Get All |
|
||||
| E-commerce Order Products | Get All |
|
||||
|
||||
### How to upgrade:
|
||||
|
||||
Open the affected resource/operation and set the parameter `Simple` to false.
|
||||
|
||||
## 0.79.0
|
||||
|
||||
### What changed?
|
||||
|
||||
@@ -19,7 +19,7 @@ Condition notice.
|
||||
|
||||
Software: n8n
|
||||
|
||||
License: Apache 2.0
|
||||
License: Apache 2.0 with Commons Clause
|
||||
|
||||
Licensor: n8n GmbH
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ process.env.NODE_CONFIG_DIR = process.env.NODE_CONFIG_DIR || path.join(__dirname
|
||||
var versionFlags = [ // tslint:disable-line:no-var-keyword
|
||||
'-v',
|
||||
'-V',
|
||||
'--version'
|
||||
'--version',
|
||||
];
|
||||
if (versionFlags.includes(process.argv.slice(-1)[0])) {
|
||||
console.log(require('../package').version);
|
||||
@@ -22,23 +22,10 @@ if (process.argv.length === 2) {
|
||||
process.argv.push('start');
|
||||
}
|
||||
|
||||
var command = process.argv[2]; // tslint:disable-line:no-var-keyword
|
||||
var nodeVersion = process.versions.node.split('.');
|
||||
|
||||
// Check if the command the user did enter is supported else stop
|
||||
var supportedCommands = [ // tslint:disable-line:no-var-keyword
|
||||
'execute',
|
||||
'help',
|
||||
'start',
|
||||
];
|
||||
|
||||
if (!supportedCommands.includes(command)) {
|
||||
console.log('\nThe command "' + command + '" is not known!\n');
|
||||
process.argv.pop();
|
||||
process.argv.push('--help');
|
||||
}
|
||||
|
||||
if (parseInt(process.versions.node.split('.')[0], 10) < 10) {
|
||||
console.log('\nThe Node.js version is too old to run n8n. Please use version 10 or later!\n');
|
||||
if (parseInt(nodeVersion[0], 10) < 12 || parseInt(nodeVersion[0], 10) === 12 && parseInt(nodeVersion[1], 10) < 9) {
|
||||
console.log(`\nYour Node.js version (${process.versions.node}) is too old to run n8n.\nPlease update to version 12.9 or later!\n`);
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
|
||||
70
packages/cli/commands/config/workflow/deactivate.ts
Normal file
70
packages/cli/commands/config/workflow/deactivate.ts
Normal file
@@ -0,0 +1,70 @@
|
||||
import {
|
||||
Command, flags,
|
||||
} from '@oclif/command';
|
||||
|
||||
import {
|
||||
IDataObject
|
||||
} from 'n8n-workflow';
|
||||
|
||||
import {
|
||||
Db,
|
||||
GenericHelpers,
|
||||
} from "../../../src";
|
||||
|
||||
|
||||
export class DeactivateCommand extends Command {
|
||||
static description = '\nDeactivates workflows';
|
||||
|
||||
static examples = [
|
||||
`$ n8n config:workflow:deactivate --all`,
|
||||
`$ n8n config:workflow:deactivate --id=5`,
|
||||
];
|
||||
|
||||
static flags = {
|
||||
help: flags.help({ char: 'h' }),
|
||||
all: flags.boolean({
|
||||
description: 'Deactivates all workflows',
|
||||
}),
|
||||
id: flags.string({
|
||||
description: 'Deactivats the workflow with the given ID',
|
||||
}),
|
||||
};
|
||||
|
||||
async run() {
|
||||
const { flags } = this.parse(DeactivateCommand);
|
||||
|
||||
if (!flags.all && !flags.id) {
|
||||
GenericHelpers.logOutput(`Either option "--all" or "--id" have to be set!`);
|
||||
return;
|
||||
}
|
||||
|
||||
if (flags.all && flags.id) {
|
||||
GenericHelpers.logOutput(`Either "--all" or "--id" can be set never both!`);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await Db.init();
|
||||
|
||||
const findQuery: IDataObject = {};
|
||||
if (flags.id) {
|
||||
console.log(`Deactivating workflow with ID: ${flags.id}`);
|
||||
findQuery.id = flags.id;
|
||||
} else {
|
||||
console.log('Deactivating all workflows');
|
||||
findQuery.active = true;
|
||||
}
|
||||
|
||||
await Db.collections.Workflow!.update(findQuery, { active: false });
|
||||
console.log('Done');
|
||||
} catch (e) {
|
||||
console.error('\nGOT ERROR');
|
||||
console.log('====================================');
|
||||
console.error(e.message);
|
||||
console.error(e.stack);
|
||||
this.exit(1);
|
||||
}
|
||||
|
||||
this.exit();
|
||||
}
|
||||
}
|
||||
@@ -10,6 +10,7 @@ import {
|
||||
import {
|
||||
ActiveExecutions,
|
||||
CredentialsOverwrites,
|
||||
CredentialTypes,
|
||||
Db,
|
||||
ExternalHooks,
|
||||
GenericHelpers,
|
||||
@@ -116,6 +117,8 @@ export class Execute extends Command {
|
||||
// Add the found types to an instance other parts of the application can use
|
||||
const nodeTypes = NodeTypes();
|
||||
await nodeTypes.init(loadNodesAndCredentials.nodeTypes);
|
||||
const credentialTypes = CredentialTypes();
|
||||
await credentialTypes.init(loadNodesAndCredentials.credentialTypes);
|
||||
|
||||
if (!WorkflowHelpers.isWorkflowIdValid(workflowId)) {
|
||||
workflowId = undefined;
|
||||
|
||||
@@ -8,12 +8,14 @@ const open = require('open');
|
||||
|
||||
import * as config from '../config';
|
||||
import {
|
||||
ActiveExecutions,
|
||||
ActiveWorkflowRunner,
|
||||
CredentialTypes,
|
||||
CredentialsOverwrites,
|
||||
CredentialTypes,
|
||||
Db,
|
||||
ExternalHooks,
|
||||
GenericHelpers,
|
||||
IExecutionsCurrentSummary,
|
||||
LoadNodesAndCredentials,
|
||||
NodeTypes,
|
||||
Server,
|
||||
@@ -68,6 +70,10 @@ export class Start extends Command {
|
||||
static async stopProcess() {
|
||||
console.log(`\nStopping n8n...`);
|
||||
|
||||
try {
|
||||
const externalHooks = ExternalHooks();
|
||||
await externalHooks.run('n8n.stop', []);
|
||||
|
||||
setTimeout(() => {
|
||||
// In case that something goes wrong with shutdown we
|
||||
// kill after max. 30 seconds no matter what
|
||||
@@ -85,6 +91,25 @@ export class Start extends Command {
|
||||
|
||||
await Promise.all(removePromises);
|
||||
|
||||
// Wait for active workflow executions to finish
|
||||
const activeExecutionsInstance = ActiveExecutions.getInstance();
|
||||
let executingWorkflows = activeExecutionsInstance.getActiveExecutions();
|
||||
|
||||
let count = 0;
|
||||
while (executingWorkflows.length !== 0) {
|
||||
if (count++ % 4 === 0) {
|
||||
console.log(`Waiting for ${executingWorkflows.length} active executions to finish...`);
|
||||
}
|
||||
await new Promise((resolve) => {
|
||||
setTimeout(resolve, 500);
|
||||
});
|
||||
executingWorkflows = activeExecutionsInstance.getActiveExecutions();
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('There was an error shutting down n8n.', error);
|
||||
}
|
||||
|
||||
process.exit(processExistCode);
|
||||
}
|
||||
|
||||
|
||||
@@ -10,58 +10,58 @@ const config = convict({
|
||||
doc: 'Type of database to use',
|
||||
format: ['sqlite', 'mariadb', 'mongodb', 'mysqldb', 'postgresdb'],
|
||||
default: 'sqlite',
|
||||
env: 'DB_TYPE'
|
||||
env: 'DB_TYPE',
|
||||
},
|
||||
mongodb: {
|
||||
connectionUrl: {
|
||||
doc: 'MongoDB Connection URL',
|
||||
format: '*',
|
||||
default: 'mongodb://user:password@localhost:27017/database',
|
||||
env: 'DB_MONGODB_CONNECTION_URL'
|
||||
}
|
||||
env: 'DB_MONGODB_CONNECTION_URL',
|
||||
},
|
||||
},
|
||||
tablePrefix: {
|
||||
doc: 'Prefix for table names',
|
||||
format: '*',
|
||||
default: '',
|
||||
env: 'DB_TABLE_PREFIX'
|
||||
env: 'DB_TABLE_PREFIX',
|
||||
},
|
||||
postgresdb: {
|
||||
database: {
|
||||
doc: 'PostgresDB Database',
|
||||
format: String,
|
||||
default: 'n8n',
|
||||
env: 'DB_POSTGRESDB_DATABASE'
|
||||
env: 'DB_POSTGRESDB_DATABASE',
|
||||
},
|
||||
host: {
|
||||
doc: 'PostgresDB Host',
|
||||
format: String,
|
||||
default: 'localhost',
|
||||
env: 'DB_POSTGRESDB_HOST'
|
||||
env: 'DB_POSTGRESDB_HOST',
|
||||
},
|
||||
password: {
|
||||
doc: 'PostgresDB Password',
|
||||
format: String,
|
||||
default: '',
|
||||
env: 'DB_POSTGRESDB_PASSWORD'
|
||||
env: 'DB_POSTGRESDB_PASSWORD',
|
||||
},
|
||||
port: {
|
||||
doc: 'PostgresDB Port',
|
||||
format: Number,
|
||||
default: 5432,
|
||||
env: 'DB_POSTGRESDB_PORT'
|
||||
env: 'DB_POSTGRESDB_PORT',
|
||||
},
|
||||
user: {
|
||||
doc: 'PostgresDB User',
|
||||
format: String,
|
||||
default: 'root',
|
||||
env: 'DB_POSTGRESDB_USER'
|
||||
env: 'DB_POSTGRESDB_USER',
|
||||
},
|
||||
schema: {
|
||||
doc: 'PostgresDB Schema',
|
||||
format: String,
|
||||
default: 'public',
|
||||
env: 'DB_POSTGRESDB_SCHEMA'
|
||||
env: 'DB_POSTGRESDB_SCHEMA',
|
||||
},
|
||||
|
||||
ssl: {
|
||||
@@ -89,7 +89,7 @@ const config = convict({
|
||||
default: true,
|
||||
env: 'DB_POSTGRESDB_SSL_REJECT_UNAUTHORIZED',
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
},
|
||||
mysqldb: {
|
||||
@@ -97,31 +97,31 @@ const config = convict({
|
||||
doc: 'MySQL Database',
|
||||
format: String,
|
||||
default: 'n8n',
|
||||
env: 'DB_MYSQLDB_DATABASE'
|
||||
env: 'DB_MYSQLDB_DATABASE',
|
||||
},
|
||||
host: {
|
||||
doc: 'MySQL Host',
|
||||
format: String,
|
||||
default: 'localhost',
|
||||
env: 'DB_MYSQLDB_HOST'
|
||||
env: 'DB_MYSQLDB_HOST',
|
||||
},
|
||||
password: {
|
||||
doc: 'MySQL Password',
|
||||
format: String,
|
||||
default: '',
|
||||
env: 'DB_MYSQLDB_PASSWORD'
|
||||
env: 'DB_MYSQLDB_PASSWORD',
|
||||
},
|
||||
port: {
|
||||
doc: 'MySQL Port',
|
||||
format: Number,
|
||||
default: 3306,
|
||||
env: 'DB_MYSQLDB_PORT'
|
||||
env: 'DB_MYSQLDB_PORT',
|
||||
},
|
||||
user: {
|
||||
doc: 'MySQL User',
|
||||
format: String,
|
||||
default: 'root',
|
||||
env: 'DB_MYSQLDB_USER'
|
||||
env: 'DB_MYSQLDB_USER',
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -136,7 +136,7 @@ const config = convict({
|
||||
doc: 'Overwrites for credentials',
|
||||
format: '*',
|
||||
default: '{}',
|
||||
env: 'CREDENTIALS_OVERWRITE_DATA'
|
||||
env: 'CREDENTIALS_OVERWRITE_DATA',
|
||||
},
|
||||
endpoint: {
|
||||
doc: 'Fetch credentials from API',
|
||||
@@ -156,7 +156,7 @@ const config = convict({
|
||||
doc: 'In what process workflows should be executed',
|
||||
format: ['main', 'own'],
|
||||
default: 'own',
|
||||
env: 'EXECUTIONS_PROCESS'
|
||||
env: 'EXECUTIONS_PROCESS',
|
||||
},
|
||||
|
||||
// A Workflow times out and gets canceled after this time (seconds).
|
||||
@@ -174,13 +174,13 @@ const config = convict({
|
||||
doc: 'Max run time (seconds) before stopping the workflow execution',
|
||||
format: Number,
|
||||
default: -1,
|
||||
env: 'EXECUTIONS_TIMEOUT'
|
||||
env: 'EXECUTIONS_TIMEOUT',
|
||||
},
|
||||
maxTimeout: {
|
||||
doc: 'Max execution time (seconds) that can be set for a workflow individually',
|
||||
format: Number,
|
||||
default: 3600,
|
||||
env: 'EXECUTIONS_TIMEOUT_MAX'
|
||||
env: 'EXECUTIONS_TIMEOUT_MAX',
|
||||
},
|
||||
|
||||
// If a workflow executes all the data gets saved by default. This
|
||||
@@ -193,13 +193,13 @@ const config = convict({
|
||||
doc: 'What workflow execution data to save on error',
|
||||
format: ['all', 'none'],
|
||||
default: 'all',
|
||||
env: 'EXECUTIONS_DATA_SAVE_ON_ERROR'
|
||||
env: 'EXECUTIONS_DATA_SAVE_ON_ERROR',
|
||||
},
|
||||
saveDataOnSuccess: {
|
||||
doc: 'What workflow execution data to save on success',
|
||||
format: ['all', 'none'],
|
||||
default: 'all',
|
||||
env: 'EXECUTIONS_DATA_SAVE_ON_SUCCESS'
|
||||
env: 'EXECUTIONS_DATA_SAVE_ON_SUCCESS',
|
||||
},
|
||||
|
||||
// If the executions of workflows which got started via the editor
|
||||
@@ -211,7 +211,7 @@ const config = convict({
|
||||
doc: 'Save data of executions when started manually via editor',
|
||||
format: 'Boolean',
|
||||
default: false,
|
||||
env: 'EXECUTIONS_DATA_SAVE_MANUAL_EXECUTIONS'
|
||||
env: 'EXECUTIONS_DATA_SAVE_MANUAL_EXECUTIONS',
|
||||
},
|
||||
|
||||
// To not exceed the database's capacity and keep its size moderate
|
||||
@@ -223,19 +223,19 @@ const config = convict({
|
||||
doc: 'Delete data of past executions on a rolling basis',
|
||||
format: 'Boolean',
|
||||
default: false,
|
||||
env: 'EXECUTIONS_DATA_PRUNE'
|
||||
env: 'EXECUTIONS_DATA_PRUNE',
|
||||
},
|
||||
pruneDataMaxAge: {
|
||||
doc: 'How old (hours) the execution data has to be to get deleted',
|
||||
format: Number,
|
||||
default: 336,
|
||||
env: 'EXECUTIONS_DATA_MAX_AGE'
|
||||
env: 'EXECUTIONS_DATA_MAX_AGE',
|
||||
},
|
||||
pruneDataTimeout: {
|
||||
doc: 'Timeout (seconds) after execution data has been pruned',
|
||||
format: Number,
|
||||
default: 3600,
|
||||
env: 'EXECUTIONS_DATA_PRUNE_TIMEOUT'
|
||||
env: 'EXECUTIONS_DATA_PRUNE_TIMEOUT',
|
||||
},
|
||||
},
|
||||
|
||||
@@ -248,7 +248,7 @@ const config = convict({
|
||||
doc: 'The timezone to use',
|
||||
format: '*',
|
||||
default: 'America/New_York',
|
||||
env: 'GENERIC_TIMEZONE'
|
||||
env: 'GENERIC_TIMEZONE',
|
||||
},
|
||||
},
|
||||
|
||||
@@ -258,66 +258,78 @@ const config = convict({
|
||||
default: '/',
|
||||
arg: 'path',
|
||||
env: 'N8N_PATH',
|
||||
doc: 'Path n8n is deployed to'
|
||||
doc: 'Path n8n is deployed to',
|
||||
},
|
||||
host: {
|
||||
format: String,
|
||||
default: 'localhost',
|
||||
arg: 'host',
|
||||
env: 'N8N_HOST',
|
||||
doc: 'Host name n8n can be reached'
|
||||
doc: 'Host name n8n can be reached',
|
||||
},
|
||||
port: {
|
||||
format: Number,
|
||||
default: 5678,
|
||||
arg: 'port',
|
||||
env: 'N8N_PORT',
|
||||
doc: 'HTTP port n8n can be reached'
|
||||
doc: 'HTTP port n8n can be reached',
|
||||
},
|
||||
listen_address: {
|
||||
format: String,
|
||||
default: '0.0.0.0',
|
||||
env: 'N8N_LISTEN_ADDRESS',
|
||||
doc: 'IP address n8n should listen on'
|
||||
doc: 'IP address n8n should listen on',
|
||||
},
|
||||
protocol: {
|
||||
format: ['http', 'https'],
|
||||
default: 'http',
|
||||
env: 'N8N_PROTOCOL',
|
||||
doc: 'HTTP Protocol via which n8n can be reached'
|
||||
doc: 'HTTP Protocol via which n8n can be reached',
|
||||
},
|
||||
ssl_key: {
|
||||
format: String,
|
||||
default: '',
|
||||
env: 'N8N_SSL_KEY',
|
||||
doc: 'SSL Key for HTTPS Protocol'
|
||||
doc: 'SSL Key for HTTPS Protocol',
|
||||
},
|
||||
ssl_cert: {
|
||||
format: String,
|
||||
default: '',
|
||||
env: 'N8N_SSL_CERT',
|
||||
doc: 'SSL Cert for HTTPS Protocol'
|
||||
doc: 'SSL Cert for HTTPS Protocol',
|
||||
},
|
||||
|
||||
security: {
|
||||
excludeEndpoints: {
|
||||
doc: 'Additional endpoints to exclude auth checks. Multiple endpoints can be separated by colon (":")',
|
||||
format: String,
|
||||
default: '',
|
||||
env: 'N8N_AUTH_EXCLUDE_ENDPOINTS',
|
||||
},
|
||||
basicAuth: {
|
||||
active: {
|
||||
format: 'Boolean',
|
||||
default: false,
|
||||
env: 'N8N_BASIC_AUTH_ACTIVE',
|
||||
doc: 'If basic auth should be activated for editor and REST-API'
|
||||
doc: 'If basic auth should be activated for editor and REST-API',
|
||||
},
|
||||
user: {
|
||||
format: String,
|
||||
default: '',
|
||||
env: 'N8N_BASIC_AUTH_USER',
|
||||
doc: 'The name of the basic auth user'
|
||||
doc: 'The name of the basic auth user',
|
||||
},
|
||||
password: {
|
||||
format: String,
|
||||
default: '',
|
||||
env: 'N8N_BASIC_AUTH_PASSWORD',
|
||||
doc: 'The password of the basic auth user'
|
||||
doc: 'The password of the basic auth user',
|
||||
},
|
||||
hash: {
|
||||
format: 'Boolean',
|
||||
default: false,
|
||||
env: 'N8N_BASIC_AUTH_HASH',
|
||||
doc: 'If password for basic auth is hashed',
|
||||
},
|
||||
},
|
||||
jwtAuth: {
|
||||
@@ -325,49 +337,49 @@ const config = convict({
|
||||
format: 'Boolean',
|
||||
default: false,
|
||||
env: 'N8N_JWT_AUTH_ACTIVE',
|
||||
doc: 'If JWT auth should be activated for editor and REST-API'
|
||||
doc: 'If JWT auth should be activated for editor and REST-API',
|
||||
},
|
||||
jwtHeader: {
|
||||
format: String,
|
||||
default: '',
|
||||
env: 'N8N_JWT_AUTH_HEADER',
|
||||
doc: 'The request header containing a signed JWT'
|
||||
doc: 'The request header containing a signed JWT',
|
||||
},
|
||||
jwtHeaderValuePrefix: {
|
||||
format: String,
|
||||
default: '',
|
||||
env: 'N8N_JWT_AUTH_HEADER_VALUE_PREFIX',
|
||||
doc: 'The request header value prefix to strip (optional)'
|
||||
doc: 'The request header value prefix to strip (optional)',
|
||||
},
|
||||
jwksUri: {
|
||||
format: String,
|
||||
default: '',
|
||||
env: 'N8N_JWKS_URI',
|
||||
doc: 'The URI to fetch JWK Set for JWT authentication'
|
||||
doc: 'The URI to fetch JWK Set for JWT authentication',
|
||||
},
|
||||
jwtIssuer: {
|
||||
format: String,
|
||||
default: '',
|
||||
env: 'N8N_JWT_ISSUER',
|
||||
doc: 'JWT issuer to expect (optional)'
|
||||
doc: 'JWT issuer to expect (optional)',
|
||||
},
|
||||
jwtNamespace: {
|
||||
format: String,
|
||||
default: '',
|
||||
env: 'N8N_JWT_NAMESPACE',
|
||||
doc: 'JWT namespace to expect (optional)'
|
||||
doc: 'JWT namespace to expect (optional)',
|
||||
},
|
||||
jwtAllowedTenantKey: {
|
||||
format: String,
|
||||
default: '',
|
||||
env: 'N8N_JWT_ALLOWED_TENANT_KEY',
|
||||
doc: 'JWT tenant key name to inspect within JWT namespace (optional)'
|
||||
doc: 'JWT tenant key name to inspect within JWT namespace (optional)',
|
||||
},
|
||||
jwtAllowedTenant: {
|
||||
format: String,
|
||||
default: '',
|
||||
env: 'N8N_JWT_ALLOWED_TENANT',
|
||||
doc: 'JWT tenant to allow (optional)'
|
||||
doc: 'JWT tenant to allow (optional)',
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -377,19 +389,19 @@ const config = convict({
|
||||
format: String,
|
||||
default: 'rest',
|
||||
env: 'N8N_ENDPOINT_REST',
|
||||
doc: 'Path for rest endpoint'
|
||||
doc: 'Path for rest endpoint',
|
||||
},
|
||||
webhook: {
|
||||
format: String,
|
||||
default: 'webhook',
|
||||
env: 'N8N_ENDPOINT_WEBHOOK',
|
||||
doc: 'Path for webhook endpoint'
|
||||
doc: 'Path for webhook endpoint',
|
||||
},
|
||||
webhookTest: {
|
||||
format: String,
|
||||
default: 'webhook-test',
|
||||
env: 'N8N_ENDPOINT_WEBHOOK_TEST',
|
||||
doc: 'Path for test-webhook endpoint'
|
||||
doc: 'Path for test-webhook endpoint',
|
||||
},
|
||||
},
|
||||
|
||||
@@ -397,7 +409,7 @@ const config = convict({
|
||||
doc: 'Files containing external hooks. Multiple files can be separated by colon (":")',
|
||||
format: String,
|
||||
default: '',
|
||||
env: 'EXTERNAL_HOOK_FILES'
|
||||
env: 'EXTERNAL_HOOK_FILES',
|
||||
},
|
||||
|
||||
nodes: {
|
||||
@@ -421,13 +433,13 @@ const config = convict({
|
||||
}
|
||||
},
|
||||
default: '[]',
|
||||
env: 'NODES_EXCLUDE'
|
||||
env: 'NODES_EXCLUDE',
|
||||
},
|
||||
errorTriggerType: {
|
||||
doc: 'Node Type to use as Error Trigger',
|
||||
format: String,
|
||||
default: 'n8n-nodes-base.errorTrigger',
|
||||
env: 'NODES_ERROR_TRIGGER_TYPE'
|
||||
env: 'NODES_ERROR_TRIGGER_TYPE',
|
||||
},
|
||||
},
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "n8n",
|
||||
"version": "0.80.0",
|
||||
"version": "0.89.2",
|
||||
"description": "n8n Workflow Automation Tool",
|
||||
"license": "SEE LICENSE IN LICENSE.md",
|
||||
"homepage": "https://n8n.io",
|
||||
@@ -28,6 +28,7 @@
|
||||
"start:windows": "cd bin && n8n",
|
||||
"test": "jest",
|
||||
"tslint": "tslint -p tsconfig.json -c tslint.json",
|
||||
"tslintfix": "tslint --fix -p tsconfig.json -c tslint.json",
|
||||
"watch": "tsc --watch",
|
||||
"typeorm": "ts-node ./node_modules/typeorm/cli.js"
|
||||
},
|
||||
@@ -54,33 +55,35 @@
|
||||
"devDependencies": {
|
||||
"@oclif/dev-cli": "^1.22.2",
|
||||
"@types/basic-auth": "^1.1.2",
|
||||
"@types/bcryptjs": "^2.4.1",
|
||||
"@types/compression": "1.0.1",
|
||||
"@types/connect-history-api-fallback": "^1.3.1",
|
||||
"@types/convict": "^4.2.1",
|
||||
"@types/dotenv": "^8.2.0",
|
||||
"@types/express": "^4.17.6",
|
||||
"@types/jest": "^25.2.1",
|
||||
"@types/jest": "^26.0.13",
|
||||
"@types/localtunnel": "^1.9.0",
|
||||
"@types/lodash.get": "^4.4.6",
|
||||
"@types/node": "^14.0.27",
|
||||
"@types/node": "14.0.27",
|
||||
"@types/open": "^6.1.0",
|
||||
"@types/parseurl": "^1.3.1",
|
||||
"@types/request-promise-native": "~1.0.15",
|
||||
"concurrently": "^5.1.0",
|
||||
"jest": "^24.9.0",
|
||||
"jest": "^26.4.2",
|
||||
"nodemon": "^2.0.2",
|
||||
"p-cancelable": "^2.0.0",
|
||||
"run-script-os": "^1.0.7",
|
||||
"ts-jest": "^25.4.0",
|
||||
"ts-jest": "^26.3.0",
|
||||
"ts-node": "^8.9.1",
|
||||
"tslint": "^6.1.2",
|
||||
"typescript": "~3.7.4",
|
||||
"ts-node": "^8.9.1"
|
||||
"typescript": "~3.9.7"
|
||||
},
|
||||
"dependencies": {
|
||||
"@oclif/command": "^1.5.18",
|
||||
"@oclif/errors": "^1.2.2",
|
||||
"@types/jsonwebtoken": "^8.3.4",
|
||||
"basic-auth": "^2.0.1",
|
||||
"bcryptjs": "^2.4.3",
|
||||
"body-parser": "^1.18.3",
|
||||
"body-parser-xml": "^1.1.0",
|
||||
"client-oauth2": "^4.2.5",
|
||||
@@ -95,15 +98,15 @@
|
||||
"google-timezones-json": "^1.0.2",
|
||||
"inquirer": "^7.0.1",
|
||||
"jsonwebtoken": "^8.5.1",
|
||||
"jwks-rsa": "^1.6.0",
|
||||
"jwks-rsa": "~1.9.0",
|
||||
"localtunnel": "^2.0.0",
|
||||
"lodash.get": "^4.4.2",
|
||||
"mongodb": "^3.5.5",
|
||||
"mysql2": "^2.0.1",
|
||||
"n8n-core": "~0.44.0",
|
||||
"n8n-editor-ui": "~0.55.0",
|
||||
"n8n-nodes-base": "~0.75.0",
|
||||
"n8n-workflow": "~0.39.0",
|
||||
"mysql2": "~2.1.0",
|
||||
"n8n-core": "~0.48.0",
|
||||
"n8n-editor-ui": "~0.60.0",
|
||||
"n8n-nodes-base": "~0.85.0",
|
||||
"n8n-workflow": "~0.42.0",
|
||||
"oauth-1.0a": "^2.2.6",
|
||||
"open": "^7.0.0",
|
||||
"pg": "^8.3.0",
|
||||
|
||||
@@ -7,8 +7,8 @@ import {
|
||||
} from 'n8n-core';
|
||||
|
||||
import {
|
||||
IExecutionsCurrentSummary,
|
||||
IExecutingWorkflowData,
|
||||
IExecutionsCurrentSummary,
|
||||
IWorkflowExecutionDataProcess,
|
||||
} from '.';
|
||||
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
import {
|
||||
IActivationError,
|
||||
Db,
|
||||
NodeTypes,
|
||||
IActivationError,
|
||||
IResponseCallbackData,
|
||||
IWebhookDb,
|
||||
IWorkflowDb,
|
||||
IWorkflowExecutionDataProcess,
|
||||
NodeTypes,
|
||||
ResponseHelper,
|
||||
WebhookHelpers,
|
||||
WorkflowCredentials,
|
||||
WorkflowExecuteAdditionalData,
|
||||
WorkflowHelpers,
|
||||
WorkflowRunner,
|
||||
WorkflowExecuteAdditionalData,
|
||||
IWebhookDb,
|
||||
} from './';
|
||||
|
||||
import {
|
||||
@@ -26,8 +26,8 @@ import {
|
||||
INode,
|
||||
INodeExecutionData,
|
||||
IRunExecutionData,
|
||||
NodeHelpers,
|
||||
IWorkflowExecuteAdditionalData as IWorkflowExecuteAdditionalDataWorkflow,
|
||||
NodeHelpers,
|
||||
WebhookHttpMethod,
|
||||
Workflow,
|
||||
WorkflowExecuteMode,
|
||||
@@ -181,8 +181,9 @@ export class ActiveWorkflowRunner {
|
||||
* @returns {string[]}
|
||||
* @memberof ActiveWorkflowRunner
|
||||
*/
|
||||
getActiveWorkflows(): Promise<IWorkflowDb[]> {
|
||||
return Db.collections.Workflow?.find({ where: { active: true }, select: ['id'] }) as Promise<IWorkflowDb[]>;
|
||||
async getActiveWorkflows(): Promise<IWorkflowDb[]> {
|
||||
const activeWorkflows = await Db.collections.Workflow?.find({ where: { active: true }, select: ['id'] }) as IWorkflowDb[];
|
||||
return activeWorkflows.filter(workflow => this.activationErrors[workflow.id.toString()] === undefined);
|
||||
}
|
||||
|
||||
|
||||
@@ -234,7 +235,7 @@ export class ActiveWorkflowRunner {
|
||||
path = node.parameters.path as string;
|
||||
|
||||
if (node.parameters.path === undefined) {
|
||||
path = workflow.getSimpleParameterValue(node, webhookData.webhookDescription['path']) as string | undefined;
|
||||
path = workflow.expression.getSimpleParameterValue(node, webhookData.webhookDescription['path']) as string | undefined;
|
||||
|
||||
if (path === undefined) {
|
||||
// TODO: Use a proper logger
|
||||
@@ -243,7 +244,7 @@ export class ActiveWorkflowRunner {
|
||||
}
|
||||
}
|
||||
|
||||
const isFullPath: boolean = workflow.getSimpleParameterValue(node, webhookData.webhookDescription['isFullPath'], false) as boolean;
|
||||
const isFullPath: boolean = workflow.expression.getSimpleParameterValue(node, webhookData.webhookDescription['isFullPath'], false) as boolean;
|
||||
|
||||
const webhook = {
|
||||
workflowId: webhookData.workflowId,
|
||||
@@ -257,17 +258,20 @@ export class ActiveWorkflowRunner {
|
||||
await Db.collections.Webhook?.insert(webhook);
|
||||
|
||||
const webhookExists = await workflow.runWebhookMethod('checkExists', webhookData, NodeExecuteFunctions, mode, false);
|
||||
if (webhookExists === false) {
|
||||
if (webhookExists !== true) {
|
||||
// If webhook does not exist yet create it
|
||||
await workflow.runWebhookMethod('create', webhookData, NodeExecuteFunctions, mode, false);
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
try {
|
||||
await this.removeWorkflowWebhooks(workflow.id as string);
|
||||
} catch (error) {
|
||||
console.error(`Could not remove webhooks of workflow "${workflow.id}" because of error: "${error.message}"`);
|
||||
}
|
||||
|
||||
let errorMessage = '';
|
||||
|
||||
await Db.collections.Webhook?.delete({ workflowId: workflow.id });
|
||||
|
||||
// if it's a workflow from the the insert
|
||||
// TODO check if there is standard error code for deplicate key violation that works
|
||||
// with all databases
|
||||
@@ -317,6 +321,8 @@ export class ActiveWorkflowRunner {
|
||||
await workflow.runWebhookMethod('delete', webhookData, NodeExecuteFunctions, mode, false);
|
||||
}
|
||||
|
||||
await WorkflowHelpers.saveStaticData(workflow);
|
||||
|
||||
// if it's a mongo objectId convert it to string
|
||||
if (typeof workflowData.id === 'object') {
|
||||
workflowData.id = workflowData.id.toString();
|
||||
@@ -346,8 +352,8 @@ export class ActiveWorkflowRunner {
|
||||
node,
|
||||
data: {
|
||||
main: data,
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
const executionData: IRunExecutionData = {
|
||||
@@ -411,7 +417,7 @@ export class ActiveWorkflowRunner {
|
||||
const returnFunctions = NodeExecuteFunctions.getExecuteTriggerFunctions(workflow, node, additionalData, mode);
|
||||
returnFunctions.emit = (data: INodeExecutionData[][]): void => {
|
||||
WorkflowHelpers.saveStaticData(workflow);
|
||||
this.runWorkflow(workflowData, node, data, additionalData, mode);
|
||||
this.runWorkflow(workflowData, node, data, additionalData, mode).catch((err) => console.error(err));
|
||||
};
|
||||
return returnFunctions;
|
||||
});
|
||||
@@ -495,7 +501,11 @@ export class ActiveWorkflowRunner {
|
||||
|
||||
if (this.activeWorkflows !== null) {
|
||||
// Remove all the webhooks of the workflow
|
||||
try {
|
||||
await this.removeWorkflowWebhooks(workflowId);
|
||||
} catch (error) {
|
||||
console.error(`Could not remove webhooks of workflow "${workflowId}" because of error: "${error.message}"`);
|
||||
}
|
||||
|
||||
if (this.activationErrors[workflowId] !== undefined) {
|
||||
// If there were any activation errors delete them
|
||||
|
||||
@@ -5,9 +5,14 @@ import {
|
||||
import {
|
||||
ICredentialDataDecryptedObject,
|
||||
ICredentialsHelper,
|
||||
INode,
|
||||
INodeParameters,
|
||||
INodeProperties,
|
||||
INodeType,
|
||||
INodeTypeData,
|
||||
INodeTypes,
|
||||
NodeHelpers,
|
||||
Workflow,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
import {
|
||||
@@ -18,6 +23,19 @@ import {
|
||||
} from './';
|
||||
|
||||
|
||||
const mockNodeTypes: INodeTypes = {
|
||||
nodeTypes: {},
|
||||
init: async (nodeTypes?: INodeTypeData): Promise<void> => { },
|
||||
getAll: (): INodeType[] => {
|
||||
// Does not get used in Workflow so no need to return it
|
||||
return [];
|
||||
},
|
||||
getByName: (nodeType: string): INodeType | undefined => {
|
||||
return undefined;
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
export class CredentialsHelper extends ICredentialsHelper {
|
||||
|
||||
/**
|
||||
@@ -107,7 +125,7 @@ export class CredentialsHelper extends ICredentialsHelper {
|
||||
const credentialsProperties = this.getCredentialsProperties(type);
|
||||
|
||||
// Add the default credential values
|
||||
const decryptedData = NodeHelpers.getNodeParameters(credentialsProperties, decryptedDataOriginal as INodeParameters, true, false) as ICredentialDataDecryptedObject;
|
||||
let decryptedData = NodeHelpers.getNodeParameters(credentialsProperties, decryptedDataOriginal as INodeParameters, true, false) as ICredentialDataDecryptedObject;
|
||||
|
||||
if (decryptedDataOriginal.oauthTokenData !== undefined) {
|
||||
// The OAuth data gets removed as it is not defined specifically as a parameter
|
||||
@@ -115,6 +133,18 @@ export class CredentialsHelper extends ICredentialsHelper {
|
||||
decryptedData.oauthTokenData = decryptedDataOriginal.oauthTokenData;
|
||||
}
|
||||
|
||||
const mockNode: INode = {
|
||||
name: '',
|
||||
typeVersion: 1,
|
||||
type: 'mock',
|
||||
position: [0, 0],
|
||||
parameters: decryptedData as INodeParameters,
|
||||
};
|
||||
|
||||
const workflow = new Workflow({ nodes: [mockNode], connections: {}, active: false, nodeTypes: mockNodeTypes});
|
||||
// Resolve expressions if any are set
|
||||
decryptedData = workflow.expression.getComplexParameterValue(mockNode, decryptedData as INodeParameters, undefined) as ICredentialDataDecryptedObject;
|
||||
|
||||
// Load and apply the credentials overwrites if any exist
|
||||
const credentialsOverwrites = CredentialsOverwrites();
|
||||
return credentialsOverwrites.applyOverwrite(type, decryptedData);
|
||||
|
||||
@@ -3,32 +3,53 @@ import {
|
||||
} from 'n8n-workflow';
|
||||
|
||||
import {
|
||||
ICredentialsOverwrite,
|
||||
CredentialTypes,
|
||||
GenericHelpers,
|
||||
ICredentialsOverwrite,
|
||||
} from './';
|
||||
|
||||
|
||||
class CredentialsOverwritesClass {
|
||||
|
||||
private credentialTypes = CredentialTypes();
|
||||
private overwriteData: ICredentialsOverwrite = {};
|
||||
private resolvedTypes: string[] = [];
|
||||
|
||||
|
||||
async init(overwriteData?: ICredentialsOverwrite) {
|
||||
if (overwriteData !== undefined) {
|
||||
// If data is already given it can directly be set instead of
|
||||
// loaded from environment
|
||||
this.overwriteData = overwriteData;
|
||||
this.__setData(JSON.parse(JSON.stringify(overwriteData)));
|
||||
return;
|
||||
}
|
||||
|
||||
const data = await GenericHelpers.getConfigValue('credentials.overwrite.data') as string;
|
||||
|
||||
try {
|
||||
this.overwriteData = JSON.parse(data);
|
||||
const overwriteData = JSON.parse(data);
|
||||
this.__setData(overwriteData);
|
||||
} catch (error) {
|
||||
throw new Error(`The credentials-overwrite is not valid JSON.`);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
__setData(overwriteData: ICredentialsOverwrite) {
|
||||
this.overwriteData = overwriteData;
|
||||
|
||||
for (const credentialTypeData of this.credentialTypes.getAll()) {
|
||||
const type = credentialTypeData.name;
|
||||
|
||||
const overwrites = this.__getExtended(type);
|
||||
|
||||
if (overwrites && Object.keys(overwrites).length) {
|
||||
this.overwriteData[type] = overwrites;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
applyOverwrite(type: string, data: ICredentialDataDecryptedObject) {
|
||||
|
||||
const overwrites = this.get(type);
|
||||
@@ -48,10 +69,45 @@ class CredentialsOverwritesClass {
|
||||
return returnData;
|
||||
}
|
||||
|
||||
|
||||
__getExtended(type: string): ICredentialDataDecryptedObject | undefined {
|
||||
|
||||
if (this.resolvedTypes.includes(type)) {
|
||||
// Type got already resolved and can so returned directly
|
||||
return this.overwriteData[type];
|
||||
}
|
||||
|
||||
const credentialTypeData = this.credentialTypes.getByName(type);
|
||||
|
||||
if (credentialTypeData === undefined) {
|
||||
throw new Error(`The credentials of type "${type}" are not known.`);
|
||||
}
|
||||
|
||||
if (credentialTypeData.extends === undefined) {
|
||||
this.resolvedTypes.push(type);
|
||||
return this.overwriteData[type];
|
||||
}
|
||||
|
||||
const overwrites: ICredentialDataDecryptedObject = {};
|
||||
for (const credentialsTypeName of credentialTypeData.extends) {
|
||||
Object.assign(overwrites, this.__getExtended(credentialsTypeName));
|
||||
}
|
||||
|
||||
if (this.overwriteData[type] !== undefined) {
|
||||
Object.assign(overwrites, this.overwriteData[type]);
|
||||
}
|
||||
|
||||
this.resolvedTypes.push(type);
|
||||
|
||||
return overwrites;
|
||||
}
|
||||
|
||||
|
||||
get(type: string): ICredentialDataDecryptedObject | undefined {
|
||||
return this.overwriteData[type];
|
||||
}
|
||||
|
||||
|
||||
getAll(): ICredentialsOverwrite {
|
||||
return this.overwriteData;
|
||||
}
|
||||
|
||||
@@ -33,27 +33,27 @@ export let collections: IDatabaseCollections = {
|
||||
};
|
||||
|
||||
import {
|
||||
CreateIndexStoppedAt1594828256133,
|
||||
InitialMigration1587669153312,
|
||||
WebhookModel1589476000887,
|
||||
CreateIndexStoppedAt1594828256133,
|
||||
} from './databases/postgresdb/migrations';
|
||||
|
||||
import {
|
||||
CreateIndexStoppedAt1594910478695,
|
||||
InitialMigration1587563438936,
|
||||
WebhookModel1592679094242,
|
||||
CreateIndexStoppedAt1594910478695,
|
||||
} from './databases/mongodb/migrations';
|
||||
|
||||
import {
|
||||
CreateIndexStoppedAt1594902918301,
|
||||
InitialMigration1588157391238,
|
||||
WebhookModel1592447867632,
|
||||
CreateIndexStoppedAt1594902918301,
|
||||
} from './databases/mysqldb/migrations';
|
||||
|
||||
import {
|
||||
CreateIndexStoppedAt1594825041918,
|
||||
InitialMigration1588102412422,
|
||||
WebhookModel1592445003908,
|
||||
CreateIndexStoppedAt1594825041918,
|
||||
} from './databases/sqlite/migrations';
|
||||
|
||||
import * as path from 'path';
|
||||
@@ -154,7 +154,7 @@ export async function init(): Promise<IDatabaseCollections> {
|
||||
migrations: [
|
||||
InitialMigration1588102412422,
|
||||
WebhookModel1592445003908,
|
||||
CreateIndexStoppedAt1594825041918
|
||||
CreateIndexStoppedAt1594825041918,
|
||||
],
|
||||
migrationsRun: true,
|
||||
migrationsTableName: `${entityPrefix}migrations`,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import {
|
||||
Db,
|
||||
IExternalHooksFunctions,
|
||||
IExternalHooksClass,
|
||||
IExternalHooksFunctions,
|
||||
} from './';
|
||||
|
||||
import * as config from '../config';
|
||||
@@ -64,6 +64,10 @@ class ExternalHooksClass implements IExternalHooksClass {
|
||||
}
|
||||
}
|
||||
|
||||
exists(hookName: string): boolean {
|
||||
return !!this.externalHooks[hookName];
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -288,6 +288,10 @@ export interface IN8nUISettings {
|
||||
saveManualExecutions: boolean;
|
||||
executionTimeout: number;
|
||||
maxExecutionTimeout: number;
|
||||
oauthCallbackUrls: {
|
||||
oauth1: string;
|
||||
oauth2: string;
|
||||
};
|
||||
timezone: string;
|
||||
urlBaseWebhook: string;
|
||||
versionCli: string;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import {
|
||||
INodeType,
|
||||
INodeTypes,
|
||||
INodeTypeData,
|
||||
INodeTypes,
|
||||
NodeHelpers,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
|
||||
@@ -67,7 +67,7 @@ export function sendSuccessResponse(res: Response, data: any, raw?: boolean, res
|
||||
res.json(data);
|
||||
} else {
|
||||
res.json({
|
||||
data
|
||||
data,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -183,7 +183,7 @@ export function unflattenExecutionData(fullExecutionData: IExecutionFlattedDb):
|
||||
mode: fullExecutionData.mode,
|
||||
startedAt: fullExecutionData.startedAt,
|
||||
stoppedAt: fullExecutionData.stoppedAt,
|
||||
finished: fullExecutionData.finished ? fullExecutionData.finished : false
|
||||
finished: fullExecutionData.finished ? fullExecutionData.finished : false,
|
||||
});
|
||||
|
||||
return returnData;
|
||||
|
||||
@@ -20,20 +20,24 @@ import { RequestOptions } from 'oauth-1.0a';
|
||||
import * as csrf from 'csrf';
|
||||
import * as requestPromise from 'request-promise-native';
|
||||
import { createHmac } from 'crypto';
|
||||
import { compare } from 'bcryptjs';
|
||||
|
||||
import {
|
||||
ActiveExecutions,
|
||||
ActiveWorkflowRunner,
|
||||
CredentialsHelper,
|
||||
CredentialsOverwrites,
|
||||
CredentialTypes,
|
||||
Db,
|
||||
ExternalHooks,
|
||||
GenericHelpers,
|
||||
IActivationError,
|
||||
ICustomRequest,
|
||||
ICredentialsDb,
|
||||
ICredentialsDecryptedDb,
|
||||
ICredentialsDecryptedResponse,
|
||||
ICredentialsOverwrite,
|
||||
ICredentialsResponse,
|
||||
ICustomRequest,
|
||||
IExecutionDeleteFilter,
|
||||
IExecutionFlatted,
|
||||
IExecutionFlattedDb,
|
||||
@@ -46,21 +50,18 @@ import {
|
||||
IN8nUISettings,
|
||||
IPackageVersions,
|
||||
IWorkflowBase,
|
||||
IWorkflowShortResponse,
|
||||
IWorkflowResponse,
|
||||
IWorkflowExecutionDataProcess,
|
||||
IWorkflowResponse,
|
||||
IWorkflowShortResponse,
|
||||
LoadNodesAndCredentials,
|
||||
NodeTypes,
|
||||
Push,
|
||||
ResponseHelper,
|
||||
TestWebhooks,
|
||||
WorkflowCredentials,
|
||||
WebhookHelpers,
|
||||
WorkflowCredentials,
|
||||
WorkflowExecuteAdditionalData,
|
||||
WorkflowRunner,
|
||||
GenericHelpers,
|
||||
CredentialsOverwrites,
|
||||
ICredentialsOverwrite,
|
||||
LoadNodesAndCredentials,
|
||||
} from './';
|
||||
|
||||
import {
|
||||
@@ -74,9 +75,9 @@ import {
|
||||
ICredentialType,
|
||||
IDataObject,
|
||||
INodeCredentials,
|
||||
INodeTypeDescription,
|
||||
INodeParameters,
|
||||
INodePropertyOptions,
|
||||
INodeTypeDescription,
|
||||
IRunData,
|
||||
IWorkflowCredentials,
|
||||
Workflow,
|
||||
@@ -120,7 +121,7 @@ class App {
|
||||
push: Push.Push;
|
||||
versions: IPackageVersions | undefined;
|
||||
restEndpoint: string;
|
||||
|
||||
frontendSettings: IN8nUISettings;
|
||||
protocol: string;
|
||||
sslKey: string;
|
||||
sslCert: string;
|
||||
@@ -154,6 +155,25 @@ class App {
|
||||
|
||||
this.presetCredentialsLoaded = false;
|
||||
this.endpointPresetCredentials = config.get('credentials.overwrite.endpoint') as string;
|
||||
|
||||
const urlBaseWebhook = WebhookHelpers.getWebhookBaseUrl();
|
||||
|
||||
this.frontendSettings = {
|
||||
endpointWebhook: this.endpointWebhook,
|
||||
endpointWebhookTest: this.endpointWebhookTest,
|
||||
saveDataErrorExecution: this.saveDataErrorExecution,
|
||||
saveDataSuccessExecution: this.saveDataSuccessExecution,
|
||||
saveManualExecutions: this.saveManualExecutions,
|
||||
executionTimeout: this.executionTimeout,
|
||||
maxExecutionTimeout: this.maxExecutionTimeout,
|
||||
timezone: this.timezone,
|
||||
urlBaseWebhook,
|
||||
versionCli: '',
|
||||
oauthCallbackUrls: {
|
||||
'oauth1': urlBaseWebhook + `${this.restEndpoint}/oauth1-credential/callback`,
|
||||
'oauth2': urlBaseWebhook + `${this.restEndpoint}/oauth2-credential/callback`,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -171,7 +191,16 @@ class App {
|
||||
async config(): Promise<void> {
|
||||
|
||||
this.versions = await GenericHelpers.getVersions();
|
||||
const authIgnoreRegex = new RegExp(`^\/(healthz|${this.endpointWebhook}|${this.endpointWebhookTest})\/?.*$`);
|
||||
this.frontendSettings.versionCli = this.versions.cli;
|
||||
|
||||
await this.externalHooks.run('frontend.settings', [this.frontendSettings]);
|
||||
|
||||
const excludeEndpoints = config.get('security.excludeEndpoints') as string;
|
||||
|
||||
const ignoredEndpoints = ['healthz', this.endpointWebhook, this.endpointWebhookTest, this.endpointPresetCredentials];
|
||||
ignoredEndpoints.push.apply(ignoredEndpoints, excludeEndpoints.split(':'));
|
||||
|
||||
const authIgnoreRegex = new RegExp(`^\/(${_(ignoredEndpoints).compact().join('|')})\/?.*$`);
|
||||
|
||||
// Check for basic auth credentials if activated
|
||||
const basicAuthActive = config.get('security.basicAuth.active') as boolean;
|
||||
@@ -186,7 +215,11 @@ class App {
|
||||
throw new Error('Basic auth is activated but no password got defined. Please set one!');
|
||||
}
|
||||
|
||||
this.app.use((req: express.Request, res: express.Response, next: express.NextFunction) => {
|
||||
const basicAuthHashEnabled = await GenericHelpers.getConfigValue('security.basicAuth.hash') as boolean;
|
||||
|
||||
let validPassword: null | string = null;
|
||||
|
||||
this.app.use(async (req: express.Request, res: express.Response, next: express.NextFunction) => {
|
||||
if (req.url.match(authIgnoreRegex)) {
|
||||
return next();
|
||||
}
|
||||
@@ -198,12 +231,27 @@ class App {
|
||||
return ResponseHelper.basicAuthAuthorizationError(res, realm, 'Authorization is required!');
|
||||
}
|
||||
|
||||
if (basicAuthData.name !== basicAuthUser || basicAuthData.pass !== basicAuthPassword) {
|
||||
// Provided authentication data is wrong
|
||||
return ResponseHelper.basicAuthAuthorizationError(res, realm, 'Authorization data is wrong!');
|
||||
if (basicAuthData.name === basicAuthUser) {
|
||||
if (basicAuthHashEnabled === true) {
|
||||
if (validPassword === null && await compare(basicAuthData.pass, basicAuthPassword)) {
|
||||
// Password is valid so save for future requests
|
||||
validPassword = basicAuthData.pass;
|
||||
}
|
||||
|
||||
next();
|
||||
if (validPassword === basicAuthData.pass && validPassword !== null) {
|
||||
// Provided hash is correct
|
||||
return next();
|
||||
}
|
||||
} else {
|
||||
if (basicAuthData.pass === basicAuthPassword) {
|
||||
// Provided password is correct
|
||||
return next();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Provided authentication data is wrong
|
||||
return ResponseHelper.basicAuthAuthorizationError(res, realm, 'Authorization data is wrong!');
|
||||
});
|
||||
}
|
||||
|
||||
@@ -265,7 +313,7 @@ class App {
|
||||
|
||||
const jwtVerifyOptions: jwt.VerifyOptions = {
|
||||
issuer: jwtIssuer !== '' ? jwtIssuer : undefined,
|
||||
ignoreExpiration: false
|
||||
ignoreExpiration: false,
|
||||
};
|
||||
|
||||
jwt.verify(token, getKey, jwtVerifyOptions, (err: jwt.VerifyErrors, decoded: object) => {
|
||||
@@ -307,7 +355,7 @@ class App {
|
||||
limit: '16mb', verify: (req, res, buf) => {
|
||||
// @ts-ignore
|
||||
req.rawBody = buf;
|
||||
}
|
||||
},
|
||||
}));
|
||||
|
||||
// Support application/xml type post data
|
||||
@@ -317,14 +365,14 @@ class App {
|
||||
normalize: true, // Trim whitespace inside text nodes
|
||||
normalizeTags: true, // Transform tags to lowercase
|
||||
explicitArray: false, // Only put properties in array if length > 1
|
||||
}
|
||||
},
|
||||
}));
|
||||
|
||||
this.app.use(bodyParser.text({
|
||||
limit: '16mb', verify: (req, res, buf) => {
|
||||
// @ts-ignore
|
||||
req.rawBody = buf;
|
||||
}
|
||||
},
|
||||
}));
|
||||
|
||||
// Make sure that Vue history mode works properly
|
||||
@@ -334,9 +382,9 @@ class App {
|
||||
from: new RegExp(`^\/(${this.restEndpoint}|healthz|css|js|${this.endpointWebhook}|${this.endpointWebhookTest})\/?.*$`),
|
||||
to: (context) => {
|
||||
return context.parsedUrl!.pathname!.toString();
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
},
|
||||
],
|
||||
}));
|
||||
|
||||
//support application/x-www-form-urlencoded post data
|
||||
@@ -344,7 +392,7 @@ class App {
|
||||
verify: (req, res, buf) => {
|
||||
// @ts-ignore
|
||||
req.rawBody = buf;
|
||||
}
|
||||
},
|
||||
}));
|
||||
|
||||
if (process.env['NODE_ENV'] !== 'production') {
|
||||
@@ -532,6 +580,7 @@ class App {
|
||||
newWorkflowData.updatedAt = this.getCurrentDate();
|
||||
|
||||
await Db.collections.Workflow!.update(id, newWorkflowData);
|
||||
await this.externalHooks.run('workflow.afterUpdate', [newWorkflowData]);
|
||||
|
||||
// We sadly get nothing back from "update". Neither if it updated a record
|
||||
// nor the new value. So query now the hopefully updated entry.
|
||||
@@ -580,6 +629,7 @@ class App {
|
||||
}
|
||||
|
||||
await Db.collections.Workflow!.delete(id);
|
||||
await this.externalHooks.run('workflow.afterDelete', [id]);
|
||||
|
||||
return true;
|
||||
}));
|
||||
@@ -665,13 +715,36 @@ class App {
|
||||
const allNodes = nodeTypes.getAll();
|
||||
|
||||
allNodes.forEach((nodeData) => {
|
||||
returnData.push(nodeData.description);
|
||||
// Make a copy of the object. If we don't do this, then when
|
||||
// The method below is called the properties are removed for good
|
||||
// This happens because nodes are returned as reference.
|
||||
const nodeInfo: INodeTypeDescription = {...nodeData.description};
|
||||
if (req.query.includeProperties !== 'true') {
|
||||
// @ts-ignore
|
||||
delete nodeInfo.properties;
|
||||
}
|
||||
returnData.push(nodeInfo);
|
||||
});
|
||||
|
||||
return returnData;
|
||||
}));
|
||||
|
||||
|
||||
// Returns node information baesd on namese
|
||||
this.app.post(`/${this.restEndpoint}/node-types`, ResponseHelper.send(async (req: express.Request, res: express.Response): Promise<INodeTypeDescription[]> => {
|
||||
const nodeNames = _.get(req, 'body.nodeNames', []) as string[];
|
||||
const nodeTypes = NodeTypes();
|
||||
|
||||
return nodeNames.map(name => {
|
||||
try {
|
||||
return nodeTypes.getByName(name);
|
||||
} catch (e) {
|
||||
return undefined;
|
||||
}
|
||||
}).filter(nodeData => !!nodeData).map(nodeData => nodeData!.description);
|
||||
}));
|
||||
|
||||
|
||||
|
||||
// ----------------------------------------
|
||||
// Node-Types
|
||||
@@ -1009,7 +1082,7 @@ class App {
|
||||
|
||||
const signatureMethod = _.get(oauthCredentials, 'signatureMethod') as string;
|
||||
|
||||
const oauth = new clientOAuth1({
|
||||
const oAuthOptions: clientOAuth1.Options = {
|
||||
consumer: {
|
||||
key: _.get(oauthCredentials, 'consumerKey') as string,
|
||||
secret: _.get(oauthCredentials, 'consumerSecret') as string,
|
||||
@@ -1021,16 +1094,20 @@ class App {
|
||||
.update(base)
|
||||
.digest('base64');
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const callback = `${WebhookHelpers.getWebhookBaseUrl()}${this.restEndpoint}/oauth1-credential/callback?cid=${req.query.id}`;
|
||||
const oauthRequestData = {
|
||||
oauth_callback: `${WebhookHelpers.getWebhookBaseUrl()}${this.restEndpoint}/oauth1-credential/callback?cid=${req.query.id}`,
|
||||
};
|
||||
|
||||
await this.externalHooks.run('oauth1.authenticate', [oAuthOptions, oauthRequestData]);
|
||||
|
||||
const oauth = new clientOAuth1(oAuthOptions);
|
||||
|
||||
const options: RequestOptions = {
|
||||
method: 'POST',
|
||||
url: (_.get(oauthCredentials, 'requestTokenUrl') as string),
|
||||
data: {
|
||||
oauth_callback: callback,
|
||||
},
|
||||
data: oauthRequestData,
|
||||
};
|
||||
|
||||
const data = oauth.toHeader(oauth.authorize(options as RequestOptions));
|
||||
@@ -1099,7 +1176,7 @@ class App {
|
||||
qs: {
|
||||
oauth_token,
|
||||
oauth_verifier,
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
let oauthToken;
|
||||
@@ -1169,11 +1246,11 @@ class App {
|
||||
const csrfSecret = token.secretSync();
|
||||
const state = {
|
||||
token: token.create(csrfSecret),
|
||||
cid: req.query.id
|
||||
cid: req.query.id,
|
||||
};
|
||||
const stateEncodedStr = Buffer.from(JSON.stringify(state)).toString('base64') as string;
|
||||
|
||||
const oAuthObj = new clientOAuth2({
|
||||
const oAuthOptions: clientOAuth2.Options = {
|
||||
clientId: _.get(oauthCredentials, 'clientId') as string,
|
||||
clientSecret: _.get(oauthCredentials, 'clientSecret', '') as string,
|
||||
accessTokenUri: _.get(oauthCredentials, 'accessTokenUrl', '') as string,
|
||||
@@ -1181,7 +1258,11 @@ class App {
|
||||
redirectUri: `${WebhookHelpers.getWebhookBaseUrl()}${this.restEndpoint}/oauth2-credential/callback`,
|
||||
scopes: _.split(_.get(oauthCredentials, 'scope', 'openid,') as string, ','),
|
||||
state: stateEncodedStr,
|
||||
});
|
||||
};
|
||||
|
||||
await this.externalHooks.run('oauth2.authenticate', [oAuthOptions]);
|
||||
|
||||
const oAuthObj = new clientOAuth2(oAuthOptions);
|
||||
|
||||
// Encrypt the data
|
||||
const credentials = new Credentials(result.name, result.type, result.nodesAccess);
|
||||
@@ -1267,11 +1348,11 @@ class App {
|
||||
|
||||
const oAuth2Parameters = {
|
||||
clientId: _.get(oauthCredentials, 'clientId') as string,
|
||||
clientSecret: _.get(oauthCredentials, 'clientSecret', '') as string,
|
||||
clientSecret: _.get(oauthCredentials, 'clientSecret', '') as string | undefined,
|
||||
accessTokenUri: _.get(oauthCredentials, 'accessTokenUrl', '') as string,
|
||||
authorizationUri: _.get(oauthCredentials, 'authUrl', '') as string,
|
||||
redirectUri: `${WebhookHelpers.getWebhookBaseUrl()}${this.restEndpoint}/oauth2-credential/callback`,
|
||||
scopes: _.split(_.get(oauthCredentials, 'scope', 'openid,') as string, ',')
|
||||
scopes: _.split(_.get(oauthCredentials, 'scope', 'openid,') as string, ','),
|
||||
};
|
||||
|
||||
if (_.get(oauthCredentials, 'authentication', 'header') as string === 'body') {
|
||||
@@ -1283,13 +1364,14 @@ class App {
|
||||
};
|
||||
delete oAuth2Parameters.clientSecret;
|
||||
}
|
||||
const redirectUri = `${WebhookHelpers.getWebhookBaseUrl()}${this.restEndpoint}/oauth2-credential/callback`;
|
||||
|
||||
await this.externalHooks.run('oauth2.callback', [oAuth2Parameters]);
|
||||
|
||||
const oAuthObj = new clientOAuth2(oAuth2Parameters);
|
||||
|
||||
const queryParameters = req.originalUrl.split('?').splice(1, 1).join('');
|
||||
|
||||
const oauthToken = await oAuthObj.code.getToken(`${redirectUri}?${queryParameters}`, options);
|
||||
const oauthToken = await oAuthObj.code.getToken(`${oAuth2Parameters.redirectUri}?${queryParameters}`, options);
|
||||
|
||||
if (oauthToken === undefined) {
|
||||
const errorResponse = new ResponseHelper.ResponseError('Unable to get access tokens!', undefined, 404);
|
||||
@@ -1582,18 +1664,7 @@ class App {
|
||||
|
||||
// Returns the settings which are needed in the UI
|
||||
this.app.get(`/${this.restEndpoint}/settings`, ResponseHelper.send(async (req: express.Request, res: express.Response): Promise<IN8nUISettings> => {
|
||||
return {
|
||||
endpointWebhook: this.endpointWebhook,
|
||||
endpointWebhookTest: this.endpointWebhookTest,
|
||||
saveDataErrorExecution: this.saveDataErrorExecution,
|
||||
saveDataSuccessExecution: this.saveDataSuccessExecution,
|
||||
saveManualExecutions: this.saveManualExecutions,
|
||||
executionTimeout: this.executionTimeout,
|
||||
maxExecutionTimeout: this.maxExecutionTimeout,
|
||||
timezone: this.timezone,
|
||||
urlBaseWebhook: WebhookHelpers.getWebhookBaseUrl(),
|
||||
versionCli: this.versions!.cli,
|
||||
};
|
||||
return this.frontendSettings;
|
||||
}));
|
||||
|
||||
|
||||
@@ -1829,7 +1900,7 @@ class App {
|
||||
// got used
|
||||
res.setHeader('Last-Modified', startTime);
|
||||
}
|
||||
}
|
||||
},
|
||||
}));
|
||||
}
|
||||
|
||||
@@ -1860,5 +1931,7 @@ export async function start(): Promise<void> {
|
||||
const versions = await GenericHelpers.getVersions();
|
||||
console.log(`n8n ready on ${ADDRESS}, port ${PORT}`);
|
||||
console.log(`Version: ${versions.cli}`);
|
||||
|
||||
await app.externalHooks.run('n8n.ready', [app]);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -3,11 +3,9 @@ import * as express from 'express';
|
||||
import {
|
||||
IResponseCallbackData,
|
||||
IWorkflowDb,
|
||||
NodeTypes,
|
||||
Push,
|
||||
ResponseHelper,
|
||||
WebhookHelpers,
|
||||
WorkflowHelpers,
|
||||
} from './';
|
||||
|
||||
import {
|
||||
@@ -31,6 +29,7 @@ export class TestWebhooks {
|
||||
sessionId?: string;
|
||||
timeout: NodeJS.Timeout,
|
||||
workflowData: IWorkflowDb;
|
||||
workflow: Workflow;
|
||||
};
|
||||
} = {};
|
||||
private activeWebhooks: ActiveWebhooks | null = null;
|
||||
@@ -64,10 +63,13 @@ export class TestWebhooks {
|
||||
|
||||
const webhookKey = this.activeWebhooks!.getWebhookKey(webhookData.httpMethod, webhookData.path);
|
||||
|
||||
const workflowData = this.testWebhookData[webhookKey].workflowData;
|
||||
// TODO: Clean that duplication up one day and improve code generally
|
||||
if (this.testWebhookData[webhookKey] === undefined) {
|
||||
// The requested webhook is not registered
|
||||
throw new ResponseHelper.ResponseError(`The requested webhook "${httpMethod} ${path}" is not registered.`, 404, 404);
|
||||
}
|
||||
|
||||
const nodeTypes = NodeTypes();
|
||||
const workflow = new Workflow({ id: webhookData.workflowId, name: workflowData.name, nodes: workflowData.nodes, connections: workflowData.connections, active: workflowData.active, nodeTypes, staticData: workflowData.staticData, settings: workflowData.settings});
|
||||
const workflow = this.testWebhookData[webhookKey].workflow;
|
||||
|
||||
// Get the node which has the webhook defined to know where to start from and to
|
||||
// get additional data
|
||||
@@ -154,19 +156,26 @@ export class TestWebhooks {
|
||||
}, 120000);
|
||||
|
||||
let key: string;
|
||||
const activatedKey: string[] = [];
|
||||
for (const webhookData of webhooks) {
|
||||
key = this.activeWebhooks!.getWebhookKey(webhookData.httpMethod, webhookData.path);
|
||||
|
||||
await this.activeWebhooks!.add(workflow, webhookData, mode);
|
||||
activatedKey.push(key);
|
||||
|
||||
this.testWebhookData[key] = {
|
||||
sessionId,
|
||||
timeout,
|
||||
workflow,
|
||||
workflowData,
|
||||
};
|
||||
|
||||
// Save static data!
|
||||
this.testWebhookData[key].workflowData.staticData = workflow.staticData;
|
||||
try {
|
||||
await this.activeWebhooks!.add(workflow, webhookData, mode);
|
||||
} catch (error) {
|
||||
activatedKey.forEach(deleteKey => delete this.testWebhookData[deleteKey] );
|
||||
await this.activeWebhooks!.removeWorkflow(workflow);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
@@ -181,8 +190,6 @@ export class TestWebhooks {
|
||||
* @memberof TestWebhooks
|
||||
*/
|
||||
cancelTestWebhook(workflowId: string): boolean {
|
||||
const nodeTypes = NodeTypes();
|
||||
|
||||
let foundWebhook = false;
|
||||
for (const webhookKey of Object.keys(this.testWebhookData)) {
|
||||
const webhookData = this.testWebhookData[webhookKey];
|
||||
@@ -191,8 +198,6 @@ export class TestWebhooks {
|
||||
continue;
|
||||
}
|
||||
|
||||
foundWebhook = true;
|
||||
|
||||
clearTimeout(this.testWebhookData[webhookKey].timeout);
|
||||
|
||||
// Inform editor-ui that webhook got received
|
||||
@@ -205,14 +210,19 @@ export class TestWebhooks {
|
||||
}
|
||||
}
|
||||
|
||||
const workflowData = webhookData.workflowData;
|
||||
const workflow = new Workflow({ id: workflowData.id.toString(), name: workflowData.name, nodes: workflowData.nodes, connections: workflowData.connections, active: workflowData.active, nodeTypes, staticData: workflowData.staticData, settings: workflowData.settings });
|
||||
const workflow = this.testWebhookData[webhookKey].workflow;
|
||||
|
||||
// Remove the webhook
|
||||
delete this.testWebhookData[webhookKey];
|
||||
|
||||
if (foundWebhook === false) {
|
||||
// As it removes all webhooks of the workflow execute only once
|
||||
this.activeWebhooks!.removeWorkflow(workflow);
|
||||
}
|
||||
|
||||
foundWebhook = true;
|
||||
}
|
||||
|
||||
return foundWebhook;
|
||||
}
|
||||
|
||||
@@ -225,14 +235,10 @@ export class TestWebhooks {
|
||||
return;
|
||||
}
|
||||
|
||||
const nodeTypes = NodeTypes();
|
||||
|
||||
let workflowData: IWorkflowDb;
|
||||
let workflow: Workflow;
|
||||
const workflows: Workflow[] = [];
|
||||
for (const webhookKey of Object.keys(this.testWebhookData)) {
|
||||
workflowData = this.testWebhookData[webhookKey].workflowData;
|
||||
workflow = new Workflow({ id: workflowData.id.toString(), name: workflowData.name, nodes: workflowData.nodes, connections: workflowData.connections, active: workflowData.active, nodeTypes, staticData: workflowData.staticData, settings: workflowData.settings });
|
||||
workflow = this.testWebhookData[webhookKey].workflow;
|
||||
workflows.push(workflow);
|
||||
}
|
||||
|
||||
|
||||
@@ -3,16 +3,17 @@ import { get } from 'lodash';
|
||||
|
||||
import {
|
||||
ActiveExecutions,
|
||||
ExternalHooks,
|
||||
GenericHelpers,
|
||||
IExecutionDb,
|
||||
IResponseCallbackData,
|
||||
IWorkflowDb,
|
||||
IWorkflowExecutionDataProcess,
|
||||
ResponseHelper,
|
||||
WorkflowHelpers,
|
||||
WorkflowRunner,
|
||||
WorkflowCredentials,
|
||||
WorkflowExecuteAdditionalData,
|
||||
WorkflowHelpers,
|
||||
WorkflowRunner,
|
||||
} from './';
|
||||
|
||||
import {
|
||||
@@ -114,8 +115,8 @@ export function getWorkflowWebhooksBasic(workflow: Workflow): IWebhookData[] {
|
||||
}
|
||||
|
||||
// Get the responseMode
|
||||
const responseMode = workflow.getSimpleParameterValue(workflowStartNode, webhookData.webhookDescription['responseMode'], 'onReceived');
|
||||
const responseCode = workflow.getSimpleParameterValue(workflowStartNode, webhookData.webhookDescription['responseCode'], 200) as number;
|
||||
const responseMode = workflow.expression.getSimpleParameterValue(workflowStartNode, webhookData.webhookDescription['responseMode'], 'onReceived');
|
||||
const responseCode = workflow.expression.getSimpleParameterValue(workflowStartNode, webhookData.webhookDescription['responseCode'], 200) as number;
|
||||
|
||||
if (!['onReceived', 'lastNode'].includes(responseMode as string)) {
|
||||
// If the mode is not known we error. Is probably best like that instead of using
|
||||
@@ -173,7 +174,7 @@ export function getWorkflowWebhooksBasic(workflow: Workflow): IWebhookData[] {
|
||||
await WorkflowHelpers.saveStaticData(workflow);
|
||||
|
||||
if (webhookData.webhookDescription['responseHeaders'] !== undefined) {
|
||||
const responseHeaders = workflow.getComplexParameterValue(workflowStartNode, webhookData.webhookDescription['responseHeaders'], undefined) as {
|
||||
const responseHeaders = workflow.expression.getComplexParameterValue(workflowStartNode, webhookData.webhookDescription['responseHeaders'], undefined) as {
|
||||
entries?: Array<{
|
||||
name: string;
|
||||
value: string;
|
||||
@@ -251,7 +252,7 @@ export function getWorkflowWebhooksBasic(workflow: Workflow): IWebhookData[] {
|
||||
data: {
|
||||
main: webhookResultData.workflowData,
|
||||
},
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
const runExecutionData: IRunExecutionData = {
|
||||
@@ -325,7 +326,7 @@ export function getWorkflowWebhooksBasic(workflow: Workflow): IWebhookData[] {
|
||||
return data;
|
||||
}
|
||||
|
||||
const responseData = workflow.getSimpleParameterValue(workflowStartNode, webhookData.webhookDescription['responseData'], 'firstEntryJson');
|
||||
const responseData = workflow.expression.getSimpleParameterValue(workflowStartNode, webhookData.webhookDescription['responseData'], 'firstEntryJson');
|
||||
|
||||
if (didSendResponse === false) {
|
||||
let data: IDataObject | IDataObject[];
|
||||
@@ -340,13 +341,13 @@ export function getWorkflowWebhooksBasic(workflow: Workflow): IWebhookData[] {
|
||||
|
||||
data = returnData.data!.main[0]![0].json;
|
||||
|
||||
const responsePropertyName = workflow.getSimpleParameterValue(workflowStartNode, webhookData.webhookDescription['responsePropertyName'], undefined);
|
||||
const responsePropertyName = workflow.expression.getSimpleParameterValue(workflowStartNode, webhookData.webhookDescription['responsePropertyName'], undefined);
|
||||
|
||||
if (responsePropertyName !== undefined) {
|
||||
data = get(data, responsePropertyName as string) as IDataObject;
|
||||
}
|
||||
|
||||
const responseContentType = workflow.getSimpleParameterValue(workflowStartNode, webhookData.webhookDescription['responseContentType'], undefined);
|
||||
const responseContentType = workflow.expression.getSimpleParameterValue(workflowStartNode, webhookData.webhookDescription['responseContentType'], undefined);
|
||||
|
||||
if (responseContentType !== undefined) {
|
||||
// Send the webhook response manually to be able to set the content-type
|
||||
@@ -379,7 +380,7 @@ export function getWorkflowWebhooksBasic(workflow: Workflow): IWebhookData[] {
|
||||
didSendResponse = true;
|
||||
}
|
||||
|
||||
const responseBinaryPropertyName = workflow.getSimpleParameterValue(workflowStartNode, webhookData.webhookDescription['responseBinaryPropertyName'], 'data');
|
||||
const responseBinaryPropertyName = workflow.expression.getSimpleParameterValue(workflowStartNode, webhookData.webhookDescription['responseBinaryPropertyName'], 'data');
|
||||
|
||||
if (responseBinaryPropertyName === undefined && didSendResponse === false) {
|
||||
responseCallback(new Error('No "responseBinaryPropertyName" is set.'), {});
|
||||
|
||||
@@ -25,8 +25,8 @@ import {
|
||||
IExecuteData,
|
||||
IExecuteWorkflowInfo,
|
||||
INode,
|
||||
INodeParameters,
|
||||
INodeExecutionData,
|
||||
INodeParameters,
|
||||
IRun,
|
||||
IRunExecutionData,
|
||||
ITaskData,
|
||||
@@ -74,7 +74,7 @@ function executeErrorWorkflow(workflowData: IWorkflowBase, fullRunData: IRun, mo
|
||||
workflow: {
|
||||
id: workflowData.id !== undefined ? workflowData.id.toString() as string : undefined,
|
||||
name: workflowData.name,
|
||||
}
|
||||
},
|
||||
};
|
||||
// Run the error workflow
|
||||
WorkflowHelpers.executeErrorWorkflow(workflowData.settings.errorWorkflow as string, workflowErrorData);
|
||||
@@ -191,13 +191,13 @@ function hookFunctionsPush(): IWorkflowExecuteHooks {
|
||||
workflowId: this.workflowData.id as string,
|
||||
workflowName: this.workflowData.name,
|
||||
});
|
||||
}
|
||||
},
|
||||
],
|
||||
workflowExecuteAfter: [
|
||||
async function (this: WorkflowHooks, fullRunData: IRun, newStaticData: IDataObject): Promise<void> {
|
||||
pushExecutionFinished(this.mode, fullRunData, this.executionId, undefined, this.retryOf);
|
||||
},
|
||||
]
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
@@ -298,7 +298,7 @@ function hookFunctionsSave(parentProcessMode?: string): IWorkflowExecuteHooks {
|
||||
}
|
||||
}
|
||||
},
|
||||
]
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
@@ -374,8 +374,8 @@ export async function executeWorkflow(workflowInfo: IExecuteWorkflowInfo, additi
|
||||
// Always start with empty data if no inputData got supplied
|
||||
inputData = inputData || [
|
||||
{
|
||||
json: {}
|
||||
}
|
||||
json: {},
|
||||
},
|
||||
];
|
||||
|
||||
// Initialize the incoming data
|
||||
@@ -386,7 +386,7 @@ export async function executeWorkflow(workflowInfo: IExecuteWorkflowInfo, additi
|
||||
data: {
|
||||
main: [inputData],
|
||||
},
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
const runExecutionData: IRunExecutionData = {
|
||||
@@ -406,6 +406,8 @@ export async function executeWorkflow(workflowInfo: IExecuteWorkflowInfo, additi
|
||||
const workflowExecute = new WorkflowExecute(additionalDataIntegrated, mode, runExecutionData);
|
||||
const data = await workflowExecute.processRunExecutionData(workflow);
|
||||
|
||||
await externalHooks.run('workflow.postExecute', [data, workflowData]);
|
||||
|
||||
if (data.finished === true) {
|
||||
// Workflow did finish successfully
|
||||
const returnData = WorkflowHelpers.getDataLastExecutedNodeData(data);
|
||||
|
||||
@@ -3,8 +3,8 @@ import {
|
||||
Db,
|
||||
ICredentialsTypeData,
|
||||
ITransferNodeTypes,
|
||||
IWorkflowExecutionDataProcess,
|
||||
IWorkflowErrorData,
|
||||
IWorkflowExecutionDataProcess,
|
||||
NodeTypes,
|
||||
WorkflowCredentials,
|
||||
WorkflowRunner,
|
||||
@@ -120,12 +120,12 @@ export async function executeErrorWorkflow(workflowId: string, workflowErrorData
|
||||
main: [
|
||||
[
|
||||
{
|
||||
json: workflowErrorData
|
||||
}
|
||||
]
|
||||
json: workflowErrorData,
|
||||
},
|
||||
],
|
||||
],
|
||||
},
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
const runExecutionData: IRunExecutionData = {
|
||||
|
||||
@@ -24,8 +24,8 @@ import {
|
||||
IExecutionError,
|
||||
IRun,
|
||||
Workflow,
|
||||
WorkflowHooks,
|
||||
WorkflowExecuteMode,
|
||||
WorkflowHooks,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
import * as config from '../config';
|
||||
@@ -104,11 +104,25 @@ export class WorkflowRunner {
|
||||
await externalHooks.run('workflow.execute', [data.workflowData, data.executionMode]);
|
||||
|
||||
const executionsProcess = config.get('executions.process') as string;
|
||||
|
||||
let executionId: string;
|
||||
if (executionsProcess === 'main') {
|
||||
return this.runMainProcess(data, loadStaticData);
|
||||
executionId = await this.runMainProcess(data, loadStaticData);
|
||||
} else {
|
||||
executionId = await this.runSubprocess(data, loadStaticData);
|
||||
}
|
||||
|
||||
return this.runSubprocess(data, loadStaticData);
|
||||
if (externalHooks.exists('workflow.postExecute')) {
|
||||
this.activeExecutions.getPostExecutePromise(executionId)
|
||||
.then(async (executionData) => {
|
||||
await externalHooks.run('workflow.postExecute', [executionData, data.workflowData]);
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('There was a problem running hook "workflow.postExecute"', error);
|
||||
});
|
||||
}
|
||||
|
||||
return executionId;
|
||||
}
|
||||
|
||||
|
||||
@@ -212,6 +226,7 @@ export class WorkflowRunner {
|
||||
|
||||
let nodeTypeData: ITransferNodeTypes;
|
||||
let credentialTypeData: ICredentialsTypeData;
|
||||
let credentialsOverwrites = this.credentialsOverwrites;
|
||||
|
||||
if (loadAllNodeTypes === true) {
|
||||
// Supply all nodeTypes and credentialTypes
|
||||
@@ -219,15 +234,22 @@ export class WorkflowRunner {
|
||||
const credentialTypes = CredentialTypes();
|
||||
credentialTypeData = credentialTypes.credentialTypes;
|
||||
} else {
|
||||
// Supply only nodeTypes and credentialTypes which the workflow needs
|
||||
// Supply only nodeTypes, credentialTypes and overwrites that the workflow needs
|
||||
nodeTypeData = WorkflowHelpers.getNodeTypeData(data.workflowData.nodes);
|
||||
credentialTypeData = WorkflowHelpers.getCredentialsData(data.credentials);
|
||||
|
||||
credentialsOverwrites = {};
|
||||
for (const credentialName of Object.keys(credentialTypeData)) {
|
||||
if (this.credentialsOverwrites[credentialName] !== undefined) {
|
||||
credentialsOverwrites[credentialName] = this.credentialsOverwrites[credentialName];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
(data as unknown as IWorkflowExecutionDataProcessWithExecution).executionId = executionId;
|
||||
(data as unknown as IWorkflowExecutionDataProcessWithExecution).nodeTypeData = nodeTypeData;
|
||||
(data as unknown as IWorkflowExecutionDataProcessWithExecution).credentialsOverwrite = this.credentialsOverwrites;
|
||||
(data as unknown as IWorkflowExecutionDataProcessWithExecution).credentialsOverwrite = credentialsOverwrites;
|
||||
(data as unknown as IWorkflowExecutionDataProcessWithExecution).credentialsTypeData = credentialTypeData; // TODO: Still needs correct value
|
||||
|
||||
const workflowHooks = WorkflowExecuteAdditionalData.getWorkflowHooksMain(data, executionId);
|
||||
|
||||
@@ -66,7 +66,7 @@ export class WorkflowRunnerProcess {
|
||||
|
||||
// Load the credentials overwrites if any exist
|
||||
const credentialsOverwrites = CredentialsOverwrites();
|
||||
await credentialsOverwrites.init();
|
||||
await credentialsOverwrites.init(inputData.credentialsOverwrite);
|
||||
|
||||
this.workflow = new Workflow({ id: this.data.workflowData.id as string | undefined, name: this.data.workflowData.name, nodes: this.data.workflowData!.nodes, connections: this.data.workflowData!.connections, active: this.data.workflowData!.active, nodeTypes, staticData: this.data.workflowData!.staticData, settings: this.data.workflowData!.settings});
|
||||
const additionalData = await WorkflowExecuteAdditionalData.getBase(this.data.credentials);
|
||||
@@ -135,13 +135,13 @@ export class WorkflowRunnerProcess {
|
||||
workflowExecuteBefore: [
|
||||
async (): Promise<void> => {
|
||||
this.sendHookToParentProcess('workflowExecuteBefore', []);
|
||||
}
|
||||
},
|
||||
],
|
||||
workflowExecuteAfter: [
|
||||
async (fullRunData: IRun, newStaticData?: IDataObject): Promise<void> => {
|
||||
this.sendHookToParentProcess('workflowExecuteAfter', [fullRunData, newStaticData]);
|
||||
},
|
||||
]
|
||||
],
|
||||
};
|
||||
|
||||
return new WorkflowHooks(hookFunctions, this.data!.executionMode, this.data!.executionId, this.data!.workflowData, { sessionId: this.data!.sessionId, retryOf: this.data!.retryOf as string });
|
||||
|
||||
@@ -20,7 +20,7 @@ export class CredentialsEntity implements ICredentialsDb {
|
||||
id: number;
|
||||
|
||||
@Column({
|
||||
length: 128
|
||||
length: 128,
|
||||
})
|
||||
name: string;
|
||||
|
||||
@@ -29,7 +29,7 @@ export class CredentialsEntity implements ICredentialsDb {
|
||||
|
||||
@Index()
|
||||
@Column({
|
||||
length: 32
|
||||
length: 32,
|
||||
})
|
||||
type: string;
|
||||
|
||||
|
||||
@@ -22,7 +22,7 @@ export class WorkflowEntity implements IWorkflowDb {
|
||||
id: number;
|
||||
|
||||
@Column({
|
||||
length: 128
|
||||
length: 128,
|
||||
})
|
||||
name: string;
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@ export class CredentialsEntity implements ICredentialsDb {
|
||||
id: number;
|
||||
|
||||
@Column({
|
||||
length: 128
|
||||
length: 128,
|
||||
})
|
||||
name: string;
|
||||
|
||||
@@ -29,7 +29,7 @@ export class CredentialsEntity implements ICredentialsDb {
|
||||
|
||||
@Index()
|
||||
@Column({
|
||||
length: 32
|
||||
length: 32,
|
||||
})
|
||||
type: string;
|
||||
|
||||
|
||||
@@ -22,7 +22,7 @@ export class WorkflowEntity implements IWorkflowDb {
|
||||
id: number;
|
||||
|
||||
@Column({
|
||||
length: 128
|
||||
length: 128,
|
||||
})
|
||||
name: string;
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@ export class CredentialsEntity implements ICredentialsDb {
|
||||
id: number;
|
||||
|
||||
@Column({
|
||||
length: 128
|
||||
length: 128,
|
||||
})
|
||||
name: string;
|
||||
|
||||
@@ -29,7 +29,7 @@ export class CredentialsEntity implements ICredentialsDb {
|
||||
|
||||
@Index()
|
||||
@Column({
|
||||
length: 32
|
||||
length: 32,
|
||||
})
|
||||
type: string;
|
||||
|
||||
|
||||
@@ -22,7 +22,7 @@ export class WorkflowEntity implements IWorkflowDb {
|
||||
id: number;
|
||||
|
||||
@Column({
|
||||
length: 128
|
||||
length: 128,
|
||||
})
|
||||
name: string;
|
||||
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
<html>
|
||||
<script>
|
||||
<script>
|
||||
(function messageParent() {
|
||||
window.opener.postMessage('success', '*');
|
||||
}());
|
||||
</script>
|
||||
</script>
|
||||
|
||||
Got connected. The window can be closed now.
|
||||
Got connected. The window can be closed now.
|
||||
</html>
|
||||
|
||||
@@ -46,6 +46,11 @@
|
||||
"forin": true,
|
||||
"jsdoc-format": true,
|
||||
"label-position": true,
|
||||
"indent": [
|
||||
true,
|
||||
"tabs",
|
||||
2
|
||||
],
|
||||
"member-access": [
|
||||
true,
|
||||
"no-public"
|
||||
@@ -60,6 +65,13 @@
|
||||
"no-default-export": true,
|
||||
"no-duplicate-variable": true,
|
||||
"no-inferrable-types": true,
|
||||
"ordered-imports": [
|
||||
true,
|
||||
{
|
||||
"import-sources-order": "any",
|
||||
"named-imports-order": "case-insensitive"
|
||||
}
|
||||
],
|
||||
"no-namespace": [
|
||||
true,
|
||||
"allow-declarations"
|
||||
@@ -82,6 +94,18 @@
|
||||
"ignore-bound-class-methods"
|
||||
],
|
||||
"switch-default": true,
|
||||
"trailing-comma": [
|
||||
true,
|
||||
{
|
||||
"multiline": {
|
||||
"objects": "always",
|
||||
"arrays": "always",
|
||||
"functions": "never",
|
||||
"typeLiterals": "ignore"
|
||||
},
|
||||
"esSpecCompliant": true
|
||||
}
|
||||
],
|
||||
"triple-equals": [
|
||||
true,
|
||||
"allow-null-check"
|
||||
|
||||
@@ -19,7 +19,7 @@ Condition notice.
|
||||
|
||||
Software: n8n
|
||||
|
||||
License: Apache 2.0
|
||||
License: Apache 2.0 with Commons Clause
|
||||
|
||||
Licensor: n8n GmbH
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "n8n-core",
|
||||
"version": "0.44.0",
|
||||
"version": "0.48.1",
|
||||
"description": "Core functionality of n8n",
|
||||
"license": "SEE LICENSE IN LICENSE.md",
|
||||
"homepage": "https://n8n.io",
|
||||
@@ -18,6 +18,7 @@
|
||||
"build": "tsc",
|
||||
"dev": "npm run watch",
|
||||
"tslint": "tslint -p tsconfig.json -c tslint.json",
|
||||
"tslintfix": "tslint --fix -p tsconfig.json -c tslint.json",
|
||||
"watch": "tsc --watch",
|
||||
"test": "jest"
|
||||
},
|
||||
@@ -26,27 +27,28 @@
|
||||
],
|
||||
"devDependencies": {
|
||||
"@types/cron": "^1.7.1",
|
||||
"@types/crypto-js": "^3.1.43",
|
||||
"@types/crypto-js": "^4.0.1",
|
||||
"@types/express": "^4.17.6",
|
||||
"@types/jest": "^25.2.1",
|
||||
"@types/jest": "^26.0.13",
|
||||
"@types/lodash.get": "^4.4.6",
|
||||
"@types/mime-types": "^2.1.0",
|
||||
"@types/node": "^14.0.27",
|
||||
"@types/node": "14.0.27",
|
||||
"@types/request-promise-native": "~1.0.15",
|
||||
"jest": "^24.9.0",
|
||||
"jest": "^26.4.2",
|
||||
"source-map-support": "^0.5.9",
|
||||
"ts-jest": "^25.4.0",
|
||||
"ts-jest": "^26.3.0",
|
||||
"tslint": "^6.1.2",
|
||||
"typescript": "~3.7.4"
|
||||
"typescript": "~3.9.7"
|
||||
},
|
||||
"dependencies": {
|
||||
"client-oauth2": "^4.2.5",
|
||||
"cron": "^1.7.2",
|
||||
"crypto-js": "3.1.9-1",
|
||||
"crypto-js": "4.0.0",
|
||||
"file-type": "^14.6.2",
|
||||
"lodash.get": "^4.4.2",
|
||||
"mime-types": "^2.1.27",
|
||||
"n8n-workflow": "~0.39.0",
|
||||
"n8n-workflow": "~0.42.0",
|
||||
"oauth-1.0a": "^2.2.6",
|
||||
"p-cancelable": "^2.0.0",
|
||||
"request": "^2.88.2",
|
||||
"request-promise-native": "^1.0.7"
|
||||
|
||||
@@ -52,7 +52,7 @@ export class ActiveWebhooks {
|
||||
|
||||
try {
|
||||
const webhookExists = await workflow.runWebhookMethod('checkExists', webhookData, NodeExecuteFunctions, mode, this.testWebhooks);
|
||||
if (webhookExists === false) {
|
||||
if (webhookExists !== true) {
|
||||
// If webhook does not exist yet create it
|
||||
await workflow.runWebhookMethod('create', webhookData, NodeExecuteFunctions, mode, this.testWebhooks);
|
||||
|
||||
@@ -60,7 +60,6 @@ export class ActiveWebhooks {
|
||||
} catch (error) {
|
||||
// If there was a problem unregister the webhook again
|
||||
delete this.webhookUrls[webhookKey];
|
||||
delete this.workflowWebhooks[webhookData.workflowId];
|
||||
|
||||
throw error;
|
||||
}
|
||||
@@ -159,7 +158,7 @@ export class ActiveWebhooks {
|
||||
|
||||
|
||||
/**
|
||||
* Removes all the webhooks of the given workflow
|
||||
* Removes all the webhooks of the given workflows
|
||||
*/
|
||||
async removeAll(workflows: Workflow[]): Promise<void> {
|
||||
const removePromises = [];
|
||||
|
||||
@@ -67,8 +67,6 @@ export class ActiveWorkflows {
|
||||
* @memberof ActiveWorkflows
|
||||
*/
|
||||
async add(id: string, workflow: Workflow, additionalData: IWorkflowExecuteAdditionalData, getTriggerFunctions: IGetExecuteTriggerFunctions, getPollFunctions: IGetExecutePollFunctions): Promise<void> {
|
||||
console.log('ADD ID (active): ' + id);
|
||||
|
||||
this.workflowData[id] = {};
|
||||
const triggerNodes = workflow.getTriggerNodes();
|
||||
|
||||
@@ -204,8 +202,6 @@ export class ActiveWorkflows {
|
||||
* @memberof ActiveWorkflows
|
||||
*/
|
||||
async remove(id: string): Promise<void> {
|
||||
console.log('REMOVE ID (active): ' + id);
|
||||
|
||||
if (!this.isActive(id)) {
|
||||
// Workflow is currently not registered
|
||||
throw new Error(`The workflow with the id "${id}" is currently not active and can so not be removed`);
|
||||
|
||||
@@ -5,7 +5,7 @@ import {
|
||||
ICredentialsEncrypted,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
import { enc, AES } from 'crypto-js';
|
||||
import { AES, enc } from 'crypto-js';
|
||||
|
||||
|
||||
export class Credentials extends ICredentials {
|
||||
|
||||
@@ -9,13 +9,13 @@ import {
|
||||
ILoadOptionsFunctions as ILoadOptionsFunctionsBase,
|
||||
INodeExecutionData,
|
||||
INodeType,
|
||||
IOAuth2Options,
|
||||
IPollFunctions as IPollFunctionsBase,
|
||||
IPollResponse,
|
||||
ITriggerFunctions as ITriggerFunctionsBase,
|
||||
ITriggerResponse,
|
||||
IWebhookFunctions as IWebhookFunctionsBase,
|
||||
IWorkflowSettings as IWorkflowSettingsWorkflow,
|
||||
IOAuth2Options,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
|
||||
|
||||
@@ -36,7 +36,7 @@ export class LoadNodeParameterOptions {
|
||||
position: [
|
||||
0,
|
||||
0,
|
||||
]
|
||||
],
|
||||
};
|
||||
|
||||
if (credentials) {
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import {
|
||||
BINARY_ENCODING,
|
||||
IHookFunctions,
|
||||
ILoadOptionsFunctions,
|
||||
IResponseError,
|
||||
IWorkflowSettings,
|
||||
BINARY_ENCODING,
|
||||
} from './';
|
||||
|
||||
import {
|
||||
@@ -19,6 +19,7 @@ import {
|
||||
INodeExecutionData,
|
||||
INodeParameters,
|
||||
INodeType,
|
||||
IOAuth2Options,
|
||||
IPollFunctions,
|
||||
IRunExecutionData,
|
||||
ITaskDataConnections,
|
||||
@@ -34,7 +35,6 @@ import {
|
||||
Workflow,
|
||||
WorkflowDataProxy,
|
||||
WorkflowExecuteMode,
|
||||
IOAuth2Options,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
import * as clientOAuth1 from 'oauth-1.0a';
|
||||
@@ -43,7 +43,7 @@ import * as clientOAuth2 from 'client-oauth2';
|
||||
import { get } from 'lodash';
|
||||
import * as express from 'express';
|
||||
import * as path from 'path';
|
||||
import { OptionsWithUrl, OptionsWithUri } from 'request';
|
||||
import { OptionsWithUri, OptionsWithUrl } from 'request';
|
||||
import * as requestPromise from 'request-promise-native';
|
||||
import { createHmac } from 'crypto';
|
||||
import { fromBuffer } from 'file-type';
|
||||
@@ -91,7 +91,7 @@ export async function prepareBinaryData(binaryData: Buffer, filePath?: string, m
|
||||
// TODO: Should program it in a way that it does not have to converted to base64
|
||||
// It should only convert to and from base64 when saved in database because
|
||||
// of for example an error or when there is a wait node.
|
||||
data: binaryData.toString(BINARY_ENCODING)
|
||||
data: binaryData.toString(BINARY_ENCODING),
|
||||
};
|
||||
|
||||
if (filePath) {
|
||||
@@ -152,11 +152,16 @@ export function requestOAuth2(this: IAllExecuteFunctions, credentialsType: strin
|
||||
// on the token-type used.
|
||||
const newRequestOptions = token.sign(requestOptions as clientOAuth2.RequestObject);
|
||||
|
||||
// If keep bearer is false remove the it from the authorization header
|
||||
if (oAuth2Options?.keepBearer === false) {
|
||||
//@ts-ignore
|
||||
newRequestOptions?.headers?.Authorization = newRequestOptions?.headers?.Authorization.split(' ')[1];
|
||||
}
|
||||
|
||||
return this.helpers.request!(newRequestOptions)
|
||||
.catch(async (error: IResponseError) => {
|
||||
// TODO: Check if also other codes are possible
|
||||
if (error.statusCode === 401) {
|
||||
// TODO: Whole refresh process is not tested yet
|
||||
// Token is probably not valid anymore. So try refresh it.
|
||||
|
||||
const tokenRefreshOptions: IDataObject = {};
|
||||
@@ -388,7 +393,7 @@ export function getNodeParameter(workflow: Workflow, runExecutionData: IRunExecu
|
||||
|
||||
let returnData;
|
||||
try {
|
||||
returnData = workflow.getParameterValue(value, runExecutionData, runIndex, itemIndex, node.name, connectionInputData);
|
||||
returnData = workflow.expression.getParameterValue(value, runExecutionData, runIndex, itemIndex, node.name, connectionInputData);
|
||||
} catch (e) {
|
||||
e.message += ` [Error in parameter: "${parameterName}"]`;
|
||||
throw e;
|
||||
@@ -434,12 +439,12 @@ export function getNodeWebhookUrl(name: string, workflow: Workflow, node: INode,
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const path = workflow.getSimpleParameterValue(node, webhookDescription['path']);
|
||||
const path = workflow.expression.getSimpleParameterValue(node, webhookDescription['path']);
|
||||
if (path === undefined) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const isFullPath: boolean = workflow.getSimpleParameterValue(node, webhookDescription['isFullPath'], false) as boolean;
|
||||
const isFullPath: boolean = workflow.expression.getSimpleParameterValue(node, webhookDescription['isFullPath'], false) as boolean;
|
||||
return NodeHelpers.getNodeWebhookUrl(baseUrl, workflow.id!, node, path.toString(), isFullPath);
|
||||
}
|
||||
|
||||
@@ -654,7 +659,7 @@ export function getExecuteFunctions(workflow: Workflow, runExecutionData: IRunEx
|
||||
return continueOnFail(node);
|
||||
},
|
||||
evaluateExpression: (expression: string, itemIndex: number) => {
|
||||
return workflow.resolveSimpleParameterValue('=' + expression, runExecutionData, runIndex, itemIndex, node.name, connectionInputData);
|
||||
return workflow.expression.resolveSimpleParameterValue('=' + expression, runExecutionData, runIndex, itemIndex, node.name, connectionInputData);
|
||||
},
|
||||
async executeWorkflow(workflowInfo: IExecuteWorkflowInfo, inputData?: INodeExecutionData[]): Promise<any> { // tslint:disable-line:no-any
|
||||
return additionalData.executeWorkflow(workflowInfo, additionalData, inputData);
|
||||
@@ -752,7 +757,7 @@ export function getExecuteSingleFunctions(workflow: Workflow, runExecutionData:
|
||||
},
|
||||
evaluateExpression: (expression: string, evaluateItemIndex: number | undefined) => {
|
||||
evaluateItemIndex = evaluateItemIndex === undefined ? itemIndex : evaluateItemIndex;
|
||||
return workflow.resolveSimpleParameterValue('=' + expression, runExecutionData, runIndex, evaluateItemIndex, node.name, connectionInputData);
|
||||
return workflow.expression.resolveSimpleParameterValue('=' + expression, runExecutionData, runIndex, evaluateItemIndex, node.name, connectionInputData);
|
||||
},
|
||||
getContext(type: string): IContextObject {
|
||||
return NodeHelpers.getContext(runExecutionData, type, node);
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import {
|
||||
ENCRYPTION_KEY_ENV_OVERWRITE,
|
||||
EXTENSIONS_SUBDIRECTORY,
|
||||
IUserSettings,
|
||||
USER_FOLDER_ENV_OVERWRITE,
|
||||
USER_SETTINGS_FILE_NAME,
|
||||
USER_SETTINGS_SUBFOLDER,
|
||||
IUserSettings,
|
||||
} from '.';
|
||||
|
||||
|
||||
|
||||
@@ -15,8 +15,8 @@ import {
|
||||
ITaskDataConnections,
|
||||
IWaitingForExecution,
|
||||
IWorkflowExecuteAdditionalData,
|
||||
WorkflowExecuteMode,
|
||||
Workflow,
|
||||
WorkflowExecuteMode,
|
||||
} from 'n8n-workflow';
|
||||
import {
|
||||
NodeExecuteFunctions,
|
||||
@@ -84,7 +84,7 @@ export class WorkflowExecute {
|
||||
],
|
||||
],
|
||||
},
|
||||
}
|
||||
},
|
||||
];
|
||||
|
||||
this.runExecutionData = {
|
||||
@@ -137,8 +137,8 @@ export class WorkflowExecute {
|
||||
// If it has no incoming data add the default empty data
|
||||
incomingData.push([
|
||||
{
|
||||
json: {}
|
||||
}
|
||||
json: {},
|
||||
},
|
||||
]);
|
||||
} else {
|
||||
// Get the data of the incoming connections
|
||||
@@ -156,7 +156,7 @@ export class WorkflowExecute {
|
||||
node: workflow.getNode(startNode) as INode,
|
||||
data: {
|
||||
main: incomingData,
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
nodeExecutionStack.push(executeData);
|
||||
@@ -252,7 +252,7 @@ export class WorkflowExecute {
|
||||
if (this.runExecutionData.executionData!.waitingExecution[connectionData.node][runIndex] === undefined) {
|
||||
// Node does not have data for runIndex yet so create also empty one and init it
|
||||
this.runExecutionData.executionData!.waitingExecution[connectionData.node][runIndex] = {
|
||||
main: []
|
||||
main: [],
|
||||
};
|
||||
for (let i = 0; i < workflow.connectionsByDestinationNode[connectionData.node]['main'].length; i++) {
|
||||
this.runExecutionData.executionData!.waitingExecution[connectionData.node][runIndex].main.push(null);
|
||||
@@ -282,7 +282,7 @@ export class WorkflowExecute {
|
||||
// So add it to the execution stack
|
||||
this.runExecutionData.executionData!.nodeExecutionStack.push({
|
||||
node: workflow.nodes[connectionData.node],
|
||||
data: this.runExecutionData.executionData!.waitingExecution[connectionData.node][runIndex]
|
||||
data: this.runExecutionData.executionData!.waitingExecution[connectionData.node][runIndex],
|
||||
});
|
||||
|
||||
// Remove the data from waiting
|
||||
@@ -426,15 +426,15 @@ export class WorkflowExecute {
|
||||
this.runExecutionData.executionData!.waitingExecution[connectionData.node] = {};
|
||||
}
|
||||
this.runExecutionData.executionData!.waitingExecution[connectionData.node][runIndex] = {
|
||||
main: connectionDataArray
|
||||
main: connectionDataArray,
|
||||
};
|
||||
} else {
|
||||
// All data is there so add it directly to stack
|
||||
this.runExecutionData.executionData!.nodeExecutionStack.push({
|
||||
node: workflow.nodes[connectionData.node],
|
||||
data: {
|
||||
main: connectionDataArray
|
||||
}
|
||||
main: connectionDataArray,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -608,7 +608,7 @@ export class WorkflowExecute {
|
||||
nodeSuccessData[0] = [
|
||||
{
|
||||
json: {},
|
||||
}
|
||||
},
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -622,6 +622,8 @@ export class WorkflowExecute {
|
||||
|
||||
break;
|
||||
} catch (error) {
|
||||
this.runExecutionData.resultData.lastNodeExecuted = executionData.node.name;
|
||||
|
||||
executionError = {
|
||||
message: error.message,
|
||||
stack: error.stack,
|
||||
@@ -637,7 +639,7 @@ export class WorkflowExecute {
|
||||
}
|
||||
taskData = {
|
||||
startTime,
|
||||
executionTime: (new Date().getTime()) - startTime
|
||||
executionTime: (new Date().getTime()) - startTime,
|
||||
};
|
||||
|
||||
if (executionError !== undefined) {
|
||||
@@ -667,7 +669,7 @@ export class WorkflowExecute {
|
||||
|
||||
// Node executed successfully. So add data and go on.
|
||||
taskData.data = ({
|
||||
'main': nodeSuccessData
|
||||
'main': nodeSuccessData,
|
||||
} as ITaskDataConnections);
|
||||
|
||||
this.executeHook('nodeExecuteAfter', [executionNode.name, taskData]);
|
||||
@@ -698,12 +700,15 @@ export class WorkflowExecute {
|
||||
return Promise.reject(new Error(`The node "${executionNode.name}" connects to not found node "${connectionData.node}"`));
|
||||
}
|
||||
|
||||
if (nodeSuccessData![outputIndex] && nodeSuccessData![outputIndex].length !== 0) {
|
||||
// Add the node only if there is data for it to process
|
||||
this.addNodeToBeExecuted(workflow, connectionData, parseInt(outputIndex, 10), executionNode.name, nodeSuccessData!, runIndex);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return Promise.resolve();
|
||||
})()
|
||||
|
||||
@@ -51,7 +51,7 @@ describe('Credentials', () => {
|
||||
nodeType: 'base.noOp',
|
||||
user: 'userName',
|
||||
date: new Date(),
|
||||
}
|
||||
},
|
||||
];
|
||||
|
||||
const credentials = new Credentials('testName', 'testType', nodeAccess);
|
||||
|
||||
@@ -7,8 +7,8 @@ import {
|
||||
INodeExecutionData,
|
||||
INodeParameters,
|
||||
INodeType,
|
||||
INodeTypes,
|
||||
INodeTypeData,
|
||||
INodeTypes,
|
||||
IRun,
|
||||
ITaskData,
|
||||
IWorkflowBase,
|
||||
@@ -87,7 +87,7 @@ class NodeTypesClass implements INodeTypes {
|
||||
displayOptions: {
|
||||
show: {
|
||||
mode: [
|
||||
'passThrough'
|
||||
'passThrough',
|
||||
],
|
||||
},
|
||||
},
|
||||
@@ -104,7 +104,7 @@ class NodeTypesClass implements INodeTypes {
|
||||
default: 'input1',
|
||||
description: 'Defines of which input the data should be used as output of node.',
|
||||
},
|
||||
]
|
||||
],
|
||||
},
|
||||
async execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]> {
|
||||
// const itemsInput2 = this.getInputData(1);
|
||||
@@ -131,7 +131,7 @@ class NodeTypesClass implements INodeTypes {
|
||||
}
|
||||
|
||||
return [returnData];
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
'n8n-nodes-base.set': {
|
||||
@@ -186,11 +186,11 @@ class NodeTypesClass implements INodeTypes {
|
||||
default: 0,
|
||||
description: 'The number value to write in the property.',
|
||||
},
|
||||
]
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
]
|
||||
],
|
||||
},
|
||||
execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]> {
|
||||
const items = this.getInputData();
|
||||
@@ -213,7 +213,7 @@ class NodeTypesClass implements INodeTypes {
|
||||
}
|
||||
|
||||
return this.prepareOutputData(returnData);
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
'n8n-nodes-base.start': {
|
||||
@@ -231,7 +231,7 @@ class NodeTypesClass implements INodeTypes {
|
||||
},
|
||||
inputs: [],
|
||||
outputs: ['main'],
|
||||
properties: []
|
||||
properties: [],
|
||||
},
|
||||
execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]> {
|
||||
const items = this.getInputData();
|
||||
|
||||
@@ -47,8 +47,8 @@ describe('WorkflowExecute', () => {
|
||||
"typeVersion": 1,
|
||||
"position": [
|
||||
100,
|
||||
300
|
||||
]
|
||||
300,
|
||||
],
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
@@ -56,19 +56,19 @@ describe('WorkflowExecute', () => {
|
||||
"number": [
|
||||
{
|
||||
"name": "value1",
|
||||
"value": 1
|
||||
}
|
||||
]
|
||||
}
|
||||
"value": 1,
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
"name": "Set",
|
||||
"type": "n8n-nodes-base.set",
|
||||
"typeVersion": 1,
|
||||
"position": [
|
||||
280,
|
||||
300
|
||||
]
|
||||
}
|
||||
300,
|
||||
],
|
||||
},
|
||||
],
|
||||
"connections": {
|
||||
"Start": {
|
||||
@@ -77,12 +77,12 @@ describe('WorkflowExecute', () => {
|
||||
{
|
||||
"node": "Set",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
}
|
||||
"index": 0,
|
||||
},
|
||||
],
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
output: {
|
||||
@@ -115,8 +115,8 @@ describe('WorkflowExecute', () => {
|
||||
"typeVersion": 1,
|
||||
"position": [
|
||||
100,
|
||||
300
|
||||
]
|
||||
300,
|
||||
],
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
@@ -124,18 +124,18 @@ describe('WorkflowExecute', () => {
|
||||
"number": [
|
||||
{
|
||||
"name": "value1",
|
||||
"value": 1
|
||||
}
|
||||
]
|
||||
}
|
||||
"value": 1,
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
"name": "Set1",
|
||||
"type": "n8n-nodes-base.set",
|
||||
"typeVersion": 1,
|
||||
"position": [
|
||||
300,
|
||||
250
|
||||
]
|
||||
250,
|
||||
],
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
@@ -143,19 +143,19 @@ describe('WorkflowExecute', () => {
|
||||
"number": [
|
||||
{
|
||||
"name": "value2",
|
||||
"value": 2
|
||||
}
|
||||
]
|
||||
}
|
||||
"value": 2,
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
"name": "Set2",
|
||||
"type": "n8n-nodes-base.set",
|
||||
"typeVersion": 1,
|
||||
"position": [
|
||||
500,
|
||||
400
|
||||
]
|
||||
}
|
||||
400,
|
||||
],
|
||||
},
|
||||
],
|
||||
"connections": {
|
||||
"Start": {
|
||||
@@ -164,15 +164,15 @@ describe('WorkflowExecute', () => {
|
||||
{
|
||||
"node": "Set1",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
"index": 0,
|
||||
},
|
||||
{
|
||||
"node": "Set2",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
"index": 0,
|
||||
},
|
||||
],
|
||||
],
|
||||
},
|
||||
"Set1": {
|
||||
"main": [
|
||||
@@ -180,12 +180,12 @@ describe('WorkflowExecute', () => {
|
||||
{
|
||||
"node": "Set2",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
}
|
||||
"index": 0,
|
||||
},
|
||||
],
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
output: {
|
||||
@@ -201,7 +201,7 @@ describe('WorkflowExecute', () => {
|
||||
{
|
||||
value1: 1,
|
||||
},
|
||||
]
|
||||
],
|
||||
],
|
||||
Set2: [
|
||||
[
|
||||
@@ -228,15 +228,15 @@ describe('WorkflowExecute', () => {
|
||||
"nodes": [
|
||||
{
|
||||
"parameters": {
|
||||
"mode": "passThrough"
|
||||
"mode": "passThrough",
|
||||
},
|
||||
"name": "Merge4",
|
||||
"type": "n8n-nodes-base.merge",
|
||||
"typeVersion": 1,
|
||||
"position": [
|
||||
1150,
|
||||
500
|
||||
]
|
||||
500,
|
||||
],
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
@@ -244,18 +244,18 @@ describe('WorkflowExecute', () => {
|
||||
"number": [
|
||||
{
|
||||
"name": "value2",
|
||||
"value": 2
|
||||
}
|
||||
]
|
||||
}
|
||||
"value": 2,
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
"name": "Set2",
|
||||
"type": "n8n-nodes-base.set",
|
||||
"typeVersion": 1,
|
||||
"position": [
|
||||
290,
|
||||
400
|
||||
]
|
||||
400,
|
||||
],
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
@@ -263,18 +263,18 @@ describe('WorkflowExecute', () => {
|
||||
"number": [
|
||||
{
|
||||
"name": "value4",
|
||||
"value": 4
|
||||
}
|
||||
]
|
||||
}
|
||||
"value": 4,
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
"name": "Set4",
|
||||
"type": "n8n-nodes-base.set",
|
||||
"typeVersion": 1,
|
||||
"position": [
|
||||
850,
|
||||
200
|
||||
]
|
||||
200,
|
||||
],
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
@@ -282,30 +282,30 @@ describe('WorkflowExecute', () => {
|
||||
"number": [
|
||||
{
|
||||
"name": "value3",
|
||||
"value": 3
|
||||
}
|
||||
]
|
||||
}
|
||||
"value": 3,
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
"name": "Set3",
|
||||
"type": "n8n-nodes-base.set",
|
||||
"typeVersion": 1,
|
||||
"position": [
|
||||
650,
|
||||
200
|
||||
]
|
||||
200,
|
||||
],
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"mode": "passThrough"
|
||||
"mode": "passThrough",
|
||||
},
|
||||
"name": "Merge4",
|
||||
"type": "n8n-nodes-base.merge",
|
||||
"typeVersion": 1,
|
||||
"position": [
|
||||
1150,
|
||||
500
|
||||
]
|
||||
500,
|
||||
],
|
||||
},
|
||||
{
|
||||
"parameters": {},
|
||||
@@ -314,21 +314,21 @@ describe('WorkflowExecute', () => {
|
||||
"typeVersion": 1,
|
||||
"position": [
|
||||
1000,
|
||||
400
|
||||
]
|
||||
400,
|
||||
],
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"mode": "passThrough",
|
||||
"output": "input2"
|
||||
"output": "input2",
|
||||
},
|
||||
"name": "Merge2",
|
||||
"type": "n8n-nodes-base.merge",
|
||||
"typeVersion": 1,
|
||||
"position": [
|
||||
700,
|
||||
400
|
||||
]
|
||||
400,
|
||||
],
|
||||
},
|
||||
{
|
||||
"parameters": {},
|
||||
@@ -337,8 +337,8 @@ describe('WorkflowExecute', () => {
|
||||
"typeVersion": 1,
|
||||
"position": [
|
||||
500,
|
||||
300
|
||||
]
|
||||
300,
|
||||
],
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
@@ -346,18 +346,18 @@ describe('WorkflowExecute', () => {
|
||||
"number": [
|
||||
{
|
||||
"name": "value1",
|
||||
"value": 1
|
||||
}
|
||||
]
|
||||
}
|
||||
"value": 1,
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
"name": "Set1",
|
||||
"type": "n8n-nodes-base.set",
|
||||
"typeVersion": 1,
|
||||
"position": [
|
||||
300,
|
||||
200
|
||||
]
|
||||
200,
|
||||
],
|
||||
},
|
||||
{
|
||||
"parameters": {},
|
||||
@@ -366,9 +366,9 @@ describe('WorkflowExecute', () => {
|
||||
"typeVersion": 1,
|
||||
"position": [
|
||||
100,
|
||||
300
|
||||
]
|
||||
}
|
||||
300,
|
||||
],
|
||||
},
|
||||
],
|
||||
"connections": {
|
||||
"Set2": {
|
||||
@@ -377,15 +377,15 @@ describe('WorkflowExecute', () => {
|
||||
{
|
||||
"node": "Merge1",
|
||||
"type": "main",
|
||||
"index": 1
|
||||
"index": 1,
|
||||
},
|
||||
{
|
||||
"node": "Merge2",
|
||||
"type": "main",
|
||||
"index": 1
|
||||
}
|
||||
]
|
||||
]
|
||||
"index": 1,
|
||||
},
|
||||
],
|
||||
],
|
||||
},
|
||||
"Set4": {
|
||||
"main": [
|
||||
@@ -393,10 +393,10 @@ describe('WorkflowExecute', () => {
|
||||
{
|
||||
"node": "Merge3",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
"index": 0,
|
||||
},
|
||||
],
|
||||
],
|
||||
},
|
||||
"Set3": {
|
||||
"main": [
|
||||
@@ -404,10 +404,10 @@ describe('WorkflowExecute', () => {
|
||||
{
|
||||
"node": "Set4",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
"index": 0,
|
||||
},
|
||||
],
|
||||
],
|
||||
},
|
||||
"Merge3": {
|
||||
"main": [
|
||||
@@ -415,10 +415,10 @@ describe('WorkflowExecute', () => {
|
||||
{
|
||||
"node": "Merge4",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
"index": 0,
|
||||
},
|
||||
],
|
||||
],
|
||||
},
|
||||
"Merge2": {
|
||||
"main": [
|
||||
@@ -426,10 +426,10 @@ describe('WorkflowExecute', () => {
|
||||
{
|
||||
"node": "Merge3",
|
||||
"type": "main",
|
||||
"index": 1
|
||||
}
|
||||
]
|
||||
]
|
||||
"index": 1,
|
||||
},
|
||||
],
|
||||
],
|
||||
},
|
||||
"Merge1": {
|
||||
"main": [
|
||||
@@ -437,10 +437,10 @@ describe('WorkflowExecute', () => {
|
||||
{
|
||||
"node": "Merge2",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
"index": 0,
|
||||
},
|
||||
],
|
||||
],
|
||||
},
|
||||
"Set1": {
|
||||
"main": [
|
||||
@@ -448,15 +448,15 @@ describe('WorkflowExecute', () => {
|
||||
{
|
||||
"node": "Merge1",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
"index": 0,
|
||||
},
|
||||
{
|
||||
"node": "Set3",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
"index": 0,
|
||||
},
|
||||
],
|
||||
],
|
||||
},
|
||||
"Start": {
|
||||
"main": [
|
||||
@@ -464,22 +464,22 @@ describe('WorkflowExecute', () => {
|
||||
{
|
||||
"node": "Set1",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
"index": 0,
|
||||
},
|
||||
{
|
||||
"node": "Set2",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
"index": 0,
|
||||
},
|
||||
{
|
||||
"node": "Merge4",
|
||||
"type": "main",
|
||||
"index": 1
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
}
|
||||
"index": 1,
|
||||
},
|
||||
],
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
output: {
|
||||
@@ -534,14 +534,14 @@ describe('WorkflowExecute', () => {
|
||||
{
|
||||
value2: 2,
|
||||
},
|
||||
]
|
||||
],
|
||||
],
|
||||
Merge2: [
|
||||
[
|
||||
{
|
||||
value2: 2,
|
||||
},
|
||||
]
|
||||
],
|
||||
],
|
||||
Merge3: [
|
||||
[
|
||||
@@ -553,7 +553,7 @@ describe('WorkflowExecute', () => {
|
||||
{
|
||||
value2: 2,
|
||||
},
|
||||
]
|
||||
],
|
||||
],
|
||||
Merge4: [
|
||||
[
|
||||
@@ -565,7 +565,7 @@ describe('WorkflowExecute', () => {
|
||||
{
|
||||
value2: 2,
|
||||
},
|
||||
]
|
||||
],
|
||||
],
|
||||
},
|
||||
},
|
||||
|
||||
@@ -46,6 +46,11 @@
|
||||
"forin": true,
|
||||
"jsdoc-format": true,
|
||||
"label-position": true,
|
||||
"indent": [
|
||||
true,
|
||||
"tabs",
|
||||
2
|
||||
],
|
||||
"member-access": [
|
||||
true,
|
||||
"no-public"
|
||||
@@ -60,6 +65,13 @@
|
||||
"no-default-export": true,
|
||||
"no-duplicate-variable": true,
|
||||
"no-inferrable-types": true,
|
||||
"ordered-imports": [
|
||||
true,
|
||||
{
|
||||
"import-sources-order": "any",
|
||||
"named-imports-order": "case-insensitive"
|
||||
}
|
||||
],
|
||||
"no-namespace": [
|
||||
true,
|
||||
"allow-declarations"
|
||||
@@ -82,6 +94,18 @@
|
||||
"ignore-bound-class-methods"
|
||||
],
|
||||
"switch-default": true,
|
||||
"trailing-comma": [
|
||||
true,
|
||||
{
|
||||
"multiline": {
|
||||
"objects": "always",
|
||||
"arrays": "always",
|
||||
"functions": "always",
|
||||
"typeLiterals": "ignore"
|
||||
},
|
||||
"esSpecCompliant": true
|
||||
}
|
||||
],
|
||||
"triple-equals": [
|
||||
true,
|
||||
"allow-null-check"
|
||||
|
||||
@@ -19,7 +19,7 @@ Condition notice.
|
||||
|
||||
Software: n8n
|
||||
|
||||
License: Apache 2.0
|
||||
License: Apache 2.0 with Commons Clause
|
||||
|
||||
Licensor: n8n GmbH
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "n8n-editor-ui",
|
||||
"version": "0.55.0",
|
||||
"version": "0.60.0",
|
||||
"description": "Workflow Editor UI for n8n",
|
||||
"license": "SEE LICENSE IN LICENSE.md",
|
||||
"homepage": "https://n8n.io",
|
||||
@@ -20,12 +20,11 @@
|
||||
"serve": "cross-env VUE_APP_URL_BASE_API=http://localhost:5678/ vue-cli-service serve",
|
||||
"test": "npm run test:unit",
|
||||
"tslint": "tslint -p tsconfig.json -c tslint.json",
|
||||
"tslintfix": "tslint --fix -p tsconfig.json -c tslint.json",
|
||||
"test:e2e": "vue-cli-service test:e2e",
|
||||
"test:unit": "vue-cli-service test:unit"
|
||||
},
|
||||
"dependencies": {
|
||||
"uuid": "^8.1.0"
|
||||
},
|
||||
"dependencies": {},
|
||||
"devDependencies": {
|
||||
"@beyonk/google-fonts-webpack-plugin": "^1.2.3",
|
||||
"@fortawesome/fontawesome-svg-core": "^1.2.19",
|
||||
@@ -34,16 +33,16 @@
|
||||
"@types/dateformat": "^3.0.0",
|
||||
"@types/express": "^4.17.6",
|
||||
"@types/file-saver": "^2.0.1",
|
||||
"@types/jest": "^25.2.1",
|
||||
"@types/jest": "^26.0.13",
|
||||
"@types/lodash.get": "^4.4.6",
|
||||
"@types/lodash.set": "^4.3.6",
|
||||
"@types/node": "^14.0.27",
|
||||
"@types/node": "14.0.27",
|
||||
"@types/quill": "^2.0.1",
|
||||
"@typescript-eslint/eslint-plugin": "^2.13.0",
|
||||
"@typescript-eslint/parser": "^2.13.0",
|
||||
"@vue/cli-plugin-babel": "^4.1.2",
|
||||
"@vue/cli-plugin-eslint": "^4.1.2",
|
||||
"@vue/cli-plugin-typescript": "~4.1.2",
|
||||
"@vue/cli-plugin-typescript": "~4.5.6",
|
||||
"@vue/cli-plugin-unit-jest": "^4.1.2",
|
||||
"@vue/cli-service": "^3.11.0",
|
||||
"@vue/eslint-config-standard": "^5.0.1",
|
||||
@@ -66,16 +65,18 @@
|
||||
"lodash.debounce": "^4.0.8",
|
||||
"lodash.get": "^4.4.2",
|
||||
"lodash.set": "^4.3.2",
|
||||
"n8n-workflow": "~0.39.0",
|
||||
"n8n-workflow": "~0.42.0",
|
||||
"node-sass": "^4.12.0",
|
||||
"normalize-wheel": "^1.0.1",
|
||||
"prismjs": "^1.17.1",
|
||||
"quill": "^2.0.0-dev.3",
|
||||
"quill-autoformat": "^0.1.1",
|
||||
"sass-loader": "^8.0.0",
|
||||
"string-template-parser": "^1.2.6",
|
||||
"ts-jest": "^25.4.0",
|
||||
"ts-jest": "^26.3.0",
|
||||
"tslint": "^6.1.2",
|
||||
"typescript": "~3.7.4",
|
||||
"typescript": "~3.9.7",
|
||||
"uuid": "^8.1.0",
|
||||
"vue": "^2.6.9",
|
||||
"vue-cli-plugin-webpack-bundle-analyzer": "^2.0.0",
|
||||
"vue-json-tree": "^0.4.1",
|
||||
@@ -83,6 +84,7 @@
|
||||
"vue-router": "^3.0.6",
|
||||
"vue-template-compiler": "^2.5.17",
|
||||
"vue-typed-mixins": "^0.2.0",
|
||||
"vue2-touch-events": "^2.3.2",
|
||||
"vuex": "^3.1.1"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -126,6 +126,7 @@ export interface IRestApi {
|
||||
makeRestApiRequest(method: string, endpoint: string, data?: any): Promise<any>; // tslint:disable-line:no-any
|
||||
getSettings(): Promise<IN8nUISettings>;
|
||||
getNodeTypes(): Promise<INodeTypeDescription[]>;
|
||||
getNodesInformation(nodeList: string[]): Promise<INodeTypeDescription[]>;
|
||||
getNodeParameterOptions(nodeType: string, methodName: string, currentNodeParameters: INodeParameters, credentials?: INodeCredentials): Promise<INodePropertyOptions[]>;
|
||||
removeTestWebhook(workflowId: string): Promise<boolean>;
|
||||
runWorkflow(runData: IStartRunData): Promise<IExecutionPushResponse>;
|
||||
@@ -399,6 +400,10 @@ export interface IN8nUISettings {
|
||||
timezone: string;
|
||||
executionTimeout: number;
|
||||
maxExecutionTimeout: number;
|
||||
oauthCallbackUrls: {
|
||||
oauth1: string;
|
||||
oauth2: string;
|
||||
};
|
||||
urlBaseWebhook: string;
|
||||
versionCli: string;
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<div name="title" class="title-container" slot="title">
|
||||
<div class="title-left">{{title}}</div>
|
||||
<div class="title-right">
|
||||
<div v-if="credentialType" class="docs-container">
|
||||
<div v-if="credentialType && documentationUrl" class="docs-container">
|
||||
<svg class="help-logo" target="_blank" width="18px" height="18px" viewBox="0 0 18 18" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<title>Node Documentation</title>
|
||||
<g stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||
@@ -20,7 +20,7 @@
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
<span v-if="credentialType" class="doc-link-text">Need help? <a class="doc-hyperlink" :href="'https://docs.n8n.io/credentials/' + documentationUrl + '/?utm_source=n8n_app&utm_medium=left_nav_menu&utm_campaign=create_new_credentials_modal'" target="_blank">Open credential docs</a></span>
|
||||
<span class="doc-link-text">Need help? <a class="doc-hyperlink" :href="'https://docs.n8n.io/credentials/' + documentationUrl + '/?utm_source=n8n_app&utm_medium=left_nav_menu&utm_campaign=create_new_credentials_modal'" target="_blank">Open credential docs</a></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -109,27 +109,19 @@ export default mixins(
|
||||
}
|
||||
}
|
||||
},
|
||||
documentationUrl (): string {
|
||||
documentationUrl (): string | undefined {
|
||||
let credentialTypeName = '';
|
||||
if (this.editCredentials) {
|
||||
const credentialType = this.$store.getters.credentialType(this.editCredentials.type);
|
||||
if (credentialType.documentationUrl === undefined) {
|
||||
return credentialType.name;
|
||||
credentialTypeName = this.editCredentials.type as string;
|
||||
} else {
|
||||
return `${credentialType.documentationUrl}`;
|
||||
credentialTypeName = this.credentialType as string;
|
||||
}
|
||||
} else {
|
||||
if (this.credentialType) {
|
||||
const credentialType = this.$store.getters.credentialType(this.credentialType);
|
||||
|
||||
if (credentialType.documentationUrl === undefined) {
|
||||
return credentialType.name;
|
||||
} else {
|
||||
const credentialType = this.$store.getters.credentialType(credentialTypeName);
|
||||
if (credentialType.documentationUrl !== undefined) {
|
||||
return `${credentialType.documentationUrl}`;
|
||||
}
|
||||
} else {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
},
|
||||
node (): INodeUi {
|
||||
return this.$store.getters.activeNode;
|
||||
|
||||
@@ -235,7 +235,7 @@ export default mixins(
|
||||
oAuthCallbackUrl (): string {
|
||||
const types = this.parentTypes(this.credentialTypeData.name);
|
||||
const oauthType = (this.credentialTypeData.name === 'oAuth2Api' || types.includes('oAuth2Api')) ? 'oauth2' : 'oauth1';
|
||||
return this.$store.getters.getWebhookBaseUrl + `rest/${oauthType}-credential/callback`;
|
||||
return this.$store.getters.oauthCallbackUrls[oauthType];
|
||||
},
|
||||
requiredPropertiesFilled (): boolean {
|
||||
for (const property of this.credentialProperties) {
|
||||
@@ -404,10 +404,11 @@ export default mixins(
|
||||
message: 'Connected successfully!',
|
||||
type: 'success',
|
||||
});
|
||||
}
|
||||
|
||||
// Make sure that the event gets removed again
|
||||
window.removeEventListener('message', receiveMessage, false);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
window.addEventListener('message', receiveMessage, false);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<div class="node-wrapper" :style="nodePosition">
|
||||
<div class="node-default" :ref="data.name" :style="nodeStyle" :class="nodeClass" @dblclick="setNodeActive" @click.left="mouseLeftClick">
|
||||
<div class="node-default" :ref="data.name" :style="nodeStyle" :class="nodeClass" @dblclick="setNodeActive" @click.left="mouseLeftClick" v-touch:start="touchStart" v-touch:end="touchEnd">
|
||||
<div v-if="hasIssues" class="node-info-icon node-issues">
|
||||
<el-tooltip placement="top" effect="light">
|
||||
<div slot="content" v-html="nodeIssues"></div>
|
||||
@@ -13,19 +13,19 @@
|
||||
<font-awesome-icon icon="sync-alt" spin />
|
||||
</div>
|
||||
<div class="node-options" v-if="!isReadOnly">
|
||||
<div @click.stop.left="deleteNode" class="option" title="Delete Node" >
|
||||
<div v-touch:tap="deleteNode" class="option" title="Delete Node" >
|
||||
<font-awesome-icon icon="trash" />
|
||||
</div>
|
||||
<div @click.stop.left="disableNode" class="option" title="Activate/Deactivate Node" >
|
||||
<div v-touch:tap="disableNode" class="option" title="Activate/Deactivate Node" >
|
||||
<font-awesome-icon :icon="nodeDisabledIcon" />
|
||||
</div>
|
||||
<div @click.stop.left="duplicateNode" class="option" title="Duplicate Node" >
|
||||
<div v-touch:tap="duplicateNode" class="option" title="Duplicate Node" >
|
||||
<font-awesome-icon icon="clone" />
|
||||
</div>
|
||||
<div @click.stop.left="setNodeActive" class="option touch" title="Edit Node" v-if="!isReadOnly">
|
||||
<div v-touch:tap="setNodeActive" class="option touch" title="Edit Node" v-if="!isReadOnly">
|
||||
<font-awesome-icon class="execute-icon" icon="cog" />
|
||||
</div>
|
||||
<div @click.stop.left="executeNode" class="option" title="Execute Node" v-if="!isReadOnly && !workflowRunning">
|
||||
<div v-touch:tap="executeNode" class="option" title="Execute Node" v-if="!isReadOnly && !workflowRunning">
|
||||
<font-awesome-icon class="execute-icon" icon="play-circle" />
|
||||
</div>
|
||||
</div>
|
||||
@@ -110,6 +110,10 @@ export default mixins(nodeBase, workflowHelpers).extend({
|
||||
classes.push('is-touch-device');
|
||||
}
|
||||
|
||||
if (this.isTouchActive) {
|
||||
classes.push('touch-active');
|
||||
}
|
||||
|
||||
return classes;
|
||||
},
|
||||
nodeIssues (): string {
|
||||
@@ -134,7 +138,7 @@ export default mixins(nodeBase, workflowHelpers).extend({
|
||||
}
|
||||
|
||||
if (this.nodeType !== null && this.nodeType.subtitle !== undefined) {
|
||||
return this.workflow.getSimpleParameterValue(this.data as INode, this.nodeType.subtitle) as string | undefined;
|
||||
return this.workflow.expression.getSimpleParameterValue(this.data as INode, this.nodeType.subtitle) as string | undefined;
|
||||
}
|
||||
|
||||
if (this.data.parameters.operation !== undefined) {
|
||||
@@ -174,7 +178,7 @@ export default mixins(nodeBase, workflowHelpers).extend({
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
isTouchDevice: 'ontouchstart' in window || navigator.msMaxTouchPoints,
|
||||
isTouchActive: false,
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
@@ -199,6 +203,14 @@ export default mixins(nodeBase, workflowHelpers).extend({
|
||||
setNodeActive () {
|
||||
this.$store.commit('setActiveNode', this.data.name);
|
||||
},
|
||||
touchStart () {
|
||||
if (this.isTouchDevice === true && this.isMacOs === false && this.isTouchActive === false) {
|
||||
this.isTouchActive = true;
|
||||
setTimeout(() => {
|
||||
this.isTouchActive = false;
|
||||
}, 2000);
|
||||
}
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
@@ -268,6 +280,7 @@ export default mixins(nodeBase, workflowHelpers).extend({
|
||||
}
|
||||
}
|
||||
|
||||
&.touch-active,
|
||||
&:hover {
|
||||
.node-execute {
|
||||
display: initial;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div class="node-icon-wrapper" :style="iconStyleData">
|
||||
<div class="node-icon-wrapper" :style="iconStyleData" :class="{full: isSvgIcon}">
|
||||
<div v-if="nodeIconData !== null" class="icon">
|
||||
<img :src="nodeIconData.path" style="width: 100%; height: 100%;" v-if="nodeIconData.type === 'file'"/>
|
||||
<font-awesome-icon :icon="nodeIconData.path" v-else-if="nodeIconData.type === 'fa'" />
|
||||
@@ -17,6 +17,7 @@ import Vue from 'vue';
|
||||
interface NodeIconData {
|
||||
type: string;
|
||||
path: string;
|
||||
fileExtension?: string;
|
||||
}
|
||||
|
||||
export default Vue.extend({
|
||||
@@ -41,6 +42,12 @@ export default Vue.extend({
|
||||
'border-radius': Math.ceil(size / 2) + 'px',
|
||||
};
|
||||
},
|
||||
isSvgIcon (): boolean {
|
||||
if (this.nodeIconData && this.nodeIconData.type === 'file' && this.nodeIconData.fileExtension === 'svg') {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
nodeIconData (): null | NodeIconData {
|
||||
if (this.nodeType === null) {
|
||||
return null;
|
||||
@@ -51,13 +58,14 @@ export default Vue.extend({
|
||||
if (this.nodeType.icon) {
|
||||
let type, path;
|
||||
[type, path] = this.nodeType.icon.split(':');
|
||||
const returnData = {
|
||||
const returnData: NodeIconData = {
|
||||
type,
|
||||
path,
|
||||
};
|
||||
|
||||
if (type === 'file') {
|
||||
returnData.path = restUrl + '/node-icon/' + this.nodeType.name;
|
||||
returnData.fileExtension = path.split('.').slice(-1).join();
|
||||
}
|
||||
|
||||
return returnData;
|
||||
@@ -83,6 +91,10 @@ export default Vue.extend({
|
||||
font-weight: bold;
|
||||
font-size: 20px;
|
||||
|
||||
&.full .icon {
|
||||
margin: 0.24em;
|
||||
}
|
||||
|
||||
.node-icon-placeholder {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
@@ -248,7 +248,7 @@ export default mixins(
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
noDataExpression: true,
|
||||
description: 'If active, the workflow continues even if this node\'s <br /execution fails. When this occurs, the node passes along input data from<br />previous nodes - so your workflow should account for unexpected output data.',
|
||||
description: 'If active, the workflow continues even if this node\'s <br />execution fails. When this occurs, the node passes along input data from<br />previous nodes - so your workflow should account for unexpected output data.',
|
||||
},
|
||||
] as INodeProperties[],
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ import Vue from 'vue';
|
||||
export default Vue.extend(
|
||||
{
|
||||
name: 'PageContentWrapper',
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
</script>
|
||||
|
||||
@@ -250,7 +250,7 @@ export default mixins(
|
||||
* @returns
|
||||
* @memberof Workflow
|
||||
*/
|
||||
getNodeOutputData (runData: IRunData, nodeName: string, filterText: string, itemIndex = 0, runIndex = 0, inputName = 'main', outputIndex = 0): IVariableSelectorOption[] | null {
|
||||
getNodeOutputData (runData: IRunData, nodeName: string, filterText: string, itemIndex = 0, runIndex = 0, inputName = 'main', outputIndex = 0, useShort = false): IVariableSelectorOption[] | null {
|
||||
if (!runData.hasOwnProperty(nodeName)) {
|
||||
// No data found for node
|
||||
return null;
|
||||
@@ -291,9 +291,12 @@ export default mixins(
|
||||
|
||||
// Get json data
|
||||
if (outputData.hasOwnProperty('json')) {
|
||||
|
||||
const jsonPropertyPrefix = useShort === true ? '$json' : `$node["${nodeName}"].json`;
|
||||
|
||||
const jsonDataOptions: IVariableSelectorOption[] = [];
|
||||
for (const propertyName of Object.keys(outputData.json)) {
|
||||
jsonDataOptions.push.apply(jsonDataOptions, this.jsonDataToFilterOption(outputData.json[propertyName], `$node["${nodeName}"].json`, propertyName, filterText));
|
||||
jsonDataOptions.push.apply(jsonDataOptions, this.jsonDataToFilterOption(outputData.json[propertyName], jsonPropertyPrefix, propertyName, filterText));
|
||||
}
|
||||
|
||||
if (jsonDataOptions.length) {
|
||||
@@ -308,6 +311,9 @@ export default mixins(
|
||||
|
||||
// Get binary data
|
||||
if (outputData.hasOwnProperty('binary')) {
|
||||
|
||||
const binaryPropertyPrefix = useShort === true ? '$binary' : `$node["${nodeName}"].binary`;
|
||||
|
||||
const binaryData = [];
|
||||
let binaryPropertyData = [];
|
||||
|
||||
@@ -326,7 +332,7 @@ export default mixins(
|
||||
binaryPropertyData.push(
|
||||
{
|
||||
name: propertyName,
|
||||
key: `$node["${nodeName}"].binary.${dataPropertyName}.${propertyName}`,
|
||||
key: `${binaryPropertyPrefix}.${dataPropertyName}.${propertyName}`,
|
||||
value: outputData.binary![dataPropertyName][propertyName],
|
||||
},
|
||||
);
|
||||
@@ -336,7 +342,7 @@ export default mixins(
|
||||
binaryData.push(
|
||||
{
|
||||
name: dataPropertyName,
|
||||
key: `$node["${nodeName}"].binary.${dataPropertyName}`,
|
||||
key: `${binaryPropertyPrefix}.${dataPropertyName}`,
|
||||
options: this.sortOptions(binaryPropertyData),
|
||||
allowParentSelect: true,
|
||||
},
|
||||
@@ -347,7 +353,7 @@ export default mixins(
|
||||
returnData.push(
|
||||
{
|
||||
name: 'Binary',
|
||||
key: `$node["${nodeName}"].binary`,
|
||||
key: binaryPropertyPrefix,
|
||||
options: this.sortOptions(binaryData),
|
||||
allowParentSelect: true,
|
||||
},
|
||||
@@ -474,7 +480,7 @@ export default mixins(
|
||||
// (example "IF" node. If node is connected to "true" or to "false" output)
|
||||
const outputIndex = this.workflow.getNodeConnectionOutputIndex(activeNode.name, parentNode[0], 'main');
|
||||
|
||||
tempOutputData = this.getNodeOutputData(runData, parentNode[0], filterText, itemIndex, 0, 'main', outputIndex) as IVariableSelectorOption[];
|
||||
tempOutputData = this.getNodeOutputData(runData, parentNode[0], filterText, itemIndex, 0, 'main', outputIndex, true) as IVariableSelectorOption[];
|
||||
|
||||
if (tempOutputData) {
|
||||
if (JSON.stringify(tempOutputData).length < 102400) {
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
import Vue from 'vue';
|
||||
|
||||
export const deviceSupportHelpers = Vue.extend({
|
||||
data() {
|
||||
return {
|
||||
isTouchDevice: 'ontouchstart' in window || navigator.msMaxTouchPoints,
|
||||
isMacOs: /(ipad|iphone|ipod|mac)/i.test(navigator.platform),
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
// TODO: Check if used anywhere
|
||||
controlKeyCode(): string {
|
||||
if (this.isMacOs) {
|
||||
return 'Meta';
|
||||
}
|
||||
return 'Control';
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
isCtrlKeyPressed(e: MouseEvent | KeyboardEvent): boolean {
|
||||
if (this.isTouchDevice === true) {
|
||||
return true;
|
||||
}
|
||||
if (this.isMacOs) {
|
||||
return e.metaKey;
|
||||
}
|
||||
return e.ctrlKey;
|
||||
},
|
||||
},
|
||||
});
|
||||
@@ -2,20 +2,19 @@ import { INodeUi } from '@/Interface';
|
||||
|
||||
import mixins from 'vue-typed-mixins';
|
||||
|
||||
import { deviceSupportHelpers } from '@/components/mixins/deviceSupportHelpers';
|
||||
import { nodeIndex } from '@/components/mixins/nodeIndex';
|
||||
|
||||
export const mouseSelect = mixins(nodeIndex).extend({
|
||||
export const mouseSelect = mixins(
|
||||
deviceSupportHelpers,
|
||||
nodeIndex,
|
||||
).extend({
|
||||
data () {
|
||||
return {
|
||||
selectActive: false,
|
||||
selectBox: document.createElement('span'),
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
isMacOs (): boolean {
|
||||
return /(ipad|iphone|ipod|mac)/i.test(navigator.platform);
|
||||
},
|
||||
},
|
||||
mounted () {
|
||||
this.createSelectBox();
|
||||
},
|
||||
@@ -34,6 +33,9 @@ export const mouseSelect = mixins(nodeIndex).extend({
|
||||
this.$el.appendChild(this.selectBox);
|
||||
},
|
||||
isCtrlKeyPressed (e: MouseEvent | KeyboardEvent): boolean {
|
||||
if (this.isTouchDevice === true) {
|
||||
return true;
|
||||
}
|
||||
if (this.isMacOs) {
|
||||
return e.metaKey;
|
||||
}
|
||||
@@ -125,6 +127,13 @@ export const mouseSelect = mixins(nodeIndex).extend({
|
||||
},
|
||||
mouseUpMouseSelect (e: MouseEvent) {
|
||||
if (this.selectActive === false) {
|
||||
if (this.isTouchDevice === true) {
|
||||
// @ts-ignore
|
||||
if (e.target && e.target.id.includes('node-view')) {
|
||||
// Deselect all nodes
|
||||
this.deselectAllNodes();
|
||||
}
|
||||
}
|
||||
// If it is not active return direcly.
|
||||
// Else normal node dragging will not work.
|
||||
return;
|
||||
|
||||
@@ -1,41 +1,43 @@
|
||||
import mixins from 'vue-typed-mixins';
|
||||
|
||||
// @ts-ignore
|
||||
import normalizeWheel from 'normalize-wheel';
|
||||
import { deviceSupportHelpers } from '@/components/mixins/deviceSupportHelpers';
|
||||
import { nodeIndex } from '@/components/mixins/nodeIndex';
|
||||
|
||||
export const moveNodeWorkflow = mixins(nodeIndex).extend({
|
||||
export const moveNodeWorkflow = mixins(
|
||||
deviceSupportHelpers,
|
||||
nodeIndex,
|
||||
).extend({
|
||||
data () {
|
||||
return {
|
||||
moveLastPosition: [0, 0],
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
controlKeyCode (): string {
|
||||
if (this.isMacOs) {
|
||||
return 'Meta';
|
||||
}
|
||||
return 'Control';
|
||||
},
|
||||
isMacOs (): boolean {
|
||||
return /(ipad|iphone|ipod|mac)/i.test(navigator.platform);
|
||||
},
|
||||
},
|
||||
|
||||
methods: {
|
||||
isCtrlKeyPressed (e: MouseEvent | KeyboardEvent): boolean {
|
||||
if (this.isMacOs) {
|
||||
return e.metaKey;
|
||||
}
|
||||
return e.ctrlKey;
|
||||
getMousePosition(e: MouseEvent | TouchEvent) {
|
||||
// @ts-ignore
|
||||
const x = e.pageX !== undefined ? e.pageX : (e.touches && e.touches[0] && e.touches[0].pageX ? e.touches[0].pageX : 0);
|
||||
// @ts-ignore
|
||||
const y = e.pageY !== undefined ? e.pageY : (e.touches && e.touches[0] && e.touches[0].pageY ? e.touches[0].pageY : 0);
|
||||
|
||||
return {
|
||||
x,
|
||||
y,
|
||||
};
|
||||
},
|
||||
moveWorkflow (e: MouseEvent) {
|
||||
const offsetPosition = this.$store.getters.getNodeViewOffsetPosition;
|
||||
|
||||
const nodeViewOffsetPositionX = offsetPosition[0] + (e.pageX - this.moveLastPosition[0]);
|
||||
const nodeViewOffsetPositionY = offsetPosition[1] + (e.pageY - this.moveLastPosition[1]);
|
||||
const position = this.getMousePosition(e);
|
||||
|
||||
const nodeViewOffsetPositionX = offsetPosition[0] + (position.x - this.moveLastPosition[0]);
|
||||
const nodeViewOffsetPositionY = offsetPosition[1] + (position.y - this.moveLastPosition[1]);
|
||||
this.$store.commit('setNodeViewOffsetPosition', {newOffset: [nodeViewOffsetPositionX, nodeViewOffsetPositionY], setStateDirty: true});
|
||||
|
||||
// Update the last position
|
||||
this.moveLastPosition[0] = e.pageX;
|
||||
this.moveLastPosition[1] = e.pageY;
|
||||
this.moveLastPosition[0] = position.x;
|
||||
this.moveLastPosition[1] = position.y;
|
||||
},
|
||||
mouseDownMoveWorkflow (e: MouseEvent) {
|
||||
if (this.isCtrlKeyPressed(e) === false) {
|
||||
@@ -51,8 +53,10 @@ export const moveNodeWorkflow = mixins(nodeIndex).extend({
|
||||
|
||||
this.$store.commit('setNodeViewMoveInProgress', true);
|
||||
|
||||
this.moveLastPosition[0] = e.pageX;
|
||||
this.moveLastPosition[1] = e.pageY;
|
||||
const position = this.getMousePosition(e);
|
||||
|
||||
this.moveLastPosition[0] = position.x;
|
||||
this.moveLastPosition[1] = position.y;
|
||||
|
||||
// @ts-ignore
|
||||
this.$el.addEventListener('mousemove', this.mouseMoveNodeWorkflow);
|
||||
@@ -72,6 +76,15 @@ export const moveNodeWorkflow = mixins(nodeIndex).extend({
|
||||
// Nothing else to do. Simply leave the node view at the current offset
|
||||
},
|
||||
mouseMoveNodeWorkflow (e: MouseEvent) {
|
||||
// @ts-ignore
|
||||
if (e.target && !e.target.id.includes('node-view')) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.$store.getters.isActionActive('dragActive')) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (e.buttons === 0) {
|
||||
// Mouse button is not pressed anymore so stop selection mode
|
||||
// Happens normally when mouse leave the view pressed and then
|
||||
@@ -84,9 +97,10 @@ export const moveNodeWorkflow = mixins(nodeIndex).extend({
|
||||
this.moveWorkflow(e);
|
||||
},
|
||||
wheelMoveWorkflow (e: WheelEvent) {
|
||||
const normalized = normalizeWheel(e);
|
||||
const offsetPosition = this.$store.getters.getNodeViewOffsetPosition;
|
||||
const nodeViewOffsetPositionX = offsetPosition[0] - e.deltaX;
|
||||
const nodeViewOffsetPositionY = offsetPosition[1] - e.deltaY;
|
||||
const nodeViewOffsetPositionX = offsetPosition[0] - normalized.pixelX;
|
||||
const nodeViewOffsetPositionY = offsetPosition[1] - normalized.pixelY;
|
||||
this.$store.commit('setNodeViewOffsetPosition', {newOffset: [nodeViewOffsetPositionX, nodeViewOffsetPositionY], setStateDirty: true});
|
||||
},
|
||||
},
|
||||
|
||||
@@ -2,20 +2,20 @@ import { IConnectionsUi, IEndpointOptions, INodeUi, XYPositon } from '@/Interfac
|
||||
|
||||
import mixins from 'vue-typed-mixins';
|
||||
|
||||
import { deviceSupportHelpers } from '@/components/mixins/deviceSupportHelpers';
|
||||
import { nodeIndex } from '@/components/mixins/nodeIndex';
|
||||
import { NODE_NAME_PREFIX } from '@/constants';
|
||||
|
||||
export const nodeBase = mixins(nodeIndex).extend({
|
||||
export const nodeBase = mixins(
|
||||
deviceSupportHelpers,
|
||||
nodeIndex,
|
||||
).extend({
|
||||
mounted () {
|
||||
// Initialize the node
|
||||
if (this.data !== null) {
|
||||
this.__addNode(this.data);
|
||||
}
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
data (): INodeUi {
|
||||
return this.$store.getters.nodeByName(this.name);
|
||||
@@ -26,9 +26,6 @@ export const nodeBase = mixins(nodeIndex).extend({
|
||||
}
|
||||
return false;
|
||||
},
|
||||
isMacOs (): boolean {
|
||||
return /(ipad|iphone|ipod|mac)/i.test(navigator.platform);
|
||||
},
|
||||
nodeName (): string {
|
||||
return NODE_NAME_PREFIX + this.nodeIndex;
|
||||
},
|
||||
@@ -336,15 +333,15 @@ export const nodeBase = mixins(nodeIndex).extend({
|
||||
});
|
||||
|
||||
},
|
||||
|
||||
isCtrlKeyPressed (e: MouseEvent | KeyboardEvent): boolean {
|
||||
if (this.isMacOs) {
|
||||
return e.metaKey;
|
||||
touchEnd(e: MouseEvent) {
|
||||
if (this.isTouchDevice) {
|
||||
if (this.$store.getters.isActionActive('dragActive')) {
|
||||
this.$store.commit('removeActiveAction', 'dragActive');
|
||||
}
|
||||
}
|
||||
return e.ctrlKey;
|
||||
},
|
||||
|
||||
mouseLeftClick (e: MouseEvent) {
|
||||
if (!this.isTouchDevice) {
|
||||
if (this.$store.getters.isActionActive('dragActive')) {
|
||||
this.$store.commit('removeActiveAction', 'dragActive');
|
||||
} else {
|
||||
@@ -358,6 +355,7 @@ export const nodeBase = mixins(nodeIndex).extend({
|
||||
this.$emit('nodeSelected', this.name);
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
@@ -152,6 +152,10 @@ export const restApi = Vue.extend({
|
||||
return self.restApi().makeRestApiRequest('GET', `/node-types`);
|
||||
},
|
||||
|
||||
getNodesInformation: (nodeList: string[]): Promise<INodeTypeDescription[]> => {
|
||||
return self.restApi().makeRestApiRequest('POST', `/node-types`, {nodeNames: nodeList});
|
||||
},
|
||||
|
||||
// Returns all the parameter options from the server
|
||||
getNodeParameterOptions: (nodeType: string, methodName: string, currentNodeParameters: INodeParameters, credentials?: INodeCredentials): Promise<INodePropertyOptions[]> => {
|
||||
const sendData = {
|
||||
|
||||
@@ -360,7 +360,7 @@ export const workflowHelpers = mixins(
|
||||
connectionInputData = [];
|
||||
}
|
||||
|
||||
return workflow.getParameterValue(expression, runExecutionData, runIndex, itemIndex, activeNode.name, connectionInputData, true);
|
||||
return workflow.expression.getParameterValue(expression, runExecutionData, runIndex, itemIndex, activeNode.name, connectionInputData, true);
|
||||
},
|
||||
|
||||
// Saves the currently loaded workflow to the database.
|
||||
|
||||
@@ -5,6 +5,7 @@ import Vue from 'vue';
|
||||
import 'prismjs';
|
||||
import 'prismjs/themes/prism.css';
|
||||
import 'vue-prism-editor/dist/VuePrismEditor.css';
|
||||
import Vue2TouchEvents from 'vue2-touch-events';
|
||||
|
||||
import * as ElementUI from 'element-ui';
|
||||
// @ts-ignore
|
||||
@@ -91,6 +92,9 @@ import {
|
||||
import { FontAwesomeIcon } from '@fortawesome/vue-fontawesome';
|
||||
|
||||
import { store } from './store';
|
||||
|
||||
Vue.use(Vue2TouchEvents);
|
||||
|
||||
Vue.use(ElementUI, { locale });
|
||||
|
||||
library.add(faAngleDoubleLeft);
|
||||
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
IConnection,
|
||||
IConnections,
|
||||
ICredentialType,
|
||||
IDataObject,
|
||||
INodeConnections,
|
||||
INodeIssueData,
|
||||
INodeTypeDescription,
|
||||
@@ -56,6 +57,7 @@ export const store = new Vuex.Store({
|
||||
executionTimeout: -1,
|
||||
maxExecutionTimeout: Number.MAX_SAFE_INTEGER,
|
||||
versionCli: '0.0.0',
|
||||
oauthCallbackUrls: {},
|
||||
workflowExecutionData: null as IExecutionResponse | null,
|
||||
lastSelectedNode: null as string | null,
|
||||
lastSelectedNodeOutputIndex: null as number | null,
|
||||
@@ -535,6 +537,9 @@ export const store = new Vuex.Store({
|
||||
setVersionCli (state, version: string) {
|
||||
Vue.set(state, 'versionCli', version);
|
||||
},
|
||||
setOauthCallbackUrls(state, urls: IDataObject) {
|
||||
Vue.set(state, 'oauthCallbackUrls', urls);
|
||||
},
|
||||
|
||||
addNodeType (state, typeData: INodeTypeDescription) {
|
||||
if (!typeData.hasOwnProperty('name')) {
|
||||
@@ -602,6 +607,14 @@ export const store = new Vuex.Store({
|
||||
Vue.set(state.workflow, 'settings', {});
|
||||
}
|
||||
},
|
||||
|
||||
updateNodeTypes (state, nodeTypes: INodeTypeDescription[]) {
|
||||
const updatedNodeNames = nodeTypes.map(node => node.name) as string[];
|
||||
const oldNodesNotChanged = state.nodeTypes.filter(node => !updatedNodeNames.includes(node.name));
|
||||
const updatedNodes = [...oldNodesNotChanged, ...nodeTypes];
|
||||
Vue.set(state, 'nodeTypes', updatedNodes);
|
||||
state.nodeTypes = updatedNodes;
|
||||
},
|
||||
},
|
||||
getters: {
|
||||
|
||||
@@ -658,6 +671,9 @@ export const store = new Vuex.Store({
|
||||
versionCli: (state): string => {
|
||||
return state.versionCli;
|
||||
},
|
||||
oauthCallbackUrls: (state): object => {
|
||||
return state.oauthCallbackUrls;
|
||||
},
|
||||
|
||||
// Push Connection
|
||||
pushConnectionActive: (state): boolean => {
|
||||
|
||||
@@ -3,11 +3,15 @@
|
||||
<div
|
||||
class="node-view-wrapper"
|
||||
:class="workflowClasses"
|
||||
@touchstart="mouseDown"
|
||||
@touchend="mouseUp"
|
||||
@touchmove="mouseMoveNodeWorkflow"
|
||||
@mousedown="mouseDown"
|
||||
v-touch:tap="touchTap"
|
||||
@mouseup="mouseUp"
|
||||
@wheel="wheelScroll"
|
||||
>
|
||||
<div class="node-view-background" :style="backgroundStyle"></div>
|
||||
<div id="node-view-background" class="node-view-background" :style="backgroundStyle"></div>
|
||||
<div id="node-view" class="node-view" :style="workflowStyle">
|
||||
<node
|
||||
v-for="nodeData in nodes"
|
||||
@@ -356,14 +360,20 @@ export default mixins(
|
||||
|
||||
return data;
|
||||
},
|
||||
mouseDown (e: MouseEvent) {
|
||||
touchTap (e: MouseEvent | TouchEvent) {
|
||||
if (this.isTouchDevice) {
|
||||
this.mouseDown(e);
|
||||
}
|
||||
},
|
||||
mouseDown (e: MouseEvent | TouchEvent) {
|
||||
// Save the location of the mouse click
|
||||
const position = this.getMousePosition(e);
|
||||
const offsetPosition = this.$store.getters.getNodeViewOffsetPosition;
|
||||
this.lastClickPosition[0] = e.pageX - offsetPosition[0];
|
||||
this.lastClickPosition[1] = e.pageY - offsetPosition[1];
|
||||
this.lastClickPosition[0] = position.x - offsetPosition[0];
|
||||
this.lastClickPosition[1] = position.y - offsetPosition[1];
|
||||
|
||||
this.mouseDownMouseSelect(e);
|
||||
this.mouseDownMoveWorkflow(e);
|
||||
this.mouseDownMouseSelect(e as MouseEvent);
|
||||
this.mouseDownMoveWorkflow(e as MouseEvent);
|
||||
|
||||
// Hide the node-creator
|
||||
this.createNodeActive = false;
|
||||
@@ -962,7 +972,7 @@ export default mixins(
|
||||
// If a node is active then add the new node directly after the current one
|
||||
// newNodeData.position = [activeNode.position[0], activeNode.position[1] + 60];
|
||||
newNodeData.position = this.getNewNodePosition(
|
||||
[lastSelectedNode.position[0] + 150, lastSelectedNode.position[1]],
|
||||
[lastSelectedNode.position[0] + 200, lastSelectedNode.position[1]],
|
||||
[100, 0],
|
||||
);
|
||||
} else {
|
||||
@@ -1456,6 +1466,11 @@ export default mixins(
|
||||
[0, 150],
|
||||
);
|
||||
|
||||
if (newNodeData.webhookId) {
|
||||
// Make sure that the node gets a new unique webhook-ID
|
||||
newNodeData.webhookId = uuidv4();
|
||||
}
|
||||
|
||||
await this.addNodes([newNodeData]);
|
||||
|
||||
// Automatically deselect all nodes and select the current one and also active
|
||||
@@ -1593,6 +1608,11 @@ export default mixins(
|
||||
return;
|
||||
}
|
||||
|
||||
// Before proceeding we must check if all nodes contain the `properties` attribute.
|
||||
// Nodes are loaded without this information so we must make sure that all nodes
|
||||
// being added have this information.
|
||||
await this.loadNodesProperties(nodes.map(node => node.type));
|
||||
|
||||
// Add the node to the node-list
|
||||
let nodeType: INodeTypeDescription | null;
|
||||
let foundNodeIssues: INodeIssues | null;
|
||||
@@ -1703,6 +1723,9 @@ export default mixins(
|
||||
let oldName: string;
|
||||
let newName: string;
|
||||
const createNodes: INode[] = [];
|
||||
|
||||
await this.loadNodesProperties(data.nodes.map(node => node.type));
|
||||
|
||||
data.nodes.forEach(node => {
|
||||
if (nodeTypesCount[node.type] !== undefined) {
|
||||
if (nodeTypesCount[node.type].exist >= nodeTypesCount[node.type].max) {
|
||||
@@ -1745,6 +1768,10 @@ export default mixins(
|
||||
for (type of Object.keys(currentConnections[sourceNode])) {
|
||||
connection[type] = [];
|
||||
for (sourceIndex = 0; sourceIndex < currentConnections[sourceNode][type].length; sourceIndex++) {
|
||||
if (!currentConnections[sourceNode][type][sourceIndex]) {
|
||||
// There is so something wrong with the data so ignore
|
||||
continue;
|
||||
}
|
||||
const nodeSourceConnections = [];
|
||||
for (connectionIndex = 0; connectionIndex < currentConnections[sourceNode][type][sourceIndex].length; connectionIndex++) {
|
||||
const nodeConnection: NodeInputConnections = [];
|
||||
@@ -1908,6 +1935,7 @@ export default mixins(
|
||||
this.$store.commit('setExecutionTimeout', settings.executionTimeout);
|
||||
this.$store.commit('setMaxExecutionTimeout', settings.maxExecutionTimeout);
|
||||
this.$store.commit('setVersionCli', settings.versionCli);
|
||||
this.$store.commit('setOauthCallbackUrls', settings.oauthCallbackUrls);
|
||||
},
|
||||
async loadNodeTypes (): Promise<void> {
|
||||
const nodeTypes = await this.restApi().getNodeTypes();
|
||||
@@ -1921,6 +1949,17 @@ export default mixins(
|
||||
const credentials = await this.restApi().getAllCredentials();
|
||||
this.$store.commit('setCredentials', credentials);
|
||||
},
|
||||
async loadNodesProperties(nodeNames: string[]): Promise<void> {
|
||||
const allNodes = this.$store.getters.allNodeTypes;
|
||||
const nodesToBeFetched = allNodes.filter((node: INodeTypeDescription) => nodeNames.includes(node.name) && !node.hasOwnProperty('properties')).map((node: INodeTypeDescription) => node.name) as string[];
|
||||
if (nodesToBeFetched.length > 0) {
|
||||
// Only call API if node information is actually missing
|
||||
this.startLoading();
|
||||
const nodeInfo = await this.restApi().getNodesInformation(nodesToBeFetched);
|
||||
this.$store.commit('updateNodeTypes', nodeInfo);
|
||||
this.stopLoading();
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
async mounted () {
|
||||
|
||||
@@ -47,6 +47,7 @@
|
||||
"forin": true,
|
||||
"jsdoc-format": true,
|
||||
"label-position": true,
|
||||
"indent": [true, "tabs", 2],
|
||||
"member-access": [
|
||||
true,
|
||||
"no-public"
|
||||
@@ -83,6 +84,18 @@
|
||||
"ignore-bound-class-methods"
|
||||
],
|
||||
"switch-default": true,
|
||||
"trailing-comma": [
|
||||
true,
|
||||
{
|
||||
"multiline": {
|
||||
"objects": "always",
|
||||
"arrays": "always",
|
||||
"functions": "always",
|
||||
"typeLiterals": "ignore"
|
||||
},
|
||||
"esSpecCompliant": true
|
||||
}
|
||||
],
|
||||
"triple-equals": [
|
||||
true,
|
||||
"allow-null-check"
|
||||
|
||||
@@ -19,7 +19,7 @@ Condition notice.
|
||||
|
||||
Software: n8n
|
||||
|
||||
License: Apache 2.0
|
||||
License: Apache 2.0 with Commons Clause
|
||||
|
||||
Licensor: n8n GmbH
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "n8n-node-dev",
|
||||
"version": "0.10.0",
|
||||
"version": "0.11.0",
|
||||
"description": "CLI to simplify n8n credentials/node development",
|
||||
"license": "SEE LICENSE IN LICENSE.md",
|
||||
"homepage": "https://n8n.io",
|
||||
@@ -24,6 +24,7 @@
|
||||
"postpack": "rm -f oclif.manifest.json",
|
||||
"prepack": "echo \"Building project...\" && rm -rf dist && tsc -b && oclif-dev manifest",
|
||||
"tslint": "tslint -p tsconfig.json -c tslint.json",
|
||||
"tslintfix": "tslint --fix -p tsconfig.json -c tslint.json",
|
||||
"watch": "tsc --watch"
|
||||
},
|
||||
"bin": {
|
||||
@@ -54,15 +55,16 @@
|
||||
"@oclif/command": "^1.5.18",
|
||||
"@oclif/errors": "^1.2.2",
|
||||
"@types/express": "^4.17.6",
|
||||
"@types/node": "^14.0.27",
|
||||
"@types/node": "14.0.27",
|
||||
"change-case": "^4.1.1",
|
||||
"copyfiles": "^2.1.1",
|
||||
"inquirer": "^7.0.1",
|
||||
"n8n-core": "^0.44.0",
|
||||
"n8n-workflow": "^0.39.0",
|
||||
"n8n-core": "^0.48.0",
|
||||
"n8n-workflow": "^0.42.0",
|
||||
"oauth-1.0a": "^2.2.6",
|
||||
"replace-in-file": "^6.0.0",
|
||||
"request": "^2.88.2",
|
||||
"tmp-promise": "^2.0.2",
|
||||
"typescript": "~3.7.4"
|
||||
"typescript": "~3.9.7"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,7 +23,7 @@ export async function createTemplate(sourceFilePath: string, destinationFilePath
|
||||
// Replace the variables in the template file
|
||||
const options: ReplaceInFileConfig = {
|
||||
files: [
|
||||
destinationFilePath
|
||||
destinationFilePath,
|
||||
],
|
||||
from: [],
|
||||
to: [],
|
||||
|
||||
@@ -46,6 +46,11 @@
|
||||
"forin": true,
|
||||
"jsdoc-format": true,
|
||||
"label-position": true,
|
||||
"indent": [
|
||||
true,
|
||||
"tabs",
|
||||
2
|
||||
],
|
||||
"member-access": [
|
||||
true,
|
||||
"no-public"
|
||||
@@ -60,6 +65,13 @@
|
||||
"no-default-export": true,
|
||||
"no-duplicate-variable": true,
|
||||
"no-inferrable-types": true,
|
||||
"ordered-imports": [
|
||||
true,
|
||||
{
|
||||
"import-sources-order": "any",
|
||||
"named-imports-order": "case-insensitive"
|
||||
}
|
||||
],
|
||||
"no-namespace": [
|
||||
true,
|
||||
"allow-declarations"
|
||||
@@ -82,6 +94,18 @@
|
||||
"ignore-bound-class-methods"
|
||||
],
|
||||
"switch-default": true,
|
||||
"trailing-comma": [
|
||||
true,
|
||||
{
|
||||
"multiline": {
|
||||
"objects": "always",
|
||||
"arrays": "always",
|
||||
"functions": "always",
|
||||
"typeLiterals": "ignore"
|
||||
},
|
||||
"esSpecCompliant": true
|
||||
}
|
||||
],
|
||||
"triple-equals": [
|
||||
true,
|
||||
"allow-null-check"
|
||||
|
||||
@@ -19,7 +19,7 @@ Condition notice.
|
||||
|
||||
Software: n8n
|
||||
|
||||
License: Apache 2.0
|
||||
License: Apache 2.0 with Commons Clause
|
||||
|
||||
Licensor: n8n GmbH
|
||||
|
||||
|
||||
@@ -31,7 +31,7 @@ export class AcuitySchedulingOAuth2Api implements ICredentialType {
|
||||
name: 'scope',
|
||||
type: 'hidden' as NodePropertyTypes,
|
||||
default: 'api-v1',
|
||||
required: true
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
displayName: 'Auth URI Query Parameters',
|
||||
|
||||
@@ -3,7 +3,6 @@ import {
|
||||
NodePropertyTypes,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
|
||||
export class AsanaApi implements ICredentialType {
|
||||
name = 'asanaApi';
|
||||
displayName = 'Asana API';
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
import {
|
||||
ICredentialType,
|
||||
NodePropertyTypes,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
export class AsanaOAuth2Api implements ICredentialType {
|
||||
name = 'asanaOAuth2Api';
|
||||
extends = [
|
||||
'oAuth2Api',
|
||||
];
|
||||
displayName = 'Asana OAuth2 API';
|
||||
documentationUrl = 'asana';
|
||||
properties = [
|
||||
{
|
||||
displayName: 'Authorization URL',
|
||||
name: 'authUrl',
|
||||
type: 'hidden' as NodePropertyTypes,
|
||||
default: 'https://app.asana.com/-/oauth_authorize',
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
displayName: 'Access Token URL',
|
||||
name: 'accessTokenUrl',
|
||||
type: 'hidden' as NodePropertyTypes,
|
||||
default: 'https://app.asana.com/-/oauth_token',
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
displayName: 'Scope',
|
||||
name: 'scope',
|
||||
type: 'hidden' as NodePropertyTypes,
|
||||
default: '',
|
||||
},
|
||||
{
|
||||
displayName: 'Auth URI Query Parameters',
|
||||
name: 'authQueryParameters',
|
||||
type: 'hidden' as NodePropertyTypes,
|
||||
default: '',
|
||||
},
|
||||
{
|
||||
displayName: 'Authentication',
|
||||
name: 'authentication',
|
||||
type: 'hidden' as NodePropertyTypes,
|
||||
default: 'body',
|
||||
description: 'Resource to consume.',
|
||||
},
|
||||
];
|
||||
}
|
||||
18
packages/nodes-base/credentials/AutomizyApi.credentials.ts
Normal file
18
packages/nodes-base/credentials/AutomizyApi.credentials.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
import {
|
||||
ICredentialType,
|
||||
NodePropertyTypes,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
export class AutomizyApi implements ICredentialType {
|
||||
name = 'automizyApi';
|
||||
displayName = 'Automizy API';
|
||||
documentationUrl = 'automizy';
|
||||
properties = [
|
||||
{
|
||||
displayName: 'API Token',
|
||||
name: 'apiToken',
|
||||
type: 'string' as NodePropertyTypes,
|
||||
default: '',
|
||||
},
|
||||
];
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
import {
|
||||
ICredentialType,
|
||||
NodePropertyTypes,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
export class ClickUpOAuth2Api implements ICredentialType {
|
||||
name = 'clickUpOAuth2Api';
|
||||
extends = [
|
||||
'oAuth2Api',
|
||||
];
|
||||
displayName = 'ClickUp OAuth2 API';
|
||||
documentationUrl = 'clickUp';
|
||||
properties = [
|
||||
{
|
||||
displayName: 'Authorization URL',
|
||||
name: 'authUrl',
|
||||
type: 'hidden' as NodePropertyTypes,
|
||||
default: 'https://app.clickup.com/api',
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
displayName: 'Access Token URL',
|
||||
name: 'accessTokenUrl',
|
||||
type: 'hidden' as NodePropertyTypes,
|
||||
default: 'https://api.clickup.com/api/v2/oauth/token',
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
displayName: 'Scope',
|
||||
name: 'scope',
|
||||
type: 'hidden' as NodePropertyTypes,
|
||||
default: '',
|
||||
},
|
||||
{
|
||||
displayName: 'Auth URI Query Parameters',
|
||||
name: 'authQueryParameters',
|
||||
type: 'hidden' as NodePropertyTypes,
|
||||
default: '',
|
||||
},
|
||||
{
|
||||
displayName: 'Authentication',
|
||||
name: 'authentication',
|
||||
type: 'hidden' as NodePropertyTypes,
|
||||
default: 'body',
|
||||
description: 'Resource to consume.',
|
||||
},
|
||||
];
|
||||
}
|
||||
@@ -7,6 +7,7 @@ import {
|
||||
export class ContentfulApi implements ICredentialType {
|
||||
name = 'contentfulApi';
|
||||
displayName = 'Contenful API';
|
||||
documentationUrl = 'contentful';
|
||||
properties = [
|
||||
{
|
||||
displayName: 'Space ID',
|
||||
|
||||
@@ -7,6 +7,7 @@ import {
|
||||
export class ConvertKitApi implements ICredentialType {
|
||||
name = 'convertKitApi';
|
||||
displayName = 'ConvertKit API';
|
||||
documentationUrl = 'convertKit';
|
||||
properties = [
|
||||
{
|
||||
displayName: 'API Secret',
|
||||
|
||||
@@ -13,7 +13,7 @@ export class DisqusApi implements ICredentialType {
|
||||
name: 'accessToken',
|
||||
type: 'string' as NodePropertyTypes,
|
||||
default: '',
|
||||
description: 'Visit your account details page, and grab the Access Token. See <a href="https://disqus.com/api/docs/auth/">Disqus auth</a>.'
|
||||
description: 'Visit your account details page, and grab the Access Token. See <a href="https://disqus.com/api/docs/auth/">Disqus auth</a>.',
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@ export class DriftApi implements ICredentialType {
|
||||
name: 'accessToken',
|
||||
type: 'string' as NodePropertyTypes,
|
||||
default: '',
|
||||
description: 'Visit your account details page, and grab the Access Token. See <a href="https://devdocs.drift.com/docs/quick-start">Drift auth</a>.'
|
||||
description: 'Visit your account details page, and grab the Access Token. See <a href="https://devdocs.drift.com/docs/quick-start">Drift auth</a>.',
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
@@ -42,7 +42,7 @@ export class EventbriteOAuth2Api implements ICredentialType {
|
||||
displayName: 'Authentication',
|
||||
name: 'authentication',
|
||||
type: 'hidden' as NodePropertyTypes,
|
||||
default: 'body'
|
||||
default: 'body',
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@ export class FreshdeskApi implements ICredentialType {
|
||||
type: 'string' as NodePropertyTypes,
|
||||
placeholder: 'company',
|
||||
description: 'If the URL you get displayed on Freshdesk is "https://company.freshdesk.com" enter "company"',
|
||||
default: ''
|
||||
}
|
||||
default: '',
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@ export class Ftp implements ICredentialType {
|
||||
required: true,
|
||||
type: 'string' as NodePropertyTypes,
|
||||
default: '',
|
||||
placeholder: 'localhost'
|
||||
placeholder: 'localhost',
|
||||
},
|
||||
{
|
||||
displayName: 'Port',
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
import {
|
||||
ICredentialType,
|
||||
NodePropertyTypes,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
const scopes = [
|
||||
'https://www.googleapis.com/auth/admin.directory.group',
|
||||
'https://www.googleapis.com/auth/admin.directory.user',
|
||||
'https://www.googleapis.com/auth/admin.directory.domain.readonly',
|
||||
'https://www.googleapis.com/auth/admin.directory.userschema.readonly',
|
||||
];
|
||||
|
||||
export class GSuiteAdminOAuth2Api implements ICredentialType {
|
||||
name = 'gSuiteAdminOAuth2Api';
|
||||
extends = [
|
||||
'googleOAuth2Api',
|
||||
];
|
||||
displayName = 'G Suite Admin OAuth2 API';
|
||||
documentationUrl = 'google';
|
||||
properties = [
|
||||
{
|
||||
displayName: 'Scope',
|
||||
name: 'scope',
|
||||
type: 'hidden' as NodePropertyTypes,
|
||||
default: scopes.join(' '),
|
||||
},
|
||||
];
|
||||
}
|
||||
@@ -13,7 +13,7 @@ export class GitlabApi implements ICredentialType {
|
||||
displayName: 'Gitlab Server',
|
||||
name: 'server',
|
||||
type: 'string' as NodePropertyTypes,
|
||||
default: 'https://gitlab.com'
|
||||
default: 'https://gitlab.com',
|
||||
},
|
||||
{
|
||||
displayName: 'Access Token',
|
||||
|
||||
@@ -16,7 +16,7 @@ export class GitlabOAuth2Api implements ICredentialType {
|
||||
displayName: 'Gitlab Server',
|
||||
name: 'server',
|
||||
type: 'string' as NodePropertyTypes,
|
||||
default: 'https://gitlab.com'
|
||||
default: 'https://gitlab.com',
|
||||
},
|
||||
{
|
||||
displayName: 'Authorization URL',
|
||||
|
||||
@@ -27,7 +27,7 @@ export class GoogleOAuth2Api implements ICredentialType {
|
||||
displayName: 'Auth URI Query Parameters',
|
||||
name: 'authQueryParameters',
|
||||
type: 'hidden' as NodePropertyTypes,
|
||||
default: 'access_type=offline',
|
||||
default: 'access_type=offline&prompt=consent',
|
||||
},
|
||||
{
|
||||
displayName: 'Authentication',
|
||||
|
||||
@@ -4,7 +4,6 @@ import {
|
||||
} from 'n8n-workflow';
|
||||
|
||||
const scopes = [
|
||||
'https://www.googleapis.com/auth/drive',
|
||||
'https://www.googleapis.com/auth/drive.file',
|
||||
'https://www.googleapis.com/auth/spreadsheets',
|
||||
];
|
||||
|
||||
@@ -17,7 +17,7 @@ export class GoogleTasksOAuth2Api implements ICredentialType {
|
||||
displayName: 'Scope',
|
||||
name: 'scope',
|
||||
type: 'hidden' as NodePropertyTypes,
|
||||
default: scopes.join(' ')
|
||||
default: scopes.join(' '),
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
import {
|
||||
ICredentialType,
|
||||
NodePropertyTypes,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
const scopes = [
|
||||
'https://www.googleapis.com/auth/cloud-translation',
|
||||
];
|
||||
|
||||
export class GoogleTranslateOAuth2Api implements ICredentialType {
|
||||
name = 'googleTranslateOAuth2Api';
|
||||
extends = [
|
||||
'googleOAuth2Api',
|
||||
];
|
||||
displayName = 'Google Translate OAuth2 API';
|
||||
documentationUrl = 'google';
|
||||
properties = [
|
||||
{
|
||||
displayName: 'Scope',
|
||||
name: 'scope',
|
||||
type: 'hidden' as NodePropertyTypes,
|
||||
default: scopes.join(' '),
|
||||
},
|
||||
];
|
||||
}
|
||||
@@ -13,14 +13,14 @@ export class HarvestApi implements ICredentialType {
|
||||
name: 'accountId',
|
||||
type: 'string' as NodePropertyTypes,
|
||||
default: '',
|
||||
description: 'Visit your account details page, and grab the Account ID. See <a href="https://help.getharvest.com/api-v2/authentication-api/authentication/authentication/">Harvest Personal Access Tokens</a>.'
|
||||
description: 'Visit your account details page, and grab the Account ID. See <a href="https://help.getharvest.com/api-v2/authentication-api/authentication/authentication/">Harvest Personal Access Tokens</a>.',
|
||||
},
|
||||
{
|
||||
displayName: 'Access Token',
|
||||
name: 'accessToken',
|
||||
type: 'string' as NodePropertyTypes,
|
||||
default: '',
|
||||
description: 'Visit your account details page, and grab the Access Token. See <a href="https://help.getharvest.com/api-v2/authentication-api/authentication/authentication/">Harvest Personal Access Tokens</a>.'
|
||||
description: 'Visit your account details page, and grab the Access Token. See <a href="https://help.getharvest.com/api-v2/authentication-api/authentication/authentication/">Harvest Personal Access Tokens</a>.',
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ import {
|
||||
|
||||
export class HubspotDeveloperApi implements ICredentialType {
|
||||
name = 'hubspotDeveloperApi';
|
||||
displayName = 'Hubspot API';
|
||||
displayName = 'Hubspot Developer API';
|
||||
documentationUrl = 'hubspot';
|
||||
properties = [
|
||||
{
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
import {
|
||||
ICredentialType,
|
||||
NodePropertyTypes,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
|
||||
export class LinkedInOAuth2Api implements ICredentialType {
|
||||
name = 'linkedInOAuth2Api';
|
||||
extends = [
|
||||
'oAuth2Api',
|
||||
];
|
||||
displayName = 'LinkedIn OAuth2 API';
|
||||
documentationUrl = 'linkedIn';
|
||||
properties = [
|
||||
{
|
||||
displayName: 'Organization Support',
|
||||
name: 'organizationSupport',
|
||||
type: 'boolean' as NodePropertyTypes,
|
||||
default: true,
|
||||
description: 'Request permissions to post as an orgaization.',
|
||||
},
|
||||
{
|
||||
displayName: 'Authorization URL',
|
||||
name: 'authUrl',
|
||||
type: 'hidden' as NodePropertyTypes,
|
||||
default: 'https://www.linkedin.com/oauth/v2/authorization',
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
displayName: 'Access Token URL',
|
||||
name: 'accessTokenUrl',
|
||||
type: 'hidden' as NodePropertyTypes,
|
||||
default: 'https://www.linkedin.com/oauth/v2/accessToken',
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
displayName: 'Scope',
|
||||
name: 'scope',
|
||||
type: 'hidden' as NodePropertyTypes,
|
||||
default: '=r_liteprofile,r_emailaddress,w_member_social{{$parameter["organizationSupport"] === true ? ",w_organization_social":""}}',
|
||||
description: 'Standard scopes for posting on behalf of a user or organization. See <a href="https://docs.microsoft.com/en-us/linkedin/marketing/getting-started#available-permissions"> this resource </a>.',
|
||||
},
|
||||
{
|
||||
displayName: 'Auth URI Query Parameters',
|
||||
name: 'authQueryParameters',
|
||||
type: 'hidden' as NodePropertyTypes,
|
||||
default: '',
|
||||
},
|
||||
{
|
||||
displayName: 'Authentication',
|
||||
name: 'authentication',
|
||||
type: 'hidden' as NodePropertyTypes,
|
||||
default: 'body',
|
||||
},
|
||||
];
|
||||
}
|
||||
25
packages/nodes-base/credentials/MatrixApi.credentials.ts
Normal file
25
packages/nodes-base/credentials/MatrixApi.credentials.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
import {
|
||||
ICredentialType,
|
||||
NodePropertyTypes,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
|
||||
export class MatrixApi implements ICredentialType {
|
||||
name = 'matrixApi';
|
||||
displayName = 'Matrix API';
|
||||
documentationUrl = 'matrix';
|
||||
properties = [
|
||||
{
|
||||
displayName: 'Access Token',
|
||||
name: 'accessToken',
|
||||
type: 'string' as NodePropertyTypes,
|
||||
default: '',
|
||||
},
|
||||
{
|
||||
displayName: 'Homeserver URL',
|
||||
name: 'homeserverUrl',
|
||||
type: 'string' as NodePropertyTypes,
|
||||
default: 'https://matrix-client.matrix.org',
|
||||
},
|
||||
];
|
||||
}
|
||||
@@ -9,7 +9,7 @@ export class MessageBirdApi implements ICredentialType {
|
||||
displayName: 'API Key',
|
||||
name: 'accessKey',
|
||||
type: 'string' as NodePropertyTypes,
|
||||
default: ''
|
||||
}
|
||||
default: '',
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user