Skip to content

Commit d0fffe5

Browse files
committed
Feat: Complete Kubectl Dialog Across Project,Workspace,Delete
1 parent fcaf193 commit d0fffe5

13 files changed

+617
-12
lines changed

src/components/ControlPlanes/List/ControlPlaneListWorkspaceGridTile.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,7 @@ export function ControlPlaneListWorkspaceGridTile({
153153
</ObjectPageSection>
154154
<DeleteConfirmationDialog
155155
resourceName={workspaceName}
156+
projectName={projectName}
156157
isOpen={dialogDeleteWsIsOpen}
157158
setIsOpen={setDialogDeleteWsIsOpen}
158159
onDeletionConfirmed={async () => {

src/components/Dialogs/CreateProjectDialog.tsx

Whitespace-only changes.

src/components/Dialogs/CreateProjectWorkspaceDialog.tsx

Lines changed: 35 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,12 @@ import {
1313
import { Member } from '../../lib/api/types/shared/members';
1414
import { ErrorDialog, ErrorDialogHandle } from '../Shared/ErrorMessageBox.tsx';
1515

16+
import { useState } from 'react';
17+
import { KubectlInfoButton } from './KubectlCommandInfo/KubectlInfoButton.tsx';
18+
import { KubectlWorkspaceDialog } from './KubectlCommandInfo/KubectlWorkspaceDialog.tsx';
19+
import { KubectlProjectDialog } from './KubectlCommandInfo/KubectlProjectDialog.tsx';
20+
1621
import { EditMembers } from '../Members/EditMembers.tsx';
17-
import ButtonDesign from '@ui5/webcomponents/dist/types/ButtonDesign.js';
1822
import { useFrontendConfig } from '../../context/FrontendConfigContext.tsx';
1923
import { useTranslation } from 'react-i18next';
2024

@@ -36,6 +40,7 @@ export interface CreateProjectWorkspaceDialogProps {
3640
nameInputRef: React.RefObject<InputDomRef | null>;
3741
displayNameInputRef: React.RefObject<InputDomRef | null>;
3842
chargingTargetInputRef: React.RefObject<InputDomRef | null>;
43+
projectName?: string;
3944
}
4045

4146
export function CreateProjectWorkspaceDialog({
@@ -49,9 +54,19 @@ export function CreateProjectWorkspaceDialog({
4954
nameInputRef,
5055
chargingTargetInputRef,
5156
displayNameInputRef,
57+
projectName,
5258
}: CreateProjectWorkspaceDialogProps) {
5359
const { links } = useFrontendConfig();
5460
const { t } = useTranslation();
61+
const [isKubectlDialogOpen, setIsKubectlDialogOpen] = useState(false);
62+
63+
const handleKubectlInfoClick = () => {
64+
setIsKubectlDialogOpen(true);
65+
};
66+
const handleKubectlDialogClose = () => {
67+
setIsKubectlDialogOpen(false);
68+
};
69+
5570
return (
5671
<>
5772
<Dialog
@@ -63,24 +78,20 @@ export function CreateProjectWorkspaceDialog({
6378
<Bar
6479
design="Footer"
6580
endContent={
66-
<>
67-
<Button
68-
design={ButtonDesign.Transparent}
69-
icon="sap-icon://question-mark"
70-
onClick={() => {
71-
window.open(links.COM_PAGE_GETTING_STARTED, '_blank');
72-
}}
73-
>
74-
{t('CreateProjectWorkspaceDialog.learnButton')}
75-
</Button>
81+
<div
82+
style={{ display: 'flex', alignItems: 'center', gap: '8px' }}
83+
>
84+
<KubectlInfoButton
85+
onClick={handleKubectlInfoClick}
86+
></KubectlInfoButton>
7687
<Button onClick={() => setIsOpen(false)}>
7788
{' '}
7889
{t('CreateProjectWorkspaceDialog.cancelButton')}
7990
</Button>
8091
<Button design="Emphasized" onClick={() => onCreate()}>
8192
{t('CreateProjectWorkspaceDialog.createButton')}
8293
</Button>
83-
</>
94+
</div>
8495
}
8596
/>
8697
}
@@ -94,6 +105,18 @@ export function CreateProjectWorkspaceDialog({
94105
chargingTargetInput={chargingTargetInputRef}
95106
/>
96107
</Dialog>
108+
109+
{isKubectlDialogOpen &&
110+
(projectName ? (
111+
<KubectlWorkspaceDialog
112+
onClose={handleKubectlDialogClose}
113+
project={projectName}
114+
></KubectlWorkspaceDialog>
115+
) : (
116+
<KubectlProjectDialog
117+
onClose={handleKubectlDialogClose}
118+
></KubectlProjectDialog>
119+
))}
97120
<ErrorDialog ref={errorDialogRef} />
98121
</>
99122
);

src/components/Dialogs/CreateWorkspaceDialogContainer.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,7 @@ export function CreateWorkspaceDialogContainer({
130130
displayNameInputRef={displayNameInput}
131131
chargingTargetInputRef={chargingTargetInput}
132132
onCreate={handleOnCreate}
133+
projectName={project}
133134
/>
134135
</>
135136
);

src/components/Dialogs/DeleteConfirmationDialog.tsx

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,13 @@ import {
1212
} from '@ui5/webcomponents-react';
1313
import ButtonDesign from '@ui5/webcomponents/dist/types/ButtonDesign.js';
1414
import { useTranslation } from 'react-i18next';
15+
import { KubectlDeleteWorkspace } from './KubectlCommandInfo/Controllers/KubectlDeleteWorkspace';
1516

1617
interface DeleteConfirmationDialogProps {
1718
isOpen: boolean;
1819
setIsOpen: (isOpen: boolean) => void;
1920
resourceName: string;
21+
projectName?: string;
2022
onDeletionConfirmed?: () => void;
2123
onCanceled?: () => void;
2224
}
@@ -25,6 +27,7 @@ export function DeleteConfirmationDialog({
2527
isOpen,
2628
setIsOpen,
2729
resourceName,
30+
projectName,
2831
onDeletionConfirmed,
2932
onCanceled,
3033
}: DeleteConfirmationDialogProps) {
@@ -113,6 +116,12 @@ export function DeleteConfirmationDialog({
113116
onInput={onConfirmationInputChange}
114117
/>
115118
</FormItem>
119+
<FormItem>
120+
<KubectlDeleteWorkspace
121+
projectName={projectName}
122+
resourceName={resourceName}
123+
/>
124+
</FormItem>
116125
</FormGroup>
117126
</Form>
118127
</Dialog>
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
import { DeleteWorkspaceDialog } from '../KubectlDeleteWorkspaceDialog';
2+
import { KubectlInfoButton } from '../KubectlInfoButton';
3+
import { useState } from 'react';
4+
5+
interface KubectlDeleteWorkspaceProps {
6+
projectName?: string;
7+
resourceName: string;
8+
}
9+
10+
export const KubectlDeleteWorkspace = ({
11+
projectName,
12+
resourceName,
13+
}: KubectlDeleteWorkspaceProps) => {
14+
const [isDialogOpen, setIsDialogOpen] = useState(false);
15+
16+
const closeDialog = () => {
17+
setIsDialogOpen(false);
18+
};
19+
20+
return (
21+
<>
22+
<KubectlInfoButton
23+
onClick={() => setIsDialogOpen(true)}
24+
></KubectlInfoButton>
25+
26+
{isDialogOpen && (
27+
<DeleteWorkspaceDialog
28+
onClose={closeDialog}
29+
projectName={projectName}
30+
resourceName={resourceName}
31+
></DeleteWorkspaceDialog>
32+
)}
33+
</>
34+
);
35+
};
Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
1+
import {
2+
FlexBox,
3+
MessageStrip,
4+
Text,
5+
Link,
6+
Input,
7+
Panel,
8+
} from '@ui5/webcomponents-react';
9+
import { KubectlDialog } from './KubectlDialog';
10+
import { KubectlTerminal } from './KubectlTerminal';
11+
import { useState, useEffect, ReactNode } from 'react';
12+
import { InputDomRef } from '@ui5/webcomponents-react';
13+
import { Ui5CustomEvent } from '@ui5/webcomponents-react';
14+
15+
export interface FormField {
16+
id: string;
17+
label: string;
18+
placeholder: string;
19+
defaultValue: string;
20+
}
21+
22+
export interface CustomCommand {
23+
command: string;
24+
description: string;
25+
isMainCommand?: boolean;
26+
}
27+
28+
interface KubectlBaseDialogProps {
29+
onClose: () => void;
30+
title: string;
31+
introSection: ReactNode[];
32+
formFields?: FormField[];
33+
customCommands: CustomCommand[];
34+
}
35+
36+
export const KubectlBaseDialog = ({
37+
onClose,
38+
title,
39+
introSection,
40+
formFields = [],
41+
customCommands,
42+
}: KubectlBaseDialogProps) => {
43+
const [formValues, setFormValues] = useState<Record<string, string>>({});
44+
45+
useEffect(() => {
46+
const initialValues: Record<string, string> = {};
47+
formFields?.forEach((field) => {
48+
initialValues[field.id] = field.defaultValue;
49+
});
50+
setFormValues(initialValues);
51+
}, []);
52+
53+
const handleFieldChange =
54+
(fieldId: string) => (event: Ui5CustomEvent<InputDomRef>) => {
55+
setFormValues((prev) => ({
56+
...prev,
57+
[fieldId]: event.target.value,
58+
}));
59+
};
60+
61+
const getFormattedCommand = (template: string) => {
62+
let result = template;
63+
64+
Object.entries(formValues).forEach(([key, value]) => {
65+
const placeholder = new RegExp(`\\$\\{${key}\\}`, 'g');
66+
result = result.replace(placeholder, value);
67+
});
68+
69+
return result;
70+
};
71+
72+
const showFormFields = formFields && formFields.length > 0;
73+
74+
return (
75+
<KubectlDialog onClose={onClose} headerText={title}>
76+
<FlexBox direction="Column" style={{ gap: '16px' }}>
77+
<Panel headerText="Prerequisites" collapsed>
78+
<Text>
79+
Make sure you have installed <b>kubectl</b> and the <b>kubelogin</b>{' '}
80+
plugin. We recommend using <b>krew</b> which is a plugin manager for
81+
kubectl.
82+
</Text>
83+
</Panel>
84+
85+
{introSection}
86+
87+
{customCommands
88+
.filter((template) => template.isMainCommand)
89+
.map((template, index) => (
90+
<div key={`main-command-${index}`}>
91+
<Text>{template.description}</Text>
92+
<KubectlTerminal
93+
command={getFormattedCommand(template.command)}
94+
/>
95+
</div>
96+
))}
97+
98+
{showFormFields && (
99+
<>
100+
<Text>
101+
<b>Important:</b> Before executing, modify the commands below:
102+
</Text>
103+
104+
<div
105+
style={{
106+
display: 'grid',
107+
gridTemplateColumns: '1fr 1fr',
108+
gap: '16px',
109+
}}
110+
>
111+
{formFields.map((field) => (
112+
<div key={field.id}>
113+
<Text style={{ fontWeight: 'bold', marginBottom: '8px' }}>
114+
{field.label}
115+
</Text>
116+
<Input
117+
placeholder={field.placeholder}
118+
value={formValues[field.id] || ''}
119+
onChange={handleFieldChange(field.id)}
120+
onInput={handleFieldChange(field.id)}
121+
style={{ width: '100%' }}
122+
/>
123+
</div>
124+
))}
125+
</div>
126+
</>
127+
)}
128+
129+
{customCommands
130+
.filter((template) => !template.isMainCommand)
131+
.map((template, index) => (
132+
<div key={`additional-command-${index}`}>
133+
<Text>{template.description}</Text>
134+
<KubectlTerminal
135+
command={getFormattedCommand(template.command)}
136+
/>
137+
</div>
138+
))}
139+
140+
<MessageStrip design="Information" hideCloseButton={true}>
141+
<Text>
142+
You can also use our{' '}
143+
<Link
144+
href="https://pages.github.tools.sap/cloud-orchestration/docs/managed-control-planes/get-started/get-started-mcp#before-you-begin"
145+
target="_blank"
146+
>
147+
Onboarding Guide
148+
</Link>{' '}
149+
for more information.
150+
</Text>
151+
</MessageStrip>
152+
</FlexBox>
153+
</KubectlDialog>
154+
);
155+
};
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
import { KubectlBaseDialog, CustomCommand } from './KubectlBaseDialog';
2+
import { Text } from '@ui5/webcomponents-react';
3+
4+
interface DeleteWorkspaceDialogProps {
5+
onClose: () => void;
6+
resourceName?: string;
7+
projectName?: string;
8+
}
9+
10+
export const DeleteWorkspaceDialog = ({
11+
onClose,
12+
resourceName,
13+
projectName,
14+
}: DeleteWorkspaceDialogProps) => {
15+
const projectNamespace = projectName
16+
? `project-${projectName}`
17+
: '<project-namespace>"';
18+
const workspaceName = resourceName || '<workspace-name>';
19+
20+
const customCommands: CustomCommand[] = [
21+
{
22+
command: `kubectl delete workspace ${resourceName} -n ${projectNamespace}`,
23+
description: 'Run this command to delete the workspace:',
24+
isMainCommand: true,
25+
},
26+
{
27+
command: `kubectl get workspace -n ${projectNamespace}`,
28+
description: 'To verify the workspace has been deleted, run:',
29+
},
30+
];
31+
32+
const introSection = [
33+
<Text>
34+
The below instructions will help you delete the workspace "{workspaceName}
35+
" from project namespace "{projectNamespace}" using kubectl.
36+
</Text>,
37+
<Text>
38+
Remember that this action is <b>irreversible</b> and all resources within
39+
the workspace will be <b>permanently deleted</b>.
40+
</Text>,
41+
];
42+
43+
return (
44+
<KubectlBaseDialog
45+
onClose={onClose}
46+
title="Delete a Workspace"
47+
introSection={introSection}
48+
customCommands={customCommands}
49+
/>
50+
);
51+
};

0 commit comments

Comments
 (0)