Skip to content

Commit e605b1f

Browse files
committed
Merge branch 'main' into ft/bento
2 parents d0bddfd + 8b54878 commit e605b1f

17 files changed

+1838
-776
lines changed

Dockerfile

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
# BUILD STAGE
2-
FROM node:24-slim@sha256:0104d9447ea3ddf7373643be7f9915fc7b7c896e41d0d33229338e457217cd78 AS build-stage
2+
FROM node:24-slim@sha256:cadbfafeb6baf87eaaffa40b3640209c4b7fd38cebde65059d15bc39cd636b85 AS build-stage
33
WORKDIR /usr/src/app
44

55
# Copy package.json and package-lock.json
@@ -18,7 +18,7 @@ RUN npm prune --omit=dev
1818

1919

2020
# PRODUCTION STAGE
21-
FROM gcr.io/distroless/nodejs24-debian12@sha256:33204de28faf16f5bb1f8730fe12ea99609625d44aefdec32dab73b290d33943 AS production
21+
FROM gcr.io/distroless/nodejs24-debian12@sha256:e6af293a96996c2a6d1e8a440600518ec614571a91356a38c55f316ff1ee9924 AS production
2222
WORKDIR /usr/src/app
2323

2424
# Copy built files

package-lock.json

Lines changed: 1095 additions & 702 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -34,14 +34,14 @@
3434
"@fastify/static": "8.2.0",
3535
"@fastify/vite": "8.2.0",
3636
"@hookform/resolvers": "5.2.1",
37-
"@sentry/node": "10.10.0",
38-
"@sentry/react": "10.10.0",
37+
"@sentry/node": "10.11.0",
38+
"@sentry/react": "10.11.0",
3939
"@sentry/vite-plugin": "4.3.0",
4040
"@ui5/webcomponents": "2.14.0",
4141
"@ui5/webcomponents-fiori": "2.14.0",
4242
"@ui5/webcomponents-icons": "2.14.0",
43-
"@ui5/webcomponents-react": "2.14.0",
44-
"@ui5/webcomponents-react-charts": "2.14.0",
43+
"@ui5/webcomponents-react": "2.14.1",
44+
"@ui5/webcomponents-react-charts": "2.14.1",
4545
"@xyflow/react": "12.8.4",
4646
"clsx": "2.1.1",
4747
"dagre": "0.8.5",
@@ -51,35 +51,35 @@
5151
"graphql": "16.11.0",
5252
"graphql-config": "5.1.5",
5353
"i18next": "25.5.2",
54-
"javascript-time-ago": "2.5.11",
54+
"javascript-time-ago": "2.5.12",
5555
"js-yaml": "4.1.0",
5656
"react": "19.1.1",
5757
"react-dom": "19.1.1",
5858
"react-error-boundary": "6.0.0",
5959
"react-hook-form": "7.62.0",
6060
"react-i18next": "15.7.3",
61-
"react-router-dom": "7.8.2",
61+
"react-router-dom": "7.9.1",
6262
"react-syntax-highlighter": "15.6.6",
63-
"react-time-ago": "7.3.3",
63+
"react-time-ago": "7.3.5",
6464
"swr": "2.3.6",
6565
"yaml": "2.8.1",
66-
"zod": "4.1.5"
66+
"zod": "4.1.8"
6767
},
6868
"devDependencies": {
6969
"@eslint/eslintrc": "3.3.1",
7070
"@eslint/js": "9.35.0",
71-
"@graphql-codegen/cli": "5.0.7",
72-
"@graphql-codegen/client-preset": "4.8.3",
71+
"@graphql-codegen/cli": "6.0.0",
72+
"@graphql-codegen/client-preset": "5.0.0",
7373
"@types/dagre": "0.7.53",
7474
"@types/js-yaml": "4.0.9",
75-
"@types/node": "22.18.1",
76-
"@types/react": "19.1.12",
75+
"@types/node": "22.18.3",
76+
"@types/react": "19.1.13",
7777
"@types/react-dom": "19.1.9",
7878
"@types/react-syntax-highlighter": "15.5.13",
79-
"@ui5/webcomponents-cypress-commands": "2.14.0",
79+
"@ui5/webcomponents-cypress-commands": "2.14.1",
8080
"@vitejs/plugin-react": "5.0.2",
8181
"@vitest/eslint-plugin": "1.3.9",
82-
"cypress": "15.1.0",
82+
"cypress": "15.2.0",
8383
"eslint-config-prettier": "10.1.8",
8484
"eslint-import-resolver-typescript": "4.4.4",
8585
"eslint-plugin-i18next": "6.1.3",
@@ -90,11 +90,11 @@
9090
"eslint-plugin-react": "7.37.5",
9191
"eslint-plugin-react-hooks": "5.2.0",
9292
"fastify-tsconfig": "3.0.0",
93-
"globals": "16.3.0",
93+
"globals": "16.4.0",
9494
"prettier": "3.6.2",
9595
"tsx": "4.20.5",
9696
"typescript": "5.9.2",
97-
"typescript-eslint": "8.42.0",
97+
"typescript-eslint": "8.43.0",
9898
"vite": "7.1.5",
9999
"vitest": "3.2.4"
100100
}

