Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,7 @@ export function ControlPlaneListWorkspaceGridTile({
</ObjectPageSection>
<DeleteConfirmationDialog
resourceName={workspaceName}
projectName={projectName}
isOpen={dialogDeleteWsIsOpen}
setIsOpen={setDialogDeleteWsIsOpen}
onDeletionConfirmed={async () => {
Expand Down
46 changes: 31 additions & 15 deletions src/components/Dialogs/CreateProjectWorkspaceDialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,16 @@ import {
import { Member } from '../../lib/api/types/shared/members';
import { ErrorDialog, ErrorDialogHandle } from '../Shared/ErrorMessageBox.tsx';

import { useState, FormEvent } from 'react';
import { KubectlInfoButton } from './KubectlCommandInfo/KubectlInfoButton.tsx';
import { KubectlWorkspaceDialog } from './KubectlCommandInfo/KubectlWorkspaceDialog.tsx';
import { KubectlProjectDialog } from './KubectlCommandInfo/KubectlProjectDialog.tsx';

import { EditMembers } from '../Members/EditMembers.tsx';
import ButtonDesign from '@ui5/webcomponents/dist/types/ButtonDesign.js';
import { useFrontendConfig } from '../../context/FrontendConfigContext.tsx';
// import { useFrontendConfig } from '../../context/FrontendConfigContext.tsx';
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Delete unused code

Suggested change
// import { useFrontendConfig } from '../../context/FrontendConfigContext.tsx';

(Also further down)

import { useTranslation } from 'react-i18next';

import { CreateDialogProps } from './CreateWorkspaceDialogContainer.tsx';
import { FormEvent } from 'react';
import { FieldErrors, UseFormRegister, UseFormSetValue } from 'react-hook-form';

export type OnCreatePayload = {
Expand All @@ -38,6 +41,7 @@ export interface CreateProjectWorkspaceDialogProps {
register: UseFormRegister<CreateDialogProps>;
errors: FieldErrors<CreateDialogProps>;
setValue: UseFormSetValue<CreateDialogProps>;
projectName?: string;
}

export function CreateProjectWorkspaceDialog({
Expand All @@ -50,10 +54,18 @@ export function CreateProjectWorkspaceDialog({
register,
errors,
setValue,
projectName,
}: CreateProjectWorkspaceDialogProps) {
const { links } = useFrontendConfig();
// const { links } = useFrontendConfig();
const { t } = useTranslation();
const [isKubectlDialogOpen, setIsKubectlDialogOpen] = useState(false);

const handleKubectlInfoClick = () => {
setIsKubectlDialogOpen(true);
};
const handleKubectlDialogClose = () => {
setIsKubectlDialogOpen(false);
};
return (
<>
<Dialog
Expand All @@ -65,24 +77,18 @@ export function CreateProjectWorkspaceDialog({
<Bar
design="Footer"
endContent={
<>
<Button
design={ButtonDesign.Transparent}
icon="sap-icon://question-mark"
onClick={() => {
window.open(links.COM_PAGE_GETTING_STARTED, '_blank');
}}
>
{t('CreateProjectWorkspaceDialog.learnButton')}
</Button>
<div
style={{ display: 'flex', alignItems: 'center', gap: '8px' }}
>
<KubectlInfoButton onClick={handleKubectlInfoClick} />
<Button onClick={() => setIsOpen(false)}>
{' '}
{t('CreateProjectWorkspaceDialog.cancelButton')}
</Button>
<Button design="Emphasized" onClick={() => onCreate()}>
{t('CreateProjectWorkspaceDialog.createButton')}
</Button>
</>
</div>
}
/>
}
Expand All @@ -95,6 +101,16 @@ export function CreateProjectWorkspaceDialog({
setValue={setValue}
/>
</Dialog>

{isKubectlDialogOpen &&
(projectName ? (
<KubectlWorkspaceDialog
project={projectName}
onClose={handleKubectlDialogClose}
/>
) : (
<KubectlProjectDialog onClose={handleKubectlDialogClose} />
))}
<ErrorDialog ref={errorDialogRef} />
</>
);
Expand Down
1 change: 1 addition & 0 deletions src/components/Dialogs/CreateWorkspaceDialogContainer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,7 @@ export function CreateWorkspaceDialogContainer({
register={register}
errors={errors}
setValue={setValue}
projectName={project}
onCreate={handleSubmit(handleWorkspaceCreate)}
/>
);
Expand Down
9 changes: 9 additions & 0 deletions src/components/Dialogs/DeleteConfirmationDialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,13 @@ import {
} from '@ui5/webcomponents-react';
import ButtonDesign from '@ui5/webcomponents/dist/types/ButtonDesign.js';
import { useTranslation } from 'react-i18next';
import { KubectlDeleteWorkspace } from './KubectlCommandInfo/Controllers/KubectlDeleteWorkspace';

interface DeleteConfirmationDialogProps {
isOpen: boolean;
setIsOpen: (isOpen: boolean) => void;
resourceName: string;
projectName?: string;
onDeletionConfirmed?: () => void;
onCanceled?: () => void;
}
Expand All @@ -25,6 +27,7 @@ export function DeleteConfirmationDialog({
isOpen,
setIsOpen,
resourceName,
projectName,
onDeletionConfirmed,
onCanceled,
}: DeleteConfirmationDialogProps) {
Expand Down Expand Up @@ -113,6 +116,12 @@ export function DeleteConfirmationDialog({
onInput={onConfirmationInputChange}
/>
</FormItem>
<FormItem>
<KubectlDeleteWorkspace
projectName={projectName}
resourceName={resourceName}
/>
</FormItem>
</FormGroup>
</Form>
</Dialog>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { DeleteWorkspaceDialog } from '../KubectlDeleteWorkspaceDialog';
import { KubectlInfoButton } from '../KubectlInfoButton';
import { useState } from 'react';

interface KubectlDeleteWorkspaceProps {
projectName?: string;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also here: Can’t we just assume that there’s always a projectName?

resourceName: string;
}

export const KubectlDeleteWorkspace = ({
projectName,
resourceName,
}: KubectlDeleteWorkspaceProps) => {
const [isDialogOpen, setIsDialogOpen] = useState(false);

const closeDialog = () => {
setIsDialogOpen(false);
};

return (
<>
<KubectlInfoButton
onClick={() => setIsDialogOpen(true)}
></KubectlInfoButton>

{isDialogOpen && (
<DeleteWorkspaceDialog
onClose={closeDialog}
projectName={projectName}
resourceName={resourceName}
></DeleteWorkspaceDialog>
)}
</>
);
};
155 changes: 155 additions & 0 deletions src/components/Dialogs/KubectlCommandInfo/KubectlBaseDialog.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
import {
FlexBox,
MessageStrip,
Text,
Link,
Input,
Panel,
} from '@ui5/webcomponents-react';
import { KubectlDialog } from './KubectlDialog';
import { KubectlTerminal } from './KubectlTerminal';
import { useState, useEffect, ReactNode } from 'react';
import { InputDomRef } from '@ui5/webcomponents-react';
import { Ui5CustomEvent } from '@ui5/webcomponents-react';

export interface FormField {
id: string;
label: string;
placeholder: string;
defaultValue: string;
}

export interface CustomCommand {
command: string;
description: string;
isMainCommand?: boolean;
}

interface KubectlBaseDialogProps {
onClose: () => void;
title: string;
introSection: ReactNode[];
formFields?: FormField[];
customCommands: CustomCommand[];
}

export const KubectlBaseDialog = ({
onClose,
title,
introSection,
formFields = [],
customCommands,
}: KubectlBaseDialogProps) => {
const [formValues, setFormValues] = useState<Record<string, string>>({});

useEffect(() => {
const initialValues: Record<string, string> = {};
formFields?.forEach((field) => {
initialValues[field.id] = field.defaultValue;
});
setFormValues(initialValues);
}, []);

const handleFieldChange =
(fieldId: string) => (event: Ui5CustomEvent<InputDomRef>) => {
setFormValues((prev) => ({
...prev,
[fieldId]: event.target.value,
}));
};

const getFormattedCommand = (template: string) => {
let result = template;

Object.entries(formValues).forEach(([key, value]) => {
const placeholder = new RegExp(`\\$\\{${key}\\}`, 'g');
result = result.replace(placeholder, value);
});

return result;
};

const showFormFields = formFields && formFields.length > 0;

return (
<KubectlDialog onClose={onClose} headerText={title}>
<FlexBox direction="Column" style={{ gap: '16px' }}>
<Panel headerText="Prerequisites" collapsed>
<Text>
Make sure you have installed <b>kubectl</b> and the <b>kubelogin</b>{' '}
plugin. We recommend using <b>krew</b> which is a plugin manager for
kubectl.
</Text>
</Panel>

{introSection}

{customCommands
.filter((template) => template.isMainCommand)
.map((template, index) => (
<div key={`main-command-${index}`}>
<Text>{template.description}</Text>
<KubectlTerminal
command={getFormattedCommand(template.command)}
/>
</div>
))}

{showFormFields && (
<>
<Text>
<b>Important:</b> Before executing, modify the commands below:
</Text>

<div
style={{
display: 'grid',
gridTemplateColumns: '1fr 1fr',
gap: '16px',
}}
>
{formFields.map((field) => (
<div key={field.id}>
<Text style={{ fontWeight: 'bold', marginBottom: '8px' }}>
{field.label}
</Text>
<Input
placeholder={field.placeholder}
value={formValues[field.id] || ''}
onChange={handleFieldChange(field.id)}
onInput={handleFieldChange(field.id)}
style={{ width: '100%' }}
/>
</div>
))}
</div>
</>
)}

{customCommands
.filter((template) => !template.isMainCommand)
.map((template, index) => (
<div key={`additional-command-${index}`}>
<Text>{template.description}</Text>
<KubectlTerminal
command={getFormattedCommand(template.command)}
/>
</div>
))}

<MessageStrip design="Information" hideCloseButton={true}>
<Text>
You can also use our{' '}
<Link
href="https://pages.github.tools.sap/cloud-orchestration/docs/managed-control-planes/get-started/get-started-mcp#before-you-begin"
target="_blank"
>
Onboarding Guide
</Link>{' '}
for more information.
</Text>
</MessageStrip>
</FlexBox>
</KubectlDialog>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import { KubectlBaseDialog, CustomCommand } from './KubectlBaseDialog';
import { Text } from '@ui5/webcomponents-react';

interface DeleteWorkspaceDialogProps {
onClose: () => void;
resourceName?: string;
projectName?: string;
}

export const DeleteWorkspaceDialog = ({
onClose,
resourceName,
projectName,
}: DeleteWorkspaceDialogProps) => {
const projectNamespace = projectName
? `project-${projectName}`
: '<project-namespace>"';
const workspaceName = resourceName || '<workspace-name>';

const customCommands: CustomCommand[] = [
{
command: `kubectl delete workspace ${resourceName} -n ${projectNamespace}`,
description: 'Run this command to delete the workspace:',
isMainCommand: true,
},
{
command: `kubectl get workspace -n ${projectNamespace}`,
description: 'To verify the workspace has been deleted, run:',
},
];

const introSection = [
<Text>
The below instructions will help you delete the workspace "{workspaceName}
" from project namespace "{projectNamespace}" using kubectl.
</Text>,
<Text>
Remember that this action is <b>irreversible</b> and all resources within
the workspace will be <b>permanently deleted</b>.
</Text>,
];

return (
<KubectlBaseDialog
onClose={onClose}
title="Delete a Workspace"
introSection={introSection}
customCommands={customCommands}
/>
);
};
Loading