Skip to content

Commit 82fad67

Browse files
authored
Merge pull request #3840 from headlamp-k8s/project-deletion
frontend: Add options when deleting a project
2 parents 95d0eb6 + 538da8b commit 82fad67

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
@@ -538,6 +538,17 @@
538538
"Creating project": "",
539539
"Creating following resources in this project:": "",
540540
"Something went wrong": "",
541+
"Delete project": "",
542+
"Deleting project {{ projectName }}…": "",
543+
"Cancelled deletion of project {{ projectName }}.": "",
544+
"Deleted project {{ projectName }}.": "",
545+
"Error deleting project {{ projectName }}.": "",
546+
"Delete Project": "",
547+
"Are you sure you want to delete project \"{{projectName}}\"?": "",
548+
"By default, this will only remove the project label from the following namespaces:": "",
549+
"Also delete the namespaces (this will remove all resources within them)": "",
550+
"Warning: This action cannot be undone. All resources in these namespaces will be permanently deleted.": "",
551+
"Delete Project & Namespaces": "",
541552
"Resources": "",
542553
"Access": "",
543554
"Map": "",

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

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -538,6 +538,17 @@
538538
"Creating project": "Creating project",
539539
"Creating following resources in this project:": "Creating following resources in this project:",
540540
"Something went wrong": "Something went wrong",
541+
"Delete project": "Delete project",
542+
"Deleting project {{ projectName }}…": "Deleting project {{ projectName }}…",
543+
"Cancelled deletion of project {{ projectName }}.": "Cancelled deletion of project {{ projectName }}.",
544+
"Deleted project {{ projectName }}.": "Deleted project {{ projectName }}.",
545+
"Error deleting project {{ projectName }}.": "Error deleting project {{ projectName }}.",
546+
"Delete Project": "Delete Project",
547+
"Are you sure you want to delete project \"{{projectName}}\"?": "Are you sure you want to delete project \"{{projectName}}\"?",
548+
"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:",
549+
"Also delete the namespaces (this will remove all resources within them)": "Also delete the namespaces (this will remove all resources within them)",
550+
"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.",
551+
"Delete Project & Namespaces": "Delete Project & Namespaces",
541552
"Resources": "Resources",
542553
"Access": "Access",
543554
"Map": "Map",

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

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -542,6 +542,17 @@
542542
"Creating project": "",
543543
"Creating following resources in this project:": "",
544544
"Something went wrong": "",
545+
"Delete project": "",
546+
"Deleting project {{ projectName }}…": "",
547+
"Cancelled deletion of project {{ projectName }}.": "",
548+
"Deleted project {{ projectName }}.": "",
549+
"Error deleting project {{ projectName }}.": "",
550+
"Delete Project": "",
551+
"Are you sure you want to delete project \"{{projectName}}\"?": "",
552+
"By default, this will only remove the project label from the following namespaces:": "",
553+
"Also delete the namespaces (this will remove all resources within them)": "",
554+
"Warning: This action cannot be undone. All resources in these namespaces will be permanently deleted.": "",
555+
"Delete Project & Namespaces": "",
545556
"Resources": "",
546557
"Access": "",
547558
"Map": "",

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

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -542,6 +542,17 @@
542542
"Creating project": "",
543543
"Creating following resources in this project:": "",
544544
"Something went wrong": "",
545+
"Delete project": "",
546+
"Deleting project {{ projectName }}…": "",
547+
"Cancelled deletion of project {{ projectName }}.": "",
548+
"Deleted project {{ projectName }}.": "",
549+
"Error deleting project {{ projectName }}.": "",
550+
"Delete Project": "",
551+
"Are you sure you want to delete project \"{{projectName}}\"?": "",
552+
"By default, this will only remove the project label from the following namespaces:": "",
553+
"Also delete the namespaces (this will remove all resources within them)": "",
554+
"Warning: This action cannot be undone. All resources in these namespaces will be permanently deleted.": "",
555+
"Delete Project & Namespaces": "",
545556
"Resources": "",
546557
"Access": "",
547558
"Map": "",

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

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -538,6 +538,17 @@
538538
"Creating project": "",
539539
"Creating following resources in this project:": "",
540540
"Something went wrong": "",
541+
"Delete project": "",
542+
"Deleting project {{ projectName }}…": "",
543+
"Cancelled deletion of project {{ projectName }}.": "",
544+
"Deleted project {{ projectName }}.": "",
545+
"Error deleting project {{ projectName }}.": "",
546+
"Delete Project": "",
547+
"Are you sure you want to delete project \"{{projectName}}\"?": "",
548+
"By default, this will only remove the project label from the following namespaces:": "",
549+
"Also delete the namespaces (this will remove all resources within them)": "",
550+
"Warning: This action cannot be undone. All resources in these namespaces will be permanently deleted.": "",
551+
"Delete Project & Namespaces": "",
541552
"Resources": "",
542553
"Access": "",
543554
"Map": "",

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

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -542,6 +542,17 @@
542542
"Creating project": "",
543543
"Creating following resources in this project:": "",
544544
"Something went wrong": "",
545+
"Delete project": "",
546+
"Deleting project {{ projectName }}…": "",
547+
"Cancelled deletion of project {{ projectName }}.": "",
548+
"Deleted project {{ projectName }}.": "",
549+
"Error deleting project {{ projectName }}.": "",
550+
"Delete Project": "",
551+
"Are you sure you want to delete project \"{{projectName}}\"?": "",
552+
"By default, this will only remove the project label from the following namespaces:": "",
553+
"Also delete the namespaces (this will remove all resources within them)": "",
554+
"Warning: This action cannot be undone. All resources in these namespaces will be permanently deleted.": "",
555+
"Delete Project & Namespaces": "",
545556
"Resources": "",
546557
"Access": "",
547558
"Map": "",

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

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -534,6 +534,17 @@
534534
"Creating project": "",
535535
"Creating following resources in this project:": "",
536536
"Something went wrong": "",
537+
"Delete project": "",
538+
"Deleting project {{ projectName }}…": "",
539+
"Cancelled deletion of project {{ projectName }}.": "",
540+
"Deleted project {{ projectName }}.": "",
541+
"Error deleting project {{ projectName }}.": "",
542+
"Delete Project": "",
543+
"Are you sure you want to delete project \"{{projectName}}\"?": "",
544+
"By default, this will only remove the project label from the following namespaces:": "",
545+
"Also delete the namespaces (this will remove all resources within them)": "",
546+
"Warning: This action cannot be undone. All resources in these namespaces will be permanently deleted.": "",
547+
"Delete Project & Namespaces": "",
537548
"Resources": "",
538549
"Access": "",
539550
"Map": "",

0 commit comments

Comments
 (0)