mirror of
https://github.com/n8n-io/n8n.git
synced 2024-12-25 12:44:07 -08:00
🔀 Merge branch 'master' into 'feature/n8n-public-api''
This commit is contained in:
commit
55c62b02f1
18
CHANGELOG.md
18
CHANGELOG.md
|
@ -1,3 +1,21 @@
|
|||
# [0.175.0](https://github.com/n8n-io/n8n/compare/n8n@0.174.0...n8n@0.175.0) (2022-05-02)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- **core:** Do not applying auth if UM is disabled ([#3218](https://github.com/n8n-io/n8n/issues/3218)) ([4ceac38](https://github.com/n8n-io/n8n/commit/4ceac38e0397be91bfc1b50d8a06ebf20de8c32e))
|
||||
- **core:** Skip credential check of disabled nodes and improve error ([79ced8f](https://github.com/n8n-io/n8n/commit/79ced8f6774cc70d63369001d7c56d1d4a340261))
|
||||
- **editor:** Fix bug with touchscreens ([#3206](https://github.com/n8n-io/n8n/issues/3206)) ([8d9e05e](https://github.com/n8n-io/n8n/commit/8d9e05e3c3ef61cd5a65ec00d3d1474f1195f653))
|
||||
- **Hubspot Node:** Fix search operators ([#3208](https://github.com/n8n-io/n8n/issues/3208)) ([ea4a8b8](https://github.com/n8n-io/n8n/commit/ea4a8b88c9bbfde3304073070a430f2421477921))
|
||||
- **Sendgrid Node:** Fix issue sending attachments ([#3213](https://github.com/n8n-io/n8n/issues/3213)) ([2b00881](https://github.com/n8n-io/n8n/commit/2b008815cad82619a66d4b30d1f79630c82be978))
|
||||
- **Wise Node:** Respect time parameter on get: exchangeRate ([#3227](https://github.com/n8n-io/n8n/issues/3227)) ([c7d525a](https://github.com/n8n-io/n8n/commit/c7d525a60fda4aad653017cb90253426cce98b3b))
|
||||
|
||||
### Features
|
||||
|
||||
- **core:** Introduce simplified node versioning ([#3205](https://github.com/n8n-io/n8n/issues/3205)) ([d5b9b0c](https://github.com/n8n-io/n8n/commit/d5b9b0cb9596688b3bcad0010b65888428c297c6))
|
||||
- **Google Sheets Node:** Allow to use header names as JSON path ([#3165](https://github.com/n8n-io/n8n/issues/3165)) ([770c4fe](https://github.com/n8n-io/n8n/commit/770c4fe6ebe70c7a507ee5e57348508b98fda11d))
|
||||
- **Microsoft Dynamics CRM Node:** Add support for other regions than North America ([#3157](https://github.com/n8n-io/n8n/issues/3157)) ([4bdd607](https://github.com/n8n-io/n8n/commit/4bdd607fdf00f6c2155c9b3e3c9e74ac50e317f4))
|
||||
- **Telegram Node:** Allow querying chat administrators ([#3226](https://github.com/n8n-io/n8n/issues/3226)) ([c02d259](https://github.com/n8n-io/n8n/commit/c02d259453f2f5b444569f4c1e06fbfc95cd3305)), closes [#3157](https://github.com/n8n-io/n8n/issues/3157)
|
||||
|
||||
# [0.174.0](https://github.com/n8n-io/n8n/compare/n8n@0.173.1...n8n@0.174.0) (2022-04-25)
|
||||
|
||||
### Bug Fixes
|
||||
|
|
4517
package-lock.json
generated
4517
package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "n8n",
|
||||
"version": "0.168.2",
|
||||
"version": "0.175.0",
|
||||
"private": true,
|
||||
"homepage": "https://n8n.io",
|
||||
"scripts": {
|
||||
|
|
|
@ -28,7 +28,7 @@ export class DbRevertMigrationCommand extends Command {
|
|||
let connection: Connection | undefined;
|
||||
try {
|
||||
await Db.init();
|
||||
connection = Db.collections.Credentials?.manager.connection;
|
||||
connection = Db.collections.Credentials.manager.connection;
|
||||
|
||||
if (!connection) {
|
||||
throw new Error(`No database connection available.`);
|
||||
|
|
|
@ -82,7 +82,7 @@ export class ImportWorkflowsCommand extends Command {
|
|||
|
||||
// Make sure the settings exist
|
||||
await UserSettings.prepareUserSettings();
|
||||
const credentials = (await Db.collections.Credentials?.find()) ?? [];
|
||||
const credentials = (await Db.collections.Credentials.find()) ?? [];
|
||||
|
||||
let totalImported = 0;
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "n8n",
|
||||
"version": "0.174.0",
|
||||
"version": "0.175.0",
|
||||
"description": "n8n Workflow Automation Tool",
|
||||
"license": "SEE LICENSE IN LICENSE.md",
|
||||
"homepage": "https://n8n.io",
|
||||
|
@ -130,10 +130,10 @@
|
|||
"lodash.get": "^4.4.2",
|
||||
"lodash.merge": "^4.6.2",
|
||||
"mysql2": "~2.3.0",
|
||||
"n8n-core": "~0.115.0",
|
||||
"n8n-editor-ui": "~0.141.0",
|
||||
"n8n-nodes-base": "~0.172.0",
|
||||
"n8n-workflow": "~0.97.0",
|
||||
"n8n-core": "~0.116.0",
|
||||
"n8n-editor-ui": "~0.142.0",
|
||||
"n8n-nodes-base": "~0.173.0",
|
||||
"n8n-workflow": "~0.98.0",
|
||||
"nodemailer": "^6.7.1",
|
||||
"oauth-1.0a": "^2.2.6",
|
||||
"open": "^7.0.0",
|
||||
|
|
|
@ -70,9 +70,7 @@ export class ActiveExecutions {
|
|||
|
||||
const execution = ResponseHelper.flattenExecutionData(fullExecutionData);
|
||||
|
||||
const executionResult = await Db.collections.Execution!.save(
|
||||
execution as IExecutionFlattedDb,
|
||||
);
|
||||
const executionResult = await Db.collections.Execution.save(execution as IExecutionFlattedDb);
|
||||
executionId =
|
||||
typeof executionResult.id === 'object'
|
||||
? // @ts-ignore
|
||||
|
@ -87,8 +85,7 @@ export class ActiveExecutions {
|
|||
waitTill: null,
|
||||
};
|
||||
|
||||
// @ts-ignore
|
||||
await Db.collections.Execution!.update(executionId, execution);
|
||||
await Db.collections.Execution.update(executionId, execution);
|
||||
}
|
||||
|
||||
// @ts-ignore
|
||||
|
|
|
@ -83,7 +83,7 @@ export class ActiveWorkflowRunner {
|
|||
// This is not officially supported but there is no reason
|
||||
// it should not work.
|
||||
// Clear up active workflow table
|
||||
await Db.collections.Webhook?.clear();
|
||||
await Db.collections.Webhook.clear();
|
||||
}
|
||||
|
||||
this.activeWorkflows = new ActiveWorkflows();
|
||||
|
@ -189,7 +189,7 @@ export class ActiveWorkflowRunner {
|
|||
path = path.slice(0, -1);
|
||||
}
|
||||
|
||||
let webhook = (await Db.collections.Webhook?.findOne({
|
||||
let webhook = (await Db.collections.Webhook.findOne({
|
||||
webhookPath: path,
|
||||
method: httpMethod,
|
||||
})) as IWebhookDb;
|
||||
|
@ -200,7 +200,7 @@ export class ActiveWorkflowRunner {
|
|||
// check if a dynamic webhook path exists
|
||||
const pathElements = path.split('/');
|
||||
webhookId = pathElements.shift();
|
||||
const dynamicWebhooks = await Db.collections.Webhook?.find({
|
||||
const dynamicWebhooks = await Db.collections.Webhook.find({
|
||||
webhookId,
|
||||
method: httpMethod,
|
||||
pathLength: pathElements.length,
|
||||
|
@ -332,7 +332,7 @@ export class ActiveWorkflowRunner {
|
|||
* @memberof ActiveWorkflowRunner
|
||||
*/
|
||||
async getWebhookMethods(path: string): Promise<string[]> {
|
||||
const webhooks = await Db.collections.Webhook?.find({ webhookPath: path });
|
||||
const webhooks = await Db.collections.Webhook.find({ webhookPath: path });
|
||||
|
||||
// Gather all request methods in string array
|
||||
const webhookMethods: string[] = webhooks.map((webhook) => webhook.method);
|
||||
|
@ -443,7 +443,7 @@ export class ActiveWorkflowRunner {
|
|||
|
||||
try {
|
||||
// eslint-disable-next-line no-await-in-loop
|
||||
await Db.collections.Webhook?.insert(webhook);
|
||||
await Db.collections.Webhook.insert(webhook);
|
||||
const webhookExists = await workflow.runWebhookMethod(
|
||||
'checkExists',
|
||||
webhookData,
|
||||
|
@ -556,7 +556,7 @@ export class ActiveWorkflowRunner {
|
|||
workflowId: workflowData.id,
|
||||
} as IWebhookDb;
|
||||
|
||||
await Db.collections.Webhook?.delete(webhook);
|
||||
await Db.collections.Webhook.delete(webhook);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -342,6 +342,7 @@ export class CredentialsHelper extends ICredentialsHelper {
|
|||
decryptedDataOriginal as INodeParameters,
|
||||
true,
|
||||
false,
|
||||
null,
|
||||
) as ICredentialDataDecryptedObject;
|
||||
|
||||
if (decryptedDataOriginal.oauthTokenData !== undefined) {
|
||||
|
@ -436,8 +437,6 @@ export class CredentialsHelper extends ICredentialsHelper {
|
|||
// Add special database related data
|
||||
newCredentialsData.updatedAt = new Date();
|
||||
|
||||
// TODO: also add user automatically depending on who is logged in, if anybody is logged in
|
||||
|
||||
// Save the credentials in DB
|
||||
const findQuery = {
|
||||
id: credentials.id,
|
||||
|
@ -562,7 +561,9 @@ export class CredentialsHelper extends ICredentialsHelper {
|
|||
parameters: {},
|
||||
name: 'Temp-Node',
|
||||
type: nodeType.description.name,
|
||||
typeVersion: nodeType.description.version,
|
||||
typeVersion: Array.isArray(nodeType.description.version)
|
||||
? nodeType.description.version.slice(-1)[0]
|
||||
: nodeType.description.version,
|
||||
position: [0, 0],
|
||||
};
|
||||
|
||||
|
|
|
@ -35,15 +35,12 @@ import { readFile } from 'fs/promises';
|
|||
import _, { cloneDeep } from 'lodash';
|
||||
import { dirname as pathDirname, join as pathJoin, resolve as pathResolve } from 'path';
|
||||
import {
|
||||
FindConditions,
|
||||
FindManyOptions,
|
||||
getConnection,
|
||||
getConnectionManager,
|
||||
In,
|
||||
IsNull,
|
||||
LessThan,
|
||||
LessThanOrEqual,
|
||||
MoreThan,
|
||||
Not,
|
||||
Raw,
|
||||
} from 'typeorm';
|
||||
|
@ -61,13 +58,7 @@ import { createHmac, randomBytes } from 'crypto';
|
|||
// tested with all possible systems like Windows, Alpine on ARM, FreeBSD, ...
|
||||
import { compare } from 'bcryptjs';
|
||||
|
||||
import {
|
||||
BinaryDataManager,
|
||||
Credentials,
|
||||
IBinaryDataConfig,
|
||||
LoadNodeParameterOptions,
|
||||
UserSettings,
|
||||
} from 'n8n-core';
|
||||
import { BinaryDataManager, Credentials, LoadNodeParameterOptions, UserSettings } from 'n8n-core';
|
||||
|
||||
import {
|
||||
ICredentialType,
|
||||
|
@ -154,7 +145,6 @@ import { WEBHOOK_METHODS } from './WebhookHelpers';
|
|||
import { userManagementRouter } from './UserManagement';
|
||||
import { resolveJwt } from './UserManagement/auth/jwt';
|
||||
import { User } from './databases/entities/User';
|
||||
import { CredentialsEntity } from './databases/entities/CredentialsEntity';
|
||||
import type {
|
||||
AuthenticatedRequest,
|
||||
CredentialRequest,
|
||||
|
@ -169,7 +159,11 @@ import { ExecutionEntity } from './databases/entities/ExecutionEntity';
|
|||
import { SharedWorkflow } from './databases/entities/SharedWorkflow';
|
||||
import { AUTH_COOKIE_NAME, RESPONSE_ERROR_MESSAGES } from './constants';
|
||||
import { credentialsController } from './api/credentials.api';
|
||||
import { getInstanceBaseUrl, isEmailSetUp } from './UserManagement/UserManagementHelper';
|
||||
import {
|
||||
getInstanceBaseUrl,
|
||||
isEmailSetUp,
|
||||
isUserManagementEnabled,
|
||||
} from './UserManagement/UserManagementHelper';
|
||||
import { loadPublicApiVersions } from './PublicApi';
|
||||
|
||||
require('body-parser-xml')(bodyParser);
|
||||
|
@ -315,9 +309,7 @@ class App {
|
|||
config.getEnv('personalization.enabled') && config.getEnv('diagnostics.enabled'),
|
||||
defaultLocale: config.getEnv('defaultLocale'),
|
||||
userManagement: {
|
||||
enabled:
|
||||
config.getEnv('userManagement.disabled') === false ||
|
||||
config.getEnv('userManagement.isInstanceOwnerSetUp') === true,
|
||||
enabled: isUserManagementEnabled(),
|
||||
showSetupOnFirstLoad:
|
||||
config.getEnv('userManagement.disabled') === false &&
|
||||
config.getEnv('userManagement.isInstanceOwnerSetUp') === false &&
|
||||
|
@ -353,9 +345,7 @@ class App {
|
|||
getSettingsForFrontend(): IN8nUISettings {
|
||||
// refresh user management status
|
||||
Object.assign(this.frontendSettings.userManagement, {
|
||||
enabled:
|
||||
config.getEnv('userManagement.disabled') === false ||
|
||||
config.getEnv('userManagement.isInstanceOwnerSetUp') === true,
|
||||
enabled: isUserManagementEnabled(),
|
||||
showSetupOnFirstLoad:
|
||||
config.getEnv('userManagement.disabled') === false &&
|
||||
config.getEnv('userManagement.isInstanceOwnerSetUp') === false &&
|
||||
|
@ -601,6 +591,7 @@ class App {
|
|||
return;
|
||||
}
|
||||
|
||||
if (isUserManagementEnabled()) {
|
||||
try {
|
||||
const authCookie = req.cookies?.[AUTH_COOKIE_NAME] ?? '';
|
||||
await resolveJwt(authCookie);
|
||||
|
@ -608,6 +599,7 @@ class App {
|
|||
res.status(401).send('Unauthorized');
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
this.push.add(req.query.sessionId as string, req, res);
|
||||
return;
|
||||
|
@ -790,7 +782,7 @@ class App {
|
|||
const { tags: tagIds } = req.body;
|
||||
|
||||
if (tagIds?.length && !config.getEnv('workflowTagsDisabled')) {
|
||||
newWorkflow.tags = await Db.collections.Tag!.findByIds(tagIds, {
|
||||
newWorkflow.tags = await Db.collections.Tag.findByIds(tagIds, {
|
||||
select: ['id', 'name'],
|
||||
});
|
||||
}
|
||||
|
@ -802,7 +794,7 @@ class App {
|
|||
await getConnection().transaction(async (transactionManager) => {
|
||||
savedWorkflow = await transactionManager.save<WorkflowEntity>(newWorkflow);
|
||||
|
||||
const role = await Db.collections.Role!.findOneOrFail({
|
||||
const role = await Db.collections.Role.findOneOrFail({
|
||||
name: 'owner',
|
||||
scope: 'workflow',
|
||||
});
|
||||
|
@ -912,13 +904,13 @@ class App {
|
|||
}
|
||||
|
||||
if (req.user.globalRole.name === 'owner') {
|
||||
workflows = await Db.collections.Workflow!.find(
|
||||
workflows = await Db.collections.Workflow.find(
|
||||
Object.assign(query, {
|
||||
where: filter,
|
||||
}),
|
||||
);
|
||||
} else {
|
||||
const shared = await Db.collections.SharedWorkflow!.find({
|
||||
const shared = await Db.collections.SharedWorkflow.find({
|
||||
relations: ['workflow'],
|
||||
where: whereClause({
|
||||
user: req.user,
|
||||
|
@ -928,7 +920,7 @@ class App {
|
|||
|
||||
if (!shared.length) return [];
|
||||
|
||||
workflows = await Db.collections.Workflow!.find(
|
||||
workflows = await Db.collections.Workflow.find(
|
||||
Object.assign(query, {
|
||||
where: {
|
||||
id: In(shared.map(({ workflow }) => workflow.id)),
|
||||
|
@ -971,7 +963,7 @@ class App {
|
|||
relations = relations.filter((relation) => relation !== 'workflow.tags');
|
||||
}
|
||||
|
||||
const shared = await Db.collections.SharedWorkflow!.findOne({
|
||||
const shared = await Db.collections.SharedWorkflow.findOne({
|
||||
relations,
|
||||
where: whereClause({
|
||||
user: req.user,
|
||||
|
@ -1013,7 +1005,7 @@ class App {
|
|||
const { tags, ...rest } = req.body;
|
||||
Object.assign(updateData, rest);
|
||||
|
||||
const shared = await Db.collections.SharedWorkflow!.findOne({
|
||||
const shared = await Db.collections.SharedWorkflow.findOne({
|
||||
relations: ['workflow'],
|
||||
where: whereClause({
|
||||
user: req.user,
|
||||
|
@ -1075,7 +1067,7 @@ class App {
|
|||
await validateEntity(updateData);
|
||||
}
|
||||
|
||||
await Db.collections.Workflow!.update(workflowId, updateData);
|
||||
await Db.collections.Workflow.update(workflowId, updateData);
|
||||
|
||||
if (tags && !config.getEnv('workflowTagsDisabled')) {
|
||||
const tablePrefix = config.getEnv('database.tablePrefix');
|
||||
|
@ -1096,7 +1088,7 @@ class App {
|
|||
|
||||
// We sadly get nothing back from "update". Neither if it updated a record
|
||||
// nor the new value. So query now the hopefully updated entry.
|
||||
const updatedWorkflow = await Db.collections.Workflow!.findOne(workflowId, options);
|
||||
const updatedWorkflow = await Db.collections.Workflow.findOne(workflowId, options);
|
||||
|
||||
if (updatedWorkflow === undefined) {
|
||||
throw new ResponseHelper.ResponseError(
|
||||
|
@ -1113,7 +1105,6 @@ class App {
|
|||
}
|
||||
|
||||
await this.externalHooks.run('workflow.afterUpdate', [updatedWorkflow]);
|
||||
// @ts-ignore
|
||||
void InternalHooksManager.getInstance().onWorkflowSaved(req.user.id, updatedWorkflow);
|
||||
|
||||
if (updatedWorkflow.active) {
|
||||
|
@ -1127,8 +1118,7 @@ class App {
|
|||
} catch (error) {
|
||||
// If workflow could not be activated set it again to inactive
|
||||
updateData.active = false;
|
||||
// @ts-ignore
|
||||
await Db.collections.Workflow!.update(workflowId, updateData);
|
||||
await Db.collections.Workflow.update(workflowId, updateData);
|
||||
|
||||
// Also set it in the returned data
|
||||
updatedWorkflow.active = false;
|
||||
|
@ -1155,7 +1145,7 @@ class App {
|
|||
|
||||
await this.externalHooks.run('workflow.delete', [workflowId]);
|
||||
|
||||
const shared = await Db.collections.SharedWorkflow!.findOne({
|
||||
const shared = await Db.collections.SharedWorkflow.findOne({
|
||||
relations: ['workflow'],
|
||||
where: whereClause({
|
||||
user: req.user,
|
||||
|
@ -1181,7 +1171,7 @@ class App {
|
|||
await this.activeWorkflowRunner.remove(workflowId);
|
||||
}
|
||||
|
||||
await Db.collections.Workflow!.delete(workflowId);
|
||||
await Db.collections.Workflow.delete(workflowId);
|
||||
|
||||
void InternalHooksManager.getInstance().onWorkflowDeleted(req.user.id, workflowId);
|
||||
await this.externalHooks.run('workflow.afterDelete', [workflowId]);
|
||||
|
@ -1280,7 +1270,7 @@ class App {
|
|||
return TagHelpers.getTagsWithCountDb(tablePrefix);
|
||||
}
|
||||
|
||||
return Db.collections.Tag!.find({ select: ['id', 'name'] });
|
||||
return Db.collections.Tag.find({ select: ['id', 'name'] });
|
||||
},
|
||||
),
|
||||
);
|
||||
|
@ -1299,7 +1289,7 @@ class App {
|
|||
await this.externalHooks.run('tag.beforeCreate', [newTag]);
|
||||
|
||||
await validateEntity(newTag);
|
||||
const tag = await Db.collections.Tag!.save(newTag);
|
||||
const tag = await Db.collections.Tag.save(newTag);
|
||||
|
||||
await this.externalHooks.run('tag.afterCreate', [tag]);
|
||||
|
||||
|
@ -1328,7 +1318,7 @@ class App {
|
|||
await this.externalHooks.run('tag.beforeUpdate', [newTag]);
|
||||
|
||||
await validateEntity(newTag);
|
||||
const tag = await Db.collections.Tag!.save(newTag);
|
||||
const tag = await Db.collections.Tag.save(newTag);
|
||||
|
||||
await this.externalHooks.run('tag.afterUpdate', [tag]);
|
||||
|
||||
|
@ -1360,7 +1350,7 @@ class App {
|
|||
|
||||
await this.externalHooks.run('tag.beforeDelete', [id]);
|
||||
|
||||
await Db.collections.Tag!.delete({ id });
|
||||
await Db.collections.Tag.delete({ id });
|
||||
|
||||
await this.externalHooks.run('tag.afterDelete', [id]);
|
||||
|
||||
|
@ -1625,7 +1615,7 @@ class App {
|
|||
ResponseHelper.send(async (req: WorkflowRequest.GetAllActivationErrors) => {
|
||||
const { id: workflowId } = req.params;
|
||||
|
||||
const shared = await Db.collections.SharedWorkflow!.findOne({
|
||||
const shared = await Db.collections.SharedWorkflow.findOne({
|
||||
relations: ['workflow'],
|
||||
where: whereClause({
|
||||
user: req.user,
|
||||
|
@ -1828,7 +1818,7 @@ class App {
|
|||
newCredentialsData.updatedAt = this.getCurrentDate();
|
||||
|
||||
// Update the credentials in DB
|
||||
await Db.collections.Credentials!.update(credentialId, newCredentialsData);
|
||||
await Db.collections.Credentials.update(credentialId, newCredentialsData);
|
||||
|
||||
LoggerProxy.verbose('OAuth1 authorization successful for new credential', {
|
||||
userId: req.user.id,
|
||||
|
@ -1945,7 +1935,7 @@ class App {
|
|||
// Add special database related data
|
||||
newCredentialsData.updatedAt = this.getCurrentDate();
|
||||
// Save the credentials in DB
|
||||
await Db.collections.Credentials!.update(credentialId, newCredentialsData);
|
||||
await Db.collections.Credentials.update(credentialId, newCredentialsData);
|
||||
|
||||
LoggerProxy.verbose('OAuth1 callback successful for new credential', {
|
||||
userId: req.user?.id,
|
||||
|
@ -2060,7 +2050,7 @@ class App {
|
|||
newCredentialsData.updatedAt = this.getCurrentDate();
|
||||
|
||||
// Update the credentials in DB
|
||||
await Db.collections.Credentials!.update(req.query.id as string, newCredentialsData);
|
||||
await Db.collections.Credentials.update(req.query.id as string, newCredentialsData);
|
||||
|
||||
const authQueryParameters = _.get(oauthCredentials, 'authQueryParameters', '') as string;
|
||||
let returnUri = oAuthObj.code.getUri();
|
||||
|
@ -2247,7 +2237,7 @@ class App {
|
|||
// Add special database related data
|
||||
newCredentialsData.updatedAt = this.getCurrentDate();
|
||||
// Save the credentials in DB
|
||||
await Db.collections.Credentials!.update(state.cid, newCredentialsData);
|
||||
await Db.collections.Credentials.update(state.cid, newCredentialsData);
|
||||
LoggerProxy.verbose('OAuth2 callback successful for new credential', {
|
||||
userId: req.user?.id,
|
||||
credentialId: state.cid,
|
||||
|
@ -2353,7 +2343,7 @@ class App {
|
|||
});
|
||||
}
|
||||
|
||||
const executions = await Db.collections.Execution!.find(findOptions);
|
||||
const executions = await Db.collections.Execution.find(findOptions);
|
||||
|
||||
const { count, estimated } = await getExecutionsCount(countFilter, req.user);
|
||||
|
||||
|
@ -2394,7 +2384,7 @@ class App {
|
|||
|
||||
if (!sharedWorkflowIds.length) return undefined;
|
||||
|
||||
const execution = await Db.collections.Execution!.findOne({
|
||||
const execution = await Db.collections.Execution.findOne({
|
||||
where: {
|
||||
id: executionId,
|
||||
workflowId: In(sharedWorkflowIds),
|
||||
|
@ -2437,7 +2427,7 @@ class App {
|
|||
|
||||
if (!sharedWorkflowIds.length) return false;
|
||||
|
||||
const execution = await Db.collections.Execution!.findOne({
|
||||
const execution = await Db.collections.Execution.findOne({
|
||||
where: {
|
||||
id: executionId,
|
||||
workflowId: In(sharedWorkflowIds),
|
||||
|
@ -2499,9 +2489,7 @@ class App {
|
|||
// Loads the currently saved workflow to execute instead of the
|
||||
// one saved at the time of the execution.
|
||||
const workflowId = fullExecutionData.workflowData.id;
|
||||
const workflowData = (await Db.collections.Workflow!.findOne(
|
||||
workflowId,
|
||||
)) as IWorkflowBase;
|
||||
const workflowData = (await Db.collections.Workflow.findOne(workflowId)) as IWorkflowBase;
|
||||
|
||||
if (workflowData === undefined) {
|
||||
throw new Error(
|
||||
|
@ -2583,7 +2571,7 @@ class App {
|
|||
Object.assign(filters, requestFilters);
|
||||
}
|
||||
|
||||
const executions = await Db.collections.Execution!.find({
|
||||
const executions = await Db.collections.Execution.find({
|
||||
where: {
|
||||
workflowId: In(sharedWorkflowIds),
|
||||
...filters,
|
||||
|
@ -2598,7 +2586,7 @@ class App {
|
|||
idsToDelete.map(async (id) => binaryDataManager.deleteBinaryDataByExecutionId(id)),
|
||||
);
|
||||
|
||||
await Db.collections.Execution!.delete({ id: In(idsToDelete) });
|
||||
await Db.collections.Execution.delete({ id: In(idsToDelete) });
|
||||
|
||||
return;
|
||||
}
|
||||
|
@ -2606,7 +2594,7 @@ class App {
|
|||
// delete executions by IDs, if user may access the underyling worfklows
|
||||
|
||||
if (ids) {
|
||||
const executions = await Db.collections.Execution!.find({
|
||||
const executions = await Db.collections.Execution.find({
|
||||
where: {
|
||||
id: In(ids),
|
||||
workflowId: In(sharedWorkflowIds),
|
||||
|
@ -2627,7 +2615,7 @@ class App {
|
|||
idsToDelete.map(async (id) => binaryDataManager.deleteBinaryDataByExecutionId(id)),
|
||||
);
|
||||
|
||||
await Db.collections.Execution!.delete(idsToDelete);
|
||||
await Db.collections.Execution.delete(idsToDelete);
|
||||
}
|
||||
}),
|
||||
);
|
||||
|
@ -2678,7 +2666,7 @@ class App {
|
|||
Object.assign(findOptions.where, { workflowId: In(sharedWorkflowIds) });
|
||||
}
|
||||
|
||||
const executions = await Db.collections.Execution!.find(findOptions);
|
||||
const executions = await Db.collections.Execution.find(findOptions);
|
||||
|
||||
if (!executions.length) return [];
|
||||
|
||||
|
@ -2739,7 +2727,7 @@ class App {
|
|||
throw new ResponseHelper.ResponseError('Execution not found', undefined, 404);
|
||||
}
|
||||
|
||||
const execution = await Db.collections.Execution!.findOne({
|
||||
const execution = await Db.collections.Execution.findOne({
|
||||
where: {
|
||||
id: executionId,
|
||||
workflowId: In(sharedWorkflowIds),
|
||||
|
@ -2782,7 +2770,7 @@ class App {
|
|||
await Queue.getInstance().stopJob(job);
|
||||
}
|
||||
|
||||
const executionDb = (await Db.collections.Execution?.findOne(
|
||||
const executionDb = (await Db.collections.Execution.findOne(
|
||||
req.params.id,
|
||||
)) as IExecutionFlattedDb;
|
||||
const fullExecutionData = ResponseHelper.unflattenExecutionData(executionDb);
|
||||
|
@ -3079,9 +3067,7 @@ export async function start(): Promise<void> {
|
|||
},
|
||||
deploymentType: config.getEnv('deployment.type'),
|
||||
binaryDataMode: binarDataConfig.mode,
|
||||
n8n_multi_user_allowed:
|
||||
config.getEnv('userManagement.disabled') === false ||
|
||||
config.getEnv('userManagement.isInstanceOwnerSetUp') === true,
|
||||
n8n_multi_user_allowed: isUserManagementEnabled(),
|
||||
smtp_set_up: config.getEnv('userManagement.emails.mode') === 'smtp',
|
||||
};
|
||||
|
||||
|
@ -3117,7 +3103,7 @@ async function getExecutionsCount(
|
|||
if (dbType !== 'postgresdb' || filteredFields.length > 0 || user.globalRole.name !== 'owner') {
|
||||
const sharedWorkflowIds = await getSharedWorkflowIds(user);
|
||||
|
||||
const count = await Db.collections.Execution!.count({
|
||||
const count = await Db.collections.Execution.count({
|
||||
where: {
|
||||
workflowId: In(sharedWorkflowIds),
|
||||
...countFilter,
|
||||
|
@ -3131,7 +3117,7 @@ async function getExecutionsCount(
|
|||
// Get an estimate of rows count.
|
||||
const estimateRowsNumberSql =
|
||||
"SELECT n_live_tup FROM pg_stat_all_tables WHERE relname = 'execution_entity';";
|
||||
const rows: Array<{ n_live_tup: string }> = await Db.collections.Execution!.query(
|
||||
const rows: Array<{ n_live_tup: string }> = await Db.collections.Execution.query(
|
||||
estimateRowsNumberSql,
|
||||
);
|
||||
|
||||
|
@ -3148,7 +3134,7 @@ async function getExecutionsCount(
|
|||
|
||||
const sharedWorkflowIds = await getSharedWorkflowIds(user);
|
||||
|
||||
const count = await Db.collections.Execution!.count({
|
||||
const count = await Db.collections.Execution.count({
|
||||
where: {
|
||||
workflowId: In(sharedWorkflowIds),
|
||||
},
|
||||
|
|
|
@ -32,6 +32,20 @@ export function isEmailSetUp(): boolean {
|
|||
return smtp && host && user && pass;
|
||||
}
|
||||
|
||||
export function isUserManagementEnabled(): boolean {
|
||||
return (
|
||||
!config.getEnv('userManagement.disabled') ||
|
||||
config.getEnv('userManagement.isInstanceOwnerSetUp')
|
||||
);
|
||||
}
|
||||
|
||||
export function isUserManagementDisabled(): boolean {
|
||||
return (
|
||||
config.getEnv('userManagement.disabled') &&
|
||||
!config.getEnv('userManagement.isInstanceOwnerSetUp')
|
||||
);
|
||||
}
|
||||
|
||||
async function getInstanceOwnerRole(): Promise<Role> {
|
||||
const ownerRole = await Db.collections.Role.findOneOrFail({
|
||||
where: {
|
||||
|
@ -136,6 +150,10 @@ export async function checkPermissionsForExecution(
|
|||
// Iterate over all nodes
|
||||
nodeNames.forEach((nodeName) => {
|
||||
const node = workflow.nodes[nodeName];
|
||||
if (node.disabled === true) {
|
||||
// If a node is disabled there is no need to check its credentials
|
||||
return;
|
||||
}
|
||||
// And check if any of the nodes uses credentials.
|
||||
if (node.credentials) {
|
||||
const credentialNames = Object.keys(node.credentials);
|
||||
|
@ -146,9 +164,14 @@ export async function checkPermissionsForExecution(
|
|||
// workflow. Nowaways it should not happen anymore.
|
||||
// Migrations should handle the case where a credential does
|
||||
// not have an id.
|
||||
if (credentialDetail.id === null) {
|
||||
throw new Error(
|
||||
`The credential on node '${node.name}' is not valid. Please open the workflow and set it to a valid value.`,
|
||||
);
|
||||
}
|
||||
if (!credentialDetail.id) {
|
||||
throw new Error(
|
||||
'Error initializing workflow: credential ID not present. Please open the workflow and save it to fix this error.',
|
||||
`Error initializing workflow: credential ID not present. Please open the workflow and save it to fix this error. [Node: '${node.name}']`,
|
||||
);
|
||||
}
|
||||
credentialIds.add(credentialDetail.id.toString());
|
||||
|
|
|
@ -19,7 +19,13 @@ import { usersNamespace } from './users';
|
|||
import { passwordResetNamespace } from './passwordReset';
|
||||
import { AuthenticatedRequest } from '../../requests';
|
||||
import { ownerNamespace } from './owner';
|
||||
import { isAuthExcluded, isPostUsersId, isAuthenticatedRequest } from '../UserManagementHelper';
|
||||
import {
|
||||
isAuthExcluded,
|
||||
isPostUsersId,
|
||||
isAuthenticatedRequest,
|
||||
isUserManagementDisabled,
|
||||
} from '../UserManagementHelper';
|
||||
import { Db } from '../..';
|
||||
|
||||
export function addRoutes(this: N8nApp, ignoredEndpoints: string[], restEndpoint: string): void {
|
||||
// needed for testing; not adding overhead since it directly returns if req.cookies exists
|
||||
|
@ -47,7 +53,7 @@ export function addRoutes(this: N8nApp, ignoredEndpoints: string[], restEndpoint
|
|||
|
||||
this.app.use(passport.initialize());
|
||||
|
||||
this.app.use((req: Request, res: Response, next: NextFunction) => {
|
||||
this.app.use(async (req: Request, res: Response, next: NextFunction) => {
|
||||
if (
|
||||
// TODO: refactor me!!!
|
||||
// skip authentication for preflight requests
|
||||
|
@ -73,6 +79,17 @@ export function addRoutes(this: N8nApp, ignoredEndpoints: string[], restEndpoint
|
|||
return next();
|
||||
}
|
||||
|
||||
// skip authentication if user management is disabled
|
||||
if (isUserManagementDisabled()) {
|
||||
req.user = await Db.collections.User.findOneOrFail(
|
||||
{},
|
||||
{
|
||||
relations: ['globalRole'],
|
||||
},
|
||||
);
|
||||
return next();
|
||||
}
|
||||
|
||||
return passport.authenticate('jwt', { session: false })(req, res, next);
|
||||
});
|
||||
|
||||
|
|
|
@ -12,6 +12,7 @@ import {
|
|||
getInstanceBaseUrl,
|
||||
hashPassword,
|
||||
isEmailSetUp,
|
||||
isUserManagementDisabled,
|
||||
sanitizeUser,
|
||||
validatePassword,
|
||||
} from '../UserManagementHelper';
|
||||
|
@ -55,7 +56,7 @@ export function usersNamespace(this: N8nApp): void {
|
|||
}
|
||||
|
||||
// TODO: this should be checked in the middleware rather than here
|
||||
if (config.getEnv('userManagement.disabled')) {
|
||||
if (isUserManagementDisabled()) {
|
||||
Logger.debug(
|
||||
'Request to send email invite(s) to user(s) failed because user management is disabled',
|
||||
);
|
||||
|
|
|
@ -50,7 +50,7 @@ export class WaitingWebhooks {
|
|||
const executionId = pathParts.shift();
|
||||
const path = pathParts.join('/');
|
||||
|
||||
const execution = await Db.collections.Execution?.findOne(executionId);
|
||||
const execution = await Db.collections.Execution.findOne(executionId);
|
||||
|
||||
if (execution === undefined) {
|
||||
throw new ResponseHelper.ResponseError(
|
||||
|
|
|
@ -584,7 +584,7 @@ function hookFunctionsSave(parentProcessMode?: string): IWorkflowExecuteHooks {
|
|||
|
||||
if (fullRunData.finished === true && this.retryOf !== undefined) {
|
||||
// If the retry was successful save the reference it on the original execution
|
||||
// await Db.collections.Execution!.save(executionData as IExecutionFlattedDb);
|
||||
// await Db.collections.Execution.save(executionData as IExecutionFlattedDb);
|
||||
await Db.collections.Execution.update(this.retryOf, {
|
||||
retrySuccessId: this.executionId,
|
||||
});
|
||||
|
|
|
@ -479,7 +479,7 @@ export async function replaceInvalidCredentials(workflow: WorkflowEntity): Promi
|
|||
credentialsByName[nodeCredentialType] = {};
|
||||
}
|
||||
if (credentialsByName[nodeCredentialType][name] === undefined) {
|
||||
const credentials = await Db.collections.Credentials?.find({
|
||||
const credentials = await Db.collections.Credentials.find({
|
||||
name,
|
||||
type: nodeCredentialType,
|
||||
});
|
||||
|
@ -515,7 +515,7 @@ export async function replaceInvalidCredentials(workflow: WorkflowEntity): Promi
|
|||
// check if credentials for ID-type are not yet cached
|
||||
if (credentialsById[nodeCredentialType][nodeCredentials.id] === undefined) {
|
||||
// check first if ID-type combination exists
|
||||
const credentials = await Db.collections.Credentials?.findOne({
|
||||
const credentials = await Db.collections.Credentials.findOne({
|
||||
id: nodeCredentials.id,
|
||||
type: nodeCredentialType,
|
||||
});
|
||||
|
@ -529,7 +529,7 @@ export async function replaceInvalidCredentials(workflow: WorkflowEntity): Promi
|
|||
continue;
|
||||
}
|
||||
// no credentials found for ID, check if some exist for name
|
||||
const credsByName = await Db.collections.Credentials?.find({
|
||||
const credsByName = await Db.collections.Credentials.find({
|
||||
name: nodeCredentials.name,
|
||||
type: nodeCredentialType,
|
||||
});
|
||||
|
|
|
@ -33,7 +33,7 @@ beforeEach(async () => {
|
|||
|
||||
config.set('userManagement.isInstanceOwnerSetUp', true);
|
||||
|
||||
await Db.collections.Settings!.update(
|
||||
await Db.collections.Settings.update(
|
||||
{ key: 'userManagement.isInstanceOwnerSetUp' },
|
||||
{ value: JSON.stringify(true) },
|
||||
);
|
||||
|
@ -102,7 +102,7 @@ test('GET /login should return cookie if UM is disabled', async () => {
|
|||
|
||||
config.set('userManagement.isInstanceOwnerSetUp', false);
|
||||
|
||||
await Db.collections.Settings!.update(
|
||||
await Db.collections.Settings.update(
|
||||
{ key: 'userManagement.isInstanceOwnerSetUp' },
|
||||
{ value: JSON.stringify(false) },
|
||||
);
|
||||
|
|
|
@ -1,157 +0,0 @@
|
|||
import express from 'express';
|
||||
import validator from 'validator';
|
||||
import { v4 as uuid } from 'uuid';
|
||||
|
||||
import * as config from '../../config';
|
||||
import * as utils from './shared/utils';
|
||||
import { LOGGED_OUT_RESPONSE_BODY } from './shared/constants';
|
||||
import { Db } from '../../src';
|
||||
import { Role } from '../../src/databases/entities/Role';
|
||||
import { randomEmail, randomValidPassword, randomName } from './shared/random';
|
||||
import { getGlobalOwnerRole } from './shared/testDb';
|
||||
import * as testDb from './shared/testDb';
|
||||
|
||||
jest.mock('../../src/telemetry');
|
||||
|
||||
let globalOwnerRole: Role;
|
||||
|
||||
let app: express.Application;
|
||||
let testDbName = '';
|
||||
|
||||
beforeAll(async () => {
|
||||
app = await utils.initTestServer({ endpointGroups: ['auth'], applyAuth: true });
|
||||
const initResult = await testDb.init();
|
||||
testDbName = initResult.testDbName;
|
||||
|
||||
await testDb.truncate(['User'], testDbName);
|
||||
|
||||
globalOwnerRole = await getGlobalOwnerRole();
|
||||
utils.initTestLogger();
|
||||
utils.initTestTelemetry();
|
||||
});
|
||||
|
||||
beforeEach(async () => {
|
||||
await testDb.createUser({
|
||||
id: uuid(),
|
||||
email: TEST_USER.email,
|
||||
firstName: TEST_USER.firstName,
|
||||
lastName: TEST_USER.lastName,
|
||||
password: TEST_USER.password,
|
||||
globalRole: globalOwnerRole,
|
||||
});
|
||||
|
||||
config.set('userManagement.isInstanceOwnerSetUp', true);
|
||||
|
||||
await Db.collections.Settings!.update(
|
||||
{ key: 'userManagement.isInstanceOwnerSetUp' },
|
||||
{ value: JSON.stringify(true) },
|
||||
);
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await testDb.truncate(['User'], testDbName);
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
await testDb.terminate(testDbName);
|
||||
});
|
||||
|
||||
test('POST /login should log user in', async () => {
|
||||
const authlessAgent = utils.createAgent(app);
|
||||
|
||||
await Promise.all(
|
||||
[
|
||||
{
|
||||
email: TEST_USER.email,
|
||||
password: TEST_USER.password,
|
||||
},
|
||||
{
|
||||
email: TEST_USER.email.toUpperCase(),
|
||||
password: TEST_USER.password,
|
||||
},
|
||||
].map(async (payload) => {
|
||||
const response = await authlessAgent.post('/login').send(payload);
|
||||
|
||||
expect(response.statusCode).toBe(200);
|
||||
|
||||
const {
|
||||
id,
|
||||
email,
|
||||
firstName,
|
||||
lastName,
|
||||
password,
|
||||
personalizationAnswers,
|
||||
globalRole,
|
||||
resetPasswordToken,
|
||||
} = response.body.data;
|
||||
|
||||
expect(validator.isUUID(id)).toBe(true);
|
||||
expect(email).toBe(TEST_USER.email);
|
||||
expect(firstName).toBe(TEST_USER.firstName);
|
||||
expect(lastName).toBe(TEST_USER.lastName);
|
||||
expect(password).toBeUndefined();
|
||||
expect(personalizationAnswers).toBeNull();
|
||||
expect(resetPasswordToken).toBeUndefined();
|
||||
expect(globalRole).toBeDefined();
|
||||
expect(globalRole.name).toBe('owner');
|
||||
expect(globalRole.scope).toBe('global');
|
||||
|
||||
const authToken = utils.getAuthToken(response);
|
||||
expect(authToken).toBeDefined();
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
test('GET /login should receive logged in user', async () => {
|
||||
const owner = await Db.collections.User!.findOneOrFail();
|
||||
const authOwnerAgent = utils.createAgent(app, { auth: true, user: owner });
|
||||
|
||||
const response = await authOwnerAgent.get('/login');
|
||||
|
||||
expect(response.statusCode).toBe(200);
|
||||
|
||||
const {
|
||||
id,
|
||||
email,
|
||||
firstName,
|
||||
lastName,
|
||||
password,
|
||||
personalizationAnswers,
|
||||
globalRole,
|
||||
resetPasswordToken,
|
||||
} = response.body.data;
|
||||
|
||||
expect(validator.isUUID(id)).toBe(true);
|
||||
expect(email).toBe(TEST_USER.email);
|
||||
expect(firstName).toBe(TEST_USER.firstName);
|
||||
expect(lastName).toBe(TEST_USER.lastName);
|
||||
expect(password).toBeUndefined();
|
||||
expect(personalizationAnswers).toBeNull();
|
||||
expect(password).toBeUndefined();
|
||||
expect(resetPasswordToken).toBeUndefined();
|
||||
expect(globalRole).toBeDefined();
|
||||
expect(globalRole.name).toBe('owner');
|
||||
expect(globalRole.scope).toBe('global');
|
||||
|
||||
expect(response.headers['set-cookie']).toBeUndefined();
|
||||
});
|
||||
|
||||
test('POST /logout should log user out', async () => {
|
||||
const owner = await Db.collections.User!.findOneOrFail();
|
||||
const authOwnerAgent = utils.createAgent(app, { auth: true, user: owner });
|
||||
|
||||
const response = await authOwnerAgent.post('/logout');
|
||||
|
||||
expect(response.statusCode).toBe(200);
|
||||
expect(response.body).toEqual(LOGGED_OUT_RESPONSE_BODY);
|
||||
|
||||
const authToken = utils.getAuthToken(response);
|
||||
expect(authToken).toBeUndefined();
|
||||
});
|
||||
|
||||
const TEST_USER = {
|
||||
email: randomEmail(),
|
||||
password: randomValidPassword(),
|
||||
firstName: randomName(),
|
||||
lastName: randomName(),
|
||||
};
|
|
@ -62,14 +62,14 @@ test('POST /credentials should create cred', async () => {
|
|||
expect(nodesAccess[0].nodeType).toBe(payload.nodesAccess[0].nodeType);
|
||||
expect(encryptedData).not.toBe(payload.data);
|
||||
|
||||
const credential = await Db.collections.Credentials!.findOneOrFail(id);
|
||||
const credential = await Db.collections.Credentials.findOneOrFail(id);
|
||||
|
||||
expect(credential.name).toBe(payload.name);
|
||||
expect(credential.type).toBe(payload.type);
|
||||
expect(credential.nodesAccess[0].nodeType).toBe(payload.nodesAccess[0].nodeType);
|
||||
expect(credential.data).not.toBe(payload.data);
|
||||
|
||||
const sharedCredential = await Db.collections.SharedCredentials!.findOneOrFail({
|
||||
const sharedCredential = await Db.collections.SharedCredentials.findOneOrFail({
|
||||
relations: ['user', 'credentials'],
|
||||
where: { credentials: credential },
|
||||
});
|
||||
|
@ -131,11 +131,11 @@ test('DELETE /credentials/:id should delete owned cred for owner', async () => {
|
|||
expect(response.statusCode).toBe(200);
|
||||
expect(response.body).toEqual({ data: true });
|
||||
|
||||
const deletedCredential = await Db.collections.Credentials!.findOne(savedCredential.id);
|
||||
const deletedCredential = await Db.collections.Credentials.findOne(savedCredential.id);
|
||||
|
||||
expect(deletedCredential).toBeUndefined(); // deleted
|
||||
|
||||
const deletedSharedCredential = await Db.collections.SharedCredentials!.findOne();
|
||||
const deletedSharedCredential = await Db.collections.SharedCredentials.findOne();
|
||||
|
||||
expect(deletedSharedCredential).toBeUndefined(); // deleted
|
||||
});
|
||||
|
@ -151,11 +151,11 @@ test('DELETE /credentials/:id should delete non-owned cred for owner', async ()
|
|||
expect(response.statusCode).toBe(200);
|
||||
expect(response.body).toEqual({ data: true });
|
||||
|
||||
const deletedCredential = await Db.collections.Credentials!.findOne(savedCredential.id);
|
||||
const deletedCredential = await Db.collections.Credentials.findOne(savedCredential.id);
|
||||
|
||||
expect(deletedCredential).toBeUndefined(); // deleted
|
||||
|
||||
const deletedSharedCredential = await Db.collections.SharedCredentials!.findOne();
|
||||
const deletedSharedCredential = await Db.collections.SharedCredentials.findOne();
|
||||
|
||||
expect(deletedSharedCredential).toBeUndefined(); // deleted
|
||||
});
|
||||
|
@ -170,11 +170,11 @@ test('DELETE /credentials/:id should delete owned cred for member', async () =>
|
|||
expect(response.statusCode).toBe(200);
|
||||
expect(response.body).toEqual({ data: true });
|
||||
|
||||
const deletedCredential = await Db.collections.Credentials!.findOne(savedCredential.id);
|
||||
const deletedCredential = await Db.collections.Credentials.findOne(savedCredential.id);
|
||||
|
||||
expect(deletedCredential).toBeUndefined(); // deleted
|
||||
|
||||
const deletedSharedCredential = await Db.collections.SharedCredentials!.findOne();
|
||||
const deletedSharedCredential = await Db.collections.SharedCredentials.findOne();
|
||||
|
||||
expect(deletedSharedCredential).toBeUndefined(); // deleted
|
||||
});
|
||||
|
@ -189,11 +189,11 @@ test('DELETE /credentials/:id should not delete non-owned cred for member', asyn
|
|||
|
||||
expect(response.statusCode).toBe(404);
|
||||
|
||||
const shellCredential = await Db.collections.Credentials!.findOne(savedCredential.id);
|
||||
const shellCredential = await Db.collections.Credentials.findOne(savedCredential.id);
|
||||
|
||||
expect(shellCredential).toBeDefined(); // not deleted
|
||||
|
||||
const deletedSharedCredential = await Db.collections.SharedCredentials!.findOne();
|
||||
const deletedSharedCredential = await Db.collections.SharedCredentials.findOne();
|
||||
|
||||
expect(deletedSharedCredential).toBeDefined(); // not deleted
|
||||
});
|
||||
|
@ -226,14 +226,14 @@ test('PATCH /credentials/:id should update owned cred for owner', async () => {
|
|||
expect(nodesAccess[0].nodeType).toBe(patchPayload.nodesAccess[0].nodeType);
|
||||
expect(encryptedData).not.toBe(patchPayload.data);
|
||||
|
||||
const credential = await Db.collections.Credentials!.findOneOrFail(id);
|
||||
const credential = await Db.collections.Credentials.findOneOrFail(id);
|
||||
|
||||
expect(credential.name).toBe(patchPayload.name);
|
||||
expect(credential.type).toBe(patchPayload.type);
|
||||
expect(credential.nodesAccess[0].nodeType).toBe(patchPayload.nodesAccess[0].nodeType);
|
||||
expect(credential.data).not.toBe(patchPayload.data);
|
||||
|
||||
const sharedCredential = await Db.collections.SharedCredentials!.findOneOrFail({
|
||||
const sharedCredential = await Db.collections.SharedCredentials.findOneOrFail({
|
||||
relations: ['credentials'],
|
||||
where: { credentials: credential },
|
||||
});
|
||||
|
@ -261,14 +261,14 @@ test('PATCH /credentials/:id should update non-owned cred for owner', async () =
|
|||
expect(nodesAccess[0].nodeType).toBe(patchPayload.nodesAccess[0].nodeType);
|
||||
expect(encryptedData).not.toBe(patchPayload.data);
|
||||
|
||||
const credential = await Db.collections.Credentials!.findOneOrFail(id);
|
||||
const credential = await Db.collections.Credentials.findOneOrFail(id);
|
||||
|
||||
expect(credential.name).toBe(patchPayload.name);
|
||||
expect(credential.type).toBe(patchPayload.type);
|
||||
expect(credential.nodesAccess[0].nodeType).toBe(patchPayload.nodesAccess[0].nodeType);
|
||||
expect(credential.data).not.toBe(patchPayload.data);
|
||||
|
||||
const sharedCredential = await Db.collections.SharedCredentials!.findOneOrFail({
|
||||
const sharedCredential = await Db.collections.SharedCredentials.findOneOrFail({
|
||||
relations: ['credentials'],
|
||||
where: { credentials: credential },
|
||||
});
|
||||
|
@ -295,14 +295,14 @@ test('PATCH /credentials/:id should update owned cred for member', async () => {
|
|||
expect(nodesAccess[0].nodeType).toBe(patchPayload.nodesAccess[0].nodeType);
|
||||
expect(encryptedData).not.toBe(patchPayload.data);
|
||||
|
||||
const credential = await Db.collections.Credentials!.findOneOrFail(id);
|
||||
const credential = await Db.collections.Credentials.findOneOrFail(id);
|
||||
|
||||
expect(credential.name).toBe(patchPayload.name);
|
||||
expect(credential.type).toBe(patchPayload.type);
|
||||
expect(credential.nodesAccess[0].nodeType).toBe(patchPayload.nodesAccess[0].nodeType);
|
||||
expect(credential.data).not.toBe(patchPayload.data);
|
||||
|
||||
const sharedCredential = await Db.collections.SharedCredentials!.findOneOrFail({
|
||||
const sharedCredential = await Db.collections.SharedCredentials.findOneOrFail({
|
||||
relations: ['credentials'],
|
||||
where: { credentials: credential },
|
||||
});
|
||||
|
@ -323,7 +323,7 @@ test('PATCH /credentials/:id should not update non-owned cred for member', async
|
|||
|
||||
expect(response.statusCode).toBe(404);
|
||||
|
||||
const shellCredential = await Db.collections.Credentials!.findOneOrFail(savedCredential.id);
|
||||
const shellCredential = await Db.collections.Credentials.findOneOrFail(savedCredential.id);
|
||||
|
||||
expect(shellCredential.name).not.toBe(patchPayload.name); // not updated
|
||||
});
|
||||
|
|
|
@ -101,7 +101,7 @@ describe('Owner shell', () => {
|
|||
expect(globalRole.name).toBe('owner');
|
||||
expect(globalRole.scope).toBe('global');
|
||||
|
||||
const storedOwnerShell = await Db.collections.User!.findOneOrFail(id);
|
||||
const storedOwnerShell = await Db.collections.User.findOneOrFail(id);
|
||||
|
||||
expect(storedOwnerShell.email).toBe(validPayload.email.toLowerCase());
|
||||
expect(storedOwnerShell.firstName).toBe(validPayload.firstName);
|
||||
|
@ -117,7 +117,7 @@ describe('Owner shell', () => {
|
|||
const response = await authOwnerShellAgent.patch('/me').send(invalidPayload);
|
||||
expect(response.statusCode).toBe(400);
|
||||
|
||||
const storedOwnerShell = await Db.collections.User!.findOneOrFail();
|
||||
const storedOwnerShell = await Db.collections.User.findOneOrFail();
|
||||
expect(storedOwnerShell.email).toBeNull();
|
||||
expect(storedOwnerShell.firstName).toBeNull();
|
||||
expect(storedOwnerShell.lastName).toBeNull();
|
||||
|
@ -140,7 +140,7 @@ describe('Owner shell', () => {
|
|||
const response = await authOwnerShellAgent.patch('/me/password').send(payload);
|
||||
expect([400, 500].includes(response.statusCode)).toBe(true);
|
||||
|
||||
const storedMember = await Db.collections.User!.findOneOrFail();
|
||||
const storedMember = await Db.collections.User.findOneOrFail();
|
||||
|
||||
if (payload.newPassword) {
|
||||
expect(storedMember.password).not.toBe(payload.newPassword);
|
||||
|
@ -152,7 +152,7 @@ describe('Owner shell', () => {
|
|||
}),
|
||||
);
|
||||
|
||||
const storedOwnerShell = await Db.collections.User!.findOneOrFail();
|
||||
const storedOwnerShell = await Db.collections.User.findOneOrFail();
|
||||
expect(storedOwnerShell.password).toBeNull();
|
||||
});
|
||||
|
||||
|
@ -168,7 +168,7 @@ describe('Owner shell', () => {
|
|||
expect(response.statusCode).toBe(200);
|
||||
expect(response.body).toEqual(SUCCESS_RESPONSE_BODY);
|
||||
|
||||
const storedShellOwner = await Db.collections.User!.findOneOrFail({
|
||||
const storedShellOwner = await Db.collections.User.findOneOrFail({
|
||||
where: { email: IsNull() },
|
||||
});
|
||||
|
||||
|
@ -181,7 +181,7 @@ describe('Member', () => {
|
|||
beforeEach(async () => {
|
||||
config.set('userManagement.isInstanceOwnerSetUp', true);
|
||||
|
||||
await Db.collections.Settings!.update(
|
||||
await Db.collections.Settings.update(
|
||||
{ key: 'userManagement.isInstanceOwnerSetUp' },
|
||||
{ value: JSON.stringify(true) },
|
||||
);
|
||||
|
@ -255,7 +255,7 @@ describe('Member', () => {
|
|||
expect(globalRole.name).toBe('member');
|
||||
expect(globalRole.scope).toBe('global');
|
||||
|
||||
const storedMember = await Db.collections.User!.findOneOrFail(id);
|
||||
const storedMember = await Db.collections.User.findOneOrFail(id);
|
||||
|
||||
expect(storedMember.email).toBe(validPayload.email.toLowerCase());
|
||||
expect(storedMember.firstName).toBe(validPayload.firstName);
|
||||
|
@ -271,7 +271,7 @@ describe('Member', () => {
|
|||
const response = await authMemberAgent.patch('/me').send(invalidPayload);
|
||||
expect(response.statusCode).toBe(400);
|
||||
|
||||
const storedMember = await Db.collections.User!.findOneOrFail();
|
||||
const storedMember = await Db.collections.User.findOneOrFail();
|
||||
expect(storedMember.email).toBe(member.email);
|
||||
expect(storedMember.firstName).toBe(member.firstName);
|
||||
expect(storedMember.lastName).toBe(member.lastName);
|
||||
|
@ -295,7 +295,7 @@ describe('Member', () => {
|
|||
expect(response.statusCode).toBe(200);
|
||||
expect(response.body).toEqual(SUCCESS_RESPONSE_BODY);
|
||||
|
||||
const storedMember = await Db.collections.User!.findOneOrFail();
|
||||
const storedMember = await Db.collections.User.findOneOrFail();
|
||||
expect(storedMember.password).not.toBe(member.password);
|
||||
expect(storedMember.password).not.toBe(validPayload.newPassword);
|
||||
});
|
||||
|
@ -308,7 +308,7 @@ describe('Member', () => {
|
|||
const response = await authMemberAgent.patch('/me/password').send(payload);
|
||||
expect([400, 500].includes(response.statusCode)).toBe(true);
|
||||
|
||||
const storedMember = await Db.collections.User!.findOneOrFail();
|
||||
const storedMember = await Db.collections.User.findOneOrFail();
|
||||
|
||||
if (payload.newPassword) {
|
||||
expect(storedMember.password).not.toBe(payload.newPassword);
|
||||
|
@ -330,7 +330,7 @@ describe('Member', () => {
|
|||
expect(response.statusCode).toBe(200);
|
||||
expect(response.body).toEqual(SUCCESS_RESPONSE_BODY);
|
||||
|
||||
const { personalizationAnswers: storedAnswers } = await Db.collections.User!.findOneOrFail();
|
||||
const { personalizationAnswers: storedAnswers } = await Db.collections.User.findOneOrFail();
|
||||
|
||||
expect(storedAnswers).toEqual(validPayload);
|
||||
}
|
||||
|
@ -410,7 +410,7 @@ describe('Owner', () => {
|
|||
expect(globalRole.name).toBe('owner');
|
||||
expect(globalRole.scope).toBe('global');
|
||||
|
||||
const storedOwner = await Db.collections.User!.findOneOrFail(id);
|
||||
const storedOwner = await Db.collections.User.findOneOrFail(id);
|
||||
|
||||
expect(storedOwner.email).toBe(validPayload.email.toLowerCase());
|
||||
expect(storedOwner.firstName).toBe(validPayload.firstName);
|
||||
|
|
|
@ -81,7 +81,7 @@ test('POST /owner should create owner and enable isInstanceOwnerSetUp', async ()
|
|||
expect(globalRole.name).toBe('owner');
|
||||
expect(globalRole.scope).toBe('global');
|
||||
|
||||
const storedOwner = await Db.collections.User!.findOneOrFail(id);
|
||||
const storedOwner = await Db.collections.User.findOneOrFail(id);
|
||||
expect(storedOwner.password).not.toBe(newOwnerData.password);
|
||||
expect(storedOwner.email).toBe(newOwnerData.email);
|
||||
expect(storedOwner.firstName).toBe(newOwnerData.firstName);
|
||||
|
@ -113,7 +113,7 @@ test('POST /owner should create owner with lowercased email', async () => {
|
|||
|
||||
expect(email).toBe(newOwnerData.email.toLowerCase());
|
||||
|
||||
const storedOwner = await Db.collections.User!.findOneOrFail(id);
|
||||
const storedOwner = await Db.collections.User.findOneOrFail(id);
|
||||
expect(storedOwner.email).toBe(newOwnerData.email.toLowerCase());
|
||||
});
|
||||
|
||||
|
@ -140,7 +140,7 @@ test('POST /owner/skip-setup should persist skipping setup to the DB', async ()
|
|||
const skipConfig = config.getEnv('userManagement.skipInstanceOwnerSetup');
|
||||
expect(skipConfig).toBe(true);
|
||||
|
||||
const { value } = await Db.collections.Settings!.findOneOrFail({
|
||||
const { value } = await Db.collections.Settings.findOneOrFail({
|
||||
key: 'userManagement.skipInstanceOwnerSetup',
|
||||
});
|
||||
expect(value).toBe('true');
|
||||
|
|
|
@ -67,7 +67,7 @@ test(
|
|||
expect(response.statusCode).toBe(200);
|
||||
expect(response.body).toEqual({});
|
||||
|
||||
const user = await Db.collections.User!.findOneOrFail({ email: payload.email });
|
||||
const user = await Db.collections.User.findOneOrFail({ email: payload.email });
|
||||
expect(user.resetPasswordToken).toBeDefined();
|
||||
expect(user.resetPasswordTokenExpiration).toBeGreaterThan(Math.ceil(Date.now() / 1000));
|
||||
}),
|
||||
|
@ -85,7 +85,7 @@ test('POST /forgot-password should fail if emailing is not set up', async () =>
|
|||
|
||||
expect(response.statusCode).toBe(500);
|
||||
|
||||
const storedOwner = await Db.collections.User!.findOneOrFail({ email: owner.email });
|
||||
const storedOwner = await Db.collections.User.findOneOrFail({ email: owner.email });
|
||||
expect(storedOwner.resetPasswordToken).toBeNull();
|
||||
});
|
||||
|
||||
|
@ -109,7 +109,7 @@ test('POST /forgot-password should fail with invalid inputs', async () => {
|
|||
const response = await authlessAgent.post('/forgot-password').send(invalidPayload);
|
||||
expect(response.statusCode).toBe(400);
|
||||
|
||||
const storedOwner = await Db.collections.User!.findOneOrFail({ email: owner.email });
|
||||
const storedOwner = await Db.collections.User.findOneOrFail({ email: owner.email });
|
||||
expect(storedOwner.resetPasswordToken).toBeNull();
|
||||
}),
|
||||
);
|
||||
|
@ -133,7 +133,7 @@ test('GET /resolve-password-token should succeed with valid inputs', async () =>
|
|||
const resetPasswordToken = uuid();
|
||||
const resetPasswordTokenExpiration = Math.floor(Date.now() / 1000) + 100;
|
||||
|
||||
await Db.collections.User!.update(owner.id, {
|
||||
await Db.collections.User.update(owner.id, {
|
||||
resetPasswordToken,
|
||||
resetPasswordTokenExpiration,
|
||||
});
|
||||
|
@ -183,7 +183,7 @@ test('GET /resolve-password-token should fail if token is expired', async () =>
|
|||
const resetPasswordToken = uuid();
|
||||
const resetPasswordTokenExpiration = Math.floor(Date.now() / 1000) - 1;
|
||||
|
||||
await Db.collections.User!.update(owner.id, {
|
||||
await Db.collections.User.update(owner.id, {
|
||||
resetPasswordToken,
|
||||
resetPasswordTokenExpiration,
|
||||
});
|
||||
|
@ -205,7 +205,7 @@ test('POST /change-password should succeed with valid inputs', async () => {
|
|||
const resetPasswordToken = uuid();
|
||||
const resetPasswordTokenExpiration = Math.floor(Date.now() / 1000) + 100;
|
||||
|
||||
await Db.collections.User!.update(owner.id, {
|
||||
await Db.collections.User.update(owner.id, {
|
||||
resetPasswordToken,
|
||||
resetPasswordTokenExpiration,
|
||||
});
|
||||
|
@ -223,7 +223,7 @@ test('POST /change-password should succeed with valid inputs', async () => {
|
|||
const authToken = utils.getAuthToken(response);
|
||||
expect(authToken).toBeDefined();
|
||||
|
||||
const { password: storedPassword } = await Db.collections.User!.findOneOrFail(owner.id);
|
||||
const { password: storedPassword } = await Db.collections.User.findOneOrFail(owner.id);
|
||||
|
||||
const comparisonResult = await compare(passwordToStore, storedPassword);
|
||||
expect(comparisonResult).toBe(true);
|
||||
|
@ -238,7 +238,7 @@ test('POST /change-password should fail with invalid inputs', async () => {
|
|||
const resetPasswordToken = uuid();
|
||||
const resetPasswordTokenExpiration = Math.floor(Date.now() / 1000) + 100;
|
||||
|
||||
await Db.collections.User!.update(owner.id, {
|
||||
await Db.collections.User.update(owner.id, {
|
||||
resetPasswordToken,
|
||||
resetPasswordTokenExpiration,
|
||||
});
|
||||
|
@ -267,7 +267,7 @@ test('POST /change-password should fail with invalid inputs', async () => {
|
|||
const response = await authlessAgent.post('/change-password').query(invalidPayload);
|
||||
expect(response.statusCode).toBe(400);
|
||||
|
||||
const { password: storedPassword } = await Db.collections.User!.findOneOrFail();
|
||||
const { password: storedPassword } = await Db.collections.User.findOneOrFail();
|
||||
expect(owner.password).toBe(storedPassword);
|
||||
}),
|
||||
);
|
||||
|
@ -281,7 +281,7 @@ test('POST /change-password should fail when token has expired', async () => {
|
|||
const resetPasswordToken = uuid();
|
||||
const resetPasswordTokenExpiration = Math.floor(Date.now() / 1000) - 1;
|
||||
|
||||
await Db.collections.User!.update(owner.id, {
|
||||
await Db.collections.User.update(owner.id, {
|
||||
resetPasswordToken,
|
||||
resetPasswordTokenExpiration,
|
||||
});
|
||||
|
|
|
@ -109,7 +109,7 @@ export async function truncate(collections: CollectionName[], testDbName: string
|
|||
|
||||
if (dbType === 'sqlite') {
|
||||
await testDb.query('PRAGMA foreign_keys=OFF');
|
||||
await Promise.all(collections.map((collection) => Db.collections[collection]!.clear()));
|
||||
await Promise.all(collections.map((collection) => Db.collections[collection].clear()));
|
||||
return testDb.query('PRAGMA foreign_keys=ON');
|
||||
}
|
||||
|
||||
|
@ -182,11 +182,11 @@ export async function saveCredential(
|
|||
|
||||
Object.assign(newCredential, encryptedData);
|
||||
|
||||
const savedCredential = await Db.collections.Credentials!.save(newCredential);
|
||||
const savedCredential = await Db.collections.Credentials.save(newCredential);
|
||||
|
||||
savedCredential.data = newCredential.data;
|
||||
|
||||
await Db.collections.SharedCredentials!.save({
|
||||
await Db.collections.SharedCredentials.save({
|
||||
user,
|
||||
credentials: savedCredential,
|
||||
role,
|
||||
|
@ -214,7 +214,7 @@ export async function createUser(attributes: Partial<User> = {}): Promise<User>
|
|||
...rest,
|
||||
};
|
||||
|
||||
return Db.collections.User!.save(user);
|
||||
return Db.collections.User.save(user);
|
||||
}
|
||||
|
||||
export function createUserShell(globalRole: Role): Promise<User> {
|
||||
|
@ -228,7 +228,7 @@ export function createUserShell(globalRole: Role): Promise<User> {
|
|||
shell.email = randomEmail();
|
||||
}
|
||||
|
||||
return Db.collections.User!.save(shell);
|
||||
return Db.collections.User.save(shell);
|
||||
}
|
||||
|
||||
// ----------------------------------
|
||||
|
@ -236,28 +236,28 @@ export function createUserShell(globalRole: Role): Promise<User> {
|
|||
// ----------------------------------
|
||||
|
||||
export function getGlobalOwnerRole() {
|
||||
return Db.collections.Role!.findOneOrFail({
|
||||
return Db.collections.Role.findOneOrFail({
|
||||
name: 'owner',
|
||||
scope: 'global',
|
||||
});
|
||||
}
|
||||
|
||||
export function getGlobalMemberRole() {
|
||||
return Db.collections.Role!.findOneOrFail({
|
||||
return Db.collections.Role.findOneOrFail({
|
||||
name: 'member',
|
||||
scope: 'global',
|
||||
});
|
||||
}
|
||||
|
||||
export function getWorkflowOwnerRole() {
|
||||
return Db.collections.Role!.findOneOrFail({
|
||||
return Db.collections.Role.findOneOrFail({
|
||||
name: 'owner',
|
||||
scope: 'workflow',
|
||||
});
|
||||
}
|
||||
|
||||
export function getCredentialOwnerRole() {
|
||||
return Db.collections.Role!.findOneOrFail({
|
||||
return Db.collections.Role.findOneOrFail({
|
||||
name: 'owner',
|
||||
scope: 'credential',
|
||||
});
|
||||
|
|
|
@ -221,7 +221,7 @@ export function getAuthToken(response: request.Response, authCookieName = AUTH_C
|
|||
// ----------------------------------
|
||||
|
||||
export async function isInstanceOwnerSetUp() {
|
||||
const { value } = await Db.collections.Settings!.findOneOrFail({
|
||||
const { value } = await Db.collections.Settings.findOneOrFail({
|
||||
key: 'userManagement.isInstanceOwnerSetUp',
|
||||
});
|
||||
|
||||
|
|
|
@ -119,9 +119,9 @@ test('DELETE /users/:id should delete the user', async () => {
|
|||
nodes: [],
|
||||
});
|
||||
|
||||
const savedWorkflow = await Db.collections.Workflow!.save(newWorkflow);
|
||||
const savedWorkflow = await Db.collections.Workflow.save(newWorkflow);
|
||||
|
||||
await Db.collections.SharedWorkflow!.save({
|
||||
await Db.collections.SharedWorkflow.save({
|
||||
role: workflowOwnerRole,
|
||||
user: userToDelete,
|
||||
workflow: savedWorkflow,
|
||||
|
@ -136,9 +136,9 @@ test('DELETE /users/:id should delete the user', async () => {
|
|||
nodesAccess: [],
|
||||
});
|
||||
|
||||
const savedCredential = await Db.collections.Credentials!.save(newCredential);
|
||||
const savedCredential = await Db.collections.Credentials.save(newCredential);
|
||||
|
||||
await Db.collections.SharedCredentials!.save({
|
||||
await Db.collections.SharedCredentials.save({
|
||||
role: credentialOwnerRole,
|
||||
user: userToDelete,
|
||||
credentials: savedCredential,
|
||||
|
@ -149,27 +149,27 @@ test('DELETE /users/:id should delete the user', async () => {
|
|||
expect(response.statusCode).toBe(200);
|
||||
expect(response.body).toEqual(SUCCESS_RESPONSE_BODY);
|
||||
|
||||
const user = await Db.collections.User!.findOne(userToDelete.id);
|
||||
const user = await Db.collections.User.findOne(userToDelete.id);
|
||||
expect(user).toBeUndefined(); // deleted
|
||||
|
||||
const sharedWorkflow = await Db.collections.SharedWorkflow!.findOne({
|
||||
const sharedWorkflow = await Db.collections.SharedWorkflow.findOne({
|
||||
relations: ['user'],
|
||||
where: { user: userToDelete },
|
||||
});
|
||||
expect(sharedWorkflow).toBeUndefined(); // deleted
|
||||
|
||||
const sharedCredential = await Db.collections.SharedCredentials!.findOne({
|
||||
const sharedCredential = await Db.collections.SharedCredentials.findOne({
|
||||
relations: ['user'],
|
||||
where: { user: userToDelete },
|
||||
});
|
||||
expect(sharedCredential).toBeUndefined(); // deleted
|
||||
|
||||
const workflow = await Db.collections.Workflow!.findOne(savedWorkflow.id);
|
||||
const workflow = await Db.collections.Workflow.findOne(savedWorkflow.id);
|
||||
expect(workflow).toBeUndefined(); // deleted
|
||||
|
||||
// TODO: Include active workflow and check whether webhook has been removed
|
||||
|
||||
const credential = await Db.collections.Credentials!.findOne(savedCredential.id);
|
||||
const credential = await Db.collections.Credentials.findOne(savedCredential.id);
|
||||
expect(credential).toBeUndefined(); // deleted
|
||||
});
|
||||
|
||||
|
@ -181,7 +181,7 @@ test('DELETE /users/:id should fail to delete self', async () => {
|
|||
|
||||
expect(response.statusCode).toBe(400);
|
||||
|
||||
const user = await Db.collections.User!.findOne(owner.id);
|
||||
const user = await Db.collections.User.findOne(owner.id);
|
||||
expect(user).toBeDefined();
|
||||
});
|
||||
|
||||
|
@ -197,7 +197,7 @@ test('DELETE /users/:id should fail if user to delete is transferee', async () =
|
|||
|
||||
expect(response.statusCode).toBe(400);
|
||||
|
||||
const user = await Db.collections.User!.findOne(idToDelete);
|
||||
const user = await Db.collections.User.findOne(idToDelete);
|
||||
expect(user).toBeDefined();
|
||||
});
|
||||
|
||||
|
@ -205,7 +205,7 @@ test('DELETE /users/:id with transferId should perform transfer', async () => {
|
|||
const owner = await testDb.createUser({ globalRole: globalOwnerRole });
|
||||
const authOwnerAgent = utils.createAgent(app, { auth: true, user: owner });
|
||||
|
||||
const userToDelete = await Db.collections.User!.save({
|
||||
const userToDelete = await Db.collections.User.save({
|
||||
id: uuid(),
|
||||
email: randomEmail(),
|
||||
password: randomValidPassword(),
|
||||
|
@ -225,9 +225,9 @@ test('DELETE /users/:id with transferId should perform transfer', async () => {
|
|||
nodes: [],
|
||||
});
|
||||
|
||||
const savedWorkflow = await Db.collections.Workflow!.save(newWorkflow);
|
||||
const savedWorkflow = await Db.collections.Workflow.save(newWorkflow);
|
||||
|
||||
await Db.collections.SharedWorkflow!.save({
|
||||
await Db.collections.SharedWorkflow.save({
|
||||
role: workflowOwnerRole,
|
||||
user: userToDelete,
|
||||
workflow: savedWorkflow,
|
||||
|
@ -242,9 +242,9 @@ test('DELETE /users/:id with transferId should perform transfer', async () => {
|
|||
nodesAccess: [],
|
||||
});
|
||||
|
||||
const savedCredential = await Db.collections.Credentials!.save(newCredential);
|
||||
const savedCredential = await Db.collections.Credentials.save(newCredential);
|
||||
|
||||
await Db.collections.SharedCredentials!.save({
|
||||
await Db.collections.SharedCredentials.save({
|
||||
role: credentialOwnerRole,
|
||||
user: userToDelete,
|
||||
credentials: savedCredential,
|
||||
|
@ -256,17 +256,17 @@ test('DELETE /users/:id with transferId should perform transfer', async () => {
|
|||
|
||||
expect(response.statusCode).toBe(200);
|
||||
|
||||
const sharedWorkflow = await Db.collections.SharedWorkflow!.findOneOrFail({
|
||||
const sharedWorkflow = await Db.collections.SharedWorkflow.findOneOrFail({
|
||||
relations: ['user'],
|
||||
where: { user: owner },
|
||||
});
|
||||
|
||||
const sharedCredential = await Db.collections.SharedCredentials!.findOneOrFail({
|
||||
const sharedCredential = await Db.collections.SharedCredentials.findOneOrFail({
|
||||
relations: ['user'],
|
||||
where: { user: owner },
|
||||
});
|
||||
|
||||
const deletedUser = await Db.collections.User!.findOne(userToDelete);
|
||||
const deletedUser = await Db.collections.User.findOne(userToDelete);
|
||||
|
||||
expect(sharedWorkflow.user.id).toBe(owner.id);
|
||||
expect(sharedCredential.user.id).toBe(owner.id);
|
||||
|
@ -317,7 +317,7 @@ test('GET /resolve-signup-token should fail with invalid inputs', async () => {
|
|||
.query({ inviteeId });
|
||||
|
||||
// cause inconsistent DB state
|
||||
await Db.collections.User!.update(owner.id, { email: '' });
|
||||
await Db.collections.User.update(owner.id, { email: '' });
|
||||
const fifth = await authOwnerAgent
|
||||
.get('/resolve-signup-token')
|
||||
.query({ inviterId: owner.id })
|
||||
|
@ -369,7 +369,7 @@ test('POST /users/:id should fill out a user shell', async () => {
|
|||
const authToken = utils.getAuthToken(response);
|
||||
expect(authToken).toBeDefined();
|
||||
|
||||
const member = await Db.collections.User!.findOneOrFail(memberShell.id);
|
||||
const member = await Db.collections.User.findOneOrFail(memberShell.id);
|
||||
expect(member.firstName).toBe(memberData.firstName);
|
||||
expect(member.lastName).toBe(memberData.lastName);
|
||||
expect(member.password).not.toBe(memberData.password);
|
||||
|
@ -382,7 +382,7 @@ test('POST /users/:id should fail with invalid inputs', async () => {
|
|||
|
||||
const memberShellEmail = randomEmail();
|
||||
|
||||
const memberShell = await Db.collections.User!.save({
|
||||
const memberShell = await Db.collections.User.save({
|
||||
email: memberShellEmail,
|
||||
globalRole: globalMemberRole,
|
||||
});
|
||||
|
@ -421,7 +421,7 @@ test('POST /users/:id should fail with invalid inputs', async () => {
|
|||
const response = await authlessAgent.post(`/users/${memberShell.id}`).send(invalidPayload);
|
||||
expect(response.statusCode).toBe(400);
|
||||
|
||||
const storedUser = await Db.collections.User!.findOneOrFail({
|
||||
const storedUser = await Db.collections.User.findOneOrFail({
|
||||
where: { email: memberShellEmail },
|
||||
});
|
||||
expect(storedUser.firstName).toBeNull();
|
||||
|
@ -448,7 +448,7 @@ test('POST /users/:id should fail with already accepted invite', async () => {
|
|||
|
||||
expect(response.statusCode).toBe(400);
|
||||
|
||||
const storedMember = await Db.collections.User!.findOneOrFail({
|
||||
const storedMember = await Db.collections.User.findOneOrFail({
|
||||
where: { email: member.email },
|
||||
});
|
||||
expect(storedMember.firstName).not.toBe(newMemberData.firstName);
|
||||
|
@ -517,7 +517,7 @@ test(
|
|||
expect(error).toBe('Email could not be sent');
|
||||
}
|
||||
|
||||
const storedUser = await Db.collections.User!.findOneOrFail(id);
|
||||
const storedUser = await Db.collections.User.findOneOrFail(id);
|
||||
const { firstName, lastName, personalizationAnswers, password, resetPasswordToken } =
|
||||
storedUser;
|
||||
|
||||
|
@ -552,7 +552,7 @@ test(
|
|||
const response = await authOwnerAgent.post('/users').send(invalidPayload);
|
||||
expect(response.statusCode).toBe(400);
|
||||
|
||||
const users = await Db.collections.User!.find();
|
||||
const users = await Db.collections.User.find();
|
||||
expect(users.length).toBe(1); // DB unaffected
|
||||
}),
|
||||
);
|
||||
|
@ -576,7 +576,7 @@ test(
|
|||
expect(Array.isArray(data)).toBe(true);
|
||||
expect(data.length).toBe(0);
|
||||
|
||||
const users = await Db.collections.User!.find();
|
||||
const users = await Db.collections.User.find();
|
||||
expect(users.length).toBe(1);
|
||||
},
|
||||
SMTP_TEST_TIMEOUT,
|
||||
|
@ -586,7 +586,7 @@ test(
|
|||
|
||||
// TODO: UserManagementMailer is a singleton - cannot reinstantiate with wrong creds
|
||||
// test('POST /users should error for wrong SMTP config', async () => {
|
||||
// const owner = await Db.collections.User!.findOneOrFail();
|
||||
// const owner = await Db.collections.User.findOneOrFail();
|
||||
// const authOwnerAgent = utils.createAgent(app, { auth: true, user: owner });
|
||||
|
||||
// config.set('userManagement.emails.mode', 'smtp');
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "n8n-core",
|
||||
"version": "0.115.0",
|
||||
"version": "0.116.0",
|
||||
"description": "Core functionality of n8n",
|
||||
"license": "SEE LICENSE IN LICENSE.md",
|
||||
"homepage": "https://n8n.io",
|
||||
|
@ -52,7 +52,7 @@
|
|||
"form-data": "^4.0.0",
|
||||
"lodash.get": "^4.4.2",
|
||||
"mime-types": "^2.1.27",
|
||||
"n8n-workflow": "~0.97.0",
|
||||
"n8n-workflow": "~0.98.0",
|
||||
"oauth-1.0a": "^2.2.6",
|
||||
"p-cancelable": "^2.0.0",
|
||||
"qs": "^6.10.1",
|
||||
|
|
|
@ -1298,6 +1298,7 @@ export async function getCredentials(
|
|||
!NodeHelpers.displayParameter(
|
||||
additionalData.currentNodeParameters || node.parameters,
|
||||
nodeCredentialDescription,
|
||||
node,
|
||||
node.parameters,
|
||||
)
|
||||
) {
|
||||
|
|
|
@ -517,6 +517,65 @@ class NodeTypesClass implements INodeTypes {
|
|||
},
|
||||
},
|
||||
},
|
||||
'n8n-nodes-base.versionTest': {
|
||||
sourcePath: '',
|
||||
type: {
|
||||
description: {
|
||||
displayName: 'Version Test',
|
||||
name: 'versionTest',
|
||||
group: ['input'],
|
||||
version: 1,
|
||||
description: 'Tests if versioning works',
|
||||
defaults: {
|
||||
name: 'Version Test',
|
||||
color: '#0000FF',
|
||||
},
|
||||
inputs: ['main'],
|
||||
outputs: ['main'],
|
||||
properties: [
|
||||
{
|
||||
displayName: 'Display V1',
|
||||
name: 'versionTest',
|
||||
type: 'number',
|
||||
displayOptions: {
|
||||
show: {
|
||||
'@version': [1],
|
||||
},
|
||||
},
|
||||
default: 1,
|
||||
},
|
||||
{
|
||||
displayName: 'Display V2',
|
||||
name: 'versionTest',
|
||||
type: 'number',
|
||||
displayOptions: {
|
||||
show: {
|
||||
'@version': [2],
|
||||
},
|
||||
},
|
||||
default: 2,
|
||||
},
|
||||
],
|
||||
},
|
||||
execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]> {
|
||||
const items = this.getInputData();
|
||||
const returnData: INodeExecutionData[] = [];
|
||||
|
||||
for (let itemIndex = 0; itemIndex < items.length; itemIndex++) {
|
||||
const newItem: INodeExecutionData = {
|
||||
json: {
|
||||
versionFromParameter: this.getNodeParameter('versionTest', itemIndex),
|
||||
versionFromNode: this.getNode().typeVersion,
|
||||
},
|
||||
};
|
||||
|
||||
returnData.push(newItem);
|
||||
}
|
||||
|
||||
return this.prepareOutputData(returnData);
|
||||
},
|
||||
},
|
||||
},
|
||||
'n8n-nodes-base.set': {
|
||||
sourcePath: '',
|
||||
type: {
|
||||
|
|
|
@ -1169,6 +1169,145 @@ describe('WorkflowExecute', () => {
|
|||
},
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
description:
|
||||
'should display the correct parameters and so correct data when simplified node-versioning is used',
|
||||
input: {
|
||||
workflowData: {
|
||||
nodes: [
|
||||
{
|
||||
parameters: {},
|
||||
name: 'Start',
|
||||
type: 'n8n-nodes-base.start',
|
||||
typeVersion: 1,
|
||||
position: [240, 300],
|
||||
},
|
||||
{
|
||||
parameters: {},
|
||||
name: 'VersionTest1a',
|
||||
type: 'n8n-nodes-base.versionTest',
|
||||
typeVersion: 1,
|
||||
position: [460, 300],
|
||||
},
|
||||
{
|
||||
parameters: {
|
||||
versionTest: 11,
|
||||
},
|
||||
name: 'VersionTest1b',
|
||||
type: 'n8n-nodes-base.versionTest',
|
||||
typeVersion: 1,
|
||||
position: [680, 300],
|
||||
},
|
||||
{
|
||||
parameters: {},
|
||||
name: 'VersionTest2a',
|
||||
type: 'n8n-nodes-base.versionTest',
|
||||
typeVersion: 2,
|
||||
position: [880, 300],
|
||||
},
|
||||
{
|
||||
parameters: {
|
||||
versionTest: 22,
|
||||
},
|
||||
name: 'VersionTest2b',
|
||||
type: 'n8n-nodes-base.versionTest',
|
||||
typeVersion: 2,
|
||||
position: [1080, 300],
|
||||
},
|
||||
],
|
||||
connections: {
|
||||
Start: {
|
||||
main: [
|
||||
[
|
||||
{
|
||||
node: 'VersionTest1a',
|
||||
type: 'main',
|
||||
index: 0,
|
||||
},
|
||||
],
|
||||
],
|
||||
},
|
||||
VersionTest1a: {
|
||||
main: [
|
||||
[
|
||||
{
|
||||
node: 'VersionTest1b',
|
||||
type: 'main',
|
||||
index: 0,
|
||||
},
|
||||
],
|
||||
],
|
||||
},
|
||||
VersionTest1b: {
|
||||
main: [
|
||||
[
|
||||
{
|
||||
node: 'VersionTest2a',
|
||||
type: 'main',
|
||||
index: 0,
|
||||
},
|
||||
],
|
||||
],
|
||||
},
|
||||
VersionTest2a: {
|
||||
main: [
|
||||
[
|
||||
{
|
||||
node: 'VersionTest2b',
|
||||
type: 'main',
|
||||
index: 0,
|
||||
},
|
||||
],
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
output: {
|
||||
nodeExecutionOrder: [
|
||||
'Start',
|
||||
'VersionTest1a',
|
||||
'VersionTest1b',
|
||||
'VersionTest2a',
|
||||
'VersionTest2b',
|
||||
],
|
||||
nodeData: {
|
||||
VersionTest1a: [
|
||||
[
|
||||
{
|
||||
versionFromNode: 1,
|
||||
versionFromParameter: 1,
|
||||
},
|
||||
],
|
||||
],
|
||||
VersionTest1b: [
|
||||
[
|
||||
{
|
||||
versionFromNode: 1,
|
||||
versionFromParameter: 11,
|
||||
},
|
||||
],
|
||||
],
|
||||
VersionTest2a: [
|
||||
[
|
||||
{
|
||||
versionFromNode: 2,
|
||||
versionFromParameter: 2,
|
||||
},
|
||||
],
|
||||
],
|
||||
VersionTest2b: [
|
||||
[
|
||||
{
|
||||
versionFromNode: 2,
|
||||
versionFromParameter: 22,
|
||||
},
|
||||
],
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
const fakeLogger = {
|
||||
|
|
|
@ -35,6 +35,11 @@ module.exports = {
|
|||
],
|
||||
});
|
||||
|
||||
config.resolve.alias = {
|
||||
...config.resolve.alias,
|
||||
"@/": path.resolve(__dirname, "../src/"),
|
||||
};
|
||||
|
||||
return config;
|
||||
},
|
||||
};
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "n8n-design-system",
|
||||
"version": "0.18.0",
|
||||
"version": "0.19.0",
|
||||
"license": "SEE LICENSE IN LICENSE.md",
|
||||
"homepage": "https://n8n.io",
|
||||
"author": {
|
||||
|
@ -14,12 +14,14 @@
|
|||
},
|
||||
"scripts": {
|
||||
"build": "npm run build:theme",
|
||||
"build:vue": "vue-cli-service build --target lib ./src/main.js --report",
|
||||
"build:vue": "vite build",
|
||||
"build:vue:typecheck": "vue-tsc --emitDeclarationOnly",
|
||||
"dev": "npm run watch:theme",
|
||||
"test": "npm run test:unit",
|
||||
"test": "vitest run",
|
||||
"test:ci": "vitest run --coverage",
|
||||
"test:dev": "vitest",
|
||||
"build:storybook": "build-storybook",
|
||||
"storybook": "start-storybook -p 6006",
|
||||
"test:unit": "vue-cli-service test:unit --passWithNoTests",
|
||||
"lint": "tslint -p tsconfig.json -c tslint.json",
|
||||
"lintfix": "tslint --fix -p tsconfig.json -c tslint.json",
|
||||
"build:theme": "gulp build:theme",
|
||||
|
@ -36,14 +38,15 @@
|
|||
"@fortawesome/fontawesome-svg-core": "^1.2.35",
|
||||
"@fortawesome/free-solid-svg-icons": "^5.15.3",
|
||||
"@fortawesome/vue-fontawesome": "^2.0.2",
|
||||
"core-js": "^3.6.5",
|
||||
"element-ui": "~2.15.7",
|
||||
"@storybook/addon-actions": "^6.3.6",
|
||||
"@storybook/addon-essentials": "^6.3.6",
|
||||
"@storybook/addon-links": "^6.3.6",
|
||||
"@storybook/vue": "^6.3.6",
|
||||
"@testing-library/jest-dom": "^5.16.4",
|
||||
"@testing-library/vue": "^5.8.2",
|
||||
"@types/jest": "^27.4.0",
|
||||
"@types/markdown-it": "^12.2.3",
|
||||
"@types/sanitize-html": "^2.6.2",
|
||||
"@typescript-eslint/eslint-plugin": "^4.29.0",
|
||||
"@typescript-eslint/parser": "^4.29.0",
|
||||
"@vue/cli-plugin-babel": "~4.5.0",
|
||||
|
@ -55,6 +58,8 @@
|
|||
"@vue/eslint-config-typescript": "^7.0.0",
|
||||
"@vue/test-utils": "^1.0.3",
|
||||
"babel-loader": "^8.2.2",
|
||||
"c8": "7.11.0",
|
||||
"core-js": "^3.6.5",
|
||||
"eslint": "^7.32.0",
|
||||
"eslint-plugin-prettier": "^3.4.0",
|
||||
"eslint-plugin-vue": "^7.16.0",
|
||||
|
@ -62,11 +67,12 @@
|
|||
"gulp-autoprefixer": "^4.0.0",
|
||||
"gulp-clean-css": "^4.3.0",
|
||||
"gulp-dart-sass": "^1.0.2",
|
||||
"node-notifier": ">=8.0.1",
|
||||
"jsdom": "19.0.0",
|
||||
"markdown-it": "^12.3.2",
|
||||
"markdown-it-emoji": "^2.0.0",
|
||||
"markdown-it-link-attributes": "^4.0.0",
|
||||
"markdown-it-task-lists": "^2.1.1",
|
||||
"node-notifier": ">=8.0.1",
|
||||
"prettier": "^2.3.2",
|
||||
"sass": "^1.26.5",
|
||||
"sass-loader": "^8.0.2",
|
||||
|
@ -74,6 +80,9 @@
|
|||
"storybook-addon-themes": "^6.1.0",
|
||||
"trim": ">=0.0.3",
|
||||
"typescript": "~4.6.0",
|
||||
"vite": "2.9.5",
|
||||
"vite-plugin-vue2": "1.9.3",
|
||||
"vitest": "0.9.3",
|
||||
"vue": "^2.6.11",
|
||||
"vue-class-component": "^7.2.3",
|
||||
"vue-loader": "^15.9.7",
|
||||
|
@ -81,6 +90,12 @@
|
|||
"vue-template-compiler": "^2.6.11",
|
||||
"vue-typed-mixins": "^0.2.0",
|
||||
"vue2-boring-avatars": "0.3.4",
|
||||
"vue-tsc": "0.34.8",
|
||||
"xss": "^1.0.10"
|
||||
},
|
||||
"dependencies": {
|
||||
"element-ui": "~2.15.7",
|
||||
"sanitize-html": "2.7.0",
|
||||
"vue2-boring-avatars": "0.3.4"
|
||||
}
|
||||
}
|
||||
|
|
1
packages/design-system/src/__tests__/setup.ts
Normal file
1
packages/design-system/src/__tests__/setup.ts
Normal file
|
@ -0,0 +1 @@
|
|||
import '@testing-library/jest-dom';
|
|
@ -58,7 +58,7 @@ import N8nOption from '../N8nOption';
|
|||
import N8nInputLabel from '../N8nInputLabel';
|
||||
|
||||
import { getValidationError, VALIDATORS } from './validators';
|
||||
import { Rule, RuleGroup, IValidator } from "../../../../editor-ui/src/Interface";
|
||||
import { Rule, RuleGroup, IValidator } from "../../types";
|
||||
|
||||
import Locale from '../../mixins/locale';
|
||||
import mixins from 'vue-typed-mixins';
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
|
||||
import { IValidator, RuleGroup } from "../../../../editor-ui/src/Interface";
|
||||
import { IValidator, RuleGroup } from "../../types";
|
||||
|
||||
export const emailRegex =
|
||||
/^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
|
||||
|
|
|
@ -29,7 +29,7 @@
|
|||
<script lang="ts">
|
||||
import Vue from 'vue';
|
||||
import N8nFormInput from '../N8nFormInput';
|
||||
import { IFormInputs } from '../../Interface';
|
||||
import { IFormInputs } from '../../types';
|
||||
import ResizeObserver from '../ResizeObserver';
|
||||
|
||||
export default Vue.extend({
|
||||
|
|
|
@ -0,0 +1,71 @@
|
|||
/* tslint:disable:variable-name */
|
||||
|
||||
import N8nNotice from './Notice.vue';
|
||||
import {StoryFn} from "@storybook/vue";
|
||||
|
||||
export default {
|
||||
title: 'Atoms/Notice',
|
||||
component: N8nNotice,
|
||||
argTypes: {
|
||||
theme: {
|
||||
control: 'select',
|
||||
options: ['success', 'warning', 'danger', 'info'],
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const SlotTemplate: StoryFn = (args, {argTypes}) => ({
|
||||
props: Object.keys(argTypes),
|
||||
components: {
|
||||
N8nNotice,
|
||||
},
|
||||
template: `<n8n-notice v-bind="$props">This is a notice! Thread carefully from this point forward.</n8n-notice>`,
|
||||
});
|
||||
|
||||
const PropTemplate: StoryFn = (args, {argTypes}) => ({
|
||||
props: Object.keys(argTypes),
|
||||
components: {
|
||||
N8nNotice,
|
||||
},
|
||||
template: `<n8n-notice v-bind="$props"/>`,
|
||||
});
|
||||
|
||||
export const Warning = SlotTemplate.bind({});
|
||||
Warning.args = {
|
||||
theme: 'warning',
|
||||
};
|
||||
|
||||
export const Danger = SlotTemplate.bind({});
|
||||
Danger.args = {
|
||||
theme: 'danger',
|
||||
};
|
||||
|
||||
export const Success = SlotTemplate.bind({});
|
||||
Success.args = {
|
||||
theme: 'success',
|
||||
};
|
||||
|
||||
export const Info = SlotTemplate.bind({});
|
||||
Info.args = {
|
||||
theme: 'info',
|
||||
};
|
||||
|
||||
export const Sanitized = PropTemplate.bind({});
|
||||
Sanitized.args = {
|
||||
theme: 'warning',
|
||||
content: '<script>alert(1)</script> This content contains a script tag and is <strong>sanitized</strong>.',
|
||||
};
|
||||
|
||||
export const Truncated = PropTemplate.bind({});
|
||||
Truncated.args = {
|
||||
theme: 'warning',
|
||||
truncate: true,
|
||||
content: 'This content is long and will be truncated at 150 characters. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.',
|
||||
};
|
||||
|
||||
export const HtmlEdgeCase = PropTemplate.bind({});
|
||||
HtmlEdgeCase.args = {
|
||||
theme: 'warning',
|
||||
truncate: true,
|
||||
content: 'This content is long and will be truncated at 150 characters. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod <a href="">read the documentation</a> ut labore et dolore magna aliqua.',
|
||||
};
|
150
packages/design-system/src/components/N8nNotice/Notice.vue
Normal file
150
packages/design-system/src/components/N8nNotice/Notice.vue
Normal file
|
@ -0,0 +1,150 @@
|
|||
<template>
|
||||
<div :id="id" :class="classes" role="alert">
|
||||
<div class="notice-content">
|
||||
<n8n-text size="small">
|
||||
<slot>
|
||||
<span
|
||||
:class="expanded ? $style['expanded'] : $style['truncated']"
|
||||
:id="`${id}-content`"
|
||||
role="region"
|
||||
v-html="sanitizedContent"
|
||||
/>
|
||||
<span v-if="canTruncate">
|
||||
<a
|
||||
role="button"
|
||||
:aria-controls="`${id}-content`"
|
||||
:aria-expanded="canTruncate && !expanded ? 'false' : 'true'"
|
||||
@click="toggleExpanded"
|
||||
>
|
||||
{{ t(expanded ? 'notice.showLess' : 'notice.showMore') }}
|
||||
</a>
|
||||
</span>
|
||||
</slot>
|
||||
</n8n-text>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import Vue from 'vue';
|
||||
import sanitizeHtml from 'sanitize-html';
|
||||
import N8nText from "../../components/N8nText";
|
||||
import Locale from "../../mixins/locale";
|
||||
import {uid} from "../../utils";
|
||||
|
||||
const DEFAULT_TRUNCATION_MAX_LENGTH = 150;
|
||||
|
||||
export default Vue.extend({
|
||||
name: 'n8n-notice',
|
||||
directives: {},
|
||||
mixins: [
|
||||
Locale,
|
||||
],
|
||||
props: {
|
||||
id: {
|
||||
type: String,
|
||||
default: () => uid('notice'),
|
||||
},
|
||||
theme: {
|
||||
type: String,
|
||||
default: 'warning',
|
||||
},
|
||||
truncateAt: {
|
||||
type: Number,
|
||||
default: 150,
|
||||
},
|
||||
truncate: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
content: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
},
|
||||
components: {
|
||||
N8nText,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
expanded: false,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
classes(): string[] {
|
||||
return [
|
||||
'notice',
|
||||
this.$style['notice'],
|
||||
this.$style[this.theme],
|
||||
];
|
||||
},
|
||||
canTruncate(): boolean {
|
||||
return this.truncate && this.content.length > this.truncateAt;
|
||||
},
|
||||
truncatedContent(): string {
|
||||
if (!this.canTruncate || this.expanded) {
|
||||
return this.content;
|
||||
}
|
||||
|
||||
return this.content.slice(0, this.truncateAt as number) + '...';
|
||||
},
|
||||
sanitizedContent(): string {
|
||||
return sanitizeHtml(this.truncatedContent);
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
toggleExpanded() {
|
||||
this.expanded = !this.expanded;
|
||||
},
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" module>
|
||||
.notice {
|
||||
display: flex;
|
||||
color: var(--custom-font-black);
|
||||
margin: 0;
|
||||
padding: var(--spacing-xs);
|
||||
background-color: var(--background-color);
|
||||
border-width: 1px 1px 1px 7px;
|
||||
border-style: solid;
|
||||
border-color: var(--border-color);
|
||||
border-radius: var(--border-radius-small);
|
||||
|
||||
a {
|
||||
font-weight: var(--font-weight-bold);
|
||||
}
|
||||
}
|
||||
|
||||
.warning {
|
||||
--border-color: var(--color-warning-tint-1);
|
||||
--background-color: var(--color-warning-tint-2);
|
||||
}
|
||||
|
||||
.danger {
|
||||
--border-color: var(--color-danger-tint-1);
|
||||
--background-color: var(--color-danger-tint-2);
|
||||
}
|
||||
|
||||
.success {
|
||||
--border-color: var(--color-success-tint-1);
|
||||
--background-color: var(--color-success-tint-2);
|
||||
}
|
||||
|
||||
.info {
|
||||
--border-color: var(--color-info-tint-1);
|
||||
--background-color: var(--color-info-tint-2);
|
||||
}
|
||||
|
||||
.expanded {
|
||||
+ span {
|
||||
margin-top: var(--spacing-4xs);
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
.truncated {
|
||||
display: inline;
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,95 @@
|
|||
import {fireEvent, render} from '@testing-library/vue';
|
||||
import N8nNotice from "../Notice.vue";
|
||||
|
||||
describe('components', () => {
|
||||
describe('N8nNotice', () => {
|
||||
it('should render correctly', () => {
|
||||
const wrapper = render(N8nNotice, {
|
||||
props: {
|
||||
id: 'notice',
|
||||
},
|
||||
slots: {
|
||||
default: 'This is a notice.',
|
||||
},
|
||||
});
|
||||
expect(wrapper.html()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
describe('props', () => {
|
||||
describe('content', () => {
|
||||
it('should render correctly with content prop', () => {
|
||||
const wrapper = render(N8nNotice, {
|
||||
props: {
|
||||
id: 'notice',
|
||||
content: 'This is a notice.',
|
||||
},
|
||||
});
|
||||
expect(wrapper.html()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should render html', () => {
|
||||
const wrapper = render(N8nNotice, {
|
||||
props: {
|
||||
id: 'notice',
|
||||
content: '<strong>Hello world!</strong> This is a notice.',
|
||||
},
|
||||
});
|
||||
|
||||
expect(wrapper.container.querySelectorAll('strong')).toHaveLength(1);
|
||||
expect(wrapper.html()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should sanitize rendered html', () => {
|
||||
const wrapper = render(N8nNotice, {
|
||||
props: {
|
||||
id: 'notice',
|
||||
content: '<script>alert(1);</script> This is a notice.',
|
||||
},
|
||||
});
|
||||
|
||||
expect(wrapper.container.querySelector('script')).not.toBeTruthy();
|
||||
expect(wrapper.html()).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('truncation', () => {
|
||||
it('should truncate content longer than 150 characters', async () => {
|
||||
const wrapper = render(N8nNotice, {
|
||||
props: {
|
||||
id: 'notice',
|
||||
truncate: true,
|
||||
content: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.',
|
||||
},
|
||||
});
|
||||
|
||||
const button = await wrapper.findByRole('button');
|
||||
const region = await wrapper.findByRole('region');
|
||||
|
||||
expect(button).toBeVisible();
|
||||
expect(button).toHaveTextContent('Show more');
|
||||
|
||||
expect(region).toBeVisible();
|
||||
expect(region.textContent!.endsWith('...')).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should expand truncated text when clicking show more', async () => {
|
||||
const wrapper = render(N8nNotice, {
|
||||
props: {
|
||||
id: 'notice',
|
||||
truncate: true,
|
||||
content: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.',
|
||||
},
|
||||
});
|
||||
|
||||
const button = await wrapper.findByRole('button');
|
||||
const region = await wrapper.findByRole('region');
|
||||
|
||||
await fireEvent.click(button);
|
||||
|
||||
expect(button).toHaveTextContent('Show less');
|
||||
expect(region.textContent!.endsWith('...')).not.toBeTruthy();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,28 @@
|
|||
// Vitest Snapshot v1
|
||||
|
||||
exports[`components > N8nNotice > props > content > should render correctly with content prop 1`] = `
|
||||
"<div id=\\"notice\\" role=\\"alert\\" class=\\"notice _notice_4m4il_1 _warning_4m4il_16\\">
|
||||
<div class=\\"notice-content\\"><span class=\\"_size-small_9dlpz_24 _regular_9dlpz_5\\"><span id=\\"notice-content\\" role=\\"region\\" class=\\"_truncated_4m4il_41\\">This is a notice.</span>
|
||||
<!----></span></div>
|
||||
</div>"
|
||||
`;
|
||||
|
||||
exports[`components > N8nNotice > props > content > should render html 1`] = `
|
||||
"<div id=\\"notice\\" role=\\"alert\\" class=\\"notice _notice_4m4il_1 _warning_4m4il_16\\">
|
||||
<div class=\\"notice-content\\"><span class=\\"_size-small_9dlpz_24 _regular_9dlpz_5\\"><span id=\\"notice-content\\" role=\\"region\\" class=\\"_truncated_4m4il_41\\"><strong>Hello world!</strong> This is a notice.</span>
|
||||
<!----></span></div>
|
||||
</div>"
|
||||
`;
|
||||
|
||||
exports[`components > N8nNotice > props > content > should sanitize rendered html 1`] = `
|
||||
"<div id=\\"notice\\" role=\\"alert\\" class=\\"notice _notice_4m4il_1 _warning_4m4il_16\\">
|
||||
<div class=\\"notice-content\\"><span class=\\"_size-small_9dlpz_24 _regular_9dlpz_5\\"><span id=\\"notice-content\\" role=\\"region\\" class=\\"_truncated_4m4il_41\\"> This is a notice.</span>
|
||||
<!----></span></div>
|
||||
</div>"
|
||||
`;
|
||||
|
||||
exports[`components > N8nNotice > should render correctly 1`] = `
|
||||
"<div id=\\"notice\\" role=\\"alert\\" class=\\"notice _notice_4m4il_1 _warning_4m4il_16\\">
|
||||
<div class=\\"notice-content\\"><span class=\\"_size-small_9dlpz_24 _regular_9dlpz_5\\">This is a notice.</span></div>
|
||||
</div>"
|
||||
`;
|
3
packages/design-system/src/components/N8nNotice/index.ts
Normal file
3
packages/design-system/src/components/N8nNotice/index.ts
Normal file
|
@ -0,0 +1,3 @@
|
|||
import N8nNotice from './Notice.vue';
|
||||
|
||||
export default N8nNotice;
|
|
@ -11,7 +11,7 @@
|
|||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import RadioButton from './RadioButton';
|
||||
import RadioButton from './RadioButton.vue';
|
||||
|
||||
export default {
|
||||
name: 'n8n-radio-buttons',
|
||||
|
|
|
@ -27,7 +27,7 @@
|
|||
<script lang="ts">
|
||||
import Vue from 'vue';
|
||||
import N8nUserInfo from '../N8nUserInfo';
|
||||
import { IUser } from '../../Interface';
|
||||
import { IUser } from '../../types';
|
||||
import ElSelect from 'element-ui/lib/select';
|
||||
import ElOption from 'element-ui/lib/option';
|
||||
import Locale from '../../mixins/locale';
|
||||
|
|
|
@ -20,7 +20,7 @@
|
|||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { IUser } from '../../Interface';
|
||||
import { IUser } from '../../types';
|
||||
import Vue from 'vue';
|
||||
import N8nActionToggle from '../N8nActionToggle';
|
||||
import N8nBadge from '../N8nBadge';
|
||||
|
|
|
@ -1,10 +0,0 @@
|
|||
import Vue from 'vue';
|
||||
|
||||
/** N8n component common definition */
|
||||
export declare class N8nComponent extends Vue {
|
||||
/** Install component into Vue */
|
||||
static install(vue: typeof Vue): void;
|
||||
}
|
||||
|
||||
/** Component size definition for button, input, etc */
|
||||
export type N8nComponentSize = 'xlarge' | 'large' | 'medium' | 'small' | 'mini';
|
|
@ -1 +0,0 @@
|
|||
declare module './N8nButton';
|
|
@ -52,6 +52,7 @@ import N8nLoading from './N8nLoading';
|
|||
import N8nMarkdown from './N8nMarkdown';
|
||||
import N8nMenu from './N8nMenu';
|
||||
import N8nMenuItem from './N8nMenuItem';
|
||||
import N8nNotice from './N8nNotice';
|
||||
import N8nLink from './N8nLink';
|
||||
import N8nOption from './N8nOption';
|
||||
import N8nRadioButtons from './N8nRadioButtons';
|
||||
|
@ -90,6 +91,7 @@ export {
|
|||
N8nMarkdown,
|
||||
N8nMenu,
|
||||
N8nMenuItem,
|
||||
N8nNotice,
|
||||
N8nOption,
|
||||
N8nRadioButtons,
|
||||
N8nSelect,
|
|
@ -5,6 +5,8 @@ export default {
|
|||
'nds.userSelect.noMatchingUsers': 'No matching users',
|
||||
'nds.usersList.deleteUser': 'Delete User',
|
||||
'nds.usersList.reinviteUser': 'Resend invite',
|
||||
'notice.showMore': 'Show more',
|
||||
'notice.showLess': 'Show less',
|
||||
'formInput.validator.fieldRequired': 'This field is required',
|
||||
'formInput.validator.minCharactersRequired': 'Must be at least {minimum} characters',
|
||||
'formInput.validator.maxCharactersRequired': 'Must be at most {maximum} characters',
|
||||
|
|
17
packages/design-system/src/main.d.ts
vendored
17
packages/design-system/src/main.d.ts
vendored
|
@ -1 +1,16 @@
|
|||
declare module 'n8n-design-system';
|
||||
import Vue from 'vue';
|
||||
import * as locale from './locale';
|
||||
|
||||
declare module 'vue/types/vue' {
|
||||
interface Vue {
|
||||
$style: Record<string, string>;
|
||||
}
|
||||
}
|
||||
|
||||
declare module 'n8n-design-system' {
|
||||
export * from './components';
|
||||
export { N8nUserSelect, N8nUsersList } from './components'; // Workaround for circular imports, will be removed when migrated to typescript
|
||||
export { locale };
|
||||
}
|
||||
|
||||
export * from './types';
|
||||
|
|
|
@ -1,10 +1,15 @@
|
|||
import * as components from './components';
|
||||
import * as locale from './locale';
|
||||
|
||||
// @TODO Define proper plugin that loads all components
|
||||
// tslint:disable-next-line:forin
|
||||
for (const key in components) {
|
||||
const component = components[key];
|
||||
component.install = function (Vue) {
|
||||
Vue.component(component.name, component);
|
||||
|
||||
component.install = (app) => {
|
||||
app.component(component.name, component);
|
||||
};
|
||||
}
|
||||
|
||||
export { locale };
|
||||
export * from './components';
|
||||
|
|
1
packages/design-system/src/mixins/index.ts
Normal file
1
packages/design-system/src/mixins/index.ts
Normal file
|
@ -0,0 +1 @@
|
|||
export { default as localeMixin } from './locale';
|
|
@ -2,7 +2,7 @@ import { t } from '../locale';
|
|||
|
||||
export default {
|
||||
methods: {
|
||||
t(...args) {
|
||||
t(...args: string[]) {
|
||||
return t.apply(this, args);
|
||||
},
|
||||
},
|
44
packages/design-system/src/shims-element-ui.d.ts
vendored
44
packages/design-system/src/shims-element-ui.d.ts
vendored
|
@ -1,14 +1,38 @@
|
|||
declare module 'element-ui/lib/button';
|
||||
declare module 'element-ui/lib/col';
|
||||
declare module 'element-ui/lib/input';
|
||||
declare module 'element-ui/lib/tooltip';
|
||||
declare module 'element-ui/lib/input-number';
|
||||
declare module 'element-ui/lib/drawer';
|
||||
declare module 'element-ui/lib/dialog';
|
||||
declare module 'element-ui/lib/dropdown';
|
||||
declare module 'element-ui/lib/dropdown-menu';
|
||||
declare module 'element-ui/lib/dropdown-item';
|
||||
declare module 'element-ui/lib/submenu';
|
||||
declare module 'element-ui/lib/radio';
|
||||
declare module 'element-ui/lib/radio-group';
|
||||
declare module 'element-ui/lib/radio-button';
|
||||
declare module 'element-ui/lib/checkbox';
|
||||
declare module 'element-ui/lib/switch';
|
||||
declare module 'element-ui/lib/select';
|
||||
declare module 'element-ui/lib/option';
|
||||
declare module 'element-ui/lib/option-group';
|
||||
declare module 'element-ui/lib/pagination';
|
||||
declare module 'element-ui/lib/button-group';
|
||||
declare module 'element-ui/lib/table';
|
||||
declare module 'element-ui/lib/table-column';
|
||||
declare module 'element-ui/lib/date-picker';
|
||||
declare module 'element-ui/lib/tabs';
|
||||
declare module 'element-ui/lib/tab-pane';
|
||||
declare module 'element-ui/lib/tag';
|
||||
declare module 'element-ui/lib/row';
|
||||
declare module 'element-ui/lib/col';
|
||||
declare module 'element-ui/lib/badge';
|
||||
declare module 'element-ui/lib/card';
|
||||
declare module 'element-ui/lib/color-picker';
|
||||
declare module 'element-ui/lib/container';
|
||||
declare module 'element-ui/lib/loading';
|
||||
declare module 'element-ui/lib/message-box';
|
||||
declare module 'element-ui/lib/message';
|
||||
declare module 'element-ui/lib/menu';
|
||||
declare module 'element-ui/lib/menu-item';
|
||||
declare module 'element-ui/lib/row';
|
||||
declare module 'element-ui/lib/tag';
|
||||
declare module 'element-ui/lib/skeleton';
|
||||
declare module 'element-ui/lib/skeleton-item';
|
||||
|
||||
declare module 'element-ui/lib/notification';
|
||||
declare module 'element-ui/lib/popover';
|
||||
declare module 'element-ui/lib/transitions/collapse-transition';
|
||||
declare module 'element-ui/lib/tooltip';
|
||||
declare module 'element-ui/lib/input-number';
|
||||
|
|
46
packages/design-system/src/types/form.ts
Normal file
46
packages/design-system/src/types/form.ts
Normal file
|
@ -0,0 +1,46 @@
|
|||
export type Rule = { name: string; config?: any}; // tslint:disable-line:no-any
|
||||
|
||||
export type RuleGroup = {
|
||||
rules: Array<Rule | RuleGroup>;
|
||||
defaultError?: {messageKey: string, options?: any}; // tslint:disable-line:no-any
|
||||
};
|
||||
|
||||
export type IValidator = {
|
||||
validate: (value: string | number | boolean | null | undefined, config: any) => false | {messageKey: string, options?: any}; // tslint:disable-line:no-any
|
||||
};
|
||||
|
||||
|
||||
export type IFormInput = {
|
||||
name: string;
|
||||
initialValue?: string | number | boolean | null;
|
||||
properties: {
|
||||
label?: string;
|
||||
type?: 'text' | 'email' | 'password' | 'select' | 'multi-select' | 'info';
|
||||
maxlength?: number;
|
||||
required?: boolean;
|
||||
showRequiredAsterisk?: boolean;
|
||||
validators?: {
|
||||
[name: string]: IValidator;
|
||||
};
|
||||
validationRules?: Array<Rule | RuleGroup>;
|
||||
validateOnBlur?: boolean;
|
||||
infoText?: string;
|
||||
placeholder?: string;
|
||||
options?: Array<{label: string; value: string}>;
|
||||
autocomplete?: 'off' | 'new-password' | 'current-password' | 'given-name' | 'family-name' | 'email'; // https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/autocomplete
|
||||
capitalize?: boolean;
|
||||
focusInitially?: boolean;
|
||||
};
|
||||
shouldDisplay?: (values: {[key: string]: unknown}) => boolean;
|
||||
};
|
||||
|
||||
export type IFormInputs = IFormInput[];
|
||||
|
||||
export type IFormBoxConfig = {
|
||||
title: string;
|
||||
buttonText?: string;
|
||||
secondaryButtonText?: string;
|
||||
inputs: IFormInputs;
|
||||
redirectLink?: string;
|
||||
redirectText?: string;
|
||||
};
|
2
packages/design-system/src/types/index.ts
Normal file
2
packages/design-system/src/types/index.ts
Normal file
|
@ -0,0 +1,2 @@
|
|||
export * from './form';
|
||||
export * from './user';
|
8
packages/design-system/src/types/user.ts
Normal file
8
packages/design-system/src/types/user.ts
Normal file
|
@ -0,0 +1,8 @@
|
|||
export interface IUser {
|
||||
id: string;
|
||||
firstName?: string;
|
||||
lastName?: string;
|
||||
email?: string;
|
||||
isPending: boolean;
|
||||
isOwner: boolean;
|
||||
}
|
2
packages/design-system/src/utils/index.ts
Normal file
2
packages/design-system/src/utils/index.ts
Normal file
|
@ -0,0 +1,2 @@
|
|||
export * from './markdown';
|
||||
export * from './uid';
|
9
packages/design-system/src/utils/uid.ts
Normal file
9
packages/design-system/src/utils/uid.ts
Normal file
|
@ -0,0 +1,9 @@
|
|||
/**
|
||||
* Math.random should be unique because of its seeding algorithm.
|
||||
* Convert it to base 36 (numbers + letters), and grab the first 9 characters after the decimal.
|
||||
*
|
||||
* @param baseId
|
||||
*/
|
||||
export function uid (baseId?: string): string {
|
||||
return `${baseId ? `${baseId}-` : ''}${Math.random().toString(36).substring(2, 11)}`;
|
||||
}
|
|
@ -111,17 +111,21 @@
|
|||
var(--color-warning-l)
|
||||
);
|
||||
|
||||
--color-warning-tint-1-l: 88%;
|
||||
--color-warning-tint-1-h: 35;
|
||||
--color-warning-tint-1-s: 78%;
|
||||
--color-warning-tint-1-l: 84%;
|
||||
--color-warning-tint-1: hsl(
|
||||
var(--color-warning-h),
|
||||
var(--color-warning-s),
|
||||
var(--color-warning-tint-1-l)
|
||||
);
|
||||
|
||||
--color-warning-tint-2-h: 34%;
|
||||
--color-warning-tint-2-s: 80%;
|
||||
--color-warning-tint-2-l: 96%;
|
||||
--color-warning-tint-2: hsl(
|
||||
var(--color-warning-h),
|
||||
var(--color-warning-s),
|
||||
var(--color-warning-tint-2-h),
|
||||
var(--color-warning-tint-2-s),
|
||||
var(--color-warning-tint-2-l)
|
||||
);
|
||||
|
||||
|
|
|
@ -3,16 +3,26 @@
|
|||
"target": "esnext",
|
||||
"module": "esnext",
|
||||
"strict": true,
|
||||
"jsx": "preserve",
|
||||
"importHelpers": true,
|
||||
"moduleResolution": "node",
|
||||
"declaration": true,
|
||||
"experimentalDecorators": true,
|
||||
"skipLibCheck": true,
|
||||
"esModuleInterop": true,
|
||||
"allowJs": true,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"sourceMap": true,
|
||||
"baseUrl": ".",
|
||||
"types": ["webpack-env", "jest"],
|
||||
"outDir": "dist",
|
||||
"types": [
|
||||
"webpack-env",
|
||||
"jest",
|
||||
"vitest/globals"
|
||||
],
|
||||
"typeRoots": [
|
||||
"@testing-library",
|
||||
"@types"
|
||||
],
|
||||
"paths": {
|
||||
"@/*": ["src/*"]
|
||||
},
|
||||
|
@ -21,9 +31,7 @@
|
|||
"include": [
|
||||
"src/**/*.ts",
|
||||
"src/**/*.tsx",
|
||||
"src/**/*.vue",
|
||||
"tests/**/*.ts",
|
||||
"tests/**/*.tsx"
|
||||
"src/**/*.vue"
|
||||
],
|
||||
"exclude": ["node_modules"]
|
||||
}
|
||||
|
|
42
packages/design-system/vite.config.ts
Normal file
42
packages/design-system/vite.config.ts
Normal file
|
@ -0,0 +1,42 @@
|
|||
import { createVuePlugin } from 'vite-plugin-vue2';
|
||||
import { resolve } from 'path';
|
||||
|
||||
export default {
|
||||
plugins: [
|
||||
createVuePlugin(),
|
||||
],
|
||||
resolve: {
|
||||
alias: {
|
||||
'@': resolve(__dirname, 'src'),
|
||||
'vue2-boring-avatars': resolve(__dirname, '..', '..', 'node_modules', 'vue2-boring-avatars', 'dist', 'vue-2-boring-avatars.umd.js'),
|
||||
// 'vue2-boring-avatars': 'vue2-boring-avatars/dist/vue-2-boring-avatars.umd.js',
|
||||
},
|
||||
},
|
||||
build: {
|
||||
lib: {
|
||||
entry: resolve(__dirname, 'src', 'main.js'),
|
||||
name: 'N8nDesignSystem',
|
||||
fileName: (format) => `n8n-design-system.${format}.js`,
|
||||
},
|
||||
rollupOptions: {
|
||||
// make sure to externalize deps that shouldn't be bundled
|
||||
// into your library
|
||||
external: ['vue'],
|
||||
output: {
|
||||
exports: 'named',
|
||||
// Provide global variables to use in the UMD build
|
||||
// for externalized deps
|
||||
globals: {
|
||||
vue: 'Vue',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
test: {
|
||||
globals: true,
|
||||
environment: 'jsdom',
|
||||
setupFiles: [
|
||||
'./src/__tests__/setup.ts',
|
||||
],
|
||||
},
|
||||
};
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "n8n-editor-ui",
|
||||
"version": "0.141.0",
|
||||
"version": "0.142.0",
|
||||
"description": "Workflow Editor UI for n8n",
|
||||
"license": "SEE LICENSE IN LICENSE.md",
|
||||
"homepage": "https://n8n.io",
|
||||
|
@ -28,7 +28,7 @@
|
|||
"@fortawesome/free-regular-svg-icons": "^6.1.1",
|
||||
"luxon": "^2.3.0",
|
||||
"monaco-editor": "^0.29.1",
|
||||
"n8n-design-system": "~0.18.0",
|
||||
"n8n-design-system": "~0.19.0",
|
||||
"timeago.js": "^4.0.2",
|
||||
"v-click-outside": "^3.1.2",
|
||||
"vue-fragment": "^1.5.2",
|
||||
|
@ -78,7 +78,7 @@
|
|||
"lodash.debounce": "^4.0.8",
|
||||
"lodash.get": "^4.4.2",
|
||||
"lodash.set": "^4.3.2",
|
||||
"n8n-workflow": "~0.97.0",
|
||||
"n8n-workflow": "~0.98.0",
|
||||
"monaco-editor-webpack-plugin": "^5.0.0",
|
||||
"normalize-wheel": "^1.0.1",
|
||||
"prismjs": "^1.17.1",
|
||||
|
|
|
@ -23,6 +23,8 @@ import {
|
|||
WorkflowExecuteMode,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
export * from 'n8n-design-system/src/types';
|
||||
|
||||
declare module 'jsplumb' {
|
||||
interface PaintStyle {
|
||||
stroke?: string;
|
||||
|
@ -477,12 +479,6 @@ export interface IPushDataConsoleMessage {
|
|||
messages: string[];
|
||||
}
|
||||
|
||||
export interface IVersionNotificationSettings {
|
||||
enabled: boolean;
|
||||
endpoint: string;
|
||||
infoUrl: string;
|
||||
}
|
||||
|
||||
export type IPersonalizationSurveyAnswersV1 = {
|
||||
codingSkill?: string | null;
|
||||
companyIndustry?: string[] | null;
|
||||
|
@ -505,6 +501,34 @@ export type IPersonalizationSurveyAnswersV2 = {
|
|||
otherCompanyIndustryExtended?: string[] | null;
|
||||
};
|
||||
|
||||
export type IRole = 'default' | 'owner' | 'member';
|
||||
|
||||
export interface IUserResponse {
|
||||
id: string;
|
||||
firstName?: string;
|
||||
lastName?: string;
|
||||
email?: string;
|
||||
globalRole?: {
|
||||
name: IRole;
|
||||
id: string;
|
||||
};
|
||||
personalizationAnswers?: IPersonalizationSurveyAnswersV1 | IPersonalizationSurveyAnswersV2 | null;
|
||||
isPending: boolean;
|
||||
}
|
||||
|
||||
export interface IUser extends IUserResponse {
|
||||
isDefaultUser: boolean;
|
||||
isPendingUser: boolean;
|
||||
isOwner: boolean;
|
||||
fullName?: string;
|
||||
}
|
||||
|
||||
export interface IVersionNotificationSettings {
|
||||
enabled: boolean;
|
||||
endpoint: string;
|
||||
infoUrl: string;
|
||||
}
|
||||
|
||||
export interface IN8nPrompts {
|
||||
message: string;
|
||||
title: string;
|
||||
|
@ -888,21 +912,6 @@ export interface IBounds {
|
|||
|
||||
export type ILogInStatus = 'LoggedIn' | 'LoggedOut';
|
||||
|
||||
export type IRole = 'default' | 'owner' | 'member';
|
||||
|
||||
export interface IUserResponse {
|
||||
id: string;
|
||||
firstName?: string;
|
||||
lastName?: string;
|
||||
email?: string;
|
||||
globalRole?: {
|
||||
name: IRole;
|
||||
id: string;
|
||||
};
|
||||
personalizationAnswers?: IPersonalizationSurveyAnswersV1 | IPersonalizationSurveyAnswersV2 | null;
|
||||
isPending: boolean;
|
||||
}
|
||||
|
||||
export interface IInviteResponse {
|
||||
user: {
|
||||
id: string;
|
||||
|
@ -911,59 +920,6 @@ export interface IInviteResponse {
|
|||
error?: string;
|
||||
}
|
||||
|
||||
export interface IUser extends IUserResponse {
|
||||
isDefaultUser: boolean;
|
||||
isPendingUser: boolean;
|
||||
isOwner: boolean;
|
||||
fullName?: string;
|
||||
}
|
||||
|
||||
export type Rule = { name: string; config?: any}; // tslint:disable-line:no-any
|
||||
|
||||
export type RuleGroup = {
|
||||
rules: Array<Rule | RuleGroup>;
|
||||
defaultError?: {messageKey: string, options?: any}; // tslint:disable-line:no-any
|
||||
};
|
||||
|
||||
export type IValidator = {
|
||||
validate: (value: string | number | boolean | null | undefined, config: any) => false | {messageKey: string, options?: any}; // tslint:disable-line:no-any
|
||||
};
|
||||
|
||||
export type IFormInput = {
|
||||
name: string;
|
||||
initialValue?: string | number | boolean | null;
|
||||
properties: {
|
||||
label?: string;
|
||||
type?: 'text' | 'email' | 'password' | 'select' | 'multi-select' | 'info';
|
||||
maxlength?: number;
|
||||
required?: boolean;
|
||||
showRequiredAsterisk?: boolean;
|
||||
validators?: {
|
||||
[name: string]: IValidator;
|
||||
};
|
||||
validationRules?: Array<Rule | RuleGroup>;
|
||||
validateOnBlur?: boolean;
|
||||
infoText?: string;
|
||||
placeholder?: string;
|
||||
options?: Array<{label: string; value: string}>;
|
||||
autocomplete?: 'off' | 'new-password' | 'current-password' | 'given-name' | 'family-name' | 'email'; // https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/autocomplete
|
||||
capitalize?: boolean;
|
||||
focusInitially?: boolean;
|
||||
};
|
||||
shouldDisplay?: (values: {[key: string]: unknown}) => boolean;
|
||||
};
|
||||
|
||||
export type IFormInputs = IFormInput[];
|
||||
|
||||
export type IFormBoxConfig = {
|
||||
title: string;
|
||||
buttonText?: string;
|
||||
secondaryButtonText?: string;
|
||||
inputs: IFormInputs;
|
||||
redirectLink?: string;
|
||||
redirectText?: string;
|
||||
};
|
||||
|
||||
export interface ITab {
|
||||
value: string | number;
|
||||
label?: string;
|
||||
|
|
|
@ -32,6 +32,7 @@
|
|||
|
||||
<script lang="ts">
|
||||
import {
|
||||
INodeUi,
|
||||
IUpdateInformation,
|
||||
} from '@/Interface';
|
||||
|
||||
|
@ -87,6 +88,9 @@ export default mixins(
|
|||
return this.displayNodeParameter(option as INodeProperties);
|
||||
});
|
||||
},
|
||||
node (): INodeUi {
|
||||
return this.$store.getters.activeNode;
|
||||
},
|
||||
// Returns all the options which did not get added already
|
||||
parameterOptions (): Array<INodePropertyOptions | INodeProperties> {
|
||||
return (this.filteredOptions as Array<INodePropertyOptions | INodeProperties>).filter((option) => {
|
||||
|
@ -127,7 +131,7 @@ export default mixins(
|
|||
// If it is not defined no need to do a proper check
|
||||
return true;
|
||||
}
|
||||
return this.displayParameter(this.nodeValues, parameter, this.path);
|
||||
return this.displayParameter(this.nodeValues, parameter, this.path, this.node);
|
||||
},
|
||||
optionSelected (optionName: string) {
|
||||
const options = this.getOptionProperties(optionName);
|
||||
|
|
|
@ -395,6 +395,7 @@ export default mixins(showMessage, nodeHelpers).extend({
|
|||
this.credentialData as INodeParameters,
|
||||
parameter,
|
||||
'',
|
||||
null,
|
||||
);
|
||||
},
|
||||
getCredentialProperties(name: string): INodeProperties[] {
|
||||
|
@ -598,6 +599,7 @@ export default mixins(showMessage, nodeHelpers).extend({
|
|||
this.credentialData as INodeParameters,
|
||||
false,
|
||||
false,
|
||||
null,
|
||||
);
|
||||
|
||||
const credentialDetails: ICredentialsDecrypted = {
|
||||
|
|
|
@ -251,7 +251,7 @@ export default mixins(
|
|||
// If it is not defined no need to do a proper check
|
||||
return true;
|
||||
}
|
||||
return this.displayParameter(this.node.parameters, credentialTypeDescription, '');
|
||||
return this.displayParameter(this.node.parameters, credentialTypeDescription, '', this.node);
|
||||
},
|
||||
|
||||
getIssues (credentialTypeName: string): string[] {
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue