Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
efbb271
refactor
lucasgoral Sep 23, 2025
35f0b08
Update CreateManagedControlPlaneWizardContainer.tsx
lucasgoral Sep 23, 2025
f75d6bf
fix
lucasgoral Sep 23, 2025
0ff1465
refactor
lucasgoral Sep 24, 2025
f277134
Update ComponentsSelectionProvider.tsx
lucasgoral Sep 24, 2025
c7700ce
refactor
lucasgoral Sep 24, 2025
815fe43
Update CreateManagedControlPlaneWizardContainer.tsx
lucasgoral Sep 24, 2025
628cdcd
fix
lucasgoral Sep 24, 2025
a7aab69
Update CreateManagedControlPlaneWizardContainer.tsx
lucasgoral Sep 24, 2025
442d006
fix
lucasgoral Sep 24, 2025
8faf51f
Update CreateManagedControlPlaneWizardContainer.tsx
lucasgoral Sep 24, 2025
b9cf6b4
fix
lucasgoral Sep 24, 2025
b63b121
Update CreateManagedControlPlaneWizardContainer.tsx
lucasgoral Sep 24, 2025
90a0a28
Update CreateManagedControlPlaneWizardContainer.tsx
lucasgoral Sep 24, 2025
4695a6a
Update CreateManagedControlPlaneWizardContainer.tsx
lucasgoral Sep 24, 2025
3989c8d
Update CreateManagedControlPlaneWizardContainer.tsx
lucasgoral Sep 24, 2025
1536e9f
Update CreateManagedControlPlaneWizardContainer.tsx
lucasgoral Sep 24, 2025
53a3ade
Update CreateManagedControlPlaneWizardContainer.tsx
lucasgoral Sep 24, 2025
5f47a34
Update useComponentsSelectionData.ts
lucasgoral Sep 25, 2025
3683515
Update en.json
lucasgoral Sep 25, 2025
c99e6c7
Update CreateManagedControlPlaneWizardContainer.tsx
lucasgoral Sep 25, 2025
488d486
refactor
lucasgoral Sep 25, 2025
a5bde05
fix
lucasgoral Sep 25, 2025
812e9a5
Update Infobox.tsx
lucasgoral Sep 25, 2025
0162546
fixes
lucasgoral Sep 25, 2025
50c8764
fix
lucasgoral Sep 25, 2025
e3cfe02
Update Infobox.module.css
lucasgoral Sep 25, 2025
79b641f
fix
lucasgoral Sep 25, 2025
469f024
Update Infobox.module.css
lucasgoral Sep 25, 2025
6c50182
fix
lucasgoral Sep 25, 2025
c6f6cd9
fix
lucasgoral Sep 26, 2025
bf81e6a
fix
lucasgoral Sep 26, 2025
162ad21
fix
lucasgoral Sep 26, 2025
b8d792e
fix
lucasgoral Sep 26, 2025
5186f90
Merge branch 'main' into edit-mcp-improvements
lucasgoral Sep 26, 2025
55f6220
Merge branch 'main' into edit-mcp-improvements
lucasgoral Sep 26, 2025
469fd24
Update src/spaces/mcp/pages/McpPage.tsx
lucasgoral Sep 26, 2025
935e375
Update src/components/ControlPlanes/ControlPlaneCard/ControlPlaneCard…
lucasgoral Sep 26, 2025
f18b211
Update en.json
lucasgoral Sep 26, 2025
62a9e56
Merge branch 'edit-mcp-improvements' of https://github.com/openmcp-pr…
lucasgoral Sep 26, 2025
d805a71
Merge branch 'main' into edit-mcp-improvements
lucasgoral Sep 26, 2025
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
16 changes: 11 additions & 5 deletions public/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -59,8 +59,9 @@
},
"ControlPlaneCard": {
"deleteConfirmationDialog": "MCP deletion triggered. The list will refresh automatically once completed.",
"editMCP": "Edit Managed Control Plane",
"deleteMCP": "Delete Managed Control Plane"
"editMCP": "Edit ManagedControlPlane",
"duplicateMCP": "Duplicate ManagedControlPlane",
"deleteMCP": "Delete ManagedControlPlane"

},
"ControlPlaneListAllWorkspaces": {
Expand Down Expand Up @@ -375,18 +376,23 @@
"createMCP": {
"dialogTitle": "Create Managed Control Plane",
"titleText": "Managed Control Plane Created Successfully!",
"subtitleText": "Your Managed Control Plane is being set up. It will be ready to use in just a few minutes. You can safely close this window."
"subtitleText": "Your Managed Control Plane is being set up. It will be ready to use in just a few minutes. You can safely close this window.",
"copySuffix": "-copy"
},
"editMCP": {
"dialogTitle": "Edit Managed Control Plane",
"titleText": "Managed Control Plane Updated Successfully!",
"subtitleText": "Your Managed Control Plane is being updated. It will be ready to use in just a few minutes. You can safely close this window."
"subtitleText": "Your Managed Control Plane is being updated. It will be ready to use in just a few minutes. You can safely close this window.",
"editComponents": "Edit components",
"duplicatingMCPInfo1": "Duplicating a <span>ManagedControlPlane</span> will only create a <span>ManagedControlPlane</span> with the same configuration. ",
"duplicatingMCPInfo2": "<b>It will NOT copy the managed resources inside</b>"
},
"componentsSelection": {
"selectComponents": "Select Components",
"selectedComponents": "Selected Components",
"pleaseSelectComponents": "Choose the components you want to add to your Managed Control Plane.",
"cannotLoad": "Cannot load components list"
"cannotLoad": "Cannot load components list",
"noComponentsFound": "No components found matching your search."
},
"Hints": {
"CrossplaneHint": {
Expand Down
10 changes: 5 additions & 5 deletions src/components/ComponentsSelection/ComponentsSelection.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ export const ComponentsSelection: React.FC<ComponentsSelectionProps> = ({
/>

<Grid>
<div data-layout-span="XL8 L8 M8 S8">
<div data-layout-span="XL7 L7 M7 S7">
{searchResults.length > 0 ? (
searchResults.map((component) => {
const providerDisabled = isProviderDisabled(component);
Expand Down Expand Up @@ -167,13 +167,13 @@ export const ComponentsSelection: React.FC<ComponentsSelectionProps> = ({
);
})
) : (
<Infobox fullWidth variant="success">
<Text>{t('componentsSelection.pleaseSelectComponents')}</Text>
<Infobox fullWidth variant="normal" icon="search">
<Text>{t('componentsSelection.noComponentsFound')}</Text>
</Infobox>
)}
</div>

<div data-layout-span="XL4 L4 M4 S4">
<div data-layout-span="XL5 L5 M5 S5">
{templateDefaultsError ? (
<div style={{ marginBottom: 8 }}>
<IllustratedError title={templateDefaultsError} compact />
Expand All @@ -191,7 +191,7 @@ export const ComponentsSelection: React.FC<ComponentsSelectionProps> = ({
))}
</List>
) : (
<Infobox fullWidth variant="success">
<Infobox variant="success" icon="add">
<Text>{t('componentsSelection.pleaseSelectComponents')}</Text>
</Infobox>
)}
Expand Down
133 changes: 8 additions & 125 deletions src/components/ComponentsSelection/ComponentsSelectionContainer.tsx
Original file line number Diff line number Diff line change
@@ -1,24 +1,16 @@
import React, { useEffect, useMemo, useState } from 'react';
import React from 'react';
import { ComponentsSelection } from './ComponentsSelection.tsx';

import IllustratedError from '../Shared/IllustratedError.tsx';
import { sortVersions } from '../../utils/componentsVersions.ts';

import { ListManagedComponents } from '../../lib/api/types/crate/listManagedComponents.ts';
import { useApiResource } from '../../lib/api/useApiResource.ts';
import Loading from '../Shared/Loading.tsx';
import { ComponentsListItem, removeComponents } from '../../lib/api/types/crate/createManagedControlPlane.ts';
import { ComponentsListItem } from '../../lib/api/types/crate/createManagedControlPlane.ts';
import { useTranslation } from 'react-i18next';
import { ManagedControlPlaneTemplate } from '../../lib/api/types/templates/mcpTemplate.ts';

export interface ComponentsSelectionProps {
componentsList: ComponentsListItem[];
setComponentsList: (components: ComponentsListItem[]) => void;
setInitialComponentsList: (components: ComponentsListItem[]) => void;
managedControlPlaneTemplate?: ManagedControlPlaneTemplate;
initialSelection?: Record<string, { isSelected: boolean; version: string }>;
isOnMcpPage?: boolean;
initializedComponents: React.RefObject<boolean>;
isLoading: boolean;
error: unknown;
templateDefaultsError?: string;
}

/**
Expand All @@ -34,124 +26,15 @@ export const getSelectedComponents = (components: ComponentsListItem[]) => {
});
};

type TemplateDefaultComponent = {
name: string;
version: string;
removable?: boolean;
versionChangeable?: boolean;
};

export const ComponentsSelectionContainer: React.FC<ComponentsSelectionProps> = ({
setComponentsList,
componentsList,
managedControlPlaneTemplate,
initialSelection,
isOnMcpPage,
setInitialComponentsList,
initializedComponents,
isLoading,
error,
templateDefaultsError,
}) => {
const {
data: availableManagedComponentsListData,
error,
isLoading,
} = useApiResource(ListManagedComponents(), undefined, !!isOnMcpPage);
const { t } = useTranslation();

const [templateDefaultsError, setTemplateDefaultsError] = useState<string | null>(null);
const defaultComponents = useMemo<TemplateDefaultComponent[]>(
() => managedControlPlaneTemplate?.spec?.spec?.components?.defaultComponents ?? [],
[managedControlPlaneTemplate],
);

useEffect(() => {
if (
initializedComponents.current ||
!availableManagedComponentsListData?.items ||
availableManagedComponentsListData.items.length === 0
) {
return;
}

const newComponentsList = availableManagedComponentsListData.items
.map((item) => {
const versions = sortVersions(item.status?.versions ?? []);
const template = defaultComponents.find((dc) => dc.name === (item.metadata?.name ?? ''));
const templateVersion = template?.version;
let selectedVersion = template
? templateVersion && versions.includes(templateVersion)
? templateVersion
: ''
: (versions[0] ?? '');
let isSelected = !!template;

const initSel = initialSelection?.[item.metadata?.name ?? ''];
if (initSel) {
// Override selection and version from initial selection if provided
isSelected = Boolean(initSel.isSelected);
selectedVersion = initSel.version && versions.includes(initSel.version) ? initSel.version : '';
}
return {
name: item.metadata?.name ?? '',
versions,
selectedVersion,
isSelected,
documentationUrl: '',
};
})
.filter((component) => !removeComponents.find((item) => item === component.name));
setInitialComponentsList(newComponentsList);
setComponentsList(newComponentsList);
initializedComponents.current = true;
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [setComponentsList, defaultComponents, initialSelection, availableManagedComponentsListData?.items]);

useEffect(() => {
const items = availableManagedComponentsListData?.items ?? [];
if (items.length === 0 || !defaultComponents.length) {
setTemplateDefaultsError(null);
return;
}

const errors: string[] = [];
defaultComponents.forEach((dc: TemplateDefaultComponent) => {
if (!dc?.name) return;
const item = items.find((it) => it.metadata?.name === dc.name);
if (!item) {
errors.push(`Component "${dc.name}" from template is not available.`);
return;
}
const versions: string[] = Array.isArray(item.status?.versions) ? (item.status?.versions as string[]) : [];
if (dc.version && !versions.includes(dc.version)) {
errors.push(`Component "${dc.name}" version "${dc.version}" from template is not available.`);
}
});

setTemplateDefaultsError(errors.length ? errors.join('\n') : null);
}, [availableManagedComponentsListData, defaultComponents]);

useEffect(() => {
if (!initializedComponents.current) return;
if (!defaultComponents?.length) return;
if (!componentsList?.length) return;
// If initialSelection is provided, do not auto-apply template defaults
if (initialSelection && Object.keys(initialSelection).length > 0) return;

const anySelected = componentsList.some((c) => c.isSelected);
if (anySelected) return;

const updated = componentsList.map((c) => {
const template = defaultComponents.find((dc) => dc.name === c.name);
if (!template) return c;
const templateVersion = template.version;
const selectedVersion =
templateVersion && Array.isArray(c.versions) && c.versions.includes(templateVersion) ? templateVersion : '';
return { ...c, isSelected: true, selectedVersion };
});

setComponentsList(updated);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [defaultComponents, componentsList, setComponentsList, initialSelection]);

if (isLoading) {
return <Loading />;
}
Expand Down
24 changes: 18 additions & 6 deletions src/components/ControlPlanes/ControlPlaneCard/ControlPlaneCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,11 +38,22 @@ interface Props {
projectName: string;
}

export function ControlPlaneCard({ controlPlane, workspace, projectName }: Props) {
type MCPWizardState = {
isOpen: boolean;
mode?: 'edit' | 'duplicate';
};
export const ControlPlaneCard = ({ controlPlane, workspace, projectName }: Props) => {
const [dialogDeleteMcpIsOpen, setDialogDeleteMcpIsOpen] = useState(false);
const toast = useToast();
const { t } = useTranslation();
const [isEditManagedControlPlaneWizardOpen, setIsEditManagedControlPlaneWizardOpen] = useState(false);
const [managedControlPlaneWizardState, setManagedControlPlaneWizardState] = useState<MCPWizardState>({
isOpen: false,
mode: undefined,
});

const handleIsManagedControlPlaneWizardOpen = (isOpen: boolean, mode?: 'edit' | 'duplicate') => {
setManagedControlPlaneWizardState({ isOpen, mode });
};
const { trigger: patchTrigger } = useApiResourceMutation<DeleteMCPType>(
PatchMCPResourceForDeletion(controlPlane.metadata.namespace, controlPlane.metadata.name),
);
Expand Down Expand Up @@ -85,7 +96,7 @@ export function ControlPlaneCard({ controlPlane, workspace, projectName }: Props
<ControlPlaneCardMenu
setDialogDeleteMcpIsOpen={setDialogDeleteMcpIsOpen}
isDeleteMcpButtonDisabled={controlPlane.status?.status === ReadyStatus.InDeletion}
setIsEditManagedControlPlaneWizardOpen={setIsEditManagedControlPlaneWizardOpen}
setIsEditManagedControlPlaneWizardOpen={handleIsManagedControlPlaneWizardOpen}
/>
<FlexBox direction="Row" justifyContent="SpaceBetween" alignItems="Center" gap={10}>
<YamlViewButtonWithLoader
Expand Down Expand Up @@ -130,11 +141,12 @@ export function ControlPlaneCard({ controlPlane, workspace, projectName }: Props
}}
/>
<EditManagedControlPlaneWizardDataLoader
isOpen={isEditManagedControlPlaneWizardOpen}
setIsOpen={setIsEditManagedControlPlaneWizardOpen}
isOpen={managedControlPlaneWizardState.isOpen}
setIsOpen={(isOpen) => handleIsManagedControlPlaneWizardOpen(isOpen)}
workspaceName={namespace}
resourceName={name}
mode={managedControlPlaneWizardState.mode}
/>
</>
);
}
};
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { useTranslation } from 'react-i18next';
type ControlPlanesListMenuProps = {
setDialogDeleteMcpIsOpen: Dispatch<SetStateAction<boolean>>;
isDeleteMcpButtonDisabled: boolean;
setIsEditManagedControlPlaneWizardOpen: Dispatch<SetStateAction<boolean>>;
setIsEditManagedControlPlaneWizardOpen: (isOpen: boolean, mode?: 'edit' | 'duplicate') => void;
};

export const ControlPlaneCardMenu: FC<ControlPlanesListMenuProps> = ({
Expand All @@ -34,7 +34,10 @@ export const ControlPlaneCardMenu: FC<ControlPlanesListMenuProps> = ({
onItemClick={(event) => {
const action = (event.detail.item as HTMLElement).dataset.action;
if (action === 'editMcp') {
setIsEditManagedControlPlaneWizardOpen(true);
setIsEditManagedControlPlaneWizardOpen(true, 'edit');
}
if (action === 'duplicateMcp') {
setIsEditManagedControlPlaneWizardOpen(true, 'duplicate');
}
if (action === 'deleteMcp') {
setDialogDeleteMcpIsOpen(true);
Expand All @@ -53,6 +56,7 @@ export const ControlPlaneCardMenu: FC<ControlPlanesListMenuProps> = ({
icon="delete"
disabled={isDeleteMcpButtonDisabled}
/>
<MenuItem key={'duplicate'} text={t('ControlPlaneCard.duplicateMCP')} data-action="duplicateMcp" icon="copy" />
<MenuItem
key={'edit'}
text={t('ControlPlaneCard.editMCP')}
Expand Down
58 changes: 39 additions & 19 deletions src/components/Ui/Infobox/Infobox.module.css
Original file line number Diff line number Diff line change
@@ -1,16 +1,35 @@
.infobox {
display: inline-block;
border: 1px solid;
margin: 1rem 0;
display: flex;
align-items: center;
border-radius: 1rem;
padding: 1rem;
margin: 0;
overflow: hidden;
box-shadow: 0 6px 20px rgba(0, 0, 0, 0.12);
margin-bottom: 2rem;
}

.icon {
flex-shrink: 0;
width: 1.5rem;
height: 1.5rem;
color: var(--sapBackgroundColor);
margin-right: 1rem;
}

.content {
flex-grow: 1;
padding-right: 0.5rem;
color: var(--sapBackgroundColor);
}

.full-width {
display: block;
margin: 0;
width: 100%;
margin: 1rem 0;
}

.size-sm {
padding: 0.5rem 0.75rem;
padding: 0.75rem 1rem;
font-size: 0.875rem;
}

Expand All @@ -21,29 +40,30 @@

.size-lg {
padding: 1.5rem 2rem;
font-size: 1.25rem;
font-size: 1.125rem;
}

.variant-normal {
border-color: var(--sapNeutralTextColor);
background-color: var(--sapNeutralBackground);
color: var(--sapNeutralTextColor);
background: var(--sapNeutralTextColor);
}

.variant-success {
border-color: var(--sapPositiveTextColor);
background-color: var(--sapPositiveBackground);
color: var(--sapPositiveTextColor);
background: var(--sapPositiveTextColor);
}

.variant-warning {
border-color: var(--sapCriticalTextColor);
background-color: var(--sapCriticalBackground);
color: var(--sapCriticalTextColor);
background: var(--sapCriticalTextColor);
}

.variant-danger {
border-color: var(--sapNegativeTextColor);
background-color: var(--sapErrorBackground);
color: var(--sapNegativeTextColor);
background: var(--sapErrorTextColor);
}

.content > *:not(:last-child) {
margin-bottom: 1rem;
}

.content > * {
color: var(--sapBackgroundColor);
line-height: 1.2rem;
}
Loading
Loading