diff --git a/packages/cli/src/AbstractServer.ts b/packages/cli/src/AbstractServer.ts index 9496b1dffc453..1610600e50e88 100644 --- a/packages/cli/src/AbstractServer.ts +++ b/packages/cli/src/AbstractServer.ts @@ -154,7 +154,7 @@ export abstract class AbstractServer { this.server.on('error', (error: Error & { code: string }) => { if (error.code === 'EADDRINUSE') { - console.log( + this.logger.info( `n8n's port ${PORT} is already in use. Do you have another instance of n8n running already?`, ); process.exit(1); @@ -167,7 +167,7 @@ export abstract class AbstractServer { await this.setupHealthCheck(); - console.log(`n8n ready on ${ADDRESS}, port ${PORT}`); + this.logger.info(`n8n ready on ${ADDRESS}, port ${PORT}`); } async start(): Promise { @@ -236,11 +236,11 @@ export abstract class AbstractServer { await this.configure(); if (!inTest) { - console.log(`Version: ${N8N_VERSION}`); + this.logger.info(`Version: ${N8N_VERSION}`); const defaultLocale = config.getEnv('defaultLocale'); if (defaultLocale !== 'en') { - console.log(`Locale: ${defaultLocale}`); + this.logger.info(`Locale: ${defaultLocale}`); } await this.externalHooks.run('n8n.ready', [this, config]); diff --git a/packages/cli/src/License.ts b/packages/cli/src/License.ts index ce678939c8b97..1e31222571b10 100644 --- a/packages/cli/src/License.ts +++ b/packages/cli/src/License.ts @@ -41,6 +41,26 @@ export class License { private readonly usageMetricsService: UsageMetricsService, ) {} + /** + * Whether this instance should renew the license - on init and periodically. + */ + private renewalEnabled(instanceType: N8nInstanceType) { + if (instanceType !== 'main') return false; + + const autoRenewEnabled = config.getEnv('license.autoRenewEnabled'); + + /** + * In multi-main setup, all mains start off with `unset` status and so renewal disabled. + * On becoming leader or follower, each will enable or disable renewal, respectively. + * This ensures the mains do not cause a 429 (too many requests) on license init. + */ + if (config.getEnv('multiMainSetup.enabled')) { + return autoRenewEnabled && config.getEnv('multiMainSetup.instanceType') === 'leader'; + } + + return autoRenewEnabled; + } + async init(instanceType: N8nInstanceType = 'main') { if (this.manager) { this.logger.warn('License manager already initialized or shutting down'); @@ -53,7 +73,6 @@ export class License { const isMainInstance = instanceType === 'main'; const server = config.getEnv('license.serverUrl'); - const autoRenewEnabled = isMainInstance && config.getEnv('license.autoRenewEnabled'); const offlineMode = !isMainInstance; const autoRenewOffset = config.getEnv('license.autoRenewOffset'); const saveCertStr = isMainInstance @@ -66,13 +85,15 @@ export class License { ? async () => await this.usageMetricsService.collectUsageMetrics() : async () => []; + const renewalEnabled = this.renewalEnabled(instanceType); + try { this.manager = new LicenseManager({ server, tenantId: config.getEnv('license.tenantId'), productIdentifier: `n8n-${N8N_VERSION}`, - autoRenewEnabled, - renewOnInit: autoRenewEnabled, + autoRenewEnabled: renewalEnabled, + renewOnInit: renewalEnabled, autoRenewOffset, offlineMode, logger: this.logger, @@ -126,7 +147,7 @@ export class License { if (this.orchestrationService.isMultiMainSetupEnabled && !isMultiMainLicensed) { this.logger.debug( - '[Multi-main setup] License changed with no support for multi-main setup - no new followers will be allowed to init. To restore multi-main setup, please upgrade to a license that supporst this feature.', + '[Multi-main setup] License changed with no support for multi-main setup - no new followers will be allowed to init. To restore multi-main setup, please upgrade to a license that supports this feature.', ); } } @@ -335,4 +356,9 @@ export class License { isWithinUsersLimit() { return this.getUsersLimit() === UNLIMITED_LICENSE_QUOTA; } + + async reinit() { + this.manager?.reset(); + await this.init(); + } } diff --git a/packages/cli/src/ResponseHelper.ts b/packages/cli/src/ResponseHelper.ts index e9e27d02bed38..42dcdee353331 100644 --- a/packages/cli/src/ResponseHelper.ts +++ b/packages/cli/src/ResponseHelper.ts @@ -10,6 +10,8 @@ import { Readable } from 'node:stream'; import { inDevelopment } from '@/constants'; import { ResponseError } from './errors/response-errors/abstract/response.error'; +import Container from 'typedi'; +import { Logger } from './Logger'; export function sendSuccessResponse( res: Response, @@ -83,7 +85,7 @@ export function sendErrorResponse(res: Response, error: Error) { if (isResponseError(error)) { if (inDevelopment) { - console.error(picocolors.red(error.httpStatusCode), error.message); + Container.get(Logger).error(picocolors.red([error.httpStatusCode, error.message].join(' '))); } //render custom 404 page for form triggers @@ -112,7 +114,7 @@ export function sendErrorResponse(res: Response, error: Error) { if (error instanceof NodeApiError) { if (inDevelopment) { - console.error(picocolors.red(error.name), error.message); + Container.get(Logger).error([picocolors.red(error.name), error.message].join(' ')); } Object.assign(response, error); diff --git a/packages/cli/src/WorkflowExecuteAdditionalData.ts b/packages/cli/src/WorkflowExecuteAdditionalData.ts index 17799baedf480..88374f0c659dc 100644 --- a/packages/cli/src/WorkflowExecuteAdditionalData.ts +++ b/packages/cli/src/WorkflowExecuteAdditionalData.ts @@ -637,7 +637,10 @@ function hookFunctionsSaveWorker(): IWorkflowExecuteHooks { ]); } catch (error) { ErrorReporter.error(error); - console.error('There was a problem running hook "workflow.postExecute"', error); + Container.get(Logger).error( + 'There was a problem running hook "workflow.postExecute"', + error, + ); } } }, diff --git a/packages/cli/src/WorkflowRunner.ts b/packages/cli/src/WorkflowRunner.ts index 4c46d45a7d1c8..10ca743fae7e6 100644 --- a/packages/cli/src/WorkflowRunner.ts +++ b/packages/cli/src/WorkflowRunner.ts @@ -211,13 +211,16 @@ export class WorkflowRunner { ]); } catch (error) { ErrorReporter.error(error); - console.error('There was a problem running hook "workflow.postExecute"', error); + this.logger.error('There was a problem running hook "workflow.postExecute"', error); } } }) .catch((error) => { ErrorReporter.error(error); - console.error('There was a problem running internal hook "onWorkflowPostExecute"', error); + this.logger.error( + 'There was a problem running internal hook "onWorkflowPostExecute"', + error, + ); }); } @@ -411,7 +414,7 @@ export class WorkflowRunner { try { job = await this.jobQueue.add(jobData, jobOptions); - console.log(`Started with job ID: ${job.id.toString()} (Execution ID: ${executionId})`); + this.logger.info(`Started with job ID: ${job.id.toString()} (Execution ID: ${executionId})`); hooks = WorkflowExecuteAdditionalData.getWorkflowHooksWorkerMain( data.executionMode, diff --git a/packages/cli/src/commands/BaseCommand.ts b/packages/cli/src/commands/BaseCommand.ts index faa5db165df43..61db09f3a57b7 100644 --- a/packages/cli/src/commands/BaseCommand.ts +++ b/packages/cli/src/commands/BaseCommand.ts @@ -41,6 +41,8 @@ export abstract class BaseCommand extends Command { protected shutdownService: ShutdownService = Container.get(ShutdownService); + protected license: License; + /** * How long to wait for graceful shutdown before force killing the process. */ @@ -269,13 +271,13 @@ export abstract class BaseCommand extends Command { } async initLicense(): Promise { - const license = Container.get(License); - await license.init(this.instanceType ?? 'main'); + this.license = Container.get(License); + await this.license.init(this.instanceType ?? 'main'); const activationKey = config.getEnv('license.activationKey'); if (activationKey) { - const hasCert = (await license.loadCertStr()).length > 0; + const hasCert = (await this.license.loadCertStr()).length > 0; if (hasCert) { return this.logger.debug('Skipping license activation'); @@ -283,7 +285,7 @@ export abstract class BaseCommand extends Command { try { this.logger.debug('Attempting license activation'); - await license.activate(activationKey); + await this.license.activate(activationKey); this.logger.debug('License init complete'); } catch (e) { this.logger.error('Could not activate license', e as Error); @@ -320,7 +322,7 @@ export abstract class BaseCommand extends Command { const forceShutdownTimer = setTimeout(async () => { // In case that something goes wrong with shutdown we // kill after timeout no matter what - console.log(`process exited after ${this.gracefulShutdownTimeoutInS}s`); + this.logger.info(`process exited after ${this.gracefulShutdownTimeoutInS}s`); const errorMsg = `Shutdown timed out after ${this.gracefulShutdownTimeoutInS} seconds`; await this.exitWithCrash(errorMsg, new Error(errorMsg)); }, this.gracefulShutdownTimeoutInS * 1000); diff --git a/packages/cli/src/commands/executeBatch.ts b/packages/cli/src/commands/executeBatch.ts index affec09e52fc3..cd01c9c99b207 100644 --- a/packages/cli/src/commands/executeBatch.ts +++ b/packages/cli/src/commands/executeBatch.ts @@ -178,11 +178,11 @@ export class ExecuteBatch extends BaseCommand { if (flags.snapshot !== undefined) { if (fs.existsSync(flags.snapshot)) { if (!fs.lstatSync(flags.snapshot).isDirectory()) { - console.log('The parameter --snapshot must be an existing directory'); + this.logger.error('The parameter --snapshot must be an existing directory'); return; } } else { - console.log('The parameter --snapshot must be an existing directory'); + this.logger.error('The parameter --snapshot must be an existing directory'); return; } @@ -191,11 +191,11 @@ export class ExecuteBatch extends BaseCommand { if (flags.compare !== undefined) { if (fs.existsSync(flags.compare)) { if (!fs.lstatSync(flags.compare).isDirectory()) { - console.log('The parameter --compare must be an existing directory'); + this.logger.error('The parameter --compare must be an existing directory'); return; } } else { - console.log('The parameter --compare must be an existing directory'); + this.logger.error('The parameter --compare must be an existing directory'); return; } @@ -205,7 +205,7 @@ export class ExecuteBatch extends BaseCommand { if (flags.output !== undefined) { if (fs.existsSync(flags.output)) { if (fs.lstatSync(flags.output).isDirectory()) { - console.log('The parameter --output must be a writable file'); + this.logger.error('The parameter --output must be a writable file'); return; } } @@ -225,7 +225,7 @@ export class ExecuteBatch extends BaseCommand { const matchedIds = paramIds.filter((id) => re.exec(id)); if (matchedIds.length === 0) { - console.log( + this.logger.error( 'The parameter --ids must be a list of numeric IDs separated by a comma or a file with this content.', ); return; @@ -245,7 +245,7 @@ export class ExecuteBatch extends BaseCommand { .filter((id) => re.exec(id)), ); } else { - console.log('Skip list file not found. Exiting.'); + this.logger.error('Skip list file not found. Exiting.'); return; } } @@ -302,18 +302,18 @@ export class ExecuteBatch extends BaseCommand { if (flags.output !== undefined) { fs.writeFileSync(flags.output, this.formatJsonOutput(results)); - console.log('\nExecution finished.'); - console.log('Summary:'); - console.log(`\tSuccess: ${results.summary.successfulExecutions}`); - console.log(`\tFailures: ${results.summary.failedExecutions}`); - console.log(`\tWarnings: ${results.summary.warningExecutions}`); - console.log('\nNodes successfully tested:'); + this.logger.info('\nExecution finished.'); + this.logger.info('Summary:'); + this.logger.info(`\tSuccess: ${results.summary.successfulExecutions}`); + this.logger.info(`\tFailures: ${results.summary.failedExecutions}`); + this.logger.info(`\tWarnings: ${results.summary.warningExecutions}`); + this.logger.info('\nNodes successfully tested:'); Object.entries(results.coveredNodes).forEach(([nodeName, nodeCount]) => { - console.log(`\t${nodeName}: ${nodeCount}`); + this.logger.info(`\t${nodeName}: ${nodeCount}`); }); - console.log('\nCheck the JSON file for more details.'); + this.logger.info('\nCheck the JSON file for more details.'); } else if (flags.shortOutput) { - console.log( + this.logger.info( this.formatJsonOutput({ ...results, executions: results.executions.filter( @@ -322,7 +322,7 @@ export class ExecuteBatch extends BaseCommand { }), ); } else { - console.log(this.formatJsonOutput(results)); + this.logger.info(this.formatJsonOutput(results)); } await this.stopProcess(true); diff --git a/packages/cli/src/commands/start.ts b/packages/cli/src/commands/start.ts index 3b7ee763a3244..4ddd999d0c1c5 100644 --- a/packages/cli/src/commands/start.ts +++ b/packages/cli/src/commands/start.ts @@ -76,7 +76,7 @@ export class Start extends BaseCommand { const editorUrl = Container.get(UrlService).baseUrl; open(editorUrl, { wait: true }).catch(() => { - console.log( + this.logger.info( `\nWas not able to open URL in browser. Please open manually by visiting:\n${editorUrl}\n`, ); }); @@ -211,9 +211,11 @@ export class Start extends BaseCommand { orchestrationService.multiMainSetup .on('leader-stepdown', async () => { + await this.license.reinit(); // to disable renewal await this.activeWorkflowRunner.removeAllTriggerAndPollerBasedWorkflows(); }) .on('leader-takeover', async () => { + await this.license.reinit(); // to enable renewal await this.activeWorkflowRunner.addAllTriggerAndPollerBasedWorkflows(); }); } @@ -339,7 +341,7 @@ export class Start extends BaseCommand { } async catch(error: Error) { - console.log(error.stack); + if (error.stack) this.logger.error(error.stack); await this.exitWithCrash('Exiting due to an error.', error); } } diff --git a/packages/cli/src/commands/update/workflow.ts b/packages/cli/src/commands/update/workflow.ts index 0a8f0314615c6..f365db2d9814a 100644 --- a/packages/cli/src/commands/update/workflow.ts +++ b/packages/cli/src/commands/update/workflow.ts @@ -28,24 +28,24 @@ export class UpdateWorkflowCommand extends BaseCommand { const { flags } = await this.parse(UpdateWorkflowCommand); if (!flags.all && !flags.id) { - console.info('Either option "--all" or "--id" have to be set!'); + this.logger.error('Either option "--all" or "--id" have to be set!'); return; } if (flags.all && flags.id) { - console.info( + this.logger.error( 'Either something else on top should be "--all" or "--id" can be set never both!', ); return; } if (flags.active === undefined) { - console.info('No update flag like "--active=true" has been set!'); + this.logger.error('No update flag like "--active=true" has been set!'); return; } if (!['false', 'true'].includes(flags.active)) { - console.info('Valid values for flag "--active" are only "false" or "true"!'); + this.logger.error('Valid values for flag "--active" are only "false" or "true"!'); return; } diff --git a/packages/cli/src/controllers/e2e.controller.ts b/packages/cli/src/controllers/e2e.controller.ts index cdf02852af453..6d99c785b95e0 100644 --- a/packages/cli/src/controllers/e2e.controller.ts +++ b/packages/cli/src/controllers/e2e.controller.ts @@ -14,9 +14,11 @@ import { MfaService } from '@/Mfa/mfa.service'; import { Push } from '@/push'; import { CacheService } from '@/services/cache/cache.service'; import { PasswordUtility } from '@/services/password.utility'; +import Container from 'typedi'; +import { Logger } from '@/Logger'; if (!inE2ETests) { - console.error('E2E endpoints only allowed during E2E tests'); + Container.get(Logger).error('E2E endpoints only allowed during E2E tests'); process.exit(1); } @@ -149,7 +151,9 @@ export class E2EController { `DELETE FROM ${table}; DELETE FROM sqlite_sequence WHERE name=${table};`, ); } catch (error) { - console.warn('Dropping Table for E2E Reset error: ', error); + Container.get(Logger).warn('Dropping Table for E2E Reset error', { + error: error as Error, + }); } } } diff --git a/packages/cli/src/eventbus/MessageEventBusDestination/MessageEventBusDestinationSyslog.ee.ts b/packages/cli/src/eventbus/MessageEventBusDestination/MessageEventBusDestinationSyslog.ee.ts index 5fe894ee7d280..ac68d3856a675 100644 --- a/packages/cli/src/eventbus/MessageEventBusDestination/MessageEventBusDestinationSyslog.ee.ts +++ b/packages/cli/src/eventbus/MessageEventBusDestination/MessageEventBusDestinationSyslog.ee.ts @@ -10,6 +10,8 @@ import { MessageEventBusDestination } from './MessageEventBusDestination.ee'; import { isLogStreamingEnabled } from '../MessageEventBus/MessageEventBusHelper'; import { eventMessageGenericDestinationTestEvent } from '../EventMessageClasses/EventMessageGeneric'; import type { MessageEventBus, MessageWithCallback } from '../MessageEventBus/MessageEventBus'; +import Container from 'typedi'; +import { Logger } from '@/Logger'; export const isMessageEventBusDestinationSyslogOptions = ( candidate: unknown, ): candidate is MessageEventBusDestinationSyslogOptions => { @@ -63,7 +65,7 @@ export class MessageEventBusDestinationSyslog }); this.logger.debug(`MessageEventBusDestinationSyslog with id ${this.getId()} initialized`); this.client.on('error', function (error) { - console.error(error); + Container.get(Logger).error(`${error.message}`); }); } diff --git a/packages/cli/src/eventbus/MessageEventBusDestination/MessageEventBusDestinationWebhook.ee.ts b/packages/cli/src/eventbus/MessageEventBusDestination/MessageEventBusDestinationWebhook.ee.ts index 59070a023b5b5..6b23c2de78ecb 100644 --- a/packages/cli/src/eventbus/MessageEventBusDestination/MessageEventBusDestinationWebhook.ee.ts +++ b/packages/cli/src/eventbus/MessageEventBusDestination/MessageEventBusDestinationWebhook.ee.ts @@ -180,7 +180,7 @@ export class MessageEventBusDestinationWebhook try { JSON.parse(this.jsonQuery); } catch { - console.log('JSON parameter need to be an valid JSON'); + this.logger.error('JSON parameter need to be an valid JSON'); } this.axiosRequestOptions.params = jsonParse(this.jsonQuery); } @@ -198,7 +198,7 @@ export class MessageEventBusDestinationWebhook try { JSON.parse(this.jsonHeaders); } catch { - console.log('JSON parameter need to be an valid JSON'); + this.logger.error('JSON parameter need to be an valid JSON'); } this.axiosRequestOptions.headers = jsonParse(this.jsonHeaders); } diff --git a/packages/cli/src/help.ts b/packages/cli/src/help.ts index a95a3381e0211..8581483577402 100644 --- a/packages/cli/src/help.ts +++ b/packages/cli/src/help.ts @@ -1,10 +1,12 @@ import { Help } from '@oclif/core'; +import Container from 'typedi'; +import { Logger } from 'winston'; // oclif expects a default export // eslint-disable-next-line import/no-default-export export default class CustomHelp extends Help { async showRootHelp() { - console.log( + Container.get(Logger).info( 'You can find up to date information about the CLI here:\nhttps://docs.n8n.io/hosting/cli-commands/', ); } diff --git a/packages/cli/src/security-audit/risk-reporters/InstanceRiskReporter.ts b/packages/cli/src/security-audit/risk-reporters/InstanceRiskReporter.ts index 637fab4d02659..47568b06f3220 100644 --- a/packages/cli/src/security-audit/risk-reporters/InstanceRiskReporter.ts +++ b/packages/cli/src/security-audit/risk-reporters/InstanceRiskReporter.ts @@ -14,10 +14,14 @@ import { getN8nPackageJson, inDevelopment } from '@/constants'; import type { WorkflowEntity } from '@db/entities/WorkflowEntity'; import type { RiskReporter, Risk, n8n } from '@/security-audit/types'; import { isApiEnabled } from '@/PublicApi'; +import { Logger } from '@/Logger'; @Service() export class InstanceRiskReporter implements RiskReporter { - constructor(private readonly instanceSettings: InstanceSettings) {} + constructor( + private readonly instanceSettings: InstanceSettings, + private readonly logger: Logger, + ) {} async report(workflows: WorkflowEntity[]) { const unprotectedWebhooks = this.getUnprotectedWebhookNodes(workflows); @@ -174,7 +178,7 @@ export class InstanceRiskReporter implements RiskReporter { versions = await this.getNextVersions(localVersion).then((v) => this.removeIconData(v)); } catch (error) { if (inDevelopment) { - console.error('Failed to fetch n8n versions. Skipping outdated instance report...'); + this.logger.error('Failed to fetch n8n versions. Skipping outdated instance report...'); } return null; } diff --git a/packages/cli/src/services/orchestration/main/MultiMainSetup.ee.ts b/packages/cli/src/services/orchestration/main/MultiMainSetup.ee.ts index eda788ae670ce..5f74f1931bd73 100644 --- a/packages/cli/src/services/orchestration/main/MultiMainSetup.ee.ts +++ b/packages/cli/src/services/orchestration/main/MultiMainSetup.ee.ts @@ -77,7 +77,10 @@ export class MultiMainSetup extends EventEmitter { config.set('multiMainSetup.instanceType', 'follower'); - this.emit('leader-stepdown'); // lost leadership - stop triggers, pollers, pruning + /** + * Lost leadership - stop triggers, pollers, pruning, wait tracking, license renewal + */ + this.emit('leader-stepdown'); await this.tryBecomeLeader(); } @@ -97,7 +100,10 @@ export class MultiMainSetup extends EventEmitter { await this.redisPublisher.setExpiration(this.leaderKey, this.leaderKeyTtl); - this.emit('leader-takeover'); // gained leadership - start triggers, pollers, pruning, wait-tracking + /** + * Gained leadership - start triggers, pollers, pruning, wait-tracking, license renewal + */ + this.emit('leader-takeover'); } else { config.set('multiMainSetup.instanceType', 'follower'); } diff --git a/packages/cli/test/integration/credentials.test.ts b/packages/cli/test/integration/credentials.test.ts index aa40616ad8681..a25f0108cf3e7 100644 --- a/packages/cli/test/integration/credentials.test.ts +++ b/packages/cli/test/integration/credentials.test.ts @@ -398,9 +398,6 @@ describe('PATCH /credentials/:id', () => { .patch(`/credentials/${savedCredential.id}`) .send(invalidPayload); - if (response.statusCode === 500) { - console.log(response.statusCode, response.body); - } expect(response.statusCode).toBe(400); } }); diff --git a/packages/cli/test/unit/License.test.ts b/packages/cli/test/unit/License.test.ts index 354e672b0bfdb..182656f39a8f1 100644 --- a/packages/cli/test/unit/License.test.ts +++ b/packages/cli/test/unit/License.test.ts @@ -175,3 +175,81 @@ describe('License', () => { expect(mainPlan).toBeUndefined(); }); }); + +describe('License', () => { + beforeEach(() => { + config.load(config.default); + }); + + describe('init', () => { + describe('in single-main setup', () => { + describe('with `license.autoRenewEnabled` enabled', () => { + it('should enable renewal', async () => { + config.set('multiMainSetup.enabled', false); + + await new License(mock(), mock(), mock(), mock(), mock()).init(); + + expect(LicenseManager).toHaveBeenCalledWith( + expect.objectContaining({ autoRenewEnabled: true, renewOnInit: true }), + ); + }); + }); + + describe('with `license.autoRenewEnabled` disabled', () => { + it('should disable renewal', async () => { + config.set('license.autoRenewEnabled', false); + + await new License(mock(), mock(), mock(), mock(), mock()).init(); + + expect(LicenseManager).toHaveBeenCalledWith( + expect.objectContaining({ autoRenewEnabled: false, renewOnInit: false }), + ); + }); + }); + }); + + describe('in multi-main setup', () => { + describe('with `license.autoRenewEnabled` disabled', () => { + test.each(['unset', 'leader', 'follower'])( + 'if %s status, should disable removal', + async (status) => { + config.set('multiMainSetup.enabled', true); + config.set('multiMainSetup.instanceType', status); + config.set('license.autoRenewEnabled', false); + + await new License(mock(), mock(), mock(), mock(), mock()).init(); + + expect(LicenseManager).toHaveBeenCalledWith( + expect.objectContaining({ autoRenewEnabled: false, renewOnInit: false }), + ); + }, + ); + }); + + describe('with `license.autoRenewEnabled` enabled', () => { + test.each(['unset', 'follower'])('if %s status, should disable removal', async (status) => { + config.set('multiMainSetup.enabled', true); + config.set('multiMainSetup.instanceType', status); + config.set('license.autoRenewEnabled', false); + + await new License(mock(), mock(), mock(), mock(), mock()).init(); + + expect(LicenseManager).toHaveBeenCalledWith( + expect.objectContaining({ autoRenewEnabled: false, renewOnInit: false }), + ); + }); + + it('if leader status, should enable renewal', async () => { + config.set('multiMainSetup.enabled', true); + config.set('multiMainSetup.instanceType', 'leader'); + + await new License(mock(), mock(), mock(), mock(), mock()).init(); + + expect(LicenseManager).toHaveBeenCalledWith( + expect.objectContaining({ autoRenewEnabled: true, renewOnInit: true }), + ); + }); + }); + }); + }); +}); diff --git a/packages/cli/test/unit/services/redis.service.test.ts b/packages/cli/test/unit/services/redis.service.test.ts index 04fb980db6797..a3e4b1b162dd5 100644 --- a/packages/cli/test/unit/services/redis.service.test.ts +++ b/packages/cli/test/unit/services/redis.service.test.ts @@ -66,7 +66,7 @@ describe('RedisService', () => { const mockHandler = jest.fn(); mockHandler.mockImplementation((stream: string, id: string, message: string[]) => { - console.log('Received message', stream, id, message); + Container.get(Logger).info('Received message', { stream, id, message }); }); consumer.addMessageHandler('some handler', mockHandler); diff --git a/packages/editor-ui/src/Interface.ts b/packages/editor-ui/src/Interface.ts index 34227865e5cb4..f1c0a688a2b15 100644 --- a/packages/editor-ui/src/Interface.ts +++ b/packages/editor-ui/src/Interface.ts @@ -448,6 +448,7 @@ export type IPushData = | PushDataExecutionStarted | PushDataExecuteAfter | PushDataExecuteBefore + | PushDataNodeDescriptionUpdated | PushDataConsoleMessage | PushDataReloadNodeType | PushDataRemoveNodeType @@ -459,67 +460,72 @@ export type IPushData = | PushDataWorkflowFailedToActivate | PushDataWorkflowUsersChanged; -type PushDataActiveWorkflowAdded = { +export type PushDataActiveWorkflowAdded = { data: IActiveWorkflowAdded; type: 'workflowActivated'; }; -type PushDataActiveWorkflowRemoved = { +export type PushDataActiveWorkflowRemoved = { data: IActiveWorkflowRemoved; type: 'workflowDeactivated'; }; -type PushDataWorkflowFailedToActivate = { +export type PushDataWorkflowFailedToActivate = { data: IWorkflowFailedToActivate; type: 'workflowFailedToActivate'; }; -type PushDataExecutionRecovered = { +export type PushDataExecutionRecovered = { data: IPushDataExecutionRecovered; type: 'executionRecovered'; }; -type PushDataExecutionFinished = { +export type PushDataExecutionFinished = { data: IPushDataExecutionFinished; type: 'executionFinished'; }; -type PushDataExecutionStarted = { +export type PushDataExecutionStarted = { data: IPushDataExecutionStarted; type: 'executionStarted'; }; -type PushDataExecuteAfter = { +export type PushDataExecuteAfter = { data: IPushDataNodeExecuteAfter; type: 'nodeExecuteAfter'; }; -type PushDataExecuteBefore = { +export type PushDataExecuteBefore = { data: IPushDataNodeExecuteBefore; type: 'nodeExecuteBefore'; }; -type PushDataConsoleMessage = { +export type PushDataNodeDescriptionUpdated = { + data: {}; + type: 'nodeDescriptionUpdated'; +}; + +export type PushDataConsoleMessage = { data: IPushDataConsoleMessage; type: 'sendConsoleMessage'; }; -type PushDataReloadNodeType = { +export type PushDataReloadNodeType = { data: IPushDataReloadNodeType; type: 'reloadNodeType'; }; -type PushDataRemoveNodeType = { +export type PushDataRemoveNodeType = { data: IPushDataRemoveNodeType; type: 'removeNodeType'; }; -type PushDataTestWebhook = { +export type PushDataTestWebhook = { data: IPushDataTestWebhook; type: 'testWebhookDeleted' | 'testWebhookReceived'; }; -type PushDataWorkerStatusMessage = { +export type PushDataWorkerStatusMessage = { data: IPushDataWorkerStatusMessage; type: 'sendWorkerStatusMessage'; }; diff --git a/packages/editor-ui/src/components/MainHeader/CollaborationPane.vue b/packages/editor-ui/src/components/MainHeader/CollaborationPane.vue index 31d387b3dbe46..daabc0a1780bc 100644 --- a/packages/editor-ui/src/components/MainHeader/CollaborationPane.vue +++ b/packages/editor-ui/src/components/MainHeader/CollaborationPane.vue @@ -54,6 +54,7 @@ const onDocumentVisibilityChange = () => { }; onMounted(() => { + collaborationStore.initialize(); startHeartbeat(); document.addEventListener('visibilitychange', onDocumentVisibilityChange); }); @@ -61,6 +62,7 @@ onMounted(() => { onBeforeUnmount(() => { document.removeEventListener('visibilitychange', onDocumentVisibilityChange); stopHeartbeat(); + collaborationStore.terminate(); }); diff --git a/packages/editor-ui/src/components/MainHeader/MainHeader.vue b/packages/editor-ui/src/components/MainHeader/MainHeader.vue index c0e70681402a7..ed7b43715e910 100644 --- a/packages/editor-ui/src/components/MainHeader/MainHeader.vue +++ b/packages/editor-ui/src/components/MainHeader/MainHeader.vue @@ -16,9 +16,9 @@