* ⚡ Generalize unique entity name generation * ⚡ Standardize variable names * redo credentials * revert some changes, replace got with was * fix v-if order * fix v-if order * update linting * update gulpfile * update ssh display name * update height * update params * update info tip sizes * address design comments * update google button disabled * update icon size to 28px * update design issues * update info tab design * address design comments * update tab size * update run data spacing * address comments, update logo design * fix spacing issues * clean up store * fix create new bug * add loading state * rename prop * remove unused prop * fix select bug * remove label tag * update word break * build * address design comments * update font family of button * update menu opacity * update text * update title * address more comments * update oauth messages * add oauth validation * hide disabled state * update warning modal * show button on text input * clean up cred details * add validation errors * fix bug when deleting cred * Frontend hack to display test button * Created interfaces for testing and endpoint * Testing slack node credentials working * Adding test with node to endpoint for credential testing * Fixed linting and test detectability * Adding required for slack token * Added google sheets credential testing * update message * Adding suggestions by Ivan and Mutasem * Address comments * keep blurred when focused * update font weight of errors * add oauth banner * remove toast * Fixed code bug and added telegram credential testing * scroll to top on success * clean up duplication * Fixed telegram trigger node and added tests to typeform * refactor modal * add more validation support * refactor info tab * scroll to bottom on save, handle cred saving * refactor save button * save cred on valid * save cred on valid * scroll to top if has error * add targets on input labels * delete credentails input * revert fe changes * update validation logic * clean interface * test credentials * update banner design * show testing state * update x position * fix issues * fix focus issues * clean up validation behavior * make error relative * update banner component * update error spacing * don't close dialog * rename button * update how banners behave * if has unsaved changes first * move confirm message * add success banner * update time state * disable transitions * test on open * clean up banner behavior * update banner styling * capitalize * update error banner styling to handle long texts * avoid unnessary content jostling * add loading label * show validation warnings when opening modal * retest cred if not all props req * update scroll to auto * add error warning * update color saturation * set overflow to auto * fix bug to get credentials when connected * round down to minutes * change tab name * update casing oauth * disable credential testing if it has expressions * label same as title * add more space between close and save * remove check on making any changes * hide close on confirm modals * don't accept clicks outside dialog * fix build issues * undo test changes * fix table scrollbar logs * rename modals * fix bug with same name * refactor modal * fix tslint issue * refactor name * update name behavior * update monospace font * remove comment * refactor inputs * refactor error handling * reduce spacing changes * fix doc url oauth1 oauth2 * build * hide infotip if no inputs * address most comments * rename file * fix menu alignment * gst * update types Co-authored-by: Iván Ovejero <ivov.src@gmail.com> Co-authored-by: Omar Ajoue <krynble@gmail.com>
231 lines
6.9 KiB
TypeScript
231 lines
6.9 KiB
TypeScript
/* eslint-disable @typescript-eslint/no-non-null-assertion */
|
|
/* eslint-disable @typescript-eslint/restrict-template-expressions */
|
|
/* eslint-disable @typescript-eslint/no-unsafe-return */
|
|
/* eslint-disable no-param-reassign */
|
|
/* eslint-disable no-underscore-dangle */
|
|
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
|
import * as express from 'express';
|
|
import { join as pathJoin } from 'path';
|
|
import { readFile as fsReadFile } from 'fs/promises';
|
|
import { readFileSync as fsReadFileSync } from 'fs';
|
|
import { IDataObject } from 'n8n-workflow';
|
|
import * as config from '../config';
|
|
|
|
// eslint-disable-next-line import/no-cycle
|
|
import { Db, ICredentialsDb, IPackageVersions } from '.';
|
|
// eslint-disable-next-line import/order
|
|
import { Like } from 'typeorm';
|
|
// eslint-disable-next-line import/no-cycle
|
|
import { WorkflowEntity } from './databases/entities/WorkflowEntity';
|
|
|
|
let versionCache: IPackageVersions | undefined;
|
|
|
|
/**
|
|
* Returns the base URL n8n is reachable from
|
|
*
|
|
* @export
|
|
* @returns {string}
|
|
*/
|
|
export function getBaseUrl(): string {
|
|
const protocol = config.get('protocol');
|
|
const host = config.get('host');
|
|
const port = config.get('port');
|
|
const path = config.get('path');
|
|
|
|
if ((protocol === 'http' && port === 80) || (protocol === 'https' && port === 443)) {
|
|
return `${protocol}://${host}${path}`;
|
|
}
|
|
return `${protocol}://${host}:${port}${path}`;
|
|
}
|
|
|
|
/**
|
|
* Returns the session id if one is set
|
|
*
|
|
* @export
|
|
* @param {express.Request} req
|
|
* @returns {(string | undefined)}
|
|
*/
|
|
export function getSessionId(req: express.Request): string | undefined {
|
|
return req.headers.sessionid as string | undefined;
|
|
}
|
|
|
|
/**
|
|
* Returns information which version of the packages are installed
|
|
*
|
|
* @export
|
|
* @returns {Promise<IPackageVersions>}
|
|
*/
|
|
export async function getVersions(): Promise<IPackageVersions> {
|
|
if (versionCache !== undefined) {
|
|
return versionCache;
|
|
}
|
|
|
|
const packageFile = await fsReadFile(pathJoin(__dirname, '../../package.json'), 'utf8');
|
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
|
const packageData = JSON.parse(packageFile);
|
|
|
|
versionCache = {
|
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
|
cli: packageData.version,
|
|
};
|
|
|
|
return versionCache;
|
|
}
|
|
|
|
/**
|
|
* Extracts configuration schema for key
|
|
*
|
|
* @param {string} configKey
|
|
* @param {IDataObject} configSchema
|
|
* @returns {IDataObject} schema of the configKey
|
|
*/
|
|
function extractSchemaForKey(configKey: string, configSchema: IDataObject): IDataObject {
|
|
const configKeyParts = configKey.split('.');
|
|
|
|
// eslint-disable-next-line no-restricted-syntax
|
|
for (const key of configKeyParts) {
|
|
if (configSchema[key] === undefined) {
|
|
throw new Error(`Key "${key}" of ConfigKey "${configKey}" does not exist`);
|
|
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
} else if ((configSchema[key]! as IDataObject)._cvtProperties === undefined) {
|
|
configSchema = configSchema[key] as IDataObject;
|
|
} else {
|
|
configSchema = (configSchema[key] as IDataObject)._cvtProperties as IDataObject;
|
|
}
|
|
}
|
|
return configSchema;
|
|
}
|
|
|
|
/**
|
|
* Gets value from config with support for "_FILE" environment variables
|
|
*
|
|
* @export
|
|
* @param {string} configKey The key of the config data to get
|
|
* @returns {(Promise<string | boolean | number | undefined>)}
|
|
*/
|
|
export async function getConfigValue(
|
|
configKey: string,
|
|
): Promise<string | boolean | number | undefined> {
|
|
// Get the environment variable
|
|
const configSchema = config.getSchema();
|
|
// @ts-ignore
|
|
const currentSchema = extractSchemaForKey(configKey, configSchema._cvtProperties as IDataObject);
|
|
// Check if environment variable is defined for config key
|
|
if (currentSchema.env === undefined) {
|
|
// No environment variable defined, so return value from config
|
|
return config.get(configKey);
|
|
}
|
|
|
|
// Check if special file enviroment variable exists
|
|
const fileEnvironmentVariable = process.env[`${currentSchema.env}_FILE`];
|
|
if (fileEnvironmentVariable === undefined) {
|
|
// Does not exist, so return value from config
|
|
return config.get(configKey);
|
|
}
|
|
|
|
let data;
|
|
try {
|
|
data = await fsReadFile(fileEnvironmentVariable, 'utf8');
|
|
} catch (error) {
|
|
if (error.code === 'ENOENT') {
|
|
throw new Error(`The file "${fileEnvironmentVariable}" could not be found.`);
|
|
}
|
|
|
|
throw error;
|
|
}
|
|
|
|
return data;
|
|
}
|
|
|
|
/**
|
|
* Gets value from config with support for "_FILE" environment variables synchronously
|
|
*
|
|
* @export
|
|
* @param {string} configKey The key of the config data to get
|
|
* @returns {(string | boolean | number | undefined)}
|
|
*/
|
|
export function getConfigValueSync(configKey: string): string | boolean | number | undefined {
|
|
// Get the environment variable
|
|
const configSchema = config.getSchema();
|
|
// @ts-ignore
|
|
const currentSchema = extractSchemaForKey(configKey, configSchema._cvtProperties as IDataObject);
|
|
// Check if environment variable is defined for config key
|
|
if (currentSchema.env === undefined) {
|
|
// No environment variable defined, so return value from config
|
|
return config.get(configKey);
|
|
}
|
|
|
|
// Check if special file enviroment variable exists
|
|
const fileEnvironmentVariable = process.env[`${currentSchema.env}_FILE`];
|
|
if (fileEnvironmentVariable === undefined) {
|
|
// Does not exist, so return value from config
|
|
return config.get(configKey);
|
|
}
|
|
|
|
let data;
|
|
try {
|
|
data = fsReadFileSync(fileEnvironmentVariable, 'utf8');
|
|
} catch (error) {
|
|
if (error.code === 'ENOENT') {
|
|
throw new Error(`The file "${fileEnvironmentVariable}" could not be found.`);
|
|
}
|
|
|
|
throw error;
|
|
}
|
|
|
|
return data;
|
|
}
|
|
|
|
/**
|
|
* Generate a unique name for a workflow or credentials entity.
|
|
*
|
|
* - If the name does not yet exist, it returns the requested name.
|
|
* - If the name already exists once, it returns the requested name suffixed with 2.
|
|
* - If the name already exists more than once with suffixes, it looks for the max suffix
|
|
* and returns the requested name with max suffix + 1.
|
|
*/
|
|
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
|
|
export async function generateUniqueName(
|
|
requestedName: string,
|
|
entityType: 'workflow' | 'credentials',
|
|
) {
|
|
const findConditions = {
|
|
select: ['name' as const],
|
|
where: {
|
|
name: Like(`${requestedName}%`),
|
|
},
|
|
};
|
|
|
|
const found: Array<WorkflowEntity | ICredentialsDb> =
|
|
entityType === 'workflow'
|
|
? await Db.collections.Workflow!.find(findConditions)
|
|
: await Db.collections.Credentials!.find(findConditions);
|
|
|
|
// name is unique
|
|
if (found.length === 0) {
|
|
return { name: requestedName };
|
|
}
|
|
|
|
const maxSuffix = found.reduce((acc, { name }) => {
|
|
const parts = name.split(`${requestedName} `);
|
|
|
|
if (parts.length > 2) return acc;
|
|
|
|
const suffix = Number(parts[1]);
|
|
|
|
// eslint-disable-next-line no-restricted-globals
|
|
if (!isNaN(suffix) && Math.ceil(suffix) > acc) {
|
|
acc = Math.ceil(suffix);
|
|
}
|
|
|
|
return acc;
|
|
}, 0);
|
|
|
|
// name is duplicate but no numeric suffixes exist yet
|
|
if (maxSuffix === 0) {
|
|
return { name: `${requestedName} 2` };
|
|
}
|
|
|
|
return { name: `${requestedName} ${maxSuffix + 1}` };
|
|
}
|