Merge branch 'master' into static-stateless-webhooks

This commit is contained in:
ricardo
2020-06-22 16:16:50 -04:00
217 changed files with 32466 additions and 2487 deletions

View File

@@ -145,6 +145,9 @@ export interface IRestApi {
deleteExecutions(sendData: IExecutionDeleteFilter): Promise<void>;
retryExecution(id: string, loadWorkflow?: boolean): Promise<boolean>;
getTimezones(): Promise<IDataObject>;
oAuth1CredentialAuthorize(sendData: ICredentialsResponse): Promise<string>;
oAuth2CredentialAuthorize(sendData: ICredentialsResponse): Promise<string>;
oAuth2Callback(code: string, state: string): Promise<string>;
}
export interface IBinaryDisplayData {
@@ -155,6 +158,13 @@ export interface IBinaryDisplayData {
runIndex: number;
}
export interface ICredentialsCreatedEvent {
data: ICredentialsDecryptedResponse;
options: {
closeDialog: boolean,
};
}
export interface IStartRunData {
workflowData: IWorkflowData;
startNodes?: string[];

View File

@@ -8,7 +8,7 @@
Credential type:
</el-col>
<el-col :span="18">
<el-select v-model="credentialType" placeholder="Select Type" size="small">
<el-select v-model="credentialType" filterable placeholder="Select Type" size="small">
<el-option
v-for="item in credentialTypes"
:key="item.name"
@@ -31,10 +31,15 @@ import Vue from 'vue';
import { restApi } from '@/components/mixins/restApi';
import { showMessage } from '@/components/mixins/showMessage';
import CredentialsInput from '@/components/CredentialsInput.vue';
import { ICredentialsDecryptedResponse } from '@/Interface';
import {
ICredentialsCreatedEvent,
ICredentialsDecryptedResponse,
} from '@/Interface';
import {
NodeHelpers,
ICredentialType,
INodeProperties,
} from 'n8n-workflow';
import mixins from 'vue-typed-mixins';
@@ -168,36 +173,67 @@ export default mixins(
},
},
methods: {
getCredentialTypeData (name: string): ICredentialType | null {
for (const credentialData of this.credentialTypes) {
if (credentialData.name === name) {
return credentialData;
}
getCredentialProperties (name: string): INodeProperties[] {
const credentialsData = this.$store.getters.credentialType(name);
if (credentialsData === null) {
throw new Error(`Could not find credentials of type: ${name}`);
}
return null;
if (credentialsData.extends === undefined) {
return credentialsData.properties;
}
const combineProperties = [] as INodeProperties[];
for (const credentialsTypeName of credentialsData.extends) {
const mergeCredentialProperties = this.getCredentialProperties(credentialsTypeName);
NodeHelpers.mergeNodeProperties(combineProperties, mergeCredentialProperties);
}
// The properties defined on the parent credentials take presidence
NodeHelpers.mergeNodeProperties(combineProperties, credentialsData.properties);
return combineProperties;
},
credentialsCreated (data: ICredentialsDecryptedResponse): void {
this.$emit('credentialsCreated', data);
getCredentialTypeData (name: string): ICredentialType | null {
let credentialData = this.$store.getters.credentialType(name);
if (credentialData === null || credentialData.extends === undefined) {
return credentialData;
}
// Credentials extends another one. So get the properties of the one it
// extends and add them.
credentialData = JSON.parse(JSON.stringify(credentialData));
credentialData.properties = this.getCredentialProperties(credentialData.name);
return credentialData;
},
credentialsCreated (eventData: ICredentialsCreatedEvent): void {
this.$emit('credentialsCreated', eventData);
this.$showMessage({
title: 'Credentials created',
message: `The credential "${data.name}" got created!`,
message: `The credential "${eventData.data.name}" got created!`,
type: 'success',
});
this.closeDialog();
if (eventData.options.closeDialog === true) {
this.closeDialog();
}
},
credentialsUpdated (data: ICredentialsDecryptedResponse): void {
this.$emit('credentialsUpdated', data);
credentialsUpdated (eventData: ICredentialsCreatedEvent): void {
this.$emit('credentialsUpdated', eventData);
this.$showMessage({
title: 'Credentials updated',
message: `The credential "${data.name}" got updated!`,
message: `The credential "${eventData.data.name}" got updated!`,
type: 'success',
});
this.closeDialog();
if (eventData.options.closeDialog === true) {
this.closeDialog();
}
},
closeDialog (): void {
// Handle the close externally as the visible parameter is an external prop

View File

@@ -12,17 +12,16 @@
<el-input size="small" type="text" v-model="name"></el-input>
</el-col>
</el-row>
<br />
<div class="headline">
<div class="headline" v-if="credentialProperties.length">
Credential Data:
<el-tooltip class="credentials-info" placement="top" effect="light">
<div slot="content" v-html="helpTexts.credentialsData"></div>
<font-awesome-icon icon="question-circle" />
</el-tooltip>
</div>
<span v-for="parameter in credentialTypeData.properties" :key="parameter.name">
<el-row v-if="displayCredentialParameter(parameter)" class="parameter-wrapper">
<div v-for="parameter in credentialProperties" :key="parameter.name">
<el-row class="parameter-wrapper">
<el-col :span="6" class="parameter-name">
{{parameter.displayName}}:
<el-tooltip placement="top" class="parameter-info" v-if="parameter.description" effect="light">
@@ -34,7 +33,46 @@
<parameter-input :parameter="parameter" :value="propertyValue[parameter.name]" :path="parameter.name" :isCredential="true" @valueChanged="valueChanged" />
</el-col>
</el-row>
</span>
</div>
<el-row v-if="isOAuthType" class="oauth-information">
<el-col :span="6" class="headline">
OAuth
</el-col>
<el-col :span="18">
<span v-if="requiredPropertiesFilled === false">
<el-button title="Connect OAuth Credentials" circle :disabled="true">
<font-awesome-icon icon="redo" />
</el-button>
Not all required credential properties are filled
</span>
<span v-else-if="isOAuthConnected === true">
<el-button title="Reconnect OAuth Credentials" @click.stop="oAuthCredentialAuthorize()" circle>
<font-awesome-icon icon="redo" />
</el-button>
Is connected
</span>
<span v-else>
<el-button title="Connect OAuth Credentials" @click.stop="oAuthCredentialAuthorize()" circle>
<font-awesome-icon icon="sign-in-alt" />
</el-button>
Is NOT connected
</span>
<div v-if="credentialProperties.length">
<div class="clickable oauth-callback-headline" :class="{expanded: !isMinimized}" @click="isMinimized=!isMinimized" :title="isMinimized ? 'Click to display Webhook URLs' : 'Click to hide Webhook URLs'">
<font-awesome-icon icon="angle-up" class="minimize-button minimize-icon" />
OAuth Callback URL
</div>
<el-tooltip v-if="!isMinimized" class="item" effect="light" content="Click to copy Callback URL" placement="right">
<div class="callback-url left-ellipsis clickable" @click="copyCallbackUrl">
{{oAuthCallbackUrl}}
</div>
</el-tooltip>
</div>
</el-col>
</el-row>
<el-row class="nodes-access-wrapper">
<el-col :span="6" class="headline">
@@ -61,10 +99,10 @@
</el-row>
<div class="action-buttons">
<el-button type="success" @click="updateCredentials" v-if="credentialData">
<el-button type="success" @click="updateCredentials(true)" v-if="credentialDataDynamic">
Save
</el-button>
<el-button type="success" @click="createCredentials" v-else>
<el-button type="success" @click="createCredentials(true)" v-else>
Create
</el-button>
</div>
@@ -75,11 +113,16 @@
<script lang="ts">
import Vue from 'vue';
import { copyPaste } from '@/components/mixins/copyPaste';
import { restApi } from '@/components/mixins/restApi';
import { nodeHelpers } from '@/components/mixins/nodeHelpers';
import { showMessage } from '@/components/mixins/showMessage';
import { ICredentialsDecryptedResponse, IUpdateInformation } from '@/Interface';
import {
ICredentialsDecryptedResponse,
ICredentialsResponse,
IUpdateInformation,
} from '@/Interface';
import {
CredentialInformation,
ICredentialDataDecryptedObject,
@@ -87,8 +130,10 @@ import {
ICredentialType,
ICredentialNodeAccess,
INodeCredentialDescription,
INodeParameters,
INodeProperties,
INodeTypeDescription,
NodeHelpers,
} from 'n8n-workflow';
import ParameterInput from '@/components/ParameterInput.vue';
@@ -96,6 +141,7 @@ import ParameterInput from '@/components/ParameterInput.vue';
import mixins from 'vue-typed-mixins';
export default mixins(
copyPaste,
nodeHelpers,
restApi,
showMessage,
@@ -114,11 +160,13 @@ export default mixins(
},
data () {
return {
isMinimized: true,
helpTexts: {
credentialsData: 'The credentials to set.',
credentialsName: 'The name the credentials should be saved as. Use a name<br />which makes it clear to what exactly they give access to.<br />For credentials of an Email account that could be the Email address itself.',
nodesWithAccess: 'The nodes which allowed to use this credentials.',
},
credentialDataTemp: null as ICredentialsDecryptedResponse | null,
nodesAccess: [] as string[],
name: '',
propertyValue: {} as ICredentialDataDecryptedObject,
@@ -155,8 +203,78 @@ export default mixins(
};
});
},
credentialProperties (): INodeProperties[] {
return this.credentialTypeData.properties.filter((propertyData: INodeProperties) => {
if (!this.displayCredentialParameter(propertyData)) {
return false;
}
return !this.credentialTypeData.__overwrittenProperties || !this.credentialTypeData.__overwrittenProperties.includes(propertyData.name);
});
},
credentialDataDynamic (): ICredentialsDecryptedResponse | null {
if (this.credentialData) {
return this.credentialData;
}
return this.credentialDataTemp;
},
isOAuthType (): boolean {
if (['oAuth1Api', 'oAuth2Api'].includes(this.credentialTypeData.name)) {
return true;
}
const types = this.parentTypes(this.credentialTypeData.name);
return types.includes('oAuth1Api') || types.includes('oAuth2Api');
},
isOAuthConnected (): boolean {
if (this.isOAuthType === false) {
return false;
}
return this.credentialDataDynamic !== null && !!this.credentialDataDynamic.data!.oauthTokenData;
},
oAuthCallbackUrl (): string {
const types = this.parentTypes(this.credentialTypeData.name);
const oauthType = (this.credentialTypeData.name === 'oAuth2Api' || types.includes('oAuth2Api')) ? 'oauth2' : 'oauth1';
return this.$store.getters.getWebhookBaseUrl + `rest/${oauthType}-credential/callback`;
},
requiredPropertiesFilled (): boolean {
for (const property of this.credentialProperties) {
if (property.required !== true) {
continue;
}
if (!this.propertyValue[property.name]) {
return false;
}
}
return true;
},
},
methods: {
copyCallbackUrl (): void {
this.copyToClipboard(this.oAuthCallbackUrl);
this.$showMessage({
title: 'Copied',
message: `The callback URL got copied!`,
type: 'success',
});
},
parentTypes (name: string): string[] {
const credentialType = this.$store.getters.credentialType(name);
if (credentialType === undefined || credentialType.extends === undefined) {
return [];
}
const types: string[] = [];
for (const typeName of credentialType.extends) {
types.push(typeName);
types.push.apply(types, this.parentTypes(typeName));
}
return types;
},
valueChanged (parameterData: IUpdateInformation) {
const name = parameterData.name.split('.').pop() as string;
// For a currently for me unknown reason can In not simply just
@@ -166,14 +284,18 @@ export default mixins(
Vue.set(this, 'propertyValue', tempValue);
},
displayCredentialParameter (parameter: INodeProperties): boolean {
if (parameter.type === 'hidden') {
return false;
}
if (parameter.displayOptions === undefined) {
// If it is not defined no need to do a proper check
return true;
}
return this.displayParameter(this.propertyValue, parameter, '');
return this.displayParameter(this.propertyValue as INodeParameters, parameter, '');
},
async createCredentials (): Promise<void> {
async createCredentials (closeDialog: boolean): Promise<ICredentialsResponse | null> {
const nodesAccess = this.nodesAccess.map((nodeType) => {
return {
nodeType,
@@ -184,7 +306,8 @@ export default mixins(
name: this.name,
type: (this.credentialTypeData as ICredentialType).name,
nodesAccess,
data: this.propertyValue,
// Save only the none default data
data: NodeHelpers.getNodeParameters(this.credentialTypeData.properties as INodeProperties[], this.propertyValue as INodeParameters, false, false),
} as ICredentialsDecrypted;
let result;
@@ -192,21 +315,110 @@ export default mixins(
result = await this.restApi().createNewCredentials(newCredentials);
} catch (error) {
this.$showError(error, 'Problem Creating Credentials', 'There was a problem creating the credentials:');
return;
return null;
}
// Add also to local store
this.$store.commit('addCredentials', result);
this.$emit('credentialsCreated', result);
this.$emit('credentialsCreated', {data: result, options: { closeDialog }});
return result;
},
async updateCredentials () {
async oAuthCredentialAuthorize () {
let url;
let credentialData = this.credentialDataDynamic;
let newCredentials = false;
if (!credentialData) {
// Credentials did not get created yet. So create first before
// doing oauth authorize
credentialData = await this.createCredentials(false) as ICredentialsDecryptedResponse;
newCredentials = true;
if (credentialData === null) {
return;
}
// Set the internal data directly so that even if it fails it displays a "Save" instead
// of the "Create" button. If that would not be done, people could not retry after a
// connect issue as it woult try to create credentials again which would fail as they
// exist already.
Vue.set(this, 'credentialDataTemp', credentialData);
} else {
// Exists already but got maybe changed. So save first
credentialData = await this.updateCredentials(false) as ICredentialsDecryptedResponse;
if (credentialData === null) {
return;
}
}
const types = this.parentTypes(this.credentialTypeData.name);
try {
if (this.credentialTypeData.name === 'oAuth2Api' || types.includes('oAuth2Api')) {
url = await this.restApi().oAuth2CredentialAuthorize(credentialData as ICredentialsResponse) as string;
} else if (this.credentialTypeData.name === 'oAuth1Api' || types.includes('oAuth1Api')) {
url = await this.restApi().oAuth1CredentialAuthorize(credentialData as ICredentialsResponse) as string;
}
} catch (error) {
this.$showError(error, 'OAuth Authorization Error', 'Error generating authorization URL:');
return;
}
const params = `scrollbars=no,resizable=yes,status=no,titlebar=noe,location=no,toolbar=no,menubar=no,width=500,height=700`;
const oauthPopup = window.open(url, 'OAuth2 Authorization', params);
const receiveMessage = (event: MessageEvent) => {
// // TODO: Add check that it came from n8n
// if (event.origin !== 'http://example.org:8080') {
// return;
// }
if (event.data === 'success') {
// Set some kind of data that status changes.
// As data does not get displayed directly it does not matter what data.
if (this.credentialData === null) {
// Are new credentials so did not get send via "credentialData"
Vue.set(this, 'credentialDataTemp', credentialData);
Vue.set(this.credentialDataTemp!.data!, 'oauthTokenData', {});
} else {
// Credentials did already exist so can be set directly
Vue.set(this.credentialData.data, 'oauthTokenData', {});
}
// Save that OAuth got authorized locally
this.$store.commit('updateCredentials', this.credentialDataDynamic);
// Close the window
if (oauthPopup) {
oauthPopup.close();
}
if (newCredentials === true) {
this.$emit('credentialsCreated', {data: credentialData, options: { closeDialog: false }});
}
this.$showMessage({
title: 'Connected',
message: 'Got connected!',
type: 'success',
});
}
// Make sure that the event gets removed again
window.removeEventListener('message', receiveMessage, false);
};
window.addEventListener('message', receiveMessage, false);
},
async updateCredentials (closeDialog: boolean): Promise<ICredentialsResponse | null> {
const nodesAccess: ICredentialNodeAccess[] = [];
const addedNodeTypes: string[] = [];
// Add Node-type which already had access to keep the original added date
let nodeAccessData: ICredentialNodeAccess;
for (nodeAccessData of (this.credentialData as ICredentialsDecryptedResponse).nodesAccess) {
for (nodeAccessData of (this.credentialDataDynamic as ICredentialsDecryptedResponse).nodesAccess) {
if (this.nodesAccess.includes((nodeAccessData.nodeType))) {
nodesAccess.push(nodeAccessData);
addedNodeTypes.push(nodeAccessData.nodeType);
@@ -226,15 +438,16 @@ export default mixins(
name: this.name,
type: (this.credentialTypeData as ICredentialType).name,
nodesAccess,
data: this.propertyValue,
// Save only the none default data
data: NodeHelpers.getNodeParameters(this.credentialTypeData.properties as INodeProperties[], this.propertyValue as INodeParameters, false, false),
} as ICredentialsDecrypted;
let result;
try {
result = await this.restApi().updateCredentials((this.credentialData as ICredentialsDecryptedResponse).id as string, newCredentials);
result = await this.restApi().updateCredentials((this.credentialDataDynamic as ICredentialsDecryptedResponse).id as string, newCredentials);
} catch (error) {
this.$showError(error, 'Problem Updating Credentials', 'There was a problem updating the credentials:');
return;
return null;
}
// Update also in local store
@@ -244,7 +457,9 @@ export default mixins(
// which have now a different name
this.updateNodesCredentialsIssues();
this.$emit('credentialsUpdated', result);
this.$emit('credentialsUpdated', {data: result, options: { closeDialog }});
return result;
},
init () {
if (this.credentialData) {
@@ -312,6 +527,11 @@ export default mixins(
line-height: 1.75em;
}
.oauth-information {
line-height: 2.5em;
margin: 2em 0;
}
.parameter-wrapper {
line-height: 3em;
@@ -334,6 +554,20 @@ export default mixins(
display: none;
}
.callback-url {
position: relative;
top: 0;
width: 100%;
font-size: 0.9em;
white-space: normal;
overflow: visible;
text-overflow: initial;
color: #404040;
text-align: left;
direction: ltr;
word-break: break-all;
}
.headline:hover,
.headline-regular:hover {
.credentials-info {
@@ -341,6 +575,17 @@ export default mixins(
}
}
.expanded .minimize-button {
-webkit-transform: rotate(180deg);
-moz-transform: rotate(180deg);
-o-transform: rotate(180deg);
transform: rotate(180deg);
}
.oauth-callback-headline {
padding-top: 1em;
font-weight: 500;
}
}
</style>

View File

@@ -45,6 +45,7 @@ import Vue from 'vue';
import { restApi } from '@/components/mixins/restApi';
import {
ICredentialsCreatedEvent,
ICredentialsResponse,
INodeUi,
INodeUpdatePropertiesInformation,
@@ -134,21 +135,23 @@ export default mixins(
closeCredentialNewDialog () {
this.credentialNewDialogVisible = false;
},
async credentialsCreated (data: ICredentialsResponse) {
await this.credentialsUpdated(data);
async credentialsCreated (eventData: ICredentialsCreatedEvent) {
await this.credentialsUpdated(eventData);
},
credentialsUpdated (data: ICredentialsResponse) {
if (!this.credentialTypesNode.includes(data.type)) {
credentialsUpdated (eventData: ICredentialsCreatedEvent) {
if (!this.credentialTypesNode.includes(eventData.data.type)) {
return;
}
this.init();
Vue.set(this.credentials, data.type, data.name);
Vue.set(this.credentials, eventData.data.type, eventData.data.name);
// Makes sure that it does also get set correctly on the node not just the UI
this.credentialSelected(data.type);
this.credentialSelected(eventData.data.type);
this.closeCredentialNewDialog();
if (eventData.options.closeDialog === true) {
this.closeCredentialNewDialog();
}
},
credentialInputWrapperStyle (credentialType: string) {
let deductWidth = 0;

View File

@@ -149,6 +149,10 @@ export default mixins(
this.$emit('valueChanged', parameterData);
},
displayNodeParameter (parameter: INodeProperties): boolean {
if (parameter.type === 'hidden') {
return false;
}
if (parameter.displayOptions === undefined) {
// If it is not defined no need to do a proper check
return true;

View File

@@ -29,12 +29,6 @@ export const nodeBase = mixins(nodeIndex).extend({
isMacOs (): boolean {
return /(ipad|iphone|ipod|mac)/i.test(navigator.platform);
},
isReadOnly (): boolean {
if (['NodeViewExisting', 'NodeViewNew'].includes(this.$route.name as string)) {
return false;
}
return true;
},
nodeName (): string {
return NODE_NAME_PREFIX + this.nodeIndex;
},
@@ -276,63 +270,71 @@ export const nodeBase = mixins(nodeIndex).extend({
this.instance.addEndpoint(this.nodeName, newEndpointData);
});
if (this.isReadOnly === false) {
// Make nodes draggable
this.instance.draggable(this.nodeName, {
grid: [10, 10],
start: (params: { e: MouseEvent }) => {
if (params.e && !this.$store.getters.isNodeSelected(this.data.name)) {
// Only the node which gets dragged directly gets an event, for all others it is
// undefined. So check if the currently dragged node is selected and if not clear
// the drag-selection.
this.instance.clearDragSelection();
this.$store.commit('resetSelectedNodes');
// TODO: This caused problems with displaying old information
// https://github.com/jsplumb/katavorio/wiki
// https://jsplumb.github.io/jsplumb/home.html
// Make nodes draggable
this.instance.draggable(this.nodeName, {
grid: [10, 10],
start: (params: { e: MouseEvent }) => {
if (this.isReadOnly === true) {
// Do not allow to move nodes in readOnly mode
return false;
}
if (params.e && !this.$store.getters.isNodeSelected(this.data.name)) {
// Only the node which gets dragged directly gets an event, for all others it is
// undefined. So check if the currently dragged node is selected and if not clear
// the drag-selection.
this.instance.clearDragSelection();
this.$store.commit('resetSelectedNodes');
}
this.$store.commit('addActiveAction', 'dragActive');
return true;
},
stop: (params: { e: MouseEvent }) => {
if (this.$store.getters.isActionActive('dragActive')) {
const moveNodes = this.$store.getters.getSelectedNodes.slice();
const selectedNodeNames = moveNodes.map((node: INodeUi) => node.name);
if (!selectedNodeNames.includes(this.data.name)) {
// If the current node is not in selected add it to the nodes which
// got moved manually
moveNodes.push(this.data);
}
this.$store.commit('addActiveAction', 'dragActive');
},
stop: (params: { e: MouseEvent }) => {
if (this.$store.getters.isActionActive('dragActive')) {
const moveNodes = this.$store.getters.getSelectedNodes.slice();
const selectedNodeNames = moveNodes.map((node: INodeUi) => node.name);
if (!selectedNodeNames.includes(this.data.name)) {
// If the current node is not in selected add it to the nodes which
// got moved manually
moveNodes.push(this.data);
// This does for some reason just get called once for the node that got clicked
// even though "start" and "drag" gets called for all. So lets do for now
// some dirty DOM query to get the new positions till I have more time to
// create a proper solution
let newNodePositon: XYPositon;
moveNodes.forEach((node: INodeUi) => {
const nodeElement = `node-${this.getNodeIndex(node.name)}`;
const element = document.getElementById(nodeElement);
if (element === null) {
return;
}
// This does for some reason just get called once for the node that got clicked
// even though "start" and "drag" gets called for all. So lets do for now
// some dirty DOM query to get the new positions till I have more time to
// create a proper solution
let newNodePositon: XYPositon;
moveNodes.forEach((node: INodeUi) => {
const nodeElement = `node-${this.getNodeIndex(node.name)}`;
const element = document.getElementById(nodeElement);
if (element === null) {
return;
}
newNodePositon = [
parseInt(element.style.left!.slice(0, -2), 10),
parseInt(element.style.top!.slice(0, -2), 10),
];
newNodePositon = [
parseInt(element.style.left!.slice(0, -2), 10),
parseInt(element.style.top!.slice(0, -2), 10),
];
const updateInformation = {
name: node.name,
properties: {
// @ts-ignore, draggable does not have definitions
position: newNodePositon,
},
};
const updateInformation = {
name: node.name,
properties: {
// @ts-ignore, draggable does not have definitions
position: newNodePositon,
},
};
this.$store.commit('updateNodeProperties', updateInformation);
});
}
},
filter: '.node-description, .node-description .node-name, .node-description .node-subtitle',
});
this.$store.commit('updateNodeProperties', updateInformation);
});
}
},
filter: '.node-description, .node-description .node-name, .node-description .node-subtitle',
});
}
},
isCtrlKeyPressed (e: MouseEvent | KeyboardEvent): boolean {

View File

@@ -252,6 +252,26 @@ export const restApi = Vue.extend({
return self.restApi().makeRestApiRequest('GET', `/credential-types`);
},
// Get OAuth1 Authorization URL using the stored credentials
oAuth1CredentialAuthorize: (sendData: ICredentialsResponse): Promise<string> => {
return self.restApi().makeRestApiRequest('GET', `/oauth1-credential/auth`, sendData);
},
// Get OAuth2 Authorization URL using the stored credentials
oAuth2CredentialAuthorize: (sendData: ICredentialsResponse): Promise<string> => {
return self.restApi().makeRestApiRequest('GET', `/oauth2-credential/auth`, sendData);
},
// Verify OAuth2 provider callback and kick off token generation
oAuth2Callback: (code: string, state: string): Promise<string> => {
const sendData = {
'code': code,
'state': state,
};
return self.restApi().makeRestApiRequest('POST', `/oauth2-credential/callback`, sendData);
},
// Returns the execution with the given name
getExecution: async (id: string): Promise<IExecutionResponse> => {
const response = await self.restApi().makeRestApiRequest('GET', `/executions/${id}`);

View File

@@ -71,6 +71,7 @@ import {
faSave,
faSearchMinus,
faSearchPlus,
faSignInAlt,
faSlidersH,
faSpinner,
faStop,
@@ -145,6 +146,7 @@ library.add(faRss);
library.add(faSave);
library.add(faSearchMinus);
library.add(faSearchPlus);
library.add(faSignInAlt);
library.add(faSlidersH);
library.add(faSpinner);
library.add(faStop);

View File

@@ -19,6 +19,12 @@ export default new Router({
sidebar: MainSidebar,
},
},
{
path: '/oauth2/callback',
name: 'oAuth2Callback',
components: {
},
},
{
path: '/workflow',
name: 'NodeViewNew',

View File

@@ -571,6 +571,9 @@ export const store = new Vuex.Store({
}
return `${state.baseUrl}${endpoint}`;
},
getWebhookBaseUrl: (state): string => {
return state.urlBaseWebhook;
},
getWebhookUrl: (state): string => {
return `${state.urlBaseWebhook}${state.endpointWebhook}`;
},