Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
47 commits
Select commit Hold shift + click to select a range
7f7e755
refactor: consolidate security pages into new unified structure
weeco Mar 7, 2026
abaf941
feat: rework security pages with ACL editor, pattern type badges, and…
weeco Mar 10, 2026
8b40c55
fix: remove obsolete role edit redirect route and regenerate route tree
weeco Apr 7, 2026
70b2da9
fix: improve security detail pages layout, empty states, and consistency
weeco Apr 7, 2026
4cdc3af
feat(frontend): add composable renderPrincipal slot to CreateACL
sago2k8 Apr 10, 2026
d44a06b
feat(frontend): show Group principals in ACLs and Permissions List
sago2k8 Apr 10, 2026
e1c6089
feat(frontend): add lockPrincipal query param to ACL create page
sago2k8 Apr 10, 2026
ec7f48f
test(frontend): add E2E tests for roles, permissions list, and lockPr…
sago2k8 Apr 10, 2026
48f83e8
docs(frontend): clarify bun run usage in AGENTS.md
sago2k8 Apr 10, 2026
32b83e5
fix(frontend): fix silent toast errors in mutation hooks
sago2k8 Apr 10, 2026
e80f8eb
feat(frontend): improve gRPC error formatting with findDetails
sago2k8 Apr 10, 2026
ed98cd5
refactor(frontend): migrate user-create page to Connect gRPC and UI R…
sago2k8 Apr 10, 2026
492cea4
refactor(frontend): extract resolveAclSearchParams and remove lockPri…
sago2k8 Apr 10, 2026
8e0a208
fix(frontend): sync CreateACL sharedConfig when props change after mount
sago2k8 Apr 10, 2026
b8aaca7
test(frontend): update E2E tests with testIds and Create ACLs flow
sago2k8 Apr 10, 2026
fbd08b8
refactor(frontend): remove legacy ACL list page and old security tab …
sago2k8 Apr 10, 2026
97a3401
refactor(frontend): update security routes to file-based tab structure
sago2k8 Apr 10, 2026
978769c
refactor(frontend): update ACL and role pages for new security routing
sago2k8 Apr 10, 2026
71cbce9
chore(frontend): minor cleanup in add-user-step and confirmation modal
sago2k8 Apr 10, 2026
969514d
feat(frontend): add file-based security tab routes
sago2k8 Apr 10, 2026
f427d5f
feat(frontend): add security tab components, hooks, and shared utilities
sago2k8 Apr 10, 2026
ed137f4
refactor(frontend): extract generatePassword to utils/password.ts
sago2k8 Apr 10, 2026
c77dd5e
refactor(frontend): delete orphaned ACL components
sago2k8 Apr 10, 2026
da43baa
refactor(frontend): flatten new-acl subdirectory into acls
sago2k8 Apr 10, 2026
8ea1878
fix(tests): update acl.model import paths to match new-acl flatten re…
sago2k8 Apr 11, 2026
dd2288b
refactor(frontend): use shared generatePassword utility in acl and on…
sago2k8 Apr 11, 2026
4b7700d
chore(deps): upgrade react-doctor to 0.0.33 and add ultracite script …
sago2k8 Apr 11, 2026
379806e
refactor(frontend): move ACL and role components under security direc…
sago2k8 Apr 11, 2026
7cc2326
fix(frontend): restore propSharedConfig sync effect and revert react-…
sago2k8 Apr 11, 2026
ea7e93a
fix(frontend): add htmlFor/id to Username field for label-input assoc…
sago2k8 Apr 11, 2026
371085d
Move parameters to the URL & rely more on GRPC enums for security page
jvorcak Apr 13, 2026
4931192
Add regex filter abstraction
jvorcak Apr 13, 2026
842b918
Merge branch 'fix/security-acl-improvements' into security-page-refactor
jvorcak Apr 13, 2026
daaa85e
Fix imports after resolving merge conflicts
jvorcak Apr 13, 2026
a25ba0f
Fix imports after resolving merge conflicts
jvorcak Apr 13, 2026
1ed3630
Fix types after resolving merge conflicts
jvorcak Apr 13, 2026
bca05de
Fixes e2e tests
jvorcak Apr 13, 2026
60a4af1
Fix integration tests
jvorcak Apr 13, 2026
8c80099
Fix integration tests
jvorcak Apr 13, 2026
c1e33e3
Restore role edit redirect route for backward compatibility
jvorcak Apr 13, 2026
b90f84c
Migrate Alert to a new UI lib
jvorcak Apr 14, 2026
7a4821a
Saerch query should be present in the URL for all access control tabs
jvorcak Apr 14, 2026
2a4ac18
Fixes e2e tests non-deterministic startup
jvorcak Apr 14, 2026
9af1e18
Adds NuqsTestingAdapter to integration tests
jvorcak Apr 14, 2026
ce2a5c0
Fix integration tests: mock useLocation in TanStack Router mock
jvorcak Apr 14, 2026
292718e
Apply lint auto-fix: simplify search prop in AclsTab Link
jvorcak Apr 14, 2026
53a9ecd
Table skeletons & improved loading in security page
jvorcak Apr 15, 2026
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
18 changes: 0 additions & 18 deletions frontend/src/components/layout/header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -164,18 +164,6 @@ function useShouldShowRefresh() {
const connectWizardPagesMatch = matchRoute({ to: '/rp-connect/wizard' });
const getStartedApiMatch = matchRoute({ to: '/get-started/api' });

// matches acls
const aclCreateMatch = matchRoute({ to: '/security/acls/create' });
const aclUpdateMatch = matchRoute({ to: '/security/acls/$aclName/update' });
const aclDetailMatch = matchRoute({ to: '/security/acls/$aclName/details' });
const isACLRelated = aclCreateMatch || aclUpdateMatch || aclDetailMatch;

// matches roles
const roleCreateMatch = matchRoute({ to: '/security/roles/create' });
const roleUpdateMatch = matchRoute({ to: '/security/roles/$roleName/update' });
const roleDetailMatch = matchRoute({ to: '/security/roles/$roleName/details' });
const isRoleRelated = roleCreateMatch || roleUpdateMatch || roleDetailMatch;

if (connectClusterMatch && connectClusterMatch.connector === 'create-connector') {
return false;
}
Expand All @@ -188,12 +176,6 @@ function useShouldShowRefresh() {
if (secretsMatch) {
return false;
}
if (isACLRelated) {
return false;
}
if (isRoleRelated) {
return false;
}
if (connectWizardPagesMatch) {
return false;
}
Expand Down
188 changes: 102 additions & 86 deletions frontend/src/components/license/feature-license-notification.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Alert, AlertDescription, AlertIcon, Box, Flex, Text } from '@redpanda-data/ui';
import { Flex, Text } from '@redpanda-data/ui';
import { Link } from 'components/redpanda-ui/components/typography';
import { type FC, type ReactElement, useEffect, useState } from 'react';

Expand All @@ -20,13 +20,16 @@ import {

const WARNING_THRESHOLD_DAYS = 5;

import { RegisterModal } from './register-modal';
import { InfoIcon } from 'components/icons';
import { Alert, AlertDescription, AlertTitle } from 'components/redpanda-ui/components/alert';
import {
type License,
License_Type,
type ListEnterpriseFeaturesResponse_Feature,
} from '../../protogen/redpanda/api/console/v1alpha1/license_pb';
import { api } from '../../state/backend-api';
} from 'protogen/redpanda/api/console/v1alpha1/license_pb';
import { api } from 'state/backend-api';

import { RegisterModal } from './register-modal';

// biome-ignore lint/nursery/useMaxParams: Refactoring to options object would require updating all call sites
const getLicenseAlertContentForFeature = (
Expand All @@ -36,7 +39,7 @@ const getLicenseAlertContentForFeature = (
bakedInTrial: boolean,
onRegisterModalOpen: () => void
// biome-ignore lint/complexity/noExcessiveCognitiveComplexity: complex business logic
): { message: ReactElement; status: 'warning' | 'info' } | null => {
): { message: ReactElement; status: 'warning' | 'destructive' } | null => {
if (license === undefined) {
return null;
}
Expand All @@ -47,23 +50,28 @@ const getLicenseAlertContentForFeature = (
if (bakedInTrial) {
return {
message: (
<Box>
<Text>This is an enterprise feature. Register for an additional 30 days of enterprise features.</Text>
<Flex gap={2} my={2}>
<RegisterButton onRegisterModalOpen={onRegisterModalOpen} />
</Flex>
</Box>
<Alert icon={<InfoIcon />} variant="destructive">
<AlertDescription>
<Text>This is an enterprise feature. Register for an additional 30 days of enterprise features.</Text>
<Flex gap={2} my={2}>
<RegisterButton onRegisterModalOpen={onRegisterModalOpen} />
</Flex>
</AlertDescription>
</Alert>
),
status: msToExpiration > WARNING_THRESHOLD_DAYS * MS_IN_DAY ? 'info' : 'warning',
status: msToExpiration > WARNING_THRESHOLD_DAYS * MS_IN_DAY ? 'warning' : 'destructive',
};
}
return {
message: (
<Box>
<Text>This is an enterprise feature.</Text>
</Box>
<Alert
icon={<InfoIcon />}
variant={msToExpiration > WARNING_THRESHOLD_DAYS * MS_IN_DAY ? 'warning' : 'destructive'}
>
<AlertTitle>This is an enterprise feature.</AlertTitle>
</Alert>
),
status: msToExpiration > WARNING_THRESHOLD_DAYS * MS_IN_DAY ? 'info' : 'warning',
status: msToExpiration > WARNING_THRESHOLD_DAYS * MS_IN_DAY ? 'warning' : 'destructive',
};
}

Expand All @@ -76,39 +84,43 @@ const getLicenseAlertContentForFeature = (
) {
return {
message: (
<Box>
<Text>This is an enterprise feature, active until {getPrettyExpirationDate(license)}.</Text>
<Flex gap={2} my={2}>
<UploadLicenseButton />
<UpgradeButton />
</Flex>
</Box>
<Alert icon={<InfoIcon />} variant="info">
<AlertDescription>
<Text>This is an enterprise feature, active until {getPrettyExpirationDate(license)}.</Text>
<Flex gap={2} my={2}>
<UploadLicenseButton />
<UpgradeButton />
</Flex>
</AlertDescription>
</Alert>
),
status: 'info',
status: 'warning',
};
}
if (msToExpiration > -1 && msToExpiration < 15 * MS_IN_DAY && coreHasEnterpriseFeatures(enterpriseFeaturesUsed)) {
return {
message: (
<Box>
<Text>
Your Redpanda Enterprise trial is expiring in {getPrettyTimeToExpiration(license)}; at that point, your{' '}
<Link href={ENTERPRISE_FEATURES_DOCS_LINK} rel="noopener noreferrer" target="_blank">
enterprise features
</Link>{' '}
will become unavailable. To get a full Redpanda Enterprise license,{' '}
<Link href={getEnterpriseCTALink('upgrade')} rel="noopener noreferrer" target="_blank">
contact us
</Link>
.
</Text>
<Flex gap={2} my={2}>
<UploadLicenseButton />
<UpgradeButton />
</Flex>
</Box>
<Alert icon={<InfoIcon />} variant="destructive">
<AlertDescription>
<Text>
Your Redpanda Enterprise trial is expiring in {getPrettyTimeToExpiration(license)}; at that point, your{' '}
<Link href={ENTERPRISE_FEATURES_DOCS_LINK} rel="noopener noreferrer" target="_blank">
enterprise features
</Link>{' '}
will become unavailable. To get a full Redpanda Enterprise license,{' '}
<Link href={getEnterpriseCTALink('upgrade')} rel="noopener noreferrer" target="_blank">
contact us
</Link>
.
</Text>
<Flex gap={2} my={2}>
<UploadLicenseButton />
<UpgradeButton />
</Flex>
</AlertDescription>
</Alert>
),
status: 'warning',
status: 'destructive',
};
}
} else {
Expand All @@ -117,54 +129,62 @@ const getLicenseAlertContentForFeature = (
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>
<Alert icon={<InfoIcon />} variant="warning">
<AlertDescription>
<Text>
This is an enterprise feature. Your trial is active until {getPrettyExpirationDate(license)}
</Text>
<Flex gap={2} my={2}>
<UploadLicenseButton />
<UpgradeButton />
</Flex>
</AlertDescription>
</Alert>
),
status: 'info',
status: 'warning',
};
}
return {
message: (
<Box>
<Text>
This is a Redpanda Enterprise feature. Try it with our{' '}
<Link href={getEnterpriseCTALink('tryEnterprise')} rel="noopener noreferrer" target="_blank">
Redpanda Enterprise Trial
</Link>
.
</Text>
</Box>
<Alert icon={<InfoIcon />} variant="warning">
<AlertDescription>
<Text>
This is a Redpanda Enterprise feature. Try it with our{' '}
<Link href={getEnterpriseCTALink('tryEnterprise')} rel="noopener noreferrer" target="_blank">
Redpanda Enterprise Trial
</Link>
.
</Text>
</AlertDescription>
</Alert>
),
status: 'info',
status: 'warning',
};
}
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{' '}
<Link href={ENTERPRISE_FEATURES_DOCS_LINK} rel="noopener noreferrer" target="_blank">
enterprise features
</Link>{' '}
will become unavailable. To get a full Redpanda Enterprise license,{' '}
<Link href={getEnterpriseCTALink('upgrade')} rel="noopener noreferrer" target="_blank">
contact us
</Link>
.
</Text>
<Flex gap={2} my={2}>
<UploadLicenseButton />
<UpgradeButton />
</Flex>
</Box>
<Alert icon={<InfoIcon />} variant="warning">
<AlertDescription>
<Text>
Your Redpanda Enterprise trial is expiring in {getPrettyTimeToExpiration(license)}; at that point, your{' '}
<Link href={ENTERPRISE_FEATURES_DOCS_LINK} rel="noopener noreferrer" target="_blank">
enterprise features
</Link>{' '}
will become unavailable. To get a full Redpanda Enterprise license,{' '}
<Link href={getEnterpriseCTALink('upgrade')} rel="noopener noreferrer" target="_blank">
contact us
</Link>
.
</Text>
<Flex gap={2} my={2}>
<UploadLicenseButton />
<UpgradeButton />
</Flex>
</AlertDescription>
</Alert>
),
status: 'warning',
status: 'destructive',
};
}
}
Expand Down Expand Up @@ -220,16 +240,12 @@ export const FeatureLicenseNotification: FC<{ featureName: 'reassignPartitions'
return null;
}

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

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

