diff --git a/app/kubernetes/components/datatables/events-datatable/eventsDatatable.html b/app/kubernetes/components/datatables/events-datatable/eventsDatatable.html deleted file mode 100644 index e68ca35ec..000000000 --- a/app/kubernetes/components/datatables/events-datatable/eventsDatatable.html +++ /dev/null @@ -1,150 +0,0 @@ -
-
-
-
- -
- {{ $ctrl.titleText }} -
- -
- - - - -
-
-
- - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - -
{{ item.Date | getisodate }}{{ item.Involved.kind }}{{ item.Type }}{{ item.Message }}
Loading...
No event available.
-
- -
diff --git a/app/kubernetes/components/datatables/events-datatable/eventsDatatable.js b/app/kubernetes/components/datatables/events-datatable/eventsDatatable.js deleted file mode 100644 index 09a99976e..000000000 --- a/app/kubernetes/components/datatables/events-datatable/eventsDatatable.js +++ /dev/null @@ -1,13 +0,0 @@ -angular.module('portainer.kubernetes').component('kubernetesEventsDatatable', { - templateUrl: './eventsDatatable.html', - controller: 'GenericDatatableController', - bindings: { - titleText: '@', - dataset: '<', - tableKey: '@', - orderBy: '@', - reverseOrder: '<', - loading: '<', - refreshCallback: '<', - }, -}); diff --git a/app/kubernetes/react/components/clusterManagement.ts b/app/kubernetes/react/components/clusterManagement.ts index 72440fc7a..8132ea80e 100644 --- a/app/kubernetes/react/components/clusterManagement.ts +++ b/app/kubernetes/react/components/clusterManagement.ts @@ -4,6 +4,8 @@ import { r2a } from '@/react-tools/react2angular'; import { withUIRouter } from '@/react-tools/withUIRouter'; import { withCurrentUser } from '@/react-tools/withCurrentUser'; import { NodeApplicationsDatatable } from '@/react/kubernetes/cluster/NodeView/NodeApplicationsDatatable/NodeApplicationsDatatable'; +import { ResourceEventsDatatable } from '@/react/kubernetes/components/EventsDatatable/ResourceEventsDatatable'; +import { withReactQuery } from '@/react-tools/withReactQuery'; export const clusterManagementModule = angular .module('portainer.kubernetes.react.components.clusterManagement', []) @@ -14,4 +16,11 @@ export const clusterManagementModule = angular 'isLoading', 'onRefresh', ]) + ) + .component( + 'resourceEventsDatatable', + r2a( + withUIRouter(withReactQuery(withCurrentUser(ResourceEventsDatatable))), + ['resourceId', 'storageKey', 'namespace'] + ) ).name; diff --git a/app/kubernetes/views/cluster/node/node.html b/app/kubernetes/views/cluster/node/node.html index 7055f0d6b..6610aa8ae 100644 --- a/app/kubernetes/views/cluster/node/node.html +++ b/app/kubernetes/views/cluster/node/node.html @@ -251,16 +251,7 @@ {{ ctrl.state.eventWarningCount }} warning(s) - - + diff --git a/app/kubernetes/views/cluster/node/nodeController.js b/app/kubernetes/views/cluster/node/nodeController.js index 2c4b3daa4..db392d282 100644 --- a/app/kubernetes/views/cluster/node/nodeController.js +++ b/app/kubernetes/views/cluster/node/nodeController.js @@ -320,7 +320,7 @@ class KubernetesNodeController { try { this.state.eventsLoading = true; const events = await this.KubernetesEventService.get(); - this.events = events.filter((item) => item.Involved.kind === 'Node'); + this.events = events.filter((item) => item.Involved.uid === this.node.Id); this.state.eventWarningCount = KubernetesEventHelper.warningCount(this.events); } catch (err) { this.Notifications.error('Failure', err, 'Unable to retrieve node events'); diff --git a/app/kubernetes/views/configurations/configmap/edit/configMap.html b/app/kubernetes/views/configurations/configmap/edit/configMap.html index fdd9b1151..2fa61285d 100644 --- a/app/kubernetes/views/configurations/configmap/edit/configMap.html +++ b/app/kubernetes/views/configurations/configmap/edit/configMap.html @@ -55,17 +55,11 @@ {{ ctrl.state.eventWarningCount }} warning(s) - - + diff --git a/app/kubernetes/views/configurations/secret/edit/secret.html b/app/kubernetes/views/configurations/secret/edit/secret.html index e0c0d43b5..856e59846 100644 --- a/app/kubernetes/views/configurations/secret/edit/secret.html +++ b/app/kubernetes/views/configurations/secret/edit/secret.html @@ -62,17 +62,11 @@ {{ ctrl.state.eventWarningCount }} warning(s) - - + diff --git a/app/kubernetes/views/resource-pools/edit/resourcePool.html b/app/kubernetes/views/resource-pools/edit/resourcePool.html index f893c72ff..134f6d6e9 100644 --- a/app/kubernetes/views/resource-pools/edit/resourcePool.html +++ b/app/kubernetes/views/resource-pools/edit/resourcePool.html @@ -286,15 +286,7 @@ {{ ctrl.state.eventWarningCount }} warning(s) - + YAML diff --git a/app/kubernetes/views/volumes/edit/volume.html b/app/kubernetes/views/volumes/edit/volume.html index 2da384fe6..7134016f4 100644 --- a/app/kubernetes/views/volumes/edit/volume.html +++ b/app/kubernetes/views/volumes/edit/volume.html @@ -156,17 +156,12 @@ {{ ctrl.state.eventWarningCount }} warning(s) - - + diff --git a/app/react/kubernetes/applications/DetailsView/ApplicationDetailsView.tsx b/app/react/kubernetes/applications/DetailsView/ApplicationDetailsView.tsx index 918bf0091..5f343c29d 100644 --- a/app/react/kubernetes/applications/DetailsView/ApplicationDetailsView.tsx +++ b/app/react/kubernetes/applications/DetailsView/ApplicationDetailsView.tsx @@ -8,7 +8,7 @@ import { Tab, WidgetTabs, findSelectedTabIndex } from '@@/Widget/WidgetTabs'; import { Icon } from '@@/Icon'; import { Badge } from '@@/Badge'; -import { EventsDatatable } from '../../components/KubernetesEventsDatatable'; +import { EventsDatatable } from '../../components/EventsDatatable'; import { PlacementsDatatable, diff --git a/app/react/kubernetes/applications/DetailsView/ApplicationEventsDatatable.tsx b/app/react/kubernetes/applications/DetailsView/ApplicationEventsDatatable.tsx index 2f36d7934..f2046d55c 100644 --- a/app/react/kubernetes/applications/DetailsView/ApplicationEventsDatatable.tsx +++ b/app/react/kubernetes/applications/DetailsView/ApplicationEventsDatatable.tsx @@ -2,6 +2,7 @@ import { useCurrentStateAndParams } from '@uirouter/react'; import { useMemo } from 'react'; import { createStore } from '@/react/kubernetes/datatables/default-kube-datatable-store'; +import { EnvironmentId } from '@/react/portainer/environments/types'; import { useTableState } from '@@/datatables/useTableState'; @@ -10,9 +11,9 @@ import { useApplicationPods, useApplicationServices, } from '../application.queries'; -import { EventsDatatable } from '../../components/KubernetesEventsDatatable'; - -import { useNamespaceEventsQuery } from './useNamespaceEventsQuery'; +import { EventsDatatable } from '../../components/EventsDatatable'; +import { useEvents } from '../../queries/useEvents'; +import { AppKind } from '../types'; const storageKey = 'k8sAppEventsDatatable'; const settingsStore = createStore(storageKey, { id: 'Date', desc: true }); @@ -23,41 +24,73 @@ export function ApplicationEventsDatatable() { params: { namespace, name, - 'resource-type': resourceType, + 'resource-type': appKind, endpointId: environmentId, }, } = useCurrentStateAndParams(); + const { relatedEvents, isInitialLoading } = useApplicationEvents( + environmentId, + namespace, + name, + appKind, + { + autoRefreshRate: tableState.autoRefreshRate, + } + ); + + return ( + + ); +} + +export function useApplicationEvents( + environmentId: EnvironmentId, + namespace: string, + name: string, + appKind?: AppKind, + options?: { autoRefreshRate?: number; yaml?: boolean } +) { const { data: application, ...applicationQuery } = useApplication( environmentId, namespace, name, - resourceType + appKind ); - const { data: services, ...servicesQuery } = useApplicationServices( + const servicesQuery = useApplicationServices( environmentId, namespace, name, application ); - const { data: pods, ...podsQuery } = useApplicationPods( + const podsQuery = useApplicationPods( environmentId, namespace, name, application ); - const { data: events, ...eventsQuery } = useNamespaceEventsQuery( - environmentId, + + const { data: events, ...eventsQuery } = useEvents(environmentId, { namespace, - { - autoRefreshRate: tableState.autoRefreshRate * 1000, - } - ); + queryOptions: { + autoRefreshRate: options?.autoRefreshRate + ? options.autoRefreshRate * 1000 + : undefined, + }, + }); // related events are events that have the application id, or the id of a service or pod from the application const relatedEvents = useMemo(() => { - const serviceIds = services?.map((service) => service?.metadata?.uid); - const podIds = pods?.map((pod) => pod?.metadata?.uid); + const serviceIds = servicesQuery.data?.map( + (service) => service?.metadata?.uid + ); + const podIds = podsQuery.data?.map((pod) => pod?.metadata?.uid); return ( events?.filter( (event) => @@ -66,20 +99,13 @@ export function ApplicationEventsDatatable() { podIds?.includes(event.involvedObject.uid) ) || [] ); - }, [application?.metadata?.uid, events, pods, services]); + }, [application?.metadata?.uid, events, podsQuery.data, servicesQuery.data]); - return ( - - ); + const isInitialLoading = + applicationQuery.isInitialLoading || + servicesQuery.isInitialLoading || + podsQuery.isInitialLoading || + eventsQuery.isInitialLoading; + + return { relatedEvents, isInitialLoading }; } diff --git a/app/react/kubernetes/applications/DetailsView/useApplicationEventsTableData.tsx b/app/react/kubernetes/applications/DetailsView/useApplicationEventsTableData.tsx index 1c2ab95e5..9ab709b5d 100644 --- a/app/react/kubernetes/applications/DetailsView/useApplicationEventsTableData.tsx +++ b/app/react/kubernetes/applications/DetailsView/useApplicationEventsTableData.tsx @@ -10,8 +10,7 @@ import { useApplicationPods, useApplicationServices, } from '../application.queries'; - -import { useNamespaceEventsQuery } from './useNamespaceEventsQuery'; +import { useEvents } from '../../queries/useEvents'; const storageKey = 'k8sAppEventsDatatable'; const settingsStore = createStore(storageKey, { id: 'Date', desc: true }); @@ -49,13 +48,12 @@ export function useApplicationEventsTableData() { name, application ); - const { data: events, ...eventsQuery } = useNamespaceEventsQuery( - environmentId, + const { data: events, ...eventsQuery } = useEvents(environmentId, { namespace, - { + queryOptions: { autoRefreshRate: appEventsTableState.autoRefreshRate * 1000, - } - ); + }, + }); // related events are events that have the application id, or the id of a service or pod from the application const appEventsData = useMemo(() => { diff --git a/app/react/kubernetes/applications/DetailsView/useNamespaceEventsQuery.ts b/app/react/kubernetes/applications/DetailsView/useNamespaceEventsQuery.ts deleted file mode 100644 index 56a711b77..000000000 --- a/app/react/kubernetes/applications/DetailsView/useNamespaceEventsQuery.ts +++ /dev/null @@ -1,53 +0,0 @@ -import { EventList } from 'kubernetes-types/core/v1'; -import { useQuery } from '@tanstack/react-query'; - -import { EnvironmentId } from '@/react/portainer/environments/types'; -import axios from '@/portainer/services/axios'; -import { withError } from '@/react-tools/react-query'; - -import { parseKubernetesAxiosError } from '../../axiosError'; - -async function getNamespaceEvents( - environmentId: EnvironmentId, - namespace: string, - labelSelector?: string -) { - try { - const { data } = await axios.get( - `/endpoints/${environmentId}/kubernetes/api/v1/namespaces/${namespace}/events`, - { - params: { - labelSelector, - }, - } - ); - return data.items; - } catch (e) { - throw parseKubernetesAxiosError(e, 'Unable to retrieve events'); - } -} - -export function useNamespaceEventsQuery( - environmentId: EnvironmentId, - namespace: string, - options?: { autoRefreshRate?: number }, - labelSelector?: string -) { - return useQuery( - [ - 'environments', - environmentId, - 'kubernetes', - 'events', - namespace, - labelSelector, - ], - () => getNamespaceEvents(environmentId, namespace, labelSelector), - { - ...withError('Unable to retrieve events'), - refetchInterval() { - return options?.autoRefreshRate ?? false; - }, - } - ); -} diff --git a/app/react/kubernetes/components/KubernetesEventsDatatable/EventsDatatable.tsx b/app/react/kubernetes/components/EventsDatatable/EventsDatatable.tsx similarity index 100% rename from app/react/kubernetes/components/KubernetesEventsDatatable/EventsDatatable.tsx rename to app/react/kubernetes/components/EventsDatatable/EventsDatatable.tsx diff --git a/app/react/kubernetes/components/EventsDatatable/ResourceEventsDatatable.tsx b/app/react/kubernetes/components/EventsDatatable/ResourceEventsDatatable.tsx new file mode 100644 index 000000000..d94d4a3b5 --- /dev/null +++ b/app/react/kubernetes/components/EventsDatatable/ResourceEventsDatatable.tsx @@ -0,0 +1,53 @@ +import { useCurrentStateAndParams } from '@uirouter/react'; + +import { useKubeStore } from '@/react/kubernetes/datatables/default-kube-datatable-store'; +import { useEvents } from '@/react/kubernetes/queries/useEvents'; +import { EventsDatatable } from '@/react/kubernetes/components/EventsDatatable'; + +type Props = { + storageKey: string; + /** if undefined, all resources for the namespace (or cluster are returned) */ + resourceId?: string; + /** if undefined, events are fetched for the cluster */ + namespace?: string; +}; + +/** ResourceEventsDatatable returns the EventsDatatable for all events that relate to a specific resource id */ +export function ResourceEventsDatatable({ + storageKey, + resourceId, + namespace, +}: Props) { + const tableState = useKubeStore(storageKey, { + id: 'Date', + desc: true, + }); + + const { + params: { endpointId }, + } = useCurrentStateAndParams(); + + const params = resourceId + ? { fieldSelector: `involvedObject.uid=${resourceId}` } + : {}; + const resourceEventsQuery = useEvents(endpointId, { + namespace, + params, + queryOptions: { + autoRefreshRate: tableState.autoRefreshRate + ? tableState.autoRefreshRate * 1000 + : undefined, + }, + }); + const nodeEvents = resourceEventsQuery.data || []; + + return ( + + ); +} diff --git a/app/react/kubernetes/components/KubernetesEventsDatatable/columns/date.tsx b/app/react/kubernetes/components/EventsDatatable/columns/date.tsx similarity index 100% rename from app/react/kubernetes/components/KubernetesEventsDatatable/columns/date.tsx rename to app/react/kubernetes/components/EventsDatatable/columns/date.tsx diff --git a/app/react/kubernetes/components/KubernetesEventsDatatable/columns/eventType.tsx b/app/react/kubernetes/components/EventsDatatable/columns/eventType.tsx similarity index 100% rename from app/react/kubernetes/components/KubernetesEventsDatatable/columns/eventType.tsx rename to app/react/kubernetes/components/EventsDatatable/columns/eventType.tsx diff --git a/app/react/kubernetes/components/KubernetesEventsDatatable/columns/helper.ts b/app/react/kubernetes/components/EventsDatatable/columns/helper.ts similarity index 100% rename from app/react/kubernetes/components/KubernetesEventsDatatable/columns/helper.ts rename to app/react/kubernetes/components/EventsDatatable/columns/helper.ts diff --git a/app/react/kubernetes/components/KubernetesEventsDatatable/columns/index.ts b/app/react/kubernetes/components/EventsDatatable/columns/index.ts similarity index 100% rename from app/react/kubernetes/components/KubernetesEventsDatatable/columns/index.ts rename to app/react/kubernetes/components/EventsDatatable/columns/index.ts diff --git a/app/react/kubernetes/components/KubernetesEventsDatatable/columns/kind.tsx b/app/react/kubernetes/components/EventsDatatable/columns/kind.tsx similarity index 100% rename from app/react/kubernetes/components/KubernetesEventsDatatable/columns/kind.tsx rename to app/react/kubernetes/components/EventsDatatable/columns/kind.tsx diff --git a/app/react/kubernetes/components/KubernetesEventsDatatable/columns/message.tsx b/app/react/kubernetes/components/EventsDatatable/columns/message.tsx similarity index 100% rename from app/react/kubernetes/components/KubernetesEventsDatatable/columns/message.tsx rename to app/react/kubernetes/components/EventsDatatable/columns/message.tsx diff --git a/app/react/kubernetes/components/KubernetesEventsDatatable/index.ts b/app/react/kubernetes/components/EventsDatatable/index.ts similarity index 100% rename from app/react/kubernetes/components/KubernetesEventsDatatable/index.ts rename to app/react/kubernetes/components/EventsDatatable/index.ts diff --git a/app/react/kubernetes/queries/query-keys.ts b/app/react/kubernetes/queries/query-keys.ts new file mode 100644 index 000000000..ff225ac3f --- /dev/null +++ b/app/react/kubernetes/queries/query-keys.ts @@ -0,0 +1,5 @@ +/** Kubernetes environment base query keys */ +export const queryKeys = { + base: (environmentId: number) => + ['environments', environmentId, 'kubernetes'] as const, +}; diff --git a/app/react/kubernetes/queries/useEvents.ts b/app/react/kubernetes/queries/useEvents.ts new file mode 100644 index 000000000..870a468ca --- /dev/null +++ b/app/react/kubernetes/queries/useEvents.ts @@ -0,0 +1,86 @@ +import { EventList } from 'kubernetes-types/core/v1'; +import { useQuery } from '@tanstack/react-query'; + +import { EnvironmentId } from '@/react/portainer/environments/types'; +import axios from '@/portainer/services/axios'; +import { withError } from '@/react-tools/react-query'; + +import { parseKubernetesAxiosError } from '../axiosError'; + +import { queryKeys as environmentQueryKeys } from './query-keys'; + +type RequestOptions = { + /** if undefined, events are fetched at the cluster scope */ + namespace?: string; + params?: { + /** https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/#label-selectors */ + labelSelector?: string; + /** https://kubernetes.io/docs/concepts/overview/working-with-objects/field-selectors */ + fieldSelector?: string; + }; +}; + +const queryKeys = { + base: (environmentId: number, { namespace, params }: RequestOptions) => { + if (namespace) { + return [ + ...environmentQueryKeys.base(environmentId), + 'events', + namespace, + params, + ] as const; + } + return [ + ...environmentQueryKeys.base(environmentId), + 'events', + params, + ] as const; + }, +}; + +async function getEvents( + environmentId: EnvironmentId, + options?: RequestOptions +) { + const { namespace, params } = options ?? {}; + try { + const { data } = await axios.get( + buildUrl(environmentId, namespace), + { + params, + } + ); + return data.items; + } catch (e) { + throw parseKubernetesAxiosError(e, 'Unable to retrieve events'); + } +} + +type QueryOptions = { + queryOptions?: { + autoRefreshRate?: number; + }; +} & RequestOptions; + +export function useEvents( + environmentId: EnvironmentId, + options?: QueryOptions +) { + const { queryOptions, params, namespace } = options ?? {}; + return useQuery( + queryKeys.base(environmentId, { params, namespace }), + () => getEvents(environmentId, { params, namespace }), + { + ...withError('Unable to retrieve events'), + refetchInterval() { + return queryOptions?.autoRefreshRate ?? false; + }, + } + ); +} + +function buildUrl(environmentId: EnvironmentId, namespace?: string) { + return namespace + ? `/endpoints/${environmentId}/kubernetes/api/v1/namespaces/${namespace}/events` + : `/endpoints/${environmentId}/kubernetes/api/v1/events`; +}