Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 16 additions & 14 deletions frontend/src/components/layout/header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,11 @@ import { Box, Button, ColorModeSwitch, CopyButton, Flex } from '@redpanda-data/u
import { Link, useLocation, useMatchRoute } from '@tanstack/react-router';
import { Heading } from 'components/redpanda-ui/components/typography';
import { cn } from 'components/redpanda-ui/lib/utils';
import { computed } from 'mobx';
import { observer } from 'mobx-react';
import { Fragment } from 'react';
import { Fragment, useMemo } from 'react';

import { isEmbedded } from '../../config';
import { api } from '../../state/backend-api';
import { type BreadcrumbEntry, uiState } from '../../state/ui-state';
import { type BreadcrumbEntry, useUIStateStore } from '../../state/ui-state';
import { IsDev } from '../../utils/env';
import DataRefreshButton from '../misc/buttons/data-refresh/component';
import { UserPreferencesButton } from '../misc/user-preferences';
Expand Down Expand Up @@ -69,16 +67,19 @@ function BreadcrumbHeaderRow({ useNewSidebar, breadcrumbItems }: BreadcrumbHeade
);
}

const AppPageHeader = observer(() => {
function AppPageHeader() {
const showRefresh = useShouldShowRefresh();

const shouldHideHeader = useShouldHideHeader();
const useNewSidebar = !isEmbedded();

const breadcrumbItems = computed(() => {
const items: BreadcrumbEntry[] = [...uiState.pageBreadcrumbs];
const pageBreadcrumbs = useUIStateStore((s) => s.pageBreadcrumbs);
const selectedClusterName = useUIStateStore((s) => s.selectedClusterName);
const shouldHidePageHeader = useUIStateStore((s) => s.shouldHidePageHeader);

const breadcrumbItems = useMemo(() => {
const items: BreadcrumbEntry[] = [...pageBreadcrumbs];

if (!isEmbedded() && uiState.selectedClusterName) {
if (!isEmbedded() && selectedClusterName) {
items.unshift({
heading: '',
title: 'Cluster',
Expand All @@ -87,18 +88,19 @@ const AppPageHeader = observer(() => {
}

return items;
}).get();
}, [pageBreadcrumbs, selectedClusterName]);

const lastBreadcrumb = breadcrumbItems.pop();
const lastBreadcrumb = breadcrumbItems.at(-1);
const breadcrumbsExceptLast = breadcrumbItems.slice(0, -1);

if (shouldHideHeader || uiState.shouldHidePageHeader) {
if (shouldHideHeader || shouldHidePageHeader) {
return null;
}

return (
<Box>
{/* we need to refactor out #mainLayout > div rule, for now I've added this box as a workaround */}
<BreadcrumbHeaderRow breadcrumbItems={breadcrumbItems} useNewSidebar={useNewSidebar} />
<BreadcrumbHeaderRow breadcrumbItems={breadcrumbsExceptLast} useNewSidebar={useNewSidebar} />

<Flex alignItems="center" justifyContent="space-between" pb={2}>
<Flex alignItems="center">
Expand Down Expand Up @@ -140,7 +142,7 @@ const AppPageHeader = observer(() => {
</Flex>
</Box>
);
});
}

export default AppPageHeader;

Expand Down
113 changes: 55 additions & 58 deletions frontend/src/components/license/feature-license-notification.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { Alert, AlertDescription, AlertIcon, Box, Flex, Text } from '@redpanda-data/ui';
import { Link } from 'components/redpanda-ui/components/typography';
import { observer } from 'mobx-react';
import { type FC, type ReactElement, useEffect, useState } from 'react';

import {
Expand Down Expand Up @@ -173,66 +172,64 @@ const getLicenseAlertContentForFeature = (
return null;
};

export const FeatureLicenseNotification: FC<{ featureName: 'reassignPartitions' | 'rbac' }> = observer(
({ featureName }) => {
const [registerModalOpen, setIsRegisterModalOpen] = useState(false);

useEffect(() => {
api.refreshClusterOverview().catch(() => {
// Error handling managed by API layer
});
api.listLicenses().catch(() => {
// Error handling managed by API layer
});
}, []);

const licenses = api.licenses
.filter((lic) => lic.type === License_Type.TRIAL || lic.type === License_Type.COMMUNITY)
.sort((a, b) => LICENSE_WEIGHT[a.type] - LICENSE_WEIGHT[b.type]); // Sort by priority

// Choose the license with the latest expiration time
const license = getLatestExpiringLicense(licenses);

// Trial is either baked-in or extended. We need to check if any of the licenses are baked-in.
// We say the trial is baked-in if and only if all the licenses are baked-in. There can be a situation where,
// use has registered a license, it's updated in the brokers, but the console doesn't have the license re-loaded yet.
const bakedInTrial = licenses.every((lic) => isBakedInTrial(lic));

const enterpriseFeaturesUsed = api.enterpriseFeaturesUsed;
const alertContent = getLicenseAlertContentForFeature(
featureName,
license,
enterpriseFeaturesUsed,
bakedInTrial,
() => {
setIsRegisterModalOpen(true);
}
);

// 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;
export const FeatureLicenseNotification: FC<{ featureName: 'reassignPartitions' | 'rbac' }> = ({ featureName }) => {
const [registerModalOpen, setIsRegisterModalOpen] = useState(false);

useEffect(() => {
api.refreshClusterOverview().catch(() => {
// Error handling managed by API layer
});
api.listLicenses().catch(() => {
// Error handling managed by API layer
});
}, []);

const licenses = api.licenses
.filter((lic) => lic.type === License_Type.TRIAL || lic.type === License_Type.COMMUNITY)
.sort((a, b) => LICENSE_WEIGHT[a.type] - LICENSE_WEIGHT[b.type]); // Sort by priority

// Choose the license with the latest expiration time
const license = getLatestExpiringLicense(licenses);

// Trial is either baked-in or extended. We need to check if any of the licenses are baked-in.
// We say the trial is baked-in if and only if all the licenses are baked-in. There can be a situation where,
// use has registered a license, it's updated in the brokers, but the console doesn't have the license re-loaded yet.
const bakedInTrial = licenses.every((lic) => isBakedInTrial(lic));

const enterpriseFeaturesUsed = api.enterpriseFeaturesUsed;
const alertContent = getLicenseAlertContentForFeature(
featureName,
license,
enterpriseFeaturesUsed,
bakedInTrial,
() => {
setIsRegisterModalOpen(true);
}
);

if (!license) {
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 (alertContent === null) {
return null;
}
if (!license) {
return null;
}

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

const { message, status } = alertContent;
const { message, status } = alertContent;

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

<RegisterModal isOpen={registerModalOpen} onClose={() => setIsRegisterModalOpen(false)} />
</Box>
);
}
);
<RegisterModal isOpen={registerModalOpen} onClose={() => setIsRegisterModalOpen(false)} />
</Box>
);
};
5 changes: 2 additions & 3 deletions frontend/src/components/license/license-notification.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { Alert, AlertDescription, AlertIcon, Box, Button, Flex } from '@redpanda-data/ui';
import { Link, useLocation } from '@tanstack/react-router';
import { observer } from 'mobx-react';

import {
coreHasEnterpriseFeatures,
Expand All @@ -16,7 +15,7 @@ import { api } from '../../state/backend-api';
import { capitalizeFirst } from '../../utils/utils';

// biome-ignore lint/complexity/noExcessiveCognitiveComplexity: complex business logic
export const LicenseNotification = observer(() => {
export const LicenseNotification = () => {
const location = useLocation();

// This Global License Notification banner is used only for Enterprise licenses
Expand Down Expand Up @@ -134,4 +133,4 @@ export const LicenseNotification = observer(() => {
</Alert>
</Box>
);
});
};
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { Alert, AlertDescription, AlertIcon, Box, Flex, Text } from '@redpanda-data/ui';
import { Link } from 'components/redpanda-ui/components/typography';
import { observer } from 'mobx-react';
import { type FC, type ReactElement, useEffect, useState } from 'react';

import {
Expand Down Expand Up @@ -254,7 +253,7 @@ const getLicenseAlertContent = (
return null;
};

export const OverviewLicenseNotification: FC = observer(() => {
export const OverviewLicenseNotification: FC = () => {
const [registerModalOpen, setIsRegisterModalOpen] = useState(false);

useEffect(() => {
Expand Down Expand Up @@ -297,4 +296,4 @@ export const OverviewLicenseNotification: FC = observer(() => {
<RegisterModal isOpen={registerModalOpen} onClose={() => setIsRegisterModalOpen(false)} />
</Box>
);
});
};
5 changes: 2 additions & 3 deletions frontend/src/components/license/register-modal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@ import {
VStack,
} from '@redpanda-data/ui';
import { CheckIcon } from 'components/icons';
import { observer } from 'mobx-react';
import { useState } from 'react';
import { Controller, type SubmitHandler, useForm } from 'react-hook-form';
import { capitalizeFirst } from 'utils/utils';
Expand Down Expand Up @@ -62,7 +61,7 @@ type RegisterModalProps = {
onClose: () => void;
};

export const RegisterModal = observer(({ isOpen, onClose }: RegisterModalProps) => {
export const RegisterModal = ({ isOpen, onClose }: RegisterModalProps) => {
const [isSubmitting, setIsSubmitting] = useState(false);
const [fieldErrors, setFieldErrors] = useState<Record<string, string>>({});
const [isSuccess, setIsSuccess] = useState(false);
Expand Down Expand Up @@ -309,4 +308,4 @@ export const RegisterModal = observer(({ isOpen, onClose }: RegisterModalProps)
</ModalContent>
</Modal>
);
});
};
Loading
Loading