Skip to content

Commit

Permalink
Merge pull request #1541 from redpanda-data/CONSOLE-67-self-service-l…
Browse files Browse the repository at this point in the history
…icense-trials-and-graceful-license-expiry-merge

Console 67 self service license trials and graceful license expiry merge
  • Loading branch information
jvorcak authored Nov 29, 2024
2 parents 7941b8a + 0f46062 commit ea1a7eb
Show file tree
Hide file tree
Showing 20 changed files with 765 additions and 60 deletions.
2 changes: 2 additions & 0 deletions backend/pkg/console/overview_kafka.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ type OverviewKafka struct {
ReplicasCount int `json:"replicasCount"`
ControllerID int32 `json:"controllerId"`
Brokers []OverviewKafkaBroker `json:"brokers"`
ClusterID string `json:"clusterId"`

Authorizer *OverviewKafkaAuthorizer `json:"authorizer,omitempty"`
}
Expand Down Expand Up @@ -137,6 +138,7 @@ func (s *Service) getKafkaOverview(ctx context.Context) OverviewKafka {
ReplicasCount: replicaCount,
ControllerID: metadata.Controller,
Authorizer: &authorizer,
ClusterID: metadata.Cluster,
}
}

Expand Down
50 changes: 28 additions & 22 deletions backend/pkg/protogen/redpanda/api/dataplane/v1alpha2/error.pb.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 4 additions & 1 deletion frontend/src/components/RequireAuth.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { observer } from 'mobx-react';
import { Component, type ReactNode } from 'react';
import { Route, Switch } from 'react-router-dom';
import { api } from '../state/backendApi';
import { api, handleExpiredLicenseError } from '../state/backendApi';
import type { UserData } from '../state/restInterfaces';
import { featureErrors } from '../state/supportedFeatures';
import { uiState } from '../state/uiState';
Expand Down Expand Up @@ -81,6 +81,7 @@ export default class RequireAuth extends Component<{ children: ReactNode }> {
canListTransforms: true,
canCreateTransforms: true,
canDeleteTransforms: true,
canViewDebugBundle: true,
seat: null as any,
user: {
providerID: -1,
Expand All @@ -90,6 +91,8 @@ export default class RequireAuth extends Component<{ children: ReactNode }> {
meta: { avatarUrl: '', email: '', name: 'local fake user for debugging' },
},
};
} else if (r.status === 403) {
void handleExpiredLicenseError(r);
}
});

Expand Down
174 changes: 174 additions & 0 deletions frontend/src/components/license/FeatureLicenseNotification.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +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 { 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 msToExpiration = getMillisecondsToExpiration(license);

// Redpanda
if (api.isRedpanda) {
if (
msToExpiration > 15 * MS_IN_DAY &&
msToExpiration < 30 * MS_IN_DAY &&
coreHasEnterpriseFeatures(enterpriseFeaturesUsed)
) {
return {
message: (
<Box>
<Text>This is an enterprise feature, active until {getPrettyExpirationDate(license)}.</Text>
<Flex gap={2} my={2}>
<UploadLicenseButton />
<UpgradeButton />
</Flex>
</Box>
),
status: 'info',
};
} else if (
msToExpiration > 0 &&
msToExpiration < 15 * MS_IN_DAY &&
coreHasEnterpriseFeatures(enterpriseFeaturesUsed)
) {
return {
message: (
<Box>
<Text>
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,{' '}
<Link href={getEnterpriseCTALink('upgrade')} target="_blank">
contact us
</Link>
.
</Text>
<Flex gap={2} my={2}>
<UploadLicenseButton />
<UpgradeButton />
</Flex>
</Box>
),
status: 'warning',
};
}
} else {
// Kafka
if (msToExpiration > 15 * MS_IN_DAY && msToExpiration < 30 * MS_IN_DAY) {
if (license.type === License_Type.TRIAL) {
return {
message: (
<Box>
<Text>This is an enterprise feature. Your trial is active until {getPrettyExpirationDate(license)}</Text>
<Flex gap={2} my={2}>
<UploadLicenseButton />
<UpgradeButton />
</Flex>
</Box>
),
status: 'info',
};
} else {
return {
message: (
<Box>
<Text>
This is a Redpanda Enterprise feature. Try it with our{' '}
<Link href={getEnterpriseCTALink('tryEnterprise')} target="_blank">
Redpanda Enterprise Trial
</Link>
.
</Text>
</Box>
),
status: 'info',
};
}
} else if (msToExpiration > 0 && msToExpiration < 15 * MS_IN_DAY && license.type === License_Type.TRIAL) {
return {
message: (
<Box>
<Text>
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,{' '}
<Link href={getEnterpriseCTALink('upgrade')} target="_blank">
contact us
</Link>
.
</Text>
<Flex gap={2} my={2}>
<UploadLicenseButton />
<UpgradeButton />
</Flex>
</Box>
),
status: 'warning',
};
}
}

return null;
};

export const FeatureLicenseNotification: FC<{ featureName: 'reassignPartitions' | 'rbac' }> = observer(
({ featureName }) => {
useEffect(() => {
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 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;
}

if (!license) {
return null;
}

if (alertContent === null) {
return null;
}

const { message, status } = alertContent;

return (
<Box>
<Alert mb={4} status={status} variant="subtle">
<AlertIcon />
<AlertDescription>{message}</AlertDescription>
</Alert>
</Box>
);
},
);
Loading

0 comments on commit ea1a7eb

Please sign in to comment.