diff --git a/cypress/support/component.tsx b/cypress/support/component.tsx index 78400390..7f7328b0 100644 --- a/cypress/support/component.tsx +++ b/cypress/support/component.tsx @@ -22,7 +22,7 @@ import { mount } from 'cypress/react'; import './commands'; import { FrontendConfigContext } from '../../src/context/FrontendConfigContext'; import { mockedFrontendConfig } from '../../src/utils/testing'; - +import { ToastProvider } from '../../src/context/ToastContext.tsx'; // Augment the Cypress namespace to include type definitions for // your custom command. // Alternatively, can be defined in cypress/support/component.d.ts @@ -36,12 +36,15 @@ declare global { } } - Cypress.Commands.add('mount', (component, options) => { - return mount( - - {component} - - , options); + return mount( + + + + {component} + + + , + options, + ); }); - diff --git a/public/locales/en.json b/public/locales/en.json index 597090b5..a9f23741 100644 --- a/public/locales/en.json +++ b/public/locales/en.json @@ -56,9 +56,9 @@ "messageHeader": "Message", "reasonHeader": "Reason", "transitionHeader": "Last transition time" - }, + }, "CopyKubeconfigButton": { - "copiedMessage": "Copied to Clipboard", + "copiedMessage": "Copied to Clipboard", "failedMessage": "Failed to copy, Error:", "menuDownload": "Download", "menuCopy": "Copy to clipboard" @@ -113,7 +113,6 @@ "errorMessage": "useCopyButton must be used within a CopyButtonProvider" }, "CreateProjectWorkspaceDialog": { - "learnButton": "Learn how to do this in code", "createButton": "Create", "cancelButton": "Cancel", "chargingTargetLabel": "Charging Target", @@ -146,6 +145,79 @@ "copiedMessage": "Copied To Clipboard", "failedMessage": "Failed to copy" }, + "DeleteWorkspaceDialog": { + "title": "Delete a Workspace", + "introSection1": "The below instructions will help you delete the workspace \"{{workspaceName}}\" from project namespace \"{{projectNamespace}}\" using kubectl.", + "introSection2": "Remember that this action is irreversible and all resources within the workspace will be permanently deleted.", + "mainCommandDescription": "Run this command to delete the workspace:", + "verificationCommandDescription": "To verify the workspace has been deleted, run:" + }, + "KubectlDeleteMcpDialog": { + "title": "Delete a Managed Control Plane", + "introSection1": "The below will help you delete the Managed Control Plane \"{{mcpName}}\" from workspace \"{{workspaceNamespace}}\" using kubectl.", + "introSection2": "Remember that this action is irreversible and all resources managed by this control plane will be permanently deleted.", + "annotateCommand": "First, annotate the ManagedControlPlane to confirm deletion:", + "deleteCommand": "Run this command to delete the Managed Control Plane:", + "verificationCommandDescription": "To verify the MCP has been deleted, run:" + }, + "KubectlCreateProjectDialog": { + "title": "Create a Project", + "introSection": "A Project is the starting point to our ManagedControlPlane offering. Projects are usually created for each Organization/Team or similar root setups.", + "formFields": { + "projectName": { + "label": "Project Name", + "placeholder": "Enter project name" + }, + "chargingTargetId": { + "label": "BTP Global Account ID", + "placeholder": "Enter your Global Account ID", + "defaultValue": "" + }, + "userEmail": { + "label": "User Email", + "placeholder": "Enter your email address", + "defaultValue": "" + } + }, + "mainCommandDescription": "Run this command to create a new project:", + "resultCommandDescription": "To see the result of the project creation, run the below command:", + "namespaceCommandDescription": "A namespace is automatically generated for your project:" + }, + "KubectlCreateWorkspaceDialog": { + "title": "Create a Workspace", + "introSection": "Let's add a Workspace to our Project. We use workspaces to separate productive and non-productive ManagedControlPlanes.", + "formFields": { + "workspaceName": { + "label": "Workspace Name", + "placeholder": "Enter workspace name" + }, + "chargingTargetId": { + "label": "BTP Global Account ID", + "placeholder": "Enter your Global Account ID", + "defaultValue": "" + }, + "userEmail": { + "label": "User Email", + "placeholder": "Enter your email address", + "defaultValue": "" + } + }, + "mainCommandDescription": "Run this command to create a new workspace in your project:", + "resultCommandDescription": "To see the result of the workspace creation, run the below command:" + }, + "KubectlBaseDialog": { + "prerequisites": "Prerequisites", + "prerequisitesText": "Make sure you have installed kubectl and the kubelogin plugin. We recommend using krew which is a plugin manager for kubectl.", + "formFieldsNote": "Important: Before executing, modify the commands below:", + "onboardingGuide": "You can also use our Onboarding Guide for more information." + }, + "CommonKubectl": { + "emphasis": { + "irreversible": "irreversible", + "permanentlyDeleted": "permanently deleted" + }, + "learnButton": "Learn how to do this in code" + }, "App": { "loading": "Loading..." }, @@ -164,4 +236,4 @@ "userExists": "User with this email already exists!", "atLeastOneUser": "You need to have at least one member assigned." } -} \ No newline at end of file +} diff --git a/src/components/ControlPlanes/ControlPlaneCard.tsx b/src/components/ControlPlanes/ControlPlaneCard.tsx index 3b80eeb8..e0ff996c 100644 --- a/src/components/ControlPlanes/ControlPlaneCard.tsx +++ b/src/components/ControlPlanes/ControlPlaneCard.tsx @@ -20,6 +20,7 @@ import { } from '../../lib/api/types/crate/deleteMCP.ts'; import { DeleteConfirmationDialog } from '../Dialogs/DeleteConfirmationDialog.tsx'; import MCPHealthPopoverButton from '../ControlPlane/MCPHealthPopoverButton.tsx'; +import { KubectlDeleteMcp } from '../Dialogs/KubectlCommandInfo/Controllers/KubectlDeleteMcp.tsx'; interface Props { controlPlane: ListControlPlanesType; @@ -98,6 +99,13 @@ export function ControlPlaneCard({ + } isOpen={dialogDeleteMcpIsOpen} setIsOpen={setDialogDeleteMcpIsOpen} onDeletionConfirmed={async () => { diff --git a/src/components/ControlPlanes/List/ControlPlaneListWorkspaceGridTile.tsx b/src/components/ControlPlanes/List/ControlPlaneListWorkspaceGridTile.tsx index a421e2f7..d6a7ed12 100644 --- a/src/components/ControlPlanes/List/ControlPlaneListWorkspaceGridTile.tsx +++ b/src/components/ControlPlanes/List/ControlPlaneListWorkspaceGridTile.tsx @@ -27,6 +27,7 @@ import useApiResource, { } from '../../../lib/api/useApiResource.ts'; import { DISPLAY_NAME_ANNOTATION } from '../../../lib/api/types/shared/keyNames.ts'; import { DeleteConfirmationDialog } from '../../Dialogs/DeleteConfirmationDialog.tsx'; +import { KubectlDeleteWorkspace } from '../../Dialogs/KubectlCommandInfo/Controllers/KubectlDeleteWorkspace.tsx'; import { useToast } from '../../../context/ToastContext.tsx'; import { ListControlPlanes } from '../../../lib/api/types/crate/controlPlanes.ts'; import IllustratedError from '../../Shared/IllustratedError.tsx'; @@ -154,6 +155,12 @@ export function ControlPlaneListWorkspaceGridTile({ + } isOpen={dialogDeleteWsIsOpen} setIsOpen={setDialogDeleteWsIsOpen} onDeletionConfirmed={async () => { diff --git a/src/components/Dialogs/CreateProjectWorkspaceDialog.tsx b/src/components/Dialogs/CreateProjectWorkspaceDialog.tsx index e6337e85..f0ac47ca 100644 --- a/src/components/Dialogs/CreateProjectWorkspaceDialog.tsx +++ b/src/components/Dialogs/CreateProjectWorkspaceDialog.tsx @@ -12,13 +12,16 @@ import { import { Member } from '../../lib/api/types/shared/members'; import { ErrorDialog, ErrorDialogHandle } from '../Shared/ErrorMessageBox.tsx'; +import { FormEvent, useState } from 'react'; +import { KubectlInfoButton } from './KubectlCommandInfo/KubectlInfoButton.tsx'; +import { KubectlCreateWorkspaceDialog } from './KubectlCommandInfo/KubectlCreateWorkspaceDialog.tsx'; +import { KubectlCreateProjectDialog } from './KubectlCommandInfo/KubectlCreateProjectDialog.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'; 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 = { @@ -38,6 +41,7 @@ export interface CreateProjectWorkspaceDialogProps { register: UseFormRegister; errors: FieldErrors; setValue: UseFormSetValue; + projectName?: string; } export function CreateProjectWorkspaceDialog({ @@ -50,9 +54,14 @@ export function CreateProjectWorkspaceDialog({ register, errors, setValue, + projectName, }: CreateProjectWorkspaceDialogProps) { - const { links } = useFrontendConfig(); + // const { links } = useFrontendConfig(); const { t } = useTranslation(); + const [isKubectlDialogOpen, setIsKubectlDialogOpen] = useState(false); + + const openKubectlDialog = () => setIsKubectlDialogOpen(true); + const closeKubectlDialog = () => setIsKubectlDialogOpen(false); return ( <> @@ -65,16 +74,10 @@ export function CreateProjectWorkspaceDialog({ - +
+ - +
} /> } @@ -95,6 +98,15 @@ export function CreateProjectWorkspaceDialog({ setValue={setValue} /> + + ); diff --git a/src/components/Dialogs/CreateWorkspaceDialogContainer.tsx b/src/components/Dialogs/CreateWorkspaceDialogContainer.tsx index 96ab0f24..7af1cca6 100644 --- a/src/components/Dialogs/CreateWorkspaceDialogContainer.tsx +++ b/src/components/Dialogs/CreateWorkspaceDialogContainer.tsx @@ -127,6 +127,7 @@ export function CreateWorkspaceDialogContainer({ register={register} errors={errors} setValue={setValue} + projectName={project} onCreate={handleSubmit(handleWorkspaceCreate)} /> ); diff --git a/src/components/Dialogs/DeleteConfirmationDialog.test.tsx b/src/components/Dialogs/DeleteConfirmationDialog.test.tsx index 1cd60a3e..37c7670e 100644 --- a/src/components/Dialogs/DeleteConfirmationDialog.test.tsx +++ b/src/components/Dialogs/DeleteConfirmationDialog.test.tsx @@ -8,6 +8,7 @@ describe('DeleteConfirmationDialog', () => { isOpen={true} setIsOpen={cy.stub().as('setIsOpen')} resourceName="test-resource" + kubectl={
Resource Component
} onDeletionConfirmed={cy.stub().as('onDeletionConfirmed')} onCanceled={cy.stub().as('onCanceled')} {...props} diff --git a/src/components/Dialogs/DeleteConfirmationDialog.tsx b/src/components/Dialogs/DeleteConfirmationDialog.tsx index 64d059eb..a65cf6ab 100644 --- a/src/components/Dialogs/DeleteConfirmationDialog.tsx +++ b/src/components/Dialogs/DeleteConfirmationDialog.tsx @@ -1,4 +1,4 @@ -import { useEffect, useRef, useState } from 'react'; +import { ReactNode, useEffect, useRef, useState } from 'react'; import { Bar, Button, @@ -17,6 +17,7 @@ interface DeleteConfirmationDialogProps { isOpen: boolean; setIsOpen: (isOpen: boolean) => void; resourceName: string; + kubectl: ReactNode; onDeletionConfirmed?: () => void; onCanceled?: () => void; } @@ -27,6 +28,7 @@ export function DeleteConfirmationDialog({ resourceName, onDeletionConfirmed, onCanceled, + kubectl, }: DeleteConfirmationDialogProps) { const [confirmed, setConfirmed] = useState(false); const confirmationInput = useRef(null); @@ -113,6 +115,7 @@ export function DeleteConfirmationDialog({ onInput={onConfirmationInputChange} /> + {kubectl} diff --git a/src/components/Dialogs/KubectlCommandInfo/Controllers/KubectlDeleteMcp.tsx b/src/components/Dialogs/KubectlCommandInfo/Controllers/KubectlDeleteMcp.tsx new file mode 100644 index 00000000..9d6c65ad --- /dev/null +++ b/src/components/Dialogs/KubectlCommandInfo/Controllers/KubectlDeleteMcp.tsx @@ -0,0 +1,33 @@ +import { useState } from 'react'; +import { KubectlDeleteMcpDialog } from '../KubectlDeleteMcpDialog'; +import { KubectlInfoButton } from '../KubectlInfoButton'; + +interface KubectlDeleteMcpProps { + projectName: string; + workspaceName: string; + resourceName: string; +} + +export const KubectlDeleteMcp = ({ + projectName, + workspaceName, + resourceName, +}: KubectlDeleteMcpProps) => { + const [isInfoDialogOpen, setIsInfoDialogOpen] = useState(false); + + const openInfoDialog = () => setIsInfoDialogOpen(true); + const closeInfoDialog = () => setIsInfoDialogOpen(false); + + return ( + <> + + + + ); +}; diff --git a/src/components/Dialogs/KubectlCommandInfo/Controllers/KubectlDeleteWorkspace.tsx b/src/components/Dialogs/KubectlCommandInfo/Controllers/KubectlDeleteWorkspace.tsx new file mode 100644 index 00000000..d4636ffa --- /dev/null +++ b/src/components/Dialogs/KubectlCommandInfo/Controllers/KubectlDeleteWorkspace.tsx @@ -0,0 +1,30 @@ +import { useState } from 'react'; +import { DeleteWorkspaceDialog } from '../KubectlDeleteWorkspaceDialog'; +import { KubectlInfoButton } from '../KubectlInfoButton'; + +interface KubectlDeleteWorkspaceProps { + projectName?: string; + resourceName: string; +} + +export const KubectlDeleteWorkspace = ({ + projectName, + resourceName, +}: KubectlDeleteWorkspaceProps) => { + const [isInfoDialogOpen, setIsInfoDialogOpen] = useState(false); + + const openInfoDialog = () => setIsInfoDialogOpen(true); + const closeInfoDialog = () => setIsInfoDialogOpen(false); + + return ( + <> + + + + ); +}; \ No newline at end of file diff --git a/src/components/Dialogs/KubectlCommandInfo/KubectlBaseDialog.tsx b/src/components/Dialogs/KubectlCommandInfo/KubectlBaseDialog.tsx new file mode 100644 index 00000000..c99cc3c4 --- /dev/null +++ b/src/components/Dialogs/KubectlCommandInfo/KubectlBaseDialog.tsx @@ -0,0 +1,178 @@ +import { + FlexBox, + MessageStrip, + Text, + Link, + Input, + Panel, + InputDomRef, + Ui5CustomEvent, + Dialog, + Button, + Bar, +} from '@ui5/webcomponents-react'; +import { KubectlTerminal } from './KubectlTerminal'; +import { useState, useEffect, ReactNode } from 'react'; +import { useTranslation, Trans } from 'react-i18next'; + +export interface FormField { + id: string; + label: string; + placeholder: string; + defaultValue: string; +} + +export interface CustomCommand { + command: string; + description: string; + isMainCommand?: boolean; +} + +interface KubectlBaseDialogProps { + onClose: () => void; + open?: boolean; + title: string; + introSection: ReactNode[]; + formFields?: FormField[]; + customCommands: CustomCommand[]; +} + +export const KubectlBaseDialog = ({ + onClose, + open, + title, + introSection, + formFields = [], + customCommands, +}: KubectlBaseDialogProps) => { + const { t } = useTranslation(); + const [formValues, setFormValues] = useState>({}); + + useEffect(() => { + const initialValues: Record = {}; + formFields?.forEach((field) => { + initialValues[field.id] = field.defaultValue; + }); + setFormValues(initialValues); + }, []); + + const handleFieldChange = + (fieldId: string) => (event: Ui5CustomEvent) => { + 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 ( + Close} />} + onClose={onClose} + > + + + + , + bold2: , + bold3: , + }} + /> + + + + {introSection} + + {customCommands + .filter((template) => template.isMainCommand) + .map((template, index) => ( +
+ {template.description} + +
+ ))} + + {showFormFields && ( + <> + + , + }} + /> + + +
+ {formFields.map((field) => ( +
+ + {field.label} + + +
+ ))} +
+ + )} + + {customCommands + .filter((template) => !template.isMainCommand) + .map((template, index) => ( +
+ {template.description} + +
+ ))} + + + + ), + }} + /> + +
+
+ ); +}; diff --git a/src/components/Dialogs/KubectlCommandInfo/KubectlCreateProjectDialog.tsx b/src/components/Dialogs/KubectlCommandInfo/KubectlCreateProjectDialog.tsx new file mode 100644 index 00000000..7b4e7d63 --- /dev/null +++ b/src/components/Dialogs/KubectlCommandInfo/KubectlCreateProjectDialog.tsx @@ -0,0 +1,95 @@ +import { + KubectlBaseDialog, + FormField, + CustomCommand, +} from './KubectlBaseDialog'; +import { useTranslation } from 'react-i18next'; + +interface KubectlCreateProjectDialogProps { + onClose: () => void; + isOpen: boolean; +} + +export const KubectlCreateProjectDialog = ({ + onClose, + isOpen, +}: KubectlCreateProjectDialogProps) => { + const { t } = useTranslation(); + const randomProjectName = Math.random().toString(36).substring(2, 8); + + const formFields: FormField[] = [ + { + id: 'projectName', + label: t('KubectlCreateProjectDialog.formFields.projectName.label'), + placeholder: t( + 'KubectlCreateProjectDialog.formFields.projectName.placeholder', + ), + defaultValue: randomProjectName, + }, + { + id: 'chargingTargetId', + label: t('KubectlCreateProjectDialog.formFields.chargingTargetId.label'), + placeholder: t( + 'KubectlCreateProjectDialog.formFields.chargingTargetId.placeholder', + ), + defaultValue: t( + 'KubectlCreateProjectDialog.formFields.chargingTargetId.defaultValue', + ), + }, + { + id: 'userEmail', + label: t('KubectlCreateProjectDialog.formFields.userEmail.label'), + placeholder: t( + 'KubectlCreateProjectDialog.formFields.userEmail.placeholder', + ), + defaultValue: t( + 'KubectlCreateProjectDialog.formFields.userEmail.defaultValue', + ), + }, + ]; + + const customCommands: CustomCommand[] = [ + { + command: `echo ' + apiVersion: core.openmcp.cloud/v1alpha1 + kind: Project + metadata: + name: \${projectName} + annotations: + openmcp.cloud/display-name: My Project + labels: + openmcp.cloud.sap/charging-target-type: btp + openmcp.cloud.sap/charging-target: \${chargingTargetId} + spec: + members: + - kind: User + name: \${userEmail} + roles: + - admin + ' | kubectl create -f -`, + description: t('KubectlCreateProjectDialog.mainCommandDescription'), + isMainCommand: true, + }, + { + command: `kubectl get project \${projectName}`, + description: t('KubectlCreateProjectDialog.resultCommandDescription'), + }, + { + command: `kubectl get namespace project-\${projectName}`, + description: t('KubectlCreateProjectDialog.namespaceCommandDescription'), + }, + ]; + + const introSection = [t('KubectlCreateProjectDialog.introSection')]; + + return ( + + ); +}; diff --git a/src/components/Dialogs/KubectlCommandInfo/KubectlCreateWorkspaceDialog.tsx b/src/components/Dialogs/KubectlCommandInfo/KubectlCreateWorkspaceDialog.tsx new file mode 100644 index 00000000..7cbc40bd --- /dev/null +++ b/src/components/Dialogs/KubectlCommandInfo/KubectlCreateWorkspaceDialog.tsx @@ -0,0 +1,97 @@ +import { + KubectlBaseDialog, + FormField, + CustomCommand, +} from './KubectlBaseDialog'; +import { useTranslation } from 'react-i18next'; + +interface KubectlCreateWorkspaceDialogProps { + onClose: () => void; + isOpen: boolean; + project?: string; +} + +export const KubectlCreateWorkspaceDialog = ({ + onClose, + isOpen, + project, +}: KubectlCreateWorkspaceDialogProps) => { + const { t } = useTranslation(); + const randomWorkspaceName = Math.random().toString(36).substring(2, 8); + const projectName = project || ''; + const projectNamespace = `project-${projectName}`; + + const formFields: FormField[] = [ + { + id: 'workspaceName', + label: t('KubectlCreateWorkspaceDialog.formFields.workspaceName.label'), + placeholder: t( + 'KubectlCreateWorkspaceDialog.formFields.workspaceName.placeholder', + ), + defaultValue: randomWorkspaceName, + }, + { + id: 'chargingTargetId', + label: t( + 'KubectlCreateWorkspaceDialog.formFields.chargingTargetId.label', + ), + placeholder: t( + 'KubectlCreateWorkspaceDialog.formFields.chargingTargetId.placeholder', + ), + defaultValue: t( + 'KubectlCreateWorkspaceDialog.formFields.chargingTargetId.defaultValue', + ), + }, + { + id: 'userEmail', + label: t('KubectlCreateWorkspaceDialog.formFields.userEmail.label'), + placeholder: t( + 'KubectlCreateWorkspaceDialog.formFields.userEmail.placeholder', + ), + defaultValue: t( + 'KubectlCreateWorkspaceDialog.formFields.userEmail.defaultValue', + ), + }, + ]; + + const customCommands: CustomCommand[] = [ + { + command: `echo ' + apiVersion: core.openmcp.cloud/v1alpha1 + kind: Workspace + metadata: + name: \${workspaceName} + namespace: ${projectNamespace} + annotations: + openmcp.cloud/display-name: My Workspace + labels: + openmcp.cloud.sap/charging-target: \${chargingTargetId} + spec: + members: + - kind: User + name: \${userEmail} + roles: + - admin + ' | kubectl create -f -`, + description: t('KubectlCreateWorkspaceDialog.mainCommandDescription'), + isMainCommand: true, + }, + { + command: `kubectl get workspace \${workspaceName} -n ${projectNamespace}`, + description: t('KubectlCreateWorkspaceDialog.resultCommandDescription'), + }, + ]; + + const introSection = [t('KubectlCreateWorkspaceDialog.introSection')]; + + return ( + + ); +}; diff --git a/src/components/Dialogs/KubectlCommandInfo/KubectlDeleteMcpDialog.tsx b/src/components/Dialogs/KubectlCommandInfo/KubectlDeleteMcpDialog.tsx new file mode 100644 index 00000000..7724297f --- /dev/null +++ b/src/components/Dialogs/KubectlCommandInfo/KubectlDeleteMcpDialog.tsx @@ -0,0 +1,73 @@ +import { KubectlBaseDialog, CustomCommand } from './KubectlBaseDialog'; +import { Text } from '@ui5/webcomponents-react'; +import { useTranslation, Trans } from 'react-i18next'; +import { Fragment } from 'react/jsx-runtime'; + +interface KubectlDeleteMcpDialogProps { + onClose: () => void; + workspaceName: string; + projectName: string; + resourceName: string; + isOpen: boolean; +} + +export const KubectlDeleteMcpDialog = ({ + onClose, + workspaceName, + projectName, + resourceName, + isOpen, +}: KubectlDeleteMcpDialogProps) => { + const { t } = useTranslation(); + const workspaceNamespace = projectName + ? `project-${projectName}--ws-${workspaceName}` + : ''; + const mcpName = resourceName || ''; + + const customCommands: CustomCommand[] = [ + { + command: `kubectl -n ${workspaceNamespace} annotate managedcontrolplane ${mcpName} "confirmation.openmcp.cloud/deletion=true"`, + description: t('KubectlDeleteMcpDialog.annotateCommand'), + isMainCommand: true, + }, + { + command: `kubectl -n ${workspaceNamespace} delete managedcontrolplane ${mcpName}`, + description: t('KubectlDeleteMcpDialog.deleteCommand'), + isMainCommand: true, + }, + { + command: `kubectl -n ${workspaceNamespace} get managedcontrolplane`, + description: t('KubectlDeleteMcpDialog.verificationCommandDescription'), + }, + ]; + + const introSection = [ + + + {t('KubectlDeleteMcpDialog.introSection1', { + mcpName, + workspaceNamespace, + })} + + + , + bold2: , + }} + /> + + , + ]; + + return ( + + ); +}; diff --git a/src/components/Dialogs/KubectlCommandInfo/KubectlDeleteWorkspaceDialog.tsx b/src/components/Dialogs/KubectlCommandInfo/KubectlDeleteWorkspaceDialog.tsx new file mode 100644 index 00000000..67793c4b --- /dev/null +++ b/src/components/Dialogs/KubectlCommandInfo/KubectlDeleteWorkspaceDialog.tsx @@ -0,0 +1,68 @@ +import { KubectlBaseDialog, CustomCommand } from './KubectlBaseDialog'; +import { Text } from '@ui5/webcomponents-react'; +import { useTranslation, Trans } from 'react-i18next'; +import { Fragment } from 'react/jsx-runtime'; + +interface DeleteWorkspaceDialogProps { + onClose: () => void; + resourceName?: string; + projectName?: string; + isOpen: boolean; +} + +export const DeleteWorkspaceDialog = ({ + onClose, + resourceName, + projectName, + isOpen, +}: DeleteWorkspaceDialogProps) => { + const { t } = useTranslation(); + + const projectNamespace = projectName + ? `project-${projectName}` + : '"'; + const workspaceName = resourceName || ''; + + const customCommands: CustomCommand[] = [ + { + command: `kubectl delete workspace ${resourceName} -n ${projectNamespace}`, + description: t('DeleteWorkspaceDialog.mainCommandDescription'), + isMainCommand: true, + }, + { + command: `kubectl get workspace -n ${projectNamespace}`, + description: t('DeleteWorkspaceDialog.verificationCommandDescription'), + isMainCommand: true, + }, + ]; + + const introSection = [ + + + {t('DeleteWorkspaceDialog.introSection1', { + workspaceName, + projectNamespace, + })} + + + , + bold2: , + }} + /> + + , + ]; + + return ( + + ); +}; diff --git a/src/components/Dialogs/KubectlCommandInfo/KubectlInfoButton.tsx b/src/components/Dialogs/KubectlCommandInfo/KubectlInfoButton.tsx new file mode 100644 index 00000000..4654d7c7 --- /dev/null +++ b/src/components/Dialogs/KubectlCommandInfo/KubectlInfoButton.tsx @@ -0,0 +1,24 @@ +import { Button, ButtonPropTypes } from '@ui5/webcomponents-react'; +import ButtonDesign from '@ui5/webcomponents/dist/types/ButtonDesign.js'; +import { useTranslation } from 'react-i18next'; +import '@ui5/webcomponents-icons/dist/command-line-interfaces.js'; + +interface KubectlInfoButtonProps extends Omit {} + +export const KubectlInfoButton = ({ + onClick, + ...buttonProps +}: KubectlInfoButtonProps) => { + const { t } = useTranslation(); + + return ( + + ); +}; diff --git a/src/components/Dialogs/KubectlCommandInfo/KubectlTerminal.tsx b/src/components/Dialogs/KubectlCommandInfo/KubectlTerminal.tsx new file mode 100644 index 00000000..54e4f547 --- /dev/null +++ b/src/components/Dialogs/KubectlCommandInfo/KubectlTerminal.tsx @@ -0,0 +1,110 @@ +import { FlexBox, Button } from '@ui5/webcomponents-react'; +import { useToast } from '../../../context/ToastContext'; +import '@ui5/webcomponents-icons/dist/copy'; +import { ThemingParameters } from '@ui5/webcomponents-react-base'; + +interface KubeCtlTerminalProps { + command: string; +} + +export const KubectlTerminal = ({ command }: KubeCtlTerminalProps) => { + const { show } = useToast(); + + const handleCopy = () => { + navigator.clipboard.writeText(command).then( + () => { + show('Command copied to clipboard'); + }, + (err) => { + console.error('Could not copy text: ', err); + }, + ); + }; + + const FormattedCommand = () => { + if (command.startsWith("echo '") && command.includes('apiVersion:')) { + const [_, afterEcho] = command.split("echo '", 2); + const [yamlContent, afterYaml] = afterEcho.split("' | kubectl", 2); + const kubectlPart = 'kubectl' + afterYaml; + + const yamlLines = yamlContent.split('\n').map((line, index) => ( +
+ {line} +
+ )); + + return ( + <> + echo ' +
{yamlLines}
+ ' | {kubectlPart} + + ); + } + + return {command}; + }; + + return ( +
+ + +
+
+
+ +
+ ); +};