diff --git a/frontend/src/api/infraMonitoring/getK8sJobsList.ts b/frontend/src/api/infraMonitoring/getK8sJobsList.ts new file mode 100644 index 0000000000..6a2d43e97c --- /dev/null +++ b/frontend/src/api/infraMonitoring/getK8sJobsList.ts @@ -0,0 +1,69 @@ +import { ApiBaseInstance } 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; + desiredPods: number; + availablePods: number; + cpuRequest: number; + memoryRequest: number; + cpuLimit: number; + memoryLimit: number; + restarts: number; + meta: { + 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 ApiBaseInstance.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/getK8sNodesList.ts b/frontend/src/api/infraMonitoring/getK8sNodesList.ts new file mode 100644 index 0000000000..18fc3ce42a --- /dev/null +++ b/frontend/src/api/infraMonitoring/getK8sNodesList.ts @@ -0,0 +1,64 @@ +import { ApiBaseInstance } 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 K8sNodesListPayload { + filters: TagFilter; + groupBy?: BaseAutocompleteData[]; + offset?: number; + limit?: number; + orderBy?: { + columnName: string; + order: 'asc' | 'desc'; + }; +} + +export interface K8sNodesData { + nodeUID: string; + nodeCPUUsage: number; + nodeCPUAllocatable: number; + nodeMemoryUsage: number; + nodeMemoryAllocatable: number; + meta: { + k8s_node_name: string; + k8s_node_uid: string; + }; +} + +export interface K8sNodesListResponse { + status: string; + data: { + type: string; + records: K8sNodesData[]; + groups: null; + total: number; + sentAnyHostMetricsData: boolean; + isSendingK8SAgentMetrics: boolean; + }; +} + +export const getK8sNodesList = async ( + props: K8sNodesListPayload, + signal?: AbortSignal, + headers?: Record, +): Promise | ErrorResponse> => { + try { + const response = await ApiBaseInstance.post('/nodes/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/container/InfraMonitoringK8s/Jobs/K8sJobsList.tsx b/frontend/src/container/InfraMonitoringK8s/Jobs/K8sJobsList.tsx new file mode 100644 index 0000000000..ddf24a901d --- /dev/null +++ b/frontend/src/container/InfraMonitoringK8s/Jobs/K8sJobsList.tsx @@ -0,0 +1,243 @@ +/* eslint-disable @typescript-eslint/explicit-function-return-type */ +import '../InfraMonitoringK8s.styles.scss'; + +import { LoadingOutlined } from '@ant-design/icons'; +import { + Skeleton, + Spin, + Table, + TablePaginationConfig, + TableProps, + Typography, +} from 'antd'; +import { SorterResult } from 'antd/es/table/interface'; +import logEvent from 'api/common/logEvent'; +import { K8sJobsListPayload } from 'api/infraMonitoring/getK8sJobsList'; +import { useGetK8sJobsList } from 'hooks/infraMonitoring/useGetK8sJobsList'; +import { useCallback, useEffect, useMemo, useState } from 'react'; +import { useSelector } from 'react-redux'; +import { AppState } from 'store/reducers'; +import { IBuilderQuery } from 'types/api/queryBuilder/queryBuilderData'; +import { GlobalReducer } from 'types/reducer/globalTime'; + +import K8sHeader from '../K8sHeader'; +import { + defaultAddedColumns, + formatDataForTable, + getK8sJobsListColumns, + getK8sJobsListQuery, + K8sJobsRowData, +} from './utils'; + +function K8sJobsList({ + isFiltersVisible, + handleFilterVisibilityChange, +}: { + isFiltersVisible: boolean; + handleFilterVisibilityChange: () => void; +}): JSX.Element { + const { maxTime, minTime } = useSelector( + (state) => state.globalTime, + ); + + const [currentPage, setCurrentPage] = useState(1); + + const [filters, setFilters] = useState({ + items: [], + op: 'and', + }); + + const [orderBy, setOrderBy] = useState<{ + columnName: string; + order: 'asc' | 'desc'; + } | null>(null); + + // const [selectedJobUID, setselectedJobUID] = useState(null); + + const pageSize = 10; + + const query = useMemo(() => { + const baseQuery = getK8sJobsListQuery(); + return { + ...baseQuery, + limit: pageSize, + offset: (currentPage - 1) * pageSize, + filters, + start: Math.floor(minTime / 1000000), + end: Math.floor(maxTime / 1000000), + orderBy, + }; + }, [currentPage, filters, minTime, maxTime, orderBy]); + + const { data, isFetching, isLoading, isError } = useGetK8sJobsList( + query as K8sJobsListPayload, + { + queryKey: ['hostList', query], + enabled: !!query, + }, + ); + + const JobsData = useMemo(() => data?.payload?.data?.records || [], [data]); + const totalCount = data?.payload?.data?.total || 0; + + const formattedJobsData = useMemo(() => formatDataForTable(JobsData), [ + JobsData, + ]); + + const columns = useMemo(() => getK8sJobsListColumns(), []); + + const handleTableChange: TableProps['onChange'] = useCallback( + ( + pagination: TablePaginationConfig, + _filters: Record, + sorter: SorterResult | SorterResult[], + ): void => { + if (pagination.current) { + setCurrentPage(pagination.current); + } + + if ('field' in sorter && sorter.order) { + setOrderBy({ + columnName: sorter.field as string, + order: sorter.order === 'ascend' ? 'asc' : 'desc', + }); + } else { + setOrderBy(null); + } + }, + [], + ); + + const handleFiltersChange = useCallback( + (value: IBuilderQuery['filters']): void => { + const isNewFilterAdded = value.items.length !== filters.items.length; + if (isNewFilterAdded) { + setFilters(value); + setCurrentPage(1); + + logEvent('Infra Monitoring: K8s list filters applied', { + filters: value, + }); + } + }, + [filters], + ); + + useEffect(() => { + logEvent('Infra Monitoring: K8s list page visited', {}); + }, []); + + // const selectedJobData = useMemo(() => { + // if (!selectedJobUID) return null; + // return JobsData.find((job) => job.JobUID === selectedJobUID) || null; + // }, [selectedJobUID, JobsData]); + + const handleRowClick = (record: K8sJobsRowData): void => { + // setselectedJobUID(record.JobUID); + + logEvent('Infra Monitoring: K8s job list item clicked', { + jobName: record.jobName, + }); + }; + + // const handleCloseJobDetail = (): void => { + // setselectedJobUID(null); + // }; + + const showsJobsTable = + !isError && + !isLoading && + !isFetching && + !(formattedJobsData.length === 0 && filters.items.length > 0); + + const showNoFilteredJobsMessage = + !isFetching && + !isLoading && + formattedJobsData.length === 0 && + filters.items.length > 0; + + return ( +
+ {}} + onRemoveColumn={() => {}} + /> + {isError && {data?.error || 'Something went wrong'}} + + {showNoFilteredJobsMessage && ( +
+
+ thinking-emoji + + + This query had no results. Edit your query and try again! + +
+
+ )} + + {(isFetching || isLoading) && ( +
+ + + +
+ )} + + {showsJobsTable && ( + } />, + }} + tableLayout="fixed" + rowKey={(record): string => record.jobName} + onChange={handleTableChange} + onRow={(record): { onClick: () => void; className: string } => ({ + onClick: (): void => handleRowClick(record), + className: 'clickable-row', + })} + /> + )} + {/* TODO - Handle Job Details flow */} + + ); +} + +export default K8sJobsList; diff --git a/frontend/src/container/InfraMonitoringK8s/Jobs/utils.tsx b/frontend/src/container/InfraMonitoringK8s/Jobs/utils.tsx new file mode 100644 index 0000000000..874d81a77f --- /dev/null +++ b/frontend/src/container/InfraMonitoringK8s/Jobs/utils.tsx @@ -0,0 +1,236 @@ +import { Color } from '@signozhq/design-tokens'; +import { Progress } from 'antd'; +import { ColumnType } from 'antd/es/table'; +import { + K8sJobsData, + K8sJobsListPayload, +} from 'api/infraMonitoring/getK8sJobsList'; + +import { IEntityColumn } from '../utils'; + +export const defaultAddedColumns: IEntityColumn[] = [ + { + label: 'Namespace Status', + value: 'NamespaceStatus', + id: 'NamespaceStatus', + canRemove: false, + }, + { + label: 'CPU Utilization (cores)', + value: 'cpuUsage', + id: 'cpuUsage', + canRemove: false, + }, + { + label: 'CPU Allocatable (cores)', + value: 'cpuAllocatable', + id: 'cpuAllocatable', + canRemove: false, + }, + { + label: 'Memory Allocatable (bytes)', + value: 'memoryAllocatable', + id: 'memoryAllocatable', + canRemove: false, + }, + { + label: 'Pods count by phase', + value: 'podsCount', + id: 'podsCount', + canRemove: false, + }, +]; + +export interface K8sJobsRowData { + key: string; + jobName: string; + availableReplicas: number; + desiredReplicas: number; + cpuRequestUtilization: React.ReactNode; + cpuLimitUtilization: React.ReactNode; + cpuUtilization: number; + memoryRequestUtilization: React.ReactNode; + memoryLimitUtilization: React.ReactNode; + memoryUtilization: number; + jobRestarts: number; +} + +export const getK8sJobsListQuery = (): K8sJobsListPayload => ({ + filters: { + items: [], + op: 'and', + }, + orderBy: { columnName: 'cpu', order: 'desc' }, +}); + +const columnsConfig = [ + { + title:
Job
, + dataIndex: 'jobName', + key: 'jobName', + ellipsis: true, + width: 150, + sorter: true, + align: 'left', + }, + { + title:
Available Replicas
, + dataIndex: 'availableReplicas', + key: 'availableReplicas', + width: 100, + sorter: true, + align: 'left', + }, + { + title:
Desired Replicas
, + dataIndex: 'desiredReplicas', + key: 'desiredReplicas', + width: 80, + sorter: true, + align: 'left', + }, + { + title: ( +
+ CPU Request Utilization (% of limit) +
+ ), + dataIndex: 'cpuRequestUtilization', + key: 'cpuRequestUtilization', + width: 80, + sorter: true, + align: 'left', + }, + { + title: ( +
+ CPU Limit Utilization (% of request) +
+ ), + dataIndex: 'cpuLimitUtilization', + key: 'cpuLimitUtilization', + width: 50, + sorter: true, + align: 'left', + }, + { + title:
CPU Utilization (cores)
, + dataIndex: 'cpuUtilization', + key: 'cpuUtilization', + width: 80, + sorter: true, + align: 'left', + }, + { + title: ( +
+ Memory Request Utilization (% of limit) +
+ ), + dataIndex: 'memoryRequestUtilization', + key: 'memoryRequestUtilization', + width: 50, + sorter: true, + align: 'left', + }, + { + title: ( +
+ Memory Limit Utilization (% of request) +
+ ), + dataIndex: 'memoryLimitUtilization', + key: 'memoryLimitUtilization', + width: 80, + sorter: true, + align: 'left', + }, + { + title:
Job Restarts
, + dataIndex: 'jobRestarts', + key: 'jobRestarts', + width: 50, + sorter: true, + align: 'left', + }, +]; + +export const getK8sJobsListColumns = (): ColumnType[] => + columnsConfig as ColumnType[]; + +const getStrokeColorForProgressBar = (value: number): string => { + if (value >= 90) return Color.BG_SAKURA_500; + if (value >= 60) return Color.BG_AMBER_500; + return Color.BG_FOREST_500; +}; + +export const formatDataForTable = (data: K8sJobsData[]): K8sJobsRowData[] => + data.map((job, index) => ({ + key: `${job.meta.k8s_job_name}-${index}`, + jobName: job.meta.k8s_job_name, + availableReplicas: job.availablePods, + desiredReplicas: job.desiredPods, + jobRestarts: job.restarts, + cpuUtilization: job.cpuUsage, + cpuRequestUtilization: ( +
+ { + const cpuPercent = Number((job.cpuRequest * 100).toFixed(1)); + return getStrokeColorForProgressBar(cpuPercent); + })()} + className="progress-bar" + /> +
+ ), + cpuLimitUtilization: ( +
+ { + const cpuPercent = Number((job.cpuLimit * 100).toFixed(1)); + return getStrokeColorForProgressBar(cpuPercent); + })()} + className="progress-bar" + /> +
+ ), + memoryUtilization: job.memoryUsage, + memoryRequestUtilization: ( +
+ { + const memoryPercent = Number((job.memoryRequest * 100).toFixed(1)); + return getStrokeColorForProgressBar(memoryPercent); + })()} + className="progress-bar" + /> +
+ ), + memoryLimitUtilization: ( +
+ { + const memoryPercent = Number((job.memoryLimit * 100).toFixed(1)); + return getStrokeColorForProgressBar(memoryPercent); + })()} + className="progress-bar" + /> +
+ ), + })); diff --git a/frontend/src/container/InfraMonitoringK8s/K8sHeader.tsx b/frontend/src/container/InfraMonitoringK8s/K8sHeader.tsx index eee4c6fa9d..c04151b4fc 100644 --- a/frontend/src/container/InfraMonitoringK8s/K8sHeader.tsx +++ b/frontend/src/container/InfraMonitoringK8s/K8sHeader.tsx @@ -1,3 +1,4 @@ +/* eslint-disable @typescript-eslint/explicit-function-return-type */ import './InfraMonitoringK8s.styles.scss'; import { Button, Input } from 'antd'; @@ -13,20 +14,20 @@ import { IPodColumn } from './utils'; function K8sHeader({ defaultAddedColumns, - addedColumns, - availableColumns, + addedColumns = [], + availableColumns = [], handleFiltersChange, - onAddColumn, - onRemoveColumn, + onAddColumn = () => {}, + onRemoveColumn = () => {}, handleFilterVisibilityChange, isFiltersVisible, }: { defaultAddedColumns: IPodColumn[]; - addedColumns: IPodColumn[]; - availableColumns: IPodColumn[]; + addedColumns?: IPodColumn[]; + availableColumns?: IPodColumn[]; handleFiltersChange: (value: IBuilderQuery['filters']) => void; - onAddColumn: (column: IPodColumn) => void; - onRemoveColumn: (column: IPodColumn) => void; + onAddColumn?: (column: IPodColumn) => void; + onRemoveColumn?: (column: IPodColumn) => void; handleFilterVisibilityChange: () => void; isFiltersVisible: boolean; }): JSX.Element { @@ -127,4 +128,11 @@ function K8sHeader({ ); } +K8sHeader.defaultProps = { + addedColumns: [], + availableColumns: [], + onAddColumn: () => {}, + onRemoveColumn: () => {}, +}; + export default K8sHeader; diff --git a/frontend/src/container/InfraMonitoringK8s/Nodes/K8sNodesList.tsx b/frontend/src/container/InfraMonitoringK8s/Nodes/K8sNodesList.tsx new file mode 100644 index 0000000000..ae75ea16dd --- /dev/null +++ b/frontend/src/container/InfraMonitoringK8s/Nodes/K8sNodesList.tsx @@ -0,0 +1,240 @@ +/* eslint-disable @typescript-eslint/explicit-function-return-type */ +import '../InfraMonitoringK8s.styles.scss'; + +import { LoadingOutlined } from '@ant-design/icons'; +import { + Skeleton, + Spin, + Table, + TablePaginationConfig, + TableProps, + Typography, +} from 'antd'; +import { SorterResult } from 'antd/es/table/interface'; +import logEvent from 'api/common/logEvent'; +import { K8sNodesListPayload } from 'api/infraMonitoring/getK8sNodesList'; +import { useGetK8sNodesList } from 'hooks/infraMonitoring/useGetK8sNodesList'; +import { useCallback, useEffect, useMemo, useState } from 'react'; +import { useSelector } from 'react-redux'; +import { AppState } from 'store/reducers'; +import { IBuilderQuery } from 'types/api/queryBuilder/queryBuilderData'; +import { GlobalReducer } from 'types/reducer/globalTime'; + +import K8sHeader from '../K8sHeader'; +import { + defaultAddedColumns, + formatDataForTable, + getK8sNodesListColumns, + getK8sNodesListQuery, + K8sNodesRowData, +} from './utils'; + +// eslint-disable-next-line sonarjs/cognitive-complexity +function K8sNodesList({ + isFiltersVisible, + handleFilterVisibilityChange, +}: { + isFiltersVisible: boolean; + handleFilterVisibilityChange: () => void; +}): JSX.Element { + const { maxTime, minTime } = useSelector( + (state) => state.globalTime, + ); + + const [currentPage, setCurrentPage] = useState(1); + + const [filters, setFilters] = useState({ + items: [], + op: 'and', + }); + + const [orderBy, setOrderBy] = useState<{ + columnName: string; + order: 'asc' | 'desc'; + } | null>(null); + + // const [selectedNodeUID, setselectedNodeUID] = useState(null); + + const pageSize = 10; + + const query = useMemo(() => { + const baseQuery = getK8sNodesListQuery(); + return { + ...baseQuery, + limit: pageSize, + offset: (currentPage - 1) * pageSize, + filters, + start: Math.floor(minTime / 1000000), + end: Math.floor(maxTime / 1000000), + orderBy, + }; + }, [currentPage, filters, minTime, maxTime, orderBy]); + + const { data, isFetching, isLoading, isError } = useGetK8sNodesList( + query as K8sNodesListPayload, + { + queryKey: ['hostList', query], + enabled: !!query, + }, + ); + + const nodesData = useMemo(() => data?.payload?.data?.records || [], [data]); + const totalCount = data?.payload?.data?.total || 0; + + const formattedNodesData = useMemo(() => formatDataForTable(nodesData), [ + nodesData, + ]); + + const columns = useMemo(() => getK8sNodesListColumns(), []); + + const handleTableChange: TableProps['onChange'] = useCallback( + ( + pagination: TablePaginationConfig, + _filters: Record, + sorter: SorterResult | SorterResult[], + ): void => { + if (pagination.current) { + setCurrentPage(pagination.current); + } + + if ('field' in sorter && sorter.order) { + setOrderBy({ + columnName: sorter.field as string, + order: sorter.order === 'ascend' ? 'asc' : 'desc', + }); + } else { + setOrderBy(null); + } + }, + [], + ); + + const handleFiltersChange = useCallback( + (value: IBuilderQuery['filters']): void => { + const isNewFilterAdded = value.items.length !== filters.items.length; + if (isNewFilterAdded) { + setFilters(value); + setCurrentPage(1); + + logEvent('Infra Monitoring: K8s list filters applied', { + filters: value, + }); + } + }, + [filters], + ); + + useEffect(() => { + logEvent('Infra Monitoring: K8s list page visited', {}); + }, []); + + // const selectedNodeData = useMemo(() => { + // if (!selectedNodeUID) return null; + // return nodesData.find((node) => node.nodeUID === selectedNodeUID) || null; + // }, [selectedNodeUID, nodesData]); + + const handleRowClick = (record: K8sNodesRowData): void => { + // setselectedNodeUID(record.nodeUID); + + logEvent('Infra Monitoring: K8s node list item clicked', { + nodeUID: record.nodeUID, + }); + }; + + // const handleCloseNodeDetail = (): void => { + // setselectedNodeUID(null); + // }; + + const showsNodesTable = + !isError && + !isLoading && + !isFetching && + !(formattedNodesData.length === 0 && filters.items.length > 0); + + const showNoFilteredNodesMessage = + !isFetching && + !isLoading && + formattedNodesData.length === 0 && + filters.items.length > 0; + + return ( +
+ + {isError && {data?.error || 'Something went wrong'}} + + {showNoFilteredNodesMessage && ( +
+
+ thinking-emoji + + + This query had no results. Edit your query and try again! + +
+
+ )} + + {(isFetching || isLoading) && ( +
+ + + +
+ )} + + {showsNodesTable && ( +
} />, + }} + tableLayout="fixed" + rowKey={(record): string => record.nodeUID} + onChange={handleTableChange} + onRow={(record): { onClick: () => void; className: string } => ({ + onClick: (): void => handleRowClick(record), + className: 'clickable-row', + })} + /> + )} + {/* TODO - Handle Node Details flow */} + + ); +} + +export default K8sNodesList; diff --git a/frontend/src/container/InfraMonitoringK8s/Nodes/utils.tsx b/frontend/src/container/InfraMonitoringK8s/Nodes/utils.tsx new file mode 100644 index 0000000000..b11633d9cf --- /dev/null +++ b/frontend/src/container/InfraMonitoringK8s/Nodes/utils.tsx @@ -0,0 +1,126 @@ +import { ColumnType } from 'antd/es/table'; +import { + K8sNodesData, + K8sNodesListPayload, +} from 'api/infraMonitoring/getK8sNodesList'; + +import { IEntityColumn } from '../utils'; + +export const defaultAddedColumns: IEntityColumn[] = [ + { + label: 'Node Status', + value: 'nodeStatus', + id: 'nodeStatus', + canRemove: false, + }, + { + label: 'CPU Utilization (cores)', + value: 'cpuUtilization', + id: 'cpuUtilization', + canRemove: false, + }, + { + label: 'CPU Allocatable (cores)', + value: 'cpuAllocatable', + id: 'cpuAllocatable', + canRemove: false, + }, + { + label: 'Memory Allocatable (bytes)', + value: 'memoryAllocatable', + id: 'memoryAllocatable', + canRemove: false, + }, + { + label: 'Pods count by phase', + value: 'podsCount', + id: 'podsCount', + canRemove: false, + }, +]; + +export interface K8sNodesRowData { + key: string; + nodeUID: string; + nodeStatus: string; + cpuUtilization: React.ReactNode; + cpuAllocatable: React.ReactNode; + memoryUtilization: React.ReactNode; + memoryAllocatable: React.ReactNode; + podsCount: number; +} + +export const getK8sNodesListQuery = (): K8sNodesListPayload => ({ + filters: { + items: [], + op: 'and', + }, + orderBy: { columnName: 'cpu', order: 'desc' }, +}); + +const columnsConfig = [ + { + title:
Node Status
, + dataIndex: 'nodeStatus', + key: 'nodeStatus', + ellipsis: true, + width: 150, + sorter: true, + align: 'left', + }, + { + title:
CPU Utilization (cores)
, + dataIndex: 'cpuUtilization', + key: 'cpuUtilization', + width: 100, + sorter: true, + align: 'left', + }, + { + title:
CPU Allocatable (cores)
, + dataIndex: 'cpuAllocatable', + key: 'cpuAllocatable', + width: 100, + sorter: true, + align: 'left', + }, + { + title:
Memory Utilization (bytes)
, + dataIndex: 'memoryUtilization', + key: 'memoryUtilization', + width: 80, + sorter: true, + align: 'left', + }, + { + title:
Memory Allocatable (bytes)
, + dataIndex: 'memoryAllocatable', + key: 'memoryAllocatable', + width: 80, + sorter: true, + align: 'left', + }, + { + title:
Pods count by phase
, + dataIndex: 'containerRestarts', + key: 'containerRestarts', + width: 50, + sorter: true, + align: 'left', + }, +]; + +export const getK8sNodesListColumns = (): ColumnType[] => + columnsConfig as ColumnType[]; + +export const formatDataForTable = (data: K8sNodesData[]): K8sNodesRowData[] => + data.map((node, index) => ({ + key: `${node.nodeUID}-${index}`, + nodeUID: node.nodeUID || '', + cpuUtilization: node.nodeCPUUsage, + memoryUtilization: node.nodeMemoryUsage, + cpuAllocatable: node.nodeCPUAllocatable, + memoryAllocatable: node.nodeMemoryAllocatable, + nodeStatus: node.meta.k8s_node_name, + podsCount: node.nodeCPUAllocatable, + })); diff --git a/frontend/src/container/InfraMonitoringK8s/utils.tsx b/frontend/src/container/InfraMonitoringK8s/utils.tsx index 1d26cb81ea..d6223f9fab 100644 --- a/frontend/src/container/InfraMonitoringK8s/utils.tsx +++ b/frontend/src/container/InfraMonitoringK8s/utils.tsx @@ -14,6 +14,13 @@ import { } from 'components/QuickFilters/QuickFilters'; import { DataTypes } from 'types/api/queryBuilder/queryAutocompleteResponse'; +export interface IEntityColumn { + label: string; + value: string; + id: string; + canRemove: boolean; +} + export interface IPodColumn { label: string; value: string; diff --git a/frontend/src/hooks/infraMonitoring/useGetK8sJobsList.ts b/frontend/src/hooks/infraMonitoring/useGetK8sJobsList.ts new file mode 100644 index 0000000000..4f775463d4 --- /dev/null +++ b/frontend/src/hooks/infraMonitoring/useGetK8sJobsList.ts @@ -0,0 +1,51 @@ +import { + getK8sJobsList, + K8sJobsListPayload, + K8sJobsListResponse, +} from 'api/infraMonitoring/getK8sJobsList'; +import { REACT_QUERY_KEY } from 'constants/reactQueryKeys'; +import { useMemo } from 'react'; +import { useQuery, UseQueryOptions, UseQueryResult } from 'react-query'; +import { ErrorResponse, SuccessResponse } from 'types/api'; + +type UseGetK8sJobsList = ( + requestData: K8sJobsListPayload, + + options?: UseQueryOptions< + SuccessResponse | ErrorResponse, + Error + >, + + headers?: Record, +) => UseQueryResult< + SuccessResponse | ErrorResponse, + Error +>; + +export const useGetK8sJobsList: UseGetK8sJobsList = ( + requestData, + + options, + + headers, +) => { + const queryKey = useMemo(() => { + if (options?.queryKey && Array.isArray(options.queryKey)) { + return [...options.queryKey]; + } + + if (options?.queryKey && typeof options.queryKey === 'string') { + return options.queryKey; + } + + return [REACT_QUERY_KEY.GET_HOST_LIST, requestData]; + }, [options?.queryKey, requestData]); + + return useQuery | ErrorResponse, Error>({ + queryFn: ({ signal }) => getK8sJobsList(requestData, signal, headers), + + ...options, + + queryKey, + }); +}; diff --git a/frontend/src/hooks/infraMonitoring/useGetK8sNodesList.ts b/frontend/src/hooks/infraMonitoring/useGetK8sNodesList.ts new file mode 100644 index 0000000000..4e2ac964d2 --- /dev/null +++ b/frontend/src/hooks/infraMonitoring/useGetK8sNodesList.ts @@ -0,0 +1,45 @@ +import { + getK8sNodesList, + K8sNodesListPayload, + K8sNodesListResponse, +} from 'api/infraMonitoring/getK8sNodesList'; +import { REACT_QUERY_KEY } from 'constants/reactQueryKeys'; +import { useMemo } from 'react'; +import { useQuery, UseQueryOptions, UseQueryResult } from 'react-query'; +import { ErrorResponse, SuccessResponse } from 'types/api'; + +type UseGetK8sNodesList = ( + requestData: K8sNodesListPayload, + options?: UseQueryOptions< + SuccessResponse | ErrorResponse, + Error + >, + headers?: Record, +) => UseQueryResult< + SuccessResponse | ErrorResponse, + Error +>; + +export const useGetK8sNodesList: UseGetK8sNodesList = ( + requestData, + options, + headers, +) => { + const queryKey = useMemo(() => { + if (options?.queryKey && Array.isArray(options.queryKey)) { + return [...options.queryKey]; + } + + if (options?.queryKey && typeof options.queryKey === 'string') { + return options.queryKey; + } + + return [REACT_QUERY_KEY.GET_HOST_LIST, requestData]; + }, [options?.queryKey, requestData]); + + return useQuery | ErrorResponse, Error>({ + queryFn: ({ signal }) => getK8sNodesList(requestData, signal, headers), + ...options, + queryKey, + }); +};