public/locales/en.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,9 @@
9393
"menuDownload": "Download",
9494
"menuCopy": "Copy to clipboard"
9595
},
96+
"ComponentsSelection": {
97+
"chooseVersion": "Please select version"
98+
},
9699
"IllustratedBanner": {
97100
"titleMessage": "No Managed Control Planes found",
98101
"subtitleMessage": "Get started by creating your first Managed Control Plane.",

src/components/ComponentsSelection/ComponentsSelection.tsx

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,13 +22,19 @@ import { Infobox } from '../Ui/Infobox/Infobox.tsx';
2222
import { useTranslation } from 'react-i18next';
2323
import { ComponentsListItem } from '../../lib/api/types/crate/createManagedControlPlane.ts';
2424
import { getSelectedComponents } from './ComponentsSelectionContainer.tsx';
25+
import IllustratedError from '../Shared/IllustratedError.tsx';
2526

2627
export interface ComponentsSelectionProps {
2728
componentsList: ComponentsListItem[];
2829
setComponentsList: (components: ComponentsListItem[]) => void;
30+
templateDefaultsError?: string;
2931
}
3032

31-
export const ComponentsSelection: React.FC<ComponentsSelectionProps> = ({ componentsList, setComponentsList }) => {
33+
export const ComponentsSelection: React.FC<ComponentsSelectionProps> = ({
34+
componentsList,
35+
setComponentsList,
36+
templateDefaultsError,
37+
}) => {
3238
const [searchTerm, setSearchTerm] = useState('');
3339
const { t } = useTranslation();
3440

@@ -132,8 +138,19 @@ export const ComponentsSelection: React.FC<ComponentsSelectionProps> = ({ compon
132138
value={component.selectedVersion}
133139
disabled={!component.isSelected || providerDisabled}
134140
aria-label={`${component.name} version`}
141+
valueState={component.isSelected && !component.selectedVersion ? 'Negative' : 'None'}
142+
valueStateMessage={
143+
component.isSelected && !component.selectedVersion ? (
144+
<span>{t('ComponentsSelection.chooseVersion')}</span>
145+
) : undefined
146+
}
135147
onChange={handleVersionChange}
136148
>
149+
{!component.selectedVersion && (
150+
<Option key="__placeholder" data-version="" data-name={component.name} selected>
151+
{t('ComponentsSelection.chooseVersion')}
152+
</Option>
153+
)}
137154
{component.versions.map((version) => (
138155
<Option
139156
key={version}
@@ -155,7 +172,14 @@ export const ComponentsSelection: React.FC<ComponentsSelectionProps> = ({ compon
155172
</Infobox>
156173
)}
157174
</div>
175+
158176
<div data-layout-span="XL4 L4 M4 S4">
177+
{templateDefaultsError ? (
178+
<div style={{ marginBottom: 8 }}>
179+
<IllustratedError title={templateDefaultsError} compact />
180+
</div>
181+
) : null}
182+
159183
{selectedComponents.length > 0 ? (
160184
<List headerText={t('componentsSelection.selectedComponents')}>
161185
{selectedComponents.map((component) => (
Lines changed: 74 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import React, { useEffect, useRef } from 'react';
1+
import React, { useEffect, useMemo, useRef, useState } from 'react';
22
import { ComponentsSelection } from './ComponentsSelection.tsx';
33

44
import IllustratedError from '../Shared/IllustratedError.tsx';
@@ -9,10 +9,12 @@ import { useApiResource } from '../../lib/api/useApiResource.ts';
99
import Loading from '../Shared/Loading.tsx';
1010
import { ComponentsListItem, removeComponents } from '../../lib/api/types/crate/createManagedControlPlane.ts';
1111
import { useTranslation } from 'react-i18next';
12+
import { ManagedControlPlaneTemplate } from '../../lib/api/types/templates/mcpTemplate.ts';
1213

1314
export interface ComponentsSelectionProps {
1415
componentsList: ComponentsListItem[];
1516
setComponentsList: (components: ComponentsListItem[]) => void;
17+
managedControlPlaneTemplate?: ManagedControlPlaneTemplate;
1618
}
1719

1820
/**
@@ -30,39 +32,82 @@ export const getSelectedComponents = (components: ComponentsListItem[]) => {
3032
});
3133
};
3234

35+
type TemplateDefaultComponent = {
36+
name: string;
37+
version: string;
38+
removable?: boolean;
39+
versionChangeable?: boolean;
40+
};
41+
3342
export const ComponentsSelectionContainer: React.FC<ComponentsSelectionProps> = ({
3443
setComponentsList,
3544
componentsList,
45+
managedControlPlaneTemplate,
3646
}) => {
3747
const { data: availableManagedComponentsListData, error, isLoading } = useApiResource(ListManagedComponents());
3848
const { t } = useTranslation();
3949
const initialized = useRef(false);
50+
const [templateDefaultsError, setTemplateDefaultsError] = useState<string | null>(null);
51+
const defaultComponents = useMemo<TemplateDefaultComponent[]>(
52+
() => managedControlPlaneTemplate?.spec?.spec?.components?.defaultComponents ?? [],
53+
[managedControlPlaneTemplate],
54+
);
4055

4156
useEffect(() => {
42-
if (
43-
initialized.current ||
44-
!availableManagedComponentsListData?.items ||
45-
availableManagedComponentsListData.items.length === 0
46-
) {
57+
const items = availableManagedComponentsListData?.items ?? [];
58+
59+
if (!items.length) {
60+
if (!initialized.current) return;
61+
setTemplateDefaultsError(null);
62+
return;
63+
}
64+
65+
if (!initialized.current) {
66+
const newComponentsList = items
67+
.map((item) => {
68+
const versions = sortVersions(item.status.versions);
69+
const template = defaultComponents.find((dc) => dc.name === item.metadata.name);
70+
const templateVersion = template?.version;
71+
const selectedVersion = template
72+
? templateVersion && versions.includes(templateVersion)
73+
? templateVersion
74+
: ''
75+
: (versions[0] ?? '');
76+
return {
77+
name: item.metadata.name,
78+
versions,
79+
selectedVersion,
80+
isSelected: !!template,
81+
documentationUrl: '',
82+
};
83+
})
84+
.filter((component) => !removeComponents.find((item) => item === component.name));
85+
86+
setComponentsList(newComponentsList);
87+
initialized.current = true;
88+
}
89+
90+
if (!defaultComponents.length) {
91+
setTemplateDefaultsError(null);
4792
return;
4893
}
4994

50-
const newComponentsList = availableManagedComponentsListData.items
51-
.map((item) => {
52-
const versions = sortVersions(item.status.versions);
53-
return {
54-
name: item.metadata.name,
55-
versions,
56-
selectedVersion: versions[0] ?? '',
57-
isSelected: false,
58-
documentationUrl: '',
59-
};
60-
})
61-
.filter((component) => !removeComponents.find((item) => item === component.name));
62-
63-
setComponentsList(newComponentsList);
64-
initialized.current = true;
65-
}, [availableManagedComponentsListData, setComponentsList]);
95+
const errors: string[] = [];
96+
defaultComponents.forEach((dc: TemplateDefaultComponent) => {
97+
if (!dc?.name) return;
98+
const item = items.find((it) => it.metadata.name === dc.name);
99+
if (!item) {
100+
errors.push(`Component "${dc.name}" from template is not available.`);
101+
return;
102+
}
103+
const versions: string[] = Array.isArray(item.status?.versions) ? item.status.versions : [];
104+
if (dc.version && !versions.includes(dc.version)) {
105+
errors.push(`Component "${dc.name}" version "${dc.version}" from template is not available.`);
106+
}
107+
});
108+
109+
setTemplateDefaultsError(errors.length ? errors.join('\n') : null);
110+
}, [availableManagedComponentsListData, defaultComponents, setComponentsList]);
66111

67112
if (isLoading) {
68113
return <Loading />;
@@ -77,5 +122,11 @@ export const ComponentsSelectionContainer: React.FC<ComponentsSelectionProps> =
77122
return <IllustratedError title={t('componentsSelection.cannotLoad')} compact={true} />;
78123
}
79124

80-
return <ComponentsSelection componentsList={componentsList} setComponentsList={setComponentsList} />;
125+
return (
126+
<ComponentsSelection
127+
componentsList={componentsList}
128+
setComponentsList={setComponentsList}
129+
templateDefaultsError={templateDefaultsError || undefined}
130+
/>
131+
);
81132
};

src/components/ControlPlanes/ControlPlanesListMenu.tsx

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,21 +5,27 @@ import '@ui5/webcomponents-icons/dist/copy';
55
import '@ui5/webcomponents-icons/dist/accept';
66

77
import { useTranslation } from 'react-i18next';
8+
import { ManagedControlPlaneTemplate } from '../../lib/api/types/templates/mcpTemplate.ts';
89

910
type ControlPlanesListMenuProps = {
1011
setDialogDeleteWsIsOpen: Dispatch<SetStateAction<boolean>>;
1112
setIsCreateManagedControlPlaneWizardOpen: Dispatch<SetStateAction<boolean>>;
13+
setInitialTemplateName: Dispatch<SetStateAction<string | undefined>>;
1214
};
1315

1416
export const ControlPlanesListMenu: FC<ControlPlanesListMenuProps> = ({
1517
setDialogDeleteWsIsOpen,
1618
setIsCreateManagedControlPlaneWizardOpen,
19+
setInitialTemplateName,
1720
}) => {
1821
const popoverRef = useRef<MenuDomRef>(null);
1922
const [open, setOpen] = useState(false);
2023

2124
const { t } = useTranslation();
2225

26+
// Here we will pass template list from OnboardingAPI
27+
const allTemplates: ManagedControlPlaneTemplate[] = [];
28+
2329
const handleOpenerClick = (e: Ui5CustomEvent<ButtonDomRef, ButtonClickEventDetail>) => {
2430
if (popoverRef.current && e.currentTarget) {
2531
popoverRef.current.opener = e.currentTarget as HTMLElement;
@@ -34,14 +40,20 @@ export const ControlPlanesListMenu: FC<ControlPlanesListMenuProps> = ({
3440
ref={popoverRef}
3541
open={open}
3642
onItemClick={(event) => {
37-
const action = (event.detail.item as HTMLElement).dataset.action;
43+
const item = event.detail.item as HTMLElement;
44+
const action = item.dataset.action;
3845
if (action === 'newManagedControlPlane') {
46+
setInitialTemplateName(undefined);
47+
setIsCreateManagedControlPlaneWizardOpen(true);
48+
}
49+
if (action === 'newManagedControlPlaneWithTemplate') {
50+
const tplName = item.dataset.templateName || undefined;
51+
setInitialTemplateName(tplName);
3952
setIsCreateManagedControlPlaneWizardOpen(true);
4053
}
4154
if (action === 'deleteWorkspace') {
4255
setDialogDeleteWsIsOpen(true);
4356
}
44-
4557
setOpen(false);
4658
}}
4759
>
@@ -51,6 +63,17 @@ export const ControlPlanesListMenu: FC<ControlPlanesListMenuProps> = ({
5163
data-action="newManagedControlPlane"
5264
icon="add"
5365
/>
66+
{allTemplates.map((tpl) => (
67+
<MenuItem
68+
key={`tpl-${tpl.metadata.name}`}
69+
text={tpl.metadata.name}
70+
title={tpl.metadata.descriptionText || ''}
71+
data-action="newManagedControlPlaneWithTemplate"
72+
data-template-name={tpl.metadata.name}
73+
icon="document-text"
74+
/>
75+
))}
76+
5477
<MenuItem
5578
key={'delete'}
5679
text={t('ControlPlaneListToolbar.deleteWorkspace')}

0 commit comments

Comments
 (0)