Skip to content

Commit 224fbcf

Browse files
lucasgoralCopilot
andauthored
feat: Added 'Access Denied' screen for unauthorized MCP users (#376)
Co-authored-by: Copilot <[email protected]>
1 parent 8542fff commit 224fbcf

File tree

12 files changed

+269
-130
lines changed

12 files changed

+269
-130
lines changed

public/locales/en.json

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -523,5 +523,14 @@
523523
"branchTitle": "Branch",
524524
"gitRepositoryCreated": "Git Repository created successfully",
525525
"gitRepositoryCreationFailed": "Failed to create Git Repository: {{error}}"
526+
},
527+
"mcp": {
528+
"authorization": {
529+
"accessDenied": {
530+
"title": "Access Denied",
531+
"details": "You are not authorized to see this Managed Control Plane."
532+
},
533+
"backToWorkspaces": "Back to Workspaces"
534+
}
526535
}
527536
}

src/components/ControlPlanes/ControlPlaneCard/ControlPlaneCard.tsx

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -100,20 +100,21 @@ export const ControlPlaneCard = ({
100100
resourceName={controlPlane.metadata.name}
101101
resourceType={'managedcontrolplanes'}
102102
/>
103-
{showWarningBecauseOfDisabledSystemIdentityProvider && (
104-
<Infobox size="sm" variant="warning">
103+
{showWarningBecauseOfDisabledSystemIdentityProvider ? (
104+
<Infobox size="sm" variant="warning" noMargin>
105105
{t('ConnectButton.unsupportedIdP')}
106106
</Infobox>
107+
) : (
108+
<ConnectButton
109+
disabled={!isConnectButtonEnabled}
110+
controlPlaneName={name}
111+
projectName={projectName}
112+
workspaceName={workspace.metadata.name ?? ''}
113+
namespace={controlPlane.status?.access?.namespace ?? ''}
114+
secretName={controlPlane.status?.access?.name ?? ''}
115+
secretKey={controlPlane.status?.access?.key ?? ''}
116+
/>
107117
)}
108-
<ConnectButton
109-
disabled={!isConnectButtonEnabled}
110-
controlPlaneName={name}
111-
projectName={projectName}
112-
workspaceName={workspace.metadata.name ?? ''}
113-
namespace={controlPlane.status?.access?.namespace ?? ''}
114-
secretName={controlPlane.status?.access?.name ?? ''}
115-
secretKey={controlPlane.status?.access?.key ?? ''}
116-
/>
117118
</FlexBox>
118119
</FlexBox>
119120
</FlexBox>
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
.wrapper {
2+
display: flex;
3+
align-items: center;
4+
justify-content: center;
5+
flex-direction: column;
6+
width: 100%;
7+
height: 100%;
8+
}
9+
10+
.textAlignCenter {
11+
text-align: center;
12+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import type { PropsWithChildren, ReactNode, CSSProperties } from 'react';
2+
import cx from 'clsx';
3+
import styles from './Center.module.css';
4+
5+
export type CenterProps = PropsWithChildren<{
6+
className?: string;
7+
style?: CSSProperties;
8+
textAlignCenter?: boolean;
9+
}>;
10+
11+
export const Center = ({ children, className, style, textAlignCenter = true }: CenterProps): ReactNode => {
12+
const classes = cx(styles.wrapper, { [styles.textAlignCenter]: textAlignCenter }, className);
13+
14+
return (
15+
<div className={classes} style={style}>
16+
{children}
17+
</div>
18+
);
19+
};

src/components/Ui/Infobox/Infobox.module.css

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,11 @@
1717
margin-right: 1rem;
1818
}
1919

20+
.icon-sm {
21+
width: 1.25rem;
22+
height: 1.25rem;
23+
}
24+
2025
.content {
2126
flex-grow: 1;
2227
padding-right: 0.5rem;
@@ -29,8 +34,9 @@
2934
}
3035

3136
.size-sm {
32-
padding: 0.75rem 1rem;
33-
font-size: 0.875rem;
37+
padding: 0.5rem 0.875rem;
38+
font-size: 0.75rem;
39+
border-radius: 0.5rem;
3440
}
3541

3642
.size-md {
@@ -67,3 +73,7 @@
6773
color: var(--sapBackgroundColor);
6874
line-height: 1.2rem;
6975
}
76+
77+
.no-margin {
78+
margin-bottom: 0;
79+
}

src/components/Ui/Infobox/Infobox.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ interface LabelProps {
1212
fullWidth?: boolean;
1313
className?: string;
1414
icon?: string;
15+
noMargin?: boolean;
1516
}
1617

1718
const variantIcons = {
@@ -29,6 +30,7 @@ export const Infobox: React.FC<LabelProps> = ({
2930
fullWidth = false,
3031
className,
3132
icon,
33+
noMargin = false,
3234
}) => {
3335
const infoboxClasses = cx(
3436
styles.infobox,
@@ -41,6 +43,7 @@ export const Infobox: React.FC<LabelProps> = ({
4143
[styles['variant-warning']]: variant === 'warning',
4244
[styles['variant-danger']]: variant === 'danger',
4345
[styles['full-width']]: fullWidth,
46+
[styles['no-margin']]: noMargin,
4447
},
4548
className,
4649
);
@@ -49,7 +52,7 @@ export const Infobox: React.FC<LabelProps> = ({
4952

5053
return (
5154
<div className={infoboxClasses} id={id}>
52-
{iconName && <Icon name={iconName} className={styles.icon} />}
55+
{iconName && <Icon name={iconName} className={cx(styles.icon, { [styles['icon-sm']]: size === 'sm' })} />}
5356
<div className={styles.content}>{children}</div>
5457
</div>
5558
);

src/components/Ui/NotFoundBanner/NotFoundBanner.module.css

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,4 @@
66
.button {
77
margin-inline: auto;
88
margin-block: 2rem;
9-
}
9+
}

src/components/Ui/NotFoundBanner/NotFoundBanner.tsx

Lines changed: 17 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { Trans, useTranslation } from 'react-i18next';
55
import styles from './NotFoundBanner.module.css';
66
import { Button } from '@ui5/webcomponents-react';
77
import { useNavigate } from 'react-router-dom';
8+
import { Center } from '../Center/Center.tsx';
89

910
export interface NotFoundBannerProps {
1011
entityType: string;
@@ -14,19 +15,21 @@ export function NotFoundBanner({ entityType }: NotFoundBannerProps) {
1415
const navigate = useNavigate();
1516

1617
return (
17-
<IllustratedBanner
18-
illustrationName={IllustrationMessageType.PageNotFound}
19-
title={t('NotFoundBanner.titleMessage', { entityType })}
20-
subtitle={
21-
<div className={styles.subtitleContainer}>
22-
<span>
23-
<Trans i18nKey="NotFoundBanner.subtitleMessage" values={{ entityType }} />
24-
</span>
25-
<Button className={styles.button} onClick={() => navigate('/')}>
26-
{t('NotFoundBanner.navigateHome')}
27-
</Button>
28-
</div>
29-
}
30-
/>
18+
<Center>
19+
<IllustratedBanner
20+
illustrationName={IllustrationMessageType.PageNotFound}
21+
title={t('NotFoundBanner.titleMessage', { entityType })}
22+
subtitle={
23+
<div className={styles.subtitleContainer}>
24+
<span>
25+
<Trans i18nKey="NotFoundBanner.subtitleMessage" values={{ entityType }} />
26+
</span>
27+
<Button className={styles.button} onClick={() => navigate('/')}>
28+
{t('NotFoundBanner.navigateHome')}
29+
</Button>
30+
</div>
31+
}
32+
/>
33+
</Center>
3134
);
3235
}

src/lib/api/types/crossplane/CRDList.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,3 +40,8 @@ export type CRDResponse = {
4040
export const CRDRequest: Resource<CRDResponse> = {
4141
path: '/apis/apiextensions.k8s.io/v1/customresourcedefinitions',
4242
};
43+
44+
export const CRDRequestAuthCheck: Resource<CRDResponse> = {
45+
path: '/apis/apiextensions.k8s.io/v1/customresourcedefinitions',
46+
jq: '{kind: .kind}',
47+
};

src/lib/shared/McpContext.tsx

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@ interface Mcp {
1010
project: string;
1111
workspace: string;
1212
name: string;
13-
1413
secretNamespace?: string;
1514
secretName?: string;
1615
secretKey?: string;

0 commit comments

Comments
 (0)