Skip to content

Commit 2c56c90

Browse files
committed
frontend: Enable cluster deletion in browser
Enable cluster deletion when running in a browser if dynamic cluster management is enabled in the backend. This was previously restricted to the Electron app.
1 parent d36a68d commit 2c56c90

File tree

5 files changed

+54
-3
lines changed

5 files changed

+54
-3
lines changed

frontend/src/components/App/Home/ClusterContextMenu.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ export default function ClusterContextMenu({ cluster }: ClusterContextMenuProps)
5252
const [openConfirmDialog, setOpenConfirmDialog] = React.useState<string | null>(null);
5353
const dialogs = useTypedSelector(state => state.clusterProvider.dialogs);
5454
const menuItems = useTypedSelector(state => state.clusterProvider.menuItems);
55+
const isDynamicClusterEnabled = useTypedSelector(state => state.config.isDynamicClusterEnabled);
5556

5657
const kubeconfigOrigin = cluster.meta_data?.origin?.kubeconfig;
5758
const deleteFromKubeconfig = cluster.meta_data?.source === 'kubeconfig';
@@ -154,7 +155,7 @@ export default function ClusterContextMenu({ cluster }: ClusterContextMenuProps)
154155
<ListItemText>{t('translation|Settings')}</ListItemText>
155156
</MenuItem>
156157
{(!menuItems || menuItems.length === 0) &&
157-
helpers.isElectron() &&
158+
(helpers.isElectron() || isDynamicClusterEnabled) &&
158159
(cluster.meta_data?.source === 'dynamic_cluster' ||
159160
cluster.meta_data?.source === 'kubeconfig') && (
160161
<MenuItem

frontend/src/components/project/ProjectCreateFromYaml.stories.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ const makeStore = () => {
4040
'cluster-a': { name: 'cluster-a' },
4141
'cluster-b': { name: 'cluster-b' },
4242
} as any,
43+
isDynamicClusterEnabled: true,
4344
settings: {
4445
tableRowsPerPageOptions: [15, 25, 50],
4546
timezone: 'UTC',

frontend/src/helpers/getHeadlampAPIHeaders.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,12 @@
2121
*
2222
* The app also sets HEADLAMP_BACKEND_TOKEN in the headlamp-server environment,
2323
* which the server checks to validate requests containing this same token.
24+
*
25+
* For development, when running the frontend separately from the backend,
26+
* the token can be initialized from the REACT_APP_HEADLAMP_BACKEND_TOKEN
27+
* environment variable to authenticate API requests.
2428
*/
25-
let backendToken: string | null = null;
29+
let backendToken: string | null = import.meta.env.REACT_APP_HEADLAMP_BACKEND_TOKEN || null;
2630

2731
/**
2832
* Sets the backend token to use when making API calls from Headlamp when running as an app.

frontend/src/redux/configSlice.test.ts

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,36 @@ describe('configSlice', () => {
3838
expect(nextState.clusters).toEqual(clusters);
3939
});
4040

41+
it('should handle setConfig with isDynamicClusterEnabled', () => {
42+
const clusters: ConfigState['clusters'] = {
43+
'cluster-1': { name: 'cluster-1' } as Cluster,
44+
};
45+
const nextState = configReducer(
46+
initialState,
47+
setConfig({ clusters, isDynamicClusterEnabled: true })
48+
);
49+
expect(nextState.clusters).toEqual(clusters);
50+
expect(nextState.isDynamicClusterEnabled).toBe(true);
51+
});
52+
53+
it('should preserve isDynamicClusterEnabled when setConfig is called without it', () => {
54+
let state = configReducer(
55+
initialState,
56+
setConfig({ clusters: {}, isDynamicClusterEnabled: true })
57+
);
58+
59+
expect(state.isDynamicClusterEnabled).toBe(true);
60+
61+
const newClusters: ConfigState['clusters'] = {
62+
'cluster-1': { name: 'cluster-1' } as Cluster,
63+
};
64+
65+
state = configReducer(state, setConfig({ clusters: newClusters }));
66+
67+
expect(state.clusters).toEqual(newClusters);
68+
expect(state.isDynamicClusterEnabled).toBe(true);
69+
});
70+
4171
it('should handle setStatelessConfig', () => {
4272
const statelessClusters: ConfigState['statelessClusters'] = {
4373
'stateless-1': { name: 'stateless-1' } as Cluster,

frontend/src/redux/configSlice.ts

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,11 @@ export interface ConfigState {
4242
allClusters: {
4343
[clusterName: string]: Cluster;
4444
} | null;
45+
/**
46+
* Whether dynamic clusters are enabled.
47+
* When true, users can add and delete clusters dynamically.
48+
*/
49+
isDynamicClusterEnabled: boolean;
4550
/**
4651
* Settings is a map of settings names to settings values.
4752
*/
@@ -71,6 +76,7 @@ export const initialState: ConfigState = {
7176
clusters: null,
7277
statelessClusters: null,
7378
allClusters: null,
79+
isDynamicClusterEnabled: false,
7480
settings: {
7581
tableRowsPerPageOptions:
7682
storedSettings.tableRowsPerPageOptions || defaultTableRowsPerPageOptions,
@@ -89,8 +95,17 @@ const configSlice = createSlice({
8995
* @param state - The current state.
9096
* @param action - The payload action containing the config.
9197
*/
92-
setConfig(state, action: PayloadAction<{ clusters: ConfigState['clusters'] }>) {
98+
setConfig(
99+
state,
100+
action: PayloadAction<{
101+
clusters: ConfigState['clusters'];
102+
isDynamicClusterEnabled?: boolean;
103+
}>
104+
) {
93105
state.clusters = action.payload.clusters;
106+
if (action.payload.isDynamicClusterEnabled !== undefined) {
107+
state.isDynamicClusterEnabled = action.payload.isDynamicClusterEnabled;
108+
}
94109
},
95110
/**
96111
* Save the config. To both the store, and localStorage.

0 commit comments

Comments
 (0)