✨ Update credentials modal (#2154)
* ⚡ 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>
This commit is contained in:
@@ -52,9 +52,16 @@ import { createHash, createHmac } from 'crypto';
|
||||
import { compare } from 'bcryptjs';
|
||||
import * as promClient from 'prom-client';
|
||||
|
||||
import { Credentials, LoadNodeParameterOptions, UserSettings } from 'n8n-core';
|
||||
import {
|
||||
Credentials,
|
||||
ICredentialTestFunctions,
|
||||
LoadNodeParameterOptions,
|
||||
NodeExecuteFunctions,
|
||||
UserSettings,
|
||||
} from 'n8n-core';
|
||||
|
||||
import {
|
||||
ICredentialsDecrypted,
|
||||
ICredentialsEncrypted,
|
||||
ICredentialType,
|
||||
IDataObject,
|
||||
@@ -66,6 +73,8 @@ import {
|
||||
IWorkflowBase,
|
||||
IWorkflowCredentials,
|
||||
LoggerProxy,
|
||||
NodeCredentialTestRequest,
|
||||
NodeCredentialTestResult,
|
||||
Workflow,
|
||||
WorkflowExecuteMode,
|
||||
} from 'n8n-workflow';
|
||||
@@ -131,7 +140,7 @@ import * as config from '../config';
|
||||
import * as TagHelpers from './TagHelpers';
|
||||
import { TagEntity } from './databases/entities/TagEntity';
|
||||
import { WorkflowEntity } from './databases/entities/WorkflowEntity';
|
||||
import { WorkflowNameRequest } from './WorkflowHelpers';
|
||||
import { NameRequest } from './WorkflowHelpers';
|
||||
|
||||
require('body-parser-xml')(bodyParser);
|
||||
|
||||
@@ -156,6 +165,8 @@ class App {
|
||||
|
||||
defaultWorkflowName: string;
|
||||
|
||||
defaultCredentialsName: string;
|
||||
|
||||
saveDataErrorExecution: string;
|
||||
|
||||
saveDataSuccessExecution: string;
|
||||
@@ -196,6 +207,7 @@ class App {
|
||||
this.endpointWebhookTest = config.get('endpoints.webhookTest') as string;
|
||||
|
||||
this.defaultWorkflowName = config.get('workflows.defaultName') as string;
|
||||
this.defaultCredentialsName = config.get('credentials.defaultName') as string;
|
||||
|
||||
this.saveDataErrorExecution = config.get('executions.saveDataOnError') as string;
|
||||
this.saveDataSuccessExecution = config.get('executions.saveDataOnSuccess') as string;
|
||||
@@ -720,41 +732,11 @@ class App {
|
||||
this.app.get(
|
||||
`/${this.restEndpoint}/workflows/new`,
|
||||
ResponseHelper.send(
|
||||
async (req: WorkflowNameRequest, res: express.Response): Promise<{ name: string }> => {
|
||||
const nameToReturn =
|
||||
async (req: NameRequest, res: express.Response): Promise<{ name: string }> => {
|
||||
const requestedName =
|
||||
req.query.name && req.query.name !== '' ? req.query.name : this.defaultWorkflowName;
|
||||
|
||||
const workflows = await Db.collections.Workflow!.find({
|
||||
select: ['name'],
|
||||
where: { name: Like(`${nameToReturn}%`) },
|
||||
});
|
||||
|
||||
// name is unique
|
||||
if (workflows.length === 0) {
|
||||
return { name: nameToReturn };
|
||||
}
|
||||
|
||||
const maxSuffix = workflows.reduce((acc: number, { name }) => {
|
||||
const parts = name.split(`${nameToReturn} `);
|
||||
|
||||
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: `${nameToReturn} 2` };
|
||||
}
|
||||
|
||||
return { name: `${nameToReturn} ${maxSuffix + 1}` };
|
||||
return await GenericHelpers.generateUniqueName(requestedName, 'workflow');
|
||||
},
|
||||
),
|
||||
);
|
||||
@@ -1237,6 +1219,18 @@ class App {
|
||||
// Credentials
|
||||
// ----------------------------------------
|
||||
|
||||
this.app.get(
|
||||
`/${this.restEndpoint}/credentials/new`,
|
||||
ResponseHelper.send(
|
||||
async (req: NameRequest, res: express.Response): Promise<{ name: string }> => {
|
||||
const requestedName =
|
||||
req.query.name && req.query.name !== '' ? req.query.name : this.defaultCredentialsName;
|
||||
|
||||
return await GenericHelpers.generateUniqueName(requestedName, 'credentials');
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
// Deletes a specific credential
|
||||
this.app.delete(
|
||||
`/${this.restEndpoint}/credentials/:id`,
|
||||
@@ -1323,6 +1317,67 @@ class App {
|
||||
),
|
||||
);
|
||||
|
||||
// Test credentials
|
||||
this.app.post(
|
||||
`/${this.restEndpoint}/credentials-test`,
|
||||
ResponseHelper.send(
|
||||
async (req: express.Request, res: express.Response): Promise<NodeCredentialTestResult> => {
|
||||
const incomingData = req.body as NodeCredentialTestRequest;
|
||||
const credentialType = incomingData.credentials.type;
|
||||
|
||||
// Find nodes that can test this credential.
|
||||
const nodeTypes = NodeTypes();
|
||||
const allNodes = nodeTypes.getAll();
|
||||
|
||||
let foundTestFunction:
|
||||
| ((
|
||||
this: ICredentialTestFunctions,
|
||||
credential: ICredentialsDecrypted,
|
||||
) => Promise<NodeCredentialTestResult>)
|
||||
| undefined;
|
||||
const nodeThatCanTestThisCredential = allNodes.find((node) => {
|
||||
if (
|
||||
incomingData.nodeToTestWith &&
|
||||
node.description.name !== incomingData.nodeToTestWith
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
const credentialTestable = node.description.credentials?.find((credential) => {
|
||||
const testFunctionSearch =
|
||||
credential.name === credentialType && !!credential.testedBy;
|
||||
if (testFunctionSearch) {
|
||||
foundTestFunction = node.methods!.credentialTest![credential.testedBy!];
|
||||
}
|
||||
return testFunctionSearch;
|
||||
});
|
||||
return !!credentialTestable;
|
||||
});
|
||||
|
||||
if (!nodeThatCanTestThisCredential) {
|
||||
return Promise.resolve({
|
||||
status: 'Error',
|
||||
message: 'There are no nodes that can test this credential.',
|
||||
});
|
||||
}
|
||||
|
||||
if (foundTestFunction === undefined) {
|
||||
return Promise.resolve({
|
||||
status: 'Error',
|
||||
message: 'No testing function found for this credential.',
|
||||
});
|
||||
}
|
||||
|
||||
const credentialTestFunctions = NodeExecuteFunctions.getCredentialTestFunctions();
|
||||
|
||||
const output = await foundTestFunction.call(
|
||||
credentialTestFunctions,
|
||||
incomingData.credentials,
|
||||
);
|
||||
return Promise.resolve(output);
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
// Updates existing credentials
|
||||
this.app.patch(
|
||||
`/${this.restEndpoint}/credentials/:id`,
|
||||
@@ -1540,6 +1595,42 @@ class App {
|
||||
),
|
||||
);
|
||||
|
||||
this.app.get(
|
||||
`/${this.restEndpoint}/credential-icon/:credentialType`,
|
||||
async (req: express.Request, res: express.Response): Promise<void> => {
|
||||
try {
|
||||
const credentialName = req.params.credentialType;
|
||||
|
||||
const credentialType = CredentialTypes().getByName(credentialName);
|
||||
|
||||
if (credentialType === undefined) {
|
||||
res.status(404).send('The credentialType is not known.');
|
||||
return;
|
||||
}
|
||||
|
||||
if (credentialType.icon === undefined) {
|
||||
res.status(404).send('No icon found for credential.');
|
||||
return;
|
||||
}
|
||||
|
||||
if (!credentialType.icon.startsWith('file:')) {
|
||||
res.status(404).send('Credential does not have a file icon.');
|
||||
return;
|
||||
}
|
||||
|
||||
const filepath = credentialType.icon.substr(5);
|
||||
|
||||
const maxAge = 7 * 24 * 60 * 60 * 1000; // 7 days
|
||||
res.setHeader('Cache-control', `private max-age=${maxAge}`);
|
||||
|
||||
res.sendFile(filepath);
|
||||
} catch (error) {
|
||||
// Error response
|
||||
return ResponseHelper.sendErrorResponse(res, error);
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
// ----------------------------------------
|
||||
// OAuth1-Credential/Auth
|
||||
// ----------------------------------------
|
||||
|
||||
Reference in New Issue
Block a user