From 23c23d7d0667030215bda30d2b476ca115e5150e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Goral?= Date: Tue, 26 Aug 2025 12:22:15 +0200 Subject: [PATCH 01/75] init --- src/components/Members/EditMembers.tsx | 21 +++ .../Members/ImportMembersDialog.tsx | 134 ++++++++++++++++++ 2 files changed, 155 insertions(+) create mode 100644 src/components/Members/ImportMembersDialog.tsx diff --git a/src/components/Members/EditMembers.tsx b/src/components/Members/EditMembers.tsx index 4d1eeb3d..292c6ea9 100644 --- a/src/components/Members/EditMembers.tsx +++ b/src/components/Members/EditMembers.tsx @@ -6,6 +6,7 @@ import { useTranslation } from 'react-i18next'; import styles from './Members.module.css'; import { RadioButtonsSelectOption } from '../Ui/RadioButtonsSelect/RadioButtonsSelect.tsx'; import { AddEditMemberDialog } from './AddEditMemberDialog.tsx'; +import { ImportMembersDialog } from './ImportMembersDialog.tsx'; export interface EditMembersProps { members: Member[]; @@ -31,6 +32,7 @@ export const EditMembers: FC = ({ const [isMemberDialogOpen, setIsMemberDialogOpen] = useState(false); const [memberToEdit, setMemberToEdit] = useState(undefined); + const [isImportDialogOpen, setIsImportDialogOpen] = useState(false); const handleRemoveMember = useCallback( (email: string) => { @@ -53,6 +55,14 @@ export const EditMembers: FC = ({ setIsMemberDialogOpen(false); }, []); + const handleOpenImportDialog = useCallback(() => { + setIsImportDialogOpen(true); + }, []); + + const handleCloseImportDialog = useCallback(() => { + setIsImportDialogOpen(false); + }, []); + const handleSaveMember = useCallback( (member: Member, isEdit: boolean) => { let updatedMembers: Member[]; @@ -85,6 +95,15 @@ export const EditMembers: FC = ({ > {t('EditMembers.addButton')} + = ({ onSave={handleSaveMember} /> + + void; +}; + +export const ImportMembersDialog: FC = ({ open, onClose }) => { + const [step, setStep] = useState(1); + + const formSchema = useMemo( + () => + z + .object({ + parentType: z.union([z.literal('Workspace'), z.literal('Project')]), + importMembers: z.boolean(), + importServiceAccounts: z.boolean(), + }) + .refine((data) => data.importMembers || data.importServiceAccounts, { + path: ['importMembers'], + message: 'Select at least one option', + }), + [], + ); + + const { handleSubmit, setValue, watch, reset } = useForm({ + resolver: zodResolver(formSchema), + mode: 'onChange', + defaultValues: { + parentType: '', + importMembers: false, + importServiceAccounts: false, + }, + }); + + const parentType = watch('parentType'); + const importMembers = watch('importMembers'); + const importServiceAccounts = watch('importServiceAccounts'); + const canProceed = parentType !== '' && (importMembers || importServiceAccounts); + + const handleDialogAfterClose = () => { + reset(); + setStep(1); + }; + + const onSubmitStepOne = () => { + setStep(2); + }; + + return ( + + {step === 1 && ( + + + + + + + + setValue('importMembers', e.detail.checked, { + shouldValidate: true, + shouldDirty: true, + shouldTouch: true, + }) + } + /> + + setValue('importServiceAccounts', e.detail.checked, { + shouldValidate: true, + shouldDirty: true, + shouldTouch: true, + }) + } + /> + + + + + + + + )} + + {step === 2 && ( + + Step 1 + + + + + + + )} + + ); +}; From 0c01a1a4ceaf0876da9bb491f0d734135ffe3ae1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Goral?= Date: Wed, 27 Aug 2025 10:41:25 +0200 Subject: [PATCH 02/75] Update ImportMembersDialog.tsx --- .../Members/ImportMembersDialog.tsx | 146 ++++++++++++++---- 1 file changed, 118 insertions(+), 28 deletions(-) diff --git a/src/components/Members/ImportMembersDialog.tsx b/src/components/Members/ImportMembersDialog.tsx index 536e1483..0ab2058a 100644 --- a/src/components/Members/ImportMembersDialog.tsx +++ b/src/components/Members/ImportMembersDialog.tsx @@ -1,8 +1,22 @@ import { FC, useMemo, useState } from 'react'; -import { Button, CheckBox, Dialog, FlexBox, Label, Option, Select, Title } from '@ui5/webcomponents-react'; +import { + Button, + CheckBox, + Dialog, + FlexBox, + Label, + Option, + Select, + Ui5CustomEvent, + CheckBoxDomRef, +} from '@ui5/webcomponents-react'; import { useForm } from 'react-hook-form'; import { z } from 'zod'; import { zodResolver } from '@hookform/resolvers/zod'; +import { AnalyticalTable, Icon } from '@ui5/webcomponents-react'; +import { AnalyticalTableColumnDefinition } from '@ui5/webcomponents-react/wrappers'; +import { Member, MemberRoles, MemberRolesDetailed } from '../../lib/api/types/shared/members'; +import { ACCOUNT_TYPES } from './EditMembers.tsx'; type ParentType = 'Workspace' | 'Project'; @@ -24,7 +38,7 @@ export const ImportMembersDialog: FC = ({ open, onClos () => z .object({ - parentType: z.union([z.literal('Workspace'), z.literal('Project')]), + parentType: z.union([z.literal('Workspace'), z.literal('Project'), z.literal('')]), importMembers: z.boolean(), importServiceAccounts: z.boolean(), }) @@ -35,11 +49,11 @@ export const ImportMembersDialog: FC = ({ open, onClos [], ); - const { handleSubmit, setValue, watch, reset } = useForm({ + const { handleSubmit, setValue, watch } = useForm({ resolver: zodResolver(formSchema), mode: 'onChange', defaultValues: { - parentType: '', + parentType: 'Project', importMembers: false, importServiceAccounts: false, }, @@ -49,21 +63,15 @@ export const ImportMembersDialog: FC = ({ open, onClos const importMembers = watch('importMembers'); const importServiceAccounts = watch('importServiceAccounts'); const canProceed = parentType !== '' && (importMembers || importServiceAccounts); - - const handleDialogAfterClose = () => { - reset(); - setStep(1); - }; - const onSubmitStepOne = () => { setStep(2); }; - + console.log(parentType, importMembers, importServiceAccounts); return ( {step === 1 && ( @@ -71,8 +79,9 @@ export const ImportMembersDialog: FC = ({ open, onClos @@ -130,11 +145,13 @@ export const ImportMembersDialog: FC = ({ open, onClos {step === 2 && ( )} @@ -148,19 +165,9 @@ type SelectionRow = { _member: Member; }; -const getMockedProjectMembers = (): Member[] => [ - { name: 'p.project@example.com', role: MemberRoles.view, kind: 'User' }, - { name: 'pp.project@example.com', role: MemberRoles.admin, kind: 'User' }, - { name: 'sa-project-reader', role: MemberRoles.view, kind: 'ServiceAccount', namespace: 'project-default' }, - { name: 'sa-project-admin', role: MemberRoles.admin, kind: 'ServiceAccount', namespace: 'project-ops' }, -]; - -const getMockedWorkspaceMembers = (): Member[] => [ - { name: 'w.workspace@example.com', role: MemberRoles.admin, kind: 'User' }, - { name: 'ww.workspace@example.com', role: MemberRoles.view, kind: 'User' }, - { name: 'sa-ws-view', role: MemberRoles.view, kind: 'ServiceAccount', namespace: 'workspace-default' }, - { name: 'sa-ws-admin', role: MemberRoles.admin, kind: 'ServiceAccount', namespace: 'workspace-ops' }, -]; +interface SpecMembers { + spec?: { members: { name: string; roles: string[]; kind: 'User' | 'ServiceAccount'; namespace?: string }[] }; +} const ImportMembersSelectionTable: FC<{ onCancel: () => void; @@ -168,15 +175,37 @@ const ImportMembersSelectionTable: FC<{ parentType: ParentType; includeMembers: boolean; includeServiceAccounts: boolean; -}> = ({ onCancel, onImport, parentType, includeMembers, includeServiceAccounts }) => { - const mockedMembers: Member[] = parentType === 'Project' ? getMockedProjectMembers() : getMockedWorkspaceMembers(); + workspaceName?: string; + projectName?: string; +}> = ({ onCancel, onImport, parentType, workspaceName, projectName, includeMembers, includeServiceAccounts }) => { + const { + isLoading, + data: parentResourceData, + error, + } = useApiResource( + parentType === 'Project' + ? ResourceObject('', 'projects', projectName ?? '') + : ResourceObject(workspaceName ?? '', 'projects', projectName ?? ''), + undefined, + true, + ); + const [selectedEmails, setSelectedEmails] = useState>(new Set()); + if (isLoading) { + return ; + } + console.log(parentResourceData?.spec?.members); + const membersData = parentResourceData?.spec?.members ?? []; + const mockedMembers: Member[] = membersData.map(({ name, namespace, kind, roles }) => ({ + kind, + name, + role: roles.includes('admin') ? 'admin' : 'view', + namespace, + })); const filteredMockedMembers: Member[] = mockedMembers.filter( (m) => (m.kind === 'User' && includeMembers) || (m.kind === 'ServiceAccount' && includeServiceAccounts), ); - const [selectedEmails, setSelectedEmails] = useState>(new Set()); - const columns: AnalyticalTableColumnDefinition[] = [ { Header: '', @@ -242,7 +271,7 @@ const ImportMembersSelectionTable: FC<{ - diff --git a/src/components/Wizards/CreateManagedControlPlane/CreateManagedControlPlaneWizardContainer.tsx b/src/components/Wizards/CreateManagedControlPlane/CreateManagedControlPlaneWizardContainer.tsx index 8f952082..cedeab89 100644 --- a/src/components/Wizards/CreateManagedControlPlane/CreateManagedControlPlaneWizardContainer.tsx +++ b/src/components/Wizards/CreateManagedControlPlane/CreateManagedControlPlaneWizardContainer.tsx @@ -281,6 +281,8 @@ export const CreateManagedControlPlaneWizardContainer: FC diff --git a/src/lib/api/types/crate/resourceObject.ts b/src/lib/api/types/crate/resourceObject.ts index 70aa75f5..be6c1397 100644 --- a/src/lib/api/types/crate/resourceObject.ts +++ b/src/lib/api/types/crate/resourceObject.ts @@ -1,10 +1,6 @@ import { Resource } from '../resource.ts'; -export const ResourceObject = ( - workspaceName: string, - resourceType: string, - resourceName: string, -): Resource => { +export const ResourceObject = (workspaceName: string, resourceType: string, resourceName: string): Resource => { return { path: `/apis/core.openmcp.cloud/v1alpha1/${workspaceName ? `namespaces/${workspaceName}/` : ''}${resourceType}/${resourceName}`, }; From 590a1fbdaeadefcc005ded8e8be73f8e4ac09a7a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Goral?= Date: Thu, 28 Aug 2025 14:04:48 +0200 Subject: [PATCH 09/75] fixes --- src/components/Dialogs/CreateProjectDialogContainer.tsx | 2 +- src/components/Dialogs/CreateWorkspaceDialogContainer.tsx | 2 +- src/components/Members/AddEditMemberDialog.tsx | 4 ++-- src/components/Members/MemberTable.tsx | 4 ++-- .../CreateManagedControlPlaneWizardContainer.tsx | 2 +- .../Wizards/CreateManagedControlPlane/SummarizeStep.tsx | 2 +- src/lib/api/types/crate/createManagedControlPlane.ts | 2 +- src/lib/api/types/crate/createProject.ts | 4 ++-- src/lib/api/types/crate/createWorkspace.ts | 4 ++-- src/lib/api/types/crate/listWorkspaces.ts | 2 +- src/lib/api/types/shared/members.ts | 2 +- 11 files changed, 15 insertions(+), 15 deletions(-) diff --git a/src/components/Dialogs/CreateProjectDialogContainer.tsx b/src/components/Dialogs/CreateProjectDialogContainer.tsx index 8e4abed5..11eb95f4 100644 --- a/src/components/Dialogs/CreateProjectDialogContainer.tsx +++ b/src/components/Dialogs/CreateProjectDialogContainer.tsx @@ -53,7 +53,7 @@ export function CreateProjectDialogContainer({ useEffect(() => { if (username) { - setValue('members', [{ name: username, role: MemberRoles.admin, kind: 'User' }]); + setValue('members', [{ name: username, roles: [MemberRoles.admin], kind: 'User' }]); } if (!isOpen) { clearForm(); diff --git a/src/components/Dialogs/CreateWorkspaceDialogContainer.tsx b/src/components/Dialogs/CreateWorkspaceDialogContainer.tsx index adfa1264..bca3b319 100644 --- a/src/components/Dialogs/CreateWorkspaceDialogContainer.tsx +++ b/src/components/Dialogs/CreateWorkspaceDialogContainer.tsx @@ -68,7 +68,7 @@ export function CreateWorkspaceDialogContainer({ useEffect(() => { if (username) { - setValue('members', [{ name: username, role: MemberRoles.admin, kind: 'User' }]); + setValue('members', [{ name: username, roles: [MemberRoles.admin], kind: 'User' }]); } if (!isOpen) { clearForm(); diff --git a/src/components/Members/AddEditMemberDialog.tsx b/src/components/Members/AddEditMemberDialog.tsx index 2d577f40..aff541ad 100644 --- a/src/components/Members/AddEditMemberDialog.tsx +++ b/src/components/Members/AddEditMemberDialog.tsx @@ -91,7 +91,7 @@ export const AddEditMemberDialog: FC = ({ if (memberToEdit) { reset({ name: memberToEdit.name, - role: memberToEdit.role || MemberRoles.view, + role: memberToEdit.roles?.[0] || MemberRoles.view, accountType: memberToEdit.kind === 'User' ? 'User' : 'ServiceAccount', namespace: memberToEdit?.namespace || '', }); @@ -111,7 +111,7 @@ export const AddEditMemberDialog: FC = ({ const newMember: Member = { name: trimmedName, - role: data.role, + roles: [data.role], kind: data.accountType, ...(data.accountType === 'ServiceAccount' && data.namespace && { namespace: data.namespace }), }; diff --git a/src/components/Members/MemberTable.tsx b/src/components/Members/MemberTable.tsx index 5513408d..69e7a2b9 100644 --- a/src/components/Members/MemberTable.tsx +++ b/src/components/Members/MemberTable.tsx @@ -111,11 +111,11 @@ export const MemberTable: FC = ({ ); } - + console.log(members); const data: MemberTableRow[] = members.map((m) => { return { email: m.name, - role: MemberRolesDetailed[m.role as MemberRoles]?.displayValue, + role: MemberRolesDetailed[m.roles?.[0] as MemberRoles]?.displayValue ?? m.roles?.toString(), kind: m.kind, namespace: m.namespace ?? '', _member: m, diff --git a/src/components/Wizards/CreateManagedControlPlane/CreateManagedControlPlaneWizardContainer.tsx b/src/components/Wizards/CreateManagedControlPlane/CreateManagedControlPlaneWizardContainer.tsx index 8f952082..17707c24 100644 --- a/src/components/Wizards/CreateManagedControlPlane/CreateManagedControlPlaneWizardContainer.tsx +++ b/src/components/Wizards/CreateManagedControlPlane/CreateManagedControlPlaneWizardContainer.tsx @@ -111,7 +111,7 @@ export const CreateManagedControlPlaneWizardContainer: FC { if (user?.email && isOpen) { - setValue('members', [{ name: user.email, role: MemberRoles.admin, kind: 'User' }]); + setValue('members', [{ name: user.email, roles: [MemberRoles.admin], kind: 'User' }]); } if (!isOpen) { clearFormFields(); diff --git a/src/components/Wizards/CreateManagedControlPlane/SummarizeStep.tsx b/src/components/Wizards/CreateManagedControlPlane/SummarizeStep.tsx index 62a0686d..d9805f24 100644 --- a/src/components/Wizards/CreateManagedControlPlane/SummarizeStep.tsx +++ b/src/components/Wizards/CreateManagedControlPlane/SummarizeStep.tsx @@ -37,7 +37,7 @@ export const SummarizeStep: React.FC = ({ watch, projectName
{watch('members').map((member) => ( - + ))}
diff --git a/src/lib/api/types/crate/createManagedControlPlane.ts b/src/lib/api/types/crate/createManagedControlPlane.ts index 13b1b6b6..b1a1dc06 100644 --- a/src/lib/api/types/crate/createManagedControlPlane.ts +++ b/src/lib/api/types/crate/createManagedControlPlane.ts @@ -135,7 +135,7 @@ export const CreateManagedControlPlane = ( authorization: { roleBindings: optional?.members?.map((member) => ({ - role: member.role, + role: member.roles?.[0], subjects: [ { kind: member.kind as AccountType, diff --git a/src/lib/api/types/crate/createProject.ts b/src/lib/api/types/crate/createProject.ts index 95bd00e5..eefeb309 100644 --- a/src/lib/api/types/crate/createProject.ts +++ b/src/lib/api/types/crate/createProject.ts @@ -45,10 +45,10 @@ export const CreateProject = ( }, spec: { members: - optional?.members?.map(({ kind, namespace, role, name }) => ({ + optional?.members?.map(({ kind, namespace, roles, name }) => ({ kind, name, - roles: [role], + roles, namespace: kind === 'ServiceAccount' ? (namespace ?? 'default') : undefined, })) ?? [], }, diff --git a/src/lib/api/types/crate/createWorkspace.ts b/src/lib/api/types/crate/createWorkspace.ts index 6f682b97..7b7c5fa0 100644 --- a/src/lib/api/types/crate/createWorkspace.ts +++ b/src/lib/api/types/crate/createWorkspace.ts @@ -48,10 +48,10 @@ export const CreateWorkspace = ( }, spec: { members: - optional?.members?.map(({ kind, namespace, role, name }) => ({ + optional?.members?.map(({ kind, namespace, roles, name }) => ({ kind, name, - roles: [role], + roles, namespace: kind === 'ServiceAccount' ? (namespace ?? 'default') : undefined, })) ?? [], }, diff --git a/src/lib/api/types/crate/listWorkspaces.ts b/src/lib/api/types/crate/listWorkspaces.ts index ffbbe59c..db45adce 100644 --- a/src/lib/api/types/crate/listWorkspaces.ts +++ b/src/lib/api/types/crate/listWorkspaces.ts @@ -22,6 +22,6 @@ export function isWorkspaceReady(workspace: ListWorkspacesType): boolean { export const ListWorkspaces = (projectName?: string): Resource => { return { path: `/apis/core.openmcp.cloud/v1alpha1/namespaces/project-${projectName}/workspaces`, - jq: '[.items[] | {metadata: .metadata | {name, namespace, annotations, deletionTimestamp}, status: .status, spec: .spec | {members: [.members[] | {name, roles}]}}]', + jq: '[.items[] | {metadata: .metadata | {name, namespace, annotations, deletionTimestamp}, status: .status, spec: .spec | {members: [.members[] | {name, roles, kind, namespace}]}}]', }; }; diff --git a/src/lib/api/types/shared/members.ts b/src/lib/api/types/shared/members.ts index f24b5c9d..bead99f7 100644 --- a/src/lib/api/types/shared/members.ts +++ b/src/lib/api/types/shared/members.ts @@ -25,7 +25,7 @@ export enum MemberKind { export interface Member { kind: string; name: string; - role: string; + roles: string[]; namespace?: string; } From 9b833dc52932621dfa96571979d4d01e15e3e171 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Goral?= Date: Thu, 28 Aug 2025 14:06:49 +0200 Subject: [PATCH 10/75] Update MemberTable.tsx --- src/components/Members/MemberTable.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/components/Members/MemberTable.tsx b/src/components/Members/MemberTable.tsx index 69e7a2b9..35704ed0 100644 --- a/src/components/Members/MemberTable.tsx +++ b/src/components/Members/MemberTable.tsx @@ -111,7 +111,6 @@ export const MemberTable: FC = ({ ); } - console.log(members); const data: MemberTableRow[] = members.map((m) => { return { email: m.name, From ba36f9479d92b7e30e593a0000fac0d3a3a271a6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Goral?= Date: Thu, 28 Aug 2025 14:26:42 +0200 Subject: [PATCH 11/75] Update ImportMembersDialog.tsx --- src/components/Members/ImportMembersDialog.tsx | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/components/Members/ImportMembersDialog.tsx b/src/components/Members/ImportMembersDialog.tsx index 85a3bbb9..cdc7a3ae 100644 --- a/src/components/Members/ImportMembersDialog.tsx +++ b/src/components/Members/ImportMembersDialog.tsx @@ -22,6 +22,7 @@ import { ACCOUNT_TYPES } from './EditMembers.tsx'; import { ResourceObject } from '../../lib/api/types/crate/resourceObject.ts'; import { useApiResource } from '../../lib/api/useApiResource.ts'; +import { useTranslation } from 'react-i18next'; type ParentType = 'Workspace' | 'Project'; @@ -47,7 +48,7 @@ export const ImportMembersDialog: FC = ({ onImport, }) => { const [step, setStep] = useState(1); - + const { t } = useTranslation(); const formSchema = useMemo( () => z @@ -198,7 +199,7 @@ const ImportMembersSelectionTable: FC<{ const mockedMembers: Member[] = membersData.map(({ name, namespace, kind, roles }) => ({ kind, name, - role: roles.includes('admin') ? 'admin' : 'view', + roles, namespace, })); @@ -252,7 +253,7 @@ const ImportMembersSelectionTable: FC<{ const data: SelectionRow[] = filteredMockedMembers.map((m) => ({ email: m.name, - role: MemberRolesDetailed[m.role as MemberRoles]?.displayValue, + role: MemberRolesDetailed[m.roles?.[0] as MemberRoles]?.displayValue, kind: m.kind, _member: m, })); From 74b192dbb1bb817948d60a4213d0f8839da49677 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Goral?= Date: Thu, 28 Aug 2025 14:49:24 +0200 Subject: [PATCH 12/75] refactor --- public/locales/en.json | 12 ++++- .../Dialogs/CreateProjectWorkspaceDialog.tsx | 3 +- src/components/Members/EditMembers.tsx | 22 +++++---- .../Members/ImportMembersDialog.module.css | 9 ++++ .../Members/ImportMembersDialog.tsx | 46 +++++++++---------- ...eateManagedControlPlaneWizardContainer.tsx | 1 + 6 files changed, 57 insertions(+), 36 deletions(-) create mode 100644 src/components/Members/ImportMembersDialog.module.css diff --git a/public/locales/en.json b/public/locales/en.json index ccea36bc..226d56e6 100644 --- a/public/locales/en.json +++ b/public/locales/en.json @@ -7,7 +7,8 @@ }, "Entities": { "ManagedControlPlane": "Managed Control Plane", - "Project": "Project" + "Project": "Project", + "Workspace": "Workspace" }, "ComponentList": { "tableComponentHeader": "Name", @@ -312,6 +313,7 @@ "maxChars": "Max length is {{maxLength}} characters.", "userExists": "User with this name already exists!", "atLeastOneUser": "You need to have at least one member assigned.", + "selectAtLeastOneOption": "Select at least one option", "notValidChargingTargetFormat": "Use lowercase letters a-f, numbers 0-9, and hyphens (-) in the format: aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa" }, "common": { @@ -421,4 +423,12 @@ "activate": "Activate" } } + , + "ImportMembersDialog": { + "dialogTitle": "Import members", + "chooseParentLabel": "Choose parent to import members from", + "whatToImportLabel": "What would you like to import?", + "serviceAccountsLabel": "Service Accounts", + "addMembersButton": "Add members" + } } diff --git a/src/components/Dialogs/CreateProjectWorkspaceDialog.tsx b/src/components/Dialogs/CreateProjectWorkspaceDialog.tsx index 8907159e..f07d8cbf 100644 --- a/src/components/Dialogs/CreateProjectWorkspaceDialog.tsx +++ b/src/components/Dialogs/CreateProjectWorkspaceDialog.tsx @@ -35,7 +35,7 @@ export interface CreateProjectWorkspaceDialogProps { errors: FieldErrors; setValue: UseFormSetValue; projectName?: string; - type: 'workspace' | 'project'; + type: 'workspace' | 'project' | 'mcp'; watch: UseFormWatch; } @@ -94,6 +94,7 @@ export function CreateProjectWorkspaceDialog({ sideFormContent={ = ({ requireAtLeastOneMember = true, workspaceName, projectName, + type, }) => { const { t } = useTranslation(); @@ -111,15 +113,17 @@ export const EditMembers: FC = ({ > {t('EditMembers.addButton')} - + {type !== 'project' && ( + + )} = ({ }) .refine((data) => data.importMembers || data.importServiceAccounts, { path: ['importMembers'], - message: 'Select at least one option', + message: t('validationErrors.selectAtLeastOneOption'), }), - [], + [t], ); const { handleSubmit, setValue, watch, reset, getValues } = useForm({ @@ -82,8 +83,6 @@ export const ImportMembersDialog: FC = ({ setStep(2); }; - console.log(projectName); - console.log(workspaceName); useEffect(() => { if (!open) { setStep(1); @@ -91,10 +90,10 @@ export const ImportMembersDialog: FC = ({ } }, [open, reset]); return ( - + {step === 1 && ( - + - + ) => setValue('importMembers', e.target.checked, { @@ -121,7 +120,7 @@ export const ImportMembersDialog: FC = ({ } /> ) => setValue('importServiceAccounts', e.target.checked, { @@ -135,10 +134,10 @@ export const ImportMembersDialog: FC = ({ @@ -179,14 +178,11 @@ const ImportMembersSelectionTable: FC<{ workspaceName?: string; projectName?: string; }> = ({ onCancel, onImport, parentType, workspaceName, projectName, includeMembers, includeServiceAccounts }) => { - const { - isLoading, - data: parentResourceData, - error, - } = useApiResource( + const { t } = useTranslation(); + const { isLoading, data: parentResourceData } = useApiResource( parentType === 'Project' ? ResourceObject('', 'projects', projectName ?? '') - : ResourceObject(workspaceName ?? '', 'projects', projectName ?? ''), + : ResourceObject(projectName ?? '', 'workspaces', workspaceName ?? ''), undefined, true, ); @@ -233,9 +229,9 @@ const ImportMembersSelectionTable: FC<{ ); }, }, - { Header: 'Email', accessor: 'email' }, + { Header: t('MemberTable.columnEmailHeader'), accessor: 'email' }, { - Header: 'Type', + Header: t('MemberTable.columnTypeHeader'), accessor: 'kind', width: 145, Cell: (instance: { cell: { row: { original: SelectionRow } } }) => { @@ -248,7 +244,7 @@ const ImportMembersSelectionTable: FC<{ ); }, }, - { Header: 'Role', accessor: 'role', width: 120 }, + { Header: t('MemberTable.columnRoleHeader'), accessor: 'role', width: 120 }, ]; const data: SelectionRow[] = filteredMockedMembers.map((m) => ({ @@ -266,14 +262,14 @@ const ImportMembersSelectionTable: FC<{ return ( - + diff --git a/src/components/Wizards/CreateManagedControlPlane/CreateManagedControlPlaneWizardContainer.tsx b/src/components/Wizards/CreateManagedControlPlane/CreateManagedControlPlaneWizardContainer.tsx index 1d5cf86c..e6b63c42 100644 --- a/src/components/Wizards/CreateManagedControlPlane/CreateManagedControlPlaneWizardContainer.tsx +++ b/src/components/Wizards/CreateManagedControlPlane/CreateManagedControlPlaneWizardContainer.tsx @@ -283,6 +283,7 @@ export const CreateManagedControlPlaneWizardContainer: FC From 402c41035c18625c463372ea62d2ab71811916c2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Goral?= Date: Thu, 28 Aug 2025 14:56:28 +0200 Subject: [PATCH 13/75] fix --- public/locales/en.json | 4 +++- src/components/Members/ImportMembersDialog.tsx | 11 +++++++++++ 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/public/locales/en.json b/public/locales/en.json index 226d56e6..1ecbab57 100644 --- a/public/locales/en.json +++ b/public/locales/en.json @@ -429,6 +429,8 @@ "chooseParentLabel": "Choose parent to import members from", "whatToImportLabel": "What would you like to import?", "serviceAccountsLabel": "Service Accounts", - "addMembersButton": "Add members" + "addMembersButton": "Add members", + "selectAllButton": "Select all", + "deselectAllButton": "Deselect all" } } diff --git a/src/components/Members/ImportMembersDialog.tsx b/src/components/Members/ImportMembersDialog.tsx index 822b20c4..1ba322bd 100644 --- a/src/components/Members/ImportMembersDialog.tsx +++ b/src/components/Members/ImportMembersDialog.tsx @@ -262,6 +262,17 @@ const ImportMembersSelectionTable: FC<{ return ( + + + + From ccdd7a5a5a3b454c0457189b459c08c5d3b3b7a4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Goral?= Date: Thu, 28 Aug 2025 14:57:49 +0200 Subject: [PATCH 14/75] Update ImportMembersDialog.tsx --- src/components/Members/ImportMembersDialog.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/components/Members/ImportMembersDialog.tsx b/src/components/Members/ImportMembersDialog.tsx index 1ba322bd..73fcc27c 100644 --- a/src/components/Members/ImportMembersDialog.tsx +++ b/src/components/Members/ImportMembersDialog.tsx @@ -86,7 +86,7 @@ export const ImportMembersDialog: FC = ({ useEffect(() => { if (!open) { setStep(1); - reset({ parentType: 'Project', importMembers: false, importServiceAccounts: false }); + reset({ parentType: 'Project', importMembers: true, importServiceAccounts: true }); } }, [open, reset]); return ( @@ -110,7 +110,7 @@ export const ImportMembersDialog: FC = ({ ) => setValue('importMembers', e.target.checked, { shouldValidate: true, @@ -121,7 +121,7 @@ export const ImportMembersDialog: FC = ({ /> ) => setValue('importServiceAccounts', e.target.checked, { shouldValidate: true, From 0ae01d9e2651c1b6efff673d064df5065c82e35f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Goral?= Date: Fri, 29 Aug 2025 09:24:00 +0200 Subject: [PATCH 15/75] Update ImportMembersDialog.tsx --- src/components/Members/ImportMembersDialog.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/Members/ImportMembersDialog.tsx b/src/components/Members/ImportMembersDialog.tsx index 73fcc27c..ae3df70c 100644 --- a/src/components/Members/ImportMembersDialog.tsx +++ b/src/components/Members/ImportMembersDialog.tsx @@ -190,7 +190,7 @@ const ImportMembersSelectionTable: FC<{ if (isLoading) { return ; } - console.log(parentResourceData?.spec?.members); + const membersData = parentResourceData?.spec?.members ?? []; const mockedMembers: Member[] = membersData.map(({ name, namespace, kind, roles }) => ({ kind, From 3dd5531f4cb58a662ce77574667b72836d265a51 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Goral?= Date: Fri, 29 Aug 2025 09:30:45 +0200 Subject: [PATCH 16/75] Update ImportMembersDialog.tsx --- .../Members/ImportMembersDialog.tsx | 44 ++++++++++++------- 1 file changed, 28 insertions(+), 16 deletions(-) diff --git a/src/components/Members/ImportMembersDialog.tsx b/src/components/Members/ImportMembersDialog.tsx index ae3df70c..37d5cd86 100644 --- a/src/components/Members/ImportMembersDialog.tsx +++ b/src/components/Members/ImportMembersDialog.tsx @@ -24,6 +24,7 @@ import { ACCOUNT_TYPES } from './EditMembers.tsx'; import { ResourceObject } from '../../lib/api/types/crate/resourceObject.ts'; import { useApiResource } from '../../lib/api/useApiResource.ts'; import { useTranslation } from 'react-i18next'; +import IllustratedError from '../Shared/IllustratedError.tsx'; type ParentType = 'Workspace' | 'Project'; @@ -65,7 +66,7 @@ export const ImportMembersDialog: FC = ({ [t], ); - const { handleSubmit, setValue, watch, reset, getValues } = useForm({ + const { handleSubmit, setValue, watch, reset } = useForm({ resolver: zodResolver(formSchema), mode: 'onChange', defaultValues: { @@ -97,8 +98,8 @@ export const ImportMembersDialog: FC = ({ ) => { - const selected = e.detail.selectedOption?.dataset?.value as ParentType; + onChange={(e: CustomEvent<{ selectedOption: { value?: string } }>) => { + const selected = e.detail.selectedOption?.value as ParentType; setValue('parentType', selected, { shouldValidate: true, shouldDirty: true, shouldTouch: true }); }} > @@ -187,7 +187,7 @@ const ImportMembersSelectionTable: FC<{ } = useApiResource( parentType === 'Project' ? ResourceObject('', 'projects', projectName ?? '') - : ResourceObject(projectName ?? '', 'workspaces', workspaceName ?? ''), + : ResourceObject(`project-${projectName ?? ''}`, 'workspaces', workspaceName ?? ''), undefined, true, ); From 781b8734ebb8a40a3aec442f2674df3000a73df9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Goral?= Date: Fri, 29 Aug 2025 11:38:53 +0200 Subject: [PATCH 18/75] fix --- src/components/Members/EditMembers.tsx | 9 +++++++-- src/components/Members/ImportMembersDialog.module.css | 2 +- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/src/components/Members/EditMembers.tsx b/src/components/Members/EditMembers.tsx index a2e3aac4..2ca5475f 100644 --- a/src/components/Members/EditMembers.tsx +++ b/src/components/Members/EditMembers.tsx @@ -1,4 +1,4 @@ -import { FC, useCallback, useState } from 'react'; +import { FC, useCallback, useMemo, useState } from 'react'; import { Button, FlexBox } from '@ui5/webcomponents-react'; import { MemberTable } from './MemberTable.tsx'; import { Member } from '../../lib/api/types/shared/members'; @@ -104,6 +104,11 @@ export const EditMembers: FC = ({ [members, onMemberChanged, memberToEdit], ); + const computedProjectName = useMemo( + () => (type === 'mcp' ? removeProjectPrefix(projectName) : projectName), + [type, projectName], + ); + return ( - {type !== 'project' && ( + - )} + {type !== 'project' && ( + + )} + = ({ const columns: AnalyticalTableColumnDefinition[] = [ { - Header: t('MemberTable.columnEmailHeader'), + Header: t('MemberTable.columnNameHeader'), accessor: 'email', }, @@ -70,36 +70,30 @@ export const MemberTable: FC = ({ }, ]; - if (onEditMember) { + if (onEditMember && onDeleteMember) { columns.push({ Header: '', id: 'edit', - width: 50, + width: 100, Cell: (instance: CellInstance) => ( - )} @@ -140,13 +139,15 @@ export const EditMembers: FC = ({ onSave={handleSaveMember} /> - + {computedProjectName && ( + + )} void; onImport: (members: Member[]) => void; - projectName?: string; + projectName: string; workspaceName?: string; -}; - +} export const ImportMembersDialog: FC = ({ - open, + isOpen, projectName, workspaceName, onClose, onImport, }) => { - const [step, setStep] = useState(1); - const { t } = useTranslation(); - const formSchema = useMemo( - () => - z - .object({ - parentType: z.union([z.literal('Workspace'), z.literal('Project'), z.literal('')]), - importMembers: z.boolean(), - importServiceAccounts: z.boolean(), - }) - .refine((data) => data.importMembers || data.importServiceAccounts, { - path: ['importMembers'], - message: t('validationErrors.selectAtLeastOneOption'), - }), - [t], - ); - - const { handleSubmit, setValue, watch, reset } = useForm({ - resolver: zodResolver(formSchema), - mode: 'onChange', - defaultValues: { - parentType: 'Project', - importMembers: true, - importServiceAccounts: true, - }, - }); - - const parentType = watch('parentType'); - const importMembers = watch('importMembers'); - const importServiceAccounts = watch('importServiceAccounts'); - const canProceed = parentType !== '' && (importMembers || importServiceAccounts); - const onSubmitStepOne = () => { - setStep(2); - }; - - useEffect(() => { - if (!open) { - setStep(1); - reset({ parentType: 'Project', importMembers: true, importServiceAccounts: true }); - } - }, [open, reset]); - return ( - - {step === 1 && ( - - - - - - - ) => - setValue('importMembers', e.target.checked, { - shouldValidate: true, - shouldDirty: true, - shouldTouch: true, - }) - } - /> - ) => - setValue('importServiceAccounts', e.target.checked, { - shouldValidate: true, - shouldDirty: true, - shouldTouch: true, - }) - } - /> - - - - - - - - )} - - {step === 2 && ( - - )} - - ); -}; + const [filteredFor, setFilteredFor] = useState('All'); + const [sourceType, setSourceType] = useState('Project'); + const [selectedRowIds, setSelectedRowIds] = useState({}); -type SelectionRow = { - email: string; - role: string; - kind: string; - _member: Member; -}; - -interface SpecMembers { - spec?: { members: { name: string; roles: string[]; kind: 'User' | 'ServiceAccount'; namespace?: string }[] }; -} - -const ImportMembersSelectionTable: FC<{ - onCancel: () => void; - onImport: (members: Member[]) => void; - parentType: ParentType; - includeMembers: boolean; - includeServiceAccounts: boolean; - workspaceName?: string; - projectName?: string; -}> = ({ onCancel, onImport, parentType, workspaceName, projectName, includeMembers, includeServiceAccounts }) => { const { t } = useTranslation(); + const { isLoading, data: parentResourceData, error, } = useApiResource( - parentType === 'Project' - ? ResourceObject('', 'projects', projectName ?? '') - : ResourceObject(`project-${projectName ?? ''}`, 'workspaces', workspaceName ?? ''), + sourceType === 'Project' + ? ResourceObject('', 'projects', projectName) + : ResourceObject(`project-${projectName}`, 'workspaces', workspaceName ?? ''), undefined, true, - ); - const [selectedEmails, setSelectedEmails] = useState>(new Set()); - if (isLoading) { - return ; - } - - const membersData = parentResourceData?.spec?.members ?? []; - const mockedMembers: Member[] = membersData.map(({ name, namespace, kind, roles }) => ({ - kind, - name, - roles, - namespace, - })); - - const filteredMockedMembers: Member[] = mockedMembers.filter( - (m) => (m.kind === 'User' && includeMembers) || (m.kind === 'ServiceAccount' && includeServiceAccounts), + !isOpen, ); - const columns: AnalyticalTableColumnDefinition[] = [ - { - Header: '', - id: 'select', - width: 60, - Cell: (instance: { cell: { row: { original: SelectionRow } } }) => { - const email = instance.cell.row.original.email; - const checked = selectedEmails.has(email); - return ( - ) => { - setSelectedEmails((prev) => { - const next = new Set(prev); - if (e.target.checked) { - next.add(email); - } else { - next.delete(email); - } - return next; - }); - }} - /> - ); + const selectedMembersCount = Object.keys(selectedRowIds ?? {}).length; + + const tableData: TableRow[] = useMemo(() => { + const members = parentResourceData?.spec?.members ?? []; + const showUsers = filteredFor !== 'ServiceAccounts'; + const showServiceAccounts = filteredFor !== 'Users'; + + return members + .filter(({ kind }) => (kind === 'User' && showUsers) || (kind === 'ServiceAccount' && showServiceAccounts)) + .map((m) => ({ + email: m.name, + role: MemberRolesDetailed[m.roles?.[0] as MemberRoles]?.displayValue, + kind: m.kind, + _member: m, + })); + }, [parentResourceData, filteredFor]); + + const columns: AnalyticalTableColumnDefinition[] = useMemo( + () => [ + { Header: t('MemberTable.columnNameHeader'), accessor: 'email' }, + { + Header: t('MemberTable.columnTypeHeader'), + accessor: 'kind', + width: 145, + Cell: (instance: { cell: { row: { original: TableRow } } }) => { + const kind = ACCOUNT_TYPES.find(({ value }) => value === instance.cell.row.original.kind); + return ( + + + {kind?.label} + + ); + }, }, - }, - { Header: t('MemberTable.columnEmailHeader'), accessor: 'email' }, - { - Header: t('MemberTable.columnTypeHeader'), - accessor: 'kind', - width: 145, - Cell: (instance: { cell: { row: { original: SelectionRow } } }) => { - const kind = ACCOUNT_TYPES.find(({ value }) => value === instance.cell.row.original.kind); - return ( - - - {kind?.label} - - ); - }, - }, - { Header: t('MemberTable.columnRoleHeader'), accessor: 'role', width: 120 }, - ]; + { Header: t('MemberTable.columnRoleHeader'), accessor: 'role', width: 120 }, + ], + [t], + ); - const data: SelectionRow[] = filteredMockedMembers.map((m) => ({ - email: m.name, - role: MemberRolesDetailed[m.roles?.[0] as MemberRoles]?.displayValue, - kind: m.kind, - _member: m, - })); + useEffect(() => { + setSelectedRowIds({}); + }, [isOpen]); const handleAddMembers = () => { - const selected = filteredMockedMembers.filter((m) => selectedEmails.has(m.name)); - onImport(selected); - onCancel(); + const selectedMembers = Object.entries(selectedRowIds ?? {}) + .filter(([, isSelected]) => isSelected) + .map(([idx]) => tableData[Number(idx)]._member); + + onImport(selectedMembers); + onClose(); }; return ( - + + + + + } + /> + } + onClose={onClose} + > {error ? ( ) : ( -
- - - - - - -
+ <> +
+
+ +
+
+ +
+
+ +
+
+ { + setFilteredFor(e.detail.selectedItems[0].dataset.id as FilteredFor); + setSelectedRowIds({}); + }} + > + + {t('common.all')} + + + {t('Entities.Users')} + + + {t('Entities.ServiceAccounts')} + + +
+
+ +
+ { + setSelectedRowIds(e?.detail.selectedRowIds); + }} + /> +
+ )} - - - - - -
+ ); }; + +function getAddMembersButtonText(selectedMembersCount: number, t: TFunction) { + let addButtonText = ''; + switch (selectedMembersCount) { + case 0: + addButtonText = t('ImportMembersDialog.addMembersButton0'); + break; + case 1: + addButtonText = t('ImportMembersDialog.addMembersButton1'); + break; + default: + addButtonText = t('ImportMembersDialog.addMembersButtonN', { count: selectedMembersCount }); + break; + } + return addButtonText; +} + +interface SpecMembers { + spec?: { members: { name: string; roles: string[]; kind: 'User' | 'ServiceAccount'; namespace?: string }[] }; +} diff --git a/src/lib/api/useApiResource.ts b/src/lib/api/useApiResource.ts index 42f304ef..3563ebed 100644 --- a/src/lib/api/useApiResource.ts +++ b/src/lib/api/useApiResource.ts @@ -10,13 +10,16 @@ import { MutatorOptions } from 'swr/_internal'; import { CRDRequest, CRDResponse } from './types/crossplane/CRDList'; import { ProviderConfigs, ProviderConfigsData, ProviderConfigsDataForRequest } from '../shared/types'; -export const useApiResource = (resource: Resource, config?: SWRConfiguration, excludeMcpConfig?: boolean) => { +export const useApiResource = ( + resource: Resource, + config?: SWRConfiguration, + excludeMcpConfig?: boolean, + disable?: boolean, +) => { const apiConfig = useContext(ApiConfigContext); const { data, error, isLoading, isValidating } = useSWR( - resource.path === null - ? null //TODO: is null a valid key? - : [resource.path, apiConfig], + disable || resource.path === null ? null : [resource.path, apiConfig], ([path, apiConfig]) => fetchApiServerJson( path, From 3aa41c97c63709d78ae3bfba095178f972246dd7 Mon Sep 17 00:00:00 2001 From: Andreas Kienle Date: Thu, 4 Sep 2025 14:26:32 +0200 Subject: [PATCH 24/75] Implement review feedback --- src/components/Members/EditMembers.tsx | 4 +++- src/components/Members/ImportMembersDialog.tsx | 11 +++-------- 2 files changed, 6 insertions(+), 9 deletions(-) diff --git a/src/components/Members/EditMembers.tsx b/src/components/Members/EditMembers.tsx index 0538639e..e899aa10 100644 --- a/src/components/Members/EditMembers.tsx +++ b/src/components/Members/EditMembers.tsx @@ -25,7 +25,9 @@ export const ACCOUNT_TYPES: RadioButtonsSelectOption[] = [ export type AccountType = 'User' | 'ServiceAccount'; -const removeProjectPrefix = (name?: string) => (name?.startsWith('project-') ? name.slice('project-'.length) : name); +const PROJECT_PREFIX = 'project-'; +const removeProjectPrefix = (name?: string) => + name?.startsWith(PROJECT_PREFIX) ? name.slice(PROJECT_PREFIX.length) : name; export const EditMembers: FC = ({ members, diff --git a/src/components/Members/ImportMembersDialog.tsx b/src/components/Members/ImportMembersDialog.tsx index f225bee3..0128976c 100644 --- a/src/components/Members/ImportMembersDialog.tsx +++ b/src/components/Members/ImportMembersDialog.tsx @@ -212,19 +212,14 @@ export const ImportMembersDialog: FC = ({ }; function getAddMembersButtonText(selectedMembersCount: number, t: TFunction) { - let addButtonText = ''; switch (selectedMembersCount) { case 0: - addButtonText = t('ImportMembersDialog.addMembersButton0'); - break; + return t('ImportMembersDialog.addMembersButton0'); case 1: - addButtonText = t('ImportMembersDialog.addMembersButton1'); - break; + return t('ImportMembersDialog.addMembersButton1'); default: - addButtonText = t('ImportMembersDialog.addMembersButtonN', { count: selectedMembersCount }); - break; + return t('ImportMembersDialog.addMembersButtonN', { count: selectedMembersCount }); } - return addButtonText; } interface SpecMembers { From 503af2a0b76d853a97dd9f8be0db3f3ca58ad7c5 Mon Sep 17 00:00:00 2001 From: Andreas Kienle Date: Thu, 4 Sep 2025 16:52:04 +0200 Subject: [PATCH 25/75] Add toast --- public/locales/en.json | 9 ++- src/components/Members/EditMembers.tsx | 56 ++++++++++++++-- src/lib/api/types/shared/members.spec.ts | 84 ++++++++++++++++++++++++ src/lib/api/types/shared/members.ts | 11 ++++ 4 files changed, 151 insertions(+), 9 deletions(-) create mode 100644 src/lib/api/types/shared/members.spec.ts diff --git a/public/locales/en.json b/public/locales/en.json index 87e89528..dd5c315b 100644 --- a/public/locales/en.json +++ b/public/locales/en.json @@ -174,7 +174,12 @@ "saveButton": "Save changes", "defaultNamespaceInfo": "Leave empty to use default namespace", "serviceAccoutsGuide": "You can also use our Service Account Guide for more information.", - "reuseMembersButton": "Reuse" + "reuseMembersButton": "Reuse", + "membersToastNoChanges": "No changes.", + "membersToastAdded1": "1 member added.", + "membersToastAddedN": "{{count}} members added.", + "membersToastChanged1": "1 member changed.", + "membersToastChangedN": "{{count}} members changed." }, "ProjectsPage": { @@ -432,7 +437,7 @@ "reuseFromLabel": "Reuse from", "filterForLabel": "Filter for", "addMembersButton0": "Add members", - "addMembersButton1": "Add 1 member", + "addMembersButton1": "Add member", "addMembersButtonN": "Add {{count}} members" } } diff --git a/src/components/Members/EditMembers.tsx b/src/components/Members/EditMembers.tsx index e899aa10..5209e693 100644 --- a/src/components/Members/EditMembers.tsx +++ b/src/components/Members/EditMembers.tsx @@ -1,12 +1,14 @@ import { FC, useCallback, useMemo, useState } from 'react'; import { Button, FlexBox } from '@ui5/webcomponents-react'; import { MemberTable } from './MemberTable.tsx'; -import { Member } from '../../lib/api/types/shared/members'; +import { areMembersEqual, Member } from '../../lib/api/types/shared/members'; import { useTranslation } from 'react-i18next'; import styles from './Members.module.css'; import { RadioButtonsSelectOption } from '../Ui/RadioButtonsSelect/RadioButtonsSelect.tsx'; import { AddEditMemberDialog } from './AddEditMemberDialog.tsx'; import { ImportMembersDialog } from './ImportMembersDialog.tsx'; +import { useToast } from '../../context/ToastContext.tsx'; +import { TFunction } from 'i18next'; export interface EditMembersProps { members: Member[]; @@ -73,15 +75,29 @@ export const EditMembers: FC = ({ setIsImportDialogOpen(false); }, []); + const toast = useToast(); + const handleImportMembers = useCallback( (imported: Member[]) => { - const byName = new Map(); - members.forEach((m) => byName.set(m.name, m)); - imported.forEach((m) => byName.set(m.name, m)); - const merged = Array.from(byName.values()); - onMemberChanged(merged); + let numberOfAddedMembers = 0; + let numberOfChangedMembers = 0; + + const membersByName = new Map(members.map((member) => [member.name, member])); + imported.forEach((importedMember) => { + const existingMember = membersByName.get(importedMember.name); + if (!existingMember) { + numberOfAddedMembers++; + } else if (!areMembersEqual(importedMember, existingMember)) { + numberOfChangedMembers++; + } + membersByName.set(importedMember.name, importedMember); + }); + const updatedMembers = Array.from(membersByName.values()); + + toast.show(buildToastMessage(numberOfAddedMembers, numberOfChangedMembers, t)); + onMemberChanged(updatedMembers); }, - [members, onMemberChanged], + [members, onMemberChanged, t], ); const handleSaveMember = useCallback( @@ -161,3 +177,29 @@ export const EditMembers: FC = ({
); }; + +function buildToastMessage(addedCount: number, changedCount: number, t: TFunction) { + const messages: string[] = []; + + if (addedCount === 0 && changedCount === 0) { + return t('EditMembers.membersToastNoChanges'); + } + + if (addedCount > 0) { + messages.push( + addedCount === 1 + ? t('EditMembers.membersToastAdded1') + : t('EditMembers.membersToastAddedN', { count: addedCount }), + ); + } + + if (changedCount > 0) { + messages.push( + changedCount === 1 + ? t('EditMembers.membersToastChanged1') + : t('EditMembers.membersToastChangedN', { count: changedCount }), + ); + } + + return messages.join(' '); +} diff --git a/src/lib/api/types/shared/members.spec.ts b/src/lib/api/types/shared/members.spec.ts new file mode 100644 index 00000000..90768c47 --- /dev/null +++ b/src/lib/api/types/shared/members.spec.ts @@ -0,0 +1,84 @@ +import { describe, it, expect } from 'vitest'; +import { areMembersEqual, Member } from './members.ts'; + +const makeMember = (overrides: Partial = {}): Member => ({ + kind: 'User', + name: 'alice', + namespace: 'default-namespace', + roles: ['Viewer', 'Admin'], + ...overrides, +}); + +describe('members', () => { + describe('areMembersEqual', () => { + it('returns true when a and b are the same reference', () => { + const a = makeMember(); + expect(areMembersEqual(a, a)).toBe(true); + }); + + it('returns true when the members have the very same attributes', () => { + const a = makeMember(); + const b = makeMember(); + expect(areMembersEqual(a, b)).toBe(true); + }); + + it('returns true even if the roles are sorted differently', () => { + const a = makeMember({ roles: ['Viewer', 'Admin'] }); + const b = makeMember({ roles: ['Admin', 'Viewer'] }); + expect(areMembersEqual(a, b)).toBe(true); + }); + + it('handles empty roles correctly', () => { + const a = makeMember({ roles: [] }); + const b = makeMember({ roles: [] }); + expect(areMembersEqual(a, b)).toBe(true); + }); + + it('returns false when b is undefined', () => { + const a = makeMember(); + expect(areMembersEqual(a, undefined)).toBe(false); + }); + + it('returns false when kinds differ', () => { + const a = makeMember({ kind: 'User' }); + const b = makeMember({ kind: 'ServiceAccount' }); + expect(areMembersEqual(a, b)).toBe(false); + }); + + it('returns false when names differ', () => { + const a = makeMember({ name: 'alice' }); + const b = makeMember({ name: 'bob' }); + expect(areMembersEqual(a, b)).toBe(false); + }); + + it('returns false when namespaces differ', () => { + const a = makeMember({ namespace: 'namespace-a' }); + const b = makeMember({ namespace: 'namespace-b' }); + expect(areMembersEqual(a, b)).toBe(false); + }); + + it('returns false when role counts differ', () => { + const a = makeMember({ roles: ['Viewer', 'Admin'] }); + const b = makeMember({ roles: ['Viewer'] }); + expect(areMembersEqual(a, b)).toBe(false); + }); + + it('returns false when roles differ (same length)', () => { + const a = makeMember({ roles: ['Viewer', 'Admin'] }); + const b = makeMember({ roles: ['Viewer', 'OtherRole'] }); + expect(areMembersEqual(a, b)).toBe(false); + }); + + it('does not treat duplicate roles in a special way (debatable)', () => { + const a = makeMember({ roles: ['Viewer', 'Admin'] }); + const b = makeMember({ roles: ['Viewer', 'Viewer', 'Admin'] }); + expect(areMembersEqual(a, b)).toBe(false); + }); + + it('fails when b has a role a does not have', () => { + const a = makeMember({ roles: ['Viewer'] }); + const b = makeMember({ roles: ['Viewer', 'Admin'] }); + expect(areMembersEqual(a, b)).toBe(false); + }); + }); +}); diff --git a/src/lib/api/types/shared/members.ts b/src/lib/api/types/shared/members.ts index bead99f7..14eeac90 100644 --- a/src/lib/api/types/shared/members.ts +++ b/src/lib/api/types/shared/members.ts @@ -29,6 +29,17 @@ export interface Member { namespace?: string; } +export function areMembersEqual(a: Member, b?: Member): boolean { + return ( + !!b && + a.kind === b.kind && + a.name === b.name && + a.namespace === b.namespace && + a.roles.length === b.roles.length && + a.roles.every((r) => b.roles.includes(r)) + ); +} + export interface MemberPayload { kind: string; name: string; From e8b65f7b533b170aaa2889bb013c6673b3250881 Mon Sep 17 00:00:00 2001 From: Andreas Kienle Date: Thu, 4 Sep 2025 16:54:13 +0200 Subject: [PATCH 26/75] Fix linting --- src/components/Members/EditMembers.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/Members/EditMembers.tsx b/src/components/Members/EditMembers.tsx index 5209e693..1dea17b9 100644 --- a/src/components/Members/EditMembers.tsx +++ b/src/components/Members/EditMembers.tsx @@ -97,7 +97,7 @@ export const EditMembers: FC = ({ toast.show(buildToastMessage(numberOfAddedMembers, numberOfChangedMembers, t)); onMemberChanged(updatedMembers); }, - [members, onMemberChanged, t], + [members, onMemberChanged, t, toast], ); const handleSaveMember = useCallback( From 471e21d7d338331371463208eb0cb54753b0b80f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Goral?= Date: Tue, 9 Sep 2025 11:19:31 +0200 Subject: [PATCH 27/75] init --- public/locales/en.json | 4 +- .../ControlPlaneCard/ControlPlaneCard.tsx | 13 ++-- .../ControlPlaneCard/ControlPlaneCardMenu.tsx | 66 +++++++++++++++++++ 3 files changed, 75 insertions(+), 8 deletions(-) create mode 100644 src/components/ControlPlanes/ControlPlaneCard/ControlPlaneCardMenu.tsx diff --git a/public/locales/en.json b/public/locales/en.json index ccea36bc..fec882d3 100644 --- a/public/locales/en.json +++ b/public/locales/en.json @@ -55,7 +55,9 @@ "loadingErrorMessage": "Failed to list mcps in workspace" }, "ControlPlaneCard": { - "deleteConfirmationDialog": "MCP deletion triggered. The list will refresh automatically once completed." + "deleteConfirmationDialog": "MCP deletion triggered. The list will refresh automatically once completed.", + "deleteMCP": "Delete Managed Control Plane" + }, "ControlPlaneListAllWorkspaces": { "emptyListTitleMessage": "No Workspaces created yet", diff --git a/src/components/ControlPlanes/ControlPlaneCard/ControlPlaneCard.tsx b/src/components/ControlPlanes/ControlPlaneCard/ControlPlaneCard.tsx index 19de48d8..01522a40 100644 --- a/src/components/ControlPlanes/ControlPlaneCard/ControlPlaneCard.tsx +++ b/src/components/ControlPlanes/ControlPlaneCard/ControlPlaneCard.tsx @@ -27,6 +27,8 @@ import { useToast } from '../../../context/ToastContext.tsx'; import { canConnectToMCP } from '../controlPlanes.ts'; import { Infobox } from '../../Ui/Infobox/Infobox.tsx'; +import { ControlPlaneCardMenu } from './ControlPlaneCardMenu.tsx'; + interface Props { controlPlane: ListControlPlanesType; workspace: ListWorkspacesType; @@ -74,13 +76,10 @@ export function ControlPlaneCard({ controlPlane, workspace, projectName }: Props
- - ) : ( - - ))} - - + <> + {isOpen ? ( + + {selectedStep !== 'success' && + (selectedStep === 'metadata' ? ( + + ) : ( + + ))} + + + } + /> } - /> - } - data-testid="create-mcp-dialog" - onClose={resetFormAndClose} - > - - - - - - - - - + + + + + + + + + + + + + {/* this condition is to remount the component from scratch to fix a bug with data loading */} + {selectedStep === 'componentSelection' && ( + + )} + + + - - - - - {/* this condition is to remount the component from scratch to fix a bug with data loading */} - {selectedStep === 'componentSelection' && ( - - )} - - - - - - {isEditMode ? ( - - ) : ( - - )} - - - + + + {isEditMode ? ( + + ) : ( + + )} + + + + ) : ( +
+ )} + ); }; From 442575b63d4a22e7f2c1cd0b4a72ff0654608456 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Goral?= Date: Mon, 15 Sep 2025 13:13:33 +0200 Subject: [PATCH 48/75] fixes --- .../ComponentsSelection/ComponentsSelectionContainer.tsx | 8 ++------ .../ControlPlanes/ControlPlaneCard/ControlPlaneCard.tsx | 3 +-- 2 files changed, 3 insertions(+), 8 deletions(-) diff --git a/src/components/ComponentsSelection/ComponentsSelectionContainer.tsx b/src/components/ComponentsSelection/ComponentsSelectionContainer.tsx index bea0a5f7..b5af37d9 100644 --- a/src/components/ComponentsSelection/ComponentsSelectionContainer.tsx +++ b/src/components/ComponentsSelection/ComponentsSelectionContainer.tsx @@ -26,10 +26,7 @@ export const getSelectedComponents = (components: ComponentsListItem[]) => { const isCrossplaneSelected = components.some(({ name, isSelected }) => name === 'crossplane' && isSelected); return components.filter((component) => { if (!component.isSelected) return false; - if (component.name?.includes('provider') && !isCrossplaneSelected) { - return false; - } - return true; + return !(component.name?.includes('provider') && !isCrossplaneSelected); }); }; @@ -140,12 +137,11 @@ export const ComponentsSelectionContainer: React.FC = }); setComponentsList(updated); - }, [defaultComponents, componentsList, setComponentsList]); + }, [defaultComponents, componentsList, setComponentsList, initialSelection]); if (isLoading) { return ; } - console.log(error); if (error) { return ; diff --git a/src/components/ControlPlanes/ControlPlaneCard/ControlPlaneCard.tsx b/src/components/ControlPlanes/ControlPlaneCard/ControlPlaneCard.tsx index b4c1ec4a..28b48244 100644 --- a/src/components/ControlPlanes/ControlPlaneCard/ControlPlaneCard.tsx +++ b/src/components/ControlPlanes/ControlPlaneCard/ControlPlaneCard.tsx @@ -28,7 +28,7 @@ import { canConnectToMCP } from '../controlPlanes.ts'; import { Infobox } from '../../Ui/Infobox/Infobox.tsx'; import { ControlPlaneCardMenu } from './ControlPlaneCardMenu.tsx'; -import { CreateManagedControlPlaneWizardContainer } from '../../Wizards/CreateManagedControlPlane/CreateManagedControlPlaneWizardContainer.tsx'; + import { EditManagedControlPlaneWizardDataLoader } from '../../Wizards/CreateManagedControlPlane/EditManagedControlPlaneWizardDataLoader.tsx'; interface Props { @@ -128,7 +128,6 @@ export function ControlPlaneCard({ controlPlane, workspace, projectName }: Props From bd2005302eb5b6304b68c9f1d273e0d34d22b9a3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Goral?= Date: Mon, 15 Sep 2025 13:34:04 +0200 Subject: [PATCH 49/75] fixes --- .../ControlPlaneListWorkspaceGridTile.tsx | 18 +- ...eateManagedControlPlaneWizardContainer.tsx | 250 +++++++++--------- ...ditManagedControlPlaneWizardDataLoader.tsx | 18 +- .../api/types/mcpResource.ts} | 0 4 files changed, 144 insertions(+), 142 deletions(-) rename src/{components/Wizards/CreateManagedControlPlane/mcp_type.ts => lib/api/types/mcpResource.ts} (100%) diff --git a/src/components/ControlPlanes/List/ControlPlaneListWorkspaceGridTile.tsx b/src/components/ControlPlanes/List/ControlPlaneListWorkspaceGridTile.tsx index 889880fc..0deb2639 100644 --- a/src/components/ControlPlanes/List/ControlPlaneListWorkspaceGridTile.tsx +++ b/src/components/ControlPlanes/List/ControlPlaneListWorkspaceGridTile.tsx @@ -183,14 +183,16 @@ export function ControlPlaneListWorkspaceGridTile({ projectName, workspace }: Pr toast.show(t('ControlPlaneListWorkspaceGridTile.deleteConfirmationDialog')); }} /> - + {isCreateManagedControlPlaneWizardOpen ? ( + + ) : null} ); } diff --git a/src/components/Wizards/CreateManagedControlPlane/CreateManagedControlPlaneWizardContainer.tsx b/src/components/Wizards/CreateManagedControlPlane/CreateManagedControlPlaneWizardContainer.tsx index c91a247e..32d540df 100644 --- a/src/components/Wizards/CreateManagedControlPlane/CreateManagedControlPlaneWizardContainer.tsx +++ b/src/components/Wizards/CreateManagedControlPlane/CreateManagedControlPlaneWizardContainer.tsx @@ -123,6 +123,7 @@ export const CreateManagedControlPlaneWizardContainer: FC({ resolver: zodResolver(validationSchemaCreateManagedControlPlane), @@ -156,7 +157,8 @@ export const CreateManagedControlPlaneWizardContainer: FC k + 1); }, [selectedTemplate, selectedStep, setValue, normalizeChargingTargetType]); - + console.log(getValues()); + console.log(errors); const nextButtonText = useMemo( () => ({ metadata: t('buttons.next'), @@ -488,134 +490,130 @@ export const CreateManagedControlPlaneWizardContainer: FC - {isOpen ? ( - - {selectedStep !== 'success' && - (selectedStep === 'metadata' ? ( - - ) : ( - - ))} - -
- } + + {selectedStep !== 'success' && + (selectedStep === 'metadata' ? ( + + ) : ( + + ))} + + + } + /> + } + data-testid="create-mcp-dialog" + onClose={resetFormAndClose} + > + + + + - } - data-testid="create-mcp-dialog" - onClose={resetFormAndClose} - > - - - - - - -
- - - -
-
- - {/* this condition is to remount the component from scratch to fix a bug with data loading */} - {selectedStep === 'componentSelection' && ( - + +
+ + - )} - - - + + + + {/* this condition is to remount the component from scratch to fix a bug with data loading */} + {selectedStep === 'componentSelection' && ( + - - - {isEditMode ? ( - - ) : ( - - )} - -
-
- ) : ( -
- )} + )} + + + + + + {isEditMode ? ( + + ) : ( + + )} + + + ); }; diff --git a/src/components/Wizards/CreateManagedControlPlane/EditManagedControlPlaneWizardDataLoader.tsx b/src/components/Wizards/CreateManagedControlPlane/EditManagedControlPlaneWizardDataLoader.tsx index 95e49d7d..994a26bc 100644 --- a/src/components/Wizards/CreateManagedControlPlane/EditManagedControlPlaneWizardDataLoader.tsx +++ b/src/components/Wizards/CreateManagedControlPlane/EditManagedControlPlaneWizardDataLoader.tsx @@ -41,14 +41,16 @@ export const EditManagedControlPlaneWizardDataLoader: FC - + {isOpen ? ( + + ) : null} ); }; diff --git a/src/components/Wizards/CreateManagedControlPlane/mcp_type.ts b/src/lib/api/types/mcpResource.ts similarity index 100% rename from src/components/Wizards/CreateManagedControlPlane/mcp_type.ts rename to src/lib/api/types/mcpResource.ts From 0fbe5b33083f8a86ef98b88a2fbd28ccef78ec48 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Goral?= Date: Mon, 15 Sep 2025 13:57:56 +0200 Subject: [PATCH 50/75] Update CreateManagedControlPlaneWizardContainer.tsx --- .../CreateManagedControlPlaneWizardContainer.tsx | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/components/Wizards/CreateManagedControlPlane/CreateManagedControlPlaneWizardContainer.tsx b/src/components/Wizards/CreateManagedControlPlane/CreateManagedControlPlaneWizardContainer.tsx index 32d540df..81ddcef6 100644 --- a/src/components/Wizards/CreateManagedControlPlane/CreateManagedControlPlaneWizardContainer.tsx +++ b/src/components/Wizards/CreateManagedControlPlane/CreateManagedControlPlaneWizardContainer.tsx @@ -150,9 +150,6 @@ export const CreateManagedControlPlaneWizardContainer: FC k + 1); From 7ff45f5bf3d903b64eeb0b9cc2e067ddece99b8d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Goral?= Date: Mon, 15 Sep 2025 15:28:31 +0200 Subject: [PATCH 51/75] reafctor --- .../ControlPlaneCard/ControlPlaneCardMenu.tsx | 26 +------- .../ControlPlanes/ControlPlanePageMenu.tsx | 66 +++++++++++++++++++ ...eateManagedControlPlaneWizardContainer.tsx | 2 +- src/spaces/mcp/pages/McpPage.tsx | 14 +++- 4 files changed, 82 insertions(+), 26 deletions(-) create mode 100644 src/components/ControlPlanes/ControlPlanePageMenu.tsx diff --git a/src/components/ControlPlanes/ControlPlaneCard/ControlPlaneCardMenu.tsx b/src/components/ControlPlanes/ControlPlaneCard/ControlPlaneCardMenu.tsx index 6af2ea65..556286a9 100644 --- a/src/components/ControlPlanes/ControlPlaneCard/ControlPlaneCardMenu.tsx +++ b/src/components/ControlPlanes/ControlPlaneCard/ControlPlaneCardMenu.tsx @@ -7,16 +7,10 @@ import '@ui5/webcomponents-icons/dist/accept'; import { useTranslation } from 'react-i18next'; type ControlPlanesListMenuProps = { - setDialogDeleteMcpIsOpen: Dispatch>; - isDeleteMcpButtonDisabled: boolean; setIsEditManagedControlPlaneWizardOpen: Dispatch>; }; -export const ControlPlaneCardMenu: FC = ({ - setDialogDeleteMcpIsOpen, - isDeleteMcpButtonDisabled, - setIsEditManagedControlPlaneWizardOpen, -}) => { +export const ControlPlaneCardMenu: FC = ({ setIsEditManagedControlPlaneWizardOpen }) => { const buttonRef = useRef(null); const [menuIsOpen, setMenuIsOpen] = useState(false); const { t } = useTranslation(); @@ -36,9 +30,6 @@ export const ControlPlaneCardMenu: FC = ({ if (action === 'editMcp') { setIsEditManagedControlPlaneWizardOpen(true); } - if (action === 'deleteMcp') { - setDialogDeleteMcpIsOpen(true); - } setMenuIsOpen(false); }} @@ -46,20 +37,7 @@ export const ControlPlaneCardMenu: FC = ({ setMenuIsOpen(false); }} > - - + ); diff --git a/src/components/ControlPlanes/ControlPlanePageMenu.tsx b/src/components/ControlPlanes/ControlPlanePageMenu.tsx new file mode 100644 index 00000000..e52c3260 --- /dev/null +++ b/src/components/ControlPlanes/ControlPlanePageMenu.tsx @@ -0,0 +1,66 @@ +import { Button, Menu, MenuItem } from '@ui5/webcomponents-react'; + +import { Dispatch, FC, SetStateAction, useRef, useState } from 'react'; +import '@ui5/webcomponents-icons/dist/copy'; +import '@ui5/webcomponents-icons/dist/accept'; + +import { useTranslation } from 'react-i18next'; + +type ControlPlanesListMenuProps = { + setDialogDeleteMcpIsOpen: Dispatch>; + isDeleteMcpButtonDisabled: boolean; + setIsEditManagedControlPlaneWizardOpen: Dispatch>; +}; + +export const ControlPlanePageeMenu: FC = ({ + setDialogDeleteMcpIsOpen, + isDeleteMcpButtonDisabled, + setIsEditManagedControlPlaneWizardOpen, +}) => { + const buttonRef = useRef(null); + const [menuIsOpen, setMenuIsOpen] = useState(false); + const { t } = useTranslation(); + + const handleOpenerClick = () => { + setMenuIsOpen(true); + }; + + return ( + <> +
} /> From 4ad9b4d395b36423dcb765dafad8b21bc128e456 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Goral?= Date: Tue, 16 Sep 2025 10:40:14 +0200 Subject: [PATCH 52/75] fix --- src/views/Login.tsx | 10 ++++++---- src/views/login.css | 8 +++++++- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/src/views/Login.tsx b/src/views/Login.tsx index ca935f9d..5317a3bc 100644 --- a/src/views/Login.tsx +++ b/src/views/Login.tsx @@ -1,5 +1,5 @@ import { useAuthOnboarding } from '../spaces/onboarding/auth/AuthContextOnboarding.tsx'; -import { Button, Card, FlexBox, Text } from '@ui5/webcomponents-react'; +import { Button, Card, FlexBox, Link, Text } from '@ui5/webcomponents-react'; import ButtonDesign from '@ui5/webcomponents/dist/types/ButtonDesign.js'; import './login.css'; import { ThemingParameters } from '@ui5/webcomponents-react-base'; @@ -31,12 +31,14 @@ export default function LoginView() {
Logo
{t('Login.welcomeMessage')}
- {t('Login.description')} + + {t('Login.description')} +

- + {t('Login.learnMore')} - +

diff --git a/src/views/login.css b/src/views/login.css index b510b4b2..11531f82 100644 --- a/src/views/login.css +++ b/src/views/login.css @@ -4,6 +4,12 @@ margin-bottom: 8px; } +.description { + max-width: 50ch; + text-align: center; + margin: 0 auto; +} + .logo { width: 400px; } @@ -17,4 +23,4 @@ .signinBtn { font-size: 1rem; padding: 10px 20px; -} \ No newline at end of file +} From d0484aa5b3513f98e317289af8e6b01cd9e12757 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Goral?= Date: Tue, 16 Sep 2025 11:29:38 +0200 Subject: [PATCH 53/75] fixes --- .../ComponentsSelectionContainer.tsx | 8 ++++- .../ControlPlaneCard/ControlPlaneCardMenu.tsx | 26 +++++++++++++-- .../ControlPlanes/ControlPlanePageMenu.tsx | 32 ++----------------- ...eateManagedControlPlaneWizardContainer.tsx | 10 ++++-- ...ditManagedControlPlaneWizardDataLoader.tsx | 6 +++- src/lib/api/useApiResource.ts | 9 +++++- src/spaces/mcp/pages/McpPage.tsx | 1 + 7 files changed, 55 insertions(+), 37 deletions(-) diff --git a/src/components/ComponentsSelection/ComponentsSelectionContainer.tsx b/src/components/ComponentsSelection/ComponentsSelectionContainer.tsx index b5af37d9..b8d21cc1 100644 --- a/src/components/ComponentsSelection/ComponentsSelectionContainer.tsx +++ b/src/components/ComponentsSelection/ComponentsSelectionContainer.tsx @@ -16,6 +16,7 @@ export interface ComponentsSelectionProps { setComponentsList: (components: ComponentsListItem[]) => void; managedControlPlaneTemplate?: ManagedControlPlaneTemplate; initialSelection?: Record; + isOnMcpPage?: boolean; } /** @@ -42,8 +43,13 @@ export const ComponentsSelectionContainer: React.FC = componentsList, managedControlPlaneTemplate, initialSelection, + isOnMcpPage, }) => { - const { data: availableManagedComponentsListData, error, isLoading } = useApiResource(ListManagedComponents()); + const { + data: availableManagedComponentsListData, + error, + isLoading, + } = useApiResource(ListManagedComponents(), undefined, !!isOnMcpPage); const { t } = useTranslation(); const initialized = useRef(false); const [templateDefaultsError, setTemplateDefaultsError] = useState(null); diff --git a/src/components/ControlPlanes/ControlPlaneCard/ControlPlaneCardMenu.tsx b/src/components/ControlPlanes/ControlPlaneCard/ControlPlaneCardMenu.tsx index 556286a9..6af2ea65 100644 --- a/src/components/ControlPlanes/ControlPlaneCard/ControlPlaneCardMenu.tsx +++ b/src/components/ControlPlanes/ControlPlaneCard/ControlPlaneCardMenu.tsx @@ -7,10 +7,16 @@ import '@ui5/webcomponents-icons/dist/accept'; import { useTranslation } from 'react-i18next'; type ControlPlanesListMenuProps = { + setDialogDeleteMcpIsOpen: Dispatch>; + isDeleteMcpButtonDisabled: boolean; setIsEditManagedControlPlaneWizardOpen: Dispatch>; }; -export const ControlPlaneCardMenu: FC = ({ setIsEditManagedControlPlaneWizardOpen }) => { +export const ControlPlaneCardMenu: FC = ({ + setDialogDeleteMcpIsOpen, + isDeleteMcpButtonDisabled, + setIsEditManagedControlPlaneWizardOpen, +}) => { const buttonRef = useRef(null); const [menuIsOpen, setMenuIsOpen] = useState(false); const { t } = useTranslation(); @@ -30,6 +36,9 @@ export const ControlPlaneCardMenu: FC = ({ setIsEdit if (action === 'editMcp') { setIsEditManagedControlPlaneWizardOpen(true); } + if (action === 'deleteMcp') { + setDialogDeleteMcpIsOpen(true); + } setMenuIsOpen(false); }} @@ -37,7 +46,20 @@ export const ControlPlaneCardMenu: FC = ({ setIsEdit setMenuIsOpen(false); }} > - + + ); diff --git a/src/components/ControlPlanes/ControlPlanePageMenu.tsx b/src/components/ControlPlanes/ControlPlanePageMenu.tsx index e52c3260..04eebc9f 100644 --- a/src/components/ControlPlanes/ControlPlanePageMenu.tsx +++ b/src/components/ControlPlanes/ControlPlanePageMenu.tsx @@ -1,22 +1,12 @@ -import { Button, Menu, MenuItem } from '@ui5/webcomponents-react'; - import { Dispatch, FC, SetStateAction, useRef, useState } from 'react'; -import '@ui5/webcomponents-icons/dist/copy'; -import '@ui5/webcomponents-icons/dist/accept'; - import { useTranslation } from 'react-i18next'; +import { Button, Menu, MenuItem } from '@ui5/webcomponents-react'; type ControlPlanesListMenuProps = { - setDialogDeleteMcpIsOpen: Dispatch>; - isDeleteMcpButtonDisabled: boolean; setIsEditManagedControlPlaneWizardOpen: Dispatch>; }; -export const ControlPlanePageeMenu: FC = ({ - setDialogDeleteMcpIsOpen, - isDeleteMcpButtonDisabled, - setIsEditManagedControlPlaneWizardOpen, -}) => { +export const ControlPlanePageMenu: FC = ({ setIsEditManagedControlPlaneWizardOpen }) => { const buttonRef = useRef(null); const [menuIsOpen, setMenuIsOpen] = useState(false); const { t } = useTranslation(); @@ -36,9 +26,6 @@ export const ControlPlanePageeMenu: FC = ({ if (action === 'editMcp') { setIsEditManagedControlPlaneWizardOpen(true); } - if (action === 'deleteMcp') { - setDialogDeleteMcpIsOpen(true); - } setMenuIsOpen(false); }} @@ -46,20 +33,7 @@ export const ControlPlanePageeMenu: FC = ({ setMenuIsOpen(false); }} > - - + ); diff --git a/src/components/Wizards/CreateManagedControlPlane/CreateManagedControlPlaneWizardContainer.tsx b/src/components/Wizards/CreateManagedControlPlane/CreateManagedControlPlaneWizardContainer.tsx index ea9f6b18..8d532cf1 100644 --- a/src/components/Wizards/CreateManagedControlPlane/CreateManagedControlPlaneWizardContainer.tsx +++ b/src/components/Wizards/CreateManagedControlPlane/CreateManagedControlPlaneWizardContainer.tsx @@ -58,6 +58,7 @@ type CreateManagedControlPlaneWizardContainerProps = { isEditMode: boolean; initialTemplateName?: string; initialData?: ManagedControlPlaneInterface; + isOnMcpPage?: boolean; }; type WizardStepType = 'metadata' | 'members' | 'componentSelection' | 'summarize' | 'success'; @@ -72,6 +73,7 @@ export const CreateManagedControlPlaneWizardContainer: FC { const { t } = useTranslation(); const { user } = useAuthOnboarding(); @@ -123,7 +125,7 @@ export const CreateManagedControlPlaneWizardContainer: FC({ resolver: zodResolver(validationSchemaCreateManagedControlPlane), @@ -154,8 +156,7 @@ export const CreateManagedControlPlaneWizardContainer: FC k + 1); }, [selectedTemplate, selectedStep, setValue, normalizeChargingTargetType]); - console.log(getValues()); - console.log(errors); + const nextButtonText = useMemo( () => ({ metadata: t('buttons.next'), @@ -194,6 +195,8 @@ export const CreateManagedControlPlaneWizardContainer: FC( UpdateManagedControlPlaneResource(projectName, workspaceName, initialData?.metadata?.name ?? ''), + undefined, + !!isOnMcpPage, ); const componentsList = watch('componentsList'); @@ -571,6 +574,7 @@ export const CreateManagedControlPlaneWizardContainer: FC )} diff --git a/src/components/Wizards/CreateManagedControlPlane/EditManagedControlPlaneWizardDataLoader.tsx b/src/components/Wizards/CreateManagedControlPlane/EditManagedControlPlaneWizardDataLoader.tsx index 994a26bc..3c973f9e 100644 --- a/src/components/Wizards/CreateManagedControlPlane/EditManagedControlPlaneWizardDataLoader.tsx +++ b/src/components/Wizards/CreateManagedControlPlane/EditManagedControlPlaneWizardDataLoader.tsx @@ -5,14 +5,16 @@ import styles from './EditManagedControlPlaneWizardDataLoader.module.css'; import { CreateManagedControlPlaneWizardContainer } from './CreateManagedControlPlaneWizardContainer.tsx'; import { PROJECT_NAME_LABEL, WORKSPACE_LABEL } from '../../../lib/api/types/shared/keyNames.ts'; -import { ManagedControlPlaneInterface } from './mcp_type.ts'; + import { BusyIndicator } from '@ui5/webcomponents-react'; +import { ManagedControlPlaneInterface } from '../../../lib/api/types/mcpResource.ts'; export type EditManagedControlPlaneWizardDataLoaderProps = { workspaceName?: string; resourceName: string; isOpen: boolean; setIsOpen: (isOpen: boolean) => void; + isOnMcpPage?: boolean; }; export const EditManagedControlPlaneWizardDataLoader: FC = ({ @@ -20,6 +22,7 @@ export const EditManagedControlPlaneWizardDataLoader: FC { const { isLoading, data, error } = useApiResource( ResourceObject(workspaceName ?? '', 'managedcontrolplanes', resourceName), @@ -49,6 +52,7 @@ export const EditManagedControlPlaneWizardDataLoader: FC ) : null} diff --git a/src/lib/api/useApiResource.ts b/src/lib/api/useApiResource.ts index 3563ebed..63d846d4 100644 --- a/src/lib/api/useApiResource.ts +++ b/src/lib/api/useApiResource.ts @@ -158,6 +158,7 @@ export const useApiResourceMutation = ( resource: Resource, // eslint-disable-next-line @typescript-eslint/no-explicit-any config?: SWRMutationConfiguration, + excludeMcpConfig?: boolean, ) => { const apiConfig = useContext(ApiConfigContext); @@ -167,7 +168,13 @@ export const useApiResourceMutation = ( : [resource.path, apiConfig], // eslint-disable-next-line @typescript-eslint/no-explicit-any ([path, apiConfig]: [path: string, config: ApiConfig], arg: any) => - fetchApiServerJson(path, apiConfig, resource.jq, resource.method, JSON.stringify(arg.arg)), + fetchApiServerJson( + path, + excludeMcpConfig ? { ...apiConfig, mcpConfig: undefined } : apiConfig, + resource.jq, + resource.method, + JSON.stringify(arg.arg), + ), config, ); diff --git a/src/spaces/mcp/pages/McpPage.tsx b/src/spaces/mcp/pages/McpPage.tsx index 154bb85b..116ebdca 100644 --- a/src/spaces/mcp/pages/McpPage.tsx +++ b/src/spaces/mcp/pages/McpPage.tsx @@ -99,6 +99,7 @@ export default function McpPage() { setIsOpen={setIsEditManagedControlPlaneWizardOpen} workspaceName={mcp?.status?.access?.namespace} resourceName={controlPlaneName} + isOnMcpPage />
} From d0dfd22da8d17eb36c720ed0b269abfdfd91fe11 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Goral?= Date: Tue, 16 Sep 2025 12:24:34 +0200 Subject: [PATCH 54/75] Update CreateManagedControlPlaneWizardContainer.tsx --- ...eateManagedControlPlaneWizardContainer.tsx | 28 +++++++++++++------ 1 file changed, 19 insertions(+), 9 deletions(-) diff --git a/src/components/Wizards/CreateManagedControlPlane/CreateManagedControlPlaneWizardContainer.tsx b/src/components/Wizards/CreateManagedControlPlane/CreateManagedControlPlaneWizardContainer.tsx index 8d532cf1..d4452449 100644 --- a/src/components/Wizards/CreateManagedControlPlane/CreateManagedControlPlaneWizardContainer.tsx +++ b/src/components/Wizards/CreateManagedControlPlane/CreateManagedControlPlaneWizardContainer.tsx @@ -48,7 +48,13 @@ import { IllustratedBanner } from '../../Ui/IllustratedBanner/IllustratedBanner. import { ManagedControlPlaneTemplate, noTemplateValue } from '../../../lib/api/types/templates/mcpTemplate.ts'; import { stripIdpPrefix } from '../../../utils/stripIdpPrefix.ts'; import { buildNameWithPrefixesAndSuffixes } from '../../../utils/buildNameWithPrefixesAndSuffixes.ts'; -import { ManagedControlPlaneInterface } from '../../../lib/api/types/mcpResource.ts'; +import { + ManagedControlPlaneInterface, + MCPComponentsSpec, + MCPCrossplaneComponent, + MCPVersionedComponent, + MCPSubject, +} from '../../../lib/api/types/mcpResource.ts'; type CreateManagedControlPlaneWizardContainerProps = { isOpen: boolean; @@ -355,17 +361,21 @@ export const CreateManagedControlPlaneWizardContainer: FC { if (!isEditMode) return undefined; const selection: Record = {}; - const componentsMap = (initialData?.spec.components ?? {}) as Record; - Object.keys(componentsMap).forEach((key) => { - if (key === 'apiServer' || key === 'landscaper') return; + const componentsMap: MCPComponentsSpec = initialData?.spec.components ?? {}; + (Object.keys(componentsMap) as (keyof MCPComponentsSpec)[]).forEach((key) => { + if (key === 'apiServer') return; const value = componentsMap[key]; if (key === 'crossplane') { - selection[key] = { isSelected: true, version: value.version ?? '' }; - (value.providers ?? []).forEach((prov: { name: string; version: string }) => { + const crossplane = (value as MCPCrossplaneComponent) ?? {}; + selection[key as string] = { isSelected: true, version: crossplane.version ?? '' }; + (crossplane.providers ?? []).forEach((prov) => { selection[prov.name] = { isSelected: true, version: prov.version ?? '' }; }); } else { - selection[key] = { isSelected: true, version: value.version ?? '' }; + const versioned = value as MCPVersionedComponent | undefined; + if (versioned) { + selection[key as string] = { isSelected: true, version: versioned.version ?? '' }; + } } }); return selection; @@ -376,10 +386,10 @@ export const CreateManagedControlPlaneWizardContainer: FC - (rb.subjects ?? []).map((s: any) => ({ + (rb.subjects ?? []).map((s: MCPSubject) => ({ kind: s.kind, name: s.kind === 'User' && s.name?.includes(':') ? s.name.split(':').slice(1).join(':') : s.name, - roles: [rb.role], + roles: [normalizeMemberRole(rb.role)], namespace: s.namespace, })), ); From 2291c56d8fac84e876dde26d1f493d67ba1cb0ae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Goral?= Date: Tue, 16 Sep 2025 12:55:39 +0200 Subject: [PATCH 55/75] Update CreateManagedControlPlaneWizardContainer.tsx --- .../CreateManagedControlPlaneWizardContainer.tsx | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/components/Wizards/CreateManagedControlPlane/CreateManagedControlPlaneWizardContainer.tsx b/src/components/Wizards/CreateManagedControlPlane/CreateManagedControlPlaneWizardContainer.tsx index d4452449..2f658505 100644 --- a/src/components/Wizards/CreateManagedControlPlane/CreateManagedControlPlaneWizardContainer.tsx +++ b/src/components/Wizards/CreateManagedControlPlane/CreateManagedControlPlaneWizardContainer.tsx @@ -171,6 +171,8 @@ export const CreateManagedControlPlaneWizardContainer: FC( UpdateManagedControlPlaneResource(projectName, workspaceName, initialData?.metadata?.name ?? ''), undefined, - !!isOnMcpPage, + isOnMcpPage, ); const componentsList = watch('componentsList'); @@ -260,6 +262,7 @@ export const CreateManagedControlPlaneWizardContainer: FC { From dbce6affbf53c1adcf2b7e1d489c1b14cbf70714 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Goral?= Date: Tue, 16 Sep 2025 15:29:33 +0200 Subject: [PATCH 56/75] Update ComponentsSelectionContainer.tsx --- .../ComponentsSelection/ComponentsSelectionContainer.tsx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/components/ComponentsSelection/ComponentsSelectionContainer.tsx b/src/components/ComponentsSelection/ComponentsSelectionContainer.tsx index b8d21cc1..b6660404 100644 --- a/src/components/ComponentsSelection/ComponentsSelectionContainer.tsx +++ b/src/components/ComponentsSelection/ComponentsSelectionContainer.tsx @@ -106,21 +106,21 @@ export const ComponentsSelectionContainer: React.FC = return; } - const errs: string[] = []; + const errors: string[] = []; defaultComponents.forEach((dc: TemplateDefaultComponent) => { if (!dc?.name) return; const item = items.find((it) => it.metadata?.name === dc.name); if (!item) { - errs.push(`Component "${dc.name}" from template is not available.`); + 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)) { - errs.push(`Component "${dc.name}" version "${dc.version}" from template is not available.`); + errors.push(`Component "${dc.name}" version "${dc.version}" from template is not available.`); } }); - setTemplateDefaultsError(errs.length ? errs.join('\n') : null); + setTemplateDefaultsError(errors.length ? errors.join('\n') : null); }, [availableManagedComponentsListData, defaultComponents]); useEffect(() => { From 3e3301656cd9b4a12d66af0e10ae47c715a98861 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Goral?= Date: Wed, 17 Sep 2025 08:56:42 +0200 Subject: [PATCH 57/75] fix --- .../ComponentsSelection/ComponentsSelectionContainer.tsx | 2 +- .../CreateManagedControlPlaneWizardContainer.tsx | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/src/components/ComponentsSelection/ComponentsSelectionContainer.tsx b/src/components/ComponentsSelection/ComponentsSelectionContainer.tsx index b6660404..bbe9dc15 100644 --- a/src/components/ComponentsSelection/ComponentsSelectionContainer.tsx +++ b/src/components/ComponentsSelection/ComponentsSelectionContainer.tsx @@ -97,7 +97,7 @@ export const ComponentsSelectionContainer: React.FC = setComponentsList(newComponentsList); initialized.current = true; - }, [availableManagedComponentsListData, setComponentsList, defaultComponents, initialSelection]); + }, [setComponentsList, defaultComponents, initialSelection, availableManagedComponentsListData?.items]); useEffect(() => { const items = availableManagedComponentsListData?.items ?? []; diff --git a/src/components/Wizards/CreateManagedControlPlane/CreateManagedControlPlaneWizardContainer.tsx b/src/components/Wizards/CreateManagedControlPlane/CreateManagedControlPlaneWizardContainer.tsx index 2f658505..931fd297 100644 --- a/src/components/Wizards/CreateManagedControlPlane/CreateManagedControlPlaneWizardContainer.tsx +++ b/src/components/Wizards/CreateManagedControlPlane/CreateManagedControlPlaneWizardContainer.tsx @@ -515,6 +515,9 @@ export const CreateManagedControlPlaneWizardContainer: FC + {selectedStep !== 'metadata' && isEditMode && ( + + )} {selectedStep !== 'success' && (selectedStep === 'metadata' ? ( From e9d5c2d753373b8b95788fb2c8f91cff9661755d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Goral?= Date: Wed, 17 Sep 2025 14:58:57 +0200 Subject: [PATCH 58/75] fixes --- package-lock.json | 10 +++ package.json | 1 + .../ComponentsSelectionContainer.tsx | 4 +- ...eateManagedControlPlaneWizardContainer.tsx | 44 ++++++++++-- .../SummarizeStep.tsx | 67 ++++++++++++++----- src/components/Yaml/YamlDiff.module.css | 13 ++++ src/components/Yaml/YamlDiff.tsx | 58 ++++++++++++++++ src/components/Yaml/YamlViewer.tsx | 21 ++++-- 8 files changed, 187 insertions(+), 31 deletions(-) create mode 100644 src/components/Yaml/YamlDiff.module.css create mode 100644 src/components/Yaml/YamlDiff.tsx diff --git a/package-lock.json b/package-lock.json index c9374322..4e02f633 100644 --- a/package-lock.json +++ b/package-lock.json @@ -31,6 +31,7 @@ "@xyflow/react": "12.8.4", "clsx": "2.1.1", "dagre": "0.8.5", + "diff": "^8.0.2", "dotenv": "17.2.2", "fastify": "5.6.0", "fastify-plugin": "5.0.1", @@ -8776,6 +8777,15 @@ "node": ">=8" } }, + "node_modules/diff": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-8.0.2.tgz", + "integrity": "sha512-sSuxWU5j5SR9QQji/o2qMvqRNYRDOcBTgsJ/DeCf4iSN4gW+gNMXM7wFIP+fdXZxoNiAnHUTGjCr+TSWXdRDKg==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.3.1" + } + }, "node_modules/dir-glob": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", diff --git a/package.json b/package.json index 5dde5ad4..c75258f7 100644 --- a/package.json +++ b/package.json @@ -45,6 +45,7 @@ "@xyflow/react": "12.8.4", "clsx": "2.1.1", "dagre": "0.8.5", + "diff": "^8.0.2", "dotenv": "17.2.2", "fastify": "5.6.0", "fastify-plugin": "5.0.1", diff --git a/src/components/ComponentsSelection/ComponentsSelectionContainer.tsx b/src/components/ComponentsSelection/ComponentsSelectionContainer.tsx index bbe9dc15..0ef4aa64 100644 --- a/src/components/ComponentsSelection/ComponentsSelectionContainer.tsx +++ b/src/components/ComponentsSelection/ComponentsSelectionContainer.tsx @@ -14,6 +14,7 @@ import { ManagedControlPlaneTemplate } from '../../lib/api/types/templates/mcpTe export interface ComponentsSelectionProps { componentsList: ComponentsListItem[]; setComponentsList: (components: ComponentsListItem[]) => void; + setInitialComponentsList: (components: ComponentsListItem[]) => void; managedControlPlaneTemplate?: ManagedControlPlaneTemplate; initialSelection?: Record; isOnMcpPage?: boolean; @@ -44,6 +45,7 @@ export const ComponentsSelectionContainer: React.FC = managedControlPlaneTemplate, initialSelection, isOnMcpPage, + setInitialComponentsList, }) => { const { data: availableManagedComponentsListData, @@ -94,7 +96,7 @@ export const ComponentsSelectionContainer: React.FC = }; }) .filter((component) => !removeComponents.find((item) => item === component.name)); - + setInitialComponentsList(newComponentsList); setComponentsList(newComponentsList); initialized.current = true; }, [setComponentsList, defaultComponents, initialSelection, availableManagedComponentsListData?.items]); diff --git a/src/components/Wizards/CreateManagedControlPlane/CreateManagedControlPlaneWizardContainer.tsx b/src/components/Wizards/CreateManagedControlPlane/CreateManagedControlPlaneWizardContainer.tsx index 931fd297..eb2a9e2e 100644 --- a/src/components/Wizards/CreateManagedControlPlane/CreateManagedControlPlaneWizardContainer.tsx +++ b/src/components/Wizards/CreateManagedControlPlane/CreateManagedControlPlaneWizardContainer.tsx @@ -55,6 +55,7 @@ import { MCPVersionedComponent, MCPSubject, } from '../../../lib/api/types/mcpResource.ts'; +import { stringify } from 'yaml'; type CreateManagedControlPlaneWizardContainerProps = { isOpen: boolean; @@ -145,7 +146,14 @@ export const CreateManagedControlPlaneWizardContainer: FC({ + name: '', + displayName: '', + chargingTarget: '', + chargingTargetType: '', + members: [], + componentsList: [], + }); useEffect(() => { if (selectedStep !== 'metadata') return; @@ -162,6 +170,7 @@ export const CreateManagedControlPlaneWizardContainer: FC k + 1); }, [selectedTemplate, selectedStep, setValue, normalizeChargingTargetType]); + console.log(initialMcpDataWhenInEditMode); const nextButtonText = useMemo( () => ({ @@ -399,18 +408,21 @@ export const CreateManagedControlPlaneWizardContainer: FC) ?? {}; const annotations = (initialData?.metadata.annotations as unknown as Record) ?? {}; - - reset({ - name: initialData?.metadata.name, + const data = { + name: initialData?.metadata.name ?? '', displayName: annotations?.[DISPLAY_NAME_ANNOTATION] ?? '', chargingTarget: labels?.[CHARGING_TARGET_LABEL] ?? '', chargingTargetType: labels?.[CHARGING_TARGET_TYPE_LABEL] ?? '', members, componentsList: componentsList ?? [], - }); + }; + reset(data); + + setInitialMcpDataWhenInEditMode(data); // eslint-disable-next-line react-hooks/exhaustive-deps }, [isOpen, isEditMode]); - + console.log('initialMcpDataWhenInEditMode'); + console.log(initialMcpDataWhenInEditMode); const normalizeMemberKind = useCallback((kindInput?: string | null) => { const normalizedKind = (kindInput ?? '').toString().trim().toLowerCase(); return normalizedKind === 'serviceaccount' ? 'ServiceAccount' : 'User'; @@ -467,7 +479,10 @@ export const CreateManagedControlPlaneWizardContainer: FC { + if (!isEditMode) return; + setInitialMcpDataWhenInEditMode({ ...initialMcpDataWhenInEditMode, componentsList: components }); + }; useEffect(() => { if (selectedStep !== 'componentSelection') return; if (!selectedTemplate) return; @@ -593,6 +608,7 @@ export const CreateManagedControlPlaneWizardContainer: FC )} @@ -604,6 +620,20 @@ export const CreateManagedControlPlaneWizardContainer: FC ; projectName: string; workspaceName: string; componentsList?: ComponentsListItem[]; + originalYamlString?: string; } -export const SummarizeStep: React.FC = ({ watch, projectName, workspaceName, componentsList }) => { +export const SummarizeStep: React.FC = ({ + originalYamlString, + watch, + projectName, + workspaceName, + componentsList, +}) => { const { t } = useTranslation(); + console.log('componentsList'); + console.log(componentsList); return ( <> {t('common.summarize')} @@ -48,23 +58,44 @@ export const SummarizeStep: React.FC = ({ watch, projectName
- + {originalYamlString ? ( + + ) : ( + + )}
diff --git a/src/components/Yaml/YamlDiff.module.css b/src/components/Yaml/YamlDiff.module.css new file mode 100644 index 00000000..5e74e9b4 --- /dev/null +++ b/src/components/Yaml/YamlDiff.module.css @@ -0,0 +1,13 @@ +/* Container wrapper to match YamlViewer spacing if needed */ +.container { + width: 100%; +} + +/* Line highlight colors for additions/removals */ +.added { + background-color: rgba(56, 142, 60, 0.18); /* green-ish */ +} + +.removed { + background-color: rgba(211, 47, 47, 0.18); /* red-ish */ +} diff --git a/src/components/Yaml/YamlDiff.tsx b/src/components/Yaml/YamlDiff.tsx new file mode 100644 index 00000000..b54da172 --- /dev/null +++ b/src/components/Yaml/YamlDiff.tsx @@ -0,0 +1,58 @@ +import { FC, useMemo } from 'react'; +import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter'; +import { materialLight, materialDark } from 'react-syntax-highlighter/dist/esm/styles/prism'; +import { diffLines } from 'diff'; +import styles from './YamlDiff.module.css'; +import { useTheme } from '../../hooks/useTheme.ts'; + +type YamlDiffProps = { + originalYaml: string; + modifiedYaml: string; +}; + +export const YamlDiff: FC = ({ originalYaml, modifiedYaml }) => { + const { isDarkTheme } = useTheme(); + + const hunks = useMemo(() => diffLines(originalYaml ?? '', modifiedYaml ?? ''), [originalYaml, modifiedYaml]); + + const { content, lineKinds } = useMemo(() => { + const lines: string[] = []; + const kinds: ('added' | 'removed' | 'context')[] = []; + hunks.forEach((part) => { + const prefix = part.added ? '+' : part.removed ? '-' : ' '; + const kind: 'added' | 'removed' | 'context' = part.added ? 'added' : part.removed ? 'removed' : 'context'; + const partLines = part.value.replace(/\n$/, '').split('\n'); + partLines.forEach((line) => { + lines.push(`${prefix}${line}`); + kinds.push(kind); + }); + }); + return { content: lines.join('\n'), lineKinds: kinds }; + }, [hunks]); + + const lineNumberStyle = useMemo(() => ({ paddingRight: '20px', minWidth: '40px', textAlign: 'right' as const }), []); + + return ( +
+ { + const kind = lineKinds[lineNumber - 1]; + if (kind === 'added') return { className: styles.added }; + if (kind === 'removed') return { className: styles.removed }; + return {}; + }} + customStyle={{ margin: 0, padding: '20px', borderRadius: '4px', fontSize: '1rem', background: 'transparent' }} + codeTagProps={{ style: { whiteSpace: 'pre-wrap' } }} + > + {content} + +
+ ); +}; + +export default YamlDiff; diff --git a/src/components/Yaml/YamlViewer.tsx b/src/components/Yaml/YamlViewer.tsx index fa1cefa7..dec86add 100644 --- a/src/components/Yaml/YamlViewer.tsx +++ b/src/components/Yaml/YamlViewer.tsx @@ -1,14 +1,14 @@ import { FC } from 'react'; import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter'; import { materialLight, materialDark } from 'react-syntax-highlighter/dist/esm/styles/prism'; - +import { createPatch, diffLines } from 'diff'; import { Button, FlexBox } from '@ui5/webcomponents-react'; import styles from './YamlViewer.module.css'; import { useToast } from '../../context/ToastContext.tsx'; import { useTranslation } from 'react-i18next'; import { useTheme } from '../../hooks/useTheme.ts'; -type YamlViewerProps = { yamlString: string; filename: string }; -const YamlViewer: FC = ({ yamlString, filename }) => { +type YamlViewerProps = { originalYamlString?: string; yamlString: string; filename: string }; +const YamlViewer: FC = ({ originalYamlString, yamlString, filename }) => { const toast = useToast(); const { t } = useTranslation(); const { isDarkTheme } = useTheme(); @@ -16,6 +16,7 @@ const YamlViewer: FC = ({ yamlString, filename }) => { navigator.clipboard.writeText(yamlString); toast.show(t('yaml.copiedToClipboard')); }; + const downloadYaml = () => { const blob = new Blob([yamlString], { type: 'text/yaml' }); const url = window.URL.createObjectURL(blob); @@ -28,6 +29,16 @@ const YamlViewer: FC = ({ yamlString, filename }) => { window.URL.revokeObjectURL(url); }; + const contentToRender = + originalYamlString !== undefined ? createPatch(`${filename}.yaml`, originalYamlString, yamlString) : yamlString; + + // console.log(originalYamlString !== undefined ? diffLines(originalYamlString, yamlString) : yamlString); + + const language = originalYamlString !== undefined ? 'diff' : 'yaml'; + console.log('1'); + console.log(yamlString); + console.log('2'); + console.log(originalYamlString); return (
@@ -39,7 +50,7 @@ const YamlViewer: FC = ({ yamlString, filename }) => { = ({ yamlString, filename }) => { }, }} > - {yamlString} + {contentToRender}
); From 6ce77598eef16673c45b8a33f163c1ef1a11c857 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Goral?= Date: Thu, 18 Sep 2025 09:31:42 +0200 Subject: [PATCH 59/75] fix --- .../CreateManagedControlPlaneWizardContainer.tsx | 1 + .../Wizards/CreateManagedControlPlane/SummarizeStep.tsx | 9 +++++---- src/components/Yaml/YamlDiff.tsx | 2 -- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/components/Wizards/CreateManagedControlPlane/CreateManagedControlPlaneWizardContainer.tsx b/src/components/Wizards/CreateManagedControlPlane/CreateManagedControlPlaneWizardContainer.tsx index eb2a9e2e..5233626d 100644 --- a/src/components/Wizards/CreateManagedControlPlane/CreateManagedControlPlaneWizardContainer.tsx +++ b/src/components/Wizards/CreateManagedControlPlane/CreateManagedControlPlaneWizardContainer.tsx @@ -638,6 +638,7 @@ export const CreateManagedControlPlaneWizardContainer: FC ; @@ -18,6 +18,7 @@ interface SummarizeStepProps { workspaceName: string; componentsList?: ComponentsListItem[]; originalYamlString?: string; + isEditMode?: boolean; } export const SummarizeStep: React.FC = ({ @@ -26,10 +27,10 @@ export const SummarizeStep: React.FC = ({ projectName, workspaceName, componentsList, + isEditMode = false, }) => { const { t } = useTranslation(); - console.log('componentsList'); - console.log(componentsList); + return ( <> {t('common.summarize')} @@ -58,7 +59,7 @@ export const SummarizeStep: React.FC = ({
- {originalYamlString ? ( + {isEditMode ? ( = ({ originalYaml, modifiedYaml }) => {
); }; - -export default YamlDiff; From 6653985e3b3e8d01ee775631bca9110cd673fd8d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Goral?= Date: Thu, 18 Sep 2025 10:44:43 +0200 Subject: [PATCH 60/75] fix --- .../ComponentsSelectionContainer.tsx | 10 ++++++---- .../CreateManagedControlPlaneWizardContainer.tsx | 7 ++++--- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/src/components/ComponentsSelection/ComponentsSelectionContainer.tsx b/src/components/ComponentsSelection/ComponentsSelectionContainer.tsx index 0ef4aa64..622eb362 100644 --- a/src/components/ComponentsSelection/ComponentsSelectionContainer.tsx +++ b/src/components/ComponentsSelection/ComponentsSelectionContainer.tsx @@ -18,6 +18,7 @@ export interface ComponentsSelectionProps { managedControlPlaneTemplate?: ManagedControlPlaneTemplate; initialSelection?: Record; isOnMcpPage?: boolean; + initializedComponents: React.RefObject; } /** @@ -46,6 +47,7 @@ export const ComponentsSelectionContainer: React.FC = initialSelection, isOnMcpPage, setInitialComponentsList, + initializedComponents, }) => { const { data: availableManagedComponentsListData, @@ -53,7 +55,7 @@ export const ComponentsSelectionContainer: React.FC = isLoading, } = useApiResource(ListManagedComponents(), undefined, !!isOnMcpPage); const { t } = useTranslation(); - const initialized = useRef(false); + const [templateDefaultsError, setTemplateDefaultsError] = useState(null); const defaultComponents = useMemo( () => managedControlPlaneTemplate?.spec?.spec?.components?.defaultComponents ?? [], @@ -62,7 +64,7 @@ export const ComponentsSelectionContainer: React.FC = useEffect(() => { if ( - initialized.current || + initializedComponents.current || !availableManagedComponentsListData?.items || availableManagedComponentsListData.items.length === 0 ) { @@ -98,7 +100,7 @@ export const ComponentsSelectionContainer: React.FC = .filter((component) => !removeComponents.find((item) => item === component.name)); setInitialComponentsList(newComponentsList); setComponentsList(newComponentsList); - initialized.current = true; + initializedComponents.current = true; }, [setComponentsList, defaultComponents, initialSelection, availableManagedComponentsListData?.items]); useEffect(() => { @@ -126,7 +128,7 @@ export const ComponentsSelectionContainer: React.FC = }, [availableManagedComponentsListData, defaultComponents]); useEffect(() => { - if (!initialized.current) return; + if (!initializedComponents.current) return; if (!defaultComponents?.length) return; if (!componentsList?.length) return; // If initialSelection is provided, do not auto-apply template defaults diff --git a/src/components/Wizards/CreateManagedControlPlane/CreateManagedControlPlaneWizardContainer.tsx b/src/components/Wizards/CreateManagedControlPlane/CreateManagedControlPlaneWizardContainer.tsx index 5233626d..305ed954 100644 --- a/src/components/Wizards/CreateManagedControlPlane/CreateManagedControlPlaneWizardContainer.tsx +++ b/src/components/Wizards/CreateManagedControlPlane/CreateManagedControlPlaneWizardContainer.tsx @@ -124,7 +124,7 @@ export const CreateManagedControlPlaneWizardContainer: FC createManagedControlPlaneSchema(t), [t]); - + const initializedComponents = useRef(false); const { register, handleSubmit, @@ -406,8 +406,8 @@ export const CreateManagedControlPlaneWizardContainer: FC) ?? {}; - const annotations = (initialData?.metadata.annotations as unknown as Record) ?? {}; + const labels = (initialData?.metadata?.labels as unknown as Record) ?? {}; + const annotations = (initialData?.metadata?.annotations as unknown as Record) ?? {}; const data = { name: initialData?.metadata.name ?? '', displayName: annotations?.[DISPLAY_NAME_ANNOTATION] ?? '', @@ -609,6 +609,7 @@ export const CreateManagedControlPlaneWizardContainer: FC )}
From 70e8bc01f80cae882185b254edcee1cce608aa76 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Goral?= Date: Thu, 18 Sep 2025 10:56:07 +0200 Subject: [PATCH 61/75] Update CreateManagedControlPlaneWizardContainer.tsx --- .../CreateManagedControlPlaneWizardContainer.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/Wizards/CreateManagedControlPlane/CreateManagedControlPlaneWizardContainer.tsx b/src/components/Wizards/CreateManagedControlPlane/CreateManagedControlPlaneWizardContainer.tsx index 305ed954..60ab4b02 100644 --- a/src/components/Wizards/CreateManagedControlPlane/CreateManagedControlPlaneWizardContainer.tsx +++ b/src/components/Wizards/CreateManagedControlPlane/CreateManagedControlPlaneWizardContainer.tsx @@ -409,7 +409,7 @@ export const CreateManagedControlPlaneWizardContainer: FC) ?? {}; const annotations = (initialData?.metadata?.annotations as unknown as Record) ?? {}; const data = { - name: initialData?.metadata.name ?? '', + name: initialData?.metadata?.name ?? '', displayName: annotations?.[DISPLAY_NAME_ANNOTATION] ?? '', chargingTarget: labels?.[CHARGING_TARGET_LABEL] ?? '', chargingTargetType: labels?.[CHARGING_TARGET_TYPE_LABEL] ?? '', From 9fe2de1237025b7b5e12f3d6b5658fb75dc8a90d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Goral?= Date: Thu, 18 Sep 2025 10:56:15 +0200 Subject: [PATCH 62/75] fix --- .../ControlPlanes/ControlPlaneCard/ControlPlaneCard.tsx | 5 ++++- src/lib/api/types/crate/controlPlanes.ts | 6 +++++- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/src/components/ControlPlanes/ControlPlaneCard/ControlPlaneCard.tsx b/src/components/ControlPlanes/ControlPlaneCard/ControlPlaneCard.tsx index 28b48244..e554e029 100644 --- a/src/components/ControlPlanes/ControlPlaneCard/ControlPlaneCard.tsx +++ b/src/components/ControlPlanes/ControlPlaneCard/ControlPlaneCard.tsx @@ -30,6 +30,7 @@ import { Infobox } from '../../Ui/Infobox/Infobox.tsx'; import { ControlPlaneCardMenu } from './ControlPlaneCardMenu.tsx'; import { EditManagedControlPlaneWizardDataLoader } from '../../Wizards/CreateManagedControlPlane/EditManagedControlPlaneWizardDataLoader.tsx'; +import { DISPLAY_NAME_ANNOTATION } from '../../../lib/api/types/shared/keyNames.ts'; interface Props { controlPlane: ListControlPlanesType; @@ -50,6 +51,8 @@ export function ControlPlaneCard({ controlPlane, workspace, projectName }: Props ); const name = controlPlane.metadata.name; + const displayName = controlPlane?.metadata?.annotations?.[DISPLAY_NAME_ANNOTATION]; + console.log(controlPlane); const namespace = controlPlane.metadata.namespace; const isSystemIdentityProviderEnabled = Boolean(controlPlane.spec?.authentication?.enableSystemIdentityProvider); @@ -65,7 +68,7 @@ export function ControlPlaneCard({ controlPlane, workspace, projectName }: Props - {name} + {displayName ?? name}
diff --git a/src/lib/api/types/crate/controlPlanes.ts b/src/lib/api/types/crate/controlPlanes.ts index 0df87bcf..2ef9da43 100644 --- a/src/lib/api/types/crate/controlPlanes.ts +++ b/src/lib/api/types/crate/controlPlanes.ts @@ -5,6 +5,10 @@ export type ListControlPlanesType = ControlPlaneType; export interface Metadata { name: string; namespace: string; + annotations: { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + [annotation: string]: any; + }; } export interface ControlPlaneType { @@ -68,7 +72,7 @@ export const ListControlPlanes = ( projectName === null ? null : `/apis/core.openmcp.cloud/v1alpha1/namespaces/project-${projectName}--ws-${workspaceName}/managedcontrolplanes`, - jq: '[.items[] |{spec: .spec | {authentication}, metadata: .metadata | {name, namespace}, status: { conditions: [.status.conditions[] | {type: .type, status: .status, message: .message, reason: .reason, lastTransitionTime: .lastTransitionTime}], access: .status.components.authentication.access, status: .status.status } }]', + jq: '[.items[] |{spec: .spec | {authentication}, metadata: .metadata | {name, namespace, annotations}, status: { conditions: [.status.conditions[] | {type: .type, status: .status, message: .message, reason: .reason, lastTransitionTime: .lastTransitionTime}], access: .status.components.authentication.access, status: .status.status } }]', }; }; From 74a7a94bb289d0797fbfdde86f56c16fc75149c7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Goral?= Date: Fri, 19 Sep 2025 08:51:29 +0200 Subject: [PATCH 63/75] fix --- .../ComponentsSelectionContainer.tsx | 2 +- src/lib/api/types/crate/controlPlanes.ts | 2 +- src/spaces/mcp/pages/McpPage.tsx | 10 ++++++---- 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/src/components/ComponentsSelection/ComponentsSelectionContainer.tsx b/src/components/ComponentsSelection/ComponentsSelectionContainer.tsx index 622eb362..1c70367e 100644 --- a/src/components/ComponentsSelection/ComponentsSelectionContainer.tsx +++ b/src/components/ComponentsSelection/ComponentsSelectionContainer.tsx @@ -1,4 +1,4 @@ -import React, { useEffect, useMemo, useRef, useState } from 'react'; +import React, { useEffect, useMemo, useState } from 'react'; import { ComponentsSelection } from './ComponentsSelection.tsx'; import IllustratedError from '../Shared/IllustratedError.tsx'; diff --git a/src/lib/api/types/crate/controlPlanes.ts b/src/lib/api/types/crate/controlPlanes.ts index 2ef9da43..b6ff154e 100644 --- a/src/lib/api/types/crate/controlPlanes.ts +++ b/src/lib/api/types/crate/controlPlanes.ts @@ -83,6 +83,6 @@ export const ControlPlane = ( ): Resource => { return { path: `/apis/core.openmcp.cloud/v1alpha1/namespaces/project-${projectName}--ws-${workspaceName}/managedcontrolplanes/${controlPlaneName}`, - jq: '{ spec: .spec | {components}, 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}, metadata: .metadata | {name, namespace, 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/spaces/mcp/pages/McpPage.tsx b/src/spaces/mcp/pages/McpPage.tsx index 116ebdca..58669c28 100644 --- a/src/spaces/mcp/pages/McpPage.tsx +++ b/src/spaces/mcp/pages/McpPage.tsx @@ -27,9 +27,11 @@ import { isNotFoundError } from '../../../lib/api/error.ts'; import { NotFoundBanner } from '../../../components/Ui/NotFoundBanner/NotFoundBanner.tsx'; import Graph from '../../../components/Graphs/Graph.tsx'; import HintsCardsRow from '../../../components/HintsCardsRow/HintsCardsRow.tsx'; -import { ControlPlaneCardMenu } from '../../../components/ControlPlanes/ControlPlaneCard/ControlPlaneCardMenu.tsx'; + import { useState } from 'react'; import { EditManagedControlPlaneWizardDataLoader } from '../../../components/Wizards/CreateManagedControlPlane/EditManagedControlPlaneWizardDataLoader.tsx'; +import { ControlPlanePageMenu } from '../../../components/ControlPlanes/ControlPlanePageMenu.tsx'; +import { DISPLAY_NAME_ANNOTATION } from '../../../lib/api/types/shared/keyNames.ts'; export default function McpPage() { const { projectName, workspaceName, controlPlaneName } = useParams(); @@ -40,7 +42,7 @@ export default function McpPage() { error, isLoading, } = useApiResource(ControlPlaneResource(projectName, workspaceName, controlPlaneName)); - + const displayName = mcp?.metadata?.annotations?.[DISPLAY_NAME_ANNOTATION]; if (isLoading) { return ; } @@ -67,7 +69,7 @@ export default function McpPage() { preserveHeaderStateOnClick={true} titleArea={ } //TODO: actionBar should use Toolbar and ToolbarButton for consistent design actionsBar={ @@ -91,7 +93,7 @@ export default function McpPage() { resourceName={controlPlaneName} /> - Date: Fri, 19 Sep 2025 09:07:57 +0200 Subject: [PATCH 64/75] Update YamlDiff.module.css --- src/components/Yaml/YamlDiff.module.css | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/components/Yaml/YamlDiff.module.css b/src/components/Yaml/YamlDiff.module.css index 5e74e9b4..be7300f6 100644 --- a/src/components/Yaml/YamlDiff.module.css +++ b/src/components/Yaml/YamlDiff.module.css @@ -1,13 +1,11 @@ -/* Container wrapper to match YamlViewer spacing if needed */ .container { width: 100%; } -/* Line highlight colors for additions/removals */ .added { - background-color: rgba(56, 142, 60, 0.18); /* green-ish */ + background-color: rgba(56, 142, 60, 0.18); } .removed { - background-color: rgba(211, 47, 47, 0.18); /* red-ish */ + background-color: rgba(211, 47, 47, 0.18); } From 8fb31051b733b835b6286dbcef78de00ceba111a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Goral?= Date: Fri, 19 Sep 2025 09:12:24 +0200 Subject: [PATCH 65/75] Update YamlViewer.tsx --- src/components/Yaml/YamlViewer.tsx | 21 +++++---------------- 1 file changed, 5 insertions(+), 16 deletions(-) diff --git a/src/components/Yaml/YamlViewer.tsx b/src/components/Yaml/YamlViewer.tsx index dec86add..fa1cefa7 100644 --- a/src/components/Yaml/YamlViewer.tsx +++ b/src/components/Yaml/YamlViewer.tsx @@ -1,14 +1,14 @@ import { FC } from 'react'; import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter'; import { materialLight, materialDark } from 'react-syntax-highlighter/dist/esm/styles/prism'; -import { createPatch, diffLines } from 'diff'; + import { Button, FlexBox } from '@ui5/webcomponents-react'; import styles from './YamlViewer.module.css'; import { useToast } from '../../context/ToastContext.tsx'; import { useTranslation } from 'react-i18next'; import { useTheme } from '../../hooks/useTheme.ts'; -type YamlViewerProps = { originalYamlString?: string; yamlString: string; filename: string }; -const YamlViewer: FC = ({ originalYamlString, yamlString, filename }) => { +type YamlViewerProps = { yamlString: string; filename: string }; +const YamlViewer: FC = ({ yamlString, filename }) => { const toast = useToast(); const { t } = useTranslation(); const { isDarkTheme } = useTheme(); @@ -16,7 +16,6 @@ const YamlViewer: FC = ({ originalYamlString, yamlString, filen navigator.clipboard.writeText(yamlString); toast.show(t('yaml.copiedToClipboard')); }; - const downloadYaml = () => { const blob = new Blob([yamlString], { type: 'text/yaml' }); const url = window.URL.createObjectURL(blob); @@ -29,16 +28,6 @@ const YamlViewer: FC = ({ originalYamlString, yamlString, filen window.URL.revokeObjectURL(url); }; - const contentToRender = - originalYamlString !== undefined ? createPatch(`${filename}.yaml`, originalYamlString, yamlString) : yamlString; - - // console.log(originalYamlString !== undefined ? diffLines(originalYamlString, yamlString) : yamlString); - - const language = originalYamlString !== undefined ? 'diff' : 'yaml'; - console.log('1'); - console.log(yamlString); - console.log('2'); - console.log(originalYamlString); return (
@@ -50,7 +39,7 @@ const YamlViewer: FC = ({ originalYamlString, yamlString, filen = ({ originalYamlString, yamlString, filen }, }} > - {contentToRender} + {yamlString}
); From 2469b9346461a09ea234029e0576980bdafab079 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Goral?= Date: Fri, 19 Sep 2025 09:18:32 +0200 Subject: [PATCH 66/75] fix --- .../ComponentsSelection/ComponentsSelectionContainer.tsx | 3 ++- src/lib/api/types/crate/controlPlanes.ts | 3 +-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/components/ComponentsSelection/ComponentsSelectionContainer.tsx b/src/components/ComponentsSelection/ComponentsSelectionContainer.tsx index 1c70367e..939b4f96 100644 --- a/src/components/ComponentsSelection/ComponentsSelectionContainer.tsx +++ b/src/components/ComponentsSelection/ComponentsSelectionContainer.tsx @@ -101,6 +101,7 @@ export const ComponentsSelectionContainer: React.FC = setInitialComponentsList(newComponentsList); setComponentsList(newComponentsList); initializedComponents.current = true; + // eslint-disable-next-line react-hooks/exhaustive-deps }, [setComponentsList, defaultComponents, initialSelection, availableManagedComponentsListData?.items]); useEffect(() => { @@ -147,6 +148,7 @@ export const ComponentsSelectionContainer: React.FC = }); setComponentsList(updated); + // eslint-disable-next-line react-hooks/exhaustive-deps }, [defaultComponents, componentsList, setComponentsList, initialSelection]); if (isLoading) { @@ -157,7 +159,6 @@ export const ComponentsSelectionContainer: React.FC = return ; } - // Defensive: If the API returned no items, show error if (!componentsList || componentsList.length === 0) { return ; } diff --git a/src/lib/api/types/crate/controlPlanes.ts b/src/lib/api/types/crate/controlPlanes.ts index b6ff154e..3f652568 100644 --- a/src/lib/api/types/crate/controlPlanes.ts +++ b/src/lib/api/types/crate/controlPlanes.ts @@ -6,8 +6,7 @@ export interface Metadata { name: string; namespace: string; annotations: { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - [annotation: string]: any; + [annotation: string]: unknown; }; } From bd623a5649e9f09257e18c3a026cbfa061c63b9e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Goral?= Date: Fri, 19 Sep 2025 09:22:26 +0200 Subject: [PATCH 67/75] fix --- .../ControlPlanes/ControlPlaneCard/ControlPlaneCard.tsx | 5 +++-- src/lib/api/types/crate/controlPlanes.ts | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/components/ControlPlanes/ControlPlaneCard/ControlPlaneCard.tsx b/src/components/ControlPlanes/ControlPlaneCard/ControlPlaneCard.tsx index e554e029..11e2fea6 100644 --- a/src/components/ControlPlanes/ControlPlaneCard/ControlPlaneCard.tsx +++ b/src/components/ControlPlanes/ControlPlaneCard/ControlPlaneCard.tsx @@ -51,8 +51,9 @@ export function ControlPlaneCard({ controlPlane, workspace, projectName }: Props ); const name = controlPlane.metadata.name; - const displayName = controlPlane?.metadata?.annotations?.[DISPLAY_NAME_ANNOTATION]; - console.log(controlPlane); + const displayName = + controlPlane?.metadata?.annotations?.[DISPLAY_NAME_ANNOTATION as keyof typeof controlPlane.metadata.annotations]; + const namespace = controlPlane.metadata.namespace; const isSystemIdentityProviderEnabled = Boolean(controlPlane.spec?.authentication?.enableSystemIdentityProvider); diff --git a/src/lib/api/types/crate/controlPlanes.ts b/src/lib/api/types/crate/controlPlanes.ts index 3f652568..a00b9b9a 100644 --- a/src/lib/api/types/crate/controlPlanes.ts +++ b/src/lib/api/types/crate/controlPlanes.ts @@ -6,7 +6,7 @@ export interface Metadata { name: string; namespace: string; annotations: { - [annotation: string]: unknown; + 'openmcp.cloud/display-name': string; }; } From 39e81ccf16d4888a5defe48f9a0faea3a0fe47fa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Goral?= Date: Fri, 19 Sep 2025 09:53:25 +0200 Subject: [PATCH 68/75] Update src/components/Wizards/CreateManagedControlPlane/CreateManagedControlPlaneWizardContainer.tsx Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../CreateManagedControlPlaneWizardContainer.tsx | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/components/Wizards/CreateManagedControlPlane/CreateManagedControlPlaneWizardContainer.tsx b/src/components/Wizards/CreateManagedControlPlane/CreateManagedControlPlaneWizardContainer.tsx index 60ab4b02..ece068bf 100644 --- a/src/components/Wizards/CreateManagedControlPlane/CreateManagedControlPlaneWizardContainer.tsx +++ b/src/components/Wizards/CreateManagedControlPlane/CreateManagedControlPlaneWizardContainer.tsx @@ -421,8 +421,6 @@ export const CreateManagedControlPlaneWizardContainer: FC { const normalizedKind = (kindInput ?? '').toString().trim().toLowerCase(); return normalizedKind === 'serviceaccount' ? 'ServiceAccount' : 'User'; From 3710cae789a044c2860c3b0a9e4ae005231688fd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Goral?= Date: Fri, 19 Sep 2025 09:53:55 +0200 Subject: [PATCH 69/75] Update src/components/Wizards/CreateManagedControlPlane/CreateManagedControlPlaneWizardContainer.tsx Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../CreateManagedControlPlaneWizardContainer.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/components/Wizards/CreateManagedControlPlane/CreateManagedControlPlaneWizardContainer.tsx b/src/components/Wizards/CreateManagedControlPlane/CreateManagedControlPlaneWizardContainer.tsx index ece068bf..e535d6c7 100644 --- a/src/components/Wizards/CreateManagedControlPlane/CreateManagedControlPlaneWizardContainer.tsx +++ b/src/components/Wizards/CreateManagedControlPlane/CreateManagedControlPlaneWizardContainer.tsx @@ -170,7 +170,6 @@ export const CreateManagedControlPlaneWizardContainer: FC k + 1); }, [selectedTemplate, selectedStep, setValue, normalizeChargingTargetType]); - console.log(initialMcpDataWhenInEditMode); const nextButtonText = useMemo( () => ({ From 05dc482f0fdee14efed500e8e3c8257e6a2ed6ee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Goral?= Date: Fri, 19 Sep 2025 09:54:25 +0200 Subject: [PATCH 70/75] Update src/components/ComponentsSelection/ComponentsSelectionContainer.tsx Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../ComponentsSelection/ComponentsSelectionContainer.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/components/ComponentsSelection/ComponentsSelectionContainer.tsx b/src/components/ComponentsSelection/ComponentsSelectionContainer.tsx index 939b4f96..16aeb250 100644 --- a/src/components/ComponentsSelection/ComponentsSelectionContainer.tsx +++ b/src/components/ComponentsSelection/ComponentsSelectionContainer.tsx @@ -29,7 +29,8 @@ export const getSelectedComponents = (components: ComponentsListItem[]) => { const isCrossplaneSelected = components.some(({ name, isSelected }) => name === 'crossplane' && isSelected); return components.filter((component) => { if (!component.isSelected) return false; - return !(component.name?.includes('provider') && !isCrossplaneSelected); + if (component.name?.includes('provider') && !isCrossplaneSelected) return false; + return true; }); }; From ee76d3dbbbaa5509511b5691848894bdddca77d6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Goral?= Date: Fri, 19 Sep 2025 10:28:10 +0200 Subject: [PATCH 71/75] Update ControlPlaneCard.tsx --- .../ControlPlanes/ControlPlaneCard/ControlPlaneCard.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/ControlPlanes/ControlPlaneCard/ControlPlaneCard.tsx b/src/components/ControlPlanes/ControlPlaneCard/ControlPlaneCard.tsx index 11e2fea6..98f1e74f 100644 --- a/src/components/ControlPlanes/ControlPlaneCard/ControlPlaneCard.tsx +++ b/src/components/ControlPlanes/ControlPlaneCard/ControlPlaneCard.tsx @@ -69,7 +69,7 @@ export function ControlPlaneCard({ controlPlane, workspace, projectName }: Props - {displayName ?? name} + {!!displayName ? displayName : name}
From 9883a24754fb4201ad2c5d85ee3a7707ba829327 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Goral?= Date: Fri, 19 Sep 2025 10:32:51 +0200 Subject: [PATCH 72/75] fix --- src/components/Dialogs/MetadataForm.tsx | 3 --- .../CreateManagedControlPlaneWizardContainer.tsx | 2 +- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/src/components/Dialogs/MetadataForm.tsx b/src/components/Dialogs/MetadataForm.tsx index 1ce2895b..2e6735ab 100644 --- a/src/components/Dialogs/MetadataForm.tsx +++ b/src/components/Dialogs/MetadataForm.tsx @@ -56,9 +56,6 @@ export function MetadataForm({ const selectedOption = event.detail.selectedOption as HTMLElement; const value = selectedOption.dataset.value ?? ''; setValue('chargingTargetType', value, { shouldValidate: true, shouldDirty: true }); - if (value === '') { - setValue('chargingTarget', '', { shouldValidate: true, shouldDirty: true }); - } }; const chargingTypes: SelectOption[] = [ diff --git a/src/components/Wizards/CreateManagedControlPlane/CreateManagedControlPlaneWizardContainer.tsx b/src/components/Wizards/CreateManagedControlPlane/CreateManagedControlPlaneWizardContainer.tsx index e535d6c7..45744a5e 100644 --- a/src/components/Wizards/CreateManagedControlPlane/CreateManagedControlPlaneWizardContainer.tsx +++ b/src/components/Wizards/CreateManagedControlPlane/CreateManagedControlPlaneWizardContainer.tsx @@ -411,7 +411,7 @@ export const CreateManagedControlPlaneWizardContainer: FC Date: Fri, 19 Sep 2025 11:02:42 +0200 Subject: [PATCH 73/75] Update ControlPlaneCard.tsx --- .../ControlPlanes/ControlPlaneCard/ControlPlaneCard.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/ControlPlanes/ControlPlaneCard/ControlPlaneCard.tsx b/src/components/ControlPlanes/ControlPlaneCard/ControlPlaneCard.tsx index 98f1e74f..00e339f8 100644 --- a/src/components/ControlPlanes/ControlPlaneCard/ControlPlaneCard.tsx +++ b/src/components/ControlPlanes/ControlPlaneCard/ControlPlaneCard.tsx @@ -69,7 +69,7 @@ export function ControlPlaneCard({ controlPlane, workspace, projectName }: Props - {!!displayName ? displayName : name} + {displayName ? displayName : name}
From 49882c18fed7f730e0607443fd999ba933fc4e8d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Goral?= Date: Mon, 22 Sep 2025 12:51:05 +0200 Subject: [PATCH 74/75] Update MetadataForm.tsx --- src/components/Dialogs/MetadataForm.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/components/Dialogs/MetadataForm.tsx b/src/components/Dialogs/MetadataForm.tsx index 2e6735ab..7457ff71 100644 --- a/src/components/Dialogs/MetadataForm.tsx +++ b/src/components/Dialogs/MetadataForm.tsx @@ -56,6 +56,7 @@ export function MetadataForm({ const selectedOption = event.detail.selectedOption as HTMLElement; const value = selectedOption.dataset.value ?? ''; setValue('chargingTargetType', value, { shouldValidate: true, shouldDirty: true }); + setValue('chargingTarget', '', { shouldValidate: false, shouldDirty: true }); }; const chargingTypes: SelectOption[] = [ From cd1dc0d548969e4e7b4873145ca65d41f0297e77 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Goral?= Date: Tue, 23 Sep 2025 09:46:45 +0200 Subject: [PATCH 75/75] Update MetadataForm.tsx --- src/components/Dialogs/MetadataForm.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/components/Dialogs/MetadataForm.tsx b/src/components/Dialogs/MetadataForm.tsx index 7457ff71..1ce2895b 100644 --- a/src/components/Dialogs/MetadataForm.tsx +++ b/src/components/Dialogs/MetadataForm.tsx @@ -56,7 +56,9 @@ export function MetadataForm({ const selectedOption = event.detail.selectedOption as HTMLElement; const value = selectedOption.dataset.value ?? ''; setValue('chargingTargetType', value, { shouldValidate: true, shouldDirty: true }); - setValue('chargingTarget', '', { shouldValidate: false, shouldDirty: true }); + if (value === '') { + setValue('chargingTarget', '', { shouldValidate: true, shouldDirty: true }); + } }; const chargingTypes: SelectOption[] = [