From 5fb865982c8a6d707af3a2139a643b0d8edf1b52 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Goral?= Date: Tue, 18 Nov 2025 09:54:19 +0100 Subject: [PATCH 01/25] Update controlPlanes.ts --- src/lib/api/types/crate/controlPlanes.ts | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/src/lib/api/types/crate/controlPlanes.ts b/src/lib/api/types/crate/controlPlanes.ts index 1176614e..7ce612f9 100644 --- a/src/lib/api/types/crate/controlPlanes.ts +++ b/src/lib/api/types/crate/controlPlanes.ts @@ -12,6 +12,20 @@ export interface Metadata { }; } +export interface Subject { + kind: string; + name: string; +} + +export interface RoleBinding { + role: string; + subjects: Subject[]; +} + +export interface Authorization { + roleBindings: RoleBinding[]; +} + export interface ControlPlaneType { metadata: Metadata; spec: @@ -19,6 +33,7 @@ export interface ControlPlaneType { authentication: { enableSystemIdentityProvider?: boolean; }; + authorization?: Authorization; components: ControlPlaneComponentsType; } | undefined; @@ -85,6 +100,6 @@ export const ControlPlane = ( ): Resource => { return { path: `/apis/core.openmcp.cloud/v1alpha1/namespaces/project-${projectName}--ws-${workspaceName}/managedcontrolplanes/${controlPlaneName}`, - jq: '{ spec: .spec | {components}, metadata: .metadata | {name, namespace, creationTimestamp, annotations}, status: { conditions: [.status.conditions[] | {type: .type, status: .status, message: .message, reason: .reason, lastTransitionTime: .lastTransitionTime}], access: .status.components.authentication.access, status: .status.status }}', + jq: '{ spec: .spec | {components, authorization}, metadata: .metadata | {name, namespace, creationTimestamp, annotations}, status: { conditions: [.status.conditions[] | {type: .type, status: .status, message: .message, reason: .reason, lastTransitionTime: .lastTransitionTime}], access: .status.components.authentication.access, status: .status.status }}', }; }; From 0b62d18577c449329a8409054594d30526fbc537 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Goral?= Date: Tue, 18 Nov 2025 11:21:02 +0100 Subject: [PATCH 02/25] init --- src/lib/shared/McpContext.tsx | 4 +++- src/spaces/mcp/auth/useHasMcpAdminRights.ts | 24 +++++++++++++++++++++ src/spaces/mcp/pages/McpPage.tsx | 4 ++++ 3 files changed, 31 insertions(+), 1 deletion(-) create mode 100644 src/spaces/mcp/auth/useHasMcpAdminRights.ts diff --git a/src/lib/shared/McpContext.tsx b/src/lib/shared/McpContext.tsx index 8c46a06d..4c6fcebe 100644 --- a/src/lib/shared/McpContext.tsx +++ b/src/lib/shared/McpContext.tsx @@ -1,5 +1,5 @@ import { createContext, ReactNode, useContext } from 'react'; -import { ControlPlane as ManagedControlPlaneResource } from '../api/types/crate/controlPlanes.ts'; +import { ControlPlane as ManagedControlPlaneResource, RoleBinding } from '../api/types/crate/controlPlanes.ts'; import { ApiConfigProvider } from '../../components/Shared/k8s'; import { useApiResource } from '../api/useApiResource.ts'; import { GetKubeconfig } from '../api/types/crate/getKubeconfig.ts'; @@ -15,6 +15,7 @@ interface Mcp { secretName?: string; secretKey?: string; kubeconfig?: string; + roleBindings?: RoleBinding[]; } interface Props { @@ -44,6 +45,7 @@ export const McpContextProvider = ({ children, context }: Props) => { return <>; } context.kubeconfig = kubeconfig.data; + context.roleBindings = mcp.data?.spec?.authorization?.roleBindings; return {children}; }; diff --git a/src/spaces/mcp/auth/useHasMcpAdminRights.ts b/src/spaces/mcp/auth/useHasMcpAdminRights.ts new file mode 100644 index 00000000..950ff0a3 --- /dev/null +++ b/src/spaces/mcp/auth/useHasMcpAdminRights.ts @@ -0,0 +1,24 @@ +import { useAuthOnboarding } from '../../onboarding/auth/AuthContextOnboarding.tsx'; +import { useMcp } from '../../../lib/shared/McpContext.tsx'; + +export function useHasMcpAdminRights(): boolean { + const auth = useAuthOnboarding(); + const mcp = useMcp(); + const userEmail = auth.user?.email; + const mcpUsers = mcp.roleBindings ?? []; + + console.log('auth'); + console.log(auth); + console.log('mcp'); + console.log(mcp); + if (!userEmail) { + return false; + } + + const matchingRoleBinding = mcpUsers.find( + (roleBinding) => + Array.isArray(roleBinding.subjects) && roleBinding.subjects.some((subject) => subject?.name?.includes(userEmail)), + ); + + return matchingRoleBinding?.role === 'admin'; +} diff --git a/src/spaces/mcp/pages/McpPage.tsx b/src/spaces/mcp/pages/McpPage.tsx index b33e3bde..f5669c1d 100644 --- a/src/spaces/mcp/pages/McpPage.tsx +++ b/src/spaces/mcp/pages/McpPage.tsx @@ -41,6 +41,7 @@ import { GitRepositories } from '../../../components/ControlPlane/GitRepositorie import { Kustomizations } from '../../../components/ControlPlane/Kustomizations.tsx'; import { McpHeader } from '../components/McpHeader/McpHeader.tsx'; import { ComponentsDashboard } from '../components/ComponentsDashboard/ComponentsDashboard.tsx'; +import { useHasMcpAdminRights } from '../auth/useHasMcpAdminRights.ts'; export type McpPageSectionId = 'overview' | 'crossplane' | 'flux' | 'landscapers'; @@ -52,11 +53,14 @@ export default function McpPage() { undefined | WizardStepType >(undefined); const [selectedSectionId, setSelectedSectionId] = useState('overview'); + const { data: mcp, error, isLoading, } = useApiResource(ControlPlaneResource(projectName, workspaceName, controlPlaneName)); + const hasMCPAdminRights = useHasMcpAdminRights(); + console.log(hasMCPAdminRights); const displayName = mcp?.metadata?.annotations && typeof mcp.metadata.annotations === 'object' ? (mcp.metadata.annotations as Record)[DISPLAY_NAME_ANNOTATION] From cb189cc0dcc25af3be36fb0ad2f9d752e95a8b32 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Goral?= Date: Tue, 18 Nov 2025 11:52:56 +0100 Subject: [PATCH 03/25] Create useGetMcpUserRights.ts --- .../mcp/authorization/useGetMcpUserRights.ts | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 src/spaces/mcp/authorization/useGetMcpUserRights.ts diff --git a/src/spaces/mcp/authorization/useGetMcpUserRights.ts b/src/spaces/mcp/authorization/useGetMcpUserRights.ts new file mode 100644 index 00000000..2de5f22c --- /dev/null +++ b/src/spaces/mcp/authorization/useGetMcpUserRights.ts @@ -0,0 +1,24 @@ +import { useAuthOnboarding } from '../../onboarding/auth/AuthContextOnboarding.tsx'; +import { useMcp } from '../../../lib/shared/McpContext.tsx'; + +export const useGetMcpUserRights = (): { hasMcpAdminRights: boolean } => { + const auth = useAuthOnboarding(); + const mcp = useMcp(); + const userEmail = auth.user?.email; + const mcpUsers = mcp.roleBindings ?? []; + + console.log('auth'); + console.log(auth); + console.log('mcp'); + console.log(mcp); + + const matchingRoleBinding = userEmail + ? mcpUsers.find( + (roleBinding) => + Array.isArray(roleBinding.subjects) && + roleBinding.subjects.some((subject) => subject?.name?.includes(userEmail)), + ) + : undefined; + + return { hasMcpAdminRights: !!userEmail && matchingRoleBinding?.role === 'admin' }; +}; From a37efd7d593d50d869f57fbcb7ade1b90d9dd88d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Goral?= Date: Tue, 18 Nov 2025 13:38:21 +0100 Subject: [PATCH 04/25] fix --- src/spaces/mcp/authorization/useGetMcpUserRights.ts | 4 ++-- src/spaces/mcp/pages/McpPage.tsx | 7 ++++--- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/src/spaces/mcp/authorization/useGetMcpUserRights.ts b/src/spaces/mcp/authorization/useGetMcpUserRights.ts index 2de5f22c..5a6fd9b8 100644 --- a/src/spaces/mcp/authorization/useGetMcpUserRights.ts +++ b/src/spaces/mcp/authorization/useGetMcpUserRights.ts @@ -1,7 +1,7 @@ import { useAuthOnboarding } from '../../onboarding/auth/AuthContextOnboarding.tsx'; import { useMcp } from '../../../lib/shared/McpContext.tsx'; -export const useGetMcpUserRights = (): { hasMcpAdminRights: boolean } => { +export const useGetMcpUserRights = (): { isMcpAdmin: boolean } => { const auth = useAuthOnboarding(); const mcp = useMcp(); const userEmail = auth.user?.email; @@ -20,5 +20,5 @@ export const useGetMcpUserRights = (): { hasMcpAdminRights: boolean } => { ) : undefined; - return { hasMcpAdminRights: !!userEmail && matchingRoleBinding?.role === 'admin' }; + return { isMcpAdmin: !!userEmail && matchingRoleBinding?.role === 'admin' }; }; diff --git a/src/spaces/mcp/pages/McpPage.tsx b/src/spaces/mcp/pages/McpPage.tsx index f5669c1d..6e5d049f 100644 --- a/src/spaces/mcp/pages/McpPage.tsx +++ b/src/spaces/mcp/pages/McpPage.tsx @@ -41,7 +41,8 @@ import { GitRepositories } from '../../../components/ControlPlane/GitRepositorie import { Kustomizations } from '../../../components/ControlPlane/Kustomizations.tsx'; import { McpHeader } from '../components/McpHeader/McpHeader.tsx'; import { ComponentsDashboard } from '../components/ComponentsDashboard/ComponentsDashboard.tsx'; -import { useHasMcpAdminRights } from '../auth/useHasMcpAdminRights.ts'; + +import { useGetMcpUserRights } from '../authorization/useGetMcpUserRights.ts'; export type McpPageSectionId = 'overview' | 'crossplane' | 'flux' | 'landscapers'; @@ -59,8 +60,7 @@ export default function McpPage() { error, isLoading, } = useApiResource(ControlPlaneResource(projectName, workspaceName, controlPlaneName)); - const hasMCPAdminRights = useHasMcpAdminRights(); - console.log(hasMCPAdminRights); + const { isMcpAdmin } = useGetMcpUserRights(); const displayName = mcp?.metadata?.annotations && typeof mcp.metadata.annotations === 'object' ? (mcp.metadata.annotations as Record)[DISPLAY_NAME_ANNOTATION] @@ -73,6 +73,7 @@ export default function McpPage() { setIsEditManagedControlPlaneWizardOpen(false); setEditManagedControlPlaneWizardSection(undefined); }; + if (isLoading) { return ; } From 32b7f19d685245e42ef36f1950fa1a5a795e6b59 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Goral?= Date: Tue, 18 Nov 2025 14:11:31 +0100 Subject: [PATCH 05/25] refactor --- src/lib/shared/McpContext.tsx | 5 +- .../ManagedControlPlaneAuthorization.tsx | 11 + .../mcp/authorization/useGetMcpUserRights.ts | 6 +- src/spaces/mcp/pages/McpPage.tsx | 214 +++++++++--------- 4 files changed, 131 insertions(+), 105 deletions(-) create mode 100644 src/spaces/mcp/authorization/ManagedControlPlaneAuthorization.tsx diff --git a/src/lib/shared/McpContext.tsx b/src/lib/shared/McpContext.tsx index 4c6fcebe..ad6009dc 100644 --- a/src/lib/shared/McpContext.tsx +++ b/src/lib/shared/McpContext.tsx @@ -5,6 +5,7 @@ import { useApiResource } from '../api/useApiResource.ts'; import { GetKubeconfig } from '../api/types/crate/getKubeconfig.ts'; import { useAuthMcp } from '../../spaces/mcp/auth/AuthContextMcp.tsx'; import { BusyIndicator } from '@ui5/webcomponents-react'; +import { useGetMcpUserRights } from '../../spaces/mcp/authorization/useGetMcpUserRights.ts'; interface Mcp { project: string; @@ -71,7 +72,9 @@ function RequireDownstreamLogin(props: { children?: ReactNode }) { export function WithinManagedControlPlane({ children }: { children?: ReactNode }) { const auth = useAuthMcp(); - + const { isMcpAdmin } = useGetMcpUserRights(); + console.log('isMcpAdmin'); + console.log(isMcpAdmin); if (auth.isLoading) { return ; } diff --git a/src/spaces/mcp/authorization/ManagedControlPlaneAuthorization.tsx b/src/spaces/mcp/authorization/ManagedControlPlaneAuthorization.tsx new file mode 100644 index 00000000..8189dc04 --- /dev/null +++ b/src/spaces/mcp/authorization/ManagedControlPlaneAuthorization.tsx @@ -0,0 +1,11 @@ +import { ReactNode } from 'react'; +import { useGetMcpUserRights } from './useGetMcpUserRights.ts'; + +import IllustratedError from '../../../components/Shared/IllustratedError.tsx'; + +export const ManagedControlPlaneAuthorization = ({ children }: { children: ReactNode }) => { + const { isMcpMember } = useGetMcpUserRights(); + if (!isMcpMember) return ; + + return <>{children}; +}; diff --git a/src/spaces/mcp/authorization/useGetMcpUserRights.ts b/src/spaces/mcp/authorization/useGetMcpUserRights.ts index 5a6fd9b8..22ef7c18 100644 --- a/src/spaces/mcp/authorization/useGetMcpUserRights.ts +++ b/src/spaces/mcp/authorization/useGetMcpUserRights.ts @@ -1,7 +1,7 @@ import { useAuthOnboarding } from '../../onboarding/auth/AuthContextOnboarding.tsx'; import { useMcp } from '../../../lib/shared/McpContext.tsx'; -export const useGetMcpUserRights = (): { isMcpAdmin: boolean } => { +export const useGetMcpUserRights = (): { isMcpMember: boolean; isMcpAdmin: boolean } => { const auth = useAuthOnboarding(); const mcp = useMcp(); const userEmail = auth.user?.email; @@ -12,7 +12,7 @@ export const useGetMcpUserRights = (): { isMcpAdmin: boolean } => { console.log('mcp'); console.log(mcp); - const matchingRoleBinding = userEmail + const mcpUser = userEmail ? mcpUsers.find( (roleBinding) => Array.isArray(roleBinding.subjects) && @@ -20,5 +20,5 @@ export const useGetMcpUserRights = (): { isMcpAdmin: boolean } => { ) : undefined; - return { isMcpAdmin: !!userEmail && matchingRoleBinding?.role === 'admin' }; + return { isMcpMember: !!mcpUser, isMcpAdmin: !!userEmail && mcpUser?.role === 'admin' }; }; diff --git a/src/spaces/mcp/pages/McpPage.tsx b/src/spaces/mcp/pages/McpPage.tsx index 6e5d049f..dd84722a 100644 --- a/src/spaces/mcp/pages/McpPage.tsx +++ b/src/spaces/mcp/pages/McpPage.tsx @@ -41,8 +41,7 @@ import { GitRepositories } from '../../../components/ControlPlane/GitRepositorie import { Kustomizations } from '../../../components/ControlPlane/Kustomizations.tsx'; import { McpHeader } from '../components/McpHeader/McpHeader.tsx'; import { ComponentsDashboard } from '../components/ComponentsDashboard/ComponentsDashboard.tsx'; - -import { useGetMcpUserRights } from '../authorization/useGetMcpUserRights.ts'; +import { ManagedControlPlaneAuthorization } from '../authorization/ManagedControlPlaneAuthorization.tsx'; export type McpPageSectionId = 'overview' | 'crossplane' | 'flux' | 'landscapers'; @@ -60,7 +59,7 @@ export default function McpPage() { error, isLoading, } = useApiResource(ControlPlaneResource(projectName, workspaceName, controlPlaneName)); - const { isMcpAdmin } = useGetMcpUserRights(); + const displayName = mcp?.metadata?.annotations && typeof mcp.metadata.annotations === 'object' ? (mcp.metadata.annotations as Record)[DISPLAY_NAME_ANNOTATION] @@ -89,7 +88,6 @@ export default function McpPage() { const isComponentInstalledCrossplane = !!mcp.spec?.components.crossplane; const isComponentInstalledFlux = !!mcp.spec?.components.flux; const isComponentInstalledLandscaper = !!mcp.spec?.components.landscaper; - return ( - } - //TODO: actionBar should use Toolbar and ToolbarButton for consistent design - actionsBar={ -
- - - - - -
- } - /> - } - selectedSectionId={selectedSectionId} - headerArea={ - - - - } - onSelectedSectionChange={() => setSelectedSectionId(undefined)} - > - - - setSelectedSectionId(sectionId)} + + } + //TODO: actionBar should use Toolbar and ToolbarButton for consistent design + actionsBar={ +
+ + + + + +
+ } /> -
- - - - - - -
- - {isComponentInstalledCrossplane && ( - - - + } + selectedSectionId={selectedSectionId} + headerArea={ + + + + } + onSelectedSectionChange={() => setSelectedSectionId(undefined)} + > + + + setSelectedSectionId(sectionId)} + /> - - + + - + - )} - {isComponentInstalledFlux && ( - - - - - + + + + + + + + + + + )} + + {isComponentInstalledFlux && ( + + + + + + + + + )} + + {isComponentInstalledLandscaper && ( + - - - - )} - - {isComponentInstalledLandscaper && ( - - - - )} -
+ + + )} + +
From 2c331abb0f3862a6e2cd2bfb454ffb4fc65942d3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Goral?= Date: Tue, 18 Nov 2025 14:19:39 +0100 Subject: [PATCH 06/25] Update ManagedControlPlaneAuthorization.tsx --- .../authorization/ManagedControlPlaneAuthorization.tsx | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/spaces/mcp/authorization/ManagedControlPlaneAuthorization.tsx b/src/spaces/mcp/authorization/ManagedControlPlaneAuthorization.tsx index 8189dc04..64e9737d 100644 --- a/src/spaces/mcp/authorization/ManagedControlPlaneAuthorization.tsx +++ b/src/spaces/mcp/authorization/ManagedControlPlaneAuthorization.tsx @@ -2,10 +2,16 @@ import { ReactNode } from 'react'; import { useGetMcpUserRights } from './useGetMcpUserRights.ts'; import IllustratedError from '../../../components/Shared/IllustratedError.tsx'; +import { FlexBox } from '@ui5/webcomponents-react'; export const ManagedControlPlaneAuthorization = ({ children }: { children: ReactNode }) => { const { isMcpMember } = useGetMcpUserRights(); - if (!isMcpMember) return ; + if (!isMcpMember) + return ( + + + + ); return <>{children}; }; From 167641a67b066bcda2ca40cd16e85d6000d135b6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Goral?= Date: Tue, 18 Nov 2025 14:32:13 +0100 Subject: [PATCH 07/25] fix --- .../ManagedControlPlaneAuthorization.tsx | 21 ++++++++++++++----- src/spaces/mcp/pages/McpPage.tsx | 2 +- 2 files changed, 17 insertions(+), 6 deletions(-) diff --git a/src/spaces/mcp/authorization/ManagedControlPlaneAuthorization.tsx b/src/spaces/mcp/authorization/ManagedControlPlaneAuthorization.tsx index 64e9737d..78f137b5 100644 --- a/src/spaces/mcp/authorization/ManagedControlPlaneAuthorization.tsx +++ b/src/spaces/mcp/authorization/ManagedControlPlaneAuthorization.tsx @@ -2,15 +2,26 @@ import { ReactNode } from 'react'; import { useGetMcpUserRights } from './useGetMcpUserRights.ts'; import IllustratedError from '../../../components/Shared/IllustratedError.tsx'; -import { FlexBox } from '@ui5/webcomponents-react'; +import { FlexBox, ObjectPageSection } from '@ui5/webcomponents-react'; +import { ControlPlaneType } from '../../../lib/api/types/crate/controlPlanes.ts'; -export const ManagedControlPlaneAuthorization = ({ children }: { children: ReactNode }) => { +export interface ManagedControlPlaneAuthorization { + mcp: ControlPlaneType; + children: ReactNode; +} +export const ManagedControlPlaneAuthorization = ({ children, mcp }: ManagedControlPlaneAuthorization) => { + const createdBy = mcp?.metadata?.annotations?.['openmcp.cloud/created-by']; const { isMcpMember } = useGetMcpUserRights(); if (!isMcpMember) return ( - - - + + + + + ); return <>{children}; diff --git a/src/spaces/mcp/pages/McpPage.tsx b/src/spaces/mcp/pages/McpPage.tsx index dd84722a..5d433e9c 100644 --- a/src/spaces/mcp/pages/McpPage.tsx +++ b/src/spaces/mcp/pages/McpPage.tsx @@ -98,7 +98,7 @@ export default function McpPage() { > - + Date: Tue, 18 Nov 2025 14:56:42 +0100 Subject: [PATCH 08/25] fix --- public/locales/en.json | 9 ++++ .../ManagedControlPlaneAuthorization.tsx | 47 ++++++++++++++----- 2 files changed, 44 insertions(+), 12 deletions(-) diff --git a/public/locales/en.json b/public/locales/en.json index f6fd2513..3f8991c9 100644 --- a/public/locales/en.json +++ b/public/locales/en.json @@ -507,5 +507,14 @@ "addMembersButton0": "Add members", "addMembersButton1": "Add member", "addMembersButtonN": "Add {{count}} members" + }, + "mcp": { + "authorization": { + "accessDenied": { + "title": "Access Denied", + "details": "You must be a member to access this Managed Control Plane. Please contact {{createdBy}} to request access.", + "administrator": "an administrator" + } + } } } diff --git a/src/spaces/mcp/authorization/ManagedControlPlaneAuthorization.tsx b/src/spaces/mcp/authorization/ManagedControlPlaneAuthorization.tsx index 78f137b5..cb2642b7 100644 --- a/src/spaces/mcp/authorization/ManagedControlPlaneAuthorization.tsx +++ b/src/spaces/mcp/authorization/ManagedControlPlaneAuthorization.tsx @@ -1,27 +1,50 @@ import { ReactNode } from 'react'; import { useGetMcpUserRights } from './useGetMcpUserRights.ts'; +import { useTranslation } from 'react-i18next'; import IllustratedError from '../../../components/Shared/IllustratedError.tsx'; -import { FlexBox, ObjectPageSection } from '@ui5/webcomponents-react'; +import { Button, FlexBox } from '@ui5/webcomponents-react'; import { ControlPlaneType } from '../../../lib/api/types/crate/controlPlanes.ts'; +import { generatePath, useNavigate, useParams } from 'react-router-dom'; +import { Routes } from '../../../Routes.ts'; -export interface ManagedControlPlaneAuthorization { +export interface ManagedControlPlaneAuthorizationProps { mcp: ControlPlaneType; children: ReactNode; } -export const ManagedControlPlaneAuthorization = ({ children, mcp }: ManagedControlPlaneAuthorization) => { - const createdBy = mcp?.metadata?.annotations?.['openmcp.cloud/created-by']; +export const ManagedControlPlaneAuthorization = ({ children, mcp }: ManagedControlPlaneAuthorizationProps) => { + const { t } = useTranslation(); + const navigate = useNavigate(); + const { projectName, workspaceName } = useParams(); + const onBack = () => { + if (workspaceName) { + navigate( + generatePath(Routes.Project, { + projectName: projectName ?? '', + }), + ); + } + }; + const createdBy = + mcp?.metadata?.annotations?.['openmcp.cloud/created-by']?.split(':')[1] || + t('mcp.authorization.accessDenied.administrator'); const { isMcpMember } = useGetMcpUserRights(); if (!isMcpMember) return ( - - - - - + + + + ); return <>{children}; From dc19d5a2d514b7e1cfd1a9a24e907d87bd07d78e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Goral?= Date: Tue, 18 Nov 2025 15:05:48 +0100 Subject: [PATCH 09/25] fix --- public/locales/en.json | 3 ++- .../ManagedControlPlaneAuthorization.module.css | 4 ++++ .../authorization/ManagedControlPlaneAuthorization.tsx | 10 +++------- 3 files changed, 9 insertions(+), 8 deletions(-) create mode 100644 src/spaces/mcp/authorization/ManagedControlPlaneAuthorization.module.css diff --git a/public/locales/en.json b/public/locales/en.json index 3f8991c9..01382617 100644 --- a/public/locales/en.json +++ b/public/locales/en.json @@ -514,7 +514,8 @@ "title": "Access Denied", "details": "You must be a member to access this Managed Control Plane. Please contact {{createdBy}} to request access.", "administrator": "an administrator" - } + }, + "backToWorkspaces": "Back to workspaces" } } } diff --git a/src/spaces/mcp/authorization/ManagedControlPlaneAuthorization.module.css b/src/spaces/mcp/authorization/ManagedControlPlaneAuthorization.module.css new file mode 100644 index 00000000..7303f2ab --- /dev/null +++ b/src/spaces/mcp/authorization/ManagedControlPlaneAuthorization.module.css @@ -0,0 +1,4 @@ +.container { + height: 100%; + width: 100%; +} diff --git a/src/spaces/mcp/authorization/ManagedControlPlaneAuthorization.tsx b/src/spaces/mcp/authorization/ManagedControlPlaneAuthorization.tsx index cb2642b7..39b68abb 100644 --- a/src/spaces/mcp/authorization/ManagedControlPlaneAuthorization.tsx +++ b/src/spaces/mcp/authorization/ManagedControlPlaneAuthorization.tsx @@ -7,6 +7,7 @@ import { Button, FlexBox } from '@ui5/webcomponents-react'; import { ControlPlaneType } from '../../../lib/api/types/crate/controlPlanes.ts'; import { generatePath, useNavigate, useParams } from 'react-router-dom'; import { Routes } from '../../../Routes.ts'; +import styles from './ManagedControlPlaneAuthorization.module.css'; export interface ManagedControlPlaneAuthorizationProps { mcp: ControlPlaneType; @@ -31,18 +32,13 @@ export const ManagedControlPlaneAuthorization = ({ children, mcp }: ManagedContr const { isMcpMember } = useGetMcpUserRights(); if (!isMcpMember) return ( - + ); From 61e524b265365a058c44ac2951d5baffffd03563 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Goral?= Date: Tue, 18 Nov 2025 15:30:51 +0100 Subject: [PATCH 10/25] fixes --- .../ControlPlaneCard/ControlPlaneCard.tsx | 23 ++++++++++--------- src/components/Ui/Infobox/Infobox.module.css | 9 ++++++-- src/components/Ui/Infobox/Infobox.tsx | 3 +++ .../types/crate/createManagedControlPlane.ts | 2 +- 4 files changed, 23 insertions(+), 14 deletions(-) diff --git a/src/components/ControlPlanes/ControlPlaneCard/ControlPlaneCard.tsx b/src/components/ControlPlanes/ControlPlaneCard/ControlPlaneCard.tsx index 0a9e11e0..98a9bffe 100644 --- a/src/components/ControlPlanes/ControlPlaneCard/ControlPlaneCard.tsx +++ b/src/components/ControlPlanes/ControlPlaneCard/ControlPlaneCard.tsx @@ -100,20 +100,21 @@ export const ControlPlaneCard = ({ resourceName={controlPlane.metadata.name} resourceType={'managedcontrolplanes'} /> - {showWarningBecauseOfDisabledSystemIdentityProvider && ( - + {showWarningBecauseOfDisabledSystemIdentityProvider ? ( + {t('ConnectButton.unsupportedIdP')} + ) : ( + )} - diff --git a/src/components/Ui/Infobox/Infobox.module.css b/src/components/Ui/Infobox/Infobox.module.css index 6d1b8a0d..e1d20343 100644 --- a/src/components/Ui/Infobox/Infobox.module.css +++ b/src/components/Ui/Infobox/Infobox.module.css @@ -29,8 +29,9 @@ } .size-sm { - padding: 0.75rem 1rem; - font-size: 0.875rem; + padding: 0.5rem 0.75rem; + font-size: 0.75rem; + border-radius: 0.5rem; } .size-md { @@ -67,3 +68,7 @@ color: var(--sapBackgroundColor); line-height: 1.2rem; } + +.no-margin { + margin-bottom: 0; +} diff --git a/src/components/Ui/Infobox/Infobox.tsx b/src/components/Ui/Infobox/Infobox.tsx index e0eb4e3f..c5a9bbce 100644 --- a/src/components/Ui/Infobox/Infobox.tsx +++ b/src/components/Ui/Infobox/Infobox.tsx @@ -12,6 +12,7 @@ interface LabelProps { fullWidth?: boolean; className?: string; icon?: string; + noMargin?: boolean; } const variantIcons = { @@ -29,6 +30,7 @@ export const Infobox: React.FC = ({ fullWidth = false, className, icon, + noMargin = false, }) => { const infoboxClasses = cx( styles.infobox, @@ -41,6 +43,7 @@ export const Infobox: React.FC = ({ [styles['variant-warning']]: variant === 'warning', [styles['variant-danger']]: variant === 'danger', [styles['full-width']]: fullWidth, + [styles['no-margin']]: noMargin, }, className, ); diff --git a/src/lib/api/types/crate/createManagedControlPlane.ts b/src/lib/api/types/crate/createManagedControlPlane.ts index 25873ef4..ab63368e 100644 --- a/src/lib/api/types/crate/createManagedControlPlane.ts +++ b/src/lib/api/types/crate/createManagedControlPlane.ts @@ -126,7 +126,7 @@ export const CreateManagedControlPlane = ( }, }, spec: { - authentication: { enableSystemIdentityProvider: true }, + authentication: { enableSystemIdentityProvider: false }, components: { ...selectedComponents, apiServer: { type: 'GardenerDedicated' }, From ec476674b50b7edc57443bb964d533953536699d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Goral?= Date: Wed, 19 Nov 2025 08:56:56 +0100 Subject: [PATCH 11/25] Update createManagedControlPlane.ts --- src/lib/api/types/crate/createManagedControlPlane.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/api/types/crate/createManagedControlPlane.ts b/src/lib/api/types/crate/createManagedControlPlane.ts index ab63368e..25873ef4 100644 --- a/src/lib/api/types/crate/createManagedControlPlane.ts +++ b/src/lib/api/types/crate/createManagedControlPlane.ts @@ -126,7 +126,7 @@ export const CreateManagedControlPlane = ( }, }, spec: { - authentication: { enableSystemIdentityProvider: false }, + authentication: { enableSystemIdentityProvider: true }, components: { ...selectedComponents, apiServer: { type: 'GardenerDedicated' }, From 7ea2c60723ec840b419266e4897d0855125a32e0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Goral?= Date: Wed, 19 Nov 2025 08:57:03 +0100 Subject: [PATCH 12/25] fix --- src/components/Ui/Infobox/Infobox.module.css | 7 ++++++- src/components/Ui/Infobox/Infobox.tsx | 2 +- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/src/components/Ui/Infobox/Infobox.module.css b/src/components/Ui/Infobox/Infobox.module.css index e1d20343..5f5312b5 100644 --- a/src/components/Ui/Infobox/Infobox.module.css +++ b/src/components/Ui/Infobox/Infobox.module.css @@ -17,6 +17,11 @@ margin-right: 1rem; } +.icon-sm { + width: 1.25rem; + height: 1.25rem; +} + .content { flex-grow: 1; padding-right: 0.5rem; @@ -29,7 +34,7 @@ } .size-sm { - padding: 0.5rem 0.75rem; + padding: 0.5rem 0.875rem; font-size: 0.75rem; border-radius: 0.5rem; } diff --git a/src/components/Ui/Infobox/Infobox.tsx b/src/components/Ui/Infobox/Infobox.tsx index c5a9bbce..743e7bf7 100644 --- a/src/components/Ui/Infobox/Infobox.tsx +++ b/src/components/Ui/Infobox/Infobox.tsx @@ -52,7 +52,7 @@ export const Infobox: React.FC = ({ return (
- {iconName && } + {iconName && }
{children}
); From 31f683db8d750e03f0025282879b93f33d3d0e23 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Goral?= Date: Wed, 19 Nov 2025 15:22:26 +0100 Subject: [PATCH 13/25] fix --- public/locales/en.json | 6 +++- src/components/Ui/Center/Center.module.css | 12 +++++++ src/components/Ui/Center/Center.tsx | 19 +++++++++++ .../NotFoundBanner/NotFoundBanner.module.css | 2 +- .../Ui/NotFoundBanner/NotFoundBanner.tsx | 33 ++++++++++--------- src/lib/api/types/crate/controlPlanes.ts | 2 +- src/lib/shared/McpContext.tsx | 4 +-- ...anagedControlPlaneAuthorization.module.css | 4 --- .../ManagedControlPlaneAuthorization.tsx | 25 +++++++++++--- 9 files changed, 77 insertions(+), 30 deletions(-) create mode 100644 src/components/Ui/Center/Center.module.css create mode 100644 src/components/Ui/Center/Center.tsx delete mode 100644 src/spaces/mcp/authorization/ManagedControlPlaneAuthorization.module.css diff --git a/public/locales/en.json b/public/locales/en.json index 01382617..1c9fb968 100644 --- a/public/locales/en.json +++ b/public/locales/en.json @@ -515,7 +515,11 @@ "details": "You must be a member to access this Managed Control Plane. Please contact {{createdBy}} to request access.", "administrator": "an administrator" }, - "backToWorkspaces": "Back to workspaces" + "customIdp": { + "title": "Access Denied", + "details": "non-default Identity Providers are not supported yet" + }, + "backToWorkspaces": "Back to Workspaces" } } } diff --git a/src/components/Ui/Center/Center.module.css b/src/components/Ui/Center/Center.module.css new file mode 100644 index 00000000..92996e3b --- /dev/null +++ b/src/components/Ui/Center/Center.module.css @@ -0,0 +1,12 @@ +.root { + display: flex; + align-items: center; + justify-content: center; + flex-direction: column; + width: 100%; + height: 100%; +} + +.textAlignCenter { + text-align: center; +} diff --git a/src/components/Ui/Center/Center.tsx b/src/components/Ui/Center/Center.tsx new file mode 100644 index 00000000..47de03e8 --- /dev/null +++ b/src/components/Ui/Center/Center.tsx @@ -0,0 +1,19 @@ +import type { PropsWithChildren, ReactNode, CSSProperties } from 'react'; +import cx from 'clsx'; +import styles from './Center.module.css'; + +export type CenterProps = PropsWithChildren<{ + className?: string; + style?: CSSProperties; + textAlignCenter?: boolean; +}>; + +export const Center = ({ children, className, style, textAlignCenter = true }: CenterProps): ReactNode => { + const classes = cx(styles.root, { [styles.textAlignCenter]: textAlignCenter }, className); + + return ( +
+ {children} +
+ ); +}; diff --git a/src/components/Ui/NotFoundBanner/NotFoundBanner.module.css b/src/components/Ui/NotFoundBanner/NotFoundBanner.module.css index f017a659..0fbd6379 100644 --- a/src/components/Ui/NotFoundBanner/NotFoundBanner.module.css +++ b/src/components/Ui/NotFoundBanner/NotFoundBanner.module.css @@ -6,4 +6,4 @@ .button { margin-inline: auto; margin-block: 2rem; -} \ No newline at end of file +} diff --git a/src/components/Ui/NotFoundBanner/NotFoundBanner.tsx b/src/components/Ui/NotFoundBanner/NotFoundBanner.tsx index 8e2e7741..3b5c0b2b 100644 --- a/src/components/Ui/NotFoundBanner/NotFoundBanner.tsx +++ b/src/components/Ui/NotFoundBanner/NotFoundBanner.tsx @@ -3,8 +3,9 @@ import IllustrationMessageType from '@ui5/webcomponents-fiori/dist/types/Illustr import { Trans, useTranslation } from 'react-i18next'; import styles from './NotFoundBanner.module.css'; -import { Button } from '@ui5/webcomponents-react'; +import { Button, FlexBox } from '@ui5/webcomponents-react'; import { useNavigate } from 'react-router-dom'; +import { Center } from '../Center/Center.tsx'; export interface NotFoundBannerProps { entityType: string; @@ -14,19 +15,21 @@ export function NotFoundBanner({ entityType }: NotFoundBannerProps) { const navigate = useNavigate(); return ( - - - - - - - } - /> +
+ + + + + + + } + /> +
); } diff --git a/src/lib/api/types/crate/controlPlanes.ts b/src/lib/api/types/crate/controlPlanes.ts index 7ce612f9..1dd41f15 100644 --- a/src/lib/api/types/crate/controlPlanes.ts +++ b/src/lib/api/types/crate/controlPlanes.ts @@ -100,6 +100,6 @@ export const ControlPlane = ( ): Resource => { return { path: `/apis/core.openmcp.cloud/v1alpha1/namespaces/project-${projectName}--ws-${workspaceName}/managedcontrolplanes/${controlPlaneName}`, - jq: '{ spec: .spec | {components, authorization}, metadata: .metadata | {name, namespace, creationTimestamp, annotations}, status: { conditions: [.status.conditions[] | {type: .type, status: .status, message: .message, reason: .reason, lastTransitionTime: .lastTransitionTime}], access: .status.components.authentication.access, status: .status.status }}', + jq: '{ spec: .spec | {components, authorization, authentication}, metadata: .metadata | {name, namespace, creationTimestamp, annotations}, status: { conditions: [.status.conditions[] | {type: .type, status: .status, message: .message, reason: .reason, lastTransitionTime: .lastTransitionTime}], access: .status.components.authentication.access, status: .status.status }}', }; }; diff --git a/src/lib/shared/McpContext.tsx b/src/lib/shared/McpContext.tsx index ad6009dc..4cca0efb 100644 --- a/src/lib/shared/McpContext.tsx +++ b/src/lib/shared/McpContext.tsx @@ -72,9 +72,7 @@ function RequireDownstreamLogin(props: { children?: ReactNode }) { export function WithinManagedControlPlane({ children }: { children?: ReactNode }) { const auth = useAuthMcp(); - const { isMcpAdmin } = useGetMcpUserRights(); - console.log('isMcpAdmin'); - console.log(isMcpAdmin); + if (auth.isLoading) { return ; } diff --git a/src/spaces/mcp/authorization/ManagedControlPlaneAuthorization.module.css b/src/spaces/mcp/authorization/ManagedControlPlaneAuthorization.module.css deleted file mode 100644 index 7303f2ab..00000000 --- a/src/spaces/mcp/authorization/ManagedControlPlaneAuthorization.module.css +++ /dev/null @@ -1,4 +0,0 @@ -.container { - height: 100%; - width: 100%; -} diff --git a/src/spaces/mcp/authorization/ManagedControlPlaneAuthorization.tsx b/src/spaces/mcp/authorization/ManagedControlPlaneAuthorization.tsx index 39b68abb..11b250a8 100644 --- a/src/spaces/mcp/authorization/ManagedControlPlaneAuthorization.tsx +++ b/src/spaces/mcp/authorization/ManagedControlPlaneAuthorization.tsx @@ -3,11 +3,12 @@ import { useGetMcpUserRights } from './useGetMcpUserRights.ts'; import { useTranslation } from 'react-i18next'; import IllustratedError from '../../../components/Shared/IllustratedError.tsx'; -import { Button, FlexBox } from '@ui5/webcomponents-react'; +import { Button } from '@ui5/webcomponents-react'; import { ControlPlaneType } from '../../../lib/api/types/crate/controlPlanes.ts'; import { generatePath, useNavigate, useParams } from 'react-router-dom'; import { Routes } from '../../../Routes.ts'; -import styles from './ManagedControlPlaneAuthorization.module.css'; + +import { Center } from '../../../components/Ui/Center/Center.tsx'; export interface ManagedControlPlaneAuthorizationProps { mcp: ControlPlaneType; @@ -29,18 +30,32 @@ export const ManagedControlPlaneAuthorization = ({ children, mcp }: ManagedContr const createdBy = mcp?.metadata?.annotations?.['openmcp.cloud/created-by']?.split(':')[1] || t('mcp.authorization.accessDenied.administrator'); + const isSystemIdentityProviderEnabled = Boolean(mcp.spec?.authentication?.enableSystemIdentityProvider); const { isMcpMember } = useGetMcpUserRights(); + if (!isSystemIdentityProviderEnabled) + return ( +
+ + +
+ ); + if (!isMcpMember) return ( - +
- - +
); return <>{children}; From 298e43359e4049221753699070a6918d5f409210 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Goral?= Date: Thu, 20 Nov 2025 09:00:11 +0100 Subject: [PATCH 14/25] fix --- src/components/Ui/NotFoundBanner/NotFoundBanner.tsx | 2 +- src/lib/shared/McpContext.tsx | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/src/components/Ui/NotFoundBanner/NotFoundBanner.tsx b/src/components/Ui/NotFoundBanner/NotFoundBanner.tsx index 3b5c0b2b..4eff5a38 100644 --- a/src/components/Ui/NotFoundBanner/NotFoundBanner.tsx +++ b/src/components/Ui/NotFoundBanner/NotFoundBanner.tsx @@ -3,7 +3,7 @@ import IllustrationMessageType from '@ui5/webcomponents-fiori/dist/types/Illustr import { Trans, useTranslation } from 'react-i18next'; import styles from './NotFoundBanner.module.css'; -import { Button, FlexBox } from '@ui5/webcomponents-react'; +import { Button } from '@ui5/webcomponents-react'; import { useNavigate } from 'react-router-dom'; import { Center } from '../Center/Center.tsx'; diff --git a/src/lib/shared/McpContext.tsx b/src/lib/shared/McpContext.tsx index 4cca0efb..4c6fcebe 100644 --- a/src/lib/shared/McpContext.tsx +++ b/src/lib/shared/McpContext.tsx @@ -5,7 +5,6 @@ import { useApiResource } from '../api/useApiResource.ts'; import { GetKubeconfig } from '../api/types/crate/getKubeconfig.ts'; import { useAuthMcp } from '../../spaces/mcp/auth/AuthContextMcp.tsx'; import { BusyIndicator } from '@ui5/webcomponents-react'; -import { useGetMcpUserRights } from '../../spaces/mcp/authorization/useGetMcpUserRights.ts'; interface Mcp { project: string; From 9fef1f3fb14c9b0fac30b62b4c8a79bdbf7bf835 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Goral?= Date: Thu, 20 Nov 2025 13:38:32 +0100 Subject: [PATCH 15/25] fixes --- public/locales/en.json | 7 +---- src/components/Projects/ProjectsList.tsx | 6 ++++ src/lib/shared/McpContext.tsx | 1 + .../ManagedControlPlaneAuthorization.tsx | 29 +++++++------------ 4 files changed, 18 insertions(+), 25 deletions(-) diff --git a/public/locales/en.json b/public/locales/en.json index 1c9fb968..ad122a5c 100644 --- a/public/locales/en.json +++ b/public/locales/en.json @@ -512,12 +512,7 @@ "authorization": { "accessDenied": { "title": "Access Denied", - "details": "You must be a member to access this Managed Control Plane. Please contact {{createdBy}} to request access.", - "administrator": "an administrator" - }, - "customIdp": { - "title": "Access Denied", - "details": "non-default Identity Providers are not supported yet" + "details": "You are not authorized to see this Managed Control Plane." }, "backToWorkspaces": "Back to Workspaces" } diff --git a/src/components/Projects/ProjectsList.tsx b/src/components/Projects/ProjectsList.tsx index 39f7e2e1..fe781ee3 100644 --- a/src/components/Projects/ProjectsList.tsx +++ b/src/components/Projects/ProjectsList.tsx @@ -12,6 +12,7 @@ import { t } from 'i18next'; import { YamlViewButton } from '../Yaml/YamlViewButton.tsx'; import { useMemo } from 'react'; import { ProjectsListItemMenu } from './ProjectsListItemMenu.tsx'; +import { CRDRequest } from '../../lib/api/types/crossplane/CRDList.ts'; type ProjectListRow = { projectName: string; @@ -23,6 +24,11 @@ export default function ProjectsList() { const { data, error } = useApiResource(ListProjectNames, { refreshInterval: 3000, }); + const { error: crdError, data: crdData } = useApiResource(CRDRequest, undefined); + console.log('crdError projects'); + console.log(crdError); + console.log('crdData projects'); + console.log(crdData); const stabilizedData = useMemo( () => data?.map((projectName) => { diff --git a/src/lib/shared/McpContext.tsx b/src/lib/shared/McpContext.tsx index 4c6fcebe..7cb12bf8 100644 --- a/src/lib/shared/McpContext.tsx +++ b/src/lib/shared/McpContext.tsx @@ -5,6 +5,7 @@ import { useApiResource } from '../api/useApiResource.ts'; import { GetKubeconfig } from '../api/types/crate/getKubeconfig.ts'; import { useAuthMcp } from '../../spaces/mcp/auth/AuthContextMcp.tsx'; import { BusyIndicator } from '@ui5/webcomponents-react'; +import { CRDRequest } from '../api/types/crossplane/CRDList.ts'; interface Mcp { project: string; diff --git a/src/spaces/mcp/authorization/ManagedControlPlaneAuthorization.tsx b/src/spaces/mcp/authorization/ManagedControlPlaneAuthorization.tsx index 11b250a8..a7f96274 100644 --- a/src/spaces/mcp/authorization/ManagedControlPlaneAuthorization.tsx +++ b/src/spaces/mcp/authorization/ManagedControlPlaneAuthorization.tsx @@ -9,6 +9,8 @@ import { generatePath, useNavigate, useParams } from 'react-router-dom'; import { Routes } from '../../../Routes.ts'; import { Center } from '../../../components/Ui/Center/Center.tsx'; +import { CRDRequest } from '../../../lib/api/types/crossplane/CRDList.ts'; +import { useApiResource } from '../../../lib/api/useApiResource.ts'; export interface ManagedControlPlaneAuthorizationProps { mcp: ControlPlaneType; @@ -27,30 +29,19 @@ export const ManagedControlPlaneAuthorization = ({ children, mcp }: ManagedContr ); } }; - const createdBy = - mcp?.metadata?.annotations?.['openmcp.cloud/created-by']?.split(':')[1] || - t('mcp.authorization.accessDenied.administrator'); - const isSystemIdentityProviderEnabled = Boolean(mcp.spec?.authentication?.enableSystemIdentityProvider); - const { isMcpMember } = useGetMcpUserRights(); - if (!isSystemIdentityProviderEnabled) - return ( -
- - -
- ); - if (!isMcpMember) + const { error: crdError, data: crdData } = useApiResource(CRDRequest); + console.log('crdError:'); + console.log(crdError?.status); + console.log('crdData:'); + console.log(crdData); + const isUserNotAuthorized = crdError?.status === 403 || crdError?.status === 401; + if (isUserNotAuthorized) return (