diff --git a/app/e2e-tests/tests/clusterRename.spec.ts b/app/e2e-tests/tests/clusterRename.spec.ts
new file mode 100644
index 00000000000..f79f7e925f8
--- /dev/null
+++ b/app/e2e-tests/tests/clusterRename.spec.ts
@@ -0,0 +1,102 @@
+import { expect, test } from '@playwright/test';
+import path from 'path';
+import { _electron, Page } from 'playwright';
+import { HeadlampPage } from './headlampPage';
+
+// Electron setup
+const electronExecutable = process.platform === 'win32' ? 'electron.cmd' : 'electron';
+const electronPath = path.resolve(__dirname, `../../node_modules/.bin/${electronExecutable}`);
+const appPath = path.resolve(__dirname, '../../');
+let electronApp;
+let electronPage: Page;
+
+// Test configuration
+const TEST_CONFIG = {
+ originalName: 'minikube',
+ newName: 'test-cluster',
+ invalidName: 'Invalid Cluster!',
+};
+
+// Helper functions
+async function navigateToSettings(page: Page) {
+ await page.waitForLoadState('load');
+ await page.getByRole('button', { name: 'Settings' }).click();
+ await page.waitForLoadState('load');
+}
+
+async function verifyClusterName(page: Page, expectedName: string) {
+ await navigateToSettings(page);
+ await expect(page.locator('h2')).toContainText(`Cluster Settings (${expectedName})`);
+}
+
+async function renameCluster(
+ page: Page,
+ fromName: string,
+ toName: string,
+ confirm: boolean = true
+) {
+ await page.fill(`input[placeholder="${fromName}"]`, toName);
+ await page.getByRole('button', { name: 'Apply' }).click();
+ await page.getByRole('button', { name: confirm ? 'Yes' : 'No' }).click();
+}
+
+// Setup
+test.beforeAll(async () => {
+ electronApp = await _electron.launch({
+ cwd: appPath,
+ executablePath: electronPath,
+ args: ['.'],
+ env: {
+ ...process.env,
+ NODE_ENV: 'development',
+ ELECTRON_DEV: 'true',
+ },
+ });
+
+ electronPage = await electronApp.firstWindow();
+});
+
+test.beforeEach(async ({ page }) => {
+ page.close();
+});
+
+// Tests
+test.describe('Cluster rename functionality', () => {
+ test.beforeEach(() => {
+ test.skip(process.env.PLAYWRIGHT_TEST_MODE !== 'app', 'These tests only run in app mode');
+ });
+
+ test('should rename cluster and verify changes', async ({ page: browserPage }) => {
+ const page = process.env.PLAYWRIGHT_TEST_MODE === 'app' ? electronPage : browserPage;
+ const headlampPage = new HeadlampPage(page);
+ await headlampPage.authenticate();
+
+ await navigateToSettings(page);
+ await expect(page.locator('h2')).toContainText('Cluster Settings');
+
+ // Test invalid inputs
+ await page.fill('input[placeholder="minikube"]', TEST_CONFIG.invalidName);
+ await expect(page.getByRole('button', { name: 'Apply' })).toBeDisabled();
+
+ await page.fill('input[placeholder="minikube"]', '');
+ await expect(page.getByRole('button', { name: 'Apply' })).toBeDisabled();
+
+ // Test successful rename
+ await renameCluster(page, TEST_CONFIG.originalName, TEST_CONFIG.newName);
+ await verifyClusterName(page, TEST_CONFIG.newName);
+
+ // Rename back to original
+ await renameCluster(page, TEST_CONFIG.newName, TEST_CONFIG.originalName);
+ await verifyClusterName(page, TEST_CONFIG.originalName);
+ });
+
+ test('should cancel rename operation', async ({ page: browserPage }) => {
+ const page = process.env.PLAYWRIGHT_TEST_MODE === 'app' ? electronPage : browserPage;
+ const headlampPage = new HeadlampPage(page);
+ await headlampPage.authenticate();
+
+ await navigateToSettings(page);
+ await renameCluster(page, TEST_CONFIG.originalName, TEST_CONFIG.newName, false);
+ await expect(page.getByText(`Cluster Settings (${TEST_CONFIG.originalName})`)).toBeVisible();
+ });
+});
diff --git a/frontend/src/components/App/Settings/ClusterNameEditor.stories.tsx b/frontend/src/components/App/Settings/ClusterNameEditor.stories.tsx
new file mode 100644
index 00000000000..c7bdc0ad04d
--- /dev/null
+++ b/frontend/src/components/App/Settings/ClusterNameEditor.stories.tsx
@@ -0,0 +1,39 @@
+import { Meta, StoryObj } from '@storybook/react';
+import { ClusterNameEditor } from './ClusterNameEditor';
+
+const meta: Meta = {
+ title: 'Settings/ClusterNameEditor',
+ component: ClusterNameEditor,
+ parameters: {
+ layout: 'centered',
+ },
+};
+
+export default meta;
+type Story = StoryObj;
+
+export const Default: Story = {
+ args: {
+ cluster: 'my-cluster',
+ newClusterName: '',
+ isValidCurrentName: true,
+ source: 'dynamic_cluster',
+ onClusterNameChange: () => {},
+ onUpdateClusterName: () => {},
+ },
+};
+
+export const WithInvalidName: Story = {
+ args: {
+ ...Default.args,
+ newClusterName: 'Invalid Cluster Name',
+ isValidCurrentName: false,
+ },
+};
+
+export const WithNewName: Story = {
+ args: {
+ ...Default.args,
+ newClusterName: 'new-cluster-name',
+ },
+};
diff --git a/frontend/src/components/App/Settings/ClusterNameEditor.tsx b/frontend/src/components/App/Settings/ClusterNameEditor.tsx
new file mode 100644
index 00000000000..f2c3fe01a01
--- /dev/null
+++ b/frontend/src/components/App/Settings/ClusterNameEditor.tsx
@@ -0,0 +1,88 @@
+import { Box, TextField } from '@mui/material';
+import React from 'react';
+import { useTranslation } from 'react-i18next';
+import { ConfirmButton, NameValueTable } from '../../common';
+
+interface ClusterNameEditorProps {
+ cluster: string;
+ newClusterName: string;
+ isValidCurrentName: boolean;
+ source: string;
+ onClusterNameChange: (name: string) => void;
+ onUpdateClusterName: (source: string) => void;
+}
+
+export function ClusterNameEditor({
+ cluster,
+ newClusterName,
+ isValidCurrentName,
+ source,
+ onClusterNameChange,
+ onUpdateClusterName,
+}: ClusterNameEditorProps) {
+ const { t } = useTranslation(['translation']);
+
+ const invalidClusterNameMessage = t(
+ "translation|Cluster name must contain only lowercase alphanumeric characters or '-', and must start and end with an alphanumeric character."
+ );
+
+ const handleChange = (event: React.ChangeEvent) => {
+ const value = event.target.value.replace(' ', '');
+ onClusterNameChange(value);
+ };
+
+ const handleKeyPress = (event: React.KeyboardEvent) => {
+ if (event.key === 'Enter' && isValidCurrentName) {
+ onUpdateClusterName(source);
+ }
+ };
+
+ return (
+
+ {
+ if (isValidCurrentName) {
+ onUpdateClusterName(source);
+ }
+ }}
+ confirmTitle={t('translation|Change name')}
+ confirmDescription={t(
+ 'translation|Are you sure you want to change the name for "{{ clusterName }}"?',
+ { clusterName: cluster }
+ )}
+ disabled={!newClusterName || !isValidCurrentName}
+ >
+ {t('translation|Apply')}
+
+
+ ),
+ onKeyPress: handleKeyPress,
+ autoComplete: 'off',
+ sx: { maxWidth: 250 },
+ }}
+ />
+ ),
+ },
+ ]}
+ />
+ );
+}
diff --git a/frontend/src/components/App/Settings/SettingsCluster.tsx b/frontend/src/components/App/Settings/SettingsCluster.tsx
index 67eaafa6296..4095b2995b5 100644
--- a/frontend/src/components/App/Settings/SettingsCluster.tsx
+++ b/frontend/src/components/App/Settings/SettingsCluster.tsx
@@ -22,6 +22,7 @@ import { findKubeconfigByClusterName, updateStatelessClusterKubeconfig } from '.
import { Link, Loader, NameValueTable, SectionBox } from '../../common';
import ConfirmButton from '../../common/ConfirmButton';
import Empty from '../../common/EmptyContent';
+import { ClusterNameEditor } from './ClusterNameEditor';
function isValidNamespaceFormat(namespace: string) {
// We allow empty strings just because that's the default value in our case.
@@ -272,10 +273,6 @@ export default function SettingsCluster() {
"translation|Namespaces must contain only lowercase alphanumeric characters or '-', and must start and end with an alphanumeric character."
);
- const invalidClusterNameMessage = t(
- "translation|Cluster name must contain only lowercase alphanumeric characters or '-', and must start and end with an alphanumeric character."
- );
-
// If we don't have yet a cluster name from the URL, we are still loading.
if (!clusterFromURLRef.current) {
return ;
@@ -333,59 +330,13 @@ export default function SettingsCluster() {
{helpers.isElectron() && (
- {
- let value = event.target.value;
- value = value.replace(' ', '');
- setNewClusterName(value);
- }}
- value={newClusterName}
- placeholder={cluster}
- error={!isValidCurrentName}
- helperText={
- isValidCurrentName
- ? t(
- 'translation|The current name of cluster. You can define custom modified name.'
- )
- : invalidClusterNameMessage
- }
- InputProps={{
- endAdornment: (
-
- {
- if (isValidCurrentName) {
- handleUpdateClusterName(source);
- }
- }}
- confirmTitle={t('translation|Change name')}
- confirmDescription={t(
- 'translation|Are you sure you want to change the name for "{{ clusterName }}"?',
- { clusterName: cluster }
- )}
- disabled={!newClusterName || !isValidCurrentName}
- >
- {t('translation|Apply')}
-
-
- ),
- onKeyPress: event => {
- if (event.key === 'Enter' && isValidCurrentName) {
- handleUpdateClusterName(source);
- }
- },
- autoComplete: 'off',
- sx: { maxWidth: 250 },
- }}
- />
- ),
- },
- ]}
+
)}
+
+
+ -
+ Name
+
+ -
+
+
+
+ The current name of cluster. You can define custom modified name.
+
+
+
+
+
+
+
+
+ -
+ Name
+
+ -
+
+
+
+ Cluster name must contain only lowercase alphanumeric characters or '-', and must start and end with an alphanumeric character.
+
+
+
+
+
+
+
+
+ -
+ Name
+
+ -
+
+
+
+ The current name of cluster. You can define custom modified name.
+
+
+
+
+
+