Skip to content
Merged
Show file tree
Hide file tree
Changes from 8 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
13 changes: 11 additions & 2 deletions public/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,9 @@
},
"ProjectsListView": {
"pageTitle": "Let's get started",
"title": "Projects"
"title": "Projects",
"deleteProject": "Delete project",
"deleteConfirmationDialog": "Project deleted"
},
"ControlPlaneView": {
"accessError": "Managed Control Plane does not have access information yet",
Expand Down Expand Up @@ -190,6 +192,12 @@
"mainCommandDescription": "Run this command to delete the workspace:",
"verificationCommandDescription": "To verify the workspace has been deleted, run:"
},
"DeleteProjectDialog": {
"title": "Delete a Project",
"introSection1": "The below instructions will help you delete the project named \"{{projectName}}\" using kubectl.",
"introSection2": "Remember that this action is <bold1>irreversible</bold1> and all resources within the project will be <bold2>permanently deleted</bold2>.",
"mainCommandDescription": "Run this command to delete the project:"
},
"KubectlDeleteMcpDialog": {
"title": "Delete a Managed Control Plane",
"introSection1": "The below will help you delete the Managed Control Plane \"{{mcpName}}\" from workspace \"{{workspaceNamespace}}\" using kubectl.",
Expand Down Expand Up @@ -288,7 +296,8 @@
"search": "Search",
"components": "Components",
"notSelected": "Not selected",
"btp": "BTP"
"btp": "BTP",
"options": "Options"
},
"buttons": {
"viewResource": "View resource",
Expand Down
27 changes: 5 additions & 22 deletions src/components/Dialogs/DeleteConfirmationDialog.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,5 @@
import { ReactNode, useEffect, useRef, useState } from 'react';
import {
Bar,
Button,
Dialog,
Form,
FormGroup,
FormItem,
Input,
InputDomRef,
Label,
} from '@ui5/webcomponents-react';
import { Bar, Button, Dialog, Form, FormGroup, FormItem, Input, InputDomRef, Label } from '@ui5/webcomponents-react';
import ButtonDesign from '@ui5/webcomponents/dist/types/ButtonDesign.js';
import { useTranslation } from 'react-i18next';

Expand Down Expand Up @@ -74,7 +64,7 @@ export function DeleteConfirmationDialog({
</Button>
<Button
design={ButtonDesign.Negative}
disabled={confirmed === false}
disabled={!confirmed}
onClick={() => {
setIsOpen(false);
onDeletionConfirmed && onDeletionConfirmed();
Expand All @@ -94,27 +84,20 @@ export function DeleteConfirmationDialog({
</Label>
<Label>
{' '}
{t('DeleteConfirmationDialog.deleteMessageType')}{' '}
<b>{resourceName}</b>{' '}
{t('DeleteConfirmationDialog.deleteMessageType')} <b>{resourceName}</b>{' '}
{t('DeleteConfirmationDialog.deleteMessageConfirm')}
</Label>
</FormGroup>
<FormGroup>
<FormItem
labelContent={
<Label>
{t('DeleteConfirmationDialog.deleteMessageType')}{' '}
{resourceName}{' '}
{t('DeleteConfirmationDialog.deleteMessageType')} {resourceName}{' '}
{t('DeleteConfirmationDialog.deleteMessageConfirm')}
</Label>
}
>
<Input
ref={confirmationInput}
id="mcp-name-input"
placeholder=""
onInput={onConfirmationInputChange}
/>
<Input ref={confirmationInput} id="mcp-name-input" placeholder="" onInput={onConfirmationInputChange} />
</FormItem>
<FormItem>{kubectl}</FormItem>
</FormGroup>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { useState } from 'react';

import { KubectlInfoButton } from '../KubectlInfoButton';
import { DeleteProjectDialog } from '../KubectlDeleteProjectDialog.tsx';

interface KubectlDeleteWorkspaceProps {
projectName?: string;
}

export const KubectlDeleteProject = ({ projectName }: KubectlDeleteWorkspaceProps) => {
const [isInfoDialogOpen, setIsInfoDialogOpen] = useState(false);

const openInfoDialog = () => setIsInfoDialogOpen(true);
const closeInfoDialog = () => setIsInfoDialogOpen(false);

return (
<>
<KubectlInfoButton onClick={openInfoDialog} />
<DeleteProjectDialog projectName={projectName} isOpen={isInfoDialogOpen} onClose={closeInfoDialog} />
</>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import { KubectlBaseDialog, CustomCommand } from './KubectlBaseDialog';
import { Text } from '@ui5/webcomponents-react';
import { useTranslation, Trans } from 'react-i18next';
import { Fragment } from 'react/jsx-runtime';

interface DeleteProjectDialogProps {
onClose: () => void;
resourceName?: string;
projectName?: string;
isOpen: boolean;
}

export const DeleteProjectDialog = ({ onClose, projectName, isOpen }: DeleteProjectDialogProps) => {
const { t } = useTranslation();

const projectNamespace = projectName ?? '<project-names>"';

const customCommands: CustomCommand[] = [
{
command: `kubectl delete project ${projectNamespace}`,
description: t('DeleteProjectDialog.mainCommandDescription'),
isMainCommand: true,
},
];

const introSection = [
<Fragment key="intro-1">
<Text>
{t('DeleteProjectDialog.introSection1', {
projectName,
})}
</Text>
<Text>
<Trans
i18nKey={t('DeleteProjectDialog.introSection2')}
components={{
bold1: <b />,
bold2: <b />,
}}
/>
</Text>
</Fragment>,
];

return (
<KubectlBaseDialog
title={t('DeleteProjectDialog.title')}
introSection={introSection}
customCommands={customCommands}
open={isOpen}
onClose={onClose}
/>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -10,17 +10,10 @@ interface DeleteWorkspaceDialogProps {
isOpen: boolean;
}

export const DeleteWorkspaceDialog = ({
onClose,
resourceName,
projectName,
isOpen,
}: DeleteWorkspaceDialogProps) => {
export const DeleteWorkspaceDialog = ({ onClose, resourceName, projectName, isOpen }: DeleteWorkspaceDialogProps) => {
const { t } = useTranslation();

const projectNamespace = projectName
? `project-${projectName}`
: '<project-namespace>"';
const projectNamespace = projectName ? `project-${projectName}` : '<project-namespace>"';
const workspaceName = resourceName || '<workspace-name>';

const customCommands: CustomCommand[] = [
Expand Down
59 changes: 35 additions & 24 deletions src/components/Projects/ProjectsList.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,5 @@
import {
AnalyticalTable,
AnalyticalTableColumnDefinition,
} from '@ui5/webcomponents-react';
import { ThemingParameters } from '@ui5/webcomponents-react-base';
import { AnalyticalTable, AnalyticalTableColumnDefinition, Link } from '@ui5/webcomponents-react';

import { CopyButton } from '../Shared/CopyButton.tsx';
import useLuigiNavigate from '../Shared/useLuigiNavigate.tsx';
import IllustratedError from '../Shared/IllustratedError.tsx';
Expand All @@ -14,6 +11,7 @@ import { ListProjectNames } from '../../lib/api/types/crate/listProjectNames';
import { t } from 'i18next';
import { YamlViewButtonWithLoader } from '../Yaml/YamlViewButtonWithLoader.tsx';
import { useMemo } from 'react';
import { ProjectsListItemMenu } from './ProjectsListItemMenu.tsx';

export default function ProjectsList() {
const navigate = useLuigiNavigate();
Expand All @@ -37,24 +35,26 @@ export default function ProjectsList() {
accessor: 'projectName',
// eslint-disable-next-line @typescript-eslint/no-explicit-any
Cell: (instance: any) => (
<div
<Link
design={'Emphasized'}
style={{
cursor: 'pointer',
width: '100%',
color: ThemingParameters.sapLinkColor,
fontWeight: 'bold',
display: 'flex',
justifyContent: 'start',
alignItems: 'center',
textAlign: 'left',
paddingTop: '0.5rem',
paddingBottom: '0.5rem',
}}
onClick={() => {
navigate(`/mcp/projects/${instance.cell.row.original?.projectName}`);
}}
>
{instance.cell.value}
</div>
</Link>
),
},
{
Header: 'Namespace',
accessor: 'nameSpace',
width: 340,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
Cell: (instance: any) => (
<div
Expand All @@ -64,6 +64,7 @@ export default function ProjectsList() {
gap: '0.5rem',
alignItems: 'center',
width: '100%',

cursor: 'pointer',
}}
>
Expand Down Expand Up @@ -94,6 +95,26 @@ export default function ProjectsList() {
</div>
),
},
{
Header: '',
accessor: 'options',
width: 60,
disableFilters: true,
hAlign: 'Center',
// eslint-disable-next-line @typescript-eslint/no-explicit-any
Cell: (instance: any) => (
<div
style={{
width: '100%',
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
}}
>
<ProjectsListItemMenu projectName={instance.cell.row.original?.projectName ?? ''} />
</div>
),
},
],
[],
);
Expand All @@ -103,17 +124,7 @@ export default function ProjectsList() {

return (
<>
<AnalyticalTable
style={{ margin: '12px' }}
columns={stabilizedColumns}
data={stabilizedData}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
onRowClick={(e: any) => {
navigate(
`/mcp/projects/${data ? [e.detail.row.values.projectName] : ''}`,
);
}}
/>
<AnalyticalTable style={{ margin: '12px' }} columns={stabilizedColumns} data={stabilizedData} />
</>
);
}
70 changes: 70 additions & 0 deletions src/components/Projects/ProjectsListItemMenu.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import { Button, ButtonDomRef, Menu, MenuItem, Ui5CustomEvent, MenuDomRef } from '@ui5/webcomponents-react';
import type { ButtonClickEventDetail } from '@ui5/webcomponents/dist/Button.js';
import { FC, useRef, useState } from 'react';
import '@ui5/webcomponents-icons/dist/copy';
import '@ui5/webcomponents-icons/dist/accept';

import { useTranslation } from 'react-i18next';
import { DeleteConfirmationDialog } from '../Dialogs/DeleteConfirmationDialog.tsx';

import { useToast } from '../../context/ToastContext.tsx';
import { useApiResourceMutation } from '../../lib/api/useApiResource.ts';
import { DeleteWorkspaceType } from '../../lib/api/types/crate/deleteWorkspace.ts';
import { DeleteProjectResource } from '../../lib/api/types/crate/deleteProject.ts';
import { KubectlDeleteProject } from '../Dialogs/KubectlCommandInfo/Controllers/KubectlDeleteProject.tsx';

type ProjectsListItemMenuProps = {
projectName: string;
};

export const ProjectsListItemMenu: FC<ProjectsListItemMenuProps> = ({ projectName }) => {
const popoverRef = useRef<MenuDomRef>(null);
const [open, setOpen] = useState(false);
const [dialogDeleteProjectIsOpen, setDialogDeleteProjectIsOpen] = useState(false);
const { t } = useTranslation();
const toast = useToast();
const { trigger } = useApiResourceMutation<DeleteWorkspaceType>(DeleteProjectResource(projectName));
const handleOpenerClick = (e: Ui5CustomEvent<ButtonDomRef, ButtonClickEventDetail>) => {
e.stopImmediatePropagation();
e.stopPropagation();
if (popoverRef.current && e.currentTarget) {
popoverRef.current.opener = e.currentTarget as HTMLElement;
setOpen((prev) => !prev);
}
};

return (
<div>
<Button icon="overflow" icon-end onClick={handleOpenerClick} />
<Menu
ref={popoverRef}
open={open}
onItemClick={(event) => {
event.stopImmediatePropagation();
event.stopPropagation();
const action = (event.detail.item as HTMLElement).dataset.action;
if (action === 'deleteProject') {
setDialogDeleteProjectIsOpen(true);
}

setOpen(false);
}}
>
<MenuItem key={'delete'} text={t('ProjectsListView.deleteProject')} data-action="deleteProject" icon="delete" />
</Menu>

{dialogDeleteProjectIsOpen && (
<DeleteConfirmationDialog
resourceName={projectName}
kubectl={<KubectlDeleteProject projectName={projectName} />}
isOpen={dialogDeleteProjectIsOpen}
setIsOpen={setDialogDeleteProjectIsOpen}
onDeletionConfirmed={async () => {
await trigger();
toast.show(t('ProjectsListView.deleteConfirmationDialog'));
}}
/>
)}
</div>
);
};
12 changes: 2 additions & 10 deletions src/components/Yaml/YamlViewButtonWithLoader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,7 @@ export type YamlViewButtonProps = {
resourceName: string;
};

export const YamlViewButtonWithLoader: FC<YamlViewButtonProps> = ({
workspaceName,
resourceType,
resourceName,
}) => {
export const YamlViewButtonWithLoader: FC<YamlViewButtonProps> = ({ workspaceName, resourceType, resourceName }) => {
const [isOpen, setIsOpen] = useState(false);
const { t } = useTranslation();
return (
Expand All @@ -25,11 +21,7 @@ export const YamlViewButtonWithLoader: FC<YamlViewButtonProps> = ({
isOpen={isOpen}
setIsOpen={setIsOpen}
dialogContent={
<YamlLoader
workspaceName={workspaceName}
resourceName={resourceName}
resourceType={resourceType}
/>
<YamlLoader workspaceName={workspaceName} resourceName={resourceName} resourceType={resourceType} />
}
/>

Expand Down
Loading
Loading