diff --git a/backend/cmd/headlamp.go b/backend/cmd/headlamp.go index 268f908e6e..ec920f657b 100644 --- a/backend/cmd/headlamp.go +++ b/backend/cmd/headlamp.go @@ -1384,6 +1384,20 @@ func (c *HeadlampConfig) deleteCluster(w http.ResponseWriter, r *http.Request) { return } + alsoRemoveFromKubeConfig := true + + if alsoRemoveFromKubeConfig { + // delete context from actual deafult kubecofig file + // to do : replace the hard coding of this line with an actual path + err = kubeconfig.RemoveContextFromFile(name, filepath.Join("/home/vynty/.kube/config")) + if err != nil { + logger.Log(logger.LevelError, map[string]string{"cluster": name}, + err, "my cool error") + http.Error(w, "removing cluster from kubeconfig", http.StatusInternalServerError) + return + } + } + kubeConfigPersistenceFile, err := defaultKubeConfigPersistenceFile() if err != nil { logger.Log(logger.LevelError, map[string]string{"cluster": name}, diff --git a/frontend/src/components/App/Home/index.tsx b/frontend/src/components/App/Home/index.tsx index 94c5e95159..629a97ea00 100644 --- a/frontend/src/components/App/Home/index.tsx +++ b/frontend/src/components/App/Home/index.tsx @@ -1,5 +1,5 @@ import { Icon } from '@iconify/react'; -import { useTheme } from '@mui/material'; +import { Checkbox, useTheme } from '@mui/material'; import Box from '@mui/material/Box'; import IconButton from '@mui/material/IconButton'; import ListItemText from '@mui/material/ListItemText'; @@ -25,6 +25,24 @@ import { ConfirmDialog } from '../../common'; import ResourceTable from '../../common/Resource/ResourceTable'; import RecentClusters from './RecentClusters'; +/** + * Gets the origin of a cluster. + * + * @param cluster + * @returns A description of where the cluster is picked up from: dynamic, in-cluster, or from a kubeconfig file. + */ +function getOrigin(cluster: Cluster): string { + if (cluster.meta_data?.source === 'kubeconfig') { + const kubeconfigPath = process.env.KUBECONFIG ?? '~/.kube/config'; + return `Kubeconfig: ${kubeconfigPath}`; + } else if (cluster.meta_data?.source === 'dynamic_cluster') { + return t('translation|Plugin'); + } else if (cluster.meta_data?.source === 'in_cluster') { + return t('translation|In-cluster'); + } + return 'Unknown'; +} + function ContextMenu({ cluster }: { cluster: Cluster }) { const { t } = useTranslation(['translation']); const history = useHistory(); @@ -32,6 +50,7 @@ function ContextMenu({ cluster }: { cluster: Cluster }) { const [anchorEl, setAnchorEl] = React.useState(null); const menuId = useId('context-menu'); const [openConfirmDialog, setOpenConfirmDialog] = React.useState(false); + const [openDeleteDynamicDialog, setOpenDeleteDynamicDialog] = React.useState(false); function removeCluster(cluster: Cluster) { deleteCluster(cluster.name || '') @@ -92,7 +111,8 @@ function ContextMenu({ cluster }: { cluster: Cluster }) { > {t('translation|Settings')} - {helpers.isElectron() && cluster.meta_data?.source === 'dynamic_cluster' && ( + + {helpers.isElectron() && ( { setOpenConfirmDialog(true); @@ -108,17 +128,52 @@ function ContextMenu({ cluster }: { cluster: Cluster }) { open={openConfirmDialog} handleClose={() => setOpenConfirmDialog(false)} onConfirm={() => { - setOpenConfirmDialog(false); - removeCluster(cluster); + if (cluster.meta_data?.source !== 'dynamic_cluster') { + setOpenDeleteDynamicDialog(true); + } else { + setOpenConfirmDialog(false); + removeCluster(cluster); + } }} title={t('translation|Delete Cluster')} description={t( - 'translation|Are you sure you want to remove the cluster "{{ clusterName }}"?', + 'translation|Are you sure you want to remove the cluster "{{ clusterName }}"? from {{ source }}', { clusterName: cluster.name, + source: getOrigin(cluster), } )} /> + + setOpenDeleteDynamicDialog(false)} + onConfirm={() => { + setOpenDeleteDynamicDialog(false); + removeCluster(cluster); + }} + title={t('translation|Delete Cluster')} + description={ + <> + {t( + 'translation|The cluster "{{ clusterName }}" is not a dynamic cluster from Headlamp, this cluster will be deleted from {{ source }}.', + { + clusterName: cluster.name, + source: getOrigin(cluster), + } + )} + + <> + Accept + {}} + inputProps={{ 'aria-label': 'primary checkbox' }} + /> + + + } + /> ); } @@ -239,24 +294,6 @@ function HomeComponent(props: HomeComponentProps) { .sort(); } - /** - * Gets the origin of a cluster. - * - * @param cluster - * @returns A description of where the cluster is picked up from: dynamic, in-cluster, or from a kubeconfig file. - */ - function getOrigin(cluster: Cluster): string { - if (cluster.meta_data?.source === 'kubeconfig') { - const kubeconfigPath = process.env.KUBECONFIG ?? '~/.kube/config'; - return `Kubeconfig: ${kubeconfigPath}`; - } else if (cluster.meta_data?.source === 'dynamic_cluster') { - return t('translation|Plugin'); - } else if (cluster.meta_data?.source === 'in_cluster') { - return t('translation|In-cluster'); - } - return 'Unknown'; - } - const memoizedComponent = React.useMemo( () => ( diff --git a/frontend/src/components/common/ConfirmDialog.tsx b/frontend/src/components/common/ConfirmDialog.tsx index a503fd43d9..0a7317c15a 100644 --- a/frontend/src/components/common/ConfirmDialog.tsx +++ b/frontend/src/components/common/ConfirmDialog.tsx @@ -9,7 +9,7 @@ import { DialogTitle } from './Dialog'; export interface ConfirmDialogProps extends MuiDialogProps { title: string; - description: string; + description: string | JSX.Element; onConfirm: () => void; handleClose: () => void; }