@kinvolk/headlamp-plugin
Version:
The needed infrastructure for building Headlamp plugins.
176 lines (175 loc) • 10.1 kB
JavaScript
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
/*
* Copyright 2025 The Kubernetes Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { Box, TextField, Typography } from '@mui/material';
import React from 'react';
import { useTranslation } from 'react-i18next';
import { useDispatch } from 'react-redux';
import { useHistory } from 'react-router-dom';
import { parseKubeConfig, renameCluster } from '../../../lib/k8s/api/v1/clusterApi';
import { setConfig, setStatelessConfig } from '../../../redux/configSlice';
import { findKubeconfigByClusterName } from '../../../stateless/findKubeconfigByClusterName';
import { updateStatelessClusterKubeconfig } from '../../../stateless/updateStatelessClusterKubeconfig';
import { ConfirmButton, ConfirmDialog, NameValueTable } from '../../common';
import { isValidClusterNameFormat } from './util';
export function ClusterNameEditor({ cluster, clusterConf, clusterSettings, setClusterSettings, }) {
const { t } = useTranslation(['translation']);
const [customNameInUse, setCustomNameInUse] = React.useState(false);
const [clusterErrorDialogOpen, setClusterErrorDialogOpen] = React.useState(false);
const [clusterErrorDialogMessage, setClusterErrorDialogMessage] = React.useState('');
const [newClusterName, setNewClusterName] = React.useState(cluster || '');
const dispatch = useDispatch();
const history = useHistory();
React.useEffect(() => {
if (clusterSettings?.currentName !== cluster) {
setNewClusterName(clusterSettings?.currentName || '');
}
}, [cluster, clusterSettings]);
const clusterInfo = (clusterConf && clusterConf[cluster || '']) || null;
const source = clusterInfo?.meta_data?.source;
const originalName = clusterInfo?.meta_data?.originalName;
const displayName = originalName || (clusterInfo ? clusterInfo.name : '');
/** Note: display original name is currently only supported for non dynamic clusters from kubeconfig sources. */
const clusterID = clusterInfo?.meta_data?.clusterID || '';
const invalidClusterNameMessage = t("translation|Cluster name must contain only lowercase alphanumeric characters or '-', and must start and end with an alphanumeric character.");
/**
* This function is part of a double check, this is meant to check all the cluster names currently in use as display names
* Note: if the metadata is not available or does not load, another check is done in the backend to ensure the name is unique in its own config
*
* @param name The name to check.
* @returns bool of if the name is in use.
*/
function checkNameInUse(name) {
if (!clusterConf) {
return false;
}
/** These are the display names of the clusters, renamed clusters have their display name as the custom name */
const clusterNames = Object.values(clusterConf).map(cluster => cluster.name);
/** The original name of the cluster is the name used in the kubeconfig file. */
const originalNames = Object.values(clusterConf)
.map(cluster => cluster.meta_data?.originalName)
.filter(originalName => originalName !== undefined);
const allNames = [...clusterNames, ...originalNames];
const nameInUse = allNames.includes(name);
setCustomNameInUse(nameInUse);
}
function ClusterErrorDialog() {
return (_jsx(ConfirmDialog, { onConfirm: () => {
setClusterErrorDialogOpen(false);
}, handleClose: () => {
setClusterErrorDialogOpen(false);
}, hideCancelButton: true, open: clusterErrorDialogOpen, title: t('translation|Error'), description: clusterErrorDialogMessage, confirmLabel: t('translation|Okay') }));
}
// Display the original name of the cluster if it was loaded from a kubeconfig file.
function ClusterName() {
const currentName = clusterInfo?.name;
const originalName = clusterInfo?.meta_data?.originalName;
const source = clusterInfo?.meta_data?.source;
// Note: display original name is currently only supported for non dynamic clusters from kubeconfig sources.
const displayOriginalName = source === 'kubeconfig' && originalName;
return (_jsxs(_Fragment, { children: [clusterErrorDialogOpen && _jsx(ClusterErrorDialog, {}), _jsx(Typography, { children: t('translation|Name') }), _jsx("div", { children: displayOriginalName && currentName !== displayOriginalName && (_jsx(Typography, { id: "cluster-original-name", variant: "body2", color: "textSecondary", children: t('translation|Original name: {{ displayName }}', {
displayName: displayName,
}) })) })] }));
}
function storeNewClusterName(name) {
let actualName = name;
if (name === cluster) {
actualName = '';
setNewClusterName(actualName);
}
setClusterSettings((settings) => {
const newSettings = { ...(settings || {}) };
if (isValidClusterNameFormat(name)) {
newSettings.currentName = actualName;
}
return newSettings;
});
}
const handleUpdateClusterName = (source) => {
try {
renameCluster(cluster || '', newClusterName, source, clusterID)
.then(async (config) => {
if (cluster) {
const kubeconfig = await findKubeconfigByClusterName(cluster, clusterID);
if (kubeconfig !== null) {
await updateStatelessClusterKubeconfig(kubeconfig, newClusterName, cluster);
// Make another request for updated kubeconfig
const updatedKubeconfig = await findKubeconfigByClusterName(cluster, clusterID);
if (updatedKubeconfig !== null) {
parseKubeConfig({ kubeconfig: updatedKubeconfig })
.then((config) => {
storeNewClusterName(newClusterName);
dispatch(setStatelessConfig(config));
})
.catch((err) => {
console.error('Error updating cluster name:', err.message);
});
}
}
else {
dispatch(setConfig(config));
}
}
history.push('/');
window.location.reload();
})
.catch((err) => {
console.error('Error updating cluster name:', err.message);
setClusterErrorDialogMessage(err.message);
setClusterErrorDialogOpen(true);
});
}
catch (error) {
console.error('Error updating cluster name:', error);
}
};
const isValidCurrentName = isValidClusterNameFormat(newClusterName);
const hasOriginalName = source === 'kubeconfig' && originalName && clusterInfo?.name !== originalName;
const clusterNameLabelID = 'cluster-name-label';
const clusterOriginalNameID = 'cluster-original-name';
return (_jsx(NameValueTable, { rows: [
{
// eslint-disable-next-line react-hooks/static-components
name: _jsx(ClusterName, {}),
nameID: clusterNameLabelID,
value: (_jsx(TextField, { onChange: event => {
let value = event.target.value;
value = value.replace(' ', '');
setNewClusterName(value);
checkNameInUse(value);
}, value: newClusterName, placeholder: cluster, error: !isValidCurrentName || customNameInUse, helperText: _jsxs(Typography, { children: [!isValidCurrentName && invalidClusterNameMessage, customNameInUse &&
t('translation|This custom name is already in use, please choose a different name.'), isValidCurrentName &&
!customNameInUse &&
t('translation|The current name of the cluster. You can define a custom name')] }), inputProps: {
'aria-labelledby': clusterNameLabelID,
'aria-describedby': hasOriginalName ? clusterOriginalNameID : undefined,
}, InputProps: {
endAdornment: (_jsx(Box, { display: "flex", alignItems: "center", children: _jsx(ConfirmButton, { onConfirm: () => {
if (isValidCurrentName) {
handleUpdateClusterName(source);
}
}, confirmTitle: t('translation|Change name'), confirmDescription: t('translation|Are you sure you want to change the name for "{{ clusterName }}"?', { clusterName: displayName }), disabled: !newClusterName || !isValidCurrentName || customNameInUse, children: t('translation|Apply') }) })),
onKeyPress: event => {
if (event.key === 'Enter' && isValidCurrentName) {
handleUpdateClusterName(source);
}
},
autoComplete: 'off',
sx: { maxWidth: 250 },
} })),
},
] }));
}