diff --git a/package-lock.json b/package-lock.json index fd1534c88..4bfa82df5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@devtron-labs/devtron-fe-common-lib", - "version": "1.22.0", + "version": "1.22.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@devtron-labs/devtron-fe-common-lib", - "version": "1.22.0", + "version": "1.22.1", "hasInstallScript": true, "license": "ISC", "dependencies": { diff --git a/package.json b/package.json index 745d27b21..36ca027eb 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@devtron-labs/devtron-fe-common-lib", - "version": "1.22.0", + "version": "1.22.1", "description": "Supporting common component library", "type": "module", "main": "dist/index.js", diff --git a/src/Assets/Illustration/img-celebration.svg b/src/Assets/Illustration/img-celebration.svg new file mode 100644 index 000000000..1982add63 --- /dev/null +++ b/src/Assets/Illustration/img-celebration.svg @@ -0,0 +1,149 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/Assets/Illustration/img-install-freemium-saas.svg b/src/Assets/Illustration/img-install-freemium-saas.svg new file mode 100644 index 000000000..3ee2d147b --- /dev/null +++ b/src/Assets/Illustration/img-install-freemium-saas.svg @@ -0,0 +1,40 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Assets/Illustration/img-install-via-aws-marketplace.svg b/src/Assets/Illustration/img-install-via-aws-marketplace.svg new file mode 100644 index 000000000..e98457714 --- /dev/null +++ b/src/Assets/Illustration/img-install-via-aws-marketplace.svg @@ -0,0 +1,48 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Assets/Illustration/img-page-not-found.svg b/src/Assets/Illustration/img-page-not-found.svg new file mode 100644 index 000000000..b0de197a2 --- /dev/null +++ b/src/Assets/Illustration/img-page-not-found.svg @@ -0,0 +1,81 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Shared/Components/Illustration/Illustration.tsx b/src/Shared/Components/Illustration/Illustration.tsx index 63649ea9e..4733711f4 100644 --- a/src/Shared/Components/Illustration/Illustration.tsx +++ b/src/Shared/Components/Illustration/Illustration.tsx @@ -2,14 +2,18 @@ import CreateBackupSchedule from '@Illustrations/create-backup-schedule.webp' import CreateBackupSnapshot from '@Illustrations/create-backup-snapshot.webp' +import { ReactComponent as ImgCelebration } from '@Illustrations/img-celebration.svg' import ImgCode from '@Illustrations/img-code.webp' import ImgDevtronFreemium from '@Illustrations/img-devtron-freemium.webp' import { ReactComponent as ImgFolderEmpty } from '@Illustrations/img-folder-empty.svg' +import { ReactComponent as ImgInstallFreemiumSaas } from '@Illustrations/img-install-freemium-saas.svg' +import { ReactComponent as ImgInstallViaAwsMarketplace } from '@Illustrations/img-install-via-aws-marketplace.svg' import ImgManOnRocket from '@Illustrations/img-man-on-rocket.webp' import { ReactComponent as ImgMechanicalOperation } from '@Illustrations/img-mechanical-operation.svg' import { ReactComponent as ImgNoBackupLocation } from '@Illustrations/img-no-backup-location.svg' import { ReactComponent as ImgNoRestores } from '@Illustrations/img-no-restores.svg' import ImgNoResult from '@Illustrations/img-no-result.webp' +import { ReactComponent as ImgPageNotFound } from '@Illustrations/img-page-not-found.svg' import NoClusterCostEnabled from '@Illustrations/no-cluster-cost-enabled.webp' // eslint-disable-next-line no-restricted-imports @@ -17,10 +21,14 @@ import { IllustrationBase } from './IllustrationBase' import { IllustrationBaseProps } from './types' export const illustrationMap = { + 'img-celebration': ImgCelebration, 'img-folder-empty': ImgFolderEmpty, + 'img-install-freemium-saas': ImgInstallFreemiumSaas, + 'img-install-via-aws-marketplace': ImgInstallViaAwsMarketplace, 'img-mechanical-operation': ImgMechanicalOperation, 'img-no-backup-location': ImgNoBackupLocation, 'img-no-restores': ImgNoRestores, + 'img-page-not-found': ImgPageNotFound, 'create-backup-schedule': CreateBackupSchedule, 'create-backup-snapshot': CreateBackupSnapshot, 'img-code': ImgCode, diff --git a/src/Shared/Components/License/DevtronLicenseCard.tsx b/src/Shared/Components/License/DevtronLicenseCard.tsx index f2904251d..754cd385b 100644 --- a/src/Shared/Components/License/DevtronLicenseCard.tsx +++ b/src/Shared/Components/License/DevtronLicenseCard.tsx @@ -27,7 +27,7 @@ import { LicensingErrorCodes } from '@Shared/types' import { Button, ButtonComponentType, ButtonVariantType } from '../Button' import { Icon } from '../Icon' -import { DevtronLicenseCardProps, LicenseStatus } from './types' +import { DevtronLicenseCardProps, LicenseCardSubTextProps, LicenseStatus } from './types' import { getLicenseColorsAccordingToStatus } from './utils' import './licenseCard.scss' @@ -49,10 +49,13 @@ const LicenseCardSubText = ({ isFreemium, licenseStatus, licenseStatusError, -}: Pick) => { - if (isFreemium) { - const freemiumLimitReached = licenseStatusError?.code === LicensingErrorCodes.ClusterLimitExceeded + isFreeForever, +}: LicenseCardSubTextProps) => { + const freemiumLimitReached = isFreemium && licenseStatusError?.code === LicensingErrorCodes.ClusterLimitExceeded + const showFreemiumMessage = + isFreeForever || freemiumLimitReached || (isFreemium && licenseStatus === LicenseStatus.ACTIVE) + if (showFreemiumMessage) { return (
@@ -130,10 +133,17 @@ export const DevtronLicenseCard = ({ appTheme, handleCopySuccess, licenseStatusError, + isSaasInstance, }: DevtronLicenseCardProps) => { - const { bgColor, textColor } = getLicenseColorsAccordingToStatus({ isFreemium, licenseStatus, licenseStatusError }) - const remainingTime = getTTLInHumanReadableFormat(ttl) - const remainingTimeString = ttl < 0 ? `Expired ${remainingTime} ago` : `${remainingTime} remaining` + const isFreeForever = isFreemium && !isSaasInstance + + const { bgColor, textColor } = getLicenseColorsAccordingToStatus({ + isFreemium, + licenseStatus, + licenseStatusError, + isSaasInstance, + }) + const isThemeDark = appTheme === AppThemeType.dark const cardRef = useRef(null) @@ -178,6 +188,15 @@ export const DevtronLicenseCard = ({ ? useMotionTemplate`linear-gradient(55deg, transparent, rgba(122, 127, 131, ${sheenOpacity}) ${sheenPosition}%, transparent)` : useMotionTemplate`linear-gradient(55deg, transparent, rgba(255, 255, 255, ${sheenOpacity}) ${sheenPosition}%, transparent)` + const getRemainingTimeString = () => { + if (isFreeForever) { + return null + } + + const remainingTime = getTTLInHumanReadableFormat(ttl) + return ttl < 0 ? `Expired ${remainingTime} ago` : `${remainingTime} remaining` + } + return (
@@ -217,12 +236,12 @@ export const DevtronLicenseCard = ({
- {isFreemium ? 'VALID FOREVER' : expiryDate} + {isFreeForever ? 'VALID FOREVER' : expiryDate} - {!isFreemium && ( + {!isFreeForever && ( <> ยท - {remainingTimeString} + {getRemainingTimeString()} )}
@@ -239,6 +258,7 @@ export const DevtronLicenseCard = ({ isFreemium={isFreemium} licenseStatusError={licenseStatusError} licenseStatus={licenseStatus} + isFreeForever={isFreeForever} />
) diff --git a/src/Shared/Components/License/index.tsx b/src/Shared/Components/License/index.tsx index 75fa48db5..52c765f60 100644 --- a/src/Shared/Components/License/index.tsx +++ b/src/Shared/Components/License/index.tsx @@ -17,5 +17,6 @@ export { default as ActivateLicenseDialog } from './ActivateLicenseDialog' export { default as DevtronLicenseCard } from './DevtronLicenseCard' export { ICDevtronWithBorder, default as InstallationFingerprintInfo } from './License.components' +export { activateLicense } from './services' export * from './types' export { parseDevtronLicenseData, parseDevtronLicenseDTOIntoLicenseCardData } from './utils' diff --git a/src/Shared/Components/License/types.ts b/src/Shared/Components/License/types.ts index 38b2be000..4c2d639ae 100644 --- a/src/Shared/Components/License/types.ts +++ b/src/Shared/Components/License/types.ts @@ -32,6 +32,7 @@ export type DevtronLicenseCardProps = { isFreemium: boolean appTheme: AppThemeType licenseStatusError: LicenseErrorStruct + isSaasInstance: boolean } & ( | { licenseKey: string @@ -45,6 +46,11 @@ export type DevtronLicenseCardProps = { } ) +export interface LicenseCardSubTextProps + extends Pick { + isFreeForever: boolean +} + export type DevtronLicenseInfo = Omit & Pick diff --git a/src/Shared/Components/License/utils.tsx b/src/Shared/Components/License/utils.tsx index 540d14613..423eb9648 100644 --- a/src/Shared/Components/License/utils.tsx +++ b/src/Shared/Components/License/utils.tsx @@ -27,16 +27,22 @@ export const getLicenseColorsAccordingToStatus = ({ isFreemium, licenseStatus, licenseStatusError, -}: Pick): { + isSaasInstance, +}: Pick): { bgColor: string textColor: string } => { if (isFreemium) { const freemiumLimitReached = licenseStatusError?.code === LicensingErrorCodes.ClusterLimitExceeded - return freemiumLimitReached - ? { bgColor: 'var(--R100)', textColor: 'var(--R500)' } - : { bgColor: 'var(--G100)', textColor: 'var(--G500)' } + if (freemiumLimitReached) { + return { bgColor: 'var(--R100)', textColor: 'var(--R500)' } + } + + if (!isSaasInstance) { + return { bgColor: 'var(--G100)', textColor: 'var(--G500)' } + } } + switch (licenseStatus) { case LicenseStatus.ACTIVE: return { bgColor: 'var(--G100)', textColor: 'var(--G500)' } @@ -68,16 +74,24 @@ export const parseDevtronLicenseDTOIntoLicenseCardData = => { const { isTrial, - expiry, - ttl, + expiry: onPremExpiry, + ttl: onPremTTL, reminderThreshold, organisationMetadata, license, claimedByUserDetails, isFreemium, licenseStatusError, + isSaasInstance, + timeElapsedSinceCreation, + creationTime, } = licenseDTO || {} + // In case of Saas expiry date is 30 days from creation time + const expiry = isSaasInstance && creationTime ? moment(creationTime).add(30, 'days').toISOString() : onPremExpiry + // For TTL will use timeElapsedSinceCreation to calculate remaining time for Saas license with 30 days validity, since browser time may differ from server time + const ttl = isSaasInstance && timeElapsedSinceCreation ? 30 * 24 * 60 * 60 - timeElapsedSinceCreation : onPremTTL + return { enterpriseName: organisationMetadata?.name || 'Devtron Enterprise', expiryDate: expiry ? moment(expiry).format(DATE_TIME_FORMATS['DD/MM/YYYY']) : '', @@ -86,6 +100,7 @@ export const parseDevtronLicenseDTOIntoLicenseCardData = = { [RemoteConnectionType.Direct]: 'Direct Connection', diff --git a/src/Shared/types.ts b/src/Shared/types.ts index 76a6859f3..dec0e3820 100644 --- a/src/Shared/types.ts +++ b/src/Shared/types.ts @@ -1106,18 +1106,10 @@ export interface LicenseErrorStruct { userMessage: string } -export interface DevtronLicenseBaseDTO { +export type DevtronLicenseBaseDTO = { fingerprint: string | null isTrial: boolean | null isFreemium: boolean | null - /** - * In timestamp format - */ - expiry: string | null - /** - * Can be negative, depicts time left in seconds for license to expire - */ - ttl: number | null /** * Show a reminder after these many DAYS left for license to expire, i.e, * Show if `ttl` is less than `reminderThreshold` [converted to seconds] @@ -1128,7 +1120,31 @@ export interface DevtronLicenseBaseDTO { domain: string | null } | null license: string | null -} +} & ( + | { + isSaasInstance: true + /** + * In seconds + */ + timeElapsedSinceCreation: number + creationTime: string + ttl?: never + expiry?: never + } + | { + isSaasInstance?: false + timeElapsedSinceCreation?: never + creationTime?: never + /** + * Can be negative, depicts time left in seconds for license to expire + */ + ttl: number | null + /** + * In timestamp format + */ + expiry: string | null + } +) export type DevtronLicenseDTO = DevtronLicenseBaseDTO & (isCentralDashboard extends true @@ -1141,9 +1157,14 @@ export type DevtronLicenseDTO = Devt showLicenseData?: never licenseStatusError?: never moduleLimits?: never + instanceData: { + devtronUrl: string + devtronPassword: string + } | null } : { claimedByUserDetails?: never + instanceData?: never showLicenseData: boolean licenseStatusError?: LicenseErrorStruct moduleLimits: {