diff --git a/charts/headlamp/templates/pvc.yaml b/charts/headlamp/templates/pvc.yaml
index c88b49d6d0..3c9ec9335d 100644
--- a/charts/headlamp/templates/pvc.yaml
+++ b/charts/headlamp/templates/pvc.yaml
@@ -17,7 +17,7 @@ spec:
{{- end}}
resources:
requests:
- storage: {{ .Values.persistentVolumeClaim.size }}
+ storage: {{ required "A valid .Values.persistentVolumeClaim.size entry required!" .Values.persistentVolumeClaim.size }}
{{- with .Values.persistentVolumeClaim.volumeMode }}
volumeMode: {{ . }}
{{- end }}
diff --git a/docs/platforms.md b/docs/platforms.md
index cc50505c34..044cffba06 100644
--- a/docs/platforms.md
+++ b/docs/platforms.md
@@ -24,7 +24,7 @@ The "works" column refers to the overall Kubernetes-related functionality when r
| [Microsoft AKS](https://azure.microsoft.com/) | ✔️ | - Working fine in-cluster and with the desktop application. |
| [Minikube](https://minikube.sigs.k8s.io/) | ✔️ | - For exposing with an ingress, enable ingresses with `minikube addons enable ingress`:
- There are docs about the [development](./development/index.md#minikube-in-cluster) with Minikube. |
| [Vultr Kubernetes Engine](https://www.vultr.com/kubernetes/) | ✔️ | - Simple to install / expose with the regular [in-cluster instructions](https://headlamp.dev/docs/latest/installation/in-cluster/). |
-
+| [Red Hat OpenShift](https://www.redhat.com/en/technologies/cloud-computing/openshift) | ✔️ | - Simple to install / expose with the regular [in-cluster instructions](https://headlamp.dev/docs/latest/installation/in-cluster/). |
## Tested Browsers
We mostly test with 'modern browsers' defined as the latest version and two older versions. But we try to make Headlamp work with web standards, so it's quite likely other standards-conforming browsers will also work.
diff --git a/frontend/src/components/common/ActionsNotifier.tsx b/frontend/src/components/common/ActionsNotifier.tsx
index 7a2505bdf5..afb258e6b6 100644
--- a/frontend/src/components/common/ActionsNotifier.tsx
+++ b/frontend/src/components/common/ActionsNotifier.tsx
@@ -51,16 +51,20 @@ function PureActionsNotifier({ dispatch, clusterActions }: PureActionsNotifierPr
}
const prevKey = snackbarRefs.current[clusterAction.id];
- const uniqueKey = clusterAction.key || clusterAction.id;
+ const uniqueKey = `${clusterAction.key || clusterAction.id}-${Date.now()}`;
if (prevKey && prevKey !== uniqueKey) {
closeSnackbar(prevKey);
}
if (clusterAction.message) {
- // Check for completed actions
+ // Check for success or error states
const refKey =
- clusterAction.state === 'complete' ? `${clusterAction.id}-complete` : clusterAction.id;
+ clusterAction.state === 'complete'
+ ? `${clusterAction.id}-complete`
+ : clusterAction.state === 'error'
+ ? `${clusterAction.id}-error`
+ : clusterAction.id;
if (!snackbarRefs.current[refKey]) {
snackbarRefs.current[refKey] = uniqueKey;
diff --git a/frontend/src/components/common/Resource/DeleteMultipleButton.tsx b/frontend/src/components/common/Resource/DeleteMultipleButton.tsx
index 1226f233b4..35c40dad4a 100644
--- a/frontend/src/components/common/Resource/DeleteMultipleButton.tsx
+++ b/frontend/src/components/common/Resource/DeleteMultipleButton.tsx
@@ -2,6 +2,7 @@ import _ from 'lodash';
import React from 'react';
import { useTranslation } from 'react-i18next';
import { useDispatch } from 'react-redux';
+import { useLocation } from 'react-router-dom';
import { KubeObject } from '../../../lib/k8s/KubeObject';
import { CallbackActionOptions, clusterAction } from '../../../redux/clusterActionSlice';
import {
@@ -43,6 +44,7 @@ export default function DeleteMultipleButton(props: DeleteMultipleButtonProps) {
const { items, options, afterConfirm, buttonStyle } = props;
const [openAlert, setOpenAlert] = React.useState(false);
const { t } = useTranslation(['translation']);
+ const location = useLocation();
const dispatchDeleteEvent = useEventCallback(HeadlampEventType.DELETE_RESOURCES);
const deleteFunc = React.useCallback(
diff --git a/frontend/src/lib/k8s/api/v2/useKubeObjectList.test.tsx b/frontend/src/lib/k8s/api/v2/useKubeObjectList.test.tsx
index 5b8957ad56..4018f1a533 100644
--- a/frontend/src/lib/k8s/api/v2/useKubeObjectList.test.tsx
+++ b/frontend/src/lib/k8s/api/v2/useKubeObjectList.test.tsx
@@ -4,6 +4,7 @@ import {
kubeObjectListQuery,
ListResponse,
makeListRequests,
+ useKubeObjectList,
useWatchKubeObjectLists,
} from './useKubeObjectList';
import * as websocket from './webSocket';
@@ -81,6 +82,16 @@ const mockClass = class {
static apiVersion = 'v1';
static apiName = 'pods';
+ static apiEndpoint = {
+ apiInfo: [
+ {
+ group: '',
+ resource: 'pods',
+ version: 'v1',
+ },
+ ],
+ };
+
constructor(public jsonData: any) {}
} as any;
@@ -221,3 +232,42 @@ describe('useWatchKubeObjectLists', () => {
).toBe(objectB);
});
});
+
+describe('useKubeObjectList', () => {
+ it('should call useKubeObjectList with 1 namespace after reducing amount of namespaces', async () => {
+ const spy = vi.spyOn(websocket, 'useWebSockets');
+ const queryClient = new QueryClient();
+
+ queryClient.setQueryData(['kubeObject', 'list', 'v1', 'pods', 'default', 'a', {}], {
+ list: { items: [], metadata: { resourceVersion: '0' } },
+ cluster: 'default',
+ namespace: 'a',
+ });
+ queryClient.setQueryData(['kubeObject', 'list', 'v1', 'pods', 'default', 'b', {}], {
+ list: { items: [], metadata: { resourceVersion: '0' } },
+ cluster: 'default',
+ namespace: 'b',
+ });
+
+ const result = renderHook(
+ (props: {}) =>
+ useKubeObjectList({
+ kubeObjectClass: mockClass,
+ requests: [{ cluster: 'default', namespaces: ['a', 'b'] }],
+ ...props,
+ }),
+ {
+ wrapper: ({ children }) => (
+ {children}
+ ),
+ }
+ );
+
+ result.rerender({ requests: [{ cluster: 'default', namespaces: ['a'] }] });
+
+ expect(spy.mock.calls[0][0].connections.length).toBe(0); // initial render
+ expect(spy.mock.calls[1][0].connections.length).toBe(2); // new connections with 'a' and 'b' namespaces
+ expect(spy.mock.calls[2][0].connections.length).toBe(2); // rerender with new props
+ expect(spy.mock.calls[3][0].connections.length).toBe(1); // updated connections after we removed namespace 'b'
+ });
+});
diff --git a/frontend/src/lib/k8s/api/v2/useKubeObjectList.ts b/frontend/src/lib/k8s/api/v2/useKubeObjectList.ts
index 4e11f8033f..9de0a6f737 100644
--- a/frontend/src/lib/k8s/api/v2/useKubeObjectList.ts
+++ b/frontend/src/lib/k8s/api/v2/useKubeObjectList.ts
@@ -306,6 +306,19 @@ export function useKubeObjectList({
setListsToWatch([...listsToWatch, ...listsNotYetWatched]);
}
+ const listsToStopWatching = listsToWatch.filter(
+ watching =>
+ requests.find(request =>
+ watching.cluster === request?.cluster && request.namespaces && watching.namespace
+ ? request.namespaces?.includes(watching.namespace)
+ : true
+ ) === undefined
+ );
+
+ if (listsToStopWatching.length > 0) {
+ setListsToWatch(listsToWatch.filter(it => !listsToStopWatching.includes(it)));
+ }
+
useWatchKubeObjectLists({
lists: shouldWatch ? listsToWatch : [],
endpoint,