Skip to content

Commit 538da8b

Browse files
committed
frontend: Add options when deleting a project
The projects were being deleted by completely deleting the associated namespace and this may be not what the user wants. So these changes allow the user to choose exactly how the project should be deleted (just the project notion or the full namespaces associated with it). Signed-off-by: Joaquim Rocha <joaquim.rocha@microsoft.com>
1 parent 8fff965 commit 538da8b

File tree

15 files changed

+372
-3
lines changed

15 files changed

+372
-3
lines changed
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
/*
2+
* Copyright 2025 The Kubernetes Authors
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
import React, { useState } from 'react';
18+
import { useTranslation } from 'react-i18next';
19+
import Namespace from '../../lib/k8s/namespace';
20+
import { ProjectDefinition } from '../../redux/projectsSlice';
21+
import ActionButton, { ButtonStyle } from '../common/ActionButton';
22+
import AuthVisible from '../common/Resource/AuthVisible';
23+
import { ProjectDeleteDialog } from './ProjectDeleteDialog';
24+
25+
interface ProjectDeleteButtonProps {
26+
project: ProjectDefinition;
27+
namespaces: Namespace[];
28+
buttonStyle?: ButtonStyle;
29+
}
30+
31+
export function ProjectDeleteButton({
32+
project,
33+
namespaces,
34+
buttonStyle,
35+
}: ProjectDeleteButtonProps) {
36+
const { t } = useTranslation();
37+
const [openDialog, setOpenDialog] = useState(false);
38+
39+
const projectNamespaces = namespaces.filter(ns => project.namespaces.includes(ns.metadata.name));
40+
41+
// Don't show the button if there are no namespaces for this project
42+
if (projectNamespaces.length === 0) {
43+
return null;
44+
}
45+
46+
return (
47+
<AuthVisible item={projectNamespaces[0]} authVerb="update">
48+
<ActionButton
49+
description={t('Delete project')}
50+
buttonStyle={buttonStyle}
51+
onClick={() => setOpenDialog(true)}
52+
icon="mdi:delete"
53+
/>
54+
<ProjectDeleteDialog
55+
open={openDialog}
56+
project={project}
57+
onClose={() => setOpenDialog(false)}
58+
namespaces={projectNamespaces}
59+
/>
60+
</AuthVisible>
61+
);
62+
}
Lines changed: 172 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,172 @@
1+
/*
2+
* Copyright 2025 The Kubernetes Authors
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
import {
18+
Button,
19+
Checkbox,
20+
Dialog,
21+
DialogActions,
22+
DialogContent,
23+
DialogContentText,
24+
FormControlLabel,
25+
Typography,
26+
} from '@mui/material';
27+
import React, { useState } from 'react';
28+
import { useTranslation } from 'react-i18next';
29+
import { useDispatch } from 'react-redux';
30+
import Namespace from '../../lib/k8s/namespace';
31+
import { clusterAction } from '../../redux/clusterActionSlice';
32+
import { ProjectDefinition } from '../../redux/projectsSlice';
33+
import { AppDispatch } from '../../redux/stores/store';
34+
import { DialogTitle } from '../common/Dialog';
35+
import AuthVisible from '../common/Resource/AuthVisible';
36+
import { PROJECT_ID_LABEL } from './projectUtils';
37+
38+
interface ProjectDeleteDialogProps {
39+
open: boolean;
40+
project: ProjectDefinition;
41+
onClose: () => void;
42+
namespaces: Namespace[];
43+
}
44+
45+
export function ProjectDeleteDialog({
46+
open,
47+
project,
48+
onClose,
49+
namespaces,
50+
}: ProjectDeleteDialogProps) {
51+
const { t } = useTranslation();
52+
const dispatch: AppDispatch = useDispatch();
53+
const [deleteNamespaces, setDeleteNamespaces] = useState(false);
54+
55+
const handleDelete = () => {
56+
const projectNamespaces = namespaces.filter(ns =>
57+
project.namespaces.includes(ns.metadata.name)
58+
);
59+
60+
dispatch(
61+
clusterAction(
62+
async () => {
63+
if (deleteNamespaces) {
64+
// Delete the entire namespaces
65+
await Promise.all(projectNamespaces.map(namespace => namespace.delete()));
66+
} else {
67+
// Just remove the project label from each namespace
68+
await Promise.all(
69+
projectNamespaces.map(async namespace => {
70+
const updatedData = { ...namespace.jsonData };
71+
if (updatedData.metadata?.labels) {
72+
delete updatedData.metadata.labels[PROJECT_ID_LABEL];
73+
// If labels object becomes empty, remove it entirely
74+
if (Object.keys(updatedData.metadata.labels).length === 0) {
75+
delete updatedData.metadata.labels;
76+
}
77+
}
78+
return namespace.update(updatedData);
79+
})
80+
);
81+
}
82+
},
83+
{
84+
startMessage: t('Deleting project {{ projectName }}…', { projectName: project.id }),
85+
cancelledMessage: t('Cancelled deletion of project {{ projectName }}.', {
86+
projectName: project.id,
87+
}),
88+
successMessage: t('Deleted project {{ projectName }}.', { projectName: project.id }),
89+
errorMessage: t('Error deleting project {{ projectName }}.', { projectName: project.id }),
90+
successUrl: '/',
91+
}
92+
)
93+
);
94+
95+
onClose();
96+
};
97+
98+
return (
99+
<Dialog
100+
open={open}
101+
onClose={onClose}
102+
aria-labelledby="project-delete-dialog-title"
103+
aria-describedby="project-delete-dialog-description"
104+
maxWidth="sm"
105+
fullWidth
106+
>
107+
<DialogTitle id="project-delete-dialog-title">{t('Delete Project')}</DialogTitle>
108+
<DialogContent>
109+
<DialogContentText id="project-delete-dialog-description" component="div">
110+
<Typography variant="body1" paragraph>
111+
{t('Are you sure you want to delete project "{{projectName}}"?', {
112+
projectName: project.id,
113+
})}
114+
</Typography>
115+
116+
<Typography variant="body2" paragraph>
117+
{t(
118+
'By default, this will only remove the project label from the following namespaces:'
119+
)}
120+
</Typography>
121+
122+
<ul>
123+
{project.namespaces.map(namespaceName => (
124+
<li key={namespaceName}>
125+
<strong>{namespaceName}</strong>
126+
</li>
127+
))}
128+
</ul>
129+
130+
<AuthVisible item={namespaces[0]} authVerb="delete">
131+
<FormControlLabel
132+
control={
133+
<Checkbox
134+
checked={deleteNamespaces}
135+
onChange={e => setDeleteNamespaces(e.target.checked)}
136+
name="deleteNamespaces"
137+
color="primary"
138+
/>
139+
}
140+
label={
141+
<Typography variant="body2">
142+
{t('Also delete the namespaces (this will remove all resources within them)')}
143+
</Typography>
144+
}
145+
/>
146+
147+
{deleteNamespaces && (
148+
<Typography variant="body2" color="error" sx={{ mt: 1, fontWeight: 'bold' }}>
149+
{t(
150+
'Warning: This action cannot be undone. All resources in these namespaces will be permanently deleted.'
151+
)}
152+
</Typography>
153+
)}
154+
</AuthVisible>
155+
</DialogContentText>
156+
</DialogContent>
157+
<DialogActions>
158+
<Button onClick={onClose} color="secondary" variant="contained">
159+
{t('Cancel')}
160+
</Button>
161+
<Button
162+
onClick={handleDelete}
163+
color="primary"
164+
variant="contained"
165+
sx={{ minWidth: '200px' }} // Fixed width to prevent button resize
166+
>
167+
{deleteNamespaces ? t('Delete Project & Namespaces') : t('Delete Project')}
168+
</Button>
169+
</DialogActions>
170+
</Dialog>
171+
);
172+
}

frontend/src/components/project/ProjectDetails.tsx

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,12 +28,12 @@ import { useTypedSelector } from '../../redux/hooks';
2828
import { ProjectDefinition } from '../../redux/projectsSlice';
2929
import { Activity } from '../activity/Activity';
3030
import { EditButton, EditorDialog, Loader, StatusLabel } from '../common';
31-
import DeleteMultipleButton from '../common/Resource/DeleteMultipleButton';
3231
import ResourceTable from '../common/Resource/ResourceTable';
3332
import SectionBox from '../common/SectionBox';
3433
import { GraphFilter } from '../resourceMap/graph/graphFiltering';
3534
import { GraphView } from '../resourceMap/GraphView';
3635
import { ResourceQuotaTable } from '../resourceQuota/Details';
36+
import { ProjectDeleteButton } from './ProjectDeleteButton';
3737
import { useProject } from './ProjectList';
3838
import { ProjectResourcesTab, useResourceCategoriesList } from './ProjectResourcesTab';
3939
import { getHealthIcon, getResourcesHealth } from './projectUtils';
@@ -109,8 +109,11 @@ function ProjectDetailsContent({ project }: { project: ProjectDefinition }) {
109109
{project.id}
110110
</Typography>
111111

112-
<DeleteMultipleButton
113-
items={allNamespaces?.filter(it => project.namespaces.includes(it.metadata.name))}
112+
<ProjectDeleteButton
113+
project={project}
114+
namespaces={
115+
allNamespaces?.filter(it => project.namespaces.includes(it.metadata.name)) || []
116+
}
114117
/>
115118
</Box>
116119
}

frontend/src/i18n/locales/de/translation.json

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -521,6 +521,17 @@
521521
"Creating project": "",
522522
"Creating following resources in this project:": "",
523523
"Something went wrong": "",
524+
"Delete project": "",
525+
"Deleting project {{ projectName }}…": "",
526+
"Cancelled deletion of project {{ projectName }}.": "",
527+
"Deleted project {{ projectName }}.": "",
528+
"Error deleting project {{ projectName }}.": "",
529+
"Delete Project": "",
530+
"Are you sure you want to delete project \"{{projectName}}\"?": "",
531+
"By default, this will only remove the project label from the following namespaces:": "",
532+
"Also delete the namespaces (this will remove all resources within them)": "",
533+
"Warning: This action cannot be undone. All resources in these namespaces will be permanently deleted.": "",
534+
"Delete Project & Namespaces": "",
524535
"Resources": "",
525536
"Access": "",
526537
"Map": "",

frontend/src/i18n/locales/en/translation.json

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -521,6 +521,17 @@
521521
"Creating project": "Creating project",
522522
"Creating following resources in this project:": "Creating following resources in this project:",
523523
"Something went wrong": "Something went wrong",
524+
"Delete project": "Delete project",
525+
"Deleting project {{ projectName }}…": "Deleting project {{ projectName }}…",
526+
"Cancelled deletion of project {{ projectName }}.": "Cancelled deletion of project {{ projectName }}.",
527+
"Deleted project {{ projectName }}.": "Deleted project {{ projectName }}.",
528+
"Error deleting project {{ projectName }}.": "Error deleting project {{ projectName }}.",
529+
"Delete Project": "Delete Project",
530+
"Are you sure you want to delete project \"{{projectName}}\"?": "Are you sure you want to delete project \"{{projectName}}\"?",
531+
"By default, this will only remove the project label from the following namespaces:": "By default, this will only remove the project label from the following namespaces:",
532+
"Also delete the namespaces (this will remove all resources within them)": "Also delete the namespaces (this will remove all resources within them)",
533+
"Warning: This action cannot be undone. All resources in these namespaces will be permanently deleted.": "Warning: This action cannot be undone. All resources in these namespaces will be permanently deleted.",
534+
"Delete Project & Namespaces": "Delete Project & Namespaces",
524535
"Resources": "Resources",
525536
"Access": "Access",
526537
"Map": "Map",

frontend/src/i18n/locales/es/translation.json

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -524,6 +524,17 @@
524524
"Creating project": "",
525525
"Creating following resources in this project:": "",
526526
"Something went wrong": "",
527+
"Delete project": "",
528+
"Deleting project {{ projectName }}…": "",
529+
"Cancelled deletion of project {{ projectName }}.": "",
530+
"Deleted project {{ projectName }}.": "",
531+
"Error deleting project {{ projectName }}.": "",
532+
"Delete Project": "",
533+
"Are you sure you want to delete project \"{{projectName}}\"?": "",
534+
"By default, this will only remove the project label from the following namespaces:": "",
535+
"Also delete the namespaces (this will remove all resources within them)": "",
536+
"Warning: This action cannot be undone. All resources in these namespaces will be permanently deleted.": "",
537+
"Delete Project & Namespaces": "",
527538
"Resources": "",
528539
"Access": "",
529540
"Map": "",

frontend/src/i18n/locales/fr/translation.json

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -524,6 +524,17 @@
524524
"Creating project": "",
525525
"Creating following resources in this project:": "",
526526
"Something went wrong": "",
527+
"Delete project": "",
528+
"Deleting project {{ projectName }}…": "",
529+
"Cancelled deletion of project {{ projectName }}.": "",
530+
"Deleted project {{ projectName }}.": "",
531+
"Error deleting project {{ projectName }}.": "",
532+
"Delete Project": "",
533+
"Are you sure you want to delete project \"{{projectName}}\"?": "",
534+
"By default, this will only remove the project label from the following namespaces:": "",
535+
"Also delete the namespaces (this will remove all resources within them)": "",
536+
"Warning: This action cannot be undone. All resources in these namespaces will be permanently deleted.": "",
537+
"Delete Project & Namespaces": "",
527538
"Resources": "",
528539
"Access": "",
529540
"Map": "",

frontend/src/i18n/locales/hi/translation.json

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -521,6 +521,17 @@
521521
"Creating project": "",
522522
"Creating following resources in this project:": "",
523523
"Something went wrong": "",
524+
"Delete project": "",
525+
"Deleting project {{ projectName }}…": "",
526+
"Cancelled deletion of project {{ projectName }}.": "",
527+
"Deleted project {{ projectName }}.": "",
528+
"Error deleting project {{ projectName }}.": "",
529+
"Delete Project": "",
530+
"Are you sure you want to delete project \"{{projectName}}\"?": "",
531+
"By default, this will only remove the project label from the following namespaces:": "",
532+
"Also delete the namespaces (this will remove all resources within them)": "",
533+
"Warning: This action cannot be undone. All resources in these namespaces will be permanently deleted.": "",
534+
"Delete Project & Namespaces": "",
524535
"Resources": "",
525536
"Access": "",
526537
"Map": "",

frontend/src/i18n/locales/it/translation.json

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -524,6 +524,17 @@
524524
"Creating project": "",
525525
"Creating following resources in this project:": "",
526526
"Something went wrong": "",
527+
"Delete project": "",
528+
"Deleting project {{ projectName }}…": "",
529+
"Cancelled deletion of project {{ projectName }}.": "",
530+
"Deleted project {{ projectName }}.": "",
531+
"Error deleting project {{ projectName }}.": "",
532+
"Delete Project": "",
533+
"Are you sure you want to delete project \"{{projectName}}\"?": "",
534+
"By default, this will only remove the project label from the following namespaces:": "",
535+
"Also delete the namespaces (this will remove all resources within them)": "",
536+
"Warning: This action cannot be undone. All resources in these namespaces will be permanently deleted.": "",
537+
"Delete Project & Namespaces": "",
527538
"Resources": "",
528539
"Access": "",
529540
"Map": "",

frontend/src/i18n/locales/ja/translation.json

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -518,6 +518,17 @@
518518
"Creating project": "",
519519
"Creating following resources in this project:": "",
520520
"Something went wrong": "",
521+
"Delete project": "",
522+
"Deleting project {{ projectName }}…": "",
523+
"Cancelled deletion of project {{ projectName }}.": "",
524+
"Deleted project {{ projectName }}.": "",
525+
"Error deleting project {{ projectName }}.": "",
526+
"Delete Project": "",
527+
"Are you sure you want to delete project \"{{projectName}}\"?": "",
528+
"By default, this will only remove the project label from the following namespaces:": "",
529+
"Also delete the namespaces (this will remove all resources within them)": "",
530+
"Warning: This action cannot be undone. All resources in these namespaces will be permanently deleted.": "",
531+
"Delete Project & Namespaces": "",
521532
"Resources": "",
522533
"Access": "",
523534
"Map": "",

0 commit comments

Comments
 (0)