From 813ca8bc230268d8904a786b18da8659045b18ce Mon Sep 17 00:00:00 2001 From: Amlan Kumar Nandy <45410599+amlannandy@users.noreply.github.com> Date: Mon, 27 Jan 2025 22:22:48 +0530 Subject: [PATCH] feat: statefulsets, daemonsets, jobs and volumes implementation in k8s infra monitoring (#6629) --- .../infraMonitoring/getK8sDaemonSetsList.ts | 70 ++ .../src/api/infraMonitoring/getK8sJobsList.ts | 72 ++ .../api/infraMonitoring/getK8sVolumesList.ts | 71 ++ .../getsK8sStatefulSetsList.ts | 69 ++ frontend/src/constants/reactQueryKeys.ts | 4 + .../DaemonSetDetails.interfaces.ts | 7 + .../DaemonSetDetails/DaemonSetDetails.tsx | 593 +++++++++++ .../DaemonSets/DaemonSetDetails/constants.ts | 648 ++++++++++++ .../DaemonSets/DaemonSetDetails/index.ts | 3 + .../DaemonSets/K8sDaemonSetsList.styles.scss | 62 ++ .../DaemonSets/K8sDaemonSetsList.tsx | 516 ++++++++++ .../InfraMonitoringK8s/DaemonSets/utils.tsx | 345 +++++++ .../InfraMonitoringK8s/Deployments/utils.tsx | 24 +- .../entityDetails.styles.scss | 2 +- .../EntityDetailsUtils/utils.tsx | 3 + .../InfraMonitoringK8s/InfraMonitoringK8s.tsx | 224 +++-- .../Jobs/JobDetails/JobDetails.interfaces.ts | 7 + .../Jobs/JobDetails/JobDetails.tsx | 573 +++++++++++ .../Jobs/JobDetails/constants.ts | 424 ++++++++ .../Jobs/JobDetails/index.ts | 3 + .../Jobs/K8sJobsList.styles.scss | 62 ++ .../InfraMonitoringK8s/Jobs/K8sJobsList.tsx | 501 ++++++++++ .../InfraMonitoringK8s/Jobs/utils.tsx | 382 +++++++ .../K8sFiltersSidePanel.styles.scss | 12 + .../K8sStatefulSetsList.styles.scss | 62 ++ .../StatefulSets/K8sStatefulSetsList.tsx | 518 ++++++++++ .../StatefulSetDetails.interfaces.ts | 7 + .../StatefulSetDetails/StatefulSetDetails.tsx | 588 +++++++++++ .../StatefulSetDetails/constants.ts | 946 ++++++++++++++++++ .../StatefulSets/StatefulSetDetails/index.ts | 3 + .../InfraMonitoringK8s/StatefulSets/utils.tsx | 347 +++++++ .../Volumes/K8sVolumesList.styles.scss | 45 + .../Volumes/K8sVolumesList.tsx | 508 ++++++++++ .../Volumes/VolumeDetails/VolumeDetails.tsx | 196 ++++ .../VolumeDetails/VoumeDetails.interfaces.ts | 7 + .../Volumes/VolumeDetails/constants.ts | 595 +++++++++++ .../Volumes/VolumeDetails/index.ts | 3 + .../InfraMonitoringK8s/Volumes/utils.tsx | 202 ++++ .../container/InfraMonitoringK8s/constants.ts | 146 ++- .../useGetK8sDaemonSetsList.ts | 54 + .../infraMonitoring/useGetK8sJobsList.ts | 51 + .../useGetK8sStatefulSetsList.ts | 54 + .../infraMonitoring/useGetK8sVolumesList.ts | 48 + .../app/inframetrics/daemonsets.go | 2 +- .../app/inframetrics/deployments.go | 2 +- pkg/query-service/app/inframetrics/jobs.go | 26 +- .../app/inframetrics/statefulsets.go | 2 +- .../app/metrics/v4/helpers/sub_query.go | 5 +- pkg/query-service/model/infra.go | 2 +- 49 files changed, 8968 insertions(+), 128 deletions(-) create mode 100644 frontend/src/api/infraMonitoring/getK8sDaemonSetsList.ts create mode 100644 frontend/src/api/infraMonitoring/getK8sJobsList.ts create mode 100644 frontend/src/api/infraMonitoring/getK8sVolumesList.ts create mode 100644 frontend/src/api/infraMonitoring/getsK8sStatefulSetsList.ts create mode 100644 frontend/src/container/InfraMonitoringK8s/DaemonSets/DaemonSetDetails/DaemonSetDetails.interfaces.ts create mode 100644 frontend/src/container/InfraMonitoringK8s/DaemonSets/DaemonSetDetails/DaemonSetDetails.tsx create mode 100644 frontend/src/container/InfraMonitoringK8s/DaemonSets/DaemonSetDetails/constants.ts create mode 100644 frontend/src/container/InfraMonitoringK8s/DaemonSets/DaemonSetDetails/index.ts create mode 100644 frontend/src/container/InfraMonitoringK8s/DaemonSets/K8sDaemonSetsList.styles.scss create mode 100644 frontend/src/container/InfraMonitoringK8s/DaemonSets/K8sDaemonSetsList.tsx create mode 100644 frontend/src/container/InfraMonitoringK8s/DaemonSets/utils.tsx create mode 100644 frontend/src/container/InfraMonitoringK8s/Jobs/JobDetails/JobDetails.interfaces.ts create mode 100644 frontend/src/container/InfraMonitoringK8s/Jobs/JobDetails/JobDetails.tsx create mode 100644 frontend/src/container/InfraMonitoringK8s/Jobs/JobDetails/constants.ts create mode 100644 frontend/src/container/InfraMonitoringK8s/Jobs/JobDetails/index.ts create mode 100644 frontend/src/container/InfraMonitoringK8s/Jobs/K8sJobsList.styles.scss create mode 100644 frontend/src/container/InfraMonitoringK8s/Jobs/K8sJobsList.tsx create mode 100644 frontend/src/container/InfraMonitoringK8s/Jobs/utils.tsx create mode 100644 frontend/src/container/InfraMonitoringK8s/StatefulSets/K8sStatefulSetsList.styles.scss create mode 100644 frontend/src/container/InfraMonitoringK8s/StatefulSets/K8sStatefulSetsList.tsx create mode 100644 frontend/src/container/InfraMonitoringK8s/StatefulSets/StatefulSetDetails/StatefulSetDetails.interfaces.ts create mode 100644 frontend/src/container/InfraMonitoringK8s/StatefulSets/StatefulSetDetails/StatefulSetDetails.tsx create mode 100644 frontend/src/container/InfraMonitoringK8s/StatefulSets/StatefulSetDetails/constants.ts create mode 100644 frontend/src/container/InfraMonitoringK8s/StatefulSets/StatefulSetDetails/index.ts create mode 100644 frontend/src/container/InfraMonitoringK8s/StatefulSets/utils.tsx create mode 100644 frontend/src/container/InfraMonitoringK8s/Volumes/K8sVolumesList.styles.scss create mode 100644 frontend/src/container/InfraMonitoringK8s/Volumes/K8sVolumesList.tsx create mode 100644 frontend/src/container/InfraMonitoringK8s/Volumes/VolumeDetails/VolumeDetails.tsx create mode 100644 frontend/src/container/InfraMonitoringK8s/Volumes/VolumeDetails/VoumeDetails.interfaces.ts create mode 100644 frontend/src/container/InfraMonitoringK8s/Volumes/VolumeDetails/constants.ts create mode 100644 frontend/src/container/InfraMonitoringK8s/Volumes/VolumeDetails/index.ts create mode 100644 frontend/src/container/InfraMonitoringK8s/Volumes/utils.tsx create mode 100644 frontend/src/hooks/infraMonitoring/useGetK8sDaemonSetsList.ts create mode 100644 frontend/src/hooks/infraMonitoring/useGetK8sJobsList.ts create mode 100644 frontend/src/hooks/infraMonitoring/useGetK8sStatefulSetsList.ts create mode 100644 frontend/src/hooks/infraMonitoring/useGetK8sVolumesList.ts diff --git a/frontend/src/api/infraMonitoring/getK8sDaemonSetsList.ts b/frontend/src/api/infraMonitoring/getK8sDaemonSetsList.ts new file mode 100644 index 0000000000..c09de10580 --- /dev/null +++ b/frontend/src/api/infraMonitoring/getK8sDaemonSetsList.ts @@ -0,0 +1,70 @@ +import axios from 'api'; +import { ErrorResponseHandler } from 'api/ErrorResponseHandler'; +import { AxiosError } from 'axios'; +import { ErrorResponse, SuccessResponse } from 'types/api'; +import { BaseAutocompleteData } from 'types/api/queryBuilder/queryAutocompleteResponse'; +import { TagFilter } from 'types/api/queryBuilder/queryBuilderData'; + +export interface K8sDaemonSetsListPayload { + filters: TagFilter; + groupBy?: BaseAutocompleteData[]; + offset?: number; + limit?: number; + orderBy?: { + columnName: string; + order: 'asc' | 'desc'; + }; +} + +export interface K8sDaemonSetsData { + daemonSetName: string; + cpuUsage: number; + memoryUsage: number; + cpuRequest: number; + memoryRequest: number; + cpuLimit: number; + memoryLimit: number; + restarts: number; + desiredNodes: number; + availableNodes: number; + meta: { + k8s_cluster_name: string; + k8s_daemonset_name: string; + k8s_namespace_name: string; + }; +} + +export interface K8sDaemonSetsListResponse { + status: string; + data: { + type: string; + records: K8sDaemonSetsData[]; + groups: null; + total: number; + sentAnyHostMetricsData: boolean; + isSendingK8SAgentMetrics: boolean; + }; +} + +export const getK8sDaemonSetsList = async ( + props: K8sDaemonSetsListPayload, + signal?: AbortSignal, + headers?: Record, +): Promise | ErrorResponse> => { + try { + const response = await axios.post('/daemonsets/list', props, { + signal, + headers, + }); + + return { + statusCode: 200, + error: null, + message: 'Success', + payload: response.data, + params: props, + }; + } catch (error) { + return ErrorResponseHandler(error as AxiosError); + } +}; diff --git a/frontend/src/api/infraMonitoring/getK8sJobsList.ts b/frontend/src/api/infraMonitoring/getK8sJobsList.ts new file mode 100644 index 0000000000..36a6bb973d --- /dev/null +++ b/frontend/src/api/infraMonitoring/getK8sJobsList.ts @@ -0,0 +1,72 @@ +import axios from 'api'; +import { ErrorResponseHandler } from 'api/ErrorResponseHandler'; +import { AxiosError } from 'axios'; +import { ErrorResponse, SuccessResponse } from 'types/api'; +import { BaseAutocompleteData } from 'types/api/queryBuilder/queryAutocompleteResponse'; +import { TagFilter } from 'types/api/queryBuilder/queryBuilderData'; + +export interface K8sJobsListPayload { + filters: TagFilter; + groupBy?: BaseAutocompleteData[]; + offset?: number; + limit?: number; + orderBy?: { + columnName: string; + order: 'asc' | 'desc'; + }; +} + +export interface K8sJobsData { + jobName: string; + cpuUsage: number; + memoryUsage: number; + cpuRequest: number; + memoryRequest: number; + cpuLimit: number; + memoryLimit: number; + restarts: number; + desiredSuccessfulPods: number; + activePods: number; + failedPods: number; + successfulPods: number; + meta: { + k8s_cluster_name: string; + k8s_job_name: string; + k8s_namespace_name: string; + }; +} + +export interface K8sJobsListResponse { + status: string; + data: { + type: string; + records: K8sJobsData[]; + groups: null; + total: number; + sentAnyHostMetricsData: boolean; + isSendingK8SAgentMetrics: boolean; + }; +} + +export const getK8sJobsList = async ( + props: K8sJobsListPayload, + signal?: AbortSignal, + headers?: Record, +): Promise | ErrorResponse> => { + try { + const response = await axios.post('/jobs/list', props, { + signal, + headers, + }); + + return { + statusCode: 200, + error: null, + message: 'Success', + payload: response.data, + params: props, + }; + } catch (error) { + return ErrorResponseHandler(error as AxiosError); + } +}; diff --git a/frontend/src/api/infraMonitoring/getK8sVolumesList.ts b/frontend/src/api/infraMonitoring/getK8sVolumesList.ts new file mode 100644 index 0000000000..ea825ba05d --- /dev/null +++ b/frontend/src/api/infraMonitoring/getK8sVolumesList.ts @@ -0,0 +1,71 @@ +import axios from 'api'; +import { ErrorResponseHandler } from 'api/ErrorResponseHandler'; +import { AxiosError } from 'axios'; +import { ErrorResponse, SuccessResponse } from 'types/api'; +import { BaseAutocompleteData } from 'types/api/queryBuilder/queryAutocompleteResponse'; +import { TagFilter } from 'types/api/queryBuilder/queryBuilderData'; + +export interface K8sVolumesListPayload { + filters: TagFilter; + groupBy?: BaseAutocompleteData[]; + offset?: number; + limit?: number; + orderBy?: { + columnName: string; + order: 'asc' | 'desc'; + }; +} + +export interface K8sVolumesData { + persistentVolumeClaimName: string; + volumeAvailable: number; + volumeCapacity: number; + volumeInodes: number; + volumeInodesFree: number; + volumeInodesUsed: number; + volumeUsage: number; + meta: { + k8s_cluster_name: string; + k8s_namespace_name: string; + k8s_node_name: string; + k8s_persistentvolumeclaim_name: string; + k8s_pod_name: string; + k8s_pod_uid: string; + k8s_statefulset_name: string; + }; +} + +export interface K8sVolumesListResponse { + status: string; + data: { + type: string; + records: K8sVolumesData[]; + groups: null; + total: number; + sentAnyHostMetricsData: boolean; + isSendingK8SAgentMetrics: boolean; + }; +} + +export const getK8sVolumesList = async ( + props: K8sVolumesListPayload, + signal?: AbortSignal, + headers?: Record, +): Promise | ErrorResponse> => { + try { + const response = await axios.post('/pvcs/list', props, { + signal, + headers, + }); + + return { + statusCode: 200, + error: null, + message: 'Success', + payload: response.data, + params: props, + }; + } catch (error) { + return ErrorResponseHandler(error as AxiosError); + } +}; diff --git a/frontend/src/api/infraMonitoring/getsK8sStatefulSetsList.ts b/frontend/src/api/infraMonitoring/getsK8sStatefulSetsList.ts new file mode 100644 index 0000000000..191ec069c3 --- /dev/null +++ b/frontend/src/api/infraMonitoring/getsK8sStatefulSetsList.ts @@ -0,0 +1,69 @@ +import axios from 'api'; +import { ErrorResponseHandler } from 'api/ErrorResponseHandler'; +import { AxiosError } from 'axios'; +import { ErrorResponse, SuccessResponse } from 'types/api'; +import { BaseAutocompleteData } from 'types/api/queryBuilder/queryAutocompleteResponse'; +import { TagFilter } from 'types/api/queryBuilder/queryBuilderData'; + +export interface K8sStatefulSetsListPayload { + filters: TagFilter; + groupBy?: BaseAutocompleteData[]; + offset?: number; + limit?: number; + orderBy?: { + columnName: string; + order: 'asc' | 'desc'; + }; +} + +export interface K8sStatefulSetsData { + statefulSetName: string; + cpuUsage: number; + memoryUsage: number; + desiredPods: number; + availablePods: number; + cpuRequest: number; + memoryRequest: number; + cpuLimit: number; + memoryLimit: number; + restarts: number; + meta: { + k8s_statefulset_name: string; + k8s_namespace_name: string; + }; +} + +export interface K8sStatefulSetsListResponse { + status: string; + data: { + type: string; + records: K8sStatefulSetsData[]; + groups: null; + total: number; + sentAnyHostMetricsData: boolean; + isSendingK8SAgentMetrics: boolean; + }; +} + +export const getK8sStatefulSetsList = async ( + props: K8sStatefulSetsListPayload, + signal?: AbortSignal, + headers?: Record, +): Promise | ErrorResponse> => { + try { + const response = await axios.post('/statefulsets/list', props, { + signal, + headers, + }); + + return { + statusCode: 200, + error: null, + message: 'Success', + payload: response.data, + params: props, + }; + } catch (error) { + return ErrorResponseHandler(error as AxiosError); + } +}; diff --git a/frontend/src/constants/reactQueryKeys.ts b/frontend/src/constants/reactQueryKeys.ts index 30da4e1362..19d894ab8e 100644 --- a/frontend/src/constants/reactQueryKeys.ts +++ b/frontend/src/constants/reactQueryKeys.ts @@ -26,4 +26,8 @@ export const REACT_QUERY_KEY = { GET_DEPLOYMENT_LIST: 'GET_DEPLOYMENT_LIST', GET_CLUSTER_LIST: 'GET_CLUSTER_LIST', GET_NAMESPACE_LIST: 'GET_NAMESPACE_LIST', + GET_STATEFULSET_LIST: 'GET_STATEFULSET_LIST', + GET_JOB_LIST: 'GET_JOB_LIST', + GET_DAEMONSET_LIST: 'GET_DAEMONSET_LIST,', + GET_VOLUME_LIST: 'GET_VOLUME_LIST', }; diff --git a/frontend/src/container/InfraMonitoringK8s/DaemonSets/DaemonSetDetails/DaemonSetDetails.interfaces.ts b/frontend/src/container/InfraMonitoringK8s/DaemonSets/DaemonSetDetails/DaemonSetDetails.interfaces.ts new file mode 100644 index 0000000000..6b21b2f77e --- /dev/null +++ b/frontend/src/container/InfraMonitoringK8s/DaemonSets/DaemonSetDetails/DaemonSetDetails.interfaces.ts @@ -0,0 +1,7 @@ +import { K8sDaemonSetsData } from 'api/infraMonitoring/getK8sDaemonSetsList'; + +export type DaemonSetDetailsProps = { + daemonSet: K8sDaemonSetsData | null; + isModalTimeSelection: boolean; + onClose: () => void; +}; diff --git a/frontend/src/container/InfraMonitoringK8s/DaemonSets/DaemonSetDetails/DaemonSetDetails.tsx b/frontend/src/container/InfraMonitoringK8s/DaemonSets/DaemonSetDetails/DaemonSetDetails.tsx new file mode 100644 index 0000000000..36312c59cd --- /dev/null +++ b/frontend/src/container/InfraMonitoringK8s/DaemonSets/DaemonSetDetails/DaemonSetDetails.tsx @@ -0,0 +1,593 @@ +/* eslint-disable sonarjs/no-identical-functions */ +import '../../EntityDetailsUtils/entityDetails.styles.scss'; + +import { Color, Spacing } from '@signozhq/design-tokens'; +import { Button, Divider, Drawer, Radio, Tooltip, Typography } from 'antd'; +import { RadioChangeEvent } from 'antd/lib'; +import logEvent from 'api/common/logEvent'; +import { VIEW_TYPES, VIEWS } from 'components/HostMetricsDetail/constants'; +import { QueryParams } from 'constants/query'; +import { + initialQueryBuilderFormValuesMap, + initialQueryState, +} from 'constants/queryBuilder'; +import ROUTES from 'constants/routes'; +import { K8sCategory } from 'container/InfraMonitoringK8s/constants'; +import { + CustomTimeType, + Time, +} from 'container/TopNav/DateTimeSelectionV2/config'; +import { useIsDarkMode } from 'hooks/useDarkMode'; +import useUrlQuery from 'hooks/useUrlQuery'; +import GetMinMax from 'lib/getMinMax'; +import { + BarChart2, + ChevronsLeftRight, + Compass, + DraftingCompass, + ScrollText, + X, +} from 'lucide-react'; +import { useCallback, useEffect, useMemo, useState } from 'react'; +import { useSelector } from 'react-redux'; +import { AppState } from 'store/reducers'; +import { DataTypes } from 'types/api/queryBuilder/queryAutocompleteResponse'; +import { + IBuilderQuery, + TagFilterItem, +} from 'types/api/queryBuilder/queryBuilderData'; +import { + LogsAggregatorOperator, + TracesAggregatorOperator, +} from 'types/common/queryBuilder'; +import { GlobalReducer } from 'types/reducer/globalTime'; +import { v4 as uuidv4 } from 'uuid'; + +import DaemonSetEvents from '../../EntityDetailsUtils/EntityEvents'; +import DaemonSetLogs from '../../EntityDetailsUtils/EntityLogs'; +import DaemonSetMetrics from '../../EntityDetailsUtils/EntityMetrics'; +import DaemonSetTraces from '../../EntityDetailsUtils/EntityTraces'; +import { QUERY_KEYS } from '../../EntityDetailsUtils/utils'; +import { + daemonSetWidgetInfo, + getDaemonSetMetricsQueryPayload, +} from './constants'; +import { DaemonSetDetailsProps } from './DaemonSetDetails.interfaces'; + +function DaemonSetDetails({ + daemonSet, + onClose, + isModalTimeSelection, +}: DaemonSetDetailsProps): JSX.Element { + const { maxTime, minTime, selectedTime } = useSelector< + AppState, + GlobalReducer + >((state) => state.globalTime); + + const startMs = useMemo(() => Math.floor(Number(minTime) / 1000000000), [ + minTime, + ]); + const endMs = useMemo(() => Math.floor(Number(maxTime) / 1000000000), [ + maxTime, + ]); + + const urlQuery = useUrlQuery(); + + const [modalTimeRange, setModalTimeRange] = useState(() => ({ + startTime: startMs, + endTime: endMs, + })); + + const [selectedInterval, setSelectedInterval] = useState