Skip to content

Commit 32825a4

Browse files
authored
Merge pull request #2426 from redpanda-data/security-page-refactor-2
Security page refactor
2 parents 6ba0035 + 6229c50 commit 32825a4

89 files changed

Lines changed: 4576 additions & 868 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

frontend/.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -162,3 +162,4 @@ dist
162162
.gemini/settings.json
163163

164164
tests/**/playwright-report/
165+
tests/**/*.js

frontend/src/app.tsx

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,12 @@ const router = createRouter({
7575
defaultNotFoundComponent: NotFoundPage,
7676
});
7777

78+
declare global {
79+
interface Window {
80+
__E2E_FEATURE_FLAGS__?: Record<string, boolean>;
81+
}
82+
}
83+
7884
// Register router for type safety
7985
declare module '@tanstack/react-router' {
8086
// biome-ignore lint/style/useConsistentTypeDefinitions: Required for TanStack Router module augmentation
@@ -109,7 +115,7 @@ const App = () => {
109115

110116
// Need to use CustomFeatureFlagProvider for completeness with EmbeddedApp
111117
return (
112-
<CustomFeatureFlagProvider initialFlags={{}}>
118+
<CustomFeatureFlagProvider initialFlags={window.__E2E_FEATURE_FLAGS__ ?? {}}>
113119
<Content apiKey={BUILDER_API_KEY} content={null} customComponents={builderCustomComponents} model={''} />
114120
<ChakraProvider resetCSS={false} theme={redpandaTheme} toastOptions={redpandaToastOptions}>
115121
<TransportProvider transport={dataplaneTransport}>

frontend/src/components/constants.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ export const FEATURE_FLAGS = {
1616
enableNewPipelineLogs: false,
1717
enablePipelineDiagrams: false,
1818
enableConnectSlashMenu: false,
19+
enableNewSecurityPage: true,
1920
enableTeamsBridge: false,
2021
};
2122

frontend/src/components/layout/header.tsx

Lines changed: 49 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,11 @@
99
* by the Apache License, Version 2.0
1010
*/
1111

12-
import { Box, Button, ColorModeSwitch, CopyButton, Flex } from '@redpanda-data/ui';
12+
import { Button, ColorModeSwitch, CopyButton } from '@redpanda-data/ui';
1313
import { Link, useLocation, useMatchRoute } from '@tanstack/react-router';
1414
import { Heading } from 'components/redpanda-ui/components/typography';
1515
import { cn } from 'components/redpanda-ui/lib/utils';
16+
import { ChevronLeft } from 'lucide-react';
1617
import { Fragment, useMemo } from 'react';
1718

1819
import { isEmbedded, isFeatureFlagEnabled } from '../../config';
@@ -28,6 +29,7 @@ import {
2829
BreadcrumbList,
2930
BreadcrumbSeparator,
3031
} from '../redpanda-ui/components/breadcrumb';
32+
import { Button as RegistryButton } from '../redpanda-ui/components/button';
3133
import { Separator } from '../redpanda-ui/components/separator';
3234
import { SidebarTrigger } from '../redpanda-ui/components/sidebar';
3335

@@ -38,8 +40,8 @@ type BreadcrumbHeaderRowProps = {
3840

3941
function BreadcrumbHeaderRow({ useNewSidebar, breadcrumbItems }: BreadcrumbHeaderRowProps) {
4042
return (
41-
<Flex alignItems="center" justifyContent="space-between">
42-
<Flex alignItems="center" gap={2}>
43+
<div className={cn('w-full border-b', useNewSidebar && 'py-4')}>
44+
<div className="flex items-center gap-2">
4345
{useNewSidebar ? (
4446
<>
4547
<SidebarTrigger />
@@ -50,7 +52,7 @@ function BreadcrumbHeaderRow({ useNewSidebar, breadcrumbItems }: BreadcrumbHeade
5052
<Breadcrumb>
5153
<BreadcrumbList>
5254
{breadcrumbItems.map((item, index) => (
53-
<Fragment key={item.linkTo}>
55+
<Fragment key={`${index}-${item.linkTo}`}>
5456
{index > 0 && <BreadcrumbSeparator />}
5557
<BreadcrumbItem>
5658
<BreadcrumbLink asChild>
@@ -62,8 +64,8 @@ function BreadcrumbHeaderRow({ useNewSidebar, breadcrumbItems }: BreadcrumbHeade
6264
</BreadcrumbList>
6365
</Breadcrumb>
6466
)}
65-
</Flex>
66-
</Flex>
67+
</div>
68+
</div>
6769
);
6870
}
6971

@@ -74,9 +76,10 @@ function AppPageHeader() {
7476
const useNewSidebar = !isEmbedded();
7577

7678
const pageBreadcrumbs = useUIStateStore((s) => s.pageBreadcrumbs);
79+
const pageTitle = useUIStateStore((s) => s._pageTitle);
80+
const backLink = useUIStateStore((s) => s.backLink);
7781
const selectedClusterName = useUIStateStore((s) => s.selectedClusterName);
7882
const shouldHidePageHeader = useUIStateStore((s) => s.shouldHidePageHeader);
79-
8083
const breadcrumbItems = useMemo(() => {
8184
const items: BreadcrumbEntry[] = [...pageBreadcrumbs];
8285

@@ -92,38 +95,41 @@ function AppPageHeader() {
9295
}, [pageBreadcrumbs, selectedClusterName]);
9396

9497
const lastBreadcrumb = breadcrumbItems.at(-1);
95-
const breadcrumbsExceptLast = breadcrumbItems.slice(0, -1);
9698

9799
if (shouldHideHeader || shouldHidePageHeader) {
98100
return null;
99101
}
100102

101103
return (
102-
<Box>
103-
{/* we need to refactor out #mainLayout > div rule, for now I've added this box as a workaround */}
104-
<BreadcrumbHeaderRow breadcrumbItems={breadcrumbsExceptLast} useNewSidebar={useNewSidebar} />
105-
106-
<Flex alignItems="center" justifyContent="space-between" pb={2}>
107-
<Flex alignItems="center">
108-
{lastBreadcrumb ? (
109-
<Heading
110-
// as="span"
111-
className={cn('mr-2', lastBreadcrumb.options?.canBeTruncated ? 'break-spaces break-all' : 'nowrap')}
112-
level={1}
113-
>
114-
{lastBreadcrumb.titleNode ?? lastBreadcrumb.title}
115-
</Heading>
116-
) : null}
117-
{lastBreadcrumb ? (
118-
<Box>
119-
{lastBreadcrumb.options?.canBeCopied ? (
120-
<CopyButton content={lastBreadcrumb.title} variant="ghost" />
121-
) : null}
122-
</Box>
123-
) : null}
124-
{Boolean(showRefresh) && <DataRefreshButton />}
125-
</Flex>
126-
<Flex alignItems="center" gap={2}>
104+
<div>
105+
<BreadcrumbHeaderRow breadcrumbItems={breadcrumbItems} useNewSidebar={useNewSidebar} />
106+
107+
<div className="flex items-center justify-between pt-6">
108+
<div className="flex flex-col gap-1">
109+
{backLink && (
110+
<RegistryButton asChild className="-ml-2 w-fit text-muted-foreground" variant="ghost">
111+
<Link to={backLink.linkTo}>
112+
<ChevronLeft className="h-4 w-4" />
113+
{backLink.title}
114+
</Link>
115+
</RegistryButton>
116+
)}
117+
<div className="flex items-center">
118+
{pageTitle ? (
119+
<Heading
120+
className={cn('mr-2', lastBreadcrumb?.options?.canBeTruncated ? 'break-spaces break-all' : 'nowrap')}
121+
level={1}
122+
>
123+
{pageTitle}
124+
</Heading>
125+
) : null}
126+
{lastBreadcrumb?.options?.canBeCopied ? (
127+
<CopyButton content={lastBreadcrumb.title} variant="ghost" />
128+
) : null}
129+
{Boolean(showRefresh) && <DataRefreshButton />}
130+
</div>
131+
</div>
132+
<div className="flex items-center gap-2">
127133
{!isEmbedded() && api.isRedpanda && (
128134
<Link to="/debug-bundle">
129135
<Button
@@ -139,9 +145,9 @@ function AppPageHeader() {
139145
)}
140146
<UserPreferencesButton />
141147
{IsDev && !isEmbedded() && <ColorModeSwitch m={0} p={0} variant="ghost" />}
142-
</Flex>
143-
</Flex>
144-
</Box>
148+
</div>
149+
</div>
150+
</div>
145151
);
146152
}
147153

@@ -165,17 +171,18 @@ function useShouldShowRefresh() {
165171
const getStartedApiMatch = matchRoute({ to: '/get-started/api' });
166172

167173
// matches acls
168-
const aclCreateMatch = matchRoute({ to: '/security/acls/create' });
169-
const aclUpdateMatch = matchRoute({ to: '/security/acls/$aclName/update' });
170174
const aclDetailMatch = matchRoute({ to: '/security/acls/$aclName/details' });
171-
const isACLRelated = aclCreateMatch || aclUpdateMatch || aclDetailMatch;
175+
const isACLRelated = aclDetailMatch;
172176

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

183+
// matches user detail
184+
const userDetailMatch = matchRoute({ to: '/security/users/$userName/details' });
185+
179186
if (connectClusterMatch && connectClusterMatch.connector === 'create-connector') {
180187
return false;
181188
}
@@ -194,6 +201,9 @@ function useShouldShowRefresh() {
194201
if (isRoleRelated) {
195202
return false;
196203
}
204+
if (userDetailMatch) {
205+
return false;
206+
}
197207
if (connectWizardPagesMatch) {
198208
return false;
199209
}

0 commit comments

Comments
 (0)