Skip to content

Commit 0853f6a

Browse files
committed
Security page rewrite
1 parent fe1faab commit 0853f6a

43 files changed

Lines changed: 1872 additions & 1842 deletions

Some content is hidden

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

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="w-full border-b">
44+
<div className="flex items-center gap-2 px-6 py-4">
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 px-12 pt-6">
108+
<div className="flex flex-col gap-1">
109+
{backLink && (
110+
<RegistryButton asChild className="-ml-2 w-fit text-muted-foreground" size="sm" 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
}

frontend/src/components/license/feature-license-notification.tsx

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import {
2727
} from '../../protogen/redpanda/api/console/v1alpha1/license_pb';
2828
import { api } from '../../state/backend-api';
2929
import { Alert, AlertDescription } from '../redpanda-ui/components/alert';
30+
import { Badge } from '../redpanda-ui/components/badge';
3031

3132
// biome-ignore lint/nursery/useMaxParams: Refactoring to options object would require updating all call sites
3233
const getLicenseAlertContentForFeature = (
@@ -172,7 +173,10 @@ const getLicenseAlertContentForFeature = (
172173
return null;
173174
};
174175

175-
export const FeatureLicenseNotification: FC<{ featureName: 'reassignPartitions' | 'rbac' }> = ({ featureName }) => {
176+
export const FeatureLicenseNotification: FC<{
177+
featureName: 'reassignPartitions' | 'rbac';
178+
as?: 'alert' | 'badge';
179+
}> = ({ featureName, as: renderAs = 'alert' }) => {
176180
const [registerModalOpen, setIsRegisterModalOpen] = useState(false);
177181

178182
useEffect(() => {
@@ -222,9 +226,13 @@ export const FeatureLicenseNotification: FC<{ featureName: 'reassignPartitions'
222226

223227
const { message, variant } = alertContent;
224228

229+
if (renderAs === 'badge') {
230+
return <Badge variant={variant === 'destructive' ? 'destructive' : 'simple'}>{message}</Badge>;
231+
}
232+
225233
return (
226234
<>
227-
<Alert className="mb-4" variant={variant}>
235+
<Alert variant={variant}>
228236
<AlertDescription>{message}</AlertDescription>
229237
</Alert>
230238

frontend/src/components/pages/observability/observability-page.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
import { type FC, lazy, Suspense, useCallback, useEffect, useMemo, useState } from 'react';
1313
import { useListQueries } from 'react-query/api/observability';
1414
import { appGlobal } from 'state/app-global';
15-
import { uiState } from 'state/ui-state';
15+
import { setPageHeader } from 'state/ui-state';
1616

1717
const MetricChart = lazy(() => import('./metric-chart').then((m) => ({ default: m.MetricChart })));
1818

@@ -46,7 +46,7 @@ const ObservabilityPage: FC = () => {
4646
}, [refetch]);
4747

4848
useEffect(() => {
49-
uiState.pageBreadcrumbs = [{ title: 'Metrics', linkTo: '/observability' }];
49+
setPageHeader('Metrics', [{ title: 'Metrics', linkTo: '/observability' }]);
5050
appGlobal.onRefresh = () => refreshData();
5151
}, [refreshData]);
5252

frontend/src/components/pages/overview/overview.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -384,7 +384,7 @@ function ClusterDetails() {
384384
<Details
385385
content={[
386386
[
387-
<Link key={0} to="/security/acls">
387+
<Link key={0} to="/security/permissions-list">
388388
{aclCount}
389389
</Link>,
390390
],

frontend/src/components/pages/page.ts

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ import {
1919
useRpcnSecretManagerStore,
2020
useTransformsStore,
2121
} from '../../state/backend-api';
22-
import { type BreadcrumbOptions, uiState } from '../../state/ui-state';
22+
import { type BreadcrumbEntry, type BreadcrumbOptions, setPageHeader } from '../../state/ui-state';
2323

2424
//
2525
// Page Types
@@ -30,11 +30,17 @@ export type NoRouteParams = {};
3030
export type PageProps<TRouteParams = NoRouteParams> = TRouteParams & { matchedPath: string };
3131

3232
export class PageInitHelper {
33+
private pageTitle = '';
34+
private pageBreadcrumbs: BreadcrumbEntry[] = [];
35+
3336
set title(title: string) {
34-
uiState.pageTitle = title;
37+
this.pageTitle = title;
3538
}
3639
addBreadcrumb(title: string, to: string, heading?: string, options?: BreadcrumbOptions) {
37-
uiState.pageBreadcrumbs.push({ title, linkTo: to, heading, options });
40+
this.pageBreadcrumbs.push({ title, linkTo: to, heading, options });
41+
}
42+
_flush() {
43+
setPageHeader(this.pageTitle, this.pageBreadcrumbs);
3844
}
3945
}
4046
export abstract class PageComponent<TRouteParams = NoRouteParams> extends React.Component<PageProps<TRouteParams>> {
@@ -43,9 +49,9 @@ export abstract class PageComponent<TRouteParams = NoRouteParams> extends React.
4349
constructor(props: Readonly<PageProps<TRouteParams>>) {
4450
super(props);
4551

46-
uiState.pageBreadcrumbs = [];
47-
48-
this.initPage(new PageInitHelper());
52+
const helper = new PageInitHelper();
53+
this.initPage(helper);
54+
helper._flush();
4955
}
5056

5157
componentDidMount() {

frontend/src/components/pages/rp-connect/onboarding/add-user-step.tsx

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -698,8 +698,11 @@ export const AddUserStep = forwardRef<UserStepRef, AddUserStepProps & MotionProp
698698
<AlertDescription>
699699
<Text variant="small">
700700
You will need to configure{' '}
701-
<TanStackRouterLink to="/security/acls">ACLs</TanStackRouterLink> for custom
702-
user permissions if you want the user to be able to read from the topic.
701+
<TanStackRouterLink to="/security/permissions-list">
702+
Permissions
703+
</TanStackRouterLink>{' '}
704+
for custom user permissions if you want the user to be able to read from the
705+
topic.
703706
</Text>
704707
</AlertDescription>
705708
</Alert>

frontend/src/components/pages/schemas/schema-list.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ import { api } from '../../../state/backend-api';
6767
import type { SchemaRegistrySubject } from '../../../state/rest-interfaces';
6868
import { useSupportedFeaturesStore } from '../../../state/supported-features';
6969
import { uiSettings } from '../../../state/ui';
70-
import { uiState } from '../../../state/ui-state';
70+
import { setPageHeader } from '../../../state/ui-state';
7171
import { encodeURIComponentPercents } from '../../../utils/utils';
7272
import PageContent from '../../misc/page-content';
7373
import Section from '../../misc/section';
@@ -180,7 +180,7 @@ const SchemaList: FC = () => {
180180
}, [derivedContexts, selectedContext, schemaRegistryContextsSupported, schemaMode, schemaCompatibility]);
181181

182182
useEffect(() => {
183-
uiState.pageBreadcrumbs = [{ title: 'Schema Registry', linkTo: '/schema-registry' }];
183+
setPageHeader('Schema Registry', [{ title: 'Schema Registry', linkTo: '/schema-registry' }]);
184184
appGlobal.onRefresh = () => refreshData();
185185
}, [refreshData]);
186186

0 commit comments

Comments
 (0)