Skip to content

Commit

Permalink
app: home: Add delete button for clusters
Browse files Browse the repository at this point in the history
Signed-off-by: Vincent T <[email protected]>
  • Loading branch information
vyncent-t committed Nov 27, 2024
1 parent 82eb285 commit a248958
Show file tree
Hide file tree
Showing 9 changed files with 145 additions and 48 deletions.
34 changes: 32 additions & 2 deletions backend/cmd/headlamp.go
Original file line number Diff line number Diff line change
Expand Up @@ -234,6 +234,17 @@ func serveWithNoCacheHeader(fs http.Handler) http.HandlerFunc {
}
}

// defaultKubeConfigFile returns the default path to the kubeconfig file.
func defaultKubeConfigFile() (string, error) {
homeDir, err := os.UserHomeDir()

Check failure on line 239 in backend/cmd/headlamp.go

View workflow job for this annotation

GitHub Actions / build

File is not `gofmt`-ed with `-s` (gofmt)
if err != nil {
return "", fmt.Errorf("failed to get user home directory: %v", err)
}

kubeConfigFile := filepath.Join(homeDir, ".kube", "config")
return kubeConfigFile, nil

Check failure on line 245 in backend/cmd/headlamp.go

View workflow job for this annotation

GitHub Actions / build

return statements should not be cuddled if block has more than two lines (wsl)
}

