Merge branch 'main' into feature/tts
This commit is contained in:
commit
53622d95e4
|
|
@ -99,6 +99,7 @@ JWT_TOKEN_EXPIRY_IN_MINUTES=360
|
||||||
JWT_REFRESH_TOKEN_EXPIRY_IN_MINUTES=43200
|
JWT_REFRESH_TOKEN_EXPIRY_IN_MINUTES=43200
|
||||||
# EXPIRE_AUTH_TOKENS_ON_RESTART=true # (if you need to expire all tokens on app restart)
|
# EXPIRE_AUTH_TOKENS_ON_RESTART=true # (if you need to expire all tokens on app restart)
|
||||||
# EXPRESS_SESSION_SECRET=flowise
|
# EXPRESS_SESSION_SECRET=flowise
|
||||||
|
# SECURE_COOKIES=
|
||||||
|
|
||||||
# INVITE_TOKEN_EXPIRY_IN_HOURS=24
|
# INVITE_TOKEN_EXPIRY_IN_HOURS=24
|
||||||
# PASSWORD_RESET_TOKEN_EXPIRY_IN_MINS=15
|
# PASSWORD_RESET_TOKEN_EXPIRY_IN_MINS=15
|
||||||
|
|
|
||||||
|
|
@ -89,6 +89,7 @@ services:
|
||||||
- PASSWORD_RESET_TOKEN_EXPIRY_IN_MINS=${PASSWORD_RESET_TOKEN_EXPIRY_IN_MINS}
|
- PASSWORD_RESET_TOKEN_EXPIRY_IN_MINS=${PASSWORD_RESET_TOKEN_EXPIRY_IN_MINS}
|
||||||
- PASSWORD_SALT_HASH_ROUNDS=${PASSWORD_SALT_HASH_ROUNDS}
|
- PASSWORD_SALT_HASH_ROUNDS=${PASSWORD_SALT_HASH_ROUNDS}
|
||||||
- TOKEN_HASH_SECRET=${TOKEN_HASH_SECRET}
|
- TOKEN_HASH_SECRET=${TOKEN_HASH_SECRET}
|
||||||
|
- SECURE_COOKIES=${SECURE_COOKIES}
|
||||||
|
|
||||||
# EMAIL
|
# EMAIL
|
||||||
- SMTP_HOST=${SMTP_HOST}
|
- SMTP_HOST=${SMTP_HOST}
|
||||||
|
|
@ -232,6 +233,7 @@ services:
|
||||||
- PASSWORD_RESET_TOKEN_EXPIRY_IN_MINS=${PASSWORD_RESET_TOKEN_EXPIRY_IN_MINS}
|
- PASSWORD_RESET_TOKEN_EXPIRY_IN_MINS=${PASSWORD_RESET_TOKEN_EXPIRY_IN_MINS}
|
||||||
- PASSWORD_SALT_HASH_ROUNDS=${PASSWORD_SALT_HASH_ROUNDS}
|
- PASSWORD_SALT_HASH_ROUNDS=${PASSWORD_SALT_HASH_ROUNDS}
|
||||||
- TOKEN_HASH_SECRET=${TOKEN_HASH_SECRET}
|
- TOKEN_HASH_SECRET=${TOKEN_HASH_SECRET}
|
||||||
|
- SECURE_COOKIES=${SECURE_COOKIES}
|
||||||
|
|
||||||
# EMAIL
|
# EMAIL
|
||||||
- SMTP_HOST=${SMTP_HOST}
|
- SMTP_HOST=${SMTP_HOST}
|
||||||
|
|
|
||||||
|
|
@ -74,6 +74,7 @@ services:
|
||||||
- PASSWORD_RESET_TOKEN_EXPIRY_IN_MINS=${PASSWORD_RESET_TOKEN_EXPIRY_IN_MINS}
|
- PASSWORD_RESET_TOKEN_EXPIRY_IN_MINS=${PASSWORD_RESET_TOKEN_EXPIRY_IN_MINS}
|
||||||
- PASSWORD_SALT_HASH_ROUNDS=${PASSWORD_SALT_HASH_ROUNDS}
|
- PASSWORD_SALT_HASH_ROUNDS=${PASSWORD_SALT_HASH_ROUNDS}
|
||||||
- TOKEN_HASH_SECRET=${TOKEN_HASH_SECRET}
|
- TOKEN_HASH_SECRET=${TOKEN_HASH_SECRET}
|
||||||
|
- SECURE_COOKIES=${SECURE_COOKIES}
|
||||||
|
|
||||||
# EMAIL
|
# EMAIL
|
||||||
- SMTP_HOST=${SMTP_HOST}
|
- SMTP_HOST=${SMTP_HOST}
|
||||||
|
|
|
||||||
|
|
@ -99,6 +99,7 @@ JWT_TOKEN_EXPIRY_IN_MINUTES=360
|
||||||
JWT_REFRESH_TOKEN_EXPIRY_IN_MINUTES=43200
|
JWT_REFRESH_TOKEN_EXPIRY_IN_MINUTES=43200
|
||||||
# EXPIRE_AUTH_TOKENS_ON_RESTART=true # (if you need to expire all tokens on app restart)
|
# EXPIRE_AUTH_TOKENS_ON_RESTART=true # (if you need to expire all tokens on app restart)
|
||||||
# EXPRESS_SESSION_SECRET=flowise
|
# EXPRESS_SESSION_SECRET=flowise
|
||||||
|
# SECURE_COOKIES=
|
||||||
|
|
||||||
# INVITE_TOKEN_EXPIRY_IN_HOURS=24
|
# INVITE_TOKEN_EXPIRY_IN_HOURS=24
|
||||||
# PASSWORD_RESET_TOKEN_EXPIRY_IN_MINS=15
|
# PASSWORD_RESET_TOKEN_EXPIRY_IN_MINS=15
|
||||||
|
|
|
||||||
|
|
@ -74,6 +74,7 @@ services:
|
||||||
- PASSWORD_RESET_TOKEN_EXPIRY_IN_MINS=${PASSWORD_RESET_TOKEN_EXPIRY_IN_MINS}
|
- PASSWORD_RESET_TOKEN_EXPIRY_IN_MINS=${PASSWORD_RESET_TOKEN_EXPIRY_IN_MINS}
|
||||||
- PASSWORD_SALT_HASH_ROUNDS=${PASSWORD_SALT_HASH_ROUNDS}
|
- PASSWORD_SALT_HASH_ROUNDS=${PASSWORD_SALT_HASH_ROUNDS}
|
||||||
- TOKEN_HASH_SECRET=${TOKEN_HASH_SECRET}
|
- TOKEN_HASH_SECRET=${TOKEN_HASH_SECRET}
|
||||||
|
- SECURE_COOKIES=${SECURE_COOKIES}
|
||||||
|
|
||||||
# EMAIL
|
# EMAIL
|
||||||
- SMTP_HOST=${SMTP_HOST}
|
- SMTP_HOST=${SMTP_HOST}
|
||||||
|
|
|
||||||
|
|
@ -272,6 +272,22 @@ class GoogleCalendar_Tools implements INode {
|
||||||
additionalParams: true,
|
additionalParams: true,
|
||||||
optional: true
|
optional: true
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
label: 'Send Updates to',
|
||||||
|
name: 'sendUpdates',
|
||||||
|
type: 'options',
|
||||||
|
description: 'Send Updates to attendees',
|
||||||
|
options: [
|
||||||
|
{ label: 'All', name: 'all' },
|
||||||
|
{ label: 'External Only', name: 'externalOnly' },
|
||||||
|
{ label: 'None', name: 'none' }
|
||||||
|
],
|
||||||
|
show: {
|
||||||
|
eventActions: ['createEvent', 'updateEvent']
|
||||||
|
},
|
||||||
|
additionalParams: true,
|
||||||
|
optional: true
|
||||||
|
},
|
||||||
{
|
{
|
||||||
label: 'Recurrence Rules',
|
label: 'Recurrence Rules',
|
||||||
name: 'recurrence',
|
name: 'recurrence',
|
||||||
|
|
@ -560,7 +576,6 @@ class GoogleCalendar_Tools implements INode {
|
||||||
}
|
}
|
||||||
|
|
||||||
const defaultParams = this.transformNodeInputsToToolArgs(nodeData)
|
const defaultParams = this.transformNodeInputsToToolArgs(nodeData)
|
||||||
|
|
||||||
const tools = createGoogleCalendarTools({
|
const tools = createGoogleCalendarTools({
|
||||||
accessToken,
|
accessToken,
|
||||||
actions,
|
actions,
|
||||||
|
|
@ -587,6 +602,7 @@ class GoogleCalendar_Tools implements INode {
|
||||||
if (nodeData.inputs?.startDate) defaultParams.startDate = nodeData.inputs.startDate
|
if (nodeData.inputs?.startDate) defaultParams.startDate = nodeData.inputs.startDate
|
||||||
if (nodeData.inputs?.endDate) defaultParams.endDate = nodeData.inputs.endDate
|
if (nodeData.inputs?.endDate) defaultParams.endDate = nodeData.inputs.endDate
|
||||||
if (nodeData.inputs?.attendees) defaultParams.attendees = nodeData.inputs.attendees
|
if (nodeData.inputs?.attendees) defaultParams.attendees = nodeData.inputs.attendees
|
||||||
|
if (nodeData.inputs?.sendUpdates) defaultParams.sendUpdates = nodeData.inputs.sendUpdates
|
||||||
if (nodeData.inputs?.recurrence) defaultParams.recurrence = nodeData.inputs.recurrence
|
if (nodeData.inputs?.recurrence) defaultParams.recurrence = nodeData.inputs.recurrence
|
||||||
if (nodeData.inputs?.reminderMinutes) defaultParams.reminderMinutes = nodeData.inputs.reminderMinutes
|
if (nodeData.inputs?.reminderMinutes) defaultParams.reminderMinutes = nodeData.inputs.reminderMinutes
|
||||||
if (nodeData.inputs?.visibility) defaultParams.visibility = nodeData.inputs.visibility
|
if (nodeData.inputs?.visibility) defaultParams.visibility = nodeData.inputs.visibility
|
||||||
|
|
|
||||||
|
|
@ -48,6 +48,7 @@ const CreateEventSchema = z.object({
|
||||||
endDate: z.string().optional().describe('End date for all-day events (YYYY-MM-DD)'),
|
endDate: z.string().optional().describe('End date for all-day events (YYYY-MM-DD)'),
|
||||||
timeZone: z.string().optional().describe('Time zone (e.g., America/New_York)'),
|
timeZone: z.string().optional().describe('Time zone (e.g., America/New_York)'),
|
||||||
attendees: z.string().optional().describe('Comma-separated list of attendee emails'),
|
attendees: z.string().optional().describe('Comma-separated list of attendee emails'),
|
||||||
|
sendUpdates: z.enum(['all', 'externalOnly', 'none']).optional().default('all').describe('Whether to send notifications to attendees'),
|
||||||
recurrence: z.string().optional().describe('Recurrence rules (RRULE format)'),
|
recurrence: z.string().optional().describe('Recurrence rules (RRULE format)'),
|
||||||
reminderMinutes: z.number().optional().describe('Minutes before event to send reminder'),
|
reminderMinutes: z.number().optional().describe('Minutes before event to send reminder'),
|
||||||
visibility: z.enum(['default', 'public', 'private', 'confidential']).optional().describe('Event visibility')
|
visibility: z.enum(['default', 'public', 'private', 'confidential']).optional().describe('Event visibility')
|
||||||
|
|
@ -70,6 +71,7 @@ const UpdateEventSchema = z.object({
|
||||||
endDate: z.string().optional().describe('Updated end date for all-day events (YYYY-MM-DD)'),
|
endDate: z.string().optional().describe('Updated end date for all-day events (YYYY-MM-DD)'),
|
||||||
timeZone: z.string().optional().describe('Updated time zone'),
|
timeZone: z.string().optional().describe('Updated time zone'),
|
||||||
attendees: z.string().optional().describe('Updated comma-separated list of attendee emails'),
|
attendees: z.string().optional().describe('Updated comma-separated list of attendee emails'),
|
||||||
|
sendUpdates: z.enum(['all', 'externalOnly', 'none']).optional().default('all').describe('Whether to send notifications to attendees'),
|
||||||
recurrence: z.string().optional().describe('Updated recurrence rules'),
|
recurrence: z.string().optional().describe('Updated recurrence rules'),
|
||||||
reminderMinutes: z.number().optional().describe('Updated reminder minutes'),
|
reminderMinutes: z.number().optional().describe('Updated reminder minutes'),
|
||||||
visibility: z.enum(['default', 'public', 'private', 'confidential']).optional().describe('Updated event visibility')
|
visibility: z.enum(['default', 'public', 'private', 'confidential']).optional().describe('Updated event visibility')
|
||||||
|
|
@ -286,8 +288,11 @@ class CreateEventTool extends BaseGoogleCalendarTool {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (params.visibility) eventData.visibility = params.visibility
|
if (params.visibility) eventData.visibility = params.visibility
|
||||||
|
const queryParams = new URLSearchParams()
|
||||||
|
if (params.sendUpdates) queryParams.append('sendUpdates', params.sendUpdates)
|
||||||
|
|
||||||
|
const endpoint = `calendars/${encodeURIComponent(params.calendarId)}/events?${queryParams.toString()}`
|
||||||
|
|
||||||
const endpoint = `calendars/${encodeURIComponent(params.calendarId)}/events`
|
|
||||||
const response = await this.makeGoogleCalendarRequest({ endpoint, method: 'POST', body: eventData, params })
|
const response = await this.makeGoogleCalendarRequest({ endpoint, method: 'POST', body: eventData, params })
|
||||||
return response
|
return response
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|
@ -395,8 +400,12 @@ class UpdateEventTool extends BaseGoogleCalendarTool {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (params.visibility) updateData.visibility = params.visibility
|
if (params.visibility) updateData.visibility = params.visibility
|
||||||
|
const queryParams = new URLSearchParams()
|
||||||
|
if (params.sendUpdates) queryParams.append('sendUpdates', params.sendUpdates)
|
||||||
|
|
||||||
const endpoint = `calendars/${encodeURIComponent(params.calendarId)}/events/${encodeURIComponent(params.eventId)}`
|
const endpoint = `calendars/${encodeURIComponent(params.calendarId)}/events/${encodeURIComponent(
|
||||||
|
params.eventId
|
||||||
|
)}?${queryParams.toString()}`
|
||||||
const response = await this.makeGoogleCalendarRequest({ endpoint, method: 'PUT', body: updateData, params })
|
const response = await this.makeGoogleCalendarRequest({ endpoint, method: 'PUT', body: updateData, params })
|
||||||
return response
|
return response
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ import $RefParser from '@apidevtools/json-schema-ref-parser'
|
||||||
import { z, ZodSchema, ZodTypeAny } from 'zod'
|
import { z, ZodSchema, ZodTypeAny } from 'zod'
|
||||||
import { defaultCode, DynamicStructuredTool, howToUseCode } from './core'
|
import { defaultCode, DynamicStructuredTool, howToUseCode } from './core'
|
||||||
import { DataSource } from 'typeorm'
|
import { DataSource } from 'typeorm'
|
||||||
|
import fetch from 'node-fetch'
|
||||||
|
|
||||||
class OpenAPIToolkit_Tools implements INode {
|
class OpenAPIToolkit_Tools implements INode {
|
||||||
label: string
|
label: string
|
||||||
|
|
@ -21,17 +22,64 @@ class OpenAPIToolkit_Tools implements INode {
|
||||||
constructor() {
|
constructor() {
|
||||||
this.label = 'OpenAPI Toolkit'
|
this.label = 'OpenAPI Toolkit'
|
||||||
this.name = 'openAPIToolkit'
|
this.name = 'openAPIToolkit'
|
||||||
this.version = 2.0
|
this.version = 2.1
|
||||||
this.type = 'OpenAPIToolkit'
|
this.type = 'OpenAPIToolkit'
|
||||||
this.icon = 'openapi.svg'
|
this.icon = 'openapi.svg'
|
||||||
this.category = 'Tools'
|
this.category = 'Tools'
|
||||||
this.description = 'Load OpenAPI specification, and converts each API endpoint to a tool'
|
this.description = 'Load OpenAPI specification, and converts each API endpoint to a tool'
|
||||||
this.inputs = [
|
this.inputs = [
|
||||||
{
|
{
|
||||||
label: 'YAML File',
|
label: 'Input Type',
|
||||||
name: 'yamlFile',
|
name: 'inputType',
|
||||||
|
type: 'options',
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
label: 'Upload File',
|
||||||
|
name: 'file'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Provide Link',
|
||||||
|
name: 'link'
|
||||||
|
}
|
||||||
|
],
|
||||||
|
default: 'file',
|
||||||
|
description: 'Choose how to provide the OpenAPI specification'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'OpenAPI File',
|
||||||
|
name: 'openApiFile',
|
||||||
type: 'file',
|
type: 'file',
|
||||||
fileType: '.yaml'
|
fileType: '.yaml,.json',
|
||||||
|
description: 'Upload your OpenAPI specification file (YAML or JSON)',
|
||||||
|
show: {
|
||||||
|
inputType: 'file'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'OpenAPI Link',
|
||||||
|
name: 'openApiLink',
|
||||||
|
type: 'string',
|
||||||
|
placeholder: 'https://api.example.com/openapi.yaml or https://api.example.com/openapi.json',
|
||||||
|
description: 'Provide a link to your OpenAPI specification (YAML or JSON)',
|
||||||
|
show: {
|
||||||
|
inputType: 'link'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Server',
|
||||||
|
name: 'selectedServer',
|
||||||
|
type: 'asyncOptions',
|
||||||
|
loadMethod: 'listServers',
|
||||||
|
description: 'Select which server to use for API calls',
|
||||||
|
refresh: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Available Endpoints',
|
||||||
|
name: 'selectedEndpoints',
|
||||||
|
type: 'asyncMultiOptions',
|
||||||
|
loadMethod: 'listEndpoints',
|
||||||
|
description: 'Select which endpoints to expose as tools',
|
||||||
|
refresh: true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Return Direct',
|
label: 'Return Direct',
|
||||||
|
|
@ -46,8 +94,7 @@ class OpenAPIToolkit_Tools implements INode {
|
||||||
type: 'json',
|
type: 'json',
|
||||||
description: 'Request headers to be sent with the API request. For example, {"Authorization": "Bearer token"}',
|
description: 'Request headers to be sent with the API request. For example, {"Authorization": "Bearer token"}',
|
||||||
additionalParams: true,
|
additionalParams: true,
|
||||||
optional: true,
|
optional: true
|
||||||
acceptVariable: true
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Remove null parameters',
|
label: 'Remove null parameters',
|
||||||
|
|
@ -76,49 +123,237 @@ class OpenAPIToolkit_Tools implements INode {
|
||||||
|
|
||||||
async init(nodeData: INodeData, _: string, options: ICommonObject): Promise<any> {
|
async init(nodeData: INodeData, _: string, options: ICommonObject): Promise<any> {
|
||||||
const toolReturnDirect = nodeData.inputs?.returnDirect as boolean
|
const toolReturnDirect = nodeData.inputs?.returnDirect as boolean
|
||||||
const yamlFileBase64 = nodeData.inputs?.yamlFile as string
|
const inputType = nodeData.inputs?.inputType as string
|
||||||
|
const openApiFile = nodeData.inputs?.openApiFile as string
|
||||||
|
const openApiLink = nodeData.inputs?.openApiLink as string
|
||||||
|
const selectedServer = nodeData.inputs?.selectedServer as string
|
||||||
const customCode = nodeData.inputs?.customCode as string
|
const customCode = nodeData.inputs?.customCode as string
|
||||||
const _headers = nodeData.inputs?.headers as string
|
const _headers = nodeData.inputs?.headers as string
|
||||||
const removeNulls = nodeData.inputs?.removeNulls as boolean
|
const removeNulls = nodeData.inputs?.removeNulls as boolean
|
||||||
|
|
||||||
const headers = typeof _headers === 'object' ? _headers : _headers ? JSON.parse(_headers) : {}
|
const headers = typeof _headers === 'object' ? _headers : _headers ? JSON.parse(_headers) : {}
|
||||||
|
|
||||||
let data
|
const specData = await this.loadOpenApiSpec(
|
||||||
if (yamlFileBase64.startsWith('FILE-STORAGE::')) {
|
{
|
||||||
const file = yamlFileBase64.replace('FILE-STORAGE::', '')
|
inputType,
|
||||||
const orgId = options.orgId
|
openApiFile,
|
||||||
const chatflowid = options.chatflowid
|
openApiLink
|
||||||
const fileData = await getFileFromStorage(file, orgId, chatflowid)
|
},
|
||||||
const utf8String = fileData.toString('utf-8')
|
options
|
||||||
|
)
|
||||||
|
if (!specData) throw new Error('Failed to load OpenAPI spec')
|
||||||
|
|
||||||
data = load(utf8String)
|
const _data: any = await $RefParser.dereference(specData)
|
||||||
|
|
||||||
|
// Use selected server or fallback to first server
|
||||||
|
let baseUrl: string
|
||||||
|
if (selectedServer && selectedServer !== 'error') {
|
||||||
|
baseUrl = selectedServer
|
||||||
} else {
|
} else {
|
||||||
const splitDataURI = yamlFileBase64.split(',')
|
baseUrl = _data.servers?.[0]?.url
|
||||||
splitDataURI.pop()
|
|
||||||
const bf = Buffer.from(splitDataURI.pop() || '', 'base64')
|
|
||||||
const utf8String = bf.toString('utf-8')
|
|
||||||
data = load(utf8String)
|
|
||||||
}
|
|
||||||
if (!data) {
|
|
||||||
throw new Error('Failed to load OpenAPI spec')
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const _data: any = await $RefParser.dereference(data)
|
if (!baseUrl) throw new Error('OpenAPI spec does not contain a server URL')
|
||||||
|
|
||||||
const baseUrl = _data.servers[0]?.url
|
|
||||||
if (!baseUrl) {
|
|
||||||
throw new Error('OpenAPI spec does not contain a server URL')
|
|
||||||
}
|
|
||||||
|
|
||||||
const appDataSource = options.appDataSource as DataSource
|
const appDataSource = options.appDataSource as DataSource
|
||||||
const databaseEntities = options.databaseEntities as IDatabaseEntity
|
const databaseEntities = options.databaseEntities as IDatabaseEntity
|
||||||
const variables = await getVars(appDataSource, databaseEntities, nodeData, options)
|
const variables = await getVars(appDataSource, databaseEntities, nodeData, options)
|
||||||
|
|
||||||
const flow = { chatflowId: options.chatflowid }
|
const flow = { chatflowId: options.chatflowid }
|
||||||
|
|
||||||
const tools = getTools(_data.paths, baseUrl, headers, variables, flow, toolReturnDirect, customCode, removeNulls)
|
let tools = getTools(_data.paths, baseUrl, headers, variables, flow, toolReturnDirect, customCode, removeNulls)
|
||||||
|
|
||||||
|
// Filter by selected endpoints if provided
|
||||||
|
const _selected = nodeData.inputs?.selectedEndpoints
|
||||||
|
let selected: string[] = []
|
||||||
|
if (_selected) {
|
||||||
|
try {
|
||||||
|
selected = typeof _selected === 'string' ? JSON.parse(_selected) : _selected
|
||||||
|
} catch (e) {
|
||||||
|
selected = []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (selected.length) {
|
||||||
|
tools = tools.filter((t: any) => selected.includes(t.name))
|
||||||
|
}
|
||||||
|
|
||||||
return tools
|
return tools
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//@ts-ignore
|
||||||
|
loadMethods = {
|
||||||
|
listServers: async (nodeData: INodeData, options: ICommonObject) => {
|
||||||
|
try {
|
||||||
|
const inputType = nodeData.inputs?.inputType as string
|
||||||
|
const openApiFile = nodeData.inputs?.openApiFile as string
|
||||||
|
const openApiLink = nodeData.inputs?.openApiLink as string
|
||||||
|
const specData: any = await this.loadOpenApiSpec(
|
||||||
|
{
|
||||||
|
inputType,
|
||||||
|
openApiFile,
|
||||||
|
openApiLink
|
||||||
|
},
|
||||||
|
options
|
||||||
|
)
|
||||||
|
if (!specData) return []
|
||||||
|
const _data: any = await $RefParser.dereference(specData)
|
||||||
|
const items: { label: string; name: string; description?: string }[] = []
|
||||||
|
const servers = _data.servers || []
|
||||||
|
|
||||||
|
if (servers.length === 0) {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
label: 'No Servers Found',
|
||||||
|
name: 'error',
|
||||||
|
description: 'No servers defined in the OpenAPI specification'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let i = 0; i < servers.length; i++) {
|
||||||
|
const server = servers[i]
|
||||||
|
const serverUrl = server.url || `Server ${i + 1}`
|
||||||
|
const serverDesc = server.description || serverUrl
|
||||||
|
items.push({
|
||||||
|
label: serverUrl,
|
||||||
|
name: serverUrl,
|
||||||
|
description: serverDesc
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return items
|
||||||
|
} catch (e) {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
label: 'No Servers Found',
|
||||||
|
name: 'error',
|
||||||
|
description: 'No available servers, check the link/file and refresh'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
listEndpoints: async (nodeData: INodeData, options: ICommonObject) => {
|
||||||
|
try {
|
||||||
|
const inputType = nodeData.inputs?.inputType as string
|
||||||
|
const openApiFile = nodeData.inputs?.openApiFile as string
|
||||||
|
const openApiLink = nodeData.inputs?.openApiLink as string
|
||||||
|
const specData: any = await this.loadOpenApiSpec(
|
||||||
|
{
|
||||||
|
inputType,
|
||||||
|
openApiFile,
|
||||||
|
openApiLink
|
||||||
|
},
|
||||||
|
options
|
||||||
|
)
|
||||||
|
if (!specData) return []
|
||||||
|
const _data: any = await $RefParser.dereference(specData)
|
||||||
|
const items: { label: string; name: string; description?: string }[] = []
|
||||||
|
const paths = _data.paths || {}
|
||||||
|
for (const path in paths) {
|
||||||
|
const methods = paths[path]
|
||||||
|
for (const method in methods) {
|
||||||
|
if (['get', 'post', 'put', 'delete', 'patch'].includes(method)) {
|
||||||
|
const spec = methods[method]
|
||||||
|
const opId = spec.operationId || `${method.toUpperCase()} ${path}`
|
||||||
|
const desc = spec.description || spec.summary || opId
|
||||||
|
items.push({ label: opId, name: opId, description: desc })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
items.sort((a, b) => a.label.localeCompare(b.label))
|
||||||
|
return items
|
||||||
|
} catch (e) {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
label: 'No Endpoints Found',
|
||||||
|
name: 'error',
|
||||||
|
description: 'No available endpoints, check the link/file and refresh'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async loadOpenApiSpec(
|
||||||
|
args: {
|
||||||
|
inputType?: string
|
||||||
|
openApiFile?: string
|
||||||
|
openApiLink?: string
|
||||||
|
},
|
||||||
|
options: ICommonObject
|
||||||
|
): Promise<any | null> {
|
||||||
|
const { inputType = 'file', openApiFile = '', openApiLink = '' } = args
|
||||||
|
try {
|
||||||
|
if (inputType === 'link' && openApiLink) {
|
||||||
|
const res = await fetch(openApiLink)
|
||||||
|
const text = await res.text()
|
||||||
|
|
||||||
|
// Auto-detect format from URL extension or content
|
||||||
|
const isJsonUrl = openApiLink.toLowerCase().includes('.json')
|
||||||
|
const isYamlUrl = openApiLink.toLowerCase().includes('.yaml') || openApiLink.toLowerCase().includes('.yml')
|
||||||
|
|
||||||
|
if (isJsonUrl) {
|
||||||
|
return JSON.parse(text)
|
||||||
|
} else if (isYamlUrl) {
|
||||||
|
return load(text)
|
||||||
|
} else {
|
||||||
|
// Auto-detect format from content
|
||||||
|
try {
|
||||||
|
return JSON.parse(text)
|
||||||
|
} catch (_) {
|
||||||
|
return load(text)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (inputType === 'file' && openApiFile) {
|
||||||
|
let utf8String: string
|
||||||
|
let fileName = ''
|
||||||
|
|
||||||
|
if (openApiFile.startsWith('FILE-STORAGE::')) {
|
||||||
|
const file = openApiFile.replace('FILE-STORAGE::', '')
|
||||||
|
fileName = file
|
||||||
|
const orgId = options.orgId
|
||||||
|
const chatflowid = options.chatflowid
|
||||||
|
const fileData = await getFileFromStorage(file, orgId, chatflowid)
|
||||||
|
utf8String = fileData.toString('utf-8')
|
||||||
|
} else {
|
||||||
|
// Extract filename from data URI if possible
|
||||||
|
const splitDataURI = openApiFile.split(',')
|
||||||
|
const mimeType = splitDataURI[0] || ''
|
||||||
|
if (mimeType.includes('filename=')) {
|
||||||
|
const filenameMatch = mimeType.match(/filename=([^;]+)/)
|
||||||
|
if (filenameMatch) {
|
||||||
|
fileName = filenameMatch[1]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
splitDataURI.pop()
|
||||||
|
const bf = Buffer.from(splitDataURI.pop() || '', 'base64')
|
||||||
|
utf8String = bf.toString('utf-8')
|
||||||
|
}
|
||||||
|
|
||||||
|
// Auto-detect format from file extension or content
|
||||||
|
const isJsonFile = fileName.toLowerCase().endsWith('.json')
|
||||||
|
const isYamlFile = fileName.toLowerCase().endsWith('.yaml') || fileName.toLowerCase().endsWith('.yml')
|
||||||
|
|
||||||
|
if (isJsonFile) {
|
||||||
|
return JSON.parse(utf8String)
|
||||||
|
} else if (isYamlFile) {
|
||||||
|
return load(utf8String)
|
||||||
|
} else {
|
||||||
|
// Auto-detect format from content
|
||||||
|
try {
|
||||||
|
return JSON.parse(utf8String)
|
||||||
|
} catch (_) {
|
||||||
|
return load(utf8String)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error('Error loading OpenAPI spec:', e)
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const jsonSchemaToZodSchema = (schema: any, requiredList: string[], keyName: string): ZodSchema<any> => {
|
const jsonSchemaToZodSchema = (schema: any, requiredList: string[], keyName: string): ZodSchema<any> => {
|
||||||
|
|
|
||||||
|
|
@ -99,6 +99,7 @@ JWT_TOKEN_EXPIRY_IN_MINUTES=360
|
||||||
JWT_REFRESH_TOKEN_EXPIRY_IN_MINUTES=43200
|
JWT_REFRESH_TOKEN_EXPIRY_IN_MINUTES=43200
|
||||||
# EXPIRE_AUTH_TOKENS_ON_RESTART=true # (if you need to expire all tokens on app restart)
|
# EXPIRE_AUTH_TOKENS_ON_RESTART=true # (if you need to expire all tokens on app restart)
|
||||||
# EXPRESS_SESSION_SECRET=flowise
|
# EXPRESS_SESSION_SECRET=flowise
|
||||||
|
# SECURE_COOKIES=
|
||||||
|
|
||||||
# INVITE_TOKEN_EXPIRY_IN_HOURS=24
|
# INVITE_TOKEN_EXPIRY_IN_HOURS=24
|
||||||
# PASSWORD_RESET_TOKEN_EXPIRY_IN_MINS=15
|
# PASSWORD_RESET_TOKEN_EXPIRY_IN_MINS=15
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,6 @@ import { Request, Response, NextFunction } from 'express'
|
||||||
import { StatusCodes } from 'http-status-codes'
|
import { StatusCodes } from 'http-status-codes'
|
||||||
import { AccountService } from '../services/account.service'
|
import { AccountService } from '../services/account.service'
|
||||||
import { getRunningExpressApp } from '../../utils/getRunningExpressApp'
|
import { getRunningExpressApp } from '../../utils/getRunningExpressApp'
|
||||||
import axios from 'axios'
|
|
||||||
|
|
||||||
export class AccountController {
|
export class AccountController {
|
||||||
public async register(req: Request, res: Response, next: NextFunction) {
|
public async register(req: Request, res: Response, next: NextFunction) {
|
||||||
|
|
@ -84,30 +83,6 @@ export class AccountController {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async cancelPreviousCloudSubscrption(req: Request, res: Response, next: NextFunction) {
|
|
||||||
try {
|
|
||||||
const { email } = req.body
|
|
||||||
if (!email) {
|
|
||||||
return res.status(StatusCodes.BAD_REQUEST).json({ message: 'Email is required' })
|
|
||||||
}
|
|
||||||
|
|
||||||
const headers = {
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
Accept: 'application/json'
|
|
||||||
}
|
|
||||||
|
|
||||||
const response = await axios.post(`${process.env.ENGINE_URL}/cancel-subscription`, { email }, { headers })
|
|
||||||
|
|
||||||
if (response.status === 200) {
|
|
||||||
return res.status(StatusCodes.OK).json(response.data)
|
|
||||||
} else {
|
|
||||||
return res.status(response.status).json(response.data)
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
next(error)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public async logout(req: Request, res: Response, next: NextFunction) {
|
public async logout(req: Request, res: Response, next: NextFunction) {
|
||||||
try {
|
try {
|
||||||
if (req.user) {
|
if (req.user) {
|
||||||
|
|
|
||||||
|
|
@ -79,7 +79,7 @@
|
||||||
style='
|
style='
|
||||||
position: relative;
|
position: relative;
|
||||||
background-color: #151719 !important;
|
background-color: #151719 !important;
|
||||||
background-image: url(https://auth.flowiseai.com/storage/v1/object/public/flowise-static-assets/flowise_email_bg.svg);
|
background-image: url(https://general-flowise.s3.us-east-1.amazonaws.com/flowise_email_bg.svg);
|
||||||
background-position: center top;
|
background-position: center top;
|
||||||
background-size: contain;
|
background-size: contain;
|
||||||
background-repeat: no-repeat;
|
background-repeat: no-repeat;
|
||||||
|
|
@ -220,7 +220,7 @@
|
||||||
<img
|
<img
|
||||||
alt=''
|
alt=''
|
||||||
width='auto'
|
width='auto'
|
||||||
src='https://auth.flowiseai.com/storage/v1/object/public/flowise-static-assets/flowise_logo.png'
|
src='https://general-flowise.s3.us-east-1.amazonaws.com/Flowise+Logo+Cropped+White+High+Res.png'
|
||||||
style='
|
style='
|
||||||
display: block;
|
display: block;
|
||||||
outline: none;
|
outline: none;
|
||||||
|
|
|
||||||
|
|
@ -197,7 +197,7 @@
|
||||||
style="
|
style="
|
||||||
position: relative;
|
position: relative;
|
||||||
background-color: #151719 !important;
|
background-color: #151719 !important;
|
||||||
background-image: url(https://auth.flowiseai.com/storage/v1/object/public/flowise-static-assets/flowise_email_bg.svg);
|
background-image: url(https://general-flowise.s3.us-east-1.amazonaws.com/flowise_email_bg.svg);
|
||||||
background-position: center top;
|
background-position: center top;
|
||||||
background-size: contain;
|
background-size: contain;
|
||||||
background-repeat: no-repeat;
|
background-repeat: no-repeat;
|
||||||
|
|
@ -338,7 +338,7 @@
|
||||||
<img
|
<img
|
||||||
alt=""
|
alt=""
|
||||||
width="auto"
|
width="auto"
|
||||||
src="https://auth.flowiseai.com/storage/v1/object/public/flowise-static-assets/flowise_logo.png"
|
src="https://general-flowise.s3.us-east-1.amazonaws.com/Flowise+Logo+Cropped+White+High+Res.png"
|
||||||
style="
|
style="
|
||||||
display: block;
|
display: block;
|
||||||
outline: none;
|
outline: none;
|
||||||
|
|
|
||||||
|
|
@ -78,7 +78,7 @@
|
||||||
style='
|
style='
|
||||||
position: relative;
|
position: relative;
|
||||||
background-color: #151719 !important;
|
background-color: #151719 !important;
|
||||||
background-image: url(https://auth.flowiseai.com/storage/v1/object/public/flowise-static-assets/flowise_email_bg.svg);
|
background-image: url(https://general-flowise.s3.us-east-1.amazonaws.com/flowise_email_bg.svg);
|
||||||
background-position: center top;
|
background-position: center top;
|
||||||
background-size: contain;
|
background-size: contain;
|
||||||
background-repeat: no-repeat;
|
background-repeat: no-repeat;
|
||||||
|
|
@ -219,7 +219,7 @@
|
||||||
<img
|
<img
|
||||||
alt=''
|
alt=''
|
||||||
width='auto'
|
width='auto'
|
||||||
src='https://auth.flowiseai.com/storage/v1/object/public/flowise-static-assets/flowise_logo.png'
|
src='https://general-flowise.s3.us-east-1.amazonaws.com/Flowise+Logo+Cropped+White+High+Res.png'
|
||||||
style='
|
style='
|
||||||
display: block;
|
display: block;
|
||||||
outline: none;
|
outline: none;
|
||||||
|
|
|
||||||
|
|
@ -197,7 +197,7 @@
|
||||||
style="
|
style="
|
||||||
position: relative;
|
position: relative;
|
||||||
background-color: #151719 !important;
|
background-color: #151719 !important;
|
||||||
background-image: url(https://auth.flowiseai.com/storage/v1/object/public/flowise-static-assets/flowise_email_bg.svg);
|
background-image: url(https://general-flowise.s3.us-east-1.amazonaws.com/flowise_email_bg.svg);
|
||||||
background-position: center top;
|
background-position: center top;
|
||||||
background-size: contain;
|
background-size: contain;
|
||||||
background-repeat: no-repeat;
|
background-repeat: no-repeat;
|
||||||
|
|
@ -338,7 +338,7 @@
|
||||||
<img
|
<img
|
||||||
alt=""
|
alt=""
|
||||||
width="auto"
|
width="auto"
|
||||||
src="https://auth.flowiseai.com/storage/v1/object/public/flowise-static-assets/flowise_logo.png"
|
src="https://general-flowise.s3.us-east-1.amazonaws.com/Flowise+Logo+Cropped+White+High+Res.png"
|
||||||
style="
|
style="
|
||||||
display: block;
|
display: block;
|
||||||
outline: none;
|
outline: none;
|
||||||
|
|
|
||||||
|
|
@ -78,7 +78,7 @@
|
||||||
style='
|
style='
|
||||||
position: relative;
|
position: relative;
|
||||||
background-color: #151719 !important;
|
background-color: #151719 !important;
|
||||||
background-image: url(https://auth.flowiseai.com/storage/v1/object/public/flowise-static-assets/flowise_email_bg.svg);
|
background-image: url(https://general-flowise.s3.us-east-1.amazonaws.com/flowise_email_bg.svg);
|
||||||
background-position: center top;
|
background-position: center top;
|
||||||
background-size: contain;
|
background-size: contain;
|
||||||
background-repeat: no-repeat;
|
background-repeat: no-repeat;
|
||||||
|
|
@ -219,7 +219,7 @@
|
||||||
<img
|
<img
|
||||||
alt=''
|
alt=''
|
||||||
width='auto'
|
width='auto'
|
||||||
src='https://auth.flowiseai.com/storage/v1/object/public/flowise-static-assets/flowise_logo.png'
|
src='https://general-flowise.s3.us-east-1.amazonaws.com/Flowise+Logo+Cropped+White+High+Res.png'
|
||||||
style='
|
style='
|
||||||
display: block;
|
display: block;
|
||||||
outline: none;
|
outline: none;
|
||||||
|
|
|
||||||
|
|
@ -197,7 +197,7 @@
|
||||||
style="
|
style="
|
||||||
position: relative;
|
position: relative;
|
||||||
background-color: #151719 !important;
|
background-color: #151719 !important;
|
||||||
background-image: url(https://auth.flowiseai.com/storage/v1/object/public/flowise-static-assets/flowise_email_bg.svg);
|
background-image: url(https://general-flowise.s3.us-east-1.amazonaws.com/flowise_email_bg.svg);
|
||||||
background-position: center top;
|
background-position: center top;
|
||||||
background-size: contain;
|
background-size: contain;
|
||||||
background-repeat: no-repeat;
|
background-repeat: no-repeat;
|
||||||
|
|
@ -338,7 +338,7 @@
|
||||||
<img
|
<img
|
||||||
alt=""
|
alt=""
|
||||||
width="auto"
|
width="auto"
|
||||||
src="https://auth.flowiseai.com/storage/v1/object/public/flowise-static-assets/flowise_logo.png"
|
src="https://general-flowise.s3.us-east-1.amazonaws.com/Flowise+Logo+Cropped+White+High+Res.png"
|
||||||
style="
|
style="
|
||||||
display: block;
|
display: block;
|
||||||
outline: none;
|
outline: none;
|
||||||
|
|
|
||||||
|
|
@ -68,7 +68,7 @@
|
||||||
bgcolor='#000000'
|
bgcolor='#000000'
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
style='position: relative;background-color: #151719 !important;background-image:url(https://auth.flowiseai.com/storage/v1/object/public/flowise-static-assets/flowise_email_bg.svg);background-position:center top;background-size:contain;background-repeat:no-repeat;'
|
style='position: relative;background-color: #151719 !important;background-image:url(https://general-flowise.s3.us-east-1.amazonaws.com/flowise_email_bg.svg);background-position:center top;background-size:contain;background-repeat:no-repeat;'
|
||||||
>
|
>
|
||||||
|
|
||||||
<div id='illustration' className='relative max-w-6xl mx-auto h-0 pointer-events-none' aria-hidden='true'>
|
<div id='illustration' className='relative max-w-6xl mx-auto h-0 pointer-events-none' aria-hidden='true'>
|
||||||
|
|
@ -170,7 +170,7 @@
|
||||||
<img
|
<img
|
||||||
alt=''
|
alt=''
|
||||||
width='auto'
|
width='auto'
|
||||||
src='https://auth.flowiseai.com/storage/v1/object/public/flowise-static-assets/flowise_logo.png'
|
src='https://general-flowise.s3.us-east-1.amazonaws.com/Flowise+Logo+Cropped+White+High+Res.png'
|
||||||
style='display: block; outline: none; text-decoration: none; height: 40px; font-size: 13px; line-height: 100%; -ms-interpolation-mode: bicubic; border: 0;'
|
style='display: block; outline: none; text-decoration: none; height: 40px; font-size: 13px; line-height: 100%; -ms-interpolation-mode: bicubic; border: 0;'
|
||||||
/>
|
/>
|
||||||
</td>
|
</td>
|
||||||
|
|
|
||||||
|
|
@ -197,7 +197,7 @@
|
||||||
style="
|
style="
|
||||||
position: relative;
|
position: relative;
|
||||||
background-color: #151719 !important;
|
background-color: #151719 !important;
|
||||||
background-image: url(https://auth.flowiseai.com/storage/v1/object/public/flowise-static-assets/flowise_email_bg.svg);
|
background-image: url(https://general-flowise.s3.us-east-1.amazonaws.com/flowise_email_bg.svg);
|
||||||
background-position: center top;
|
background-position: center top;
|
||||||
background-size: contain;
|
background-size: contain;
|
||||||
background-repeat: no-repeat;
|
background-repeat: no-repeat;
|
||||||
|
|
@ -338,7 +338,7 @@
|
||||||
<img
|
<img
|
||||||
alt=""
|
alt=""
|
||||||
width="auto"
|
width="auto"
|
||||||
src="https://auth.flowiseai.com/storage/v1/object/public/flowise-static-assets/flowise_logo.png"
|
src="https://general-flowise.s3.us-east-1.amazonaws.com/Flowise+Logo+Cropped+White+High+Res.png"
|
||||||
style="
|
style="
|
||||||
display: block;
|
display: block;
|
||||||
outline: none;
|
outline: none;
|
||||||
|
|
|
||||||
|
|
@ -78,7 +78,7 @@
|
||||||
style='
|
style='
|
||||||
position: relative;
|
position: relative;
|
||||||
background-color: #151719 !important;
|
background-color: #151719 !important;
|
||||||
background-image: url(https://auth.flowiseai.com/storage/v1/object/public/flowise-static-assets/flowise_email_bg.svg);
|
background-image: url(https://general-flowise.s3.us-east-1.amazonaws.com/flowise_email_bg.svg);
|
||||||
background-position: center top;
|
background-position: center top;
|
||||||
background-size: contain;
|
background-size: contain;
|
||||||
background-repeat: no-repeat;
|
background-repeat: no-repeat;
|
||||||
|
|
@ -219,7 +219,7 @@
|
||||||
<img
|
<img
|
||||||
alt=''
|
alt=''
|
||||||
width='auto'
|
width='auto'
|
||||||
src='https://auth.flowiseai.com/storage/v1/object/public/flowise-static-assets/flowise_logo.png'
|
src='https://general-flowise.s3.us-east-1.amazonaws.com/Flowise+Logo+Cropped+White+High+Res.png'
|
||||||
style='
|
style='
|
||||||
display: block;
|
display: block;
|
||||||
outline: none;
|
outline: none;
|
||||||
|
|
|
||||||
|
|
@ -197,7 +197,7 @@
|
||||||
style="
|
style="
|
||||||
position: relative;
|
position: relative;
|
||||||
background-color: #151719 !important;
|
background-color: #151719 !important;
|
||||||
background-image: url(https://auth.flowiseai.com/storage/v1/object/public/flowise-static-assets/flowise_email_bg.svg);
|
background-image: url(https://general-flowise.s3.us-east-1.amazonaws.com/flowise_email_bg.svg);
|
||||||
background-position: center top;
|
background-position: center top;
|
||||||
background-size: contain;
|
background-size: contain;
|
||||||
background-repeat: no-repeat;
|
background-repeat: no-repeat;
|
||||||
|
|
@ -338,7 +338,7 @@
|
||||||
<img
|
<img
|
||||||
alt=""
|
alt=""
|
||||||
width="auto"
|
width="auto"
|
||||||
src="https://auth.flowiseai.com/storage/v1/object/public/flowise-static-assets/flowise_logo.png"
|
src="https://general-flowise.s3.us-east-1.amazonaws.com/Flowise+Logo+Cropped+White+High+Res.png"
|
||||||
style="
|
style="
|
||||||
display: block;
|
display: block;
|
||||||
outline: none;
|
outline: none;
|
||||||
|
|
|
||||||
|
|
@ -78,7 +78,7 @@
|
||||||
style='
|
style='
|
||||||
position: relative;
|
position: relative;
|
||||||
background-color: #151719 !important;
|
background-color: #151719 !important;
|
||||||
background-image: url(https://auth.flowiseai.com/storage/v1/object/public/flowise-static-assets/flowise_email_bg.svg);
|
background-image: url(https://general-flowise.s3.us-east-1.amazonaws.com/flowise_email_bg.svg);
|
||||||
background-position: center top;
|
background-position: center top;
|
||||||
background-size: contain;
|
background-size: contain;
|
||||||
background-repeat: no-repeat;
|
background-repeat: no-repeat;
|
||||||
|
|
@ -219,7 +219,7 @@
|
||||||
<img
|
<img
|
||||||
alt=''
|
alt=''
|
||||||
width='auto'
|
width='auto'
|
||||||
src='https://auth.flowiseai.com/storage/v1/object/public/flowise-static-assets/flowise_logo.png'
|
src='https://general-flowise.s3.us-east-1.amazonaws.com/Flowise+Logo+Cropped+White+High+Res.png'
|
||||||
style='
|
style='
|
||||||
display: block;
|
display: block;
|
||||||
outline: none;
|
outline: none;
|
||||||
|
|
|
||||||
|
|
@ -197,7 +197,7 @@
|
||||||
style="
|
style="
|
||||||
position: relative;
|
position: relative;
|
||||||
background-color: #151719 !important;
|
background-color: #151719 !important;
|
||||||
background-image: url(https://auth.flowiseai.com/storage/v1/object/public/flowise-static-assets/flowise_email_bg.svg);
|
background-image: url(https://general-flowise.s3.us-east-1.amazonaws.com/flowise_email_bg.svg);
|
||||||
background-position: center top;
|
background-position: center top;
|
||||||
background-size: contain;
|
background-size: contain;
|
||||||
background-repeat: no-repeat;
|
background-repeat: no-repeat;
|
||||||
|
|
@ -338,7 +338,7 @@
|
||||||
<img
|
<img
|
||||||
alt=""
|
alt=""
|
||||||
width="auto"
|
width="auto"
|
||||||
src="https://auth.flowiseai.com/storage/v1/object/public/flowise-static-assets/flowise_logo.png"
|
src="https://general-flowise.s3.us-east-1.amazonaws.com/Flowise+Logo+Cropped+White+High+Res.png"
|
||||||
style="
|
style="
|
||||||
display: block;
|
display: block;
|
||||||
outline: none;
|
outline: none;
|
||||||
|
|
|
||||||
|
|
@ -68,7 +68,7 @@
|
||||||
bgcolor='#000000'
|
bgcolor='#000000'
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
style='position: relative;background-color: #151719 !important;background-image:url(https://auth.flowiseai.com/storage/v1/object/public/flowise-static-assets/flowise_email_bg.svg);background-position:center top;background-size:contain;background-repeat:no-repeat;'
|
style='position: relative;background-color: #151719 !important;background-image:url(https://general-flowise.s3.us-east-1.amazonaws.com/flowise_email_bg.svg);background-position:center top;background-size:contain;background-repeat:no-repeat;'
|
||||||
>
|
>
|
||||||
|
|
||||||
<div id='illustration' className='relative max-w-6xl mx-auto h-0 pointer-events-none' aria-hidden='true'>
|
<div id='illustration' className='relative max-w-6xl mx-auto h-0 pointer-events-none' aria-hidden='true'>
|
||||||
|
|
@ -170,7 +170,7 @@
|
||||||
<img
|
<img
|
||||||
alt=''
|
alt=''
|
||||||
width='auto'
|
width='auto'
|
||||||
src='https://auth.flowiseai.com/storage/v1/object/public/flowise-static-assets/flowise_logo.png'
|
src='https://general-flowise.s3.us-east-1.amazonaws.com/Flowise+Logo+Cropped+White+High+Res.png'
|
||||||
style='display: block; outline: none; text-decoration: none; height: 40px; font-size: 13px; line-height: 100%; -ms-interpolation-mode: bicubic; border: 0;'
|
style='display: block; outline: none; text-decoration: none; height: 40px; font-size: 13px; line-height: 100%; -ms-interpolation-mode: bicubic; border: 0;'
|
||||||
/>
|
/>
|
||||||
</td>
|
</td>
|
||||||
|
|
|
||||||
|
|
@ -197,7 +197,7 @@
|
||||||
style="
|
style="
|
||||||
position: relative;
|
position: relative;
|
||||||
background-color: #151719 !important;
|
background-color: #151719 !important;
|
||||||
background-image: url(https://auth.flowiseai.com/storage/v1/object/public/flowise-static-assets/flowise_email_bg.svg);
|
background-image: url(https://general-flowise.s3.us-east-1.amazonaws.com/flowise_email_bg.svg);
|
||||||
background-position: center top;
|
background-position: center top;
|
||||||
background-size: contain;
|
background-size: contain;
|
||||||
background-repeat: no-repeat;
|
background-repeat: no-repeat;
|
||||||
|
|
@ -338,7 +338,7 @@
|
||||||
<img
|
<img
|
||||||
alt=""
|
alt=""
|
||||||
width="auto"
|
width="auto"
|
||||||
src="https://auth.flowiseai.com/storage/v1/object/public/flowise-static-assets/flowise_logo.png"
|
src="https://general-flowise.s3.us-east-1.amazonaws.com/Flowise+Logo+Cropped+White+High+Res.png"
|
||||||
style="
|
style="
|
||||||
display: block;
|
display: block;
|
||||||
outline: none;
|
outline: none;
|
||||||
|
|
|
||||||
|
|
@ -33,7 +33,16 @@ const expireAuthTokensOnRestart = process.env.EXPIRE_AUTH_TOKENS_ON_RESTART ===
|
||||||
const jwtAuthTokenSecret = process.env.JWT_AUTH_TOKEN_SECRET || 'auth_token'
|
const jwtAuthTokenSecret = process.env.JWT_AUTH_TOKEN_SECRET || 'auth_token'
|
||||||
const jwtRefreshSecret = process.env.JWT_REFRESH_TOKEN_SECRET || process.env.JWT_AUTH_TOKEN_SECRET || 'refresh_token'
|
const jwtRefreshSecret = process.env.JWT_REFRESH_TOKEN_SECRET || process.env.JWT_AUTH_TOKEN_SECRET || 'refresh_token'
|
||||||
|
|
||||||
const secureCookie = process.env.APP_URL?.startsWith('https') ? true : false
|
// Allow explicit override of cookie security settings
|
||||||
|
// This is useful when running behind a reverse proxy/load balancer that terminates SSL
|
||||||
|
const secureCookie =
|
||||||
|
process.env.SECURE_COOKIES === 'false'
|
||||||
|
? false
|
||||||
|
: process.env.SECURE_COOKIES === 'true'
|
||||||
|
? true
|
||||||
|
: process.env.APP_URL?.startsWith('https')
|
||||||
|
? true
|
||||||
|
: false
|
||||||
const jwtOptions = {
|
const jwtOptions = {
|
||||||
secretOrKey: jwtAuthTokenSecret,
|
secretOrKey: jwtAuthTokenSecret,
|
||||||
audience: jwtAudience,
|
audience: jwtAudience,
|
||||||
|
|
|
||||||
|
|
@ -28,8 +28,6 @@ router.post('/forgot-password', accountController.forgotPassword)
|
||||||
|
|
||||||
router.post('/reset-password', accountController.resetPassword)
|
router.post('/reset-password', accountController.resetPassword)
|
||||||
|
|
||||||
router.post('/cancel-subscription', accountController.cancelPreviousCloudSubscrption)
|
|
||||||
|
|
||||||
router.post('/billing', accountController.createStripeCustomerPortalSession)
|
router.post('/billing', accountController.createStripeCustomerPortalSession)
|
||||||
|
|
||||||
router.get('/basic-auth', accountController.getBasicAuth)
|
router.get('/basic-auth', accountController.getBasicAuth)
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,6 @@ const resendVerificationEmail = (body) => client.post('/account/resend-verificat
|
||||||
const forgotPassword = (body) => client.post('/account/forgot-password', body)
|
const forgotPassword = (body) => client.post('/account/forgot-password', body)
|
||||||
const resetPassword = (body) => client.post('/account/reset-password', body)
|
const resetPassword = (body) => client.post('/account/reset-password', body)
|
||||||
const getBillingData = () => client.post('/account/billing')
|
const getBillingData = () => client.post('/account/billing')
|
||||||
const cancelSubscription = (body) => client.post('/account/cancel-subscription', body)
|
|
||||||
const logout = () => client.post('/account/logout')
|
const logout = () => client.post('/account/logout')
|
||||||
const getBasicAuth = () => client.get('/account/basic-auth')
|
const getBasicAuth = () => client.get('/account/basic-auth')
|
||||||
const checkBasicAuth = (body) => client.post('/account/basic-auth', body)
|
const checkBasicAuth = (body) => client.post('/account/basic-auth', body)
|
||||||
|
|
@ -20,7 +19,6 @@ export default {
|
||||||
resendVerificationEmail,
|
resendVerificationEmail,
|
||||||
forgotPassword,
|
forgotPassword,
|
||||||
resetPassword,
|
resetPassword,
|
||||||
cancelSubscription,
|
|
||||||
logout,
|
logout,
|
||||||
getBasicAuth,
|
getBasicAuth,
|
||||||
checkBasicAuth
|
checkBasicAuth
|
||||||
|
|
|
||||||
|
|
@ -77,7 +77,6 @@ const AccountSettings = () => {
|
||||||
const [confirmPassword, setConfirmPassword] = useState('')
|
const [confirmPassword, setConfirmPassword] = useState('')
|
||||||
const [usage, setUsage] = useState(null)
|
const [usage, setUsage] = useState(null)
|
||||||
const [isBillingLoading, setIsBillingLoading] = useState(false)
|
const [isBillingLoading, setIsBillingLoading] = useState(false)
|
||||||
const [isMigrateLoading, setIsMigrateLoading] = useState(false)
|
|
||||||
const [seatsQuantity, setSeatsQuantity] = useState(0)
|
const [seatsQuantity, setSeatsQuantity] = useState(0)
|
||||||
const [prorationInfo, setProrationInfo] = useState(null)
|
const [prorationInfo, setProrationInfo] = useState(null)
|
||||||
const [isUpdatingSeats, setIsUpdatingSeats] = useState(false)
|
const [isUpdatingSeats, setIsUpdatingSeats] = useState(false)
|
||||||
|
|
@ -173,45 +172,6 @@ const AccountSettings = () => {
|
||||||
return currentPlan?.title || ''
|
return currentPlan?.title || ''
|
||||||
}, [getPricingPlansApi.data, currentUser?.activeOrganizationProductId])
|
}, [getPricingPlansApi.data, currentUser?.activeOrganizationProductId])
|
||||||
|
|
||||||
const handleMigrateEmail = async () => {
|
|
||||||
setIsMigrateLoading(true)
|
|
||||||
try {
|
|
||||||
const obj = {
|
|
||||||
email: migrateEmail
|
|
||||||
}
|
|
||||||
const resp = await accountApi.cancelSubscription(obj)
|
|
||||||
if (resp.status === 200) {
|
|
||||||
enqueueSnackbar({
|
|
||||||
message: `Instruction to cancel subscription has been sent to ${migrateEmail}`,
|
|
||||||
options: {
|
|
||||||
key: new Date().getTime() + Math.random(),
|
|
||||||
variant: 'success',
|
|
||||||
action: (key) => (
|
|
||||||
<Button style={{ color: 'white' }} onClick={() => closeSnackbar(key)}>
|
|
||||||
<IconX />
|
|
||||||
</Button>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
enqueueSnackbar({
|
|
||||||
message: 'Failed to access billing portal',
|
|
||||||
options: {
|
|
||||||
key: new Date().getTime() + Math.random(),
|
|
||||||
variant: 'error',
|
|
||||||
action: (key) => (
|
|
||||||
<Button style={{ color: 'white' }} onClick={() => closeSnackbar(key)}>
|
|
||||||
<IconX />
|
|
||||||
</Button>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
} finally {
|
|
||||||
setIsMigrateLoading(false)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleBillingPortalClick = async () => {
|
const handleBillingPortalClick = async () => {
|
||||||
setIsBillingLoading(true)
|
setIsBillingLoading(true)
|
||||||
try {
|
try {
|
||||||
|
|
@ -817,70 +777,6 @@ const AccountSettings = () => {
|
||||||
</Box>
|
</Box>
|
||||||
</SettingsSection>
|
</SettingsSection>
|
||||||
)}
|
)}
|
||||||
<SettingsSection title='Cancel Previous Subscription'>
|
|
||||||
<Box
|
|
||||||
sx={{
|
|
||||||
width: '100%',
|
|
||||||
display: 'grid',
|
|
||||||
gridTemplateColumns: 'repeat(3, 1fr)'
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Box
|
|
||||||
sx={{
|
|
||||||
gridColumn: 'span 2 / span 2',
|
|
||||||
display: 'flex',
|
|
||||||
flexDirection: 'column',
|
|
||||||
alignItems: 'start',
|
|
||||||
justifyContent: 'center',
|
|
||||||
gap: 1,
|
|
||||||
px: 2.5,
|
|
||||||
py: 2
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Typography variant='body2'>Migrate from existing cloud subscription?</Typography>
|
|
||||||
<Typography variant='body2' color='text.secondary'>
|
|
||||||
{`If you have an existing cloud app like <your-app>.app.flowiseai.com, after finished migrating your work, you can cancel the previous subscription. We'll send you an email with a link to cancel your previous subscription.`}
|
|
||||||
</Typography>
|
|
||||||
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 1, width: '100%' }}>
|
|
||||||
<OutlinedInput
|
|
||||||
id='email'
|
|
||||||
type='string'
|
|
||||||
fullWidth
|
|
||||||
placeholder='Email Address'
|
|
||||||
name='email'
|
|
||||||
onChange={(e) => setMigrateEmail(e.target.value)}
|
|
||||||
value={migrateEmail}
|
|
||||||
/>
|
|
||||||
</Box>
|
|
||||||
</Box>
|
|
||||||
<Box
|
|
||||||
sx={{
|
|
||||||
display: 'flex',
|
|
||||||
alignItems: 'center',
|
|
||||||
justifyContent: 'end',
|
|
||||||
px: 2.5,
|
|
||||||
py: 2,
|
|
||||||
gap: 2
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Button
|
|
||||||
variant='outlined'
|
|
||||||
disabled={!currentUser.isOrganizationAdmin || isMigrateLoading}
|
|
||||||
onClick={handleMigrateEmail}
|
|
||||||
sx={{ borderRadius: 2, height: 40 }}
|
|
||||||
>
|
|
||||||
{isMigrateLoading ? (
|
|
||||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
|
|
||||||
<CircularProgress size={16} color='inherit' />
|
|
||||||
Loading
|
|
||||||
</Box>
|
|
||||||
) : (
|
|
||||||
'Send Instructions'
|
|
||||||
)}
|
|
||||||
</Button>
|
|
||||||
</Box>
|
|
||||||
</Box>
|
|
||||||
</SettingsSection>
|
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</Stack>
|
</Stack>
|
||||||
|
|
|
||||||
|
|
@ -241,18 +241,6 @@ const SignInPage = () => {
|
||||||
Forgot password?
|
Forgot password?
|
||||||
</Link>
|
</Link>
|
||||||
</Typography>
|
</Typography>
|
||||||
{isCloud && (
|
|
||||||
<Typography variant='body2' sx={{ color: theme.palette.grey[600], mt: 1, textAlign: 'right' }}>
|
|
||||||
<a
|
|
||||||
href='https://docs.flowiseai.com/migration-guide/cloud-migration'
|
|
||||||
target='_blank'
|
|
||||||
rel='noopener noreferrer'
|
|
||||||
style={{ color: theme.palette.primary.main }}
|
|
||||||
>
|
|
||||||
Migrate from existing account?
|
|
||||||
</a>
|
|
||||||
</Typography>
|
|
||||||
)}
|
|
||||||
</Box>
|
</Box>
|
||||||
<LoadingButton
|
<LoadingButton
|
||||||
loading={loading}
|
loading={loading}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue