Compare commits
11 Commits
tpapi-demo
...
host-lists
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b33be9c7f9 | ||
|
|
d1c85361e9 | ||
|
|
f5ec5b2b05 | ||
|
|
ecf897f769 | ||
|
|
6648e841eb | ||
|
|
403fe9d55b | ||
|
|
689440bcfb | ||
|
|
3d57dde02a | ||
|
|
08512b9392 | ||
|
|
ae014d1ead | ||
|
|
f8eeec62ad |
@@ -224,3 +224,10 @@ export const MQDetailPage = Loadable(
|
|||||||
/* webpackChunkName: "MQDetailPage" */ 'pages/MessagingQueues/MQDetailPage'
|
/* webpackChunkName: "MQDetailPage" */ 'pages/MessagingQueues/MQDetailPage'
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
export const InfrastructureMonitoring = Loadable(
|
||||||
|
() =>
|
||||||
|
import(
|
||||||
|
/* webpackChunkName: "InfrastructureMonitoring" */ 'pages/InfrastructureMonitoring'
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import {
|
|||||||
EditAlertChannelsAlerts,
|
EditAlertChannelsAlerts,
|
||||||
EditRulesPage,
|
EditRulesPage,
|
||||||
ErrorDetails,
|
ErrorDetails,
|
||||||
|
InfrastructureMonitoring,
|
||||||
IngestionSettings,
|
IngestionSettings,
|
||||||
InstalledIntegrations,
|
InstalledIntegrations,
|
||||||
LicensePage,
|
LicensePage,
|
||||||
@@ -383,6 +384,13 @@ const routes: AppRoutes[] = [
|
|||||||
key: 'MESSAGING_QUEUES_DETAIL',
|
key: 'MESSAGING_QUEUES_DETAIL',
|
||||||
isPrivate: true,
|
isPrivate: true,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: ROUTES.INFRASTRUCTURE_MONITORING_HOSTS,
|
||||||
|
exact: true,
|
||||||
|
component: InfrastructureMonitoring,
|
||||||
|
key: 'INFRASTRUCTURE_MONITORING_HOSTS',
|
||||||
|
isPrivate: true,
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
export const SUPPORT_ROUTE: AppRoutes = {
|
export const SUPPORT_ROUTE: AppRoutes = {
|
||||||
|
|||||||
75
frontend/src/api/infraMonitoring/getHostLists.ts
Normal file
75
frontend/src/api/infraMonitoring/getHostLists.ts
Normal file
@@ -0,0 +1,75 @@
|
|||||||
|
import { ApiBaseInstance } from 'api';
|
||||||
|
import { ErrorResponseHandler } from 'api/ErrorResponseHandler';
|
||||||
|
import { AxiosError } from 'axios';
|
||||||
|
import { ErrorResponse, SuccessResponse } from 'types/api';
|
||||||
|
import { BaseAutocompleteData } from 'types/api/queryBuilder/queryAutocompleteResponse';
|
||||||
|
import { TagFilter } from 'types/api/queryBuilder/queryBuilderData';
|
||||||
|
|
||||||
|
export interface HostListPayload {
|
||||||
|
filters: TagFilter;
|
||||||
|
groupBy: BaseAutocompleteData[];
|
||||||
|
offset?: number;
|
||||||
|
limit?: number;
|
||||||
|
orderBy?: {
|
||||||
|
columnName: string;
|
||||||
|
order: 'asc' | 'desc';
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface TimeSeriesValue {
|
||||||
|
timestamp: number;
|
||||||
|
value: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface TimeSeries {
|
||||||
|
labels: Record<string, string>;
|
||||||
|
labelsArray: Array<Record<string, string>>;
|
||||||
|
values: TimeSeriesValue[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface HostData {
|
||||||
|
hostName: string;
|
||||||
|
active: boolean;
|
||||||
|
os: string;
|
||||||
|
cpu: number;
|
||||||
|
cpuTimeSeries: TimeSeries;
|
||||||
|
memory: number;
|
||||||
|
memoryTimeSeries: TimeSeries;
|
||||||
|
wait: number;
|
||||||
|
waitTimeSeries: TimeSeries;
|
||||||
|
load15: number;
|
||||||
|
load15TimeSeries: TimeSeries;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface HostListResponse {
|
||||||
|
status: string;
|
||||||
|
data: {
|
||||||
|
type: string;
|
||||||
|
records: HostData[];
|
||||||
|
groups: null;
|
||||||
|
total: number;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export const getHostLists = async (
|
||||||
|
props: HostListPayload,
|
||||||
|
signal?: AbortSignal,
|
||||||
|
headers?: Record<string, string>,
|
||||||
|
): Promise<SuccessResponse<HostListResponse> | ErrorResponse> => {
|
||||||
|
try {
|
||||||
|
const response = await ApiBaseInstance.post('/hosts/list', props, {
|
||||||
|
signal,
|
||||||
|
headers,
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
statusCode: 200,
|
||||||
|
error: null,
|
||||||
|
message: 'Success',
|
||||||
|
payload: response.data,
|
||||||
|
params: props,
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
return ErrorResponseHandler(error as AxiosError);
|
||||||
|
}
|
||||||
|
};
|
||||||
38
frontend/src/api/infraMonitoring/getInfraAttributeValues.ts
Normal file
38
frontend/src/api/infraMonitoring/getInfraAttributeValues.ts
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
import { ApiBaseInstance } from 'api';
|
||||||
|
import { ErrorResponseHandler } from 'api/ErrorResponseHandler';
|
||||||
|
import { AxiosError } from 'axios';
|
||||||
|
import createQueryParams from 'lib/createQueryParams';
|
||||||
|
import { ErrorResponse, SuccessResponse } from 'types/api';
|
||||||
|
import {
|
||||||
|
IAttributeValuesResponse,
|
||||||
|
IGetAttributeValuesPayload,
|
||||||
|
} from 'types/api/queryBuilder/getAttributesValues';
|
||||||
|
|
||||||
|
export const getInfraAttributesValues = async ({
|
||||||
|
dataSource,
|
||||||
|
attributeKey,
|
||||||
|
filterAttributeKeyDataType,
|
||||||
|
tagType,
|
||||||
|
searchText,
|
||||||
|
}: IGetAttributeValuesPayload): Promise<
|
||||||
|
SuccessResponse<IAttributeValuesResponse> | ErrorResponse
|
||||||
|
> => {
|
||||||
|
try {
|
||||||
|
const response = await ApiBaseInstance.get(
|
||||||
|
`/hosts/attribute_values?${createQueryParams({
|
||||||
|
dataSource,
|
||||||
|
attributeKey,
|
||||||
|
searchText,
|
||||||
|
})}&filterAttributeKeyDataType=${filterAttributeKeyDataType}&tagType=${tagType}`,
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
statusCode: 200,
|
||||||
|
error: null,
|
||||||
|
message: response.data.status,
|
||||||
|
payload: response.data.data,
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
return ErrorResponseHandler(error as AxiosError);
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import { ApiV3Instance } from 'api';
|
import { ApiBaseInstance, ApiV3Instance } from 'api';
|
||||||
import { ErrorResponseHandler } from 'api/ErrorResponseHandler';
|
import { ErrorResponseHandler } from 'api/ErrorResponseHandler';
|
||||||
import { AxiosError, AxiosResponse } from 'axios';
|
import { AxiosError, AxiosResponse } from 'axios';
|
||||||
import { baseAutoCompleteIdKeysOrder } from 'constants/queryBuilder';
|
import { baseAutoCompleteIdKeysOrder } from 'constants/queryBuilder';
|
||||||
@@ -18,20 +18,25 @@ export const getAggregateKeys = async ({
|
|||||||
dataSource,
|
dataSource,
|
||||||
aggregateAttribute,
|
aggregateAttribute,
|
||||||
tagType,
|
tagType,
|
||||||
|
isInfraMonitoring,
|
||||||
}: IGetAttributeKeysPayload): Promise<
|
}: IGetAttributeKeysPayload): Promise<
|
||||||
SuccessResponse<IQueryAutocompleteResponse> | ErrorResponse
|
SuccessResponse<IQueryAutocompleteResponse> | ErrorResponse
|
||||||
> => {
|
> => {
|
||||||
try {
|
try {
|
||||||
|
const endpoint = isInfraMonitoring
|
||||||
|
? `/hosts/attribute_keys?dataSource=metrics&searchText=${searchText || ''}`
|
||||||
|
: `/autocomplete/attribute_keys?${createQueryParams({
|
||||||
|
aggregateOperator,
|
||||||
|
searchText,
|
||||||
|
dataSource,
|
||||||
|
aggregateAttribute,
|
||||||
|
})}&tagType=${tagType}`;
|
||||||
|
|
||||||
|
const apiInstance = isInfraMonitoring ? ApiBaseInstance : ApiV3Instance;
|
||||||
|
|
||||||
const response: AxiosResponse<{
|
const response: AxiosResponse<{
|
||||||
data: IQueryAutocompleteResponse;
|
data: IQueryAutocompleteResponse;
|
||||||
}> = await ApiV3Instance.get(
|
}> = await apiInstance.get(endpoint);
|
||||||
`/autocomplete/attribute_keys?${createQueryParams({
|
|
||||||
aggregateOperator,
|
|
||||||
searchText,
|
|
||||||
dataSource,
|
|
||||||
aggregateAttribute,
|
|
||||||
})}&tagType=${tagType}`,
|
|
||||||
);
|
|
||||||
|
|
||||||
const payload: BaseAutocompleteData[] =
|
const payload: BaseAutocompleteData[] =
|
||||||
response.data.data.attributeKeys?.map(({ id: _, ...item }) => ({
|
response.data.data.attributeKeys?.map(({ id: _, ...item }) => ({
|
||||||
|
|||||||
@@ -18,4 +18,5 @@ export const REACT_QUERY_KEY = {
|
|||||||
GET_ALL_ALLERTS: 'GET_ALL_ALLERTS',
|
GET_ALL_ALLERTS: 'GET_ALL_ALLERTS',
|
||||||
REMOVE_ALERT_RULE: 'REMOVE_ALERT_RULE',
|
REMOVE_ALERT_RULE: 'REMOVE_ALERT_RULE',
|
||||||
DUPLICATE_ALERT_RULE: 'DUPLICATE_ALERT_RULE',
|
DUPLICATE_ALERT_RULE: 'DUPLICATE_ALERT_RULE',
|
||||||
|
GET_HOST_LIST: 'GET_HOST_LIST',
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -58,6 +58,7 @@ const ROUTES = {
|
|||||||
INTEGRATIONS: '/integrations',
|
INTEGRATIONS: '/integrations',
|
||||||
MESSAGING_QUEUES: '/messaging-queues',
|
MESSAGING_QUEUES: '/messaging-queues',
|
||||||
MESSAGING_QUEUES_DETAIL: '/messaging-queues/detail',
|
MESSAGING_QUEUES_DETAIL: '/messaging-queues/detail',
|
||||||
|
INFRASTRUCTURE_MONITORING_HOSTS: '/infrastructure-monitoring/hosts',
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
export default ROUTES;
|
export default ROUTES;
|
||||||
|
|||||||
@@ -0,0 +1,19 @@
|
|||||||
|
.loading-host-metrics {
|
||||||
|
padding: 24px 0;
|
||||||
|
height: 600px;
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
.loading-host-metrics-content {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
flex-direction: column;
|
||||||
|
|
||||||
|
.loading-gif {
|
||||||
|
height: 72px;
|
||||||
|
margin-left: -24px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
import './HostMetricsLoading.styles.scss';
|
||||||
|
|
||||||
|
import { Typography } from 'antd';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { DataSource } from 'types/common/queryBuilder';
|
||||||
|
|
||||||
|
export function HostMetricsLoading(): JSX.Element {
|
||||||
|
const { t } = useTranslation('common');
|
||||||
|
return (
|
||||||
|
<div className="loading-host-metrics">
|
||||||
|
<div className="loading-host-metrics-content">
|
||||||
|
<img
|
||||||
|
className="loading-gif"
|
||||||
|
src="/Icons/loading-plane.gif"
|
||||||
|
alt="wait-icon"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Typography>
|
||||||
|
{t('pending_data_placeholder', {
|
||||||
|
dataSource: `host ${DataSource.METRICS}`,
|
||||||
|
})}
|
||||||
|
</Typography>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -4,5 +4,6 @@ import { IBuilderQuery } from 'types/api/queryBuilder/queryBuilderData';
|
|||||||
export type GroupByFilterProps = {
|
export type GroupByFilterProps = {
|
||||||
query: IBuilderQuery;
|
query: IBuilderQuery;
|
||||||
onChange: (values: BaseAutocompleteData[]) => void;
|
onChange: (values: BaseAutocompleteData[]) => void;
|
||||||
disabled: boolean;
|
disabled?: boolean;
|
||||||
|
isInfraMonitoring?: boolean;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ export const GroupByFilter = memo(function GroupByFilter({
|
|||||||
query,
|
query,
|
||||||
onChange,
|
onChange,
|
||||||
disabled,
|
disabled,
|
||||||
|
isInfraMonitoring,
|
||||||
}: GroupByFilterProps): JSX.Element {
|
}: GroupByFilterProps): JSX.Element {
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
const [searchText, setSearchText] = useState<string>('');
|
const [searchText, setSearchText] = useState<string>('');
|
||||||
@@ -85,6 +86,7 @@ export const GroupByFilter = memo(function GroupByFilter({
|
|||||||
setOptionsData(options);
|
setOptionsData(options);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
isInfraMonitoring,
|
||||||
);
|
);
|
||||||
|
|
||||||
const getAttributeKeys = useCallback(async () => {
|
const getAttributeKeys = useCallback(async () => {
|
||||||
@@ -96,6 +98,7 @@ export const GroupByFilter = memo(function GroupByFilter({
|
|||||||
dataSource: query.dataSource,
|
dataSource: query.dataSource,
|
||||||
aggregateOperator: query.aggregateOperator,
|
aggregateOperator: query.aggregateOperator,
|
||||||
searchText,
|
searchText,
|
||||||
|
isInfraMonitoring,
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -107,6 +110,7 @@ export const GroupByFilter = memo(function GroupByFilter({
|
|||||||
query.dataSource,
|
query.dataSource,
|
||||||
queryClient,
|
queryClient,
|
||||||
searchText,
|
searchText,
|
||||||
|
isInfraMonitoring,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const handleSearchKeys = (searchText: string): void => {
|
const handleSearchKeys = (searchText: string): void => {
|
||||||
|
|||||||
@@ -72,6 +72,7 @@ function QueryBuilderSearch({
|
|||||||
className,
|
className,
|
||||||
placeholder,
|
placeholder,
|
||||||
suffixIcon,
|
suffixIcon,
|
||||||
|
isInfraMonitoring,
|
||||||
}: QueryBuilderSearchProps): JSX.Element {
|
}: QueryBuilderSearchProps): JSX.Element {
|
||||||
const { pathname } = useLocation();
|
const { pathname } = useLocation();
|
||||||
const isLogsExplorerPage = useMemo(() => pathname === ROUTES.LOGS_EXPLORER, [
|
const isLogsExplorerPage = useMemo(() => pathname === ROUTES.LOGS_EXPLORER, [
|
||||||
@@ -93,7 +94,12 @@ function QueryBuilderSearch({
|
|||||||
searchKey,
|
searchKey,
|
||||||
key,
|
key,
|
||||||
exampleQueries,
|
exampleQueries,
|
||||||
} = useAutoComplete(query, whereClauseConfig, isLogsExplorerPage);
|
} = useAutoComplete(
|
||||||
|
query,
|
||||||
|
whereClauseConfig,
|
||||||
|
isLogsExplorerPage,
|
||||||
|
isInfraMonitoring,
|
||||||
|
);
|
||||||
const [isOpen, setIsOpen] = useState<boolean>(false);
|
const [isOpen, setIsOpen] = useState<boolean>(false);
|
||||||
const [showAllFilters, setShowAllFilters] = useState<boolean>(false);
|
const [showAllFilters, setShowAllFilters] = useState<boolean>(false);
|
||||||
const [dynamicPlacholder, setDynamicPlaceholder] = useState<string>(
|
const [dynamicPlacholder, setDynamicPlaceholder] = useState<string>(
|
||||||
@@ -105,6 +111,7 @@ function QueryBuilderSearch({
|
|||||||
query,
|
query,
|
||||||
searchKey,
|
searchKey,
|
||||||
isLogsExplorerPage,
|
isLogsExplorerPage,
|
||||||
|
isInfraMonitoring,
|
||||||
);
|
);
|
||||||
|
|
||||||
const { registerShortcut, deregisterShortcut } = useKeyboardHotkeys();
|
const { registerShortcut, deregisterShortcut } = useKeyboardHotkeys();
|
||||||
@@ -185,8 +192,8 @@ function QueryBuilderSearch({
|
|||||||
);
|
);
|
||||||
|
|
||||||
const isMetricsDataSource = useMemo(
|
const isMetricsDataSource = useMemo(
|
||||||
() => query.dataSource === DataSource.METRICS,
|
() => query.dataSource === DataSource.METRICS && !isInfraMonitoring,
|
||||||
[query.dataSource],
|
[query.dataSource, isInfraMonitoring],
|
||||||
);
|
);
|
||||||
|
|
||||||
const fetchValueDataType = (value: unknown, operator: string): DataTypes => {
|
const fetchValueDataType = (value: unknown, operator: string): DataTypes => {
|
||||||
@@ -426,6 +433,7 @@ interface QueryBuilderSearchProps {
|
|||||||
className?: string;
|
className?: string;
|
||||||
placeholder?: string;
|
placeholder?: string;
|
||||||
suffixIcon?: React.ReactNode;
|
suffixIcon?: React.ReactNode;
|
||||||
|
isInfraMonitoring?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
QueryBuilderSearch.defaultProps = {
|
QueryBuilderSearch.defaultProps = {
|
||||||
@@ -433,6 +441,7 @@ QueryBuilderSearch.defaultProps = {
|
|||||||
className: '',
|
className: '',
|
||||||
placeholder: PLACEHOLDER,
|
placeholder: PLACEHOLDER,
|
||||||
suffixIcon: undefined,
|
suffixIcon: undefined,
|
||||||
|
isInfraMonitoring: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
export interface CustomTagProps {
|
export interface CustomTagProps {
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import {
|
|||||||
LayoutGrid,
|
LayoutGrid,
|
||||||
ListMinus,
|
ListMinus,
|
||||||
MessageSquare,
|
MessageSquare,
|
||||||
|
PackagePlus,
|
||||||
Receipt,
|
Receipt,
|
||||||
Route,
|
Route,
|
||||||
ScrollText,
|
ScrollText,
|
||||||
@@ -118,6 +119,11 @@ const menuItems: SidebarItem[] = [
|
|||||||
label: 'Billing',
|
label: 'Billing',
|
||||||
icon: <Receipt size={16} />,
|
icon: <Receipt size={16} />,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
key: ROUTES.INFRASTRUCTURE_MONITORING_HOSTS,
|
||||||
|
label: 'Infrastructure Monitoring',
|
||||||
|
icon: <PackagePlus size={16} />,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
key: ROUTES.SETTINGS,
|
key: ROUTES.SETTINGS,
|
||||||
label: 'Settings',
|
label: 'Settings',
|
||||||
|
|||||||
@@ -212,6 +212,7 @@ export const routesToSkip = [
|
|||||||
ROUTES.ALERT_OVERVIEW,
|
ROUTES.ALERT_OVERVIEW,
|
||||||
ROUTES.MESSAGING_QUEUES,
|
ROUTES.MESSAGING_QUEUES,
|
||||||
ROUTES.MESSAGING_QUEUES_DETAIL,
|
ROUTES.MESSAGING_QUEUES_DETAIL,
|
||||||
|
ROUTES.INFRASTRUCTURE_MONITORING_HOSTS,
|
||||||
];
|
];
|
||||||
|
|
||||||
export const routesToDisable = [ROUTES.LOGS_EXPLORER, ROUTES.LIVE_LOGS];
|
export const routesToDisable = [ROUTES.LOGS_EXPLORER, ROUTES.LIVE_LOGS];
|
||||||
|
|||||||
34
frontend/src/hooks/infraMonitoring/useGetAggregateKeys.ts
Normal file
34
frontend/src/hooks/infraMonitoring/useGetAggregateKeys.ts
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
import { getAggregateKeys } from 'api/queryBuilder/getAttributeKeys';
|
||||||
|
import { QueryBuilderKeys } from 'constants/queryBuilder';
|
||||||
|
import { useMemo } from 'react';
|
||||||
|
import { useQuery, UseQueryOptions, UseQueryResult } from 'react-query';
|
||||||
|
import { ErrorResponse, SuccessResponse } from 'types/api';
|
||||||
|
import { IGetAttributeKeysPayload } from 'types/api/queryBuilder/getAttributeKeys';
|
||||||
|
import { IQueryAutocompleteResponse } from 'types/api/queryBuilder/queryAutocompleteResponse';
|
||||||
|
|
||||||
|
type UseGetAttributeKeys = (
|
||||||
|
requestData: IGetAttributeKeysPayload,
|
||||||
|
options?: UseQueryOptions<
|
||||||
|
SuccessResponse<IQueryAutocompleteResponse> | ErrorResponse
|
||||||
|
>,
|
||||||
|
) => UseQueryResult<
|
||||||
|
SuccessResponse<IQueryAutocompleteResponse> | ErrorResponse
|
||||||
|
>;
|
||||||
|
|
||||||
|
export const useGetAggregateKeys: UseGetAttributeKeys = (
|
||||||
|
requestData,
|
||||||
|
options,
|
||||||
|
) => {
|
||||||
|
const queryKey = useMemo(() => {
|
||||||
|
if (options?.queryKey && Array.isArray(options.queryKey)) {
|
||||||
|
return [QueryBuilderKeys.GET_AGGREGATE_KEYS, ...options.queryKey];
|
||||||
|
}
|
||||||
|
return [QueryBuilderKeys.GET_AGGREGATE_KEYS, requestData];
|
||||||
|
}, [options?.queryKey, requestData]);
|
||||||
|
|
||||||
|
return useQuery<SuccessResponse<IQueryAutocompleteResponse> | ErrorResponse>({
|
||||||
|
queryKey,
|
||||||
|
queryFn: () => getAggregateKeys(requestData),
|
||||||
|
...options,
|
||||||
|
});
|
||||||
|
};
|
||||||
42
frontend/src/hooks/infraMonitoring/useGetHostList.ts
Normal file
42
frontend/src/hooks/infraMonitoring/useGetHostList.ts
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
import {
|
||||||
|
getHostLists,
|
||||||
|
HostListPayload,
|
||||||
|
HostListResponse,
|
||||||
|
} from 'api/infraMonitoring/getHostLists';
|
||||||
|
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 UseGetHostList = (
|
||||||
|
requestData: HostListPayload,
|
||||||
|
options?: UseQueryOptions<
|
||||||
|
SuccessResponse<HostListResponse> | ErrorResponse,
|
||||||
|
Error
|
||||||
|
>,
|
||||||
|
headers?: Record<string, string>,
|
||||||
|
) => UseQueryResult<SuccessResponse<HostListResponse> | ErrorResponse, Error>;
|
||||||
|
|
||||||
|
export const useGetHostList: UseGetHostList = (
|
||||||
|
requestData,
|
||||||
|
options,
|
||||||
|
headers,
|
||||||
|
) => {
|
||||||
|
const queryKey = useMemo(() => {
|
||||||
|
if (options?.queryKey && Array.isArray(options.queryKey)) {
|
||||||
|
return [...options.queryKey];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options?.queryKey && typeof options.queryKey === 'string') {
|
||||||
|
return options.queryKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
return [REACT_QUERY_KEY.GET_HOST_LIST, requestData];
|
||||||
|
}, [options?.queryKey, requestData]);
|
||||||
|
|
||||||
|
return useQuery<SuccessResponse<HostListResponse> | ErrorResponse, Error>({
|
||||||
|
queryFn: ({ signal }) => getHostLists(requestData, signal, headers),
|
||||||
|
...options,
|
||||||
|
queryKey,
|
||||||
|
});
|
||||||
|
};
|
||||||
@@ -28,6 +28,7 @@ export const useAutoComplete = (
|
|||||||
query: IBuilderQuery,
|
query: IBuilderQuery,
|
||||||
whereClauseConfig?: WhereClauseConfig,
|
whereClauseConfig?: WhereClauseConfig,
|
||||||
shouldUseSuggestions?: boolean,
|
shouldUseSuggestions?: boolean,
|
||||||
|
isInfraMonitoring?: boolean,
|
||||||
): IAutoComplete => {
|
): IAutoComplete => {
|
||||||
const [searchValue, setSearchValue] = useState<string>('');
|
const [searchValue, setSearchValue] = useState<string>('');
|
||||||
const [searchKey, setSearchKey] = useState<string>('');
|
const [searchKey, setSearchKey] = useState<string>('');
|
||||||
@@ -37,6 +38,7 @@ export const useAutoComplete = (
|
|||||||
query,
|
query,
|
||||||
searchKey,
|
searchKey,
|
||||||
shouldUseSuggestions,
|
shouldUseSuggestions,
|
||||||
|
isInfraMonitoring,
|
||||||
);
|
);
|
||||||
|
|
||||||
const [key, operator, result] = useSetCurrentKeyAndOperator(searchValue, keys);
|
const [key, operator, result] = useSetCurrentKeyAndOperator(searchValue, keys);
|
||||||
@@ -170,4 +172,5 @@ interface IAutoComplete {
|
|||||||
searchKey: string;
|
searchKey: string;
|
||||||
key: string;
|
key: string;
|
||||||
exampleQueries: TagFilter[];
|
exampleQueries: TagFilter[];
|
||||||
|
isInfraMonitoring?: boolean;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { getInfraAttributesValues } from 'api/infraMonitoring/getInfraAttributeValues';
|
||||||
import { getAttributesValues } from 'api/queryBuilder/getAttributesValues';
|
import { getAttributesValues } from 'api/queryBuilder/getAttributesValues';
|
||||||
import { DEBOUNCE_DELAY } from 'constants/queryBuilderFilterConfig';
|
import { DEBOUNCE_DELAY } from 'constants/queryBuilderFilterConfig';
|
||||||
import {
|
import {
|
||||||
@@ -43,6 +44,7 @@ export const useFetchKeysAndValues = (
|
|||||||
query: IBuilderQuery,
|
query: IBuilderQuery,
|
||||||
searchKey: string,
|
searchKey: string,
|
||||||
shouldUseSuggestions?: boolean,
|
shouldUseSuggestions?: boolean,
|
||||||
|
isInfraMonitoring?: boolean,
|
||||||
): IuseFetchKeysAndValues => {
|
): IuseFetchKeysAndValues => {
|
||||||
const [keys, setKeys] = useState<BaseAutocompleteData[]>([]);
|
const [keys, setKeys] = useState<BaseAutocompleteData[]>([]);
|
||||||
const [exampleQueries, setExampleQueries] = useState<TagFilter[]>([]);
|
const [exampleQueries, setExampleQueries] = useState<TagFilter[]>([]);
|
||||||
@@ -91,10 +93,10 @@ export const useFetchKeysAndValues = (
|
|||||||
|
|
||||||
const isQueryEnabled = useMemo(
|
const isQueryEnabled = useMemo(
|
||||||
() =>
|
() =>
|
||||||
query.dataSource === DataSource.METRICS
|
query.dataSource === DataSource.METRICS && !isInfraMonitoring
|
||||||
? !!query.dataSource && !!query.aggregateAttribute.dataType
|
? !!query.dataSource && !!query.aggregateAttribute.dataType
|
||||||
: true,
|
: true,
|
||||||
[query.aggregateAttribute.dataType, query.dataSource],
|
[isInfraMonitoring, query.aggregateAttribute.dataType, query.dataSource],
|
||||||
);
|
);
|
||||||
|
|
||||||
const { data, isFetching, status } = useGetAggregateKeys(
|
const { data, isFetching, status } = useGetAggregateKeys(
|
||||||
@@ -109,6 +111,7 @@ export const useFetchKeysAndValues = (
|
|||||||
queryKey: [searchParams],
|
queryKey: [searchParams],
|
||||||
enabled: isQueryEnabled && !shouldUseSuggestions,
|
enabled: isQueryEnabled && !shouldUseSuggestions,
|
||||||
},
|
},
|
||||||
|
isInfraMonitoring,
|
||||||
);
|
);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
@@ -136,6 +139,7 @@ export const useFetchKeysAndValues = (
|
|||||||
value: string,
|
value: string,
|
||||||
query: IBuilderQuery,
|
query: IBuilderQuery,
|
||||||
keys: BaseAutocompleteData[],
|
keys: BaseAutocompleteData[],
|
||||||
|
// eslint-disable-next-line sonarjs/cognitive-complexity
|
||||||
): Promise<void> => {
|
): Promise<void> => {
|
||||||
if (!value) {
|
if (!value) {
|
||||||
return;
|
return;
|
||||||
@@ -152,17 +156,36 @@ export const useFetchKeysAndValues = (
|
|||||||
setAggregateFetching(true);
|
setAggregateFetching(true);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const { payload } = await getAttributesValues({
|
let payload;
|
||||||
aggregateOperator: query.aggregateOperator,
|
if (isInfraMonitoring) {
|
||||||
dataSource: query.dataSource,
|
const response = await getInfraAttributesValues({
|
||||||
aggregateAttribute: query.aggregateAttribute.key,
|
dataSource: query.dataSource,
|
||||||
attributeKey: filterAttributeKey?.key ?? tagKey,
|
attributeKey: filterAttributeKey?.key ?? tagKey,
|
||||||
filterAttributeKeyDataType: filterAttributeKey?.dataType ?? DataTypes.EMPTY,
|
filterAttributeKeyDataType:
|
||||||
tagType: filterAttributeKey?.type ?? '',
|
filterAttributeKey?.dataType ?? DataTypes.EMPTY,
|
||||||
searchText: isInNInOperator(tagOperator)
|
tagType: filterAttributeKey?.type ?? '',
|
||||||
? tagValue[tagValue.length - 1]?.toString() ?? '' // last element of tagvalue will be always user search value
|
searchText: isInNInOperator(tagOperator)
|
||||||
: tagValue?.toString() ?? '',
|
? tagValue[tagValue.length - 1]?.toString() ?? ''
|
||||||
});
|
: tagValue?.toString() ?? '',
|
||||||
|
aggregateOperator: query.aggregateOperator,
|
||||||
|
aggregateAttribute: query.aggregateAttribute.key,
|
||||||
|
});
|
||||||
|
payload = response.payload;
|
||||||
|
} else {
|
||||||
|
const response = await getAttributesValues({
|
||||||
|
aggregateOperator: query.aggregateOperator,
|
||||||
|
dataSource: query.dataSource,
|
||||||
|
aggregateAttribute: query.aggregateAttribute.key,
|
||||||
|
attributeKey: filterAttributeKey?.key ?? tagKey,
|
||||||
|
filterAttributeKeyDataType:
|
||||||
|
filterAttributeKey?.dataType ?? DataTypes.EMPTY,
|
||||||
|
tagType: filterAttributeKey?.type ?? '',
|
||||||
|
searchText: isInNInOperator(tagOperator)
|
||||||
|
? tagValue[tagValue.length - 1]?.toString() ?? ''
|
||||||
|
: tagValue?.toString() ?? '',
|
||||||
|
});
|
||||||
|
payload = response.payload;
|
||||||
|
}
|
||||||
|
|
||||||
if (payload) {
|
if (payload) {
|
||||||
const values = Object.values(payload).find((el) => !!el) || [];
|
const values = Object.values(payload).find((el) => !!el) || [];
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ type UseGetAttributeKeys = (
|
|||||||
options?: UseQueryOptions<
|
options?: UseQueryOptions<
|
||||||
SuccessResponse<IQueryAutocompleteResponse> | ErrorResponse
|
SuccessResponse<IQueryAutocompleteResponse> | ErrorResponse
|
||||||
>,
|
>,
|
||||||
|
isInfraMonitoring?: boolean,
|
||||||
) => UseQueryResult<
|
) => UseQueryResult<
|
||||||
SuccessResponse<IQueryAutocompleteResponse> | ErrorResponse
|
SuccessResponse<IQueryAutocompleteResponse> | ErrorResponse
|
||||||
>;
|
>;
|
||||||
@@ -18,17 +19,22 @@ type UseGetAttributeKeys = (
|
|||||||
export const useGetAggregateKeys: UseGetAttributeKeys = (
|
export const useGetAggregateKeys: UseGetAttributeKeys = (
|
||||||
requestData,
|
requestData,
|
||||||
options,
|
options,
|
||||||
|
isInfraMonitoring,
|
||||||
) => {
|
) => {
|
||||||
const queryKey = useMemo(() => {
|
const queryKey = useMemo(() => {
|
||||||
if (options?.queryKey && Array.isArray(options.queryKey)) {
|
if (options?.queryKey && Array.isArray(options.queryKey)) {
|
||||||
return [QueryBuilderKeys.GET_AGGREGATE_KEYS, ...options.queryKey];
|
return [
|
||||||
|
QueryBuilderKeys.GET_AGGREGATE_KEYS,
|
||||||
|
...options.queryKey,
|
||||||
|
isInfraMonitoring,
|
||||||
|
];
|
||||||
}
|
}
|
||||||
return [QueryBuilderKeys.GET_AGGREGATE_KEYS, requestData];
|
return [QueryBuilderKeys.GET_AGGREGATE_KEYS, requestData, isInfraMonitoring];
|
||||||
}, [options?.queryKey, requestData]);
|
}, [options?.queryKey, requestData, isInfraMonitoring]);
|
||||||
|
|
||||||
return useQuery<SuccessResponse<IQueryAutocompleteResponse> | ErrorResponse>({
|
return useQuery<SuccessResponse<IQueryAutocompleteResponse> | ErrorResponse>({
|
||||||
queryKey,
|
queryKey,
|
||||||
queryFn: () => getAggregateKeys(requestData),
|
queryFn: () => getAggregateKeys({ ...requestData, isInfraMonitoring }),
|
||||||
...options,
|
...options,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ import {
|
|||||||
} from 'container/TopNav/DateTimeSelectionV2/config';
|
} from 'container/TopNav/DateTimeSelectionV2/config';
|
||||||
import { Pagination } from 'hooks/queryPagination';
|
import { Pagination } from 'hooks/queryPagination';
|
||||||
import { convertNewDataToOld } from 'lib/newQueryBuilder/convertNewDataToOld';
|
import { convertNewDataToOld } from 'lib/newQueryBuilder/convertNewDataToOld';
|
||||||
import { isEmpty, cloneDeep } from 'lodash-es';
|
import { isEmpty } from 'lodash-es';
|
||||||
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 { Query } from 'types/api/queryBuilder/queryBuilderData';
|
import { Query } from 'types/api/queryBuilder/queryBuilderData';
|
||||||
@@ -24,6 +24,7 @@ export async function GetMetricQueryRange(
|
|||||||
version: string,
|
version: string,
|
||||||
signal?: AbortSignal,
|
signal?: AbortSignal,
|
||||||
headers?: Record<string, string>,
|
headers?: Record<string, string>,
|
||||||
|
isInfraMonitoring?: boolean,
|
||||||
): Promise<SuccessResponse<MetricRangePayloadProps>> {
|
): Promise<SuccessResponse<MetricRangePayloadProps>> {
|
||||||
const { legendMap, queryPayload } = prepareQueryRangePayload(props);
|
const { legendMap, queryPayload } = prepareQueryRangePayload(props);
|
||||||
const response = await getMetricsQueryRange(
|
const response = await getMetricsQueryRange(
|
||||||
|
|||||||
141
frontend/src/pages/InfraMonitoringHosts/HostsList.tsx
Normal file
141
frontend/src/pages/InfraMonitoringHosts/HostsList.tsx
Normal file
@@ -0,0 +1,141 @@
|
|||||||
|
import { Table, TablePaginationConfig, TableProps, Typography } from 'antd';
|
||||||
|
import { SorterResult } from 'antd/es/table/interface';
|
||||||
|
import { HostListPayload } from 'api/infraMonitoring/getHostLists';
|
||||||
|
import { HostMetricsLoading } from 'container/HostMetricsLoading/HostMetricsLoading';
|
||||||
|
import NoLogs from 'container/NoLogs/NoLogs';
|
||||||
|
import { useGetHostList } from 'hooks/infraMonitoring/useGetHostList';
|
||||||
|
import { useCallback, useMemo, useState } from 'react';
|
||||||
|
import { useSelector } from 'react-redux';
|
||||||
|
import { AppState } from 'store/reducers';
|
||||||
|
import { IBuilderQuery } from 'types/api/queryBuilder/queryBuilderData';
|
||||||
|
import { DataSource } from 'types/common/queryBuilder';
|
||||||
|
import { GlobalReducer } from 'types/reducer/globalTime';
|
||||||
|
|
||||||
|
import HostsListControls from './HostsListControls';
|
||||||
|
import {
|
||||||
|
formatDataForTable,
|
||||||
|
getHostListsQuery,
|
||||||
|
getHostsListColumns,
|
||||||
|
HostRowData,
|
||||||
|
} from './utils';
|
||||||
|
|
||||||
|
function HostsList(): JSX.Element {
|
||||||
|
const { maxTime, minTime } = useSelector<AppState, GlobalReducer>(
|
||||||
|
(state) => state.globalTime,
|
||||||
|
);
|
||||||
|
const [currentPage, setCurrentPage] = useState(1);
|
||||||
|
const [filters, setFilters] = useState<IBuilderQuery['filters']>({
|
||||||
|
items: [],
|
||||||
|
op: 'and',
|
||||||
|
});
|
||||||
|
|
||||||
|
const [orderBy, setOrderBy] = useState<{
|
||||||
|
columnName: string;
|
||||||
|
order: 'asc' | 'desc';
|
||||||
|
} | null>(null);
|
||||||
|
|
||||||
|
const pageSize = 10;
|
||||||
|
|
||||||
|
const query = useMemo(() => {
|
||||||
|
const baseQuery = getHostListsQuery();
|
||||||
|
return {
|
||||||
|
...baseQuery,
|
||||||
|
limit: pageSize,
|
||||||
|
offset: (currentPage - 1) * pageSize,
|
||||||
|
filters,
|
||||||
|
start: Math.floor(minTime / 1000000),
|
||||||
|
end: Math.floor(maxTime / 1000000),
|
||||||
|
orderBy,
|
||||||
|
};
|
||||||
|
}, [currentPage, filters, minTime, maxTime, orderBy]);
|
||||||
|
|
||||||
|
const { data, isFetching, isLoading, isError } = useGetHostList(
|
||||||
|
query as HostListPayload,
|
||||||
|
{
|
||||||
|
queryKey: ['hostList', query],
|
||||||
|
enabled: !!query,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
const hostMetricsData = useMemo(() => data?.payload?.data?.records || [], [
|
||||||
|
data,
|
||||||
|
]);
|
||||||
|
const totalCount = data?.payload?.data?.total || 0;
|
||||||
|
|
||||||
|
const formattedHostMetricsData = useMemo(
|
||||||
|
() => formatDataForTable(hostMetricsData),
|
||||||
|
[hostMetricsData],
|
||||||
|
);
|
||||||
|
|
||||||
|
const columns = useMemo(() => getHostsListColumns(), []);
|
||||||
|
|
||||||
|
const isDataPresent =
|
||||||
|
!isLoading && !isFetching && !isError && hostMetricsData.length === 0;
|
||||||
|
|
||||||
|
const handleTableChange: TableProps<HostRowData>['onChange'] = useCallback(
|
||||||
|
(
|
||||||
|
pagination: TablePaginationConfig,
|
||||||
|
_filters: Record<string, (string | number | boolean)[] | null>,
|
||||||
|
sorter: SorterResult<HostRowData> | SorterResult<HostRowData>[],
|
||||||
|
): void => {
|
||||||
|
if (pagination.current) {
|
||||||
|
setCurrentPage(pagination.current);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ('field' in sorter && sorter.order) {
|
||||||
|
setOrderBy({
|
||||||
|
columnName: sorter.field as string,
|
||||||
|
order: sorter.order === 'ascend' ? 'asc' : 'desc',
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
setOrderBy(null);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[],
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleFiltersChange = useCallback(
|
||||||
|
(value: IBuilderQuery['filters']): void => {
|
||||||
|
setFilters(value);
|
||||||
|
},
|
||||||
|
[],
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<HostsListControls handleFiltersChange={handleFiltersChange} />
|
||||||
|
{isError && <Typography>{data?.error || 'Something went wrong'}</Typography>}
|
||||||
|
|
||||||
|
{isLoading && <HostMetricsLoading />}
|
||||||
|
|
||||||
|
{isDataPresent && filters.items.length === 0 && (
|
||||||
|
<NoLogs dataSource={DataSource.METRICS} />
|
||||||
|
)}
|
||||||
|
|
||||||
|
{isDataPresent && filters.items.length > 0 && (
|
||||||
|
<div>No hosts match the applied filters.</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{!isError && formattedHostMetricsData.length > 0 && (
|
||||||
|
<Table
|
||||||
|
dataSource={formattedHostMetricsData}
|
||||||
|
columns={columns}
|
||||||
|
pagination={{
|
||||||
|
current: currentPage,
|
||||||
|
pageSize,
|
||||||
|
total: totalCount,
|
||||||
|
showSizeChanger: false,
|
||||||
|
hideOnSinglePage: true,
|
||||||
|
}}
|
||||||
|
scroll={{ x: true }}
|
||||||
|
loading={isFetching}
|
||||||
|
tableLayout="fixed"
|
||||||
|
rowKey={(record): string => record.hostName}
|
||||||
|
onChange={handleTableChange}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default HostsList;
|
||||||
@@ -0,0 +1,58 @@
|
|||||||
|
import QueryBuilderSearch from 'container/QueryBuilder/filters/QueryBuilderSearch';
|
||||||
|
import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
|
||||||
|
import { useQueryOperations } from 'hooks/queryBuilder/useQueryBuilderOperations';
|
||||||
|
import { useCallback, useMemo } from 'react';
|
||||||
|
import { IBuilderQuery } from 'types/api/queryBuilder/queryBuilderData';
|
||||||
|
|
||||||
|
function HostsListControls({
|
||||||
|
handleFiltersChange,
|
||||||
|
}: {
|
||||||
|
handleFiltersChange: (value: IBuilderQuery['filters']) => void;
|
||||||
|
}): JSX.Element {
|
||||||
|
const { currentQuery } = useQueryBuilder();
|
||||||
|
const updatedCurrentQuery = useMemo(
|
||||||
|
() => ({
|
||||||
|
...currentQuery,
|
||||||
|
builder: {
|
||||||
|
...currentQuery.builder,
|
||||||
|
queryData: [
|
||||||
|
{
|
||||||
|
...currentQuery.builder.queryData[0],
|
||||||
|
aggregateOperator: 'noop',
|
||||||
|
aggregateAttribute: {
|
||||||
|
...currentQuery.builder.queryData[0].aggregateAttribute,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
[currentQuery],
|
||||||
|
);
|
||||||
|
const query = updatedCurrentQuery?.builder?.queryData[0] || null;
|
||||||
|
const { handleChangeQueryData } = useQueryOperations({
|
||||||
|
index: 0,
|
||||||
|
query,
|
||||||
|
isListViewPanel: true,
|
||||||
|
entityVersion: '',
|
||||||
|
});
|
||||||
|
|
||||||
|
const handleChangeTagFilters = useCallback(
|
||||||
|
(value: IBuilderQuery['filters']) => {
|
||||||
|
handleChangeQueryData('filters', value);
|
||||||
|
handleFiltersChange(value);
|
||||||
|
},
|
||||||
|
[handleChangeQueryData, handleFiltersChange],
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="hosts-list-controls">
|
||||||
|
<QueryBuilderSearch
|
||||||
|
query={query}
|
||||||
|
onChange={handleChangeTagFilters}
|
||||||
|
isInfraMonitoring
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default HostsListControls;
|
||||||
@@ -0,0 +1,59 @@
|
|||||||
|
.infra-monitoring-container {
|
||||||
|
display: flex;
|
||||||
|
height: 100%;
|
||||||
|
margin-top: 1rem;
|
||||||
|
|
||||||
|
.time-selector {
|
||||||
|
position: absolute;
|
||||||
|
top: 9px;
|
||||||
|
right: 0;
|
||||||
|
padding: 0 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.infra-monitoring-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
width: 100%;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
|
||||||
|
.tabs-wrapper {
|
||||||
|
flex: 1;
|
||||||
|
margin-right: 24px;
|
||||||
|
|
||||||
|
.infra-monitoring-tabs {
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
:global(.ant-tabs-nav) {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.time-selector {
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.hosts-list-controls {
|
||||||
|
margin: 1rem 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.progress-container {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.progress-bar {
|
||||||
|
flex: 1;
|
||||||
|
margin-right: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.clickable-row {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.infra-monitoring-tags {
|
||||||
|
border-radius: 10px;
|
||||||
|
width: fit-content;
|
||||||
|
}
|
||||||
|
}
|
||||||
36
frontend/src/pages/InfraMonitoringHosts/index.tsx
Normal file
36
frontend/src/pages/InfraMonitoringHosts/index.tsx
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
import './InfraMonitoring.styles.scss';
|
||||||
|
|
||||||
|
import * as Sentry from '@sentry/react';
|
||||||
|
import { Tabs } from 'antd';
|
||||||
|
import DateTimeSelectionV2 from 'container/TopNav/DateTimeSelectionV2';
|
||||||
|
import ErrorBoundaryFallback from 'pages/ErrorBoundaryFallback/ErrorBoundaryFallback';
|
||||||
|
|
||||||
|
import { getTabsItems } from './utils';
|
||||||
|
|
||||||
|
function InfraMonitoringHosts(): JSX.Element {
|
||||||
|
return (
|
||||||
|
<Sentry.ErrorBoundary fallback={<ErrorBoundaryFallback />}>
|
||||||
|
<div className="infra-monitoring-container">
|
||||||
|
<div className="infra-monitoring-header">
|
||||||
|
<div className="tabs-wrapper">
|
||||||
|
<Tabs
|
||||||
|
defaultActiveKey="list"
|
||||||
|
items={getTabsItems()}
|
||||||
|
className="infra-monitoring-tabs"
|
||||||
|
type="card"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="time-selector">
|
||||||
|
<DateTimeSelectionV2
|
||||||
|
showAutoRefresh={false}
|
||||||
|
showRefreshText={false}
|
||||||
|
hideShareModal
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Sentry.ErrorBoundary>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default InfraMonitoringHosts;
|
||||||
121
frontend/src/pages/InfraMonitoringHosts/utils.tsx
Normal file
121
frontend/src/pages/InfraMonitoringHosts/utils.tsx
Normal file
@@ -0,0 +1,121 @@
|
|||||||
|
import './InfraMonitoring.styles.scss';
|
||||||
|
|
||||||
|
import { Color } from '@signozhq/design-tokens';
|
||||||
|
import { Progress, TabsProps, Tag } from 'antd';
|
||||||
|
import { ColumnType } from 'antd/es/table';
|
||||||
|
import { HostData, HostListPayload } from 'api/infraMonitoring/getHostLists';
|
||||||
|
import TabLabel from 'components/TabLabel';
|
||||||
|
import { PANEL_TYPES } from 'constants/queryBuilder';
|
||||||
|
|
||||||
|
import HostsList from './HostsList';
|
||||||
|
|
||||||
|
export interface HostRowData {
|
||||||
|
hostName: string;
|
||||||
|
cpu: React.ReactNode;
|
||||||
|
memory: React.ReactNode;
|
||||||
|
ioWait: number;
|
||||||
|
load15: number;
|
||||||
|
active: React.ReactNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const getHostListsQuery = (): HostListPayload => ({
|
||||||
|
filters: {
|
||||||
|
items: [],
|
||||||
|
op: 'and',
|
||||||
|
},
|
||||||
|
groupBy: [],
|
||||||
|
orderBy: { columnName: '', order: 'asc' },
|
||||||
|
});
|
||||||
|
export const getTabsItems = (): TabsProps['items'] => [
|
||||||
|
{
|
||||||
|
label: <TabLabel label="List View" isDisabled={false} tooltipText="" />,
|
||||||
|
key: PANEL_TYPES.LIST,
|
||||||
|
children: <HostsList />,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
export const getHostsListColumns = (): ColumnType<HostRowData>[] => [
|
||||||
|
{
|
||||||
|
title: 'Hostname',
|
||||||
|
dataIndex: 'hostName',
|
||||||
|
key: 'hostName',
|
||||||
|
width: 150,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Status',
|
||||||
|
dataIndex: 'active',
|
||||||
|
key: 'active',
|
||||||
|
width: 100,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'CPU Usage',
|
||||||
|
dataIndex: 'cpu',
|
||||||
|
key: 'cpu',
|
||||||
|
width: 100,
|
||||||
|
sorter: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Memory Usage',
|
||||||
|
dataIndex: 'memory',
|
||||||
|
key: 'memory',
|
||||||
|
width: 100,
|
||||||
|
sorter: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'IOWait',
|
||||||
|
dataIndex: 'wait',
|
||||||
|
key: 'wait',
|
||||||
|
width: 100,
|
||||||
|
sorter: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Load Avg',
|
||||||
|
dataIndex: 'load15',
|
||||||
|
key: 'load15',
|
||||||
|
width: 100,
|
||||||
|
sorter: true,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
export const formatDataForTable = (data: HostData[]): HostRowData[] =>
|
||||||
|
data.map((host, index) => ({
|
||||||
|
key: `${host.hostName}-${index}`,
|
||||||
|
hostName: host.hostName || '',
|
||||||
|
active: (
|
||||||
|
<Tag color={host.active ? 'success' : 'default'} bordered>
|
||||||
|
{host.active ? 'ACTIVE' : 'INACTIVE'}
|
||||||
|
</Tag>
|
||||||
|
),
|
||||||
|
cpu: (
|
||||||
|
<div className="progress-container">
|
||||||
|
<Progress
|
||||||
|
percent={Number((host.cpu * 100).toFixed(1))}
|
||||||
|
size="small"
|
||||||
|
strokeColor={((): string => {
|
||||||
|
const cpuPercent = Number((host.cpu * 100).toFixed(1));
|
||||||
|
if (cpuPercent >= 90) return Color.BG_SAKURA_500;
|
||||||
|
if (cpuPercent >= 60) return Color.BG_AMBER_500;
|
||||||
|
return Color.BG_FOREST_500;
|
||||||
|
})()}
|
||||||
|
className="progress-bar"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
memory: (
|
||||||
|
<div className="progress-container">
|
||||||
|
<Progress
|
||||||
|
percent={Number((host.memory * 100).toFixed(1))}
|
||||||
|
size="small"
|
||||||
|
strokeColor={((): string => {
|
||||||
|
const memoryPercent = Number((host.memory * 100).toFixed(1));
|
||||||
|
if (memoryPercent >= 90) return Color.BG_CHERRY_500;
|
||||||
|
if (memoryPercent >= 60) return Color.BG_AMBER_500;
|
||||||
|
return Color.BG_FOREST_500;
|
||||||
|
})()}
|
||||||
|
className="progress-bar"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
ioWait: host.wait,
|
||||||
|
load15: host.load15,
|
||||||
|
}));
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
.infra-monitoring-module-container {
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
|
||||||
|
.ant-tabs {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-tabs-nav {
|
||||||
|
padding: 0;
|
||||||
|
margin-bottom: 0px;
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
border-bottom: 1px solid var(--bg-slate-400) !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-tabs-content-holder {
|
||||||
|
display: flex;
|
||||||
|
|
||||||
|
.ant-tabs-content {
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
|
||||||
|
.ant-tabs-tabpane {
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.tab-item {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.lightMode {
|
||||||
|
.infra-monitoring-module-container {
|
||||||
|
.ant-tabs-nav {
|
||||||
|
&::before {
|
||||||
|
border-bottom: 1px solid var(--bg-vanilla-300) !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
import './InfrastructureMonitoring.styles.scss';
|
||||||
|
|
||||||
|
import RouteTab from 'components/RouteTab';
|
||||||
|
import { TabRoutes } from 'components/RouteTab/types';
|
||||||
|
import history from 'lib/history';
|
||||||
|
import { useLocation } from 'react-use';
|
||||||
|
|
||||||
|
import { Hosts } from './constants';
|
||||||
|
|
||||||
|
export default function InfrastructureMonitoringPage(): JSX.Element {
|
||||||
|
const { pathname } = useLocation();
|
||||||
|
|
||||||
|
const routes: TabRoutes[] = [Hosts];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="infra-monitoring-module-container">
|
||||||
|
<RouteTab routes={routes} activeKey={pathname} history={history} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
15
frontend/src/pages/InfrastructureMonitoring/constants.tsx
Normal file
15
frontend/src/pages/InfrastructureMonitoring/constants.tsx
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
import { TabRoutes } from 'components/RouteTab/types';
|
||||||
|
import ROUTES from 'constants/routes';
|
||||||
|
import { Inbox } from 'lucide-react';
|
||||||
|
import InfraMonitoringHosts from 'pages/InfraMonitoringHosts';
|
||||||
|
|
||||||
|
export const Hosts: TabRoutes = {
|
||||||
|
Component: InfraMonitoringHosts,
|
||||||
|
name: (
|
||||||
|
<div className="tab-item">
|
||||||
|
<Inbox size={16} /> Hosts
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
route: ROUTES.INFRASTRUCTURE_MONITORING_HOSTS,
|
||||||
|
key: ROUTES.INFRASTRUCTURE_MONITORING_HOSTS,
|
||||||
|
};
|
||||||
3
frontend/src/pages/InfrastructureMonitoring/index.tsx
Normal file
3
frontend/src/pages/InfrastructureMonitoring/index.tsx
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
import InfrastructureMonitoringPage from './InfrastructureMonitoringPage';
|
||||||
|
|
||||||
|
export default InfrastructureMonitoringPage;
|
||||||
@@ -3,9 +3,10 @@ import { DataSource } from 'types/common/queryBuilder';
|
|||||||
import { BaseAutocompleteData } from './queryAutocompleteResponse';
|
import { BaseAutocompleteData } from './queryAutocompleteResponse';
|
||||||
|
|
||||||
export interface IGetAttributeKeysPayload {
|
export interface IGetAttributeKeysPayload {
|
||||||
aggregateOperator: string;
|
aggregateOperator?: string;
|
||||||
dataSource: DataSource;
|
dataSource: DataSource;
|
||||||
searchText: string;
|
searchText: string;
|
||||||
aggregateAttribute: string;
|
aggregateAttribute?: string;
|
||||||
tagType?: BaseAutocompleteData['type'];
|
tagType?: BaseAutocompleteData['type'];
|
||||||
|
isInfraMonitoring?: boolean;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -103,4 +103,5 @@ export const routePermission: Record<keyof typeof ROUTES, ROLES[]> = {
|
|||||||
SHORTCUTS: ['ADMIN', 'EDITOR', 'VIEWER'],
|
SHORTCUTS: ['ADMIN', 'EDITOR', 'VIEWER'],
|
||||||
INTEGRATIONS: ['ADMIN', 'EDITOR', 'VIEWER'],
|
INTEGRATIONS: ['ADMIN', 'EDITOR', 'VIEWER'],
|
||||||
SERVICE_TOP_LEVEL_OPERATIONS: ['ADMIN', 'EDITOR', 'VIEWER'],
|
SERVICE_TOP_LEVEL_OPERATIONS: ['ADMIN', 'EDITOR', 'VIEWER'],
|
||||||
|
INFRASTRUCTURE_MONITORING_HOSTS: ['ADMIN', 'EDITOR', 'VIEWER'],
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user