// defaultKubeConfigPersistenceDir returns the default directory to store kubeconfig
// files of clusters that are loaded in Headlamp.
func defaultKubeConfigPersistenceDir() (string, error) {
Expand Down Expand Up @@ -1384,6 +1395,26 @@ func (c *HeadlampConfig) deleteCluster(w http.ResponseWriter, r *http.Request) {
return
}

removeKubeConfig := r.URL.Query().Get("removeKubeConfig") == "true"

if removeKubeConfig {
// delete context from actual deafult kubecofig file

Check failure on line 1401 in backend/cmd/headlamp.go

View workflow job for this annotation

GitHub Actions / build

`deafult` is a misspelling of `default` (misspell)
kubeConfigFile, err := defaultKubeConfigFile()
if err != nil {
logger.Log(logger.LevelError, map[string]string{"cluster": name}, err, "failed to get default kubeconfig file path")

Check failure on line 1404 in backend/cmd/headlamp.go

View workflow job for this annotation

GitHub Actions / build

line is 124 characters (lll)
http.Error(w, "failed to get default kubeconfig file path", http.StatusInternalServerError)
return

Check failure on line 1406 in backend/cmd/headlamp.go

View workflow job for this annotation

GitHub Actions / build

return statements should not be cuddled if block has more than two lines (wsl)
}

// Use kubeConfigFile to remove the context from the default kubeconfig file
err = kubeconfig.RemoveContextFromFile(name, kubeConfigFile)
if err != nil {
logger.Log(logger.LevelError, map[string]string{"cluster": name}, err, "removing context from default kubeconfig file")

Check failure on line 1412 in backend/cmd/headlamp.go

View workflow job for this annotation

GitHub Actions / build

line is 127 characters (lll)
http.Error(w, "removing context from default kubeconfig file", http.StatusInternalServerError)
return

Check failure on line 1414 in backend/cmd/headlamp.go

View workflow job for this annotation

GitHub Actions / build

return statements should not be cuddled if block has more than two lines (wsl)
}
}

kubeConfigPersistenceFile, err := defaultKubeConfigPersistenceFile()
if err != nil {
logger.Log(logger.LevelError, map[string]string{"cluster": name},
Expand All @@ -1396,8 +1427,7 @@ func (c *HeadlampConfig) deleteCluster(w http.ResponseWriter, r *http.Request) {
logger.Log(logger.LevelInfo, map[string]string{
"cluster": name,
"kubeConfigPersistenceFile": kubeConfigPersistenceFile,
},
nil, "Removing cluster from kubeconfig")
}, nil, "Removing cluster from kubeconfig")

err = kubeconfig.RemoveContextFromFile(name, kubeConfigPersistenceFile)
if err != nil {
Expand Down
55 changes: 32 additions & 23 deletions frontend/src/components/App/Home/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand All @@ -33,8 +51,8 @@ function ContextMenu({ cluster }: { cluster: Cluster }) {
const menuId = useId('context-menu');
const [openConfirmDialog, setOpenConfirmDialog] = React.useState(false);

function removeCluster(cluster: Cluster) {
deleteCluster(cluster.name || '')
function removeCluster(cluster: Cluster, removeKubeconfig?: boolean) {
deleteCluster(cluster.name || '', removeKubeconfig)
.then(config => {
dispatch(setConfig(config));
})
Expand Down Expand Up @@ -92,7 +110,8 @@ function ContextMenu({ cluster }: { cluster: Cluster }) {
>
<ListItemText>{t('translation|Settings')}</ListItemText>
</MenuItem>
{helpers.isElectron() && cluster.meta_data?.source === 'dynamic_cluster' && (

{helpers.isElectron() && (
<MenuItem
onClick={() => {
setOpenConfirmDialog(true);
Expand All @@ -109,15 +128,23 @@ function ContextMenu({ cluster }: { cluster: Cluster }) {
handleClose={() => setOpenConfirmDialog(false)}
onConfirm={() => {
setOpenConfirmDialog(false);
removeCluster(cluster);
if (cluster.meta_data?.source !== 'dynamic_cluster') {
removeCluster(cluster, true);
} else {
removeCluster(cluster);
}
}}
title={t('translation|Delete Cluster')}
description={t(
'translation|Are you sure you want to remove the cluster "{{ clusterName }}"?',
'translation|This action will delete cluster "{{ clusterName }}" from {{ source }}.',
{
clusterName: cluster.name,
source: getOrigin(cluster),
}
)}
checkboxDescription={
cluster.meta_data?.source !== 'dynamic_cluster' ? t('Delete from kubeconfig') : ''
}
/>
</>
);
Expand Down Expand Up @@ -239,24 +266,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(
() => (
<PageGrid>
Expand Down
45 changes: 44 additions & 1 deletion frontend/src/components/common/ConfirmDialog.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { Checkbox } from '@mui/material';
import Box from '@mui/material/Box';
import Button from '@mui/material/Button';
import MuiDialog, { DialogProps as MuiDialogProps } from '@mui/material/Dialog';
import DialogActions from '@mui/material/DialogActions';
Expand All @@ -9,7 +11,8 @@ import { DialogTitle } from './Dialog';

export interface ConfirmDialogProps extends MuiDialogProps {
title: string;
description: string;
description: string | JSX.Element;
checkboxDescription?: string;
onConfirm: () => void;
handleClose: () => void;
}
Expand All @@ -18,6 +21,8 @@ export function ConfirmDialog(props: ConfirmDialogProps) {
const { onConfirm, open, handleClose, title, description } = props;
const { t } = useTranslation();

const [checkboxClicked, setCheckboxClicked] = React.useState(false);

function onConfirmationClicked() {
handleClose();
onConfirm();
Expand All @@ -30,6 +35,44 @@ export function ConfirmDialog(props: ConfirmDialogProps) {
}
}, []);

if (props.checkboxDescription) {
return (
<div>
<MuiDialog
open={open}
onClose={handleClose}
aria-labelledby="alert-dialog-title"
aria-describedby="alert-dialog-description"
>
<DialogTitle id="alert-dialog-title">{title}</DialogTitle>
<DialogContent ref={focusedRef}>
<DialogContentText id="alert-dialog-description">{description}</DialogContentText>
<Box
sx={{
display: 'flex',
alignItems: 'center',
marginTop: '10px',
}}
>
<DialogContentText id="alert-dialog-description">
{props.checkboxDescription}
</DialogContentText>
<Checkbox onChange={() => setCheckboxClicked(!checkboxClicked)} />
</Box>
</DialogContent>
<DialogActions>
<Button onClick={handleClose} color="primary">
{t('Cancel')}
</Button>
<Button disabled={!checkboxClicked} onClick={onConfirmationClicked} color="primary">
{t('I Agree')}
</Button>
</DialogActions>
</MuiDialog>
</div>
);
}

return (
<div>
<MuiDialog
Expand Down
9 changes: 6 additions & 3 deletions frontend/src/i18n/locales/de/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,16 @@
"Cancel": "Abbrechen",
"Authenticate": "Authentifizieren Sie",
"Error authenticating": "Fehler beim Authentifizieren",
"Plugin": "",
"In-cluster": "",
"Actions": "Aktionen",
"View": "Ansicht",
"Settings": "Einstellungen",
"Delete": "Löschen",
"Delete Cluster": "",
"Are you sure you want to remove the cluster \"{{ clusterName }}\"?": "Sind Sie sicher, dass Sie den Cluster \"{{ clusterName }}\" entfernen möchten?",
"This action will delete cluster \"{{ clusterName }}\" from {{ source }}.": "",
"Delete from kubeconfig": "",
"Active": "Aktiv",
"Plugin": "",
"In-cluster": "",
"Home": "Startseite",
"All Clusters": "Alle Cluster",
"Name": "Name",
Expand Down Expand Up @@ -81,6 +82,7 @@
"The list of namespaces you are allowed to access in this cluster.": "Liste der Namespaces, auf die Sie in diesem Cluster zugreifen dürfen.",
"Add namespace": "",
"Remove Cluster": "Cluster entfernen",
"Are you sure you want to remove the cluster \"{{ clusterName }}\"?": "Sind Sie sicher, dass Sie den Cluster \"{{ clusterName }}\" entfernen möchten?",
"Server": "Server",
"light theme": "helles Design",
"dark theme": "dunkles Design",
Expand Down Expand Up @@ -144,6 +146,7 @@
"Last Seen": "Zuletzt gesehen",
"Offline": "Offline",
"Lost connection to the cluster.": "",
"I Agree": "",
"No": "Nein",
"Yes": "Ja",
"Toggle fullscreen": "Vollbild ein/aus",
Expand Down
9 changes: 6 additions & 3 deletions frontend/src/i18n/locales/en/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,16 @@
"Cancel": "Cancel",
"Authenticate": "Authenticate",
"Error authenticating": "Error authenticating",
"Plugin": "Plugin",
"In-cluster": "In-cluster",
"Actions": "Actions",
"View": "View",
"Settings": "Settings",
"Delete": "Delete",
"Delete Cluster": "Delete Cluster",
"Are you sure you want to remove the cluster \"{{ clusterName }}\"?": "Are you sure you want to remove the cluster \"{{ clusterName }}\"?",
"This action will delete cluster \"{{ clusterName }}\" from {{ source }}.": "This action will delete cluster \"{{ clusterName }}\" from {{ source }}.",
"Delete from kubeconfig": "Delete from kubeconfig",
"Active": "Active",
"Plugin": "Plugin",
"In-cluster": "In-cluster",
"Home": "Home",
"All Clusters": "All Clusters",
"Name": "Name",
Expand Down Expand Up @@ -81,6 +82,7 @@
"The list of namespaces you are allowed to access in this cluster.": "The list of namespaces you are allowed to access in this cluster.",
"Add namespace": "Add namespace",
"Remove Cluster": "Remove Cluster",
"Are you sure you want to remove the cluster \"{{ clusterName }}\"?": "Are you sure you want to remove the cluster \"{{ clusterName }}\"?",
"Server": "Server",
"light theme": "light theme",
"dark theme": "dark theme",
Expand Down Expand Up @@ -144,6 +146,7 @@
"Last Seen": "Last Seen",
"Offline": "Offline",
"Lost connection to the cluster.": "Lost connection to the cluster.",
"I Agree": "I Agree",
"No": "No",
"Yes": "Yes",
"Toggle fullscreen": "Toggle fullscreen",
Expand Down
9 changes: 6 additions & 3 deletions frontend/src/i18n/locales/es/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,16 @@
"Cancel": "Cancelar",
"Authenticate": "Autenticar",
"Error authenticating": "Error al autenticarse",
"Plugin": "",
"In-cluster": "",
"Actions": "Acciones",
"View": "Ver",
"Settings": "Definiciones",
"Delete": "Borrar",
"Delete Cluster": "",
"Are you sure you want to remove the cluster \"{{ clusterName }}\"?": "¿Está seguro de que desea eliminar el cluster \"{{ clusterName }}\"?",
"This action will delete cluster \"{{ clusterName }}\" from {{ source }}.": "",
"Delete from kubeconfig": "",
"Active": "Activo",
"Plugin": "",
"In-cluster": "",
"Home": "Inicio",
"All Clusters": "Todos los Clusters",
"Name": "Nombre",
Expand Down Expand Up @@ -81,6 +82,7 @@
"The list of namespaces you are allowed to access in this cluster.": "La lista de espacios de nombre a los que tiene permiso para acceder en este cluster.",
"Add namespace": "",
"Remove Cluster": "Eliminar cluster",
"Are you sure you want to remove the cluster \"{{ clusterName }}\"?": "¿Está seguro de que desea eliminar el cluster \"{{ clusterName }}\"?",
"Server": "Servidor",
"light theme": "tema claro",
"dark theme": "tema oscuro",
Expand Down Expand Up @@ -144,6 +146,7 @@
"Last Seen": "Últi. ocurrencia",
"Offline": "Desconectado",
"Lost connection to the cluster.": "",
"I Agree": "",
"No": "No",
"Yes": "",
"Toggle fullscreen": "Alternar pantalla completa",
Expand Down
9 changes: 6 additions & 3 deletions frontend/src/i18n/locales/fr/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,16 @@
"Cancel": "Cancel",
"Authenticate": "Authentifier",
"Error authenticating": "Erreur d'authentification",
"Plugin": "",
"In-cluster": "",
"Actions": "Actions",
"View": "Vue",
"Settings": "Paramètres",
"Delete": "Supprimer",
"Delete Cluster": "",
"Are you sure you want to remove the cluster \"{{ clusterName }}\"?": "Voulez-vous vraiment supprimer le cluster \"{{ clusterName }}\"?",
"This action will delete cluster \"{{ clusterName }}\" from {{ source }}.": "",
"Delete from kubeconfig": "",
"Active": "Actif",
"Plugin": "",
"In-cluster": "",
"Home": "Accueil",
"All Clusters": "Tous les clusters",
"Name": "Nom",
Expand Down Expand Up @@ -81,6 +82,7 @@
"The list of namespaces you are allowed to access in this cluster.": "La liste des espaces de noms que vous pouvez accéder dans ce cluster.",
"Add namespace": "",
"Remove Cluster": "Supprimer le cluster",
"Are you sure you want to remove the cluster \"{{ clusterName }}\"?": "Voulez-vous vraiment supprimer le cluster \"{{ clusterName }}\"?",
"Server": "Serveur",
"light theme": "thème clair",
"dark theme": "thème sombre",
Expand Down Expand Up @@ -144,6 +146,7 @@
"Last Seen": "Dernière vue",
"Offline": "Hors ligne",
"Lost connection to the cluster.": "",
"I Agree": "",
"No": "Non",
"Yes": "Oui",
"Toggle fullscreen": "Basculer en mode plein écran",
Expand Down
Loading

0 comments on commit a248958

Please sign in to comment.