<>
{message}
<RegisterModal isOpen={registerModalOpen} onClose={() => setIsRegisterModalOpen(false)} />
</Box>
</>
);
};
48 changes: 48 additions & 0 deletions frontend/src/components/misc/query-result.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
/**
* Copyright 2022 Redpanda Data, Inc.
*
* Use of this software is governed by the Business Source License
* included in the file https://github.com/redpanda-data/redpanda/blob/dev/licenses/bsl.md
*
* As of the Change Date specified in that file, in accordance with
* the Business Source License, use of this software will be governed
* by the Apache License, Version 2.0
*/

import type { ReactNode } from 'react';
import { DefaultSkeleton } from 'utils/tsx-utils';

import { Alert, AlertDescription, AlertTitle } from '../redpanda-ui/components/alert';

type Props = {
isLoading: boolean;
isError: boolean;
error?: { message?: string } | null;
errorTitle?: string;
children: ReactNode;
skeleton?: ReactNode;
};

export const QueryResult = ({
isLoading,
isError,
error,
errorTitle = 'Failed to load data',
children,
skeleton = DefaultSkeleton,
}: Props) => {
if (isLoading) {
return skeleton;
}

if (isError) {
return (
<Alert variant="destructive">
<AlertTitle>{errorTitle}</AlertTitle>
<AlertDescription>{error?.message ?? 'An unexpected error occurred.'}</AlertDescription>
</Alert>
);
}

return <>{children}</>;
};
Loading
Loading