Skip to content

Commit e42b3ee

Browse files
committed
Merge branch 'main' into mcp-auth
2 parents f86108a + 858c820 commit e42b3ee

21 files changed

+663
-71
lines changed

public/locales/en.json

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -269,6 +269,7 @@
269269
"atLeastOneUser": "You need to have at least one member assigned."
270270
},
271271
"common": {
272+
"documentation": "Documentation",
272273
"close": "Close",
273274
"cannotLoadData": "Cannot load data",
274275
"metadata": "Metadata",
@@ -278,7 +279,10 @@
278279
"region": "Region",
279280
"success": "Success",
280281
"displayName": "Display Name",
281-
"name": "Name"
282+
"name": "Name",
283+
"componentSelection": "Component Selection",
284+
"search": "Search",
285+
"components": "Components"
282286
},
283287
"buttons": {
284288
"viewResource": "View resource",
@@ -297,5 +301,10 @@
297301
"dialogTitle": "Create Managed Control Plane",
298302
"titleText": "Managed Control Plane Created Successfully!",
299303
"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."
304+
},
305+
"componentsSelection": {
306+
"selectComponents": "Select Components",
307+
"selectedComponents": "Selected Components",
308+
"pleaseSelectComponents": "Choose the components you want to add to your Managed Control Plane."
300309
}
301310
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
.row {
2+
padding: 1rem;
3+
background: var(--sapBackgroundColor);
4+
border-bottom: 1px solid var(--sapList_BorderColor);
5+
}
Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
1+
import React, { useState } from 'react';
2+
import {
3+
CheckBox,
4+
Select,
5+
Option,
6+
FlexBox,
7+
Title,
8+
Text,
9+
Input,
10+
Button,
11+
Grid,
12+
List,
13+
ListItemStandard,
14+
Icon,
15+
Ui5CustomEvent,
16+
CheckBoxDomRef,
17+
SelectDomRef,
18+
InputDomRef,
19+
} from '@ui5/webcomponents-react';
20+
import styles from './ComponentsSelection.module.css';
21+
import { Infobox } from '../Ui/Infobox/Infobox.tsx';
22+
import { useTranslation } from 'react-i18next';
23+
import { ComponentSelectionItem } from '../../lib/api/types/crate/createManagedControlPlane.ts';
24+
25+
export interface ComponentsSelectionProps {
26+
components: ComponentSelectionItem[];
27+
setSelectedComponents: React.Dispatch<
28+
React.SetStateAction<ComponentSelectionItem[]>
29+
>;
30+
}
31+
32+
export const ComponentsSelection: React.FC<ComponentsSelectionProps> = ({
33+
components,
34+
setSelectedComponents,
35+
}) => {
36+
const [searchTerm, setSearchTerm] = useState('');
37+
const { t } = useTranslation();
38+
const handleSelectionChange = (
39+
e: Ui5CustomEvent<CheckBoxDomRef, { checked: boolean }>,
40+
) => {
41+
const id = e.target?.id;
42+
setSelectedComponents((prev) =>
43+
prev.map((component) =>
44+
component.name === id
45+
? { ...component, isSelected: !component.isSelected }
46+
: component,
47+
),
48+
);
49+
};
50+
51+
const handleSearch = (e: Ui5CustomEvent<InputDomRef, never>) => {
52+
setSearchTerm(e.target.value.trim());
53+
};
54+
55+
const handleVersionChange = (
56+
e: Ui5CustomEvent<SelectDomRef, { selectedOption: HTMLElement }>,
57+
) => {
58+
const selectedOption = e.detail.selectedOption as HTMLElement;
59+
const name = selectedOption.dataset.name;
60+
const version = selectedOption.dataset.version;
61+
setSelectedComponents((prev) =>
62+
prev.map((component) =>
63+
component.name === name
64+
? { ...component, selectedVersion: version || '' }
65+
: component,
66+
),
67+
);
68+
};
69+
70+
const filteredComponents = components.filter(({ name }) =>
71+
name.includes(searchTerm),
72+
);
73+
const selectedComponents = components.filter(
74+
(component) => component.isSelected,
75+
);
76+
77+
return (
78+
<div>
79+
<Title>{t('componentsSelection.selectComponents')}</Title>
80+
81+
<Input
82+
placeholder={t('common.search')}
83+
id="search"
84+
showClearIcon
85+
icon={<Icon name="search" />}
86+
onInput={handleSearch}
87+
/>
88+
89+
<Grid>
90+
<div data-layout-span="XL8 L8 M8 S8">
91+
{filteredComponents.map((component) => (
92+
<FlexBox
93+
key={component.name}
94+
className={styles.row}
95+
gap={10}
96+
justifyContent="SpaceBetween"
97+
>
98+
<CheckBox
99+
valueState="None"
100+
text={component.name}
101+
id={component.name}
102+
checked={component.isSelected}
103+
onChange={handleSelectionChange}
104+
/>
105+
<FlexBox
106+
gap={10}
107+
justifyContent="SpaceBetween"
108+
alignItems="Baseline"
109+
>
110+
{/*This button will be implemented later*/}
111+
{component.documentationUrl && (
112+
<Button design="Transparent">
113+
{t('common.documentation')}
114+
</Button>
115+
)}
116+
<Select
117+
value={component.selectedVersion}
118+
onChange={handleVersionChange}
119+
>
120+
{component.versions.map((version) => (
121+
<Option
122+
key={version}
123+
data-version={version}
124+
data-name={component.name}
125+
selected={component.selectedVersion === version}
126+
>
127+
{version}
128+
</Option>
129+
))}
130+
</Select>
131+
</FlexBox>
132+
</FlexBox>
133+
))}
134+
</div>
135+
<div data-layout-span="XL4 L4 M4 S4">
136+
{selectedComponents.length > 0 ? (
137+
<List headerText={t('componentsSelection.selectedComponents')}>
138+
{selectedComponents.map((component) => (
139+
<ListItemStandard
140+
key={component.name}
141+
text={component.name}
142+
additionalText={component.selectedVersion}
143+
/>
144+
))}
145+
</List>
146+
) : (
147+
<Infobox fullWidth variant={'success'}>
148+
<Text>{t('componentsSelection.pleaseSelectComponents')}</Text>
149+
</Infobox>
150+
)}
151+
</div>
152+
</Grid>
153+
</div>
154+
);
155+
};
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
import React, { useEffect, useState } from 'react';
2+
import { ComponentsSelection } from './ComponentsSelection.tsx';
3+
4+
import IllustratedError from '../Shared/IllustratedError.tsx';
5+
import { sortVersions } from '../../utils/componentsVersions.ts';
6+
7+
import { ListManagedComponents } from '../../lib/api/types/crate/listManagedComponents.ts';
8+
import useApiResource from '../../lib/api/useApiResource.ts';
9+
import Loading from '../Shared/Loading.tsx';
10+
import { ComponentSelectionItem } from '../../lib/api/types/crate/createManagedControlPlane.ts';
11+
12+
export interface ComponentItem {
13+
name: string;
14+
versions: string[];
15+
}
16+
17+
export interface ComponentsSelectionProps {
18+
selectedComponents: ComponentSelectionItem[];
19+
setSelectedComponents: React.Dispatch<
20+
React.SetStateAction<ComponentSelectionItem[]>
21+
>;
22+
}
23+
export const ComponentsSelectionContainer: React.FC<
24+
ComponentsSelectionProps
25+
> = ({ setSelectedComponents, selectedComponents }) => {
26+
const {
27+
data: allManagedComponents,
28+
error,
29+
isLoading,
30+
} = useApiResource(ListManagedComponents());
31+
const [isReady, setIsReady] = useState(false);
32+
useEffect(() => {
33+
if (
34+
allManagedComponents?.items.length === 0 ||
35+
!allManagedComponents?.items ||
36+
isReady
37+
)
38+
return;
39+
40+
setSelectedComponents(
41+
allManagedComponents?.items?.map((item) => {
42+
const versions = sortVersions(item.status.versions);
43+
return {
44+
name: item.metadata.name,
45+
versions: versions,
46+
selectedVersion: versions[0],
47+
isSelected: false,
48+
documentationUrl: '',
49+
};
50+
}) ?? [],
51+
);
52+
setIsReady(true);
53+
}, [allManagedComponents, isReady, setSelectedComponents]);
54+
if (isLoading) {
55+
return <Loading />;
56+
}
57+
if (error) return <IllustratedError />;
58+
return (
59+
<>
60+
{selectedComponents.length > 0 ? (
61+
<ComponentsSelection
62+
components={selectedComponents}
63+
setSelectedComponents={setSelectedComponents}
64+
/>
65+
) : (
66+
<IllustratedError title={'Cannot load components list'} />
67+
)}
68+
</>
69+
);
70+
};

