mirror of
https://github.com/SigNoz/signoz.git
synced 2025-12-29 16:14:42 +00:00
feat: implement deployments, clusters and namespaces in k8s infra monitoring (#6786)
This commit is contained in:
parent
7730f76128
commit
403043e076
@ -1,4 +1,4 @@
|
|||||||
import { ApiBaseInstance } from 'api';
|
import axios from 'api';
|
||||||
import { ErrorResponseHandler } from 'api/ErrorResponseHandler';
|
import { ErrorResponseHandler } from 'api/ErrorResponseHandler';
|
||||||
import { AxiosError } from 'axios';
|
import { AxiosError } from 'axios';
|
||||||
import createQueryParams from 'lib/createQueryParams';
|
import createQueryParams from 'lib/createQueryParams';
|
||||||
@ -19,7 +19,7 @@ export const getInfraAttributesValues = async ({
|
|||||||
SuccessResponse<IAttributeValuesResponse> | ErrorResponse
|
SuccessResponse<IAttributeValuesResponse> | ErrorResponse
|
||||||
> => {
|
> => {
|
||||||
try {
|
try {
|
||||||
const response = await ApiBaseInstance.get(
|
const response = await axios.get(
|
||||||
`/hosts/attribute_values?${createQueryParams({
|
`/hosts/attribute_values?${createQueryParams({
|
||||||
dataSource,
|
dataSource,
|
||||||
attributeKey,
|
attributeKey,
|
||||||
|
|||||||
64
frontend/src/api/infraMonitoring/getK8sClustersList.ts
Normal file
64
frontend/src/api/infraMonitoring/getK8sClustersList.ts
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
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 K8sClustersListPayload {
|
||||||
|
filters: TagFilter;
|
||||||
|
groupBy?: BaseAutocompleteData[];
|
||||||
|
offset?: number;
|
||||||
|
limit?: number;
|
||||||
|
orderBy?: {
|
||||||
|
columnName: string;
|
||||||
|
order: 'asc' | 'desc';
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface K8sClustersData {
|
||||||
|
clusterUID: string;
|
||||||
|
cpuUsage: number;
|
||||||
|
cpuAllocatable: number;
|
||||||
|
memoryUsage: number;
|
||||||
|
memoryAllocatable: number;
|
||||||
|
meta: {
|
||||||
|
k8s_cluster_name: string;
|
||||||
|
k8s_cluster_uid: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface K8sClustersListResponse {
|
||||||
|
status: string;
|
||||||
|
data: {
|
||||||
|
type: string;
|
||||||
|
records: K8sClustersData[];
|
||||||
|
groups: null;
|
||||||
|
total: number;
|
||||||
|
sentAnyHostMetricsData: boolean;
|
||||||
|
isSendingK8SAgentMetrics: boolean;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export const getK8sClustersList = async (
|
||||||
|
props: K8sClustersListPayload,
|
||||||
|
signal?: AbortSignal,
|
||||||
|
headers?: Record<string, string>,
|
||||||
|
): Promise<SuccessResponse<K8sClustersListResponse> | ErrorResponse> => {
|
||||||
|
try {
|
||||||
|
const response = await axios.post('/clusters/list', props, {
|
||||||
|
signal,
|
||||||
|
headers,
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
statusCode: 200,
|
||||||
|
error: null,
|
||||||
|
message: 'Success',
|
||||||
|
payload: response.data,
|
||||||
|
params: props,
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
return ErrorResponseHandler(error as AxiosError);
|
||||||
|
}
|
||||||
|
};
|
||||||
70
frontend/src/api/infraMonitoring/getK8sDeploymentsList.ts
Normal file
70
frontend/src/api/infraMonitoring/getK8sDeploymentsList.ts
Normal file
@ -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 K8sDeploymentsListPayload {
|
||||||
|
filters: TagFilter;
|
||||||
|
groupBy?: BaseAutocompleteData[];
|
||||||
|
offset?: number;
|
||||||
|
limit?: number;
|
||||||
|
orderBy?: {
|
||||||
|
columnName: string;
|
||||||
|
order: 'asc' | 'desc';
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface K8sDeploymentsData {
|
||||||
|
deploymentName: string;
|
||||||
|
cpuUsage: number;
|
||||||
|
memoryUsage: number;
|
||||||
|
desiredPods: number;
|
||||||
|
availablePods: number;
|
||||||
|
cpuRequest: number;
|
||||||
|
memoryRequest: number;
|
||||||
|
cpuLimit: number;
|
||||||
|
memoryLimit: number;
|
||||||
|
restarts: number;
|
||||||
|
meta: {
|
||||||
|
k8s_cluster_name: string;
|
||||||
|
k8s_deployment_name: string;
|
||||||
|
k8s_namespace_name: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface K8sDeploymentsListResponse {
|
||||||
|
status: string;
|
||||||
|
data: {
|
||||||
|
type: string;
|
||||||
|
records: K8sDeploymentsData[];
|
||||||
|
groups: null;
|
||||||
|
total: number;
|
||||||
|
sentAnyHostMetricsData: boolean;
|
||||||
|
isSendingK8SAgentMetrics: boolean;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export const getK8sDeploymentsList = async (
|
||||||
|
props: K8sDeploymentsListPayload,
|
||||||
|
signal?: AbortSignal,
|
||||||
|
headers?: Record<string, string>,
|
||||||
|
): Promise<SuccessResponse<K8sDeploymentsListResponse> | ErrorResponse> => {
|
||||||
|
try {
|
||||||
|
const response = await axios.post('/deployments/list', props, {
|
||||||
|
signal,
|
||||||
|
headers,
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
statusCode: 200,
|
||||||
|
error: null,
|
||||||
|
message: 'Success',
|
||||||
|
payload: response.data,
|
||||||
|
params: props,
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
return ErrorResponseHandler(error as AxiosError);
|
||||||
|
}
|
||||||
|
};
|
||||||
62
frontend/src/api/infraMonitoring/getK8sNamespacesList.ts
Normal file
62
frontend/src/api/infraMonitoring/getK8sNamespacesList.ts
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
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 K8sNamespacesListPayload {
|
||||||
|
filters: TagFilter;
|
||||||
|
groupBy?: BaseAutocompleteData[];
|
||||||
|
offset?: number;
|
||||||
|
limit?: number;
|
||||||
|
orderBy?: {
|
||||||
|
columnName: string;
|
||||||
|
order: 'asc' | 'desc';
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface K8sNamespacesData {
|
||||||
|
namespaceName: string;
|
||||||
|
cpuUsage: number;
|
||||||
|
memoryUsage: number;
|
||||||
|
meta: {
|
||||||
|
k8s_cluster_name: string;
|
||||||
|
k8s_namespace_name: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface K8sNamespacesListResponse {
|
||||||
|
status: string;
|
||||||
|
data: {
|
||||||
|
type: string;
|
||||||
|
records: K8sNamespacesData[];
|
||||||
|
groups: null;
|
||||||
|
total: number;
|
||||||
|
sentAnyHostMetricsData: boolean;
|
||||||
|
isSendingK8SAgentMetrics: boolean;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export const getK8sNamespacesList = async (
|
||||||
|
props: K8sNamespacesListPayload,
|
||||||
|
signal?: AbortSignal,
|
||||||
|
headers?: Record<string, string>,
|
||||||
|
): Promise<SuccessResponse<K8sNamespacesListResponse> | ErrorResponse> => {
|
||||||
|
try {
|
||||||
|
const response = await axios.post('/namespaces/list', props, {
|
||||||
|
signal,
|
||||||
|
headers,
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
statusCode: 200,
|
||||||
|
error: null,
|
||||||
|
message: 'Success',
|
||||||
|
payload: response.data,
|
||||||
|
params: props,
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
return ErrorResponseHandler(error as AxiosError);
|
||||||
|
}
|
||||||
|
};
|
||||||
@ -127,6 +127,18 @@ function HostMetricsLogs({
|
|||||||
data={logToRender}
|
data={logToRender}
|
||||||
linesPerRow={5}
|
linesPerRow={5}
|
||||||
fontSize={FontSize.MEDIUM}
|
fontSize={FontSize.MEDIUM}
|
||||||
|
selectedFields={[
|
||||||
|
{
|
||||||
|
dataType: 'string',
|
||||||
|
type: '',
|
||||||
|
name: 'body',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
dataType: 'string',
|
||||||
|
type: '',
|
||||||
|
name: 'timestamp',
|
||||||
|
},
|
||||||
|
]}
|
||||||
/>
|
/>
|
||||||
),
|
),
|
||||||
[],
|
[],
|
||||||
|
|||||||
@ -22,4 +22,8 @@ export const REACT_QUERY_KEY = {
|
|||||||
UPDATE_ALERT_RULE: 'UPDATE_ALERT_RULE',
|
UPDATE_ALERT_RULE: 'UPDATE_ALERT_RULE',
|
||||||
GET_ACTIVE_LICENSE_V3: 'GET_ACTIVE_LICENSE_V3',
|
GET_ACTIVE_LICENSE_V3: 'GET_ACTIVE_LICENSE_V3',
|
||||||
GET_POD_LIST: 'GET_POD_LIST',
|
GET_POD_LIST: 'GET_POD_LIST',
|
||||||
|
GET_NODE_LIST: 'GET_NODE_LIST',
|
||||||
|
GET_DEPLOYMENT_LIST: 'GET_DEPLOYMENT_LIST',
|
||||||
|
GET_CLUSTER_LIST: 'GET_CLUSTER_LIST',
|
||||||
|
GET_NAMESPACE_LIST: 'GET_NAMESPACE_LIST',
|
||||||
};
|
};
|
||||||
|
|||||||
@ -0,0 +1,7 @@
|
|||||||
|
import { K8sClustersData } from 'api/infraMonitoring/getK8sClustersList';
|
||||||
|
|
||||||
|
export type ClusterDetailsProps = {
|
||||||
|
cluster: K8sClustersData | null;
|
||||||
|
isModalTimeSelection: boolean;
|
||||||
|
onClose: () => void;
|
||||||
|
};
|
||||||
@ -0,0 +1,558 @@
|
|||||||
|
/* 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 { K8sClustersData } from 'api/infraMonitoring/getK8sClustersList';
|
||||||
|
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 { filterDuplicateFilters } from 'container/InfraMonitoringK8s/commonUtils';
|
||||||
|
import { K8sCategory } from 'container/InfraMonitoringK8s/constants';
|
||||||
|
import { QUERY_KEYS } from 'container/InfraMonitoringK8s/EntityDetailsUtils/utils';
|
||||||
|
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 ClusterEvents from '../../EntityDetailsUtils/EntityEvents';
|
||||||
|
import ClusterLogs from '../../EntityDetailsUtils/EntityLogs';
|
||||||
|
import ClusterMetrics from '../../EntityDetailsUtils/EntityMetrics';
|
||||||
|
import ClusterTraces from '../../EntityDetailsUtils/EntityTraces';
|
||||||
|
import { ClusterDetailsProps } from './ClusterDetails.interfaces';
|
||||||
|
import { clusterWidgetInfo, getClusterMetricsQueryPayload } from './constants';
|
||||||
|
|
||||||
|
function ClusterDetails({
|
||||||
|
cluster,
|
||||||
|
onClose,
|
||||||
|
isModalTimeSelection,
|
||||||
|
}: ClusterDetailsProps): 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<Time>(
|
||||||
|
selectedTime as Time,
|
||||||
|
);
|
||||||
|
|
||||||
|
const [selectedView, setSelectedView] = useState<VIEWS>(VIEWS.METRICS);
|
||||||
|
const isDarkMode = useIsDarkMode();
|
||||||
|
|
||||||
|
const initialFilters = useMemo(
|
||||||
|
() => ({
|
||||||
|
op: 'AND',
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
id: uuidv4(),
|
||||||
|
key: {
|
||||||
|
key: QUERY_KEYS.K8S_CLUSTER_NAME,
|
||||||
|
dataType: DataTypes.String,
|
||||||
|
type: 'resource',
|
||||||
|
isColumn: false,
|
||||||
|
isJSON: false,
|
||||||
|
id: 'k8s_cluster_name--string--resource--false',
|
||||||
|
},
|
||||||
|
op: '=',
|
||||||
|
value: cluster?.meta.k8s_cluster_name || '',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
[cluster?.meta.k8s_cluster_name],
|
||||||
|
);
|
||||||
|
|
||||||
|
const initialEventsFilters = useMemo(
|
||||||
|
() => ({
|
||||||
|
op: 'AND',
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
id: uuidv4(),
|
||||||
|
key: {
|
||||||
|
key: QUERY_KEYS.K8S_OBJECT_KIND,
|
||||||
|
dataType: DataTypes.String,
|
||||||
|
type: 'resource',
|
||||||
|
isColumn: false,
|
||||||
|
isJSON: false,
|
||||||
|
id: 'k8s.object.kind--string--resource--false',
|
||||||
|
},
|
||||||
|
op: '=',
|
||||||
|
value: 'Cluster',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: uuidv4(),
|
||||||
|
key: {
|
||||||
|
key: QUERY_KEYS.K8S_OBJECT_NAME,
|
||||||
|
dataType: DataTypes.String,
|
||||||
|
type: 'resource',
|
||||||
|
isColumn: false,
|
||||||
|
isJSON: false,
|
||||||
|
id: 'k8s.object.name--string--resource--false',
|
||||||
|
},
|
||||||
|
op: '=',
|
||||||
|
value: cluster?.meta.k8s_cluster_name || '',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
[cluster?.meta.k8s_cluster_name],
|
||||||
|
);
|
||||||
|
|
||||||
|
const [logFilters, setLogFilters] = useState<IBuilderQuery['filters']>(
|
||||||
|
initialFilters,
|
||||||
|
);
|
||||||
|
|
||||||
|
const [tracesFilters, setTracesFilters] = useState<IBuilderQuery['filters']>(
|
||||||
|
initialFilters,
|
||||||
|
);
|
||||||
|
|
||||||
|
const [eventsFilters, setEventsFilters] = useState<IBuilderQuery['filters']>(
|
||||||
|
initialEventsFilters,
|
||||||
|
);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
logEvent('Infra Monitoring: Clusters list details page visited', {
|
||||||
|
cluster: cluster?.clusterUID,
|
||||||
|
});
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setLogFilters(initialFilters);
|
||||||
|
setTracesFilters(initialFilters);
|
||||||
|
setEventsFilters(initialEventsFilters);
|
||||||
|
}, [initialFilters, initialEventsFilters]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setSelectedInterval(selectedTime as Time);
|
||||||
|
|
||||||
|
if (selectedTime !== 'custom') {
|
||||||
|
const { maxTime, minTime } = GetMinMax(selectedTime);
|
||||||
|
|
||||||
|
setModalTimeRange({
|
||||||
|
startTime: Math.floor(minTime / 1000000000),
|
||||||
|
endTime: Math.floor(maxTime / 1000000000),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, [selectedTime, minTime, maxTime]);
|
||||||
|
|
||||||
|
const handleTabChange = (e: RadioChangeEvent): void => {
|
||||||
|
setSelectedView(e.target.value);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleTimeChange = useCallback(
|
||||||
|
(interval: Time | CustomTimeType, dateTimeRange?: [number, number]): void => {
|
||||||
|
setSelectedInterval(interval as Time);
|
||||||
|
|
||||||
|
if (interval === 'custom' && dateTimeRange) {
|
||||||
|
setModalTimeRange({
|
||||||
|
startTime: Math.floor(dateTimeRange[0] / 1000),
|
||||||
|
endTime: Math.floor(dateTimeRange[1] / 1000),
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
const { maxTime, minTime } = GetMinMax(interval);
|
||||||
|
|
||||||
|
setModalTimeRange({
|
||||||
|
startTime: Math.floor(minTime / 1000000000),
|
||||||
|
endTime: Math.floor(maxTime / 1000000000),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
logEvent('Infra Monitoring: Clusters list details time updated', {
|
||||||
|
cluster: cluster?.clusterUID,
|
||||||
|
interval,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
[],
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleChangeLogFilters = useCallback(
|
||||||
|
(value: IBuilderQuery['filters']) => {
|
||||||
|
setLogFilters((prevFilters) => {
|
||||||
|
const primaryFilters = prevFilters.items.filter((item) =>
|
||||||
|
[QUERY_KEYS.K8S_CLUSTER_NAME].includes(item.key?.key ?? ''),
|
||||||
|
);
|
||||||
|
const paginationFilter = value.items.find((item) => item.key?.key === 'id');
|
||||||
|
const newFilters = value.items.filter(
|
||||||
|
(item) =>
|
||||||
|
item.key?.key !== 'id' && item.key?.key !== QUERY_KEYS.K8S_CLUSTER_NAME,
|
||||||
|
);
|
||||||
|
|
||||||
|
logEvent('Infra Monitoring: Clusters list details logs filters applied', {
|
||||||
|
cluster: cluster?.clusterUID,
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
op: 'AND',
|
||||||
|
items: filterDuplicateFilters(
|
||||||
|
[
|
||||||
|
...primaryFilters,
|
||||||
|
...newFilters,
|
||||||
|
...(paginationFilter ? [paginationFilter] : []),
|
||||||
|
].filter((item): item is TagFilterItem => item !== undefined),
|
||||||
|
),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
},
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
[],
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleChangeTracesFilters = useCallback(
|
||||||
|
(value: IBuilderQuery['filters']) => {
|
||||||
|
setTracesFilters((prevFilters) => {
|
||||||
|
const primaryFilters = prevFilters.items.filter((item) =>
|
||||||
|
[QUERY_KEYS.K8S_CLUSTER_NAME].includes(item.key?.key ?? ''),
|
||||||
|
);
|
||||||
|
|
||||||
|
logEvent('Infra Monitoring: Clusters list details traces filters applied', {
|
||||||
|
cluster: cluster?.clusterUID,
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
op: 'AND',
|
||||||
|
items: filterDuplicateFilters(
|
||||||
|
[
|
||||||
|
...primaryFilters,
|
||||||
|
...value.items.filter(
|
||||||
|
(item) => item.key?.key !== QUERY_KEYS.K8S_CLUSTER_NAME,
|
||||||
|
),
|
||||||
|
].filter((item): item is TagFilterItem => item !== undefined),
|
||||||
|
),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
},
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
[],
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleChangeEventsFilters = useCallback(
|
||||||
|
(value: IBuilderQuery['filters']) => {
|
||||||
|
setEventsFilters((prevFilters) => {
|
||||||
|
const clusterKindFilter = prevFilters.items.find(
|
||||||
|
(item) => item.key?.key === QUERY_KEYS.K8S_OBJECT_KIND,
|
||||||
|
);
|
||||||
|
const clusterNameFilter = prevFilters.items.find(
|
||||||
|
(item) => item.key?.key === QUERY_KEYS.K8S_OBJECT_NAME,
|
||||||
|
);
|
||||||
|
|
||||||
|
logEvent('Infra Monitoring: Clusters list details events filters applied', {
|
||||||
|
cluster: cluster?.clusterUID,
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
op: 'AND',
|
||||||
|
items: filterDuplicateFilters(
|
||||||
|
[
|
||||||
|
clusterKindFilter,
|
||||||
|
clusterNameFilter,
|
||||||
|
...value.items.filter(
|
||||||
|
(item) =>
|
||||||
|
item.key?.key !== QUERY_KEYS.K8S_OBJECT_KIND &&
|
||||||
|
item.key?.key !== QUERY_KEYS.K8S_OBJECT_NAME,
|
||||||
|
),
|
||||||
|
].filter((item): item is TagFilterItem => item !== undefined),
|
||||||
|
),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
},
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
[],
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleExplorePagesRedirect = (): void => {
|
||||||
|
if (selectedInterval !== 'custom') {
|
||||||
|
urlQuery.set(QueryParams.relativeTime, selectedInterval);
|
||||||
|
} else {
|
||||||
|
urlQuery.delete(QueryParams.relativeTime);
|
||||||
|
urlQuery.set(QueryParams.startTime, modalTimeRange.startTime.toString());
|
||||||
|
urlQuery.set(QueryParams.endTime, modalTimeRange.endTime.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
logEvent('Infra Monitoring: Clusters list details explore clicked', {
|
||||||
|
cluster: cluster?.clusterUID,
|
||||||
|
view: selectedView,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (selectedView === VIEW_TYPES.LOGS) {
|
||||||
|
const filtersWithoutPagination = {
|
||||||
|
...logFilters,
|
||||||
|
items: logFilters.items.filter((item) => item.key?.key !== 'id'),
|
||||||
|
};
|
||||||
|
|
||||||
|
const compositeQuery = {
|
||||||
|
...initialQueryState,
|
||||||
|
queryType: 'builder',
|
||||||
|
builder: {
|
||||||
|
...initialQueryState.builder,
|
||||||
|
queryData: [
|
||||||
|
{
|
||||||
|
...initialQueryBuilderFormValuesMap.logs,
|
||||||
|
aggregateOperator: LogsAggregatorOperator.NOOP,
|
||||||
|
filters: filtersWithoutPagination,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
urlQuery.set('compositeQuery', JSON.stringify(compositeQuery));
|
||||||
|
|
||||||
|
window.open(
|
||||||
|
`${window.location.origin}${ROUTES.LOGS_EXPLORER}?${urlQuery.toString()}`,
|
||||||
|
'_blank',
|
||||||
|
);
|
||||||
|
} else if (selectedView === VIEW_TYPES.TRACES) {
|
||||||
|
const compositeQuery = {
|
||||||
|
...initialQueryState,
|
||||||
|
queryType: 'builder',
|
||||||
|
builder: {
|
||||||
|
...initialQueryState.builder,
|
||||||
|
queryData: [
|
||||||
|
{
|
||||||
|
...initialQueryBuilderFormValuesMap.traces,
|
||||||
|
aggregateOperator: TracesAggregatorOperator.NOOP,
|
||||||
|
filters: tracesFilters,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
urlQuery.set('compositeQuery', JSON.stringify(compositeQuery));
|
||||||
|
|
||||||
|
window.open(
|
||||||
|
`${window.location.origin}${ROUTES.TRACES_EXPLORER}?${urlQuery.toString()}`,
|
||||||
|
'_blank',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleClose = (): void => {
|
||||||
|
setSelectedInterval(selectedTime as Time);
|
||||||
|
|
||||||
|
if (selectedTime !== 'custom') {
|
||||||
|
const { maxTime, minTime } = GetMinMax(selectedTime);
|
||||||
|
|
||||||
|
setModalTimeRange({
|
||||||
|
startTime: Math.floor(minTime / 1000000000),
|
||||||
|
endTime: Math.floor(maxTime / 1000000000),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
setSelectedView(VIEW_TYPES.METRICS);
|
||||||
|
onClose();
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Drawer
|
||||||
|
width="70%"
|
||||||
|
title={
|
||||||
|
<>
|
||||||
|
<Divider type="vertical" />
|
||||||
|
<Typography.Text className="title">
|
||||||
|
{cluster?.meta.k8s_cluster_name}
|
||||||
|
</Typography.Text>
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
placement="right"
|
||||||
|
onClose={handleClose}
|
||||||
|
open={!!cluster}
|
||||||
|
style={{
|
||||||
|
overscrollBehavior: 'contain',
|
||||||
|
background: isDarkMode ? Color.BG_INK_400 : Color.BG_VANILLA_100,
|
||||||
|
}}
|
||||||
|
className="entity-detail-drawer"
|
||||||
|
destroyOnClose
|
||||||
|
closeIcon={<X size={16} style={{ marginTop: Spacing.MARGIN_1 }} />}
|
||||||
|
>
|
||||||
|
{cluster && (
|
||||||
|
<>
|
||||||
|
<div className="entity-detail-drawer__entity">
|
||||||
|
<div className="entity-details-grid">
|
||||||
|
<div className="labels-row">
|
||||||
|
<Typography.Text
|
||||||
|
type="secondary"
|
||||||
|
className="entity-details-metadata-label"
|
||||||
|
>
|
||||||
|
Cluster Name
|
||||||
|
</Typography.Text>
|
||||||
|
<Typography.Text
|
||||||
|
type="secondary"
|
||||||
|
className="entity-details-metadata-label"
|
||||||
|
>
|
||||||
|
Cluster Name
|
||||||
|
</Typography.Text>
|
||||||
|
</div>
|
||||||
|
<div className="values-row">
|
||||||
|
<Typography.Text className="entity-details-metadata-value">
|
||||||
|
<Tooltip title={cluster.meta.k8s_cluster_name}>
|
||||||
|
{cluster.meta.k8s_cluster_name}
|
||||||
|
</Tooltip>
|
||||||
|
</Typography.Text>
|
||||||
|
<Typography.Text className="entity-details-metadata-value">
|
||||||
|
<Tooltip title="Cluster name">{cluster.meta.k8s_cluster_name}</Tooltip>
|
||||||
|
</Typography.Text>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="views-tabs-container">
|
||||||
|
<Radio.Group
|
||||||
|
className="views-tabs"
|
||||||
|
onChange={handleTabChange}
|
||||||
|
value={selectedView}
|
||||||
|
>
|
||||||
|
<Radio.Button
|
||||||
|
className={
|
||||||
|
// eslint-disable-next-line sonarjs/no-duplicate-string
|
||||||
|
selectedView === VIEW_TYPES.METRICS ? 'selected_view tab' : 'tab'
|
||||||
|
}
|
||||||
|
value={VIEW_TYPES.METRICS}
|
||||||
|
>
|
||||||
|
<div className="view-title">
|
||||||
|
<BarChart2 size={14} />
|
||||||
|
Metrics
|
||||||
|
</div>
|
||||||
|
</Radio.Button>
|
||||||
|
<Radio.Button
|
||||||
|
className={
|
||||||
|
selectedView === VIEW_TYPES.LOGS ? 'selected_view tab' : 'tab'
|
||||||
|
}
|
||||||
|
value={VIEW_TYPES.LOGS}
|
||||||
|
>
|
||||||
|
<div className="view-title">
|
||||||
|
<ScrollText size={14} />
|
||||||
|
Logs
|
||||||
|
</div>
|
||||||
|
</Radio.Button>
|
||||||
|
<Radio.Button
|
||||||
|
className={
|
||||||
|
selectedView === VIEW_TYPES.TRACES ? 'selected_view tab' : 'tab'
|
||||||
|
}
|
||||||
|
value={VIEW_TYPES.TRACES}
|
||||||
|
>
|
||||||
|
<div className="view-title">
|
||||||
|
<DraftingCompass size={14} />
|
||||||
|
Traces
|
||||||
|
</div>
|
||||||
|
</Radio.Button>
|
||||||
|
<Radio.Button
|
||||||
|
className={
|
||||||
|
selectedView === VIEW_TYPES.EVENTS ? 'selected_view tab' : 'tab'
|
||||||
|
}
|
||||||
|
value={VIEW_TYPES.EVENTS}
|
||||||
|
>
|
||||||
|
<div className="view-title">
|
||||||
|
<ChevronsLeftRight size={14} />
|
||||||
|
Events
|
||||||
|
</div>
|
||||||
|
</Radio.Button>
|
||||||
|
</Radio.Group>
|
||||||
|
|
||||||
|
{(selectedView === VIEW_TYPES.LOGS ||
|
||||||
|
selectedView === VIEW_TYPES.TRACES) && (
|
||||||
|
<Button
|
||||||
|
icon={<Compass size={18} />}
|
||||||
|
className="compass-button"
|
||||||
|
onClick={handleExplorePagesRedirect}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
{selectedView === VIEW_TYPES.METRICS && (
|
||||||
|
<ClusterMetrics<K8sClustersData>
|
||||||
|
timeRange={modalTimeRange}
|
||||||
|
isModalTimeSelection={isModalTimeSelection}
|
||||||
|
handleTimeChange={handleTimeChange}
|
||||||
|
selectedInterval={selectedInterval}
|
||||||
|
entity={cluster}
|
||||||
|
entityWidgetInfo={clusterWidgetInfo}
|
||||||
|
getEntityQueryPayload={getClusterMetricsQueryPayload}
|
||||||
|
category={K8sCategory.CLUSTERS}
|
||||||
|
queryKey="clusterMetrics"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{selectedView === VIEW_TYPES.LOGS && (
|
||||||
|
<ClusterLogs
|
||||||
|
timeRange={modalTimeRange}
|
||||||
|
isModalTimeSelection={isModalTimeSelection}
|
||||||
|
handleTimeChange={handleTimeChange}
|
||||||
|
handleChangeLogFilters={handleChangeLogFilters}
|
||||||
|
logFilters={logFilters}
|
||||||
|
selectedInterval={selectedInterval}
|
||||||
|
queryKey="clusterLogs"
|
||||||
|
category={K8sCategory.CLUSTERS}
|
||||||
|
queryKeyFilters={[QUERY_KEYS.K8S_CLUSTER_NAME]}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{selectedView === VIEW_TYPES.TRACES && (
|
||||||
|
<ClusterTraces
|
||||||
|
timeRange={modalTimeRange}
|
||||||
|
isModalTimeSelection={isModalTimeSelection}
|
||||||
|
handleTimeChange={handleTimeChange}
|
||||||
|
handleChangeTracesFilters={handleChangeTracesFilters}
|
||||||
|
tracesFilters={tracesFilters}
|
||||||
|
selectedInterval={selectedInterval}
|
||||||
|
queryKey="clusterTraces"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{selectedView === VIEW_TYPES.EVENTS && (
|
||||||
|
<ClusterEvents
|
||||||
|
timeRange={modalTimeRange}
|
||||||
|
handleChangeEventFilters={handleChangeEventsFilters}
|
||||||
|
filters={eventsFilters}
|
||||||
|
isModalTimeSelection={isModalTimeSelection}
|
||||||
|
handleTimeChange={handleTimeChange}
|
||||||
|
selectedInterval={selectedInterval}
|
||||||
|
category={K8sCategory.CLUSTERS}
|
||||||
|
queryKey="clusterEvents"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</Drawer>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ClusterDetails;
|
||||||
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,3 @@
|
|||||||
|
import ClusterDetails from './ClusterDetails';
|
||||||
|
|
||||||
|
export default ClusterDetails;
|
||||||
@ -0,0 +1,17 @@
|
|||||||
|
.infra-monitoring-container {
|
||||||
|
.clusters-list-table {
|
||||||
|
.expanded-table-container {
|
||||||
|
padding-left: 40px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-table-cell {
|
||||||
|
min-width: 223px !important;
|
||||||
|
max-width: 223px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-table-row-expand-icon-cell {
|
||||||
|
min-width: 30px !important;
|
||||||
|
max-width: 30px !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,511 @@
|
|||||||
|
/* eslint-disable no-restricted-syntax */
|
||||||
|
/* eslint-disable @typescript-eslint/explicit-function-return-type */
|
||||||
|
import '../InfraMonitoringK8s.styles.scss';
|
||||||
|
import './K8sClustersList.styles.scss';
|
||||||
|
|
||||||
|
import { LoadingOutlined } from '@ant-design/icons';
|
||||||
|
import {
|
||||||
|
Button,
|
||||||
|
Spin,
|
||||||
|
Table,
|
||||||
|
TablePaginationConfig,
|
||||||
|
TableProps,
|
||||||
|
Typography,
|
||||||
|
} from 'antd';
|
||||||
|
import { ColumnType, SorterResult } from 'antd/es/table/interface';
|
||||||
|
import logEvent from 'api/common/logEvent';
|
||||||
|
import { K8sClustersListPayload } from 'api/infraMonitoring/getK8sClustersList';
|
||||||
|
import { useGetK8sClustersList } from 'hooks/infraMonitoring/useGetK8sClustersList';
|
||||||
|
import { useGetAggregateKeys } from 'hooks/queryBuilder/useGetAggregateKeys';
|
||||||
|
import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
|
||||||
|
import { useQueryOperations } from 'hooks/queryBuilder/useQueryBuilderOperations';
|
||||||
|
import { ChevronDown, ChevronRight } from 'lucide-react';
|
||||||
|
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 {
|
||||||
|
K8sCategory,
|
||||||
|
K8sEntityToAggregateAttributeMapping,
|
||||||
|
} from '../constants';
|
||||||
|
import K8sHeader from '../K8sHeader';
|
||||||
|
import LoadingContainer from '../LoadingContainer';
|
||||||
|
import ClusterDetails from './ClusterDetails';
|
||||||
|
import {
|
||||||
|
defaultAddedColumns,
|
||||||
|
formatDataForTable,
|
||||||
|
getK8sClustersListColumns,
|
||||||
|
getK8sClustersListQuery,
|
||||||
|
K8sClustersRowData,
|
||||||
|
} from './utils';
|
||||||
|
|
||||||
|
// eslint-disable-next-line sonarjs/cognitive-complexity
|
||||||
|
function K8sClustersList({
|
||||||
|
isFiltersVisible,
|
||||||
|
handleFilterVisibilityChange,
|
||||||
|
quickFiltersLastUpdated,
|
||||||
|
}: {
|
||||||
|
isFiltersVisible: boolean;
|
||||||
|
handleFilterVisibilityChange: () => void;
|
||||||
|
quickFiltersLastUpdated: number;
|
||||||
|
}): JSX.Element {
|
||||||
|
const { maxTime, minTime } = useSelector<AppState, GlobalReducer>(
|
||||||
|
(state) => state.globalTime,
|
||||||
|
);
|
||||||
|
|
||||||
|
const [currentPage, setCurrentPage] = useState(1);
|
||||||
|
|
||||||
|
const [expandedRowKeys, setExpandedRowKeys] = useState<string[]>([]);
|
||||||
|
|
||||||
|
const [orderBy, setOrderBy] = useState<{
|
||||||
|
columnName: string;
|
||||||
|
order: 'asc' | 'desc';
|
||||||
|
} | null>({ columnName: 'cpu', order: 'desc' });
|
||||||
|
|
||||||
|
const [selectedClusterName, setselectedClusterName] = useState<string | null>(
|
||||||
|
null,
|
||||||
|
);
|
||||||
|
|
||||||
|
const pageSize = 10;
|
||||||
|
|
||||||
|
const [groupBy, setGroupBy] = useState<IBuilderQuery['groupBy']>([]);
|
||||||
|
|
||||||
|
const [
|
||||||
|
selectedRowData,
|
||||||
|
setSelectedRowData,
|
||||||
|
] = useState<K8sClustersRowData | null>(null);
|
||||||
|
|
||||||
|
const [groupByOptions, setGroupByOptions] = useState<
|
||||||
|
{ value: string; label: string }[]
|
||||||
|
>([]);
|
||||||
|
|
||||||
|
const { currentQuery } = useQueryBuilder();
|
||||||
|
|
||||||
|
const queryFilters = useMemo(
|
||||||
|
() =>
|
||||||
|
currentQuery?.builder?.queryData[0]?.filters || {
|
||||||
|
items: [],
|
||||||
|
op: 'and',
|
||||||
|
},
|
||||||
|
[currentQuery?.builder?.queryData],
|
||||||
|
);
|
||||||
|
|
||||||
|
// Reset pagination every time quick filters are changed
|
||||||
|
useEffect(() => {
|
||||||
|
setCurrentPage(1);
|
||||||
|
}, [quickFiltersLastUpdated]);
|
||||||
|
|
||||||
|
const createFiltersForSelectedRowData = (
|
||||||
|
selectedRowData: K8sClustersRowData,
|
||||||
|
groupBy: IBuilderQuery['groupBy'],
|
||||||
|
): IBuilderQuery['filters'] => {
|
||||||
|
const baseFilters: IBuilderQuery['filters'] = {
|
||||||
|
items: [...queryFilters.items],
|
||||||
|
op: 'and',
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!selectedRowData) return baseFilters;
|
||||||
|
|
||||||
|
const { groupedByMeta } = selectedRowData;
|
||||||
|
|
||||||
|
for (const key of groupBy) {
|
||||||
|
baseFilters.items.push({
|
||||||
|
key: {
|
||||||
|
key: key.key,
|
||||||
|
type: null,
|
||||||
|
},
|
||||||
|
op: '=',
|
||||||
|
value: groupedByMeta[key.key],
|
||||||
|
id: key.key,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return baseFilters;
|
||||||
|
};
|
||||||
|
|
||||||
|
const fetchGroupedByRowDataQuery = useMemo(() => {
|
||||||
|
if (!selectedRowData) return null;
|
||||||
|
|
||||||
|
const baseQuery = getK8sClustersListQuery();
|
||||||
|
|
||||||
|
const filters = createFiltersForSelectedRowData(selectedRowData, groupBy);
|
||||||
|
|
||||||
|
return {
|
||||||
|
...baseQuery,
|
||||||
|
limit: 10,
|
||||||
|
offset: 0,
|
||||||
|
filters,
|
||||||
|
start: Math.floor(minTime / 1000000),
|
||||||
|
end: Math.floor(maxTime / 1000000),
|
||||||
|
orderBy,
|
||||||
|
};
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
}, [minTime, maxTime, orderBy, selectedRowData, groupBy]);
|
||||||
|
|
||||||
|
const {
|
||||||
|
data: groupedByRowData,
|
||||||
|
isFetching: isFetchingGroupedByRowData,
|
||||||
|
isLoading: isLoadingGroupedByRowData,
|
||||||
|
isError: isErrorGroupedByRowData,
|
||||||
|
refetch: fetchGroupedByRowData,
|
||||||
|
} = useGetK8sClustersList(
|
||||||
|
fetchGroupedByRowDataQuery as K8sClustersListPayload,
|
||||||
|
{
|
||||||
|
queryKey: ['clusterList', fetchGroupedByRowDataQuery],
|
||||||
|
enabled: !!fetchGroupedByRowDataQuery && !!selectedRowData,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
const {
|
||||||
|
data: groupByFiltersData,
|
||||||
|
isLoading: isLoadingGroupByFilters,
|
||||||
|
} = useGetAggregateKeys(
|
||||||
|
{
|
||||||
|
dataSource: currentQuery.builder.queryData[0].dataSource,
|
||||||
|
aggregateAttribute: K8sEntityToAggregateAttributeMapping[K8sCategory.NODES],
|
||||||
|
aggregateOperator: 'noop',
|
||||||
|
searchText: '',
|
||||||
|
tagType: '',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
queryKey: [currentQuery.builder.queryData[0].dataSource, 'noop'],
|
||||||
|
},
|
||||||
|
true,
|
||||||
|
K8sCategory.NODES,
|
||||||
|
);
|
||||||
|
|
||||||
|
const query = useMemo(() => {
|
||||||
|
const baseQuery = getK8sClustersListQuery();
|
||||||
|
const queryPayload = {
|
||||||
|
...baseQuery,
|
||||||
|
limit: pageSize,
|
||||||
|
offset: (currentPage - 1) * pageSize,
|
||||||
|
filters: queryFilters,
|
||||||
|
start: Math.floor(minTime / 1000000),
|
||||||
|
end: Math.floor(maxTime / 1000000),
|
||||||
|
orderBy,
|
||||||
|
};
|
||||||
|
if (groupBy.length > 0) {
|
||||||
|
queryPayload.groupBy = groupBy;
|
||||||
|
}
|
||||||
|
return queryPayload;
|
||||||
|
}, [currentPage, minTime, maxTime, orderBy, groupBy, queryFilters]);
|
||||||
|
|
||||||
|
const formattedGroupedByClustersData = useMemo(
|
||||||
|
() =>
|
||||||
|
formatDataForTable(groupedByRowData?.payload?.data?.records || [], groupBy),
|
||||||
|
[groupedByRowData, groupBy],
|
||||||
|
);
|
||||||
|
|
||||||
|
const { data, isFetching, isLoading, isError } = useGetK8sClustersList(
|
||||||
|
query as K8sClustersListPayload,
|
||||||
|
{
|
||||||
|
queryKey: ['clusterList', query],
|
||||||
|
enabled: !!query,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
const clustersData = useMemo(() => data?.payload?.data?.records || [], [data]);
|
||||||
|
const totalCount = data?.payload?.data?.total || 0;
|
||||||
|
|
||||||
|
const formattedClustersData = useMemo(
|
||||||
|
() => formatDataForTable(clustersData, groupBy),
|
||||||
|
[clustersData, groupBy],
|
||||||
|
);
|
||||||
|
|
||||||
|
const columns = useMemo(() => getK8sClustersListColumns(groupBy), [groupBy]);
|
||||||
|
|
||||||
|
const handleGroupByRowClick = (record: K8sClustersRowData): void => {
|
||||||
|
setSelectedRowData(record);
|
||||||
|
|
||||||
|
if (expandedRowKeys.includes(record.key)) {
|
||||||
|
setExpandedRowKeys(expandedRowKeys.filter((key) => key !== record.key));
|
||||||
|
} else {
|
||||||
|
setExpandedRowKeys([record.key]);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (selectedRowData) {
|
||||||
|
fetchGroupedByRowData();
|
||||||
|
}
|
||||||
|
}, [selectedRowData, fetchGroupedByRowData]);
|
||||||
|
|
||||||
|
const handleTableChange: TableProps<K8sClustersRowData>['onChange'] = useCallback(
|
||||||
|
(
|
||||||
|
pagination: TablePaginationConfig,
|
||||||
|
_filters: Record<string, (string | number | boolean)[] | null>,
|
||||||
|
sorter:
|
||||||
|
| SorterResult<K8sClustersRowData>
|
||||||
|
| SorterResult<K8sClustersRowData>[],
|
||||||
|
): 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 { handleChangeQueryData } = useQueryOperations({
|
||||||
|
index: 0,
|
||||||
|
query: currentQuery.builder.queryData[0],
|
||||||
|
entityVersion: '',
|
||||||
|
});
|
||||||
|
|
||||||
|
const handleFiltersChange = useCallback(
|
||||||
|
(value: IBuilderQuery['filters']): void => {
|
||||||
|
handleChangeQueryData('filters', value);
|
||||||
|
setCurrentPage(1);
|
||||||
|
|
||||||
|
logEvent('Infra Monitoring: K8s list filters applied', {
|
||||||
|
filters: value,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
[handleChangeQueryData],
|
||||||
|
);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
logEvent('Infra Monitoring: K8s list page visited', {});
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const selectedClusterData = useMemo(() => {
|
||||||
|
if (!selectedClusterName) return null;
|
||||||
|
return (
|
||||||
|
clustersData.find(
|
||||||
|
(cluster) => cluster.meta.k8s_cluster_name === selectedClusterName,
|
||||||
|
) || null
|
||||||
|
);
|
||||||
|
}, [selectedClusterName, clustersData]);
|
||||||
|
|
||||||
|
const handleRowClick = (record: K8sClustersRowData): void => {
|
||||||
|
if (groupBy.length === 0) {
|
||||||
|
setSelectedRowData(null);
|
||||||
|
setselectedClusterName(record.clusterUID);
|
||||||
|
} else {
|
||||||
|
handleGroupByRowClick(record);
|
||||||
|
}
|
||||||
|
|
||||||
|
logEvent('Infra Monitoring: K8s cluster list item clicked', {
|
||||||
|
clusterName: record.clusterName,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const nestedColumns = useMemo(() => getK8sClustersListColumns([]), []);
|
||||||
|
|
||||||
|
const isGroupedByAttribute = groupBy.length > 0;
|
||||||
|
|
||||||
|
const handleExpandedRowViewAllClick = (): void => {
|
||||||
|
if (!selectedRowData) return;
|
||||||
|
|
||||||
|
const filters = createFiltersForSelectedRowData(selectedRowData, groupBy);
|
||||||
|
|
||||||
|
handleFiltersChange(filters);
|
||||||
|
|
||||||
|
setCurrentPage(1);
|
||||||
|
setSelectedRowData(null);
|
||||||
|
setGroupBy([]);
|
||||||
|
setOrderBy(null);
|
||||||
|
};
|
||||||
|
|
||||||
|
const expandedRowRender = (): JSX.Element => (
|
||||||
|
<div className="expanded-table-container">
|
||||||
|
{isErrorGroupedByRowData && (
|
||||||
|
<Typography>{groupedByRowData?.error || 'Something went wrong'}</Typography>
|
||||||
|
)}
|
||||||
|
{isFetchingGroupedByRowData || isLoadingGroupedByRowData ? (
|
||||||
|
<LoadingContainer />
|
||||||
|
) : (
|
||||||
|
<div className="expanded-table">
|
||||||
|
<Table
|
||||||
|
columns={nestedColumns as ColumnType<K8sClustersRowData>[]}
|
||||||
|
dataSource={formattedGroupedByClustersData}
|
||||||
|
pagination={false}
|
||||||
|
scroll={{ x: true }}
|
||||||
|
tableLayout="fixed"
|
||||||
|
size="small"
|
||||||
|
loading={{
|
||||||
|
spinning: isFetchingGroupedByRowData || isLoadingGroupedByRowData,
|
||||||
|
indicator: <Spin indicator={<LoadingOutlined size={14} spin />} />,
|
||||||
|
}}
|
||||||
|
showHeader={false}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{groupedByRowData?.payload?.data?.total &&
|
||||||
|
groupedByRowData?.payload?.data?.total > 10 ? (
|
||||||
|
<div className="expanded-table-footer">
|
||||||
|
<Button
|
||||||
|
type="default"
|
||||||
|
size="small"
|
||||||
|
className="periscope-btn secondary"
|
||||||
|
onClick={handleExpandedRowViewAllClick}
|
||||||
|
>
|
||||||
|
View All
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
const expandRowIconRenderer = ({
|
||||||
|
expanded,
|
||||||
|
onExpand,
|
||||||
|
record,
|
||||||
|
}: {
|
||||||
|
expanded: boolean;
|
||||||
|
onExpand: (
|
||||||
|
record: K8sClustersRowData,
|
||||||
|
e: React.MouseEvent<HTMLButtonElement>,
|
||||||
|
) => void;
|
||||||
|
record: K8sClustersRowData;
|
||||||
|
}): JSX.Element | null => {
|
||||||
|
if (!isGroupedByAttribute) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return expanded ? (
|
||||||
|
<Button
|
||||||
|
className="periscope-btn ghost"
|
||||||
|
onClick={(e: React.MouseEvent<HTMLButtonElement>): void =>
|
||||||
|
onExpand(record, e)
|
||||||
|
}
|
||||||
|
role="button"
|
||||||
|
>
|
||||||
|
<ChevronDown size={14} />
|
||||||
|
</Button>
|
||||||
|
) : (
|
||||||
|
<Button
|
||||||
|
className="periscope-btn ghost"
|
||||||
|
onClick={(e: React.MouseEvent<HTMLButtonElement>): void =>
|
||||||
|
onExpand(record, e)
|
||||||
|
}
|
||||||
|
role="button"
|
||||||
|
>
|
||||||
|
<ChevronRight size={14} />
|
||||||
|
</Button>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleCloseClusterDetail = (): void => {
|
||||||
|
setselectedClusterName(null);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleGroupByChange = useCallback(
|
||||||
|
(value: IBuilderQuery['groupBy']) => {
|
||||||
|
const groupBy = [];
|
||||||
|
|
||||||
|
for (let index = 0; index < value.length; index++) {
|
||||||
|
const element = (value[index] as unknown) as string;
|
||||||
|
|
||||||
|
const key = groupByFiltersData?.payload?.attributeKeys?.find(
|
||||||
|
(key) => key.key === element,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (key) {
|
||||||
|
groupBy.push(key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reset pagination on switching to groupBy
|
||||||
|
setCurrentPage(1);
|
||||||
|
setGroupBy(groupBy);
|
||||||
|
setExpandedRowKeys([]);
|
||||||
|
},
|
||||||
|
[groupByFiltersData],
|
||||||
|
);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (groupByFiltersData?.payload) {
|
||||||
|
setGroupByOptions(
|
||||||
|
groupByFiltersData?.payload?.attributeKeys?.map((filter) => ({
|
||||||
|
value: filter.key,
|
||||||
|
label: filter.key,
|
||||||
|
})) || [],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}, [groupByFiltersData]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="k8s-list">
|
||||||
|
<K8sHeader
|
||||||
|
isFiltersVisible={isFiltersVisible}
|
||||||
|
handleFilterVisibilityChange={handleFilterVisibilityChange}
|
||||||
|
defaultAddedColumns={defaultAddedColumns}
|
||||||
|
handleFiltersChange={handleFiltersChange}
|
||||||
|
groupByOptions={groupByOptions}
|
||||||
|
isLoadingGroupByFilters={isLoadingGroupByFilters}
|
||||||
|
handleGroupByChange={handleGroupByChange}
|
||||||
|
selectedGroupBy={groupBy}
|
||||||
|
entity={K8sCategory.NODES}
|
||||||
|
/>
|
||||||
|
{isError && <Typography>{data?.error || 'Something went wrong'}</Typography>}
|
||||||
|
|
||||||
|
<Table
|
||||||
|
className="k8s-list-table clusters-list-table"
|
||||||
|
dataSource={isFetching || isLoading ? [] : formattedClustersData}
|
||||||
|
columns={columns}
|
||||||
|
pagination={{
|
||||||
|
current: currentPage,
|
||||||
|
pageSize,
|
||||||
|
total: totalCount,
|
||||||
|
showSizeChanger: false,
|
||||||
|
hideOnSinglePage: true,
|
||||||
|
}}
|
||||||
|
scroll={{ x: true }}
|
||||||
|
loading={{
|
||||||
|
spinning: isFetching || isLoading,
|
||||||
|
indicator: <Spin indicator={<LoadingOutlined size={14} spin />} />,
|
||||||
|
}}
|
||||||
|
locale={{
|
||||||
|
emptyText:
|
||||||
|
isFetching || isLoading ? null : (
|
||||||
|
<div className="no-filtered-hosts-message-container">
|
||||||
|
<div className="no-filtered-hosts-message-content">
|
||||||
|
<img
|
||||||
|
src="/Icons/emptyState.svg"
|
||||||
|
alt="thinking-emoji"
|
||||||
|
className="empty-state-svg"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Typography.Text className="no-filtered-hosts-message">
|
||||||
|
This query had no results. Edit your query and try again!
|
||||||
|
</Typography.Text>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
}}
|
||||||
|
tableLayout="fixed"
|
||||||
|
onChange={handleTableChange}
|
||||||
|
onRow={(record): { onClick: () => void; className: string } => ({
|
||||||
|
onClick: (): void => handleRowClick(record),
|
||||||
|
className: 'clickable-row',
|
||||||
|
})}
|
||||||
|
expandable={{
|
||||||
|
expandedRowRender: isGroupedByAttribute ? expandedRowRender : undefined,
|
||||||
|
expandIcon: expandRowIconRenderer,
|
||||||
|
expandedRowKeys,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<ClusterDetails
|
||||||
|
cluster={selectedClusterData}
|
||||||
|
isModalTimeSelection
|
||||||
|
onClose={handleCloseClusterDetail}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default K8sClustersList;
|
||||||
196
frontend/src/container/InfraMonitoringK8s/Clusters/utils.tsx
Normal file
196
frontend/src/container/InfraMonitoringK8s/Clusters/utils.tsx
Normal file
@ -0,0 +1,196 @@
|
|||||||
|
import { Color } from '@signozhq/design-tokens';
|
||||||
|
import { Tag, Tooltip } from 'antd';
|
||||||
|
import { ColumnType } from 'antd/es/table';
|
||||||
|
import {
|
||||||
|
K8sClustersData,
|
||||||
|
K8sClustersListPayload,
|
||||||
|
} from 'api/infraMonitoring/getK8sClustersList';
|
||||||
|
import { Group } from 'lucide-react';
|
||||||
|
import { IBuilderQuery } from 'types/api/queryBuilder/queryBuilderData';
|
||||||
|
|
||||||
|
import { formatBytes, ValidateColumnValueWrapper } from '../commonUtils';
|
||||||
|
import { IEntityColumn } from '../utils';
|
||||||
|
|
||||||
|
export const defaultAddedColumns: IEntityColumn[] = [
|
||||||
|
{
|
||||||
|
label: 'Cluster Name',
|
||||||
|
value: 'clusterName',
|
||||||
|
id: 'cluster',
|
||||||
|
canRemove: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'CPU Usage (cores)',
|
||||||
|
value: 'cpu',
|
||||||
|
id: 'cpu',
|
||||||
|
canRemove: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'CPU Allocatable (cores)',
|
||||||
|
value: 'cpu_allocatable',
|
||||||
|
id: 'cpu_allocatable',
|
||||||
|
canRemove: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Mem Usage',
|
||||||
|
value: 'memory',
|
||||||
|
id: 'memory',
|
||||||
|
canRemove: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Mem Allocatable',
|
||||||
|
value: 'memory_allocatable',
|
||||||
|
id: 'memory_allocatable',
|
||||||
|
canRemove: false,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
export interface K8sClustersRowData {
|
||||||
|
key: string;
|
||||||
|
clusterUID: string;
|
||||||
|
clusterName: React.ReactNode;
|
||||||
|
cpu: React.ReactNode;
|
||||||
|
memory: React.ReactNode;
|
||||||
|
cpu_allocatable: React.ReactNode;
|
||||||
|
memory_allocatable: React.ReactNode;
|
||||||
|
groupedByMeta?: any;
|
||||||
|
}
|
||||||
|
|
||||||
|
const clusterGroupColumnConfig = {
|
||||||
|
title: (
|
||||||
|
<div className="column-header entity-group-header">
|
||||||
|
<Group size={14} /> CLUSTER GROUP
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
dataIndex: 'clusterGroup',
|
||||||
|
key: 'clusterGroup',
|
||||||
|
ellipsis: true,
|
||||||
|
width: 150,
|
||||||
|
align: 'left',
|
||||||
|
sorter: false,
|
||||||
|
className: 'column entity-group-header',
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getK8sClustersListQuery = (): K8sClustersListPayload => ({
|
||||||
|
filters: {
|
||||||
|
items: [],
|
||||||
|
op: 'and',
|
||||||
|
},
|
||||||
|
orderBy: { columnName: 'cpu', order: 'desc' },
|
||||||
|
});
|
||||||
|
|
||||||
|
const columnsConfig = [
|
||||||
|
{
|
||||||
|
title: <div className="column-header-left">Cluster Name</div>,
|
||||||
|
dataIndex: 'clusterName',
|
||||||
|
key: 'clusterName',
|
||||||
|
ellipsis: true,
|
||||||
|
width: 150,
|
||||||
|
sorter: false,
|
||||||
|
align: 'left',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: <div className="column-header-left">CPU Utilization (cores)</div>,
|
||||||
|
dataIndex: 'cpu',
|
||||||
|
key: 'cpu',
|
||||||
|
width: 80,
|
||||||
|
sorter: true,
|
||||||
|
align: 'left',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: <div className="column-header-left">CPU Allocatable (cores)</div>,
|
||||||
|
dataIndex: 'cpu_allocatable',
|
||||||
|
key: 'cpu_allocatable',
|
||||||
|
width: 80,
|
||||||
|
sorter: true,
|
||||||
|
align: 'left',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: <div className="column-header-left">Memory Utilization (bytes)</div>,
|
||||||
|
dataIndex: 'memory',
|
||||||
|
key: 'memory',
|
||||||
|
width: 80,
|
||||||
|
sorter: true,
|
||||||
|
align: 'left',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: <div className="column-header-left">Memory Allocatable (bytes)</div>,
|
||||||
|
dataIndex: 'memory_allocatable',
|
||||||
|
key: 'memory_allocatable',
|
||||||
|
width: 80,
|
||||||
|
sorter: true,
|
||||||
|
align: 'left',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
export const getK8sClustersListColumns = (
|
||||||
|
groupBy: IBuilderQuery['groupBy'],
|
||||||
|
): ColumnType<K8sClustersRowData>[] => {
|
||||||
|
if (groupBy.length > 0) {
|
||||||
|
const filteredColumns = [...columnsConfig].filter(
|
||||||
|
(column) => column.key !== 'clusterName',
|
||||||
|
);
|
||||||
|
filteredColumns.unshift(clusterGroupColumnConfig);
|
||||||
|
return filteredColumns as ColumnType<K8sClustersRowData>[];
|
||||||
|
}
|
||||||
|
|
||||||
|
return columnsConfig as ColumnType<K8sClustersRowData>[];
|
||||||
|
};
|
||||||
|
|
||||||
|
const getGroupByEle = (
|
||||||
|
cluster: K8sClustersData,
|
||||||
|
groupBy: IBuilderQuery['groupBy'],
|
||||||
|
): React.ReactNode => {
|
||||||
|
const groupByValues: string[] = [];
|
||||||
|
|
||||||
|
groupBy.forEach((group) => {
|
||||||
|
groupByValues.push(cluster.meta[group.key as keyof typeof cluster.meta]);
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="pod-group">
|
||||||
|
{groupByValues.map((value) => (
|
||||||
|
<Tag key={value} color={Color.BG_SLATE_400} className="pod-group-tag-item">
|
||||||
|
{value === '' ? '<no-value>' : value}
|
||||||
|
</Tag>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const formatDataForTable = (
|
||||||
|
data: K8sClustersData[],
|
||||||
|
groupBy: IBuilderQuery['groupBy'],
|
||||||
|
): K8sClustersRowData[] =>
|
||||||
|
data.map((cluster, index) => ({
|
||||||
|
key: index.toString(),
|
||||||
|
clusterUID: cluster.meta.k8s_cluster_name,
|
||||||
|
clusterName: (
|
||||||
|
<Tooltip title={cluster.meta.k8s_cluster_name}>
|
||||||
|
{cluster.meta.k8s_cluster_name}
|
||||||
|
</Tooltip>
|
||||||
|
),
|
||||||
|
cpu: (
|
||||||
|
<ValidateColumnValueWrapper value={cluster.cpuUsage}>
|
||||||
|
{cluster.cpuUsage}
|
||||||
|
</ValidateColumnValueWrapper>
|
||||||
|
),
|
||||||
|
memory: (
|
||||||
|
<ValidateColumnValueWrapper value={cluster.memoryUsage}>
|
||||||
|
{formatBytes(cluster.memoryUsage)}
|
||||||
|
</ValidateColumnValueWrapper>
|
||||||
|
),
|
||||||
|
cpu_allocatable: (
|
||||||
|
<ValidateColumnValueWrapper value={cluster.cpuAllocatable}>
|
||||||
|
{cluster.cpuAllocatable}
|
||||||
|
</ValidateColumnValueWrapper>
|
||||||
|
),
|
||||||
|
memory_allocatable: (
|
||||||
|
<ValidateColumnValueWrapper value={cluster.memoryAllocatable}>
|
||||||
|
{formatBytes(cluster.memoryAllocatable)}
|
||||||
|
</ValidateColumnValueWrapper>
|
||||||
|
),
|
||||||
|
clusterGroup: getGroupByEle(cluster, groupBy),
|
||||||
|
meta: cluster.meta,
|
||||||
|
...cluster.meta,
|
||||||
|
groupedByMeta: cluster.meta,
|
||||||
|
}));
|
||||||
@ -0,0 +1,7 @@
|
|||||||
|
import { K8sDeploymentsData } from 'api/infraMonitoring/getK8sDeploymentsList';
|
||||||
|
|
||||||
|
export type DeploymentDetailsProps = {
|
||||||
|
deployment: K8sDeploymentsData | null;
|
||||||
|
isModalTimeSelection: boolean;
|
||||||
|
onClose: () => void;
|
||||||
|
};
|
||||||
@ -0,0 +1,604 @@
|
|||||||
|
/* 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 { K8sDeploymentsData } from 'api/infraMonitoring/getK8sDeploymentsList';
|
||||||
|
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 { filterDuplicateFilters } from 'container/InfraMonitoringK8s/commonUtils';
|
||||||
|
import { K8sCategory } from 'container/InfraMonitoringK8s/constants';
|
||||||
|
import { QUERY_KEYS } from 'container/InfraMonitoringK8s/EntityDetailsUtils/utils';
|
||||||
|
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 DeploymentEvents from '../../EntityDetailsUtils/EntityEvents';
|
||||||
|
import DeploymentLogs from '../../EntityDetailsUtils/EntityLogs';
|
||||||
|
import DeploymentMetrics from '../../EntityDetailsUtils/EntityMetrics';
|
||||||
|
import DeploymentTraces from '../../EntityDetailsUtils/EntityTraces';
|
||||||
|
import {
|
||||||
|
deploymentWidgetInfo,
|
||||||
|
getDeploymentMetricsQueryPayload,
|
||||||
|
} from './constants';
|
||||||
|
import { DeploymentDetailsProps } from './DeploymentDetails.interfaces';
|
||||||
|
|
||||||
|
function DeploymentDetails({
|
||||||
|
deployment,
|
||||||
|
onClose,
|
||||||
|
isModalTimeSelection,
|
||||||
|
}: DeploymentDetailsProps): 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<Time>(
|
||||||
|
selectedTime as Time,
|
||||||
|
);
|
||||||
|
|
||||||
|
const [selectedView, setSelectedView] = useState<VIEWS>(VIEWS.METRICS);
|
||||||
|
const isDarkMode = useIsDarkMode();
|
||||||
|
|
||||||
|
const initialFilters = useMemo(
|
||||||
|
() => ({
|
||||||
|
op: 'AND',
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
id: uuidv4(),
|
||||||
|
key: {
|
||||||
|
key: QUERY_KEYS.K8S_DEPLOYMENT_NAME,
|
||||||
|
dataType: DataTypes.String,
|
||||||
|
type: 'resource',
|
||||||
|
isColumn: false,
|
||||||
|
isJSON: false,
|
||||||
|
id: 'k8s_deployment_name--string--resource--false',
|
||||||
|
},
|
||||||
|
op: '=',
|
||||||
|
value: deployment?.meta.k8s_deployment_name || '',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: uuidv4(),
|
||||||
|
key: {
|
||||||
|
key: QUERY_KEYS.K8S_NAMESPACE_NAME,
|
||||||
|
dataType: DataTypes.String,
|
||||||
|
type: 'resource',
|
||||||
|
isColumn: false,
|
||||||
|
isJSON: false,
|
||||||
|
id: 'k8s_deployment_name--string--resource--false',
|
||||||
|
},
|
||||||
|
op: '=',
|
||||||
|
value: deployment?.meta.k8s_namespace_name || '',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
[deployment?.meta.k8s_deployment_name, deployment?.meta.k8s_namespace_name],
|
||||||
|
);
|
||||||
|
|
||||||
|
const initialEventsFilters = useMemo(
|
||||||
|
() => ({
|
||||||
|
op: 'AND',
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
id: uuidv4(),
|
||||||
|
key: {
|
||||||
|
key: QUERY_KEYS.K8S_OBJECT_KIND,
|
||||||
|
dataType: DataTypes.String,
|
||||||
|
type: 'resource',
|
||||||
|
isColumn: false,
|
||||||
|
isJSON: false,
|
||||||
|
id: 'k8s.object.kind--string--resource--false',
|
||||||
|
},
|
||||||
|
op: '=',
|
||||||
|
value: 'Deployment',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: uuidv4(),
|
||||||
|
key: {
|
||||||
|
key: QUERY_KEYS.K8S_OBJECT_NAME,
|
||||||
|
dataType: DataTypes.String,
|
||||||
|
type: 'resource',
|
||||||
|
isColumn: false,
|
||||||
|
isJSON: false,
|
||||||
|
id: 'k8s.object.name--string--resource--false',
|
||||||
|
},
|
||||||
|
op: '=',
|
||||||
|
value: deployment?.meta.k8s_deployment_name || '',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
[deployment?.meta.k8s_deployment_name],
|
||||||
|
);
|
||||||
|
|
||||||
|
const [logFilters, setLogFilters] = useState<IBuilderQuery['filters']>(
|
||||||
|
initialFilters,
|
||||||
|
);
|
||||||
|
|
||||||
|
const [tracesFilters, setTracesFilters] = useState<IBuilderQuery['filters']>(
|
||||||
|
initialFilters,
|
||||||
|
);
|
||||||
|
|
||||||
|
const [eventsFilters, setEventsFilters] = useState<IBuilderQuery['filters']>(
|
||||||
|
initialEventsFilters,
|
||||||
|
);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
logEvent('Infra Monitoring: Deployments list details page visited', {
|
||||||
|
deployment: deployment?.deploymentName,
|
||||||
|
});
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setLogFilters(initialFilters);
|
||||||
|
setTracesFilters(initialFilters);
|
||||||
|
setEventsFilters(initialEventsFilters);
|
||||||
|
}, [initialFilters, initialEventsFilters]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setSelectedInterval(selectedTime as Time);
|
||||||
|
|
||||||
|
if (selectedTime !== 'custom') {
|
||||||
|
const { maxTime, minTime } = GetMinMax(selectedTime);
|
||||||
|
|
||||||
|
setModalTimeRange({
|
||||||
|
startTime: Math.floor(minTime / 1000000000),
|
||||||
|
endTime: Math.floor(maxTime / 1000000000),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, [selectedTime, minTime, maxTime]);
|
||||||
|
|
||||||
|
const handleTabChange = (e: RadioChangeEvent): void => {
|
||||||
|
setSelectedView(e.target.value);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleTimeChange = useCallback(
|
||||||
|
(interval: Time | CustomTimeType, dateTimeRange?: [number, number]): void => {
|
||||||
|
setSelectedInterval(interval as Time);
|
||||||
|
|
||||||
|
if (interval === 'custom' && dateTimeRange) {
|
||||||
|
setModalTimeRange({
|
||||||
|
startTime: Math.floor(dateTimeRange[0] / 1000),
|
||||||
|
endTime: Math.floor(dateTimeRange[1] / 1000),
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
const { maxTime, minTime } = GetMinMax(interval);
|
||||||
|
|
||||||
|
setModalTimeRange({
|
||||||
|
startTime: Math.floor(minTime / 1000000000),
|
||||||
|
endTime: Math.floor(maxTime / 1000000000),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
logEvent('Infra Monitoring: Deployments list details time updated', {
|
||||||
|
deployment: deployment?.deploymentName,
|
||||||
|
interval,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
[],
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleChangeLogFilters = useCallback(
|
||||||
|
(value: IBuilderQuery['filters']) => {
|
||||||
|
setLogFilters((prevFilters) => {
|
||||||
|
const primaryFilters = prevFilters.items.filter((item) =>
|
||||||
|
[QUERY_KEYS.K8S_DEPLOYMENT_NAME, QUERY_KEYS.K8S_NAMESPACE_NAME].includes(
|
||||||
|
item.key?.key ?? '',
|
||||||
|
),
|
||||||
|
);
|
||||||
|
const paginationFilter = value.items.find((item) => item.key?.key === 'id');
|
||||||
|
const newFilters = value.items.filter(
|
||||||
|
(item) =>
|
||||||
|
item.key?.key !== 'id' &&
|
||||||
|
item.key?.key !== QUERY_KEYS.K8S_DEPLOYMENT_NAME,
|
||||||
|
);
|
||||||
|
|
||||||
|
logEvent(
|
||||||
|
'Infra Monitoring: Deployments list details logs filters applied',
|
||||||
|
{
|
||||||
|
deployment: deployment?.deploymentName,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
op: 'AND',
|
||||||
|
items: filterDuplicateFilters(
|
||||||
|
[
|
||||||
|
...primaryFilters,
|
||||||
|
...newFilters,
|
||||||
|
...(paginationFilter ? [paginationFilter] : []),
|
||||||
|
].filter((item): item is TagFilterItem => item !== undefined),
|
||||||
|
),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
},
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
[],
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleChangeTracesFilters = useCallback(
|
||||||
|
(value: IBuilderQuery['filters']) => {
|
||||||
|
setTracesFilters((prevFilters) => {
|
||||||
|
const primaryFilters = prevFilters.items.filter((item) =>
|
||||||
|
[QUERY_KEYS.K8S_DEPLOYMENT_NAME, QUERY_KEYS.K8S_NAMESPACE_NAME].includes(
|
||||||
|
item.key?.key ?? '',
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
logEvent(
|
||||||
|
'Infra Monitoring: Deployments list details traces filters applied',
|
||||||
|
{
|
||||||
|
deployment: deployment?.deploymentName,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
op: 'AND',
|
||||||
|
items: filterDuplicateFilters(
|
||||||
|
[
|
||||||
|
...primaryFilters,
|
||||||
|
...value.items.filter(
|
||||||
|
(item) => item.key?.key !== QUERY_KEYS.K8S_DEPLOYMENT_NAME,
|
||||||
|
),
|
||||||
|
].filter((item): item is TagFilterItem => item !== undefined),
|
||||||
|
),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
},
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
[],
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleChangeEventsFilters = useCallback(
|
||||||
|
(value: IBuilderQuery['filters']) => {
|
||||||
|
setEventsFilters((prevFilters) => {
|
||||||
|
const deploymentKindFilter = prevFilters.items.find(
|
||||||
|
(item) => item.key?.key === QUERY_KEYS.K8S_OBJECT_KIND,
|
||||||
|
);
|
||||||
|
const deploymentNameFilter = prevFilters.items.find(
|
||||||
|
(item) => item.key?.key === QUERY_KEYS.K8S_OBJECT_NAME,
|
||||||
|
);
|
||||||
|
|
||||||
|
logEvent(
|
||||||
|
'Infra Monitoring: Deployments list details events filters applied',
|
||||||
|
{
|
||||||
|
deployment: deployment?.deploymentName,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
op: 'AND',
|
||||||
|
items: filterDuplicateFilters(
|
||||||
|
[
|
||||||
|
deploymentKindFilter,
|
||||||
|
deploymentNameFilter,
|
||||||
|
...value.items.filter(
|
||||||
|
(item) =>
|
||||||
|
item.key?.key !== QUERY_KEYS.K8S_OBJECT_KIND &&
|
||||||
|
item.key?.key !== QUERY_KEYS.K8S_OBJECT_NAME,
|
||||||
|
),
|
||||||
|
].filter((item): item is TagFilterItem => item !== undefined),
|
||||||
|
),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
},
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
[],
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleExplorePagesRedirect = (): void => {
|
||||||
|
if (selectedInterval !== 'custom') {
|
||||||
|
urlQuery.set(QueryParams.relativeTime, selectedInterval);
|
||||||
|
} else {
|
||||||
|
urlQuery.delete(QueryParams.relativeTime);
|
||||||
|
urlQuery.set(QueryParams.startTime, modalTimeRange.startTime.toString());
|
||||||
|
urlQuery.set(QueryParams.endTime, modalTimeRange.endTime.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
logEvent('Infra Monitoring: Deployments list details explore clicked', {
|
||||||
|
deployment: deployment?.deploymentName,
|
||||||
|
view: selectedView,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (selectedView === VIEW_TYPES.LOGS) {
|
||||||
|
const filtersWithoutPagination = {
|
||||||
|
...logFilters,
|
||||||
|
items: logFilters.items.filter((item) => item.key?.key !== 'id'),
|
||||||
|
};
|
||||||
|
|
||||||
|
const compositeQuery = {
|
||||||
|
...initialQueryState,
|
||||||
|
queryType: 'builder',
|
||||||
|
builder: {
|
||||||
|
...initialQueryState.builder,
|
||||||
|
queryData: [
|
||||||
|
{
|
||||||
|
...initialQueryBuilderFormValuesMap.logs,
|
||||||
|
aggregateOperator: LogsAggregatorOperator.NOOP,
|
||||||
|
filters: filtersWithoutPagination,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
urlQuery.set('compositeQuery', JSON.stringify(compositeQuery));
|
||||||
|
|
||||||
|
window.open(
|
||||||
|
`${window.location.origin}${ROUTES.LOGS_EXPLORER}?${urlQuery.toString()}`,
|
||||||
|
'_blank',
|
||||||
|
);
|
||||||
|
} else if (selectedView === VIEW_TYPES.TRACES) {
|
||||||
|
const compositeQuery = {
|
||||||
|
...initialQueryState,
|
||||||
|
queryType: 'builder',
|
||||||
|
builder: {
|
||||||
|
...initialQueryState.builder,
|
||||||
|
queryData: [
|
||||||
|
{
|
||||||
|
...initialQueryBuilderFormValuesMap.traces,
|
||||||
|
aggregateOperator: TracesAggregatorOperator.NOOP,
|
||||||
|
filters: tracesFilters,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
urlQuery.set('compositeQuery', JSON.stringify(compositeQuery));
|
||||||
|
|
||||||
|
window.open(
|
||||||
|
`${window.location.origin}${ROUTES.TRACES_EXPLORER}?${urlQuery.toString()}`,
|
||||||
|
'_blank',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleClose = (): void => {
|
||||||
|
setSelectedInterval(selectedTime as Time);
|
||||||
|
|
||||||
|
if (selectedTime !== 'custom') {
|
||||||
|
const { maxTime, minTime } = GetMinMax(selectedTime);
|
||||||
|
|
||||||
|
setModalTimeRange({
|
||||||
|
startTime: Math.floor(minTime / 1000000000),
|
||||||
|
endTime: Math.floor(maxTime / 1000000000),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
setSelectedView(VIEW_TYPES.METRICS);
|
||||||
|
onClose();
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Drawer
|
||||||
|
width="70%"
|
||||||
|
title={
|
||||||
|
<>
|
||||||
|
<Divider type="vertical" />
|
||||||
|
<Typography.Text className="title">
|
||||||
|
{deployment?.meta.k8s_deployment_name}
|
||||||
|
</Typography.Text>
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
placement="right"
|
||||||
|
onClose={handleClose}
|
||||||
|
open={!!deployment}
|
||||||
|
style={{
|
||||||
|
overscrollBehavior: 'contain',
|
||||||
|
background: isDarkMode ? Color.BG_INK_400 : Color.BG_VANILLA_100,
|
||||||
|
}}
|
||||||
|
className="entity-detail-drawer"
|
||||||
|
destroyOnClose
|
||||||
|
closeIcon={<X size={16} style={{ marginTop: Spacing.MARGIN_1 }} />}
|
||||||
|
>
|
||||||
|
{deployment && (
|
||||||
|
<>
|
||||||
|
<div className="entity-detail-drawer__entity">
|
||||||
|
<div className="entity-details-grid">
|
||||||
|
<div className="labels-row">
|
||||||
|
<Typography.Text
|
||||||
|
type="secondary"
|
||||||
|
className="entity-details-metadata-label"
|
||||||
|
>
|
||||||
|
Deployment Name
|
||||||
|
</Typography.Text>
|
||||||
|
<Typography.Text
|
||||||
|
type="secondary"
|
||||||
|
className="entity-details-metadata-label"
|
||||||
|
>
|
||||||
|
Cluster Name
|
||||||
|
</Typography.Text>
|
||||||
|
<Typography.Text
|
||||||
|
type="secondary"
|
||||||
|
className="entity-details-metadata-label"
|
||||||
|
>
|
||||||
|
Namespace Name
|
||||||
|
</Typography.Text>
|
||||||
|
</div>
|
||||||
|
<div className="values-row">
|
||||||
|
<Typography.Text className="entity-details-metadata-value">
|
||||||
|
<Tooltip title={deployment.meta.k8s_deployment_name}>
|
||||||
|
{deployment.meta.k8s_deployment_name}
|
||||||
|
</Tooltip>
|
||||||
|
</Typography.Text>
|
||||||
|
<Typography.Text className="entity-details-metadata-value">
|
||||||
|
<Tooltip title={deployment.meta.k8s_cluster_name}>
|
||||||
|
{deployment.meta.k8s_cluster_name}
|
||||||
|
</Tooltip>
|
||||||
|
</Typography.Text>
|
||||||
|
<Typography.Text className="entity-details-metadata-value">
|
||||||
|
<Tooltip title={deployment.meta.k8s_namespace_name}>
|
||||||
|
{deployment.meta.k8s_namespace_name}
|
||||||
|
</Tooltip>
|
||||||
|
</Typography.Text>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="views-tabs-container">
|
||||||
|
<Radio.Group
|
||||||
|
className="views-tabs"
|
||||||
|
onChange={handleTabChange}
|
||||||
|
value={selectedView}
|
||||||
|
>
|
||||||
|
<Radio.Button
|
||||||
|
className={
|
||||||
|
// eslint-disable-next-line sonarjs/no-duplicate-string
|
||||||
|
selectedView === VIEW_TYPES.METRICS ? 'selected_view tab' : 'tab'
|
||||||
|
}
|
||||||
|
value={VIEW_TYPES.METRICS}
|
||||||
|
>
|
||||||
|
<div className="view-title">
|
||||||
|
<BarChart2 size={14} />
|
||||||
|
Metrics
|
||||||
|
</div>
|
||||||
|
</Radio.Button>
|
||||||
|
<Radio.Button
|
||||||
|
className={
|
||||||
|
selectedView === VIEW_TYPES.LOGS ? 'selected_view tab' : 'tab'
|
||||||
|
}
|
||||||
|
value={VIEW_TYPES.LOGS}
|
||||||
|
>
|
||||||
|
<div className="view-title">
|
||||||
|
<ScrollText size={14} />
|
||||||
|
Logs
|
||||||
|
</div>
|
||||||
|
</Radio.Button>
|
||||||
|
<Radio.Button
|
||||||
|
className={
|
||||||
|
selectedView === VIEW_TYPES.TRACES ? 'selected_view tab' : 'tab'
|
||||||
|
}
|
||||||
|
value={VIEW_TYPES.TRACES}
|
||||||
|
>
|
||||||
|
<div className="view-title">
|
||||||
|
<DraftingCompass size={14} />
|
||||||
|
Traces
|
||||||
|
</div>
|
||||||
|
</Radio.Button>
|
||||||
|
<Radio.Button
|
||||||
|
className={
|
||||||
|
selectedView === VIEW_TYPES.EVENTS ? 'selected_view tab' : 'tab'
|
||||||
|
}
|
||||||
|
value={VIEW_TYPES.EVENTS}
|
||||||
|
>
|
||||||
|
<div className="view-title">
|
||||||
|
<ChevronsLeftRight size={14} />
|
||||||
|
Events
|
||||||
|
</div>
|
||||||
|
</Radio.Button>
|
||||||
|
</Radio.Group>
|
||||||
|
|
||||||
|
{(selectedView === VIEW_TYPES.LOGS ||
|
||||||
|
selectedView === VIEW_TYPES.TRACES) && (
|
||||||
|
<Button
|
||||||
|
icon={<Compass size={18} />}
|
||||||
|
className="compass-button"
|
||||||
|
onClick={handleExplorePagesRedirect}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
{selectedView === VIEW_TYPES.METRICS && (
|
||||||
|
<DeploymentMetrics<K8sDeploymentsData>
|
||||||
|
timeRange={modalTimeRange}
|
||||||
|
isModalTimeSelection={isModalTimeSelection}
|
||||||
|
handleTimeChange={handleTimeChange}
|
||||||
|
selectedInterval={selectedInterval}
|
||||||
|
entity={deployment}
|
||||||
|
entityWidgetInfo={deploymentWidgetInfo}
|
||||||
|
getEntityQueryPayload={getDeploymentMetricsQueryPayload}
|
||||||
|
category={K8sCategory.DEPLOYMENTS}
|
||||||
|
queryKey="deploymentMetrics"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{selectedView === VIEW_TYPES.LOGS && (
|
||||||
|
<DeploymentLogs
|
||||||
|
timeRange={modalTimeRange}
|
||||||
|
isModalTimeSelection={isModalTimeSelection}
|
||||||
|
handleTimeChange={handleTimeChange}
|
||||||
|
handleChangeLogFilters={handleChangeLogFilters}
|
||||||
|
logFilters={logFilters}
|
||||||
|
selectedInterval={selectedInterval}
|
||||||
|
queryKeyFilters={[
|
||||||
|
QUERY_KEYS.K8S_DEPLOYMENT_NAME,
|
||||||
|
QUERY_KEYS.K8S_NAMESPACE_NAME,
|
||||||
|
]}
|
||||||
|
queryKey="deploymentLogs"
|
||||||
|
category={K8sCategory.DEPLOYMENTS}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{selectedView === VIEW_TYPES.TRACES && (
|
||||||
|
<DeploymentTraces
|
||||||
|
timeRange={modalTimeRange}
|
||||||
|
isModalTimeSelection={isModalTimeSelection}
|
||||||
|
handleTimeChange={handleTimeChange}
|
||||||
|
handleChangeTracesFilters={handleChangeTracesFilters}
|
||||||
|
tracesFilters={tracesFilters}
|
||||||
|
selectedInterval={selectedInterval}
|
||||||
|
queryKey="deploymentTraces"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{selectedView === VIEW_TYPES.EVENTS && (
|
||||||
|
<DeploymentEvents
|
||||||
|
timeRange={modalTimeRange}
|
||||||
|
handleChangeEventFilters={handleChangeEventsFilters}
|
||||||
|
filters={eventsFilters}
|
||||||
|
isModalTimeSelection={isModalTimeSelection}
|
||||||
|
handleTimeChange={handleTimeChange}
|
||||||
|
selectedInterval={selectedInterval}
|
||||||
|
category={K8sCategory.DEPLOYMENTS}
|
||||||
|
queryKey="deploymentEvents"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</Drawer>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default DeploymentDetails;
|
||||||
@ -0,0 +1,544 @@
|
|||||||
|
/* eslint-disable sonarjs/no-duplicate-string */
|
||||||
|
import { K8sDeploymentsData } from 'api/infraMonitoring/getK8sDeploymentsList';
|
||||||
|
import { PANEL_TYPES } from 'constants/queryBuilder';
|
||||||
|
import { GetQueryResultsProps } from 'lib/dashboard/getQueryResults';
|
||||||
|
import { DataTypes } from 'types/api/queryBuilder/queryAutocompleteResponse';
|
||||||
|
import { EQueryType } from 'types/common/dashboard';
|
||||||
|
import { DataSource } from 'types/common/queryBuilder';
|
||||||
|
import { v4 } from 'uuid';
|
||||||
|
|
||||||
|
export const deploymentWidgetInfo = [
|
||||||
|
{
|
||||||
|
title: 'CPU usage, request, limits',
|
||||||
|
yAxisUnit: '',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Memory usage, request, limits)',
|
||||||
|
yAxisUnit: 'bytes',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Network IO',
|
||||||
|
yAxisUnit: 'binBps',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Network error count',
|
||||||
|
yAxisUnit: '',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
export const getDeploymentMetricsQueryPayload = (
|
||||||
|
deployment: K8sDeploymentsData,
|
||||||
|
start: number,
|
||||||
|
end: number,
|
||||||
|
): GetQueryResultsProps[] => [
|
||||||
|
{
|
||||||
|
selectedTime: 'GLOBAL_TIME',
|
||||||
|
graphType: PANEL_TYPES.TIME_SERIES,
|
||||||
|
query: {
|
||||||
|
builder: {
|
||||||
|
queryData: [
|
||||||
|
{
|
||||||
|
aggregateAttribute: {
|
||||||
|
dataType: DataTypes.Float64,
|
||||||
|
id: 'k8s_pod_cpu_utilization--float64--Gauge--true',
|
||||||
|
isColumn: true,
|
||||||
|
isJSON: false,
|
||||||
|
key: 'k8s_pod_cpu_utilization',
|
||||||
|
type: 'Gauge',
|
||||||
|
},
|
||||||
|
aggregateOperator: 'avg',
|
||||||
|
dataSource: DataSource.METRICS,
|
||||||
|
disabled: false,
|
||||||
|
expression: 'A',
|
||||||
|
filters: {
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
id: 'aec60cba',
|
||||||
|
key: {
|
||||||
|
dataType: DataTypes.String,
|
||||||
|
id: 'k8s_deployment_name--string--tag--false',
|
||||||
|
isColumn: false,
|
||||||
|
isJSON: false,
|
||||||
|
key: 'k8s_deployment_name',
|
||||||
|
type: 'tag',
|
||||||
|
},
|
||||||
|
op: '=',
|
||||||
|
value: deployment.meta.k8s_deployment_name,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
op: 'AND',
|
||||||
|
},
|
||||||
|
functions: [],
|
||||||
|
groupBy: [],
|
||||||
|
having: [],
|
||||||
|
legend: 'usage',
|
||||||
|
limit: null,
|
||||||
|
orderBy: [],
|
||||||
|
queryName: 'A',
|
||||||
|
reduceTo: 'avg',
|
||||||
|
spaceAggregation: 'sum',
|
||||||
|
stepInterval: 60,
|
||||||
|
timeAggregation: 'avg',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
aggregateAttribute: {
|
||||||
|
dataType: DataTypes.Float64,
|
||||||
|
id: 'k8s_container_cpu_request--float64--Gauge--true',
|
||||||
|
isColumn: true,
|
||||||
|
isJSON: false,
|
||||||
|
key: 'k8s_container_cpu_request',
|
||||||
|
type: 'Gauge',
|
||||||
|
},
|
||||||
|
aggregateOperator: 'avg',
|
||||||
|
dataSource: DataSource.METRICS,
|
||||||
|
disabled: false,
|
||||||
|
expression: 'B',
|
||||||
|
filters: {
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
id: 'd047ec14',
|
||||||
|
key: {
|
||||||
|
dataType: DataTypes.String,
|
||||||
|
id: 'k8s_pod_name--string--tag--false',
|
||||||
|
isColumn: false,
|
||||||
|
isJSON: false,
|
||||||
|
key: 'k8s_pod_name',
|
||||||
|
type: 'tag',
|
||||||
|
},
|
||||||
|
op: 'contains',
|
||||||
|
value: deployment.meta.k8s_deployment_name,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
op: 'AND',
|
||||||
|
},
|
||||||
|
functions: [],
|
||||||
|
groupBy: [],
|
||||||
|
having: [],
|
||||||
|
legend: 'requests',
|
||||||
|
limit: null,
|
||||||
|
orderBy: [],
|
||||||
|
queryName: 'B',
|
||||||
|
reduceTo: 'avg',
|
||||||
|
spaceAggregation: 'sum',
|
||||||
|
stepInterval: 60,
|
||||||
|
timeAggregation: 'avg',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
aggregateAttribute: {
|
||||||
|
dataType: DataTypes.Float64,
|
||||||
|
id: 'k8s_container_cpu_limit--float64--Gauge--true',
|
||||||
|
isColumn: true,
|
||||||
|
isJSON: false,
|
||||||
|
key: 'k8s_container_cpu_limit',
|
||||||
|
type: 'Gauge',
|
||||||
|
},
|
||||||
|
aggregateOperator: 'avg',
|
||||||
|
dataSource: DataSource.METRICS,
|
||||||
|
disabled: false,
|
||||||
|
expression: 'C',
|
||||||
|
filters: {
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
id: '750b7856',
|
||||||
|
key: {
|
||||||
|
dataType: DataTypes.String,
|
||||||
|
id: 'k8s_pod_name--string--tag--false',
|
||||||
|
isColumn: false,
|
||||||
|
isJSON: false,
|
||||||
|
key: 'k8s_pod_name',
|
||||||
|
type: 'tag',
|
||||||
|
},
|
||||||
|
op: 'contains',
|
||||||
|
value: deployment.meta.k8s_deployment_name,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
op: 'AND',
|
||||||
|
},
|
||||||
|
functions: [],
|
||||||
|
groupBy: [],
|
||||||
|
having: [],
|
||||||
|
legend: 'limits',
|
||||||
|
limit: null,
|
||||||
|
orderBy: [],
|
||||||
|
queryName: 'C',
|
||||||
|
reduceTo: 'avg',
|
||||||
|
spaceAggregation: 'sum',
|
||||||
|
stepInterval: 60,
|
||||||
|
timeAggregation: 'avg',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
queryFormulas: [],
|
||||||
|
},
|
||||||
|
clickhouse_sql: [
|
||||||
|
{
|
||||||
|
disabled: false,
|
||||||
|
legend: '',
|
||||||
|
name: 'A',
|
||||||
|
query: '',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
id: v4(),
|
||||||
|
promql: [
|
||||||
|
{
|
||||||
|
disabled: false,
|
||||||
|
legend: '',
|
||||||
|
name: 'A',
|
||||||
|
query: '',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
queryType: EQueryType.QUERY_BUILDER,
|
||||||
|
},
|
||||||
|
variables: {},
|
||||||
|
formatForWeb: false,
|
||||||
|
start,
|
||||||
|
end,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
selectedTime: 'GLOBAL_TIME',
|
||||||
|
graphType: PANEL_TYPES.TIME_SERIES,
|
||||||
|
query: {
|
||||||
|
builder: {
|
||||||
|
queryData: [
|
||||||
|
{
|
||||||
|
aggregateAttribute: {
|
||||||
|
dataType: DataTypes.Float64,
|
||||||
|
id: 'k8s_pod_memory_usage--float64--Gauge--true',
|
||||||
|
isColumn: true,
|
||||||
|
isJSON: false,
|
||||||
|
key: 'k8s_pod_memory_usage',
|
||||||
|
type: 'Gauge',
|
||||||
|
},
|
||||||
|
aggregateOperator: 'avg',
|
||||||
|
dataSource: DataSource.METRICS,
|
||||||
|
disabled: false,
|
||||||
|
expression: 'A',
|
||||||
|
filters: {
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
id: '768c2f47',
|
||||||
|
key: {
|
||||||
|
dataType: DataTypes.String,
|
||||||
|
id: 'k8s_deployment_name--string--tag--false',
|
||||||
|
isColumn: false,
|
||||||
|
isJSON: false,
|
||||||
|
key: 'k8s_deployment_name',
|
||||||
|
type: 'tag',
|
||||||
|
},
|
||||||
|
op: '=',
|
||||||
|
value: deployment.meta.k8s_deployment_name,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
op: 'AND',
|
||||||
|
},
|
||||||
|
functions: [],
|
||||||
|
groupBy: [],
|
||||||
|
having: [],
|
||||||
|
legend: 'usage',
|
||||||
|
limit: null,
|
||||||
|
orderBy: [],
|
||||||
|
queryName: 'A',
|
||||||
|
reduceTo: 'avg',
|
||||||
|
spaceAggregation: 'sum',
|
||||||
|
stepInterval: 60,
|
||||||
|
timeAggregation: 'avg',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
aggregateAttribute: {
|
||||||
|
dataType: DataTypes.Float64,
|
||||||
|
id: 'k8s_container_memory_request--float64--Gauge--true',
|
||||||
|
isColumn: true,
|
||||||
|
isJSON: false,
|
||||||
|
key: 'k8s_container_memory_request',
|
||||||
|
type: 'Gauge',
|
||||||
|
},
|
||||||
|
aggregateOperator: 'avg',
|
||||||
|
dataSource: DataSource.METRICS,
|
||||||
|
disabled: false,
|
||||||
|
expression: 'B',
|
||||||
|
filters: {
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
id: '1a96fa81',
|
||||||
|
key: {
|
||||||
|
dataType: DataTypes.String,
|
||||||
|
id: 'k8s_pod_name--string--tag--false',
|
||||||
|
isColumn: false,
|
||||||
|
isJSON: false,
|
||||||
|
key: 'k8s_pod_name',
|
||||||
|
type: 'tag',
|
||||||
|
},
|
||||||
|
op: 'contains',
|
||||||
|
value: deployment.meta.k8s_deployment_name,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
op: 'AND',
|
||||||
|
},
|
||||||
|
functions: [],
|
||||||
|
groupBy: [],
|
||||||
|
having: [],
|
||||||
|
legend: 'requests',
|
||||||
|
limit: null,
|
||||||
|
orderBy: [],
|
||||||
|
queryName: 'B',
|
||||||
|
reduceTo: 'avg',
|
||||||
|
spaceAggregation: 'sum',
|
||||||
|
stepInterval: 60,
|
||||||
|
timeAggregation: 'avg',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
aggregateAttribute: {
|
||||||
|
dataType: DataTypes.Float64,
|
||||||
|
id: 'k8s_container_memory_limit--float64--Gauge--true',
|
||||||
|
isColumn: true,
|
||||||
|
isJSON: false,
|
||||||
|
key: 'k8s_container_memory_limit',
|
||||||
|
type: 'Gauge',
|
||||||
|
},
|
||||||
|
aggregateOperator: 'avg',
|
||||||
|
dataSource: DataSource.METRICS,
|
||||||
|
disabled: false,
|
||||||
|
expression: 'C',
|
||||||
|
filters: {
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
id: 'e69a2b7e',
|
||||||
|
key: {
|
||||||
|
dataType: DataTypes.String,
|
||||||
|
id: 'k8s_pod_name--string--tag--false',
|
||||||
|
isColumn: false,
|
||||||
|
isJSON: false,
|
||||||
|
key: 'k8s_pod_name',
|
||||||
|
type: 'tag',
|
||||||
|
},
|
||||||
|
op: 'contains',
|
||||||
|
value: deployment.meta.k8s_deployment_name,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
op: 'AND',
|
||||||
|
},
|
||||||
|
functions: [],
|
||||||
|
groupBy: [],
|
||||||
|
having: [],
|
||||||
|
legend: 'limits',
|
||||||
|
limit: null,
|
||||||
|
orderBy: [],
|
||||||
|
queryName: 'C',
|
||||||
|
reduceTo: 'avg',
|
||||||
|
spaceAggregation: 'sum',
|
||||||
|
stepInterval: 60,
|
||||||
|
timeAggregation: 'avg',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
queryFormulas: [],
|
||||||
|
},
|
||||||
|
clickhouse_sql: [
|
||||||
|
{
|
||||||
|
disabled: false,
|
||||||
|
legend: '',
|
||||||
|
name: 'A',
|
||||||
|
query: '',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
id: v4(),
|
||||||
|
promql: [
|
||||||
|
{
|
||||||
|
disabled: false,
|
||||||
|
legend: '',
|
||||||
|
name: 'A',
|
||||||
|
query: '',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
queryType: EQueryType.QUERY_BUILDER,
|
||||||
|
},
|
||||||
|
variables: {},
|
||||||
|
formatForWeb: false,
|
||||||
|
start,
|
||||||
|
end,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
selectedTime: 'GLOBAL_TIME',
|
||||||
|
graphType: PANEL_TYPES.TIME_SERIES,
|
||||||
|
query: {
|
||||||
|
builder: {
|
||||||
|
queryData: [
|
||||||
|
{
|
||||||
|
aggregateAttribute: {
|
||||||
|
dataType: DataTypes.Float64,
|
||||||
|
id: 'k8s_pod_network_io--float64--Sum--true',
|
||||||
|
isColumn: true,
|
||||||
|
isJSON: false,
|
||||||
|
key: 'k8s_pod_network_io',
|
||||||
|
type: 'Sum',
|
||||||
|
},
|
||||||
|
aggregateOperator: 'rate',
|
||||||
|
dataSource: DataSource.METRICS,
|
||||||
|
disabled: false,
|
||||||
|
expression: 'A',
|
||||||
|
filters: {
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
id: '8b550f6d',
|
||||||
|
key: {
|
||||||
|
dataType: DataTypes.String,
|
||||||
|
id: 'k8s_deployment_name--string--tag--false',
|
||||||
|
isColumn: false,
|
||||||
|
isJSON: false,
|
||||||
|
key: 'k8s_deployment_name',
|
||||||
|
type: 'tag',
|
||||||
|
},
|
||||||
|
op: '=',
|
||||||
|
value: deployment.meta.k8s_deployment_name,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
op: 'AND',
|
||||||
|
},
|
||||||
|
functions: [],
|
||||||
|
groupBy: [
|
||||||
|
{
|
||||||
|
dataType: DataTypes.String,
|
||||||
|
id: 'direction--string--tag--false',
|
||||||
|
isColumn: false,
|
||||||
|
isJSON: false,
|
||||||
|
key: 'direction',
|
||||||
|
type: 'tag',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
dataType: DataTypes.String,
|
||||||
|
id: 'interface--string--tag--false',
|
||||||
|
isColumn: false,
|
||||||
|
isJSON: false,
|
||||||
|
key: 'interface',
|
||||||
|
type: 'tag',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
having: [],
|
||||||
|
legend: '{{direction}} :: {{interface}}',
|
||||||
|
limit: null,
|
||||||
|
orderBy: [],
|
||||||
|
queryName: 'A',
|
||||||
|
reduceTo: 'avg',
|
||||||
|
spaceAggregation: 'sum',
|
||||||
|
stepInterval: 60,
|
||||||
|
timeAggregation: 'rate',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
queryFormulas: [],
|
||||||
|
},
|
||||||
|
clickhouse_sql: [
|
||||||
|
{
|
||||||
|
disabled: false,
|
||||||
|
legend: '',
|
||||||
|
name: 'A',
|
||||||
|
query: '',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
id: v4(),
|
||||||
|
promql: [
|
||||||
|
{
|
||||||
|
disabled: false,
|
||||||
|
legend: '',
|
||||||
|
name: 'A',
|
||||||
|
query: '',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
queryType: EQueryType.QUERY_BUILDER,
|
||||||
|
},
|
||||||
|
variables: {},
|
||||||
|
formatForWeb: false,
|
||||||
|
start,
|
||||||
|
end,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
selectedTime: 'GLOBAL_TIME',
|
||||||
|
graphType: PANEL_TYPES.TIME_SERIES,
|
||||||
|
query: {
|
||||||
|
builder: {
|
||||||
|
queryData: [
|
||||||
|
{
|
||||||
|
aggregateAttribute: {
|
||||||
|
dataType: DataTypes.Float64,
|
||||||
|
id: 'k8s_pod_network_errors--float64--Sum--true',
|
||||||
|
isColumn: true,
|
||||||
|
isJSON: false,
|
||||||
|
key: 'k8s_pod_network_errors',
|
||||||
|
type: 'Sum',
|
||||||
|
},
|
||||||
|
aggregateOperator: 'increase',
|
||||||
|
dataSource: DataSource.METRICS,
|
||||||
|
disabled: false,
|
||||||
|
expression: 'A',
|
||||||
|
filters: {
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
id: 'e16c1e4a',
|
||||||
|
key: {
|
||||||
|
dataType: DataTypes.String,
|
||||||
|
id: 'k8s_deployment_name--string--tag--false',
|
||||||
|
isColumn: false,
|
||||||
|
isJSON: false,
|
||||||
|
key: 'k8s_deployment_name',
|
||||||
|
type: 'tag',
|
||||||
|
},
|
||||||
|
op: '=',
|
||||||
|
value: deployment.meta.k8s_deployment_name,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
op: 'AND',
|
||||||
|
},
|
||||||
|
functions: [],
|
||||||
|
groupBy: [
|
||||||
|
{
|
||||||
|
dataType: DataTypes.String,
|
||||||
|
id: 'direction--string--tag--false',
|
||||||
|
isColumn: false,
|
||||||
|
isJSON: false,
|
||||||
|
key: 'direction',
|
||||||
|
type: 'tag',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
dataType: DataTypes.String,
|
||||||
|
id: 'interface--string--tag--false',
|
||||||
|
isColumn: false,
|
||||||
|
isJSON: false,
|
||||||
|
key: 'interface',
|
||||||
|
type: 'tag',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
having: [],
|
||||||
|
legend: '{{direction}} :: {{interface}}',
|
||||||
|
limit: null,
|
||||||
|
orderBy: [],
|
||||||
|
queryName: 'A',
|
||||||
|
reduceTo: 'avg',
|
||||||
|
spaceAggregation: 'sum',
|
||||||
|
stepInterval: 60,
|
||||||
|
timeAggregation: 'increase',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
queryFormulas: [],
|
||||||
|
},
|
||||||
|
clickhouse_sql: [
|
||||||
|
{
|
||||||
|
disabled: false,
|
||||||
|
legend: '',
|
||||||
|
name: 'A',
|
||||||
|
query: '',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
id: v4(),
|
||||||
|
promql: [
|
||||||
|
{
|
||||||
|
disabled: false,
|
||||||
|
legend: '',
|
||||||
|
name: 'A',
|
||||||
|
query: '',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
queryType: EQueryType.QUERY_BUILDER,
|
||||||
|
},
|
||||||
|
variables: {},
|
||||||
|
formatForWeb: false,
|
||||||
|
start,
|
||||||
|
end,
|
||||||
|
},
|
||||||
|
];
|
||||||
@ -0,0 +1,3 @@
|
|||||||
|
import DeploymentDetails from './DeploymentDetails';
|
||||||
|
|
||||||
|
export default DeploymentDetails;
|
||||||
@ -0,0 +1,57 @@
|
|||||||
|
.infra-monitoring-container {
|
||||||
|
.deployments-list-table {
|
||||||
|
.expanded-table-container {
|
||||||
|
padding-left: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-table-row-expand-icon-cell {
|
||||||
|
min-width: 30px !important;
|
||||||
|
max-width: 30px !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-table-cell {
|
||||||
|
&:has(.deployment-name-header) {
|
||||||
|
min-width: 250px !important;
|
||||||
|
max-width: 250px !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-table-cell {
|
||||||
|
&:has(.namespace-name-header) {
|
||||||
|
min-width: 220px !important;
|
||||||
|
max-width: 220px !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-table-cell {
|
||||||
|
&:has(.med-col) {
|
||||||
|
min-width: 200px !important;
|
||||||
|
max-width: 200px !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-table-cell {
|
||||||
|
&:has(.small-col) {
|
||||||
|
min-width: 120px !important;
|
||||||
|
max-width: 120px !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.progress-container {
|
||||||
|
display: flex !important;
|
||||||
|
justify-content: start !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.expanded-deployments-list-table {
|
||||||
|
.ant-table-cell {
|
||||||
|
min-width: 180px !important;
|
||||||
|
max-width: 180px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-table-row-expand-icon-cell {
|
||||||
|
min-width: 30px !important;
|
||||||
|
max-width: 30px !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,518 @@
|
|||||||
|
/* eslint-disable no-restricted-syntax */
|
||||||
|
/* eslint-disable @typescript-eslint/explicit-function-return-type */
|
||||||
|
import '../InfraMonitoringK8s.styles.scss';
|
||||||
|
import './K8sDeploymentsList.styles.scss';
|
||||||
|
|
||||||
|
import { LoadingOutlined } from '@ant-design/icons';
|
||||||
|
import {
|
||||||
|
Button,
|
||||||
|
Spin,
|
||||||
|
Table,
|
||||||
|
TablePaginationConfig,
|
||||||
|
TableProps,
|
||||||
|
Typography,
|
||||||
|
} from 'antd';
|
||||||
|
import { ColumnType, SorterResult } from 'antd/es/table/interface';
|
||||||
|
import logEvent from 'api/common/logEvent';
|
||||||
|
import { K8sDeploymentsListPayload } from 'api/infraMonitoring/getK8sDeploymentsList';
|
||||||
|
import classNames from 'classnames';
|
||||||
|
import { useGetK8sDeploymentsList } from 'hooks/infraMonitoring/useGetK8sDeploymentsList';
|
||||||
|
import { useGetAggregateKeys } from 'hooks/queryBuilder/useGetAggregateKeys';
|
||||||
|
import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
|
||||||
|
import { useQueryOperations } from 'hooks/queryBuilder/useQueryBuilderOperations';
|
||||||
|
import { ChevronDown, ChevronRight } from 'lucide-react';
|
||||||
|
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 {
|
||||||
|
K8sCategory,
|
||||||
|
K8sEntityToAggregateAttributeMapping,
|
||||||
|
} from '../constants';
|
||||||
|
import K8sHeader from '../K8sHeader';
|
||||||
|
import LoadingContainer from '../LoadingContainer';
|
||||||
|
import DeploymentDetails from './DeploymentDetails';
|
||||||
|
import {
|
||||||
|
defaultAddedColumns,
|
||||||
|
formatDataForTable,
|
||||||
|
getK8sDeploymentsListColumns,
|
||||||
|
getK8sDeploymentsListQuery,
|
||||||
|
K8sDeploymentsRowData,
|
||||||
|
} from './utils';
|
||||||
|
|
||||||
|
// eslint-disable-next-line sonarjs/cognitive-complexity
|
||||||
|
function K8sDeploymentsList({
|
||||||
|
isFiltersVisible,
|
||||||
|
handleFilterVisibilityChange,
|
||||||
|
quickFiltersLastUpdated,
|
||||||
|
}: {
|
||||||
|
isFiltersVisible: boolean;
|
||||||
|
handleFilterVisibilityChange: () => void;
|
||||||
|
quickFiltersLastUpdated: number;
|
||||||
|
}): JSX.Element {
|
||||||
|
const { maxTime, minTime } = useSelector<AppState, GlobalReducer>(
|
||||||
|
(state) => state.globalTime,
|
||||||
|
);
|
||||||
|
|
||||||
|
const [currentPage, setCurrentPage] = useState(1);
|
||||||
|
|
||||||
|
const [expandedRowKeys, setExpandedRowKeys] = useState<string[]>([]);
|
||||||
|
|
||||||
|
const [orderBy, setOrderBy] = useState<{
|
||||||
|
columnName: string;
|
||||||
|
order: 'asc' | 'desc';
|
||||||
|
} | null>(null);
|
||||||
|
|
||||||
|
const [selectedDeploymentUID, setselectedDeploymentUID] = useState<
|
||||||
|
string | null
|
||||||
|
>(null);
|
||||||
|
|
||||||
|
const pageSize = 10;
|
||||||
|
|
||||||
|
const [groupBy, setGroupBy] = useState<IBuilderQuery['groupBy']>([]);
|
||||||
|
|
||||||
|
const [
|
||||||
|
selectedRowData,
|
||||||
|
setSelectedRowData,
|
||||||
|
] = useState<K8sDeploymentsRowData | null>(null);
|
||||||
|
|
||||||
|
const [groupByOptions, setGroupByOptions] = useState<
|
||||||
|
{ value: string; label: string }[]
|
||||||
|
>([]);
|
||||||
|
|
||||||
|
const { currentQuery } = useQueryBuilder();
|
||||||
|
|
||||||
|
const queryFilters = useMemo(
|
||||||
|
() =>
|
||||||
|
currentQuery?.builder?.queryData[0]?.filters || {
|
||||||
|
items: [],
|
||||||
|
op: 'and',
|
||||||
|
},
|
||||||
|
[currentQuery?.builder?.queryData],
|
||||||
|
);
|
||||||
|
|
||||||
|
// Reset pagination every time quick filters are changed
|
||||||
|
useEffect(() => {
|
||||||
|
setCurrentPage(1);
|
||||||
|
}, [quickFiltersLastUpdated]);
|
||||||
|
|
||||||
|
const createFiltersForSelectedRowData = (
|
||||||
|
selectedRowData: K8sDeploymentsRowData,
|
||||||
|
groupBy: IBuilderQuery['groupBy'],
|
||||||
|
): IBuilderQuery['filters'] => {
|
||||||
|
const baseFilters: IBuilderQuery['filters'] = {
|
||||||
|
items: [...queryFilters.items],
|
||||||
|
op: 'and',
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!selectedRowData) return baseFilters;
|
||||||
|
|
||||||
|
const { groupedByMeta } = selectedRowData;
|
||||||
|
|
||||||
|
for (const key of groupBy) {
|
||||||
|
baseFilters.items.push({
|
||||||
|
key: {
|
||||||
|
key: key.key,
|
||||||
|
type: null,
|
||||||
|
},
|
||||||
|
op: '=',
|
||||||
|
value: groupedByMeta[key.key],
|
||||||
|
id: key.key,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return baseFilters;
|
||||||
|
};
|
||||||
|
|
||||||
|
const fetchGroupedByRowDataQuery = useMemo(() => {
|
||||||
|
if (!selectedRowData) return null;
|
||||||
|
|
||||||
|
const baseQuery = getK8sDeploymentsListQuery();
|
||||||
|
|
||||||
|
const filters = createFiltersForSelectedRowData(selectedRowData, groupBy);
|
||||||
|
|
||||||
|
return {
|
||||||
|
...baseQuery,
|
||||||
|
limit: 10,
|
||||||
|
offset: 0,
|
||||||
|
filters,
|
||||||
|
start: Math.floor(minTime / 1000000),
|
||||||
|
end: Math.floor(maxTime / 1000000),
|
||||||
|
orderBy,
|
||||||
|
};
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
}, [minTime, maxTime, orderBy, selectedRowData, groupBy]);
|
||||||
|
|
||||||
|
const {
|
||||||
|
data: groupedByRowData,
|
||||||
|
isFetching: isFetchingGroupedByRowData,
|
||||||
|
isLoading: isLoadingGroupedByRowData,
|
||||||
|
isError: isErrorGroupedByRowData,
|
||||||
|
refetch: fetchGroupedByRowData,
|
||||||
|
} = useGetK8sDeploymentsList(
|
||||||
|
fetchGroupedByRowDataQuery as K8sDeploymentsListPayload,
|
||||||
|
{
|
||||||
|
queryKey: ['deploymentList', fetchGroupedByRowDataQuery],
|
||||||
|
enabled: !!fetchGroupedByRowDataQuery && !!selectedRowData,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
const {
|
||||||
|
data: groupByFiltersData,
|
||||||
|
isLoading: isLoadingGroupByFilters,
|
||||||
|
} = useGetAggregateKeys(
|
||||||
|
{
|
||||||
|
dataSource: currentQuery.builder.queryData[0].dataSource,
|
||||||
|
aggregateAttribute: K8sEntityToAggregateAttributeMapping[K8sCategory.NODES],
|
||||||
|
aggregateOperator: 'noop',
|
||||||
|
searchText: '',
|
||||||
|
tagType: '',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
queryKey: [currentQuery.builder.queryData[0].dataSource, 'noop'],
|
||||||
|
},
|
||||||
|
true,
|
||||||
|
K8sCategory.NODES,
|
||||||
|
);
|
||||||
|
|
||||||
|
const query = useMemo(() => {
|
||||||
|
const baseQuery = getK8sDeploymentsListQuery();
|
||||||
|
const queryPayload = {
|
||||||
|
...baseQuery,
|
||||||
|
limit: pageSize,
|
||||||
|
offset: (currentPage - 1) * pageSize,
|
||||||
|
filters: queryFilters,
|
||||||
|
start: Math.floor(minTime / 1000000),
|
||||||
|
end: Math.floor(maxTime / 1000000),
|
||||||
|
orderBy,
|
||||||
|
};
|
||||||
|
if (groupBy.length > 0) {
|
||||||
|
queryPayload.groupBy = groupBy;
|
||||||
|
}
|
||||||
|
return queryPayload;
|
||||||
|
}, [currentPage, minTime, maxTime, orderBy, groupBy, queryFilters]);
|
||||||
|
|
||||||
|
const formattedGroupedByDeploymentsData = useMemo(
|
||||||
|
() =>
|
||||||
|
formatDataForTable(groupedByRowData?.payload?.data?.records || [], groupBy),
|
||||||
|
[groupedByRowData, groupBy],
|
||||||
|
);
|
||||||
|
|
||||||
|
const { data, isFetching, isLoading, isError } = useGetK8sDeploymentsList(
|
||||||
|
query as K8sDeploymentsListPayload,
|
||||||
|
{
|
||||||
|
queryKey: ['deploymentList', query],
|
||||||
|
enabled: !!query,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
const deploymentsData = useMemo(() => data?.payload?.data?.records || [], [
|
||||||
|
data,
|
||||||
|
]);
|
||||||
|
const totalCount = data?.payload?.data?.total || 0;
|
||||||
|
|
||||||
|
const formattedDeploymentsData = useMemo(
|
||||||
|
() => formatDataForTable(deploymentsData, groupBy),
|
||||||
|
[deploymentsData, groupBy],
|
||||||
|
);
|
||||||
|
|
||||||
|
const columns = useMemo(() => getK8sDeploymentsListColumns(groupBy), [
|
||||||
|
groupBy,
|
||||||
|
]);
|
||||||
|
|
||||||
|
const handleGroupByRowClick = (record: K8sDeploymentsRowData): void => {
|
||||||
|
setSelectedRowData(record);
|
||||||
|
|
||||||
|
if (expandedRowKeys.includes(record.key)) {
|
||||||
|
setExpandedRowKeys(expandedRowKeys.filter((key) => key !== record.key));
|
||||||
|
} else {
|
||||||
|
setExpandedRowKeys([record.key]);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (selectedRowData) {
|
||||||
|
fetchGroupedByRowData();
|
||||||
|
}
|
||||||
|
}, [selectedRowData, fetchGroupedByRowData]);
|
||||||
|
|
||||||
|
const handleTableChange: TableProps<K8sDeploymentsRowData>['onChange'] = useCallback(
|
||||||
|
(
|
||||||
|
pagination: TablePaginationConfig,
|
||||||
|
_filters: Record<string, (string | number | boolean)[] | null>,
|
||||||
|
sorter:
|
||||||
|
| SorterResult<K8sDeploymentsRowData>
|
||||||
|
| SorterResult<K8sDeploymentsRowData>[],
|
||||||
|
): 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 { handleChangeQueryData } = useQueryOperations({
|
||||||
|
index: 0,
|
||||||
|
query: currentQuery.builder.queryData[0],
|
||||||
|
entityVersion: '',
|
||||||
|
});
|
||||||
|
|
||||||
|
const handleFiltersChange = useCallback(
|
||||||
|
(value: IBuilderQuery['filters']): void => {
|
||||||
|
handleChangeQueryData('filters', value);
|
||||||
|
setCurrentPage(1);
|
||||||
|
|
||||||
|
logEvent('Infra Monitoring: K8s list filters applied', {
|
||||||
|
filters: value,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
[handleChangeQueryData],
|
||||||
|
);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
logEvent('Infra Monitoring: K8s list page visited', {});
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const selectedDeploymentData = useMemo(() => {
|
||||||
|
if (!selectedDeploymentUID) return null;
|
||||||
|
return (
|
||||||
|
deploymentsData.find(
|
||||||
|
(deployment) => deployment.deploymentName === selectedDeploymentUID,
|
||||||
|
) || null
|
||||||
|
);
|
||||||
|
}, [selectedDeploymentUID, deploymentsData]);
|
||||||
|
|
||||||
|
const handleRowClick = (record: K8sDeploymentsRowData): void => {
|
||||||
|
if (groupBy.length === 0) {
|
||||||
|
setSelectedRowData(null);
|
||||||
|
setselectedDeploymentUID(record.deploymentUID);
|
||||||
|
} else {
|
||||||
|
handleGroupByRowClick(record);
|
||||||
|
}
|
||||||
|
|
||||||
|
logEvent('Infra Monitoring: K8s deployment list item clicked', {
|
||||||
|
deploymentUID: record.deploymentName,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const nestedColumns = useMemo(() => getK8sDeploymentsListColumns([]), []);
|
||||||
|
|
||||||
|
const isGroupedByAttribute = groupBy.length > 0;
|
||||||
|
|
||||||
|
const handleExpandedRowViewAllClick = (): void => {
|
||||||
|
if (!selectedRowData) return;
|
||||||
|
|
||||||
|
const filters = createFiltersForSelectedRowData(selectedRowData, groupBy);
|
||||||
|
|
||||||
|
handleFiltersChange(filters);
|
||||||
|
|
||||||
|
setCurrentPage(1);
|
||||||
|
setSelectedRowData(null);
|
||||||
|
setGroupBy([]);
|
||||||
|
setOrderBy(null);
|
||||||
|
};
|
||||||
|
|
||||||
|
const expandedRowRender = (): JSX.Element => (
|
||||||
|
<div className="expanded-table-container">
|
||||||
|
{isErrorGroupedByRowData && (
|
||||||
|
<Typography>{groupedByRowData?.error || 'Something went wrong'}</Typography>
|
||||||
|
)}
|
||||||
|
{isFetchingGroupedByRowData || isLoadingGroupedByRowData ? (
|
||||||
|
<LoadingContainer />
|
||||||
|
) : (
|
||||||
|
<div className="expanded-table">
|
||||||
|
<Table
|
||||||
|
columns={nestedColumns as ColumnType<K8sDeploymentsRowData>[]}
|
||||||
|
dataSource={formattedGroupedByDeploymentsData}
|
||||||
|
pagination={false}
|
||||||
|
scroll={{ x: true }}
|
||||||
|
tableLayout="fixed"
|
||||||
|
size="small"
|
||||||
|
loading={{
|
||||||
|
spinning: isFetchingGroupedByRowData || isLoadingGroupedByRowData,
|
||||||
|
indicator: <Spin indicator={<LoadingOutlined size={14} spin />} />,
|
||||||
|
}}
|
||||||
|
showHeader={false}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{groupedByRowData?.payload?.data?.total &&
|
||||||
|
groupedByRowData?.payload?.data?.total > 10 ? (
|
||||||
|
<div className="expanded-table-footer">
|
||||||
|
<Button
|
||||||
|
type="default"
|
||||||
|
size="small"
|
||||||
|
className="periscope-btn secondary"
|
||||||
|
onClick={handleExpandedRowViewAllClick}
|
||||||
|
>
|
||||||
|
View All
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
const expandRowIconRenderer = ({
|
||||||
|
expanded,
|
||||||
|
onExpand,
|
||||||
|
record,
|
||||||
|
}: {
|
||||||
|
expanded: boolean;
|
||||||
|
onExpand: (
|
||||||
|
record: K8sDeploymentsRowData,
|
||||||
|
e: React.MouseEvent<HTMLButtonElement>,
|
||||||
|
) => void;
|
||||||
|
record: K8sDeploymentsRowData;
|
||||||
|
}): JSX.Element | null => {
|
||||||
|
if (!isGroupedByAttribute) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return expanded ? (
|
||||||
|
<Button
|
||||||
|
className="periscope-btn ghost"
|
||||||
|
onClick={(e: React.MouseEvent<HTMLButtonElement>): void =>
|
||||||
|
onExpand(record, e)
|
||||||
|
}
|
||||||
|
role="button"
|
||||||
|
>
|
||||||
|
<ChevronDown size={14} />
|
||||||
|
</Button>
|
||||||
|
) : (
|
||||||
|
<Button
|
||||||
|
className="periscope-btn ghost"
|
||||||
|
onClick={(e: React.MouseEvent<HTMLButtonElement>): void =>
|
||||||
|
onExpand(record, e)
|
||||||
|
}
|
||||||
|
role="button"
|
||||||
|
>
|
||||||
|
<ChevronRight size={14} />
|
||||||
|
</Button>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleCloseDeploymentDetail = (): void => {
|
||||||
|
setselectedDeploymentUID(null);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleGroupByChange = useCallback(
|
||||||
|
(value: IBuilderQuery['groupBy']) => {
|
||||||
|
const groupBy = [];
|
||||||
|
|
||||||
|
for (let index = 0; index < value.length; index++) {
|
||||||
|
const element = (value[index] as unknown) as string;
|
||||||
|
|
||||||
|
const key = groupByFiltersData?.payload?.attributeKeys?.find(
|
||||||
|
(key) => key.key === element,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (key) {
|
||||||
|
groupBy.push(key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reset pagination on switching to groupBy
|
||||||
|
setCurrentPage(1);
|
||||||
|
setGroupBy(groupBy);
|
||||||
|
setExpandedRowKeys([]);
|
||||||
|
},
|
||||||
|
[groupByFiltersData],
|
||||||
|
);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (groupByFiltersData?.payload) {
|
||||||
|
setGroupByOptions(
|
||||||
|
groupByFiltersData?.payload?.attributeKeys?.map((filter) => ({
|
||||||
|
value: filter.key,
|
||||||
|
label: filter.key,
|
||||||
|
})) || [],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}, [groupByFiltersData]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="k8s-list">
|
||||||
|
<K8sHeader
|
||||||
|
isFiltersVisible={isFiltersVisible}
|
||||||
|
handleFilterVisibilityChange={handleFilterVisibilityChange}
|
||||||
|
defaultAddedColumns={defaultAddedColumns}
|
||||||
|
handleFiltersChange={handleFiltersChange}
|
||||||
|
groupByOptions={groupByOptions}
|
||||||
|
isLoadingGroupByFilters={isLoadingGroupByFilters}
|
||||||
|
handleGroupByChange={handleGroupByChange}
|
||||||
|
selectedGroupBy={groupBy}
|
||||||
|
entity={K8sCategory.NODES}
|
||||||
|
/>
|
||||||
|
{isError && <Typography>{data?.error || 'Something went wrong'}</Typography>}
|
||||||
|
|
||||||
|
<Table
|
||||||
|
className={classNames('k8s-list-table', 'deployments-list-table', {
|
||||||
|
'expanded-deployments-list-table': isGroupedByAttribute,
|
||||||
|
})}
|
||||||
|
dataSource={isFetching || isLoading ? [] : formattedDeploymentsData}
|
||||||
|
columns={columns}
|
||||||
|
pagination={{
|
||||||
|
current: currentPage,
|
||||||
|
pageSize,
|
||||||
|
total: totalCount,
|
||||||
|
showSizeChanger: false,
|
||||||
|
hideOnSinglePage: true,
|
||||||
|
}}
|
||||||
|
scroll={{ x: true }}
|
||||||
|
loading={{
|
||||||
|
spinning: isFetching || isLoading,
|
||||||
|
indicator: <Spin indicator={<LoadingOutlined size={14} spin />} />,
|
||||||
|
}}
|
||||||
|
locale={{
|
||||||
|
emptyText:
|
||||||
|
isFetching || isLoading ? null : (
|
||||||
|
<div className="no-filtered-hosts-message-container">
|
||||||
|
<div className="no-filtered-hosts-message-content">
|
||||||
|
<img
|
||||||
|
src="/Icons/emptyState.svg"
|
||||||
|
alt="thinking-emoji"
|
||||||
|
className="empty-state-svg"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Typography.Text className="no-filtered-hosts-message">
|
||||||
|
This query had no results. Edit your query and try again!
|
||||||
|
</Typography.Text>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
}}
|
||||||
|
tableLayout="fixed"
|
||||||
|
onChange={handleTableChange}
|
||||||
|
onRow={(record): { onClick: () => void; className: string } => ({
|
||||||
|
onClick: (): void => handleRowClick(record),
|
||||||
|
className: 'clickable-row',
|
||||||
|
})}
|
||||||
|
expandable={{
|
||||||
|
expandedRowRender: isGroupedByAttribute ? expandedRowRender : undefined,
|
||||||
|
expandIcon: expandRowIconRenderer,
|
||||||
|
expandedRowKeys,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<DeploymentDetails
|
||||||
|
deployment={selectedDeploymentData}
|
||||||
|
isModalTimeSelection
|
||||||
|
onClose={handleCloseDeploymentDetail}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default K8sDeploymentsList;
|
||||||
323
frontend/src/container/InfraMonitoringK8s/Deployments/utils.tsx
Normal file
323
frontend/src/container/InfraMonitoringK8s/Deployments/utils.tsx
Normal file
@ -0,0 +1,323 @@
|
|||||||
|
import { Color } from '@signozhq/design-tokens';
|
||||||
|
import { Tag, Tooltip } from 'antd';
|
||||||
|
import { ColumnType } from 'antd/es/table';
|
||||||
|
import {
|
||||||
|
K8sDeploymentsData,
|
||||||
|
K8sDeploymentsListPayload,
|
||||||
|
} from 'api/infraMonitoring/getK8sDeploymentsList';
|
||||||
|
import { Group } from 'lucide-react';
|
||||||
|
import { IBuilderQuery } from 'types/api/queryBuilder/queryBuilderData';
|
||||||
|
|
||||||
|
import {
|
||||||
|
EntityProgressBar,
|
||||||
|
formatBytes,
|
||||||
|
ValidateColumnValueWrapper,
|
||||||
|
} from '../commonUtils';
|
||||||
|
import { IEntityColumn } from '../utils';
|
||||||
|
|
||||||
|
export const defaultAddedColumns: IEntityColumn[] = [
|
||||||
|
{
|
||||||
|
label: 'Deployment Name',
|
||||||
|
value: 'deploymentName',
|
||||||
|
id: 'deploymentName',
|
||||||
|
canRemove: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Namespace Name',
|
||||||
|
value: 'namespaceName',
|
||||||
|
id: 'namespaceName',
|
||||||
|
canRemove: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Available',
|
||||||
|
value: 'available',
|
||||||
|
id: 'available',
|
||||||
|
canRemove: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Desired',
|
||||||
|
value: 'desired',
|
||||||
|
id: 'desired',
|
||||||
|
canRemove: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'CPU Request Utilization (% of limit)',
|
||||||
|
value: 'cpu_request',
|
||||||
|
id: 'cpu_request',
|
||||||
|
canRemove: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'CPU Limit Utilization (% of request)',
|
||||||
|
value: 'cpu_limit',
|
||||||
|
id: 'cpu_limit',
|
||||||
|
canRemove: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'CPU Utilization (cores)',
|
||||||
|
value: 'cpu',
|
||||||
|
id: 'cpu',
|
||||||
|
canRemove: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Memory Request Utilization (% of limit)',
|
||||||
|
value: 'memory_request',
|
||||||
|
id: 'memory_request',
|
||||||
|
canRemove: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Memory Limit Utilization (% of request)',
|
||||||
|
value: 'memory_limit',
|
||||||
|
id: 'memory_limit',
|
||||||
|
canRemove: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Memory Utilization (bytes)',
|
||||||
|
value: 'memory',
|
||||||
|
id: 'memory',
|
||||||
|
canRemove: false,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
export interface K8sDeploymentsRowData {
|
||||||
|
key: string;
|
||||||
|
deploymentUID: string;
|
||||||
|
deploymentName: React.ReactNode;
|
||||||
|
availableReplicas: React.ReactNode;
|
||||||
|
desiredReplicas: React.ReactNode;
|
||||||
|
cpu_request: React.ReactNode;
|
||||||
|
cpu_limit: React.ReactNode;
|
||||||
|
cpu: React.ReactNode;
|
||||||
|
memory_request: React.ReactNode;
|
||||||
|
memory_limit: React.ReactNode;
|
||||||
|
memory: React.ReactNode;
|
||||||
|
restarts: React.ReactNode;
|
||||||
|
clusterName: string;
|
||||||
|
namespaceName: string;
|
||||||
|
groupedByMeta?: any;
|
||||||
|
}
|
||||||
|
|
||||||
|
const deploymentGroupColumnConfig = {
|
||||||
|
title: (
|
||||||
|
<div className="column-header entity-group-header">
|
||||||
|
<Group size={14} /> DEPLOYMENT GROUP
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
dataIndex: 'deploymentGroup',
|
||||||
|
key: 'deploymentGroup',
|
||||||
|
ellipsis: true,
|
||||||
|
width: 150,
|
||||||
|
align: 'left',
|
||||||
|
sorter: false,
|
||||||
|
className: 'column entity-group-header',
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getK8sDeploymentsListQuery = (): K8sDeploymentsListPayload => ({
|
||||||
|
filters: {
|
||||||
|
items: [],
|
||||||
|
op: 'and',
|
||||||
|
},
|
||||||
|
orderBy: { columnName: 'cpu', order: 'desc' },
|
||||||
|
});
|
||||||
|
|
||||||
|
const columnsConfig = [
|
||||||
|
{
|
||||||
|
title: (
|
||||||
|
<div className="column-header-left deployment-name-header">
|
||||||
|
Deployment Name
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
dataIndex: 'deploymentName',
|
||||||
|
key: 'deploymentName',
|
||||||
|
ellipsis: true,
|
||||||
|
width: 150,
|
||||||
|
sorter: false,
|
||||||
|
align: 'left',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: (
|
||||||
|
<div className="column-header-left namespace-name-header">
|
||||||
|
Namespace Name
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
dataIndex: 'namespaceName',
|
||||||
|
key: 'namespaceName',
|
||||||
|
ellipsis: true,
|
||||||
|
width: 150,
|
||||||
|
sorter: false,
|
||||||
|
align: 'left',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: <div className="column-header-left small-col">Available</div>,
|
||||||
|
dataIndex: 'availableReplicas',
|
||||||
|
key: 'availableReplicas',
|
||||||
|
width: 100,
|
||||||
|
sorter: false,
|
||||||
|
align: 'left',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: <div className="column-header-left small-col">Desired</div>,
|
||||||
|
dataIndex: 'desiredReplicas',
|
||||||
|
key: 'desiredReplicas',
|
||||||
|
width: 80,
|
||||||
|
sorter: false,
|
||||||
|
align: 'left',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: <div className="column-header-left med-col">CPU Req Usage (%)</div>,
|
||||||
|
dataIndex: 'cpu_request',
|
||||||
|
key: 'cpu_request',
|
||||||
|
width: 80,
|
||||||
|
sorter: true,
|
||||||
|
align: 'left',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: <div className="column-header-left med-col">CPU Limit Usage (%)</div>,
|
||||||
|
dataIndex: 'cpu_limit',
|
||||||
|
key: 'cpu_limit',
|
||||||
|
width: 50,
|
||||||
|
sorter: true,
|
||||||
|
align: 'left',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: <div className="column-header- small-col">CPU Usage (cores)</div>,
|
||||||
|
dataIndex: 'cpu',
|
||||||
|
key: 'cpu',
|
||||||
|
width: 80,
|
||||||
|
sorter: true,
|
||||||
|
align: 'left',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: <div className="column-header-left med-col">Mem Req Usage (%)</div>,
|
||||||
|
dataIndex: 'memory_request',
|
||||||
|
key: 'memory_request',
|
||||||
|
width: 50,
|
||||||
|
sorter: true,
|
||||||
|
align: 'left',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: <div className="column-header-left med-col">Mem Limit Usage (%)</div>,
|
||||||
|
dataIndex: 'memory_limit',
|
||||||
|
key: 'memory_limit',
|
||||||
|
width: 80,
|
||||||
|
sorter: true,
|
||||||
|
align: 'left',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: <div className="column-header-left small-col">Mem Usage</div>,
|
||||||
|
dataIndex: 'memory',
|
||||||
|
key: 'memory',
|
||||||
|
width: 80,
|
||||||
|
sorter: true,
|
||||||
|
align: 'left',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
export const getK8sDeploymentsListColumns = (
|
||||||
|
groupBy: IBuilderQuery['groupBy'],
|
||||||
|
): ColumnType<K8sDeploymentsRowData>[] => {
|
||||||
|
if (groupBy.length > 0) {
|
||||||
|
const filteredColumns = [...columnsConfig].filter(
|
||||||
|
(column) => column.key !== 'deploymentName',
|
||||||
|
);
|
||||||
|
filteredColumns.unshift(deploymentGroupColumnConfig);
|
||||||
|
return filteredColumns as ColumnType<K8sDeploymentsRowData>[];
|
||||||
|
}
|
||||||
|
|
||||||
|
return columnsConfig as ColumnType<K8sDeploymentsRowData>[];
|
||||||
|
};
|
||||||
|
|
||||||
|
const getGroupByEle = (
|
||||||
|
deployment: K8sDeploymentsData,
|
||||||
|
groupBy: IBuilderQuery['groupBy'],
|
||||||
|
): React.ReactNode => {
|
||||||
|
const groupByValues: string[] = [];
|
||||||
|
|
||||||
|
groupBy.forEach((group) => {
|
||||||
|
groupByValues.push(
|
||||||
|
deployment.meta[group.key as keyof typeof deployment.meta],
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="pod-group">
|
||||||
|
{groupByValues.map((value) => (
|
||||||
|
<Tag key={value} color={Color.BG_SLATE_400} className="pod-group-tag-item">
|
||||||
|
{value === '' ? '<no-value>' : value}
|
||||||
|
</Tag>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const formatDataForTable = (
|
||||||
|
data: K8sDeploymentsData[],
|
||||||
|
groupBy: IBuilderQuery['groupBy'],
|
||||||
|
): K8sDeploymentsRowData[] =>
|
||||||
|
data.map((deployment, index) => ({
|
||||||
|
key: index.toString(),
|
||||||
|
deploymentUID: deployment.meta.k8s_deployment_name,
|
||||||
|
deploymentName: (
|
||||||
|
<Tooltip title={deployment.meta.k8s_deployment_name}>
|
||||||
|
{deployment.meta.k8s_deployment_name}
|
||||||
|
</Tooltip>
|
||||||
|
),
|
||||||
|
availableReplicas: (
|
||||||
|
<ValidateColumnValueWrapper value={deployment.availablePods}>
|
||||||
|
{deployment.availablePods}
|
||||||
|
</ValidateColumnValueWrapper>
|
||||||
|
),
|
||||||
|
desiredReplicas: (
|
||||||
|
<ValidateColumnValueWrapper value={deployment.desiredPods}>
|
||||||
|
{deployment.desiredPods}
|
||||||
|
</ValidateColumnValueWrapper>
|
||||||
|
),
|
||||||
|
restarts: (
|
||||||
|
<ValidateColumnValueWrapper value={deployment.restarts}>
|
||||||
|
{deployment.restarts}
|
||||||
|
</ValidateColumnValueWrapper>
|
||||||
|
),
|
||||||
|
cpu: (
|
||||||
|
<ValidateColumnValueWrapper value={deployment.cpuUsage}>
|
||||||
|
{deployment.cpuUsage}
|
||||||
|
</ValidateColumnValueWrapper>
|
||||||
|
),
|
||||||
|
cpu_request: (
|
||||||
|
<ValidateColumnValueWrapper value={deployment.cpuRequest}>
|
||||||
|
<div className="progress-container">
|
||||||
|
<EntityProgressBar value={deployment.cpuRequest} type="request" />
|
||||||
|
</div>
|
||||||
|
</ValidateColumnValueWrapper>
|
||||||
|
),
|
||||||
|
cpu_limit: (
|
||||||
|
<ValidateColumnValueWrapper value={deployment.cpuLimit}>
|
||||||
|
<div className="progress-container">
|
||||||
|
<EntityProgressBar value={deployment.cpuLimit} type="limit" />
|
||||||
|
</div>
|
||||||
|
</ValidateColumnValueWrapper>
|
||||||
|
),
|
||||||
|
memory: (
|
||||||
|
<ValidateColumnValueWrapper value={deployment.memoryUsage}>
|
||||||
|
{formatBytes(deployment.memoryUsage)}
|
||||||
|
</ValidateColumnValueWrapper>
|
||||||
|
),
|
||||||
|
memory_request: (
|
||||||
|
<ValidateColumnValueWrapper value={deployment.memoryRequest}>
|
||||||
|
<div className="progress-container">
|
||||||
|
<EntityProgressBar value={deployment.memoryRequest} type="request" />
|
||||||
|
</div>
|
||||||
|
</ValidateColumnValueWrapper>
|
||||||
|
),
|
||||||
|
memory_limit: (
|
||||||
|
<ValidateColumnValueWrapper value={deployment.memoryLimit}>
|
||||||
|
<div className="progress-container">
|
||||||
|
<EntityProgressBar value={deployment.memoryLimit} type="limit" />
|
||||||
|
</div>
|
||||||
|
</ValidateColumnValueWrapper>
|
||||||
|
),
|
||||||
|
clusterName: deployment.meta.k8s_cluster_name,
|
||||||
|
namespaceName: deployment.meta.k8s_namespace_name,
|
||||||
|
deploymentGroup: getGroupByEle(deployment, groupBy),
|
||||||
|
meta: deployment.meta,
|
||||||
|
...deployment.meta,
|
||||||
|
groupedByMeta: deployment.meta,
|
||||||
|
}));
|
||||||
@ -1,10 +1,11 @@
|
|||||||
/* eslint-disable no-nested-ternary */
|
/* eslint-disable no-nested-ternary */
|
||||||
import './NodeEvents.styles.scss';
|
import './entityEvents.styles.scss';
|
||||||
|
|
||||||
import { Color } from '@signozhq/design-tokens';
|
import { Color } from '@signozhq/design-tokens';
|
||||||
import { Button, Table, TableColumnsType } from 'antd';
|
import { Button, Table, TableColumnsType } from 'antd';
|
||||||
import { DEFAULT_ENTITY_VERSION } from 'constants/app';
|
import { DEFAULT_ENTITY_VERSION } from 'constants/app';
|
||||||
import { EventContents } from 'container/InfraMonitoringK8s/commonUtils';
|
import { EventContents } from 'container/InfraMonitoringK8s/commonUtils';
|
||||||
|
import { K8sCategory } from 'container/InfraMonitoringK8s/constants';
|
||||||
import LoadingContainer from 'container/InfraMonitoringK8s/LoadingContainer';
|
import LoadingContainer from 'container/InfraMonitoringK8s/LoadingContainer';
|
||||||
import LogsError from 'container/LogsError/LogsError';
|
import LogsError from 'container/LogsError/LogsError';
|
||||||
import { ORDERBY_FILTERS } from 'container/QueryBuilder/filters/OrderByFilter/config';
|
import { ORDERBY_FILTERS } from 'container/QueryBuilder/filters/OrderByFilter/config';
|
||||||
@ -25,8 +26,10 @@ import { IBuilderQuery } from 'types/api/queryBuilder/queryBuilderData';
|
|||||||
import { DataSource } from 'types/common/queryBuilder';
|
import { DataSource } from 'types/common/queryBuilder';
|
||||||
import { v4 } from 'uuid';
|
import { v4 } from 'uuid';
|
||||||
|
|
||||||
import { getNodesEventsQueryPayload } from './constants';
|
import {
|
||||||
import NoEventsContainer from './NoEventsContainer';
|
EntityDetailsEmptyContainer,
|
||||||
|
getEntityEventsOrLogsQueryPayload,
|
||||||
|
} from '../utils';
|
||||||
|
|
||||||
interface EventDataType {
|
interface EventDataType {
|
||||||
key: string;
|
key: string;
|
||||||
@ -48,7 +51,7 @@ interface EventDataType {
|
|||||||
severity?: string;
|
severity?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface INodeEventsProps {
|
interface IEntityEventsProps {
|
||||||
timeRange: {
|
timeRange: {
|
||||||
startTime: number;
|
startTime: number;
|
||||||
endTime: number;
|
endTime: number;
|
||||||
@ -61,6 +64,8 @@ interface INodeEventsProps {
|
|||||||
dateTimeRange?: [number, number],
|
dateTimeRange?: [number, number],
|
||||||
) => void;
|
) => void;
|
||||||
selectedInterval: Time;
|
selectedInterval: Time;
|
||||||
|
category: K8sCategory;
|
||||||
|
queryKey: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const EventsPageSize = 10;
|
const EventsPageSize = 10;
|
||||||
@ -72,10 +77,12 @@ export default function Events({
|
|||||||
isModalTimeSelection,
|
isModalTimeSelection,
|
||||||
handleTimeChange,
|
handleTimeChange,
|
||||||
selectedInterval,
|
selectedInterval,
|
||||||
}: INodeEventsProps): JSX.Element {
|
category,
|
||||||
|
queryKey,
|
||||||
|
}: IEntityEventsProps): JSX.Element {
|
||||||
const { currentQuery } = useQueryBuilder();
|
const { currentQuery } = useQueryBuilder();
|
||||||
|
|
||||||
const [formattedNodeEvents, setFormattedNodeEvents] = useState<
|
const [formattedEntityEvents, setFormattedEntityEvents] = useState<
|
||||||
EventDataType[]
|
EventDataType[]
|
||||||
>([]);
|
>([]);
|
||||||
|
|
||||||
@ -110,7 +117,7 @@ export default function Events({
|
|||||||
const query = updatedCurrentQuery?.builder?.queryData[0] || null;
|
const query = updatedCurrentQuery?.builder?.queryData[0] || null;
|
||||||
|
|
||||||
const queryPayload = useMemo(() => {
|
const queryPayload = useMemo(() => {
|
||||||
const basePayload = getNodesEventsQueryPayload(
|
const basePayload = getEntityEventsOrLogsQueryPayload(
|
||||||
timeRange.startTime,
|
timeRange.startTime,
|
||||||
timeRange.endTime,
|
timeRange.endTime,
|
||||||
filters,
|
filters,
|
||||||
@ -125,7 +132,7 @@ export default function Events({
|
|||||||
}, [timeRange.startTime, timeRange.endTime, filters]);
|
}, [timeRange.startTime, timeRange.endTime, filters]);
|
||||||
|
|
||||||
const { data: eventsData, isLoading, isFetching, isError } = useQuery({
|
const { data: eventsData, isLoading, isFetching, isError } = useQuery({
|
||||||
queryKey: ['nodeEvents', timeRange.startTime, timeRange.endTime, filters],
|
queryKey: [queryKey, timeRange.startTime, timeRange.endTime, filters],
|
||||||
queryFn: () => GetMetricQueryRange(queryPayload, DEFAULT_ENTITY_VERSION),
|
queryFn: () => GetMetricQueryRange(queryPayload, DEFAULT_ENTITY_VERSION),
|
||||||
enabled: !!queryPayload,
|
enabled: !!queryPayload,
|
||||||
});
|
});
|
||||||
@ -159,7 +166,7 @@ export default function Events({
|
|||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
setFormattedNodeEvents(formattedData);
|
setFormattedEntityEvents(formattedData);
|
||||||
|
|
||||||
if (
|
if (
|
||||||
!responsePayload ||
|
!responsePayload ||
|
||||||
@ -181,11 +188,11 @@ export default function Events({
|
|||||||
);
|
);
|
||||||
|
|
||||||
const handlePrev = (): void => {
|
const handlePrev = (): void => {
|
||||||
if (!formattedNodeEvents.length) return;
|
if (!formattedEntityEvents.length) return;
|
||||||
|
|
||||||
setPage(page - 1);
|
setPage(page - 1);
|
||||||
|
|
||||||
const firstEvent = formattedNodeEvents[0];
|
const firstEvent = formattedEntityEvents[0];
|
||||||
|
|
||||||
const newItems = [
|
const newItems = [
|
||||||
...filters.items.filter((item) => item.key?.key !== 'id'),
|
...filters.items.filter((item) => item.key?.key !== 'id'),
|
||||||
@ -211,10 +218,10 @@ export default function Events({
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleNext = (): void => {
|
const handleNext = (): void => {
|
||||||
if (!formattedNodeEvents.length) return;
|
if (!formattedEntityEvents.length) return;
|
||||||
|
|
||||||
setPage(page + 1);
|
setPage(page + 1);
|
||||||
const lastEvent = formattedNodeEvents[formattedNodeEvents.length - 1];
|
const lastEvent = formattedEntityEvents[formattedEntityEvents.length - 1];
|
||||||
|
|
||||||
const newItems = [
|
const newItems = [
|
||||||
...filters.items.filter((item) => item.key?.key !== 'id'),
|
...filters.items.filter((item) => item.key?.key !== 'id'),
|
||||||
@ -277,8 +284,8 @@ export default function Events({
|
|||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="node-events-container">
|
<div className="entity-events-container">
|
||||||
<div className="node-events-header">
|
<div className="entity-events-header">
|
||||||
<div className="filter-section">
|
<div className="filter-section">
|
||||||
{query && (
|
{query && (
|
||||||
<QueryBuilderSearch
|
<QueryBuilderSearch
|
||||||
@ -303,15 +310,15 @@ export default function Events({
|
|||||||
|
|
||||||
{isLoading && <LoadingContainer />}
|
{isLoading && <LoadingContainer />}
|
||||||
|
|
||||||
{!isLoading && !isError && formattedNodeEvents.length === 0 && (
|
{!isLoading && !isError && formattedEntityEvents.length === 0 && (
|
||||||
<NoEventsContainer />
|
<EntityDetailsEmptyContainer category={category} view="events" />
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{isError && !isLoading && <LogsError />}
|
{isError && !isLoading && <LogsError />}
|
||||||
|
|
||||||
{!isLoading && !isError && formattedNodeEvents.length > 0 && (
|
{!isLoading && !isError && formattedEntityEvents.length > 0 && (
|
||||||
<div className="node-events-list-container">
|
<div className="entity-events-list-container">
|
||||||
<div className="node-events-list-card">
|
<div className="entity-events-list-card">
|
||||||
<Table<EventDataType>
|
<Table<EventDataType>
|
||||||
loading={isLoading && page > 1}
|
loading={isLoading && page > 1}
|
||||||
columns={columns}
|
columns={columns}
|
||||||
@ -320,7 +327,7 @@ export default function Events({
|
|||||||
rowExpandable: (record): boolean => record.body !== 'Not Expandable',
|
rowExpandable: (record): boolean => record.body !== 'Not Expandable',
|
||||||
expandIcon: handleExpandRowIcon,
|
expandIcon: handleExpandRowIcon,
|
||||||
}}
|
}}
|
||||||
dataSource={formattedNodeEvents}
|
dataSource={formattedEntityEvents}
|
||||||
pagination={false}
|
pagination={false}
|
||||||
rowKey={(record): string => record.id}
|
rowKey={(record): string => record.id}
|
||||||
/>
|
/>
|
||||||
@ -328,10 +335,10 @@ export default function Events({
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{!isError && formattedNodeEvents.length > 0 && (
|
{!isError && formattedEntityEvents.length > 0 && (
|
||||||
<div className="node-events-footer">
|
<div className="entity-events-footer">
|
||||||
<Button
|
<Button
|
||||||
className="node-events-footer-button periscope-btn ghost"
|
className="entity-events-footer-button periscope-btn ghost"
|
||||||
type="link"
|
type="link"
|
||||||
onClick={handlePrev}
|
onClick={handlePrev}
|
||||||
disabled={page === 1 || isFetching || isLoading}
|
disabled={page === 1 || isFetching || isLoading}
|
||||||
@ -341,7 +348,7 @@ export default function Events({
|
|||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
className="node-events-footer-button periscope-btn ghost"
|
className="entity-events-footer-button periscope-btn ghost"
|
||||||
type="link"
|
type="link"
|
||||||
onClick={handleNext}
|
onClick={handleNext}
|
||||||
disabled={hasReachedEndOfEvents || isFetching || isLoading}
|
disabled={hasReachedEndOfEvents || isFetching || isLoading}
|
||||||
@ -1,4 +1,5 @@
|
|||||||
.pod-events-container {
|
// Events
|
||||||
|
.entity-events-container {
|
||||||
margin-top: 1rem;
|
margin-top: 1rem;
|
||||||
|
|
||||||
.filter-section {
|
.filter-section {
|
||||||
@ -19,7 +20,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.pod-events-header {
|
.entity-events-header {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
gap: 8px;
|
gap: 8px;
|
||||||
@ -29,7 +30,7 @@
|
|||||||
border: 1px solid var(--bg-slate-500);
|
border: 1px solid var(--bg-slate-500);
|
||||||
}
|
}
|
||||||
|
|
||||||
.pod-events {
|
.entity-events {
|
||||||
margin-top: 1rem;
|
margin-top: 1rem;
|
||||||
|
|
||||||
.virtuoso-list {
|
.virtuoso-list {
|
||||||
@ -87,7 +88,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.ant-table-thead > tr > th:has(.hostname-column-header) {
|
.ant-table-thead > tr > th:has(.entityname-column-header) {
|
||||||
background: var(--bg-ink-400);
|
background: var(--bg-ink-400);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -100,11 +101,11 @@
|
|||||||
border-bottom: none;
|
border-bottom: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ant-table-cell:has(.hostname-column-value) {
|
.ant-table-cell:has(.entityname-column-value) {
|
||||||
background: var(--bg-ink-400);
|
background: var(--bg-ink-400);
|
||||||
}
|
}
|
||||||
|
|
||||||
.hostname-column-value {
|
.entityname-column-value {
|
||||||
color: var(--bg-vanilla-100);
|
color: var(--bg-vanilla-100);
|
||||||
font-family: 'Geist Mono';
|
font-family: 'Geist Mono';
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
@ -190,7 +191,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.pod-events-list-container {
|
.entity-events-list-container {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
height: calc(100vh - 300px) !important;
|
height: calc(100vh - 300px) !important;
|
||||||
display: flex;
|
display: flex;
|
||||||
@ -203,7 +204,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.pod-events-list-card {
|
.entity-events-list-card {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
margin-top: 12px;
|
margin-top: 12px;
|
||||||
|
|
||||||
@ -271,7 +272,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.pod-events-footer {
|
.entity-events-footer {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: flex-end;
|
justify-content: flex-end;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
@ -297,7 +298,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.periscope-btn {
|
.periscope-btn {
|
||||||
&.ghost {
|
&.gentity {
|
||||||
border: none;
|
border: none;
|
||||||
background: transparent;
|
background: transparent;
|
||||||
}
|
}
|
||||||
@ -0,0 +1,3 @@
|
|||||||
|
import Events from './EntityEvents';
|
||||||
|
|
||||||
|
export default Events;
|
||||||
@ -1,10 +1,11 @@
|
|||||||
/* eslint-disable no-nested-ternary */
|
/* eslint-disable no-nested-ternary */
|
||||||
import './NodeLogs.styles.scss';
|
import './entityLogs.styles.scss';
|
||||||
|
|
||||||
import { Card } from 'antd';
|
import { Card } from 'antd';
|
||||||
import RawLogView from 'components/Logs/RawLogView';
|
import RawLogView from 'components/Logs/RawLogView';
|
||||||
import OverlayScrollbar from 'components/OverlayScrollbar/OverlayScrollbar';
|
import OverlayScrollbar from 'components/OverlayScrollbar/OverlayScrollbar';
|
||||||
import { DEFAULT_ENTITY_VERSION } from 'constants/app';
|
import { DEFAULT_ENTITY_VERSION } from 'constants/app';
|
||||||
|
import { K8sCategory } from 'container/InfraMonitoringK8s/constants';
|
||||||
import LogsError from 'container/LogsError/LogsError';
|
import LogsError from 'container/LogsError/LogsError';
|
||||||
import { LogsLoading } from 'container/LogsLoading/LogsLoading';
|
import { LogsLoading } from 'container/LogsLoading/LogsLoading';
|
||||||
import { FontSize } from 'container/OptionsMenu/types';
|
import { FontSize } from 'container/OptionsMenu/types';
|
||||||
@ -22,9 +23,10 @@ import {
|
|||||||
} from 'types/api/queryBuilder/queryBuilderData';
|
} from 'types/api/queryBuilder/queryBuilderData';
|
||||||
import { v4 } from 'uuid';
|
import { v4 } from 'uuid';
|
||||||
|
|
||||||
import { QUERY_KEYS } from '../constants';
|
import {
|
||||||
import { getNodeLogsQueryPayload } from './constants';
|
EntityDetailsEmptyContainer,
|
||||||
import NoLogsContainer from './NoLogsContainer';
|
getEntityEventsOrLogsQueryPayload,
|
||||||
|
} from '../utils';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
timeRange: {
|
timeRange: {
|
||||||
@ -33,12 +35,18 @@ interface Props {
|
|||||||
};
|
};
|
||||||
handleChangeLogFilters: (filters: IBuilderQuery['filters']) => void;
|
handleChangeLogFilters: (filters: IBuilderQuery['filters']) => void;
|
||||||
filters: IBuilderQuery['filters'];
|
filters: IBuilderQuery['filters'];
|
||||||
|
queryKey: string;
|
||||||
|
category: K8sCategory;
|
||||||
|
queryKeyFilters: Array<string>;
|
||||||
}
|
}
|
||||||
|
|
||||||
function PodLogs({
|
function EntityLogs({
|
||||||
timeRange,
|
timeRange,
|
||||||
handleChangeLogFilters,
|
handleChangeLogFilters,
|
||||||
filters,
|
filters,
|
||||||
|
queryKey,
|
||||||
|
category,
|
||||||
|
queryKeyFilters,
|
||||||
}: Props): JSX.Element {
|
}: Props): JSX.Element {
|
||||||
const [logs, setLogs] = useState<ILog[]>([]);
|
const [logs, setLogs] = useState<ILog[]>([]);
|
||||||
const [hasReachedEndOfLogs, setHasReachedEndOfLogs] = useState(false);
|
const [hasReachedEndOfLogs, setHasReachedEndOfLogs] = useState(false);
|
||||||
@ -48,10 +56,7 @@ function PodLogs({
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const newRestFilters = filters.items.filter(
|
const newRestFilters = filters.items.filter(
|
||||||
(item) =>
|
(item) =>
|
||||||
item.key?.key !== 'id' &&
|
item.key?.key !== 'id' && !queryKeyFilters.includes(item.key?.key ?? ''),
|
||||||
![QUERY_KEYS.K8S_NODE_NAME, QUERY_KEYS.K8S_CLUSTER_NAME].includes(
|
|
||||||
item.key?.key ?? '',
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
const areFiltersSame = isEqual(restFilters, newRestFilters);
|
const areFiltersSame = isEqual(restFilters, newRestFilters);
|
||||||
@ -65,7 +70,7 @@ function PodLogs({
|
|||||||
}, [filters]);
|
}, [filters]);
|
||||||
|
|
||||||
const queryPayload = useMemo(() => {
|
const queryPayload = useMemo(() => {
|
||||||
const basePayload = getNodeLogsQueryPayload(
|
const basePayload = getEntityEventsOrLogsQueryPayload(
|
||||||
timeRange.startTime,
|
timeRange.startTime,
|
||||||
timeRange.endTime,
|
timeRange.endTime,
|
||||||
filters,
|
filters,
|
||||||
@ -82,7 +87,7 @@ function PodLogs({
|
|||||||
const [isPaginating, setIsPaginating] = useState(false);
|
const [isPaginating, setIsPaginating] = useState(false);
|
||||||
|
|
||||||
const { data, isLoading, isFetching, isError } = useQuery({
|
const { data, isLoading, isFetching, isError } = useQuery({
|
||||||
queryKey: ['nodeLogs', timeRange.startTime, timeRange.endTime, filters],
|
queryKey: [queryKey, timeRange.startTime, timeRange.endTime, filters],
|
||||||
queryFn: () => GetMetricQueryRange(queryPayload, DEFAULT_ENTITY_VERSION),
|
queryFn: () => GetMetricQueryRange(queryPayload, DEFAULT_ENTITY_VERSION),
|
||||||
enabled: !!queryPayload,
|
enabled: !!queryPayload,
|
||||||
keepPreviousData: isPaginating,
|
keepPreviousData: isPaginating,
|
||||||
@ -127,6 +132,18 @@ function PodLogs({
|
|||||||
data={logToRender}
|
data={logToRender}
|
||||||
linesPerRow={5}
|
linesPerRow={5}
|
||||||
fontSize={FontSize.MEDIUM}
|
fontSize={FontSize.MEDIUM}
|
||||||
|
selectedFields={[
|
||||||
|
{
|
||||||
|
dataType: 'string',
|
||||||
|
type: '',
|
||||||
|
name: 'body',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
dataType: 'string',
|
||||||
|
type: '',
|
||||||
|
name: 'timestamp',
|
||||||
|
},
|
||||||
|
]}
|
||||||
/>
|
/>
|
||||||
),
|
),
|
||||||
[],
|
[],
|
||||||
@ -181,11 +198,11 @@ function PodLogs({
|
|||||||
|
|
||||||
const renderContent = useMemo(
|
const renderContent = useMemo(
|
||||||
() => (
|
() => (
|
||||||
<Card bordered={false} className="node-logs-list-card">
|
<Card bordered={false} className="entity-logs-list-card">
|
||||||
<OverlayScrollbar isVirtuoso>
|
<OverlayScrollbar isVirtuoso>
|
||||||
<Virtuoso
|
<Virtuoso
|
||||||
className="node-logs-virtuoso"
|
className="entity-logs-virtuoso"
|
||||||
key="node-logs-virtuoso"
|
key="entity-logs-virtuoso"
|
||||||
data={logs}
|
data={logs}
|
||||||
endReached={loadMoreLogs}
|
endReached={loadMoreLogs}
|
||||||
totalCount={logs.length}
|
totalCount={logs.length}
|
||||||
@ -202,15 +219,17 @@ function PodLogs({
|
|||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="node-logs">
|
<div className="entity-logs">
|
||||||
{isLoading && <LogsLoading />}
|
{isLoading && <LogsLoading />}
|
||||||
{!isLoading && !isError && logs.length === 0 && <NoLogsContainer />}
|
{!isLoading && !isError && logs.length === 0 && (
|
||||||
|
<EntityDetailsEmptyContainer category={category} view="logs" />
|
||||||
|
)}
|
||||||
{isError && !isLoading && <LogsError />}
|
{isError && !isLoading && <LogsError />}
|
||||||
{!isLoading && !isError && logs.length > 0 && (
|
{!isLoading && !isError && logs.length > 0 && (
|
||||||
<div className="node-logs-list-container">{renderContent}</div>
|
<div className="entity-logs-list-container">{renderContent}</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default PodLogs;
|
export default EntityLogs;
|
||||||
@ -1,5 +1,6 @@
|
|||||||
import './NodeLogs.styles.scss';
|
import './entityLogs.styles.scss';
|
||||||
|
|
||||||
|
import { K8sCategory } from 'container/InfraMonitoringK8s/constants';
|
||||||
import QueryBuilderSearch from 'container/QueryBuilder/filters/QueryBuilderSearch';
|
import QueryBuilderSearch from 'container/QueryBuilder/filters/QueryBuilderSearch';
|
||||||
import DateTimeSelectionV2 from 'container/TopNav/DateTimeSelectionV2';
|
import DateTimeSelectionV2 from 'container/TopNav/DateTimeSelectionV2';
|
||||||
import {
|
import {
|
||||||
@ -11,7 +12,7 @@ import { useMemo } from 'react';
|
|||||||
import { IBuilderQuery } from 'types/api/queryBuilder/queryBuilderData';
|
import { IBuilderQuery } from 'types/api/queryBuilder/queryBuilderData';
|
||||||
import { DataSource } from 'types/common/queryBuilder';
|
import { DataSource } from 'types/common/queryBuilder';
|
||||||
|
|
||||||
import NodeLogs from './NodeLogs';
|
import EntityLogs from './EntityLogs';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
timeRange: {
|
timeRange: {
|
||||||
@ -26,15 +27,21 @@ interface Props {
|
|||||||
handleChangeLogFilters: (value: IBuilderQuery['filters']) => void;
|
handleChangeLogFilters: (value: IBuilderQuery['filters']) => void;
|
||||||
logFilters: IBuilderQuery['filters'];
|
logFilters: IBuilderQuery['filters'];
|
||||||
selectedInterval: Time;
|
selectedInterval: Time;
|
||||||
|
queryKey: string;
|
||||||
|
category: K8sCategory;
|
||||||
|
queryKeyFilters: Array<string>;
|
||||||
}
|
}
|
||||||
|
|
||||||
function NodeLogsDetailedView({
|
function EntityLogsDetailedView({
|
||||||
timeRange,
|
timeRange,
|
||||||
isModalTimeSelection,
|
isModalTimeSelection,
|
||||||
handleTimeChange,
|
handleTimeChange,
|
||||||
handleChangeLogFilters,
|
handleChangeLogFilters,
|
||||||
logFilters,
|
logFilters,
|
||||||
selectedInterval,
|
selectedInterval,
|
||||||
|
queryKey,
|
||||||
|
category,
|
||||||
|
queryKeyFilters,
|
||||||
}: Props): JSX.Element {
|
}: Props): JSX.Element {
|
||||||
const { currentQuery } = useQueryBuilder();
|
const { currentQuery } = useQueryBuilder();
|
||||||
const updatedCurrentQuery = useMemo(
|
const updatedCurrentQuery = useMemo(
|
||||||
@ -64,8 +71,8 @@ function NodeLogsDetailedView({
|
|||||||
const query = updatedCurrentQuery?.builder?.queryData[0] || null;
|
const query = updatedCurrentQuery?.builder?.queryData[0] || null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="node-logs-container">
|
<div className="entity-logs-container">
|
||||||
<div className="node-logs-header">
|
<div className="entity-logs-header">
|
||||||
<div className="filter-section">
|
<div className="filter-section">
|
||||||
{query && (
|
{query && (
|
||||||
<QueryBuilderSearch
|
<QueryBuilderSearch
|
||||||
@ -87,13 +94,16 @@ function NodeLogsDetailedView({
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<NodeLogs
|
<EntityLogs
|
||||||
timeRange={timeRange}
|
timeRange={timeRange}
|
||||||
handleChangeLogFilters={handleChangeLogFilters}
|
handleChangeLogFilters={handleChangeLogFilters}
|
||||||
filters={logFilters}
|
filters={logFilters}
|
||||||
|
queryKey={queryKey}
|
||||||
|
category={category}
|
||||||
|
queryKeyFilters={queryKeyFilters}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default NodeLogsDetailedView;
|
export default EntityLogsDetailedView;
|
||||||
@ -1,4 +1,4 @@
|
|||||||
.node-logs-container {
|
.entity-logs-container {
|
||||||
margin-top: 1rem;
|
margin-top: 1rem;
|
||||||
|
|
||||||
.filter-section {
|
.filter-section {
|
||||||
@ -19,7 +19,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.node-logs-header {
|
.entity-logs-header {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
gap: 8px;
|
gap: 8px;
|
||||||
@ -29,7 +29,7 @@
|
|||||||
border: 1px solid var(--bg-slate-500);
|
border: 1px solid var(--bg-slate-500);
|
||||||
}
|
}
|
||||||
|
|
||||||
.node-logs {
|
.entity-logs {
|
||||||
margin-top: 1rem;
|
margin-top: 1rem;
|
||||||
|
|
||||||
.virtuoso-list {
|
.virtuoso-list {
|
||||||
@ -64,7 +64,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.node-logs-list-container {
|
.entity-logs-list-container {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
height: calc(100vh - 272px) !important;
|
height: calc(100vh - 272px) !important;
|
||||||
display: flex;
|
display: flex;
|
||||||
@ -77,7 +77,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.node-logs-list-card {
|
.entity-logs-list-card {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
margin-top: 12px;
|
margin-top: 12px;
|
||||||
|
|
||||||
@ -0,0 +1,3 @@
|
|||||||
|
import EntityLogs from './EntityLogsDetailedView';
|
||||||
|
|
||||||
|
export default EntityLogs;
|
||||||
@ -1,10 +1,15 @@
|
|||||||
import './Metrics.styles.scss';
|
import './entityMetrics.styles.scss';
|
||||||
|
|
||||||
import { Card, Col, Row, Skeleton, Typography } from 'antd';
|
import { Card, Col, Row, Skeleton, Typography } from 'antd';
|
||||||
import { K8sPodsData } from 'api/infraMonitoring/getK8sPodsList';
|
|
||||||
import cx from 'classnames';
|
import cx from 'classnames';
|
||||||
import Uplot from 'components/Uplot';
|
import Uplot from 'components/Uplot';
|
||||||
import { ENTITY_VERSION_V4 } from 'constants/app';
|
import { ENTITY_VERSION_V4 } from 'constants/app';
|
||||||
|
import { PANEL_TYPES } from 'constants/queryBuilder';
|
||||||
|
import {
|
||||||
|
getMetricsTableData,
|
||||||
|
MetricsTable,
|
||||||
|
} from 'container/InfraMonitoringK8s/commonUtils';
|
||||||
|
import { K8sCategory } from 'container/InfraMonitoringK8s/constants';
|
||||||
import DateTimeSelectionV2 from 'container/TopNav/DateTimeSelectionV2';
|
import DateTimeSelectionV2 from 'container/TopNav/DateTimeSelectionV2';
|
||||||
import {
|
import {
|
||||||
CustomTimeType,
|
CustomTimeType,
|
||||||
@ -12,17 +17,19 @@ import {
|
|||||||
} from 'container/TopNav/DateTimeSelectionV2/config';
|
} from 'container/TopNav/DateTimeSelectionV2/config';
|
||||||
import { useIsDarkMode } from 'hooks/useDarkMode';
|
import { useIsDarkMode } from 'hooks/useDarkMode';
|
||||||
import { useResizeObserver } from 'hooks/useDimensions';
|
import { useResizeObserver } from 'hooks/useDimensions';
|
||||||
import { GetMetricQueryRange } from 'lib/dashboard/getQueryResults';
|
import {
|
||||||
|
GetMetricQueryRange,
|
||||||
|
GetQueryResultsProps,
|
||||||
|
} from 'lib/dashboard/getQueryResults';
|
||||||
import { getUPlotChartOptions } from 'lib/uPlotLib/getUplotChartOptions';
|
import { getUPlotChartOptions } from 'lib/uPlotLib/getUplotChartOptions';
|
||||||
import { getUPlotChartData } from 'lib/uPlotLib/utils/getUplotChartData';
|
import { getUPlotChartData } from 'lib/uPlotLib/utils/getUplotChartData';
|
||||||
import { useMemo, useRef } from 'react';
|
import { useMemo, useRef } from 'react';
|
||||||
import { useQueries, UseQueryResult } from 'react-query';
|
import { useQueries, UseQueryResult } from 'react-query';
|
||||||
import { SuccessResponse } from 'types/api';
|
import { SuccessResponse } from 'types/api';
|
||||||
import { MetricRangePayloadProps } from 'types/api/metrics/getQueryRange';
|
import { MetricRangePayloadProps } from 'types/api/metrics/getQueryRange';
|
||||||
|
import { Options } from 'uplot';
|
||||||
|
|
||||||
import { getPodQueryPayload, podWidgetInfo } from '../../constants';
|
interface EntityMetricsProps<T> {
|
||||||
|
|
||||||
interface MetricsTabProps {
|
|
||||||
timeRange: {
|
timeRange: {
|
||||||
startTime: number;
|
startTime: number;
|
||||||
endTime: number;
|
endTime: number;
|
||||||
@ -33,24 +40,39 @@ interface MetricsTabProps {
|
|||||||
dateTimeRange?: [number, number],
|
dateTimeRange?: [number, number],
|
||||||
) => void;
|
) => void;
|
||||||
selectedInterval: Time;
|
selectedInterval: Time;
|
||||||
pod: K8sPodsData;
|
entity: T;
|
||||||
|
entityWidgetInfo: {
|
||||||
|
title: string;
|
||||||
|
yAxisUnit: string;
|
||||||
|
}[];
|
||||||
|
getEntityQueryPayload: (
|
||||||
|
node: T,
|
||||||
|
start: number,
|
||||||
|
end: number,
|
||||||
|
) => GetQueryResultsProps[];
|
||||||
|
queryKey: string;
|
||||||
|
category: K8sCategory;
|
||||||
}
|
}
|
||||||
|
|
||||||
function Metrics({
|
function EntityMetrics<T>({
|
||||||
selectedInterval,
|
selectedInterval,
|
||||||
pod,
|
entity,
|
||||||
timeRange,
|
timeRange,
|
||||||
handleTimeChange,
|
handleTimeChange,
|
||||||
isModalTimeSelection,
|
isModalTimeSelection,
|
||||||
}: MetricsTabProps): JSX.Element {
|
entityWidgetInfo,
|
||||||
|
getEntityQueryPayload,
|
||||||
|
queryKey,
|
||||||
|
category,
|
||||||
|
}: EntityMetricsProps<T>): JSX.Element {
|
||||||
const queryPayloads = useMemo(
|
const queryPayloads = useMemo(
|
||||||
() => getPodQueryPayload(pod, timeRange.startTime, timeRange.endTime),
|
() => getEntityQueryPayload(entity, timeRange.startTime, timeRange.endTime),
|
||||||
[pod, timeRange.startTime, timeRange.endTime],
|
[getEntityQueryPayload, entity, timeRange.startTime, timeRange.endTime],
|
||||||
);
|
);
|
||||||
|
|
||||||
const queries = useQueries(
|
const queries = useQueries(
|
||||||
queryPayloads.map((payload) => ({
|
queryPayloads.map((payload) => ({
|
||||||
queryKey: ['pod-metrics', payload, ENTITY_VERSION_V4, 'POD'],
|
queryKey: [queryKey, payload, ENTITY_VERSION_V4, category],
|
||||||
queryFn: (): Promise<SuccessResponse<MetricRangePayloadProps>> =>
|
queryFn: (): Promise<SuccessResponse<MetricRangePayloadProps>> =>
|
||||||
GetMetricQueryRange(payload, ENTITY_VERSION_V4),
|
GetMetricQueryRange(payload, ENTITY_VERSION_V4),
|
||||||
enabled: !!payload,
|
enabled: !!payload,
|
||||||
@ -62,25 +84,42 @@ function Metrics({
|
|||||||
const dimensions = useResizeObserver(graphRef);
|
const dimensions = useResizeObserver(graphRef);
|
||||||
|
|
||||||
const chartData = useMemo(
|
const chartData = useMemo(
|
||||||
() => queries.map(({ data }) => getUPlotChartData(data?.payload)),
|
() =>
|
||||||
|
queries.map(({ data }) => {
|
||||||
|
const panelType = (data?.params as any)?.compositeQuery?.panelType;
|
||||||
|
return panelType === PANEL_TYPES.TABLE
|
||||||
|
? getMetricsTableData(data)
|
||||||
|
: getUPlotChartData(data?.payload);
|
||||||
|
}),
|
||||||
[queries],
|
[queries],
|
||||||
);
|
);
|
||||||
|
|
||||||
const options = useMemo(
|
const options = useMemo(
|
||||||
() =>
|
() =>
|
||||||
queries.map(({ data }, idx) =>
|
queries.map(({ data }, idx) => {
|
||||||
getUPlotChartOptions({
|
const panelType = (data?.params as any)?.compositeQuery?.panelType;
|
||||||
|
if (panelType === PANEL_TYPES.TABLE) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return getUPlotChartOptions({
|
||||||
apiResponse: data?.payload,
|
apiResponse: data?.payload,
|
||||||
isDarkMode,
|
isDarkMode,
|
||||||
dimensions,
|
dimensions,
|
||||||
yAxisUnit: podWidgetInfo[idx].yAxisUnit,
|
yAxisUnit: entityWidgetInfo[idx].yAxisUnit,
|
||||||
softMax: null,
|
softMax: null,
|
||||||
softMin: null,
|
softMin: null,
|
||||||
minTimeScale: timeRange.startTime,
|
minTimeScale: timeRange.startTime,
|
||||||
maxTimeScale: timeRange.endTime,
|
maxTimeScale: timeRange.endTime,
|
||||||
}),
|
});
|
||||||
),
|
}),
|
||||||
[queries, isDarkMode, dimensions, timeRange.startTime, timeRange.endTime],
|
[
|
||||||
|
queries,
|
||||||
|
isDarkMode,
|
||||||
|
dimensions,
|
||||||
|
entityWidgetInfo,
|
||||||
|
timeRange.startTime,
|
||||||
|
timeRange.endTime,
|
||||||
|
],
|
||||||
);
|
);
|
||||||
|
|
||||||
const renderCardContent = (
|
const renderCardContent = (
|
||||||
@ -96,6 +135,9 @@ function Metrics({
|
|||||||
(query.error as Error)?.message || 'Something went wrong';
|
(query.error as Error)?.message || 'Something went wrong';
|
||||||
return <div>{errorMessage}</div>;
|
return <div>{errorMessage}</div>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const { panelType } = (query.data?.params as any).compositeQuery;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={cx('chart-container', {
|
className={cx('chart-container', {
|
||||||
@ -103,7 +145,14 @@ function Metrics({
|
|||||||
!query.isLoading && !query?.data?.payload?.data?.result?.length,
|
!query.isLoading && !query?.data?.payload?.data?.result?.length,
|
||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
<Uplot options={options[idx]} data={chartData[idx]} />
|
{panelType === PANEL_TYPES.TABLE ? (
|
||||||
|
<MetricsTable
|
||||||
|
rows={chartData[idx][0].rows}
|
||||||
|
columns={chartData[idx][0].columns}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<Uplot options={options[idx] as Options} data={chartData[idx]} />
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@ -123,11 +172,11 @@ function Metrics({
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<Row gutter={24} className="host-metrics-container">
|
<Row gutter={24} className="entity-metrics-container">
|
||||||
{queries.map((query, idx) => (
|
{queries.map((query, idx) => (
|
||||||
<Col span={12} key={podWidgetInfo[idx].title}>
|
<Col span={12} key={entityWidgetInfo[idx].title}>
|
||||||
<Typography.Text>{podWidgetInfo[idx].title}</Typography.Text>
|
<Typography.Text>{entityWidgetInfo[idx].title}</Typography.Text>
|
||||||
<Card bordered className="host-metrics-card" ref={graphRef}>
|
<Card bordered className="entity-metrics-card" ref={graphRef}>
|
||||||
{renderCardContent(query, idx)}
|
{renderCardContent(query, idx)}
|
||||||
</Card>
|
</Card>
|
||||||
</Col>
|
</Col>
|
||||||
@ -137,4 +186,4 @@ function Metrics({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default Metrics;
|
export default EntityMetrics;
|
||||||
@ -1,11 +1,5 @@
|
|||||||
.empty-container {
|
// Metrics
|
||||||
display: flex;
|
.entity-metrics-container {
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
height: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.host-metrics-container {
|
|
||||||
margin-top: 1rem;
|
margin-top: 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -20,7 +14,7 @@
|
|||||||
border: 1px solid var(--bg-slate-500);
|
border: 1px solid var(--bg-slate-500);
|
||||||
}
|
}
|
||||||
|
|
||||||
.host-metrics-card {
|
.entity-metrics-card {
|
||||||
margin: 8px 0 1rem 0;
|
margin: 8px 0 1rem 0;
|
||||||
height: 300px;
|
height: 300px;
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
@ -0,0 +1,3 @@
|
|||||||
|
import EntityMetrics from './EntityMetrics';
|
||||||
|
|
||||||
|
export default EntityMetrics;
|
||||||
@ -1,4 +1,4 @@
|
|||||||
import './NodeTraces.styles.scss';
|
import './entityTraces.styles.scss';
|
||||||
|
|
||||||
import { getListColumns } from 'components/HostMetricsDetail/HostMetricTraces/utils';
|
import { getListColumns } from 'components/HostMetricsDetail/HostMetricTraces/utils';
|
||||||
import { ResizeTable } from 'components/ResizeTable';
|
import { ResizeTable } from 'components/ResizeTable';
|
||||||
@ -25,7 +25,10 @@ import { useQuery } from 'react-query';
|
|||||||
import { IBuilderQuery } from 'types/api/queryBuilder/queryBuilderData';
|
import { IBuilderQuery } from 'types/api/queryBuilder/queryBuilderData';
|
||||||
import { DataSource } from 'types/common/queryBuilder';
|
import { DataSource } from 'types/common/queryBuilder';
|
||||||
|
|
||||||
import { getNodeTracesQueryPayload, selectedColumns } from './constants';
|
import {
|
||||||
|
getEntityTracesQueryPayload,
|
||||||
|
selectedEntityTracesColumns,
|
||||||
|
} from '../utils';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
timeRange: {
|
timeRange: {
|
||||||
@ -40,15 +43,17 @@ interface Props {
|
|||||||
handleChangeTracesFilters: (value: IBuilderQuery['filters']) => void;
|
handleChangeTracesFilters: (value: IBuilderQuery['filters']) => void;
|
||||||
tracesFilters: IBuilderQuery['filters'];
|
tracesFilters: IBuilderQuery['filters'];
|
||||||
selectedInterval: Time;
|
selectedInterval: Time;
|
||||||
|
queryKey: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
function NodeTraces({
|
function EntityTraces({
|
||||||
timeRange,
|
timeRange,
|
||||||
isModalTimeSelection,
|
isModalTimeSelection,
|
||||||
handleTimeChange,
|
handleTimeChange,
|
||||||
handleChangeTracesFilters,
|
handleChangeTracesFilters,
|
||||||
tracesFilters,
|
tracesFilters,
|
||||||
selectedInterval,
|
selectedInterval,
|
||||||
|
queryKey,
|
||||||
}: Props): JSX.Element {
|
}: Props): JSX.Element {
|
||||||
const [traces, setTraces] = useState<any[]>([]);
|
const [traces, setTraces] = useState<any[]>([]);
|
||||||
const [offset] = useState<number>(0);
|
const [offset] = useState<number>(0);
|
||||||
@ -86,7 +91,7 @@ function NodeTraces({
|
|||||||
|
|
||||||
const queryPayload = useMemo(
|
const queryPayload = useMemo(
|
||||||
() =>
|
() =>
|
||||||
getNodeTracesQueryPayload(
|
getEntityTracesQueryPayload(
|
||||||
timeRange.startTime,
|
timeRange.startTime,
|
||||||
timeRange.endTime,
|
timeRange.endTime,
|
||||||
paginationQueryData?.offset || offset,
|
paginationQueryData?.offset || offset,
|
||||||
@ -103,7 +108,7 @@ function NodeTraces({
|
|||||||
|
|
||||||
const { data, isLoading, isFetching, isError } = useQuery({
|
const { data, isLoading, isFetching, isError } = useQuery({
|
||||||
queryKey: [
|
queryKey: [
|
||||||
'hostMetricTraces',
|
queryKey,
|
||||||
timeRange.startTime,
|
timeRange.startTime,
|
||||||
timeRange.endTime,
|
timeRange.endTime,
|
||||||
offset,
|
offset,
|
||||||
@ -115,7 +120,7 @@ function NodeTraces({
|
|||||||
enabled: !!queryPayload,
|
enabled: !!queryPayload,
|
||||||
});
|
});
|
||||||
|
|
||||||
const traceListColumns = getListColumns(selectedColumns);
|
const traceListColumns = getListColumns(selectedEntityTracesColumns);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (data?.payload?.data?.newResult?.data?.result) {
|
if (data?.payload?.data?.newResult?.data?.result) {
|
||||||
@ -138,8 +143,8 @@ function NodeTraces({
|
|||||||
data?.payload?.data?.newResult?.data?.result?.[0]?.list?.length || 0;
|
data?.payload?.data?.newResult?.data?.result?.[0]?.list?.length || 0;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="node-metric-traces">
|
<div className="entity-metric-traces">
|
||||||
<div className="node-metric-traces-header">
|
<div className="entity-metric-traces-header">
|
||||||
<div className="filter-section">
|
<div className="filter-section">
|
||||||
{query && (
|
{query && (
|
||||||
<QueryBuilderSearch
|
<QueryBuilderSearch
|
||||||
@ -175,7 +180,7 @@ function NodeTraces({
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
{!isError && traces.length > 0 && (
|
{!isError && traces.length > 0 && (
|
||||||
<div className="node-traces-table">
|
<div className="entity-traces-table">
|
||||||
<TraceExplorerControls
|
<TraceExplorerControls
|
||||||
isLoading={isFetching}
|
isLoading={isFetching}
|
||||||
totalCount={totalCount}
|
totalCount={totalCount}
|
||||||
@ -196,4 +201,4 @@ function NodeTraces({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default NodeTraces;
|
export default EntityTraces;
|
||||||
@ -1,7 +1,8 @@
|
|||||||
.pod-metric-traces {
|
// Traces
|
||||||
|
.entity-metric-traces {
|
||||||
margin-top: 1rem;
|
margin-top: 1rem;
|
||||||
|
|
||||||
.pod-metric-traces-header {
|
.entity-metric-traces-header {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
margin-bottom: 1rem;
|
margin-bottom: 1rem;
|
||||||
@ -30,7 +31,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.pod-metric-traces-table {
|
.entity-metric-traces-table {
|
||||||
.ant-table-content {
|
.ant-table-content {
|
||||||
overflow: hidden !important;
|
overflow: hidden !important;
|
||||||
}
|
}
|
||||||
@ -62,7 +63,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.ant-table-thead > tr > th:has(.hostname-column-header) {
|
.ant-table-thead > tr > th:has(.entityname-column-header) {
|
||||||
background: var(--bg-ink-400);
|
background: var(--bg-ink-400);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -74,11 +75,11 @@
|
|||||||
background: rgba(171, 189, 255, 0.01);
|
background: rgba(171, 189, 255, 0.01);
|
||||||
}
|
}
|
||||||
|
|
||||||
.ant-table-cell:has(.hostname-column-value) {
|
.ant-table-cell:has(.entityname-column-value) {
|
||||||
background: var(--bg-ink-400);
|
background: var(--bg-ink-400);
|
||||||
}
|
}
|
||||||
|
|
||||||
.hostname-column-value {
|
.entityname-column-value {
|
||||||
color: var(--bg-vanilla-100);
|
color: var(--bg-vanilla-100);
|
||||||
font-family: 'Geist Mono';
|
font-family: 'Geist Mono';
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
@ -145,7 +146,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.lightMode {
|
.lightMode {
|
||||||
.host-metric-traces-header {
|
.entity-metric-traces-header {
|
||||||
.filter-section {
|
.filter-section {
|
||||||
border-top: 1px solid var(--bg-vanilla-300);
|
border-top: 1px solid var(--bg-vanilla-300);
|
||||||
border-bottom: 1px solid var(--bg-vanilla-300);
|
border-bottom: 1px solid var(--bg-vanilla-300);
|
||||||
@ -158,7 +159,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.host-metric-traces-table {
|
.entity-metric-traces-table {
|
||||||
.ant-table {
|
.ant-table {
|
||||||
border-radius: 3px;
|
border-radius: 3px;
|
||||||
border: 1px solid var(--bg-vanilla-300);
|
border: 1px solid var(--bg-vanilla-300);
|
||||||
@ -168,7 +169,7 @@
|
|||||||
color: var(--text-ink-300);
|
color: var(--text-ink-300);
|
||||||
}
|
}
|
||||||
|
|
||||||
.ant-table-thead > tr > th:has(.hostname-column-header) {
|
.ant-table-thead > tr > th:has(.entityname-column-header) {
|
||||||
background: var(--bg-vanilla-100);
|
background: var(--bg-vanilla-100);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -177,11 +178,11 @@
|
|||||||
color: var(--bg-ink-500);
|
color: var(--bg-ink-500);
|
||||||
}
|
}
|
||||||
|
|
||||||
.ant-table-cell:has(.hostname-column-value) {
|
.ant-table-cell:has(.entityname-column-value) {
|
||||||
background: var(--bg-vanilla-100);
|
background: var(--bg-vanilla-100);
|
||||||
}
|
}
|
||||||
|
|
||||||
.hostname-column-value {
|
.entityname-column-value {
|
||||||
color: var(--bg-ink-300);
|
color: var(--bg-ink-300);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -0,0 +1,3 @@
|
|||||||
|
import EntityTraces from './EntityTraces';
|
||||||
|
|
||||||
|
export default EntityTraces;
|
||||||
@ -0,0 +1,576 @@
|
|||||||
|
// Entity Details
|
||||||
|
.entity-detail-drawer {
|
||||||
|
border-left: 1px solid var(--bg-slate-500);
|
||||||
|
background: var(--bg-ink-400);
|
||||||
|
box-shadow: -4px 10px 16px 2px rgba(0, 0, 0, 0.2);
|
||||||
|
|
||||||
|
.ant-drawer-header {
|
||||||
|
padding: 8px 16px;
|
||||||
|
border-bottom: none;
|
||||||
|
|
||||||
|
align-items: stretch;
|
||||||
|
|
||||||
|
border-bottom: 1px solid var(--bg-slate-500);
|
||||||
|
background: var(--bg-ink-400);
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-drawer-close {
|
||||||
|
margin-inline-end: 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-drawer-body {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
padding: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title {
|
||||||
|
color: var(--text-vanilla-400);
|
||||||
|
font-family: 'Geist Mono';
|
||||||
|
font-size: 14px;
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 500;
|
||||||
|
line-height: 20px; /* 142.857% */
|
||||||
|
letter-spacing: -0.07px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.radio-button {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
padding-top: var(--padding-1);
|
||||||
|
border: 1px solid var(--bg-slate-400);
|
||||||
|
background: var(--bg-ink-300);
|
||||||
|
box-shadow: 0px 0px 8px 0px rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.entity-detail-drawer__entity {
|
||||||
|
.entity-details-grid {
|
||||||
|
.labels-row,
|
||||||
|
.values-row {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr 1.5fr 1.5fr 1.5fr;
|
||||||
|
gap: 30px;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.labels-row {
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.entity-details-metadata-label {
|
||||||
|
color: var(--text-vanilla-400);
|
||||||
|
font-family: Inter;
|
||||||
|
font-size: 11px;
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 500;
|
||||||
|
line-height: 18px; /* 163.636% */
|
||||||
|
letter-spacing: 0.44px;
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
|
||||||
|
.entity-details-metadata-value {
|
||||||
|
color: var(--text-vanilla-400);
|
||||||
|
font-family: 'Geist Mono';
|
||||||
|
font-size: 12px;
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 500;
|
||||||
|
line-height: 20px; /* 142.857% */
|
||||||
|
letter-spacing: -0.07px;
|
||||||
|
|
||||||
|
width: 100%;
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-tag {
|
||||||
|
margin: 0;
|
||||||
|
|
||||||
|
&.active {
|
||||||
|
color: var(--success-500);
|
||||||
|
background: var(--success-100);
|
||||||
|
border-color: var(--success-500);
|
||||||
|
}
|
||||||
|
|
||||||
|
&.inactive {
|
||||||
|
color: var(--error-500);
|
||||||
|
background: var(--error-100);
|
||||||
|
border-color: var(--error-500);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.progress-container {
|
||||||
|
width: 158px;
|
||||||
|
.ant-progress {
|
||||||
|
margin: 0;
|
||||||
|
|
||||||
|
.ant-progress-text {
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-card {
|
||||||
|
&.ant-card-bordered {
|
||||||
|
border: 1px solid var(--bg-slate-500) !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.tabs-and-search {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
margin: 16px 0;
|
||||||
|
|
||||||
|
.action-btn {
|
||||||
|
border-radius: 2px;
|
||||||
|
border: 1px solid var(--bg-slate-400);
|
||||||
|
background: var(--bg-ink-300);
|
||||||
|
box-shadow: 0px 0px 8px 0px rgba(0, 0, 0, 0.1);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.views-tabs-container {
|
||||||
|
margin-top: 1.5rem;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
.views-tabs {
|
||||||
|
color: var(--text-vanilla-400);
|
||||||
|
|
||||||
|
.view-title {
|
||||||
|
display: flex;
|
||||||
|
gap: var(--margin-2);
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
font-size: var(--font-size-xs);
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: var(--font-weight-normal);
|
||||||
|
}
|
||||||
|
|
||||||
|
.tab {
|
||||||
|
border: 1px solid var(--bg-slate-400);
|
||||||
|
width: 114px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tab::before {
|
||||||
|
background: var(--bg-slate-400);
|
||||||
|
}
|
||||||
|
|
||||||
|
.selected_view {
|
||||||
|
background: var(--bg-slate-300);
|
||||||
|
color: var(--text-vanilla-100);
|
||||||
|
border: 1px solid var(--bg-slate-400);
|
||||||
|
}
|
||||||
|
|
||||||
|
.selected_view::before {
|
||||||
|
background: var(--bg-slate-400);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.compass-button {
|
||||||
|
width: 30px;
|
||||||
|
height: 30px;
|
||||||
|
|
||||||
|
border-radius: 2px;
|
||||||
|
border: 1px solid var(--bg-slate-400);
|
||||||
|
background: var(--bg-ink-300);
|
||||||
|
box-shadow: 0px 0px 8px 0px rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.ant-drawer-close {
|
||||||
|
padding: 0px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.lightMode {
|
||||||
|
.ant-drawer-header {
|
||||||
|
border-bottom: 1px solid var(--bg-vanilla-400);
|
||||||
|
background: var(--bg-vanilla-100);
|
||||||
|
}
|
||||||
|
|
||||||
|
.entity-detail-drawer {
|
||||||
|
.title {
|
||||||
|
color: var(--text-ink-300);
|
||||||
|
}
|
||||||
|
|
||||||
|
.entity-detail-drawer__entity {
|
||||||
|
.ant-typography {
|
||||||
|
color: var(--text-ink-300);
|
||||||
|
background: transparent;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.radio-button {
|
||||||
|
border: 1px solid var(--bg-vanilla-400);
|
||||||
|
background: var(--bg-vanilla-100);
|
||||||
|
color: var(--text-ink-300);
|
||||||
|
}
|
||||||
|
|
||||||
|
.views-tabs {
|
||||||
|
.tab {
|
||||||
|
background: var(--bg-vanilla-100);
|
||||||
|
}
|
||||||
|
|
||||||
|
.selected_view {
|
||||||
|
background: var(--bg-vanilla-300);
|
||||||
|
border: 1px solid var(--bg-slate-300);
|
||||||
|
color: var(--text-ink-400);
|
||||||
|
}
|
||||||
|
|
||||||
|
.selected_view::before {
|
||||||
|
background: var(--bg-vanilla-300);
|
||||||
|
border-left: 1px solid var(--bg-slate-300);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.compass-button {
|
||||||
|
border: 1px solid var(--bg-vanilla-300);
|
||||||
|
background: var(--bg-vanilla-100);
|
||||||
|
box-shadow: 0px 0px 8px 0px rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.tabs-and-search {
|
||||||
|
.action-btn {
|
||||||
|
border: 1px solid var(--bg-vanilla-400);
|
||||||
|
background: var(--bg-vanilla-100);
|
||||||
|
color: var(--text-ink-300);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.entity-metric-traces {
|
||||||
|
margin-top: 1rem;
|
||||||
|
|
||||||
|
.entity-metric-traces-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
|
||||||
|
gap: 8px;
|
||||||
|
padding: 12px;
|
||||||
|
border-radius: 3px;
|
||||||
|
border: 1px solid var(--bg-slate-500);
|
||||||
|
|
||||||
|
.filter-section {
|
||||||
|
flex: 1;
|
||||||
|
|
||||||
|
.ant-select-selector {
|
||||||
|
border-radius: 2px;
|
||||||
|
border: 1px solid var(--bg-slate-400) !important;
|
||||||
|
background-color: var(--bg-ink-300) !important;
|
||||||
|
|
||||||
|
input {
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-tag .ant-typography {
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.entity-metric-traces-table {
|
||||||
|
.ant-table-content {
|
||||||
|
overflow: hidden !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-table {
|
||||||
|
border-radius: 3px;
|
||||||
|
border: 1px solid var(--bg-slate-500);
|
||||||
|
|
||||||
|
.ant-table-thead > tr > th {
|
||||||
|
padding: 12px;
|
||||||
|
font-weight: 500;
|
||||||
|
font-size: 12px;
|
||||||
|
line-height: 18px;
|
||||||
|
|
||||||
|
background: rgba(171, 189, 255, 0.01);
|
||||||
|
border-bottom: none;
|
||||||
|
|
||||||
|
color: var(--Vanilla-400, #c0c1c3);
|
||||||
|
font-family: Inter;
|
||||||
|
font-size: 11px;
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 600;
|
||||||
|
line-height: 18px; /* 163.636% */
|
||||||
|
letter-spacing: 0.44px;
|
||||||
|
text-transform: uppercase;
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
background-color: transparent;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-table-thead > tr > th:has(.entityname-column-header) {
|
||||||
|
background: var(--bg-ink-400);
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-table-cell {
|
||||||
|
padding: 12px;
|
||||||
|
font-size: 13px;
|
||||||
|
line-height: 20px;
|
||||||
|
color: var(--bg-vanilla-100);
|
||||||
|
background: rgba(171, 189, 255, 0.01);
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-table-cell:has(.entityname-column-value) {
|
||||||
|
background: var(--bg-ink-400);
|
||||||
|
}
|
||||||
|
|
||||||
|
.entityname-column-value {
|
||||||
|
color: var(--bg-vanilla-100);
|
||||||
|
font-family: 'Geist Mono';
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 600;
|
||||||
|
line-height: 20px; /* 142.857% */
|
||||||
|
letter-spacing: -0.07px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-cell {
|
||||||
|
.active-tag {
|
||||||
|
color: var(--bg-forest-500);
|
||||||
|
padding: 4px 8px;
|
||||||
|
border-radius: 4px;
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.progress-container {
|
||||||
|
.ant-progress-bg {
|
||||||
|
height: 8px !important;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-table-tbody > tr:hover > td {
|
||||||
|
background: rgba(255, 255, 255, 0.04);
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-table-cell:first-child {
|
||||||
|
text-align: justify;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-table-cell:nth-child(2) {
|
||||||
|
padding-left: 16px;
|
||||||
|
padding-right: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-table-cell:nth-child(n + 3) {
|
||||||
|
padding-right: 24px;
|
||||||
|
}
|
||||||
|
.column-header-right {
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
.ant-table-tbody > tr > td {
|
||||||
|
border-bottom: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-table-thead
|
||||||
|
> tr
|
||||||
|
> th:not(:last-child):not(.ant-table-selection-column):not(.ant-table-row-expand-icon-cell):not([colspan])::before {
|
||||||
|
background-color: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-empty-normal {
|
||||||
|
visibility: hidden;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-table-container::after {
|
||||||
|
content: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.lightMode {
|
||||||
|
.entity-metric-traces-header {
|
||||||
|
.filter-section {
|
||||||
|
border-top: 1px solid var(--bg-vanilla-300);
|
||||||
|
border-bottom: 1px solid var(--bg-vanilla-300);
|
||||||
|
|
||||||
|
.ant-select-selector {
|
||||||
|
border-color: var(--bg-vanilla-300) !important;
|
||||||
|
background-color: var(--bg-vanilla-100) !important;
|
||||||
|
color: var(--bg-ink-200);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.entity-metric-traces-table {
|
||||||
|
.ant-table {
|
||||||
|
border-radius: 3px;
|
||||||
|
border: 1px solid var(--bg-vanilla-300);
|
||||||
|
|
||||||
|
.ant-table-thead > tr > th {
|
||||||
|
background: var(--bg-vanilla-100);
|
||||||
|
color: var(--text-ink-300);
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-table-thead > tr > th:has(.entityname-column-header) {
|
||||||
|
background: var(--bg-vanilla-100);
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-table-cell {
|
||||||
|
background: var(--bg-vanilla-100);
|
||||||
|
color: var(--bg-ink-500);
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-table-cell:has(.entityname-column-value) {
|
||||||
|
background: var(--bg-vanilla-100);
|
||||||
|
}
|
||||||
|
|
||||||
|
.entityname-column-value {
|
||||||
|
color: var(--bg-ink-300);
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-table-tbody > tr:hover > td {
|
||||||
|
background: rgba(0, 0, 0, 0.04);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.entity-metrics-logs-container {
|
||||||
|
margin-top: 1rem;
|
||||||
|
|
||||||
|
.filter-section {
|
||||||
|
flex: 1;
|
||||||
|
|
||||||
|
.ant-select-selector {
|
||||||
|
border-radius: 2px;
|
||||||
|
border: 1px solid var(--bg-slate-400) !important;
|
||||||
|
background-color: var(--bg-ink-300) !important;
|
||||||
|
|
||||||
|
input {
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-tag .ant-typography {
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.entity-metrics-logs-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 8px;
|
||||||
|
|
||||||
|
padding: 12px;
|
||||||
|
border-radius: 3px;
|
||||||
|
border: 1px solid var(--bg-slate-500);
|
||||||
|
}
|
||||||
|
|
||||||
|
.entity-metrics-logs {
|
||||||
|
margin-top: 1rem;
|
||||||
|
|
||||||
|
.virtuoso-list {
|
||||||
|
overflow-y: hidden !important;
|
||||||
|
|
||||||
|
&::-webkit-scrollbar {
|
||||||
|
width: 0.3rem;
|
||||||
|
height: 0.3rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
&::-webkit-scrollbar-track {
|
||||||
|
background: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
&::-webkit-scrollbar-thumb {
|
||||||
|
background: var(--bg-slate-300);
|
||||||
|
}
|
||||||
|
|
||||||
|
&::-webkit-scrollbar-thumb:hover {
|
||||||
|
background: var(--bg-slate-200);
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-row {
|
||||||
|
width: fit-content;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.skeleton-container {
|
||||||
|
height: 100%;
|
||||||
|
padding: 16px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.entity-metrics-logs-list-container {
|
||||||
|
flex: 1;
|
||||||
|
height: calc(100vh - 272px) !important;
|
||||||
|
display: flex;
|
||||||
|
height: 100%;
|
||||||
|
|
||||||
|
.raw-log-content {
|
||||||
|
width: 100%;
|
||||||
|
text-wrap: inherit;
|
||||||
|
word-wrap: break-word;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.entity-metrics-logs-list-card {
|
||||||
|
width: 100%;
|
||||||
|
margin-top: 12px;
|
||||||
|
|
||||||
|
.ant-card-body {
|
||||||
|
padding: 0;
|
||||||
|
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.logs-loading-skeleton {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 8px;
|
||||||
|
padding: 8px 0;
|
||||||
|
|
||||||
|
.ant-skeleton-input-sm {
|
||||||
|
height: 18px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.no-logs-found {
|
||||||
|
height: 50vh;
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
padding: 24px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
|
||||||
|
.ant-typography {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 16px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.lightMode {
|
||||||
|
.filter-section {
|
||||||
|
border-top: 1px solid var(--bg-vanilla-300);
|
||||||
|
border-bottom: 1px solid var(--bg-vanilla-300);
|
||||||
|
|
||||||
|
.ant-select-selector {
|
||||||
|
border-color: var(--bg-vanilla-300) !important;
|
||||||
|
background-color: var(--bg-vanilla-100) !important;
|
||||||
|
color: var(--bg-ink-200);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,5 +1,8 @@
|
|||||||
|
import { Color } from '@signozhq/design-tokens';
|
||||||
|
import { Typography } from 'antd';
|
||||||
import { PANEL_TYPES } from 'constants/queryBuilder';
|
import { PANEL_TYPES } from 'constants/queryBuilder';
|
||||||
import { GetQueryResultsProps } from 'lib/dashboard/getQueryResults';
|
import { GetQueryResultsProps } from 'lib/dashboard/getQueryResults';
|
||||||
|
import { Ghost } from 'lucide-react';
|
||||||
import {
|
import {
|
||||||
BaseAutocompleteData,
|
BaseAutocompleteData,
|
||||||
DataTypes,
|
DataTypes,
|
||||||
@ -8,8 +11,105 @@ import { IBuilderQuery } from 'types/api/queryBuilder/queryBuilderData';
|
|||||||
import { EQueryType } from 'types/common/dashboard';
|
import { EQueryType } from 'types/common/dashboard';
|
||||||
import { DataSource } from 'types/common/queryBuilder';
|
import { DataSource } from 'types/common/queryBuilder';
|
||||||
import { nanoToMilli } from 'utils/timeUtils';
|
import { nanoToMilli } from 'utils/timeUtils';
|
||||||
|
import { v4 as uuidv4 } from 'uuid';
|
||||||
|
|
||||||
export const columns = [
|
import { K8sCategory } from '../constants';
|
||||||
|
|
||||||
|
export const QUERY_KEYS = {
|
||||||
|
K8S_OBJECT_KIND: 'k8s.object.kind',
|
||||||
|
K8S_OBJECT_NAME: 'k8s.object.name',
|
||||||
|
K8S_POD_NAME: 'k8s.pod.name',
|
||||||
|
K8S_NAMESPACE_NAME: 'k8s.namespace.name',
|
||||||
|
K8S_CLUSTER_NAME: 'k8s.cluster.name',
|
||||||
|
K8S_NODE_NAME: 'k8s.node.name',
|
||||||
|
K8S_DEPLOYMENT_NAME: 'k8s.deployment.name',
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the payload configuration to fetch events for a K8s entity
|
||||||
|
*/
|
||||||
|
export const getEntityEventsOrLogsQueryPayload = (
|
||||||
|
start: number,
|
||||||
|
end: number,
|
||||||
|
filters: IBuilderQuery['filters'],
|
||||||
|
): GetQueryResultsProps => ({
|
||||||
|
graphType: PANEL_TYPES.LIST,
|
||||||
|
selectedTime: 'GLOBAL_TIME',
|
||||||
|
query: {
|
||||||
|
clickhouse_sql: [],
|
||||||
|
promql: [],
|
||||||
|
builder: {
|
||||||
|
queryData: [
|
||||||
|
{
|
||||||
|
dataSource: DataSource.LOGS,
|
||||||
|
queryName: 'A',
|
||||||
|
aggregateOperator: 'noop',
|
||||||
|
aggregateAttribute: {
|
||||||
|
id: '------false',
|
||||||
|
dataType: DataTypes.String,
|
||||||
|
key: '',
|
||||||
|
isColumn: false,
|
||||||
|
type: '',
|
||||||
|
isJSON: false,
|
||||||
|
},
|
||||||
|
timeAggregation: 'rate',
|
||||||
|
spaceAggregation: 'sum',
|
||||||
|
functions: [],
|
||||||
|
filters,
|
||||||
|
expression: 'A',
|
||||||
|
disabled: false,
|
||||||
|
stepInterval: 60,
|
||||||
|
having: [],
|
||||||
|
limit: null,
|
||||||
|
orderBy: [
|
||||||
|
{
|
||||||
|
columnName: 'timestamp',
|
||||||
|
order: 'desc',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
groupBy: [],
|
||||||
|
legend: '',
|
||||||
|
reduceTo: 'avg',
|
||||||
|
offset: 0,
|
||||||
|
pageSize: 100,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
queryFormulas: [],
|
||||||
|
},
|
||||||
|
id: uuidv4(),
|
||||||
|
queryType: EQueryType.QUERY_BUILDER,
|
||||||
|
},
|
||||||
|
params: {
|
||||||
|
lastLogLineTimestamp: null,
|
||||||
|
},
|
||||||
|
start,
|
||||||
|
end,
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Empty state container for entity details
|
||||||
|
*/
|
||||||
|
export function EntityDetailsEmptyContainer({
|
||||||
|
view,
|
||||||
|
category,
|
||||||
|
}: {
|
||||||
|
view: 'logs' | 'traces' | 'events';
|
||||||
|
category: K8sCategory;
|
||||||
|
}): React.ReactElement {
|
||||||
|
const label = category.slice(0, category.length);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="no-logs-found">
|
||||||
|
<Typography.Text type="secondary">
|
||||||
|
<Ghost size={24} color={Color.BG_AMBER_500} />
|
||||||
|
{`No ${view} found for this ${label}
|
||||||
|
in the selected time range.`}
|
||||||
|
</Typography.Text>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export const entityTracesColumns = [
|
||||||
{
|
{
|
||||||
dataIndex: 'timestamp',
|
dataIndex: 'timestamp',
|
||||||
key: 'timestamp',
|
key: 'timestamp',
|
||||||
@ -50,7 +150,7 @@ export const columns = [
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
export const selectedColumns: BaseAutocompleteData[] = [
|
export const selectedEntityTracesColumns: BaseAutocompleteData[] = [
|
||||||
{
|
{
|
||||||
key: 'timestamp',
|
key: 'timestamp',
|
||||||
dataType: DataTypes.String,
|
dataType: DataTypes.String,
|
||||||
@ -89,7 +189,7 @@ export const selectedColumns: BaseAutocompleteData[] = [
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
export const getPodTracesQueryPayload = (
|
export const getEntityTracesQueryPayload = (
|
||||||
start: number,
|
start: number,
|
||||||
end: number,
|
end: number,
|
||||||
offset = 0,
|
offset = 0,
|
||||||
@ -876,3 +876,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.entity-group-header {
|
||||||
|
width: 300px !important;
|
||||||
|
}
|
||||||
|
|||||||
@ -7,16 +7,28 @@ import { Collapse, Tooltip, Typography } from 'antd';
|
|||||||
import QuickFilters from 'components/QuickFilters/QuickFilters';
|
import QuickFilters from 'components/QuickFilters/QuickFilters';
|
||||||
import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
|
import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
|
||||||
import { useQueryOperations } from 'hooks/queryBuilder/useQueryBuilderOperations';
|
import { useQueryOperations } from 'hooks/queryBuilder/useQueryBuilderOperations';
|
||||||
import { Container, Workflow } from 'lucide-react';
|
import {
|
||||||
|
Boxes,
|
||||||
|
Computer,
|
||||||
|
Container,
|
||||||
|
FilePenLine,
|
||||||
|
Workflow,
|
||||||
|
} from 'lucide-react';
|
||||||
import ErrorBoundaryFallback from 'pages/ErrorBoundaryFallback/ErrorBoundaryFallback';
|
import ErrorBoundaryFallback from 'pages/ErrorBoundaryFallback/ErrorBoundaryFallback';
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { Query } from 'types/api/queryBuilder/queryBuilderData';
|
import { Query } from 'types/api/queryBuilder/queryBuilderData';
|
||||||
|
|
||||||
|
import K8sClustersList from './Clusters/K8sClustersList';
|
||||||
import {
|
import {
|
||||||
|
ClustersQuickFiltersConfig,
|
||||||
|
DeploymentsQuickFiltersConfig,
|
||||||
K8sCategories,
|
K8sCategories,
|
||||||
|
NamespaceQuickFiltersConfig,
|
||||||
NodesQuickFiltersConfig,
|
NodesQuickFiltersConfig,
|
||||||
PodsQuickFiltersConfig,
|
PodsQuickFiltersConfig,
|
||||||
} from './constants';
|
} from './constants';
|
||||||
|
import K8sDeploymentsList from './Deployments/K8sDeploymentsList';
|
||||||
|
import K8sNamespacesList from './Namespaces/K8sNamespacesList';
|
||||||
import K8sNodesList from './Nodes/K8sNodesList';
|
import K8sNodesList from './Nodes/K8sNodesList';
|
||||||
import K8sPodLists from './Pods/K8sPodLists';
|
import K8sPodLists from './Pods/K8sPodLists';
|
||||||
|
|
||||||
@ -87,49 +99,49 @@ export default function InfraMonitoringK8s(): JSX.Element {
|
|||||||
),
|
),
|
||||||
},
|
},
|
||||||
// NOTE - Enabled these as we release new entities
|
// NOTE - Enabled these as we release new entities
|
||||||
// {
|
{
|
||||||
// label: (
|
label: (
|
||||||
// <div className="k8s-quick-filters-category-label">
|
<div className="k8s-quick-filters-category-label">
|
||||||
// <div className="k8s-quick-filters-category-label-container">
|
<div className="k8s-quick-filters-category-label-container">
|
||||||
// <FilePenLine
|
<FilePenLine
|
||||||
// size={14}
|
size={14}
|
||||||
// className="k8s-quick-filters-category-label-icon"
|
className="k8s-quick-filters-category-label-icon"
|
||||||
// />
|
/>
|
||||||
// <Typography.Text>Namespace</Typography.Text>
|
<Typography.Text>Namespaces</Typography.Text>
|
||||||
// </div>
|
</div>
|
||||||
// </div>
|
</div>
|
||||||
// ),
|
),
|
||||||
// key: K8sCategories.NAMESPACES,
|
key: K8sCategories.NAMESPACES,
|
||||||
// showArrow: false,
|
showArrow: false,
|
||||||
// children: (
|
children: (
|
||||||
// <QuickFilters
|
<QuickFilters
|
||||||
// source="infra-monitoring"
|
source="infra-monitoring"
|
||||||
// config={NamespaceQuickFiltersConfig}
|
config={NamespaceQuickFiltersConfig}
|
||||||
// handleFilterVisibilityChange={handleFilterVisibilityChange}
|
handleFilterVisibilityChange={handleFilterVisibilityChange}
|
||||||
// onFilterChange={handleFilterChange}
|
onFilterChange={handleFilterChange}
|
||||||
// />
|
/>
|
||||||
// ),
|
),
|
||||||
// },
|
},
|
||||||
// {
|
{
|
||||||
// label: (
|
label: (
|
||||||
// <div className="k8s-quick-filters-category-label">
|
<div className="k8s-quick-filters-category-label">
|
||||||
// <div className="k8s-quick-filters-category-label-container">
|
<div className="k8s-quick-filters-category-label-container">
|
||||||
// <Boxes size={14} className="k8s-quick-filters-category-label-icon" />
|
<Boxes size={14} className="k8s-quick-filters-category-label-icon" />
|
||||||
// <Typography.Text>Clusters</Typography.Text>
|
<Typography.Text>Clusters</Typography.Text>
|
||||||
// </div>
|
</div>
|
||||||
// </div>
|
</div>
|
||||||
// ),
|
),
|
||||||
// key: K8sCategories.CLUSTERS,
|
key: K8sCategories.CLUSTERS,
|
||||||
// showArrow: false,
|
showArrow: false,
|
||||||
// children: (
|
children: (
|
||||||
// <QuickFilters
|
<QuickFilters
|
||||||
// source="infra-monitoring"
|
source="infra-monitoring"
|
||||||
// config={ClustersQuickFiltersConfig}
|
config={ClustersQuickFiltersConfig}
|
||||||
// handleFilterVisibilityChange={handleFilterVisibilityChange}
|
handleFilterVisibilityChange={handleFilterVisibilityChange}
|
||||||
// onFilterChange={handleFilterChange}
|
onFilterChange={handleFilterChange}
|
||||||
// />
|
/>
|
||||||
// ),
|
),
|
||||||
// },
|
},
|
||||||
// {
|
// {
|
||||||
// label: (
|
// label: (
|
||||||
// <div className="k8s-quick-filters-category-label">
|
// <div className="k8s-quick-filters-category-label">
|
||||||
@ -173,26 +185,26 @@ export default function InfraMonitoringK8s(): JSX.Element {
|
|||||||
// />
|
// />
|
||||||
// ),
|
// ),
|
||||||
// },
|
// },
|
||||||
// {
|
{
|
||||||
// label: (
|
label: (
|
||||||
// <div className="k8s-quick-filters-category-label">
|
<div className="k8s-quick-filters-category-label">
|
||||||
// <div className="k8s-quick-filters-category-label-container">
|
<div className="k8s-quick-filters-category-label-container">
|
||||||
// <Computer size={14} className="k8s-quick-filters-category-label-icon" />
|
<Computer size={14} className="k8s-quick-filters-category-label-icon" />
|
||||||
// <Typography.Text>Deployments</Typography.Text>
|
<Typography.Text>Deployments</Typography.Text>
|
||||||
// </div>
|
</div>
|
||||||
// </div>
|
</div>
|
||||||
// ),
|
),
|
||||||
// key: K8sCategories.DEPLOYMENTS,
|
key: K8sCategories.DEPLOYMENTS,
|
||||||
// showArrow: false,
|
showArrow: false,
|
||||||
// children: (
|
children: (
|
||||||
// <QuickFilters
|
<QuickFilters
|
||||||
// source="infra-monitoring"
|
source="infra-monitoring"
|
||||||
// config={DeploymentsQuickFiltersConfig}
|
config={DeploymentsQuickFiltersConfig}
|
||||||
// handleFilterVisibilityChange={handleFilterVisibilityChange}
|
handleFilterVisibilityChange={handleFilterVisibilityChange}
|
||||||
// onFilterChange={handleFilterChange}
|
onFilterChange={handleFilterChange}
|
||||||
// />
|
/>
|
||||||
// ),
|
),
|
||||||
// },
|
},
|
||||||
// {
|
// {
|
||||||
// label: (
|
// label: (
|
||||||
// <div className="k8s-quick-filters-category-label">
|
// <div className="k8s-quick-filters-category-label">
|
||||||
@ -314,6 +326,30 @@ export default function InfraMonitoringK8s(): JSX.Element {
|
|||||||
quickFiltersLastUpdated={quickFiltersLastUpdated}
|
quickFiltersLastUpdated={quickFiltersLastUpdated}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{selectedCategory === K8sCategories.DEPLOYMENTS && (
|
||||||
|
<K8sDeploymentsList
|
||||||
|
isFiltersVisible={showFilters}
|
||||||
|
handleFilterVisibilityChange={handleFilterVisibilityChange}
|
||||||
|
quickFiltersLastUpdated={quickFiltersLastUpdated}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{selectedCategory === K8sCategories.NAMESPACES && (
|
||||||
|
<K8sNamespacesList
|
||||||
|
isFiltersVisible={showFilters}
|
||||||
|
handleFilterVisibilityChange={handleFilterVisibilityChange}
|
||||||
|
quickFiltersLastUpdated={quickFiltersLastUpdated}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{selectedCategory === K8sCategories.CLUSTERS && (
|
||||||
|
<K8sClustersList
|
||||||
|
isFiltersVisible={showFilters}
|
||||||
|
handleFilterVisibilityChange={handleFilterVisibilityChange}
|
||||||
|
quickFiltersLastUpdated={quickFiltersLastUpdated}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -0,0 +1,17 @@
|
|||||||
|
.infra-monitoring-container {
|
||||||
|
.namespaces-list-table {
|
||||||
|
.expanded-table-container {
|
||||||
|
padding-left: 80px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-table-cell {
|
||||||
|
min-width: 223px !important;
|
||||||
|
max-width: 223px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-table-row-expand-icon-cell {
|
||||||
|
min-width: 30px !important;
|
||||||
|
max-width: 30px !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,512 @@
|
|||||||
|
/* eslint-disable no-restricted-syntax */
|
||||||
|
/* eslint-disable @typescript-eslint/explicit-function-return-type */
|
||||||
|
import '../InfraMonitoringK8s.styles.scss';
|
||||||
|
import './K8sNamespacesList.styles.scss';
|
||||||
|
|
||||||
|
import { LoadingOutlined } from '@ant-design/icons';
|
||||||
|
import {
|
||||||
|
Button,
|
||||||
|
Spin,
|
||||||
|
Table,
|
||||||
|
TablePaginationConfig,
|
||||||
|
TableProps,
|
||||||
|
Typography,
|
||||||
|
} from 'antd';
|
||||||
|
import { ColumnType, SorterResult } from 'antd/es/table/interface';
|
||||||
|
import logEvent from 'api/common/logEvent';
|
||||||
|
import { K8sNamespacesListPayload } from 'api/infraMonitoring/getK8sNamespacesList';
|
||||||
|
import { useGetK8sNamespacesList } from 'hooks/infraMonitoring/useGetK8sNamespacesList';
|
||||||
|
import { useGetAggregateKeys } from 'hooks/queryBuilder/useGetAggregateKeys';
|
||||||
|
import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
|
||||||
|
import { useQueryOperations } from 'hooks/queryBuilder/useQueryBuilderOperations';
|
||||||
|
import { ChevronDown, ChevronRight } from 'lucide-react';
|
||||||
|
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 {
|
||||||
|
K8sCategory,
|
||||||
|
K8sEntityToAggregateAttributeMapping,
|
||||||
|
} from '../constants';
|
||||||
|
import K8sHeader from '../K8sHeader';
|
||||||
|
import LoadingContainer from '../LoadingContainer';
|
||||||
|
import NamespaceDetails from './NamespaceDetails';
|
||||||
|
import {
|
||||||
|
defaultAddedColumns,
|
||||||
|
formatDataForTable,
|
||||||
|
getK8sNamespacesListColumns,
|
||||||
|
getK8sNamespacesListQuery,
|
||||||
|
K8sNamespacesRowData,
|
||||||
|
} from './utils';
|
||||||
|
|
||||||
|
// eslint-disable-next-line sonarjs/cognitive-complexity
|
||||||
|
function K8sNamespacesList({
|
||||||
|
isFiltersVisible,
|
||||||
|
handleFilterVisibilityChange,
|
||||||
|
quickFiltersLastUpdated,
|
||||||
|
}: {
|
||||||
|
isFiltersVisible: boolean;
|
||||||
|
handleFilterVisibilityChange: () => void;
|
||||||
|
quickFiltersLastUpdated: number;
|
||||||
|
}): JSX.Element {
|
||||||
|
const { maxTime, minTime } = useSelector<AppState, GlobalReducer>(
|
||||||
|
(state) => state.globalTime,
|
||||||
|
);
|
||||||
|
|
||||||
|
const [currentPage, setCurrentPage] = useState(1);
|
||||||
|
|
||||||
|
const [expandedRowKeys, setExpandedRowKeys] = useState<string[]>([]);
|
||||||
|
|
||||||
|
const [orderBy, setOrderBy] = useState<{
|
||||||
|
columnName: string;
|
||||||
|
order: 'asc' | 'desc';
|
||||||
|
} | null>(null);
|
||||||
|
|
||||||
|
const [selectedNamespaceUID, setselectedNamespaceUID] = useState<
|
||||||
|
string | null
|
||||||
|
>(null);
|
||||||
|
|
||||||
|
const pageSize = 10;
|
||||||
|
|
||||||
|
const [groupBy, setGroupBy] = useState<IBuilderQuery['groupBy']>([]);
|
||||||
|
|
||||||
|
const [
|
||||||
|
selectedRowData,
|
||||||
|
setSelectedRowData,
|
||||||
|
] = useState<K8sNamespacesRowData | null>(null);
|
||||||
|
|
||||||
|
const [groupByOptions, setGroupByOptions] = useState<
|
||||||
|
{ value: string; label: string }[]
|
||||||
|
>([]);
|
||||||
|
|
||||||
|
const { currentQuery } = useQueryBuilder();
|
||||||
|
|
||||||
|
const queryFilters = useMemo(
|
||||||
|
() =>
|
||||||
|
currentQuery?.builder?.queryData[0]?.filters || {
|
||||||
|
items: [],
|
||||||
|
op: 'and',
|
||||||
|
},
|
||||||
|
[currentQuery?.builder?.queryData],
|
||||||
|
);
|
||||||
|
|
||||||
|
// Reset pagination every time quick filters are changed
|
||||||
|
useEffect(() => {
|
||||||
|
setCurrentPage(1);
|
||||||
|
}, [quickFiltersLastUpdated]);
|
||||||
|
|
||||||
|
const createFiltersForSelectedRowData = (
|
||||||
|
selectedRowData: K8sNamespacesRowData,
|
||||||
|
groupBy: IBuilderQuery['groupBy'],
|
||||||
|
): IBuilderQuery['filters'] => {
|
||||||
|
const baseFilters: IBuilderQuery['filters'] = {
|
||||||
|
items: [...queryFilters.items],
|
||||||
|
op: 'and',
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!selectedRowData) return baseFilters;
|
||||||
|
|
||||||
|
const { groupedByMeta } = selectedRowData;
|
||||||
|
|
||||||
|
for (const key of groupBy) {
|
||||||
|
baseFilters.items.push({
|
||||||
|
key: {
|
||||||
|
key: key.key,
|
||||||
|
type: null,
|
||||||
|
},
|
||||||
|
op: '=',
|
||||||
|
value: groupedByMeta[key.key],
|
||||||
|
id: key.key,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return baseFilters;
|
||||||
|
};
|
||||||
|
|
||||||
|
const fetchGroupedByRowDataQuery = useMemo(() => {
|
||||||
|
if (!selectedRowData) return null;
|
||||||
|
|
||||||
|
const baseQuery = getK8sNamespacesListQuery();
|
||||||
|
|
||||||
|
const filters = createFiltersForSelectedRowData(selectedRowData, groupBy);
|
||||||
|
|
||||||
|
return {
|
||||||
|
...baseQuery,
|
||||||
|
limit: 10,
|
||||||
|
offset: 0,
|
||||||
|
filters,
|
||||||
|
start: Math.floor(minTime / 1000000),
|
||||||
|
end: Math.floor(maxTime / 1000000),
|
||||||
|
orderBy,
|
||||||
|
};
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
}, [minTime, maxTime, orderBy, selectedRowData, groupBy]);
|
||||||
|
|
||||||
|
const {
|
||||||
|
data: groupedByRowData,
|
||||||
|
isFetching: isFetchingGroupedByRowData,
|
||||||
|
isLoading: isLoadingGroupedByRowData,
|
||||||
|
isError: isErrorGroupedByRowData,
|
||||||
|
refetch: fetchGroupedByRowData,
|
||||||
|
} = useGetK8sNamespacesList(
|
||||||
|
fetchGroupedByRowDataQuery as K8sNamespacesListPayload,
|
||||||
|
{
|
||||||
|
queryKey: ['namespaceList', fetchGroupedByRowDataQuery],
|
||||||
|
enabled: !!fetchGroupedByRowDataQuery && !!selectedRowData,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
const {
|
||||||
|
data: groupByFiltersData,
|
||||||
|
isLoading: isLoadingGroupByFilters,
|
||||||
|
} = useGetAggregateKeys(
|
||||||
|
{
|
||||||
|
dataSource: currentQuery.builder.queryData[0].dataSource,
|
||||||
|
aggregateAttribute: K8sEntityToAggregateAttributeMapping[K8sCategory.NODES],
|
||||||
|
aggregateOperator: 'noop',
|
||||||
|
searchText: '',
|
||||||
|
tagType: '',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
queryKey: [currentQuery.builder.queryData[0].dataSource, 'noop'],
|
||||||
|
},
|
||||||
|
true,
|
||||||
|
K8sCategory.NODES,
|
||||||
|
);
|
||||||
|
|
||||||
|
const query = useMemo(() => {
|
||||||
|
const baseQuery = getK8sNamespacesListQuery();
|
||||||
|
const queryPayload = {
|
||||||
|
...baseQuery,
|
||||||
|
limit: pageSize,
|
||||||
|
offset: (currentPage - 1) * pageSize,
|
||||||
|
filters: queryFilters,
|
||||||
|
start: Math.floor(minTime / 1000000),
|
||||||
|
end: Math.floor(maxTime / 1000000),
|
||||||
|
orderBy,
|
||||||
|
};
|
||||||
|
if (groupBy.length > 0) {
|
||||||
|
queryPayload.groupBy = groupBy;
|
||||||
|
}
|
||||||
|
return queryPayload;
|
||||||
|
}, [currentPage, minTime, maxTime, orderBy, groupBy, queryFilters]);
|
||||||
|
|
||||||
|
const formattedGroupedByNamespacesData = useMemo(
|
||||||
|
() =>
|
||||||
|
formatDataForTable(groupedByRowData?.payload?.data?.records || [], groupBy),
|
||||||
|
[groupedByRowData, groupBy],
|
||||||
|
);
|
||||||
|
|
||||||
|
const { data, isFetching, isLoading, isError } = useGetK8sNamespacesList(
|
||||||
|
query as K8sNamespacesListPayload,
|
||||||
|
{
|
||||||
|
queryKey: ['namespaceList', query],
|
||||||
|
enabled: !!query,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
const namespacesData = useMemo(() => data?.payload?.data?.records || [], [
|
||||||
|
data,
|
||||||
|
]);
|
||||||
|
const totalCount = data?.payload?.data?.total || 0;
|
||||||
|
|
||||||
|
const formattedNamespacesData = useMemo(
|
||||||
|
() => formatDataForTable(namespacesData, groupBy),
|
||||||
|
[namespacesData, groupBy],
|
||||||
|
);
|
||||||
|
|
||||||
|
const columns = useMemo(() => getK8sNamespacesListColumns(groupBy), [groupBy]);
|
||||||
|
|
||||||
|
const handleGroupByRowClick = (record: K8sNamespacesRowData): void => {
|
||||||
|
setSelectedRowData(record);
|
||||||
|
|
||||||
|
if (expandedRowKeys.includes(record.key)) {
|
||||||
|
setExpandedRowKeys(expandedRowKeys.filter((key) => key !== record.key));
|
||||||
|
} else {
|
||||||
|
setExpandedRowKeys([record.key]);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (selectedRowData) {
|
||||||
|
fetchGroupedByRowData();
|
||||||
|
}
|
||||||
|
}, [selectedRowData, fetchGroupedByRowData]);
|
||||||
|
|
||||||
|
const handleTableChange: TableProps<K8sNamespacesRowData>['onChange'] = useCallback(
|
||||||
|
(
|
||||||
|
pagination: TablePaginationConfig,
|
||||||
|
_filters: Record<string, (string | number | boolean)[] | null>,
|
||||||
|
sorter:
|
||||||
|
| SorterResult<K8sNamespacesRowData>
|
||||||
|
| SorterResult<K8sNamespacesRowData>[],
|
||||||
|
): 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 { handleChangeQueryData } = useQueryOperations({
|
||||||
|
index: 0,
|
||||||
|
query: currentQuery.builder.queryData[0],
|
||||||
|
entityVersion: '',
|
||||||
|
});
|
||||||
|
|
||||||
|
const handleFiltersChange = useCallback(
|
||||||
|
(value: IBuilderQuery['filters']): void => {
|
||||||
|
handleChangeQueryData('filters', value);
|
||||||
|
setCurrentPage(1);
|
||||||
|
|
||||||
|
logEvent('Infra Monitoring: K8s list filters applied', {
|
||||||
|
filters: value,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
[handleChangeQueryData],
|
||||||
|
);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
logEvent('Infra Monitoring: K8s list page visited', {});
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const selectedNamespaceData = useMemo(() => {
|
||||||
|
if (!selectedNamespaceUID) return null;
|
||||||
|
return (
|
||||||
|
namespacesData.find(
|
||||||
|
(namespace) => namespace.namespaceName === selectedNamespaceUID,
|
||||||
|
) || null
|
||||||
|
);
|
||||||
|
}, [selectedNamespaceUID, namespacesData]);
|
||||||
|
|
||||||
|
const handleRowClick = (record: K8sNamespacesRowData): void => {
|
||||||
|
if (groupBy.length === 0) {
|
||||||
|
setSelectedRowData(null);
|
||||||
|
setselectedNamespaceUID(record.namespaceUID);
|
||||||
|
} else {
|
||||||
|
handleGroupByRowClick(record);
|
||||||
|
}
|
||||||
|
|
||||||
|
logEvent('Infra Monitoring: K8s namespace list item clicked', {
|
||||||
|
namespaceUID: record.namespaceUID,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const nestedColumns = useMemo(() => getK8sNamespacesListColumns([]), []);
|
||||||
|
|
||||||
|
const isGroupedByAttribute = groupBy.length > 0;
|
||||||
|
|
||||||
|
const handleExpandedRowViewAllClick = (): void => {
|
||||||
|
if (!selectedRowData) return;
|
||||||
|
|
||||||
|
const filters = createFiltersForSelectedRowData(selectedRowData, groupBy);
|
||||||
|
|
||||||
|
handleFiltersChange(filters);
|
||||||
|
|
||||||
|
setCurrentPage(1);
|
||||||
|
setSelectedRowData(null);
|
||||||
|
setGroupBy([]);
|
||||||
|
setOrderBy(null);
|
||||||
|
};
|
||||||
|
|
||||||
|
const expandedRowRender = (): JSX.Element => (
|
||||||
|
<div className="expanded-table-container">
|
||||||
|
{isErrorGroupedByRowData && (
|
||||||
|
<Typography>{groupedByRowData?.error || 'Something went wrong'}</Typography>
|
||||||
|
)}
|
||||||
|
{isFetchingGroupedByRowData || isLoadingGroupedByRowData ? (
|
||||||
|
<LoadingContainer />
|
||||||
|
) : (
|
||||||
|
<div className="expanded-table">
|
||||||
|
<Table
|
||||||
|
columns={nestedColumns as ColumnType<K8sNamespacesRowData>[]}
|
||||||
|
dataSource={formattedGroupedByNamespacesData}
|
||||||
|
pagination={false}
|
||||||
|
scroll={{ x: true }}
|
||||||
|
tableLayout="fixed"
|
||||||
|
size="small"
|
||||||
|
loading={{
|
||||||
|
spinning: isFetchingGroupedByRowData || isLoadingGroupedByRowData,
|
||||||
|
indicator: <Spin indicator={<LoadingOutlined size={14} spin />} />,
|
||||||
|
}}
|
||||||
|
showHeader={false}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{groupedByRowData?.payload?.data?.total &&
|
||||||
|
groupedByRowData?.payload?.data?.total > 10 ? (
|
||||||
|
<div className="expanded-table-footer">
|
||||||
|
<Button
|
||||||
|
type="default"
|
||||||
|
size="small"
|
||||||
|
className="periscope-btn secondary"
|
||||||
|
onClick={handleExpandedRowViewAllClick}
|
||||||
|
>
|
||||||
|
View All
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
const expandRowIconRenderer = ({
|
||||||
|
expanded,
|
||||||
|
onExpand,
|
||||||
|
record,
|
||||||
|
}: {
|
||||||
|
expanded: boolean;
|
||||||
|
onExpand: (
|
||||||
|
record: K8sNamespacesRowData,
|
||||||
|
e: React.MouseEvent<HTMLButtonElement>,
|
||||||
|
) => void;
|
||||||
|
record: K8sNamespacesRowData;
|
||||||
|
}): JSX.Element | null => {
|
||||||
|
if (!isGroupedByAttribute) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return expanded ? (
|
||||||
|
<Button
|
||||||
|
className="periscope-btn ghost"
|
||||||
|
onClick={(e: React.MouseEvent<HTMLButtonElement>): void =>
|
||||||
|
onExpand(record, e)
|
||||||
|
}
|
||||||
|
role="button"
|
||||||
|
>
|
||||||
|
<ChevronDown size={14} />
|
||||||
|
</Button>
|
||||||
|
) : (
|
||||||
|
<Button
|
||||||
|
className="periscope-btn ghost"
|
||||||
|
onClick={(e: React.MouseEvent<HTMLButtonElement>): void =>
|
||||||
|
onExpand(record, e)
|
||||||
|
}
|
||||||
|
role="button"
|
||||||
|
>
|
||||||
|
<ChevronRight size={14} />
|
||||||
|
</Button>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleCloseNamespaceDetail = (): void => {
|
||||||
|
setselectedNamespaceUID(null);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleGroupByChange = useCallback(
|
||||||
|
(value: IBuilderQuery['groupBy']) => {
|
||||||
|
const groupBy = [];
|
||||||
|
|
||||||
|
for (let index = 0; index < value.length; index++) {
|
||||||
|
const element = (value[index] as unknown) as string;
|
||||||
|
|
||||||
|
const key = groupByFiltersData?.payload?.attributeKeys?.find(
|
||||||
|
(key) => key.key === element,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (key) {
|
||||||
|
groupBy.push(key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reset pagination on switching to groupBy
|
||||||
|
setCurrentPage(1);
|
||||||
|
setGroupBy(groupBy);
|
||||||
|
setExpandedRowKeys([]);
|
||||||
|
},
|
||||||
|
[groupByFiltersData],
|
||||||
|
);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (groupByFiltersData?.payload) {
|
||||||
|
setGroupByOptions(
|
||||||
|
groupByFiltersData?.payload?.attributeKeys?.map((filter) => ({
|
||||||
|
value: filter.key,
|
||||||
|
label: filter.key,
|
||||||
|
})) || [],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}, [groupByFiltersData]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="k8s-list">
|
||||||
|
<K8sHeader
|
||||||
|
isFiltersVisible={isFiltersVisible}
|
||||||
|
handleFilterVisibilityChange={handleFilterVisibilityChange}
|
||||||
|
defaultAddedColumns={defaultAddedColumns}
|
||||||
|
handleFiltersChange={handleFiltersChange}
|
||||||
|
groupByOptions={groupByOptions}
|
||||||
|
isLoadingGroupByFilters={isLoadingGroupByFilters}
|
||||||
|
handleGroupByChange={handleGroupByChange}
|
||||||
|
selectedGroupBy={groupBy}
|
||||||
|
entity={K8sCategory.NODES}
|
||||||
|
/>
|
||||||
|
{isError && <Typography>{data?.error || 'Something went wrong'}</Typography>}
|
||||||
|
|
||||||
|
<Table
|
||||||
|
className="k8s-list-table namespaces-list-table"
|
||||||
|
dataSource={isFetching || isLoading ? [] : formattedNamespacesData}
|
||||||
|
columns={columns}
|
||||||
|
pagination={{
|
||||||
|
current: currentPage,
|
||||||
|
pageSize,
|
||||||
|
total: totalCount,
|
||||||
|
showSizeChanger: false,
|
||||||
|
hideOnSinglePage: true,
|
||||||
|
}}
|
||||||
|
scroll={{ x: true }}
|
||||||
|
loading={{
|
||||||
|
spinning: isFetching || isLoading,
|
||||||
|
indicator: <Spin indicator={<LoadingOutlined size={14} spin />} />,
|
||||||
|
}}
|
||||||
|
locale={{
|
||||||
|
emptyText:
|
||||||
|
isFetching || isLoading ? null : (
|
||||||
|
<div className="no-filtered-hosts-message-container">
|
||||||
|
<div className="no-filtered-hosts-message-content">
|
||||||
|
<img
|
||||||
|
src="/Icons/emptyState.svg"
|
||||||
|
alt="thinking-emoji"
|
||||||
|
className="empty-state-svg"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Typography.Text className="no-filtered-hosts-message">
|
||||||
|
This query had no results. Edit your query and try again!
|
||||||
|
</Typography.Text>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
}}
|
||||||
|
tableLayout="fixed"
|
||||||
|
onChange={handleTableChange}
|
||||||
|
onRow={(record): { onClick: () => void; className: string } => ({
|
||||||
|
onClick: (): void => handleRowClick(record),
|
||||||
|
className: 'clickable-row',
|
||||||
|
})}
|
||||||
|
expandable={{
|
||||||
|
expandedRowRender: isGroupedByAttribute ? expandedRowRender : undefined,
|
||||||
|
expandIcon: expandRowIconRenderer,
|
||||||
|
expandedRowKeys,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<NamespaceDetails
|
||||||
|
namespace={selectedNamespaceData}
|
||||||
|
isModalTimeSelection
|
||||||
|
onClose={handleCloseNamespaceDetail}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default K8sNamespacesList;
|
||||||
@ -0,0 +1,7 @@
|
|||||||
|
import { K8sNamespacesData } from 'api/infraMonitoring/getK8sNamespacesList';
|
||||||
|
|
||||||
|
export type NamespaceDetailsProps = {
|
||||||
|
namespace: K8sNamespacesData | null;
|
||||||
|
isModalTimeSelection: boolean;
|
||||||
|
onClose: () => void;
|
||||||
|
};
|
||||||
@ -1,4 +1,4 @@
|
|||||||
.pod-detail-drawer {
|
.namespace-detail-drawer {
|
||||||
border-left: 1px solid var(--bg-slate-500);
|
border-left: 1px solid var(--bg-slate-500);
|
||||||
background: var(--bg-ink-400);
|
background: var(--bg-ink-400);
|
||||||
box-shadow: -4px 10px 16px 2px rgba(0, 0, 0, 0.2);
|
box-shadow: -4px 10px 16px 2px rgba(0, 0, 0, 0.2);
|
||||||
@ -43,8 +43,8 @@
|
|||||||
box-shadow: 0px 0px 8px 0px rgba(0, 0, 0, 0.1);
|
box-shadow: 0px 0px 8px 0px rgba(0, 0, 0, 0.1);
|
||||||
}
|
}
|
||||||
|
|
||||||
.pod-detail-drawer__pod {
|
.namespace-detail-drawer__namespace {
|
||||||
.pod-details-grid {
|
.namespace-details-grid {
|
||||||
.labels-row,
|
.labels-row,
|
||||||
.values-row {
|
.values-row {
|
||||||
display: grid;
|
display: grid;
|
||||||
@ -57,7 +57,7 @@
|
|||||||
margin-bottom: 8px;
|
margin-bottom: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.pod-details-metadata-label {
|
.namespace-details-metadata-label {
|
||||||
color: var(--text-vanilla-400);
|
color: var(--text-vanilla-400);
|
||||||
font-family: Inter;
|
font-family: Inter;
|
||||||
font-size: 11px;
|
font-size: 11px;
|
||||||
@ -68,7 +68,7 @@
|
|||||||
text-transform: uppercase;
|
text-transform: uppercase;
|
||||||
}
|
}
|
||||||
|
|
||||||
.pod-details-metadata-value {
|
.namespace-details-metadata-value {
|
||||||
color: var(--text-vanilla-400);
|
color: var(--text-vanilla-400);
|
||||||
font-family: 'Geist Mono';
|
font-family: 'Geist Mono';
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
@ -195,12 +195,12 @@
|
|||||||
background: var(--bg-vanilla-100);
|
background: var(--bg-vanilla-100);
|
||||||
}
|
}
|
||||||
|
|
||||||
.pod-detail-drawer {
|
.namespace-detail-drawer {
|
||||||
.title {
|
.title {
|
||||||
color: var(--text-ink-300);
|
color: var(--text-ink-300);
|
||||||
}
|
}
|
||||||
|
|
||||||
.pod-detail-drawer__pod {
|
.namespace-detail-drawer__namespace {
|
||||||
.ant-typography {
|
.ant-typography {
|
||||||
color: var(--text-ink-300);
|
color: var(--text-ink-300);
|
||||||
background: transparent;
|
background: transparent;
|
||||||
@ -0,0 +1,566 @@
|
|||||||
|
/* eslint-disable sonarjs/no-identical-functions */
|
||||||
|
import './NamespaceDetails.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 { K8sNamespacesData } from 'api/infraMonitoring/getK8sNamespacesList';
|
||||||
|
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 { QUERY_KEYS } from 'container/InfraMonitoringK8s/EntityDetailsUtils/utils';
|
||||||
|
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 NamespaceEvents from '../../EntityDetailsUtils/EntityEvents';
|
||||||
|
import NamespaceLogs from '../../EntityDetailsUtils/EntityLogs';
|
||||||
|
import NamespaceMetrics from '../../EntityDetailsUtils/EntityMetrics';
|
||||||
|
import NamespaceTraces from '../../EntityDetailsUtils/EntityTraces';
|
||||||
|
import {
|
||||||
|
getNamespaceMetricsQueryPayload,
|
||||||
|
namespaceWidgetInfo,
|
||||||
|
} from './constants';
|
||||||
|
import { NamespaceDetailsProps } from './NamespaceDetails.interfaces';
|
||||||
|
|
||||||
|
function NamespaceDetails({
|
||||||
|
namespace,
|
||||||
|
onClose,
|
||||||
|
isModalTimeSelection,
|
||||||
|
}: NamespaceDetailsProps): 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<Time>(
|
||||||
|
selectedTime as Time,
|
||||||
|
);
|
||||||
|
|
||||||
|
const [selectedView, setSelectedView] = useState<VIEWS>(VIEWS.METRICS);
|
||||||
|
const isDarkMode = useIsDarkMode();
|
||||||
|
|
||||||
|
const initialFilters = useMemo(
|
||||||
|
() => ({
|
||||||
|
op: 'AND',
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
id: uuidv4(),
|
||||||
|
key: {
|
||||||
|
key: QUERY_KEYS.K8S_NAMESPACE_NAME,
|
||||||
|
dataType: DataTypes.String,
|
||||||
|
type: 'resource',
|
||||||
|
isColumn: false,
|
||||||
|
isJSON: false,
|
||||||
|
id: 'k8s_namespace_name--string--resource--false',
|
||||||
|
},
|
||||||
|
op: '=',
|
||||||
|
value: namespace?.namespaceName || '',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
[namespace?.namespaceName],
|
||||||
|
);
|
||||||
|
|
||||||
|
const initialEventsFilters = useMemo(
|
||||||
|
() => ({
|
||||||
|
op: 'AND',
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
id: uuidv4(),
|
||||||
|
key: {
|
||||||
|
key: QUERY_KEYS.K8S_OBJECT_KIND,
|
||||||
|
dataType: DataTypes.String,
|
||||||
|
type: 'resource',
|
||||||
|
isColumn: false,
|
||||||
|
isJSON: false,
|
||||||
|
id: 'k8s.object.kind--string--resource--false',
|
||||||
|
},
|
||||||
|
op: '=',
|
||||||
|
value: 'Namespace',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: uuidv4(),
|
||||||
|
key: {
|
||||||
|
key: QUERY_KEYS.K8S_OBJECT_NAME,
|
||||||
|
dataType: DataTypes.String,
|
||||||
|
type: 'resource',
|
||||||
|
isColumn: false,
|
||||||
|
isJSON: false,
|
||||||
|
id: 'k8s.object.name--string--resource--false',
|
||||||
|
},
|
||||||
|
op: '=',
|
||||||
|
value: namespace?.namespaceName || '',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
[namespace?.namespaceName],
|
||||||
|
);
|
||||||
|
|
||||||
|
const [logFilters, setLogFilters] = useState<IBuilderQuery['filters']>(
|
||||||
|
initialFilters,
|
||||||
|
);
|
||||||
|
|
||||||
|
const [tracesFilters, setTracesFilters] = useState<IBuilderQuery['filters']>(
|
||||||
|
initialFilters,
|
||||||
|
);
|
||||||
|
|
||||||
|
const [eventsFilters, setEventsFilters] = useState<IBuilderQuery['filters']>(
|
||||||
|
initialEventsFilters,
|
||||||
|
);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
logEvent('Infra Monitoring: Namespaces list details page visited', {
|
||||||
|
namespace: namespace?.namespaceName,
|
||||||
|
});
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setLogFilters(initialFilters);
|
||||||
|
setTracesFilters(initialFilters);
|
||||||
|
setEventsFilters(initialEventsFilters);
|
||||||
|
}, [initialFilters, initialEventsFilters]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setSelectedInterval(selectedTime as Time);
|
||||||
|
|
||||||
|
if (selectedTime !== 'custom') {
|
||||||
|
const { maxTime, minTime } = GetMinMax(selectedTime);
|
||||||
|
|
||||||
|
setModalTimeRange({
|
||||||
|
startTime: Math.floor(minTime / 1000000000),
|
||||||
|
endTime: Math.floor(maxTime / 1000000000),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, [selectedTime, minTime, maxTime]);
|
||||||
|
|
||||||
|
const handleTabChange = (e: RadioChangeEvent): void => {
|
||||||
|
setSelectedView(e.target.value);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleTimeChange = useCallback(
|
||||||
|
(interval: Time | CustomTimeType, dateTimeRange?: [number, number]): void => {
|
||||||
|
setSelectedInterval(interval as Time);
|
||||||
|
|
||||||
|
if (interval === 'custom' && dateTimeRange) {
|
||||||
|
setModalTimeRange({
|
||||||
|
startTime: Math.floor(dateTimeRange[0] / 1000),
|
||||||
|
endTime: Math.floor(dateTimeRange[1] / 1000),
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
const { maxTime, minTime } = GetMinMax(interval);
|
||||||
|
|
||||||
|
setModalTimeRange({
|
||||||
|
startTime: Math.floor(minTime / 1000000000),
|
||||||
|
endTime: Math.floor(maxTime / 1000000000),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
logEvent('Infra Monitoring: Namespaces list details time updated', {
|
||||||
|
namespace: namespace?.namespaceName,
|
||||||
|
interval,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
[],
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleChangeLogFilters = useCallback(
|
||||||
|
(value: IBuilderQuery['filters']) => {
|
||||||
|
setLogFilters((prevFilters) => {
|
||||||
|
const primaryFilters = prevFilters.items.filter((item) =>
|
||||||
|
[QUERY_KEYS.K8S_NAMESPACE_NAME, QUERY_KEYS.K8S_CLUSTER_NAME].includes(
|
||||||
|
item.key?.key ?? '',
|
||||||
|
),
|
||||||
|
);
|
||||||
|
const paginationFilter = value.items.find((item) => item.key?.key === 'id');
|
||||||
|
const newFilters = value.items.filter(
|
||||||
|
(item) =>
|
||||||
|
item.key?.key !== 'id' && item.key?.key !== QUERY_KEYS.K8S_NAMESPACE_NAME,
|
||||||
|
);
|
||||||
|
|
||||||
|
logEvent('Infra Monitoring: Namespaces list details logs filters applied', {
|
||||||
|
namespace: namespace?.namespaceName,
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
op: 'AND',
|
||||||
|
items: [
|
||||||
|
...primaryFilters,
|
||||||
|
...newFilters,
|
||||||
|
...(paginationFilter ? [paginationFilter] : []),
|
||||||
|
].filter((item): item is TagFilterItem => item !== undefined),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
},
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
[],
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleChangeTracesFilters = useCallback(
|
||||||
|
(value: IBuilderQuery['filters']) => {
|
||||||
|
setTracesFilters((prevFilters) => {
|
||||||
|
const primaryFilters = prevFilters.items.filter((item) =>
|
||||||
|
[QUERY_KEYS.K8S_NAMESPACE_NAME, QUERY_KEYS.K8S_CLUSTER_NAME].includes(
|
||||||
|
item.key?.key ?? '',
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
logEvent(
|
||||||
|
'Infra Monitoring: Namespaces list details traces filters applied',
|
||||||
|
{
|
||||||
|
namespace: namespace?.namespaceName,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
op: 'AND',
|
||||||
|
items: [
|
||||||
|
...primaryFilters,
|
||||||
|
...value.items.filter(
|
||||||
|
(item) => item.key?.key !== QUERY_KEYS.K8S_NAMESPACE_NAME,
|
||||||
|
),
|
||||||
|
].filter((item): item is TagFilterItem => item !== undefined),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
},
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
[],
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleChangeEventsFilters = useCallback(
|
||||||
|
(value: IBuilderQuery['filters']) => {
|
||||||
|
setEventsFilters((prevFilters) => {
|
||||||
|
const namespaceKindFilter = prevFilters.items.find(
|
||||||
|
(item) => item.key?.key === QUERY_KEYS.K8S_OBJECT_KIND,
|
||||||
|
);
|
||||||
|
const namespaceNameFilter = prevFilters.items.find(
|
||||||
|
(item) => item.key?.key === QUERY_KEYS.K8S_OBJECT_NAME,
|
||||||
|
);
|
||||||
|
|
||||||
|
logEvent(
|
||||||
|
'Infra Monitoring: Namespaces list details events filters applied',
|
||||||
|
{
|
||||||
|
namespace: namespace?.namespaceName,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
op: 'AND',
|
||||||
|
items: [
|
||||||
|
namespaceKindFilter,
|
||||||
|
namespaceNameFilter,
|
||||||
|
...value.items.filter(
|
||||||
|
(item) =>
|
||||||
|
item.key?.key !== QUERY_KEYS.K8S_OBJECT_KIND &&
|
||||||
|
item.key?.key !== QUERY_KEYS.K8S_OBJECT_NAME,
|
||||||
|
),
|
||||||
|
].filter((item): item is TagFilterItem => item !== undefined),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
},
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
[],
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleExplorePagesRedirect = (): void => {
|
||||||
|
if (selectedInterval !== 'custom') {
|
||||||
|
urlQuery.set(QueryParams.relativeTime, selectedInterval);
|
||||||
|
} else {
|
||||||
|
urlQuery.delete(QueryParams.relativeTime);
|
||||||
|
urlQuery.set(QueryParams.startTime, modalTimeRange.startTime.toString());
|
||||||
|
urlQuery.set(QueryParams.endTime, modalTimeRange.endTime.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
logEvent('Infra Monitoring: Namespaces list details explore clicked', {
|
||||||
|
namespace: namespace?.namespaceName,
|
||||||
|
view: selectedView,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (selectedView === VIEW_TYPES.LOGS) {
|
||||||
|
const filtersWithoutPagination = {
|
||||||
|
...logFilters,
|
||||||
|
items: logFilters.items.filter((item) => item.key?.key !== 'id'),
|
||||||
|
};
|
||||||
|
|
||||||
|
const compositeQuery = {
|
||||||
|
...initialQueryState,
|
||||||
|
queryType: 'builder',
|
||||||
|
builder: {
|
||||||
|
...initialQueryState.builder,
|
||||||
|
queryData: [
|
||||||
|
{
|
||||||
|
...initialQueryBuilderFormValuesMap.logs,
|
||||||
|
aggregateOperator: LogsAggregatorOperator.NOOP,
|
||||||
|
filters: filtersWithoutPagination,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
urlQuery.set('compositeQuery', JSON.stringify(compositeQuery));
|
||||||
|
|
||||||
|
window.open(
|
||||||
|
`${window.location.origin}${ROUTES.LOGS_EXPLORER}?${urlQuery.toString()}`,
|
||||||
|
'_blank',
|
||||||
|
);
|
||||||
|
} else if (selectedView === VIEW_TYPES.TRACES) {
|
||||||
|
const compositeQuery = {
|
||||||
|
...initialQueryState,
|
||||||
|
queryType: 'builder',
|
||||||
|
builder: {
|
||||||
|
...initialQueryState.builder,
|
||||||
|
queryData: [
|
||||||
|
{
|
||||||
|
...initialQueryBuilderFormValuesMap.traces,
|
||||||
|
aggregateOperator: TracesAggregatorOperator.NOOP,
|
||||||
|
filters: tracesFilters,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
urlQuery.set('compositeQuery', JSON.stringify(compositeQuery));
|
||||||
|
|
||||||
|
window.open(
|
||||||
|
`${window.location.origin}${ROUTES.TRACES_EXPLORER}?${urlQuery.toString()}`,
|
||||||
|
'_blank',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleClose = (): void => {
|
||||||
|
setSelectedInterval(selectedTime as Time);
|
||||||
|
|
||||||
|
if (selectedTime !== 'custom') {
|
||||||
|
const { maxTime, minTime } = GetMinMax(selectedTime);
|
||||||
|
|
||||||
|
setModalTimeRange({
|
||||||
|
startTime: Math.floor(minTime / 1000000000),
|
||||||
|
endTime: Math.floor(maxTime / 1000000000),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
setSelectedView(VIEW_TYPES.METRICS);
|
||||||
|
onClose();
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Drawer
|
||||||
|
width="70%"
|
||||||
|
title={
|
||||||
|
<>
|
||||||
|
<Divider type="vertical" />
|
||||||
|
<Typography.Text className="title">
|
||||||
|
{namespace?.namespaceName}
|
||||||
|
</Typography.Text>
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
placement="right"
|
||||||
|
onClose={handleClose}
|
||||||
|
open={!!namespace}
|
||||||
|
style={{
|
||||||
|
overscrollBehavior: 'contain',
|
||||||
|
background: isDarkMode ? Color.BG_INK_400 : Color.BG_VANILLA_100,
|
||||||
|
}}
|
||||||
|
className="entity-detail-drawer"
|
||||||
|
destroyOnClose
|
||||||
|
closeIcon={<X size={16} style={{ marginTop: Spacing.MARGIN_1 }} />}
|
||||||
|
>
|
||||||
|
{namespace && (
|
||||||
|
<>
|
||||||
|
<div className="entity-detail-drawer__entity">
|
||||||
|
<div className="entity-details-grid">
|
||||||
|
<div className="labels-row">
|
||||||
|
<Typography.Text
|
||||||
|
type="secondary"
|
||||||
|
className="entity-details-metadata-label"
|
||||||
|
>
|
||||||
|
Namespace Name
|
||||||
|
</Typography.Text>
|
||||||
|
<Typography.Text
|
||||||
|
type="secondary"
|
||||||
|
className="entity-details-metadata-label"
|
||||||
|
>
|
||||||
|
Cluster Name
|
||||||
|
</Typography.Text>
|
||||||
|
</div>
|
||||||
|
<div className="values-row">
|
||||||
|
<Typography.Text className="entity-details-metadata-value">
|
||||||
|
<Tooltip title={namespace.namespaceName}>
|
||||||
|
{namespace.namespaceName}
|
||||||
|
</Tooltip>
|
||||||
|
</Typography.Text>
|
||||||
|
<Typography.Text className="entity-details-metadata-value">
|
||||||
|
<Tooltip title="Cluster name">
|
||||||
|
{namespace.meta.k8s_cluster_name}
|
||||||
|
</Tooltip>
|
||||||
|
</Typography.Text>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="views-tabs-container">
|
||||||
|
<Radio.Group
|
||||||
|
className="views-tabs"
|
||||||
|
onChange={handleTabChange}
|
||||||
|
value={selectedView}
|
||||||
|
>
|
||||||
|
<Radio.Button
|
||||||
|
className={
|
||||||
|
// eslint-disable-next-line sonarjs/no-duplicate-string
|
||||||
|
selectedView === VIEW_TYPES.METRICS ? 'selected_view tab' : 'tab'
|
||||||
|
}
|
||||||
|
value={VIEW_TYPES.METRICS}
|
||||||
|
>
|
||||||
|
<div className="view-title">
|
||||||
|
<BarChart2 size={14} />
|
||||||
|
Metrics
|
||||||
|
</div>
|
||||||
|
</Radio.Button>
|
||||||
|
<Radio.Button
|
||||||
|
className={
|
||||||
|
selectedView === VIEW_TYPES.LOGS ? 'selected_view tab' : 'tab'
|
||||||
|
}
|
||||||
|
value={VIEW_TYPES.LOGS}
|
||||||
|
>
|
||||||
|
<div className="view-title">
|
||||||
|
<ScrollText size={14} />
|
||||||
|
Logs
|
||||||
|
</div>
|
||||||
|
</Radio.Button>
|
||||||
|
<Radio.Button
|
||||||
|
className={
|
||||||
|
selectedView === VIEW_TYPES.TRACES ? 'selected_view tab' : 'tab'
|
||||||
|
}
|
||||||
|
value={VIEW_TYPES.TRACES}
|
||||||
|
>
|
||||||
|
<div className="view-title">
|
||||||
|
<DraftingCompass size={14} />
|
||||||
|
Traces
|
||||||
|
</div>
|
||||||
|
</Radio.Button>
|
||||||
|
<Radio.Button
|
||||||
|
className={
|
||||||
|
selectedView === VIEW_TYPES.EVENTS ? 'selected_view tab' : 'tab'
|
||||||
|
}
|
||||||
|
value={VIEW_TYPES.EVENTS}
|
||||||
|
>
|
||||||
|
<div className="view-title">
|
||||||
|
<ChevronsLeftRight size={14} />
|
||||||
|
Events
|
||||||
|
</div>
|
||||||
|
</Radio.Button>
|
||||||
|
</Radio.Group>
|
||||||
|
|
||||||
|
{(selectedView === VIEW_TYPES.LOGS ||
|
||||||
|
selectedView === VIEW_TYPES.TRACES) && (
|
||||||
|
<Button
|
||||||
|
icon={<Compass size={18} />}
|
||||||
|
className="compass-button"
|
||||||
|
onClick={handleExplorePagesRedirect}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
{selectedView === VIEW_TYPES.METRICS && (
|
||||||
|
<NamespaceMetrics<K8sNamespacesData>
|
||||||
|
timeRange={modalTimeRange}
|
||||||
|
isModalTimeSelection={isModalTimeSelection}
|
||||||
|
handleTimeChange={handleTimeChange}
|
||||||
|
selectedInterval={selectedInterval}
|
||||||
|
entity={namespace}
|
||||||
|
entityWidgetInfo={namespaceWidgetInfo}
|
||||||
|
getEntityQueryPayload={getNamespaceMetricsQueryPayload}
|
||||||
|
category={K8sCategory.NAMESPACES}
|
||||||
|
queryKey="namespaceMetrics"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{selectedView === VIEW_TYPES.LOGS && (
|
||||||
|
<NamespaceLogs
|
||||||
|
timeRange={modalTimeRange}
|
||||||
|
isModalTimeSelection={isModalTimeSelection}
|
||||||
|
handleTimeChange={handleTimeChange}
|
||||||
|
handleChangeLogFilters={handleChangeLogFilters}
|
||||||
|
logFilters={logFilters}
|
||||||
|
selectedInterval={selectedInterval}
|
||||||
|
queryKey="namespaceLogs"
|
||||||
|
category={K8sCategory.NAMESPACES}
|
||||||
|
queryKeyFilters={[QUERY_KEYS.K8S_NAMESPACE_NAME]}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{selectedView === VIEW_TYPES.TRACES && (
|
||||||
|
<NamespaceTraces
|
||||||
|
timeRange={modalTimeRange}
|
||||||
|
isModalTimeSelection={isModalTimeSelection}
|
||||||
|
handleTimeChange={handleTimeChange}
|
||||||
|
handleChangeTracesFilters={handleChangeTracesFilters}
|
||||||
|
tracesFilters={tracesFilters}
|
||||||
|
selectedInterval={selectedInterval}
|
||||||
|
queryKey="namespaceTraces"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{selectedView === VIEW_TYPES.EVENTS && (
|
||||||
|
<NamespaceEvents
|
||||||
|
timeRange={modalTimeRange}
|
||||||
|
handleChangeEventFilters={handleChangeEventsFilters}
|
||||||
|
filters={eventsFilters}
|
||||||
|
isModalTimeSelection={isModalTimeSelection}
|
||||||
|
handleTimeChange={handleTimeChange}
|
||||||
|
selectedInterval={selectedInterval}
|
||||||
|
category={K8sCategory.NAMESPACES}
|
||||||
|
queryKey="namespaceEvents"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</Drawer>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default NamespaceDetails;
|
||||||
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,3 @@
|
|||||||
|
import NamespaceDetails from './NamespaceDetails';
|
||||||
|
|
||||||
|
export default NamespaceDetails;
|
||||||
169
frontend/src/container/InfraMonitoringK8s/Namespaces/utils.tsx
Normal file
169
frontend/src/container/InfraMonitoringK8s/Namespaces/utils.tsx
Normal file
@ -0,0 +1,169 @@
|
|||||||
|
import { Color } from '@signozhq/design-tokens';
|
||||||
|
import { Tag } from 'antd';
|
||||||
|
import { ColumnType } from 'antd/es/table';
|
||||||
|
import {
|
||||||
|
K8sNamespacesData,
|
||||||
|
K8sNamespacesListPayload,
|
||||||
|
} from 'api/infraMonitoring/getK8sNamespacesList';
|
||||||
|
import { Group } from 'lucide-react';
|
||||||
|
import { IBuilderQuery } from 'types/api/queryBuilder/queryBuilderData';
|
||||||
|
|
||||||
|
import { formatBytes, ValidateColumnValueWrapper } from '../commonUtils';
|
||||||
|
import { IEntityColumn } from '../utils';
|
||||||
|
|
||||||
|
export const defaultAddedColumns: IEntityColumn[] = [
|
||||||
|
{
|
||||||
|
label: 'Namespace Name',
|
||||||
|
value: 'namespaceName',
|
||||||
|
id: 'namespaceName',
|
||||||
|
canRemove: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Cluster Name',
|
||||||
|
value: 'clusterName',
|
||||||
|
id: 'clusterName',
|
||||||
|
canRemove: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'CPU Utilization (cores)',
|
||||||
|
value: 'cpu',
|
||||||
|
id: 'cpu',
|
||||||
|
canRemove: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Memory Utilization (bytes)',
|
||||||
|
value: 'memory',
|
||||||
|
id: 'memory',
|
||||||
|
canRemove: false,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
export interface K8sNamespacesRowData {
|
||||||
|
key: string;
|
||||||
|
namespaceUID: string;
|
||||||
|
namespaceName: string;
|
||||||
|
clusterName: string;
|
||||||
|
cpu: React.ReactNode;
|
||||||
|
memory: React.ReactNode;
|
||||||
|
groupedByMeta?: any;
|
||||||
|
}
|
||||||
|
|
||||||
|
const namespaceGroupColumnConfig = {
|
||||||
|
title: (
|
||||||
|
<div className="column-header entity-group-header">
|
||||||
|
<Group size={14} /> NAMESPACE GROUP
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
dataIndex: 'namespaceGroup',
|
||||||
|
key: 'namespaceGroup',
|
||||||
|
ellipsis: true,
|
||||||
|
width: 150,
|
||||||
|
align: 'left',
|
||||||
|
sorter: false,
|
||||||
|
className: 'column entity-group-header',
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getK8sNamespacesListQuery = (): K8sNamespacesListPayload => ({
|
||||||
|
filters: {
|
||||||
|
items: [],
|
||||||
|
op: 'and',
|
||||||
|
},
|
||||||
|
orderBy: { columnName: 'cpu', order: 'desc' },
|
||||||
|
});
|
||||||
|
|
||||||
|
const columnsConfig = [
|
||||||
|
{
|
||||||
|
title: <div className="column-header-left">Namespace Name</div>,
|
||||||
|
dataIndex: 'namespaceName',
|
||||||
|
key: 'namespaceName',
|
||||||
|
ellipsis: true,
|
||||||
|
width: 120,
|
||||||
|
sorter: false,
|
||||||
|
align: 'left',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: <div className="column-header-left">Cluster Name</div>,
|
||||||
|
dataIndex: 'clusterName',
|
||||||
|
key: 'clusterName',
|
||||||
|
ellipsis: true,
|
||||||
|
width: 120,
|
||||||
|
sorter: false,
|
||||||
|
align: 'left',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: <div className="column-header-left">CPU Usage (cores)</div>,
|
||||||
|
dataIndex: 'cpu',
|
||||||
|
key: 'cpu',
|
||||||
|
width: 100,
|
||||||
|
sorter: true,
|
||||||
|
align: 'left',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: <div className="column-header-left">Mem Usage</div>,
|
||||||
|
dataIndex: 'memory',
|
||||||
|
key: 'memory',
|
||||||
|
width: 80,
|
||||||
|
sorter: true,
|
||||||
|
align: 'left',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
export const getK8sNamespacesListColumns = (
|
||||||
|
groupBy: IBuilderQuery['groupBy'],
|
||||||
|
): ColumnType<K8sNamespacesRowData>[] => {
|
||||||
|
if (groupBy.length > 0) {
|
||||||
|
const filteredColumns = [...columnsConfig].filter(
|
||||||
|
(column) => column.key !== 'namespaceName',
|
||||||
|
);
|
||||||
|
filteredColumns.unshift(namespaceGroupColumnConfig);
|
||||||
|
return filteredColumns as ColumnType<K8sNamespacesRowData>[];
|
||||||
|
}
|
||||||
|
|
||||||
|
return columnsConfig as ColumnType<K8sNamespacesRowData>[];
|
||||||
|
};
|
||||||
|
|
||||||
|
const getGroupByEle = (
|
||||||
|
namespace: K8sNamespacesData,
|
||||||
|
groupBy: IBuilderQuery['groupBy'],
|
||||||
|
): React.ReactNode => {
|
||||||
|
const groupByValues: string[] = [];
|
||||||
|
|
||||||
|
groupBy.forEach((group) => {
|
||||||
|
groupByValues.push(namespace.meta[group.key as keyof typeof namespace.meta]);
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="pod-group">
|
||||||
|
{groupByValues.map((value) => (
|
||||||
|
<Tag key={value} color={Color.BG_SLATE_400} className="pod-group-tag-item">
|
||||||
|
{value === '' ? '<no-value>' : value}
|
||||||
|
</Tag>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const formatDataForTable = (
|
||||||
|
data: K8sNamespacesData[],
|
||||||
|
groupBy: IBuilderQuery['groupBy'],
|
||||||
|
): K8sNamespacesRowData[] =>
|
||||||
|
data.map((namespace, index) => ({
|
||||||
|
key: index.toString(),
|
||||||
|
namespaceUID: namespace.namespaceName,
|
||||||
|
namespaceName: namespace.namespaceName,
|
||||||
|
clusterName: namespace.meta.k8s_cluster_name,
|
||||||
|
cpu: (
|
||||||
|
<ValidateColumnValueWrapper value={namespace.cpuUsage}>
|
||||||
|
{namespace.cpuUsage}
|
||||||
|
</ValidateColumnValueWrapper>
|
||||||
|
),
|
||||||
|
memory: (
|
||||||
|
<ValidateColumnValueWrapper value={namespace.memoryUsage}>
|
||||||
|
{formatBytes(namespace.memoryUsage)}
|
||||||
|
</ValidateColumnValueWrapper>
|
||||||
|
),
|
||||||
|
namespaceGroup: getGroupByEle(namespace, groupBy),
|
||||||
|
meta: namespace.meta,
|
||||||
|
...namespace.meta,
|
||||||
|
groupedByMeta: namespace.meta,
|
||||||
|
}));
|
||||||
@ -406,7 +406,8 @@ function K8sNodesList({
|
|||||||
groupBy.push(key);
|
groupBy.push(key);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// Reset pagination on switching to groupBy
|
||||||
|
setCurrentPage(1);
|
||||||
setGroupBy(groupBy);
|
setGroupBy(groupBy);
|
||||||
setExpandedRowKeys([]);
|
setExpandedRowKeys([]);
|
||||||
},
|
},
|
||||||
|
|||||||
@ -1,16 +0,0 @@
|
|||||||
import { Color } from '@signozhq/design-tokens';
|
|
||||||
import { Typography } from 'antd';
|
|
||||||
import { Ghost } from 'lucide-react';
|
|
||||||
|
|
||||||
const { Text } = Typography;
|
|
||||||
|
|
||||||
export default function NoEventsContainer(): React.ReactElement {
|
|
||||||
return (
|
|
||||||
<div className="no-logs-found">
|
|
||||||
<Text type="secondary">
|
|
||||||
<Ghost size={24} color={Color.BG_AMBER_500} /> No events found for this node
|
|
||||||
in the selected time range.
|
|
||||||
</Text>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@ -1,289 +0,0 @@
|
|||||||
.node-events-container {
|
|
||||||
margin-top: 1rem;
|
|
||||||
|
|
||||||
.filter-section {
|
|
||||||
flex: 1;
|
|
||||||
|
|
||||||
.ant-select-selector {
|
|
||||||
border-radius: 2px;
|
|
||||||
border: 1px solid var(--bg-slate-400) !important;
|
|
||||||
background-color: var(--bg-ink-300) !important;
|
|
||||||
|
|
||||||
input {
|
|
||||||
font-size: 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.ant-tag .ant-typography {
|
|
||||||
font-size: 12px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.node-events-header {
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
gap: 8px;
|
|
||||||
|
|
||||||
padding: 12px;
|
|
||||||
border-radius: 3px;
|
|
||||||
border: 1px solid var(--bg-slate-500);
|
|
||||||
}
|
|
||||||
|
|
||||||
.node-events {
|
|
||||||
margin-top: 1rem;
|
|
||||||
|
|
||||||
.virtuoso-list {
|
|
||||||
overflow-y: hidden !important;
|
|
||||||
|
|
||||||
&::-webkit-scrollbar {
|
|
||||||
width: 0.3rem;
|
|
||||||
height: 0.3rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
&::-webkit-scrollbar-track {
|
|
||||||
background: transparent;
|
|
||||||
}
|
|
||||||
|
|
||||||
&::-webkit-scrollbar-thumb {
|
|
||||||
background: var(--bg-slate-300);
|
|
||||||
}
|
|
||||||
|
|
||||||
&::-webkit-scrollbar-thumb:hover {
|
|
||||||
background: var(--bg-slate-200);
|
|
||||||
}
|
|
||||||
|
|
||||||
.ant-row {
|
|
||||||
width: fit-content;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.skeleton-container {
|
|
||||||
height: 100%;
|
|
||||||
padding: 16px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.ant-table {
|
|
||||||
.ant-table-thead > tr > th {
|
|
||||||
padding: 12px;
|
|
||||||
font-weight: 500;
|
|
||||||
font-size: 12px;
|
|
||||||
line-height: 18px;
|
|
||||||
|
|
||||||
background: rgb(18, 19, 23);
|
|
||||||
border-bottom: none;
|
|
||||||
|
|
||||||
color: var(--Vanilla-400, #c0c1c3);
|
|
||||||
font-family: Inter;
|
|
||||||
font-size: 11px;
|
|
||||||
font-style: normal;
|
|
||||||
font-weight: 600;
|
|
||||||
line-height: 18px; /* 163.636% */
|
|
||||||
letter-spacing: 0.44px;
|
|
||||||
text-transform: uppercase;
|
|
||||||
|
|
||||||
&::before {
|
|
||||||
background-color: transparent;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.ant-table-thead > tr > th:has(.nodename-column-header) {
|
|
||||||
background: var(--bg-ink-400);
|
|
||||||
}
|
|
||||||
|
|
||||||
.ant-table-cell {
|
|
||||||
padding: 12px;
|
|
||||||
font-size: 13px;
|
|
||||||
line-height: 20px;
|
|
||||||
color: var(--bg-vanilla-100);
|
|
||||||
background: rgb(18, 19, 23);
|
|
||||||
border-bottom: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.ant-table-cell:has(.nodename-column-value) {
|
|
||||||
background: var(--bg-ink-400);
|
|
||||||
}
|
|
||||||
|
|
||||||
.nodename-column-value {
|
|
||||||
color: var(--bg-vanilla-100);
|
|
||||||
font-family: 'Geist Mono';
|
|
||||||
font-style: normal;
|
|
||||||
font-weight: 600;
|
|
||||||
line-height: 20px; /* 142.857% */
|
|
||||||
letter-spacing: -0.07px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.status-cell {
|
|
||||||
.active-tag {
|
|
||||||
color: var(--bg-forest-500);
|
|
||||||
padding: 4px 8px;
|
|
||||||
border-radius: 4px;
|
|
||||||
font-size: 12px;
|
|
||||||
font-weight: 500;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.progress-container {
|
|
||||||
.ant-progress-bg {
|
|
||||||
height: 8px !important;
|
|
||||||
border-radius: 4px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.ant-table-tbody > tr:hover > td {
|
|
||||||
background: rgba(255, 255, 255, 0.04);
|
|
||||||
}
|
|
||||||
|
|
||||||
.ant-table-cell:first-child {
|
|
||||||
text-align: justify;
|
|
||||||
}
|
|
||||||
|
|
||||||
.ant-table-cell:nth-child(2) {
|
|
||||||
padding-left: 16px;
|
|
||||||
padding-right: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.ant-table-cell:nth-child(n + 3) {
|
|
||||||
padding-right: 24px;
|
|
||||||
}
|
|
||||||
.column-header-right {
|
|
||||||
text-align: right;
|
|
||||||
}
|
|
||||||
.ant-table-tbody > tr > td {
|
|
||||||
border-bottom: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.ant-table-thead
|
|
||||||
> tr
|
|
||||||
> th:not(:last-child):not(.ant-table-selection-column):not(.ant-table-row-expand-icon-cell):not([colspan])::before {
|
|
||||||
background-color: transparent;
|
|
||||||
}
|
|
||||||
|
|
||||||
.ant-empty-normal {
|
|
||||||
visibility: hidden;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.ant-pagination {
|
|
||||||
position: fixed;
|
|
||||||
bottom: 0;
|
|
||||||
width: calc(100% - 64px);
|
|
||||||
background: rgb(18, 19, 23);
|
|
||||||
padding: 16px;
|
|
||||||
margin: 0;
|
|
||||||
|
|
||||||
// this is to offset intercom icon till we improve the design
|
|
||||||
padding-right: 72px;
|
|
||||||
|
|
||||||
.ant-pagination-item {
|
|
||||||
border-radius: 4px;
|
|
||||||
|
|
||||||
&-active {
|
|
||||||
background: var(--bg-robin-500);
|
|
||||||
border-color: var(--bg-robin-500);
|
|
||||||
|
|
||||||
a {
|
|
||||||
color: var(--bg-ink-500) !important;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.node-events-list-container {
|
|
||||||
flex: 1;
|
|
||||||
height: calc(100vh - 272px) !important;
|
|
||||||
display: flex;
|
|
||||||
height: 100%;
|
|
||||||
|
|
||||||
.raw-log-content {
|
|
||||||
width: 100%;
|
|
||||||
text-wrap: inherit;
|
|
||||||
word-wrap: break-word;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.node-events-list-card {
|
|
||||||
width: 100%;
|
|
||||||
margin-top: 12px;
|
|
||||||
|
|
||||||
.ant-table-wrapper {
|
|
||||||
height: 100%;
|
|
||||||
overflow-y: auto;
|
|
||||||
|
|
||||||
&::-webkit-scrollbar {
|
|
||||||
width: 0.3rem;
|
|
||||||
height: 0.3rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
&::-webkit-scrollbar-track {
|
|
||||||
background: transparent;
|
|
||||||
}
|
|
||||||
|
|
||||||
&::-webkit-scrollbar-thumb {
|
|
||||||
background: var(--bg-slate-300);
|
|
||||||
}
|
|
||||||
|
|
||||||
&::-webkit-scrollbar-thumb:hover {
|
|
||||||
background: var(--bg-slate-200);
|
|
||||||
}
|
|
||||||
|
|
||||||
.ant-row {
|
|
||||||
width: fit-content;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.ant-card-body {
|
|
||||||
padding: 0;
|
|
||||||
|
|
||||||
height: 100%;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.logs-loading-skeleton {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
gap: 8px;
|
|
||||||
padding: 8px 0;
|
|
||||||
|
|
||||||
.ant-skeleton-input-sm {
|
|
||||||
height: 18px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.no-logs-found {
|
|
||||||
height: 50vh;
|
|
||||||
width: 100%;
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
|
|
||||||
padding: 24px;
|
|
||||||
box-sizing: border-box;
|
|
||||||
|
|
||||||
.ant-typography {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 16px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.lightMode {
|
|
||||||
.filter-section {
|
|
||||||
border-top: 1px solid var(--bg-vanilla-300);
|
|
||||||
border-bottom: 1px solid var(--bg-vanilla-300);
|
|
||||||
|
|
||||||
.ant-select-selector {
|
|
||||||
border-color: var(--bg-vanilla-300) !important;
|
|
||||||
background-color: var(--bg-vanilla-100) !important;
|
|
||||||
color: var(--bg-ink-200);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.periscope-btn-icon {
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
@ -1,65 +0,0 @@
|
|||||||
import { PANEL_TYPES } from 'constants/queryBuilder';
|
|
||||||
import { GetQueryResultsProps } from 'lib/dashboard/getQueryResults';
|
|
||||||
import { DataTypes } from 'types/api/queryBuilder/queryAutocompleteResponse';
|
|
||||||
import { IBuilderQuery } from 'types/api/queryBuilder/queryBuilderData';
|
|
||||||
import { EQueryType } from 'types/common/dashboard';
|
|
||||||
import { DataSource } from 'types/common/queryBuilder';
|
|
||||||
import { v4 as uuidv4 } from 'uuid';
|
|
||||||
|
|
||||||
export const getNodesEventsQueryPayload = (
|
|
||||||
start: number,
|
|
||||||
end: number,
|
|
||||||
filters: IBuilderQuery['filters'],
|
|
||||||
): GetQueryResultsProps => ({
|
|
||||||
graphType: PANEL_TYPES.LIST,
|
|
||||||
selectedTime: 'GLOBAL_TIME',
|
|
||||||
query: {
|
|
||||||
clickhouse_sql: [],
|
|
||||||
promql: [],
|
|
||||||
builder: {
|
|
||||||
queryData: [
|
|
||||||
{
|
|
||||||
dataSource: DataSource.LOGS,
|
|
||||||
queryName: 'A',
|
|
||||||
aggregateOperator: 'noop',
|
|
||||||
aggregateAttribute: {
|
|
||||||
id: '------false',
|
|
||||||
dataType: DataTypes.String,
|
|
||||||
key: '',
|
|
||||||
isColumn: false,
|
|
||||||
type: '',
|
|
||||||
isJSON: false,
|
|
||||||
},
|
|
||||||
timeAggregation: 'rate',
|
|
||||||
spaceAggregation: 'sum',
|
|
||||||
functions: [],
|
|
||||||
filters,
|
|
||||||
expression: 'A',
|
|
||||||
disabled: false,
|
|
||||||
stepInterval: 60,
|
|
||||||
having: [],
|
|
||||||
limit: null,
|
|
||||||
orderBy: [
|
|
||||||
{
|
|
||||||
columnName: 'timestamp',
|
|
||||||
order: 'desc',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
groupBy: [],
|
|
||||||
legend: '',
|
|
||||||
reduceTo: 'avg',
|
|
||||||
offset: 0,
|
|
||||||
pageSize: 100,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
queryFormulas: [],
|
|
||||||
},
|
|
||||||
id: uuidv4(),
|
|
||||||
queryType: EQueryType.QUERY_BUILDER,
|
|
||||||
},
|
|
||||||
params: {
|
|
||||||
lastLogLineTimestamp: null,
|
|
||||||
},
|
|
||||||
start,
|
|
||||||
end,
|
|
||||||
});
|
|
||||||
@ -1,3 +0,0 @@
|
|||||||
import NodeEvents from './NodeEvents';
|
|
||||||
|
|
||||||
export default NodeEvents;
|
|
||||||
@ -1,16 +0,0 @@
|
|||||||
import { Color } from '@signozhq/design-tokens';
|
|
||||||
import { Typography } from 'antd';
|
|
||||||
import { Ghost } from 'lucide-react';
|
|
||||||
|
|
||||||
const { Text } = Typography;
|
|
||||||
|
|
||||||
export default function NoLogsContainer(): React.ReactElement {
|
|
||||||
return (
|
|
||||||
<div className="no-logs-found">
|
|
||||||
<Text type="secondary">
|
|
||||||
<Ghost size={24} color={Color.BG_AMBER_500} /> No logs found for this node
|
|
||||||
in the selected time range.
|
|
||||||
</Text>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@ -1,65 +0,0 @@
|
|||||||
import { PANEL_TYPES } from 'constants/queryBuilder';
|
|
||||||
import { GetQueryResultsProps } from 'lib/dashboard/getQueryResults';
|
|
||||||
import { DataTypes } from 'types/api/queryBuilder/queryAutocompleteResponse';
|
|
||||||
import { IBuilderQuery } from 'types/api/queryBuilder/queryBuilderData';
|
|
||||||
import { EQueryType } from 'types/common/dashboard';
|
|
||||||
import { DataSource } from 'types/common/queryBuilder';
|
|
||||||
import { v4 as uuidv4 } from 'uuid';
|
|
||||||
|
|
||||||
export const getNodeLogsQueryPayload = (
|
|
||||||
start: number,
|
|
||||||
end: number,
|
|
||||||
filters: IBuilderQuery['filters'],
|
|
||||||
): GetQueryResultsProps => ({
|
|
||||||
graphType: PANEL_TYPES.LIST,
|
|
||||||
selectedTime: 'GLOBAL_TIME',
|
|
||||||
query: {
|
|
||||||
clickhouse_sql: [],
|
|
||||||
promql: [],
|
|
||||||
builder: {
|
|
||||||
queryData: [
|
|
||||||
{
|
|
||||||
dataSource: DataSource.LOGS,
|
|
||||||
queryName: 'A',
|
|
||||||
aggregateOperator: 'noop',
|
|
||||||
aggregateAttribute: {
|
|
||||||
id: '------false',
|
|
||||||
dataType: DataTypes.String,
|
|
||||||
key: '',
|
|
||||||
isColumn: false,
|
|
||||||
type: '',
|
|
||||||
isJSON: false,
|
|
||||||
},
|
|
||||||
timeAggregation: 'rate',
|
|
||||||
spaceAggregation: 'sum',
|
|
||||||
functions: [],
|
|
||||||
filters,
|
|
||||||
expression: 'A',
|
|
||||||
disabled: false,
|
|
||||||
stepInterval: 60,
|
|
||||||
having: [],
|
|
||||||
limit: null,
|
|
||||||
orderBy: [
|
|
||||||
{
|
|
||||||
columnName: 'timestamp',
|
|
||||||
order: 'desc',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
groupBy: [],
|
|
||||||
legend: '',
|
|
||||||
reduceTo: 'avg',
|
|
||||||
offset: 0,
|
|
||||||
pageSize: 100,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
queryFormulas: [],
|
|
||||||
},
|
|
||||||
id: uuidv4(),
|
|
||||||
queryType: EQueryType.QUERY_BUILDER,
|
|
||||||
},
|
|
||||||
params: {
|
|
||||||
lastLogLineTimestamp: null,
|
|
||||||
},
|
|
||||||
start,
|
|
||||||
end,
|
|
||||||
});
|
|
||||||
@ -1,3 +0,0 @@
|
|||||||
import NodeLogs from './NodeLogsDetailedView';
|
|
||||||
|
|
||||||
export default NodeLogs;
|
|
||||||
@ -1,45 +0,0 @@
|
|||||||
.empty-container {
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
height: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.node-metrics-container {
|
|
||||||
margin-top: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.metrics-header {
|
|
||||||
display: flex;
|
|
||||||
justify-content: flex-end;
|
|
||||||
margin-top: 1rem;
|
|
||||||
|
|
||||||
gap: 8px;
|
|
||||||
padding: 12px;
|
|
||||||
border-radius: 3px;
|
|
||||||
border: 1px solid var(--bg-slate-500);
|
|
||||||
}
|
|
||||||
|
|
||||||
.node-metrics-card {
|
|
||||||
margin: 8px 0 1rem 0;
|
|
||||||
height: 300px;
|
|
||||||
padding: 10px;
|
|
||||||
|
|
||||||
border: 1px solid var(--bg-slate-500);
|
|
||||||
|
|
||||||
.ant-card-body {
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.chart-container {
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.no-data-container {
|
|
||||||
position: absolute;
|
|
||||||
top: 50%;
|
|
||||||
left: 50%;
|
|
||||||
transform: translate(-50%, -50%);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,140 +0,0 @@
|
|||||||
import './NodeMetrics.styles.scss';
|
|
||||||
|
|
||||||
import { Card, Col, Row, Skeleton, Typography } from 'antd';
|
|
||||||
import { K8sNodesData } from 'api/infraMonitoring/getK8sNodesList';
|
|
||||||
import cx from 'classnames';
|
|
||||||
import Uplot from 'components/Uplot';
|
|
||||||
import { ENTITY_VERSION_V4 } from 'constants/app';
|
|
||||||
import DateTimeSelectionV2 from 'container/TopNav/DateTimeSelectionV2';
|
|
||||||
import {
|
|
||||||
CustomTimeType,
|
|
||||||
Time,
|
|
||||||
} from 'container/TopNav/DateTimeSelectionV2/config';
|
|
||||||
import { useIsDarkMode } from 'hooks/useDarkMode';
|
|
||||||
import { useResizeObserver } from 'hooks/useDimensions';
|
|
||||||
import { GetMetricQueryRange } from 'lib/dashboard/getQueryResults';
|
|
||||||
import { getUPlotChartOptions } from 'lib/uPlotLib/getUplotChartOptions';
|
|
||||||
import { getUPlotChartData } from 'lib/uPlotLib/utils/getUplotChartData';
|
|
||||||
import { useMemo, useRef } from 'react';
|
|
||||||
import { useQueries, UseQueryResult } from 'react-query';
|
|
||||||
import { SuccessResponse } from 'types/api';
|
|
||||||
import { MetricRangePayloadProps } from 'types/api/metrics/getQueryRange';
|
|
||||||
|
|
||||||
import { getNodeQueryPayload, nodeWidgetInfo } from './constants';
|
|
||||||
|
|
||||||
interface NodeMetricsProps {
|
|
||||||
timeRange: {
|
|
||||||
startTime: number;
|
|
||||||
endTime: number;
|
|
||||||
};
|
|
||||||
isModalTimeSelection: boolean;
|
|
||||||
handleTimeChange: (
|
|
||||||
interval: Time | CustomTimeType,
|
|
||||||
dateTimeRange?: [number, number],
|
|
||||||
) => void;
|
|
||||||
selectedInterval: Time;
|
|
||||||
node: K8sNodesData;
|
|
||||||
}
|
|
||||||
|
|
||||||
function NodeMetrics({
|
|
||||||
selectedInterval,
|
|
||||||
node,
|
|
||||||
timeRange,
|
|
||||||
handleTimeChange,
|
|
||||||
isModalTimeSelection,
|
|
||||||
}: NodeMetricsProps): JSX.Element {
|
|
||||||
const queryPayloads = useMemo(
|
|
||||||
() => getNodeQueryPayload(node, timeRange.startTime, timeRange.endTime),
|
|
||||||
[node, timeRange.startTime, timeRange.endTime],
|
|
||||||
);
|
|
||||||
|
|
||||||
const queries = useQueries(
|
|
||||||
queryPayloads.map((payload) => ({
|
|
||||||
queryKey: ['node-metrics', payload, ENTITY_VERSION_V4, 'NODE'],
|
|
||||||
queryFn: (): Promise<SuccessResponse<MetricRangePayloadProps>> =>
|
|
||||||
GetMetricQueryRange(payload, ENTITY_VERSION_V4),
|
|
||||||
enabled: !!payload,
|
|
||||||
})),
|
|
||||||
);
|
|
||||||
|
|
||||||
const isDarkMode = useIsDarkMode();
|
|
||||||
const graphRef = useRef<HTMLDivElement>(null);
|
|
||||||
const dimensions = useResizeObserver(graphRef);
|
|
||||||
|
|
||||||
const chartData = useMemo(
|
|
||||||
() => queries.map(({ data }) => getUPlotChartData(data?.payload)),
|
|
||||||
[queries],
|
|
||||||
);
|
|
||||||
|
|
||||||
const options = useMemo(
|
|
||||||
() =>
|
|
||||||
queries.map(({ data }, idx) =>
|
|
||||||
getUPlotChartOptions({
|
|
||||||
apiResponse: data?.payload,
|
|
||||||
isDarkMode,
|
|
||||||
dimensions,
|
|
||||||
yAxisUnit: nodeWidgetInfo[idx].yAxisUnit,
|
|
||||||
softMax: null,
|
|
||||||
softMin: null,
|
|
||||||
minTimeScale: timeRange.startTime,
|
|
||||||
maxTimeScale: timeRange.endTime,
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
[queries, isDarkMode, dimensions, timeRange.startTime, timeRange.endTime],
|
|
||||||
);
|
|
||||||
|
|
||||||
const renderCardContent = (
|
|
||||||
query: UseQueryResult<SuccessResponse<MetricRangePayloadProps>, unknown>,
|
|
||||||
idx: number,
|
|
||||||
): JSX.Element => {
|
|
||||||
if (query.isLoading) {
|
|
||||||
return <Skeleton />;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (query.error) {
|
|
||||||
const errorMessage =
|
|
||||||
(query.error as Error)?.message || 'Something went wrong';
|
|
||||||
return <div>{errorMessage}</div>;
|
|
||||||
}
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
className={cx('chart-container', {
|
|
||||||
'no-data-container':
|
|
||||||
!query.isLoading && !query?.data?.payload?.data?.result?.length,
|
|
||||||
})}
|
|
||||||
>
|
|
||||||
<Uplot options={options[idx]} data={chartData[idx]} />
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<div className="metrics-header">
|
|
||||||
<div className="metrics-datetime-section">
|
|
||||||
<DateTimeSelectionV2
|
|
||||||
showAutoRefresh={false}
|
|
||||||
showRefreshText={false}
|
|
||||||
hideShareModal
|
|
||||||
onTimeChange={handleTimeChange}
|
|
||||||
defaultRelativeTime="5m"
|
|
||||||
isModalTimeSelection={isModalTimeSelection}
|
|
||||||
modalSelectedInterval={selectedInterval}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<Row gutter={24} className="node-metrics-container">
|
|
||||||
{queries.map((query, idx) => (
|
|
||||||
<Col span={12} key={nodeWidgetInfo[idx].title}>
|
|
||||||
<Typography.Text>{nodeWidgetInfo[idx].title}</Typography.Text>
|
|
||||||
<Card bordered className="node-metrics-card" ref={graphRef}>
|
|
||||||
{renderCardContent(query, idx)}
|
|
||||||
</Card>
|
|
||||||
</Col>
|
|
||||||
))}
|
|
||||||
</Row>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default NodeMetrics;
|
|
||||||
@ -1,3 +0,0 @@
|
|||||||
import NodeMetrics from './NodeMetrics';
|
|
||||||
|
|
||||||
export default NodeMetrics;
|
|
||||||
@ -1,247 +0,0 @@
|
|||||||
.node-detail-drawer {
|
|
||||||
border-left: 1px solid var(--bg-slate-500);
|
|
||||||
background: var(--bg-ink-400);
|
|
||||||
box-shadow: -4px 10px 16px 2px rgba(0, 0, 0, 0.2);
|
|
||||||
|
|
||||||
.ant-drawer-header {
|
|
||||||
padding: 8px 16px;
|
|
||||||
border-bottom: none;
|
|
||||||
|
|
||||||
align-items: stretch;
|
|
||||||
|
|
||||||
border-bottom: 1px solid var(--bg-slate-500);
|
|
||||||
background: var(--bg-ink-400);
|
|
||||||
}
|
|
||||||
|
|
||||||
.ant-drawer-close {
|
|
||||||
margin-inline-end: 0px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.ant-drawer-body {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
padding: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.title {
|
|
||||||
color: var(--text-vanilla-400);
|
|
||||||
font-family: 'Geist Mono';
|
|
||||||
font-size: 14px;
|
|
||||||
font-style: normal;
|
|
||||||
font-weight: 500;
|
|
||||||
line-height: 20px; /* 142.857% */
|
|
||||||
letter-spacing: -0.07px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.radio-button {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
padding-top: var(--padding-1);
|
|
||||||
border: 1px solid var(--bg-slate-400);
|
|
||||||
background: var(--bg-ink-300);
|
|
||||||
box-shadow: 0px 0px 8px 0px rgba(0, 0, 0, 0.1);
|
|
||||||
}
|
|
||||||
|
|
||||||
.node-detail-drawer__node {
|
|
||||||
.node-details-grid {
|
|
||||||
.labels-row,
|
|
||||||
.values-row {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: 1fr 1.5fr 1.5fr 1.5fr;
|
|
||||||
gap: 30px;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.labels-row {
|
|
||||||
margin-bottom: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.node-details-metadata-label {
|
|
||||||
color: var(--text-vanilla-400);
|
|
||||||
font-family: Inter;
|
|
||||||
font-size: 11px;
|
|
||||||
font-style: normal;
|
|
||||||
font-weight: 500;
|
|
||||||
line-height: 18px; /* 163.636% */
|
|
||||||
letter-spacing: 0.44px;
|
|
||||||
text-transform: uppercase;
|
|
||||||
}
|
|
||||||
|
|
||||||
.node-details-metadata-value {
|
|
||||||
color: var(--text-vanilla-400);
|
|
||||||
font-family: 'Geist Mono';
|
|
||||||
font-size: 12px;
|
|
||||||
font-style: normal;
|
|
||||||
font-weight: 500;
|
|
||||||
line-height: 20px; /* 142.857% */
|
|
||||||
letter-spacing: -0.07px;
|
|
||||||
|
|
||||||
width: 100%;
|
|
||||||
white-space: nowrap;
|
|
||||||
overflow: hidden;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
}
|
|
||||||
|
|
||||||
.status-tag {
|
|
||||||
margin: 0;
|
|
||||||
|
|
||||||
&.active {
|
|
||||||
color: var(--success-500);
|
|
||||||
background: var(--success-100);
|
|
||||||
border-color: var(--success-500);
|
|
||||||
}
|
|
||||||
|
|
||||||
&.inactive {
|
|
||||||
color: var(--error-500);
|
|
||||||
background: var(--error-100);
|
|
||||||
border-color: var(--error-500);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.progress-container {
|
|
||||||
width: 158px;
|
|
||||||
.ant-progress {
|
|
||||||
margin: 0;
|
|
||||||
|
|
||||||
.ant-progress-text {
|
|
||||||
font-weight: 600;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.ant-card {
|
|
||||||
&.ant-card-bordered {
|
|
||||||
border: 1px solid var(--bg-slate-500) !important;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.tabs-and-search {
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
margin: 16px 0;
|
|
||||||
|
|
||||||
.action-btn {
|
|
||||||
border-radius: 2px;
|
|
||||||
border: 1px solid var(--bg-slate-400);
|
|
||||||
background: var(--bg-ink-300);
|
|
||||||
box-shadow: 0px 0px 8px 0px rgba(0, 0, 0, 0.1);
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.views-tabs-container {
|
|
||||||
margin-top: 1.5rem;
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
|
|
||||||
.views-tabs {
|
|
||||||
color: var(--text-vanilla-400);
|
|
||||||
|
|
||||||
.view-title {
|
|
||||||
display: flex;
|
|
||||||
gap: var(--margin-2);
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
font-size: var(--font-size-xs);
|
|
||||||
font-style: normal;
|
|
||||||
font-weight: var(--font-weight-normal);
|
|
||||||
}
|
|
||||||
|
|
||||||
.tab {
|
|
||||||
border: 1px solid var(--bg-slate-400);
|
|
||||||
width: 114px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tab::before {
|
|
||||||
background: var(--bg-slate-400);
|
|
||||||
}
|
|
||||||
|
|
||||||
.selected_view {
|
|
||||||
background: var(--bg-slate-300);
|
|
||||||
color: var(--text-vanilla-100);
|
|
||||||
border: 1px solid var(--bg-slate-400);
|
|
||||||
}
|
|
||||||
|
|
||||||
.selected_view::before {
|
|
||||||
background: var(--bg-slate-400);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.compass-button {
|
|
||||||
width: 30px;
|
|
||||||
height: 30px;
|
|
||||||
|
|
||||||
border-radius: 2px;
|
|
||||||
border: 1px solid var(--bg-slate-400);
|
|
||||||
background: var(--bg-ink-300);
|
|
||||||
box-shadow: 0px 0px 8px 0px rgba(0, 0, 0, 0.1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.ant-drawer-close {
|
|
||||||
padding: 0px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.lightMode {
|
|
||||||
.ant-drawer-header {
|
|
||||||
border-bottom: 1px solid var(--bg-vanilla-400);
|
|
||||||
background: var(--bg-vanilla-100);
|
|
||||||
}
|
|
||||||
|
|
||||||
.node-detail-drawer {
|
|
||||||
.title {
|
|
||||||
color: var(--text-ink-300);
|
|
||||||
}
|
|
||||||
|
|
||||||
.node-detail-drawer__node {
|
|
||||||
.ant-typography {
|
|
||||||
color: var(--text-ink-300);
|
|
||||||
background: transparent;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.radio-button {
|
|
||||||
border: 1px solid var(--bg-vanilla-400);
|
|
||||||
background: var(--bg-vanilla-100);
|
|
||||||
color: var(--text-ink-300);
|
|
||||||
}
|
|
||||||
|
|
||||||
.views-tabs {
|
|
||||||
.tab {
|
|
||||||
background: var(--bg-vanilla-100);
|
|
||||||
}
|
|
||||||
|
|
||||||
.selected_view {
|
|
||||||
background: var(--bg-vanilla-300);
|
|
||||||
border: 1px solid var(--bg-slate-300);
|
|
||||||
color: var(--text-ink-400);
|
|
||||||
}
|
|
||||||
|
|
||||||
.selected_view::before {
|
|
||||||
background: var(--bg-vanilla-300);
|
|
||||||
border-left: 1px solid var(--bg-slate-300);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.compass-button {
|
|
||||||
border: 1px solid var(--bg-vanilla-300);
|
|
||||||
background: var(--bg-vanilla-100);
|
|
||||||
box-shadow: 0px 0px 8px 0px rgba(0, 0, 0, 0.1);
|
|
||||||
}
|
|
||||||
|
|
||||||
.tabs-and-search {
|
|
||||||
.action-btn {
|
|
||||||
border: 1px solid var(--bg-vanilla-400);
|
|
||||||
background: var(--bg-vanilla-100);
|
|
||||||
color: var(--text-ink-300);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,10 +1,11 @@
|
|||||||
/* eslint-disable sonarjs/no-identical-functions */
|
/* eslint-disable sonarjs/no-identical-functions */
|
||||||
import './NodeDetails.styles.scss';
|
import '../../EntityDetailsUtils/entityDetails.styles.scss';
|
||||||
|
|
||||||
import { Color, Spacing } from '@signozhq/design-tokens';
|
import { Color, Spacing } from '@signozhq/design-tokens';
|
||||||
import { Button, Divider, Drawer, Radio, Tooltip, Typography } from 'antd';
|
import { Button, Divider, Drawer, Radio, Tooltip, Typography } from 'antd';
|
||||||
import { RadioChangeEvent } from 'antd/lib';
|
import { RadioChangeEvent } from 'antd/lib';
|
||||||
import logEvent from 'api/common/logEvent';
|
import logEvent from 'api/common/logEvent';
|
||||||
|
import { K8sNodesData } from 'api/infraMonitoring/getK8sNodesList';
|
||||||
import { VIEW_TYPES, VIEWS } from 'components/HostMetricsDetail/constants';
|
import { VIEW_TYPES, VIEWS } from 'components/HostMetricsDetail/constants';
|
||||||
import { QueryParams } from 'constants/query';
|
import { QueryParams } from 'constants/query';
|
||||||
import {
|
import {
|
||||||
@ -12,7 +13,9 @@ import {
|
|||||||
initialQueryState,
|
initialQueryState,
|
||||||
} from 'constants/queryBuilder';
|
} from 'constants/queryBuilder';
|
||||||
import ROUTES from 'constants/routes';
|
import ROUTES from 'constants/routes';
|
||||||
import { filterDuplicateFilters } from 'container/InfraMonitoringK8s/entityDetailUtils';
|
import { filterDuplicateFilters } from 'container/InfraMonitoringK8s/commonUtils';
|
||||||
|
import { K8sCategory } from 'container/InfraMonitoringK8s/constants';
|
||||||
|
import NodeEvents from 'container/InfraMonitoringK8s/EntityDetailsUtils/EntityEvents';
|
||||||
import {
|
import {
|
||||||
CustomTimeType,
|
CustomTimeType,
|
||||||
Time,
|
Time,
|
||||||
@ -43,12 +46,12 @@ import {
|
|||||||
import { GlobalReducer } from 'types/reducer/globalTime';
|
import { GlobalReducer } from 'types/reducer/globalTime';
|
||||||
import { v4 as uuidv4 } from 'uuid';
|
import { v4 as uuidv4 } from 'uuid';
|
||||||
|
|
||||||
import { QUERY_KEYS } from './constants';
|
import NodeLogs from '../../EntityDetailsUtils/EntityLogs';
|
||||||
import NodeEvents from './Events';
|
import NodeMetrics from '../../EntityDetailsUtils/EntityMetrics';
|
||||||
import NodeLogs from './Logs';
|
import NodeTraces from '../../EntityDetailsUtils/EntityTraces';
|
||||||
import NodeMetrics from './Metrics';
|
import { QUERY_KEYS } from '../../EntityDetailsUtils/utils';
|
||||||
|
import { getNodeMetricsQueryPayload, nodeWidgetInfo } from './constants';
|
||||||
import { NodeDetailsProps } from './NodeDetails.interfaces';
|
import { NodeDetailsProps } from './NodeDetails.interfaces';
|
||||||
import NodeTraces from './Traces';
|
|
||||||
|
|
||||||
function NodeDetails({
|
function NodeDetails({
|
||||||
node,
|
node,
|
||||||
@ -402,35 +405,35 @@ function NodeDetails({
|
|||||||
overscrollBehavior: 'contain',
|
overscrollBehavior: 'contain',
|
||||||
background: isDarkMode ? Color.BG_INK_400 : Color.BG_VANILLA_100,
|
background: isDarkMode ? Color.BG_INK_400 : Color.BG_VANILLA_100,
|
||||||
}}
|
}}
|
||||||
className="node-detail-drawer"
|
className="entity-detail-drawer"
|
||||||
destroyOnClose
|
destroyOnClose
|
||||||
closeIcon={<X size={16} style={{ marginTop: Spacing.MARGIN_1 }} />}
|
closeIcon={<X size={16} style={{ marginTop: Spacing.MARGIN_1 }} />}
|
||||||
>
|
>
|
||||||
{node && (
|
{node && (
|
||||||
<>
|
<>
|
||||||
<div className="node-detail-drawer__node">
|
<div className="entity-detail-drawer__entity">
|
||||||
<div className="node-details-grid">
|
<div className="entity-details-grid">
|
||||||
<div className="labels-row">
|
<div className="labels-row">
|
||||||
<Typography.Text
|
<Typography.Text
|
||||||
type="secondary"
|
type="secondary"
|
||||||
className="node-details-metadata-label"
|
className="entity-details-metadata-label"
|
||||||
>
|
>
|
||||||
Node Name
|
Node Name
|
||||||
</Typography.Text>
|
</Typography.Text>
|
||||||
<Typography.Text
|
<Typography.Text
|
||||||
type="secondary"
|
type="secondary"
|
||||||
className="node-details-metadata-label"
|
className="entity-details-metadata-label"
|
||||||
>
|
>
|
||||||
Cluster Name
|
Cluster Name
|
||||||
</Typography.Text>
|
</Typography.Text>
|
||||||
</div>
|
</div>
|
||||||
<div className="values-row">
|
<div className="values-row">
|
||||||
<Typography.Text className="node-details-metadata-value">
|
<Typography.Text className="entity-details-metadata-value">
|
||||||
<Tooltip title={node.meta.k8s_node_name}>
|
<Tooltip title={node.meta.k8s_node_name}>
|
||||||
{node.meta.k8s_node_name}
|
{node.meta.k8s_node_name}
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</Typography.Text>
|
</Typography.Text>
|
||||||
<Typography.Text className="node-details-metadata-value">
|
<Typography.Text className="entity-details-metadata-value">
|
||||||
<Tooltip title="Cluster name">{node.meta.k8s_cluster_name}</Tooltip>
|
<Tooltip title="Cluster name">{node.meta.k8s_cluster_name}</Tooltip>
|
||||||
</Typography.Text>
|
</Typography.Text>
|
||||||
</div>
|
</div>
|
||||||
@ -500,12 +503,16 @@ function NodeDetails({
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
{selectedView === VIEW_TYPES.METRICS && (
|
{selectedView === VIEW_TYPES.METRICS && (
|
||||||
<NodeMetrics
|
<NodeMetrics<K8sNodesData>
|
||||||
timeRange={modalTimeRange}
|
timeRange={modalTimeRange}
|
||||||
isModalTimeSelection={isModalTimeSelection}
|
isModalTimeSelection={isModalTimeSelection}
|
||||||
handleTimeChange={handleTimeChange}
|
handleTimeChange={handleTimeChange}
|
||||||
selectedInterval={selectedInterval}
|
selectedInterval={selectedInterval}
|
||||||
node={node}
|
entity={node}
|
||||||
|
entityWidgetInfo={nodeWidgetInfo}
|
||||||
|
getEntityQueryPayload={getNodeMetricsQueryPayload}
|
||||||
|
category={K8sCategory.NODES}
|
||||||
|
queryKey="nodeMetrics"
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{selectedView === VIEW_TYPES.LOGS && (
|
{selectedView === VIEW_TYPES.LOGS && (
|
||||||
@ -516,6 +523,9 @@ function NodeDetails({
|
|||||||
handleChangeLogFilters={handleChangeLogFilters}
|
handleChangeLogFilters={handleChangeLogFilters}
|
||||||
logFilters={logFilters}
|
logFilters={logFilters}
|
||||||
selectedInterval={selectedInterval}
|
selectedInterval={selectedInterval}
|
||||||
|
queryKeyFilters={[QUERY_KEYS.K8S_NODE_NAME, QUERY_KEYS.K8S_CLUSTER_NAME]}
|
||||||
|
queryKey="nodeLogs"
|
||||||
|
category={K8sCategory.NODES}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{selectedView === VIEW_TYPES.TRACES && (
|
{selectedView === VIEW_TYPES.TRACES && (
|
||||||
@ -526,6 +536,7 @@ function NodeDetails({
|
|||||||
handleChangeTracesFilters={handleChangeTracesFilters}
|
handleChangeTracesFilters={handleChangeTracesFilters}
|
||||||
tracesFilters={tracesFilters}
|
tracesFilters={tracesFilters}
|
||||||
selectedInterval={selectedInterval}
|
selectedInterval={selectedInterval}
|
||||||
|
queryKey="nodeTraces"
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{selectedView === VIEW_TYPES.EVENTS && (
|
{selectedView === VIEW_TYPES.EVENTS && (
|
||||||
@ -536,6 +547,8 @@ function NodeDetails({
|
|||||||
isModalTimeSelection={isModalTimeSelection}
|
isModalTimeSelection={isModalTimeSelection}
|
||||||
handleTimeChange={handleTimeChange}
|
handleTimeChange={handleTimeChange}
|
||||||
selectedInterval={selectedInterval}
|
selectedInterval={selectedInterval}
|
||||||
|
category={K8sCategory.NODES}
|
||||||
|
queryKey="nodeEvents"
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
|
|||||||
@ -1,193 +0,0 @@
|
|||||||
.node-metric-traces {
|
|
||||||
margin-top: 1rem;
|
|
||||||
|
|
||||||
.node-metric-traces-header {
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
margin-bottom: 1rem;
|
|
||||||
|
|
||||||
gap: 8px;
|
|
||||||
padding: 12px;
|
|
||||||
border-radius: 3px;
|
|
||||||
border: 1px solid var(--bg-slate-500);
|
|
||||||
|
|
||||||
.filter-section {
|
|
||||||
flex: 1;
|
|
||||||
|
|
||||||
.ant-select-selector {
|
|
||||||
border-radius: 2px;
|
|
||||||
border: 1px solid var(--bg-slate-400) !important;
|
|
||||||
background-color: var(--bg-ink-300) !important;
|
|
||||||
|
|
||||||
input {
|
|
||||||
font-size: 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.ant-tag .ant-typography {
|
|
||||||
font-size: 12px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.node-metric-traces-table {
|
|
||||||
.ant-table-content {
|
|
||||||
overflow: hidden !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.ant-table {
|
|
||||||
border-radius: 3px;
|
|
||||||
border: 1px solid var(--bg-slate-500);
|
|
||||||
|
|
||||||
.ant-table-thead > tr > th {
|
|
||||||
padding: 12px;
|
|
||||||
font-weight: 500;
|
|
||||||
font-size: 12px;
|
|
||||||
line-height: 18px;
|
|
||||||
|
|
||||||
background: rgba(171, 189, 255, 0.01);
|
|
||||||
border-bottom: none;
|
|
||||||
|
|
||||||
color: var(--Vanilla-400, #c0c1c3);
|
|
||||||
font-family: Inter;
|
|
||||||
font-size: 11px;
|
|
||||||
font-style: normal;
|
|
||||||
font-weight: 600;
|
|
||||||
line-height: 18px; /* 163.636% */
|
|
||||||
letter-spacing: 0.44px;
|
|
||||||
text-transform: uppercase;
|
|
||||||
|
|
||||||
&::before {
|
|
||||||
background-color: transparent;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.ant-table-thead > tr > th:has(.hostname-column-header) {
|
|
||||||
background: var(--bg-ink-400);
|
|
||||||
}
|
|
||||||
|
|
||||||
.ant-table-cell {
|
|
||||||
padding: 12px;
|
|
||||||
font-size: 13px;
|
|
||||||
line-height: 20px;
|
|
||||||
color: var(--bg-vanilla-100);
|
|
||||||
background: rgba(171, 189, 255, 0.01);
|
|
||||||
}
|
|
||||||
|
|
||||||
.ant-table-cell:has(.hostname-column-value) {
|
|
||||||
background: var(--bg-ink-400);
|
|
||||||
}
|
|
||||||
|
|
||||||
.hostname-column-value {
|
|
||||||
color: var(--bg-vanilla-100);
|
|
||||||
font-family: 'Geist Mono';
|
|
||||||
font-style: normal;
|
|
||||||
font-weight: 600;
|
|
||||||
line-height: 20px; /* 142.857% */
|
|
||||||
letter-spacing: -0.07px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.status-cell {
|
|
||||||
.active-tag {
|
|
||||||
color: var(--bg-forest-500);
|
|
||||||
padding: 4px 8px;
|
|
||||||
border-radius: 4px;
|
|
||||||
font-size: 12px;
|
|
||||||
font-weight: 500;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.progress-container {
|
|
||||||
.ant-progress-bg {
|
|
||||||
height: 8px !important;
|
|
||||||
border-radius: 4px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.ant-table-tbody > tr:hover > td {
|
|
||||||
background: rgba(255, 255, 255, 0.04);
|
|
||||||
}
|
|
||||||
|
|
||||||
.ant-table-cell:first-child {
|
|
||||||
text-align: justify;
|
|
||||||
}
|
|
||||||
|
|
||||||
.ant-table-cell:nth-child(2) {
|
|
||||||
padding-left: 16px;
|
|
||||||
padding-right: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.ant-table-cell:nth-child(n + 3) {
|
|
||||||
padding-right: 24px;
|
|
||||||
}
|
|
||||||
.column-header-right {
|
|
||||||
text-align: right;
|
|
||||||
}
|
|
||||||
.ant-table-tbody > tr > td {
|
|
||||||
border-bottom: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.ant-table-thead
|
|
||||||
> tr
|
|
||||||
> th:not(:last-child):not(.ant-table-selection-column):not(.ant-table-row-expand-icon-cell):not([colspan])::before {
|
|
||||||
background-color: transparent;
|
|
||||||
}
|
|
||||||
|
|
||||||
.ant-empty-normal {
|
|
||||||
visibility: hidden;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.ant-table-container::after {
|
|
||||||
content: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.lightMode {
|
|
||||||
.host-metric-traces-header {
|
|
||||||
.filter-section {
|
|
||||||
border-top: 1px solid var(--bg-vanilla-300);
|
|
||||||
border-bottom: 1px solid var(--bg-vanilla-300);
|
|
||||||
|
|
||||||
.ant-select-selector {
|
|
||||||
border-color: var(--bg-vanilla-300) !important;
|
|
||||||
background-color: var(--bg-vanilla-100) !important;
|
|
||||||
color: var(--bg-ink-200);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.host-metric-traces-table {
|
|
||||||
.ant-table {
|
|
||||||
border-radius: 3px;
|
|
||||||
border: 1px solid var(--bg-vanilla-300);
|
|
||||||
|
|
||||||
.ant-table-thead > tr > th {
|
|
||||||
background: var(--bg-vanilla-100);
|
|
||||||
color: var(--text-ink-300);
|
|
||||||
}
|
|
||||||
|
|
||||||
.ant-table-thead > tr > th:has(.hostname-column-header) {
|
|
||||||
background: var(--bg-vanilla-100);
|
|
||||||
}
|
|
||||||
|
|
||||||
.ant-table-cell {
|
|
||||||
background: var(--bg-vanilla-100);
|
|
||||||
color: var(--bg-ink-500);
|
|
||||||
}
|
|
||||||
|
|
||||||
.ant-table-cell:has(.hostname-column-value) {
|
|
||||||
background: var(--bg-vanilla-100);
|
|
||||||
}
|
|
||||||
|
|
||||||
.hostname-column-value {
|
|
||||||
color: var(--bg-ink-300);
|
|
||||||
}
|
|
||||||
|
|
||||||
.ant-table-tbody > tr:hover > td {
|
|
||||||
background: rgba(0, 0, 0, 0.04);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,200 +0,0 @@
|
|||||||
import { PANEL_TYPES } from 'constants/queryBuilder';
|
|
||||||
import { GetQueryResultsProps } from 'lib/dashboard/getQueryResults';
|
|
||||||
import {
|
|
||||||
BaseAutocompleteData,
|
|
||||||
DataTypes,
|
|
||||||
} from 'types/api/queryBuilder/queryAutocompleteResponse';
|
|
||||||
import { IBuilderQuery } from 'types/api/queryBuilder/queryBuilderData';
|
|
||||||
import { EQueryType } from 'types/common/dashboard';
|
|
||||||
import { DataSource } from 'types/common/queryBuilder';
|
|
||||||
import { nanoToMilli } from 'utils/timeUtils';
|
|
||||||
|
|
||||||
export const columns = [
|
|
||||||
{
|
|
||||||
dataIndex: 'timestamp',
|
|
||||||
key: 'timestamp',
|
|
||||||
title: 'Timestamp',
|
|
||||||
width: 200,
|
|
||||||
render: (timestamp: string): string => new Date(timestamp).toLocaleString(),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: 'Service Name',
|
|
||||||
dataIndex: ['data', 'serviceName'],
|
|
||||||
key: 'serviceName-string-tag',
|
|
||||||
width: 150,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: 'Name',
|
|
||||||
dataIndex: ['data', 'name'],
|
|
||||||
key: 'name-string-tag',
|
|
||||||
width: 145,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: 'Duration',
|
|
||||||
dataIndex: ['data', 'durationNano'],
|
|
||||||
key: 'durationNano-float64-tag',
|
|
||||||
width: 145,
|
|
||||||
render: (duration: number): string => `${nanoToMilli(duration)}ms`,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: 'HTTP Method',
|
|
||||||
dataIndex: ['data', 'httpMethod'],
|
|
||||||
key: 'httpMethod-string-tag',
|
|
||||||
width: 145,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: 'Status Code',
|
|
||||||
dataIndex: ['data', 'responseStatusCode'],
|
|
||||||
key: 'responseStatusCode-string-tag',
|
|
||||||
width: 145,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
export const selectedColumns: BaseAutocompleteData[] = [
|
|
||||||
{
|
|
||||||
key: 'timestamp',
|
|
||||||
dataType: DataTypes.String,
|
|
||||||
type: 'tag',
|
|
||||||
isColumn: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'serviceName',
|
|
||||||
dataType: DataTypes.String,
|
|
||||||
type: 'tag',
|
|
||||||
isColumn: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'name',
|
|
||||||
dataType: DataTypes.String,
|
|
||||||
type: 'tag',
|
|
||||||
isColumn: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'durationNano',
|
|
||||||
dataType: DataTypes.Float64,
|
|
||||||
type: 'tag',
|
|
||||||
isColumn: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'httpMethod',
|
|
||||||
dataType: DataTypes.String,
|
|
||||||
type: 'tag',
|
|
||||||
isColumn: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'responseStatusCode',
|
|
||||||
dataType: DataTypes.String,
|
|
||||||
type: 'tag',
|
|
||||||
isColumn: true,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
export const getNodeTracesQueryPayload = (
|
|
||||||
start: number,
|
|
||||||
end: number,
|
|
||||||
offset = 0,
|
|
||||||
filters: IBuilderQuery['filters'],
|
|
||||||
): GetQueryResultsProps => ({
|
|
||||||
query: {
|
|
||||||
promql: [],
|
|
||||||
clickhouse_sql: [],
|
|
||||||
builder: {
|
|
||||||
queryData: [
|
|
||||||
{
|
|
||||||
dataSource: DataSource.TRACES,
|
|
||||||
queryName: 'A',
|
|
||||||
aggregateOperator: 'noop',
|
|
||||||
aggregateAttribute: {
|
|
||||||
id: '------false',
|
|
||||||
dataType: DataTypes.EMPTY,
|
|
||||||
key: '',
|
|
||||||
isColumn: false,
|
|
||||||
type: '',
|
|
||||||
isJSON: false,
|
|
||||||
},
|
|
||||||
timeAggregation: 'rate',
|
|
||||||
spaceAggregation: 'sum',
|
|
||||||
functions: [],
|
|
||||||
filters,
|
|
||||||
expression: 'A',
|
|
||||||
disabled: false,
|
|
||||||
stepInterval: 60,
|
|
||||||
having: [],
|
|
||||||
limit: null,
|
|
||||||
orderBy: [
|
|
||||||
{
|
|
||||||
columnName: 'timestamp',
|
|
||||||
order: 'desc',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
groupBy: [],
|
|
||||||
legend: '',
|
|
||||||
reduceTo: 'avg',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
queryFormulas: [],
|
|
||||||
},
|
|
||||||
id: '572f1d91-6ac0-46c0-b726-c21488b34434',
|
|
||||||
queryType: EQueryType.QUERY_BUILDER,
|
|
||||||
},
|
|
||||||
graphType: PANEL_TYPES.LIST,
|
|
||||||
selectedTime: 'GLOBAL_TIME',
|
|
||||||
start,
|
|
||||||
end,
|
|
||||||
params: {
|
|
||||||
dataSource: DataSource.TRACES,
|
|
||||||
},
|
|
||||||
tableParams: {
|
|
||||||
pagination: {
|
|
||||||
limit: 10,
|
|
||||||
offset,
|
|
||||||
},
|
|
||||||
selectColumns: [
|
|
||||||
{
|
|
||||||
key: 'serviceName',
|
|
||||||
dataType: 'string',
|
|
||||||
type: 'tag',
|
|
||||||
isColumn: true,
|
|
||||||
isJSON: false,
|
|
||||||
id: 'serviceName--string--tag--true',
|
|
||||||
isIndexed: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'name',
|
|
||||||
dataType: 'string',
|
|
||||||
type: 'tag',
|
|
||||||
isColumn: true,
|
|
||||||
isJSON: false,
|
|
||||||
id: 'name--string--tag--true',
|
|
||||||
isIndexed: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'durationNano',
|
|
||||||
dataType: 'float64',
|
|
||||||
type: 'tag',
|
|
||||||
isColumn: true,
|
|
||||||
isJSON: false,
|
|
||||||
id: 'durationNano--float64--tag--true',
|
|
||||||
isIndexed: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'httpMethod',
|
|
||||||
dataType: 'string',
|
|
||||||
type: 'tag',
|
|
||||||
isColumn: true,
|
|
||||||
isJSON: false,
|
|
||||||
id: 'httpMethod--string--tag--true',
|
|
||||||
isIndexed: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'responseStatusCode',
|
|
||||||
dataType: 'string',
|
|
||||||
type: 'tag',
|
|
||||||
isColumn: true,
|
|
||||||
isJSON: false,
|
|
||||||
id: 'responseStatusCode--string--tag--true',
|
|
||||||
isIndexed: false,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
});
|
|
||||||
@ -1,3 +0,0 @@
|
|||||||
import NodeTraces from './NodeTraces';
|
|
||||||
|
|
||||||
export default NodeTraces;
|
|
||||||
File diff suppressed because it is too large
Load Diff
@ -170,7 +170,7 @@ function K8sPodsList({
|
|||||||
selectedRowData: K8sPodsRowData,
|
selectedRowData: K8sPodsRowData,
|
||||||
): IBuilderQuery['filters'] => {
|
): IBuilderQuery['filters'] => {
|
||||||
const baseFilters: IBuilderQuery['filters'] = {
|
const baseFilters: IBuilderQuery['filters'] = {
|
||||||
items: [...query.filters.items],
|
items: [...queryFilters.items],
|
||||||
op: 'and',
|
op: 'and',
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -298,8 +298,9 @@ function K8sPodsList({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Reset pagination on switching to groupBy
|
||||||
|
setCurrentPage(1);
|
||||||
setGroupBy(groupBy);
|
setGroupBy(groupBy);
|
||||||
|
|
||||||
setExpandedRowKeys([]);
|
setExpandedRowKeys([]);
|
||||||
},
|
},
|
||||||
[groupByFiltersData],
|
[groupByFiltersData],
|
||||||
|
|||||||
@ -1,360 +0,0 @@
|
|||||||
/* eslint-disable no-nested-ternary */
|
|
||||||
import './Events.styles.scss';
|
|
||||||
|
|
||||||
import { Color } from '@signozhq/design-tokens';
|
|
||||||
import { Button, Table, TableColumnsType } from 'antd';
|
|
||||||
import { DEFAULT_ENTITY_VERSION } from 'constants/app';
|
|
||||||
import { EventContents } from 'container/InfraMonitoringK8s/commonUtils';
|
|
||||||
import LoadingContainer from 'container/InfraMonitoringK8s/LoadingContainer';
|
|
||||||
import LogsError from 'container/LogsError/LogsError';
|
|
||||||
import { ORDERBY_FILTERS } from 'container/QueryBuilder/filters/OrderByFilter/config';
|
|
||||||
import QueryBuilderSearch from 'container/QueryBuilder/filters/QueryBuilderSearch';
|
|
||||||
import DateTimeSelectionV2 from 'container/TopNav/DateTimeSelectionV2';
|
|
||||||
import {
|
|
||||||
CustomTimeType,
|
|
||||||
Time,
|
|
||||||
} from 'container/TopNav/DateTimeSelectionV2/config';
|
|
||||||
import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
|
|
||||||
import { GetMetricQueryRange } from 'lib/dashboard/getQueryResults';
|
|
||||||
import { isArray } from 'lodash-es';
|
|
||||||
import { ChevronDown, ChevronLeft, ChevronRight, Loader2 } from 'lucide-react';
|
|
||||||
import { useEffect, useMemo, useState } from 'react';
|
|
||||||
import { useQuery } from 'react-query';
|
|
||||||
import { DataTypes } from 'types/api/queryBuilder/queryAutocompleteResponse';
|
|
||||||
import { IBuilderQuery } from 'types/api/queryBuilder/queryBuilderData';
|
|
||||||
import { DataSource } from 'types/common/queryBuilder';
|
|
||||||
import { v4 } from 'uuid';
|
|
||||||
|
|
||||||
import { getPodsEventsQueryPayload } from './constants';
|
|
||||||
import NoEventsContainer from './NoEventsContainer';
|
|
||||||
|
|
||||||
interface EventDataType {
|
|
||||||
key: string;
|
|
||||||
timestamp: string;
|
|
||||||
body: string;
|
|
||||||
id: string;
|
|
||||||
attributes_bool?: Record<string, boolean>;
|
|
||||||
attributes_number?: Record<string, number>;
|
|
||||||
attributes_string?: Record<string, string>;
|
|
||||||
resources_string?: Record<string, string>;
|
|
||||||
scope_name?: string;
|
|
||||||
scope_string?: Record<string, string>;
|
|
||||||
scope_version?: string;
|
|
||||||
severity_number?: number;
|
|
||||||
severity_text?: string;
|
|
||||||
span_id?: string;
|
|
||||||
trace_flags?: number;
|
|
||||||
trace_id?: string;
|
|
||||||
severity?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface IPodEventsProps {
|
|
||||||
timeRange: {
|
|
||||||
startTime: number;
|
|
||||||
endTime: number;
|
|
||||||
};
|
|
||||||
handleChangeLogFilters: (filters: IBuilderQuery['filters']) => void;
|
|
||||||
filters: IBuilderQuery['filters'];
|
|
||||||
isModalTimeSelection: boolean;
|
|
||||||
handleTimeChange: (
|
|
||||||
interval: Time | CustomTimeType,
|
|
||||||
dateTimeRange?: [number, number],
|
|
||||||
) => void;
|
|
||||||
selectedInterval: Time;
|
|
||||||
}
|
|
||||||
|
|
||||||
const EventsPageSize = 10;
|
|
||||||
|
|
||||||
export default function Events({
|
|
||||||
timeRange,
|
|
||||||
handleChangeLogFilters,
|
|
||||||
filters,
|
|
||||||
isModalTimeSelection,
|
|
||||||
handleTimeChange,
|
|
||||||
selectedInterval,
|
|
||||||
}: IPodEventsProps): JSX.Element {
|
|
||||||
const { currentQuery } = useQueryBuilder();
|
|
||||||
|
|
||||||
const [formattedPodEvents, setFormattedPodEvents] = useState<EventDataType[]>(
|
|
||||||
[],
|
|
||||||
);
|
|
||||||
|
|
||||||
const [hasReachedEndOfEvents, setHasReachedEndOfEvents] = useState(false);
|
|
||||||
|
|
||||||
const [page, setPage] = useState(1);
|
|
||||||
|
|
||||||
const updatedCurrentQuery = useMemo(
|
|
||||||
() => ({
|
|
||||||
...currentQuery,
|
|
||||||
builder: {
|
|
||||||
...currentQuery.builder,
|
|
||||||
queryData: [
|
|
||||||
{
|
|
||||||
...currentQuery.builder.queryData[0],
|
|
||||||
dataSource: DataSource.LOGS,
|
|
||||||
aggregateOperator: 'noop',
|
|
||||||
aggregateAttribute: {
|
|
||||||
...currentQuery.builder.queryData[0].aggregateAttribute,
|
|
||||||
},
|
|
||||||
filters: {
|
|
||||||
items: [],
|
|
||||||
op: 'AND',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
[currentQuery],
|
|
||||||
);
|
|
||||||
|
|
||||||
const query = updatedCurrentQuery?.builder?.queryData[0] || null;
|
|
||||||
|
|
||||||
const queryPayload = useMemo(() => {
|
|
||||||
const basePayload = getPodsEventsQueryPayload(
|
|
||||||
timeRange.startTime,
|
|
||||||
timeRange.endTime,
|
|
||||||
filters,
|
|
||||||
);
|
|
||||||
|
|
||||||
basePayload.query.builder.queryData[0].pageSize = 10;
|
|
||||||
basePayload.query.builder.queryData[0].orderBy = [
|
|
||||||
{ columnName: 'timestamp', order: ORDERBY_FILTERS.DESC },
|
|
||||||
];
|
|
||||||
|
|
||||||
return basePayload;
|
|
||||||
}, [timeRange.startTime, timeRange.endTime, filters]);
|
|
||||||
|
|
||||||
const { data: eventsData, isLoading, isFetching, isError } = useQuery({
|
|
||||||
queryKey: ['podEvents', timeRange.startTime, timeRange.endTime, filters],
|
|
||||||
queryFn: () => GetMetricQueryRange(queryPayload, DEFAULT_ENTITY_VERSION),
|
|
||||||
enabled: !!queryPayload,
|
|
||||||
});
|
|
||||||
|
|
||||||
const columns: TableColumnsType<EventDataType> = [
|
|
||||||
{ title: 'Severity', dataIndex: 'severity', key: 'severity', width: 100 },
|
|
||||||
{
|
|
||||||
title: 'Timestamp',
|
|
||||||
dataIndex: 'timestamp',
|
|
||||||
width: 200,
|
|
||||||
ellipsis: true,
|
|
||||||
key: 'timestamp',
|
|
||||||
},
|
|
||||||
{ title: 'Body', dataIndex: 'body', key: 'body' },
|
|
||||||
];
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (eventsData?.payload?.data?.newResult?.data?.result) {
|
|
||||||
const responsePayload =
|
|
||||||
eventsData?.payload.data.newResult.data.result[0].list || [];
|
|
||||||
|
|
||||||
const formattedData = responsePayload?.map(
|
|
||||||
(event): EventDataType => ({
|
|
||||||
timestamp: event.timestamp,
|
|
||||||
severity: event.data.severity_text,
|
|
||||||
body: event.data.body,
|
|
||||||
id: event.data.id,
|
|
||||||
key: event.data.id,
|
|
||||||
resources_string: event.data.resources_string,
|
|
||||||
attributes_string: event.data.attributes_string,
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
|
|
||||||
setFormattedPodEvents(formattedData);
|
|
||||||
|
|
||||||
if (
|
|
||||||
!responsePayload ||
|
|
||||||
(responsePayload &&
|
|
||||||
isArray(responsePayload) &&
|
|
||||||
responsePayload.length < EventsPageSize)
|
|
||||||
) {
|
|
||||||
setHasReachedEndOfEvents(true);
|
|
||||||
} else {
|
|
||||||
setHasReachedEndOfEvents(false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, [eventsData]);
|
|
||||||
|
|
||||||
const handleExpandRow = (record: EventDataType): JSX.Element => (
|
|
||||||
<EventContents
|
|
||||||
data={{ ...record.attributes_string, ...record.resources_string }}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
|
|
||||||
const handlePrev = (): void => {
|
|
||||||
if (!formattedPodEvents.length) return;
|
|
||||||
|
|
||||||
setPage(page - 1);
|
|
||||||
|
|
||||||
const firstEvent = formattedPodEvents[0];
|
|
||||||
|
|
||||||
const newItems = [
|
|
||||||
...filters.items.filter((item) => item.key?.key !== 'id'),
|
|
||||||
{
|
|
||||||
id: v4(),
|
|
||||||
key: {
|
|
||||||
key: 'id',
|
|
||||||
type: '',
|
|
||||||
dataType: DataTypes.String,
|
|
||||||
isColumn: true,
|
|
||||||
},
|
|
||||||
op: '>',
|
|
||||||
value: firstEvent.id,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
const newFilters = {
|
|
||||||
op: 'AND',
|
|
||||||
items: newItems,
|
|
||||||
} as IBuilderQuery['filters'];
|
|
||||||
|
|
||||||
handleChangeLogFilters(newFilters);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleNext = (): void => {
|
|
||||||
if (!formattedPodEvents.length) return;
|
|
||||||
|
|
||||||
setPage(page + 1);
|
|
||||||
const lastEvent = formattedPodEvents[formattedPodEvents.length - 1];
|
|
||||||
|
|
||||||
const newItems = [
|
|
||||||
...filters.items.filter((item) => item.key?.key !== 'id'),
|
|
||||||
{
|
|
||||||
id: v4(),
|
|
||||||
key: {
|
|
||||||
key: 'id',
|
|
||||||
type: '',
|
|
||||||
dataType: DataTypes.String,
|
|
||||||
isColumn: true,
|
|
||||||
},
|
|
||||||
op: '<',
|
|
||||||
value: lastEvent.id,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
const newFilters = {
|
|
||||||
op: 'AND',
|
|
||||||
items: newItems,
|
|
||||||
} as IBuilderQuery['filters'];
|
|
||||||
|
|
||||||
handleChangeLogFilters(newFilters);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleExpandRowIcon = ({
|
|
||||||
expanded,
|
|
||||||
onExpand,
|
|
||||||
record,
|
|
||||||
}: {
|
|
||||||
expanded: boolean;
|
|
||||||
onExpand: (
|
|
||||||
record: EventDataType,
|
|
||||||
e: React.MouseEvent<HTMLElement, MouseEvent>,
|
|
||||||
) => void;
|
|
||||||
record: EventDataType;
|
|
||||||
}): JSX.Element =>
|
|
||||||
expanded ? (
|
|
||||||
<ChevronDown
|
|
||||||
className="periscope-btn-icon"
|
|
||||||
size={14}
|
|
||||||
onClick={(e): void =>
|
|
||||||
onExpand(
|
|
||||||
record,
|
|
||||||
(e as unknown) as React.MouseEvent<HTMLElement, MouseEvent>,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
<ChevronRight
|
|
||||||
className="periscope-btn-icon"
|
|
||||||
size={14}
|
|
||||||
// eslint-disable-next-line sonarjs/no-identical-functions
|
|
||||||
onClick={(e): void =>
|
|
||||||
onExpand(
|
|
||||||
record,
|
|
||||||
(e as unknown) as React.MouseEvent<HTMLElement, MouseEvent>,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="pod-events-container">
|
|
||||||
<div className="pod-events-header">
|
|
||||||
<div className="filter-section">
|
|
||||||
{query && (
|
|
||||||
<QueryBuilderSearch
|
|
||||||
query={query}
|
|
||||||
onChange={handleChangeLogFilters}
|
|
||||||
disableNavigationShortcuts
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
<div className="datetime-section">
|
|
||||||
<DateTimeSelectionV2
|
|
||||||
showAutoRefresh={false}
|
|
||||||
showRefreshText={false}
|
|
||||||
hideShareModal
|
|
||||||
isModalTimeSelection={isModalTimeSelection}
|
|
||||||
onTimeChange={handleTimeChange}
|
|
||||||
defaultRelativeTime="5m"
|
|
||||||
modalSelectedInterval={selectedInterval}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{isLoading && <LoadingContainer />}
|
|
||||||
|
|
||||||
{!isLoading && !isError && formattedPodEvents.length === 0 && (
|
|
||||||
<NoEventsContainer />
|
|
||||||
)}
|
|
||||||
|
|
||||||
{isError && !isLoading && <LogsError />}
|
|
||||||
|
|
||||||
{!isLoading && !isError && formattedPodEvents.length > 0 && (
|
|
||||||
<div className="pod-events-list-container">
|
|
||||||
<div className="pod-events-list-card">
|
|
||||||
<Table<EventDataType>
|
|
||||||
loading={isLoading && page > 1}
|
|
||||||
columns={columns}
|
|
||||||
expandable={{
|
|
||||||
expandedRowRender: handleExpandRow,
|
|
||||||
rowExpandable: (record): boolean => record.body !== 'Not Expandable',
|
|
||||||
expandIcon: handleExpandRowIcon,
|
|
||||||
}}
|
|
||||||
dataSource={formattedPodEvents}
|
|
||||||
pagination={false}
|
|
||||||
rowKey={(record): string => record.id}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{!isError && formattedPodEvents.length > 0 && (
|
|
||||||
<div className="pod-events-footer">
|
|
||||||
<Button
|
|
||||||
className="pod-events-footer-button periscope-btn ghost"
|
|
||||||
type="link"
|
|
||||||
onClick={handlePrev}
|
|
||||||
disabled={page === 1 || isFetching || isLoading}
|
|
||||||
>
|
|
||||||
{!isFetching && <ChevronLeft size={14} />}
|
|
||||||
Prev
|
|
||||||
</Button>
|
|
||||||
|
|
||||||
<Button
|
|
||||||
className="pod-events-footer-button periscope-btn ghost"
|
|
||||||
type="link"
|
|
||||||
onClick={handleNext}
|
|
||||||
disabled={hasReachedEndOfEvents || isFetching || isLoading}
|
|
||||||
>
|
|
||||||
Next
|
|
||||||
{!isFetching && <ChevronRight size={14} />}
|
|
||||||
</Button>
|
|
||||||
|
|
||||||
{(isFetching || isLoading) && (
|
|
||||||
<Loader2 className="animate-spin" size={16} color={Color.BG_ROBIN_500} />
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@ -1,16 +0,0 @@
|
|||||||
import { Color } from '@signozhq/design-tokens';
|
|
||||||
import { Typography } from 'antd';
|
|
||||||
import { Ghost } from 'lucide-react';
|
|
||||||
|
|
||||||
const { Text } = Typography;
|
|
||||||
|
|
||||||
export default function NoEventsContainer(): React.ReactElement {
|
|
||||||
return (
|
|
||||||
<div className="no-logs-found">
|
|
||||||
<Text type="secondary">
|
|
||||||
<Ghost size={24} color={Color.BG_AMBER_500} /> No events found for this pod
|
|
||||||
in the selected time range.
|
|
||||||
</Text>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@ -1,65 +0,0 @@
|
|||||||
import { PANEL_TYPES } from 'constants/queryBuilder';
|
|
||||||
import { GetQueryResultsProps } from 'lib/dashboard/getQueryResults';
|
|
||||||
import { DataTypes } from 'types/api/queryBuilder/queryAutocompleteResponse';
|
|
||||||
import { IBuilderQuery } from 'types/api/queryBuilder/queryBuilderData';
|
|
||||||
import { EQueryType } from 'types/common/dashboard';
|
|
||||||
import { DataSource } from 'types/common/queryBuilder';
|
|
||||||
import { v4 as uuidv4 } from 'uuid';
|
|
||||||
|
|
||||||
export const getPodsEventsQueryPayload = (
|
|
||||||
start: number,
|
|
||||||
end: number,
|
|
||||||
filters: IBuilderQuery['filters'],
|
|
||||||
): GetQueryResultsProps => ({
|
|
||||||
graphType: PANEL_TYPES.LIST,
|
|
||||||
selectedTime: 'GLOBAL_TIME',
|
|
||||||
query: {
|
|
||||||
clickhouse_sql: [],
|
|
||||||
promql: [],
|
|
||||||
builder: {
|
|
||||||
queryData: [
|
|
||||||
{
|
|
||||||
dataSource: DataSource.LOGS,
|
|
||||||
queryName: 'A',
|
|
||||||
aggregateOperator: 'noop',
|
|
||||||
aggregateAttribute: {
|
|
||||||
id: '------false',
|
|
||||||
dataType: DataTypes.String,
|
|
||||||
key: '',
|
|
||||||
isColumn: false,
|
|
||||||
type: '',
|
|
||||||
isJSON: false,
|
|
||||||
},
|
|
||||||
timeAggregation: 'rate',
|
|
||||||
spaceAggregation: 'sum',
|
|
||||||
functions: [],
|
|
||||||
filters,
|
|
||||||
expression: 'A',
|
|
||||||
disabled: false,
|
|
||||||
stepInterval: 60,
|
|
||||||
having: [],
|
|
||||||
limit: null,
|
|
||||||
orderBy: [
|
|
||||||
{
|
|
||||||
columnName: 'timestamp',
|
|
||||||
order: 'desc',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
groupBy: [],
|
|
||||||
legend: '',
|
|
||||||
reduceTo: 'avg',
|
|
||||||
offset: 0,
|
|
||||||
pageSize: 100,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
queryFormulas: [],
|
|
||||||
},
|
|
||||||
id: uuidv4(),
|
|
||||||
queryType: EQueryType.QUERY_BUILDER,
|
|
||||||
},
|
|
||||||
params: {
|
|
||||||
lastLogLineTimestamp: null,
|
|
||||||
},
|
|
||||||
start,
|
|
||||||
end,
|
|
||||||
});
|
|
||||||
@ -1,11 +1,12 @@
|
|||||||
/* eslint-disable sonarjs/no-identical-functions */
|
/* eslint-disable sonarjs/no-identical-functions */
|
||||||
/* eslint-disable sonarjs/no-duplicate-string */
|
/* eslint-disable sonarjs/no-duplicate-string */
|
||||||
import './PodDetails.styles.scss';
|
import '../../EntityDetailsUtils/entityDetails.styles.scss';
|
||||||
|
|
||||||
import { Color, Spacing } from '@signozhq/design-tokens';
|
import { Color, Spacing } from '@signozhq/design-tokens';
|
||||||
import { Button, Divider, Drawer, Radio, Tooltip, Typography } from 'antd';
|
import { Button, Divider, Drawer, Radio, Tooltip, Typography } from 'antd';
|
||||||
import { RadioChangeEvent } from 'antd/lib';
|
import { RadioChangeEvent } from 'antd/lib';
|
||||||
import logEvent from 'api/common/logEvent';
|
import logEvent from 'api/common/logEvent';
|
||||||
|
import { K8sPodsData } from 'api/infraMonitoring/getK8sPodsList';
|
||||||
import { VIEW_TYPES, VIEWS } from 'components/HostMetricsDetail/constants';
|
import { VIEW_TYPES, VIEWS } from 'components/HostMetricsDetail/constants';
|
||||||
import { QueryParams } from 'constants/query';
|
import { QueryParams } from 'constants/query';
|
||||||
import {
|
import {
|
||||||
@ -13,7 +14,9 @@ import {
|
|||||||
initialQueryState,
|
initialQueryState,
|
||||||
} from 'constants/queryBuilder';
|
} from 'constants/queryBuilder';
|
||||||
import ROUTES from 'constants/routes';
|
import ROUTES from 'constants/routes';
|
||||||
import { filterDuplicateFilters } from 'container/InfraMonitoringK8s/entityDetailUtils';
|
import { filterDuplicateFilters } from 'container/InfraMonitoringK8s/commonUtils';
|
||||||
|
import { K8sCategory } from 'container/InfraMonitoringK8s/constants';
|
||||||
|
import { QUERY_KEYS } from 'container/InfraMonitoringK8s/EntityDetailsUtils/utils';
|
||||||
import {
|
import {
|
||||||
CustomTimeType,
|
CustomTimeType,
|
||||||
Time,
|
Time,
|
||||||
@ -44,12 +47,12 @@ import {
|
|||||||
import { GlobalReducer } from 'types/reducer/globalTime';
|
import { GlobalReducer } from 'types/reducer/globalTime';
|
||||||
import { v4 as uuidv4 } from 'uuid';
|
import { v4 as uuidv4 } from 'uuid';
|
||||||
|
|
||||||
import { QUERY_KEYS } from './constants';
|
import PodEvents from '../../EntityDetailsUtils/EntityEvents';
|
||||||
import Events from './Events/Events';
|
import PodLogs from '../../EntityDetailsUtils/EntityLogs';
|
||||||
import Metrics from './Metrics/Metrics';
|
import PodMetrics from '../../EntityDetailsUtils/EntityMetrics';
|
||||||
|
import PodTraces from '../../EntityDetailsUtils/EntityTraces';
|
||||||
|
import { getPodMetricsQueryPayload, podWidgetInfo } from './constants';
|
||||||
import { PodDetailProps } from './PodDetail.interfaces';
|
import { PodDetailProps } from './PodDetail.interfaces';
|
||||||
import PodLogsDetailedView from './PodLogs/PodLogsDetailedView';
|
|
||||||
import PodTraces from './PodTraces/PodTraces';
|
|
||||||
|
|
||||||
const TimeRangeOffset = 1000000000;
|
const TimeRangeOffset = 1000000000;
|
||||||
|
|
||||||
@ -423,49 +426,49 @@ function PodDetails({
|
|||||||
overscrollBehavior: 'contain',
|
overscrollBehavior: 'contain',
|
||||||
background: isDarkMode ? Color.BG_INK_400 : Color.BG_VANILLA_100,
|
background: isDarkMode ? Color.BG_INK_400 : Color.BG_VANILLA_100,
|
||||||
}}
|
}}
|
||||||
className="pod-detail-drawer"
|
className="entity-detail-drawer"
|
||||||
destroyOnClose
|
destroyOnClose
|
||||||
closeIcon={<X size={16} style={{ marginTop: Spacing.MARGIN_1 }} />}
|
closeIcon={<X size={16} style={{ marginTop: Spacing.MARGIN_1 }} />}
|
||||||
>
|
>
|
||||||
{pod && (
|
{pod && (
|
||||||
<>
|
<>
|
||||||
<div className="pod-detail-drawer__pod">
|
<div className="entity-detail-drawer__entity">
|
||||||
<div className="pod-details-grid">
|
<div className="entity-details-grid">
|
||||||
<div className="labels-row">
|
<div className="labels-row">
|
||||||
<Typography.Text
|
<Typography.Text
|
||||||
type="secondary"
|
type="secondary"
|
||||||
className="pod-details-metadata-label"
|
className="entity-details-metadata-label"
|
||||||
>
|
>
|
||||||
NAMESPACE
|
NAMESPACE
|
||||||
</Typography.Text>
|
</Typography.Text>
|
||||||
<Typography.Text
|
<Typography.Text
|
||||||
type="secondary"
|
type="secondary"
|
||||||
className="pod-details-metadata-label"
|
className="entity-details-metadata-label"
|
||||||
>
|
>
|
||||||
Cluster Name
|
Cluster Name
|
||||||
</Typography.Text>
|
</Typography.Text>
|
||||||
<Typography.Text
|
<Typography.Text
|
||||||
type="secondary"
|
type="secondary"
|
||||||
className="pod-details-metadata-label"
|
className="entity-details-metadata-label"
|
||||||
>
|
>
|
||||||
Node
|
Node
|
||||||
</Typography.Text>
|
</Typography.Text>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="values-row">
|
<div className="values-row">
|
||||||
<Typography.Text className="pod-details-metadata-value">
|
<Typography.Text className="entity-details-metadata-value">
|
||||||
<Tooltip title={pod.meta.k8s_namespace_name}>
|
<Tooltip title={pod.meta.k8s_namespace_name}>
|
||||||
{pod.meta.k8s_namespace_name}
|
{pod.meta.k8s_namespace_name}
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</Typography.Text>
|
</Typography.Text>
|
||||||
|
|
||||||
<Typography.Text className="pod-details-metadata-value">
|
<Typography.Text className="entity-details-metadata-value">
|
||||||
<Tooltip title={pod.meta.k8s_cluster_name}>
|
<Tooltip title={pod.meta.k8s_cluster_name}>
|
||||||
{pod.meta.k8s_cluster_name}
|
{pod.meta.k8s_cluster_name}
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</Typography.Text>
|
</Typography.Text>
|
||||||
|
|
||||||
<Typography.Text className="pod-details-metadata-value">
|
<Typography.Text className="entity-details-metadata-value">
|
||||||
<Tooltip title={pod.meta.k8s_node_name}>
|
<Tooltip title={pod.meta.k8s_node_name}>
|
||||||
{pod.meta.k8s_node_name}
|
{pod.meta.k8s_node_name}
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
@ -538,22 +541,33 @@ function PodDetails({
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{selectedView === VIEW_TYPES.METRICS && (
|
{selectedView === VIEW_TYPES.METRICS && (
|
||||||
<Metrics
|
<PodMetrics<K8sPodsData>
|
||||||
pod={pod}
|
entity={pod}
|
||||||
selectedInterval={selectedInterval}
|
selectedInterval={selectedInterval}
|
||||||
timeRange={modalTimeRange}
|
timeRange={modalTimeRange}
|
||||||
handleTimeChange={handleTimeChange}
|
handleTimeChange={handleTimeChange}
|
||||||
isModalTimeSelection={isModalTimeSelection}
|
isModalTimeSelection={isModalTimeSelection}
|
||||||
|
entityWidgetInfo={podWidgetInfo}
|
||||||
|
getEntityQueryPayload={getPodMetricsQueryPayload}
|
||||||
|
category={K8sCategory.PODS}
|
||||||
|
queryKey="podMetrics"
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{selectedView === VIEW_TYPES.LOGS && (
|
{selectedView === VIEW_TYPES.LOGS && (
|
||||||
<PodLogsDetailedView
|
<PodLogs
|
||||||
timeRange={modalTimeRange}
|
timeRange={modalTimeRange}
|
||||||
isModalTimeSelection={isModalTimeSelection}
|
isModalTimeSelection={isModalTimeSelection}
|
||||||
handleTimeChange={handleTimeChange}
|
handleTimeChange={handleTimeChange}
|
||||||
handleChangeLogFilters={handleChangeLogFilters}
|
handleChangeLogFilters={handleChangeLogFilters}
|
||||||
logFilters={logFilters}
|
logFilters={logFilters}
|
||||||
selectedInterval={selectedInterval}
|
selectedInterval={selectedInterval}
|
||||||
|
queryKeyFilters={[
|
||||||
|
QUERY_KEYS.K8S_POD_NAME,
|
||||||
|
QUERY_KEYS.K8S_CLUSTER_NAME,
|
||||||
|
QUERY_KEYS.K8S_NAMESPACE_NAME,
|
||||||
|
]}
|
||||||
|
queryKey="podLogs"
|
||||||
|
category={K8sCategory.PODS}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{selectedView === VIEW_TYPES.TRACES && (
|
{selectedView === VIEW_TYPES.TRACES && (
|
||||||
@ -564,17 +578,20 @@ function PodDetails({
|
|||||||
handleChangeTracesFilters={handleChangeTracesFilters}
|
handleChangeTracesFilters={handleChangeTracesFilters}
|
||||||
tracesFilters={tracesFilters}
|
tracesFilters={tracesFilters}
|
||||||
selectedInterval={selectedInterval}
|
selectedInterval={selectedInterval}
|
||||||
|
queryKey="podTraces"
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{selectedView === VIEW_TYPES.EVENTS && (
|
{selectedView === VIEW_TYPES.EVENTS && (
|
||||||
<Events
|
<PodEvents
|
||||||
timeRange={modalTimeRange}
|
timeRange={modalTimeRange}
|
||||||
isModalTimeSelection={isModalTimeSelection}
|
isModalTimeSelection={isModalTimeSelection}
|
||||||
handleTimeChange={handleTimeChange}
|
handleTimeChange={handleTimeChange}
|
||||||
handleChangeLogFilters={handleChangeEventsFilters}
|
handleChangeEventFilters={handleChangeEventsFilters}
|
||||||
filters={eventsFilters}
|
filters={eventsFilters}
|
||||||
selectedInterval={selectedInterval}
|
selectedInterval={selectedInterval}
|
||||||
|
category={K8sCategory.PODS}
|
||||||
|
queryKey="podEvents"
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
|
|||||||
@ -1,16 +0,0 @@
|
|||||||
import { Color } from '@signozhq/design-tokens';
|
|
||||||
import { Typography } from 'antd';
|
|
||||||
import { Ghost } from 'lucide-react';
|
|
||||||
|
|
||||||
const { Text } = Typography;
|
|
||||||
|
|
||||||
export default function NoLogsContainer(): React.ReactElement {
|
|
||||||
return (
|
|
||||||
<div className="no-logs-found">
|
|
||||||
<Text type="secondary">
|
|
||||||
<Ghost size={24} color={Color.BG_AMBER_500} /> No logs found for this pod in
|
|
||||||
the selected time range.
|
|
||||||
</Text>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@ -1,133 +0,0 @@
|
|||||||
.pod-logs-container {
|
|
||||||
margin-top: 1rem;
|
|
||||||
|
|
||||||
.filter-section {
|
|
||||||
flex: 1;
|
|
||||||
|
|
||||||
.ant-select-selector {
|
|
||||||
border-radius: 2px;
|
|
||||||
border: 1px solid var(--bg-slate-400) !important;
|
|
||||||
background-color: var(--bg-ink-300) !important;
|
|
||||||
|
|
||||||
input {
|
|
||||||
font-size: 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.ant-tag .ant-typography {
|
|
||||||
font-size: 12px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.pod-logs-header {
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
gap: 8px;
|
|
||||||
|
|
||||||
padding: 12px;
|
|
||||||
border-radius: 3px;
|
|
||||||
border: 1px solid var(--bg-slate-500);
|
|
||||||
}
|
|
||||||
|
|
||||||
.pod-logs {
|
|
||||||
margin-top: 1rem;
|
|
||||||
|
|
||||||
.virtuoso-list {
|
|
||||||
overflow-y: hidden !important;
|
|
||||||
|
|
||||||
&::-webkit-scrollbar {
|
|
||||||
width: 0.3rem;
|
|
||||||
height: 0.3rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
&::-webkit-scrollbar-track {
|
|
||||||
background: transparent;
|
|
||||||
}
|
|
||||||
|
|
||||||
&::-webkit-scrollbar-thumb {
|
|
||||||
background: var(--bg-slate-300);
|
|
||||||
}
|
|
||||||
|
|
||||||
&::-webkit-scrollbar-thumb:hover {
|
|
||||||
background: var(--bg-slate-200);
|
|
||||||
}
|
|
||||||
|
|
||||||
.ant-row {
|
|
||||||
width: fit-content;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.skeleton-container {
|
|
||||||
height: 100%;
|
|
||||||
padding: 16px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.pod-logs-list-container {
|
|
||||||
flex: 1;
|
|
||||||
height: calc(100vh - 272px) !important;
|
|
||||||
display: flex;
|
|
||||||
height: 100%;
|
|
||||||
|
|
||||||
.raw-log-content {
|
|
||||||
width: 100%;
|
|
||||||
text-wrap: inherit;
|
|
||||||
word-wrap: break-word;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.pod-logs-list-card {
|
|
||||||
width: 100%;
|
|
||||||
margin-top: 12px;
|
|
||||||
|
|
||||||
.ant-card-body {
|
|
||||||
padding: 0;
|
|
||||||
|
|
||||||
height: 100%;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.logs-loading-skeleton {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
gap: 8px;
|
|
||||||
padding: 8px 0;
|
|
||||||
|
|
||||||
.ant-skeleton-input-sm {
|
|
||||||
height: 18px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.no-logs-found {
|
|
||||||
height: 50vh;
|
|
||||||
width: 100%;
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
|
|
||||||
padding: 24px;
|
|
||||||
box-sizing: border-box;
|
|
||||||
|
|
||||||
.ant-typography {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 16px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.lightMode {
|
|
||||||
.filter-section {
|
|
||||||
border-top: 1px solid var(--bg-vanilla-300);
|
|
||||||
border-bottom: 1px solid var(--bg-vanilla-300);
|
|
||||||
|
|
||||||
.ant-select-selector {
|
|
||||||
border-color: var(--bg-vanilla-300) !important;
|
|
||||||
background-color: var(--bg-vanilla-100) !important;
|
|
||||||
color: var(--bg-ink-200);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,218 +0,0 @@
|
|||||||
/* eslint-disable no-nested-ternary */
|
|
||||||
import './PodLogs.styles.scss';
|
|
||||||
|
|
||||||
import { Card } from 'antd';
|
|
||||||
import RawLogView from 'components/Logs/RawLogView';
|
|
||||||
import OverlayScrollbar from 'components/OverlayScrollbar/OverlayScrollbar';
|
|
||||||
import { DEFAULT_ENTITY_VERSION } from 'constants/app';
|
|
||||||
import LogsError from 'container/LogsError/LogsError';
|
|
||||||
import { LogsLoading } from 'container/LogsLoading/LogsLoading';
|
|
||||||
import { FontSize } from 'container/OptionsMenu/types';
|
|
||||||
import { ORDERBY_FILTERS } from 'container/QueryBuilder/filters/OrderByFilter/config';
|
|
||||||
import { GetMetricQueryRange } from 'lib/dashboard/getQueryResults';
|
|
||||||
import { isEqual } from 'lodash-es';
|
|
||||||
import { useCallback, useEffect, useMemo, useState } from 'react';
|
|
||||||
import { useQuery } from 'react-query';
|
|
||||||
import { Virtuoso } from 'react-virtuoso';
|
|
||||||
import { ILog } from 'types/api/logs/log';
|
|
||||||
import { DataTypes } from 'types/api/queryBuilder/queryAutocompleteResponse';
|
|
||||||
import {
|
|
||||||
IBuilderQuery,
|
|
||||||
TagFilterItem,
|
|
||||||
} from 'types/api/queryBuilder/queryBuilderData';
|
|
||||||
import { v4 } from 'uuid';
|
|
||||||
|
|
||||||
import { QUERY_KEYS } from '../constants';
|
|
||||||
import { getPodLogsQueryPayload } from './constants';
|
|
||||||
import NoLogsContainer from './NoLogsContainer';
|
|
||||||
|
|
||||||
interface Props {
|
|
||||||
timeRange: {
|
|
||||||
startTime: number;
|
|
||||||
endTime: number;
|
|
||||||
};
|
|
||||||
handleChangeLogFilters: (filters: IBuilderQuery['filters']) => void;
|
|
||||||
filters: IBuilderQuery['filters'];
|
|
||||||
}
|
|
||||||
|
|
||||||
function PodLogs({
|
|
||||||
timeRange,
|
|
||||||
handleChangeLogFilters,
|
|
||||||
filters,
|
|
||||||
}: Props): JSX.Element {
|
|
||||||
const [logs, setLogs] = useState<ILog[]>([]);
|
|
||||||
const [hasReachedEndOfLogs, setHasReachedEndOfLogs] = useState(false);
|
|
||||||
const [restFilters, setRestFilters] = useState<TagFilterItem[]>([]);
|
|
||||||
const [resetLogsList, setResetLogsList] = useState<boolean>(false);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const newRestFilters = filters.items.filter(
|
|
||||||
(item) =>
|
|
||||||
item.key?.key !== 'id' &&
|
|
||||||
![
|
|
||||||
QUERY_KEYS.K8S_POD_NAME,
|
|
||||||
QUERY_KEYS.K8S_CLUSTER_NAME,
|
|
||||||
QUERY_KEYS.K8S_NAMESPACE_NAME,
|
|
||||||
].includes(item.key?.key || ''),
|
|
||||||
);
|
|
||||||
|
|
||||||
const areFiltersSame = isEqual(restFilters, newRestFilters);
|
|
||||||
|
|
||||||
if (!areFiltersSame) {
|
|
||||||
setResetLogsList(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
setRestFilters(newRestFilters);
|
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
||||||
}, [filters]);
|
|
||||||
|
|
||||||
const queryPayload = useMemo(() => {
|
|
||||||
const basePayload = getPodLogsQueryPayload(
|
|
||||||
timeRange.startTime,
|
|
||||||
timeRange.endTime,
|
|
||||||
filters,
|
|
||||||
);
|
|
||||||
|
|
||||||
basePayload.query.builder.queryData[0].pageSize = 100;
|
|
||||||
basePayload.query.builder.queryData[0].orderBy = [
|
|
||||||
{ columnName: 'timestamp', order: ORDERBY_FILTERS.DESC },
|
|
||||||
];
|
|
||||||
|
|
||||||
return basePayload;
|
|
||||||
}, [timeRange.startTime, timeRange.endTime, filters]);
|
|
||||||
|
|
||||||
const [isPaginating, setIsPaginating] = useState(false);
|
|
||||||
|
|
||||||
const { data, isLoading, isFetching, isError } = useQuery({
|
|
||||||
queryKey: ['podLogs', timeRange.startTime, timeRange.endTime, filters],
|
|
||||||
queryFn: () => GetMetricQueryRange(queryPayload, DEFAULT_ENTITY_VERSION),
|
|
||||||
enabled: !!queryPayload,
|
|
||||||
keepPreviousData: isPaginating,
|
|
||||||
});
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (data?.payload?.data?.newResult?.data?.result) {
|
|
||||||
const currentData = data.payload.data.newResult.data.result;
|
|
||||||
|
|
||||||
if (resetLogsList) {
|
|
||||||
const currentLogs: ILog[] =
|
|
||||||
currentData[0].list?.map((item) => ({
|
|
||||||
...item.data,
|
|
||||||
timestamp: item.timestamp,
|
|
||||||
})) || [];
|
|
||||||
|
|
||||||
setLogs(currentLogs);
|
|
||||||
|
|
||||||
setResetLogsList(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (currentData.length > 0 && currentData[0].list) {
|
|
||||||
const currentLogs: ILog[] =
|
|
||||||
currentData[0].list.map((item) => ({
|
|
||||||
...item.data,
|
|
||||||
timestamp: item.timestamp,
|
|
||||||
})) || [];
|
|
||||||
|
|
||||||
setLogs((prev) => [...prev, ...currentLogs]);
|
|
||||||
} else {
|
|
||||||
setHasReachedEndOfLogs(true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, [data, restFilters, isPaginating, resetLogsList]);
|
|
||||||
|
|
||||||
const getItemContent = useCallback(
|
|
||||||
(_: number, logToRender: ILog): JSX.Element => (
|
|
||||||
<RawLogView
|
|
||||||
isReadOnly
|
|
||||||
isTextOverflowEllipsisDisabled
|
|
||||||
key={logToRender.id}
|
|
||||||
data={logToRender}
|
|
||||||
linesPerRow={5}
|
|
||||||
fontSize={FontSize.MEDIUM}
|
|
||||||
/>
|
|
||||||
),
|
|
||||||
[],
|
|
||||||
);
|
|
||||||
|
|
||||||
const loadMoreLogs = useCallback(() => {
|
|
||||||
if (!logs.length) return;
|
|
||||||
|
|
||||||
setIsPaginating(true);
|
|
||||||
const lastLog = logs[logs.length - 1];
|
|
||||||
|
|
||||||
const newItems = [
|
|
||||||
...filters.items.filter((item) => item.key?.key !== 'id'),
|
|
||||||
{
|
|
||||||
id: v4(),
|
|
||||||
key: {
|
|
||||||
key: 'id',
|
|
||||||
type: '',
|
|
||||||
dataType: DataTypes.String,
|
|
||||||
isColumn: true,
|
|
||||||
},
|
|
||||||
op: '<',
|
|
||||||
value: lastLog.id,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
const newFilters = {
|
|
||||||
op: 'AND',
|
|
||||||
items: newItems,
|
|
||||||
} as IBuilderQuery['filters'];
|
|
||||||
|
|
||||||
handleChangeLogFilters(newFilters);
|
|
||||||
}, [logs, filters, handleChangeLogFilters]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
setIsPaginating(false);
|
|
||||||
}, [data]);
|
|
||||||
|
|
||||||
const renderFooter = useCallback(
|
|
||||||
(): JSX.Element | null => (
|
|
||||||
// eslint-disable-next-line react/jsx-no-useless-fragment
|
|
||||||
<>
|
|
||||||
{isFetching ? (
|
|
||||||
<div className="logs-loading-skeleton"> Loading more logs ... </div>
|
|
||||||
) : hasReachedEndOfLogs ? (
|
|
||||||
<div className="logs-loading-skeleton"> *** End *** </div>
|
|
||||||
) : null}
|
|
||||||
</>
|
|
||||||
),
|
|
||||||
[isFetching, hasReachedEndOfLogs],
|
|
||||||
);
|
|
||||||
|
|
||||||
const renderContent = useMemo(
|
|
||||||
() => (
|
|
||||||
<Card bordered={false} className="pod-logs-list-card">
|
|
||||||
<OverlayScrollbar isVirtuoso>
|
|
||||||
<Virtuoso
|
|
||||||
className="pod-logs-virtuoso"
|
|
||||||
key="pod-logs-virtuoso"
|
|
||||||
data={logs}
|
|
||||||
endReached={loadMoreLogs}
|
|
||||||
totalCount={logs.length}
|
|
||||||
itemContent={getItemContent}
|
|
||||||
overscan={200}
|
|
||||||
components={{
|
|
||||||
Footer: renderFooter,
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</OverlayScrollbar>
|
|
||||||
</Card>
|
|
||||||
),
|
|
||||||
[logs, loadMoreLogs, getItemContent, renderFooter],
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="pod-logs">
|
|
||||||
{isLoading && <LogsLoading />}
|
|
||||||
{!isLoading && !isError && logs.length === 0 && <NoLogsContainer />}
|
|
||||||
{isError && !isLoading && <LogsError />}
|
|
||||||
{!isLoading && !isError && logs.length > 0 && (
|
|
||||||
<div className="pod-logs-list-container">{renderContent}</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default PodLogs;
|
|
||||||
@ -1,99 +0,0 @@
|
|||||||
import './PodLogs.styles.scss';
|
|
||||||
|
|
||||||
import QueryBuilderSearch from 'container/QueryBuilder/filters/QueryBuilderSearch';
|
|
||||||
import DateTimeSelectionV2 from 'container/TopNav/DateTimeSelectionV2';
|
|
||||||
import {
|
|
||||||
CustomTimeType,
|
|
||||||
Time,
|
|
||||||
} from 'container/TopNav/DateTimeSelectionV2/config';
|
|
||||||
import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
|
|
||||||
import { useMemo } from 'react';
|
|
||||||
import { IBuilderQuery } from 'types/api/queryBuilder/queryBuilderData';
|
|
||||||
import { DataSource } from 'types/common/queryBuilder';
|
|
||||||
|
|
||||||
import PodLogs from './PodLogs';
|
|
||||||
|
|
||||||
interface Props {
|
|
||||||
timeRange: {
|
|
||||||
startTime: number;
|
|
||||||
endTime: number;
|
|
||||||
};
|
|
||||||
isModalTimeSelection: boolean;
|
|
||||||
handleTimeChange: (
|
|
||||||
interval: Time | CustomTimeType,
|
|
||||||
dateTimeRange?: [number, number],
|
|
||||||
) => void;
|
|
||||||
handleChangeLogFilters: (value: IBuilderQuery['filters']) => void;
|
|
||||||
logFilters: IBuilderQuery['filters'];
|
|
||||||
selectedInterval: Time;
|
|
||||||
}
|
|
||||||
|
|
||||||
function PodLogsDetailedView({
|
|
||||||
timeRange,
|
|
||||||
isModalTimeSelection,
|
|
||||||
handleTimeChange,
|
|
||||||
handleChangeLogFilters,
|
|
||||||
logFilters,
|
|
||||||
selectedInterval,
|
|
||||||
}: Props): JSX.Element {
|
|
||||||
const { currentQuery } = useQueryBuilder();
|
|
||||||
const updatedCurrentQuery = useMemo(
|
|
||||||
() => ({
|
|
||||||
...currentQuery,
|
|
||||||
builder: {
|
|
||||||
...currentQuery.builder,
|
|
||||||
queryData: [
|
|
||||||
{
|
|
||||||
...currentQuery.builder.queryData[0],
|
|
||||||
dataSource: DataSource.LOGS,
|
|
||||||
aggregateOperator: 'noop',
|
|
||||||
aggregateAttribute: {
|
|
||||||
...currentQuery.builder.queryData[0].aggregateAttribute,
|
|
||||||
},
|
|
||||||
filters: {
|
|
||||||
items: [],
|
|
||||||
op: 'AND',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
[currentQuery],
|
|
||||||
);
|
|
||||||
|
|
||||||
const query = updatedCurrentQuery?.builder?.queryData[0] || null;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="host-metrics-logs-container">
|
|
||||||
<div className="host-metrics-logs-header">
|
|
||||||
<div className="filter-section">
|
|
||||||
{query && (
|
|
||||||
<QueryBuilderSearch
|
|
||||||
query={query}
|
|
||||||
onChange={handleChangeLogFilters}
|
|
||||||
disableNavigationShortcuts
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
<div className="datetime-section">
|
|
||||||
<DateTimeSelectionV2
|
|
||||||
showAutoRefresh={false}
|
|
||||||
showRefreshText={false}
|
|
||||||
hideShareModal
|
|
||||||
isModalTimeSelection={isModalTimeSelection}
|
|
||||||
onTimeChange={handleTimeChange}
|
|
||||||
defaultRelativeTime="5m"
|
|
||||||
modalSelectedInterval={selectedInterval}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<PodLogs
|
|
||||||
timeRange={timeRange}
|
|
||||||
handleChangeLogFilters={handleChangeLogFilters}
|
|
||||||
filters={logFilters}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default PodLogsDetailedView;
|
|
||||||
@ -1,65 +0,0 @@
|
|||||||
import { PANEL_TYPES } from 'constants/queryBuilder';
|
|
||||||
import { GetQueryResultsProps } from 'lib/dashboard/getQueryResults';
|
|
||||||
import { DataTypes } from 'types/api/queryBuilder/queryAutocompleteResponse';
|
|
||||||
import { IBuilderQuery } from 'types/api/queryBuilder/queryBuilderData';
|
|
||||||
import { EQueryType } from 'types/common/dashboard';
|
|
||||||
import { DataSource } from 'types/common/queryBuilder';
|
|
||||||
import { v4 as uuidv4 } from 'uuid';
|
|
||||||
|
|
||||||
export const getPodLogsQueryPayload = (
|
|
||||||
start: number,
|
|
||||||
end: number,
|
|
||||||
filters: IBuilderQuery['filters'],
|
|
||||||
): GetQueryResultsProps => ({
|
|
||||||
graphType: PANEL_TYPES.LIST,
|
|
||||||
selectedTime: 'GLOBAL_TIME',
|
|
||||||
query: {
|
|
||||||
clickhouse_sql: [],
|
|
||||||
promql: [],
|
|
||||||
builder: {
|
|
||||||
queryData: [
|
|
||||||
{
|
|
||||||
dataSource: DataSource.LOGS,
|
|
||||||
queryName: 'A',
|
|
||||||
aggregateOperator: 'noop',
|
|
||||||
aggregateAttribute: {
|
|
||||||
id: '------false',
|
|
||||||
dataType: DataTypes.String,
|
|
||||||
key: '',
|
|
||||||
isColumn: false,
|
|
||||||
type: '',
|
|
||||||
isJSON: false,
|
|
||||||
},
|
|
||||||
timeAggregation: 'rate',
|
|
||||||
spaceAggregation: 'sum',
|
|
||||||
functions: [],
|
|
||||||
filters,
|
|
||||||
expression: 'A',
|
|
||||||
disabled: false,
|
|
||||||
stepInterval: 60,
|
|
||||||
having: [],
|
|
||||||
limit: null,
|
|
||||||
orderBy: [
|
|
||||||
{
|
|
||||||
columnName: 'timestamp',
|
|
||||||
order: 'desc',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
groupBy: [],
|
|
||||||
legend: '',
|
|
||||||
reduceTo: 'avg',
|
|
||||||
offset: 0,
|
|
||||||
pageSize: 100,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
queryFormulas: [],
|
|
||||||
},
|
|
||||||
id: uuidv4(),
|
|
||||||
queryType: EQueryType.QUERY_BUILDER,
|
|
||||||
},
|
|
||||||
params: {
|
|
||||||
lastLogLineTimestamp: null,
|
|
||||||
},
|
|
||||||
start,
|
|
||||||
end,
|
|
||||||
});
|
|
||||||
@ -1,199 +0,0 @@
|
|||||||
import './PodTraces.styles.scss';
|
|
||||||
|
|
||||||
import { getListColumns } from 'components/HostMetricsDetail/HostMetricTraces/utils';
|
|
||||||
import { ResizeTable } from 'components/ResizeTable';
|
|
||||||
import { DEFAULT_ENTITY_VERSION } from 'constants/app';
|
|
||||||
import { QueryParams } from 'constants/query';
|
|
||||||
import EmptyLogsSearch from 'container/EmptyLogsSearch/EmptyLogsSearch';
|
|
||||||
import NoLogs from 'container/NoLogs/NoLogs';
|
|
||||||
import QueryBuilderSearch from 'container/QueryBuilder/filters/QueryBuilderSearch';
|
|
||||||
import { ErrorText } from 'container/TimeSeriesView/styles';
|
|
||||||
import DateTimeSelectionV2 from 'container/TopNav/DateTimeSelectionV2';
|
|
||||||
import {
|
|
||||||
CustomTimeType,
|
|
||||||
Time,
|
|
||||||
} from 'container/TopNav/DateTimeSelectionV2/config';
|
|
||||||
import TraceExplorerControls from 'container/TracesExplorer/Controls';
|
|
||||||
import { PER_PAGE_OPTIONS } from 'container/TracesExplorer/ListView/configs';
|
|
||||||
import { TracesLoading } from 'container/TracesExplorer/TraceLoading/TraceLoading';
|
|
||||||
import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
|
|
||||||
import { Pagination } from 'hooks/queryPagination';
|
|
||||||
import useUrlQueryData from 'hooks/useUrlQueryData';
|
|
||||||
import { GetMetricQueryRange } from 'lib/dashboard/getQueryResults';
|
|
||||||
import { useEffect, useMemo, useState } from 'react';
|
|
||||||
import { useQuery } from 'react-query';
|
|
||||||
import { IBuilderQuery } from 'types/api/queryBuilder/queryBuilderData';
|
|
||||||
import { DataSource } from 'types/common/queryBuilder';
|
|
||||||
|
|
||||||
import { getPodTracesQueryPayload, selectedColumns } from './constants';
|
|
||||||
|
|
||||||
interface Props {
|
|
||||||
timeRange: {
|
|
||||||
startTime: number;
|
|
||||||
endTime: number;
|
|
||||||
};
|
|
||||||
isModalTimeSelection: boolean;
|
|
||||||
handleTimeChange: (
|
|
||||||
interval: Time | CustomTimeType,
|
|
||||||
dateTimeRange?: [number, number],
|
|
||||||
) => void;
|
|
||||||
handleChangeTracesFilters: (value: IBuilderQuery['filters']) => void;
|
|
||||||
tracesFilters: IBuilderQuery['filters'];
|
|
||||||
selectedInterval: Time;
|
|
||||||
}
|
|
||||||
|
|
||||||
function PodTraces({
|
|
||||||
timeRange,
|
|
||||||
isModalTimeSelection,
|
|
||||||
handleTimeChange,
|
|
||||||
handleChangeTracesFilters,
|
|
||||||
tracesFilters,
|
|
||||||
selectedInterval,
|
|
||||||
}: Props): JSX.Element {
|
|
||||||
const [traces, setTraces] = useState<any[]>([]);
|
|
||||||
const [offset] = useState<number>(0);
|
|
||||||
|
|
||||||
const { currentQuery } = useQueryBuilder();
|
|
||||||
const updatedCurrentQuery = useMemo(
|
|
||||||
() => ({
|
|
||||||
...currentQuery,
|
|
||||||
builder: {
|
|
||||||
...currentQuery.builder,
|
|
||||||
queryData: [
|
|
||||||
{
|
|
||||||
...currentQuery.builder.queryData[0],
|
|
||||||
dataSource: DataSource.TRACES,
|
|
||||||
aggregateOperator: 'noop',
|
|
||||||
aggregateAttribute: {
|
|
||||||
...currentQuery.builder.queryData[0].aggregateAttribute,
|
|
||||||
},
|
|
||||||
filters: {
|
|
||||||
items: [],
|
|
||||||
op: 'AND',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
[currentQuery],
|
|
||||||
);
|
|
||||||
|
|
||||||
const query = updatedCurrentQuery?.builder?.queryData[0] || null;
|
|
||||||
|
|
||||||
const { queryData: paginationQueryData } = useUrlQueryData<Pagination>(
|
|
||||||
QueryParams.pagination,
|
|
||||||
);
|
|
||||||
|
|
||||||
const queryPayload = useMemo(
|
|
||||||
() =>
|
|
||||||
getPodTracesQueryPayload(
|
|
||||||
timeRange.startTime,
|
|
||||||
timeRange.endTime,
|
|
||||||
paginationQueryData?.offset || offset,
|
|
||||||
tracesFilters,
|
|
||||||
),
|
|
||||||
[
|
|
||||||
timeRange.startTime,
|
|
||||||
timeRange.endTime,
|
|
||||||
offset,
|
|
||||||
tracesFilters,
|
|
||||||
paginationQueryData,
|
|
||||||
],
|
|
||||||
);
|
|
||||||
|
|
||||||
const { data, isLoading, isFetching, isError } = useQuery({
|
|
||||||
queryKey: [
|
|
||||||
'podTraces',
|
|
||||||
timeRange.startTime,
|
|
||||||
timeRange.endTime,
|
|
||||||
offset,
|
|
||||||
tracesFilters,
|
|
||||||
DEFAULT_ENTITY_VERSION,
|
|
||||||
paginationQueryData,
|
|
||||||
],
|
|
||||||
queryFn: () => GetMetricQueryRange(queryPayload, DEFAULT_ENTITY_VERSION),
|
|
||||||
enabled: !!queryPayload,
|
|
||||||
});
|
|
||||||
|
|
||||||
const traceListColumns = getListColumns(selectedColumns);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (data?.payload?.data?.newResult?.data?.result) {
|
|
||||||
const currentData = data.payload.data.newResult.data.result;
|
|
||||||
if (currentData.length > 0 && currentData[0].list) {
|
|
||||||
if (offset === 0) {
|
|
||||||
setTraces(currentData[0].list ?? []);
|
|
||||||
} else {
|
|
||||||
setTraces((prev) => [...prev, ...(currentData[0].list ?? [])]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, [data, offset]);
|
|
||||||
|
|
||||||
const isDataEmpty =
|
|
||||||
!isLoading && !isFetching && !isError && traces.length === 0;
|
|
||||||
const hasAdditionalFilters = tracesFilters.items.length > 1;
|
|
||||||
|
|
||||||
const totalCount =
|
|
||||||
data?.payload?.data?.newResult?.data?.result?.[0]?.list?.length || 0;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="host-metric-traces">
|
|
||||||
<div className="host-metric-traces-header">
|
|
||||||
<div className="filter-section">
|
|
||||||
{query && (
|
|
||||||
<QueryBuilderSearch
|
|
||||||
query={query}
|
|
||||||
onChange={handleChangeTracesFilters}
|
|
||||||
disableNavigationShortcuts
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
<div className="datetime-section">
|
|
||||||
<DateTimeSelectionV2
|
|
||||||
showAutoRefresh={false}
|
|
||||||
showRefreshText={false}
|
|
||||||
hideShareModal
|
|
||||||
isModalTimeSelection={isModalTimeSelection}
|
|
||||||
onTimeChange={handleTimeChange}
|
|
||||||
defaultRelativeTime="5m"
|
|
||||||
modalSelectedInterval={selectedInterval}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{isError && <ErrorText>{data?.error || 'Something went wrong'}</ErrorText>}
|
|
||||||
|
|
||||||
{isLoading && traces.length === 0 && <TracesLoading />}
|
|
||||||
|
|
||||||
{isDataEmpty && !hasAdditionalFilters && (
|
|
||||||
<NoLogs dataSource={DataSource.TRACES} />
|
|
||||||
)}
|
|
||||||
|
|
||||||
{isDataEmpty && hasAdditionalFilters && (
|
|
||||||
<EmptyLogsSearch dataSource={DataSource.TRACES} panelType="LIST" />
|
|
||||||
)}
|
|
||||||
|
|
||||||
{!isError && traces.length > 0 && (
|
|
||||||
<div className="pod-traces-table">
|
|
||||||
<TraceExplorerControls
|
|
||||||
isLoading={isFetching}
|
|
||||||
totalCount={totalCount}
|
|
||||||
perPageOptions={PER_PAGE_OPTIONS}
|
|
||||||
showSizeChanger={false}
|
|
||||||
/>
|
|
||||||
<ResizeTable
|
|
||||||
tableLayout="fixed"
|
|
||||||
pagination={false}
|
|
||||||
scroll={{ x: true }}
|
|
||||||
loading={isFetching}
|
|
||||||
dataSource={traces}
|
|
||||||
columns={traceListColumns}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default PodTraces;
|
|
||||||
File diff suppressed because it is too large
Load Diff
2672
frontend/src/container/InfraMonitoringK8s/Pods/constants.ts
generated
2672
frontend/src/container/InfraMonitoringK8s/Pods/constants.ts
generated
File diff suppressed because it is too large
Load Diff
@ -1,14 +1,18 @@
|
|||||||
|
/* eslint-disable @typescript-eslint/no-unused-vars */
|
||||||
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
/* eslint-disable react/require-default-props */
|
/* eslint-disable react/require-default-props */
|
||||||
/* eslint-disable @typescript-eslint/explicit-function-return-type */
|
/* eslint-disable @typescript-eslint/explicit-function-return-type */
|
||||||
|
/* eslint-disable prefer-destructuring */
|
||||||
|
|
||||||
import { Color } from '@signozhq/design-tokens';
|
import { Color } from '@signozhq/design-tokens';
|
||||||
import { Tooltip, Typography } from 'antd';
|
import { Tooltip, Typography } from 'antd';
|
||||||
import { ColumnsType } from 'antd/es/table';
|
import Table, { ColumnsType } from 'antd/es/table';
|
||||||
import { Progress } from 'antd/lib';
|
import { Progress } from 'antd/lib';
|
||||||
import { ResizeTable } from 'components/ResizeTable';
|
import { ResizeTable } from 'components/ResizeTable';
|
||||||
import FieldRenderer from 'container/LogDetailedView/FieldRenderer';
|
import FieldRenderer from 'container/LogDetailedView/FieldRenderer';
|
||||||
import { DataType } from 'container/LogDetailedView/TableView';
|
import { DataType } from 'container/LogDetailedView/TableView';
|
||||||
import { useMemo } from 'react';
|
import { useMemo } from 'react';
|
||||||
|
import { TagFilterItem } from 'types/api/queryBuilder/queryBuilderData';
|
||||||
|
|
||||||
import { getInvalidValueTooltipText, K8sCategory } from './constants';
|
import { getInvalidValueTooltipText, K8sCategory } from './constants';
|
||||||
|
|
||||||
@ -173,3 +177,77 @@ export function EventContents({
|
|||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const getMetricsTableData = (data: any): any[] => {
|
||||||
|
if (data?.params && data?.payload?.data?.result?.length) {
|
||||||
|
const rowsData = (data?.payload.data.result[0] as any).table.rows;
|
||||||
|
const columnsData = (data?.payload.data.result[0] as any).table.columns;
|
||||||
|
const builderQueries = data.params?.compositeQuery?.builderQueries;
|
||||||
|
const columns = columnsData.map((columnData: any) => {
|
||||||
|
console.log({ columnData });
|
||||||
|
if (columnData.isValueColumn) {
|
||||||
|
return {
|
||||||
|
key: columnData.name,
|
||||||
|
label: builderQueries[columnData.name].legend,
|
||||||
|
isValueColumn: true,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
key: columnData.name,
|
||||||
|
label: columnData.name,
|
||||||
|
isValueColumn: false,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
const rows = rowsData.map((rowData: any) => rowData.data);
|
||||||
|
return [{ rows, columns }];
|
||||||
|
}
|
||||||
|
return [{ rows: [], columns: [] }];
|
||||||
|
};
|
||||||
|
|
||||||
|
export function MetricsTable({
|
||||||
|
rows,
|
||||||
|
columns,
|
||||||
|
}: {
|
||||||
|
rows: any[];
|
||||||
|
columns: any[];
|
||||||
|
}): JSX.Element {
|
||||||
|
const columnsData = columns.map((col: any) => ({
|
||||||
|
title: <Tooltip title={col.label}>{col.label}</Tooltip>,
|
||||||
|
dataIndex: col.key,
|
||||||
|
key: col.key,
|
||||||
|
sorter: false,
|
||||||
|
ellipsis: true,
|
||||||
|
render: (value: string) => <Tooltip title={value}>{value}</Tooltip>,
|
||||||
|
}));
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="metrics-table">
|
||||||
|
<Table
|
||||||
|
dataSource={rows}
|
||||||
|
columns={columnsData}
|
||||||
|
tableLayout="fixed"
|
||||||
|
pagination={{ pageSize: 10, showSizeChanger: false }}
|
||||||
|
scroll={{ y: 180 }}
|
||||||
|
sticky
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export const filterDuplicateFilters = (
|
||||||
|
filters: TagFilterItem[],
|
||||||
|
): TagFilterItem[] => {
|
||||||
|
const uniqueFilters = [];
|
||||||
|
const seenIds = new Set();
|
||||||
|
|
||||||
|
// eslint-disable-next-line no-restricted-syntax
|
||||||
|
for (const filter of filters) {
|
||||||
|
if (!seenIds.has(filter.id)) {
|
||||||
|
seenIds.add(filter.id);
|
||||||
|
uniqueFilters.push(filter);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return uniqueFilters;
|
||||||
|
};
|
||||||
|
|||||||
@ -1,3 +1,4 @@
|
|||||||
|
/* eslint-disable sonarjs/no-duplicate-string */
|
||||||
import {
|
import {
|
||||||
FiltersType,
|
FiltersType,
|
||||||
IQuickFiltersConfig,
|
IQuickFiltersConfig,
|
||||||
@ -208,7 +209,7 @@ export const NodesQuickFiltersConfig: IQuickFiltersConfig[] = [
|
|||||||
export const NamespaceQuickFiltersConfig: IQuickFiltersConfig[] = [
|
export const NamespaceQuickFiltersConfig: IQuickFiltersConfig[] = [
|
||||||
{
|
{
|
||||||
type: FiltersType.CHECKBOX,
|
type: FiltersType.CHECKBOX,
|
||||||
title: 'Namespace',
|
title: 'Namespace Name',
|
||||||
attributeKey: {
|
attributeKey: {
|
||||||
key: 'k8s_namespace_name',
|
key: 'k8s_namespace_name',
|
||||||
dataType: DataTypes.String,
|
dataType: DataTypes.String,
|
||||||
@ -216,6 +217,24 @@ export const NamespaceQuickFiltersConfig: IQuickFiltersConfig[] = [
|
|||||||
isColumn: false,
|
isColumn: false,
|
||||||
isJSON: false,
|
isJSON: false,
|
||||||
},
|
},
|
||||||
|
aggregateOperator: 'noop',
|
||||||
|
aggregateAttribute: 'k8s_pod_cpu_utilization',
|
||||||
|
dataSource: DataSource.METRICS,
|
||||||
|
defaultOpen: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: FiltersType.CHECKBOX,
|
||||||
|
title: 'Cluster Name',
|
||||||
|
attributeKey: {
|
||||||
|
key: 'k8s_cluster_name',
|
||||||
|
dataType: DataTypes.String,
|
||||||
|
type: 'resource',
|
||||||
|
isColumn: false,
|
||||||
|
isJSON: false,
|
||||||
|
},
|
||||||
|
aggregateOperator: 'noop',
|
||||||
|
aggregateAttribute: 'k8s_pod_cpu_utilization',
|
||||||
|
dataSource: DataSource.METRICS,
|
||||||
defaultOpen: true,
|
defaultOpen: true,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
@ -223,14 +242,17 @@ export const NamespaceQuickFiltersConfig: IQuickFiltersConfig[] = [
|
|||||||
export const ClustersQuickFiltersConfig: IQuickFiltersConfig[] = [
|
export const ClustersQuickFiltersConfig: IQuickFiltersConfig[] = [
|
||||||
{
|
{
|
||||||
type: FiltersType.CHECKBOX,
|
type: FiltersType.CHECKBOX,
|
||||||
title: 'Cluster',
|
title: 'Cluster Name',
|
||||||
attributeKey: {
|
attributeKey: {
|
||||||
key: 'k8s.cluster.name',
|
key: 'k8s_cluster_name',
|
||||||
dataType: DataTypes.String,
|
dataType: DataTypes.String,
|
||||||
type: 'resource',
|
type: 'resource',
|
||||||
isColumn: false,
|
isColumn: false,
|
||||||
isJSON: false,
|
isJSON: false,
|
||||||
},
|
},
|
||||||
|
aggregateOperator: 'noop',
|
||||||
|
aggregateAttribute: 'k8s_pod_cpu_utilization',
|
||||||
|
dataSource: DataSource.METRICS,
|
||||||
defaultOpen: true,
|
defaultOpen: true,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
@ -268,7 +290,7 @@ export const VolumesQuickFiltersConfig: IQuickFiltersConfig[] = [
|
|||||||
export const DeploymentsQuickFiltersConfig: IQuickFiltersConfig[] = [
|
export const DeploymentsQuickFiltersConfig: IQuickFiltersConfig[] = [
|
||||||
{
|
{
|
||||||
type: FiltersType.CHECKBOX,
|
type: FiltersType.CHECKBOX,
|
||||||
title: 'Deployment',
|
title: 'Deployment Name',
|
||||||
attributeKey: {
|
attributeKey: {
|
||||||
key: 'k8s_deployment_name',
|
key: 'k8s_deployment_name',
|
||||||
dataType: DataTypes.String,
|
dataType: DataTypes.String,
|
||||||
@ -276,6 +298,39 @@ export const DeploymentsQuickFiltersConfig: IQuickFiltersConfig[] = [
|
|||||||
isColumn: false,
|
isColumn: false,
|
||||||
isJSON: false,
|
isJSON: false,
|
||||||
},
|
},
|
||||||
|
aggregateOperator: 'noop',
|
||||||
|
aggregateAttribute: 'k8s_pod_cpu_utilization',
|
||||||
|
dataSource: DataSource.METRICS,
|
||||||
|
defaultOpen: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: FiltersType.CHECKBOX,
|
||||||
|
title: 'Namespace Name',
|
||||||
|
attributeKey: {
|
||||||
|
key: 'k8s_namespace_name',
|
||||||
|
dataType: DataTypes.String,
|
||||||
|
type: 'resource',
|
||||||
|
isColumn: false,
|
||||||
|
isJSON: false,
|
||||||
|
},
|
||||||
|
aggregateOperator: 'noop',
|
||||||
|
aggregateAttribute: 'k8s_pod_cpu_utilization',
|
||||||
|
dataSource: DataSource.METRICS,
|
||||||
|
defaultOpen: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: FiltersType.CHECKBOX,
|
||||||
|
title: 'Cluster Name',
|
||||||
|
attributeKey: {
|
||||||
|
key: 'k8s_cluster_name',
|
||||||
|
dataType: DataTypes.String,
|
||||||
|
type: 'resource',
|
||||||
|
isColumn: false,
|
||||||
|
isJSON: false,
|
||||||
|
},
|
||||||
|
aggregateOperator: 'noop',
|
||||||
|
aggregateAttribute: 'k8s_pod_cpu_utilization',
|
||||||
|
dataSource: DataSource.METRICS,
|
||||||
defaultOpen: true,
|
defaultOpen: true,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|||||||
@ -1,18 +0,0 @@
|
|||||||
import { TagFilterItem } from 'types/api/queryBuilder/queryBuilderData';
|
|
||||||
|
|
||||||
export const filterDuplicateFilters = (
|
|
||||||
filters: TagFilterItem[],
|
|
||||||
): TagFilterItem[] => {
|
|
||||||
const uniqueFilters = [];
|
|
||||||
const seenIds = new Set();
|
|
||||||
|
|
||||||
// eslint-disable-next-line no-restricted-syntax
|
|
||||||
for (const filter of filters) {
|
|
||||||
if (!seenIds.has(filter.id)) {
|
|
||||||
seenIds.add(filter.id);
|
|
||||||
uniqueFilters.push(filter);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return uniqueFilters;
|
|
||||||
};
|
|
||||||
54
frontend/src/hooks/infraMonitoring/useGetK8sClustersList.ts
Normal file
54
frontend/src/hooks/infraMonitoring/useGetK8sClustersList.ts
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
import {
|
||||||
|
getK8sClustersList,
|
||||||
|
K8sClustersListPayload,
|
||||||
|
K8sClustersListResponse,
|
||||||
|
} from 'api/infraMonitoring/getK8sClustersList';
|
||||||
|
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 UseGetK8sClustersList = (
|
||||||
|
requestData: K8sClustersListPayload,
|
||||||
|
|
||||||
|
options?: UseQueryOptions<
|
||||||
|
SuccessResponse<K8sClustersListResponse> | ErrorResponse,
|
||||||
|
Error
|
||||||
|
>,
|
||||||
|
|
||||||
|
headers?: Record<string, string>,
|
||||||
|
) => UseQueryResult<
|
||||||
|
SuccessResponse<K8sClustersListResponse> | ErrorResponse,
|
||||||
|
Error
|
||||||
|
>;
|
||||||
|
|
||||||
|
export const useGetK8sClustersList: UseGetK8sClustersList = (
|
||||||
|
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_CLUSTER_LIST, requestData];
|
||||||
|
}, [options?.queryKey, requestData]);
|
||||||
|
|
||||||
|
return useQuery<
|
||||||
|
SuccessResponse<K8sClustersListResponse> | ErrorResponse,
|
||||||
|
Error
|
||||||
|
>({
|
||||||
|
queryFn: ({ signal }) => getK8sClustersList(requestData, signal, headers),
|
||||||
|
|
||||||
|
...options,
|
||||||
|
|
||||||
|
queryKey,
|
||||||
|
});
|
||||||
|
};
|
||||||
@ -0,0 +1,48 @@
|
|||||||
|
import {
|
||||||
|
getK8sDeploymentsList,
|
||||||
|
K8sDeploymentsListPayload,
|
||||||
|
K8sDeploymentsListResponse,
|
||||||
|
} from 'api/infraMonitoring/getK8sDeploymentsList';
|
||||||
|
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 UseGetK8sDeploymentsList = (
|
||||||
|
requestData: K8sDeploymentsListPayload,
|
||||||
|
options?: UseQueryOptions<
|
||||||
|
SuccessResponse<K8sDeploymentsListResponse> | ErrorResponse,
|
||||||
|
Error
|
||||||
|
>,
|
||||||
|
headers?: Record<string, string>,
|
||||||
|
) => UseQueryResult<
|
||||||
|
SuccessResponse<K8sDeploymentsListResponse> | ErrorResponse,
|
||||||
|
Error
|
||||||
|
>;
|
||||||
|
|
||||||
|
export const useGetK8sDeploymentsList: UseGetK8sDeploymentsList = (
|
||||||
|
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_DEPLOYMENT_LIST, requestData];
|
||||||
|
}, [options?.queryKey, requestData]);
|
||||||
|
|
||||||
|
return useQuery<
|
||||||
|
SuccessResponse<K8sDeploymentsListResponse> | ErrorResponse,
|
||||||
|
Error
|
||||||
|
>({
|
||||||
|
queryFn: ({ signal }) => getK8sDeploymentsList(requestData, signal, headers),
|
||||||
|
...options,
|
||||||
|
queryKey,
|
||||||
|
});
|
||||||
|
};
|
||||||
@ -0,0 +1,48 @@
|
|||||||
|
import {
|
||||||
|
getK8sNamespacesList,
|
||||||
|
K8sNamespacesListPayload,
|
||||||
|
K8sNamespacesListResponse,
|
||||||
|
} from 'api/infraMonitoring/getK8sNamespacesList';
|
||||||
|
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 UseGetK8sNamespacesList = (
|
||||||
|
requestData: K8sNamespacesListPayload,
|
||||||
|
options?: UseQueryOptions<
|
||||||
|
SuccessResponse<K8sNamespacesListResponse> | ErrorResponse,
|
||||||
|
Error
|
||||||
|
>,
|
||||||
|
headers?: Record<string, string>,
|
||||||
|
) => UseQueryResult<
|
||||||
|
SuccessResponse<K8sNamespacesListResponse> | ErrorResponse,
|
||||||
|
Error
|
||||||
|
>;
|
||||||
|
|
||||||
|
export const useGetK8sNamespacesList: UseGetK8sNamespacesList = (
|
||||||
|
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_NAMESPACE_LIST, requestData];
|
||||||
|
}, [options?.queryKey, requestData]);
|
||||||
|
|
||||||
|
return useQuery<
|
||||||
|
SuccessResponse<K8sNamespacesListResponse> | ErrorResponse,
|
||||||
|
Error
|
||||||
|
>({
|
||||||
|
queryFn: ({ signal }) => getK8sNamespacesList(requestData, signal, headers),
|
||||||
|
...options,
|
||||||
|
queryKey,
|
||||||
|
});
|
||||||
|
};
|
||||||
@ -34,7 +34,7 @@ export const useGetK8sNodesList: UseGetK8sNodesList = (
|
|||||||
return options.queryKey;
|
return options.queryKey;
|
||||||
}
|
}
|
||||||
|
|
||||||
return [REACT_QUERY_KEY.GET_HOST_LIST, requestData];
|
return [REACT_QUERY_KEY.GET_NODE_LIST, requestData];
|
||||||
}, [options?.queryKey, requestData]);
|
}, [options?.queryKey, requestData]);
|
||||||
|
|
||||||
return useQuery<SuccessResponse<K8sNodesListResponse> | ErrorResponse, Error>({
|
return useQuery<SuccessResponse<K8sNodesListResponse> | ErrorResponse, Error>({
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user