mirror of https://github.com/portainer/portainer
				
				
				
			fix(kubernetes): events api to call the backend [R8S-243] (#563)
							parent
							
								
									32ef208278
								
							
						
					
					
						commit
						07dfd981a2
					
				| 
						 | 
					@ -30,8 +30,8 @@ func (handler *Handler) prepareKubeClient(r *http.Request) (*cli.KubeClient, *ht
 | 
				
			||||||
		log.Error().Err(err).Str("context", "prepareKubeClient").Msg("Unable to get a privileged Kubernetes client for the user.")
 | 
							log.Error().Err(err).Str("context", "prepareKubeClient").Msg("Unable to get a privileged Kubernetes client for the user.")
 | 
				
			||||||
		return nil, httperror.InternalServerError("Unable to get a privileged Kubernetes client for the user.", err)
 | 
							return nil, httperror.InternalServerError("Unable to get a privileged Kubernetes client for the user.", err)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	pcli.IsKubeAdmin = cli.IsKubeAdmin
 | 
						pcli.SetIsKubeAdmin(cli.GetIsKubeAdmin())
 | 
				
			||||||
	pcli.NonAdminNamespaces = cli.NonAdminNamespaces
 | 
						pcli.SetClientNonAdminNamespaces(cli.GetClientNonAdminNamespaces())
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	return pcli, nil
 | 
						return pcli, nil
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -32,7 +32,7 @@ func (handler *Handler) getAllKubernetesClusterRoleBindings(w http.ResponseWrite
 | 
				
			||||||
		return httperror.Forbidden("User is not authorized to fetch cluster role bindings from the Kubernetes cluster.", httpErr)
 | 
							return httperror.Forbidden("User is not authorized to fetch cluster role bindings from the Kubernetes cluster.", httpErr)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if !cli.IsKubeAdmin {
 | 
						if !cli.GetIsKubeAdmin() {
 | 
				
			||||||
		log.Error().Str("context", "getAllKubernetesClusterRoleBindings").Msg("user is not authorized to fetch cluster role bindings from the Kubernetes cluster.")
 | 
							log.Error().Str("context", "getAllKubernetesClusterRoleBindings").Msg("user is not authorized to fetch cluster role bindings from the Kubernetes cluster.")
 | 
				
			||||||
		return httperror.Forbidden("User is not authorized to fetch cluster role bindings from the Kubernetes cluster.", nil)
 | 
							return httperror.Forbidden("User is not authorized to fetch cluster role bindings from the Kubernetes cluster.", nil)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -32,7 +32,7 @@ func (handler *Handler) getAllKubernetesClusterRoles(w http.ResponseWriter, r *h
 | 
				
			||||||
		return httperror.Forbidden("User is not authorized to fetch cluster roles from the Kubernetes cluster.", httpErr)
 | 
							return httperror.Forbidden("User is not authorized to fetch cluster roles from the Kubernetes cluster.", httpErr)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if !cli.IsKubeAdmin {
 | 
						if !cli.GetIsKubeAdmin() {
 | 
				
			||||||
		log.Error().Str("context", "getAllKubernetesClusterRoles").Msg("user is not authorized to fetch cluster roles from the Kubernetes cluster.")
 | 
							log.Error().Str("context", "getAllKubernetesClusterRoles").Msg("user is not authorized to fetch cluster roles from the Kubernetes cluster.")
 | 
				
			||||||
		return httperror.Forbidden("User is not authorized to fetch cluster roles from the Kubernetes cluster.", nil)
 | 
							return httperror.Forbidden("User is not authorized to fetch cluster roles from the Kubernetes cluster.", nil)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,102 @@
 | 
				
			||||||
 | 
					package kubernetes
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"net/http"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						httperror "github.com/portainer/portainer/pkg/libhttp/error"
 | 
				
			||||||
 | 
						"github.com/portainer/portainer/pkg/libhttp/request"
 | 
				
			||||||
 | 
						"github.com/portainer/portainer/pkg/libhttp/response"
 | 
				
			||||||
 | 
						"github.com/rs/zerolog/log"
 | 
				
			||||||
 | 
						k8serrors "k8s.io/apimachinery/pkg/api/errors"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// @id getKubernetesEventsForNamespace
 | 
				
			||||||
 | 
					// @summary Gets kubernetes events for namespace
 | 
				
			||||||
 | 
					// @description Get events by optional query param resourceId for a given namespace.
 | 
				
			||||||
 | 
					// @description **Access policy**: Authenticated user.
 | 
				
			||||||
 | 
					// @tags kubernetes
 | 
				
			||||||
 | 
					// @security ApiKeyAuth || jwt
 | 
				
			||||||
 | 
					// @produce json
 | 
				
			||||||
 | 
					// @param id path int true "Environment identifier"
 | 
				
			||||||
 | 
					// @param namespace path string true "The namespace name the events are associated to"
 | 
				
			||||||
 | 
					// @param resourceId query string false "The resource id of the involved kubernetes object" example:"e5b021b6-4bce-4c06-bd3b-6cca906797aa"
 | 
				
			||||||
 | 
					// @success 200 {object} models.Event[] "Success"
 | 
				
			||||||
 | 
					// @failure 400 "Invalid request payload, such as missing required fields or fields not meeting validation criteria."
 | 
				
			||||||
 | 
					// @failure 401 "Unauthorized access - the user is not authenticated or does not have the necessary permissions. Ensure that you have provided a valid API key or JWT token, and that you have the required permissions."
 | 
				
			||||||
 | 
					// @failure 403 "Permission denied - the user is authenticated but does not have the necessary permissions to access the requested resource or perform the specified operation. Check your user roles and permissions."
 | 
				
			||||||
 | 
					// @failure 500 "Server error occurred while attempting to retrieve the events within the specified namespace."
 | 
				
			||||||
 | 
					// @router /kubernetes/{id}/namespaces/{namespace}/events [get]
 | 
				
			||||||
 | 
					func (handler *Handler) getKubernetesEventsForNamespace(w http.ResponseWriter, r *http.Request) *httperror.HandlerError {
 | 
				
			||||||
 | 
						namespace, err := request.RetrieveRouteVariableValue(r, "namespace")
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							log.Error().Err(err).Str("context", "getKubernetesEvents").Str("namespace", namespace).Msg("Unable to retrieve namespace identifier route variable")
 | 
				
			||||||
 | 
							return httperror.BadRequest("Unable to retrieve namespace identifier route variable", err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						resourceId, err := request.RetrieveQueryParameter(r, "resourceId", true)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							log.Error().Err(err).Str("context", "getKubernetesEvents").Msg("Unable to retrieve resourceId query parameter")
 | 
				
			||||||
 | 
							return httperror.BadRequest("Unable to retrieve resourceId query parameter", err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						cli, httpErr := handler.getProxyKubeClient(r)
 | 
				
			||||||
 | 
						if httpErr != nil {
 | 
				
			||||||
 | 
							log.Error().Err(httpErr).Str("context", "getKubernetesEvents").Str("resourceId", resourceId).Msg("Unable to get a Kubernetes client for the user")
 | 
				
			||||||
 | 
							return httperror.InternalServerError("Unable to get a Kubernetes client for the user", httpErr)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						events, err := cli.GetEvents(namespace, resourceId)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							if k8serrors.IsUnauthorized(err) || k8serrors.IsForbidden(err) {
 | 
				
			||||||
 | 
								log.Error().Err(err).Str("context", "getKubernetesEvents").Msg("Unauthorized access to the Kubernetes API")
 | 
				
			||||||
 | 
								return httperror.Forbidden("Unauthorized access to the Kubernetes API", err)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							log.Error().Err(err).Str("context", "getKubernetesEvents").Msg("Unable to retrieve events")
 | 
				
			||||||
 | 
							return httperror.InternalServerError("Unable to retrieve events", err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return response.JSON(w, events)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// @id getAllKubernetesEvents
 | 
				
			||||||
 | 
					// @summary Gets kubernetes events
 | 
				
			||||||
 | 
					// @description Get events by query param resourceId
 | 
				
			||||||
 | 
					// @description **Access policy**: Authenticated user.
 | 
				
			||||||
 | 
					// @tags kubernetes
 | 
				
			||||||
 | 
					// @security ApiKeyAuth || jwt
 | 
				
			||||||
 | 
					// @produce json
 | 
				
			||||||
 | 
					// @param id path int true "Environment identifier"
 | 
				
			||||||
 | 
					// @param resourceId query string false "The resource id of the involved kubernetes object"  example:"e5b021b6-4bce-4c06-bd3b-6cca906797aa"
 | 
				
			||||||
 | 
					// @success 200 {object} models.Event[] "Success"
 | 
				
			||||||
 | 
					// @failure 400 "Invalid request payload, such as missing required fields or fields not meeting validation criteria."
 | 
				
			||||||
 | 
					// @failure 401 "Unauthorized access - the user is not authenticated or does not have the necessary permissions. Ensure that you have provided a valid API key or JWT token, and that you have the required permissions."
 | 
				
			||||||
 | 
					// @failure 403 "Permission denied - the user is authenticated but does not have the necessary permissions to access the requested resource or perform the specified operation. Check your user roles and permissions."
 | 
				
			||||||
 | 
					// @failure 500 "Server error occurred while attempting to retrieve the events."
 | 
				
			||||||
 | 
					// @router /kubernetes/{id}/events [get]
 | 
				
			||||||
 | 
					func (handler *Handler) getAllKubernetesEvents(w http.ResponseWriter, r *http.Request) *httperror.HandlerError {
 | 
				
			||||||
 | 
						resourceId, err := request.RetrieveQueryParameter(r, "resourceId", true)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							log.Error().Err(err).Str("context", "getKubernetesEvents").Msg("Unable to retrieve resourceId query parameter")
 | 
				
			||||||
 | 
							return httperror.BadRequest("Unable to retrieve resourceId query parameter", err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						cli, httpErr := handler.getProxyKubeClient(r)
 | 
				
			||||||
 | 
						if httpErr != nil {
 | 
				
			||||||
 | 
							log.Error().Err(httpErr).Str("context", "getKubernetesEvents").Str("resourceId", resourceId).Msg("Unable to get a Kubernetes client for the user")
 | 
				
			||||||
 | 
							return httperror.InternalServerError("Unable to get a Kubernetes client for the user", httpErr)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						events, err := cli.GetEvents("", resourceId)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							if k8serrors.IsUnauthorized(err) || k8serrors.IsForbidden(err) {
 | 
				
			||||||
 | 
								log.Error().Err(err).Str("context", "getKubernetesEvents").Msg("Unauthorized access to the Kubernetes API")
 | 
				
			||||||
 | 
								return httperror.Forbidden("Unauthorized access to the Kubernetes API", err)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							log.Error().Err(err).Str("context", "getKubernetesEvents").Msg("Unable to retrieve events")
 | 
				
			||||||
 | 
							return httperror.InternalServerError("Unable to retrieve events", err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return response.JSON(w, events)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,60 @@
 | 
				
			||||||
 | 
					package kubernetes
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"net/http"
 | 
				
			||||||
 | 
						"net/http/httptest"
 | 
				
			||||||
 | 
						"testing"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						portainer "github.com/portainer/portainer/api"
 | 
				
			||||||
 | 
						"github.com/portainer/portainer/api/datastore"
 | 
				
			||||||
 | 
						"github.com/portainer/portainer/api/http/security"
 | 
				
			||||||
 | 
						"github.com/portainer/portainer/api/internal/authorization"
 | 
				
			||||||
 | 
						"github.com/portainer/portainer/api/internal/testhelpers"
 | 
				
			||||||
 | 
						"github.com/portainer/portainer/api/jwt"
 | 
				
			||||||
 | 
						"github.com/portainer/portainer/api/kubernetes"
 | 
				
			||||||
 | 
						kubeClient "github.com/portainer/portainer/api/kubernetes/cli"
 | 
				
			||||||
 | 
						"github.com/stretchr/testify/assert"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Currently this test just tests the HTTP Handler is setup correctly, in the future we should move the ClientFactory to a mock in order
 | 
				
			||||||
 | 
					// test the logic in event.go
 | 
				
			||||||
 | 
					func TestGetKubernetesEvents(t *testing.T) {
 | 
				
			||||||
 | 
						is := assert.New(t)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						_, store := datastore.MustNewTestStore(t, true, true)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						err := store.Endpoint().Create(&portainer.Endpoint{
 | 
				
			||||||
 | 
							ID:   1,
 | 
				
			||||||
 | 
							Type: portainer.AgentOnKubernetesEnvironment,
 | 
				
			||||||
 | 
						},
 | 
				
			||||||
 | 
						)
 | 
				
			||||||
 | 
						is.NoError(err, "error creating environment")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						err = store.User().Create(&portainer.User{Username: "admin", Role: portainer.AdministratorRole})
 | 
				
			||||||
 | 
						is.NoError(err, "error creating a user")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						jwtService, err := jwt.NewService("1h", store)
 | 
				
			||||||
 | 
						is.NoError(err, "Error initiating jwt service")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						tk, _, _ := jwtService.GenerateToken(&portainer.TokenData{ID: 1, Username: "admin", Role: portainer.AdministratorRole})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						kubeClusterAccessService := kubernetes.NewKubeClusterAccessService("", "", "")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						cli := testhelpers.NewKubernetesClient()
 | 
				
			||||||
 | 
						factory, _ := kubeClient.NewClientFactory(nil, nil, store, "", "", "")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						authorizationService := authorization.NewService(store)
 | 
				
			||||||
 | 
						handler := NewHandler(testhelpers.NewTestRequestBouncer(), authorizationService, store, jwtService, kubeClusterAccessService,
 | 
				
			||||||
 | 
							factory, cli)
 | 
				
			||||||
 | 
						is.NotNil(handler, "Handler should not fail")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						req := httptest.NewRequest(http.MethodGet, "/kubernetes/1/events?resourceId=8", nil)
 | 
				
			||||||
 | 
						ctx := security.StoreTokenData(req, &portainer.TokenData{ID: 1, Username: "admin", Role: 1})
 | 
				
			||||||
 | 
						req = req.WithContext(ctx)
 | 
				
			||||||
 | 
						testhelpers.AddTestSecurityCookie(req, tk)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						rr := httptest.NewRecorder()
 | 
				
			||||||
 | 
						handler.ServeHTTP(rr, req)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						is.Equal(http.StatusOK, rr.Code, "Status should be 200")
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -58,6 +58,7 @@ func NewHandler(bouncer security.BouncerService, authorizationService *authoriza
 | 
				
			||||||
	endpointRouter.Handle("/configmaps/count", httperror.LoggerHandler(h.getAllKubernetesConfigMapsCount)).Methods(http.MethodGet)
 | 
						endpointRouter.Handle("/configmaps/count", httperror.LoggerHandler(h.getAllKubernetesConfigMapsCount)).Methods(http.MethodGet)
 | 
				
			||||||
	endpointRouter.Handle("/cron_jobs", httperror.LoggerHandler(h.getAllKubernetesCronJobs)).Methods(http.MethodGet)
 | 
						endpointRouter.Handle("/cron_jobs", httperror.LoggerHandler(h.getAllKubernetesCronJobs)).Methods(http.MethodGet)
 | 
				
			||||||
	endpointRouter.Handle("/cron_jobs/delete", httperror.LoggerHandler(h.deleteKubernetesCronJobs)).Methods(http.MethodPost)
 | 
						endpointRouter.Handle("/cron_jobs/delete", httperror.LoggerHandler(h.deleteKubernetesCronJobs)).Methods(http.MethodPost)
 | 
				
			||||||
 | 
						endpointRouter.Handle("/events", httperror.LoggerHandler(h.getAllKubernetesEvents)).Methods(http.MethodGet)
 | 
				
			||||||
	endpointRouter.Handle("/jobs", httperror.LoggerHandler(h.getAllKubernetesJobs)).Methods(http.MethodGet)
 | 
						endpointRouter.Handle("/jobs", httperror.LoggerHandler(h.getAllKubernetesJobs)).Methods(http.MethodGet)
 | 
				
			||||||
	endpointRouter.Handle("/jobs/delete", httperror.LoggerHandler(h.deleteKubernetesJobs)).Methods(http.MethodPost)
 | 
						endpointRouter.Handle("/jobs/delete", httperror.LoggerHandler(h.deleteKubernetesJobs)).Methods(http.MethodPost)
 | 
				
			||||||
	endpointRouter.Handle("/cluster_roles", httperror.LoggerHandler(h.getAllKubernetesClusterRoles)).Methods(http.MethodGet)
 | 
						endpointRouter.Handle("/cluster_roles", httperror.LoggerHandler(h.getAllKubernetesClusterRoles)).Methods(http.MethodGet)
 | 
				
			||||||
| 
						 | 
					@ -110,6 +111,7 @@ func NewHandler(bouncer security.BouncerService, authorizationService *authoriza
 | 
				
			||||||
	// to keep it simple, we've decided to leave it like this.
 | 
						// to keep it simple, we've decided to leave it like this.
 | 
				
			||||||
	namespaceRouter := endpointRouter.PathPrefix("/namespaces/{namespace}").Subrouter()
 | 
						namespaceRouter := endpointRouter.PathPrefix("/namespaces/{namespace}").Subrouter()
 | 
				
			||||||
	namespaceRouter.Handle("/configmaps/{configmap}", httperror.LoggerHandler(h.getKubernetesConfigMap)).Methods(http.MethodGet)
 | 
						namespaceRouter.Handle("/configmaps/{configmap}", httperror.LoggerHandler(h.getKubernetesConfigMap)).Methods(http.MethodGet)
 | 
				
			||||||
 | 
						namespaceRouter.Handle("/events", httperror.LoggerHandler(h.getKubernetesEventsForNamespace)).Methods(http.MethodGet)
 | 
				
			||||||
	namespaceRouter.Handle("/system", bouncer.RestrictedAccess(httperror.LoggerHandler(h.namespacesToggleSystem))).Methods(http.MethodPut)
 | 
						namespaceRouter.Handle("/system", bouncer.RestrictedAccess(httperror.LoggerHandler(h.namespacesToggleSystem))).Methods(http.MethodPut)
 | 
				
			||||||
	namespaceRouter.Handle("/ingresscontrollers", httperror.LoggerHandler(h.getKubernetesIngressControllersByNamespace)).Methods(http.MethodGet)
 | 
						namespaceRouter.Handle("/ingresscontrollers", httperror.LoggerHandler(h.getKubernetesIngressControllersByNamespace)).Methods(http.MethodGet)
 | 
				
			||||||
	namespaceRouter.Handle("/ingresscontrollers", httperror.LoggerHandler(h.updateKubernetesIngressControllersByNamespace)).Methods(http.MethodPut)
 | 
						namespaceRouter.Handle("/ingresscontrollers", httperror.LoggerHandler(h.updateKubernetesIngressControllersByNamespace)).Methods(http.MethodPut)
 | 
				
			||||||
| 
						 | 
					@ -133,7 +135,7 @@ func NewHandler(bouncer security.BouncerService, authorizationService *authoriza
 | 
				
			||||||
// getProxyKubeClient gets a kubeclient for the user.  It's generally what you want as it retrieves the kubeclient
 | 
					// getProxyKubeClient gets a kubeclient for the user.  It's generally what you want as it retrieves the kubeclient
 | 
				
			||||||
// from the Authorization token of the currently logged in user.  The kubeclient that is not from the proxy is actually using
 | 
					// from the Authorization token of the currently logged in user.  The kubeclient that is not from the proxy is actually using
 | 
				
			||||||
// admin permissions.  If you're unsure which one to use, use this.
 | 
					// admin permissions.  If you're unsure which one to use, use this.
 | 
				
			||||||
func (h *Handler) getProxyKubeClient(r *http.Request) (*cli.KubeClient, *httperror.HandlerError) {
 | 
					func (h *Handler) getProxyKubeClient(r *http.Request) (portainer.KubeClient, *httperror.HandlerError) {
 | 
				
			||||||
	endpointID, err := request.RetrieveNumericRouteVariableValue(r, "id")
 | 
						endpointID, err := request.RetrieveNumericRouteVariableValue(r, "id")
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		return nil, httperror.BadRequest(fmt.Sprintf("an error occurred during the getProxyKubeClient operation, the environment identifier route variable is invalid for /api/kubernetes/%d. Error: ", endpointID), err)
 | 
							return nil, httperror.BadRequest(fmt.Sprintf("an error occurred during the getProxyKubeClient operation, the environment identifier route variable is invalid for /api/kubernetes/%d. Error: ", endpointID), err)
 | 
				
			||||||
| 
						 | 
					@ -253,7 +255,7 @@ func (handler *Handler) kubeClientMiddleware(next http.Handler) http.Handler {
 | 
				
			||||||
			return
 | 
								return
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		serverURL.Scheme = "https"
 | 
							serverURL.Scheme = "https"
 | 
				
			||||||
		serverURL.Host = "localhost" + handler.KubernetesClientFactory.AddrHTTPS
 | 
							serverURL.Host = "localhost" + handler.KubernetesClientFactory.GetAddrHTTPS()
 | 
				
			||||||
		config.Clusters[0].Cluster.Server = serverURL.String()
 | 
							config.Clusters[0].Cluster.Server = serverURL.String()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		yaml, err := cli.GenerateYAML(config)
 | 
							yaml, err := cli.GenerateYAML(config)
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,25 @@
 | 
				
			||||||
 | 
					package kubernetes
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import "time"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type K8sEvent struct {
 | 
				
			||||||
 | 
						Type               string                 `json:"type"`
 | 
				
			||||||
 | 
						Name               string                 `json:"name"`
 | 
				
			||||||
 | 
						Reason             string                 `json:"reason"`
 | 
				
			||||||
 | 
						Message            string                 `json:"message"`
 | 
				
			||||||
 | 
						Namespace          string                 `json:"namespace"`
 | 
				
			||||||
 | 
						EventTime          time.Time              `json:"eventTime"`
 | 
				
			||||||
 | 
						Kind               string                 `json:"kind,omitempty"`
 | 
				
			||||||
 | 
						Count              int32                  `json:"count"`
 | 
				
			||||||
 | 
						FirstTimestamp     *time.Time             `json:"firstTimestamp,omitempty"`
 | 
				
			||||||
 | 
						LastTimestamp      *time.Time             `json:"lastTimestamp,omitempty"`
 | 
				
			||||||
 | 
						UID                string                 `json:"uid"`
 | 
				
			||||||
 | 
						InvolvedObjectKind K8sEventInvolvedObject `json:"involvedObject"`
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type K8sEventInvolvedObject struct {
 | 
				
			||||||
 | 
						Kind      string `json:"kind,omitempty"`
 | 
				
			||||||
 | 
						UID       string `json:"uid"`
 | 
				
			||||||
 | 
						Name      string `json:"name"`
 | 
				
			||||||
 | 
						Namespace string `json:"namespace"`
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,19 @@
 | 
				
			||||||
 | 
					package testhelpers
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						portainer "github.com/portainer/portainer/api"
 | 
				
			||||||
 | 
						models "github.com/portainer/portainer/api/http/models/kubernetes"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type testKubeClient struct {
 | 
				
			||||||
 | 
						portainer.KubeClient
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func NewKubernetesClient() portainer.KubeClient {
 | 
				
			||||||
 | 
						return &testKubeClient{}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Event
 | 
				
			||||||
 | 
					func (kcl *testKubeClient) GetEvents(namespace string, resourceId string) ([]models.K8sEvent, error) {
 | 
				
			||||||
 | 
						return nil, nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -143,3 +143,23 @@ func (kcl *KubeClient) GetNonAdminNamespaces(userID int, teamIDs []int, isRestri
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	return nonAdminNamespaces, nil
 | 
						return nonAdminNamespaces, nil
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// GetIsKubeAdmin retrieves true if client is admin
 | 
				
			||||||
 | 
					func (client *KubeClient) GetIsKubeAdmin() bool {
 | 
				
			||||||
 | 
						return client.IsKubeAdmin
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// UpdateIsKubeAdmin sets whether the kube client is admin
 | 
				
			||||||
 | 
					func (client *KubeClient) SetIsKubeAdmin(isKubeAdmin bool) {
 | 
				
			||||||
 | 
						client.IsKubeAdmin = isKubeAdmin
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// GetClientNonAdminNamespaces retrieves non-admin namespaces
 | 
				
			||||||
 | 
					func (client *KubeClient) GetClientNonAdminNamespaces() []string {
 | 
				
			||||||
 | 
						return client.NonAdminNamespaces
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// UpdateClientNonAdminNamespaces sets the client non admin namespace list
 | 
				
			||||||
 | 
					func (client *KubeClient) SetClientNonAdminNamespaces(nonAdminNamespaces []string) {
 | 
				
			||||||
 | 
						client.NonAdminNamespaces = nonAdminNamespaces
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -82,6 +82,10 @@ func (factory *ClientFactory) RemoveKubeClient(endpointID portainer.EndpointID)
 | 
				
			||||||
	factory.endpointProxyClients.Delete(strconv.Itoa(int(endpointID)))
 | 
						factory.endpointProxyClients.Delete(strconv.Itoa(int(endpointID)))
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (factory *ClientFactory) GetAddrHTTPS() string {
 | 
				
			||||||
 | 
						return factory.AddrHTTPS
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// GetPrivilegedKubeClient checks if an existing client is already registered for the environment(endpoint) and returns it if one is found.
 | 
					// GetPrivilegedKubeClient checks if an existing client is already registered for the environment(endpoint) and returns it if one is found.
 | 
				
			||||||
// If no client is registered, it will create a new client, register it, and returns it.
 | 
					// If no client is registered, it will create a new client, register it, and returns it.
 | 
				
			||||||
func (factory *ClientFactory) GetPrivilegedKubeClient(endpoint *portainer.Endpoint) (*KubeClient, error) {
 | 
					func (factory *ClientFactory) GetPrivilegedKubeClient(endpoint *portainer.Endpoint) (*KubeClient, error) {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,93 @@
 | 
				
			||||||
 | 
					package cli
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"context"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						models "github.com/portainer/portainer/api/http/models/kubernetes"
 | 
				
			||||||
 | 
						corev1 "k8s.io/api/core/v1"
 | 
				
			||||||
 | 
						metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// GetEvents gets all the Events for a given namespace and resource
 | 
				
			||||||
 | 
					// If the user is a kube admin, it returns all events in the namespace
 | 
				
			||||||
 | 
					// Otherwise, it returns only the events in the non-admin namespaces
 | 
				
			||||||
 | 
					func (kcl *KubeClient) GetEvents(namespace string, resourceId string) ([]models.K8sEvent, error) {
 | 
				
			||||||
 | 
						if kcl.IsKubeAdmin {
 | 
				
			||||||
 | 
							return kcl.fetchAllEvents(namespace, resourceId)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return kcl.fetchEventsForNonAdmin(namespace, resourceId)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// fetchEventsForNonAdmin returns all events in the given namespace and resource
 | 
				
			||||||
 | 
					// It returns only the events in the non-admin namespaces
 | 
				
			||||||
 | 
					func (kcl *KubeClient) fetchEventsForNonAdmin(namespace string, resourceId string) ([]models.K8sEvent, error) {
 | 
				
			||||||
 | 
						if len(kcl.NonAdminNamespaces) == 0 {
 | 
				
			||||||
 | 
							return nil, nil
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						events, err := kcl.fetchAllEvents(namespace, resourceId)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return nil, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						nonAdminNamespaceSet := kcl.buildNonAdminNamespacesMap()
 | 
				
			||||||
 | 
						results := make([]models.K8sEvent, 0)
 | 
				
			||||||
 | 
						for _, event := range events {
 | 
				
			||||||
 | 
							if _, ok := nonAdminNamespaceSet[event.Namespace]; ok {
 | 
				
			||||||
 | 
								results = append(results, event)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return results, nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// fetchEventsForNonAdmin returns all events in the given namespace and resource
 | 
				
			||||||
 | 
					// It returns all events in the namespace and resource
 | 
				
			||||||
 | 
					func (kcl *KubeClient) fetchAllEvents(namespace string, resourceId string) ([]models.K8sEvent, error) {
 | 
				
			||||||
 | 
						options := metav1.ListOptions{}
 | 
				
			||||||
 | 
						if resourceId != "" {
 | 
				
			||||||
 | 
							options.FieldSelector = "involvedObject.uid=" + resourceId
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						list, err := kcl.cli.CoreV1().Events(namespace).List(context.TODO(), options)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return nil, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						results := make([]models.K8sEvent, 0)
 | 
				
			||||||
 | 
						for _, event := range list.Items {
 | 
				
			||||||
 | 
							results = append(results, parseEvent(&event))
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return results, nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func parseEvent(event *corev1.Event) models.K8sEvent {
 | 
				
			||||||
 | 
						result := models.K8sEvent{
 | 
				
			||||||
 | 
							Type:      event.Type,
 | 
				
			||||||
 | 
							Name:      event.Name,
 | 
				
			||||||
 | 
							Message:   event.Message,
 | 
				
			||||||
 | 
							Reason:    event.Reason,
 | 
				
			||||||
 | 
							Namespace: event.Namespace,
 | 
				
			||||||
 | 
							EventTime: event.EventTime.UTC(),
 | 
				
			||||||
 | 
							Kind:      event.Kind,
 | 
				
			||||||
 | 
							Count:     event.Count,
 | 
				
			||||||
 | 
							UID:       string(event.ObjectMeta.GetUID()),
 | 
				
			||||||
 | 
							InvolvedObjectKind: models.K8sEventInvolvedObject{
 | 
				
			||||||
 | 
								Kind:      event.InvolvedObject.Kind,
 | 
				
			||||||
 | 
								UID:       string(event.InvolvedObject.UID),
 | 
				
			||||||
 | 
								Name:      event.InvolvedObject.Name,
 | 
				
			||||||
 | 
								Namespace: event.InvolvedObject.Namespace,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if !event.LastTimestamp.Time.IsZero() {
 | 
				
			||||||
 | 
							result.LastTimestamp = &event.LastTimestamp.Time
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if !event.FirstTimestamp.Time.IsZero() {
 | 
				
			||||||
 | 
							result.FirstTimestamp = &event.FirstTimestamp.Time
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return result
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,108 @@
 | 
				
			||||||
 | 
					package cli
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"context"
 | 
				
			||||||
 | 
						"testing"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"github.com/stretchr/testify/assert"
 | 
				
			||||||
 | 
						"github.com/stretchr/testify/require"
 | 
				
			||||||
 | 
						corev1 "k8s.io/api/core/v1"
 | 
				
			||||||
 | 
						metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 | 
				
			||||||
 | 
						kfake "k8s.io/client-go/kubernetes/fake"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// TestGetEvents tests the GetEvents method
 | 
				
			||||||
 | 
					// It creates a fake Kubernetes client and passes it to the GetEvents method
 | 
				
			||||||
 | 
					// It then logs the fetched events and validated the data returned
 | 
				
			||||||
 | 
					func TestGetEvents(t *testing.T) {
 | 
				
			||||||
 | 
						t.Run("can get events for resource id when admin", func(t *testing.T) {
 | 
				
			||||||
 | 
							kcl := &KubeClient{
 | 
				
			||||||
 | 
								cli:         kfake.NewSimpleClientset(),
 | 
				
			||||||
 | 
								instanceID:  "instance",
 | 
				
			||||||
 | 
								IsKubeAdmin: true,
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							event := corev1.Event{
 | 
				
			||||||
 | 
								InvolvedObject: corev1.ObjectReference{UID: "resourceId"},
 | 
				
			||||||
 | 
								Action:         "something",
 | 
				
			||||||
 | 
								ObjectMeta:     metav1.ObjectMeta{Namespace: "default", Name: "myEvent"},
 | 
				
			||||||
 | 
								EventTime:      metav1.NowMicro(),
 | 
				
			||||||
 | 
								Type:           "warning",
 | 
				
			||||||
 | 
								Message:        "This event has a very serious warning",
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							_, err := kcl.cli.CoreV1().Events("default").Create(context.TODO(), &event, metav1.CreateOptions{})
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								t.Fatalf("Failed to create Event: %v", err)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							events, err := kcl.GetEvents("default", "resourceId")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								t.Fatalf("Failed to fetch Cron Jobs: %v", err)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							t.Logf("Fetched Events: %v", events)
 | 
				
			||||||
 | 
							require.Equal(t, 1, len(events), "Expected to return 1 event")
 | 
				
			||||||
 | 
							assert.Equal(t, event.Message, events[0].Message, "Expected Message to be equal to event message created")
 | 
				
			||||||
 | 
							assert.Equal(t, event.Type, events[0].Type, "Expected Type to be equal to event type created")
 | 
				
			||||||
 | 
							assert.Equal(t, event.EventTime.UTC(), events[0].EventTime, "Expected EventTime to be saved as a string from event time created")
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
						t.Run("can get kubernetes events for non admin namespace when non admin", func(t *testing.T) {
 | 
				
			||||||
 | 
							kcl := &KubeClient{
 | 
				
			||||||
 | 
								cli:                kfake.NewSimpleClientset(),
 | 
				
			||||||
 | 
								instanceID:         "instance",
 | 
				
			||||||
 | 
								IsKubeAdmin:        false,
 | 
				
			||||||
 | 
								NonAdminNamespaces: []string{"nonAdmin"},
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							event := corev1.Event{
 | 
				
			||||||
 | 
								InvolvedObject: corev1.ObjectReference{UID: "resourceId"},
 | 
				
			||||||
 | 
								Action:         "something",
 | 
				
			||||||
 | 
								ObjectMeta:     metav1.ObjectMeta{Namespace: "nonAdmin", Name: "myEvent"},
 | 
				
			||||||
 | 
								EventTime:      metav1.NowMicro(),
 | 
				
			||||||
 | 
								Type:           "warning",
 | 
				
			||||||
 | 
								Message:        "This event has a very serious warning",
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							_, err := kcl.cli.CoreV1().Events("nonAdmin").Create(context.TODO(), &event, metav1.CreateOptions{})
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								t.Fatalf("Failed to create Event: %v", err)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							events, err := kcl.GetEvents("nonAdmin", "resourceId")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								t.Fatalf("Failed to fetch Cron Jobs: %v", err)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							t.Logf("Fetched Events: %v", events)
 | 
				
			||||||
 | 
							require.Equal(t, 1, len(events), "Expected to return 1 event")
 | 
				
			||||||
 | 
							assert.Equal(t, event.Message, events[0].Message, "Expected Message to be equal to event message created")
 | 
				
			||||||
 | 
							assert.Equal(t, event.Type, events[0].Type, "Expected Type to be equal to event type created")
 | 
				
			||||||
 | 
							assert.Equal(t, event.EventTime.UTC(), events[0].EventTime, "Expected EventTime to be saved as a string from event time created")
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						t.Run("cannot get kubernetes events for admin namespace when non admin", func(t *testing.T) {
 | 
				
			||||||
 | 
							kcl := &KubeClient{
 | 
				
			||||||
 | 
								cli:                kfake.NewSimpleClientset(),
 | 
				
			||||||
 | 
								instanceID:         "instance",
 | 
				
			||||||
 | 
								IsKubeAdmin:        false,
 | 
				
			||||||
 | 
								NonAdminNamespaces: []string{"nonAdmin"},
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							event := corev1.Event{
 | 
				
			||||||
 | 
								InvolvedObject: corev1.ObjectReference{UID: "resourceId"},
 | 
				
			||||||
 | 
								Action:         "something",
 | 
				
			||||||
 | 
								ObjectMeta:     metav1.ObjectMeta{Namespace: "admin", Name: "myEvent"},
 | 
				
			||||||
 | 
								EventTime:      metav1.NowMicro(),
 | 
				
			||||||
 | 
								Type:           "warning",
 | 
				
			||||||
 | 
								Message:        "This event has a very serious warning",
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							_, err := kcl.cli.CoreV1().Events("admin").Create(context.TODO(), &event, metav1.CreateOptions{})
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								t.Fatalf("Failed to create Event: %v", err)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							events, err := kcl.GetEvents("admin", "resourceId")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								t.Fatalf("Failed to fetch Cron Jobs: %v", err)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							t.Logf("Fetched Events: %v", events)
 | 
				
			||||||
 | 
							assert.Equal(t, 0, len(events), "Expected to return 0 events")
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										147
									
								
								api/portainer.go
								
								
								
								
							
							
						
						
									
										147
									
								
								api/portainer.go
								
								
								
								
							| 
						 | 
					@ -4,6 +4,7 @@ import (
 | 
				
			||||||
	"context"
 | 
						"context"
 | 
				
			||||||
	"fmt"
 | 
						"fmt"
 | 
				
			||||||
	"io"
 | 
						"io"
 | 
				
			||||||
 | 
						"net/http"
 | 
				
			||||||
	"time"
 | 
						"time"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	"github.com/docker/docker/api/types"
 | 
						"github.com/docker/docker/api/types"
 | 
				
			||||||
| 
						 | 
					@ -13,6 +14,7 @@ import (
 | 
				
			||||||
	gittypes "github.com/portainer/portainer/api/git/types"
 | 
						gittypes "github.com/portainer/portainer/api/git/types"
 | 
				
			||||||
	models "github.com/portainer/portainer/api/http/models/kubernetes"
 | 
						models "github.com/portainer/portainer/api/http/models/kubernetes"
 | 
				
			||||||
	"github.com/portainer/portainer/pkg/featureflags"
 | 
						"github.com/portainer/portainer/pkg/featureflags"
 | 
				
			||||||
 | 
						httperror "github.com/portainer/portainer/pkg/libhttp/error"
 | 
				
			||||||
	"github.com/segmentio/encoding/json"
 | 
						"github.com/segmentio/encoding/json"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	"golang.org/x/oauth2"
 | 
						"golang.org/x/oauth2"
 | 
				
			||||||
| 
						 | 
					@ -1531,56 +1533,127 @@ type (
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// KubeClient represents a service used to query a Kubernetes environment(endpoint)
 | 
						// KubeClient represents a service used to query a Kubernetes environment(endpoint)
 | 
				
			||||||
	KubeClient interface {
 | 
						KubeClient interface {
 | 
				
			||||||
		ServerVersion() (*version.Info, error)
 | 
							// Access
 | 
				
			||||||
 | 
							GetIsKubeAdmin() bool
 | 
				
			||||||
 | 
							SetIsKubeAdmin(isKubeAdmin bool)
 | 
				
			||||||
 | 
							GetClientNonAdminNamespaces() []string
 | 
				
			||||||
 | 
							SetClientNonAdminNamespaces([]string)
 | 
				
			||||||
 | 
							NamespaceAccessPoliciesDeleteNamespace(ns string) error
 | 
				
			||||||
 | 
							UpdateNamespaceAccessPolicies(accessPolicies map[string]K8sNamespaceAccessPolicy) error
 | 
				
			||||||
 | 
							GetNamespaceAccessPolicies() (map[string]K8sNamespaceAccessPolicy, error)
 | 
				
			||||||
 | 
							GetNonAdminNamespaces(userID int, teamIDs []int, isRestrictDefaultNamespace bool) ([]string, error)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		SetupUserServiceAccount(userID int, teamIDs []int, restrictDefaultNamespace bool) error
 | 
							// Applications
 | 
				
			||||||
		IsRBACEnabled() (bool, error)
 | 
							GetApplications(namespace, nodeName string) ([]models.K8sApplication, error)
 | 
				
			||||||
		GetPortainerUserServiceAccount(tokendata *TokenData) (*corev1.ServiceAccount, error)
 | 
							GetApplicationsResource(namespace, node string) (models.K8sApplicationResource, error)
 | 
				
			||||||
		GetServiceAccounts(namespace string) ([]models.K8sServiceAccount, error)
 | 
					
 | 
				
			||||||
		DeleteServiceAccounts(reqs models.K8sServiceAccountDeleteRequests) error
 | 
							// ClusterRole
 | 
				
			||||||
		GetServiceAccountBearerToken(userID int) (string, error)
 | 
							GetClusterRoles() ([]models.K8sClusterRole, error)
 | 
				
			||||||
		CreateUserShellPod(ctx context.Context, serviceAccountName, shellPodImage string) (*KubernetesShellPod, error)
 | 
							DeleteClusterRoles(req models.K8sClusterRoleDeleteRequests) error
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// ConfigMap
 | 
				
			||||||
 | 
							GetConfigMap(namespace, configMapName string) (models.K8sConfigMap, error)
 | 
				
			||||||
 | 
							CombineConfigMapWithApplications(configMap models.K8sConfigMap) (models.K8sConfigMap, error)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// CronJob
 | 
				
			||||||
 | 
							GetCronJobs(namespace string) ([]models.K8sCronJob, error)
 | 
				
			||||||
 | 
							DeleteCronJobs(payload models.K8sCronJobDeleteRequests) error
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// Event
 | 
				
			||||||
 | 
							GetEvents(namespace string, resourceId string) ([]models.K8sEvent, error)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// Exec
 | 
				
			||||||
		StartExecProcess(token string, useAdminToken bool, namespace, podName, containerName string, command []string, stdin io.Reader, stdout io.Writer, errChan chan error)
 | 
							StartExecProcess(token string, useAdminToken bool, namespace, podName, containerName string, command []string, stdin io.Reader, stdout io.Writer, errChan chan error)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// ClusterRoleBinding
 | 
				
			||||||
 | 
							GetClusterRoleBindings() ([]models.K8sClusterRoleBinding, error)
 | 
				
			||||||
 | 
							DeleteClusterRoleBindings(reqs models.K8sClusterRoleBindingDeleteRequests) error
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// Dashboard
 | 
				
			||||||
 | 
							GetDashboard() (models.K8sDashboard, error)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// Deployment
 | 
				
			||||||
		HasStackName(namespace string, stackName string) (bool, error)
 | 
							HasStackName(namespace string, stackName string) (bool, error)
 | 
				
			||||||
		NamespaceAccessPoliciesDeleteNamespace(namespace string) error
 | 
					
 | 
				
			||||||
		CreateNamespace(info models.K8sNamespaceDetails) (*corev1.Namespace, error)
 | 
							// Ingress
 | 
				
			||||||
		UpdateNamespace(info models.K8sNamespaceDetails) (*corev1.Namespace, error)
 | 
					 | 
				
			||||||
		GetNamespaces() (map[string]K8sNamespaceInfo, error)
 | 
					 | 
				
			||||||
		GetNamespace(string) (K8sNamespaceInfo, error)
 | 
					 | 
				
			||||||
		DeleteNamespace(namespace string) (*corev1.Namespace, error)
 | 
					 | 
				
			||||||
		GetConfigMaps(namespace string) ([]models.K8sConfigMap, error)
 | 
					 | 
				
			||||||
		GetSecrets(namespace string) ([]models.K8sSecret, error)
 | 
					 | 
				
			||||||
		GetIngressControllers() (models.K8sIngressControllers, error)
 | 
							GetIngressControllers() (models.K8sIngressControllers, error)
 | 
				
			||||||
		GetApplications(namespace, nodename string) ([]models.K8sApplication, error)
 | 
							GetIngress(namespace, ingressName string) (models.K8sIngressInfo, error)
 | 
				
			||||||
		GetMetrics() (models.K8sMetrics, error)
 | 
					 | 
				
			||||||
		GetStorage() ([]KubernetesStorageClassConfig, error)
 | 
					 | 
				
			||||||
		CreateIngress(namespace string, info models.K8sIngressInfo, owner string) error
 | 
					 | 
				
			||||||
		UpdateIngress(namespace string, info models.K8sIngressInfo) error
 | 
					 | 
				
			||||||
		GetIngresses(namespace string) ([]models.K8sIngressInfo, error)
 | 
							GetIngresses(namespace string) ([]models.K8sIngressInfo, error)
 | 
				
			||||||
 | 
							CreateIngress(namespace string, info models.K8sIngressInfo, owner string) error
 | 
				
			||||||
		DeleteIngresses(reqs models.K8sIngressDeleteRequests) error
 | 
							DeleteIngresses(reqs models.K8sIngressDeleteRequests) error
 | 
				
			||||||
		CreateService(namespace string, service models.K8sServiceInfo) error
 | 
							UpdateIngress(namespace string, info models.K8sIngressInfo) error
 | 
				
			||||||
		UpdateService(namespace string, service models.K8sServiceInfo) error
 | 
							CombineIngressWithService(ingress models.K8sIngressInfo) (models.K8sIngressInfo, error)
 | 
				
			||||||
		GetServices(namespace string) ([]models.K8sServiceInfo, error)
 | 
							CombineIngressesWithServices(ingresses []models.K8sIngressInfo) ([]models.K8sIngressInfo, error)
 | 
				
			||||||
		DeleteServices(reqs models.K8sServiceDeleteRequests) error
 | 
					
 | 
				
			||||||
 | 
							// Job
 | 
				
			||||||
 | 
							GetJobs(namespace string, includeCronJobChildren bool) ([]models.K8sJob, error)
 | 
				
			||||||
 | 
							DeleteJobs(payload models.K8sJobDeleteRequests) error
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// Metrics
 | 
				
			||||||
 | 
							GetMetrics() (models.K8sMetrics, error)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// Namespace
 | 
				
			||||||
 | 
							ToggleSystemState(namespaceName string, isSystem bool) error
 | 
				
			||||||
 | 
							UpdateNamespace(info models.K8sNamespaceDetails) (*corev1.Namespace, error)
 | 
				
			||||||
 | 
							GetNamespace(name string) (K8sNamespaceInfo, error)
 | 
				
			||||||
 | 
							CreateNamespace(info models.K8sNamespaceDetails) (*corev1.Namespace, error)
 | 
				
			||||||
 | 
							GetNamespaces() (map[string]K8sNamespaceInfo, error)
 | 
				
			||||||
 | 
							CombineNamespaceWithResourceQuota(namespace K8sNamespaceInfo, w http.ResponseWriter) *httperror.HandlerError
 | 
				
			||||||
 | 
							DeleteNamespace(namespaceName string) (*corev1.Namespace, error)
 | 
				
			||||||
 | 
							CombineNamespacesWithResourceQuotas(namespaces map[string]K8sNamespaceInfo, w http.ResponseWriter) *httperror.HandlerError
 | 
				
			||||||
 | 
							ConvertNamespaceMapToSlice(namespaces map[string]K8sNamespaceInfo) []K8sNamespaceInfo
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// NodeLimits
 | 
				
			||||||
		GetNodesLimits() (K8sNodesLimits, error)
 | 
							GetNodesLimits() (K8sNodesLimits, error)
 | 
				
			||||||
		GetMaxResourceLimits(name string, overCommitEnabled bool, resourceOverCommitPercent int) (K8sNodeLimits, error)
 | 
							GetMaxResourceLimits(skipNamespace string, overCommitEnabled bool, resourceOverCommitPercent int) (K8sNodeLimits, error)
 | 
				
			||||||
		GetNamespaceAccessPolicies() (map[string]K8sNamespaceAccessPolicy, error)
 | 
					
 | 
				
			||||||
		UpdateNamespaceAccessPolicies(accessPolicies map[string]K8sNamespaceAccessPolicy) error
 | 
							// Pod
 | 
				
			||||||
 | 
							CreateUserShellPod(ctx context.Context, serviceAccountName, shellPodImage string) (*KubernetesShellPod, error)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// RBAC
 | 
				
			||||||
 | 
							IsRBACEnabled() (bool, error)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// Registries
 | 
				
			||||||
		DeleteRegistrySecret(registry RegistryID, namespace string) error
 | 
							DeleteRegistrySecret(registry RegistryID, namespace string) error
 | 
				
			||||||
		CreateRegistrySecret(registry *Registry, namespace string) error
 | 
							CreateRegistrySecret(registry *Registry, namespace string) error
 | 
				
			||||||
		IsRegistrySecret(namespace, secretName string) (bool, error)
 | 
							IsRegistrySecret(namespace, secretName string) (bool, error)
 | 
				
			||||||
		ToggleSystemState(namespace string, isSystem bool) error
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
		GetClusterRoles() ([]models.K8sClusterRole, error)
 | 
							// RoleBinding
 | 
				
			||||||
		DeleteClusterRoles(models.K8sClusterRoleDeleteRequests) error
 | 
					 | 
				
			||||||
		GetClusterRoleBindings() ([]models.K8sClusterRoleBinding, error)
 | 
					 | 
				
			||||||
		DeleteClusterRoleBindings(models.K8sClusterRoleBindingDeleteRequests) error
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		GetRoles(namespace string) ([]models.K8sRole, error)
 | 
					 | 
				
			||||||
		DeleteRoles(models.K8sRoleDeleteRequests) error
 | 
					 | 
				
			||||||
		GetRoleBindings(namespace string) ([]models.K8sRoleBinding, error)
 | 
							GetRoleBindings(namespace string) ([]models.K8sRoleBinding, error)
 | 
				
			||||||
		DeleteRoleBindings(models.K8sRoleBindingDeleteRequests) error
 | 
							DeleteRoleBindings(reqs models.K8sRoleBindingDeleteRequests) error
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// Role
 | 
				
			||||||
 | 
							DeleteRoles(reqs models.K8sRoleDeleteRequests) error
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// Secret
 | 
				
			||||||
 | 
							GetSecrets(namespace string) ([]models.K8sSecret, error)
 | 
				
			||||||
 | 
							GetSecret(namespace string, secretName string) (models.K8sSecret, error)
 | 
				
			||||||
 | 
							CombineSecretWithApplications(secret models.K8sSecret) (models.K8sSecret, error)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// ServiceAccount
 | 
				
			||||||
 | 
							GetServiceAccounts(namespace string) ([]models.K8sServiceAccount, error)
 | 
				
			||||||
 | 
							DeleteServiceAccounts(reqs models.K8sServiceAccountDeleteRequests) error
 | 
				
			||||||
 | 
							SetupUserServiceAccount(int, []int, bool) error
 | 
				
			||||||
 | 
							GetPortainerUserServiceAccount(tokendata *TokenData) (*corev1.ServiceAccount, error)
 | 
				
			||||||
 | 
							GetServiceAccountBearerToken(userID int) (string, error)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// Service
 | 
				
			||||||
 | 
							GetServices(namespace string) ([]models.K8sServiceInfo, error)
 | 
				
			||||||
 | 
							CombineServicesWithApplications(services []models.K8sServiceInfo) ([]models.K8sServiceInfo, error)
 | 
				
			||||||
 | 
							CreateService(namespace string, info models.K8sServiceInfo) error
 | 
				
			||||||
 | 
							DeleteServices(reqs models.K8sServiceDeleteRequests) error
 | 
				
			||||||
 | 
							UpdateService(namespace string, info models.K8sServiceInfo) error
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// ServerVersion
 | 
				
			||||||
 | 
							ServerVersion() (*version.Info, error)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// Storage
 | 
				
			||||||
 | 
							GetStorage() ([]KubernetesStorageClassConfig, error)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// Volumes
 | 
				
			||||||
 | 
							GetVolumes(namespace string) ([]models.K8sVolumeInfo, error)
 | 
				
			||||||
 | 
							GetVolume(namespace, volumeName string) (*models.K8sVolumeInfo, error)
 | 
				
			||||||
 | 
							CombineVolumesWithApplications(volumes *[]models.K8sVolumeInfo) (*[]models.K8sVolumeInfo, error)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// KubernetesDeployer represents a service to deploy a manifest inside a Kubernetes environment(endpoint)
 | 
						// KubernetesDeployer represents a service to deploy a manifest inside a Kubernetes environment(endpoint)
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -58,7 +58,7 @@
 | 
				
			||||||
              <resource-events-datatable
 | 
					              <resource-events-datatable
 | 
				
			||||||
                resource-id="ctrl.configuration.Id"
 | 
					                resource-id="ctrl.configuration.Id"
 | 
				
			||||||
                storage-key="'kubernetes.configmap.events'"
 | 
					                storage-key="'kubernetes.configmap.events'"
 | 
				
			||||||
                namespace="ctrl.formValues.ResourcePool.Namespace.Name"
 | 
					                namespace="ctrl.configuration.Namespace"
 | 
				
			||||||
              ></resource-events-datatable>
 | 
					              ></resource-events-datatable>
 | 
				
			||||||
            </uib-tab>
 | 
					            </uib-tab>
 | 
				
			||||||
            <uib-tab index="2" ng-if="ctrl.configuration.Yaml" classes="btn-sm" select="ctrl.showEditor()" data-cy="k8sConfigDetail-yamlTab">
 | 
					            <uib-tab index="2" ng-if="ctrl.configuration.Yaml" classes="btn-sm" select="ctrl.showEditor()" data-cy="k8sConfigDetail-yamlTab">
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -65,7 +65,7 @@
 | 
				
			||||||
              <resource-events-datatable
 | 
					              <resource-events-datatable
 | 
				
			||||||
                resource-id="ctrl.configuration.Id"
 | 
					                resource-id="ctrl.configuration.Id"
 | 
				
			||||||
                storage-key="'kubernetes.secret.events'"
 | 
					                storage-key="'kubernetes.secret.events'"
 | 
				
			||||||
                namespace="ctrl.formValues.ResourcePool.Namespace.Name"
 | 
					                namespace="ctrl.configuration.Namespace"
 | 
				
			||||||
              ></resource-events-datatable>
 | 
					              ></resource-events-datatable>
 | 
				
			||||||
            </uib-tab>
 | 
					            </uib-tab>
 | 
				
			||||||
            <uib-tab index="2" ng-if="ctrl.configuration.Yaml" classes="btn-sm" select="ctrl.showEditor()" data-cy="k8sConfigDetail-yamlTab">
 | 
					            <uib-tab index="2" ng-if="ctrl.configuration.Yaml" classes="btn-sm" select="ctrl.showEditor()" data-cy="k8sConfigDetail-yamlTab">
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,83 @@
 | 
				
			||||||
 | 
					import { render, screen } from '@testing-library/react';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import { withTestQueryProvider } from '@/react/test-utils/withTestQuery';
 | 
				
			||||||
 | 
					import { withTestRouter } from '@/react/test-utils/withRouter';
 | 
				
			||||||
 | 
					import { UserViewModel } from '@/portainer/models/user';
 | 
				
			||||||
 | 
					import { withUserProvider } from '@/react/test-utils/withUserProvider';
 | 
				
			||||||
 | 
					import { TableSettings } from '@/react/kubernetes/datatables/DefaultDatatableSettings';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import { TableState } from '@@/datatables/useTableState';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import { Event } from '../../queries/types';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import { EventsDatatable } from './EventsDatatable';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Mock the necessary hooks and dependencies
 | 
				
			||||||
 | 
					const mockTableState: TableState<TableSettings> = {
 | 
				
			||||||
 | 
					  sortBy: { id: 'Date', desc: true },
 | 
				
			||||||
 | 
					  pageSize: 10,
 | 
				
			||||||
 | 
					  search: '',
 | 
				
			||||||
 | 
					  autoRefreshRate: 0,
 | 
				
			||||||
 | 
					  showSystemResources: false,
 | 
				
			||||||
 | 
					  setSortBy: vi.fn(),
 | 
				
			||||||
 | 
					  setPageSize: vi.fn(),
 | 
				
			||||||
 | 
					  setSearch: vi.fn(),
 | 
				
			||||||
 | 
					  setAutoRefreshRate: vi.fn(),
 | 
				
			||||||
 | 
					  setShowSystemResources: vi.fn(),
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					vi.mock('../../datatables/default-kube-datatable-store', () => ({
 | 
				
			||||||
 | 
					  useKubeStore: () => mockTableState,
 | 
				
			||||||
 | 
					}));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function renderComponent() {
 | 
				
			||||||
 | 
					  const user = new UserViewModel({ Username: 'user' });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const events: Event[] = [
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					      type: 'Warning',
 | 
				
			||||||
 | 
					      name: 'name',
 | 
				
			||||||
 | 
					      message: 'not sure if this what you want to do',
 | 
				
			||||||
 | 
					      namespace: 'default',
 | 
				
			||||||
 | 
					      reason: 'unknown',
 | 
				
			||||||
 | 
					      count: 1,
 | 
				
			||||||
 | 
					      eventTime: new Date('2025-01-02T15:04:05Z'),
 | 
				
			||||||
 | 
					      uid: '4500fc9c-0cc8-4695-b4c4-989ac021d1d6',
 | 
				
			||||||
 | 
					      involvedObject: {
 | 
				
			||||||
 | 
					        kind: 'configMap',
 | 
				
			||||||
 | 
					        uid: '35',
 | 
				
			||||||
 | 
					        name: 'name',
 | 
				
			||||||
 | 
					        namespace: 'default',
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					  ];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const Wrapped = withTestQueryProvider(
 | 
				
			||||||
 | 
					    withUserProvider(
 | 
				
			||||||
 | 
					      withTestRouter(() => (
 | 
				
			||||||
 | 
					        <EventsDatatable
 | 
				
			||||||
 | 
					          dataset={events}
 | 
				
			||||||
 | 
					          tableState={mockTableState}
 | 
				
			||||||
 | 
					          isLoading={false}
 | 
				
			||||||
 | 
					          data-cy="k8sNodeDetail-eventsTable"
 | 
				
			||||||
 | 
					          noWidget
 | 
				
			||||||
 | 
					        />
 | 
				
			||||||
 | 
					      )),
 | 
				
			||||||
 | 
					      user
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					  );
 | 
				
			||||||
 | 
					  return { ...render(<Wrapped />), events };
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					describe('EventsDatatable', () => {
 | 
				
			||||||
 | 
					  it('should display events when data is loaded', async () => {
 | 
				
			||||||
 | 
					    const { events } = renderComponent();
 | 
				
			||||||
 | 
					    const event = events[0];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    expect(screen.getByText(event.message || '')).toBeInTheDocument();
 | 
				
			||||||
 | 
					    expect(screen.getAllByText(event.type || '')).toHaveLength(2);
 | 
				
			||||||
 | 
					    expect(screen.getAllByText(event.involvedObject.kind || '')).toHaveLength(
 | 
				
			||||||
 | 
					      2
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
| 
						 | 
					@ -1,7 +1,7 @@
 | 
				
			||||||
import { Event } from 'kubernetes-types/core/v1';
 | 
					 | 
				
			||||||
import { History } from 'lucide-react';
 | 
					import { History } from 'lucide-react';
 | 
				
			||||||
import { ReactNode } from 'react';
 | 
					import { ReactNode } from 'react';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import { Event } from '@/react/kubernetes/queries/types';
 | 
				
			||||||
import { IndexOptional } from '@/react/kubernetes/configs/types';
 | 
					import { IndexOptional } from '@/react/kubernetes/configs/types';
 | 
				
			||||||
import { TableSettings } from '@/react/kubernetes/datatables/DefaultDatatableSettings';
 | 
					import { TableSettings } from '@/react/kubernetes/datatables/DefaultDatatableSettings';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -38,7 +38,7 @@ export function EventsDatatable({
 | 
				
			||||||
      isLoading={isLoading}
 | 
					      isLoading={isLoading}
 | 
				
			||||||
      title={title}
 | 
					      title={title}
 | 
				
			||||||
      titleIcon={titleIcon}
 | 
					      titleIcon={titleIcon}
 | 
				
			||||||
      getRowId={(row) => row.metadata?.uid || ''}
 | 
					      getRowId={(row) => row.uid || ''}
 | 
				
			||||||
      disableSelect
 | 
					      disableSelect
 | 
				
			||||||
      renderTableSettings={() => (
 | 
					      renderTableSettings={() => (
 | 
				
			||||||
        <TableSettingsMenu>
 | 
					        <TableSettingsMenu>
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -29,9 +29,7 @@ export function ResourceEventsDatatable({
 | 
				
			||||||
    params: { endpointId },
 | 
					    params: { endpointId },
 | 
				
			||||||
  } = useCurrentStateAndParams();
 | 
					  } = useCurrentStateAndParams();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const params = resourceId
 | 
					  const params = resourceId ? { resourceId: `${resourceId}` } : {};
 | 
				
			||||||
    ? { fieldSelector: `involvedObject.uid=${resourceId}` }
 | 
					 | 
				
			||||||
    : {};
 | 
					 | 
				
			||||||
  const resourceEventsQuery = useEvents(endpointId, {
 | 
					  const resourceEventsQuery = useEvents(endpointId, {
 | 
				
			||||||
    namespace,
 | 
					    namespace,
 | 
				
			||||||
    params,
 | 
					    params,
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,5 +1,6 @@
 | 
				
			||||||
import { Row } from '@tanstack/react-table';
 | 
					import { Row } from '@tanstack/react-table';
 | 
				
			||||||
import { Event } from 'kubernetes-types/core/v1';
 | 
					
 | 
				
			||||||
 | 
					import { Event } from '@/react/kubernetes/queries/types';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import { Badge, BadgeType } from '@@/Badge';
 | 
					import { Badge, BadgeType } from '@@/Badge';
 | 
				
			||||||
import { filterHOC } from '@@/datatables/Filter';
 | 
					import { filterHOC } from '@@/datatables/Filter';
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,4 +1,5 @@
 | 
				
			||||||
import { createColumnHelper } from '@tanstack/react-table';
 | 
					import { createColumnHelper } from '@tanstack/react-table';
 | 
				
			||||||
import { Event } from 'kubernetes-types/core/v1';
 | 
					
 | 
				
			||||||
 | 
					import { Event } from '@/react/kubernetes/queries/types';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const columnHelper = createColumnHelper<Event>();
 | 
					export const columnHelper = createColumnHelper<Event>();
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,5 +1,6 @@
 | 
				
			||||||
import { Row } from '@tanstack/react-table';
 | 
					import { Row } from '@tanstack/react-table';
 | 
				
			||||||
import { Event } from 'kubernetes-types/core/v1';
 | 
					
 | 
				
			||||||
 | 
					import { Event } from '@/react/kubernetes/queries/types';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import { filterHOC } from '@@/datatables/Filter';
 | 
					import { filterHOC } from '@@/datatables/Filter';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -184,15 +184,8 @@ describe(
 | 
				
			||||||
        http.get('/api/endpoints/3/kubernetes/helm/test-release/history', () =>
 | 
					        http.get('/api/endpoints/3/kubernetes/helm/test-release/history', () =>
 | 
				
			||||||
          HttpResponse.json(helmReleaseHistory)
 | 
					          HttpResponse.json(helmReleaseHistory)
 | 
				
			||||||
        ),
 | 
					        ),
 | 
				
			||||||
        http.get(
 | 
					        http.get('/api/kubernetes/3/namespaces/default/events', () =>
 | 
				
			||||||
          '/api/endpoints/3/kubernetes/api/v1/namespaces/default/events',
 | 
					          HttpResponse.json([])
 | 
				
			||||||
          () =>
 | 
					 | 
				
			||||||
            HttpResponse.json({
 | 
					 | 
				
			||||||
              kind: 'EventList',
 | 
					 | 
				
			||||||
              apiVersion: 'v1',
 | 
					 | 
				
			||||||
              metadata: { resourceVersion: '12345' },
 | 
					 | 
				
			||||||
              items: [],
 | 
					 | 
				
			||||||
            })
 | 
					 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
      );
 | 
					      );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -236,15 +229,8 @@ describe(
 | 
				
			||||||
          HttpResponse.error()
 | 
					          HttpResponse.error()
 | 
				
			||||||
        ),
 | 
					        ),
 | 
				
			||||||
        // Add mock for events endpoint
 | 
					        // Add mock for events endpoint
 | 
				
			||||||
        http.get(
 | 
					        http.get('/api/kubernetes/3/namespaces/default/events', () =>
 | 
				
			||||||
          '/api/endpoints/3/kubernetes/api/v1/namespaces/default/events',
 | 
					          HttpResponse.json([])
 | 
				
			||||||
          () =>
 | 
					 | 
				
			||||||
            HttpResponse.json({
 | 
					 | 
				
			||||||
              kind: 'EventList',
 | 
					 | 
				
			||||||
              apiVersion: 'v1',
 | 
					 | 
				
			||||||
              metadata: { resourceVersion: '12345' },
 | 
					 | 
				
			||||||
              items: [],
 | 
					 | 
				
			||||||
            })
 | 
					 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
      );
 | 
					      );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -274,15 +260,8 @@ describe(
 | 
				
			||||||
        http.get('/api/endpoints/3/kubernetes/helm/test-release/history', () =>
 | 
					        http.get('/api/endpoints/3/kubernetes/helm/test-release/history', () =>
 | 
				
			||||||
          HttpResponse.json(helmReleaseHistory)
 | 
					          HttpResponse.json(helmReleaseHistory)
 | 
				
			||||||
        ),
 | 
					        ),
 | 
				
			||||||
        http.get(
 | 
					        http.get('/api/kubernetes/3/namespaces/default/events', () =>
 | 
				
			||||||
          '/api/endpoints/3/kubernetes/api/v1/namespaces/default/events',
 | 
					          HttpResponse.json([])
 | 
				
			||||||
          () =>
 | 
					 | 
				
			||||||
            HttpResponse.json({
 | 
					 | 
				
			||||||
              kind: 'EventList',
 | 
					 | 
				
			||||||
              apiVersion: 'v1',
 | 
					 | 
				
			||||||
              metadata: { resourceVersion: '12345' },
 | 
					 | 
				
			||||||
              items: [],
 | 
					 | 
				
			||||||
            })
 | 
					 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
      );
 | 
					      );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,7 +1,7 @@
 | 
				
			||||||
import { render, screen, waitFor } from '@testing-library/react';
 | 
					import { render, screen, waitFor } from '@testing-library/react';
 | 
				
			||||||
import { HttpResponse } from 'msw';
 | 
					import { HttpResponse } from 'msw';
 | 
				
			||||||
import { Event, EventList } from 'kubernetes-types/core/v1';
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import { Event } from '@/react/kubernetes/queries/types';
 | 
				
			||||||
import { server, http } from '@/setup-tests/server';
 | 
					import { server, http } from '@/setup-tests/server';
 | 
				
			||||||
import { withTestQueryProvider } from '@/react/test-utils/withTestQuery';
 | 
					import { withTestQueryProvider } from '@/react/test-utils/withTestQuery';
 | 
				
			||||||
import { withTestRouter } from '@/react/test-utils/withRouter';
 | 
					import { withTestRouter } from '@/react/test-utils/withRouter';
 | 
				
			||||||
| 
						 | 
					@ -56,136 +56,84 @@ const testResources: GenericResource[] = [
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
];
 | 
					];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const mockEventsResponse: EventList = {
 | 
					const mockEventsResponse: Event[] = [
 | 
				
			||||||
  kind: 'EventList',
 | 
					  {
 | 
				
			||||||
  apiVersion: 'v1',
 | 
					    name: 'test-deployment-123456',
 | 
				
			||||||
  metadata: {
 | 
					    namespace: 'default',
 | 
				
			||||||
    resourceVersion: '12345',
 | 
					    reason: 'CreatedLoadBalancer',
 | 
				
			||||||
 | 
					    eventTime: new Date('2023-01-01T00:00:00Z'),
 | 
				
			||||||
 | 
					    uid: 'event-uid-1',
 | 
				
			||||||
 | 
					    involvedObject: {
 | 
				
			||||||
 | 
					      kind: 'Deployment',
 | 
				
			||||||
 | 
					      name: 'test-deployment',
 | 
				
			||||||
 | 
					      uid: 'test-deployment-uid',
 | 
				
			||||||
 | 
					      namespace: 'default',
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    message: 'Scaled up replica set test-deployment-abc123 to 1',
 | 
				
			||||||
 | 
					    firstTimestamp: new Date('2023-01-01T00:00:00Z'),
 | 
				
			||||||
 | 
					    lastTimestamp: new Date('2023-01-01T00:00:00Z'),
 | 
				
			||||||
 | 
					    count: 1,
 | 
				
			||||||
 | 
					    type: 'Normal',
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
  items: [
 | 
					  {
 | 
				
			||||||
    {
 | 
					    name: 'test-service-123456',
 | 
				
			||||||
      metadata: {
 | 
					    namespace: 'default',
 | 
				
			||||||
        name: 'test-deployment-123456',
 | 
					    uid: 'event-uid-2',
 | 
				
			||||||
        namespace: 'default',
 | 
					    eventTime: new Date('2023-01-01T00:00:00Z'),
 | 
				
			||||||
        uid: 'event-uid-1',
 | 
					    involvedObject: {
 | 
				
			||||||
        resourceVersion: '1000',
 | 
					      kind: 'Service',
 | 
				
			||||||
        creationTimestamp: '2023-01-01T00:00:00Z',
 | 
					      namespace: 'default',
 | 
				
			||||||
      },
 | 
					      name: 'test-service',
 | 
				
			||||||
      involvedObject: {
 | 
					      uid: 'test-service-uid',
 | 
				
			||||||
        kind: 'Deployment',
 | 
					 | 
				
			||||||
        namespace: 'default',
 | 
					 | 
				
			||||||
        name: 'test-deployment',
 | 
					 | 
				
			||||||
        uid: 'test-deployment-uid',
 | 
					 | 
				
			||||||
        apiVersion: 'apps/v1',
 | 
					 | 
				
			||||||
        resourceVersion: '2000',
 | 
					 | 
				
			||||||
      },
 | 
					 | 
				
			||||||
      reason: 'ScalingReplicaSet',
 | 
					 | 
				
			||||||
      message: 'Scaled up replica set test-deployment-abc123 to 1',
 | 
					 | 
				
			||||||
      source: {
 | 
					 | 
				
			||||||
        component: 'deployment-controller',
 | 
					 | 
				
			||||||
      },
 | 
					 | 
				
			||||||
      firstTimestamp: '2023-01-01T00:00:00Z',
 | 
					 | 
				
			||||||
      lastTimestamp: '2023-01-01T00:00:00Z',
 | 
					 | 
				
			||||||
      count: 1,
 | 
					 | 
				
			||||||
      type: 'Normal',
 | 
					 | 
				
			||||||
      reportingComponent: 'deployment-controller',
 | 
					 | 
				
			||||||
      reportingInstance: '',
 | 
					 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    {
 | 
					    reason: 'CreatedLoadBalancer',
 | 
				
			||||||
      metadata: {
 | 
					    message: 'Created load balancer',
 | 
				
			||||||
        name: 'test-service-123456',
 | 
					    firstTimestamp: new Date('2023-01-01T00:00:00Z'),
 | 
				
			||||||
        namespace: 'default',
 | 
					    lastTimestamp: new Date('2023-01-01T00:00:00Z'),
 | 
				
			||||||
        uid: 'event-uid-2',
 | 
					    count: 1,
 | 
				
			||||||
        resourceVersion: '1001',
 | 
					    type: 'Normal',
 | 
				
			||||||
        creationTimestamp: '2023-01-01T00:00:00Z',
 | 
					  },
 | 
				
			||||||
      },
 | 
					];
 | 
				
			||||||
      involvedObject: {
 | 
					 | 
				
			||||||
        kind: 'Service',
 | 
					 | 
				
			||||||
        namespace: 'default',
 | 
					 | 
				
			||||||
        name: 'test-service',
 | 
					 | 
				
			||||||
        uid: 'test-service-uid',
 | 
					 | 
				
			||||||
        apiVersion: 'v1',
 | 
					 | 
				
			||||||
        resourceVersion: '2001',
 | 
					 | 
				
			||||||
      },
 | 
					 | 
				
			||||||
      reason: 'CreatedLoadBalancer',
 | 
					 | 
				
			||||||
      message: 'Created load balancer',
 | 
					 | 
				
			||||||
      source: {
 | 
					 | 
				
			||||||
        component: 'service-controller',
 | 
					 | 
				
			||||||
      },
 | 
					 | 
				
			||||||
      firstTimestamp: '2023-01-01T00:00:00Z',
 | 
					 | 
				
			||||||
      lastTimestamp: '2023-01-01T00:00:00Z',
 | 
					 | 
				
			||||||
      count: 1,
 | 
					 | 
				
			||||||
      type: 'Normal',
 | 
					 | 
				
			||||||
      reportingComponent: 'service-controller',
 | 
					 | 
				
			||||||
      reportingInstance: '',
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
  ],
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
const mixedEventsResponse: EventList = {
 | 
					const mixedEventsResponse: Event[] = [
 | 
				
			||||||
  kind: 'EventList',
 | 
					  {
 | 
				
			||||||
  apiVersion: 'v1',
 | 
					    name: 'test-deployment-123456',
 | 
				
			||||||
  metadata: {
 | 
					    namespace: 'default',
 | 
				
			||||||
    resourceVersion: '12345',
 | 
					    uid: 'event-uid-1',
 | 
				
			||||||
 | 
					    eventTime: new Date('2023-01-01T00:00:00Z'),
 | 
				
			||||||
 | 
					    involvedObject: {
 | 
				
			||||||
 | 
					      kind: 'Deployment',
 | 
				
			||||||
 | 
					      namespace: 'default',
 | 
				
			||||||
 | 
					      name: 'test-deployment',
 | 
				
			||||||
 | 
					      uid: 'test-deployment-uid', // This matches a resource UID
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    reason: 'ScalingReplicaSet',
 | 
				
			||||||
 | 
					    message: 'Scaled up replica set test-deployment-abc123 to 1',
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    firstTimestamp: new Date('2023-01-01T00:00:00Z'),
 | 
				
			||||||
 | 
					    lastTimestamp: new Date('2023-01-01T00:00:00Z'),
 | 
				
			||||||
 | 
					    count: 1,
 | 
				
			||||||
 | 
					    type: 'Normal',
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
  items: [
 | 
					  {
 | 
				
			||||||
    {
 | 
					    name: 'unrelated-pod-123456',
 | 
				
			||||||
      metadata: {
 | 
					    namespace: 'default',
 | 
				
			||||||
        name: 'test-deployment-123456',
 | 
					    uid: 'event-uid-3',
 | 
				
			||||||
        namespace: 'default',
 | 
					    eventTime: new Date('2023-01-01T00:00:00Z'),
 | 
				
			||||||
        uid: 'event-uid-1',
 | 
					    involvedObject: {
 | 
				
			||||||
        resourceVersion: '1000',
 | 
					      kind: 'Pod',
 | 
				
			||||||
        creationTimestamp: '2023-01-01T00:00:00Z',
 | 
					      namespace: 'default',
 | 
				
			||||||
      },
 | 
					      name: 'unrelated-pod',
 | 
				
			||||||
      involvedObject: {
 | 
					      uid: 'unrelated-pod-uid', // This does NOT match any resource UIDs
 | 
				
			||||||
        kind: 'Deployment',
 | 
					 | 
				
			||||||
        namespace: 'default',
 | 
					 | 
				
			||||||
        name: 'test-deployment',
 | 
					 | 
				
			||||||
        uid: 'test-deployment-uid', // This matches a resource UID
 | 
					 | 
				
			||||||
        apiVersion: 'apps/v1',
 | 
					 | 
				
			||||||
        resourceVersion: '2000',
 | 
					 | 
				
			||||||
      },
 | 
					 | 
				
			||||||
      reason: 'ScalingReplicaSet',
 | 
					 | 
				
			||||||
      message: 'Scaled up replica set test-deployment-abc123 to 1',
 | 
					 | 
				
			||||||
      source: {
 | 
					 | 
				
			||||||
        component: 'deployment-controller',
 | 
					 | 
				
			||||||
      },
 | 
					 | 
				
			||||||
      firstTimestamp: '2023-01-01T00:00:00Z',
 | 
					 | 
				
			||||||
      lastTimestamp: '2023-01-01T00:00:00Z',
 | 
					 | 
				
			||||||
      count: 1,
 | 
					 | 
				
			||||||
      type: 'Normal',
 | 
					 | 
				
			||||||
      reportingComponent: 'deployment-controller',
 | 
					 | 
				
			||||||
      reportingInstance: '',
 | 
					 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    {
 | 
					    reason: 'Scheduled',
 | 
				
			||||||
      metadata: {
 | 
					    message: 'Successfully assigned unrelated-pod to node',
 | 
				
			||||||
        name: 'unrelated-pod-123456',
 | 
					    type: 'Normal',
 | 
				
			||||||
        namespace: 'default',
 | 
					    firstTimestamp: new Date('2023-01-01T00:00:00Z'),
 | 
				
			||||||
        uid: 'event-uid-3',
 | 
					    lastTimestamp: new Date('2023-01-01T00:00:00Z'),
 | 
				
			||||||
        resourceVersion: '1002',
 | 
					    count: 1,
 | 
				
			||||||
        creationTimestamp: '2023-01-01T00:00:00Z',
 | 
					  },
 | 
				
			||||||
      },
 | 
					];
 | 
				
			||||||
      involvedObject: {
 | 
					 | 
				
			||||||
        kind: 'Pod',
 | 
					 | 
				
			||||||
        namespace: 'default',
 | 
					 | 
				
			||||||
        name: 'unrelated-pod',
 | 
					 | 
				
			||||||
        uid: 'unrelated-pod-uid', // This does NOT match any resource UIDs
 | 
					 | 
				
			||||||
        apiVersion: 'v1',
 | 
					 | 
				
			||||||
        resourceVersion: '2002',
 | 
					 | 
				
			||||||
      },
 | 
					 | 
				
			||||||
      reason: 'Scheduled',
 | 
					 | 
				
			||||||
      message: 'Successfully assigned unrelated-pod to node',
 | 
					 | 
				
			||||||
      source: {
 | 
					 | 
				
			||||||
        component: 'default-scheduler',
 | 
					 | 
				
			||||||
      },
 | 
					 | 
				
			||||||
      firstTimestamp: '2023-01-01T00:00:00Z',
 | 
					 | 
				
			||||||
      lastTimestamp: '2023-01-01T00:00:00Z',
 | 
					 | 
				
			||||||
      count: 1,
 | 
					 | 
				
			||||||
      reportingComponent: 'scheduler',
 | 
					 | 
				
			||||||
      reportingInstance: '',
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
  ],
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
function renderComponent() {
 | 
					function renderComponent() {
 | 
				
			||||||
  const user = new UserViewModel({ Username: 'user' });
 | 
					  const user = new UserViewModel({ Username: 'user' });
 | 
				
			||||||
| 
						 | 
					@ -229,7 +177,7 @@ describe('HelmEventsDatatable', () => {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  it('should correctly filter related events using the filterRelatedEvents function', () => {
 | 
					  it('should correctly filter related events using the filterRelatedEvents function', () => {
 | 
				
			||||||
    const filteredEvents = filterRelatedEvents(
 | 
					    const filteredEvents = filterRelatedEvents(
 | 
				
			||||||
      mixedEventsResponse.items as Event[],
 | 
					      mixedEventsResponse as Event[],
 | 
				
			||||||
      testResources
 | 
					      testResources
 | 
				
			||||||
    );
 | 
					    );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,6 +1,6 @@
 | 
				
			||||||
import { compact } from 'lodash';
 | 
					import { compact } from 'lodash';
 | 
				
			||||||
import { Event } from 'kubernetes-types/core/v1';
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import { Event } from '@/react/kubernetes/queries/types';
 | 
				
			||||||
import { createStore } from '@/react/kubernetes/datatables/default-kube-datatable-store';
 | 
					import { createStore } from '@/react/kubernetes/datatables/default-kube-datatable-store';
 | 
				
			||||||
import { EventsDatatable } from '@/react/kubernetes/components/EventsDatatable';
 | 
					import { EventsDatatable } from '@/react/kubernetes/components/EventsDatatable';
 | 
				
			||||||
import { useEvents } from '@/react/kubernetes/queries/useEvents';
 | 
					import { useEvents } from '@/react/kubernetes/queries/useEvents';
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,19 @@
 | 
				
			||||||
 | 
					export type Event = {
 | 
				
			||||||
 | 
					  type: string;
 | 
				
			||||||
 | 
					  name: string;
 | 
				
			||||||
 | 
					  reason: string;
 | 
				
			||||||
 | 
					  message: string;
 | 
				
			||||||
 | 
					  namespace: string;
 | 
				
			||||||
 | 
					  eventTime: Date;
 | 
				
			||||||
 | 
					  kind?: string;
 | 
				
			||||||
 | 
					  count: number;
 | 
				
			||||||
 | 
					  lastTimestamp?: Date;
 | 
				
			||||||
 | 
					  firstTimestamp?: Date;
 | 
				
			||||||
 | 
					  uid: string;
 | 
				
			||||||
 | 
					  involvedObject: {
 | 
				
			||||||
 | 
					    uid: string;
 | 
				
			||||||
 | 
					    kind?: string;
 | 
				
			||||||
 | 
					    name: string;
 | 
				
			||||||
 | 
					    namespace: string;
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
| 
						 | 
					@ -1,6 +1,6 @@
 | 
				
			||||||
import { EventList, Event } from 'kubernetes-types/core/v1';
 | 
					 | 
				
			||||||
import { useQuery } from '@tanstack/react-query';
 | 
					import { useQuery } from '@tanstack/react-query';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import { Event } from '@/react/kubernetes/queries/types';
 | 
				
			||||||
import { EnvironmentId } from '@/react/portainer/environments/types';
 | 
					import { EnvironmentId } from '@/react/portainer/environments/types';
 | 
				
			||||||
import axios from '@/portainer/services/axios';
 | 
					import axios from '@/portainer/services/axios';
 | 
				
			||||||
import { withGlobalError } from '@/react-tools/react-query';
 | 
					import { withGlobalError } from '@/react-tools/react-query';
 | 
				
			||||||
| 
						 | 
					@ -13,10 +13,7 @@ type RequestOptions = {
 | 
				
			||||||
  /** if undefined, events are fetched at the cluster scope */
 | 
					  /** if undefined, events are fetched at the cluster scope */
 | 
				
			||||||
  namespace?: string;
 | 
					  namespace?: string;
 | 
				
			||||||
  params?: {
 | 
					  params?: {
 | 
				
			||||||
    /** https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/#label-selectors */
 | 
					    resourceId?: string;
 | 
				
			||||||
    labelSelector?: string;
 | 
					 | 
				
			||||||
    /** https://kubernetes.io/docs/concepts/overview/working-with-objects/field-selectors */
 | 
					 | 
				
			||||||
    fieldSelector?: string;
 | 
					 | 
				
			||||||
  };
 | 
					  };
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -44,13 +41,13 @@ async function getEvents(
 | 
				
			||||||
): Promise<Event[]> {
 | 
					): Promise<Event[]> {
 | 
				
			||||||
  const { namespace, params } = options ?? {};
 | 
					  const { namespace, params } = options ?? {};
 | 
				
			||||||
  try {
 | 
					  try {
 | 
				
			||||||
    const { data } = await axios.get<EventList>(
 | 
					    const { data } = await axios.get<Event[]>(
 | 
				
			||||||
      buildUrl(environmentId, namespace),
 | 
					      buildUrl(environmentId, namespace),
 | 
				
			||||||
      {
 | 
					      {
 | 
				
			||||||
        params,
 | 
					        params,
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    );
 | 
					    );
 | 
				
			||||||
    return data.items;
 | 
					    return data;
 | 
				
			||||||
  } catch (e) {
 | 
					  } catch (e) {
 | 
				
			||||||
    throw parseKubernetesAxiosError(e, 'Unable to retrieve events');
 | 
					    throw parseKubernetesAxiosError(e, 'Unable to retrieve events');
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
| 
						 | 
					@ -96,6 +93,6 @@ export function useEventWarningsCount(
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function buildUrl(environmentId: EnvironmentId, namespace?: string) {
 | 
					function buildUrl(environmentId: EnvironmentId, namespace?: string) {
 | 
				
			||||||
  return namespace
 | 
					  return namespace
 | 
				
			||||||
    ? `/endpoints/${environmentId}/kubernetes/api/v1/namespaces/${namespace}/events`
 | 
					    ? `/kubernetes/${environmentId}/namespaces/${namespace}/events`
 | 
				
			||||||
    : `/endpoints/${environmentId}/kubernetes/api/v1/events`;
 | 
					    : `/kubernetes/${environmentId}/events`;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
		Reference in New Issue