src/components/ControlPlanes/ControlPlaneCard/ControlPlaneCard.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import {
2727

2828
import { YamlViewButtonWithLoader } from '../../Yaml/YamlViewButtonWithLoader.tsx';
2929
import { useToast } from '../../../context/ToastContext.tsx';
30+
import { canConnectToMCP } from '../controlPlanes.ts';
3031

3132
interface Props {
3233
controlPlane: ListControlPlanesType;
@@ -59,6 +60,8 @@ export function ControlPlaneCard({
5960
const name = controlPlane.metadata.name;
6061
const namespace = controlPlane.metadata.namespace;
6162

63+
const isConnectButtonEnabled = canConnectToMCP(controlPlane);
64+
6265
return (
6366
<>
6467
<Card key={`${name}--${namespace}`} className={styles.card}>
@@ -106,7 +109,7 @@ export function ControlPlaneCard({
106109
resourceType={'managedcontrolplanes'}
107110
/>
108111
<ConnectButton
109-
disabled={controlPlane.status?.status !== ReadyStatus.Ready}
112+
disabled={!isConnectButtonEnabled}
110113
controlPlaneName={name}
111114
projectName={projectName}
112115
workspaceName={workspace.metadata.name ?? ''}

src/components/ControlPlanes/List/ControlPlaneListAllWorkspaces.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ export default function ControlPlaneListAllWorkspaces({ projectName }: Props) {
4848
window.open(workspaceCreationGuide, '_blank');
4949
}}
5050
>
51-
Help
51+
{t('IllustratedBanner.helpButton')}
5252
</Button>
5353
</FlexBox>
5454
) : (

0 commit comments

Comments
 (0)