From b5ee4b65ce69ffcbcf847b50253bb892f1e69b5f Mon Sep 17 00:00:00 2001 From: Jan Vorcak Date: Fri, 29 Nov 2024 15:24:46 +0100 Subject: [PATCH] Formatted code: --- frontend/src/components/RequireAuth.tsx | 2 +- .../license/FeatureLicenseNotification.tsx | 244 +++++++++------- .../license/LicenseNotification.tsx | 66 +++-- .../license/OverviewLicenseNotification.tsx | 269 +++++++++++------- .../components/license/licenseUtils.spec.ts | 62 ++-- .../src/components/license/licenseUtils.tsx | 117 +++++--- .../src/components/pages/acls/Acl.List.tsx | 2 +- .../pages/admin/LicenseExpiredPage.tsx | 82 +++--- .../components/pages/overview/Overview.tsx | 35 ++- .../ReassignPartitions.tsx | 18 +- frontend/src/components/routes.tsx | 3 +- frontend/src/config.ts | 43 ++- frontend/src/state/backendApi.ts | 53 ++-- 13 files changed, 561 insertions(+), 435 deletions(-) diff --git a/frontend/src/components/RequireAuth.tsx b/frontend/src/components/RequireAuth.tsx index 40489b90e..88e74a8c5 100644 --- a/frontend/src/components/RequireAuth.tsx +++ b/frontend/src/components/RequireAuth.tsx @@ -92,7 +92,7 @@ export default class RequireAuth extends Component<{ children: ReactNode }> { }, }; } else if (r.status === 403) { - void handleExpiredLicenseError(r) + void handleExpiredLicenseError(r); } }); diff --git a/frontend/src/components/license/FeatureLicenseNotification.tsx b/frontend/src/components/license/FeatureLicenseNotification.tsx index ace52a9ce..23529bc84 100644 --- a/frontend/src/components/license/FeatureLicenseNotification.tsx +++ b/frontend/src/components/license/FeatureLicenseNotification.tsx @@ -1,132 +1,174 @@ import { Alert, AlertDescription, AlertIcon, Box, Button, Flex, Link, Text } from '@redpanda-data/ui'; import { observer } from 'mobx-react'; import { FC, ReactElement, useEffect } from 'react'; -import { License, License_Type, ListEnterpriseFeaturesResponse_Feature } from '../../protogen/redpanda/api/console/v1alpha1/license_pb'; -import { getEnterpriseCTALink, getMillisecondsToExpiration, getPrettyExpirationDate, getPrettyTimeToExpiration, LICENSE_WEIGHT, MS_IN_DAY, coreHasEnterpriseFeatures, UploadLicenseButton, UpgradeButton } from './licenseUtils'; +import { + License, + License_Type, + ListEnterpriseFeaturesResponse_Feature, +} from '../../protogen/redpanda/api/console/v1alpha1/license_pb'; +import { + getEnterpriseCTALink, + getMillisecondsToExpiration, + getPrettyExpirationDate, + getPrettyTimeToExpiration, + LICENSE_WEIGHT, + MS_IN_DAY, + coreHasEnterpriseFeatures, + UploadLicenseButton, + UpgradeButton, +} from './licenseUtils'; import { api } from '../../state/backendApi'; -const getLicenseAlertContentForFeature = (featureName: 'rbac' | 'reassignPartitions', license: License | undefined, enterpriseFeaturesUsed: ListEnterpriseFeaturesResponse_Feature[]): { message: ReactElement, status: 'warning' | 'info' } | null => { - if (license === undefined || license.type !== License_Type.TRIAL) { - return null - } +const getLicenseAlertContentForFeature = ( + featureName: 'rbac' | 'reassignPartitions', + license: License | undefined, + enterpriseFeaturesUsed: ListEnterpriseFeaturesResponse_Feature[], +): { message: ReactElement; status: 'warning' | 'info' } | null => { + if (license === undefined || license.type !== License_Type.TRIAL) { + return null; + } - const msToExpiration = getMillisecondsToExpiration(license); + const msToExpiration = getMillisecondsToExpiration(license); - // Redpanda - if (api.isRedpanda) { - if (msToExpiration > 15 * MS_IN_DAY && msToExpiration < 30 * MS_IN_DAY && coreHasEnterpriseFeatures(enterpriseFeaturesUsed)) { - return { - message: - This is an enterprise feature, active until {getPrettyExpirationDate(license)}. - - - - - , - status: 'info', - } - } else if (msToExpiration > 0 && msToExpiration < 15 * MS_IN_DAY && coreHasEnterpriseFeatures(enterpriseFeaturesUsed)) { - return { - message: - - Your Redpanda Enterprise trial is expiring in {getPrettyTimeToExpiration(license)}; at that point, your enterprise features will become unavailable. To get a full Redpanda Enterprise license, contact us. - - - - - - , - status: 'warning' - }; - } - } else { - // Kafka - if (msToExpiration > 15 * MS_IN_DAY && msToExpiration < 30 * MS_IN_DAY) { - if (license.type === License_Type.TRIAL) { - return { - message: - - - This is an enterprise feature. Your trial is active until {getPrettyExpirationDate(license)} - - - - - - , - status: 'info' - }; - } else { - return { - message: - - This is a Redpanda Enterprise feature. Try it with our Redpanda Enterprise Trial. - - , - status: 'info' - }; - } - } else if (msToExpiration > 0 && msToExpiration < 15 * MS_IN_DAY && license.type === License_Type.TRIAL) { - return { - message: - - Your Redpanda Enterprise trial is expiring in {getPrettyTimeToExpiration(license)}; at that point, your enterprise features will become unavailable. To get a full Redpanda Enterprise license, contact us. - - - - - - , - status: 'warning' - }; - } + // Redpanda + if (api.isRedpanda) { + if ( + msToExpiration > 15 * MS_IN_DAY && + msToExpiration < 30 * MS_IN_DAY && + coreHasEnterpriseFeatures(enterpriseFeaturesUsed) + ) { + return { + message: ( + + This is an enterprise feature, active until {getPrettyExpirationDate(license)}. + + + + + + ), + status: 'info', + }; + } else if ( + msToExpiration > 0 && + msToExpiration < 15 * MS_IN_DAY && + coreHasEnterpriseFeatures(enterpriseFeaturesUsed) + ) { + return { + message: ( + + + Your Redpanda Enterprise trial is expiring in {getPrettyTimeToExpiration(license)}; at that point, your + enterprise features will become unavailable. To get a full Redpanda Enterprise license,{' '} + + contact us + + . + + + + + + + ), + status: 'warning', + }; } + } else { + // Kafka + if (msToExpiration > 15 * MS_IN_DAY && msToExpiration < 30 * MS_IN_DAY) { + if (license.type === License_Type.TRIAL) { + return { + message: ( + + This is an enterprise feature. Your trial is active until {getPrettyExpirationDate(license)} + + + + + + ), + status: 'info', + }; + } else { + return { + message: ( + + + This is a Redpanda Enterprise feature. Try it with our{' '} + + Redpanda Enterprise Trial + + . + + + ), + status: 'info', + }; + } + } else if (msToExpiration > 0 && msToExpiration < 15 * MS_IN_DAY && license.type === License_Type.TRIAL) { + return { + message: ( + + + Your Redpanda Enterprise trial is expiring in {getPrettyTimeToExpiration(license)}; at that point, your + enterprise features will become unavailable. To get a full Redpanda Enterprise license,{' '} + + contact us + + . + + + + + + + ), + status: 'warning', + }; + } + } - return null; + return null; }; -export const FeatureLicenseNotification: FC<{ featureName: 'reassignPartitions' | 'rbac' }> = observer(({ featureName}) => { +export const FeatureLicenseNotification: FC<{ featureName: 'reassignPartitions' | 'rbac' }> = observer( + ({ featureName }) => { useEffect(() => { - void api.refreshClusterOverview() - void api.listLicenses(); + void api.refreshClusterOverview(); + void api.listLicenses(); }, []); - const license = api - .licenses - .filter(license => license.type === License_Type.TRIAL || license.type === License_Type.COMMUNITY) - .sort((a, b) => LICENSE_WEIGHT[a.type] - LICENSE_WEIGHT[b.type]) // Sort by priority - .first(); + const license = api.licenses + .filter((license) => license.type === License_Type.TRIAL || license.type === License_Type.COMMUNITY) + .sort((a, b) => LICENSE_WEIGHT[a.type] - LICENSE_WEIGHT[b.type]) // Sort by priority + .first(); - const enterpriseFeaturesUsed = api.enterpriseFeaturesUsed + const enterpriseFeaturesUsed = api.enterpriseFeaturesUsed; const alertContent = getLicenseAlertContentForFeature(featureName, license, enterpriseFeaturesUsed); // This component needs info about whether we're using Redpanda or Kafka, without fetching clusterOverview first, we might get a malformed result if (api.clusterOverview === null) { - return null + return null; } if (!license) { - return null; + return null; } if (alertContent === null) { - return null; + return null; } const { message, status } = alertContent; return ( - - - - - {message} - - - + + + + {message} + + ); -}); + }, +); diff --git a/frontend/src/components/license/LicenseNotification.tsx b/frontend/src/components/license/LicenseNotification.tsx index bfa569cd2..87deea4c5 100644 --- a/frontend/src/components/license/LicenseNotification.tsx +++ b/frontend/src/components/license/LicenseNotification.tsx @@ -12,26 +12,24 @@ import { } from './licenseUtils'; export const LicenseNotification = observer(() => { - const location = useLocation(); + const location = useLocation(); - // This Global License Notification banner is used only for Enterprise licenses - // Trial Licences are handled by OverviewLicenseNotification and FeatureLicenseNotification. - // Community Licenses can't expire at all. - const enterpriseLicenses = api.licenses.filter(license => license.type === License_Type.ENTERPRISE) + // This Global License Notification banner is used only for Enterprise licenses + // Trial Licences are handled by OverviewLicenseNotification and FeatureLicenseNotification. + // Community Licenses can't expire at all. + const enterpriseLicenses = api.licenses.filter((license) => license.type === License_Type.ENTERPRISE); - const visibleExpiredEnterpriseLicenses = enterpriseLicenses.filter(licenseIsExpired) ?? []; - const soonToExpireLicenses = enterpriseLicenses - .filter(license => licenseSoonToExpire(license)) - ?? []; + const visibleExpiredEnterpriseLicenses = enterpriseLicenses.filter(licenseIsExpired) ?? []; + const soonToExpireLicenses = enterpriseLicenses.filter((license) => licenseSoonToExpire(license)) ?? []; - const showSomeLicenseExpirationInfo = visibleExpiredEnterpriseLicenses.length || soonToExpireLicenses.length; + const showSomeLicenseExpirationInfo = visibleExpiredEnterpriseLicenses.length || soonToExpireLicenses.length; if (api.licensesLoaded === undefined) { return null; } // For these paths, we don't need to show a notification banner because the pages themselves handle license management - if (location.pathname === '/admin/upload-license' || location.pathname === '/trial-expired') { + if (location.pathname === '/admin/upload-license' || location.pathname === '/trial-expired') { return null; } @@ -41,20 +39,20 @@ export const LicenseNotification = observer(() => { const activeEnterpriseFeatures = api.enterpriseFeaturesUsed.filter((x) => x.enabled); - const visibleSoonToExpireLicenses = soonToExpireLicenses.length > 1 && new Set(soonToExpireLicenses.map(x => x.expiresAt)).size === 1 ? - soonToExpireLicenses.filter(x => x.source === License_Source.REDPANDA_CORE) : soonToExpireLicenses + const visibleSoonToExpireLicenses = + soonToExpireLicenses.length > 1 && new Set(soonToExpireLicenses.map((x) => x.expiresAt)).size === 1 + ? soonToExpireLicenses.filter((x) => x.source === License_Source.REDPANDA_CORE) + : soonToExpireLicenses; - const visibleExpiredLicenses = visibleExpiredEnterpriseLicenses.length > 1 && new Set(visibleExpiredEnterpriseLicenses.map(x => x.expiresAt)).size === 1 ? - visibleExpiredEnterpriseLicenses.filter(x => x.source === License_Source.REDPANDA_CORE) : visibleExpiredEnterpriseLicenses + const visibleExpiredLicenses = + visibleExpiredEnterpriseLicenses.length > 1 && + new Set(visibleExpiredEnterpriseLicenses.map((x) => x.expiresAt)).size === 1 + ? visibleExpiredEnterpriseLicenses.filter((x) => x.source === License_Source.REDPANDA_CORE) + : visibleExpiredEnterpriseLicenses; - - - - return ( - - + return ( + + {visibleSoonToExpireLicenses.length > 0 && ( @@ -87,12 +85,18 @@ export const LicenseNotification = observer(() => { )} - - {api.isAdminApiConfigured && } - - - - - - ); + + {api.isAdminApiConfigured && ( + + )} + + + + + + ); }); diff --git a/frontend/src/components/license/OverviewLicenseNotification.tsx b/frontend/src/components/license/OverviewLicenseNotification.tsx index 6dcecfe09..018280f96 100644 --- a/frontend/src/components/license/OverviewLicenseNotification.tsx +++ b/frontend/src/components/license/OverviewLicenseNotification.tsx @@ -1,127 +1,180 @@ import { Alert, AlertDescription, AlertIcon, Box, Flex, Link, Text } from '@redpanda-data/ui'; import { observer } from 'mobx-react'; import { FC, ReactElement, useEffect } from 'react'; -import { License, License_Type, ListEnterpriseFeaturesResponse_Feature } from '../../protogen/redpanda/api/console/v1alpha1/license_pb'; -import { consoleHasEnterpriseFeature, DISABLE_SSO_DOCS_LINK, getEnterpriseCTALink, getMillisecondsToExpiration, getPrettyTimeToExpiration, MS_IN_DAY, UpgradeButton, UploadLicenseButton } from './licenseUtils'; +import { + License, + License_Type, + ListEnterpriseFeaturesResponse_Feature, +} from '../../protogen/redpanda/api/console/v1alpha1/license_pb'; +import { + consoleHasEnterpriseFeature, + DISABLE_SSO_DOCS_LINK, + getEnterpriseCTALink, + getMillisecondsToExpiration, + getPrettyTimeToExpiration, + MS_IN_DAY, + UpgradeButton, + UploadLicenseButton, +} from './licenseUtils'; import { api } from '../../state/backendApi'; -const getLicenseAlertContent = (license: License | undefined, enterpriseFeaturesUsed: ListEnterpriseFeaturesResponse_Feature[]): { message: ReactElement, status: 'warning' | 'info' } | null => { - if (license === undefined || license.type !== License_Type.TRIAL) { - return null - } +const getLicenseAlertContent = ( + license: License | undefined, + enterpriseFeaturesUsed: ListEnterpriseFeaturesResponse_Feature[], +): { message: ReactElement; status: 'warning' | 'info' } | null => { + if (license === undefined || license.type !== License_Type.TRIAL) { + return null; + } - const msToExpiration = getMillisecondsToExpiration(license); + const msToExpiration = getMillisecondsToExpiration(license); - // Redpanda - if (api.isRedpanda) { - if (msToExpiration > 15 * MS_IN_DAY && msToExpiration < 30 * MS_IN_DAY) { - return { - message: Your Redpanda Enterprise trial will expire in {getPrettyTimeToExpiration(license)}. Request a full license., - status: 'info', - } - } else if (msToExpiration > 0 && msToExpiration < 15 * MS_IN_DAY) { - if (consoleHasEnterpriseFeature('SINGLE_SIGN_ON')) { - return { - message: - - Your Redpanda Enterprise trial is expiring in {getPrettyTimeToExpiration(license)} and Console SSO/RBAC is enabled. As a result, Console will be inaccessible after license expiry. To prevent this, disable SSO and RBAC, or get a full Redpanda Enterprise license. - - - - - - , - status: 'warning' - }; - } else { - return { - message: - - Your Redpanda Enterprise trial is expiring in {getPrettyTimeToExpiration(license)}; at that point, your enterprise features will become unavailable. To get a full Redpanda Enterprise license, contact us. - - - - - - , - status: 'warning' - }; - } - } - } else { - // Kafka - if (msToExpiration > 0 && msToExpiration < 15 * MS_IN_DAY) { - if (consoleHasEnterpriseFeature('SINGLE_SIGN_ON')) { - return { - message: - - Your Redpanda Enterprise trial is expiring in {getPrettyTimeToExpiration(license)} and Console SSO/RBAC is enabled. As a result, Console will be inaccessible after license expiry. To prevent this, disable SSO and RBAC, or get a full Redpanda Enterprise license. - - - - - - , - status: 'warning' - }; - } else { - return { - message: - - - Your Redpanda Enterprise trial is expiring in {getPrettyTimeToExpiration(license)}; at that point, your enterprise features will become unavailable. To get a full Redpanda Enterprise license, contact us. - - - - - - , - status: 'warning' - }; - } - } + // Redpanda + if (api.isRedpanda) { + if (msToExpiration > 15 * MS_IN_DAY && msToExpiration < 30 * MS_IN_DAY) { + return { + message: ( + + Your Redpanda Enterprise trial will expire in {getPrettyTimeToExpiration(license)}.{' '} + + Request a full license + + . + + ), + status: 'info', + }; + } else if (msToExpiration > 0 && msToExpiration < 15 * MS_IN_DAY) { + if (consoleHasEnterpriseFeature('SINGLE_SIGN_ON')) { + return { + message: ( + + + Your Redpanda Enterprise trial is expiring in {getPrettyTimeToExpiration(license)} and Console SSO/RBAC + is enabled. As a result, Console will be inaccessible after license expiry. To prevent this,{' '} + + disable + {' '} + SSO and RBAC, or get a{' '} + + full Redpanda Enterprise license + + . + + + + + + + ), + status: 'warning', + }; + } else { + return { + message: ( + + + Your Redpanda Enterprise trial is expiring in {getPrettyTimeToExpiration(license)}; at that point, your + enterprise features will become unavailable. To get a full Redpanda Enterprise license,{' '} + + contact us + + . + + + + + + + ), + status: 'warning', + }; + } + } + } else { + // Kafka + if (msToExpiration > 0 && msToExpiration < 15 * MS_IN_DAY) { + if (consoleHasEnterpriseFeature('SINGLE_SIGN_ON')) { + return { + message: ( + + + Your Redpanda Enterprise trial is expiring in {getPrettyTimeToExpiration(license)} and Console SSO/RBAC + is enabled. As a result, Console will be inaccessible after license expiry. To prevent this,{' '} + + disable + {' '} + SSO and RBAC, or get a{' '} + + full Redpanda Enterprise license + + . + + + + + + + ), + status: 'warning', + }; + } else { + return { + message: ( + + + Your Redpanda Enterprise trial is expiring in {getPrettyTimeToExpiration(license)}; at that point, your + enterprise features will become unavailable. To get a full Redpanda Enterprise license,{' '} + + contact us + + . + + + + + + + ), + status: 'warning', + }; + } } + } - return null; + return null; }; export const OverviewLicenseNotification: FC = observer(() => { - useEffect(() => { - void api.refreshClusterOverview() - void api.listLicenses(); - }, []); + useEffect(() => { + void api.refreshClusterOverview(); + void api.listLicenses(); + }, []); - const license = api.licenses.filter(license => license.type === License_Type.TRIAL).first(); - const enterpriseFeaturesUsed = api.enterpriseFeaturesUsed - const alertContent = getLicenseAlertContent(license, enterpriseFeaturesUsed); + const license = api.licenses.filter((license) => license.type === License_Type.TRIAL).first(); + const enterpriseFeaturesUsed = api.enterpriseFeaturesUsed; + const alertContent = getLicenseAlertContent(license, enterpriseFeaturesUsed); - // This component needs info about whether we're using Redpanda or Kafka, without fetching clusterOverview first, we might get a malformed result - if (api.clusterOverview === null) { - return null - } + // This component needs info about whether we're using Redpanda or Kafka, without fetching clusterOverview first, we might get a malformed result + if (api.clusterOverview === null) { + return null; + } - if (!license) { - return null; - } + if (!license) { + return null; + } - if (alertContent === null) { - return null; - } + if (alertContent === null) { + return null; + } - const { message, status } = alertContent; + const { message, status } = alertContent; - return ( - - - - - {message} - - - - ); + return ( + + + + {message} + + + ); }); diff --git a/frontend/src/components/license/licenseUtils.spec.ts b/frontend/src/components/license/licenseUtils.spec.ts index 68119a00d..66a3c8ef8 100644 --- a/frontend/src/components/license/licenseUtils.spec.ts +++ b/frontend/src/components/license/licenseUtils.spec.ts @@ -6,7 +6,7 @@ import { licensesToSimplifiedPreview, prettyExpirationDate, prettyLicenseType, - resolveEnterpriseCTALink + resolveEnterpriseCTALink, } from './licenseUtils'; import '../../utils/arrayExtensions'; @@ -155,35 +155,35 @@ describe('licenseUtils', () => { }); }); - describe('resolveEnterpriseCTALink', () => { - it('should return the correct URL for tryEnterprise with query parameters', () => { - const result = resolveEnterpriseCTALink('tryEnterprise', '12345-uuid', true); - expect(result).toBe('https://redpanda.com/try-enterprise?cluster_uuid=12345-uuid&platform=1'); - }); - - it('should return the correct URL for upgrade with query parameters', () => { - const result = resolveEnterpriseCTALink('upgrade', '67890-uuid', false); - expect(result).toBe('https://redpanda.com/upgrade?cluster_uuid=67890-uuid&platform=2'); - }); - - it('should encode special characters in query parameters', () => { - const result = resolveEnterpriseCTALink('tryEnterprise', '12345&uuid', true); - expect(result).toBe('https://redpanda.com/try-enterprise?cluster_uuid=12345%26uuid&platform=1'); - }); - - it('should throw an error for an invalid EnterpriseLinkType', () => { - // @ts-expect-error Testing invalid input - expect(() => resolveEnterpriseCTALink('invalidType', '12345-uuid', true)).toThrow(); - }); - - it('should handle redpanda platform correctly', () => { - const result = resolveEnterpriseCTALink('tryEnterprise', '12345-uuid', true); - expect(result).toContain('platform=1'); - }); - - it('should handle kafka platform correctly', () => { - const result = resolveEnterpriseCTALink('upgrade', '12345-uuid', false); - expect(result).toContain('platform=2'); - }); + describe('resolveEnterpriseCTALink', () => { + it('should return the correct URL for tryEnterprise with query parameters', () => { + const result = resolveEnterpriseCTALink('tryEnterprise', '12345-uuid', true); + expect(result).toBe('https://redpanda.com/try-enterprise?cluster_uuid=12345-uuid&platform=1'); }); + + it('should return the correct URL for upgrade with query parameters', () => { + const result = resolveEnterpriseCTALink('upgrade', '67890-uuid', false); + expect(result).toBe('https://redpanda.com/upgrade?cluster_uuid=67890-uuid&platform=2'); + }); + + it('should encode special characters in query parameters', () => { + const result = resolveEnterpriseCTALink('tryEnterprise', '12345&uuid', true); + expect(result).toBe('https://redpanda.com/try-enterprise?cluster_uuid=12345%26uuid&platform=1'); + }); + + it('should throw an error for an invalid EnterpriseLinkType', () => { + // @ts-expect-error Testing invalid input + expect(() => resolveEnterpriseCTALink('invalidType', '12345-uuid', true)).toThrow(); + }); + + it('should handle redpanda platform correctly', () => { + const result = resolveEnterpriseCTALink('tryEnterprise', '12345-uuid', true); + expect(result).toContain('platform=1'); + }); + + it('should handle kafka platform correctly', () => { + const result = resolveEnterpriseCTALink('upgrade', '12345-uuid', false); + expect(result).toContain('platform=2'); + }); + }); }); diff --git a/frontend/src/components/license/licenseUtils.tsx b/frontend/src/components/license/licenseUtils.tsx index 1cb488631..625f04257 100644 --- a/frontend/src/components/license/licenseUtils.tsx +++ b/frontend/src/components/license/licenseUtils.tsx @@ -1,4 +1,9 @@ -import { type License, License_Source, License_Type, ListEnterpriseFeaturesResponse_Feature } from '../../protogen/redpanda/api/console/v1alpha1/license_pb'; +import { + type License, + License_Source, + License_Type, + ListEnterpriseFeaturesResponse_Feature, +} from '../../protogen/redpanda/api/console/v1alpha1/license_pb'; import { prettyMilliseconds } from '../../utils/utils'; import { api } from '../../state/backendApi'; import { AppFeatures } from '../../utils/env'; @@ -6,28 +11,29 @@ import { Button, Link } from '@redpanda-data/ui'; import { Link as ReactRouterLink } from 'react-router-dom'; enum Platform { - PLATFORM_UNSPECIFIED = 0, - PLATFORM_REDPANDA = 1, - PLATFORM_NON_REDPANDA = 2, + PLATFORM_UNSPECIFIED = 0, + PLATFORM_REDPANDA = 1, + PLATFORM_NON_REDPANDA = 2, } export const MS_IN_DAY = 24 * 60 * 60 * 1000; export const LICENSE_WEIGHT: Record = { - [License_Type.UNSPECIFIED]: -1, - [License_Type.COMMUNITY]: 1, - [License_Type.TRIAL]: 2, - [License_Type.ENTERPRISE]: 3 + [License_Type.UNSPECIFIED]: -1, + [License_Type.COMMUNITY]: 1, + [License_Type.TRIAL]: 2, + [License_Type.ENTERPRISE]: 3, }; const isAuthEnterpriseFeature = (feature: ListEnterpriseFeaturesResponse_Feature) => - feature.name === 'sso' || feature.name === 'rbac' + feature.name === 'sso' || feature.name === 'rbac'; -export const isEnterpriseFeatureUsed = (featureName: string, features: ListEnterpriseFeaturesResponse_Feature[]): boolean => { - return features.some(feature => - feature.enabled && feature.name === featureName - ); -} +export const isEnterpriseFeatureUsed = ( + featureName: string, + features: ListEnterpriseFeaturesResponse_Feature[], +): boolean => { + return features.some((feature) => feature.enabled && feature.name === featureName); +}; /** * Checks if a list of enterprise features includes enabled features for authentication, @@ -36,12 +42,12 @@ export const isEnterpriseFeatureUsed = (featureName: string, features: ListEnter * @returns {boolean} - Returns `true` if an enabled feature with name 'sso' or 'reassign partitions' is found, otherwise `false`. */ export const consoleHasEnterpriseFeature = (feature: 'SINGLE_SIGN_ON' | 'REASSIGN_PARTITIONS'): boolean => { - return AppFeatures[feature] ?? false -} + return AppFeatures[feature] ?? false; +}; export const coreHasEnterpriseFeatures = (features: ListEnterpriseFeaturesResponse_Feature[]): boolean => { - return features.some(feature => feature.enabled); -} + return features.some((feature) => feature.enabled); +}; /** * Checks if a license is expired. @@ -94,8 +100,8 @@ export const licenseSoonToExpire = ( * @returns {Date} The expiration date as a JavaScript Date object. */ export const getExpirationDate = (license: License): Date => { - return new Date(Number(license.expiresAt) * 1000) -} + return new Date(Number(license.expiresAt) * 1000); +}; /** * Formats the expiration date of a given license into a user-friendly string format. @@ -109,12 +115,12 @@ export const getExpirationDate = (license: License): Date => { * with two-digit month, day, and four-digit year. */ export const getPrettyExpirationDate = (license: License): string => { - return new Intl.DateTimeFormat('en-US', { - month: 'long', - day: 'numeric', - year: 'numeric', - }).format(getExpirationDate(license)); -} + return new Intl.DateTimeFormat('en-US', { + month: 'long', + day: 'numeric', + year: 'numeric', + }).format(getExpirationDate(license)); +}; /** * Calculates the time remaining until a license expires. @@ -206,7 +212,8 @@ export const licenseCanExpire = (license: License): boolean => license.type !== * @param license - The license object to evaluate. * @returns `true` if the license type is `TRIAL` or `ENTERPRISE`, otherwise `false`. */ -export const isLicenseWithEnterpriseAccess = (license: License): boolean => license.type === License_Type.TRIAL || license.type === License_Type.ENTERPRISE +export const isLicenseWithEnterpriseAccess = (license: License): boolean => + license.type === License_Type.TRIAL || license.type === License_Type.ENTERPRISE; /** * Simplifies a list of licenses by grouping them based on their type and returning a simplified preview of each type. @@ -297,32 +304,48 @@ export const licensesToSimplifiedPreview = ( }); }; -type EnterpriseLinkType = 'tryEnterprise' | 'upgrade' +type EnterpriseLinkType = 'tryEnterprise' | 'upgrade'; export const resolveEnterpriseCTALink = ( - type: EnterpriseLinkType, - cluster_uuid: string | undefined, - isRedpanda: boolean, + type: EnterpriseLinkType, + cluster_uuid: string | undefined, + isRedpanda: boolean, ) => { - const urls: Record = { - 'tryEnterprise': 'https://redpanda.com/try-enterprise', - 'upgrade': 'https://redpanda.com/upgrade' - }; + const urls: Record = { + tryEnterprise: 'https://redpanda.com/try-enterprise', + upgrade: 'https://redpanda.com/upgrade', + }; - const baseUrl = urls[type]; - const url = new URL(baseUrl); + const baseUrl = urls[type]; + const url = new URL(baseUrl); - url.searchParams.append('cluster_id', cluster_uuid ?? ''); - url.searchParams.append('platform', `${isRedpanda ? Platform.PLATFORM_REDPANDA : Platform.PLATFORM_NON_REDPANDA}`); + url.searchParams.append('cluster_id', cluster_uuid ?? ''); + url.searchParams.append('platform', `${isRedpanda ? Platform.PLATFORM_REDPANDA : Platform.PLATFORM_NON_REDPANDA}`); - return url.toString(); + return url.toString(); }; export const getEnterpriseCTALink = (type: EnterpriseLinkType): string => { - return resolveEnterpriseCTALink(type, api.clusterOverview?.kafka.clusterId, api.isRedpanda); -} + return resolveEnterpriseCTALink(type, api.clusterOverview?.kafka.clusterId, api.isRedpanda); +}; -export const DISABLE_SSO_DOCS_LINK = 'https://docs.redpanda.com/current/console/config/configure-console/' -export const UploadLicenseButton = () => api.isAdminApiConfigured ? : null -export const UpgradeButton = () => +export const DISABLE_SSO_DOCS_LINK = 'https://docs.redpanda.com/current/console/config/configure-console/'; +export const UploadLicenseButton = () => + api.isAdminApiConfigured ? ( + + ) : null; +export const UpgradeButton = () => ( + +); diff --git a/frontend/src/components/pages/acls/Acl.List.tsx b/frontend/src/components/pages/acls/Acl.List.tsx index 9fac1c0bf..f63ddd0d2 100644 --- a/frontend/src/components/pages/acls/Acl.List.tsx +++ b/frontend/src/components/pages/acls/Acl.List.tsx @@ -443,7 +443,7 @@ const RolesTab = observer(() => { - (uiSettings.aclList.rolesTab.quickSearch = x)} diff --git a/frontend/src/components/pages/admin/LicenseExpiredPage.tsx b/frontend/src/components/pages/admin/LicenseExpiredPage.tsx index 03e209146..bcb42b4e9 100644 --- a/frontend/src/components/pages/admin/LicenseExpiredPage.tsx +++ b/frontend/src/components/pages/admin/LicenseExpiredPage.tsx @@ -7,54 +7,46 @@ import { DISABLE_SSO_DOCS_LINK } from '../../license/licenseUtils'; @observer export default class LicenseExpiredPage extends PageComponent { - initPage(): void { - this.refreshData(); - appGlobal.onRefresh = () => this.refreshData(); - } + initPage(): void { + this.refreshData(); + appGlobal.onRefresh = () => this.refreshData(); + } - refreshData() { - void api.listLicenses(); - } + refreshData() { + void api.listLicenses(); + } - render() { - const activeEnterpriseFeatures = api.enterpriseFeaturesUsed.filter(x => x.enabled) + render() { + const activeEnterpriseFeatures = api.enterpriseFeaturesUsed.filter((x) => x.enabled); - return ( - - - - - Dev Redpanda + return ( + + + + + Dev Redpanda - {/* Main Heading */} - - Your Redpanda Enterprise license has expired. - + {/* Main Heading */} + + Your Redpanda Enterprise license has expired. + - {/* Subtext */} - - You were using Console RBAC/SSO and your trial license has expired. To continue using them, you will need an Enterprise license. Alternatively, you can disable the paid features in your configuration file. - - - - - ); - } + {/* Subtext */} + + You were using Console RBAC/SSO and your trial license has expired. To continue using them, you will need + an{' '} + + Enterprise license + + . Alternatively, you can{' '} + + disable + {' '} + the paid features in your configuration file. + + + + + ); + } } diff --git a/frontend/src/components/pages/overview/Overview.tsx b/frontend/src/components/pages/overview/Overview.tsx index 233e1b5ed..4808902d3 100644 --- a/frontend/src/components/pages/overview/Overview.tsx +++ b/frontend/src/components/pages/overview/Overview.tsx @@ -21,7 +21,7 @@ import Section from '../../misc/Section'; import { PageComponent, type PageInitHelper } from '../Page'; import './Overview.scss'; import { - Badge, + Badge, Box, Button, DataTable, @@ -43,8 +43,11 @@ import colors from '../../../colors'; import { Statistic } from '../../misc/Statistic'; import ClusterHealthOverview from './ClusterHealthOverview'; import { OverviewLicenseNotification } from '../../license/OverviewLicenseNotification'; -import { getEnterpriseCTALink, isLicenseWithEnterpriseAccess, licensesToSimplifiedPreview } from '../../license/licenseUtils'; - +import { + getEnterpriseCTALink, + isLicenseWithEnterpriseAccess, + licensesToSimplifiedPreview, +} from '../../license/licenseUtils'; @observer class Overview extends PageComponent { @@ -106,8 +109,8 @@ class Overview extends PageComponent { return ( - - + +
- {!api.isRedpanda && !api.licenses.some(isLicenseWithEnterpriseAccess) && <> - - - - Redpanda Enterprise trial available - - - } + {!api.isRedpanda && !api.licenses.some(isLicenseWithEnterpriseAccess) && ( + <> + + + + + Redpanda Enterprise trial available + + + + + )} - {api.isRedpanda && api.isAdminApiConfigured && ( + {api.isRedpanda && api.isAdminApiConfigured && ( <> diff --git a/frontend/src/components/pages/reassign-partitions/ReassignPartitions.tsx b/frontend/src/components/pages/reassign-partitions/ReassignPartitions.tsx index d94474d0a..7ec719ee6 100644 --- a/frontend/src/components/pages/reassign-partitions/ReassignPartitions.tsx +++ b/frontend/src/components/pages/reassign-partitions/ReassignPartitions.tsx @@ -69,7 +69,6 @@ import { } from './logic/utils'; import { FeatureLicenseNotification } from '../../license/FeatureLicenseNotification'; - export interface PartitionSelection { // Which partitions are selected? [topicName: string]: number[]; // topicName -> array of partitionIds @@ -219,13 +218,16 @@ class ReassignPartitions extends PageComponent { - {/* Statistics */} -
- - - - - + + + + + ('/admin/:tab?', AdminPage, 'Admin'), - MakeRoute<{}>('/trial-expired', LicenseExpiredPage, 'Your enterprise trial has expired') - + MakeRoute<{}>('/trial-expired', LicenseExpiredPage, 'Your enterprise trial has expired'), ].filterNull(); diff --git a/frontend/src/config.ts b/frontend/src/config.ts index 8e725fcd9..cdc2513b7 100644 --- a/frontend/src/config.ts +++ b/frontend/src/config.ts @@ -5,7 +5,7 @@ import { type UnaryRequest, createPromiseClient, ConnectError, - Code + Code, } from '@connectrpc/connect'; import { createConnectTransport } from '@connectrpc/connect-web'; import { type Monaco, loader } from '@monaco-editor/react'; @@ -59,32 +59,31 @@ const addBearerTokenInterceptor: ConnectRpcInterceptor = (next) => async (req: U * */ const checkExpiredLicenseInterceptor: ConnectRpcInterceptor = (next) => async (req: UnaryRequest | StreamRequest) => { - try { - return await next(req); - } catch (error) { - if (error instanceof ConnectError) { - if (error.code === Code.FailedPrecondition) { - for (const detail of error.details) { - // @ts-ignore - TODO fix type checks for IncomingDetail, BE should provide types for debug field - if(detail?.type && detail?.debug) { - if ( - // @ts-ignore - TODO fix type checks for IncomingDetail, BE should provide types for debug field - detail.type === 'google.rpc.ErrorInfo' && - // @ts-ignore - TODO fix type checks for IncomingDetail, BE should provide types for debug field - detail.debug.reason === 'REASON_ENTERPRISE_LICENSE_EXPIRED' - ) { - appGlobal.history.replace('/trial-expired') - } - } - } + try { + return await next(req); + } catch (error) { + if (error instanceof ConnectError) { + if (error.code === Code.FailedPrecondition) { + for (const detail of error.details) { + // @ts-ignore - TODO fix type checks for IncomingDetail, BE should provide types for debug field + if (detail?.type && detail?.debug) { + if ( + // @ts-ignore - TODO fix type checks for IncomingDetail, BE should provide types for debug field + detail.type === 'google.rpc.ErrorInfo' && + // @ts-ignore - TODO fix type checks for IncomingDetail, BE should provide types for debug field + detail.debug.reason === 'REASON_ENTERPRISE_LICENSE_EXPIRED' + ) { + appGlobal.history.replace('/trial-expired'); } + } } - // Re-throw the error to ensure it's handled by other interceptors or the calling code - throw error; + } } + // Re-throw the error to ensure it's handled by other interceptors or the calling code + throw error; + } }; - export interface SetConfigArguments { fetch?: WindowOrWorkerGlobalScope['fetch']; jwt?: string; diff --git a/frontend/src/state/backendApi.ts b/frontend/src/state/backendApi.ts index a90623bfd..4c78d26f2 100644 --- a/frontend/src/state/backendApi.ts +++ b/frontend/src/state/backendApi.ts @@ -317,31 +317,36 @@ function cachedApiRequest(url: string, force = false): Promise { } export async function handleExpiredLicenseError(r: Response) { - const data = await r.json() - if (data.message.includes('license expired')) { - uiState.isUsingDebugUserLogin = true; - api.userData = { - canViewConsoleUsers: false, - canListAcls: true, - canListQuotas: true, - canPatchConfigs: true, - canReassignPartitions: true, - canCreateSchemas: true, - canDeleteSchemas: true, - canManageSchemaRegistry: true, - canViewSchemas: true, - canListTransforms: true, - canCreateTransforms: true, - canDeleteTransforms: true, - canViewDebugBundle: true, - seat: null as any, - user: {providerID: -1, providerName: '', id: '', internalIdentifier: '', meta: {avatarUrl: '', email: '', name: ''}} - }; - appGlobal.history.replace('/trial-expired'); - } + const data = await r.json(); + if (data.message.includes('license expired')) { + uiState.isUsingDebugUserLogin = true; + api.userData = { + canViewConsoleUsers: false, + canListAcls: true, + canListQuotas: true, + canPatchConfigs: true, + canReassignPartitions: true, + canCreateSchemas: true, + canDeleteSchemas: true, + canManageSchemaRegistry: true, + canViewSchemas: true, + canListTransforms: true, + canCreateTransforms: true, + canDeleteTransforms: true, + canViewDebugBundle: true, + seat: null as any, + user: { + providerID: -1, + providerName: '', + id: '', + internalIdentifier: '', + meta: { avatarUrl: '', email: '', name: '' }, + }, + }; + appGlobal.history.replace('/trial-expired'); + } } - // // BackendAPI // @@ -444,7 +449,7 @@ const apiStore = { }, }; } else if (r.status === 403) { - void handleExpiredLicenseError(r) + void handleExpiredLicenseError(r); } }); },