diff --git a/apps/meteor/app/cloud/server/functions/getWorkspaceAccessToken.ts b/apps/meteor/app/cloud/server/functions/getWorkspaceAccessToken.ts index d5b92c97ef7d5f0574c6bb1739736861694fb92d..7e970edfdfc239f3c7cd2b1a656e509075e5d704 100644 --- a/apps/meteor/app/cloud/server/functions/getWorkspaceAccessToken.ts +++ b/apps/meteor/app/cloud/server/functions/getWorkspaceAccessToken.ts @@ -46,6 +46,14 @@ export class CloudWorkspaceAccessTokenError extends Error { } } +export const isAbortError = (error: unknown): error is { type: 'AbortError' } => { + if (typeof error !== 'object' || error === null) { + return false; + } + + return 'type' in error && error.type === 'AbortError'; +}; + export class CloudWorkspaceAccessTokenEmptyError extends Error { constructor() { super('Workspace access token is empty'); diff --git a/apps/meteor/app/cloud/server/functions/getWorkspaceAccessTokenWithScope.ts b/apps/meteor/app/cloud/server/functions/getWorkspaceAccessTokenWithScope.ts index 62d50630dd2485b79714ddc2add51d8c4a578874..92ff94a4b8f0e52aa612b89b246b3c9d431dc211 100644 --- a/apps/meteor/app/cloud/server/functions/getWorkspaceAccessTokenWithScope.ts +++ b/apps/meteor/app/cloud/server/functions/getWorkspaceAccessTokenWithScope.ts @@ -64,13 +64,6 @@ export async function getWorkspaceAccessTokenWithScope(scope = '', throwOnError expiresAt, }; } catch (err: any) { - SystemLogger.error({ - msg: 'Failed to get Workspace AccessToken from Rocket.Chat Cloud', - url: '/api/oauth/token', - scope, - err, - }); - if (err instanceof CloudWorkspaceAccessTokenError) { SystemLogger.error('Server has been unregistered from cloud'); void removeWorkspaceRegistrationInfo(); diff --git a/apps/meteor/app/cloud/server/functions/registerPreIntentWorkspaceWizard.ts b/apps/meteor/app/cloud/server/functions/registerPreIntentWorkspaceWizard.ts index ce415d2aa98339668848176059083ed9a36a21b6..e0865c24156acf83dbe6f34d55ff9382fa91a4f8 100644 --- a/apps/meteor/app/cloud/server/functions/registerPreIntentWorkspaceWizard.ts +++ b/apps/meteor/app/cloud/server/functions/registerPreIntentWorkspaceWizard.ts @@ -21,7 +21,7 @@ export async function registerPreIntentWorkspaceWizard(): Promise<boolean> { const response = await fetch(`${cloudUrl}/api/v2/register/workspace/pre-intent`, { method: 'POST', body: regInfo, - timeout: 10 * 1000, + timeout: 3 * 1000, }); if (!response.ok) { throw new Error((await response.json()).error); diff --git a/apps/meteor/app/cloud/server/functions/syncWorkspace/index.ts b/apps/meteor/app/cloud/server/functions/syncWorkspace/index.ts index 4dca8bc35321ab94b2918ddc2012c8100aaea34b..ccc0695b22cf813d53e654f0d71c93b44e88be84 100644 --- a/apps/meteor/app/cloud/server/functions/syncWorkspace/index.ts +++ b/apps/meteor/app/cloud/server/functions/syncWorkspace/index.ts @@ -1,11 +1,16 @@ import { CloudWorkspaceRegistrationError } from '../../../../../lib/errors/CloudWorkspaceRegistrationError'; import { SystemLogger } from '../../../../../server/lib/logger/system'; -import { CloudWorkspaceAccessTokenEmptyError, CloudWorkspaceAccessTokenError } from '../getWorkspaceAccessToken'; +import { CloudWorkspaceAccessTokenEmptyError, CloudWorkspaceAccessTokenError, isAbortError } from '../getWorkspaceAccessToken'; import { getCachedSupportedVersionsToken } from '../supportedVersionsToken/supportedVersionsToken'; import { announcementSync } from './announcementSync'; import { legacySyncWorkspace } from './legacySyncWorkspace'; import { syncCloudData } from './syncCloudData'; +/** + * Syncs the workspace with the cloud + * @returns {Promise<void>} + * @throws {Error} - If there is an unexpected error during sync like a network error + */ export async function syncWorkspace() { try { await syncCloudData(); @@ -14,14 +19,15 @@ export async function syncWorkspace() { } catch (err) { switch (true) { case err instanceof CloudWorkspaceRegistrationError: - case err instanceof CloudWorkspaceAccessTokenError: - case err instanceof CloudWorkspaceAccessTokenEmptyError: { + case err instanceof CloudWorkspaceAccessTokenError: { // There is no access token, so we can't sync SystemLogger.info('Workspace does not have a valid access token, sync aborted'); break; } default: { - SystemLogger.error({ msg: 'Error during workspace sync', err }); + if (!(err instanceof CloudWorkspaceAccessTokenEmptyError) && !isAbortError(err)) { + SystemLogger.error({ msg: 'Error during workspace sync', err }); + } SystemLogger.info({ msg: 'Falling back to legacy sync', function: 'syncCloudData', @@ -32,13 +38,14 @@ export async function syncWorkspace() { } catch (err) { switch (true) { case err instanceof CloudWorkspaceRegistrationError: - case err instanceof CloudWorkspaceAccessTokenError: - case err instanceof CloudWorkspaceAccessTokenEmptyError: { + case err instanceof CloudWorkspaceAccessTokenError: { // There is no access token, so we can't sync break; } default: { - SystemLogger.error({ msg: 'Error during fallback workspace sync', err }); + if (!(err instanceof CloudWorkspaceAccessTokenEmptyError) && !isAbortError(err)) { + SystemLogger.error({ msg: 'Error during fallback workspace sync', err }); + } throw err; } } diff --git a/apps/meteor/app/cloud/server/functions/syncWorkspace/legacySyncWorkspace.ts b/apps/meteor/app/cloud/server/functions/syncWorkspace/legacySyncWorkspace.ts index bde316fae8281e666932edceaac8a460ab49bfe9..2bff8e1526d2acc3bd52968f2b722f620c994fcc 100644 --- a/apps/meteor/app/cloud/server/functions/syncWorkspace/legacySyncWorkspace.ts +++ b/apps/meteor/app/cloud/server/functions/syncWorkspace/legacySyncWorkspace.ts @@ -5,7 +5,6 @@ import { v, compile } from 'suretype'; import { CloudWorkspaceConnectionError } from '../../../../../lib/errors/CloudWorkspaceConnectionError'; import { CloudWorkspaceRegistrationError } from '../../../../../lib/errors/CloudWorkspaceRegistrationError'; -import { SystemLogger } from '../../../../../server/lib/logger/system'; import { settings } from '../../../../settings/server'; import type { WorkspaceRegistrationData } from '../buildRegistrationData'; import { buildWorkspaceRegistrationData } from '../buildRegistrationData'; @@ -99,6 +98,7 @@ const fetchWorkspaceClientPayload = async ({ Authorization: `Bearer ${token}`, }, body: workspaceRegistrationData, + timeout: 3000, }); if (!response.ok) { @@ -145,37 +145,25 @@ const consumeWorkspaceSyncPayload = async (result: Serialized<Cloud.WorkspaceSyn /** @deprecated */ export async function legacySyncWorkspace() { - try { - const { workspaceRegistered } = await retrieveRegistrationStatus(); - if (!workspaceRegistered) { - throw new CloudWorkspaceRegistrationError('Workspace is not registered'); - } + const { workspaceRegistered } = await retrieveRegistrationStatus(); + if (!workspaceRegistered) { + throw new CloudWorkspaceRegistrationError('Workspace is not registered'); + } - const token = await getWorkspaceAccessToken(true); - if (!token) { - throw new CloudWorkspaceAccessTokenEmptyError(); - } + const token = await getWorkspaceAccessToken(true); + if (!token) { + throw new CloudWorkspaceAccessTokenEmptyError(); + } - const workspaceRegistrationData = await buildWorkspaceRegistrationData(undefined); + const workspaceRegistrationData = await buildWorkspaceRegistrationData(undefined); - const payload = await fetchWorkspaceClientPayload({ token, workspaceRegistrationData }); - - if (!payload) { - return true; - } + const payload = await fetchWorkspaceClientPayload({ token, workspaceRegistrationData }); + if (payload) { await consumeWorkspaceSyncPayload(payload); - - return true; - } catch (err) { - SystemLogger.error({ - msg: 'Failed to sync with Rocket.Chat Cloud', - url: '/client', - err, - }); - - return false; - } finally { - await getWorkspaceLicense(); } + + await getWorkspaceLicense(); + + return true; } diff --git a/apps/meteor/app/cloud/server/index.ts b/apps/meteor/app/cloud/server/index.ts index c7e783d4d5aa4acd7180398cb20e2059902a5307..6a82c16331c03f14aeba6921e817d275839f085b 100644 --- a/apps/meteor/app/cloud/server/index.ts +++ b/apps/meteor/app/cloud/server/index.ts @@ -3,7 +3,7 @@ import { Meteor } from 'meteor/meteor'; import { SystemLogger } from '../../../server/lib/logger/system'; import { connectWorkspace } from './functions/connectWorkspace'; -import { getWorkspaceAccessToken } from './functions/getWorkspaceAccessToken'; +import { CloudWorkspaceAccessTokenEmptyError, getWorkspaceAccessToken } from './functions/getWorkspaceAccessToken'; import { getWorkspaceAccessTokenWithScope } from './functions/getWorkspaceAccessTokenWithScope'; import { retrieveRegistrationStatus } from './functions/retrieveRegistrationStatus'; import { syncWorkspace } from './functions/syncWorkspace'; @@ -28,9 +28,31 @@ Meteor.startup(async () => { } } - setImmediate(() => syncWorkspace()); + setImmediate(async () => { + try { + await syncWorkspace(); + } catch (e: any) { + if (e instanceof CloudWorkspaceAccessTokenEmptyError) { + return; + } + if (e.type && e.type === 'AbortError') { + return; + } + SystemLogger.error('An error occurred syncing workspace.', e.message); + } + }); await cronJobs.add(licenseCronName, '0 */12 * * *', async () => { - await syncWorkspace(); + try { + await syncWorkspace(); + } catch (e: any) { + if (e instanceof CloudWorkspaceAccessTokenEmptyError) { + return; + } + if (e.type && e.type === 'AbortError') { + return; + } + SystemLogger.error('An error occurred syncing workspace.', e.message); + } }); }); diff --git a/apps/meteor/client/views/admin/subscription/SubscriptionCalloutLimits.tsx b/apps/meteor/client/views/admin/subscription/SubscriptionCalloutLimits.tsx index e408d5e85b7bca364be62fb155f31b4e4a21f7c4..074e349081bdbfd77b79f894441a103eb0f2fb48 100644 --- a/apps/meteor/client/views/admin/subscription/SubscriptionCalloutLimits.tsx +++ b/apps/meteor/client/views/admin/subscription/SubscriptionCalloutLimits.tsx @@ -42,6 +42,10 @@ export const SubscriptionCalloutLimits = () => { return undefined; } + if (rule.max === -1) { + return undefined; + } + return [key, rule.behavior]; }) .filter(Boolean) as Array<[keyof typeof limits, LicenseBehavior]>; diff --git a/apps/meteor/client/views/admin/subscription/SubscriptionPage.tsx b/apps/meteor/client/views/admin/subscription/SubscriptionPage.tsx index d0dd1ec0be4eecfa6ece076a0e8ef25ab03bad34..8cda1c28dc19499234b3dbdd1f5bca678c30a0b4 100644 --- a/apps/meteor/client/views/admin/subscription/SubscriptionPage.tsx +++ b/apps/meteor/client/views/admin/subscription/SubscriptionPage.tsx @@ -1,4 +1,4 @@ -import { Box, Button, ButtonGroup, Callout, Grid, Throbber } from '@rocket.chat/fuselage'; +import { Accordion, Box, Button, ButtonGroup, Callout, Grid, Throbber } from '@rocket.chat/fuselage'; import { useSessionStorage } from '@rocket.chat/fuselage-hooks'; import { useRouter } from '@rocket.chat/ui-contexts'; import { t } from 'i18next'; @@ -107,60 +107,64 @@ const SubscriptionPage = () => { <SubscriptionCalloutLimits /> {isLicenseLoading && <SubscriptionPageSkeleton />} {!isLicenseLoading && ( - <Box marginBlock='none' marginInline='auto' width='full' color='default'> - <Grid m={0}> - {showLicense && ( - <Grid.Item lg={12} xs={4} p={8}> + <> + {showLicense && ( + <Accordion> + <Accordion.Item defaultExpanded={true} title={t('License')}> <pre>{JSON.stringify(licensesData, null, 2)}</pre> + </Accordion.Item> + </Accordion> + )} + <Box marginBlock='none' marginInline='auto' width='full' color='default'> + <Grid m={0}> + <Grid.Item lg={4} xs={4} p={8}> + {license && <PlanCard licenseInformation={license.information} licenseLimits={{ activeUsers: seatsLimit }} />} + {!license && <PlanCardCommunity />} </Grid.Item> - )} - <Grid.Item lg={4} xs={4} p={8}> - {license && <PlanCard licenseInformation={license.information} licenseLimits={{ activeUsers: seatsLimit }} />} - {!license && <PlanCardCommunity />} - </Grid.Item> - <Grid.Item lg={8} xs={4} p={8}> - <FeaturesCard activeModules={activeModules} isEnterprise={isEnterprise} /> - </Grid.Item> - - {seatsLimit.value !== undefined && ( - <Grid.Item lg={6} xs={4} p={8}> - {seatsLimit.max !== Infinity ? ( - <SeatsCard value={seatsLimit.value} max={seatsLimit.max} hideManageSubscription={licensesData?.trial} /> - ) : ( - <CountSeatsCard activeUsers={seatsLimit?.value} /> - )} + <Grid.Item lg={8} xs={4} p={8}> + <FeaturesCard activeModules={activeModules} isEnterprise={isEnterprise} /> </Grid.Item> - )} - - {macLimit.value !== undefined && ( - <Grid.Item lg={6} xs={4} p={8}> - {macLimit.max !== Infinity ? ( - <MACCard max={macLimit.max} value={macLimit.value} hideManageSubscription={licensesData?.trial} /> - ) : ( - <CountMACCard macsCount={macLimit.value} /> - )} - </Grid.Item> - )} - {!license && ( - <> - {limits?.marketplaceApps !== undefined && ( - <Grid.Item lg={4} xs={4} p={8}> - <AppsUsageCard privateAppsLimit={limits?.privateApps} marketplaceAppsLimit={limits.marketplaceApps} /> - </Grid.Item> - )} - - <Grid.Item lg={4} xs={4} p={8}> - <ActiveSessionsCard /> + {seatsLimit.value !== undefined && ( + <Grid.Item lg={6} xs={4} p={8}> + {seatsLimit.max !== Infinity ? ( + <SeatsCard value={seatsLimit.value} max={seatsLimit.max} hideManageSubscription={licensesData?.trial} /> + ) : ( + <CountSeatsCard activeUsers={seatsLimit?.value} /> + )} </Grid.Item> - <Grid.Item lg={4} xs={4} p={8}> - <ActiveSessionsPeakCard /> + )} + + {macLimit.value !== undefined && ( + <Grid.Item lg={6} xs={4} p={8}> + {macLimit.max !== Infinity ? ( + <MACCard max={macLimit.max} value={macLimit.value} hideManageSubscription={licensesData?.trial} /> + ) : ( + <CountMACCard macsCount={macLimit.value} /> + )} </Grid.Item> - </> - )} - </Grid> - <UpgradeToGetMore activeModules={activeModules} isEnterprise={isEnterprise} /> - </Box> + )} + + {!license && ( + <> + {limits?.marketplaceApps !== undefined && ( + <Grid.Item lg={4} xs={4} p={8}> + <AppsUsageCard privateAppsLimit={limits?.privateApps} marketplaceAppsLimit={limits.marketplaceApps} /> + </Grid.Item> + )} + + <Grid.Item lg={4} xs={4} p={8}> + <ActiveSessionsCard /> + </Grid.Item> + <Grid.Item lg={4} xs={4} p={8}> + <ActiveSessionsPeakCard /> + </Grid.Item> + </> + )} + </Grid> + <UpgradeToGetMore activeModules={activeModules} isEnterprise={isEnterprise} /> + </Box> + </> )} </Page.ScrollableContentWithShadow> </Page> diff --git a/apps/meteor/client/views/admin/workspace/VersionCard/VersionCard.tsx b/apps/meteor/client/views/admin/workspace/VersionCard/VersionCard.tsx index 12dc3b76e162dcedd0d838dfa50dedca0632b4ce..54da2af94a98173fca3251381a23404d4064f5e5 100644 --- a/apps/meteor/client/views/admin/workspace/VersionCard/VersionCard.tsx +++ b/apps/meteor/client/views/admin/workspace/VersionCard/VersionCard.tsx @@ -8,7 +8,6 @@ import { useModal, useMediaUrl } from '@rocket.chat/ui-contexts'; import type { ReactElement, ReactNode } from 'react'; import React, { useMemo } from 'react'; import { Trans, useTranslation } from 'react-i18next'; -import semver from 'semver'; import { useFormatDate } from '../../../../hooks/useFormatDate'; import { useLicense, useLicenseName } from '../../../../hooks/useLicense'; @@ -19,7 +18,7 @@ import type { VersionActionItem } from './components/VersionCardActionItem'; import VersionCardActionItemList from './components/VersionCardActionItemList'; import { VersionCardSkeleton } from './components/VersionCardSkeleton'; import { VersionTag } from './components/VersionTag'; -import type { VersionStatus } from './components/VersionTag'; +import { getVersionStatus } from './getVersionStatus'; import RegisterWorkspaceModal from './modals/RegisterWorkspaceModal'; const SUPPORT_EXTERNAL_LINK = 'https://go.rocket.chat/i/version-support'; @@ -222,25 +221,3 @@ const decodeBase64 = (b64: string): SupportedVersions | undefined => { return JSON.parse(atob(bodyEncoded)); }; - -const getVersionStatus = ( - serverVersion: string, - versions: SupportedVersions['versions'], -): { label: VersionStatus; expiration: Date | undefined } => { - const coercedServerVersion = String(semver.coerce(serverVersion)); - const highestVersion = versions.reduce((prev, current) => (prev.version > current.version ? prev : current)); - const currentVersionData = versions.find((v) => v.version.includes(coercedServerVersion) || v.version.includes(serverVersion)); - const isSupported = currentVersionData?.version === coercedServerVersion || currentVersionData?.version === serverVersion; - - const versionStatus: { - label: VersionStatus; - expiration: Date | undefined; - } = { - label: 'outdated', - ...(semver.gte(coercedServerVersion, highestVersion.version) && { label: 'latest' }), - ...(isSupported && semver.gt(highestVersion.version, coercedServerVersion) && { label: 'available_version' }), - expiration: currentVersionData?.expiration, - }; - - return versionStatus; -}; diff --git a/apps/meteor/client/views/admin/workspace/VersionCard/getVersionStatus.spec.ts b/apps/meteor/client/views/admin/workspace/VersionCard/getVersionStatus.spec.ts new file mode 100644 index 0000000000000000000000000000000000000000..40b3c5753c226df11c65c22733aad4b93f6d64fc --- /dev/null +++ b/apps/meteor/client/views/admin/workspace/VersionCard/getVersionStatus.spec.ts @@ -0,0 +1,110 @@ +import { getVersionStatus } from './getVersionStatus'; + +describe('if the server version from server and the highest version from cloud are the same', () => { + describe('the expiration date is in the future', () => { + it('should return as latest version', () => { + const status = getVersionStatus('3.0.0', [ + { + version: '3.0.0', + expiration: new Date(new Date().setFullYear(new Date().getFullYear() + 1)), + security: false, + infoUrl: '', + }, + ]); + + expect(status.label).toBe('latest'); + }); + }); + + describe('the expiration date is in the past', () => { + it('should return as outdated version', () => { + const status = getVersionStatus('3.0.0', [ + { + version: '3.0.0', + expiration: new Date('2020-01-01'), + security: false, + infoUrl: '', + }, + ]); + + expect(status.label).toBe('outdated'); + }); + }); +}); + +describe('if the server version is not in the list of supported versions', () => { + it('should return as outdated version', () => { + const status = getVersionStatus('2.0.0', [ + { + version: '3.0.0', + expiration: new Date(), + security: false, + infoUrl: '', + }, + ]); + + expect(status.label).toBe('outdated'); + }); +}); + +describe('if the server version is in the list of supported versions but is not the highest', () => { + describe('the expiration date is in the future', () => { + it('should return as available version', () => { + const status = getVersionStatus('3.0.0', [ + { + version: '3.0.0', + expiration: new Date(new Date().setFullYear(new Date().getFullYear() + 1)), + security: false, + infoUrl: '', + }, + { + version: '4.0.0', + expiration: new Date(), + security: false, + infoUrl: '', + }, + ]); + expect(status.label).toBe('available_version'); + }); + }); + describe('the expiration date is in the past', () => { + it('should return as outdated version', () => { + const status = getVersionStatus('3.0.0', [ + { + version: '3.0.0', + expiration: new Date('2020-01-01'), + security: false, + infoUrl: '', + }, + { + version: '4.0.0', + expiration: new Date(), + security: false, + infoUrl: '', + }, + ]); + expect(status.label).toBe('outdated'); + }); + }); +}); + +describe('if the server version is not in the list of supported versions but is the highest', () => { + it('should return as latest version', () => { + const status = getVersionStatus('4.0.0', [ + { + version: '2.0.0', + expiration: new Date(), + security: false, + infoUrl: '', + }, + { + version: '3.0.0', + expiration: new Date(), + security: false, + infoUrl: '', + }, + ]); + + expect(status.label).toBe('outdated'); + }); +}); diff --git a/apps/meteor/client/views/admin/workspace/VersionCard/getVersionStatus.ts b/apps/meteor/client/views/admin/workspace/VersionCard/getVersionStatus.ts new file mode 100644 index 0000000000000000000000000000000000000000..7216fac607e06e08a7202c4d3be844a8c266e6ea --- /dev/null +++ b/apps/meteor/client/views/admin/workspace/VersionCard/getVersionStatus.ts @@ -0,0 +1,29 @@ +import type { SupportedVersions } from '@rocket.chat/server-cloud-communication'; +import semver from 'semver'; + +import type { VersionStatus } from './components/VersionTag'; + +export const getVersionStatus = ( + serverVersion: string, + versions: SupportedVersions['versions'], +): { label: VersionStatus; expiration: Date | undefined } => { + const coercedServerVersion = String(semver.coerce(serverVersion)); + const highestVersion = versions.reduce((prev, current) => (prev.version > current.version ? prev : current)); + const currentVersionData = versions.find((v) => v.version.includes(coercedServerVersion) || v.version.includes(serverVersion)); + const currentVersionIsExpired = currentVersionData?.expiration && new Date(currentVersionData.expiration) < new Date(); + + const isSupported = + !currentVersionIsExpired && (currentVersionData?.version === coercedServerVersion || currentVersionData?.version === serverVersion); + + const versionStatus: { + label: VersionStatus; + expiration: Date | undefined; + } = { + label: 'outdated', + ...(isSupported && semver.gte(coercedServerVersion, highestVersion.version) && { label: 'latest' }), + ...(isSupported && semver.gt(highestVersion.version, coercedServerVersion) && { label: 'available_version' }), + expiration: currentVersionData?.expiration, + }; + + return versionStatus; +}; diff --git a/apps/meteor/ee/app/license/server/startup.ts b/apps/meteor/ee/app/license/server/startup.ts index 1078fb092dfeb807c9cad4b8fccd9a511af1e9bd..f18048de00ab39a4ab41ede59b8381c9ed0f66f8 100644 --- a/apps/meteor/ee/app/license/server/startup.ts +++ b/apps/meteor/ee/app/license/server/startup.ts @@ -83,7 +83,11 @@ const syncByTrigger = async (contexts: string[]) => { }), ); - await syncWorkspace(); + try { + await syncWorkspace(); + } catch (error) { + console.error(error); + } }; // When settings are loaded, apply the current license if there is one. diff --git a/apps/meteor/packages/rocketchat-i18n/i18n/en.i18n.json b/apps/meteor/packages/rocketchat-i18n/i18n/en.i18n.json index 6e84aec4ab7cb6ad37d9727e75df2cbaa3fcc08c..b9a5857e21515d4d6e0a027f1affd50c69189f52 100644 --- a/apps/meteor/packages/rocketchat-i18n/i18n/en.i18n.json +++ b/apps/meteor/packages/rocketchat-i18n/i18n/en.i18n.json @@ -5958,8 +5958,8 @@ "onboarding.form.standaloneServerForm.servicesUnavailable": "Some of the services will be unavailable or will require manual setup", "onboarding.form.standaloneServerForm.publishOwnApp": "In order to send push notitications you need to compile and publish your own app to Google Play and App Store", "onboarding.form.standaloneServerForm.manuallyIntegrate": "Need to manually integrate with external services", - "subscription.callout.servicesDisruptionsMayOccur": "Services Disruptions may occur", - "subscription.callout.servicesDisruptionsOccurring": "Services Disruptions occurring", + "subscription.callout.servicesDisruptionsMayOccur": "Services disruptions may occur", + "subscription.callout.servicesDisruptionsOccurring": "Services disruptions occurring", "subscription.callout.capabilitiesDisabled": "Capabilities disabled", "subscription.callout.description.limitsExceeded_one": "Your workspace exceeded the <1>{{val}}</1> license limit. <3>Manage your subscription</3> to increase limits.", "subscription.callout.description.limitsExceeded_other": "Your workspace exceeded the <1>{{val, list}}</1> license limits. <3>Manage your subscription</3> to increase limits.", diff --git a/ee/packages/license/src/validation/logKind.ts b/ee/packages/license/src/validation/logKind.ts new file mode 100644 index 0000000000000000000000000000000000000000..99a7c9d6d4198d9e60a38f6f5a25cb683c297d90 --- /dev/null +++ b/ee/packages/license/src/validation/logKind.ts @@ -0,0 +1,11 @@ +import type { LicenseBehavior } from '../definition/LicenseBehavior'; + +export const logKind = (behavior: LicenseBehavior) => { + switch (behavior) { + case 'prevent_installation': + case 'invalidate_license': + return 'error'; + default: + return 'info'; + } +}; diff --git a/ee/packages/license/src/validation/validateLimits.ts b/ee/packages/license/src/validation/validateLimits.ts index d90f5e5f014c8330e7877569918cff8e957376b9..1cae603716b6097657aa4cf0bd55ee0516575b3e 100644 --- a/ee/packages/license/src/validation/validateLimits.ts +++ b/ee/packages/license/src/validation/validateLimits.ts @@ -6,6 +6,7 @@ import type { LicenseManager } from '../license'; import { logger } from '../logger'; import { getCurrentValueForLicenseLimit } from './getCurrentValueForLicenseLimit'; import { getResultingBehavior } from './getResultingBehavior'; +import { logKind } from './logKind'; import { validateLimit } from './validateLimit'; export async function validateLimits( @@ -30,11 +31,22 @@ export async function validateLimits( .filter(({ max, behavior }) => validateLimit(max, currentValue, behavior, extraCount)) .map((limit) => { if (!options.suppressLog) { - logger.error({ - msg: 'Limit validation failed', - kind: limitKey, - limit, - }); + switch (logKind(limit.behavior)) { + case 'error': + logger.error({ + msg: 'Limit validation failed', + kind: limitKey, + limit, + }); + break; + case 'info': + logger.info({ + msg: 'Limit validation failed', + kind: limitKey, + limit, + }); + break; + } } return getResultingBehavior(limit, { reason: 'limit', limit: limitKey }); });