mirror of
https://github.com/SigNoz/signoz.git
synced 2026-07-10 16:40:34 +01:00
Compare commits
1 Commits
main
...
issue_5552
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1b7f1cb654 |
@@ -3148,8 +3148,6 @@ components:
|
||||
type: string
|
||||
image:
|
||||
type: string
|
||||
legacy:
|
||||
type: boolean
|
||||
locked:
|
||||
type: boolean
|
||||
name:
|
||||
@@ -3182,7 +3180,6 @@ components:
|
||||
- name
|
||||
- tags
|
||||
- spec
|
||||
- legacy
|
||||
- pinned
|
||||
type: object
|
||||
DashboardtypesListedDashboardV2:
|
||||
@@ -3196,8 +3193,6 @@ components:
|
||||
type: string
|
||||
image:
|
||||
type: string
|
||||
legacy:
|
||||
type: boolean
|
||||
locked:
|
||||
type: boolean
|
||||
name:
|
||||
@@ -3228,7 +3223,6 @@ components:
|
||||
- name
|
||||
- tags
|
||||
- spec
|
||||
- legacy
|
||||
type: object
|
||||
DashboardtypesListedDashboardV2Spec:
|
||||
properties:
|
||||
@@ -4484,14 +4478,10 @@ components:
|
||||
type: string
|
||||
nullable: true
|
||||
type: object
|
||||
misscheduledNodes:
|
||||
type: integer
|
||||
podCountsByPhase:
|
||||
$ref: '#/components/schemas/InframonitoringtypesPodCountsByPhase'
|
||||
podCountsByStatus:
|
||||
$ref: '#/components/schemas/InframonitoringtypesPodCountsByStatus'
|
||||
readyNodes:
|
||||
type: integer
|
||||
required:
|
||||
- daemonSetName
|
||||
- daemonSetCPU
|
||||
@@ -4502,8 +4492,6 @@ components:
|
||||
- daemonSetMemoryLimit
|
||||
- desiredNodes
|
||||
- currentNodes
|
||||
- readyNodes
|
||||
- misscheduledNodes
|
||||
- podCountsByPhase
|
||||
- podCountsByStatus
|
||||
- meta
|
||||
@@ -16005,24 +15993,21 @@ paths:
|
||||
daemonSetCPULimit, daemonSetMemoryRequest, daemonSetMemoryLimit). Each row
|
||||
also reports the latest known node-level counters from kube-state-metrics:
|
||||
desiredNodes (k8s.daemonset.desired_scheduled_nodes, the number of nodes the
|
||||
daemonset wants to run on), currentNodes (k8s.daemonset.current_scheduled_nodes,
|
||||
the number of nodes the daemonset currently runs on), readyNodes (k8s.daemonset.ready_nodes,
|
||||
the number of nodes running at least one ready daemon pod) and misscheduledNodes
|
||||
(k8s.daemonset.misscheduled_nodes, the number of nodes running the daemon
|
||||
pod but not supposed to) — note these are node counts, not pod counts. It
|
||||
also reports per-group podCountsByPhase ({ pending, running, succeeded, failed,
|
||||
unknown } from each pod''s latest k8s.pod.phase value). Each daemonset includes
|
||||
metadata attributes (k8s.daemonset.name, k8s.namespace.name, k8s.cluster.name).
|
||||
The response type is ''list'' for the default k8s.daemonset.name grouping
|
||||
or ''grouped_list'' for custom groupBy keys; in both modes every row aggregates
|
||||
pods owned by daemonsets in the group. Supports filtering via a filter expression,
|
||||
custom groupBy, ordering by cpu / cpu_request / cpu_limit / memory / memory_request
|
||||
/ memory_limit / desired_nodes / current_nodes / ready_nodes / misscheduled_nodes,
|
||||
daemonset wants to run on) and currentNodes (k8s.daemonset.current_scheduled_nodes,
|
||||
the number of nodes the daemonset currently runs on) — note these are node
|
||||
counts, not pod counts. It also reports per-group podCountsByPhase ({ pending,
|
||||
running, succeeded, failed, unknown } from each pod''s latest k8s.pod.phase
|
||||
value). Each daemonset includes metadata attributes (k8s.daemonset.name, k8s.namespace.name,
|
||||
k8s.cluster.name). The response type is ''list'' for the default k8s.daemonset.name
|
||||
grouping or ''grouped_list'' for custom groupBy keys; in both modes every
|
||||
row aggregates pods owned by daemonsets in the group. Supports filtering via
|
||||
a filter expression, custom groupBy, ordering by cpu / cpu_request / cpu_limit
|
||||
/ memory / memory_request / memory_limit / desired_nodes / current_nodes,
|
||||
and pagination via offset/limit. Also reports whether the requested time range
|
||||
falls before the data retention boundary. Numeric metric fields (daemonSetCPU,
|
||||
daemonSetCPURequest, daemonSetCPULimit, daemonSetMemory, daemonSetMemoryRequest,
|
||||
daemonSetMemoryLimit, desiredNodes, currentNodes, readyNodes, misscheduledNodes)
|
||||
return -1 as a sentinel when no data is available for that field.'
|
||||
daemonSetMemoryLimit, desiredNodes, currentNodes) return -1 as a sentinel
|
||||
when no data is available for that field.'
|
||||
operationId: ListDaemonSets
|
||||
requestBody:
|
||||
content:
|
||||
|
||||
@@ -219,7 +219,7 @@ export const useListClusters = <
|
||||
return useMutation(getListClustersMutationOptions(options));
|
||||
};
|
||||
/**
|
||||
* Returns a paginated list of Kubernetes DaemonSets with key aggregated pod metrics: CPU usage and memory working set summed across pods owned by the daemonset, plus average CPU/memory request and limit utilization (daemonSetCPURequest, daemonSetCPULimit, daemonSetMemoryRequest, daemonSetMemoryLimit). Each row also reports the latest known node-level counters from kube-state-metrics: desiredNodes (k8s.daemonset.desired_scheduled_nodes, the number of nodes the daemonset wants to run on), currentNodes (k8s.daemonset.current_scheduled_nodes, the number of nodes the daemonset currently runs on), readyNodes (k8s.daemonset.ready_nodes, the number of nodes running at least one ready daemon pod) and misscheduledNodes (k8s.daemonset.misscheduled_nodes, the number of nodes running the daemon pod but not supposed to) — note these are node counts, not pod counts. It also reports per-group podCountsByPhase ({ pending, running, succeeded, failed, unknown } from each pod's latest k8s.pod.phase value). Each daemonset includes metadata attributes (k8s.daemonset.name, k8s.namespace.name, k8s.cluster.name). The response type is 'list' for the default k8s.daemonset.name grouping or 'grouped_list' for custom groupBy keys; in both modes every row aggregates pods owned by daemonsets in the group. Supports filtering via a filter expression, custom groupBy, ordering by cpu / cpu_request / cpu_limit / memory / memory_request / memory_limit / desired_nodes / current_nodes / ready_nodes / misscheduled_nodes, and pagination via offset/limit. Also reports whether the requested time range falls before the data retention boundary. Numeric metric fields (daemonSetCPU, daemonSetCPURequest, daemonSetCPULimit, daemonSetMemory, daemonSetMemoryRequest, daemonSetMemoryLimit, desiredNodes, currentNodes, readyNodes, misscheduledNodes) return -1 as a sentinel when no data is available for that field.
|
||||
* Returns a paginated list of Kubernetes DaemonSets with key aggregated pod metrics: CPU usage and memory working set summed across pods owned by the daemonset, plus average CPU/memory request and limit utilization (daemonSetCPURequest, daemonSetCPULimit, daemonSetMemoryRequest, daemonSetMemoryLimit). Each row also reports the latest known node-level counters from kube-state-metrics: desiredNodes (k8s.daemonset.desired_scheduled_nodes, the number of nodes the daemonset wants to run on) and currentNodes (k8s.daemonset.current_scheduled_nodes, the number of nodes the daemonset currently runs on) — note these are node counts, not pod counts. It also reports per-group podCountsByPhase ({ pending, running, succeeded, failed, unknown } from each pod's latest k8s.pod.phase value). Each daemonset includes metadata attributes (k8s.daemonset.name, k8s.namespace.name, k8s.cluster.name). The response type is 'list' for the default k8s.daemonset.name grouping or 'grouped_list' for custom groupBy keys; in both modes every row aggregates pods owned by daemonsets in the group. Supports filtering via a filter expression, custom groupBy, ordering by cpu / cpu_request / cpu_limit / memory / memory_request / memory_limit / desired_nodes / current_nodes, and pagination via offset/limit. Also reports whether the requested time range falls before the data retention boundary. Numeric metric fields (daemonSetCPU, daemonSetCPURequest, daemonSetCPULimit, daemonSetMemory, daemonSetMemoryRequest, daemonSetMemoryLimit, desiredNodes, currentNodes) return -1 as a sentinel when no data is available for that field.
|
||||
* @summary List DaemonSets for Infra Monitoring
|
||||
*/
|
||||
export const listDaemonSets = (
|
||||
|
||||
@@ -5005,10 +5005,6 @@ export interface DashboardtypesListedDashboardForUserV2DTO {
|
||||
* @type string
|
||||
*/
|
||||
image?: string;
|
||||
/**
|
||||
* @type boolean
|
||||
*/
|
||||
legacy: boolean;
|
||||
/**
|
||||
* @type boolean
|
||||
*/
|
||||
@@ -5084,10 +5080,6 @@ export interface DashboardtypesListedDashboardV2DTO {
|
||||
* @type string
|
||||
*/
|
||||
image?: string;
|
||||
/**
|
||||
* @type boolean
|
||||
*/
|
||||
legacy: boolean;
|
||||
/**
|
||||
* @type boolean
|
||||
*/
|
||||
@@ -6090,16 +6082,8 @@ export interface InframonitoringtypesDaemonSetRecordDTO {
|
||||
* @type object,null
|
||||
*/
|
||||
meta: InframonitoringtypesDaemonSetRecordDTOMeta;
|
||||
/**
|
||||
* @type integer
|
||||
*/
|
||||
misscheduledNodes: number;
|
||||
podCountsByPhase: InframonitoringtypesPodCountsByPhaseDTO;
|
||||
podCountsByStatus: InframonitoringtypesPodCountsByStatusDTO;
|
||||
/**
|
||||
* @type integer
|
||||
*/
|
||||
readyNodes: number;
|
||||
}
|
||||
|
||||
export interface InframonitoringtypesDaemonSetsDTO {
|
||||
|
||||
@@ -22,7 +22,6 @@ export const getKeySuggestions = (
|
||||
fieldContext = '',
|
||||
fieldDataType = '',
|
||||
signalSource = '',
|
||||
metricNamespace = '',
|
||||
} = props;
|
||||
|
||||
const encodedSignal = encodeURIComponent(signal);
|
||||
@@ -31,9 +30,8 @@ export const getKeySuggestions = (
|
||||
const encodedFieldContext = encodeURIComponent(fieldContext);
|
||||
const encodedFieldDataType = encodeURIComponent(fieldDataType);
|
||||
const encodedSource = encodeURIComponent(signalSource);
|
||||
const encodedMetricNamespace = encodeURIComponent(metricNamespace);
|
||||
|
||||
return axios.get(
|
||||
`/fields/keys?signal=${encodedSignal}&searchText=${encodedSearchText}&metricName=${encodedMetricName}&fieldContext=${encodedFieldContext}&fieldDataType=${encodedFieldDataType}&source=${encodedSource}&metricNamespace=${encodedMetricNamespace}`,
|
||||
`/fields/keys?signal=${encodedSignal}&searchText=${encodedSearchText}&metricName=${encodedMetricName}&fieldContext=${encodedFieldContext}&fieldDataType=${encodedFieldDataType}&source=${encodedSource}`,
|
||||
);
|
||||
};
|
||||
|
||||
@@ -2,7 +2,7 @@ import { Controller, useForm } from 'react-hook-form';
|
||||
import { useQueryClient } from 'react-query';
|
||||
import { X } from '@signozhq/icons';
|
||||
import { Button } from '@signozhq/ui/button';
|
||||
import AuthZButton from 'lib/authz/components/AuthZButton/AuthZButton';
|
||||
import AuthZTooltip from 'lib/authz/components/AuthZTooltip/AuthZTooltip';
|
||||
import { SACreatePermission } from 'lib/authz/hooks/useAuthZ/permissions/service-account.permissions';
|
||||
import { DialogFooter, DialogWrapper } from '@signozhq/ui/dialog';
|
||||
import { Input } from '@signozhq/ui/input';
|
||||
@@ -134,17 +134,18 @@ function CreateServiceAccountModal(): JSX.Element {
|
||||
Cancel
|
||||
</Button>
|
||||
|
||||
<AuthZButton
|
||||
checks={[SACreatePermission]}
|
||||
type="submit"
|
||||
form="create-sa-form"
|
||||
variant="solid"
|
||||
color="primary"
|
||||
loading={isSubmitting}
|
||||
disabled={!isValid}
|
||||
>
|
||||
Create Service Account
|
||||
</AuthZButton>
|
||||
<AuthZTooltip checks={[SACreatePermission]}>
|
||||
<Button
|
||||
type="submit"
|
||||
form="create-sa-form"
|
||||
variant="solid"
|
||||
color="primary"
|
||||
loading={isSubmitting}
|
||||
disabled={!isValid}
|
||||
>
|
||||
Create Service Account
|
||||
</Button>
|
||||
</AuthZTooltip>
|
||||
</DialogFooter>
|
||||
</DialogWrapper>
|
||||
);
|
||||
|
||||
@@ -89,7 +89,6 @@ interface QuerySearchProps {
|
||||
onChange: (value: string) => void;
|
||||
queryData: IBuilderQuery;
|
||||
dataSource: DataSource;
|
||||
metricNamespace?: string;
|
||||
signalSource?: string;
|
||||
hardcodedAttributeKeys?: QueryKeyDataSuggestionsProps[];
|
||||
onRun?: (query: string) => void;
|
||||
@@ -108,7 +107,6 @@ function QuerySearch({
|
||||
hardcodedAttributeKeys,
|
||||
showFilterSuggestionsWithoutMetric,
|
||||
initialExpression,
|
||||
metricNamespace,
|
||||
}: QuerySearchProps): JSX.Element {
|
||||
const isDarkMode = useIsDarkMode();
|
||||
const [valueSuggestions, setValueSuggestions] = useState<any[]>([]);
|
||||
@@ -301,7 +299,6 @@ function QuerySearch({
|
||||
searchText: searchText || '',
|
||||
metricName: debouncedMetricName ?? undefined,
|
||||
signalSource: signalSource as 'meter' | '',
|
||||
metricNamespace,
|
||||
});
|
||||
|
||||
if (response.data.data) {
|
||||
@@ -334,7 +331,6 @@ function QuerySearch({
|
||||
signalSource,
|
||||
hardcodedAttributeKeys,
|
||||
showFilterSuggestionsWithoutMetric,
|
||||
metricNamespace,
|
||||
],
|
||||
);
|
||||
|
||||
|
||||
@@ -20,7 +20,6 @@ function OtherFiltersSkeleton(): JSX.Element {
|
||||
<Skeleton.Input
|
||||
active
|
||||
size="small"
|
||||
className="qf-other-filters-skeleton"
|
||||
// eslint-disable-next-line react/no-array-index-key
|
||||
key={index}
|
||||
/>
|
||||
|
||||
@@ -4,7 +4,7 @@ import { Button } from '@signozhq/ui/button';
|
||||
import { Input } from '@signozhq/ui/input';
|
||||
import { ToggleGroupSimple } from '@signozhq/ui/toggle-group';
|
||||
import { DatePicker } from 'antd';
|
||||
import AuthZButton from 'lib/authz/components/AuthZButton/AuthZButton';
|
||||
import AuthZTooltip from 'lib/authz/components/AuthZTooltip/AuthZTooltip';
|
||||
import {
|
||||
APIKeyCreatePermission,
|
||||
buildSAAttachPermission,
|
||||
@@ -109,21 +109,24 @@ function KeyFormPhase({
|
||||
<Button variant="solid" color="secondary" onClick={onClose}>
|
||||
Cancel
|
||||
</Button>
|
||||
<AuthZButton
|
||||
<AuthZTooltip
|
||||
checks={[
|
||||
APIKeyCreatePermission,
|
||||
buildSAAttachPermission(accountId ?? ''),
|
||||
]}
|
||||
authZEnabled={!!accountId}
|
||||
type="submit"
|
||||
form={FORM_ID}
|
||||
variant="solid"
|
||||
color="primary"
|
||||
loading={isSubmitting}
|
||||
disabled={!isValid}
|
||||
enabled={!!accountId}
|
||||
>
|
||||
Create Key
|
||||
</AuthZButton>
|
||||
<Button
|
||||
type="submit"
|
||||
form={FORM_ID}
|
||||
variant="solid"
|
||||
color="primary"
|
||||
loading={isSubmitting}
|
||||
disabled={!isValid}
|
||||
>
|
||||
Create Key
|
||||
</Button>
|
||||
</AuthZTooltip>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { useQueryClient } from 'react-query';
|
||||
import { Trash2, X } from '@signozhq/icons';
|
||||
import { Button } from '@signozhq/ui/button';
|
||||
import AuthZButton from 'lib/authz/components/AuthZButton/AuthZButton';
|
||||
import AuthZTooltip from 'lib/authz/components/AuthZTooltip/AuthZTooltip';
|
||||
import { buildSADeletePermission } from 'lib/authz/hooks/useAuthZ/permissions/service-account.permissions';
|
||||
import { DialogWrapper } from '@signozhq/ui/dialog';
|
||||
import { toast } from '@signozhq/ui/sonner';
|
||||
@@ -84,17 +84,20 @@ function DeleteAccountModal(): JSX.Element {
|
||||
<X size={12} />
|
||||
Cancel
|
||||
</Button>
|
||||
<AuthZButton
|
||||
<AuthZTooltip
|
||||
checks={[buildSADeletePermission(accountId ?? '')]}
|
||||
authZEnabled={!!accountId}
|
||||
variant="solid"
|
||||
color="destructive"
|
||||
loading={isDeleting}
|
||||
onClick={handleConfirm}
|
||||
enabled={!!accountId}
|
||||
>
|
||||
<Trash2 size={12} />
|
||||
Delete
|
||||
</AuthZButton>
|
||||
<Button
|
||||
variant="solid"
|
||||
color="destructive"
|
||||
loading={isDeleting}
|
||||
onClick={handleConfirm}
|
||||
>
|
||||
<Trash2 size={12} />
|
||||
Delete
|
||||
</Button>
|
||||
</AuthZTooltip>
|
||||
</div>
|
||||
);
|
||||
|
||||
|
||||
@@ -7,7 +7,6 @@ import { Input } from '@signozhq/ui/input';
|
||||
import { ToggleGroupSimple } from '@signozhq/ui/toggle-group';
|
||||
import { DatePicker } from 'antd';
|
||||
import type { ServiceaccounttypesGettableFactorAPIKeyDTO } from 'api/generated/services/sigNoz.schemas';
|
||||
import AuthZButton from 'lib/authz/components/AuthZButton/AuthZButton';
|
||||
import AuthZTooltip from 'lib/authz/components/AuthZTooltip/AuthZTooltip';
|
||||
import {
|
||||
buildAPIKeyDeletePermission,
|
||||
@@ -159,36 +158,38 @@ function EditKeyForm({
|
||||
</form>
|
||||
|
||||
<div className="edit-key-modal__footer">
|
||||
<AuthZButton
|
||||
<AuthZTooltip
|
||||
checks={[
|
||||
buildAPIKeyDeletePermission(keyItem?.id ?? ''),
|
||||
buildSADetachPermission(accountId ?? ''),
|
||||
]}
|
||||
authZEnabled={!!accountId && !!keyItem?.id}
|
||||
variant="link"
|
||||
color="destructive"
|
||||
onClick={onRevokeClick}
|
||||
enabled={!!accountId && !!keyItem?.id}
|
||||
>
|
||||
<Trash2 size={12} />
|
||||
Revoke Key
|
||||
</AuthZButton>
|
||||
<Button variant="link" color="destructive" onClick={onRevokeClick}>
|
||||
<Trash2 size={12} />
|
||||
Revoke Key
|
||||
</Button>
|
||||
</AuthZTooltip>
|
||||
<div className="edit-key-modal__footer-right">
|
||||
<Button variant="solid" color="secondary" onClick={onClose}>
|
||||
<X size={12} />
|
||||
Cancel
|
||||
</Button>
|
||||
<AuthZButton
|
||||
<AuthZTooltip
|
||||
checks={[buildAPIKeyUpdatePermission(keyItem?.id ?? '')]}
|
||||
authZEnabled={!!accountId && !!keyItem?.id}
|
||||
type="submit"
|
||||
form={FORM_ID}
|
||||
variant="solid"
|
||||
color="primary"
|
||||
loading={isSaving}
|
||||
disabled={!isDirty}
|
||||
enabled={!!accountId && !!keyItem?.id}
|
||||
>
|
||||
Save Changes
|
||||
</AuthZButton>
|
||||
<Button
|
||||
type="submit"
|
||||
form={FORM_ID}
|
||||
variant="solid"
|
||||
color="primary"
|
||||
loading={isSaving}
|
||||
disabled={!isDirty}
|
||||
>
|
||||
Save Changes
|
||||
</Button>
|
||||
</AuthZTooltip>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
import React, { useCallback, useMemo } from 'react';
|
||||
import { KeyRound, X } from '@signozhq/icons';
|
||||
import { Pagination, Skeleton, Table, Tooltip } from 'antd';
|
||||
import { Button } from '@signozhq/ui/button';
|
||||
import { Skeleton, Table, Tooltip } from 'antd';
|
||||
import type { ColumnsType } from 'antd/es/table/interface';
|
||||
import type { ServiceaccounttypesGettableFactorAPIKeyDTO } from 'api/generated/services/sigNoz.schemas';
|
||||
import AuthZButton from 'lib/authz/components/AuthZButton/AuthZButton';
|
||||
import { withAuthZContent } from 'lib/authz/components/withAuthZ/withAuthZContent';
|
||||
import AuthZTooltip from 'lib/authz/components/AuthZTooltip/AuthZTooltip';
|
||||
import {
|
||||
APIKeyCreatePermission,
|
||||
APIKeyListPermission,
|
||||
buildAPIKeyDeletePermission,
|
||||
buildSAAttachPermission,
|
||||
buildSADetachPermission,
|
||||
@@ -25,10 +24,10 @@ interface KeysTabProps {
|
||||
keys: ServiceaccounttypesGettableFactorAPIKeyDTO[];
|
||||
isLoading: boolean;
|
||||
isDisabled?: boolean;
|
||||
canUpdate?: boolean;
|
||||
accountId?: string;
|
||||
currentPage: number;
|
||||
pageSize: number;
|
||||
onPageChange: (page: number) => void;
|
||||
}
|
||||
|
||||
interface BuildColumnsParams {
|
||||
@@ -114,26 +113,29 @@ function buildColumns({
|
||||
render: (_, record): JSX.Element => {
|
||||
const tooltipTitle = isDisabled ? 'Service account disabled' : 'Revoke Key';
|
||||
return (
|
||||
<Tooltip title={tooltipTitle}>
|
||||
<AuthZButton
|
||||
checks={[
|
||||
buildAPIKeyDeletePermission(record.id),
|
||||
buildSADetachPermission(accountId),
|
||||
]}
|
||||
authZEnabled={!isDisabled && !!accountId}
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
color="destructive"
|
||||
disabled={isDisabled}
|
||||
onClick={(e): void => {
|
||||
e.stopPropagation();
|
||||
onRevokeClick(record.id);
|
||||
}}
|
||||
className="keys-tab__revoke-btn"
|
||||
>
|
||||
<X size={12} />
|
||||
</AuthZButton>
|
||||
</Tooltip>
|
||||
<AuthZTooltip
|
||||
checks={[
|
||||
buildAPIKeyDeletePermission(record.id),
|
||||
buildSADetachPermission(accountId),
|
||||
]}
|
||||
enabled={!isDisabled && !!accountId}
|
||||
>
|
||||
<Tooltip title={tooltipTitle}>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
color="destructive"
|
||||
disabled={isDisabled}
|
||||
onClick={(e): void => {
|
||||
e.stopPropagation();
|
||||
onRevokeClick(record.id);
|
||||
}}
|
||||
className="keys-tab__revoke-btn"
|
||||
>
|
||||
<X size={12} />
|
||||
</Button>
|
||||
</Tooltip>
|
||||
</AuthZTooltip>
|
||||
);
|
||||
},
|
||||
},
|
||||
@@ -147,7 +149,6 @@ function KeysTab({
|
||||
accountId = '',
|
||||
currentPage,
|
||||
pageSize,
|
||||
onPageChange,
|
||||
}: KeysTabProps): JSX.Element {
|
||||
const [, setIsAddKeyOpen] = useQueryState(
|
||||
'add-key',
|
||||
@@ -211,18 +212,21 @@ function KeysTab({
|
||||
Learn more
|
||||
</a>
|
||||
</p>
|
||||
<AuthZButton
|
||||
<AuthZTooltip
|
||||
checks={[APIKeyCreatePermission, buildSAAttachPermission(accountId)]}
|
||||
authZEnabled={!isDisabled && !!accountId}
|
||||
variant="link"
|
||||
color="primary"
|
||||
onClick={async (): Promise<void> => {
|
||||
await setIsAddKeyOpen(true);
|
||||
}}
|
||||
disabled={isDisabled}
|
||||
enabled={!isDisabled && !!accountId}
|
||||
>
|
||||
+ Add your first key
|
||||
</AuthZButton>
|
||||
<Button
|
||||
variant="link"
|
||||
color="primary"
|
||||
onClick={async (): Promise<void> => {
|
||||
await setIsAddKeyOpen(true);
|
||||
}}
|
||||
disabled={isDisabled}
|
||||
>
|
||||
+ Add your first key
|
||||
</Button>
|
||||
</AuthZTooltip>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -274,24 +278,6 @@ function KeysTab({
|
||||
})}
|
||||
/>
|
||||
|
||||
<Pagination
|
||||
current={currentPage}
|
||||
pageSize={pageSize}
|
||||
total={keys.length}
|
||||
showTotal={(total: number, range: number[]): JSX.Element => (
|
||||
<>
|
||||
<span className="sa-drawer__pagination-range">
|
||||
{range[0]} — {range[1]}
|
||||
</span>
|
||||
<span className="sa-drawer__pagination-total"> of {total}</span>
|
||||
</>
|
||||
)}
|
||||
showSizeChanger={false}
|
||||
hideOnSinglePage
|
||||
onChange={onPageChange}
|
||||
className="sa-drawer__keys-pagination"
|
||||
/>
|
||||
|
||||
<EditKeyModal keyItem={editKey} />
|
||||
|
||||
<RevokeKeyModal />
|
||||
@@ -299,7 +285,4 @@ function KeysTab({
|
||||
);
|
||||
}
|
||||
|
||||
export default withAuthZContent(KeysTab, {
|
||||
checks: [APIKeyListPermission],
|
||||
fallbackOnLoading: <Skeleton active paragraph={{ rows: 6 }} />,
|
||||
});
|
||||
export default KeysTab;
|
||||
|
||||
@@ -6,20 +6,15 @@ import { Input } from '@signozhq/ui/input';
|
||||
import { useCopyToClipboard } from 'react-use';
|
||||
import type { AuthtypesRoleDTO } from 'api/generated/services/sigNoz.schemas';
|
||||
import AuthZTooltip from 'lib/authz/components/AuthZTooltip/AuthZTooltip';
|
||||
import { withAuthZContent } from 'lib/authz/components/withAuthZ/withAuthZContent';
|
||||
import RolesSelect from 'components/RolesSelect';
|
||||
import { DATE_TIME_FORMATS } from 'constants/dateTimeFormats';
|
||||
import { ServiceAccountRow } from 'container/ServiceAccountsSettings/utils';
|
||||
import {
|
||||
buildSAReadPermission,
|
||||
buildSAUpdatePermission,
|
||||
} from 'lib/authz/hooks/useAuthZ/permissions/service-account.permissions';
|
||||
import { buildSAUpdatePermission } from 'lib/authz/hooks/useAuthZ/permissions/service-account.permissions';
|
||||
import { useTimezone } from 'providers/Timezone';
|
||||
import APIError from 'types/api/error';
|
||||
|
||||
import SaveErrorItem from './SaveErrorItem';
|
||||
import type { SaveError } from './utils';
|
||||
import { Skeleton } from 'antd';
|
||||
|
||||
interface OverviewTabProps {
|
||||
account: ServiceAccountRow;
|
||||
@@ -28,6 +23,7 @@ interface OverviewTabProps {
|
||||
localRoles: string[];
|
||||
onRolesChange: (v: string[]) => void;
|
||||
isDisabled: boolean;
|
||||
canUpdate?: boolean;
|
||||
availableRoles: AuthtypesRoleDTO[];
|
||||
rolesLoading?: boolean;
|
||||
rolesError?: boolean;
|
||||
@@ -43,6 +39,7 @@ function OverviewTab({
|
||||
localRoles,
|
||||
onRolesChange,
|
||||
isDisabled,
|
||||
canUpdate = true,
|
||||
availableRoles,
|
||||
rolesLoading,
|
||||
rolesError,
|
||||
@@ -89,22 +86,23 @@ function OverviewTab({
|
||||
<label className="sa-drawer__label" htmlFor="sa-name">
|
||||
Name
|
||||
</label>
|
||||
{isDisabled ? (
|
||||
<AuthZTooltip checks={[buildSAUpdatePermission(account.id)]}>
|
||||
{isDisabled || !canUpdate ? (
|
||||
<AuthZTooltip
|
||||
checks={[buildSAUpdatePermission(account.id)]}
|
||||
enabled={!isDisabled && !canUpdate}
|
||||
>
|
||||
<div className="sa-drawer__input-wrapper sa-drawer__input-wrapper--disabled">
|
||||
<span className="sa-drawer__input-text">{localName || '—'}</span>
|
||||
<LockKeyhole size={14} className="sa-drawer__lock-icon" />
|
||||
</div>
|
||||
</AuthZTooltip>
|
||||
) : (
|
||||
<AuthZTooltip checks={[buildSAUpdatePermission(account.id)]}>
|
||||
<Input
|
||||
id="sa-name"
|
||||
value={localName}
|
||||
onChange={(e): void => onNameChange(e.target.value)}
|
||||
placeholder="Enter name"
|
||||
/>
|
||||
</AuthZTooltip>
|
||||
<Input
|
||||
id="sa-name"
|
||||
value={localName}
|
||||
onChange={(e): void => onNameChange(e.target.value)}
|
||||
placeholder="Enter name"
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -222,9 +220,4 @@ function OverviewTab({
|
||||
);
|
||||
}
|
||||
|
||||
export default withAuthZContent(OverviewTab, {
|
||||
checks: (props): ReturnType<typeof buildSAReadPermission>[] => [
|
||||
buildSAReadPermission(props.account.id),
|
||||
],
|
||||
fallbackOnLoading: <Skeleton active paragraph={{ rows: 6 }} />,
|
||||
});
|
||||
export default OverviewTab;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { useQueryClient } from 'react-query';
|
||||
import { Trash2, X } from '@signozhq/icons';
|
||||
import { Button } from '@signozhq/ui/button';
|
||||
import AuthZButton from 'lib/authz/components/AuthZButton/AuthZButton';
|
||||
import AuthZTooltip from 'lib/authz/components/AuthZTooltip/AuthZTooltip';
|
||||
import {
|
||||
buildAPIKeyDeletePermission,
|
||||
buildSADetachPermission,
|
||||
@@ -45,20 +45,23 @@ export function RevokeKeyFooter({
|
||||
<X size={12} />
|
||||
Cancel
|
||||
</Button>
|
||||
<AuthZButton
|
||||
<AuthZTooltip
|
||||
checks={[
|
||||
buildAPIKeyDeletePermission(keyId ?? ''),
|
||||
buildSADetachPermission(accountId ?? ''),
|
||||
]}
|
||||
authZEnabled={!!accountId && !!keyId}
|
||||
variant="solid"
|
||||
color="destructive"
|
||||
loading={isRevoking}
|
||||
onClick={onConfirm}
|
||||
enabled={!!accountId && !!keyId}
|
||||
>
|
||||
<Trash2 size={12} />
|
||||
Revoke Key
|
||||
</AuthZButton>
|
||||
<Button
|
||||
variant="solid"
|
||||
color="destructive"
|
||||
loading={isRevoking}
|
||||
onClick={onConfirm}
|
||||
>
|
||||
<Trash2 size={12} />
|
||||
Revoke Key
|
||||
</Button>
|
||||
</AuthZTooltip>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -108,7 +111,7 @@ function RevokeKeyModal(): JSX.Element {
|
||||
}
|
||||
|
||||
function handleCancel(): void {
|
||||
void setRevokeKeyId(null);
|
||||
setRevokeKeyId(null);
|
||||
}
|
||||
|
||||
return (
|
||||
|
||||
@@ -1,17 +1,11 @@
|
||||
import React, {
|
||||
useCallback,
|
||||
useEffect,
|
||||
useMemo,
|
||||
useRef,
|
||||
useState,
|
||||
} from 'react';
|
||||
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||
import { useQueryClient } from 'react-query';
|
||||
import { Key, LayoutGrid, Plus, Trash2, X } from '@signozhq/icons';
|
||||
import { Button } from '@signozhq/ui/button';
|
||||
import { DrawerWrapper } from '@signozhq/ui/drawer';
|
||||
import { toast } from '@signozhq/ui/sonner';
|
||||
import { ToggleGroupSimple } from '@signozhq/ui/toggle-group';
|
||||
import { Skeleton } from 'antd';
|
||||
import { Pagination, Skeleton } from 'antd';
|
||||
import { convertToApiError } from 'api/ErrorResponseHandlerForGeneratedAPIs';
|
||||
import {
|
||||
getListServiceAccountsQueryKey,
|
||||
@@ -22,6 +16,7 @@ import {
|
||||
import type { RenderErrorResponseDTO } from 'api/generated/services/sigNoz.schemas';
|
||||
import { AxiosError } from 'axios';
|
||||
import ErrorInPlace from 'components/ErrorInPlace/ErrorInPlace';
|
||||
import PermissionDeniedCallout from 'lib/authz/components/PermissionDeniedCallout/PermissionDeniedCallout';
|
||||
import { useRoles } from 'components/RolesSelect';
|
||||
import { SA_QUERY_PARAMS } from 'container/ServiceAccountsSettings/constants';
|
||||
import {
|
||||
@@ -33,13 +28,15 @@ import {
|
||||
RoleUpdateFailure,
|
||||
useServiceAccountRoleManager,
|
||||
} from 'hooks/serviceAccount/useServiceAccountRoleManager';
|
||||
import AuthZButton from 'lib/authz/components/AuthZButton/AuthZButton';
|
||||
import {
|
||||
APIKeyCreatePermission,
|
||||
APIKeyListPermission,
|
||||
buildSAAttachPermission,
|
||||
buildSADeletePermission,
|
||||
buildSAReadPermission,
|
||||
buildSAUpdatePermission,
|
||||
} from 'lib/authz/hooks/useAuthZ/permissions/service-account.permissions';
|
||||
import { useAuthZ } from 'lib/authz/hooks/useAuthZ/useAuthZ';
|
||||
import {
|
||||
parseAsBoolean,
|
||||
parseAsInteger,
|
||||
@@ -50,6 +47,7 @@ import {
|
||||
import APIError from 'types/api/error';
|
||||
import { toAPIError } from 'utils/errorUtils';
|
||||
|
||||
import AuthZTooltip from 'lib/authz/components/AuthZTooltip/AuthZTooltip';
|
||||
import AddKeyModal from './AddKeyModal';
|
||||
import DeleteAccountModal from './DeleteAccountModal';
|
||||
import KeysTab from './KeysTab';
|
||||
@@ -72,12 +70,14 @@ function toSaveApiError(err: unknown): APIError {
|
||||
);
|
||||
}
|
||||
|
||||
// eslint-disable-next-line sonarjs/cognitive-complexity
|
||||
function ServiceAccountDrawer({
|
||||
onSuccess,
|
||||
}: ServiceAccountDrawerProps): JSX.Element {
|
||||
const [selectedAccountId, setSelectedAccountId] = useQueryState(
|
||||
SA_QUERY_PARAMS.ACCOUNT,
|
||||
);
|
||||
const open = !!selectedAccountId;
|
||||
const [activeTab, setActiveTab] = useQueryState(
|
||||
SA_QUERY_PARAMS.TAB,
|
||||
parseAsStringEnum<ServiceAccountDrawerTab>(
|
||||
@@ -100,14 +100,28 @@ function ServiceAccountDrawer({
|
||||
SA_QUERY_PARAMS.DELETE_SA,
|
||||
parseAsBoolean.withDefault(false),
|
||||
);
|
||||
|
||||
const [localName, setLocalName] = useState('');
|
||||
const [localRoles, setLocalRoles] = useState<string[]>([]);
|
||||
const [isSaving, setIsSaving] = useState(false);
|
||||
const [saveErrors, setSaveErrors] = useState<SaveError[]>([]);
|
||||
|
||||
const queryClient = useQueryClient();
|
||||
const open = !!selectedAccountId;
|
||||
|
||||
const { permissions: drawerPermissions, isLoading: isAuthZLoading } = useAuthZ(
|
||||
selectedAccountId
|
||||
? [
|
||||
buildSAReadPermission(selectedAccountId),
|
||||
buildSAUpdatePermission(selectedAccountId),
|
||||
buildSADeletePermission(selectedAccountId),
|
||||
APIKeyListPermission,
|
||||
]
|
||||
: [],
|
||||
{ enabled: !!selectedAccountId },
|
||||
);
|
||||
|
||||
const canRead =
|
||||
drawerPermissions?.[buildSAReadPermission(selectedAccountId ?? '')]
|
||||
?.isGranted ?? false;
|
||||
|
||||
const {
|
||||
data: accountData,
|
||||
@@ -117,7 +131,7 @@ function ServiceAccountDrawer({
|
||||
refetch: refetchAccount,
|
||||
} = useGetServiceAccount(
|
||||
{ id: selectedAccountId ?? '' },
|
||||
{ query: { enabled: !!selectedAccountId } },
|
||||
{ query: { enabled: canRead && !!selectedAccountId } },
|
||||
);
|
||||
|
||||
const account = useMemo(
|
||||
@@ -131,7 +145,7 @@ function ServiceAccountDrawer({
|
||||
isLoading: isRolesLoading,
|
||||
applyDiff,
|
||||
} = useServiceAccountRoleManager(selectedAccountId ?? '', {
|
||||
enabled: !!selectedAccountId,
|
||||
enabled: canRead && !!selectedAccountId,
|
||||
});
|
||||
|
||||
const roleSessionRef = useRef<string | null>(null);
|
||||
@@ -180,9 +194,16 @@ function ServiceAccountDrawer({
|
||||
refetch: refetchRoles,
|
||||
} = useRoles();
|
||||
|
||||
const canListKeys =
|
||||
drawerPermissions?.[APIKeyListPermission]?.isGranted ?? false;
|
||||
|
||||
const canUpdate =
|
||||
drawerPermissions?.[buildSAUpdatePermission(selectedAccountId ?? '')]
|
||||
?.isGranted ?? true;
|
||||
|
||||
const { data: keysData, isLoading: keysLoading } = useListServiceAccountKeys(
|
||||
{ id: selectedAccountId ?? '' },
|
||||
{ query: { enabled: !!selectedAccountId } },
|
||||
{ query: { enabled: !!selectedAccountId && canListKeys } },
|
||||
);
|
||||
const keys = keysData?.data ?? [];
|
||||
|
||||
@@ -196,6 +217,7 @@ function ServiceAccountDrawer({
|
||||
}
|
||||
}, [keysLoading, keys.length, keysPage, setKeysPage]);
|
||||
|
||||
// the retry for this mutation is safe due to the api being idempotent on backend
|
||||
const { mutateAsync: updateMutateAsync } = useUpdateServiceAccount();
|
||||
|
||||
const retryNameUpdate = useCallback(async (): Promise<void> => {
|
||||
@@ -353,71 +375,23 @@ function ServiceAccountDrawer({
|
||||
]);
|
||||
|
||||
const handleClose = useCallback((): void => {
|
||||
setSaveErrors([]);
|
||||
void setIsDeleteOpen(null);
|
||||
void setIsAddKeyOpen(null);
|
||||
void setSelectedAccountId(null);
|
||||
void setActiveTab(null);
|
||||
void setKeysPage(null);
|
||||
void setEditKeyId(null);
|
||||
void setIsAddKeyOpen(null);
|
||||
void setIsDeleteOpen(null);
|
||||
void setSelectedAccountId(null);
|
||||
setSaveErrors([]);
|
||||
}, [
|
||||
setSelectedAccountId,
|
||||
setActiveTab,
|
||||
setKeysPage,
|
||||
setEditKeyId,
|
||||
setIsAddKeyOpen,
|
||||
setIsDeleteOpen,
|
||||
setSelectedAccountId,
|
||||
]);
|
||||
|
||||
const footer = useMemo(
|
||||
() =>
|
||||
activeTab === ServiceAccountDrawerTab.Overview && !isDeleted && open ? (
|
||||
<div className="sa-drawer__footer">
|
||||
<AuthZButton
|
||||
checks={[buildSADeletePermission(selectedAccountId ?? '')]}
|
||||
authZEnabled={!!selectedAccountId}
|
||||
variant="link"
|
||||
color="destructive"
|
||||
onClick={(): void => {
|
||||
void setIsDeleteOpen(true);
|
||||
}}
|
||||
>
|
||||
<Trash2 size={12} />
|
||||
Delete Service Account
|
||||
</AuthZButton>
|
||||
<div className="sa-drawer__footer-right">
|
||||
<Button variant="outlined" color="secondary" onClick={handleClose}>
|
||||
<X size={14} />
|
||||
Cancel
|
||||
</Button>
|
||||
<AuthZButton
|
||||
checks={[buildSAUpdatePermission(selectedAccountId ?? '')]}
|
||||
authZEnabled={!!selectedAccountId}
|
||||
variant="solid"
|
||||
color="primary"
|
||||
loading={isSaving}
|
||||
disabled={!isDirty}
|
||||
onClick={handleSave}
|
||||
>
|
||||
Save Changes
|
||||
</AuthZButton>
|
||||
</div>
|
||||
</div>
|
||||
) : null,
|
||||
[
|
||||
activeTab,
|
||||
isDeleted,
|
||||
open,
|
||||
selectedAccountId,
|
||||
isSaving,
|
||||
isDirty,
|
||||
handleClose,
|
||||
handleSave,
|
||||
setIsDeleteOpen,
|
||||
],
|
||||
);
|
||||
|
||||
const body = (
|
||||
const drawerContent = (
|
||||
<div className="sa-drawer__layout">
|
||||
<div className="sa-drawer__tabs">
|
||||
<ToggleGroupSimple
|
||||
@@ -459,23 +433,26 @@ function ServiceAccountDrawer({
|
||||
]}
|
||||
/>
|
||||
{activeTab === ServiceAccountDrawerTab.Keys && (
|
||||
<AuthZButton
|
||||
<AuthZTooltip
|
||||
checks={[
|
||||
APIKeyCreatePermission,
|
||||
buildSAAttachPermission(selectedAccountId ?? ''),
|
||||
]}
|
||||
authZEnabled={!isDeleted && !!selectedAccountId}
|
||||
variant="outlined"
|
||||
size="sm"
|
||||
color="secondary"
|
||||
disabled={isDeleted}
|
||||
onClick={(): void => {
|
||||
void setIsAddKeyOpen(true);
|
||||
}}
|
||||
enabled={!isDeleted && !!selectedAccountId}
|
||||
>
|
||||
<Plus size={12} />
|
||||
Add Key
|
||||
</AuthZButton>
|
||||
<Button
|
||||
variant="outlined"
|
||||
size="sm"
|
||||
color="secondary"
|
||||
disabled={isDeleted}
|
||||
onClick={(): void => {
|
||||
void setIsAddKeyOpen(true);
|
||||
}}
|
||||
>
|
||||
<Plus size={12} />
|
||||
Add Key
|
||||
</Button>
|
||||
</AuthZTooltip>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -484,7 +461,9 @@ function ServiceAccountDrawer({
|
||||
activeTab === ServiceAccountDrawerTab.Keys ? ' sa-drawer__body--keys' : ''
|
||||
}`}
|
||||
>
|
||||
{isAccountLoading && <Skeleton active paragraph={{ rows: 6 }} />}
|
||||
{(isAuthZLoading || isAccountLoading) && (
|
||||
<Skeleton active paragraph={{ rows: 6 }} />
|
||||
)}
|
||||
{isAccountError && (
|
||||
<ErrorInPlace
|
||||
error={toAPIError(
|
||||
@@ -493,73 +472,141 @@ function ServiceAccountDrawer({
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
{!isAccountLoading && !isAccountError && (
|
||||
<>
|
||||
{activeTab === ServiceAccountDrawerTab.Overview &&
|
||||
(account ? (
|
||||
<OverviewTab
|
||||
account={account}
|
||||
localName={localName}
|
||||
onNameChange={handleNameChange}
|
||||
localRoles={localRoles}
|
||||
onRolesChange={(roles): void => {
|
||||
setLocalRoles(roles);
|
||||
clearRoleErrors();
|
||||
}}
|
||||
isDisabled={isDeleted}
|
||||
availableRoles={availableRoles}
|
||||
rolesLoading={rolesLoading}
|
||||
rolesError={rolesError}
|
||||
rolesErrorObj={rolesErrorObj}
|
||||
onRefetchRoles={refetchRoles}
|
||||
saveErrors={saveErrors}
|
||||
/>
|
||||
) : (
|
||||
<Skeleton active />
|
||||
))}
|
||||
{activeTab === ServiceAccountDrawerTab.Keys && (
|
||||
<KeysTab
|
||||
keys={keys}
|
||||
isLoading={keysLoading}
|
||||
isDisabled={isDeleted}
|
||||
accountId={selectedAccountId ?? ''}
|
||||
currentPage={keysPage}
|
||||
pageSize={PAGE_SIZE}
|
||||
onPageChange={(page): void => {
|
||||
void setKeysPage(page);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
{!isAuthZLoading &&
|
||||
!isAccountLoading &&
|
||||
!isAccountError &&
|
||||
selectedAccountId && (
|
||||
<>
|
||||
{activeTab === ServiceAccountDrawerTab.Overview &&
|
||||
(canRead && account ? (
|
||||
<OverviewTab
|
||||
account={account}
|
||||
localName={localName}
|
||||
onNameChange={handleNameChange}
|
||||
localRoles={localRoles}
|
||||
onRolesChange={(roles): void => {
|
||||
setLocalRoles(roles);
|
||||
clearRoleErrors();
|
||||
}}
|
||||
isDisabled={isDeleted}
|
||||
canUpdate={canUpdate}
|
||||
availableRoles={availableRoles}
|
||||
rolesLoading={rolesLoading}
|
||||
rolesError={rolesError}
|
||||
rolesErrorObj={rolesErrorObj}
|
||||
onRefetchRoles={refetchRoles}
|
||||
saveErrors={saveErrors}
|
||||
/>
|
||||
) : (
|
||||
<PermissionDeniedCallout permissionName="serviceaccount:read" />
|
||||
))}
|
||||
{activeTab === ServiceAccountDrawerTab.Keys &&
|
||||
(canListKeys ? (
|
||||
<KeysTab
|
||||
keys={keys}
|
||||
isLoading={keysLoading}
|
||||
isDisabled={isDeleted}
|
||||
canUpdate={canUpdate}
|
||||
accountId={selectedAccountId}
|
||||
currentPage={keysPage}
|
||||
pageSize={PAGE_SIZE}
|
||||
/>
|
||||
) : (
|
||||
<PermissionDeniedCallout permissionName="factor-api-key:list" />
|
||||
))}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
return (
|
||||
<DrawerWrapper
|
||||
open={open}
|
||||
onOpenChange={(isOpen): void => {
|
||||
if (!isOpen) {
|
||||
handleClose();
|
||||
}
|
||||
}}
|
||||
direction="right"
|
||||
showCloseButton
|
||||
showOverlay={false}
|
||||
title="Service Account Details"
|
||||
className="sa-drawer"
|
||||
width="wide"
|
||||
footer={footer}
|
||||
>
|
||||
{open && (
|
||||
const footer = (
|
||||
<div className="sa-drawer__footer">
|
||||
{activeTab === ServiceAccountDrawerTab.Keys ? (
|
||||
<Pagination
|
||||
current={keysPage}
|
||||
pageSize={PAGE_SIZE}
|
||||
total={keys.length}
|
||||
showTotal={(total: number, range: number[]): JSX.Element => (
|
||||
<>
|
||||
<span className="sa-drawer__pagination-range">
|
||||
{range[0]} — {range[1]}
|
||||
</span>
|
||||
<span className="sa-drawer__pagination-total"> of {total}</span>
|
||||
</>
|
||||
)}
|
||||
showSizeChanger={false}
|
||||
hideOnSinglePage
|
||||
onChange={(page): void => {
|
||||
void setKeysPage(page);
|
||||
}}
|
||||
className="sa-drawer__keys-pagination"
|
||||
/>
|
||||
) : (
|
||||
<>
|
||||
{body}
|
||||
<DeleteAccountModal />
|
||||
<AddKeyModal />
|
||||
{!isDeleted && (
|
||||
<AuthZTooltip
|
||||
checks={[buildSADeletePermission(selectedAccountId ?? '')]}
|
||||
enabled={!!selectedAccountId}
|
||||
>
|
||||
<Button
|
||||
variant="link"
|
||||
color="destructive"
|
||||
onClick={(): void => {
|
||||
void setIsDeleteOpen(true);
|
||||
}}
|
||||
>
|
||||
<Trash2 size={12} />
|
||||
Delete Service Account
|
||||
</Button>
|
||||
</AuthZTooltip>
|
||||
)}
|
||||
{!isDeleted && (
|
||||
<div className="sa-drawer__footer-right">
|
||||
<Button variant="outlined" color="secondary" onClick={handleClose}>
|
||||
<X size={14} />
|
||||
Cancel
|
||||
</Button>
|
||||
<Button
|
||||
variant="solid"
|
||||
color="primary"
|
||||
loading={isSaving}
|
||||
disabled={!isDirty}
|
||||
onClick={handleSave}
|
||||
>
|
||||
Save Changes
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</DrawerWrapper>
|
||||
</div>
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<DrawerWrapper
|
||||
open={open}
|
||||
onOpenChange={(isOpen): void => {
|
||||
if (!isOpen) {
|
||||
handleClose();
|
||||
}
|
||||
}}
|
||||
direction="right"
|
||||
showCloseButton
|
||||
showOverlay={false}
|
||||
title="Service Account Details"
|
||||
className="sa-drawer"
|
||||
width="wide"
|
||||
footer={footer}
|
||||
>
|
||||
{drawerContent}
|
||||
</DrawerWrapper>
|
||||
|
||||
<DeleteAccountModal />
|
||||
|
||||
<AddKeyModal />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { toast } from '@signozhq/ui/sonner';
|
||||
import { setupAuthzAdmin } from 'lib/authz/utils/authz-test-utils';
|
||||
import { rest, server } from 'mocks-server/server';
|
||||
import { NuqsTestingAdapter } from 'nuqs/adapters/testing';
|
||||
import {
|
||||
@@ -60,7 +59,6 @@ describe('AddKeyModal', () => {
|
||||
rest.post(SA_KEYS_ENDPOINT, (_, res, ctx) =>
|
||||
res(ctx.status(201), ctx.json(createdKeyResponse)),
|
||||
),
|
||||
setupAuthzAdmin(),
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { toast } from '@signozhq/ui/sonner';
|
||||
import type { ServiceaccounttypesGettableFactorAPIKeyDTO } from 'api/generated/services/sigNoz.schemas';
|
||||
import { setupAuthzAdmin } from 'lib/authz/utils/authz-test-utils';
|
||||
import { rest, server } from 'mocks-server/server';
|
||||
import { NuqsTestingAdapter } from 'nuqs/adapters/testing';
|
||||
import { render, screen, userEvent, waitFor } from 'tests/test-utils';
|
||||
@@ -62,7 +61,6 @@ describe('EditKeyModal (URL-controlled)', () => {
|
||||
rest.delete(SA_KEY_ENDPOINT, (_, res, ctx) =>
|
||||
res(ctx.status(200), ctx.json({ status: 'success', data: {} })),
|
||||
),
|
||||
setupAuthzAdmin(),
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { toast } from '@signozhq/ui/sonner';
|
||||
import { ServiceaccounttypesGettableFactorAPIKeyDTO } from 'api/generated/services/sigNoz.schemas';
|
||||
import { setupAuthzAdmin } from 'lib/authz/utils/authz-test-utils';
|
||||
import { rest, server } from 'mocks-server/server';
|
||||
import { NuqsTestingAdapter } from 'nuqs/adapters/testing';
|
||||
import { render, screen, userEvent, waitFor } from 'tests/test-utils';
|
||||
@@ -36,7 +35,7 @@ const keys: ServiceaccounttypesGettableFactorAPIKeyDTO[] = [
|
||||
{
|
||||
id: 'key-2',
|
||||
name: 'Staging Key',
|
||||
expiresAt: 1924948800, // 2030-12-31 12:00 UTC (noon to avoid timezone issues)
|
||||
expiresAt: 1924905600, // 2030-12-31
|
||||
lastObservedAt: '2026-03-10T10:00:00Z',
|
||||
serviceAccountId: 'sa-1',
|
||||
},
|
||||
@@ -48,7 +47,6 @@ const defaultProps = {
|
||||
isDisabled: false,
|
||||
currentPage: 1,
|
||||
pageSize: 10,
|
||||
onPageChange: jest.fn(),
|
||||
};
|
||||
|
||||
function renderKeysTab(
|
||||
@@ -69,7 +67,6 @@ describe('KeysTab', () => {
|
||||
rest.delete(SA_KEY_ENDPOINT, (_, res, ctx) =>
|
||||
res(ctx.status(200), ctx.json({ status: 'success', data: {} })),
|
||||
),
|
||||
setupAuthzAdmin(),
|
||||
);
|
||||
});
|
||||
|
||||
@@ -77,12 +74,9 @@ describe('KeysTab', () => {
|
||||
server.resetHandlers();
|
||||
});
|
||||
|
||||
it('renders loading state', async () => {
|
||||
it('renders loading state', () => {
|
||||
renderKeysTab({ isLoading: true });
|
||||
// Wait for authz to complete, then check for skeleton
|
||||
await waitFor(() => {
|
||||
expect(document.querySelector('.ant-skeleton')).toBeInTheDocument();
|
||||
});
|
||||
expect(document.querySelector('.ant-skeleton')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('renders empty state when no keys and clicking add sets add-key param', async () => {
|
||||
@@ -97,9 +91,9 @@ describe('KeysTab', () => {
|
||||
</NuqsTestingAdapter>,
|
||||
);
|
||||
|
||||
await expect(
|
||||
screen.findByText(/No keys. Start by creating one./i),
|
||||
).resolves.toBeInTheDocument();
|
||||
expect(
|
||||
screen.getByText(/No keys. Start by creating one./i),
|
||||
).toBeInTheDocument();
|
||||
const addBtn = screen.getByRole('button', { name: /\+ Add your first key/i });
|
||||
await user.click(addBtn);
|
||||
expect(onUrlUpdate).toHaveBeenCalledWith(
|
||||
@@ -109,12 +103,10 @@ describe('KeysTab', () => {
|
||||
);
|
||||
});
|
||||
|
||||
it('renders table with keys', async () => {
|
||||
it('renders table with keys', () => {
|
||||
renderKeysTab();
|
||||
|
||||
await expect(
|
||||
screen.findByText('Production Key'),
|
||||
).resolves.toBeInTheDocument();
|
||||
expect(screen.getByText('Production Key')).toBeInTheDocument();
|
||||
expect(screen.getByText('Staging Key')).toBeInTheDocument();
|
||||
expect(screen.getByText('Never')).toBeInTheDocument();
|
||||
expect(screen.getByText('Dec 31, 2030')).toBeInTheDocument();
|
||||
@@ -130,7 +122,7 @@ describe('KeysTab', () => {
|
||||
</NuqsTestingAdapter>,
|
||||
);
|
||||
|
||||
const row = (await screen.findByText('Production Key')).closest('tr');
|
||||
const row = screen.getByText('Production Key').closest('tr');
|
||||
if (!row) {
|
||||
throw new Error('Row not found');
|
||||
}
|
||||
@@ -154,8 +146,6 @@ describe('KeysTab', () => {
|
||||
</NuqsTestingAdapter>,
|
||||
);
|
||||
|
||||
// Wait for authz to complete and table to render
|
||||
await screen.findByText('Production Key');
|
||||
const revokeBtns = screen
|
||||
.getAllByRole('button')
|
||||
.filter((btn) => btn.className.includes('keys-tab__revoke-btn'));
|
||||
@@ -173,8 +163,7 @@ describe('KeysTab', () => {
|
||||
|
||||
renderKeysTab();
|
||||
|
||||
// Wait for authz to complete and table to render
|
||||
await screen.findByText('Production Key');
|
||||
// Seed the keys cache so RevokeKeyModal can read the key name
|
||||
const revokeBtns = screen
|
||||
.getAllByRole('button')
|
||||
.filter((btn) => btn.className.includes('keys-tab__revoke-btn'));
|
||||
@@ -188,11 +177,9 @@ describe('KeysTab', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('disables actions when isDisabled is true', async () => {
|
||||
it('disables actions when isDisabled is true', () => {
|
||||
renderKeysTab({ isDisabled: true });
|
||||
|
||||
// Wait for authz to complete and table to render
|
||||
await screen.findByText('Production Key');
|
||||
const revokeBtns = screen
|
||||
.getAllByRole('button')
|
||||
.filter((btn) => btn.className.includes('keys-tab__revoke-btn'));
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import type { ReactNode } from 'react';
|
||||
import { listRolesSuccessResponse } from 'mocks-server/__mockdata__/roles';
|
||||
import { rest, server } from 'mocks-server/server';
|
||||
import { NuqsTestingAdapter } from 'nuqs/adapters/testing';
|
||||
@@ -31,6 +32,30 @@ const activeAccountResponse = {
|
||||
updatedAt: '2026-01-02T00:00:00Z',
|
||||
};
|
||||
|
||||
jest.mock('@signozhq/ui/drawer', () => ({
|
||||
...jest.requireActual('@signozhq/ui/drawer'),
|
||||
DrawerWrapper: ({
|
||||
children,
|
||||
footer,
|
||||
open,
|
||||
}: {
|
||||
children?: ReactNode;
|
||||
footer?: ReactNode;
|
||||
open: boolean;
|
||||
}): JSX.Element | null =>
|
||||
open ? (
|
||||
<div>
|
||||
{children}
|
||||
{footer}
|
||||
</div>
|
||||
) : null,
|
||||
}));
|
||||
|
||||
jest.mock('@signozhq/ui/sonner', () => ({
|
||||
...jest.requireActual('@signozhq/ui/sonner'),
|
||||
toast: { success: jest.fn(), error: jest.fn() },
|
||||
}));
|
||||
|
||||
function renderDrawer(
|
||||
searchParams: Record<string, string> = { account: 'sa-1' },
|
||||
): ReturnType<typeof render> {
|
||||
@@ -93,7 +118,7 @@ describe('ServiceAccountDrawer — permissions', () => {
|
||||
renderDrawer();
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText(/read:serviceaccount/)).toBeInTheDocument();
|
||||
expect(screen.getByText(/serviceaccount:read/)).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -115,7 +140,7 @@ describe('ServiceAccountDrawer — permissions', () => {
|
||||
fireEvent.click(screen.getByRole('radio', { name: /keys/i }));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText(/list:factor-api-key/)).toBeInTheDocument();
|
||||
expect(screen.getByText(/factor-api-key:list/)).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import type { ReactNode } from 'react';
|
||||
import { listRolesSuccessResponse } from 'mocks-server/__mockdata__/roles';
|
||||
import { rest, server } from 'mocks-server/server';
|
||||
import { NuqsTestingAdapter } from 'nuqs/adapters/testing';
|
||||
@@ -6,6 +7,30 @@ import { setupAuthzAdmin } from 'lib/authz/utils/authz-test-utils';
|
||||
|
||||
import ServiceAccountDrawer from '../ServiceAccountDrawer';
|
||||
|
||||
jest.mock('@signozhq/ui/drawer', () => ({
|
||||
...jest.requireActual('@signozhq/ui/drawer'),
|
||||
DrawerWrapper: ({
|
||||
children,
|
||||
footer,
|
||||
open,
|
||||
}: {
|
||||
children?: ReactNode;
|
||||
footer?: ReactNode;
|
||||
open: boolean;
|
||||
}): JSX.Element | null =>
|
||||
open ? (
|
||||
<div>
|
||||
{children}
|
||||
{footer}
|
||||
</div>
|
||||
) : null,
|
||||
}));
|
||||
|
||||
jest.mock('@signozhq/ui/sonner', () => ({
|
||||
...jest.requireActual('@signozhq/ui/sonner'),
|
||||
toast: { success: jest.fn(), error: jest.fn() },
|
||||
}));
|
||||
|
||||
const ROLES_ENDPOINT = '*/api/v1/roles';
|
||||
const SA_KEYS_ENDPOINT = '*/api/v1/service_accounts/:id/keys';
|
||||
const SA_ENDPOINT = '*/api/v1/service_accounts/sa-1';
|
||||
|
||||
@@ -2,22 +2,10 @@
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 2;
|
||||
padding: var(
|
||||
--tanstack-cell-header-padding-top-override,
|
||||
var(--tanstack-cell-padding-top, 0.3rem)
|
||||
)
|
||||
var(
|
||||
--tanstack-cell-header-padding-right-override,
|
||||
var(--tanstack-cell-padding-right, 0.3rem)
|
||||
)
|
||||
var(
|
||||
--tanstack-cell-header-padding-bottom-override,
|
||||
var(--tanstack-cell-padding-bottom, 0.3rem)
|
||||
)
|
||||
var(
|
||||
--tanstack-cell-header-padding-left-override,
|
||||
var(--tanstack-cell-padding-left, 0.3rem)
|
||||
);
|
||||
padding: var(--tanstack-cell-padding-top, 0.3rem)
|
||||
var(--tanstack-cell-padding-right, 0.3rem)
|
||||
var(--tanstack-cell-padding-bottom, 0.3rem)
|
||||
var(--tanstack-cell-padding-left, 0.3rem);
|
||||
transform: translate3d(
|
||||
var(--tanstack-header-translate-x, 0px),
|
||||
var(--tanstack-header-translate-y, 0px),
|
||||
@@ -251,7 +239,6 @@
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
min-width: 0;
|
||||
width: var(--tanstack-table-header-label-width);
|
||||
}
|
||||
|
||||
.tanstackSortIndicator {
|
||||
|
||||
@@ -1,37 +1,33 @@
|
||||
import { useCallback, useEffect, useRef, useState } from 'react';
|
||||
import { Button, Tooltip } from 'antd';
|
||||
import { Typography } from '@signozhq/ui/typography';
|
||||
import { listHosts } from 'api/generated/services/inframonitoring';
|
||||
import {
|
||||
InframonitoringtypesHostRecordDTO,
|
||||
InframonitoringtypesHostStatusDTO,
|
||||
InframonitoringtypesResponseTypeDTO,
|
||||
Querybuildertypesv5OrderDirectionDTO,
|
||||
} from 'api/generated/services/sigNoz.schemas';
|
||||
import logEvent from 'api/common/logEvent';
|
||||
import QuickFilters from 'components/QuickFilters/QuickFilters';
|
||||
import { QuickFiltersSource } from 'components/QuickFilters/types';
|
||||
import { InfraMonitoringEvents } from 'constants/events';
|
||||
import { FeatureKeys } from 'constants/features';
|
||||
import { initialQueriesMap } from 'constants/queryBuilder';
|
||||
import K8sBaseDetails, {
|
||||
K8sDetailsFilters,
|
||||
} from 'container/InfraMonitoringK8sV2/Base/K8sBaseDetails';
|
||||
import K8sBaseDetails from 'container/InfraMonitoringK8sV2/Base/K8sBaseDetails';
|
||||
import { K8sBaseList } from 'container/InfraMonitoringK8sV2/Base/K8sBaseList';
|
||||
import StatusFilter from 'container/InfraMonitoringHostsV2/StatusFilter';
|
||||
import { K8sBaseFilters } from 'container/InfraMonitoringK8sV2/Base/types';
|
||||
import { InfraMonitoringEntity } from 'container/InfraMonitoringK8sV2/constants';
|
||||
import { useGetCompositeQueryParam } from 'hooks/queryBuilder/useGetCompositeQueryParam';
|
||||
import {
|
||||
useInfraMonitoringFiltersK8s,
|
||||
useInfraMonitoringPageListing,
|
||||
} from 'container/InfraMonitoringK8sV2/hooks';
|
||||
import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
|
||||
import { useQueryOperations } from 'hooks/queryBuilder/useQueryBuilderOperations';
|
||||
import { useAppContext } from 'providers/App/App';
|
||||
import { DataSource } from 'types/common/queryBuilder';
|
||||
import { Query } from 'types/api/queryBuilder/queryBuilderData';
|
||||
|
||||
import {
|
||||
fetchHostEntityData,
|
||||
fetchHostListData,
|
||||
getHostMetricsQueryPayload,
|
||||
hostDetailsMetadataConfig,
|
||||
hostGetEntityName,
|
||||
hostGetSelectedItemExpression,
|
||||
hostInitialEventsExpression,
|
||||
hostInitialLogTracesExpression,
|
||||
hostGetSelectedItemFilters,
|
||||
hostInitialEventsFilter,
|
||||
hostInitialLogTracesFilter,
|
||||
hostWidgetInfo,
|
||||
} from './constants';
|
||||
import {
|
||||
@@ -46,135 +42,85 @@ import { ArrowUpToLine, Filter } from '@signozhq/icons';
|
||||
|
||||
function Hosts(): JSX.Element {
|
||||
const [showFilters, setShowFilters] = useState(true);
|
||||
|
||||
const compositeQuery = useGetCompositeQueryParam();
|
||||
const { redirectWithQueryBuilderData } = useQueryBuilder();
|
||||
const isInitialized = useRef(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (isInitialized.current) {
|
||||
return;
|
||||
}
|
||||
isInitialized.current = true;
|
||||
|
||||
if (!compositeQuery) {
|
||||
const defaultQuery = initialQueriesMap[DataSource.METRICS];
|
||||
redirectWithQueryBuilderData({
|
||||
...defaultQuery,
|
||||
builder: {
|
||||
...defaultQuery.builder,
|
||||
queryData: defaultQuery.builder.queryData.map((query) => ({
|
||||
...query,
|
||||
filter: { expression: '' },
|
||||
filters: { items: [], op: 'AND' as const },
|
||||
})),
|
||||
},
|
||||
});
|
||||
}
|
||||
}, [compositeQuery, redirectWithQueryBuilderData]);
|
||||
const [, setCurrentPage] = useInfraMonitoringPageListing();
|
||||
const [urlFilters, setUrlFilters] = useInfraMonitoringFiltersK8s();
|
||||
|
||||
const { featureFlags } = useAppContext();
|
||||
const dotMetricsEnabled =
|
||||
featureFlags?.find((flag) => flag.name === FeatureKeys.DOT_METRICS_ENABLED)
|
||||
?.active || false;
|
||||
|
||||
const { currentQuery } = useQueryBuilder();
|
||||
const { handleChangeQueryData } = useQueryOperations({
|
||||
index: 0,
|
||||
query: currentQuery.builder.queryData[0],
|
||||
entityVersion: '',
|
||||
});
|
||||
|
||||
// Track previous urlFilters to only sync when the value actually changes
|
||||
// (not when handleChangeQueryData changes due to query updates)
|
||||
const prevUrlFiltersRef = useRef<string | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
const currentFiltersJson = urlFilters ? JSON.stringify(urlFilters) : null;
|
||||
|
||||
// Only sync if urlFilters value has actually changed
|
||||
if (prevUrlFiltersRef.current !== currentFiltersJson) {
|
||||
prevUrlFiltersRef.current = currentFiltersJson;
|
||||
// Sync filters to query builder, using empty filter when urlFilters is null
|
||||
handleChangeQueryData('filters', urlFilters || { items: [], op: 'and' });
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [urlFilters]); // handleChangeQueryData intentionally omitted - we call the current version but don't re-run when it changes
|
||||
|
||||
const handleFilterVisibilityChange = (): void => {
|
||||
setShowFilters(!showFilters);
|
||||
};
|
||||
|
||||
const handleQuickFiltersChange = (query: Query): void => {
|
||||
const filters = query.builder.queryData[0].filters;
|
||||
// Nuqs batches these calls into a single URL update
|
||||
// The useEffect will sync filters to query builder
|
||||
setUrlFilters(filters || null);
|
||||
setCurrentPage(1);
|
||||
logEvent(InfraMonitoringEvents.FilterApplied, {
|
||||
entity: InfraMonitoringEvents.HostEntity,
|
||||
page: InfraMonitoringEvents.ListPage,
|
||||
});
|
||||
};
|
||||
|
||||
const fetchListData = useCallback(
|
||||
async (filters: K8sBaseFilters, signal?: AbortSignal) => {
|
||||
try {
|
||||
const response = await listHosts(
|
||||
{
|
||||
filter: {
|
||||
expression: filters.filter.expression,
|
||||
filterByStatus: filters.filter.filterByStatus
|
||||
? (filters.filter.filterByStatus as InframonitoringtypesHostStatusDTO)
|
||||
: undefined,
|
||||
},
|
||||
groupBy: filters.groupBy?.map((g) => ({ name: g.name })),
|
||||
offset: filters.offset,
|
||||
limit: filters.limit ?? 10,
|
||||
start: filters.start,
|
||||
end: filters.end,
|
||||
orderBy: filters.orderBy
|
||||
? {
|
||||
key: { name: filters.orderBy.key.name },
|
||||
direction:
|
||||
filters.orderBy.direction === 'asc'
|
||||
? Querybuildertypesv5OrderDirectionDTO.asc
|
||||
: Querybuildertypesv5OrderDirectionDTO.desc,
|
||||
}
|
||||
: undefined,
|
||||
},
|
||||
signal,
|
||||
);
|
||||
filters.orderBy ||= {
|
||||
columnName: 'cpu',
|
||||
order: 'desc',
|
||||
};
|
||||
|
||||
const data = response.data;
|
||||
return {
|
||||
type:
|
||||
data.type === InframonitoringtypesResponseTypeDTO.grouped_list
|
||||
? ('grouped_list' as const)
|
||||
: ('list' as const),
|
||||
records: data.records,
|
||||
total: data.total,
|
||||
endTimeBeforeRetention: data.endTimeBeforeRetention,
|
||||
};
|
||||
} catch (error) {
|
||||
const errMsg =
|
||||
error instanceof Error ? error.message : 'Failed to fetch hosts';
|
||||
return {
|
||||
type: 'list' as const,
|
||||
records: [] as InframonitoringtypesHostRecordDTO[],
|
||||
total: 0,
|
||||
error: errMsg,
|
||||
};
|
||||
}
|
||||
return fetchHostListData(filters, signal);
|
||||
},
|
||||
[],
|
||||
);
|
||||
|
||||
const fetchEntityData = useCallback(
|
||||
async (
|
||||
filters: K8sDetailsFilters,
|
||||
filters: Parameters<typeof fetchHostEntityData>[0],
|
||||
signal?: AbortSignal,
|
||||
): Promise<{
|
||||
data: InframonitoringtypesHostRecordDTO | null;
|
||||
error?: string | null;
|
||||
}> => {
|
||||
try {
|
||||
const response = await listHosts(
|
||||
{
|
||||
filter: { expression: filters.filter.expression },
|
||||
start: filters.start,
|
||||
end: filters.end,
|
||||
limit: 1,
|
||||
offset: 0,
|
||||
},
|
||||
signal,
|
||||
);
|
||||
|
||||
return {
|
||||
data: response.data.records.length > 0 ? response.data.records[0] : null,
|
||||
};
|
||||
} catch (error) {
|
||||
const errMsg =
|
||||
error instanceof Error ? error.message : 'Failed to fetch host';
|
||||
return {
|
||||
data: null,
|
||||
error: errMsg,
|
||||
};
|
||||
}
|
||||
},
|
||||
) => fetchHostEntityData(filters, signal),
|
||||
[],
|
||||
);
|
||||
|
||||
const getInitialLogTracesExpression = useCallback(
|
||||
(host: InframonitoringtypesHostRecordDTO) =>
|
||||
hostInitialLogTracesExpression(host, dotMetricsEnabled),
|
||||
const getSelectedItemFilters = useCallback(
|
||||
(selectedItem: string) =>
|
||||
hostGetSelectedItemFilters(selectedItem, dotMetricsEnabled),
|
||||
[dotMetricsEnabled],
|
||||
);
|
||||
|
||||
const getInitialLogTracesFilters = useCallback(
|
||||
(host: import('api/infraMonitoring/getHostLists').HostData) =>
|
||||
hostInitialLogTracesFilter(host, dotMetricsEnabled),
|
||||
[dotMetricsEnabled],
|
||||
);
|
||||
|
||||
const controlListPrefix = !showFilters ? (
|
||||
<div className={styles.quickFiltersToggleContainer}>
|
||||
<Button
|
||||
@@ -208,6 +154,7 @@ function Hosts(): JSX.Element {
|
||||
source={QuickFiltersSource.INFRA_MONITORING}
|
||||
config={getHostsQuickFiltersConfig(dotMetricsEnabled)}
|
||||
handleFilterVisibilityChange={handleFilterVisibilityChange}
|
||||
onFilterChange={handleQuickFiltersChange}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
@@ -216,9 +163,8 @@ function Hosts(): JSX.Element {
|
||||
showFilters ? ` ${styles.listContainerFiltersVisible}` : ''
|
||||
}`}
|
||||
>
|
||||
<K8sBaseList<InframonitoringtypesHostRecordDTO>
|
||||
<K8sBaseList
|
||||
controlListPrefix={controlListPrefix}
|
||||
leftFilters={<StatusFilter />}
|
||||
entity={InfraMonitoringEntity.HOSTS}
|
||||
tableColumns={hostColumnsConfig}
|
||||
fetchListData={fetchListData}
|
||||
@@ -232,11 +178,11 @@ function Hosts(): JSX.Element {
|
||||
<K8sBaseDetails
|
||||
category={InfraMonitoringEntity.HOSTS}
|
||||
eventCategory={InfraMonitoringEvents.HostEntity}
|
||||
getSelectedItemExpression={hostGetSelectedItemExpression}
|
||||
getSelectedItemFilters={getSelectedItemFilters}
|
||||
fetchEntityData={fetchEntityData}
|
||||
getEntityName={hostGetEntityName}
|
||||
getInitialLogTracesExpression={getInitialLogTracesExpression}
|
||||
getInitialEventsExpression={hostInitialEventsExpression}
|
||||
getInitialLogTracesFilters={getInitialLogTracesFilters}
|
||||
getInitialEventsFilters={hostInitialEventsFilter}
|
||||
metadataConfig={hostDetailsMetadataConfig}
|
||||
entityWidgetInfo={hostWidgetInfo}
|
||||
getEntityQueryPayload={getHostMetricsQueryPayload}
|
||||
|
||||
@@ -26,8 +26,8 @@
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
width: 0.1rem;
|
||||
height: 0.1rem;
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar-track {
|
||||
@@ -35,12 +35,11 @@
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar-thumb {
|
||||
background: color-mix(in srgb, var(--l1-foreground) 20%, transparent);
|
||||
border-radius: 9999px;
|
||||
background: var(--accent-primary);
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar-thumb:hover {
|
||||
background: color-mix(in srgb, var(--l1-foreground) 30%, transparent);
|
||||
background: colox-mix(var(--accent-primary), var(--bg-vanilla-100), 20%);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -99,7 +98,7 @@
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar-thumb:hover {
|
||||
background: var(--accent-primary);
|
||||
background: colox-mix(var(--accent-primary), var(--bg-vanilla-100), 20%);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -114,17 +113,32 @@
|
||||
|
||||
.listContainer {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
max-width: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
min-width: 595px;
|
||||
|
||||
> * {
|
||||
min-width: 0;
|
||||
max-width: 100%;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
> :global(.ant-table-wrapper) {
|
||||
width: 100%;
|
||||
max-width: 100%;
|
||||
|
||||
:global(.ant-spin-container) {
|
||||
display: flex !important;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
:global(.ant-table),
|
||||
:global(.ant-table-container) {
|
||||
max-width: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.listContainerFiltersVisible {
|
||||
@@ -132,7 +146,7 @@
|
||||
}
|
||||
|
||||
.quickFiltersToggleContainer {
|
||||
grid-area: quickFilters;
|
||||
padding: 0 8px;
|
||||
}
|
||||
|
||||
.infraMonitoringTags {
|
||||
|
||||
@@ -1,56 +0,0 @@
|
||||
.statusFilterContainer {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
--toggle-group-item-size: 100%;
|
||||
--toggle-group-radius: 0px 2px 2px 0px;
|
||||
--toggle-group-item-font-size: var(--periscope-font-size-base);
|
||||
}
|
||||
|
||||
.statusLabel {
|
||||
min-width: max-content;
|
||||
font-size: var(--periscope-font-size-base);
|
||||
font-weight: var(--periscope-font-weight-regular);
|
||||
line-height: 18px;
|
||||
|
||||
border-radius: 2px 0px 0px 2px;
|
||||
border: 1px solid var(--l2-border);
|
||||
border-right: none;
|
||||
|
||||
display: flex;
|
||||
height: 32px;
|
||||
padding: var(--spacing-3) var(--spacing-3) var(--spacing-3) var(--spacing-4);
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
gap: var(--spacing-2);
|
||||
}
|
||||
|
||||
.statusToggleGroup {
|
||||
height: 32px;
|
||||
}
|
||||
|
||||
.statusToggleItem {
|
||||
flex: unset;
|
||||
}
|
||||
|
||||
.statusDot {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
min-height: 8px;
|
||||
min-width: 8px;
|
||||
border-radius: 50%;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.allDot {
|
||||
border: 1px solid var(--text-muted);
|
||||
background-color: var(--bg-slate-100);
|
||||
}
|
||||
|
||||
.activeDot {
|
||||
background-color: var(--bg-forest-500);
|
||||
}
|
||||
|
||||
.inactiveDot {
|
||||
background-color: var(--bg-amber-500);
|
||||
}
|
||||
@@ -1,152 +0,0 @@
|
||||
import { QueryClient, QueryClientProvider } from 'react-query';
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { Provider } from 'react-redux';
|
||||
import { MemoryRouter } from 'react-router-dom';
|
||||
import { fireEvent, render, screen, waitFor } from '@testing-library/react';
|
||||
import {
|
||||
NuqsTestingAdapter,
|
||||
OnUrlUpdateFunction,
|
||||
UrlUpdateEvent,
|
||||
} from 'nuqs/adapters/testing';
|
||||
import { AppContext } from 'providers/App/App';
|
||||
import TimezoneProvider from 'providers/Timezone';
|
||||
import store from 'store';
|
||||
import { getAppContextMock } from 'tests/test-utils';
|
||||
|
||||
import StatusFilter from './StatusFilter';
|
||||
|
||||
const queryClient = new QueryClient({
|
||||
defaultOptions: {
|
||||
queries: { retry: false },
|
||||
mutations: { retry: false },
|
||||
},
|
||||
});
|
||||
|
||||
function renderStatusFilter({
|
||||
searchParams = {},
|
||||
onUrlUpdate,
|
||||
}: {
|
||||
searchParams?: Record<string, string>;
|
||||
onUrlUpdate?: OnUrlUpdateFunction;
|
||||
}): ReturnType<typeof render> {
|
||||
return render(
|
||||
<MemoryRouter>
|
||||
<TimezoneProvider>
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<Provider store={store}>
|
||||
<AppContext.Provider value={getAppContextMock('ADMIN')}>
|
||||
<NuqsTestingAdapter
|
||||
searchParams={searchParams}
|
||||
onUrlUpdate={onUrlUpdate}
|
||||
>
|
||||
<StatusFilter />
|
||||
</NuqsTestingAdapter>
|
||||
</AppContext.Provider>
|
||||
</Provider>
|
||||
</QueryClientProvider>
|
||||
</TimezoneProvider>
|
||||
</MemoryRouter>,
|
||||
);
|
||||
}
|
||||
|
||||
describe('StatusFilter', () => {
|
||||
beforeEach(() => {
|
||||
queryClient.clear();
|
||||
});
|
||||
|
||||
it('renders all status options', () => {
|
||||
renderStatusFilter({});
|
||||
|
||||
expect(screen.getByText('Status')).toBeInTheDocument();
|
||||
expect(screen.getByRole('radio', { name: 'All' })).toBeInTheDocument();
|
||||
expect(screen.getByRole('radio', { name: 'Active' })).toBeInTheDocument();
|
||||
expect(screen.getByRole('radio', { name: 'Inactive' })).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('selects "All" by default when no URL param', () => {
|
||||
renderStatusFilter({});
|
||||
|
||||
const allButton = screen.getByRole('radio', { name: 'All' });
|
||||
expect(allButton).toHaveAttribute('aria-checked', 'true');
|
||||
});
|
||||
|
||||
it('reads "active" from URL and shows Active selected', () => {
|
||||
renderStatusFilter({ searchParams: { statusFilter: 'active' } });
|
||||
|
||||
const activeButton = screen.getByRole('radio', { name: 'Active' });
|
||||
expect(activeButton).toHaveAttribute('aria-checked', 'true');
|
||||
});
|
||||
|
||||
it('reads "inactive" from URL and shows Inactive selected', () => {
|
||||
renderStatusFilter({ searchParams: { statusFilter: 'inactive' } });
|
||||
|
||||
const inactiveButton = screen.getByRole('radio', { name: 'Inactive' });
|
||||
expect(inactiveButton).toHaveAttribute('aria-checked', 'true');
|
||||
});
|
||||
|
||||
it('updates URL to "active" when Active clicked', async () => {
|
||||
const onUrlUpdate = jest.fn<void, [UrlUpdateEvent]>();
|
||||
renderStatusFilter({ onUrlUpdate });
|
||||
|
||||
const activeButton = screen.getByRole('radio', { name: 'Active' });
|
||||
fireEvent.click(activeButton);
|
||||
|
||||
await waitFor(() => {
|
||||
const statusFilterValue = onUrlUpdate.mock.calls
|
||||
.map((call) => call[0].searchParams.get('statusFilter'))
|
||||
.filter(Boolean)
|
||||
.pop();
|
||||
expect(statusFilterValue).toBe('active');
|
||||
});
|
||||
});
|
||||
|
||||
it('updates URL to "inactive" when Inactive clicked', async () => {
|
||||
const onUrlUpdate = jest.fn<void, [UrlUpdateEvent]>();
|
||||
renderStatusFilter({ onUrlUpdate });
|
||||
|
||||
const inactiveButton = screen.getByRole('radio', { name: 'Inactive' });
|
||||
fireEvent.click(inactiveButton);
|
||||
|
||||
await waitFor(() => {
|
||||
const statusFilterValue = onUrlUpdate.mock.calls
|
||||
.map((call) => call[0].searchParams.get('statusFilter'))
|
||||
.filter(Boolean)
|
||||
.pop();
|
||||
expect(statusFilterValue).toBe('inactive');
|
||||
});
|
||||
});
|
||||
|
||||
it('removes statusFilter from URL when All clicked', async () => {
|
||||
const onUrlUpdate = jest.fn<void, [UrlUpdateEvent]>();
|
||||
renderStatusFilter({
|
||||
searchParams: { statusFilter: 'active' },
|
||||
onUrlUpdate,
|
||||
});
|
||||
|
||||
const allButton = screen.getByRole('radio', { name: 'All' });
|
||||
fireEvent.click(allButton);
|
||||
|
||||
await waitFor(() => {
|
||||
const lastCall = onUrlUpdate.mock.calls[onUrlUpdate.mock.calls.length - 1];
|
||||
expect(lastCall[0].searchParams.get('statusFilter')).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
it('resets page when filter changes', async () => {
|
||||
const onUrlUpdate = jest.fn<void, [UrlUpdateEvent]>();
|
||||
renderStatusFilter({
|
||||
searchParams: { page: '3' },
|
||||
onUrlUpdate,
|
||||
});
|
||||
|
||||
const activeButton = screen.getByRole('radio', { name: 'Active' });
|
||||
fireEvent.click(activeButton);
|
||||
|
||||
await waitFor(() => {
|
||||
const lastCall = onUrlUpdate.mock.calls[onUrlUpdate.mock.calls.length - 1];
|
||||
const pageValue = lastCall[0].searchParams.get('page');
|
||||
// page=1 is default, so nuqs removes it from URL (null) or keeps as "1"
|
||||
expect(pageValue === null || pageValue === '1').toBe(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,63 +0,0 @@
|
||||
import { ToggleGroup, ToggleGroupItem } from '@signozhq/ui/toggle-group';
|
||||
import {
|
||||
StatusFilterValue,
|
||||
useInfraMonitoringPageListing,
|
||||
useInfraMonitoringStatusFilter,
|
||||
} from 'container/InfraMonitoringK8sV2/hooks';
|
||||
|
||||
import styles from './StatusFilter.module.scss';
|
||||
|
||||
const statusOptions: Array<{
|
||||
label: string;
|
||||
value: StatusFilterValue | 'all';
|
||||
}> = [
|
||||
{ label: 'All', value: 'all' },
|
||||
{ label: 'Active', value: 'active' },
|
||||
{ label: 'Inactive', value: 'inactive' },
|
||||
];
|
||||
|
||||
function StatusFilter(): JSX.Element {
|
||||
const [statusFilter, setStatusFilter] = useInfraMonitoringStatusFilter();
|
||||
const [, setCurrentPage] = useInfraMonitoringPageListing();
|
||||
|
||||
const handleChange = (value: string): void => {
|
||||
if (value !== undefined) {
|
||||
void setStatusFilter(value === 'all' ? '' : (value as StatusFilterValue));
|
||||
void setCurrentPage(1);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={styles.statusFilterContainer}>
|
||||
<div className={styles.statusLabel}>Status</div>
|
||||
<ToggleGroup
|
||||
type="single"
|
||||
value={statusFilter === '' ? 'all' : statusFilter}
|
||||
onChange={handleChange}
|
||||
className={styles.statusToggleGroup}
|
||||
>
|
||||
{statusOptions.map((option) => (
|
||||
<ToggleGroupItem
|
||||
key={option.value}
|
||||
value={option.value}
|
||||
aria-label={option.label}
|
||||
className={styles.statusToggleItem}
|
||||
>
|
||||
<span
|
||||
className={`${styles.statusDot} ${
|
||||
option.value === 'active'
|
||||
? styles.activeDot
|
||||
: option.value === 'inactive'
|
||||
? styles.inactiveDot
|
||||
: styles.allDot
|
||||
}`}
|
||||
/>
|
||||
{option.label}
|
||||
</ToggleGroupItem>
|
||||
))}
|
||||
</ToggleGroup>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default StatusFilter;
|
||||
@@ -4,16 +4,26 @@ import { Badge } from '@signozhq/ui/badge';
|
||||
import { Progress } from '@signozhq/ui/progress';
|
||||
import { Typography } from '@signozhq/ui/typography';
|
||||
import {
|
||||
InframonitoringtypesHostRecordDTO,
|
||||
InframonitoringtypesHostStatusDTO,
|
||||
} from 'api/generated/services/sigNoz.schemas';
|
||||
import { K8sDetailsMetadataConfig } from 'container/InfraMonitoringK8sV2/Base/K8sBaseDetails';
|
||||
import { formatValueForExpression } from 'components/QueryBuilderV2/utils';
|
||||
import { INFRA_MONITORING_ATTR_KEYS } from 'container/InfraMonitoringK8sV2/constants';
|
||||
getHostLists,
|
||||
HostData,
|
||||
HostListPayload,
|
||||
} from 'api/infraMonitoring/getHostLists';
|
||||
import {
|
||||
createFilterItem,
|
||||
K8sDetailsFilters,
|
||||
K8sDetailsMetadataConfig,
|
||||
} from 'container/InfraMonitoringK8sV2/Base/K8sBaseDetails';
|
||||
import { K8sBaseFilters } from 'container/InfraMonitoringK8sV2/Base/types';
|
||||
import {
|
||||
getHostQueryPayload,
|
||||
hostWidgetInfo,
|
||||
} from 'container/LogDetailedView/InfraMetrics/constants';
|
||||
import {
|
||||
TagFilter,
|
||||
TagFilterItem,
|
||||
} from 'types/api/queryBuilder/queryBuilderData';
|
||||
|
||||
import { getHostListsQuery } from './utils';
|
||||
|
||||
import infraHostsStyles from './InfraMonitoringHosts.module.scss';
|
||||
|
||||
@@ -37,32 +47,24 @@ export function getMemoryProgressColor(percent: number): string {
|
||||
return Color.BG_FOREST_500;
|
||||
}
|
||||
|
||||
export type HostDetailMetadataConfigType =
|
||||
K8sDetailsMetadataConfig<InframonitoringtypesHostRecordDTO>;
|
||||
export const hostDetailsMetadataConfig: HostDetailMetadataConfigType[] = [
|
||||
export const hostDetailsMetadataConfig: K8sDetailsMetadataConfig<HostData>[] = [
|
||||
{
|
||||
label: 'STATUS',
|
||||
getValue: (h): string =>
|
||||
h.status === InframonitoringtypesHostStatusDTO.active
|
||||
? 'ACTIVE'
|
||||
: 'INACTIVE',
|
||||
render: (value, h): React.ReactNode => {
|
||||
const isActive = h.status === InframonitoringtypesHostStatusDTO.active;
|
||||
return (
|
||||
<Badge
|
||||
variant="outline"
|
||||
className={`${infraHostsStyles.infraMonitoringTags} ${
|
||||
isActive ? infraHostsStyles.tagsActive : infraHostsStyles.tagsInactive
|
||||
}`}
|
||||
>
|
||||
{value}
|
||||
</Badge>
|
||||
);
|
||||
},
|
||||
getValue: (h): string => (h.active ? 'ACTIVE' : 'INACTIVE'),
|
||||
render: (value, h): React.ReactNode => (
|
||||
<Badge
|
||||
variant="outline"
|
||||
className={`${infraHostsStyles.infraMonitoringTags} ${
|
||||
h.active ? infraHostsStyles.tagsActive : infraHostsStyles.tagsInactive
|
||||
}`}
|
||||
>
|
||||
{value}
|
||||
</Badge>
|
||||
),
|
||||
},
|
||||
{
|
||||
label: 'OPERATING SYSTEM',
|
||||
getValue: (h): string => h.meta?.['os.type'] || '-',
|
||||
getValue: (h): string => h.os || '-',
|
||||
render: (value): React.ReactNode =>
|
||||
value !== '-' ? (
|
||||
<Badge variant="outline" className={infraHostsStyles.infraMonitoringTags}>
|
||||
@@ -97,7 +99,7 @@ export const hostDetailsMetadataConfig: HostDetailMetadataConfigType[] = [
|
||||
];
|
||||
|
||||
export function getHostMetricsQueryPayload(
|
||||
host: InframonitoringtypesHostRecordDTO,
|
||||
host: HostData,
|
||||
start: number,
|
||||
end: number,
|
||||
dotMetricsEnabled: boolean,
|
||||
@@ -107,26 +109,83 @@ export function getHostMetricsQueryPayload(
|
||||
|
||||
export { hostWidgetInfo };
|
||||
|
||||
export const hostGetSelectedItemExpression = (hostName: string): string =>
|
||||
`host.name = ${formatValueForExpression(hostName)}`;
|
||||
|
||||
export function hostInitialLogTracesExpression(
|
||||
host: InframonitoringtypesHostRecordDTO,
|
||||
export function hostGetSelectedItemFilters(
|
||||
selectedItem: string,
|
||||
dotMetricsEnabled: boolean,
|
||||
): string {
|
||||
const hostKey = dotMetricsEnabled
|
||||
? INFRA_MONITORING_ATTR_KEYS.HOST_NAME
|
||||
: 'host_name';
|
||||
const hostName = formatValueForExpression(host.hostName || '');
|
||||
return `${hostKey} = ${hostName}`;
|
||||
): TagFilter {
|
||||
const hostKey = dotMetricsEnabled ? 'host.name' : 'host_name';
|
||||
return {
|
||||
op: 'AND',
|
||||
items: [createFilterItem(hostKey, selectedItem)],
|
||||
};
|
||||
}
|
||||
|
||||
export function hostInitialEventsExpression(
|
||||
_host: InframonitoringtypesHostRecordDTO,
|
||||
): string {
|
||||
return '';
|
||||
export function hostInitialLogTracesFilter(
|
||||
host: HostData,
|
||||
dotMetricsEnabled: boolean,
|
||||
): TagFilterItem[] {
|
||||
const hostKey = dotMetricsEnabled ? 'host.name' : 'host_name';
|
||||
return [createFilterItem(hostKey, host.hostName || '')];
|
||||
}
|
||||
|
||||
export const hostGetEntityName = (
|
||||
host: InframonitoringtypesHostRecordDTO,
|
||||
): string => host.hostName;
|
||||
export function hostInitialEventsFilter(_host: HostData): TagFilterItem[] {
|
||||
return [];
|
||||
}
|
||||
|
||||
export const hostGetEntityName = (host: HostData): string => host.hostName;
|
||||
|
||||
export async function fetchHostListData(
|
||||
filters: K8sBaseFilters,
|
||||
signal?: AbortSignal,
|
||||
): Promise<{
|
||||
data: HostData[];
|
||||
total: number;
|
||||
error?: string | null;
|
||||
rawData?: unknown;
|
||||
}> {
|
||||
const baseQuery = getHostListsQuery();
|
||||
const payload: HostListPayload = {
|
||||
...baseQuery,
|
||||
limit: filters.limit,
|
||||
offset: filters.offset,
|
||||
filters: filters.filters ?? { items: [], op: 'and' },
|
||||
orderBy: filters.orderBy,
|
||||
start: filters.start,
|
||||
end: filters.end,
|
||||
groupBy: filters.groupBy ?? [],
|
||||
};
|
||||
|
||||
const response = await getHostLists(payload, signal);
|
||||
|
||||
return {
|
||||
data: response.payload?.data?.records || [],
|
||||
total: response.payload?.data?.total || 0,
|
||||
error: response.error,
|
||||
rawData: response.payload?.data,
|
||||
};
|
||||
}
|
||||
|
||||
export async function fetchHostEntityData(
|
||||
filters: K8sDetailsFilters,
|
||||
signal?: AbortSignal,
|
||||
): Promise<{ data: HostData | null; error?: string | null }> {
|
||||
const response = await getHostLists(
|
||||
{
|
||||
...getHostListsQuery(),
|
||||
filters: filters.filters,
|
||||
start: filters.start,
|
||||
end: filters.end,
|
||||
limit: 1,
|
||||
offset: 0,
|
||||
groupBy: [],
|
||||
},
|
||||
signal,
|
||||
);
|
||||
|
||||
const records = response.payload?.data?.records || [];
|
||||
|
||||
return {
|
||||
data: records.length > 0 ? records[0] : null,
|
||||
error: response.error,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,91 +1,55 @@
|
||||
import React from 'react';
|
||||
import { Color } from '@signozhq/design-tokens';
|
||||
import { Container } from '@signozhq/icons';
|
||||
import {
|
||||
InframonitoringtypesHostRecordDTO,
|
||||
InframonitoringtypesHostStatusDTO,
|
||||
} from 'api/generated/services/sigNoz.schemas';
|
||||
import { Tooltip } from 'antd';
|
||||
import { Badge } from '@signozhq/ui/badge';
|
||||
import { HostData } from 'api/infraMonitoring/getHostLists';
|
||||
import TanStackTable, { TableColumnDef } from 'components/TanStackTableView';
|
||||
import { getGroupByEl } from 'container/InfraMonitoringK8sV2/Base/utils';
|
||||
import {
|
||||
EntityProgressBar,
|
||||
ExpandButtonWrapper,
|
||||
GroupedStatusCounts,
|
||||
ValidateColumnValueWrapper,
|
||||
} from 'container/InfraMonitoringK8sV2/components';
|
||||
import {
|
||||
INFRA_MONITORING_ATTR_KEYS,
|
||||
InfraMonitoringEntity,
|
||||
} from 'container/InfraMonitoringK8sV2/constants';
|
||||
import { InfraMonitoringEntity } from 'container/InfraMonitoringK8sV2/constants';
|
||||
import { useInfraMonitoringGroupBy } from 'container/InfraMonitoringK8sV2/hooks';
|
||||
import ColumnHeader from 'container/InfraMonitoringK8sV2/Base/ColumnHeader';
|
||||
import EntityGroupHeader from 'container/InfraMonitoringK8sV2/Base/EntityGroupHeader';
|
||||
|
||||
import { HostnameCell } from './utils';
|
||||
|
||||
import styles from './table.module.scss';
|
||||
import { Badge, BadgeColor } from '@signozhq/ui/badge';
|
||||
import { Container, Info } from '@signozhq/icons';
|
||||
|
||||
const statusMap: Record<
|
||||
InframonitoringtypesHostStatusDTO,
|
||||
{
|
||||
label: string;
|
||||
color: BadgeColor;
|
||||
}
|
||||
> = {
|
||||
[InframonitoringtypesHostStatusDTO.active]: {
|
||||
label: 'ACTIVE',
|
||||
color: 'forest',
|
||||
},
|
||||
[InframonitoringtypesHostStatusDTO.inactive]: {
|
||||
label: 'INACTIVE',
|
||||
color: 'amber',
|
||||
},
|
||||
['']: {
|
||||
label: 'UNKNOWN',
|
||||
color: 'secondary',
|
||||
},
|
||||
};
|
||||
|
||||
function hostRowSource(host: InframonitoringtypesHostRecordDTO): {
|
||||
meta: Record<string, string>;
|
||||
} {
|
||||
function hostRowSource(host: HostData): { meta: Record<string, string> } {
|
||||
return {
|
||||
meta: {
|
||||
...(host.meta ?? {}),
|
||||
[INFRA_MONITORING_ATTR_KEYS.HOST_NAME]: host.hostName ?? '',
|
||||
host_name: host.hostName ?? '',
|
||||
'host.name': host.hostName ?? '',
|
||||
os_type: host.os ?? '',
|
||||
'os.type': host.os ?? '',
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export function getHostRowKey(host: InframonitoringtypesHostRecordDTO): string {
|
||||
export function getHostRowKey(host: HostData): string {
|
||||
return host.hostName || 'unknown';
|
||||
}
|
||||
|
||||
export function getHostItemKey(
|
||||
host: InframonitoringtypesHostRecordDTO,
|
||||
): string {
|
||||
export function getHostItemKey(host: HostData): string {
|
||||
return host.hostName ?? '';
|
||||
}
|
||||
|
||||
function HostGroupCell({
|
||||
row,
|
||||
}: {
|
||||
row: InframonitoringtypesHostRecordDTO;
|
||||
}): JSX.Element {
|
||||
function HostGroupCell({ row }: { row: HostData }): JSX.Element {
|
||||
const [groupBy] = useInfraMonitoringGroupBy();
|
||||
const synthetic = hostRowSource(row);
|
||||
return getGroupByEl(synthetic, groupBy) as JSX.Element;
|
||||
}
|
||||
|
||||
export type HostColumnConfigType =
|
||||
TableColumnDef<InframonitoringtypesHostRecordDTO>;
|
||||
export const hostColumnsConfig: HostColumnConfigType[] = [
|
||||
export const hostColumnsConfig: TableColumnDef<HostData>[] = [
|
||||
{
|
||||
id: 'hostGroup',
|
||||
header: (): React.ReactNode => <EntityGroupHeader title="Host Group" />,
|
||||
header: (): React.ReactNode => <EntityGroupHeader title="HOST GROUP" />,
|
||||
accessorFn: (row): string => row.hostName ?? '',
|
||||
width: { min: 290 },
|
||||
width: { min: 300 },
|
||||
enableSort: false,
|
||||
enableRemove: false,
|
||||
enableMove: false,
|
||||
@@ -100,11 +64,7 @@ export const hostColumnsConfig: HostColumnConfigType[] = [
|
||||
{
|
||||
id: 'hostName',
|
||||
header: (): React.ReactNode => (
|
||||
<EntityGroupHeader
|
||||
title="Hostname"
|
||||
icon={<Container size={14} />}
|
||||
docPath="/infrastructure-monitoring/host-monitoring#hostname"
|
||||
/>
|
||||
<EntityGroupHeader title="Hostname" icon={<Container size={14} />} />
|
||||
),
|
||||
accessorFn: (row): string => row.hostName ?? '',
|
||||
width: { min: 290 },
|
||||
@@ -118,48 +78,27 @@ export const hostColumnsConfig: HostColumnConfigType[] = [
|
||||
),
|
||||
},
|
||||
{
|
||||
id: 'status',
|
||||
id: 'active',
|
||||
header: (): React.ReactNode => (
|
||||
<ColumnHeader
|
||||
tooltip="Sent system metrics in last 10 mins."
|
||||
docPath="/infrastructure-monitoring/host-monitoring#status"
|
||||
>
|
||||
<div className={styles.statusHeader}>
|
||||
Status
|
||||
</ColumnHeader>
|
||||
<Tooltip title="Sent system metrics in last 10 mins">
|
||||
<Info size="md" />
|
||||
</Tooltip>
|
||||
</div>
|
||||
),
|
||||
accessorFn: (row): string => row.status,
|
||||
width: { min: 140 },
|
||||
accessorFn: (row): boolean => row.active,
|
||||
width: { min: 150, default: 150 },
|
||||
enableSort: false,
|
||||
cell: ({ value, groupMeta, row }): React.ReactNode => {
|
||||
const status = value as InframonitoringtypesHostStatusDTO;
|
||||
|
||||
if (groupMeta) {
|
||||
return (
|
||||
<GroupedStatusCounts
|
||||
items={[
|
||||
{
|
||||
value: row.activeHostCount,
|
||||
label: 'Active',
|
||||
color: Color.BG_FOREST_500,
|
||||
},
|
||||
{
|
||||
value: row.inactiveHostCount,
|
||||
label: 'Inactive',
|
||||
color: Color.BG_AMBER_500,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
const statusDetails = statusMap[status] || statusMap[''];
|
||||
cell: ({ value }): React.ReactNode => {
|
||||
const active = value as boolean;
|
||||
return (
|
||||
<Badge
|
||||
variant="outline"
|
||||
color={statusDetails.color}
|
||||
className={`${styles.statusTag}`}
|
||||
className={`${styles.statusTag} ${
|
||||
active ? styles.statusTagActive : styles.statusTagInactive
|
||||
}`}
|
||||
>
|
||||
{statusDetails.label}
|
||||
{active ? 'ACTIVE' : 'INACTIVE'}
|
||||
</Badge>
|
||||
);
|
||||
},
|
||||
@@ -167,9 +106,7 @@ export const hostColumnsConfig: HostColumnConfigType[] = [
|
||||
{
|
||||
id: 'cpu',
|
||||
header: (): React.ReactNode => (
|
||||
<ColumnHeader docPath="/infrastructure-monitoring/host-monitoring#cpu-usage">
|
||||
CPU Usage
|
||||
</ColumnHeader>
|
||||
<div className={styles.columnHeaderRight}>CPU Usage</div>
|
||||
),
|
||||
accessorFn: (row): number => row.cpu,
|
||||
width: { min: 220 },
|
||||
@@ -192,16 +129,15 @@ export const hostColumnsConfig: HostColumnConfigType[] = [
|
||||
{
|
||||
id: 'memory',
|
||||
header: (): React.ReactNode => (
|
||||
<ColumnHeader
|
||||
tooltip="Excluding cache memory."
|
||||
docPath="/infrastructure-monitoring/host-monitoring#memory-usage"
|
||||
className={styles.memoryUsageHeader}
|
||||
>
|
||||
Memory Usage (WSS)
|
||||
</ColumnHeader>
|
||||
<div className={`${styles.columnHeaderRight} ${styles.memoryUsageHeader}`}>
|
||||
Memory Usage
|
||||
<Tooltip title="Excluding cache memory">
|
||||
<Info size="md" />
|
||||
</Tooltip>
|
||||
</div>
|
||||
),
|
||||
accessorFn: (row): number => row.memory,
|
||||
width: { min: 240 },
|
||||
width: { min: 220 },
|
||||
enableSort: true,
|
||||
cell: ({ value }): React.ReactNode => {
|
||||
const memory = value as number;
|
||||
@@ -221,12 +157,10 @@ export const hostColumnsConfig: HostColumnConfigType[] = [
|
||||
{
|
||||
id: 'wait',
|
||||
header: (): React.ReactNode => (
|
||||
<ColumnHeader docPath="/infrastructure-monitoring/host-monitoring#iowait">
|
||||
IOWait
|
||||
</ColumnHeader>
|
||||
<div className={styles.columnHeaderRight}>IOWait</div>
|
||||
),
|
||||
accessorFn: (row): number => row.wait,
|
||||
width: { min: 120 },
|
||||
width: { min: 100, default: 100 },
|
||||
enableSort: true,
|
||||
cell: ({ value }): React.ReactNode => {
|
||||
const wait = value as number;
|
||||
@@ -237,9 +171,7 @@ export const hostColumnsConfig: HostColumnConfigType[] = [
|
||||
entity={InfraMonitoringEntity.HOSTS}
|
||||
attribute="IOWait metric"
|
||||
>
|
||||
<TanStackTable.Text>
|
||||
{`${Number((wait * 100).toFixed(1))}%`}
|
||||
</TanStackTable.Text>
|
||||
<TanStackTable.Text>{`${Number((wait * 100).toFixed(1))}%`}</TanStackTable.Text>
|
||||
</ValidateColumnValueWrapper>
|
||||
);
|
||||
},
|
||||
@@ -247,15 +179,13 @@ export const hostColumnsConfig: HostColumnConfigType[] = [
|
||||
{
|
||||
id: 'load15',
|
||||
header: (): React.ReactNode => (
|
||||
<ColumnHeader docPath="/infrastructure-monitoring/host-monitoring#load-avg">
|
||||
Load Avg (15min)
|
||||
</ColumnHeader>
|
||||
<div className={styles.columnHeaderRight}>Load Avg</div>
|
||||
),
|
||||
accessorFn: (row): number => row.load15,
|
||||
width: { min: 200 },
|
||||
width: { min: 100, default: 100 },
|
||||
enableSort: true,
|
||||
cell: ({ value }): React.ReactNode => {
|
||||
const load15 = Number(value);
|
||||
const load15 = value as number;
|
||||
|
||||
return (
|
||||
<ValidateColumnValueWrapper
|
||||
@@ -263,35 +193,9 @@ export const hostColumnsConfig: HostColumnConfigType[] = [
|
||||
entity={InfraMonitoringEntity.HOSTS}
|
||||
attribute="load average metric"
|
||||
>
|
||||
<TanStackTable.Text>{load15.toFixed(2)}</TanStackTable.Text>
|
||||
<TanStackTable.Text>{load15}</TanStackTable.Text>
|
||||
</ValidateColumnValueWrapper>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'diskUsage',
|
||||
header: (): React.ReactNode => (
|
||||
<ColumnHeader docPath="/infrastructure-monitoring/host-monitoring#disk-usage">
|
||||
Disk Usage
|
||||
</ColumnHeader>
|
||||
),
|
||||
accessorFn: (row): number => row.diskUsage,
|
||||
width: { min: 160 },
|
||||
enableSort: true,
|
||||
cell: ({ value }): React.ReactNode => {
|
||||
const diskUsage = value as number;
|
||||
|
||||
return (
|
||||
<div className={styles.progressContainer}>
|
||||
<ValidateColumnValueWrapper
|
||||
value={diskUsage}
|
||||
entity={InfraMonitoringEntity.HOSTS}
|
||||
attribute="disk usage metric"
|
||||
>
|
||||
<EntityProgressBar value={diskUsage} type="disk" />
|
||||
</ValidateColumnValueWrapper>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
@@ -9,6 +9,13 @@
|
||||
display: block;
|
||||
}
|
||||
|
||||
.statusHeader {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: var(--spacing-2);
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.columnHeaderRight {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
@@ -2,16 +2,14 @@ import React from 'react';
|
||||
import { Color } from '@signozhq/design-tokens';
|
||||
import { Tooltip } from 'antd';
|
||||
import { Typography } from '@signozhq/ui/typography';
|
||||
import { HostListPayload } from 'api/infraMonitoring/getHostLists';
|
||||
import {
|
||||
FiltersType,
|
||||
IQuickFiltersConfig,
|
||||
} from 'components/QuickFilters/types';
|
||||
import { TriangleAlert } from '@signozhq/icons';
|
||||
import { INFRA_MONITORING_ATTR_KEYS } from 'container/InfraMonitoringK8sV2/constants';
|
||||
import { CellValueTooltip } from 'container/InfraMonitoringK8sV2/components';
|
||||
import { DataTypes } from 'types/api/queryBuilder/queryAutocompleteResponse';
|
||||
import { DataSource } from 'types/common/queryBuilder';
|
||||
import TanStackTable from 'components/TanStackTableView';
|
||||
|
||||
const HOSTNAME_DOCS_URL =
|
||||
'https://signoz.io/docs/infrastructure-monitoring/hostmetrics/#host-name-is-blankempty';
|
||||
@@ -23,15 +21,13 @@ export function HostnameCell({
|
||||
}): React.ReactElement {
|
||||
const isEmpty = !hostName || !hostName.trim();
|
||||
if (!isEmpty) {
|
||||
return (
|
||||
<CellValueTooltip value={hostName}>
|
||||
<TanStackTable.Text>{hostName}</TanStackTable.Text>
|
||||
</CellValueTooltip>
|
||||
);
|
||||
return <div className="hostname-column-value">{hostName}</div>;
|
||||
}
|
||||
return (
|
||||
<>
|
||||
<Typography.Text color="muted">-</Typography.Text>
|
||||
<div className="hostname-cell-missing">
|
||||
<Typography.Text color="muted" className="hostname-cell-placeholder">
|
||||
-
|
||||
</Typography.Text>
|
||||
<Tooltip
|
||||
title={
|
||||
<div>
|
||||
@@ -64,16 +60,52 @@ export function HostnameCell({
|
||||
<TriangleAlert size={14} color={Color.BG_CHERRY_500} />
|
||||
</span>
|
||||
</Tooltip>
|
||||
</>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export const getHostListsQuery = (): HostListPayload => ({
|
||||
filters: {
|
||||
items: [],
|
||||
op: 'and',
|
||||
},
|
||||
groupBy: [],
|
||||
orderBy: { columnName: 'cpu', order: 'desc' },
|
||||
});
|
||||
|
||||
export const HostsQuickFiltersConfig: IQuickFiltersConfig[] = [
|
||||
{
|
||||
type: FiltersType.CHECKBOX,
|
||||
title: 'Host Name',
|
||||
attributeKey: {
|
||||
key: 'host_name',
|
||||
dataType: DataTypes.String,
|
||||
type: 'resource',
|
||||
},
|
||||
aggregateOperator: 'noop',
|
||||
aggregateAttribute: 'system_cpu_load_average_15m',
|
||||
dataSource: DataSource.METRICS,
|
||||
defaultOpen: true,
|
||||
},
|
||||
{
|
||||
type: FiltersType.CHECKBOX,
|
||||
title: 'OS Type',
|
||||
attributeKey: {
|
||||
key: 'os_type',
|
||||
dataType: DataTypes.String,
|
||||
type: 'resource',
|
||||
},
|
||||
aggregateOperator: 'noop',
|
||||
aggregateAttribute: 'system_cpu_load_average_15m',
|
||||
dataSource: DataSource.METRICS,
|
||||
defaultOpen: true,
|
||||
},
|
||||
];
|
||||
|
||||
export function getHostsQuickFiltersConfig(
|
||||
dotMetricsEnabled: boolean,
|
||||
): IQuickFiltersConfig[] {
|
||||
const hostNameKey = dotMetricsEnabled
|
||||
? INFRA_MONITORING_ATTR_KEYS.HOST_NAME
|
||||
: 'host_name';
|
||||
const hostNameKey = dotMetricsEnabled ? 'host.name' : 'host_name';
|
||||
const osTypeKey = dotMetricsEnabled ? 'os.type' : 'os_type';
|
||||
const metricName = dotMetricsEnabled
|
||||
? 'system.cpu.load_average.15m'
|
||||
|
||||
@@ -1,16 +0,0 @@
|
||||
.columnHeader {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: var(--spacing-2);
|
||||
}
|
||||
|
||||
.infoIcon {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.columnHeaderLabel {
|
||||
text-align: center;
|
||||
padding: var(--spacing-2) var(--spacing-2) var(--spacing-2) 0px;
|
||||
}
|
||||
@@ -1,90 +0,0 @@
|
||||
import { Info } from '@signozhq/icons';
|
||||
import { TooltipSimple } from '@signozhq/ui/tooltip';
|
||||
|
||||
import styles from './ColumnHeader.module.scss';
|
||||
import cx from 'classnames';
|
||||
|
||||
const DOCS_BASE_URL = `${process.env.DOCS_BASE_URL}/docs`;
|
||||
|
||||
interface ColumnHeaderProps {
|
||||
children?: React.ReactNode;
|
||||
title?: string;
|
||||
docPath?: string;
|
||||
tooltip?: string;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
function ColumnHeader({
|
||||
children,
|
||||
title,
|
||||
docPath,
|
||||
tooltip,
|
||||
className,
|
||||
}: ColumnHeaderProps): JSX.Element {
|
||||
const renderContent = (): React.ReactNode => {
|
||||
if (children) {
|
||||
return children;
|
||||
}
|
||||
|
||||
if (title) {
|
||||
const parts = title.split('\n');
|
||||
return parts.map((part, index) => (
|
||||
<div key={`${part}-${index}`}>
|
||||
{part}
|
||||
{index < parts.length - 1 && <br />}
|
||||
</div>
|
||||
));
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
const renderInfoIcon = (): React.ReactNode => {
|
||||
if (docPath) {
|
||||
const tooltipTitle = tooltip || 'Not sure what this means?';
|
||||
return (
|
||||
<TooltipSimple
|
||||
arrow
|
||||
title={
|
||||
<>
|
||||
{tooltipTitle}{' '}
|
||||
<a
|
||||
href={`${DOCS_BASE_URL}${docPath}`}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
onClick={(e): void => e.stopPropagation()}
|
||||
>
|
||||
Learn more.
|
||||
</a>
|
||||
</>
|
||||
}
|
||||
>
|
||||
<div className={styles.infoIcon}>
|
||||
<Info size="md" />
|
||||
</div>
|
||||
</TooltipSimple>
|
||||
);
|
||||
}
|
||||
|
||||
if (tooltip) {
|
||||
return (
|
||||
<TooltipSimple title={tooltip}>
|
||||
<div className={styles.infoIcon}>
|
||||
<Info size="md" />
|
||||
</div>
|
||||
</TooltipSimple>
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={cx(styles.columnHeader, className)} data-slot="column-header">
|
||||
<div className={styles.columnHeaderLabel}>{renderContent()}</div>
|
||||
{renderInfoIcon()}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default ColumnHeader;
|
||||
@@ -2,16 +2,4 @@
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--spacing-5);
|
||||
padding-left: 4px;
|
||||
}
|
||||
|
||||
.infoIcon {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
color: var(--text-slate-secondary);
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
color: var(--text-slate-primary);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,70 +1,19 @@
|
||||
import { Group, Info } from '@signozhq/icons';
|
||||
import { TooltipSimple } from '@signozhq/ui/tooltip';
|
||||
import { Group } from '@signozhq/icons';
|
||||
|
||||
import styles from './EntityGroupHeader.module.scss';
|
||||
|
||||
const DOCS_BASE_URL = `${process.env.DOCS_BASE_URL}/docs`;
|
||||
|
||||
interface EntityGroupHeaderProps {
|
||||
title: string;
|
||||
icon?: React.ReactNode;
|
||||
docPath?: string;
|
||||
tooltip?: string;
|
||||
}
|
||||
|
||||
function EntityGroupHeader({
|
||||
title,
|
||||
icon,
|
||||
docPath,
|
||||
tooltip,
|
||||
}: EntityGroupHeaderProps): JSX.Element {
|
||||
const renderInfoIcon = (): React.ReactNode => {
|
||||
if (docPath) {
|
||||
const tooltipTitle = tooltip || 'Not sure what this means?';
|
||||
return (
|
||||
<TooltipSimple
|
||||
arrow
|
||||
title={
|
||||
<>
|
||||
{tooltipTitle}{' '}
|
||||
<a
|
||||
href={`${DOCS_BASE_URL}${docPath}`}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
onClick={(e): void => e.stopPropagation()}
|
||||
>
|
||||
Learn more.
|
||||
</a>
|
||||
</>
|
||||
}
|
||||
>
|
||||
<span className={styles.infoIcon}>
|
||||
<Info size="md" />
|
||||
</span>
|
||||
</TooltipSimple>
|
||||
);
|
||||
}
|
||||
|
||||
if (tooltip) {
|
||||
return (
|
||||
<TooltipSimple title={tooltip}>
|
||||
<span className={styles.infoIcon}>
|
||||
<Info size="md" />
|
||||
</span>
|
||||
</TooltipSimple>
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={styles.entityGroupHeader} data-slot="entity-group-header">
|
||||
<span data-slot="icon">
|
||||
{icon || <Group size={14} data-hide-expanded="true" />}
|
||||
</span>{' '}
|
||||
{title}
|
||||
{renderInfoIcon()}
|
||||
<div className={styles.entityGroupHeader}>
|
||||
{icon || <Group size={14} data-hide-expanded="true" />} {title}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import React, {
|
||||
useState,
|
||||
} from 'react';
|
||||
import { useQuery } from 'react-query';
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { Color, Spacing } from '@signozhq/design-tokens';
|
||||
import { Button, Drawer, Tooltip } from 'antd';
|
||||
import { ToggleGroupSimple } from '@signozhq/ui/toggle-group';
|
||||
@@ -13,6 +14,7 @@ import { Divider } from '@signozhq/ui/divider';
|
||||
import { Typography } from '@signozhq/ui/typography';
|
||||
import logEvent from 'api/common/logEvent';
|
||||
import { combineInitialAndUserExpression } from 'components/QueryBuilderV2/QueryV2/QuerySearch/utils';
|
||||
import { convertFiltersToExpression } from 'components/QueryBuilderV2/utils';
|
||||
import { InfraMonitoringEvents } from 'constants/events';
|
||||
import { QueryParams } from 'constants/query';
|
||||
import {
|
||||
@@ -38,11 +40,17 @@ import {
|
||||
} from '@signozhq/icons';
|
||||
import { isCustomTimeRange, useGlobalTimeStore } from 'store/globalTime';
|
||||
import { NANO_SECOND_MULTIPLIER } from 'store/globalTime/utils';
|
||||
import { DataTypes } from 'types/api/queryBuilder/queryAutocompleteResponse';
|
||||
import {
|
||||
TagFilter,
|
||||
TagFilterItem,
|
||||
} from 'types/api/queryBuilder/queryBuilderData';
|
||||
import {
|
||||
LogsAggregatorOperator,
|
||||
TracesAggregatorOperator,
|
||||
} from 'types/common/queryBuilder';
|
||||
import { openInNewTab } from 'utils/navigation';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
|
||||
import { InfraMonitoringEntity, VIEW_TYPES } from '../constants';
|
||||
import EntityEvents from '../EntityDetailsUtils/EntityEvents';
|
||||
@@ -72,7 +80,7 @@ export interface K8sDetailsMetadataConfig<T> {
|
||||
}
|
||||
|
||||
export interface K8sDetailsFilters {
|
||||
filter: { expression: string };
|
||||
filters: TagFilter;
|
||||
start: number;
|
||||
end: number;
|
||||
}
|
||||
@@ -81,15 +89,15 @@ export interface K8sBaseDetailsProps<T> {
|
||||
category: InfraMonitoringEntity;
|
||||
eventCategory: string;
|
||||
// Data fetching configuration
|
||||
getSelectedItemExpression: (selectedItem: string) => string;
|
||||
getSelectedItemFilters: (selectedItem: string) => TagFilter;
|
||||
fetchEntityData: (
|
||||
filters: K8sDetailsFilters,
|
||||
signal?: AbortSignal,
|
||||
) => Promise<{ data: T | null; error?: string | null }>;
|
||||
// Entity configuration
|
||||
getEntityName: (entity: T) => string;
|
||||
getInitialLogTracesExpression: (entity: T) => string;
|
||||
getInitialEventsExpression: (entity: T) => string;
|
||||
getInitialLogTracesFilters: (entity: T) => TagFilterItem[];
|
||||
getInitialEventsFilters: (entity: T) => TagFilterItem[];
|
||||
metadataConfig: K8sDetailsMetadataConfig<T>[];
|
||||
entityWidgetInfo: {
|
||||
title: string;
|
||||
@@ -126,15 +134,33 @@ export interface K8sBaseDetailsProps<T> {
|
||||
}>;
|
||||
}
|
||||
|
||||
export function createFilterItem(
|
||||
key: string,
|
||||
value: string,
|
||||
dataType: DataTypes = DataTypes.String,
|
||||
): TagFilterItem {
|
||||
return {
|
||||
id: uuidv4(),
|
||||
key: {
|
||||
key,
|
||||
dataType,
|
||||
type: 'resource',
|
||||
id: `${key}--string--resource--false`,
|
||||
},
|
||||
op: '=',
|
||||
value,
|
||||
};
|
||||
}
|
||||
|
||||
// eslint-disable-next-line sonarjs/cognitive-complexity
|
||||
export default function K8sBaseDetails<T>({
|
||||
category,
|
||||
eventCategory,
|
||||
getSelectedItemExpression,
|
||||
getSelectedItemFilters,
|
||||
fetchEntityData,
|
||||
getEntityName,
|
||||
getInitialLogTracesExpression,
|
||||
getInitialEventsExpression,
|
||||
getInitialLogTracesFilters,
|
||||
getInitialEventsFilters,
|
||||
metadataConfig,
|
||||
entityWidgetInfo,
|
||||
getEntityQueryPayload,
|
||||
@@ -175,12 +201,17 @@ export default function K8sBaseDetails<T>({
|
||||
if (!selectedItem) {
|
||||
return { data: null };
|
||||
}
|
||||
const filters = getSelectedItemFilters(selectedItem);
|
||||
const { minTime, maxTime } = getMinMaxTime();
|
||||
const start = Math.floor(minTime / NANO_SECOND_MULTIPLIER);
|
||||
const end = Math.floor(maxTime / NANO_SECOND_MULTIPLIER);
|
||||
const expression = getSelectedItemExpression(selectedItem);
|
||||
|
||||
return fetchEntityData({ filter: { expression }, start, end }, signal);
|
||||
return fetchEntityData(
|
||||
{
|
||||
filters,
|
||||
start: Math.floor(minTime / NANO_SECOND_MULTIPLIER),
|
||||
end: Math.floor(maxTime / NANO_SECOND_MULTIPLIER),
|
||||
},
|
||||
signal,
|
||||
);
|
||||
},
|
||||
enabled: !!selectedItem,
|
||||
});
|
||||
@@ -192,15 +223,23 @@ export default function K8sBaseDetails<T>({
|
||||
if (!entity) {
|
||||
return '';
|
||||
}
|
||||
return getInitialLogTracesExpression(entity);
|
||||
}, [entity, getInitialLogTracesExpression]);
|
||||
const primaryFiltersOnly = {
|
||||
op: 'AND' as const,
|
||||
items: getInitialLogTracesFilters(entity),
|
||||
};
|
||||
return convertFiltersToExpression(primaryFiltersOnly).expression;
|
||||
}, [entity, getInitialLogTracesFilters]);
|
||||
|
||||
const eventsInitialExpression = useMemo(() => {
|
||||
if (!entity) {
|
||||
return '';
|
||||
}
|
||||
return getInitialEventsExpression(entity);
|
||||
}, [entity, getInitialEventsExpression]);
|
||||
const primaryFiltersOnly = {
|
||||
op: 'AND' as const,
|
||||
items: getInitialEventsFilters(entity),
|
||||
};
|
||||
return convertFiltersToExpression(primaryFiltersOnly).expression;
|
||||
}, [entity, getInitialEventsFilters]);
|
||||
|
||||
const handleClose = useCallback((): void => {
|
||||
setSelectedItem(null);
|
||||
|
||||
@@ -1,10 +1,3 @@
|
||||
.tableContainer {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
flex-direction: column;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
.emptyStateContainer {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
@@ -14,6 +7,7 @@
|
||||
}
|
||||
|
||||
.k8SListTable {
|
||||
padding-left: var(--spacing-2);
|
||||
--tanstack-table-header-cell-bg: var(--l2-background);
|
||||
--tanstack-table-header-cell-color: var(--l2-foreground);
|
||||
--tanstack-table-cell-bg: var(--l2-background);
|
||||
@@ -24,12 +18,9 @@
|
||||
--tanstack-table-resize-handle-hover-bg: var(--l2-border);
|
||||
--tanstack-table-row-height: 42px;
|
||||
|
||||
--tanstack-table-header-label-width: 100%;
|
||||
|
||||
--tanstack-cell-padding-top-override: 5px;
|
||||
--tanstack-cell-padding-bottom-override: 5px;
|
||||
--tanstack-cell-header-padding-left-override: 5px;
|
||||
--tanstack-cell-padding-left-override: 26px;
|
||||
--tanstack-cell-padding-left-override: 5px;
|
||||
--tanstack-cell-padding-right-override: 5px;
|
||||
|
||||
--tanstack-expansion-first-col-padding-left: 30px;
|
||||
|
||||
@@ -1,19 +1,15 @@
|
||||
import { useCallback, useEffect, useMemo } from 'react';
|
||||
import { useQuery, useQueryClient } from 'react-query';
|
||||
import { useQuery } from 'react-query';
|
||||
import { Typography } from '@signozhq/ui/typography';
|
||||
import logEvent from 'api/common/logEvent';
|
||||
import TanStackTable, {
|
||||
TableColumnDef,
|
||||
useCalculatedPageSize,
|
||||
useHiddenColumnIds,
|
||||
useTableParams,
|
||||
} from 'components/TanStackTableView';
|
||||
import { InfraMonitoringEvents } from 'constants/events';
|
||||
import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
|
||||
import { parseAsString, useQueryState } from 'nuqs';
|
||||
import { useGlobalTimeStore } from 'store/globalTime';
|
||||
import { NANO_SECOND_MULTIPLIER } from 'store/globalTime/utils';
|
||||
import { Querybuildertypesv5QueryWarnDataDTO } from 'api/generated/services/sigNoz.schemas';
|
||||
import { openInNewTab } from 'utils/navigation';
|
||||
|
||||
import {
|
||||
@@ -21,15 +17,15 @@ import {
|
||||
InfraMonitoringEntity,
|
||||
} from '../constants';
|
||||
import {
|
||||
useInfraMonitoringFiltersK8s,
|
||||
useInfraMonitoringGroupBy,
|
||||
useInfraMonitoringOrderBy,
|
||||
useInfraMonitoringStatusFilter,
|
||||
useInfraMonitoringPageListing,
|
||||
useInfraMonitoringPageSizeListing,
|
||||
} from '../hooks';
|
||||
import { useInfraMonitoringLineClamp } from '../components';
|
||||
import { K8sEmptyState } from './K8sEmptyState';
|
||||
import { K8sExpandedRow } from './K8sExpandedRow';
|
||||
import K8sHeader from './K8sHeader';
|
||||
import { K8sPaginationWarning } from './K8sPaginationWarning';
|
||||
import { K8sBaseFilters } from './types';
|
||||
import { getGroupedByMeta } from './utils';
|
||||
|
||||
@@ -42,30 +38,24 @@ export type K8sBaseListEmptyStateContext = {
|
||||
totalCount: number;
|
||||
hasFilters: boolean;
|
||||
isLoading: boolean;
|
||||
endTimeBeforeRetention?: boolean;
|
||||
rawData?: unknown;
|
||||
};
|
||||
|
||||
/** Base type constraint for K8s entity data */
|
||||
export type K8sEntityData = { meta?: Record<string, string> | null };
|
||||
export type K8sEntityData = { meta?: Record<string, string> };
|
||||
|
||||
export type K8sBaseListProps<T extends K8sEntityData> = {
|
||||
controlListPrefix?: React.ReactNode;
|
||||
leftFilters?: React.ReactNode;
|
||||
entity: InfraMonitoringEntity;
|
||||
tableColumns: TableColumnDef<T>[];
|
||||
fetchListData: (
|
||||
filters: K8sBaseFilters,
|
||||
signal?: AbortSignal,
|
||||
) => Promise<{
|
||||
type?: 'list' | 'grouped_list';
|
||||
records?: T[];
|
||||
data?: T[];
|
||||
data: T[];
|
||||
total: number;
|
||||
error?: string | null;
|
||||
rawData?: unknown;
|
||||
endTimeBeforeRetention?: boolean;
|
||||
warning?: Querybuildertypesv5QueryWarnDataDTO | null;
|
||||
}>;
|
||||
/** Function to get the unique key for a row. */
|
||||
getRowKey?: (record: T) => string;
|
||||
@@ -75,12 +65,10 @@ export type K8sBaseListProps<T extends K8sEntityData> = {
|
||||
renderEmptyState?: (
|
||||
context: K8sBaseListEmptyStateContext,
|
||||
) => React.ReactNode | null;
|
||||
extraQueryKeyParts?: string[];
|
||||
};
|
||||
|
||||
export function K8sBaseList<T extends K8sEntityData>({
|
||||
controlListPrefix,
|
||||
leftFilters,
|
||||
entity,
|
||||
tableColumns,
|
||||
fetchListData,
|
||||
@@ -88,14 +76,12 @@ export function K8sBaseList<T extends K8sEntityData>({
|
||||
getItemKey,
|
||||
eventCategory,
|
||||
renderEmptyState,
|
||||
extraQueryKeyParts = [],
|
||||
}: K8sBaseListProps<T>): JSX.Element {
|
||||
const { currentQuery } = useQueryBuilder();
|
||||
const expression = currentQuery.builder.queryData[0]?.filter?.expression || '';
|
||||
const lineClamp = useInfraMonitoringLineClamp();
|
||||
const [queryFilters] = useInfraMonitoringFiltersK8s();
|
||||
const [currentPage] = useInfraMonitoringPageListing();
|
||||
const [currentPageSize] = useInfraMonitoringPageSizeListing();
|
||||
const [groupBy] = useInfraMonitoringGroupBy();
|
||||
const [orderBy] = useInfraMonitoringOrderBy();
|
||||
const [statusFilter] = useInfraMonitoringStatusFilter();
|
||||
const [selectedItem, setSelectedItem] = useQueryState(
|
||||
'selectedItem',
|
||||
parseAsString,
|
||||
@@ -104,27 +90,6 @@ export function K8sBaseList<T extends K8sEntityData>({
|
||||
const columnStorageKey = `k8s-${entity}-columns`;
|
||||
const hiddenColumnIds = useHiddenColumnIds(columnStorageKey);
|
||||
|
||||
const { containerRef, calculatedPageSize } = useCalculatedPageSize({
|
||||
rowHeight: 42,
|
||||
});
|
||||
|
||||
const {
|
||||
page: currentPage,
|
||||
limit: currentPageSize,
|
||||
setLimit,
|
||||
} = useTableParams(
|
||||
{
|
||||
page: INFRA_MONITORING_K8S_PARAMS_KEYS.PAGE,
|
||||
limit: INFRA_MONITORING_K8S_PARAMS_KEYS.PAGE_SIZE,
|
||||
},
|
||||
{
|
||||
page: 1,
|
||||
limit: 10,
|
||||
storageKey: `k8s-${entity}`,
|
||||
calculatedPageSize,
|
||||
},
|
||||
);
|
||||
|
||||
const selectedTime = useGlobalTimeStore((s) => s.selectedTime);
|
||||
const refreshInterval = useGlobalTimeStore((s) => s.refreshInterval);
|
||||
const isRefreshEnabled = useGlobalTimeStore((s) => s.isRefreshEnabled);
|
||||
@@ -140,11 +105,9 @@ export function K8sBaseList<T extends K8sEntityData>({
|
||||
entity,
|
||||
String(currentPageSize),
|
||||
String(currentPage),
|
||||
expression || '',
|
||||
JSON.stringify(queryFilters),
|
||||
JSON.stringify(orderBy),
|
||||
JSON.stringify(groupBy),
|
||||
statusFilter,
|
||||
...extraQueryKeyParts,
|
||||
);
|
||||
}, [
|
||||
getAutoRefreshQueryKey,
|
||||
@@ -152,64 +115,35 @@ export function K8sBaseList<T extends K8sEntityData>({
|
||||
entity,
|
||||
currentPageSize,
|
||||
currentPage,
|
||||
expression,
|
||||
queryFilters,
|
||||
orderBy,
|
||||
groupBy,
|
||||
statusFilter,
|
||||
extraQueryKeyParts,
|
||||
]);
|
||||
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
const { data, isLoading, isFetching, isError } = useQuery({
|
||||
const { data, isLoading, isError } = useQuery({
|
||||
queryKey,
|
||||
queryFn: async ({ signal }) => {
|
||||
queryFn: ({ signal }) => {
|
||||
const { minTime, maxTime } = getMinMaxTime();
|
||||
const start = Math.floor(minTime / NANO_SECOND_MULTIPLIER);
|
||||
const end = Math.floor(maxTime / NANO_SECOND_MULTIPLIER);
|
||||
|
||||
const response = await fetchListData(
|
||||
return fetchListData(
|
||||
{
|
||||
filter: {
|
||||
expression: expression || '',
|
||||
filterByStatus:
|
||||
statusFilter === 'active' || statusFilter === 'inactive'
|
||||
? statusFilter
|
||||
: undefined,
|
||||
},
|
||||
groupBy:
|
||||
groupBy && groupBy.length > 0
|
||||
? groupBy.map((g) => ({ name: g }))
|
||||
: undefined,
|
||||
offset: (currentPage - 1) * currentPageSize,
|
||||
limit: currentPageSize,
|
||||
start,
|
||||
end,
|
||||
orderBy: orderBy
|
||||
? { key: { name: orderBy.columnName }, direction: orderBy.order }
|
||||
: undefined,
|
||||
offset: (currentPage - 1) * currentPageSize,
|
||||
filters: queryFilters || { items: [], op: 'AND' },
|
||||
start: Math.floor(minTime / NANO_SECOND_MULTIPLIER),
|
||||
end: Math.floor(maxTime / NANO_SECOND_MULTIPLIER),
|
||||
orderBy: orderBy || undefined,
|
||||
groupBy: groupBy?.length > 0 ? groupBy : undefined,
|
||||
},
|
||||
signal,
|
||||
);
|
||||
return {
|
||||
data: response.records || response.data || [],
|
||||
total: response.total,
|
||||
error: response.error,
|
||||
endTimeBeforeRetention: response.endTimeBeforeRetention,
|
||||
rawData: response.rawData ?? response,
|
||||
warning: response.warning ?? null,
|
||||
};
|
||||
},
|
||||
refetchInterval: isRefreshEnabled ? refreshInterval : false,
|
||||
});
|
||||
|
||||
const cancelQuery = useCallback((): void => {
|
||||
void queryClient.cancelQueries({ queryKey });
|
||||
}, [queryClient, queryKey]);
|
||||
|
||||
const pageData = data?.data ?? [];
|
||||
const totalCount = data?.total || 0;
|
||||
const hasFilters = !!expression?.trim();
|
||||
const hasFilters = (queryFilters?.items?.length ?? 0) > 0;
|
||||
|
||||
const getGroupKeyFn = useCallback(
|
||||
(item: T) => getGroupedByMeta(item, groupBy),
|
||||
@@ -217,7 +151,7 @@ export function K8sBaseList<T extends K8sEntityData>({
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
void logEvent(InfraMonitoringEvents.PageVisited, {
|
||||
logEvent(InfraMonitoringEvents.PageVisited, {
|
||||
entity: InfraMonitoringEvents.K8sEntity,
|
||||
page: InfraMonitoringEvents.ListPage,
|
||||
category: eventCategory,
|
||||
@@ -228,10 +162,10 @@ export function K8sBaseList<T extends K8sEntityData>({
|
||||
const handleRowClick = useCallback(
|
||||
(_record: T, itemKey: string): void => {
|
||||
if (groupBy.length === 0) {
|
||||
void setSelectedItem(itemKey);
|
||||
setSelectedItem(itemKey);
|
||||
}
|
||||
|
||||
void logEvent(InfraMonitoringEvents.ItemClicked, {
|
||||
logEvent(InfraMonitoringEvents.ItemClicked, {
|
||||
entity: InfraMonitoringEvents.K8sEntity,
|
||||
page: InfraMonitoringEvents.ListPage,
|
||||
category: eventCategory,
|
||||
@@ -251,7 +185,7 @@ export function K8sBaseList<T extends K8sEntityData>({
|
||||
url.searchParams.set('selectedItem', itemKey);
|
||||
openInNewTab(url.pathname + url.search);
|
||||
|
||||
void logEvent(InfraMonitoringEvents.ItemClicked, {
|
||||
logEvent(InfraMonitoringEvents.ItemClicked, {
|
||||
entity: InfraMonitoringEvents.K8sEntity,
|
||||
page: InfraMonitoringEvents.ListPage,
|
||||
category: eventCategory,
|
||||
@@ -280,19 +214,11 @@ export function K8sBaseList<T extends K8sEntityData>({
|
||||
entity={entity}
|
||||
tableColumns={expandedRowColumns}
|
||||
fetchListData={fetchListData}
|
||||
extraQueryKeyParts={extraQueryKeyParts}
|
||||
getRowKey={getRowKey}
|
||||
getItemKey={getItemKey}
|
||||
/>
|
||||
),
|
||||
[
|
||||
entity,
|
||||
fetchListData,
|
||||
getRowKey,
|
||||
getItemKey,
|
||||
expandedRowColumns,
|
||||
extraQueryKeyParts,
|
||||
],
|
||||
[entity, fetchListData, getRowKey, getItemKey, expandedRowColumns],
|
||||
);
|
||||
|
||||
const getRowCanExpand = useCallback(
|
||||
@@ -308,78 +234,64 @@ export function K8sBaseList<T extends K8sEntityData>({
|
||||
totalCount,
|
||||
hasFilters,
|
||||
isLoading: showTableLoadingState,
|
||||
endTimeBeforeRetention: data?.endTimeBeforeRetention,
|
||||
rawData: data?.rawData,
|
||||
}) || (
|
||||
<K8sEmptyState
|
||||
isError={isError}
|
||||
error={data?.error}
|
||||
isLoading={showTableLoadingState}
|
||||
endTimeBeforeRetention={data?.endTimeBeforeRetention}
|
||||
rawData={data?.rawData}
|
||||
/>
|
||||
);
|
||||
|
||||
const showEmptyState = !showTableLoadingState && pageData.length === 0;
|
||||
|
||||
const paginationWarningContent = data?.warning ? (
|
||||
<K8sPaginationWarning warning={data.warning} />
|
||||
) : null;
|
||||
|
||||
return (
|
||||
<>
|
||||
<K8sHeader
|
||||
controlListPrefix={controlListPrefix}
|
||||
leftFilters={leftFilters}
|
||||
entity={entity}
|
||||
showAutoRefresh={!selectedItem}
|
||||
columns={tableColumns}
|
||||
columnStorageKey={columnStorageKey}
|
||||
isFetching={isFetching}
|
||||
cancelQuery={cancelQuery}
|
||||
/>
|
||||
<div ref={containerRef} className={styles.tableContainer}>
|
||||
{isError && (
|
||||
<Typography>
|
||||
{data?.error?.toString() || 'Something went wrong'}
|
||||
</Typography>
|
||||
)}
|
||||
{isError && (
|
||||
<Typography>{data?.error?.toString() || 'Something went wrong'}</Typography>
|
||||
)}
|
||||
|
||||
{showEmptyState ? (
|
||||
<div className={styles.emptyStateContainer}>{emptyTableMessage}</div>
|
||||
) : (
|
||||
<TanStackTable<T>
|
||||
data={pageData}
|
||||
columns={tableColumns}
|
||||
columnStorageKey={columnStorageKey}
|
||||
isLoading={showTableLoadingState}
|
||||
getRowKey={getRowKey}
|
||||
getItemKey={getItemKey}
|
||||
groupBy={groupBy.map((g) => ({ key: g }))}
|
||||
getGroupKey={getGroupKeyFn}
|
||||
onRowClick={handleRowClick}
|
||||
onRowClickNewTab={handleRowClickNewTab}
|
||||
renderExpandedRow={isGroupedByAttribute ? renderExpandedRow : undefined}
|
||||
getRowCanExpand={isGroupedByAttribute ? getRowCanExpand : undefined}
|
||||
className={cx(styles.k8SListTable, expandedRowColumns)}
|
||||
enableQueryParams={{
|
||||
page: INFRA_MONITORING_K8S_PARAMS_KEYS.PAGE,
|
||||
limit: INFRA_MONITORING_K8S_PARAMS_KEYS.PAGE_SIZE,
|
||||
orderBy: INFRA_MONITORING_K8S_PARAMS_KEYS.ORDER_BY,
|
||||
expanded: INFRA_MONITORING_K8S_PARAMS_KEYS.EXPANDED,
|
||||
}}
|
||||
pagination={{
|
||||
total: totalCount,
|
||||
showTotalCount: true,
|
||||
totalCountLabel: entity.charAt(0).toUpperCase() + entity.slice(1),
|
||||
calculatedPageSize,
|
||||
onLimitChange: setLimit,
|
||||
}}
|
||||
plainTextCellLineClamp={lineClamp}
|
||||
prefixPaginationContent={paginationWarningContent}
|
||||
paginationClassname={styles.paginationContainer}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
{showEmptyState ? (
|
||||
<div className={styles.emptyStateContainer}>{emptyTableMessage}</div>
|
||||
) : (
|
||||
<TanStackTable<T>
|
||||
data={pageData}
|
||||
columns={tableColumns}
|
||||
columnStorageKey={columnStorageKey}
|
||||
isLoading={showTableLoadingState}
|
||||
getRowKey={getRowKey}
|
||||
getItemKey={getItemKey}
|
||||
groupBy={groupBy}
|
||||
getGroupKey={getGroupKeyFn}
|
||||
onRowClick={handleRowClick}
|
||||
onRowClickNewTab={handleRowClickNewTab}
|
||||
renderExpandedRow={isGroupedByAttribute ? renderExpandedRow : undefined}
|
||||
getRowCanExpand={isGroupedByAttribute ? getRowCanExpand : undefined}
|
||||
className={cx(styles.k8SListTable, expandedRowColumns)}
|
||||
enableQueryParams={{
|
||||
page: INFRA_MONITORING_K8S_PARAMS_KEYS.PAGE,
|
||||
limit: INFRA_MONITORING_K8S_PARAMS_KEYS.PAGE_SIZE,
|
||||
orderBy: INFRA_MONITORING_K8S_PARAMS_KEYS.ORDER_BY,
|
||||
expanded: INFRA_MONITORING_K8S_PARAMS_KEYS.EXPANDED,
|
||||
}}
|
||||
pagination={{
|
||||
total: totalCount,
|
||||
defaultLimit: 10,
|
||||
defaultPage: 1,
|
||||
showTotalCount: true,
|
||||
totalCountLabel: entity.charAt(0).toUpperCase() + entity.slice(1),
|
||||
}}
|
||||
paginationClassname={styles.paginationContainer}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -11,6 +11,12 @@ import type { K8sBaseListEmptyStateContext } from './K8sBaseList';
|
||||
|
||||
import styles from './K8sEmptyState.module.scss';
|
||||
|
||||
export interface K8sListResponseMetadata {
|
||||
sentAnyHostMetricsData?: boolean;
|
||||
isSendingK8SAgentMetrics?: boolean;
|
||||
endTimeBeforeRetention?: boolean;
|
||||
}
|
||||
|
||||
type K8sEmptyStateProps = Partial<K8sBaseListEmptyStateContext>;
|
||||
|
||||
const handleContactSupport = (isCloudUser: boolean): void => {
|
||||
@@ -25,7 +31,7 @@ export function K8sEmptyState({
|
||||
isError,
|
||||
error,
|
||||
isLoading,
|
||||
endTimeBeforeRetention,
|
||||
rawData,
|
||||
}: K8sEmptyStateProps): JSX.Element | null {
|
||||
const { isCloudUser } = useGetTenantLicense();
|
||||
|
||||
@@ -64,7 +70,47 @@ export function K8sEmptyState({
|
||||
);
|
||||
}
|
||||
|
||||
if (endTimeBeforeRetention) {
|
||||
const metadata = rawData as K8sListResponseMetadata | undefined;
|
||||
|
||||
if (metadata?.sentAnyHostMetricsData === false) {
|
||||
return (
|
||||
<div className={styles.container}>
|
||||
<div className={styles.content}>
|
||||
<img className={styles.eyesEmoji} src={eyesEmojiUrl} alt="eyes emoji" />
|
||||
<div className={styles.noDataMessage}>
|
||||
<h5 className={styles.title}>No host metrics data received yet</h5>
|
||||
<span className={styles.message}>
|
||||
Please refer to{' '}
|
||||
<a
|
||||
href="https://signoz.io/docs/infrastructure-monitoring/hostmetrics/"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
our documentation
|
||||
</a>{' '}
|
||||
to learn how to send host metrics.
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (metadata?.isSendingK8SAgentMetrics) {
|
||||
return (
|
||||
<div className={styles.container}>
|
||||
<div className={styles.content}>
|
||||
<img className={styles.eyesEmoji} src={eyesEmojiUrl} alt="eyes emoji" />
|
||||
<span className={styles.message}>
|
||||
To see K8s metrics, upgrade to the latest version of SigNoz k8s-infra
|
||||
chart. Please contact support if you need help.
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (metadata?.endTimeBeforeRetention) {
|
||||
return (
|
||||
<div className={styles.container}>
|
||||
<div className={styles.content}>
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
import { useCallback, useEffect, useMemo } from 'react';
|
||||
import { useQuery } from 'react-query';
|
||||
import { useLocation } from 'react-router-dom';
|
||||
import { Querybuildertypesv5QueryWarnDataDTO } from 'api/generated/services/sigNoz.schemas';
|
||||
import { Button } from '@signozhq/ui/button';
|
||||
import { Typography } from '@signozhq/ui/typography';
|
||||
import TanStackTable, {
|
||||
@@ -9,19 +7,16 @@ import TanStackTable, {
|
||||
TableColumnDef,
|
||||
TanStackTableStateProvider,
|
||||
} from 'components/TanStackTableView';
|
||||
import { QueryParams } from 'constants/query';
|
||||
import { CornerDownRight } from '@signozhq/icons';
|
||||
import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
|
||||
import { useSafeNavigate } from 'hooks/useSafeNavigate';
|
||||
import useUrlQuery from 'hooks/useUrlQuery';
|
||||
import { v4 as uuid } from 'uuid';
|
||||
import { useQueryState } from 'nuqs';
|
||||
import { useGlobalTimeStore } from 'store/globalTime';
|
||||
import { NANO_SECOND_MULTIPLIER } from 'store/globalTime/utils';
|
||||
import { IBuilderQuery } from 'types/api/queryBuilder/queryBuilderData';
|
||||
import { parseAsJsonNoValidate } from 'utils/nuqsParsers';
|
||||
|
||||
import { InfraMonitoringEntity } from '../constants';
|
||||
import {
|
||||
useInfraMonitoringFiltersK8s,
|
||||
useInfraMonitoringGroupBy,
|
||||
useInfraMonitoringOrderBy,
|
||||
useInfraMonitoringPageListing,
|
||||
@@ -30,7 +25,6 @@ import {
|
||||
import { K8sBaseFilters } from './types';
|
||||
|
||||
import styles from './K8sExpandedRow.module.scss';
|
||||
import { buildExpressionFromGroupMeta } from './utils';
|
||||
|
||||
const EXPANDED_ROW_LIMIT = 10;
|
||||
|
||||
@@ -41,21 +35,15 @@ export type K8sExpandedRowProps<T> = {
|
||||
groupMeta?: Record<string, string>;
|
||||
entity: InfraMonitoringEntity;
|
||||
tableColumns: TableColumnDef<T>[];
|
||||
/** API fetch function for expanded row data */
|
||||
fetchListData?: (
|
||||
fetchListData: (
|
||||
filters: K8sBaseFilters,
|
||||
signal?: AbortSignal,
|
||||
) => Promise<{
|
||||
type?: 'list' | 'grouped_list';
|
||||
records?: T[];
|
||||
data?: T[];
|
||||
data: T[];
|
||||
total: number;
|
||||
error?: string | null;
|
||||
rawData?: unknown;
|
||||
warning?: Querybuildertypesv5QueryWarnDataDTO | null;
|
||||
}>;
|
||||
/** Extra parts to include in the react-query cache key (e.g., status filter). */
|
||||
extraQueryKeyParts?: string[];
|
||||
/** Function to get the unique key for a row. */
|
||||
getRowKey?: (record: T) => string;
|
||||
/** Function to get the item key used for selection. Defaults to getRowKey if not provided. */
|
||||
@@ -68,20 +56,14 @@ export function K8sExpandedRow<T>({
|
||||
entity,
|
||||
tableColumns,
|
||||
fetchListData,
|
||||
extraQueryKeyParts = [],
|
||||
getRowKey,
|
||||
getItemKey,
|
||||
}: K8sExpandedRowProps<T>): JSX.Element {
|
||||
const [, setGroupBy] = useInfraMonitoringGroupBy();
|
||||
const [, setCurrentPage] = useInfraMonitoringPageListing();
|
||||
const { currentQuery } = useQueryBuilder();
|
||||
const parentExpression =
|
||||
currentQuery.builder.queryData[0]?.filter?.expression || '';
|
||||
const [queryFilters, setFilters] = useInfraMonitoringFiltersK8s();
|
||||
const [, setSelectedItem] = useInfraMonitoringSelectedItem();
|
||||
const [, setMainOrderBy] = useInfraMonitoringOrderBy();
|
||||
const { safeNavigate } = useSafeNavigate();
|
||||
const urlQuery = useUrlQuery();
|
||||
const location = useLocation();
|
||||
|
||||
const orderByParamKey = useMemo(
|
||||
() => `orderBy_${rowKey.replace(/[^a-zA-Z0-9]/g, '_')}`,
|
||||
@@ -103,10 +85,35 @@ export function K8sExpandedRow<T>({
|
||||
|
||||
const storageKey = `k8s-${entity}-columns-expanded`;
|
||||
|
||||
const expressionForRecord = useMemo(
|
||||
() => buildExpressionFromGroupMeta(parentExpression || '', groupMeta),
|
||||
[parentExpression, groupMeta],
|
||||
);
|
||||
const createFiltersForRecord = useCallback((): NonNullable<
|
||||
IBuilderQuery['filters']
|
||||
> => {
|
||||
const baseFilters: IBuilderQuery['filters'] = {
|
||||
items: [...(queryFilters?.items || [])],
|
||||
op: 'and',
|
||||
};
|
||||
|
||||
const metaKeys = groupMeta ?? {};
|
||||
|
||||
for (const key of Object.keys(metaKeys)) {
|
||||
const value = metaKeys[key];
|
||||
// Skip empty values to avoid creating invalid filters
|
||||
if (value === '' || value === undefined || value === null) {
|
||||
continue;
|
||||
}
|
||||
baseFilters.items.push({
|
||||
key: {
|
||||
key,
|
||||
type: 'resource',
|
||||
},
|
||||
op: '=',
|
||||
value,
|
||||
id: key,
|
||||
});
|
||||
}
|
||||
|
||||
return baseFilters;
|
||||
}, [queryFilters?.items, groupMeta]);
|
||||
|
||||
const selectedTime = useGlobalTimeStore((s) => s.selectedTime);
|
||||
const refreshInterval = useGlobalTimeStore((s) => s.refreshInterval);
|
||||
@@ -123,9 +130,8 @@ export function K8sExpandedRow<T>({
|
||||
'k8sExpandedRow',
|
||||
JSON.stringify(groupMeta),
|
||||
rowKey,
|
||||
expressionForRecord,
|
||||
JSON.stringify(queryFilters),
|
||||
JSON.stringify(orderBy),
|
||||
...extraQueryKeyParts,
|
||||
);
|
||||
}, [
|
||||
getAutoRefreshQueryKey,
|
||||
@@ -133,83 +139,49 @@ export function K8sExpandedRow<T>({
|
||||
entity,
|
||||
groupMeta,
|
||||
rowKey,
|
||||
expressionForRecord,
|
||||
queryFilters,
|
||||
orderBy,
|
||||
extraQueryKeyParts,
|
||||
]);
|
||||
|
||||
const { data, isLoading, isError } = useQuery({
|
||||
queryKey,
|
||||
queryFn: async ({ signal }) => {
|
||||
if (!fetchListData) {
|
||||
return { data: [] as T[], total: 0 };
|
||||
}
|
||||
const { minTime, maxTime } = getMinMaxTime();
|
||||
|
||||
const response = await fetchListData(
|
||||
return await fetchListData(
|
||||
{
|
||||
filter: { expression: expressionForRecord },
|
||||
limit: EXPANDED_ROW_LIMIT,
|
||||
offset: 0,
|
||||
filters: createFiltersForRecord(),
|
||||
start: Math.floor(minTime / NANO_SECOND_MULTIPLIER),
|
||||
end: Math.floor(maxTime / NANO_SECOND_MULTIPLIER),
|
||||
orderBy: orderBy
|
||||
? { key: { name: orderBy.columnName }, direction: orderBy.order }
|
||||
: undefined,
|
||||
orderBy: orderBy || undefined,
|
||||
groupBy: undefined,
|
||||
},
|
||||
signal,
|
||||
);
|
||||
|
||||
return {
|
||||
data: response.records || response.data || [],
|
||||
total: response.total,
|
||||
error: response.error,
|
||||
};
|
||||
},
|
||||
staleTime: 1000 * 60 * 30,
|
||||
refetchInterval: isRefreshEnabled ? refreshInterval : false,
|
||||
enabled: !!fetchListData,
|
||||
});
|
||||
|
||||
const expandedData = data?.data ?? [];
|
||||
|
||||
const handleRowClick = useCallback(
|
||||
(_row: T, itemKey: string): void => {
|
||||
void setSelectedItem(itemKey);
|
||||
setSelectedItem(itemKey);
|
||||
},
|
||||
[setSelectedItem],
|
||||
);
|
||||
|
||||
const handleViewAllClick = (): void => {
|
||||
void setGroupBy([]);
|
||||
void setCurrentPage(1);
|
||||
const filters = createFiltersForRecord();
|
||||
setGroupBy([]);
|
||||
setCurrentPage(1);
|
||||
setFilters(filters);
|
||||
if (orderBy) {
|
||||
void setMainOrderBy(orderBy);
|
||||
setMainOrderBy(orderBy);
|
||||
}
|
||||
|
||||
const updatedQuery = {
|
||||
...currentQuery,
|
||||
id: uuid(),
|
||||
builder: {
|
||||
...currentQuery.builder,
|
||||
queryData: [
|
||||
{
|
||||
...(currentQuery.builder.queryData[0] || {}),
|
||||
filter: { expression: expressionForRecord },
|
||||
filters: { items: [], op: 'AND' as const },
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
const newUrlQuery = new URLSearchParams(urlQuery.toString());
|
||||
newUrlQuery.set(
|
||||
QueryParams.compositeQuery,
|
||||
encodeURIComponent(JSON.stringify(updatedQuery)),
|
||||
);
|
||||
|
||||
safeNavigate(`${location.pathname}?${newUrlQuery.toString()}`);
|
||||
};
|
||||
|
||||
const total = data?.total ?? 0;
|
||||
|
||||
@@ -24,37 +24,11 @@
|
||||
.columnsList {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
[data-slot='icon'] {
|
||||
display: none;
|
||||
}
|
||||
|
||||
[data-slot='entity-group-header'] {
|
||||
padding-left: 0;
|
||||
}
|
||||
|
||||
[data-slot='column-header'] {
|
||||
display: flex;
|
||||
width: auto;
|
||||
|
||||
span {
|
||||
padding: 0;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
br {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.columnItem {
|
||||
justify-content: flex-start !important;
|
||||
width: 100%;
|
||||
|
||||
> span {
|
||||
width: auto !important;
|
||||
}
|
||||
}
|
||||
|
||||
.horizontalDivider {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { ReactNode, useMemo } from 'react';
|
||||
import { useMemo } from 'react';
|
||||
import { Button } from '@signozhq/ui/button';
|
||||
import { DrawerWrapper } from '@signozhq/ui/drawer';
|
||||
import {
|
||||
@@ -12,7 +12,7 @@ import styles from './K8sFiltersSidePanel.module.scss';
|
||||
|
||||
type ColumnPickerItem = {
|
||||
id: string;
|
||||
label: ReactNode;
|
||||
label: string;
|
||||
canBeHidden: boolean;
|
||||
visibilityBehavior:
|
||||
| 'hidden-on-expand'
|
||||
@@ -20,16 +20,15 @@ type ColumnPickerItem = {
|
||||
| 'always-visible';
|
||||
};
|
||||
|
||||
function renderHeader(header: string | (() => ReactNode)): ReactNode {
|
||||
return typeof header === 'function' ? header() : header;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts TableColumnDef to column picker item format
|
||||
*/
|
||||
function toColumnPickerItems<T>(
|
||||
columns: TableColumnDef<T>[],
|
||||
): ColumnPickerItem[] {
|
||||
return columns.map((col) => ({
|
||||
id: col.id,
|
||||
label: renderHeader(col.header),
|
||||
label: typeof col.header === 'string' ? col.header : col.id,
|
||||
canBeHidden: col.canBeHidden !== false && col.enableRemove !== false,
|
||||
visibilityBehavior: col.visibilityBehavior ?? 'always-visible',
|
||||
}));
|
||||
|
||||
@@ -2,7 +2,7 @@ import { getGroupByEl } from './utils';
|
||||
import { useInfraMonitoringGroupBy } from '../hooks';
|
||||
|
||||
interface K8sEntityWithMeta {
|
||||
meta?: Record<string, string> | null;
|
||||
meta?: Record<string, string>;
|
||||
}
|
||||
|
||||
function K8sGroupCell<T extends K8sEntityWithMeta>({
|
||||
|
||||
@@ -1,14 +1,13 @@
|
||||
.k8SListControls {
|
||||
padding: var(--spacing-4);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
gap: var(--spacing-4);
|
||||
width: 100%;
|
||||
|
||||
:global(.ant-select-selector) {
|
||||
border-radius: 2px;
|
||||
border: 1px solid var(--l2-border) !important;
|
||||
border: 1px solid var(--border) !important;
|
||||
background-color: var(--l2-background) !important;
|
||||
|
||||
input {
|
||||
@@ -21,81 +20,28 @@
|
||||
}
|
||||
}
|
||||
|
||||
.k8SListControlsRow {
|
||||
.k8SListControlsLeft {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
gap: var(--spacing-4);
|
||||
width: 100%;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.k8SQbSearchContainer {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.k8SFiltersGroupByRow {
|
||||
display: flex;
|
||||
flex: 1 1 auto;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
gap: var(--spacing-4);
|
||||
min-width: 0;
|
||||
|
||||
@media (max-width: 1600px) {
|
||||
flex: 1 1 100%;
|
||||
order: 9;
|
||||
.k8SQbSearchContainer {
|
||||
flex: 1;
|
||||
min-width: 240px;
|
||||
max-width: 60%;
|
||||
}
|
||||
}
|
||||
|
||||
.k8SAttributeSearchContainer {
|
||||
flex: 1 1 240px;
|
||||
max-width: 400px;
|
||||
flex: 1;
|
||||
min-width: 240px;
|
||||
max-width: 40%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.k8SRunButton {
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
|
||||
.k8SFiltersButton {
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
|
||||
.k8SDateTimeSelection {
|
||||
flex: 0 0 auto;
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
|
||||
--date-time-selector-border-color: var(--l2-border);
|
||||
|
||||
@media (max-width: 1200px) {
|
||||
flex: 1 1 100%;
|
||||
order: 10;
|
||||
}
|
||||
|
||||
:global(.timeSelection-input) {
|
||||
height: 32px;
|
||||
}
|
||||
|
||||
:global(.ant-input-affix-wrapper) {
|
||||
border-color: var(--l2-border);
|
||||
|
||||
&:hover {
|
||||
border-color: var(--l2-border) !important;
|
||||
}
|
||||
}
|
||||
|
||||
:global(.refresh-actions .ant-form-item-control-input) {
|
||||
&,
|
||||
:global(.ant-btn) {
|
||||
height: 30px;
|
||||
min-height: unset;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.groupByLabel {
|
||||
min-width: max-content;
|
||||
font-size: var(--periscope-font-size-base, 13px);
|
||||
@@ -104,7 +50,7 @@
|
||||
letter-spacing: -0.07px;
|
||||
|
||||
border-radius: 2px 0px 0px 2px;
|
||||
border: 1px solid var(--l2-border);
|
||||
border: 1px solid var(--border);
|
||||
border-right: none;
|
||||
border-top-right-radius: 0px;
|
||||
border-bottom-right-radius: 0px;
|
||||
@@ -118,8 +64,6 @@
|
||||
}
|
||||
|
||||
.groupBySelect {
|
||||
width: 100%;
|
||||
|
||||
:global(.ant-select-selector) {
|
||||
border-left: none;
|
||||
border-top-left-radius: 0px;
|
||||
|
||||
@@ -1,215 +1,167 @@
|
||||
import React, { useCallback, useMemo, useRef, useState } from 'react';
|
||||
import { useLocation } from 'react-router-dom';
|
||||
import React, { useCallback, useMemo, useState } from 'react';
|
||||
import { Button } from '@signozhq/ui/button';
|
||||
import { Select } from 'antd';
|
||||
import logEvent from 'api/common/logEvent';
|
||||
import { useGetFieldsKeys } from 'api/generated/services/fields';
|
||||
import {
|
||||
TelemetrytypesFieldContextDTO,
|
||||
TelemetrytypesSignalDTO,
|
||||
} from 'api/generated/services/sigNoz.schemas';
|
||||
import { TableColumnDef } from 'components/TanStackTableView';
|
||||
import QuerySearch from 'components/QueryBuilderV2/QueryV2/QuerySearch/QuerySearch';
|
||||
import { InfraMonitoringEvents } from 'constants/events';
|
||||
import { QueryParams } from 'constants/query';
|
||||
import RunQueryBtn from 'container/QueryBuilder/components/RunQueryBtn/RunQueryBtn';
|
||||
import { FeatureKeys } from 'constants/features';
|
||||
import { initialQueriesMap } from 'constants/queryBuilder';
|
||||
import QueryBuilderSearch from 'container/QueryBuilder/filters/QueryBuilderSearch';
|
||||
import DateTimeSelectionV2 from 'container/TopNav/DateTimeSelectionV2';
|
||||
import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
|
||||
import { IBuilderQuery } from 'types/api/queryBuilder/queryBuilderData';
|
||||
import { useSafeNavigate } from 'hooks/useSafeNavigate';
|
||||
import { useGetAggregateKeys } from 'hooks/queryBuilder/useGetAggregateKeys';
|
||||
import { useQueryOperations } from 'hooks/queryBuilder/useQueryBuilderOperations';
|
||||
import { SlidersHorizontal } from '@signozhq/icons';
|
||||
import { v4 as uuid } from 'uuid';
|
||||
import {
|
||||
useGlobalTimeQueryInvalidate,
|
||||
useGlobalTimeStore,
|
||||
} from 'store/globalTime';
|
||||
import { NANO_SECOND_MULTIPLIER } from 'store/globalTime/utils';
|
||||
import { useAppContext } from 'providers/App/App';
|
||||
import { IBuilderQuery } from 'types/api/queryBuilder/queryBuilderData';
|
||||
import { DataSource } from 'types/common/queryBuilder';
|
||||
|
||||
import {
|
||||
ENTITY_FILTER_PLACEHOLDERS,
|
||||
GetK8sEntityToAggregateAttribute,
|
||||
InfraMonitoringEntity,
|
||||
METRIC_NAMESPACE_BY_ENTITY,
|
||||
} from '../constants';
|
||||
import {
|
||||
useInfraMonitoringFiltersK8s,
|
||||
useInfraMonitoringGroupBy,
|
||||
useInfraMonitoringPageListing,
|
||||
} from '../hooks';
|
||||
import K8sFiltersSidePanel from './K8sFiltersSidePanel';
|
||||
|
||||
import styles from './K8sHeader.module.scss';
|
||||
import { TooltipSimple } from '@signozhq/ui/tooltip';
|
||||
|
||||
interface K8sHeaderProps<TData> {
|
||||
controlListPrefix?: React.ReactNode;
|
||||
leftFilters?: React.ReactNode;
|
||||
entity: InfraMonitoringEntity;
|
||||
showAutoRefresh: boolean;
|
||||
columns: TableColumnDef<TData>[];
|
||||
columnStorageKey: string;
|
||||
isFetching?: boolean;
|
||||
cancelQuery: () => void;
|
||||
}
|
||||
|
||||
function K8sHeader<TData>({
|
||||
controlListPrefix,
|
||||
leftFilters,
|
||||
entity,
|
||||
showAutoRefresh,
|
||||
columns,
|
||||
columnStorageKey,
|
||||
isFetching = false,
|
||||
cancelQuery,
|
||||
}: K8sHeaderProps<TData>): JSX.Element {
|
||||
const [isFiltersSidePanelOpen, setIsFiltersSidePanelOpen] = useState(false);
|
||||
// null = user never touched the search box; fall back to the current
|
||||
// query expression on Run so an untouched search doesn't wipe filters
|
||||
const stagedExpressionRef = useRef<string | null>(null);
|
||||
const [urlFilters, setUrlFilters] = useInfraMonitoringFiltersK8s();
|
||||
|
||||
const { currentQuery } = useQueryBuilder();
|
||||
const { safeNavigate } = useSafeNavigate();
|
||||
const location = useLocation();
|
||||
const invalidateQueries = useGlobalTimeQueryInvalidate();
|
||||
const currentQuery = initialQueriesMap[DataSource.METRICS];
|
||||
|
||||
const queryData = useMemo(
|
||||
(): IBuilderQuery => ({
|
||||
...currentQuery.builder.queryData[0],
|
||||
aggregateOperator: 'noop',
|
||||
}),
|
||||
[currentQuery],
|
||||
const updatedCurrentQuery = useMemo(() => {
|
||||
let { filters } = currentQuery.builder.queryData[0];
|
||||
if (urlFilters) {
|
||||
filters = urlFilters;
|
||||
}
|
||||
return {
|
||||
...currentQuery,
|
||||
builder: {
|
||||
...currentQuery.builder,
|
||||
queryData: [
|
||||
{
|
||||
...currentQuery.builder.queryData[0],
|
||||
aggregateOperator: 'noop',
|
||||
aggregateAttribute: {
|
||||
...currentQuery.builder.queryData[0].aggregateAttribute,
|
||||
},
|
||||
filters,
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
}, [currentQuery, urlFilters]);
|
||||
|
||||
const query = useMemo(
|
||||
() => updatedCurrentQuery?.builder?.queryData[0] || null,
|
||||
[updatedCurrentQuery],
|
||||
);
|
||||
|
||||
const { handleChangeQueryData } = useQueryOperations({
|
||||
index: 0,
|
||||
query: currentQuery.builder.queryData[0],
|
||||
entityVersion: '',
|
||||
});
|
||||
|
||||
const [, setCurrentPage] = useInfraMonitoringPageListing();
|
||||
const handleChangeTagFilters = useCallback(
|
||||
(value: IBuilderQuery['filters']) => {
|
||||
setUrlFilters(value || null);
|
||||
handleChangeQueryData('filters', value);
|
||||
setCurrentPage(1);
|
||||
|
||||
const handleRunQuery = useCallback(
|
||||
(newExpression?: string): void => {
|
||||
const currentExpression =
|
||||
currentQuery.builder.queryData[0]?.filter?.expression || '';
|
||||
const finalExpression = newExpression ?? currentExpression;
|
||||
void setCurrentPage(1);
|
||||
|
||||
const updatedQuery = {
|
||||
...currentQuery,
|
||||
id: uuid(),
|
||||
builder: {
|
||||
...currentQuery.builder,
|
||||
queryData: currentQuery.builder.queryData.map((query, idx) =>
|
||||
idx === 0
|
||||
? {
|
||||
...query,
|
||||
filter: { expression: finalExpression || '' },
|
||||
filters: { items: [], op: 'AND' as const },
|
||||
}
|
||||
: query,
|
||||
),
|
||||
},
|
||||
};
|
||||
|
||||
// Use window.location.search to get fresh URL params (avoids stale hook state)
|
||||
const newUrlQuery = new URLSearchParams(window.location.search);
|
||||
newUrlQuery.set(
|
||||
QueryParams.compositeQuery,
|
||||
encodeURIComponent(JSON.stringify(updatedQuery)),
|
||||
);
|
||||
|
||||
safeNavigate(`${location.pathname}?${newUrlQuery.toString()}`);
|
||||
void invalidateQueries();
|
||||
|
||||
if (finalExpression?.trim()) {
|
||||
void logEvent(InfraMonitoringEvents.FilterApplied, {
|
||||
if (value?.items && value?.items?.length > 0) {
|
||||
logEvent(InfraMonitoringEvents.FilterApplied, {
|
||||
entity: InfraMonitoringEvents.K8sEntity,
|
||||
page: InfraMonitoringEvents.ListPage,
|
||||
category: entity,
|
||||
category: InfraMonitoringEvents.Pod,
|
||||
});
|
||||
}
|
||||
},
|
||||
[
|
||||
currentQuery,
|
||||
safeNavigate,
|
||||
location.pathname,
|
||||
setCurrentPage,
|
||||
entity,
|
||||
invalidateQueries,
|
||||
],
|
||||
[handleChangeQueryData, setCurrentPage, setUrlFilters],
|
||||
);
|
||||
|
||||
const handleStageRunQuery = useCallback((): void => {
|
||||
handleRunQuery(stagedExpressionRef.current ?? undefined);
|
||||
}, [handleRunQuery]);
|
||||
|
||||
const handleExpressionChange = useCallback((value: string): void => {
|
||||
stagedExpressionRef.current = value;
|
||||
}, []);
|
||||
|
||||
const handleCancelQuery = useCallback((): void => {
|
||||
cancelQuery();
|
||||
}, [cancelQuery]);
|
||||
|
||||
const getMinMaxTime = useGlobalTimeStore((s) => s.getMinMaxTime);
|
||||
const { minTime, maxTime } = getMinMaxTime();
|
||||
const startUnixMilli = Math.floor(minTime / NANO_SECOND_MULTIPLIER);
|
||||
const endUnixMilli = Math.floor(maxTime / NANO_SECOND_MULTIPLIER);
|
||||
const { featureFlags } = useAppContext();
|
||||
const dotMetricsEnabled =
|
||||
featureFlags?.find((flag) => flag.name === FeatureKeys.DOT_METRICS_ENABLED)
|
||||
?.active || false;
|
||||
|
||||
const { data: groupByFiltersData, isLoading: isLoadingGroupByFilters } =
|
||||
useGetFieldsKeys(
|
||||
useGetAggregateKeys(
|
||||
{
|
||||
signal: TelemetrytypesSignalDTO.metrics,
|
||||
metricNamespace: METRIC_NAMESPACE_BY_ENTITY[entity],
|
||||
limit: 100,
|
||||
startUnixMilli,
|
||||
endUnixMilli,
|
||||
fieldContext: TelemetrytypesFieldContextDTO.resource,
|
||||
// the search text is intentionally not included
|
||||
// searchText: expression,
|
||||
dataSource: currentQuery.builder.queryData[0].dataSource,
|
||||
aggregateAttribute: GetK8sEntityToAggregateAttribute(
|
||||
entity,
|
||||
dotMetricsEnabled,
|
||||
),
|
||||
aggregateOperator: 'noop',
|
||||
searchText: '',
|
||||
tagType: '',
|
||||
},
|
||||
{
|
||||
query: {
|
||||
queryKey: ['getFieldsKeys', entity, startUnixMilli, endUnixMilli],
|
||||
},
|
||||
queryKey: [currentQuery.builder.queryData[0].dataSource, 'noop'],
|
||||
},
|
||||
true,
|
||||
entity,
|
||||
);
|
||||
|
||||
const flatFieldKeys = useMemo(() => {
|
||||
const keys = groupByFiltersData?.data?.keys;
|
||||
if (!keys) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const allKeys = Object.values(keys).flat();
|
||||
const seen = new Set<string>();
|
||||
|
||||
return allKeys.filter((field) => {
|
||||
if (seen.has(field.name)) {
|
||||
return false;
|
||||
}
|
||||
seen.add(field.name);
|
||||
return true;
|
||||
});
|
||||
}, [groupByFiltersData]);
|
||||
|
||||
const groupByOptions = useMemo(
|
||||
() =>
|
||||
flatFieldKeys.map((field) => ({
|
||||
value: field.name,
|
||||
label: field.name,
|
||||
})),
|
||||
[flatFieldKeys],
|
||||
groupByFiltersData?.payload?.attributeKeys?.map((filter) => ({
|
||||
value: filter.key,
|
||||
label: filter.key,
|
||||
})) || [],
|
||||
[groupByFiltersData],
|
||||
);
|
||||
|
||||
const [groupBy, setGroupBy] = useInfraMonitoringGroupBy();
|
||||
|
||||
const handleGroupByChange = useCallback(
|
||||
(value: string[]) => {
|
||||
void setCurrentPage(1);
|
||||
void setGroupBy(value);
|
||||
(value: IBuilderQuery['groupBy']) => {
|
||||
const newGroupBy = [];
|
||||
|
||||
void logEvent(InfraMonitoringEvents.GroupByChanged, {
|
||||
for (let index = 0; index < value.length; index++) {
|
||||
const element = value[index] as unknown as string;
|
||||
|
||||
const key = groupByFiltersData?.payload?.attributeKeys?.find(
|
||||
(k) => k.key === element,
|
||||
);
|
||||
|
||||
if (key) {
|
||||
newGroupBy.push(key);
|
||||
}
|
||||
}
|
||||
|
||||
// Reset pagination on switching to groupBy
|
||||
setCurrentPage(1);
|
||||
setGroupBy(newGroupBy);
|
||||
|
||||
logEvent(InfraMonitoringEvents.GroupByChanged, {
|
||||
entity: InfraMonitoringEvents.K8sEntity,
|
||||
page: InfraMonitoringEvents.ListPage,
|
||||
category: InfraMonitoringEvents.Pod,
|
||||
});
|
||||
},
|
||||
[setCurrentPage, setGroupBy],
|
||||
[groupByFiltersData, setCurrentPage, setGroupBy],
|
||||
);
|
||||
|
||||
const onClickOutside = useCallback(() => {
|
||||
@@ -218,69 +170,53 @@ function K8sHeader<TData>({
|
||||
|
||||
return (
|
||||
<div className={styles.k8SListControls}>
|
||||
<div className={styles.k8SListControlsRow}>
|
||||
<div className={styles.k8SListControlsLeft}>
|
||||
{controlListPrefix}
|
||||
|
||||
<div className={styles.k8SFiltersGroupByRow}>
|
||||
{leftFilters}
|
||||
|
||||
<div className={styles.k8SAttributeSearchContainer}>
|
||||
<div className={styles.groupByLabel}>Group by</div>
|
||||
<Select
|
||||
className={styles.groupBySelect}
|
||||
loading={isLoadingGroupByFilters}
|
||||
mode="multiple"
|
||||
value={groupBy}
|
||||
allowClear
|
||||
maxTagCount="responsive"
|
||||
placeholder="Search for attribute"
|
||||
options={groupByOptions}
|
||||
onChange={handleGroupByChange}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className={styles.k8SDateTimeSelection}>
|
||||
<DateTimeSelectionV2
|
||||
showAutoRefresh={showAutoRefresh}
|
||||
showRefreshText={false}
|
||||
hideShareModal
|
||||
<div className={styles.k8SQbSearchContainer}>
|
||||
<QueryBuilderSearch
|
||||
query={query as IBuilderQuery}
|
||||
onChange={handleChangeTagFilters}
|
||||
isInfraMonitoring
|
||||
disableNavigationShortcuts
|
||||
entity={entity}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<RunQueryBtn
|
||||
isLoadingQueries={isFetching}
|
||||
onStageRunQuery={handleStageRunQuery}
|
||||
handleCancelQuery={handleCancelQuery}
|
||||
className={styles.k8SRunButton}
|
||||
/>
|
||||
|
||||
<TooltipSimple title="Click to add more columns to this table">
|
||||
<Button
|
||||
type="button"
|
||||
variant="outlined"
|
||||
size="icon"
|
||||
color="secondary"
|
||||
data-testid="k8s-list-filters-button"
|
||||
onClick={(): void => setIsFiltersSidePanelOpen(true)}
|
||||
className={styles.k8SFiltersButton}
|
||||
>
|
||||
<SlidersHorizontal size={14} />
|
||||
</Button>
|
||||
</TooltipSimple>
|
||||
<div className={styles.k8SAttributeSearchContainer}>
|
||||
<div className={styles.groupByLabel}> Group by </div>
|
||||
<Select
|
||||
className={styles.groupBySelect}
|
||||
loading={isLoadingGroupByFilters}
|
||||
mode="multiple"
|
||||
value={groupBy}
|
||||
allowClear
|
||||
maxTagCount="responsive"
|
||||
placeholder="Search for attribute"
|
||||
style={{ width: '100%' }}
|
||||
options={groupByOptions}
|
||||
onChange={handleGroupByChange}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className={styles.k8SQbSearchContainer}>
|
||||
<QuerySearch
|
||||
queryData={queryData}
|
||||
dataSource={DataSource.METRICS}
|
||||
onChange={handleExpressionChange}
|
||||
onRun={handleRunQuery}
|
||||
signalSource=""
|
||||
showFilterSuggestionsWithoutMetric
|
||||
placeholder={ENTITY_FILTER_PLACEHOLDERS[entity]}
|
||||
metricNamespace={METRIC_NAMESPACE_BY_ENTITY[entity]}
|
||||
<div className={styles.k8SListControlsRight}>
|
||||
<DateTimeSelectionV2
|
||||
showAutoRefresh={showAutoRefresh}
|
||||
showRefreshText={false}
|
||||
hideShareModal
|
||||
/>
|
||||
|
||||
<Button
|
||||
type="button"
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
color="none"
|
||||
data-testid="k8s-list-filters-button"
|
||||
onClick={(): void => setIsFiltersSidePanelOpen(true)}
|
||||
>
|
||||
<SlidersHorizontal size={14} />
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<K8sFiltersSidePanel
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
.paginationWarning {
|
||||
border: 1px solid var(--callout-warning-border);
|
||||
background-color: var(--callout-warning-background);
|
||||
|
||||
padding: var(--spacing-2) var(--spacing-4);
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
|
||||
gap: var(--spacing-4);
|
||||
}
|
||||
|
||||
.warningIcon {
|
||||
cursor: pointer;
|
||||
}
|
||||
@@ -1,37 +0,0 @@
|
||||
import { Color } from '@signozhq/design-tokens';
|
||||
import WarningPopover from 'components/WarningPopover/WarningPopover';
|
||||
import { Querybuildertypesv5QueryWarnDataDTO } from 'api/generated/services/sigNoz.schemas';
|
||||
|
||||
import styles from './K8sPaginationWarning.module.scss';
|
||||
import TriangleAlert from '@signozhq/icons/TriangleAlert';
|
||||
|
||||
export type K8sPaginationWarningProps = {
|
||||
warning: Querybuildertypesv5QueryWarnDataDTO;
|
||||
};
|
||||
|
||||
export function K8sPaginationWarning({
|
||||
warning,
|
||||
}: K8sPaginationWarningProps): JSX.Element {
|
||||
return (
|
||||
<span data-testid="k8s-list-warning-popover">
|
||||
<WarningPopover
|
||||
warningData={{
|
||||
code: 'WARNING',
|
||||
message: warning.message ?? '',
|
||||
url: warning.url ?? '',
|
||||
warnings:
|
||||
warning.warnings?.map((w) => ({ message: w.message ?? '' })) ?? [],
|
||||
}}
|
||||
>
|
||||
<div className={styles.paginationWarning}>
|
||||
Your data contains some warnings
|
||||
<TriangleAlert
|
||||
size={16}
|
||||
className={styles.warningIcon}
|
||||
color={Color.BG_AMBER_500}
|
||||
/>
|
||||
</div>
|
||||
</WarningPopover>
|
||||
</span>
|
||||
);
|
||||
}
|
||||
@@ -22,15 +22,6 @@ import { openInNewTab } from 'utils/navigation';
|
||||
import { TableColumnDef } from 'components/TanStackTableView';
|
||||
|
||||
import { InfraMonitoringEntity } from '../../constants';
|
||||
|
||||
window.ResizeObserver =
|
||||
window.ResizeObserver ||
|
||||
jest.fn().mockImplementation(() => ({
|
||||
disconnect: jest.fn(),
|
||||
observe: jest.fn(),
|
||||
unobserve: jest.fn(),
|
||||
}));
|
||||
|
||||
import { K8sBaseList, K8sBaseListProps, K8sEntityData } from '../K8sBaseList';
|
||||
|
||||
jest.mock('utils/navigation', () => ({
|
||||
@@ -215,10 +206,8 @@ describe('K8sBaseList', () => {
|
||||
const itemId2 = Math.random().toString(36).slice(7);
|
||||
const onUrlUpdateMock = jest.fn<void, [UrlUpdateEvent]>();
|
||||
const fetchListDataMock = jest.fn<
|
||||
ReturnType<
|
||||
NonNullable<K8sBaseListProps<TestItemWithTitle>['fetchListData']>
|
||||
>,
|
||||
Parameters<NonNullable<K8sBaseListProps<TestItemWithTitle>['fetchListData']>>
|
||||
ReturnType<K8sBaseListProps<TestItemWithTitle>['fetchListData']>,
|
||||
Parameters<K8sBaseListProps<TestItemWithTitle>['fetchListData']>
|
||||
>();
|
||||
|
||||
beforeEach(() => {
|
||||
@@ -270,10 +259,7 @@ describe('K8sBaseList', () => {
|
||||
const [filters] = fetchListDataMock.mock.calls[0];
|
||||
expect(filters.limit).toBe(10);
|
||||
expect(filters.offset).toBe(0);
|
||||
expect(filters.filter).toStrictEqual({
|
||||
expression: '',
|
||||
filterByStatus: undefined,
|
||||
});
|
||||
expect(filters.filters).toStrictEqual({ items: [], op: 'AND' });
|
||||
expect(filters.groupBy).toBeUndefined();
|
||||
expect(filters.orderBy).toBeUndefined();
|
||||
});
|
||||
@@ -433,11 +419,11 @@ describe('K8sBaseList', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('with URL params (orderBy, groupBy old format, pagination)', () => {
|
||||
describe('with URL params (orderBy, groupBy, pagination)', () => {
|
||||
const onUrlUpdateMock = jest.fn<void, [UrlUpdateEvent]>();
|
||||
const fetchListDataMock = jest.fn<
|
||||
ReturnType<NonNullable<K8sBaseListProps<TestItem>['fetchListData']>>,
|
||||
Parameters<NonNullable<K8sBaseListProps<TestItem>['fetchListData']>>
|
||||
ReturnType<K8sBaseListProps<TestItem>['fetchListData']>,
|
||||
Parameters<K8sBaseListProps<TestItem>['fetchListData']>
|
||||
>();
|
||||
const groupByValue = [
|
||||
{ key: 'k8s.namespace.name', dataType: 'string', type: 'resource' },
|
||||
@@ -476,11 +462,8 @@ describe('K8sBaseList', () => {
|
||||
});
|
||||
|
||||
const [filters] = fetchListDataMock.mock.calls[0];
|
||||
expect(filters.orderBy).toStrictEqual({
|
||||
key: { name: 'cpu' },
|
||||
direction: 'desc',
|
||||
});
|
||||
expect(filters.groupBy).toStrictEqual([{ name: 'k8s.namespace.name' }]);
|
||||
expect(filters.orderBy).toStrictEqual({ columnName: 'cpu', order: 'desc' });
|
||||
expect(filters.groupBy).toStrictEqual(groupByValue);
|
||||
expect(filters.offset).toBe(20); // (3 - 1) * 10 = 20
|
||||
expect(filters.limit).toBe(10);
|
||||
});
|
||||
@@ -504,84 +487,14 @@ describe('K8sBaseList', () => {
|
||||
(c) => c[0].groupBy && c[0].groupBy.length > 0,
|
||||
);
|
||||
expect(callWithGroupBy).toBeDefined();
|
||||
expect(callWithGroupBy?.[0].groupBy).toStrictEqual([
|
||||
{ name: 'k8s.namespace.name' },
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('with URL params (groupBy new format - string array)', () => {
|
||||
const onUrlUpdateMock = jest.fn<void, [UrlUpdateEvent]>();
|
||||
const fetchListDataMock = jest.fn<
|
||||
ReturnType<NonNullable<K8sBaseListProps<TestItem>['fetchListData']>>,
|
||||
Parameters<NonNullable<K8sBaseListProps<TestItem>['fetchListData']>>
|
||||
>();
|
||||
const groupByValue = ['k8s.namespace.name'];
|
||||
|
||||
beforeEach(() => {
|
||||
onUrlUpdateMock.mockClear();
|
||||
fetchListDataMock.mockClear();
|
||||
fetchListDataMock.mockResolvedValue({
|
||||
data: [
|
||||
{ id: 'namespace-default', meta: { 'k8s.namespace.name': 'default' } },
|
||||
],
|
||||
total: 50,
|
||||
error: null,
|
||||
});
|
||||
|
||||
renderComponent<TestItem>({
|
||||
onUrlUpdate: onUrlUpdateMock,
|
||||
entity: InfraMonitoringEntity.PODS,
|
||||
eventCategory: InfraMonitoringEvents.Pod,
|
||||
fetchListData: fetchListDataMock,
|
||||
queryParams: {
|
||||
orderBy: JSON.stringify({ columnName: 'cpu', order: 'desc' }),
|
||||
groupBy: JSON.stringify(groupByValue),
|
||||
page: '3',
|
||||
},
|
||||
tableColumns: createTestColumns(),
|
||||
getRowKey: (row): string => row.id,
|
||||
getItemKey: (row): string => row.id,
|
||||
});
|
||||
});
|
||||
|
||||
it('should call fetchListData with groupBy from new format URL', async () => {
|
||||
await waitFor(() => {
|
||||
expect(fetchListDataMock).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
const [filters] = fetchListDataMock.mock.calls[0];
|
||||
expect(filters.groupBy).toStrictEqual([{ name: 'k8s.namespace.name' }]);
|
||||
});
|
||||
|
||||
it('should render expand icons when groupBy is set with new format', async () => {
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('namespace-default')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
const expandButtons = screen.getAllByRole('button');
|
||||
expect(expandButtons.length).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
it('should render data with new groupBy format', async () => {
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('namespace-default')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
const callWithGroupBy = fetchListDataMock.mock.calls.find(
|
||||
(c) => c[0].groupBy && c[0].groupBy.length > 0,
|
||||
);
|
||||
expect(callWithGroupBy).toBeDefined();
|
||||
expect(callWithGroupBy?.[0].groupBy).toStrictEqual([
|
||||
{ name: 'k8s.namespace.name' },
|
||||
]);
|
||||
expect(callWithGroupBy?.[0].groupBy).toStrictEqual(groupByValue);
|
||||
});
|
||||
});
|
||||
|
||||
describe('with empty data', () => {
|
||||
const fetchListDataMock = jest.fn<
|
||||
ReturnType<NonNullable<K8sBaseListProps<TestItem>['fetchListData']>>,
|
||||
Parameters<NonNullable<K8sBaseListProps<TestItem>['fetchListData']>>
|
||||
ReturnType<K8sBaseListProps<TestItem>['fetchListData']>,
|
||||
Parameters<K8sBaseListProps<TestItem>['fetchListData']>
|
||||
>();
|
||||
|
||||
beforeEach(() => {
|
||||
@@ -590,6 +503,10 @@ describe('K8sBaseList', () => {
|
||||
data: [],
|
||||
total: 0,
|
||||
error: null,
|
||||
rawData: {
|
||||
sentAnyHostMetricsData: true,
|
||||
isSendingK8SAgentMetrics: false,
|
||||
},
|
||||
});
|
||||
|
||||
renderComponent<TestItem>({
|
||||
@@ -617,8 +534,8 @@ describe('K8sBaseList', () => {
|
||||
|
||||
describe('with error response', () => {
|
||||
const fetchListDataMock = jest.fn<
|
||||
ReturnType<NonNullable<K8sBaseListProps<TestItem>['fetchListData']>>,
|
||||
Parameters<NonNullable<K8sBaseListProps<TestItem>['fetchListData']>>
|
||||
ReturnType<K8sBaseListProps<TestItem>['fetchListData']>,
|
||||
Parameters<K8sBaseListProps<TestItem>['fetchListData']>
|
||||
>();
|
||||
|
||||
beforeEach(() => {
|
||||
@@ -652,10 +569,10 @@ describe('K8sBaseList', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('with end time before retention (endTimeBeforeRetention=true)', () => {
|
||||
describe('with no metrics data (sentAnyHostMetricsData=false)', () => {
|
||||
const fetchListDataMock = jest.fn<
|
||||
ReturnType<NonNullable<K8sBaseListProps<TestItem>['fetchListData']>>,
|
||||
Parameters<NonNullable<K8sBaseListProps<TestItem>['fetchListData']>>
|
||||
ReturnType<K8sBaseListProps<TestItem>['fetchListData']>,
|
||||
Parameters<K8sBaseListProps<TestItem>['fetchListData']>
|
||||
>();
|
||||
|
||||
beforeEach(() => {
|
||||
@@ -664,7 +581,96 @@ describe('K8sBaseList', () => {
|
||||
data: [],
|
||||
total: 0,
|
||||
error: null,
|
||||
endTimeBeforeRetention: true,
|
||||
rawData: {
|
||||
sentAnyHostMetricsData: false,
|
||||
isSendingK8SAgentMetrics: false,
|
||||
},
|
||||
});
|
||||
|
||||
renderComponent<TestItem>({
|
||||
entity: InfraMonitoringEntity.PODS,
|
||||
eventCategory: InfraMonitoringEvents.Pod,
|
||||
fetchListData: fetchListDataMock,
|
||||
tableColumns: createTestColumns(),
|
||||
getRowKey: (row): string => row.id,
|
||||
getItemKey: (row): string => row.id,
|
||||
});
|
||||
});
|
||||
|
||||
it('should display no metrics data message', async () => {
|
||||
await waitFor(() => {
|
||||
expect(
|
||||
screen.getByText(/No host metrics data received yet/i),
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
it('should display link to documentation', async () => {
|
||||
await waitFor(() => {
|
||||
const link = screen.getByRole('link', { name: /our documentation/i });
|
||||
expect(link).toBeInTheDocument();
|
||||
expect(link).toHaveAttribute(
|
||||
'href',
|
||||
'https://signoz.io/docs/infrastructure-monitoring/hostmetrics/',
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('with incorrect K8s agent metrics (isSendingK8SAgentMetrics=true)', () => {
|
||||
const fetchListDataMock = jest.fn<
|
||||
ReturnType<K8sBaseListProps<TestItem>['fetchListData']>,
|
||||
Parameters<K8sBaseListProps<TestItem>['fetchListData']>
|
||||
>();
|
||||
|
||||
beforeEach(() => {
|
||||
fetchListDataMock.mockClear();
|
||||
fetchListDataMock.mockResolvedValue({
|
||||
data: [],
|
||||
total: 0,
|
||||
error: null,
|
||||
rawData: {
|
||||
sentAnyHostMetricsData: true,
|
||||
isSendingK8SAgentMetrics: true,
|
||||
},
|
||||
});
|
||||
|
||||
renderComponent<TestItem>({
|
||||
entity: InfraMonitoringEntity.PODS,
|
||||
eventCategory: InfraMonitoringEvents.Pod,
|
||||
fetchListData: fetchListDataMock,
|
||||
tableColumns: createTestColumns(),
|
||||
getRowKey: (row): string => row.id,
|
||||
getItemKey: (row): string => row.id,
|
||||
});
|
||||
});
|
||||
|
||||
it('should display upgrade message', async () => {
|
||||
await waitFor(() => {
|
||||
expect(
|
||||
screen.getByText(/upgrade to the latest version of SigNoz k8s-infra/i),
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('with end time before retention (endTimeBeforeRetention=true)', () => {
|
||||
const fetchListDataMock = jest.fn<
|
||||
ReturnType<K8sBaseListProps<TestItem>['fetchListData']>,
|
||||
Parameters<K8sBaseListProps<TestItem>['fetchListData']>
|
||||
>();
|
||||
|
||||
beforeEach(() => {
|
||||
fetchListDataMock.mockClear();
|
||||
fetchListDataMock.mockResolvedValue({
|
||||
data: [],
|
||||
total: 0,
|
||||
error: null,
|
||||
rawData: {
|
||||
sentAnyHostMetricsData: true,
|
||||
isSendingK8SAgentMetrics: false,
|
||||
endTimeBeforeRetention: true,
|
||||
},
|
||||
});
|
||||
|
||||
renderComponent<TestItem>({
|
||||
@@ -691,8 +697,8 @@ describe('K8sBaseList', () => {
|
||||
|
||||
describe('column visibility based on TanStack columns', () => {
|
||||
const fetchListDataMock = jest.fn<
|
||||
ReturnType<NonNullable<K8sBaseListProps<TestItemWithName>['fetchListData']>>,
|
||||
Parameters<NonNullable<K8sBaseListProps<TestItemWithName>['fetchListData']>>
|
||||
ReturnType<K8sBaseListProps<TestItemWithName>['fetchListData']>,
|
||||
Parameters<K8sBaseListProps<TestItemWithName>['fetchListData']>
|
||||
>();
|
||||
|
||||
beforeEach(() => {
|
||||
@@ -730,10 +736,8 @@ describe('K8sBaseList', () => {
|
||||
|
||||
describe('column behavior with groupBy (expanded/collapsed)', () => {
|
||||
const fetchListDataMock = jest.fn<
|
||||
ReturnType<
|
||||
NonNullable<K8sBaseListProps<TestItemWithGroup>['fetchListData']>
|
||||
>,
|
||||
Parameters<NonNullable<K8sBaseListProps<TestItemWithGroup>['fetchListData']>>
|
||||
ReturnType<K8sBaseListProps<TestItemWithGroup>['fetchListData']>,
|
||||
Parameters<K8sBaseListProps<TestItemWithGroup>['fetchListData']>
|
||||
>();
|
||||
|
||||
beforeEach(() => {
|
||||
@@ -803,8 +807,8 @@ describe('K8sBaseList', () => {
|
||||
|
||||
describe('column visibility in expanded row (nested table)', () => {
|
||||
const fetchListDataMock = jest.fn<
|
||||
ReturnType<NonNullable<K8sBaseListProps<TestItem>['fetchListData']>>,
|
||||
Parameters<NonNullable<K8sBaseListProps<TestItem>['fetchListData']>>
|
||||
ReturnType<K8sBaseListProps<TestItem>['fetchListData']>,
|
||||
Parameters<K8sBaseListProps<TestItem>['fetchListData']>
|
||||
>();
|
||||
const groupByValue = [
|
||||
{ key: 'k8s.namespace.name', dataType: 'string', type: 'resource' },
|
||||
@@ -848,8 +852,8 @@ describe('K8sBaseList', () => {
|
||||
|
||||
describe('TanStack table column rendering', () => {
|
||||
const fetchListDataMock = jest.fn<
|
||||
ReturnType<NonNullable<K8sBaseListProps<TestItemWithName>['fetchListData']>>,
|
||||
Parameters<NonNullable<K8sBaseListProps<TestItemWithName>['fetchListData']>>
|
||||
ReturnType<K8sBaseListProps<TestItemWithName>['fetchListData']>,
|
||||
Parameters<K8sBaseListProps<TestItemWithName>['fetchListData']>
|
||||
>();
|
||||
|
||||
beforeEach(() => {
|
||||
@@ -905,40 +909,4 @@ describe('K8sBaseList', () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('with warnings from API', () => {
|
||||
const fetchListDataMock = jest.fn<
|
||||
ReturnType<NonNullable<K8sBaseListProps<TestItem>['fetchListData']>>,
|
||||
Parameters<NonNullable<K8sBaseListProps<TestItem>['fetchListData']>>
|
||||
>();
|
||||
|
||||
beforeEach(() => {
|
||||
fetchListDataMock.mockClear();
|
||||
fetchListDataMock.mockResolvedValue({
|
||||
data: [{ id: 'item-1' }],
|
||||
total: 1,
|
||||
error: null,
|
||||
warning: {
|
||||
message: 'Some data may be incomplete',
|
||||
url: 'https://docs.example.com/partial-data',
|
||||
warnings: [{ message: 'Node xyz did not report metrics' }],
|
||||
},
|
||||
});
|
||||
|
||||
renderComponent<TestItem>({
|
||||
entity: InfraMonitoringEntity.PODS,
|
||||
eventCategory: InfraMonitoringEvents.Pod,
|
||||
fetchListData: fetchListDataMock,
|
||||
tableColumns: createTestColumns(),
|
||||
getRowKey: (row): string => row.id,
|
||||
getItemKey: (row): string => row.id,
|
||||
});
|
||||
});
|
||||
|
||||
it('should render warning popover in pagination area', async () => {
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId('k8s-list-warning-popover')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,25 +1,16 @@
|
||||
import { BaseAutocompleteData } from 'types/api/queryBuilder/queryAutocompleteResponse';
|
||||
import { TagFilter } from 'types/api/queryBuilder/queryBuilderData';
|
||||
|
||||
import { OrderBySchemaType } from '../schemas';
|
||||
|
||||
export type K8sBaseFilters = {
|
||||
filter: {
|
||||
expression: string;
|
||||
filterByStatus?: 'active' | 'inactive' | '';
|
||||
};
|
||||
groupBy?: Array<{ name: string }>;
|
||||
filters: TagFilter;
|
||||
groupBy?: BaseAutocompleteData[];
|
||||
offset?: number;
|
||||
limit?: number;
|
||||
start: number;
|
||||
end: number;
|
||||
orderBy?: {
|
||||
key: { name: string };
|
||||
direction: 'asc' | 'desc';
|
||||
};
|
||||
};
|
||||
|
||||
export type K8sListResponse<T> = {
|
||||
type: 'list' | 'grouped_list';
|
||||
records: T[];
|
||||
total: number;
|
||||
endTimeBeforeRetention?: boolean;
|
||||
error?: string | null;
|
||||
orderBy?: OrderBySchemaType;
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { Badge } from '@signozhq/ui/badge';
|
||||
import { BaseAutocompleteData } from 'types/api/queryBuilder/queryAutocompleteResponse';
|
||||
import { IBuilderQuery } from 'types/api/queryBuilder/queryBuilderData';
|
||||
|
||||
import styles from './utils.module.scss';
|
||||
import { TagFilterItem } from 'types/api/queryBuilder/queryBuilderData';
|
||||
import { convertFiltersToExpression } from 'components/QueryBuilderV2/utils';
|
||||
|
||||
const dotToUnder: Record<string, string> = {
|
||||
'os.type': 'os_type',
|
||||
@@ -22,13 +22,15 @@ const dotToUnder: Record<string, string> = {
|
||||
'k8s.persistentvolumeclaim.name': 'k8s_persistentvolumeclaim_name',
|
||||
};
|
||||
|
||||
export function getGroupedByMeta<
|
||||
T extends { meta?: Record<string, string> | null },
|
||||
>(itemData: T, groupBy: string[]): Record<string, string> {
|
||||
export function getGroupedByMeta<T extends { meta?: Record<string, string> }>(
|
||||
itemData: T,
|
||||
groupBy: BaseAutocompleteData[],
|
||||
): Record<string, string> {
|
||||
const result: Record<string, string> = {};
|
||||
const meta = itemData.meta ?? {};
|
||||
|
||||
groupBy.forEach((rawKey) => {
|
||||
groupBy.forEach((group) => {
|
||||
const rawKey = group.key as string;
|
||||
const metaKey = (dotToUnder[rawKey] ?? rawKey) as keyof typeof meta;
|
||||
result[rawKey] = (meta[metaKey] || meta[rawKey]) ?? '';
|
||||
});
|
||||
@@ -36,13 +38,45 @@ export function getGroupedByMeta<
|
||||
return result;
|
||||
}
|
||||
|
||||
export function getGroupByEl<
|
||||
T extends { meta?: Record<string, string> | null },
|
||||
>(itemData: T, groupBy: string[]): React.ReactNode {
|
||||
export function getRowKey<T extends { meta?: Record<string, string> }>(
|
||||
itemData: T,
|
||||
getItemIdentifier: () => string,
|
||||
groupBy: BaseAutocompleteData[],
|
||||
): string {
|
||||
const nodeIdentifier = getItemIdentifier();
|
||||
const meta = itemData.meta ?? {};
|
||||
|
||||
if (groupBy.length === 0) {
|
||||
return nodeIdentifier || JSON.stringify(meta);
|
||||
}
|
||||
|
||||
const groupedMeta = getGroupedByMeta(itemData, groupBy);
|
||||
const groupKey = Object.values(groupedMeta).join('-');
|
||||
|
||||
if (groupKey && nodeIdentifier) {
|
||||
return `${groupKey}-${nodeIdentifier}`;
|
||||
}
|
||||
if (groupKey) {
|
||||
return groupKey;
|
||||
}
|
||||
if (nodeIdentifier) {
|
||||
return nodeIdentifier;
|
||||
}
|
||||
|
||||
return JSON.stringify(meta);
|
||||
}
|
||||
|
||||
export function getGroupByEl<T extends { meta?: Record<string, string> }>(
|
||||
itemData: T,
|
||||
groupBy: IBuilderQuery['groupBy'],
|
||||
): React.ReactNode {
|
||||
const groupByValues: string[] = [];
|
||||
const meta = itemData.meta ?? {};
|
||||
|
||||
groupBy.forEach((rawKey) => {
|
||||
groupBy.forEach((group) => {
|
||||
const rawKey = group.key as string;
|
||||
|
||||
// Choose mapped key if present, otherwise use rawKey
|
||||
const metaKey = (dotToUnder[rawKey] ?? rawKey) as keyof typeof meta;
|
||||
const value = meta[metaKey] || meta[rawKey] || '<no-value>';
|
||||
|
||||
@@ -64,28 +98,3 @@ export function getGroupByEl<
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export function buildExpressionFromGroupMeta(
|
||||
parentExpression: string,
|
||||
groupMeta: Record<string, string> | undefined,
|
||||
): string {
|
||||
const items: TagFilterItem[] = Object.entries(groupMeta ?? {})
|
||||
.filter(([, value]) => value !== '' && value !== undefined && value !== null)
|
||||
.map(([key, value]) => ({
|
||||
key: { key, type: 'resource' },
|
||||
op: '=',
|
||||
value,
|
||||
id: key,
|
||||
}));
|
||||
|
||||
const metaExpression = convertFiltersToExpression({
|
||||
items,
|
||||
op: 'AND',
|
||||
}).expression;
|
||||
|
||||
const parent = parentExpression?.trim();
|
||||
if (parent && metaExpression) {
|
||||
return `${parent} AND ${metaExpression}`;
|
||||
}
|
||||
return parent || metaExpression;
|
||||
}
|
||||
|
||||
@@ -1,24 +1,21 @@
|
||||
import { useCallback } from 'react';
|
||||
import { listClusters } from 'api/generated/services/inframonitoring';
|
||||
import {
|
||||
InframonitoringtypesClusterRecordDTO,
|
||||
InframonitoringtypesResponseTypeDTO,
|
||||
Querybuildertypesv5OrderDirectionDTO,
|
||||
} from 'api/generated/services/sigNoz.schemas';
|
||||
import React, { useCallback } from 'react';
|
||||
import { InfraMonitoringEvents } from 'constants/events';
|
||||
import { FeatureKeys } from 'constants/features';
|
||||
import { useAppContext } from 'providers/App/App';
|
||||
|
||||
import K8sBaseDetails, { K8sDetailsFilters } from '../Base/K8sBaseDetails';
|
||||
import { K8sBaseList } from '../Base/K8sBaseList';
|
||||
import { K8sBaseFilters } from '../Base/types';
|
||||
import { InfraMonitoringEntity } from '../constants';
|
||||
import { getK8sClustersList, K8sClusterData } from './api';
|
||||
import {
|
||||
clusterWidgetInfo,
|
||||
getClusterMetricsQueryPayload,
|
||||
k8sClusterDetailsMetadataConfig,
|
||||
k8sClusterGetEntityName,
|
||||
k8sClusterGetSelectedItemExpression,
|
||||
k8sClusterInitialEventsExpression,
|
||||
k8sClusterInitialLogTracesExpression,
|
||||
k8sClusterGetSelectedItemFilters,
|
||||
k8sClusterInitialEventsFilter,
|
||||
k8sClusterInitialLogTracesFilter,
|
||||
} from './constants';
|
||||
import {
|
||||
getK8sClusterItemKey,
|
||||
@@ -31,93 +28,66 @@ function K8sClustersList({
|
||||
}: {
|
||||
controlListPrefix?: React.ReactNode;
|
||||
}): JSX.Element {
|
||||
const { featureFlags } = useAppContext();
|
||||
const dotMetricsEnabled =
|
||||
featureFlags?.find((flag) => flag.name === FeatureKeys.DOT_METRICS_ENABLED)
|
||||
?.active || false;
|
||||
|
||||
const fetchListData = useCallback(
|
||||
async (filters: K8sBaseFilters, signal?: AbortSignal) => {
|
||||
try {
|
||||
const response = await listClusters(
|
||||
{
|
||||
filter: { expression: filters.filter.expression },
|
||||
groupBy: filters.groupBy?.map((g) => ({ name: g.name })),
|
||||
offset: filters.offset,
|
||||
limit: filters.limit ?? 10,
|
||||
start: filters.start,
|
||||
end: filters.end,
|
||||
orderBy: filters.orderBy
|
||||
? {
|
||||
key: { name: filters.orderBy.key.name },
|
||||
direction:
|
||||
filters.orderBy.direction === 'asc'
|
||||
? Querybuildertypesv5OrderDirectionDTO.asc
|
||||
: Querybuildertypesv5OrderDirectionDTO.desc,
|
||||
}
|
||||
: undefined,
|
||||
},
|
||||
signal,
|
||||
);
|
||||
filters.orderBy ||= {
|
||||
columnName: 'cpu',
|
||||
order: 'desc',
|
||||
};
|
||||
|
||||
const data = response.data;
|
||||
return {
|
||||
type:
|
||||
data.type === InframonitoringtypesResponseTypeDTO.grouped_list
|
||||
? ('grouped_list' as const)
|
||||
: ('list' as const),
|
||||
records: data.records,
|
||||
total: data.total,
|
||||
endTimeBeforeRetention: data.endTimeBeforeRetention,
|
||||
warning: data.warning,
|
||||
};
|
||||
} catch (error) {
|
||||
const errMsg =
|
||||
error instanceof Error ? error.message : 'Failed to fetch clusters';
|
||||
return {
|
||||
type: 'list' as const,
|
||||
records: [] as InframonitoringtypesClusterRecordDTO[],
|
||||
total: 0,
|
||||
error: errMsg,
|
||||
};
|
||||
}
|
||||
const response = await getK8sClustersList(
|
||||
filters,
|
||||
signal,
|
||||
undefined,
|
||||
dotMetricsEnabled,
|
||||
);
|
||||
|
||||
return {
|
||||
data: response.payload?.data.records || [],
|
||||
total: response.payload?.data.total || 0,
|
||||
error: response.error,
|
||||
rawData: response.payload?.data,
|
||||
};
|
||||
},
|
||||
[],
|
||||
[dotMetricsEnabled],
|
||||
);
|
||||
|
||||
const fetchEntityData = useCallback(
|
||||
async (
|
||||
filters: K8sDetailsFilters,
|
||||
signal?: AbortSignal,
|
||||
): Promise<{
|
||||
data: InframonitoringtypesClusterRecordDTO | null;
|
||||
error?: string | null;
|
||||
}> => {
|
||||
try {
|
||||
const response = await listClusters(
|
||||
{
|
||||
filter: { expression: filters.filter.expression },
|
||||
start: filters.start,
|
||||
end: filters.end,
|
||||
limit: 1,
|
||||
offset: 0,
|
||||
},
|
||||
signal,
|
||||
);
|
||||
): Promise<{ data: K8sClusterData | null; error?: string | null }> => {
|
||||
const response = await getK8sClustersList(
|
||||
{
|
||||
filters: filters.filters,
|
||||
start: filters.start,
|
||||
end: filters.end,
|
||||
limit: 1,
|
||||
offset: 0,
|
||||
},
|
||||
signal,
|
||||
undefined,
|
||||
dotMetricsEnabled,
|
||||
);
|
||||
|
||||
return {
|
||||
data: response.data.records.length > 0 ? response.data.records[0] : null,
|
||||
};
|
||||
} catch (error) {
|
||||
const errMsg =
|
||||
error instanceof Error ? error.message : 'Failed to fetch cluster';
|
||||
return {
|
||||
data: null,
|
||||
error: errMsg,
|
||||
};
|
||||
}
|
||||
const records = response.payload?.data.records || [];
|
||||
|
||||
return {
|
||||
data: records.length > 0 ? records[0] : null,
|
||||
error: response.error,
|
||||
};
|
||||
},
|
||||
[],
|
||||
[dotMetricsEnabled],
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<K8sBaseList<InframonitoringtypesClusterRecordDTO>
|
||||
<K8sBaseList<K8sClusterData>
|
||||
controlListPrefix={controlListPrefix}
|
||||
entity={InfraMonitoringEntity.CLUSTERS}
|
||||
tableColumns={k8sClustersColumnsConfig}
|
||||
@@ -127,14 +97,14 @@ function K8sClustersList({
|
||||
eventCategory={InfraMonitoringEvents.Cluster}
|
||||
/>
|
||||
|
||||
<K8sBaseDetails<InframonitoringtypesClusterRecordDTO>
|
||||
<K8sBaseDetails<K8sClusterData>
|
||||
category={InfraMonitoringEntity.CLUSTERS}
|
||||
eventCategory={InfraMonitoringEvents.Cluster}
|
||||
getSelectedItemExpression={k8sClusterGetSelectedItemExpression}
|
||||
getSelectedItemFilters={k8sClusterGetSelectedItemFilters}
|
||||
fetchEntityData={fetchEntityData}
|
||||
getEntityName={k8sClusterGetEntityName}
|
||||
getInitialLogTracesExpression={k8sClusterInitialLogTracesExpression}
|
||||
getInitialEventsExpression={k8sClusterInitialEventsExpression}
|
||||
getInitialLogTracesFilters={k8sClusterInitialLogTracesFilter}
|
||||
getInitialEventsFilters={k8sClusterInitialEventsFilter}
|
||||
metadataConfig={k8sClusterDetailsMetadataConfig}
|
||||
entityWidgetInfo={clusterWidgetInfo}
|
||||
getEntityQueryPayload={getClusterMetricsQueryPayload}
|
||||
|
||||
125
frontend/src/container/InfraMonitoringK8sV2/Clusters/api.ts
Normal file
125
frontend/src/container/InfraMonitoringK8sV2/Clusters/api.ts
Normal file
@@ -0,0 +1,125 @@
|
||||
import axios from 'api';
|
||||
import { ErrorResponseHandler } from 'api/ErrorResponseHandler';
|
||||
import { UnderscoreToDotMap } from 'api/utils';
|
||||
import { AxiosError } from 'axios';
|
||||
import { ErrorResponse, SuccessResponse } from 'types/api';
|
||||
import { BaseAutocompleteData } from 'types/api/queryBuilder/queryAutocompleteResponse';
|
||||
import { TagFilter } from 'types/api/queryBuilder/queryBuilderData';
|
||||
|
||||
import { K8sBaseFilters } from '../Base/types';
|
||||
|
||||
export interface K8sClustersListPayload {
|
||||
filters: TagFilter;
|
||||
groupBy?: BaseAutocompleteData[];
|
||||
offset?: number;
|
||||
limit?: number;
|
||||
orderBy?: {
|
||||
columnName: string;
|
||||
order: 'asc' | 'desc';
|
||||
};
|
||||
}
|
||||
|
||||
export interface K8sClusterData {
|
||||
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: K8sClusterData[];
|
||||
groups: null;
|
||||
total: number;
|
||||
sentAnyHostMetricsData: boolean;
|
||||
isSendingK8SAgentMetrics: boolean;
|
||||
};
|
||||
}
|
||||
|
||||
// TODO(H4ad): Erase this whole file when migrating to openapi
|
||||
export const clustersMetaMap = [
|
||||
{ dot: 'k8s.cluster.name', under: 'k8s_cluster_name' },
|
||||
{ dot: 'k8s.cluster.uid', under: 'k8s_cluster_uid' },
|
||||
] as const;
|
||||
|
||||
export function mapClustersMeta(
|
||||
raw: Record<string, unknown>,
|
||||
): K8sClusterData['meta'] {
|
||||
const out: Record<string, unknown> = { ...raw };
|
||||
clustersMetaMap.forEach(({ dot, under }) => {
|
||||
if (dot in raw) {
|
||||
const v = raw[dot];
|
||||
out[under] = typeof v === 'string' ? v : raw[under];
|
||||
}
|
||||
});
|
||||
return out as K8sClusterData['meta'];
|
||||
}
|
||||
|
||||
export const getK8sClustersList = async (
|
||||
props: K8sBaseFilters,
|
||||
signal?: AbortSignal,
|
||||
headers?: Record<string, string>,
|
||||
dotMetricsEnabled = false,
|
||||
): Promise<SuccessResponse<K8sClustersListResponse> | ErrorResponse> => {
|
||||
try {
|
||||
const requestProps = dotMetricsEnabled
|
||||
? {
|
||||
...props,
|
||||
filters: {
|
||||
...props.filters,
|
||||
items: props.filters.items.reduce<typeof props.filters.items>(
|
||||
(acc, item) => {
|
||||
if (item.value === undefined) {
|
||||
return acc;
|
||||
}
|
||||
if (
|
||||
item.key &&
|
||||
typeof item.key === 'object' &&
|
||||
'key' in item.key &&
|
||||
typeof item.key.key === 'string'
|
||||
) {
|
||||
const mappedKey = UnderscoreToDotMap[item.key.key] ?? item.key.key;
|
||||
acc.push({
|
||||
...item,
|
||||
key: { ...item.key, key: mappedKey },
|
||||
});
|
||||
} else {
|
||||
acc.push(item);
|
||||
}
|
||||
return acc;
|
||||
},
|
||||
[] as typeof props.filters.items,
|
||||
),
|
||||
},
|
||||
}
|
||||
: props;
|
||||
|
||||
const response = await axios.post('/clusters/list', requestProps, {
|
||||
signal,
|
||||
headers,
|
||||
});
|
||||
const payload: K8sClustersListResponse = response.data;
|
||||
|
||||
payload.data.records = payload.data.records.map((record) => ({
|
||||
...record,
|
||||
meta: mapClustersMeta(record.meta as Record<string, unknown>),
|
||||
}));
|
||||
|
||||
return {
|
||||
statusCode: 200,
|
||||
error: null,
|
||||
message: 'Success',
|
||||
payload,
|
||||
params: requestProps,
|
||||
};
|
||||
} catch (error) {
|
||||
return ErrorResponseHandler(error as AxiosError);
|
||||
}
|
||||
};
|
||||
@@ -1,39 +1,53 @@
|
||||
import { InframonitoringtypesClusterRecordDTO } from 'api/generated/services/sigNoz.schemas';
|
||||
import { PANEL_TYPES } from 'constants/queryBuilder';
|
||||
import { GetQueryResultsProps } from 'lib/dashboard/getQueryResults';
|
||||
import { DataTypes } from 'types/api/queryBuilder/queryAutocompleteResponse';
|
||||
import { TagFilter } from 'types/api/queryBuilder/queryBuilderData';
|
||||
import { EQueryType } from 'types/common/dashboard';
|
||||
import { DataSource, ReduceOperators } from 'types/common/queryBuilder';
|
||||
import { v4 } from 'uuid';
|
||||
|
||||
import { K8sDetailsMetadataConfig } from '../Base/K8sBaseDetails';
|
||||
import { formatValueForExpression } from 'components/QueryBuilderV2/utils';
|
||||
import { INFRA_MONITORING_ATTR_KEYS } from '../constants';
|
||||
import {
|
||||
createFilterItem,
|
||||
K8sDetailsMetadataConfig,
|
||||
} from '../Base/K8sBaseDetails';
|
||||
import { QUERY_KEYS } from '../EntityDetailsUtils/utils';
|
||||
import { K8sClusterData } from './api';
|
||||
|
||||
export const k8sClusterGetSelectedItemExpression = (
|
||||
export const k8sClusterGetSelectedItemFilters = (
|
||||
selectedItemId: string,
|
||||
): string => `k8s.cluster.name = ${formatValueForExpression(selectedItemId)}`;
|
||||
): TagFilter => ({
|
||||
op: 'AND',
|
||||
items: [
|
||||
{
|
||||
id: 'k8s_cluster_name',
|
||||
key: {
|
||||
key: 'k8s_cluster_name',
|
||||
type: null,
|
||||
},
|
||||
op: '=',
|
||||
value: selectedItemId,
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
export const k8sClusterDetailsMetadataConfig: K8sDetailsMetadataConfig<InframonitoringtypesClusterRecordDTO>[] =
|
||||
[{ label: 'Cluster Name', getValue: (p): string => p.clusterName || '' }];
|
||||
export const k8sClusterDetailsMetadataConfig: K8sDetailsMetadataConfig<K8sClusterData>[] =
|
||||
[{ label: 'Cluster Name', getValue: (p): string => p.meta.k8s_cluster_name }];
|
||||
|
||||
export const k8sClusterInitialEventsExpression = (
|
||||
item: InframonitoringtypesClusterRecordDTO,
|
||||
): string => {
|
||||
const objectName = formatValueForExpression(item.clusterName || '');
|
||||
return `${INFRA_MONITORING_ATTR_KEYS.K8S_OBJECT_KIND} = 'Cluster' AND ${INFRA_MONITORING_ATTR_KEYS.K8S_OBJECT_NAME} = ${objectName}`;
|
||||
};
|
||||
export const k8sClusterInitialEventsFilter = (
|
||||
item: K8sClusterData,
|
||||
): ReturnType<typeof createFilterItem>[] => [
|
||||
createFilterItem(QUERY_KEYS.K8S_OBJECT_KIND, 'Cluster'),
|
||||
createFilterItem(QUERY_KEYS.K8S_OBJECT_NAME, item.meta.k8s_cluster_name),
|
||||
];
|
||||
|
||||
export const k8sClusterInitialLogTracesExpression = (
|
||||
item: InframonitoringtypesClusterRecordDTO,
|
||||
): string => {
|
||||
const clusterName = formatValueForExpression(item.clusterName || '');
|
||||
return `${INFRA_MONITORING_ATTR_KEYS.K8S_CLUSTER_NAME} = ${clusterName}`;
|
||||
};
|
||||
export const k8sClusterInitialLogTracesFilter = (
|
||||
item: K8sClusterData,
|
||||
): ReturnType<typeof createFilterItem>[] => [
|
||||
createFilterItem(QUERY_KEYS.K8S_CLUSTER_NAME, item.meta.k8s_cluster_name),
|
||||
];
|
||||
|
||||
export const k8sClusterGetEntityName = (
|
||||
item: InframonitoringtypesClusterRecordDTO,
|
||||
): string => item.clusterName || '';
|
||||
export const k8sClusterGetEntityName = (item: K8sClusterData): string =>
|
||||
item.meta.k8s_cluster_name;
|
||||
|
||||
export const clusterWidgetInfo = [
|
||||
{
|
||||
@@ -71,7 +85,7 @@ export const clusterWidgetInfo = [
|
||||
];
|
||||
|
||||
export const getClusterMetricsQueryPayload = (
|
||||
cluster: InframonitoringtypesClusterRecordDTO,
|
||||
cluster: K8sClusterData,
|
||||
start: number,
|
||||
end: number,
|
||||
dotMetricsEnabled: boolean,
|
||||
@@ -193,7 +207,7 @@ export const getClusterMetricsQueryPayload = (
|
||||
type: 'tag',
|
||||
},
|
||||
op: '=',
|
||||
value: cluster.clusterName,
|
||||
value: cluster.meta.k8s_cluster_name,
|
||||
},
|
||||
],
|
||||
op: 'AND',
|
||||
@@ -232,7 +246,7 @@ export const getClusterMetricsQueryPayload = (
|
||||
type: 'tag',
|
||||
},
|
||||
op: '=',
|
||||
value: cluster.clusterName,
|
||||
value: cluster.meta.k8s_cluster_name,
|
||||
},
|
||||
],
|
||||
op: 'AND',
|
||||
@@ -271,7 +285,7 @@ export const getClusterMetricsQueryPayload = (
|
||||
type: 'tag',
|
||||
},
|
||||
op: '=',
|
||||
value: cluster.clusterName,
|
||||
value: cluster.meta.k8s_cluster_name,
|
||||
},
|
||||
],
|
||||
op: 'AND',
|
||||
@@ -310,7 +324,7 @@ export const getClusterMetricsQueryPayload = (
|
||||
type: 'tag',
|
||||
},
|
||||
op: '=',
|
||||
value: cluster.clusterName,
|
||||
value: cluster.meta.k8s_cluster_name,
|
||||
},
|
||||
],
|
||||
op: 'AND',
|
||||
@@ -383,7 +397,7 @@ export const getClusterMetricsQueryPayload = (
|
||||
type: 'tag',
|
||||
},
|
||||
op: '=',
|
||||
value: cluster.clusterName,
|
||||
value: cluster.meta.k8s_cluster_name,
|
||||
},
|
||||
],
|
||||
op: 'AND',
|
||||
@@ -422,7 +436,7 @@ export const getClusterMetricsQueryPayload = (
|
||||
type: 'tag',
|
||||
},
|
||||
op: '=',
|
||||
value: cluster.clusterName,
|
||||
value: cluster.meta.k8s_cluster_name,
|
||||
},
|
||||
],
|
||||
op: 'AND',
|
||||
@@ -461,7 +475,7 @@ export const getClusterMetricsQueryPayload = (
|
||||
type: 'tag',
|
||||
},
|
||||
op: '=',
|
||||
value: cluster.clusterName,
|
||||
value: cluster.meta.k8s_cluster_name,
|
||||
},
|
||||
],
|
||||
op: 'AND',
|
||||
@@ -500,7 +514,7 @@ export const getClusterMetricsQueryPayload = (
|
||||
type: 'tag',
|
||||
},
|
||||
op: '=',
|
||||
value: cluster.clusterName,
|
||||
value: cluster.meta.k8s_cluster_name,
|
||||
},
|
||||
],
|
||||
op: 'AND',
|
||||
@@ -573,7 +587,7 @@ export const getClusterMetricsQueryPayload = (
|
||||
type: 'tag',
|
||||
},
|
||||
op: '=',
|
||||
value: cluster.clusterName,
|
||||
value: cluster.meta.k8s_cluster_name,
|
||||
},
|
||||
],
|
||||
op: 'AND',
|
||||
@@ -659,7 +673,7 @@ export const getClusterMetricsQueryPayload = (
|
||||
type: 'tag',
|
||||
},
|
||||
op: '=',
|
||||
value: cluster.clusterName,
|
||||
value: cluster.meta.k8s_cluster_name,
|
||||
},
|
||||
],
|
||||
op: 'AND',
|
||||
@@ -745,7 +759,7 @@ export const getClusterMetricsQueryPayload = (
|
||||
type: 'tag',
|
||||
},
|
||||
op: '=',
|
||||
value: cluster.clusterName,
|
||||
value: cluster.meta.k8s_cluster_name,
|
||||
},
|
||||
],
|
||||
op: 'AND',
|
||||
@@ -797,7 +811,7 @@ export const getClusterMetricsQueryPayload = (
|
||||
type: 'tag',
|
||||
},
|
||||
op: '=',
|
||||
value: cluster.clusterName,
|
||||
value: cluster.meta.k8s_cluster_name,
|
||||
},
|
||||
],
|
||||
op: 'AND',
|
||||
@@ -895,7 +909,7 @@ export const getClusterMetricsQueryPayload = (
|
||||
type: 'tag',
|
||||
},
|
||||
op: '=',
|
||||
value: cluster.clusterName,
|
||||
value: cluster.meta.k8s_cluster_name,
|
||||
},
|
||||
],
|
||||
op: 'AND',
|
||||
@@ -947,7 +961,7 @@ export const getClusterMetricsQueryPayload = (
|
||||
type: 'tag',
|
||||
},
|
||||
op: '=',
|
||||
value: cluster.clusterName,
|
||||
value: cluster.meta.k8s_cluster_name,
|
||||
},
|
||||
],
|
||||
op: 'AND',
|
||||
@@ -999,7 +1013,7 @@ export const getClusterMetricsQueryPayload = (
|
||||
type: 'tag',
|
||||
},
|
||||
op: '=',
|
||||
value: cluster.clusterName,
|
||||
value: cluster.meta.k8s_cluster_name,
|
||||
},
|
||||
],
|
||||
op: 'AND',
|
||||
@@ -1051,7 +1065,7 @@ export const getClusterMetricsQueryPayload = (
|
||||
type: 'tag',
|
||||
},
|
||||
op: '=',
|
||||
value: cluster.clusterName,
|
||||
value: cluster.meta.k8s_cluster_name,
|
||||
},
|
||||
],
|
||||
op: 'AND',
|
||||
@@ -1173,7 +1187,7 @@ export const getClusterMetricsQueryPayload = (
|
||||
type: 'tag',
|
||||
},
|
||||
op: '=',
|
||||
value: cluster.clusterName,
|
||||
value: cluster.meta.k8s_cluster_name,
|
||||
},
|
||||
],
|
||||
op: 'AND',
|
||||
@@ -1219,7 +1233,7 @@ export const getClusterMetricsQueryPayload = (
|
||||
type: 'tag',
|
||||
},
|
||||
op: '=',
|
||||
value: cluster.clusterName,
|
||||
value: cluster.meta.k8s_cluster_name,
|
||||
},
|
||||
],
|
||||
op: 'AND',
|
||||
@@ -1265,7 +1279,7 @@ export const getClusterMetricsQueryPayload = (
|
||||
type: 'tag',
|
||||
},
|
||||
op: '=',
|
||||
value: cluster.clusterName,
|
||||
value: cluster.meta.k8s_cluster_name,
|
||||
},
|
||||
],
|
||||
op: 'AND',
|
||||
@@ -1369,7 +1383,7 @@ export const getClusterMetricsQueryPayload = (
|
||||
type: 'tag',
|
||||
},
|
||||
op: '=',
|
||||
value: cluster.clusterName,
|
||||
value: cluster.meta.k8s_cluster_name,
|
||||
},
|
||||
],
|
||||
op: 'AND',
|
||||
@@ -1421,7 +1435,7 @@ export const getClusterMetricsQueryPayload = (
|
||||
type: 'tag',
|
||||
},
|
||||
op: '=',
|
||||
value: cluster.clusterName,
|
||||
value: cluster.meta.k8s_cluster_name,
|
||||
},
|
||||
],
|
||||
op: 'AND',
|
||||
@@ -1473,7 +1487,7 @@ export const getClusterMetricsQueryPayload = (
|
||||
type: 'tag',
|
||||
},
|
||||
op: '=',
|
||||
value: cluster.clusterName,
|
||||
value: cluster.meta.k8s_cluster_name,
|
||||
},
|
||||
],
|
||||
op: 'AND',
|
||||
@@ -1525,7 +1539,7 @@ export const getClusterMetricsQueryPayload = (
|
||||
type: 'tag',
|
||||
},
|
||||
op: '=',
|
||||
value: cluster.clusterName,
|
||||
value: cluster.meta.k8s_cluster_name,
|
||||
},
|
||||
],
|
||||
op: 'AND',
|
||||
|
||||
@@ -1,48 +1,42 @@
|
||||
import { Color } from '@signozhq/design-tokens';
|
||||
import { Boxes } from '@signozhq/icons';
|
||||
import { InframonitoringtypesClusterRecordDTO } from 'api/generated/services/sigNoz.schemas';
|
||||
import { Tooltip } from 'antd';
|
||||
import { TableColumnDef } from 'components/TanStackTableView';
|
||||
import TanStackTable from 'components/TanStackTableView';
|
||||
import { ExpandButtonWrapper } from 'container/InfraMonitoringK8sV2/components';
|
||||
|
||||
import ColumnHeader from '../Base/ColumnHeader';
|
||||
import EntityGroupHeader from '../Base/EntityGroupHeader';
|
||||
import K8sGroupCell from '../Base/K8sGroupCell';
|
||||
import { formatBytes, getPodPhaseStatusItems } from '../commonUtils';
|
||||
import {
|
||||
CellValueTooltip,
|
||||
GroupedStatusCounts,
|
||||
ValidateColumnValueWrapper,
|
||||
} from '../components';
|
||||
import {
|
||||
INFRA_MONITORING_ATTR_KEYS,
|
||||
InfraMonitoringEntity,
|
||||
} from '../constants';
|
||||
import { formatBytes } from '../commonUtils';
|
||||
import { ValidateColumnValueWrapper } from '../components';
|
||||
import { InfraMonitoringEntity } from '../constants';
|
||||
import { K8sClusterData, K8sClustersListPayload } from './api';
|
||||
import { Boxes } from '@signozhq/icons';
|
||||
|
||||
export function getK8sClusterRowKey(
|
||||
cluster: InframonitoringtypesClusterRecordDTO,
|
||||
): string {
|
||||
export function getK8sClusterRowKey(cluster: K8sClusterData): string {
|
||||
return (
|
||||
cluster.clusterName ||
|
||||
cluster.meta?.[INFRA_MONITORING_ATTR_KEYS.K8S_CLUSTER_UID] ||
|
||||
''
|
||||
cluster.clusterUID ||
|
||||
cluster.meta.k8s_cluster_uid ||
|
||||
cluster.meta.k8s_cluster_name
|
||||
);
|
||||
}
|
||||
|
||||
export function getK8sClusterItemKey(
|
||||
cluster: InframonitoringtypesClusterRecordDTO,
|
||||
): string {
|
||||
return cluster.clusterName;
|
||||
export function getK8sClusterItemKey(cluster: K8sClusterData): string {
|
||||
return cluster.meta.k8s_cluster_name;
|
||||
}
|
||||
|
||||
export type ClusterTableColumnConfig =
|
||||
TableColumnDef<InframonitoringtypesClusterRecordDTO>;
|
||||
export const k8sClustersColumnsConfig: ClusterTableColumnConfig[] = [
|
||||
export const getK8sClustersListQuery = (): K8sClustersListPayload => ({
|
||||
filters: {
|
||||
items: [],
|
||||
op: 'and',
|
||||
},
|
||||
orderBy: { columnName: 'cpu', order: 'desc' },
|
||||
});
|
||||
|
||||
export const k8sClustersColumnsConfig: TableColumnDef<K8sClusterData>[] = [
|
||||
{
|
||||
id: 'clusterGroup',
|
||||
header: (): React.ReactNode => <EntityGroupHeader title="Cluster Group" />,
|
||||
accessorFn: (row): string => row.clusterName || '',
|
||||
width: { min: 290 },
|
||||
header: (): React.ReactNode => <EntityGroupHeader title="CLUSTER GROUP" />,
|
||||
accessorFn: (row): string => row.meta.k8s_cluster_name || '',
|
||||
width: { min: 300 },
|
||||
enableSort: false,
|
||||
enableRemove: false,
|
||||
enableMove: false,
|
||||
@@ -65,10 +59,9 @@ export const k8sClustersColumnsConfig: ClusterTableColumnConfig[] = [
|
||||
<EntityGroupHeader
|
||||
title="Cluster Name"
|
||||
icon={<Boxes data-hide-expanded="true" size={14} />}
|
||||
docPath="/infrastructure-monitoring/kubernetes/clusters#cluster-name"
|
||||
/>
|
||||
),
|
||||
accessorFn: (row): string => row.clusterName || '',
|
||||
accessorFn: (row): string => row.meta.k8s_cluster_name || '',
|
||||
width: { min: 290 },
|
||||
enableSort: false,
|
||||
enableRemove: false,
|
||||
@@ -78,127 +71,55 @@ export const k8sClustersColumnsConfig: ClusterTableColumnConfig[] = [
|
||||
cell: ({ value }): React.ReactNode => {
|
||||
const clusterName = value as string;
|
||||
return (
|
||||
<CellValueTooltip value={clusterName}>
|
||||
<Tooltip title={clusterName}>
|
||||
<TanStackTable.Text>{clusterName}</TanStackTable.Text>
|
||||
</CellValueTooltip>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'nodeCountsByReadiness',
|
||||
header: (): React.ReactNode => (
|
||||
<ColumnHeader docPath="/infrastructure-monitoring/kubernetes/clusters#node-readiness">
|
||||
Node Readiness
|
||||
</ColumnHeader>
|
||||
),
|
||||
accessorFn: (
|
||||
row,
|
||||
): InframonitoringtypesClusterRecordDTO['nodeCountsByReadiness'] =>
|
||||
row.nodeCountsByReadiness,
|
||||
width: { min: 180 },
|
||||
enableSort: false,
|
||||
cell: ({ row }): React.ReactNode => {
|
||||
if (!row.nodeCountsByReadiness) {
|
||||
return <TanStackTable.Text>-</TanStackTable.Text>;
|
||||
}
|
||||
|
||||
return (
|
||||
<GroupedStatusCounts
|
||||
items={[
|
||||
{
|
||||
value: row.nodeCountsByReadiness.ready,
|
||||
label: 'Ready',
|
||||
color: Color.BG_FOREST_500,
|
||||
},
|
||||
{
|
||||
value: row.nodeCountsByReadiness.notReady,
|
||||
label: 'Not Ready',
|
||||
color: Color.BG_AMBER_500,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'podCountsByPhase',
|
||||
header: (): React.ReactNode => (
|
||||
<ColumnHeader docPath="/infrastructure-monitoring/kubernetes/clusters#pod-counts-by-phase">
|
||||
Pod Phases
|
||||
</ColumnHeader>
|
||||
),
|
||||
accessorFn: (row): InframonitoringtypesClusterRecordDTO['podCountsByPhase'] =>
|
||||
row.podCountsByPhase,
|
||||
width: { min: 250 },
|
||||
enableSort: false,
|
||||
cell: ({ row }): React.ReactNode => {
|
||||
const podCountsByPhase = row.podCountsByPhase;
|
||||
if (!podCountsByPhase) {
|
||||
return <TanStackTable.Text>-</TanStackTable.Text>;
|
||||
}
|
||||
return (
|
||||
<GroupedStatusCounts items={getPodPhaseStatusItems(row.podCountsByPhase)} />
|
||||
</Tooltip>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'cpu',
|
||||
header: (): React.ReactNode => (
|
||||
<ColumnHeader docPath="/infrastructure-monitoring/kubernetes/clusters#cpu-usage-cores">
|
||||
CPU Usage
|
||||
<br /> (cores)
|
||||
</ColumnHeader>
|
||||
),
|
||||
accessorFn: (row): number => row.clusterCPU,
|
||||
width: { min: 160 },
|
||||
header: 'CPU Usage (cores)',
|
||||
accessorFn: (row): number => row.cpuUsage,
|
||||
width: { min: 220 },
|
||||
enableSort: true,
|
||||
cell: ({ value }): React.ReactNode => {
|
||||
const cpu = Number(value);
|
||||
const cpu = value as number;
|
||||
return (
|
||||
<ValidateColumnValueWrapper
|
||||
value={cpu}
|
||||
entity={InfraMonitoringEntity.CLUSTERS}
|
||||
attribute="CPU metric"
|
||||
>
|
||||
<TanStackTable.Text>{cpu.toFixed(2)}</TanStackTable.Text>
|
||||
<TanStackTable.Text>{cpu}</TanStackTable.Text>
|
||||
</ValidateColumnValueWrapper>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'cpu_allocatable',
|
||||
header: (): React.ReactNode => (
|
||||
<ColumnHeader docPath="/infrastructure-monitoring/kubernetes/clusters#cpu-alloc-cores">
|
||||
CPU Allocatable
|
||||
<br /> (cores)
|
||||
</ColumnHeader>
|
||||
),
|
||||
accessorFn: (row): number => row.clusterCPUAllocatable,
|
||||
width: { min: 200 },
|
||||
header: 'CPU Alloc (cores)',
|
||||
accessorFn: (row): number => row.cpuAllocatable,
|
||||
width: { min: 220 },
|
||||
enableSort: true,
|
||||
cell: ({ value }): React.ReactNode => {
|
||||
const cpuAllocatable = Number(value);
|
||||
const cpuAllocatable = value as number;
|
||||
return (
|
||||
<ValidateColumnValueWrapper
|
||||
value={cpuAllocatable}
|
||||
entity={InfraMonitoringEntity.CLUSTERS}
|
||||
attribute="CPU allocatable metric"
|
||||
>
|
||||
<TanStackTable.Text>{cpuAllocatable.toFixed(2)}</TanStackTable.Text>
|
||||
<TanStackTable.Text>{cpuAllocatable}</TanStackTable.Text>
|
||||
</ValidateColumnValueWrapper>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'memory',
|
||||
header: (): React.ReactNode => (
|
||||
<ColumnHeader docPath="/infrastructure-monitoring/kubernetes/clusters#memory-usage-wss">
|
||||
Memory Usage
|
||||
<br /> (WSS)
|
||||
</ColumnHeader>
|
||||
),
|
||||
accessorFn: (row): number => row.clusterMemory,
|
||||
width: { min: 180 },
|
||||
header: 'Memory Usage (WSS)',
|
||||
accessorFn: (row): number => row.memoryUsage,
|
||||
width: { min: 220 },
|
||||
enableSort: true,
|
||||
cell: ({ value }): React.ReactNode => {
|
||||
const memory = value as number;
|
||||
@@ -215,14 +136,9 @@ export const k8sClustersColumnsConfig: ClusterTableColumnConfig[] = [
|
||||
},
|
||||
{
|
||||
id: 'memory_allocatable',
|
||||
header: (): React.ReactNode => (
|
||||
<ColumnHeader docPath="/infrastructure-monitoring/kubernetes/clusters#memory-allocatable">
|
||||
Memory
|
||||
<br /> Allocatable
|
||||
</ColumnHeader>
|
||||
),
|
||||
accessorFn: (row): number => row.clusterMemoryAllocatable,
|
||||
width: { min: 180 },
|
||||
header: 'Memory Allocatable',
|
||||
accessorFn: (row): number => row.memoryAllocatable,
|
||||
width: { min: 220 },
|
||||
enableSort: true,
|
||||
cell: ({ value }): React.ReactNode => {
|
||||
const memoryAllocatable = value as number;
|
||||
|
||||
@@ -1,117 +1,93 @@
|
||||
import { useCallback } from 'react';
|
||||
import { listDaemonSets } from 'api/generated/services/inframonitoring';
|
||||
import {
|
||||
InframonitoringtypesDaemonSetRecordDTO,
|
||||
InframonitoringtypesResponseTypeDTO,
|
||||
Querybuildertypesv5OrderDirectionDTO,
|
||||
} from 'api/generated/services/sigNoz.schemas';
|
||||
import React, { useCallback } from 'react';
|
||||
import { InfraMonitoringEvents } from 'constants/events';
|
||||
import { FeatureKeys } from 'constants/features';
|
||||
import { useAppContext } from 'providers/App/App';
|
||||
|
||||
import K8sBaseDetails, { K8sDetailsFilters } from '../Base/K8sBaseDetails';
|
||||
import { K8sBaseList } from '../Base/K8sBaseList';
|
||||
import { K8sBaseFilters } from '../Base/types';
|
||||
import { InfraMonitoringEntity } from '../constants';
|
||||
import { getK8sDaemonSetsList, K8sDaemonSetsData } from './api';
|
||||
import {
|
||||
daemonSetWidgetInfo,
|
||||
getDaemonSetMetricsQueryPayload,
|
||||
k8sDaemonSetDetailsMetadataConfig,
|
||||
k8sDaemonSetGetEntityName,
|
||||
k8sDaemonSetGetSelectedItemExpression,
|
||||
k8sDaemonSetInitialEventsExpression,
|
||||
k8sDaemonSetInitialLogTracesExpression,
|
||||
k8sDaemonSetGetSelectedItemFilters,
|
||||
k8sDaemonSetInitialEventsFilter,
|
||||
k8sDaemonSetInitialLogTracesFilter,
|
||||
} from './constants';
|
||||
import {
|
||||
getK8sDaemonSetItemKey,
|
||||
getK8sDaemonSetRowKey,
|
||||
k8sDaemonSetsColumnsConfig,
|
||||
} from './table.config';
|
||||
|
||||
function K8sDaemonSetsList({
|
||||
controlListPrefix,
|
||||
}: {
|
||||
controlListPrefix?: React.ReactNode;
|
||||
}): JSX.Element {
|
||||
const { featureFlags } = useAppContext();
|
||||
const dotMetricsEnabled =
|
||||
featureFlags?.find((flag) => flag.name === FeatureKeys.DOT_METRICS_ENABLED)
|
||||
?.active || false;
|
||||
|
||||
const fetchListData = useCallback(
|
||||
async (filters: K8sBaseFilters, signal?: AbortSignal) => {
|
||||
try {
|
||||
const response = await listDaemonSets(
|
||||
{
|
||||
filter: { expression: filters.filter.expression },
|
||||
groupBy: filters.groupBy?.map((g) => ({ name: g.name })),
|
||||
offset: filters.offset,
|
||||
limit: filters.limit ?? 10,
|
||||
start: filters.start,
|
||||
end: filters.end,
|
||||
orderBy: filters.orderBy
|
||||
? {
|
||||
key: { name: filters.orderBy.key.name },
|
||||
direction:
|
||||
filters.orderBy.direction === 'asc'
|
||||
? Querybuildertypesv5OrderDirectionDTO.asc
|
||||
: Querybuildertypesv5OrderDirectionDTO.desc,
|
||||
}
|
||||
: undefined,
|
||||
},
|
||||
signal,
|
||||
);
|
||||
const data = response.data;
|
||||
return {
|
||||
type:
|
||||
data.type === InframonitoringtypesResponseTypeDTO.grouped_list
|
||||
? ('grouped_list' as const)
|
||||
: ('list' as const),
|
||||
records: data.records,
|
||||
total: data.total,
|
||||
endTimeBeforeRetention: data.endTimeBeforeRetention,
|
||||
warning: data.warning,
|
||||
};
|
||||
} catch (error) {
|
||||
const errMsg =
|
||||
error instanceof Error ? error.message : 'Failed to fetch daemonsets';
|
||||
return {
|
||||
type: 'list' as const,
|
||||
records: [] as InframonitoringtypesDaemonSetRecordDTO[],
|
||||
total: 0,
|
||||
error: errMsg,
|
||||
};
|
||||
}
|
||||
filters.orderBy ||= {
|
||||
columnName: 'cpu',
|
||||
order: 'desc',
|
||||
};
|
||||
|
||||
const response = await getK8sDaemonSetsList(
|
||||
filters,
|
||||
signal,
|
||||
undefined,
|
||||
dotMetricsEnabled,
|
||||
);
|
||||
|
||||
return {
|
||||
data: response.payload?.data.records || [],
|
||||
total: response.payload?.data.total || 0,
|
||||
error: response.error,
|
||||
rawData: response.payload?.data,
|
||||
};
|
||||
},
|
||||
[],
|
||||
[dotMetricsEnabled],
|
||||
);
|
||||
|
||||
const fetchEntityData = useCallback(
|
||||
async (
|
||||
filters: K8sDetailsFilters,
|
||||
signal?: AbortSignal,
|
||||
): Promise<{
|
||||
data: InframonitoringtypesDaemonSetRecordDTO | null;
|
||||
error?: string | null;
|
||||
}> => {
|
||||
try {
|
||||
const response = await listDaemonSets(
|
||||
{
|
||||
filter: { expression: filters.filter.expression },
|
||||
start: filters.start,
|
||||
end: filters.end,
|
||||
limit: 1,
|
||||
offset: 0,
|
||||
},
|
||||
signal,
|
||||
);
|
||||
return {
|
||||
data: response.data.records.length > 0 ? response.data.records[0] : null,
|
||||
};
|
||||
} catch (error) {
|
||||
const errMsg =
|
||||
error instanceof Error ? error.message : 'Failed to fetch daemonset';
|
||||
return {
|
||||
data: null,
|
||||
error: errMsg,
|
||||
};
|
||||
}
|
||||
): Promise<{ data: K8sDaemonSetsData | null; error?: string | null }> => {
|
||||
const response = await getK8sDaemonSetsList(
|
||||
{
|
||||
filters: filters.filters,
|
||||
start: filters.start,
|
||||
end: filters.end,
|
||||
limit: 1,
|
||||
offset: 0,
|
||||
},
|
||||
signal,
|
||||
undefined,
|
||||
dotMetricsEnabled,
|
||||
);
|
||||
|
||||
const records = response.payload?.data.records || [];
|
||||
|
||||
return {
|
||||
data: records.length > 0 ? records[0] : null,
|
||||
error: response.error,
|
||||
};
|
||||
},
|
||||
[],
|
||||
[dotMetricsEnabled],
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<K8sBaseList<InframonitoringtypesDaemonSetRecordDTO>
|
||||
<K8sBaseList<K8sDaemonSetsData>
|
||||
controlListPrefix={controlListPrefix}
|
||||
entity={InfraMonitoringEntity.DAEMONSETS}
|
||||
tableColumns={k8sDaemonSetsColumnsConfig}
|
||||
@@ -120,14 +96,15 @@ function K8sDaemonSetsList({
|
||||
getItemKey={getK8sDaemonSetItemKey}
|
||||
eventCategory={InfraMonitoringEvents.DaemonSet}
|
||||
/>
|
||||
<K8sBaseDetails<InframonitoringtypesDaemonSetRecordDTO>
|
||||
|
||||
<K8sBaseDetails<K8sDaemonSetsData>
|
||||
category={InfraMonitoringEntity.DAEMONSETS}
|
||||
eventCategory={InfraMonitoringEvents.DaemonSet}
|
||||
getSelectedItemExpression={k8sDaemonSetGetSelectedItemExpression}
|
||||
getSelectedItemFilters={k8sDaemonSetGetSelectedItemFilters}
|
||||
fetchEntityData={fetchEntityData}
|
||||
getEntityName={k8sDaemonSetGetEntityName}
|
||||
getInitialLogTracesExpression={k8sDaemonSetInitialLogTracesExpression}
|
||||
getInitialEventsExpression={k8sDaemonSetInitialEventsExpression}
|
||||
getInitialLogTracesFilters={k8sDaemonSetInitialLogTracesFilter}
|
||||
getInitialEventsFilters={k8sDaemonSetInitialEventsFilter}
|
||||
metadataConfig={k8sDaemonSetDetailsMetadataConfig}
|
||||
entityWidgetInfo={daemonSetWidgetInfo}
|
||||
getEntityQueryPayload={getDaemonSetMetricsQueryPayload}
|
||||
@@ -136,4 +113,5 @@ function K8sDaemonSetsList({
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default K8sDaemonSetsList;
|
||||
|
||||
119
frontend/src/container/InfraMonitoringK8sV2/DaemonSets/api.ts
Normal file
119
frontend/src/container/InfraMonitoringK8sV2/DaemonSets/api.ts
Normal file
@@ -0,0 +1,119 @@
|
||||
import axios from 'api';
|
||||
import { ErrorResponseHandler } from 'api/ErrorResponseHandler';
|
||||
import { UnderscoreToDotMap } from 'api/utils';
|
||||
import { AxiosError } from 'axios';
|
||||
import { ErrorResponse, SuccessResponse } from 'types/api';
|
||||
|
||||
import { K8sBaseFilters } from '../Base/types';
|
||||
|
||||
export interface K8sDaemonSetsData {
|
||||
daemonSetName: string;
|
||||
cpuUsage: number;
|
||||
memoryUsage: number;
|
||||
cpuRequest: number;
|
||||
memoryRequest: number;
|
||||
cpuLimit: number;
|
||||
memoryLimit: number;
|
||||
restarts: number;
|
||||
desiredNodes: number;
|
||||
availableNodes: number;
|
||||
meta: {
|
||||
k8s_cluster_name: string;
|
||||
k8s_daemonset_name: string;
|
||||
k8s_namespace_name: string;
|
||||
};
|
||||
}
|
||||
|
||||
export interface K8sDaemonSetsListResponse {
|
||||
status: string;
|
||||
data: {
|
||||
type: string;
|
||||
records: K8sDaemonSetsData[];
|
||||
groups: null;
|
||||
total: number;
|
||||
sentAnyHostMetricsData: boolean;
|
||||
isSendingK8SAgentMetrics: boolean;
|
||||
};
|
||||
}
|
||||
|
||||
// TODO(H4ad): Erase this whole file when migrating to openapi
|
||||
export const daemonSetsMetaMap = [
|
||||
{ dot: 'k8s.namespace.name', under: 'k8s_namespace_name' },
|
||||
{ dot: 'k8s.daemonset.name', under: 'k8s_daemonset_name' },
|
||||
{ dot: 'k8s.cluster.name', under: 'k8s_cluster_name' },
|
||||
] as const;
|
||||
|
||||
export function mapDaemonSetsMeta(
|
||||
raw: Record<string, unknown>,
|
||||
): K8sDaemonSetsData['meta'] {
|
||||
const out: Record<string, unknown> = { ...raw };
|
||||
daemonSetsMetaMap.forEach(({ dot, under }) => {
|
||||
if (dot in raw) {
|
||||
const v = raw[dot];
|
||||
out[under] = typeof v === 'string' ? v : raw[under];
|
||||
}
|
||||
});
|
||||
return out as K8sDaemonSetsData['meta'];
|
||||
}
|
||||
|
||||
export const getK8sDaemonSetsList = async (
|
||||
props: K8sBaseFilters,
|
||||
signal?: AbortSignal,
|
||||
headers?: Record<string, string>,
|
||||
dotMetricsEnabled = false,
|
||||
): Promise<SuccessResponse<K8sDaemonSetsListResponse> | ErrorResponse> => {
|
||||
try {
|
||||
const requestProps = dotMetricsEnabled
|
||||
? {
|
||||
...props,
|
||||
filters: {
|
||||
...props.filters,
|
||||
items: props.filters.items.reduce<typeof props.filters.items>(
|
||||
(acc, item) => {
|
||||
if (item.value === undefined) {
|
||||
return acc;
|
||||
}
|
||||
if (
|
||||
item.key &&
|
||||
typeof item.key === 'object' &&
|
||||
'key' in item.key &&
|
||||
typeof item.key.key === 'string'
|
||||
) {
|
||||
const mappedKey = UnderscoreToDotMap[item.key.key] ?? item.key.key;
|
||||
acc.push({
|
||||
...item,
|
||||
key: { ...item.key, key: mappedKey },
|
||||
});
|
||||
} else {
|
||||
acc.push(item);
|
||||
}
|
||||
return acc;
|
||||
},
|
||||
[] as typeof props.filters.items,
|
||||
),
|
||||
},
|
||||
}
|
||||
: props;
|
||||
|
||||
const response = await axios.post('/daemonsets/list', requestProps, {
|
||||
signal,
|
||||
headers,
|
||||
});
|
||||
const payload: K8sDaemonSetsListResponse = response.data;
|
||||
|
||||
payload.data.records = payload.data.records.map((record) => ({
|
||||
...record,
|
||||
meta: mapDaemonSetsMeta(record.meta as Record<string, unknown>),
|
||||
}));
|
||||
|
||||
return {
|
||||
statusCode: 200,
|
||||
error: null,
|
||||
message: 'Success',
|
||||
payload,
|
||||
params: requestProps,
|
||||
};
|
||||
} catch (error) {
|
||||
return ErrorResponseHandler(error as AxiosError);
|
||||
}
|
||||
};
|
||||
@@ -1,52 +1,67 @@
|
||||
import { InframonitoringtypesDaemonSetRecordDTO } from 'api/generated/services/sigNoz.schemas';
|
||||
import { PANEL_TYPES } from 'constants/queryBuilder';
|
||||
import { GetQueryResultsProps } from 'lib/dashboard/getQueryResults';
|
||||
import { DataTypes } from 'types/api/queryBuilder/queryAutocompleteResponse';
|
||||
import { TagFilter } from 'types/api/queryBuilder/queryBuilderData';
|
||||
import { EQueryType } from 'types/common/dashboard';
|
||||
import { DataSource, ReduceOperators } from 'types/common/queryBuilder';
|
||||
import { v4 } from 'uuid';
|
||||
|
||||
import { K8sDetailsMetadataConfig } from '../Base/K8sBaseDetails';
|
||||
import { formatValueForExpression } from 'components/QueryBuilderV2/utils';
|
||||
import { INFRA_MONITORING_ATTR_KEYS } from '../constants';
|
||||
import {
|
||||
createFilterItem,
|
||||
K8sDetailsMetadataConfig,
|
||||
} from '../Base/K8sBaseDetails';
|
||||
import { QUERY_KEYS } from '../EntityDetailsUtils/utils';
|
||||
import { K8sDaemonSetsData } from './api';
|
||||
|
||||
export const k8sDaemonSetGetSelectedItemExpression = (
|
||||
export const k8sDaemonSetGetSelectedItemFilters = (
|
||||
selectedItemId: string,
|
||||
): string =>
|
||||
`${INFRA_MONITORING_ATTR_KEYS.K8S_DAEMONSET_NAME} = ${formatValueForExpression(selectedItemId)}`;
|
||||
): TagFilter => ({
|
||||
op: 'AND',
|
||||
items: [
|
||||
{
|
||||
id: 'k8s_daemonset_name',
|
||||
key: {
|
||||
key: 'k8s_daemonset_name',
|
||||
type: null,
|
||||
},
|
||||
op: '=',
|
||||
value: selectedItemId,
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
export const k8sDaemonSetDetailsMetadataConfig: K8sDetailsMetadataConfig<InframonitoringtypesDaemonSetRecordDTO>[] =
|
||||
export const k8sDaemonSetDetailsMetadataConfig: K8sDetailsMetadataConfig<K8sDaemonSetsData>[] =
|
||||
[
|
||||
{
|
||||
label: 'Daemonset Name',
|
||||
getValue: (p): string =>
|
||||
p.meta?.[INFRA_MONITORING_ATTR_KEYS.K8S_DAEMONSET_NAME] ?? '',
|
||||
getValue: (p): string => p.meta.k8s_daemonset_name,
|
||||
},
|
||||
{
|
||||
label: 'Cluster Name',
|
||||
getValue: (p): string =>
|
||||
p.meta?.[INFRA_MONITORING_ATTR_KEYS.K8S_CLUSTER_NAME] ?? '',
|
||||
getValue: (p): string => p.meta.k8s_cluster_name,
|
||||
},
|
||||
{
|
||||
label: 'Namespace Name',
|
||||
getValue: (p): string =>
|
||||
p.meta?.[INFRA_MONITORING_ATTR_KEYS.K8S_NAMESPACE_NAME] ?? '',
|
||||
getValue: (p): string => p.meta.k8s_namespace_name,
|
||||
},
|
||||
];
|
||||
|
||||
export const k8sDaemonSetInitialEventsExpression = (
|
||||
item: InframonitoringtypesDaemonSetRecordDTO,
|
||||
): string =>
|
||||
`${INFRA_MONITORING_ATTR_KEYS.K8S_OBJECT_KIND} = 'DaemonSet' AND ${INFRA_MONITORING_ATTR_KEYS.K8S_OBJECT_NAME} = ${formatValueForExpression(item.meta?.[INFRA_MONITORING_ATTR_KEYS.K8S_DAEMONSET_NAME] ?? '')}`;
|
||||
export const k8sDaemonSetInitialEventsFilter = (
|
||||
item: K8sDaemonSetsData,
|
||||
): ReturnType<typeof createFilterItem>[] => [
|
||||
createFilterItem(QUERY_KEYS.K8S_OBJECT_KIND, 'DaemonSet'),
|
||||
createFilterItem(QUERY_KEYS.K8S_OBJECT_NAME, item.meta.k8s_daemonset_name),
|
||||
];
|
||||
|
||||
export const k8sDaemonSetInitialLogTracesExpression = (
|
||||
item: InframonitoringtypesDaemonSetRecordDTO,
|
||||
): string =>
|
||||
`${INFRA_MONITORING_ATTR_KEYS.K8S_DAEMONSET_NAME} = ${formatValueForExpression(item.meta?.[INFRA_MONITORING_ATTR_KEYS.K8S_DAEMONSET_NAME] ?? '')} AND ${INFRA_MONITORING_ATTR_KEYS.K8S_NAMESPACE_NAME} = ${formatValueForExpression(item.meta?.[INFRA_MONITORING_ATTR_KEYS.K8S_NAMESPACE_NAME] ?? '')}`;
|
||||
export const k8sDaemonSetInitialLogTracesFilter = (
|
||||
item: K8sDaemonSetsData,
|
||||
): ReturnType<typeof createFilterItem>[] => [
|
||||
createFilterItem(QUERY_KEYS.K8S_DAEMON_SET_NAME, item.meta.k8s_daemonset_name),
|
||||
createFilterItem(QUERY_KEYS.K8S_NAMESPACE_NAME, item.meta.k8s_namespace_name),
|
||||
];
|
||||
|
||||
export const k8sDaemonSetGetEntityName = (
|
||||
item: InframonitoringtypesDaemonSetRecordDTO,
|
||||
): string => item.meta?.[INFRA_MONITORING_ATTR_KEYS.K8S_DAEMONSET_NAME] ?? '';
|
||||
export const k8sDaemonSetGetEntityName = (item: K8sDaemonSetsData): string =>
|
||||
item.meta.k8s_daemonset_name;
|
||||
|
||||
export const daemonSetWidgetInfo = [
|
||||
{
|
||||
@@ -68,7 +83,7 @@ export const daemonSetWidgetInfo = [
|
||||
];
|
||||
|
||||
export const getDaemonSetMetricsQueryPayload = (
|
||||
daemonSet: InframonitoringtypesDaemonSetRecordDTO,
|
||||
daemonSet: K8sDaemonSetsData,
|
||||
start: number,
|
||||
end: number,
|
||||
dotMetricsEnabled: boolean,
|
||||
@@ -144,9 +159,7 @@ export const getDaemonSetMetricsQueryPayload = (
|
||||
type: 'tag',
|
||||
},
|
||||
op: '=',
|
||||
value:
|
||||
daemonSet.meta?.[INFRA_MONITORING_ATTR_KEYS.K8S_DAEMONSET_NAME] ??
|
||||
'',
|
||||
value: daemonSet.meta.k8s_daemonset_name,
|
||||
},
|
||||
{
|
||||
id: '47b3adae',
|
||||
@@ -157,9 +170,7 @@ export const getDaemonSetMetricsQueryPayload = (
|
||||
type: 'tag',
|
||||
},
|
||||
op: '=',
|
||||
value:
|
||||
daemonSet.meta?.[INFRA_MONITORING_ATTR_KEYS.K8S_NAMESPACE_NAME] ??
|
||||
'',
|
||||
value: daemonSet.meta.k8s_namespace_name,
|
||||
},
|
||||
],
|
||||
op: 'AND',
|
||||
@@ -198,9 +209,7 @@ export const getDaemonSetMetricsQueryPayload = (
|
||||
type: 'tag',
|
||||
},
|
||||
op: 'contains',
|
||||
value:
|
||||
daemonSet.meta?.[INFRA_MONITORING_ATTR_KEYS.K8S_DAEMONSET_NAME] ??
|
||||
'',
|
||||
value: daemonSet.meta.k8s_daemonset_name,
|
||||
},
|
||||
{
|
||||
id: '47b3adae',
|
||||
@@ -211,9 +220,7 @@ export const getDaemonSetMetricsQueryPayload = (
|
||||
type: 'tag',
|
||||
},
|
||||
op: '=',
|
||||
value:
|
||||
daemonSet.meta?.[INFRA_MONITORING_ATTR_KEYS.K8S_NAMESPACE_NAME] ??
|
||||
'',
|
||||
value: daemonSet.meta.k8s_namespace_name,
|
||||
},
|
||||
],
|
||||
op: 'AND',
|
||||
@@ -252,9 +259,7 @@ export const getDaemonSetMetricsQueryPayload = (
|
||||
type: 'tag',
|
||||
},
|
||||
op: 'contains',
|
||||
value:
|
||||
daemonSet.meta?.[INFRA_MONITORING_ATTR_KEYS.K8S_DAEMONSET_NAME] ??
|
||||
'',
|
||||
value: daemonSet.meta.k8s_daemonset_name,
|
||||
},
|
||||
{
|
||||
id: '47b3adae',
|
||||
@@ -265,9 +270,7 @@ export const getDaemonSetMetricsQueryPayload = (
|
||||
type: 'tag',
|
||||
},
|
||||
op: '=',
|
||||
value:
|
||||
daemonSet.meta?.[INFRA_MONITORING_ATTR_KEYS.K8S_NAMESPACE_NAME] ??
|
||||
'',
|
||||
value: daemonSet.meta.k8s_namespace_name,
|
||||
},
|
||||
],
|
||||
op: 'AND',
|
||||
@@ -340,9 +343,7 @@ export const getDaemonSetMetricsQueryPayload = (
|
||||
type: 'tag',
|
||||
},
|
||||
op: '=',
|
||||
value:
|
||||
daemonSet.meta?.[INFRA_MONITORING_ATTR_KEYS.K8S_DAEMONSET_NAME] ??
|
||||
'',
|
||||
value: daemonSet.meta.k8s_daemonset_name,
|
||||
},
|
||||
{
|
||||
id: '47b3adae',
|
||||
@@ -353,9 +354,7 @@ export const getDaemonSetMetricsQueryPayload = (
|
||||
type: 'tag',
|
||||
},
|
||||
op: '=',
|
||||
value:
|
||||
daemonSet.meta?.[INFRA_MONITORING_ATTR_KEYS.K8S_NAMESPACE_NAME] ??
|
||||
'',
|
||||
value: daemonSet.meta.k8s_namespace_name,
|
||||
},
|
||||
],
|
||||
op: 'AND',
|
||||
@@ -394,9 +393,7 @@ export const getDaemonSetMetricsQueryPayload = (
|
||||
type: 'tag',
|
||||
},
|
||||
op: 'contains',
|
||||
value:
|
||||
daemonSet.meta?.[INFRA_MONITORING_ATTR_KEYS.K8S_DAEMONSET_NAME] ??
|
||||
'',
|
||||
value: daemonSet.meta.k8s_daemonset_name,
|
||||
},
|
||||
{
|
||||
id: '47b3adae',
|
||||
@@ -407,9 +404,7 @@ export const getDaemonSetMetricsQueryPayload = (
|
||||
type: 'tag',
|
||||
},
|
||||
op: '=',
|
||||
value:
|
||||
daemonSet.meta?.[INFRA_MONITORING_ATTR_KEYS.K8S_NAMESPACE_NAME] ??
|
||||
'',
|
||||
value: daemonSet.meta.k8s_namespace_name,
|
||||
},
|
||||
],
|
||||
op: 'AND',
|
||||
@@ -448,9 +443,7 @@ export const getDaemonSetMetricsQueryPayload = (
|
||||
type: 'tag',
|
||||
},
|
||||
op: 'contains',
|
||||
value:
|
||||
daemonSet.meta?.[INFRA_MONITORING_ATTR_KEYS.K8S_DAEMONSET_NAME] ??
|
||||
'',
|
||||
value: daemonSet.meta.k8s_daemonset_name,
|
||||
},
|
||||
{
|
||||
id: '47b3adae',
|
||||
@@ -461,9 +454,7 @@ export const getDaemonSetMetricsQueryPayload = (
|
||||
type: 'tag',
|
||||
},
|
||||
op: '=',
|
||||
value:
|
||||
daemonSet.meta?.[INFRA_MONITORING_ATTR_KEYS.K8S_NAMESPACE_NAME] ??
|
||||
'',
|
||||
value: daemonSet.meta.k8s_namespace_name,
|
||||
},
|
||||
],
|
||||
op: 'AND',
|
||||
@@ -536,9 +527,7 @@ export const getDaemonSetMetricsQueryPayload = (
|
||||
type: 'tag',
|
||||
},
|
||||
op: '=',
|
||||
value:
|
||||
daemonSet.meta?.[INFRA_MONITORING_ATTR_KEYS.K8S_DAEMONSET_NAME] ??
|
||||
'',
|
||||
value: daemonSet.meta.k8s_daemonset_name,
|
||||
},
|
||||
{
|
||||
id: '47b3adae',
|
||||
@@ -549,9 +538,7 @@ export const getDaemonSetMetricsQueryPayload = (
|
||||
type: 'tag',
|
||||
},
|
||||
op: '=',
|
||||
value:
|
||||
daemonSet.meta?.[INFRA_MONITORING_ATTR_KEYS.K8S_NAMESPACE_NAME] ??
|
||||
'',
|
||||
value: daemonSet.meta.k8s_namespace_name,
|
||||
},
|
||||
],
|
||||
op: 'AND',
|
||||
@@ -637,9 +624,7 @@ export const getDaemonSetMetricsQueryPayload = (
|
||||
type: 'tag',
|
||||
},
|
||||
op: '=',
|
||||
value:
|
||||
daemonSet.meta?.[INFRA_MONITORING_ATTR_KEYS.K8S_DAEMONSET_NAME] ??
|
||||
'',
|
||||
value: daemonSet.meta.k8s_daemonset_name,
|
||||
},
|
||||
{
|
||||
id: '47b3adae',
|
||||
@@ -650,9 +635,7 @@ export const getDaemonSetMetricsQueryPayload = (
|
||||
type: 'tag',
|
||||
},
|
||||
op: '=',
|
||||
value:
|
||||
daemonSet.meta?.[INFRA_MONITORING_ATTR_KEYS.K8S_NAMESPACE_NAME] ??
|
||||
'',
|
||||
value: daemonSet.meta.k8s_namespace_name,
|
||||
},
|
||||
],
|
||||
op: 'AND',
|
||||
|
||||
@@ -1,49 +1,34 @@
|
||||
import { Color } from '@signozhq/design-tokens';
|
||||
import { InframonitoringtypesDaemonSetRecordDTO } from 'api/generated/services/sigNoz.schemas';
|
||||
import TanStackTable, { TableColumnDef } from 'components/TanStackTableView';
|
||||
import { Tooltip } from 'antd';
|
||||
import { TableColumnDef } from 'components/TanStackTableView';
|
||||
import TanStackTable from 'components/TanStackTableView';
|
||||
import { ExpandButtonWrapper } from 'container/InfraMonitoringK8sV2/components';
|
||||
|
||||
import ColumnHeader from '../Base/ColumnHeader';
|
||||
import EntityGroupHeader from '../Base/EntityGroupHeader';
|
||||
import K8sGroupCell from '../Base/K8sGroupCell';
|
||||
import { formatBytes, getPodPhaseStatusItems } from '../commonUtils';
|
||||
import {
|
||||
CellValueTooltip,
|
||||
EntityProgressBar,
|
||||
GroupedStatusCounts,
|
||||
ValidateColumnValueWrapper,
|
||||
} from '../components';
|
||||
import {
|
||||
INFRA_MONITORING_ATTR_KEYS,
|
||||
InfraMonitoringEntity,
|
||||
} from '../constants';
|
||||
import { formatBytes } from '../commonUtils';
|
||||
import { EntityProgressBar, ValidateColumnValueWrapper } from '../components';
|
||||
import { InfraMonitoringEntity } from '../constants';
|
||||
import { K8sDaemonSetsData } from './api';
|
||||
import { Group } from '@signozhq/icons';
|
||||
|
||||
export function getK8sDaemonSetRowKey(
|
||||
daemonSet: InframonitoringtypesDaemonSetRecordDTO,
|
||||
): string {
|
||||
export function getK8sDaemonSetRowKey(daemonSet: K8sDaemonSetsData): string {
|
||||
return (
|
||||
daemonSet.meta?.[INFRA_MONITORING_ATTR_KEYS.K8S_DAEMONSET_NAME] ||
|
||||
daemonSet.daemonSetName ||
|
||||
`${daemonSet.meta?.[INFRA_MONITORING_ATTR_KEYS.K8S_NAMESPACE_NAME]}-${daemonSet.meta?.[INFRA_MONITORING_ATTR_KEYS.K8S_DAEMONSET_NAME]}`
|
||||
daemonSet.meta.k8s_daemonset_name ||
|
||||
`${daemonSet.meta.k8s_namespace_name}-${daemonSet.meta.k8s_daemonset_name}`
|
||||
);
|
||||
}
|
||||
|
||||
export function getK8sDaemonSetItemKey(
|
||||
daemonSet: InframonitoringtypesDaemonSetRecordDTO,
|
||||
): string {
|
||||
return daemonSet.meta?.[INFRA_MONITORING_ATTR_KEYS.K8S_DAEMONSET_NAME] || '';
|
||||
export function getK8sDaemonSetItemKey(daemonSet: K8sDaemonSetsData): string {
|
||||
return daemonSet.meta.k8s_daemonset_name;
|
||||
}
|
||||
|
||||
export type DaemonSetTableColumnConfig =
|
||||
TableColumnDef<InframonitoringtypesDaemonSetRecordDTO>;
|
||||
export const k8sDaemonSetsColumnsConfig: DaemonSetTableColumnConfig[] = [
|
||||
export const k8sDaemonSetsColumnsConfig: TableColumnDef<K8sDaemonSetsData>[] = [
|
||||
{
|
||||
id: 'daemonSetGroup',
|
||||
header: (): React.ReactNode => <EntityGroupHeader title="DaemonSet Group" />,
|
||||
accessorFn: (row): string =>
|
||||
row.meta?.[INFRA_MONITORING_ATTR_KEYS.K8S_DAEMONSET_NAME] || '',
|
||||
width: { min: 290 },
|
||||
header: (): React.ReactNode => <EntityGroupHeader title="DAEMONSET GROUP" />,
|
||||
accessorFn: (row): string => row.meta.k8s_daemonset_name || '',
|
||||
width: { min: 300 },
|
||||
enableSort: false,
|
||||
enableRemove: false,
|
||||
enableMove: false,
|
||||
@@ -66,11 +51,9 @@ export const k8sDaemonSetsColumnsConfig: DaemonSetTableColumnConfig[] = [
|
||||
<EntityGroupHeader
|
||||
title="DaemonSet Name"
|
||||
icon={<Group data-hide-expanded="true" size={14} />}
|
||||
docPath="/infrastructure-monitoring/kubernetes/daemonsets#daemonset-name"
|
||||
/>
|
||||
),
|
||||
accessorFn: (row): string =>
|
||||
row.meta?.[INFRA_MONITORING_ATTR_KEYS.K8S_DAEMONSET_NAME] || '',
|
||||
accessorFn: (row): string => row.meta.k8s_daemonset_name || '',
|
||||
width: { min: 290 },
|
||||
enableSort: false,
|
||||
enableRemove: false,
|
||||
@@ -80,98 +63,71 @@ export const k8sDaemonSetsColumnsConfig: DaemonSetTableColumnConfig[] = [
|
||||
cell: ({ value }): React.ReactNode => {
|
||||
const daemonsetName = value as string;
|
||||
return (
|
||||
<CellValueTooltip value={daemonsetName}>
|
||||
<Tooltip title={daemonsetName}>
|
||||
<TanStackTable.Text>{daemonsetName}</TanStackTable.Text>
|
||||
</CellValueTooltip>
|
||||
</Tooltip>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'namespaceName',
|
||||
header: (): React.ReactNode => (
|
||||
<ColumnHeader docPath="/infrastructure-monitoring/kubernetes/daemonsets#namespace-name">
|
||||
Namespace
|
||||
</ColumnHeader>
|
||||
),
|
||||
accessorFn: (row): string =>
|
||||
row.meta?.[INFRA_MONITORING_ATTR_KEYS.K8S_NAMESPACE_NAME] || '',
|
||||
width: { min: 160 },
|
||||
header: 'Namespace Name',
|
||||
accessorFn: (row): string => row.meta.k8s_namespace_name || '',
|
||||
width: { default: 100 },
|
||||
enableSort: false,
|
||||
enableResize: true,
|
||||
cell: ({ value }): React.ReactNode => {
|
||||
const namespaceName = value as string;
|
||||
return (
|
||||
<CellValueTooltip value={namespaceName}>
|
||||
<Tooltip title={namespaceName}>
|
||||
<TanStackTable.Text>{namespaceName}</TanStackTable.Text>
|
||||
</CellValueTooltip>
|
||||
</Tooltip>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'pod_counts_by_phase',
|
||||
header: (): React.ReactNode => (
|
||||
<ColumnHeader docPath="/infrastructure-monitoring/kubernetes/daemonsets#pod-counts-by-phase">
|
||||
Pod Phases
|
||||
</ColumnHeader>
|
||||
),
|
||||
accessorFn: (
|
||||
row,
|
||||
): InframonitoringtypesDaemonSetRecordDTO['podCountsByPhase'] =>
|
||||
row.podCountsByPhase,
|
||||
width: { min: 250 },
|
||||
enableSort: false,
|
||||
enableResize: true,
|
||||
cell: ({ row }): React.ReactNode => {
|
||||
const podCountsByPhase = row.podCountsByPhase;
|
||||
if (!podCountsByPhase) {
|
||||
return <TanStackTable.Text>-</TanStackTable.Text>;
|
||||
}
|
||||
id: 'available_nodes',
|
||||
header: 'Available',
|
||||
accessorFn: (row): number => row.availableNodes,
|
||||
width: { min: 140 },
|
||||
enableSort: true,
|
||||
cell: ({ value }): React.ReactNode => {
|
||||
const availableNodes = value as number;
|
||||
return (
|
||||
<GroupedStatusCounts items={getPodPhaseStatusItems(podCountsByPhase)} />
|
||||
<ValidateColumnValueWrapper
|
||||
value={availableNodes}
|
||||
entity={InfraMonitoringEntity.DAEMONSETS}
|
||||
attribute="available node"
|
||||
>
|
||||
<TanStackTable.Text>{availableNodes}</TanStackTable.Text>
|
||||
</ValidateColumnValueWrapper>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'node_status',
|
||||
header: (): React.ReactNode => (
|
||||
<ColumnHeader docPath="/infrastructure-monitoring/kubernetes/daemonsets#node-status">
|
||||
Node Status
|
||||
</ColumnHeader>
|
||||
),
|
||||
accessorFn: (row): number => row.currentNodes,
|
||||
width: { min: 180 },
|
||||
enableSort: false,
|
||||
enableResize: true,
|
||||
cell: ({ row }): React.ReactNode => (
|
||||
<GroupedStatusCounts
|
||||
items={[
|
||||
{
|
||||
value: row.currentNodes,
|
||||
label: 'Current',
|
||||
color: Color.BG_FOREST_500,
|
||||
},
|
||||
{
|
||||
value: row.desiredNodes,
|
||||
label: 'Desired',
|
||||
color: Color.BG_ROBIN_500,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
),
|
||||
id: 'desired_nodes',
|
||||
header: 'Desired',
|
||||
accessorFn: (row): number => row.desiredNodes,
|
||||
width: { min: 140 },
|
||||
enableSort: true,
|
||||
cell: ({ value }): React.ReactNode => {
|
||||
const desiredNodes = value as number;
|
||||
return (
|
||||
<ValidateColumnValueWrapper
|
||||
value={desiredNodes}
|
||||
entity={InfraMonitoringEntity.DAEMONSETS}
|
||||
attribute="desired node"
|
||||
>
|
||||
<TanStackTable.Text>{desiredNodes}</TanStackTable.Text>
|
||||
</ValidateColumnValueWrapper>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'cpu_request',
|
||||
header: (): React.ReactNode => (
|
||||
<ColumnHeader docPath="/infrastructure-monitoring/kubernetes/daemonsets#cpu-req-usage-">
|
||||
CPU Request
|
||||
<br /> Usage (%)
|
||||
</ColumnHeader>
|
||||
),
|
||||
accessorFn: (row): number => row.daemonSetCPURequest,
|
||||
header: 'CPU Req Usage (%)',
|
||||
accessorFn: (row): number => row.cpuRequest,
|
||||
width: { min: 200, default: 200 },
|
||||
enableSort: true,
|
||||
enableResize: true,
|
||||
defaultVisibility: false,
|
||||
cell: ({ value }): React.ReactNode => {
|
||||
const cpuRequest = value as number;
|
||||
return (
|
||||
@@ -187,16 +143,10 @@ export const k8sDaemonSetsColumnsConfig: DaemonSetTableColumnConfig[] = [
|
||||
},
|
||||
{
|
||||
id: 'cpu_limit',
|
||||
header: (): React.ReactNode => (
|
||||
<ColumnHeader docPath="/infrastructure-monitoring/kubernetes/daemonsets#cpu-limit-usage-">
|
||||
CPU Limit
|
||||
<br /> Usage (%)
|
||||
</ColumnHeader>
|
||||
),
|
||||
accessorFn: (row): number => row.daemonSetCPULimit,
|
||||
width: { min: 160 },
|
||||
header: 'CPU Limit Usage (%)',
|
||||
accessorFn: (row): number => row.cpuLimit,
|
||||
width: { min: 200, default: 200 },
|
||||
enableSort: true,
|
||||
enableResize: true,
|
||||
cell: ({ value }): React.ReactNode => {
|
||||
const cpuLimit = value as number;
|
||||
return (
|
||||
@@ -212,42 +162,30 @@ export const k8sDaemonSetsColumnsConfig: DaemonSetTableColumnConfig[] = [
|
||||
},
|
||||
{
|
||||
id: 'cpu',
|
||||
header: (): React.ReactNode => (
|
||||
<ColumnHeader docPath="/infrastructure-monitoring/kubernetes/daemonsets#cpu-usage-cores">
|
||||
CPU Usage
|
||||
<br /> (cores)
|
||||
</ColumnHeader>
|
||||
),
|
||||
accessorFn: (row): number => row.daemonSetCPU,
|
||||
width: { min: 160 },
|
||||
header: 'CPU Usage (cores)',
|
||||
accessorFn: (row): number => row.cpuUsage,
|
||||
width: { min: 190 },
|
||||
enableSort: true,
|
||||
enableResize: true,
|
||||
defaultVisibility: false,
|
||||
cell: ({ value }): React.ReactNode => {
|
||||
const cpu = Number(value);
|
||||
const cpu = value as number;
|
||||
return (
|
||||
<ValidateColumnValueWrapper
|
||||
value={cpu}
|
||||
entity={InfraMonitoringEntity.DAEMONSETS}
|
||||
attribute="CPU metric"
|
||||
>
|
||||
<TanStackTable.Text>{cpu.toFixed(2)}</TanStackTable.Text>
|
||||
<TanStackTable.Text>{cpu}</TanStackTable.Text>
|
||||
</ValidateColumnValueWrapper>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'memory_request',
|
||||
header: (): React.ReactNode => (
|
||||
<ColumnHeader docPath="/infrastructure-monitoring/kubernetes/daemonsets#mem-req-usage-">
|
||||
Memory Request
|
||||
<br /> Usage (%)
|
||||
</ColumnHeader>
|
||||
),
|
||||
accessorFn: (row): number => row.daemonSetMemoryRequest,
|
||||
header: 'Mem Req Usage (%)',
|
||||
accessorFn: (row): number => row.memoryRequest,
|
||||
width: { min: 190 },
|
||||
enableSort: true,
|
||||
enableResize: true,
|
||||
defaultVisibility: false,
|
||||
cell: ({ value }): React.ReactNode => {
|
||||
const memoryRequest = value as number;
|
||||
@@ -264,16 +202,10 @@ export const k8sDaemonSetsColumnsConfig: DaemonSetTableColumnConfig[] = [
|
||||
},
|
||||
{
|
||||
id: 'memory_limit',
|
||||
header: (): React.ReactNode => (
|
||||
<ColumnHeader docPath="/infrastructure-monitoring/kubernetes/daemonsets#mem-limit-usage-">
|
||||
Memory Limit
|
||||
<br /> Usage (%)
|
||||
</ColumnHeader>
|
||||
),
|
||||
accessorFn: (row): number => row.daemonSetMemoryLimit,
|
||||
width: { min: 190 },
|
||||
header: 'Mem Limit Usage (%)',
|
||||
accessorFn: (row): number => row.memoryLimit,
|
||||
width: { min: 180 },
|
||||
enableSort: true,
|
||||
enableResize: true,
|
||||
cell: ({ value }): React.ReactNode => {
|
||||
const memoryLimit = value as number;
|
||||
return (
|
||||
@@ -289,16 +221,10 @@ export const k8sDaemonSetsColumnsConfig: DaemonSetTableColumnConfig[] = [
|
||||
},
|
||||
{
|
||||
id: 'memory',
|
||||
header: (): React.ReactNode => (
|
||||
<ColumnHeader docPath="/infrastructure-monitoring/kubernetes/daemonsets#mem-usage-wss">
|
||||
Memory Usage
|
||||
<br /> (WSS)
|
||||
</ColumnHeader>
|
||||
),
|
||||
accessorFn: (row): number => row.daemonSetMemory,
|
||||
width: { min: 180 },
|
||||
header: 'Mem Usage (WSS)',
|
||||
accessorFn: (row): number => row.memoryUsage,
|
||||
width: { min: 160 },
|
||||
enableSort: true,
|
||||
enableResize: true,
|
||||
cell: ({ value }): React.ReactNode => {
|
||||
const memory = value as number;
|
||||
return (
|
||||
@@ -312,52 +238,4 @@ export const k8sDaemonSetsColumnsConfig: DaemonSetTableColumnConfig[] = [
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'current_nodes',
|
||||
header: (): React.ReactNode => (
|
||||
<ColumnHeader docPath="/infrastructure-monitoring/kubernetes/daemonsets#current">
|
||||
Current Nodes
|
||||
</ColumnHeader>
|
||||
),
|
||||
accessorFn: (row): number => row.currentNodes,
|
||||
width: { min: 140 },
|
||||
enableSort: true,
|
||||
defaultVisibility: false,
|
||||
cell: ({ value }): React.ReactNode => {
|
||||
const currentNodes = value as number;
|
||||
return (
|
||||
<ValidateColumnValueWrapper
|
||||
value={currentNodes}
|
||||
entity={InfraMonitoringEntity.DAEMONSETS}
|
||||
attribute="current node"
|
||||
>
|
||||
<TanStackTable.Text>{currentNodes}</TanStackTable.Text>
|
||||
</ValidateColumnValueWrapper>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'desired_nodes',
|
||||
header: (): React.ReactNode => (
|
||||
<ColumnHeader docPath="/infrastructure-monitoring/kubernetes/daemonsets#desired">
|
||||
Desired Nodes
|
||||
</ColumnHeader>
|
||||
),
|
||||
accessorFn: (row): number => row.desiredNodes,
|
||||
width: { min: 140 },
|
||||
enableSort: true,
|
||||
defaultVisibility: false,
|
||||
cell: ({ value }): React.ReactNode => {
|
||||
const desiredNodes = value as number;
|
||||
return (
|
||||
<ValidateColumnValueWrapper
|
||||
value={desiredNodes}
|
||||
entity={InfraMonitoringEntity.DAEMONSETS}
|
||||
attribute="desired node"
|
||||
>
|
||||
<TanStackTable.Text>{desiredNodes}</TanStackTable.Text>
|
||||
</ValidateColumnValueWrapper>
|
||||
);
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
@@ -1,24 +1,21 @@
|
||||
import { useCallback } from 'react';
|
||||
import { listDeployments } from 'api/generated/services/inframonitoring';
|
||||
import {
|
||||
InframonitoringtypesDeploymentRecordDTO,
|
||||
InframonitoringtypesResponseTypeDTO,
|
||||
Querybuildertypesv5OrderDirectionDTO,
|
||||
} from 'api/generated/services/sigNoz.schemas';
|
||||
import React, { useCallback } from 'react';
|
||||
import { InfraMonitoringEvents } from 'constants/events';
|
||||
import { FeatureKeys } from 'constants/features';
|
||||
import { useAppContext } from 'providers/App/App';
|
||||
|
||||
import K8sBaseDetails, { K8sDetailsFilters } from '../Base/K8sBaseDetails';
|
||||
import { K8sBaseList } from '../Base/K8sBaseList';
|
||||
import { K8sBaseFilters } from '../Base/types';
|
||||
import { InfraMonitoringEntity } from '../constants';
|
||||
import { getK8sDeploymentsList, K8sDeploymentsData } from './api';
|
||||
import {
|
||||
deploymentWidgetInfo,
|
||||
getDeploymentMetricsQueryPayload,
|
||||
k8sDeploymentDetailsMetadataConfig,
|
||||
k8sDeploymentGetEntityName,
|
||||
k8sDeploymentGetSelectedItemExpression,
|
||||
k8sDeploymentInitialEventsExpression,
|
||||
k8sDeploymentInitialLogTracesExpression,
|
||||
k8sDeploymentGetSelectedItemFilters,
|
||||
k8sDeploymentInitialEventsFilter,
|
||||
k8sDeploymentInitialLogTracesFilter,
|
||||
} from './constants';
|
||||
import {
|
||||
getK8sDeploymentItemKey,
|
||||
@@ -31,93 +28,66 @@ function K8sDeploymentsList({
|
||||
}: {
|
||||
controlListPrefix?: React.ReactNode;
|
||||
}): JSX.Element {
|
||||
const { featureFlags } = useAppContext();
|
||||
const dotMetricsEnabled =
|
||||
featureFlags?.find((flag) => flag.name === FeatureKeys.DOT_METRICS_ENABLED)
|
||||
?.active || false;
|
||||
|
||||
const fetchListData = useCallback(
|
||||
async (filters: K8sBaseFilters, signal?: AbortSignal) => {
|
||||
try {
|
||||
const response = await listDeployments(
|
||||
{
|
||||
filter: { expression: filters.filter.expression },
|
||||
groupBy: filters.groupBy?.map((g) => ({ name: g.name })),
|
||||
offset: filters.offset,
|
||||
limit: filters.limit ?? 10,
|
||||
start: filters.start,
|
||||
end: filters.end,
|
||||
orderBy: filters.orderBy
|
||||
? {
|
||||
key: { name: filters.orderBy.key.name },
|
||||
direction:
|
||||
filters.orderBy.direction === 'asc'
|
||||
? Querybuildertypesv5OrderDirectionDTO.asc
|
||||
: Querybuildertypesv5OrderDirectionDTO.desc,
|
||||
}
|
||||
: undefined,
|
||||
},
|
||||
signal,
|
||||
);
|
||||
filters.orderBy ||= {
|
||||
columnName: 'cpu',
|
||||
order: 'desc',
|
||||
};
|
||||
|
||||
const data = response.data;
|
||||
return {
|
||||
type:
|
||||
data.type === InframonitoringtypesResponseTypeDTO.grouped_list
|
||||
? ('grouped_list' as const)
|
||||
: ('list' as const),
|
||||
records: data.records,
|
||||
total: data.total,
|
||||
endTimeBeforeRetention: data.endTimeBeforeRetention,
|
||||
warning: data.warning,
|
||||
};
|
||||
} catch (error) {
|
||||
const errMsg =
|
||||
error instanceof Error ? error.message : 'Failed to fetch deployments';
|
||||
return {
|
||||
type: 'list' as const,
|
||||
records: [] as InframonitoringtypesDeploymentRecordDTO[],
|
||||
total: 0,
|
||||
error: errMsg,
|
||||
};
|
||||
}
|
||||
const response = await getK8sDeploymentsList(
|
||||
filters,
|
||||
signal,
|
||||
undefined,
|
||||
dotMetricsEnabled,
|
||||
);
|
||||
|
||||
return {
|
||||
data: response.payload?.data.records || [],
|
||||
total: response.payload?.data.total || 0,
|
||||
error: response.error,
|
||||
rawData: response.payload?.data,
|
||||
};
|
||||
},
|
||||
[],
|
||||
[dotMetricsEnabled],
|
||||
);
|
||||
|
||||
const fetchEntityData = useCallback(
|
||||
async (
|
||||
filters: K8sDetailsFilters,
|
||||
signal?: AbortSignal,
|
||||
): Promise<{
|
||||
data: InframonitoringtypesDeploymentRecordDTO | null;
|
||||
error?: string | null;
|
||||
}> => {
|
||||
try {
|
||||
const response = await listDeployments(
|
||||
{
|
||||
filter: { expression: filters.filter.expression },
|
||||
start: filters.start,
|
||||
end: filters.end,
|
||||
limit: 1,
|
||||
offset: 0,
|
||||
},
|
||||
signal,
|
||||
);
|
||||
): Promise<{ data: K8sDeploymentsData | null; error?: string | null }> => {
|
||||
const response = await getK8sDeploymentsList(
|
||||
{
|
||||
filters: filters.filters,
|
||||
start: filters.start,
|
||||
end: filters.end,
|
||||
limit: 1,
|
||||
offset: 0,
|
||||
},
|
||||
signal,
|
||||
undefined,
|
||||
dotMetricsEnabled,
|
||||
);
|
||||
|
||||
return {
|
||||
data: response.data.records.length > 0 ? response.data.records[0] : null,
|
||||
};
|
||||
} catch (error) {
|
||||
const errMsg =
|
||||
error instanceof Error ? error.message : 'Failed to fetch deployment';
|
||||
return {
|
||||
data: null,
|
||||
error: errMsg,
|
||||
};
|
||||
}
|
||||
const records = response.payload?.data.records || [];
|
||||
|
||||
return {
|
||||
data: records.length > 0 ? records[0] : null,
|
||||
error: response.error,
|
||||
};
|
||||
},
|
||||
[],
|
||||
[dotMetricsEnabled],
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<K8sBaseList<InframonitoringtypesDeploymentRecordDTO>
|
||||
<K8sBaseList<K8sDeploymentsData>
|
||||
controlListPrefix={controlListPrefix}
|
||||
entity={InfraMonitoringEntity.DEPLOYMENTS}
|
||||
tableColumns={k8sDeploymentsColumnsConfig}
|
||||
@@ -127,14 +97,14 @@ function K8sDeploymentsList({
|
||||
eventCategory={InfraMonitoringEvents.Deployment}
|
||||
/>
|
||||
|
||||
<K8sBaseDetails<InframonitoringtypesDeploymentRecordDTO>
|
||||
<K8sBaseDetails<K8sDeploymentsData>
|
||||
category={InfraMonitoringEntity.DEPLOYMENTS}
|
||||
eventCategory={InfraMonitoringEvents.Deployment}
|
||||
getSelectedItemExpression={k8sDeploymentGetSelectedItemExpression}
|
||||
getSelectedItemFilters={k8sDeploymentGetSelectedItemFilters}
|
||||
fetchEntityData={fetchEntityData}
|
||||
getEntityName={k8sDeploymentGetEntityName}
|
||||
getInitialLogTracesExpression={k8sDeploymentInitialLogTracesExpression}
|
||||
getInitialEventsExpression={k8sDeploymentInitialEventsExpression}
|
||||
getInitialLogTracesFilters={k8sDeploymentInitialLogTracesFilter}
|
||||
getInitialEventsFilters={k8sDeploymentInitialEventsFilter}
|
||||
metadataConfig={k8sDeploymentDetailsMetadataConfig}
|
||||
entityWidgetInfo={deploymentWidgetInfo}
|
||||
getEntityQueryPayload={getDeploymentMetricsQueryPayload}
|
||||
|
||||
132
frontend/src/container/InfraMonitoringK8sV2/Deployments/api.ts
Normal file
132
frontend/src/container/InfraMonitoringK8sV2/Deployments/api.ts
Normal file
@@ -0,0 +1,132 @@
|
||||
import axios from 'api';
|
||||
import { ErrorResponseHandler } from 'api/ErrorResponseHandler';
|
||||
import { UnderscoreToDotMap } from 'api/utils';
|
||||
import { AxiosError } from 'axios';
|
||||
import { ErrorResponse, SuccessResponse } from 'types/api';
|
||||
import { BaseAutocompleteData } from 'types/api/queryBuilder/queryAutocompleteResponse';
|
||||
import { TagFilter } from 'types/api/queryBuilder/queryBuilderData';
|
||||
|
||||
import { K8sBaseFilters } from '../Base/types';
|
||||
|
||||
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;
|
||||
};
|
||||
}
|
||||
|
||||
// TODO(H4ad): Erase this whole file when migrating to openapi
|
||||
export const deploymentsMetaMap = [
|
||||
{ dot: 'k8s.cluster.name', under: 'k8s_cluster_name' },
|
||||
{ dot: 'k8s.deployment.name', under: 'k8s_deployment_name' },
|
||||
{ dot: 'k8s.namespace.name', under: 'k8s_namespace_name' },
|
||||
] as const;
|
||||
|
||||
export function mapDeploymentsMeta(
|
||||
raw: Record<string, unknown>,
|
||||
): K8sDeploymentsData['meta'] {
|
||||
const out: Record<string, unknown> = { ...raw };
|
||||
deploymentsMetaMap.forEach(({ dot, under }) => {
|
||||
if (dot in raw) {
|
||||
const v = raw[dot];
|
||||
out[under] = typeof v === 'string' ? v : raw[under];
|
||||
}
|
||||
});
|
||||
return out as K8sDeploymentsData['meta'];
|
||||
}
|
||||
|
||||
export const getK8sDeploymentsList = async (
|
||||
props: K8sBaseFilters,
|
||||
signal?: AbortSignal,
|
||||
headers?: Record<string, string>,
|
||||
dotMetricsEnabled = false,
|
||||
): Promise<SuccessResponse<K8sDeploymentsListResponse> | ErrorResponse> => {
|
||||
try {
|
||||
const requestProps = dotMetricsEnabled
|
||||
? {
|
||||
...props,
|
||||
filters: {
|
||||
...props.filters,
|
||||
items: props.filters.items.reduce<typeof props.filters.items>(
|
||||
(acc, item) => {
|
||||
if (item.value === undefined) {
|
||||
return acc;
|
||||
}
|
||||
if (
|
||||
item.key &&
|
||||
typeof item.key === 'object' &&
|
||||
'key' in item.key &&
|
||||
typeof item.key.key === 'string'
|
||||
) {
|
||||
const mappedKey = UnderscoreToDotMap[item.key.key] ?? item.key.key;
|
||||
acc.push({
|
||||
...item,
|
||||
key: { ...item.key, key: mappedKey },
|
||||
});
|
||||
} else {
|
||||
acc.push(item);
|
||||
}
|
||||
return acc;
|
||||
},
|
||||
[] as typeof props.filters.items,
|
||||
),
|
||||
},
|
||||
}
|
||||
: props;
|
||||
|
||||
const response = await axios.post('/deployments/list', requestProps, {
|
||||
signal,
|
||||
headers,
|
||||
});
|
||||
const payload: K8sDeploymentsListResponse = response.data;
|
||||
|
||||
payload.data.records = payload.data.records.map((record) => ({
|
||||
...record,
|
||||
meta: mapDeploymentsMeta(record.meta as Record<string, unknown>),
|
||||
}));
|
||||
|
||||
return {
|
||||
statusCode: 200,
|
||||
error: null,
|
||||
message: 'Success',
|
||||
payload,
|
||||
params: requestProps,
|
||||
};
|
||||
} catch (error) {
|
||||
return ErrorResponseHandler(error as AxiosError);
|
||||
}
|
||||
};
|
||||
@@ -1,63 +1,70 @@
|
||||
import { InframonitoringtypesDeploymentRecordDTO } from 'api/generated/services/sigNoz.schemas';
|
||||
import { PANEL_TYPES } from 'constants/queryBuilder';
|
||||
import { GetQueryResultsProps } from 'lib/dashboard/getQueryResults';
|
||||
import { DataTypes } from 'types/api/queryBuilder/queryAutocompleteResponse';
|
||||
import { TagFilter } from 'types/api/queryBuilder/queryBuilderData';
|
||||
import { EQueryType } from 'types/common/dashboard';
|
||||
import { DataSource, ReduceOperators } from 'types/common/queryBuilder';
|
||||
import { v4 } from 'uuid';
|
||||
|
||||
import { K8sDetailsMetadataConfig } from '../Base/K8sBaseDetails';
|
||||
import { formatValueForExpression } from 'components/QueryBuilderV2/utils';
|
||||
import { INFRA_MONITORING_ATTR_KEYS } from '../constants';
|
||||
import {
|
||||
createFilterItem,
|
||||
K8sDetailsMetadataConfig,
|
||||
} from '../Base/K8sBaseDetails';
|
||||
import { QUERY_KEYS } from '../EntityDetailsUtils/utils';
|
||||
import { K8sDeploymentsData } from './api';
|
||||
|
||||
export const k8sDeploymentGetSelectedItemExpression = (
|
||||
export const k8sDeploymentGetSelectedItemFilters = (
|
||||
selectedItemId: string,
|
||||
): string =>
|
||||
`${INFRA_MONITORING_ATTR_KEYS.K8S_DEPLOYMENT_NAME} = ${formatValueForExpression(selectedItemId)}`;
|
||||
): TagFilter => ({
|
||||
op: 'AND',
|
||||
items: [
|
||||
{
|
||||
id: 'k8s_deployment_name',
|
||||
key: {
|
||||
key: 'k8s_deployment_name',
|
||||
type: null,
|
||||
},
|
||||
op: '=',
|
||||
value: selectedItemId,
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
export const k8sDeploymentDetailsMetadataConfig: K8sDetailsMetadataConfig<InframonitoringtypesDeploymentRecordDTO>[] =
|
||||
export const k8sDeploymentDetailsMetadataConfig: K8sDetailsMetadataConfig<K8sDeploymentsData>[] =
|
||||
[
|
||||
{
|
||||
label: 'Deployment Name',
|
||||
getValue: (p): string =>
|
||||
p.meta?.[INFRA_MONITORING_ATTR_KEYS.K8S_DEPLOYMENT_NAME] ?? '',
|
||||
getValue: (p): string => p.meta.k8s_deployment_name,
|
||||
},
|
||||
{
|
||||
label: 'Cluster Name',
|
||||
getValue: (p): string =>
|
||||
p.meta?.[INFRA_MONITORING_ATTR_KEYS.K8S_CLUSTER_NAME] ?? '',
|
||||
getValue: (p): string => p.meta.k8s_cluster_name,
|
||||
},
|
||||
{
|
||||
label: 'Namespace Name',
|
||||
getValue: (p): string =>
|
||||
p.meta?.[INFRA_MONITORING_ATTR_KEYS.K8S_NAMESPACE_NAME] ?? '',
|
||||
getValue: (p): string => p.meta.k8s_namespace_name,
|
||||
},
|
||||
];
|
||||
|
||||
export const k8sDeploymentInitialEventsExpression = (
|
||||
item: InframonitoringtypesDeploymentRecordDTO,
|
||||
): string => {
|
||||
const objectName = formatValueForExpression(
|
||||
item.meta?.[INFRA_MONITORING_ATTR_KEYS.K8S_DEPLOYMENT_NAME] ?? '',
|
||||
);
|
||||
return `${INFRA_MONITORING_ATTR_KEYS.K8S_OBJECT_KIND} = 'Deployment' AND ${INFRA_MONITORING_ATTR_KEYS.K8S_OBJECT_NAME} = ${objectName}`;
|
||||
};
|
||||
export const k8sDeploymentInitialEventsFilter = (
|
||||
item: K8sDeploymentsData,
|
||||
): ReturnType<typeof createFilterItem>[] => [
|
||||
createFilterItem(QUERY_KEYS.K8S_OBJECT_KIND, 'Deployment'),
|
||||
createFilterItem(QUERY_KEYS.K8S_OBJECT_NAME, item.meta.k8s_deployment_name),
|
||||
];
|
||||
|
||||
export const k8sDeploymentInitialLogTracesExpression = (
|
||||
item: InframonitoringtypesDeploymentRecordDTO,
|
||||
): string => {
|
||||
const deploymentName = formatValueForExpression(
|
||||
item.meta?.[INFRA_MONITORING_ATTR_KEYS.K8S_DEPLOYMENT_NAME] ?? '',
|
||||
);
|
||||
const namespaceName = formatValueForExpression(
|
||||
item.meta?.[INFRA_MONITORING_ATTR_KEYS.K8S_NAMESPACE_NAME] ?? '',
|
||||
);
|
||||
return `${INFRA_MONITORING_ATTR_KEYS.K8S_DEPLOYMENT_NAME} = ${deploymentName} AND ${INFRA_MONITORING_ATTR_KEYS.K8S_NAMESPACE_NAME} = ${namespaceName}`;
|
||||
};
|
||||
export const k8sDeploymentInitialLogTracesFilter = (
|
||||
item: K8sDeploymentsData,
|
||||
): ReturnType<typeof createFilterItem>[] => [
|
||||
createFilterItem(
|
||||
QUERY_KEYS.K8S_DEPLOYMENT_NAME,
|
||||
item.meta.k8s_deployment_name,
|
||||
),
|
||||
createFilterItem(QUERY_KEYS.K8S_NAMESPACE_NAME, item.meta.k8s_namespace_name),
|
||||
];
|
||||
|
||||
export const k8sDeploymentGetEntityName = (
|
||||
item: InframonitoringtypesDeploymentRecordDTO,
|
||||
): string => item.meta?.[INFRA_MONITORING_ATTR_KEYS.K8S_DEPLOYMENT_NAME] ?? '';
|
||||
export const k8sDeploymentGetEntityName = (item: K8sDeploymentsData): string =>
|
||||
item.meta.k8s_deployment_name;
|
||||
|
||||
export const deploymentWidgetInfo = [
|
||||
{
|
||||
@@ -79,7 +86,7 @@ export const deploymentWidgetInfo = [
|
||||
];
|
||||
|
||||
export const getDeploymentMetricsQueryPayload = (
|
||||
deployment: InframonitoringtypesDeploymentRecordDTO,
|
||||
deployment: K8sDeploymentsData,
|
||||
start: number,
|
||||
end: number,
|
||||
dotMetricsEnabled: boolean,
|
||||
@@ -151,9 +158,7 @@ export const getDeploymentMetricsQueryPayload = (
|
||||
type: 'tag',
|
||||
},
|
||||
op: '=',
|
||||
value:
|
||||
deployment.meta?.[INFRA_MONITORING_ATTR_KEYS.K8S_DEPLOYMENT_NAME] ??
|
||||
'',
|
||||
value: deployment.meta.k8s_deployment_name,
|
||||
},
|
||||
],
|
||||
op: 'AND',
|
||||
@@ -192,9 +197,7 @@ export const getDeploymentMetricsQueryPayload = (
|
||||
type: 'tag',
|
||||
},
|
||||
op: 'contains',
|
||||
value:
|
||||
deployment.meta?.[INFRA_MONITORING_ATTR_KEYS.K8S_DEPLOYMENT_NAME] ??
|
||||
'',
|
||||
value: deployment.meta.k8s_deployment_name,
|
||||
},
|
||||
],
|
||||
op: 'AND',
|
||||
@@ -233,9 +236,7 @@ export const getDeploymentMetricsQueryPayload = (
|
||||
type: 'tag',
|
||||
},
|
||||
op: 'contains',
|
||||
value:
|
||||
deployment.meta?.[INFRA_MONITORING_ATTR_KEYS.K8S_DEPLOYMENT_NAME] ??
|
||||
'',
|
||||
value: deployment.meta.k8s_deployment_name,
|
||||
},
|
||||
],
|
||||
op: 'AND',
|
||||
@@ -308,9 +309,7 @@ export const getDeploymentMetricsQueryPayload = (
|
||||
type: 'tag',
|
||||
},
|
||||
op: '=',
|
||||
value:
|
||||
deployment.meta?.[INFRA_MONITORING_ATTR_KEYS.K8S_DEPLOYMENT_NAME] ??
|
||||
'',
|
||||
value: deployment.meta.k8s_deployment_name,
|
||||
},
|
||||
],
|
||||
op: 'AND',
|
||||
@@ -349,9 +348,7 @@ export const getDeploymentMetricsQueryPayload = (
|
||||
type: 'tag',
|
||||
},
|
||||
op: 'contains',
|
||||
value:
|
||||
deployment.meta?.[INFRA_MONITORING_ATTR_KEYS.K8S_DEPLOYMENT_NAME] ??
|
||||
'',
|
||||
value: deployment.meta.k8s_deployment_name,
|
||||
},
|
||||
],
|
||||
op: 'AND',
|
||||
@@ -390,9 +387,7 @@ export const getDeploymentMetricsQueryPayload = (
|
||||
type: 'tag',
|
||||
},
|
||||
op: 'contains',
|
||||
value:
|
||||
deployment.meta?.[INFRA_MONITORING_ATTR_KEYS.K8S_DEPLOYMENT_NAME] ??
|
||||
'',
|
||||
value: deployment.meta.k8s_deployment_name,
|
||||
},
|
||||
],
|
||||
op: 'AND',
|
||||
@@ -465,9 +460,7 @@ export const getDeploymentMetricsQueryPayload = (
|
||||
type: 'tag',
|
||||
},
|
||||
op: '=',
|
||||
value:
|
||||
deployment.meta?.[INFRA_MONITORING_ATTR_KEYS.K8S_DEPLOYMENT_NAME] ??
|
||||
'',
|
||||
value: deployment.meta.k8s_deployment_name,
|
||||
},
|
||||
],
|
||||
op: 'AND',
|
||||
@@ -553,9 +546,7 @@ export const getDeploymentMetricsQueryPayload = (
|
||||
type: 'tag',
|
||||
},
|
||||
op: '=',
|
||||
value:
|
||||
deployment.meta?.[INFRA_MONITORING_ATTR_KEYS.K8S_DEPLOYMENT_NAME] ??
|
||||
'',
|
||||
value: deployment.meta.k8s_deployment_name,
|
||||
},
|
||||
],
|
||||
op: 'AND',
|
||||
|
||||
@@ -1,50 +1,35 @@
|
||||
import { Color } from '@signozhq/design-tokens';
|
||||
import { InframonitoringtypesDeploymentRecordDTO } from 'api/generated/services/sigNoz.schemas';
|
||||
import TanStackTable, { TableColumnDef } from 'components/TanStackTableView';
|
||||
import { Tooltip } from 'antd';
|
||||
import { TableColumnDef } from 'components/TanStackTableView';
|
||||
import TanStackTable from 'components/TanStackTableView';
|
||||
import { ExpandButtonWrapper } from 'container/InfraMonitoringK8sV2/components';
|
||||
|
||||
import ColumnHeader from '../Base/ColumnHeader';
|
||||
import EntityGroupHeader from '../Base/EntityGroupHeader';
|
||||
import K8sGroupCell from '../Base/K8sGroupCell';
|
||||
import { formatBytes, getPodPhaseStatusItems } from '../commonUtils';
|
||||
import {
|
||||
CellValueTooltip,
|
||||
EntityProgressBar,
|
||||
GroupedStatusCounts,
|
||||
ValidateColumnValueWrapper,
|
||||
} from '../components';
|
||||
import {
|
||||
INFRA_MONITORING_ATTR_KEYS,
|
||||
InfraMonitoringEntity,
|
||||
} from '../constants';
|
||||
import { formatBytes } from '../commonUtils';
|
||||
import { EntityProgressBar, ValidateColumnValueWrapper } from '../components';
|
||||
import { InfraMonitoringEntity } from '../constants';
|
||||
import { K8sDeploymentsData } from './api';
|
||||
import { Computer } from '@signozhq/icons';
|
||||
|
||||
export function getK8sDeploymentRowKey(
|
||||
deployment: InframonitoringtypesDeploymentRecordDTO,
|
||||
): string {
|
||||
return (
|
||||
deployment.meta?.[INFRA_MONITORING_ATTR_KEYS.K8S_DEPLOYMENT_NAME] ||
|
||||
deployment.deploymentName ||
|
||||
''
|
||||
);
|
||||
export function getK8sDeploymentRowKey(deployment: K8sDeploymentsData): string {
|
||||
return deployment.meta.k8s_deployment_name || deployment.deploymentName;
|
||||
}
|
||||
|
||||
export function getK8sDeploymentItemKey(
|
||||
deployment: InframonitoringtypesDeploymentRecordDTO,
|
||||
deployment: K8sDeploymentsData,
|
||||
): string {
|
||||
return deployment.meta?.[INFRA_MONITORING_ATTR_KEYS.K8S_DEPLOYMENT_NAME] || '';
|
||||
return deployment.meta.k8s_deployment_name;
|
||||
}
|
||||
|
||||
export const k8sDeploymentsColumnsConfig: TableColumnDef<InframonitoringtypesDeploymentRecordDTO>[] =
|
||||
export const k8sDeploymentsColumnsConfig: TableColumnDef<K8sDeploymentsData>[] =
|
||||
[
|
||||
{
|
||||
id: 'deploymentGroup',
|
||||
header: (): React.ReactNode => (
|
||||
<EntityGroupHeader title="Deployment Group" />
|
||||
<EntityGroupHeader title="DEPLOYMENT GROUP" />
|
||||
),
|
||||
accessorFn: (row): string =>
|
||||
row.meta?.[INFRA_MONITORING_ATTR_KEYS.K8S_DEPLOYMENT_NAME] || '',
|
||||
width: { min: 290 },
|
||||
accessorFn: (row): string => row.meta.k8s_deployment_name || '',
|
||||
width: { min: 220 },
|
||||
enableSort: false,
|
||||
enableRemove: false,
|
||||
enableMove: false,
|
||||
@@ -67,12 +52,10 @@ export const k8sDeploymentsColumnsConfig: TableColumnDef<InframonitoringtypesDep
|
||||
<EntityGroupHeader
|
||||
title="Deployment Name"
|
||||
icon={<Computer data-hide-expanded="true" size={14} />}
|
||||
docPath="/infrastructure-monitoring/kubernetes/deployments#deployment-name"
|
||||
/>
|
||||
),
|
||||
accessorFn: (row): string =>
|
||||
row.meta?.[INFRA_MONITORING_ATTR_KEYS.K8S_DEPLOYMENT_NAME] || '',
|
||||
width: { min: 290 },
|
||||
accessorFn: (row): string => row.meta.k8s_deployment_name || '',
|
||||
width: { min: 210 },
|
||||
enableSort: false,
|
||||
enableRemove: false,
|
||||
enableMove: false,
|
||||
@@ -81,239 +64,30 @@ export const k8sDeploymentsColumnsConfig: TableColumnDef<InframonitoringtypesDep
|
||||
cell: ({ value }): React.ReactNode => {
|
||||
const deploymentName = value as string;
|
||||
return (
|
||||
<CellValueTooltip value={deploymentName}>
|
||||
<Tooltip title={deploymentName}>
|
||||
<TanStackTable.Text>{deploymentName}</TanStackTable.Text>
|
||||
</CellValueTooltip>
|
||||
</Tooltip>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'namespaceName',
|
||||
header: (): React.ReactNode => (
|
||||
<ColumnHeader docPath="/infrastructure-monitoring/kubernetes/deployments#namespace-name">
|
||||
Namespace
|
||||
</ColumnHeader>
|
||||
),
|
||||
accessorFn: (row): string =>
|
||||
row.meta?.[INFRA_MONITORING_ATTR_KEYS.K8S_NAMESPACE_NAME] || '',
|
||||
width: { min: 160 },
|
||||
header: 'Namespace Name',
|
||||
accessorFn: (row): string => row.meta.k8s_namespace_name || '',
|
||||
width: { default: 220 },
|
||||
enableSort: false,
|
||||
enableResize: true,
|
||||
cell: ({ value }): React.ReactNode => (
|
||||
<TanStackTable.Text>{value as string}</TanStackTable.Text>
|
||||
),
|
||||
},
|
||||
{
|
||||
id: 'podCountsByPhase',
|
||||
header: (): React.ReactNode => (
|
||||
<ColumnHeader docPath="/infrastructure-monitoring/kubernetes/deployments#pod-counts-by-phase">
|
||||
Pod Phases
|
||||
</ColumnHeader>
|
||||
),
|
||||
accessorFn: (row): object | undefined => row.podCountsByPhase,
|
||||
width: { min: 250 },
|
||||
enableSort: false,
|
||||
enableResize: true,
|
||||
cell: ({ row }): React.ReactNode => {
|
||||
const podCountsByPhase = row.podCountsByPhase;
|
||||
if (!podCountsByPhase) {
|
||||
return <TanStackTable.Text>-</TanStackTable.Text>;
|
||||
}
|
||||
return (
|
||||
<GroupedStatusCounts items={getPodPhaseStatusItems(podCountsByPhase)} />
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'replica_status',
|
||||
header: (): React.ReactNode => (
|
||||
<ColumnHeader docPath="/infrastructure-monitoring/kubernetes/deployments#replica-status">
|
||||
Replica Status
|
||||
</ColumnHeader>
|
||||
),
|
||||
accessorFn: (row): number => row.availablePods,
|
||||
width: { min: 180 },
|
||||
enableSort: false,
|
||||
enableResize: true,
|
||||
cell: ({ row }): React.ReactNode => (
|
||||
<GroupedStatusCounts
|
||||
items={[
|
||||
{
|
||||
value: row.availablePods,
|
||||
label: 'Available',
|
||||
color: Color.BG_FOREST_500,
|
||||
},
|
||||
{
|
||||
value: row.desiredPods,
|
||||
label: 'Desired',
|
||||
color: Color.BG_ROBIN_500,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
),
|
||||
},
|
||||
{
|
||||
id: 'cpu_request',
|
||||
header: (): React.ReactNode => (
|
||||
<ColumnHeader docPath="/infrastructure-monitoring/kubernetes/deployments#cpu-req-usage-">
|
||||
CPU Request
|
||||
<br /> Usage (%)
|
||||
</ColumnHeader>
|
||||
),
|
||||
accessorFn: (row): number => row.deploymentCPURequest,
|
||||
width: { min: 210 },
|
||||
enableSort: true,
|
||||
enableResize: true,
|
||||
defaultVisibility: false,
|
||||
cell: ({ value }): React.ReactNode => {
|
||||
const cpuRequest = value as number;
|
||||
return (
|
||||
<ValidateColumnValueWrapper
|
||||
value={cpuRequest}
|
||||
entity={InfraMonitoringEntity.DEPLOYMENTS}
|
||||
attribute="CPU Request"
|
||||
>
|
||||
<EntityProgressBar value={cpuRequest} type="request" />
|
||||
</ValidateColumnValueWrapper>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'cpu_limit',
|
||||
header: (): React.ReactNode => (
|
||||
<ColumnHeader docPath="/infrastructure-monitoring/kubernetes/deployments#cpu-limit-usage-">
|
||||
CPU Limit
|
||||
<br /> Usage (%)
|
||||
</ColumnHeader>
|
||||
),
|
||||
accessorFn: (row): number => row.deploymentCPULimit,
|
||||
width: { min: 210 },
|
||||
enableSort: true,
|
||||
enableResize: true,
|
||||
cell: ({ value }): React.ReactNode => {
|
||||
const cpuLimit = Number(value);
|
||||
return (
|
||||
<ValidateColumnValueWrapper
|
||||
value={cpuLimit}
|
||||
entity={InfraMonitoringEntity.DEPLOYMENTS}
|
||||
attribute="CPU Limit"
|
||||
>
|
||||
<EntityProgressBar value={cpuLimit} type="limit" />
|
||||
</ValidateColumnValueWrapper>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'cpu',
|
||||
header: (): React.ReactNode => (
|
||||
<ColumnHeader docPath="/infrastructure-monitoring/kubernetes/deployments#cpu-usage-cores">
|
||||
CPU Usage
|
||||
<br /> (cores)
|
||||
</ColumnHeader>
|
||||
),
|
||||
accessorFn: (row): number => row.deploymentCPU,
|
||||
width: { min: 160 },
|
||||
enableSort: true,
|
||||
enableResize: true,
|
||||
cell: ({ value }): React.ReactNode => {
|
||||
const cpu = Number(value);
|
||||
return (
|
||||
<ValidateColumnValueWrapper
|
||||
value={cpu}
|
||||
entity={InfraMonitoringEntity.DEPLOYMENTS}
|
||||
attribute="CPU metric"
|
||||
>
|
||||
<TanStackTable.Text>{cpu.toFixed(2)}</TanStackTable.Text>
|
||||
</ValidateColumnValueWrapper>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'memory_request',
|
||||
header: (): React.ReactNode => (
|
||||
<ColumnHeader docPath="/infrastructure-monitoring/kubernetes/deployments#mem-req-usage-">
|
||||
Memory Request
|
||||
<br /> Usage (%)
|
||||
</ColumnHeader>
|
||||
),
|
||||
accessorFn: (row): number => row.deploymentMemoryRequest,
|
||||
width: { min: 210 },
|
||||
enableSort: true,
|
||||
enableResize: true,
|
||||
defaultVisibility: false,
|
||||
cell: ({ value }): React.ReactNode => {
|
||||
const memoryRequest = Number(value);
|
||||
return (
|
||||
<ValidateColumnValueWrapper
|
||||
value={memoryRequest}
|
||||
entity={InfraMonitoringEntity.DEPLOYMENTS}
|
||||
attribute="Memory Request"
|
||||
>
|
||||
<EntityProgressBar value={memoryRequest} type="request" />
|
||||
</ValidateColumnValueWrapper>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'memory_limit',
|
||||
header: (): React.ReactNode => (
|
||||
<ColumnHeader docPath="/infrastructure-monitoring/kubernetes/deployments#mem-limit-usage-">
|
||||
Memory Limit
|
||||
<br /> Usage (%)
|
||||
</ColumnHeader>
|
||||
),
|
||||
accessorFn: (row): number => row.deploymentMemoryLimit,
|
||||
width: { min: 210 },
|
||||
enableSort: true,
|
||||
enableResize: true,
|
||||
cell: ({ value }): React.ReactNode => {
|
||||
const memoryLimit = Number(value);
|
||||
return (
|
||||
<ValidateColumnValueWrapper
|
||||
value={memoryLimit}
|
||||
entity={InfraMonitoringEntity.DEPLOYMENTS}
|
||||
attribute="Memory Limit"
|
||||
>
|
||||
<EntityProgressBar value={memoryLimit} type="limit" />
|
||||
</ValidateColumnValueWrapper>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'memory',
|
||||
header: (): React.ReactNode => (
|
||||
<ColumnHeader docPath="/infrastructure-monitoring/kubernetes/deployments#mem-usage-wss">
|
||||
Memory Usage
|
||||
<br /> (WSS)
|
||||
</ColumnHeader>
|
||||
),
|
||||
accessorFn: (row): number => row.deploymentMemory,
|
||||
width: { min: 180 },
|
||||
enableSort: true,
|
||||
enableResize: true,
|
||||
cell: ({ value }): React.ReactNode => {
|
||||
const memory = value as number;
|
||||
return (
|
||||
<ValidateColumnValueWrapper
|
||||
value={memory}
|
||||
entity={InfraMonitoringEntity.DEPLOYMENTS}
|
||||
attribute="memory metric"
|
||||
>
|
||||
<TanStackTable.Text>{formatBytes(memory)}</TanStackTable.Text>
|
||||
</ValidateColumnValueWrapper>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'available_pods',
|
||||
header: (): React.ReactNode => (
|
||||
<ColumnHeader docPath="/infrastructure-monitoring/kubernetes/deployments#available">
|
||||
Available Pods
|
||||
</ColumnHeader>
|
||||
),
|
||||
header: 'Available',
|
||||
accessorFn: (row): number => row.availablePods,
|
||||
width: { min: 120 },
|
||||
enableSort: true,
|
||||
width: { min: 100 },
|
||||
enableSort: false,
|
||||
enableResize: true,
|
||||
defaultVisibility: false,
|
||||
cell: ({ value }): React.ReactNode => {
|
||||
const availablePods = value as number;
|
||||
@@ -330,14 +104,11 @@ export const k8sDeploymentsColumnsConfig: TableColumnDef<InframonitoringtypesDep
|
||||
},
|
||||
{
|
||||
id: 'desired_pods',
|
||||
header: (): React.ReactNode => (
|
||||
<ColumnHeader docPath="/infrastructure-monitoring/kubernetes/deployments#desired">
|
||||
Desired Pods
|
||||
</ColumnHeader>
|
||||
),
|
||||
header: 'Desired',
|
||||
accessorFn: (row): number => row.desiredPods,
|
||||
width: { min: 120 },
|
||||
enableSort: true,
|
||||
width: { min: 80 },
|
||||
enableSort: false,
|
||||
enableResize: true,
|
||||
defaultVisibility: false,
|
||||
cell: ({ value }): React.ReactNode => {
|
||||
const desiredPods = value as number;
|
||||
@@ -352,4 +123,124 @@ export const k8sDeploymentsColumnsConfig: TableColumnDef<InframonitoringtypesDep
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'cpu_request',
|
||||
header: 'CPU Req Usage (%)',
|
||||
accessorFn: (row): number => row.cpuRequest,
|
||||
width: { min: 210 },
|
||||
enableSort: true,
|
||||
enableResize: true,
|
||||
cell: ({ value }): React.ReactNode => {
|
||||
const cpuRequest = value as number;
|
||||
return (
|
||||
<ValidateColumnValueWrapper
|
||||
value={cpuRequest}
|
||||
entity={InfraMonitoringEntity.DEPLOYMENTS}
|
||||
attribute="CPU Request"
|
||||
>
|
||||
<EntityProgressBar value={cpuRequest} type="request" />
|
||||
</ValidateColumnValueWrapper>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'cpu_limit',
|
||||
header: 'CPU Limit Usage (%)',
|
||||
accessorFn: (row): number => row.cpuLimit,
|
||||
width: { min: 210 },
|
||||
enableSort: true,
|
||||
enableResize: true,
|
||||
cell: ({ value }): React.ReactNode => {
|
||||
const cpuLimit = value as number;
|
||||
return (
|
||||
<ValidateColumnValueWrapper
|
||||
value={cpuLimit}
|
||||
entity={InfraMonitoringEntity.DEPLOYMENTS}
|
||||
attribute="CPU Limit"
|
||||
>
|
||||
<EntityProgressBar value={cpuLimit} type="limit" />
|
||||
</ValidateColumnValueWrapper>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'cpu',
|
||||
header: 'CPU Usage (cores)',
|
||||
accessorFn: (row): number => row.cpuUsage,
|
||||
width: { min: 210 },
|
||||
enableSort: true,
|
||||
enableResize: true,
|
||||
cell: ({ value }): React.ReactNode => {
|
||||
const cpu = value as number;
|
||||
return (
|
||||
<ValidateColumnValueWrapper
|
||||
value={cpu}
|
||||
entity={InfraMonitoringEntity.DEPLOYMENTS}
|
||||
attribute="CPU metric"
|
||||
>
|
||||
<TanStackTable.Text>{cpu}</TanStackTable.Text>
|
||||
</ValidateColumnValueWrapper>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'memory_request',
|
||||
header: 'Mem Req Usage (%)',
|
||||
accessorFn: (row): number => row.memoryRequest,
|
||||
width: { min: 210 },
|
||||
enableSort: true,
|
||||
enableResize: true,
|
||||
cell: ({ value }): React.ReactNode => {
|
||||
const memoryRequest = value as number;
|
||||
return (
|
||||
<ValidateColumnValueWrapper
|
||||
value={memoryRequest}
|
||||
entity={InfraMonitoringEntity.DEPLOYMENTS}
|
||||
attribute="Memory Request"
|
||||
>
|
||||
<EntityProgressBar value={memoryRequest} type="request" />
|
||||
</ValidateColumnValueWrapper>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'memory_limit',
|
||||
header: 'Mem Limit Usage (%)',
|
||||
accessorFn: (row): number => row.memoryLimit,
|
||||
width: { min: 210 },
|
||||
enableSort: true,
|
||||
enableResize: true,
|
||||
cell: ({ value }): React.ReactNode => {
|
||||
const memoryLimit = value as number;
|
||||
return (
|
||||
<ValidateColumnValueWrapper
|
||||
value={memoryLimit}
|
||||
entity={InfraMonitoringEntity.DEPLOYMENTS}
|
||||
attribute="Memory Limit"
|
||||
>
|
||||
<EntityProgressBar value={memoryLimit} type="limit" />
|
||||
</ValidateColumnValueWrapper>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'memory',
|
||||
header: 'Mem Usage (WSS)',
|
||||
accessorFn: (row): number => row.memoryUsage,
|
||||
width: { min: 140 },
|
||||
enableSort: true,
|
||||
enableResize: true,
|
||||
cell: ({ value }): React.ReactNode => {
|
||||
const memory = value as number;
|
||||
return (
|
||||
<ValidateColumnValueWrapper
|
||||
value={memory}
|
||||
entity={InfraMonitoringEntity.DEPLOYMENTS}
|
||||
attribute="memory metric"
|
||||
>
|
||||
<TanStackTable.Text>{formatBytes(memory)}</TanStackTable.Text>
|
||||
</ValidateColumnValueWrapper>
|
||||
);
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
@@ -14,12 +14,7 @@ import {
|
||||
import APIError from 'types/api/error';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
|
||||
import { INFRA_MONITORING_ATTR_KEYS } from '../../constants';
|
||||
|
||||
const K8S_EVENT_KEYS = [
|
||||
INFRA_MONITORING_ATTR_KEYS.K8S_OBJECT_KIND,
|
||||
INFRA_MONITORING_ATTR_KEYS.K8S_OBJECT_NAME,
|
||||
];
|
||||
const K8S_EVENT_KEYS = ['k8s.object.kind', 'k8s.object.name'];
|
||||
|
||||
export function isEventsKeyNotFoundError(error: unknown): boolean {
|
||||
if (!(error instanceof APIError)) {
|
||||
|
||||
@@ -1,8 +1,144 @@
|
||||
import { PANEL_TYPES } from 'constants/queryBuilder';
|
||||
import { GetQueryResultsProps } from 'lib/dashboard/getQueryResults';
|
||||
import {
|
||||
BaseAutocompleteData,
|
||||
DataTypes,
|
||||
} from 'types/api/queryBuilder/queryAutocompleteResponse';
|
||||
import {
|
||||
IBuilderQuery,
|
||||
TagFilterItem,
|
||||
} from 'types/api/queryBuilder/queryBuilderData';
|
||||
import APIError from 'types/api/error';
|
||||
import { EQueryType } from 'types/common/dashboard';
|
||||
import { DataSource, ReduceOperators } from 'types/common/queryBuilder';
|
||||
import { nanoToMilli } from 'utils/timeUtils';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
|
||||
export function isKeyNotFoundError(error: unknown): boolean {
|
||||
if (!(error instanceof APIError)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const errorDetails = error.getErrorDetails();
|
||||
if (errorDetails.error.code !== 'invalid_input') {
|
||||
return false;
|
||||
}
|
||||
|
||||
const errors = errorDetails.error.errors || [];
|
||||
return errors.some((err) => err.message?.includes('not found'));
|
||||
}
|
||||
|
||||
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',
|
||||
K8S_STATEFUL_SET_NAME: 'k8s.statefulset.name',
|
||||
K8S_JOB_NAME: 'k8s.job.name',
|
||||
K8S_DAEMON_SET_NAME: 'k8s.daemonset.name',
|
||||
K8S_PERSISTENT_VOLUME_CLAIM_NAME: 'k8s.persistentvolumeclaim.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: '',
|
||||
type: '',
|
||||
},
|
||||
timeAggregation: 'rate',
|
||||
spaceAggregation: 'sum',
|
||||
functions: [],
|
||||
filters,
|
||||
expression: 'A',
|
||||
disabled: false,
|
||||
stepInterval: 60,
|
||||
having: [],
|
||||
limit: null,
|
||||
orderBy: [
|
||||
{
|
||||
columnName: 'timestamp',
|
||||
order: 'desc',
|
||||
},
|
||||
],
|
||||
groupBy: [],
|
||||
legend: '',
|
||||
reduceTo: ReduceOperators.AVG,
|
||||
offset: 0,
|
||||
pageSize: 100,
|
||||
},
|
||||
],
|
||||
queryFormulas: [],
|
||||
queryTraceOperator: [],
|
||||
},
|
||||
id: uuidv4(),
|
||||
queryType: EQueryType.QUERY_BUILDER,
|
||||
},
|
||||
start,
|
||||
end,
|
||||
});
|
||||
|
||||
export const entityTracesColumns = [
|
||||
{
|
||||
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 selectedEntityTracesColumns: BaseAutocompleteData[] = [
|
||||
{
|
||||
@@ -37,16 +173,110 @@ export const selectedEntityTracesColumns: BaseAutocompleteData[] = [
|
||||
},
|
||||
];
|
||||
|
||||
export function isKeyNotFoundError(error: unknown): boolean {
|
||||
if (!(error instanceof APIError)) {
|
||||
return false;
|
||||
}
|
||||
export const getEntityTracesQueryPayload = (
|
||||
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: '',
|
||||
type: '',
|
||||
},
|
||||
timeAggregation: 'rate',
|
||||
spaceAggregation: 'sum',
|
||||
functions: [],
|
||||
filters,
|
||||
expression: 'A',
|
||||
disabled: false,
|
||||
stepInterval: 60,
|
||||
having: [],
|
||||
limit: null,
|
||||
orderBy: [
|
||||
{
|
||||
columnName: 'timestamp',
|
||||
order: 'desc',
|
||||
},
|
||||
],
|
||||
groupBy: [],
|
||||
legend: '',
|
||||
reduceTo: ReduceOperators.AVG,
|
||||
},
|
||||
],
|
||||
queryFormulas: [],
|
||||
queryTraceOperator: [],
|
||||
},
|
||||
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',
|
||||
id: 'serviceName--string--tag--true',
|
||||
isIndexed: false,
|
||||
},
|
||||
{
|
||||
key: 'name',
|
||||
dataType: 'string',
|
||||
type: 'tag',
|
||||
id: 'name--string--tag--true',
|
||||
isIndexed: false,
|
||||
},
|
||||
{
|
||||
key: 'durationNano',
|
||||
dataType: 'float64',
|
||||
type: 'tag',
|
||||
id: 'durationNano--float64--tag--true',
|
||||
isIndexed: false,
|
||||
},
|
||||
{
|
||||
key: 'httpMethod',
|
||||
dataType: 'string',
|
||||
type: 'tag',
|
||||
id: 'httpMethod--string--tag--true',
|
||||
isIndexed: false,
|
||||
},
|
||||
{
|
||||
key: 'responseStatusCode',
|
||||
dataType: 'string',
|
||||
type: 'tag',
|
||||
id: 'responseStatusCode--string--tag--true',
|
||||
isIndexed: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
|
||||
const errorDetails = error.getErrorDetails();
|
||||
if (errorDetails.error.code !== 'invalid_input') {
|
||||
return false;
|
||||
}
|
||||
|
||||
const errors = errorDetails.error.errors || [];
|
||||
return errors.some((err) => err.message?.includes('not found'));
|
||||
}
|
||||
export const filterOutPrimaryFilters = (
|
||||
filters: TagFilterItem[],
|
||||
primaryKeys: string[],
|
||||
): TagFilterItem[] =>
|
||||
filters.filter(
|
||||
(filter) =>
|
||||
!primaryKeys.includes(filter.key?.key ?? '') && filter.key?.key !== 'id',
|
||||
);
|
||||
|
||||
@@ -26,8 +26,8 @@
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
width: 0.1rem;
|
||||
height: 0.1rem;
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar-track {
|
||||
@@ -35,12 +35,11 @@
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar-thumb {
|
||||
background: color-mix(in srgb, var(--l1-foreground) 20%, transparent);
|
||||
border-radius: 9999px;
|
||||
background: var(--accent-primary);
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar-thumb:hover {
|
||||
background: color-mix(in srgb, var(--l1-foreground) 30%, transparent);
|
||||
background: colox-mix(var(--accent-primary), var(--bg-vanilla-100), 20%);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -85,7 +84,7 @@
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar-thumb:hover {
|
||||
background: var(--accent-primary);
|
||||
background: colox-mix(var(--accent-primary), var(--bg-vanilla-100), 20%);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -100,12 +99,11 @@
|
||||
|
||||
.listContainer {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
max-width: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
min-width: 595px;
|
||||
overflow-x: auto;
|
||||
|
||||
> * {
|
||||
min-width: 0;
|
||||
@@ -235,7 +233,3 @@
|
||||
background: var(--accent-primary);
|
||||
}
|
||||
}
|
||||
|
||||
.k8SOpenQuickFilters {
|
||||
grid-area: quickFilters;
|
||||
}
|
||||
|
||||
@@ -2,11 +2,12 @@ import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||
import * as Sentry from '@sentry/react';
|
||||
import { Button, Tooltip } from 'antd';
|
||||
import { Typography } from '@signozhq/ui/typography';
|
||||
import logEvent from 'api/common/logEvent';
|
||||
import QuickFilters from 'components/QuickFilters/QuickFilters';
|
||||
import { QuickFiltersSource } from 'components/QuickFilters/types';
|
||||
import { initialQueriesMap } from 'constants/queryBuilder';
|
||||
import { useGetCompositeQueryParam } from 'hooks/queryBuilder/useGetCompositeQueryParam';
|
||||
import { InfraMonitoringEvents } from 'constants/events';
|
||||
import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
|
||||
import { useQueryOperations } from 'hooks/queryBuilder/useQueryBuilderOperations';
|
||||
import {
|
||||
ArrowUpDown,
|
||||
ArrowUpToLine,
|
||||
@@ -21,7 +22,7 @@ import {
|
||||
Workflow,
|
||||
} from '@signozhq/icons';
|
||||
import ErrorBoundaryFallback from 'pages/ErrorBoundaryFallback/ErrorBoundaryFallback';
|
||||
import { DataSource } from 'types/common/queryBuilder';
|
||||
import { Query } from 'types/api/queryBuilder/queryBuilderData';
|
||||
|
||||
import { FeatureKeys } from '../../constants/features';
|
||||
import { useAppContext } from '../../providers/App/App';
|
||||
@@ -42,9 +43,9 @@ import K8sDaemonSetsList from './DaemonSets/K8sDaemonSetsList';
|
||||
import K8sDeploymentsList from './Deployments/K8sDeploymentsList';
|
||||
import {
|
||||
useInfraMonitoringCategory,
|
||||
useInfraMonitoringFiltersK8s,
|
||||
useInfraMonitoringGroupBy,
|
||||
useInfraMonitoringOrderBy,
|
||||
useInfraMonitoringSelectedItem,
|
||||
} from './hooks';
|
||||
import K8sJobsList from './Jobs/K8sJobsList';
|
||||
import K8sNamespacesList from './Namespaces/K8sNamespacesList';
|
||||
@@ -54,61 +55,63 @@ import K8sStatefulSetsList from './StatefulSets/K8sStatefulSetsList';
|
||||
import K8sVolumesList from './Volumes/K8sVolumesList';
|
||||
|
||||
import styles from './InfraMonitoringK8s.module.scss';
|
||||
import { InfraMonitoringEvents } from 'constants/events';
|
||||
import logEvent from 'api/common/logEvent';
|
||||
|
||||
export default function InfraMonitoringK8s(): JSX.Element {
|
||||
const [showFilters, setShowFilters] = useState(true);
|
||||
|
||||
const [selectedCategory, setSelectedCategory] = useInfraMonitoringCategory();
|
||||
const [urlFilters, setUrlFilters] = useInfraMonitoringFiltersK8s();
|
||||
const [, setGroupBy] = useInfraMonitoringGroupBy();
|
||||
const [, setOrderBy] = useInfraMonitoringOrderBy();
|
||||
const [, setSelectedItem] = useInfraMonitoringSelectedItem();
|
||||
|
||||
const compositeQuery = useGetCompositeQueryParam();
|
||||
const { currentQuery, redirectWithQueryBuilderData } = useQueryBuilder();
|
||||
const isInitialized = useRef(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (isInitialized.current) {
|
||||
return;
|
||||
}
|
||||
isInitialized.current = true;
|
||||
|
||||
if (!compositeQuery) {
|
||||
const defaultQuery = initialQueriesMap[DataSource.METRICS];
|
||||
redirectWithQueryBuilderData({
|
||||
...defaultQuery,
|
||||
builder: {
|
||||
...defaultQuery.builder,
|
||||
queryData: defaultQuery.builder.queryData.map((query) => ({
|
||||
...query,
|
||||
filter: { expression: '' },
|
||||
filters: { items: [], op: 'AND' as const },
|
||||
})),
|
||||
},
|
||||
});
|
||||
}
|
||||
}, [compositeQuery, redirectWithQueryBuilderData]);
|
||||
|
||||
useEffect(() => {
|
||||
void logEvent(InfraMonitoringEvents.FilterApplied, {
|
||||
entity: InfraMonitoringEvents.K8sEntity,
|
||||
page: InfraMonitoringEvents.ListPage,
|
||||
category: selectedCategory,
|
||||
view: InfraMonitoringEvents.QuickFiltersView,
|
||||
});
|
||||
}, [selectedCategory]);
|
||||
const { currentQuery } = useQueryBuilder();
|
||||
|
||||
const handleFilterVisibilityChange = useCallback((): void => {
|
||||
setShowFilters((show) => !show);
|
||||
}, []);
|
||||
|
||||
const { handleChangeQueryData } = useQueryOperations({
|
||||
index: 0,
|
||||
query: currentQuery.builder.queryData[0],
|
||||
entityVersion: '',
|
||||
});
|
||||
|
||||
// Track previous urlFilters to only sync when the value actually changes
|
||||
// (not when handleChangeQueryData changes due to query updates)
|
||||
const prevUrlFiltersRef = useRef<string | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
const currentFiltersJson = urlFilters ? JSON.stringify(urlFilters) : null;
|
||||
|
||||
// Only sync if urlFilters value has actually changed
|
||||
if (prevUrlFiltersRef.current !== currentFiltersJson) {
|
||||
prevUrlFiltersRef.current = currentFiltersJson;
|
||||
// Sync filters to query builder, using empty filter when urlFilters is null
|
||||
handleChangeQueryData('filters', urlFilters || { items: [], op: 'and' });
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [urlFilters]); // handleChangeQueryData intentionally omitted - we call the current version but don't re-run when it changes
|
||||
|
||||
const { featureFlags } = useAppContext();
|
||||
const dotMetricsEnabled =
|
||||
featureFlags?.find((flag) => flag.name === FeatureKeys.DOT_METRICS_ENABLED)
|
||||
?.active || false;
|
||||
|
||||
const handleFilterChange = (query: Query): void => {
|
||||
// update the current query with the new filters
|
||||
// in infra monitoring k8s, we are using only one query, hence updating the 0th index of queryData
|
||||
const filters = query.builder.queryData[0].filters;
|
||||
// The useEffect will sync filters to query builder, avoiding double state updates
|
||||
setUrlFilters(filters || null);
|
||||
|
||||
logEvent(InfraMonitoringEvents.FilterApplied, {
|
||||
entity: InfraMonitoringEvents.K8sEntity,
|
||||
page: InfraMonitoringEvents.ListPage,
|
||||
category: selectedCategory,
|
||||
view: InfraMonitoringEvents.QuickFiltersView,
|
||||
});
|
||||
};
|
||||
|
||||
const categories = useMemo(
|
||||
() => [
|
||||
{
|
||||
@@ -176,23 +179,12 @@ export default function InfraMonitoringK8s(): JSX.Element {
|
||||
|
||||
const handleCategorySelect = (key: string): void => {
|
||||
if (key !== selectedCategory) {
|
||||
void setSelectedCategory(key as string);
|
||||
void setOrderBy(null);
|
||||
void setGroupBy(null);
|
||||
void setSelectedItem(null);
|
||||
redirectWithQueryBuilderData({
|
||||
...currentQuery,
|
||||
builder: {
|
||||
...currentQuery.builder,
|
||||
queryData: [
|
||||
{
|
||||
...(currentQuery.builder.queryData[0] || {}),
|
||||
filter: { expression: '' },
|
||||
filters: { items: [], op: 'AND' as const },
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
setSelectedCategory(key);
|
||||
// Reset filters
|
||||
setUrlFilters(null);
|
||||
setOrderBy(null);
|
||||
setGroupBy(null);
|
||||
handleChangeQueryData('filters', { items: [], op: 'and' });
|
||||
}
|
||||
};
|
||||
|
||||
@@ -200,7 +192,7 @@ export default function InfraMonitoringK8s(): JSX.Element {
|
||||
return (
|
||||
<>
|
||||
{!showFilters && (
|
||||
<div className={styles.k8SOpenQuickFilters}>
|
||||
<div>
|
||||
<Button
|
||||
className="periscope-btn ghost"
|
||||
type="text"
|
||||
@@ -269,6 +261,7 @@ export default function InfraMonitoringK8s(): JSX.Element {
|
||||
source={QuickFiltersSource.INFRA_MONITORING}
|
||||
config={selectedCategoryConfig}
|
||||
handleFilterVisibilityChange={handleFilterVisibilityChange}
|
||||
onFilterChange={handleFilterChange}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -1,24 +1,21 @@
|
||||
import { useCallback } from 'react';
|
||||
import { listJobs } from 'api/generated/services/inframonitoring';
|
||||
import {
|
||||
InframonitoringtypesJobRecordDTO,
|
||||
InframonitoringtypesResponseTypeDTO,
|
||||
Querybuildertypesv5OrderDirectionDTO,
|
||||
} from 'api/generated/services/sigNoz.schemas';
|
||||
import React, { useCallback } from 'react';
|
||||
import { InfraMonitoringEvents } from 'constants/events';
|
||||
import { FeatureKeys } from 'constants/features';
|
||||
import { useAppContext } from 'providers/App/App';
|
||||
|
||||
import K8sBaseDetails, { K8sDetailsFilters } from '../Base/K8sBaseDetails';
|
||||
import { K8sBaseList } from '../Base/K8sBaseList';
|
||||
import { K8sBaseFilters } from '../Base/types';
|
||||
import { InfraMonitoringEntity } from '../constants';
|
||||
import { getK8sJobsList, K8sJobsData } from './api';
|
||||
import {
|
||||
getJobMetricsQueryPayload,
|
||||
jobWidgetInfo,
|
||||
k8sJobDetailsMetadataConfig,
|
||||
k8sJobGetEntityName,
|
||||
k8sJobGetSelectedItemExpression,
|
||||
k8sJobInitialEventsExpression,
|
||||
k8sJobInitialLogTracesExpression,
|
||||
k8sJobGetSelectedItemFilters,
|
||||
k8sJobInitialEventsFilter,
|
||||
k8sJobInitialLogTracesFilter,
|
||||
} from './constants';
|
||||
import {
|
||||
getK8sJobItemKey,
|
||||
@@ -31,93 +28,66 @@ function K8sJobsList({
|
||||
}: {
|
||||
controlListPrefix?: React.ReactNode;
|
||||
}): JSX.Element {
|
||||
const { featureFlags } = useAppContext();
|
||||
const dotMetricsEnabled =
|
||||
featureFlags?.find((flag) => flag.name === FeatureKeys.DOT_METRICS_ENABLED)
|
||||
?.active || false;
|
||||
|
||||
const fetchListData = useCallback(
|
||||
async (filters: K8sBaseFilters, signal?: AbortSignal) => {
|
||||
try {
|
||||
const response = await listJobs(
|
||||
{
|
||||
filter: { expression: filters.filter.expression },
|
||||
groupBy: filters.groupBy?.map((g) => ({ name: g.name })),
|
||||
offset: filters.offset,
|
||||
limit: filters.limit ?? 10,
|
||||
start: filters.start,
|
||||
end: filters.end,
|
||||
orderBy: filters.orderBy
|
||||
? {
|
||||
key: { name: filters.orderBy.key.name },
|
||||
direction:
|
||||
filters.orderBy.direction === 'asc'
|
||||
? Querybuildertypesv5OrderDirectionDTO.asc
|
||||
: Querybuildertypesv5OrderDirectionDTO.desc,
|
||||
}
|
||||
: undefined,
|
||||
},
|
||||
signal,
|
||||
);
|
||||
filters.orderBy ||= {
|
||||
columnName: 'cpu',
|
||||
order: 'desc',
|
||||
};
|
||||
|
||||
const data = response.data;
|
||||
return {
|
||||
type:
|
||||
data.type === InframonitoringtypesResponseTypeDTO.grouped_list
|
||||
? ('grouped_list' as const)
|
||||
: ('list' as const),
|
||||
records: data.records,
|
||||
total: data.total,
|
||||
endTimeBeforeRetention: data.endTimeBeforeRetention,
|
||||
warning: data.warning,
|
||||
};
|
||||
} catch (error) {
|
||||
const errMsg =
|
||||
error instanceof Error ? error.message : 'Failed to fetch jobs';
|
||||
return {
|
||||
type: 'list' as const,
|
||||
records: [] as InframonitoringtypesJobRecordDTO[],
|
||||
total: 0,
|
||||
error: errMsg,
|
||||
};
|
||||
}
|
||||
const response = await getK8sJobsList(
|
||||
filters,
|
||||
signal,
|
||||
undefined,
|
||||
dotMetricsEnabled,
|
||||
);
|
||||
|
||||
return {
|
||||
data: response.payload?.data.records || [],
|
||||
total: response.payload?.data.total || 0,
|
||||
error: response.error,
|
||||
rawData: response.payload?.data,
|
||||
};
|
||||
},
|
||||
[],
|
||||
[dotMetricsEnabled],
|
||||
);
|
||||
|
||||
const fetchEntityData = useCallback(
|
||||
async (
|
||||
filters: K8sDetailsFilters,
|
||||
signal?: AbortSignal,
|
||||
): Promise<{
|
||||
data: InframonitoringtypesJobRecordDTO | null;
|
||||
error?: string | null;
|
||||
}> => {
|
||||
try {
|
||||
const response = await listJobs(
|
||||
{
|
||||
filter: { expression: filters.filter.expression },
|
||||
start: filters.start,
|
||||
end: filters.end,
|
||||
limit: 1,
|
||||
offset: 0,
|
||||
},
|
||||
signal,
|
||||
);
|
||||
): Promise<{ data: K8sJobsData | null; error?: string | null }> => {
|
||||
const response = await getK8sJobsList(
|
||||
{
|
||||
filters: filters.filters,
|
||||
start: filters.start,
|
||||
end: filters.end,
|
||||
limit: 1,
|
||||
offset: 0,
|
||||
},
|
||||
signal,
|
||||
undefined,
|
||||
dotMetricsEnabled,
|
||||
);
|
||||
|
||||
return {
|
||||
data: response.data.records.length > 0 ? response.data.records[0] : null,
|
||||
};
|
||||
} catch (error) {
|
||||
const errMsg =
|
||||
error instanceof Error ? error.message : 'Failed to fetch job';
|
||||
return {
|
||||
data: null,
|
||||
error: errMsg,
|
||||
};
|
||||
}
|
||||
const records = response.payload?.data.records || [];
|
||||
|
||||
return {
|
||||
data: records.length > 0 ? records[0] : null,
|
||||
error: response.error,
|
||||
};
|
||||
},
|
||||
[],
|
||||
[dotMetricsEnabled],
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<K8sBaseList<InframonitoringtypesJobRecordDTO>
|
||||
<K8sBaseList<K8sJobsData>
|
||||
controlListPrefix={controlListPrefix}
|
||||
entity={InfraMonitoringEntity.JOBS}
|
||||
tableColumns={k8sJobsColumnsConfig}
|
||||
@@ -127,14 +97,14 @@ function K8sJobsList({
|
||||
eventCategory={InfraMonitoringEvents.Job}
|
||||
/>
|
||||
|
||||
<K8sBaseDetails<InframonitoringtypesJobRecordDTO>
|
||||
<K8sBaseDetails<K8sJobsData>
|
||||
category={InfraMonitoringEntity.JOBS}
|
||||
eventCategory={InfraMonitoringEvents.Job}
|
||||
getSelectedItemExpression={k8sJobGetSelectedItemExpression}
|
||||
getSelectedItemFilters={k8sJobGetSelectedItemFilters}
|
||||
fetchEntityData={fetchEntityData}
|
||||
getEntityName={k8sJobGetEntityName}
|
||||
getInitialLogTracesExpression={k8sJobInitialLogTracesExpression}
|
||||
getInitialEventsExpression={k8sJobInitialEventsExpression}
|
||||
getInitialLogTracesFilters={k8sJobInitialLogTracesFilter}
|
||||
getInitialEventsFilters={k8sJobInitialEventsFilter}
|
||||
metadataConfig={k8sJobDetailsMetadataConfig}
|
||||
entityWidgetInfo={jobWidgetInfo}
|
||||
getEntityQueryPayload={getJobMetricsQueryPayload}
|
||||
|
||||
119
frontend/src/container/InfraMonitoringK8sV2/Jobs/api.ts
Normal file
119
frontend/src/container/InfraMonitoringK8sV2/Jobs/api.ts
Normal file
@@ -0,0 +1,119 @@
|
||||
import axios from 'api';
|
||||
import { ErrorResponseHandler } from 'api/ErrorResponseHandler';
|
||||
import { UnderscoreToDotMap } from 'api/utils';
|
||||
import { AxiosError } from 'axios';
|
||||
import { ErrorResponse, SuccessResponse } from 'types/api';
|
||||
|
||||
import { K8sBaseFilters } from '../Base/types';
|
||||
|
||||
export interface K8sJobsData {
|
||||
jobName: string;
|
||||
cpuUsage: number;
|
||||
memoryUsage: number;
|
||||
cpuRequest: number;
|
||||
memoryRequest: number;
|
||||
cpuLimit: number;
|
||||
memoryLimit: number;
|
||||
restarts: number;
|
||||
desiredSuccessfulPods: number;
|
||||
activePods: number;
|
||||
failedPods: number;
|
||||
successfulPods: number;
|
||||
meta: {
|
||||
k8s_cluster_name: string;
|
||||
k8s_job_name: string;
|
||||
k8s_namespace_name: string;
|
||||
};
|
||||
}
|
||||
|
||||
export interface K8sJobsListResponse {
|
||||
status: string;
|
||||
data: {
|
||||
type: string;
|
||||
records: K8sJobsData[];
|
||||
groups: null;
|
||||
total: number;
|
||||
sentAnyHostMetricsData: boolean;
|
||||
isSendingK8SAgentMetrics: boolean;
|
||||
};
|
||||
}
|
||||
|
||||
// TODO(H4ad): Erase this whole file when migrating to openapi
|
||||
export const jobsMetaMap = [
|
||||
{ dot: 'k8s.cluster.name', under: 'k8s_cluster_name' },
|
||||
{ dot: 'k8s.job.name', under: 'k8s_job_name' },
|
||||
{ dot: 'k8s.namespace.name', under: 'k8s_namespace_name' },
|
||||
] as const;
|
||||
|
||||
export function mapJobsMeta(raw: Record<string, unknown>): K8sJobsData['meta'] {
|
||||
const out: Record<string, unknown> = { ...raw };
|
||||
jobsMetaMap.forEach(({ dot, under }) => {
|
||||
if (dot in raw) {
|
||||
const v = raw[dot];
|
||||
out[under] = typeof v === 'string' ? v : raw[under];
|
||||
}
|
||||
});
|
||||
return out as K8sJobsData['meta'];
|
||||
}
|
||||
|
||||
export const getK8sJobsList = async (
|
||||
props: K8sBaseFilters,
|
||||
signal?: AbortSignal,
|
||||
headers?: Record<string, string>,
|
||||
dotMetricsEnabled = false,
|
||||
): Promise<SuccessResponse<K8sJobsListResponse> | ErrorResponse> => {
|
||||
try {
|
||||
const requestProps = dotMetricsEnabled
|
||||
? {
|
||||
...props,
|
||||
filters: {
|
||||
...props.filters,
|
||||
items: props.filters?.items.reduce<typeof props.filters.items>(
|
||||
(acc, item) => {
|
||||
if (item.value === undefined) {
|
||||
return acc;
|
||||
}
|
||||
if (
|
||||
item.key &&
|
||||
typeof item.key === 'object' &&
|
||||
'key' in item.key &&
|
||||
typeof item.key.key === 'string'
|
||||
) {
|
||||
const mappedKey = UnderscoreToDotMap[item.key.key] ?? item.key.key;
|
||||
acc.push({
|
||||
...item,
|
||||
key: { ...item.key, key: mappedKey },
|
||||
});
|
||||
} else {
|
||||
acc.push(item);
|
||||
}
|
||||
return acc;
|
||||
},
|
||||
[] as typeof props.filters.items,
|
||||
),
|
||||
},
|
||||
}
|
||||
: props;
|
||||
|
||||
const response = await axios.post('/jobs/list', requestProps, {
|
||||
signal,
|
||||
headers,
|
||||
});
|
||||
const payload: K8sJobsListResponse = response.data;
|
||||
|
||||
payload.data.records = payload.data.records.map((record) => ({
|
||||
...record,
|
||||
meta: mapJobsMeta(record.meta as Record<string, unknown>),
|
||||
}));
|
||||
|
||||
return {
|
||||
statusCode: 200,
|
||||
error: null,
|
||||
message: 'Success',
|
||||
payload,
|
||||
params: requestProps,
|
||||
};
|
||||
} catch (error) {
|
||||
return ErrorResponseHandler(error as AxiosError);
|
||||
}
|
||||
};
|
||||
@@ -1,63 +1,67 @@
|
||||
import { InframonitoringtypesJobRecordDTO } from 'api/generated/services/sigNoz.schemas';
|
||||
import { PANEL_TYPES } from 'constants/queryBuilder';
|
||||
import { GetQueryResultsProps } from 'lib/dashboard/getQueryResults';
|
||||
import { DataTypes } from 'types/api/queryBuilder/queryAutocompleteResponse';
|
||||
import { TagFilter } from 'types/api/queryBuilder/queryBuilderData';
|
||||
import { EQueryType } from 'types/common/dashboard';
|
||||
import { DataSource, ReduceOperators } from 'types/common/queryBuilder';
|
||||
import { v4 } from 'uuid';
|
||||
|
||||
import { K8sDetailsMetadataConfig } from '../Base/K8sBaseDetails';
|
||||
import { formatValueForExpression } from 'components/QueryBuilderV2/utils';
|
||||
import { INFRA_MONITORING_ATTR_KEYS } from '../constants';
|
||||
import {
|
||||
createFilterItem,
|
||||
K8sDetailsMetadataConfig,
|
||||
} from '../Base/K8sBaseDetails';
|
||||
import { QUERY_KEYS } from '../EntityDetailsUtils/utils';
|
||||
import { K8sJobsData } from './api';
|
||||
|
||||
export const k8sJobGetSelectedItemExpression = (
|
||||
export const k8sJobGetSelectedItemFilters = (
|
||||
selectedItemId: string,
|
||||
): string =>
|
||||
`${INFRA_MONITORING_ATTR_KEYS.K8S_JOB_NAME} = ${formatValueForExpression(selectedItemId)}`;
|
||||
): TagFilter => ({
|
||||
op: 'AND',
|
||||
items: [
|
||||
{
|
||||
id: 'k8s_job_name',
|
||||
key: {
|
||||
key: 'k8s_job_name',
|
||||
type: null,
|
||||
},
|
||||
op: '=',
|
||||
value: selectedItemId,
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
export const k8sJobDetailsMetadataConfig: K8sDetailsMetadataConfig<InframonitoringtypesJobRecordDTO>[] =
|
||||
export const k8sJobDetailsMetadataConfig: K8sDetailsMetadataConfig<K8sJobsData>[] =
|
||||
[
|
||||
{
|
||||
label: 'Job Name',
|
||||
getValue: (p): string =>
|
||||
p.meta?.[INFRA_MONITORING_ATTR_KEYS.K8S_JOB_NAME] ?? '',
|
||||
getValue: (p): string => p.meta.k8s_job_name,
|
||||
},
|
||||
{
|
||||
label: 'Cluster Name',
|
||||
getValue: (p): string =>
|
||||
p.meta?.[INFRA_MONITORING_ATTR_KEYS.K8S_CLUSTER_NAME] ?? '',
|
||||
getValue: (p): string => p.meta.k8s_cluster_name,
|
||||
},
|
||||
{
|
||||
label: 'Namespace Name',
|
||||
getValue: (p): string =>
|
||||
p.meta?.[INFRA_MONITORING_ATTR_KEYS.K8S_NAMESPACE_NAME] ?? '',
|
||||
getValue: (p): string => p.meta.k8s_namespace_name,
|
||||
},
|
||||
];
|
||||
|
||||
export const k8sJobInitialEventsExpression = (
|
||||
item: InframonitoringtypesJobRecordDTO,
|
||||
): string => {
|
||||
const name = formatValueForExpression(
|
||||
item.meta?.[INFRA_MONITORING_ATTR_KEYS.K8S_JOB_NAME] ?? '',
|
||||
);
|
||||
return `${INFRA_MONITORING_ATTR_KEYS.K8S_OBJECT_KIND} = 'Job' AND ${INFRA_MONITORING_ATTR_KEYS.K8S_OBJECT_NAME} = ${name}`;
|
||||
};
|
||||
export const k8sJobInitialEventsFilter = (
|
||||
item: K8sJobsData,
|
||||
): ReturnType<typeof createFilterItem>[] => [
|
||||
createFilterItem(QUERY_KEYS.K8S_OBJECT_KIND, 'Job'),
|
||||
createFilterItem(QUERY_KEYS.K8S_OBJECT_NAME, item.meta.k8s_job_name),
|
||||
];
|
||||
|
||||
export const k8sJobInitialLogTracesExpression = (
|
||||
item: InframonitoringtypesJobRecordDTO,
|
||||
): string => {
|
||||
const jobName = formatValueForExpression(
|
||||
item.meta?.[INFRA_MONITORING_ATTR_KEYS.K8S_JOB_NAME] ?? '',
|
||||
);
|
||||
const namespaceName = formatValueForExpression(
|
||||
item.meta?.[INFRA_MONITORING_ATTR_KEYS.K8S_NAMESPACE_NAME] ?? '',
|
||||
);
|
||||
return `${INFRA_MONITORING_ATTR_KEYS.K8S_JOB_NAME} = ${jobName} AND ${INFRA_MONITORING_ATTR_KEYS.K8S_NAMESPACE_NAME} = ${namespaceName}`;
|
||||
};
|
||||
export const k8sJobInitialLogTracesFilter = (
|
||||
item: K8sJobsData,
|
||||
): ReturnType<typeof createFilterItem>[] => [
|
||||
createFilterItem(QUERY_KEYS.K8S_JOB_NAME, item.meta.k8s_job_name),
|
||||
createFilterItem(QUERY_KEYS.K8S_NAMESPACE_NAME, item.meta.k8s_namespace_name),
|
||||
];
|
||||
|
||||
export const k8sJobGetEntityName = (
|
||||
item: InframonitoringtypesJobRecordDTO,
|
||||
): string => item.meta?.[INFRA_MONITORING_ATTR_KEYS.K8S_JOB_NAME] ?? '';
|
||||
export const k8sJobGetEntityName = (item: K8sJobsData): string =>
|
||||
item.meta.k8s_job_name;
|
||||
|
||||
export const jobWidgetInfo = [
|
||||
{
|
||||
@@ -79,7 +83,7 @@ export const jobWidgetInfo = [
|
||||
];
|
||||
|
||||
export const getJobMetricsQueryPayload = (
|
||||
job: InframonitoringtypesJobRecordDTO,
|
||||
job: K8sJobsData,
|
||||
start: number,
|
||||
end: number,
|
||||
dotMetricsEnabled: boolean,
|
||||
@@ -141,8 +145,7 @@ export const getJobMetricsQueryPayload = (
|
||||
type: 'tag',
|
||||
},
|
||||
op: '=',
|
||||
value:
|
||||
job.meta?.[INFRA_MONITORING_ATTR_KEYS.K8S_NAMESPACE_NAME] ?? '',
|
||||
value: job.meta.k8s_namespace_name,
|
||||
},
|
||||
],
|
||||
op: 'AND',
|
||||
@@ -226,8 +229,7 @@ export const getJobMetricsQueryPayload = (
|
||||
type: 'tag',
|
||||
},
|
||||
op: '=',
|
||||
value:
|
||||
job.meta?.[INFRA_MONITORING_ATTR_KEYS.K8S_NAMESPACE_NAME] ?? '',
|
||||
value: job.meta.k8s_namespace_name,
|
||||
},
|
||||
],
|
||||
op: 'AND',
|
||||
@@ -311,8 +313,7 @@ export const getJobMetricsQueryPayload = (
|
||||
type: 'tag',
|
||||
},
|
||||
op: '=',
|
||||
value:
|
||||
job.meta?.[INFRA_MONITORING_ATTR_KEYS.K8S_NAMESPACE_NAME] ?? '',
|
||||
value: job.meta.k8s_namespace_name,
|
||||
},
|
||||
],
|
||||
op: 'AND',
|
||||
@@ -409,8 +410,7 @@ export const getJobMetricsQueryPayload = (
|
||||
type: 'tag',
|
||||
},
|
||||
op: '=',
|
||||
value:
|
||||
job.meta?.[INFRA_MONITORING_ATTR_KEYS.K8S_NAMESPACE_NAME] ?? '',
|
||||
value: job.meta.k8s_namespace_name,
|
||||
},
|
||||
],
|
||||
op: 'AND',
|
||||
|
||||
@@ -1,45 +1,30 @@
|
||||
import { Color } from '@signozhq/design-tokens';
|
||||
import { InframonitoringtypesJobRecordDTO } from 'api/generated/services/sigNoz.schemas';
|
||||
import TanStackTable, { TableColumnDef } from 'components/TanStackTableView';
|
||||
import { Tooltip } from 'antd';
|
||||
import { TableColumnDef } from 'components/TanStackTableView';
|
||||
import TanStackTable from 'components/TanStackTableView';
|
||||
import { ExpandButtonWrapper } from 'container/InfraMonitoringK8sV2/components';
|
||||
|
||||
import ColumnHeader from '../Base/ColumnHeader';
|
||||
import EntityGroupHeader from '../Base/EntityGroupHeader';
|
||||
import K8sGroupCell from '../Base/K8sGroupCell';
|
||||
import { formatBytes, getPodPhaseStatusItems } from '../commonUtils';
|
||||
import {
|
||||
CellValueTooltip,
|
||||
EntityProgressBar,
|
||||
GroupedStatusCounts,
|
||||
ValidateColumnValueWrapper,
|
||||
} from '../components';
|
||||
import {
|
||||
INFRA_MONITORING_ATTR_KEYS,
|
||||
InfraMonitoringEntity,
|
||||
} from '../constants';
|
||||
import { formatBytes } from '../commonUtils';
|
||||
import { EntityProgressBar, ValidateColumnValueWrapper } from '../components';
|
||||
import { InfraMonitoringEntity } from '../constants';
|
||||
import { K8sJobsData } from './api';
|
||||
import { Bolt } from '@signozhq/icons';
|
||||
|
||||
export function getK8sJobRowKey(job: InframonitoringtypesJobRecordDTO): string {
|
||||
return (
|
||||
job.meta?.[INFRA_MONITORING_ATTR_KEYS.K8S_JOB_NAME] || job.jobName || ''
|
||||
);
|
||||
export function getK8sJobRowKey(job: K8sJobsData): string {
|
||||
return job.jobName || job.meta.k8s_job_name || '';
|
||||
}
|
||||
|
||||
export function getK8sJobItemKey(
|
||||
job: InframonitoringtypesJobRecordDTO,
|
||||
): string {
|
||||
return job.meta?.[INFRA_MONITORING_ATTR_KEYS.K8S_JOB_NAME] || '';
|
||||
export function getK8sJobItemKey(job: K8sJobsData): string {
|
||||
return job.meta.k8s_job_name;
|
||||
}
|
||||
|
||||
export type JobTableColumnConfig =
|
||||
TableColumnDef<InframonitoringtypesJobRecordDTO>;
|
||||
export const k8sJobsColumnsConfig: JobTableColumnConfig[] = [
|
||||
export const k8sJobsColumnsConfig: TableColumnDef<K8sJobsData>[] = [
|
||||
{
|
||||
id: 'jobGroup',
|
||||
header: (): React.ReactNode => <EntityGroupHeader title="Job Group" />,
|
||||
accessorFn: (row): string =>
|
||||
row.meta?.[INFRA_MONITORING_ATTR_KEYS.K8S_JOB_NAME] || '',
|
||||
width: { min: 290 },
|
||||
header: (): React.ReactNode => <EntityGroupHeader title="JOB GROUP" />,
|
||||
accessorFn: (row): string => row.meta.k8s_job_name || '',
|
||||
width: { min: 270 },
|
||||
enableSort: false,
|
||||
enableRemove: false,
|
||||
enableMove: false,
|
||||
@@ -62,12 +47,10 @@ export const k8sJobsColumnsConfig: JobTableColumnConfig[] = [
|
||||
<EntityGroupHeader
|
||||
title="Job Name"
|
||||
icon={<Bolt data-hide-expanded="true" size={14} />}
|
||||
docPath="/infrastructure-monitoring/kubernetes/jobs#job-name"
|
||||
/>
|
||||
),
|
||||
accessorFn: (row): string =>
|
||||
row.meta?.[INFRA_MONITORING_ATTR_KEYS.K8S_JOB_NAME] || '',
|
||||
width: { min: 290 },
|
||||
accessorFn: (row): string => row.meta.k8s_job_name || '',
|
||||
width: { min: 260 },
|
||||
enableSort: false,
|
||||
enableRemove: false,
|
||||
enableMove: false,
|
||||
@@ -76,98 +59,109 @@ export const k8sJobsColumnsConfig: JobTableColumnConfig[] = [
|
||||
cell: ({ value }): React.ReactNode => {
|
||||
const jobName = value as string;
|
||||
return (
|
||||
<CellValueTooltip value={jobName}>
|
||||
<Tooltip title={jobName}>
|
||||
<TanStackTable.Text>{jobName}</TanStackTable.Text>
|
||||
</CellValueTooltip>
|
||||
</Tooltip>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'namespaceName',
|
||||
header: (): React.ReactNode => (
|
||||
<ColumnHeader docPath="/infrastructure-monitoring/kubernetes/jobs#namespace-name">
|
||||
Namespace
|
||||
</ColumnHeader>
|
||||
),
|
||||
accessorFn: (row): string =>
|
||||
row.meta?.[INFRA_MONITORING_ATTR_KEYS.K8S_NAMESPACE_NAME] || '',
|
||||
width: { min: 160 },
|
||||
header: 'Namespace Name',
|
||||
accessorFn: (row): string => row.meta.k8s_namespace_name || '',
|
||||
width: { default: 150 },
|
||||
enableSort: false,
|
||||
enableResize: true,
|
||||
cell: ({ value }): React.ReactNode => {
|
||||
const namespaceName = value as string;
|
||||
return (
|
||||
<CellValueTooltip value={namespaceName}>
|
||||
<Tooltip title={namespaceName}>
|
||||
<TanStackTable.Text>{namespaceName}</TanStackTable.Text>
|
||||
</CellValueTooltip>
|
||||
</Tooltip>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'pod_counts_by_phase',
|
||||
header: (): React.ReactNode => (
|
||||
<ColumnHeader docPath="/infrastructure-monitoring/kubernetes/jobs#pod-counts-by-phase">
|
||||
Pod Phases
|
||||
</ColumnHeader>
|
||||
),
|
||||
accessorFn: (row): InframonitoringtypesJobRecordDTO['podCountsByPhase'] =>
|
||||
row.podCountsByPhase,
|
||||
width: { min: 250 },
|
||||
enableSort: false,
|
||||
enableResize: true,
|
||||
cell: ({ row }): React.ReactNode => {
|
||||
const podCountsByPhase = row.podCountsByPhase;
|
||||
if (!podCountsByPhase) {
|
||||
return <TanStackTable.Text>-</TanStackTable.Text>;
|
||||
}
|
||||
return (
|
||||
<GroupedStatusCounts items={getPodPhaseStatusItems(podCountsByPhase)} />
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'completion_status',
|
||||
header: (): React.ReactNode => (
|
||||
<ColumnHeader docPath="/infrastructure-monitoring/kubernetes/jobs#completion-status">
|
||||
Completion Status
|
||||
</ColumnHeader>
|
||||
),
|
||||
id: 'successful_pods',
|
||||
header: 'Successful',
|
||||
accessorFn: (row): number => row.successfulPods,
|
||||
width: { min: 210 },
|
||||
enableSort: false,
|
||||
enableResize: true,
|
||||
cell: ({ row }): React.ReactNode => (
|
||||
<GroupedStatusCounts
|
||||
items={[
|
||||
{ value: row.activePods, label: 'Active', color: Color.BG_ROBIN_500 },
|
||||
{ value: row.failedPods, label: 'Failed', color: Color.BG_CHERRY_500 },
|
||||
{
|
||||
value: row.successfulPods,
|
||||
label: 'Successful',
|
||||
color: Color.BG_FOREST_500,
|
||||
},
|
||||
{
|
||||
value: row.desiredSuccessfulPods,
|
||||
label: 'Desired',
|
||||
color: Color.BG_ROBIN_500,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
),
|
||||
width: { min: 120 },
|
||||
enableSort: true,
|
||||
cell: ({ value }): React.ReactNode => {
|
||||
const successfulPods = value as number;
|
||||
return (
|
||||
<ValidateColumnValueWrapper
|
||||
value={successfulPods}
|
||||
entity={InfraMonitoringEntity.JOBS}
|
||||
attribute="successful pod"
|
||||
>
|
||||
<TanStackTable.Text>{successfulPods}</TanStackTable.Text>
|
||||
</ValidateColumnValueWrapper>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'failed_pods',
|
||||
header: 'Failed',
|
||||
accessorFn: (row): number => row.failedPods,
|
||||
width: { min: 100 },
|
||||
enableSort: true,
|
||||
cell: ({ value }): React.ReactNode => {
|
||||
const failedPods = value as number;
|
||||
return (
|
||||
<ValidateColumnValueWrapper
|
||||
value={failedPods}
|
||||
entity={InfraMonitoringEntity.JOBS}
|
||||
attribute="failed pod"
|
||||
>
|
||||
<TanStackTable.Text>{failedPods}</TanStackTable.Text>
|
||||
</ValidateColumnValueWrapper>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'desired_successful_pods',
|
||||
header: 'Desired Successful',
|
||||
accessorFn: (row): number => row.desiredSuccessfulPods,
|
||||
width: { min: 160 },
|
||||
enableSort: true,
|
||||
cell: ({ value }): React.ReactNode => {
|
||||
const desiredSuccessfulPods = value as number;
|
||||
return (
|
||||
<ValidateColumnValueWrapper
|
||||
value={desiredSuccessfulPods}
|
||||
entity={InfraMonitoringEntity.JOBS}
|
||||
attribute="desired successful pod"
|
||||
>
|
||||
<TanStackTable.Text>{desiredSuccessfulPods}</TanStackTable.Text>
|
||||
</ValidateColumnValueWrapper>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'active_pods',
|
||||
header: 'Active',
|
||||
accessorFn: (row): number => row.activePods,
|
||||
width: { min: 100 },
|
||||
enableSort: true,
|
||||
cell: ({ value }): React.ReactNode => {
|
||||
const activePods = value as number;
|
||||
return (
|
||||
<ValidateColumnValueWrapper
|
||||
value={activePods}
|
||||
entity={InfraMonitoringEntity.JOBS}
|
||||
attribute="active pod"
|
||||
>
|
||||
<TanStackTable.Text>{activePods}</TanStackTable.Text>
|
||||
</ValidateColumnValueWrapper>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'cpu_request',
|
||||
header: (): React.ReactNode => (
|
||||
<ColumnHeader docPath="/infrastructure-monitoring/kubernetes/jobs#cpu-req-usage-">
|
||||
CPU Request
|
||||
<br /> Usage (%)
|
||||
</ColumnHeader>
|
||||
),
|
||||
accessorFn: (row): number => row.jobCPURequest,
|
||||
header: 'CPU Req Usage (%)',
|
||||
accessorFn: (row): number => row.cpuRequest,
|
||||
width: { min: 200, default: 200 },
|
||||
enableSort: true,
|
||||
enableResize: true,
|
||||
defaultVisibility: false,
|
||||
cell: ({ value }): React.ReactNode => {
|
||||
const cpuRequest = value as number;
|
||||
return (
|
||||
@@ -183,16 +177,10 @@ export const k8sJobsColumnsConfig: JobTableColumnConfig[] = [
|
||||
},
|
||||
{
|
||||
id: 'cpu_limit',
|
||||
header: (): React.ReactNode => (
|
||||
<ColumnHeader docPath="/infrastructure-monitoring/kubernetes/jobs#cpu-limit-usage-">
|
||||
CPU Limit
|
||||
<br /> Usage (%)
|
||||
</ColumnHeader>
|
||||
),
|
||||
accessorFn: (row): number => row.jobCPULimit,
|
||||
header: 'CPU Limit Usage (%)',
|
||||
accessorFn: (row): number => row.cpuLimit,
|
||||
width: { min: 200, default: 200 },
|
||||
enableSort: true,
|
||||
enableResize: true,
|
||||
cell: ({ value }): React.ReactNode => {
|
||||
const cpuLimit = value as number;
|
||||
return (
|
||||
@@ -208,42 +196,29 @@ export const k8sJobsColumnsConfig: JobTableColumnConfig[] = [
|
||||
},
|
||||
{
|
||||
id: 'cpu',
|
||||
header: (): React.ReactNode => (
|
||||
<ColumnHeader docPath="/infrastructure-monitoring/kubernetes/jobs#cpu-usage-cores">
|
||||
CPU Usage
|
||||
<br /> (cores)
|
||||
</ColumnHeader>
|
||||
),
|
||||
accessorFn: (row): number => row.jobCPU,
|
||||
width: { min: 160 },
|
||||
header: 'CPU Usage (cores)',
|
||||
accessorFn: (row): number => row.cpuUsage,
|
||||
width: { min: 190 },
|
||||
enableSort: true,
|
||||
enableResize: true,
|
||||
cell: ({ value }): React.ReactNode => {
|
||||
const cpu = Number(value);
|
||||
const cpu = value as number;
|
||||
return (
|
||||
<ValidateColumnValueWrapper
|
||||
value={cpu}
|
||||
entity={InfraMonitoringEntity.JOBS}
|
||||
attribute="CPU metric"
|
||||
>
|
||||
<TanStackTable.Text>{cpu.toFixed(2)}</TanStackTable.Text>
|
||||
<TanStackTable.Text>{cpu}</TanStackTable.Text>
|
||||
</ValidateColumnValueWrapper>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'memory_request',
|
||||
header: (): React.ReactNode => (
|
||||
<ColumnHeader docPath="/infrastructure-monitoring/kubernetes/jobs#mem-req-usage-">
|
||||
Memory Request
|
||||
<br /> Usage (%)
|
||||
</ColumnHeader>
|
||||
),
|
||||
accessorFn: (row): number => row.jobMemoryRequest,
|
||||
header: 'Mem Req Usage (%)',
|
||||
accessorFn: (row): number => row.memoryRequest,
|
||||
width: { min: 190 },
|
||||
enableSort: true,
|
||||
enableResize: true,
|
||||
defaultVisibility: false,
|
||||
cell: ({ value }): React.ReactNode => {
|
||||
const memoryRequest = value as number;
|
||||
return (
|
||||
@@ -259,16 +234,10 @@ export const k8sJobsColumnsConfig: JobTableColumnConfig[] = [
|
||||
},
|
||||
{
|
||||
id: 'memory_limit',
|
||||
header: (): React.ReactNode => (
|
||||
<ColumnHeader docPath="/infrastructure-monitoring/kubernetes/jobs#mem-limit-usage-">
|
||||
Memory Limit
|
||||
<br /> Usage (%)
|
||||
</ColumnHeader>
|
||||
),
|
||||
accessorFn: (row): number => row.jobMemoryLimit,
|
||||
header: 'Mem Limit Usage (%)',
|
||||
accessorFn: (row): number => row.memoryLimit,
|
||||
width: { min: 180 },
|
||||
enableSort: true,
|
||||
enableResize: true,
|
||||
cell: ({ value }): React.ReactNode => {
|
||||
const memoryLimit = value as number;
|
||||
return (
|
||||
@@ -284,16 +253,10 @@ export const k8sJobsColumnsConfig: JobTableColumnConfig[] = [
|
||||
},
|
||||
{
|
||||
id: 'memory',
|
||||
header: (): React.ReactNode => (
|
||||
<ColumnHeader docPath="/infrastructure-monitoring/kubernetes/jobs#mem-usage-wss">
|
||||
Memory Usage
|
||||
<br /> (WSS)
|
||||
</ColumnHeader>
|
||||
),
|
||||
accessorFn: (row): number => row.jobMemory,
|
||||
header: 'Mem Usage (WSS)',
|
||||
accessorFn: (row): number => row.memoryUsage,
|
||||
width: { min: 160 },
|
||||
enableSort: true,
|
||||
enableResize: true,
|
||||
cell: ({ value }): React.ReactNode => {
|
||||
const memory = value as number;
|
||||
return (
|
||||
@@ -307,100 +270,4 @@ export const k8sJobsColumnsConfig: JobTableColumnConfig[] = [
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'active_pods',
|
||||
header: (): React.ReactNode => (
|
||||
<ColumnHeader docPath="/infrastructure-monitoring/kubernetes/jobs#active">
|
||||
Active Pods
|
||||
</ColumnHeader>
|
||||
),
|
||||
accessorFn: (row): number => row.activePods,
|
||||
width: { min: 100 },
|
||||
enableSort: true,
|
||||
defaultVisibility: false,
|
||||
cell: ({ value }): React.ReactNode => {
|
||||
const activePods = value as number;
|
||||
return (
|
||||
<ValidateColumnValueWrapper
|
||||
value={activePods}
|
||||
entity={InfraMonitoringEntity.JOBS}
|
||||
attribute="active pod"
|
||||
>
|
||||
<TanStackTable.Text>{activePods}</TanStackTable.Text>
|
||||
</ValidateColumnValueWrapper>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'failed_pods',
|
||||
header: (): React.ReactNode => (
|
||||
<ColumnHeader docPath="/infrastructure-monitoring/kubernetes/jobs#failed">
|
||||
Failed Pods
|
||||
</ColumnHeader>
|
||||
),
|
||||
accessorFn: (row): number => row.failedPods,
|
||||
width: { min: 100 },
|
||||
enableSort: true,
|
||||
defaultVisibility: false,
|
||||
cell: ({ value }): React.ReactNode => {
|
||||
const failedPods = value as number;
|
||||
return (
|
||||
<ValidateColumnValueWrapper
|
||||
value={failedPods}
|
||||
entity={InfraMonitoringEntity.JOBS}
|
||||
attribute="failed pod"
|
||||
>
|
||||
<TanStackTable.Text>{failedPods}</TanStackTable.Text>
|
||||
</ValidateColumnValueWrapper>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'successful_pods',
|
||||
header: (): React.ReactNode => (
|
||||
<ColumnHeader docPath="/infrastructure-monitoring/kubernetes/jobs#successful">
|
||||
Successful Pods
|
||||
</ColumnHeader>
|
||||
),
|
||||
accessorFn: (row): number => row.successfulPods,
|
||||
width: { min: 120 },
|
||||
enableSort: true,
|
||||
defaultVisibility: false,
|
||||
cell: ({ value }): React.ReactNode => {
|
||||
const successfulPods = value as number;
|
||||
return (
|
||||
<ValidateColumnValueWrapper
|
||||
value={successfulPods}
|
||||
entity={InfraMonitoringEntity.JOBS}
|
||||
attribute="successful pod"
|
||||
>
|
||||
<TanStackTable.Text>{successfulPods}</TanStackTable.Text>
|
||||
</ValidateColumnValueWrapper>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'desired_successful_pods',
|
||||
header: (): React.ReactNode => (
|
||||
<ColumnHeader docPath="/infrastructure-monitoring/kubernetes/jobs#desired-successful">
|
||||
Desired Successful Pods
|
||||
</ColumnHeader>
|
||||
),
|
||||
accessorFn: (row): number => row.desiredSuccessfulPods,
|
||||
width: { min: 160 },
|
||||
enableSort: true,
|
||||
defaultVisibility: false,
|
||||
cell: ({ value }): React.ReactNode => {
|
||||
const desiredSuccessfulPods = value as number;
|
||||
return (
|
||||
<ValidateColumnValueWrapper
|
||||
value={desiredSuccessfulPods}
|
||||
entity={InfraMonitoringEntity.JOBS}
|
||||
attribute="desired successful pod"
|
||||
>
|
||||
<TanStackTable.Text>{desiredSuccessfulPods}</TanStackTable.Text>
|
||||
</ValidateColumnValueWrapper>
|
||||
);
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
@@ -1,23 +1,20 @@
|
||||
import { useCallback } from 'react';
|
||||
import { listNamespaces } from 'api/generated/services/inframonitoring';
|
||||
import {
|
||||
InframonitoringtypesNamespaceRecordDTO,
|
||||
InframonitoringtypesResponseTypeDTO,
|
||||
Querybuildertypesv5OrderDirectionDTO,
|
||||
} from 'api/generated/services/sigNoz.schemas';
|
||||
import React, { useCallback } from 'react';
|
||||
import { InfraMonitoringEvents } from 'constants/events';
|
||||
import { FeatureKeys } from 'constants/features';
|
||||
import { useAppContext } from 'providers/App/App';
|
||||
|
||||
import K8sBaseDetails, { K8sDetailsFilters } from '../Base/K8sBaseDetails';
|
||||
import { K8sBaseList } from '../Base/K8sBaseList';
|
||||
import { K8sBaseFilters } from '../Base/types';
|
||||
import { InfraMonitoringEntity } from '../constants';
|
||||
import { getK8sNamespacesList, K8sNamespacesData } from './api';
|
||||
import {
|
||||
getNamespaceMetricsQueryPayload,
|
||||
k8sNamespaceDetailsMetadataConfig,
|
||||
k8sNamespaceGetEntityName,
|
||||
k8sNamespaceGetSelectedItemExpression,
|
||||
k8sNamespaceInitialEventsExpression,
|
||||
k8sNamespaceInitialLogTracesExpression,
|
||||
k8sNamespaceGetSelectedItemFilters,
|
||||
k8sNamespaceInitialEventsFilter,
|
||||
k8sNamespaceInitialLogTracesFilter,
|
||||
namespaceWidgetInfo,
|
||||
} from './constants';
|
||||
import {
|
||||
@@ -31,93 +28,66 @@ function K8sNamespacesList({
|
||||
}: {
|
||||
controlListPrefix?: React.ReactNode;
|
||||
}): JSX.Element {
|
||||
const { featureFlags } = useAppContext();
|
||||
const dotMetricsEnabled =
|
||||
featureFlags?.find((flag) => flag.name === FeatureKeys.DOT_METRICS_ENABLED)
|
||||
?.active || false;
|
||||
|
||||
const fetchListData = useCallback(
|
||||
async (filters: K8sBaseFilters, signal?: AbortSignal) => {
|
||||
try {
|
||||
const response = await listNamespaces(
|
||||
{
|
||||
filter: { expression: filters.filter.expression },
|
||||
groupBy: filters.groupBy?.map((g) => ({ name: g.name })),
|
||||
offset: filters.offset,
|
||||
limit: filters.limit ?? 10,
|
||||
start: filters.start,
|
||||
end: filters.end,
|
||||
orderBy: filters.orderBy
|
||||
? {
|
||||
key: { name: filters.orderBy.key.name },
|
||||
direction:
|
||||
filters.orderBy.direction === 'asc'
|
||||
? Querybuildertypesv5OrderDirectionDTO.asc
|
||||
: Querybuildertypesv5OrderDirectionDTO.desc,
|
||||
}
|
||||
: undefined,
|
||||
},
|
||||
signal,
|
||||
);
|
||||
filters.orderBy ||= {
|
||||
columnName: 'cpu',
|
||||
order: 'desc',
|
||||
};
|
||||
|
||||
const data = response.data;
|
||||
return {
|
||||
type:
|
||||
data.type === InframonitoringtypesResponseTypeDTO.grouped_list
|
||||
? ('grouped_list' as const)
|
||||
: ('list' as const),
|
||||
records: data.records,
|
||||
total: data.total,
|
||||
endTimeBeforeRetention: data.endTimeBeforeRetention,
|
||||
warning: data.warning,
|
||||
};
|
||||
} catch (error) {
|
||||
const errMsg =
|
||||
error instanceof Error ? error.message : 'Failed to fetch namespaces';
|
||||
return {
|
||||
type: 'list' as const,
|
||||
records: [] as InframonitoringtypesNamespaceRecordDTO[],
|
||||
total: 0,
|
||||
error: errMsg,
|
||||
};
|
||||
}
|
||||
const response = await getK8sNamespacesList(
|
||||
filters,
|
||||
signal,
|
||||
undefined,
|
||||
dotMetricsEnabled,
|
||||
);
|
||||
|
||||
return {
|
||||
data: response.payload?.data.records || [],
|
||||
total: response.payload?.data.total || 0,
|
||||
error: response.error,
|
||||
rawData: response.payload?.data,
|
||||
};
|
||||
},
|
||||
[],
|
||||
[dotMetricsEnabled],
|
||||
);
|
||||
|
||||
const fetchEntityData = useCallback(
|
||||
async (
|
||||
filters: K8sDetailsFilters,
|
||||
signal?: AbortSignal,
|
||||
): Promise<{
|
||||
data: InframonitoringtypesNamespaceRecordDTO | null;
|
||||
error?: string | null;
|
||||
}> => {
|
||||
try {
|
||||
const response = await listNamespaces(
|
||||
{
|
||||
filter: { expression: filters.filter.expression },
|
||||
start: filters.start,
|
||||
end: filters.end,
|
||||
limit: 1,
|
||||
offset: 0,
|
||||
},
|
||||
signal,
|
||||
);
|
||||
): Promise<{ data: K8sNamespacesData | null; error?: string | null }> => {
|
||||
const response = await getK8sNamespacesList(
|
||||
{
|
||||
filters: filters.filters,
|
||||
start: filters.start,
|
||||
end: filters.end,
|
||||
limit: 1,
|
||||
offset: 0,
|
||||
},
|
||||
signal,
|
||||
undefined,
|
||||
dotMetricsEnabled,
|
||||
);
|
||||
|
||||
return {
|
||||
data: response.data.records.length > 0 ? response.data.records[0] : null,
|
||||
};
|
||||
} catch (error) {
|
||||
const errMsg =
|
||||
error instanceof Error ? error.message : 'Failed to fetch namespace';
|
||||
return {
|
||||
data: null,
|
||||
error: errMsg,
|
||||
};
|
||||
}
|
||||
const records = response.payload?.data.records || [];
|
||||
|
||||
return {
|
||||
data: records.length > 0 ? records[0] : null,
|
||||
error: response.error,
|
||||
};
|
||||
},
|
||||
[],
|
||||
[dotMetricsEnabled],
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<K8sBaseList<InframonitoringtypesNamespaceRecordDTO>
|
||||
<K8sBaseList<K8sNamespacesData>
|
||||
controlListPrefix={controlListPrefix}
|
||||
entity={InfraMonitoringEntity.NAMESPACES}
|
||||
tableColumns={k8sNamespacesColumnsConfig}
|
||||
@@ -127,14 +97,14 @@ function K8sNamespacesList({
|
||||
eventCategory={InfraMonitoringEvents.Namespace}
|
||||
/>
|
||||
|
||||
<K8sBaseDetails<InframonitoringtypesNamespaceRecordDTO>
|
||||
<K8sBaseDetails<K8sNamespacesData>
|
||||
category={InfraMonitoringEntity.NAMESPACES}
|
||||
eventCategory={InfraMonitoringEvents.Namespace}
|
||||
getSelectedItemExpression={k8sNamespaceGetSelectedItemExpression}
|
||||
getSelectedItemFilters={k8sNamespaceGetSelectedItemFilters}
|
||||
fetchEntityData={fetchEntityData}
|
||||
getEntityName={k8sNamespaceGetEntityName}
|
||||
getInitialLogTracesExpression={k8sNamespaceInitialLogTracesExpression}
|
||||
getInitialEventsExpression={k8sNamespaceInitialEventsExpression}
|
||||
getInitialLogTracesFilters={k8sNamespaceInitialLogTracesFilter}
|
||||
getInitialEventsFilters={k8sNamespaceInitialEventsFilter}
|
||||
metadataConfig={k8sNamespaceDetailsMetadataConfig}
|
||||
entityWidgetInfo={namespaceWidgetInfo}
|
||||
getEntityQueryPayload={getNamespaceMetricsQueryPayload}
|
||||
|
||||
123
frontend/src/container/InfraMonitoringK8sV2/Namespaces/api.ts
Normal file
123
frontend/src/container/InfraMonitoringK8sV2/Namespaces/api.ts
Normal file
@@ -0,0 +1,123 @@
|
||||
import axios from 'api';
|
||||
import { ErrorResponseHandler } from 'api/ErrorResponseHandler';
|
||||
import { UnderscoreToDotMap } from 'api/utils';
|
||||
import { AxiosError } from 'axios';
|
||||
import { ErrorResponse, SuccessResponse } from 'types/api';
|
||||
import { BaseAutocompleteData } from 'types/api/queryBuilder/queryAutocompleteResponse';
|
||||
import { TagFilter } from 'types/api/queryBuilder/queryBuilderData';
|
||||
|
||||
import { K8sBaseFilters } from '../Base/types';
|
||||
|
||||
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;
|
||||
};
|
||||
}
|
||||
|
||||
// TODO(H4ad): Erase this whole file when migrating to openapi
|
||||
export const namespacesMetaMap = [
|
||||
{ dot: 'k8s.cluster.name', under: 'k8s_cluster_name' },
|
||||
{ dot: 'k8s.namespace.name', under: 'k8s_namespace_name' },
|
||||
] as const;
|
||||
|
||||
export function mapNamespacesMeta(
|
||||
raw: Record<string, unknown>,
|
||||
): K8sNamespacesData['meta'] {
|
||||
const out: Record<string, unknown> = { ...raw };
|
||||
namespacesMetaMap.forEach(({ dot, under }) => {
|
||||
if (dot in raw) {
|
||||
const v = raw[dot];
|
||||
out[under] = typeof v === 'string' ? v : raw[under];
|
||||
}
|
||||
});
|
||||
return out as K8sNamespacesData['meta'];
|
||||
}
|
||||
|
||||
export const getK8sNamespacesList = async (
|
||||
props: K8sBaseFilters,
|
||||
signal?: AbortSignal,
|
||||
headers?: Record<string, string>,
|
||||
dotMetricsEnabled = false,
|
||||
): Promise<SuccessResponse<K8sNamespacesListResponse> | ErrorResponse> => {
|
||||
try {
|
||||
const requestProps = dotMetricsEnabled
|
||||
? {
|
||||
...props,
|
||||
filters: {
|
||||
...props.filters,
|
||||
items: props.filters?.items.reduce<typeof props.filters.items>(
|
||||
(acc, item) => {
|
||||
if (item.value === undefined) {
|
||||
return acc;
|
||||
}
|
||||
if (
|
||||
item.key &&
|
||||
typeof item.key === 'object' &&
|
||||
'key' in item.key &&
|
||||
typeof item.key.key === 'string'
|
||||
) {
|
||||
const mappedKey = UnderscoreToDotMap[item.key.key] ?? item.key.key;
|
||||
acc.push({
|
||||
...item,
|
||||
key: { ...item.key, key: mappedKey },
|
||||
});
|
||||
} else {
|
||||
acc.push(item);
|
||||
}
|
||||
return acc;
|
||||
},
|
||||
[] as typeof props.filters.items,
|
||||
),
|
||||
},
|
||||
}
|
||||
: props;
|
||||
|
||||
const response = await axios.post('/namespaces/list', requestProps, {
|
||||
signal,
|
||||
headers,
|
||||
});
|
||||
const payload: K8sNamespacesListResponse = response.data;
|
||||
|
||||
payload.data.records = payload.data.records.map((record) => ({
|
||||
...record,
|
||||
meta: mapNamespacesMeta(record.meta as Record<string, unknown>),
|
||||
}));
|
||||
|
||||
return {
|
||||
statusCode: 200,
|
||||
error: null,
|
||||
message: 'Success',
|
||||
payload,
|
||||
params: requestProps,
|
||||
};
|
||||
} catch (error) {
|
||||
return ErrorResponseHandler(error as AxiosError);
|
||||
}
|
||||
};
|
||||
@@ -1,47 +1,67 @@
|
||||
import { InframonitoringtypesNamespaceRecordDTO } from 'api/generated/services/sigNoz.schemas';
|
||||
import { PANEL_TYPES } from 'constants/queryBuilder';
|
||||
import { GetQueryResultsProps } from 'lib/dashboard/getQueryResults';
|
||||
import { DataTypes } from 'types/api/queryBuilder/queryAutocompleteResponse';
|
||||
import {
|
||||
TagFilter,
|
||||
TagFilterItem,
|
||||
} from 'types/api/queryBuilder/queryBuilderData';
|
||||
import { EQueryType } from 'types/common/dashboard';
|
||||
import { DataSource, ReduceOperators } from 'types/common/queryBuilder';
|
||||
import { v4 } from 'uuid';
|
||||
|
||||
import { K8sDetailsMetadataConfig } from '../Base/K8sBaseDetails';
|
||||
import { formatValueForExpression } from 'components/QueryBuilderV2/utils';
|
||||
import { INFRA_MONITORING_ATTR_KEYS } from '../constants';
|
||||
import {
|
||||
createFilterItem,
|
||||
K8sDetailsMetadataConfig,
|
||||
} from '../Base/K8sBaseDetails';
|
||||
import { QUERY_KEYS } from '../EntityDetailsUtils/utils';
|
||||
import { K8sNamespacesData } from './api';
|
||||
|
||||
export const k8sNamespaceGetSelectedItemExpression = (
|
||||
export const k8sNamespaceGetSelectedItemFilters = (
|
||||
selectedItemId: string,
|
||||
): string =>
|
||||
`${INFRA_MONITORING_ATTR_KEYS.K8S_NAMESPACE_NAME} = ${formatValueForExpression(selectedItemId)}`;
|
||||
): TagFilter => ({
|
||||
op: 'AND',
|
||||
items: [
|
||||
{
|
||||
id: 'k8s_namespace_name',
|
||||
key: {
|
||||
key: 'k8s_namespace_name',
|
||||
type: null,
|
||||
},
|
||||
op: '=',
|
||||
value: selectedItemId,
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
export const k8sNamespaceDetailsMetadataConfig: K8sDetailsMetadataConfig<InframonitoringtypesNamespaceRecordDTO>[] =
|
||||
export const k8sNamespaceDetailsMetadataConfig: K8sDetailsMetadataConfig<K8sNamespacesData>[] =
|
||||
[
|
||||
{ label: 'Namespace Name', getValue: (p): string => p.namespaceName || '' },
|
||||
{ label: 'Namespace Name', getValue: (p): string => p.namespaceName },
|
||||
{
|
||||
label: 'Cluster Name',
|
||||
getValue: (p): string =>
|
||||
p.meta?.[INFRA_MONITORING_ATTR_KEYS.K8S_CLUSTER_NAME] || '',
|
||||
getValue: (p): string => p.meta.k8s_cluster_name,
|
||||
},
|
||||
];
|
||||
|
||||
export const k8sNamespaceInitialEventsExpression = (
|
||||
item: InframonitoringtypesNamespaceRecordDTO,
|
||||
): string => {
|
||||
const name = formatValueForExpression(item.namespaceName || '');
|
||||
return `${INFRA_MONITORING_ATTR_KEYS.K8S_OBJECT_KIND} = 'Namespace' AND ${INFRA_MONITORING_ATTR_KEYS.K8S_OBJECT_NAME} = ${name}`;
|
||||
};
|
||||
export const k8sNamespaceInitialFilters = [
|
||||
QUERY_KEYS.K8S_NAMESPACE_NAME,
|
||||
QUERY_KEYS.K8S_CLUSTER_NAME,
|
||||
];
|
||||
|
||||
export const k8sNamespaceInitialLogTracesExpression = (
|
||||
item: InframonitoringtypesNamespaceRecordDTO,
|
||||
): string => {
|
||||
const name = formatValueForExpression(item.namespaceName || '');
|
||||
return `${INFRA_MONITORING_ATTR_KEYS.K8S_NAMESPACE_NAME} = ${name}`;
|
||||
};
|
||||
export const k8sNamespaceInitialEventsFilter = (
|
||||
item: K8sNamespacesData,
|
||||
): TagFilterItem[] => [
|
||||
createFilterItem(QUERY_KEYS.K8S_OBJECT_KIND, 'Namespace'),
|
||||
createFilterItem(QUERY_KEYS.K8S_OBJECT_NAME, item.namespaceName),
|
||||
];
|
||||
|
||||
export const k8sNamespaceGetEntityName = (
|
||||
item: InframonitoringtypesNamespaceRecordDTO,
|
||||
): string => item.namespaceName || '';
|
||||
export const k8sNamespaceInitialLogTracesFilter = (
|
||||
item: K8sNamespacesData,
|
||||
): TagFilterItem[] => [
|
||||
createFilterItem(QUERY_KEYS.K8S_NAMESPACE_NAME, item.namespaceName),
|
||||
];
|
||||
|
||||
export const k8sNamespaceGetEntityName = (item: K8sNamespacesData): string =>
|
||||
item.namespaceName;
|
||||
|
||||
export const namespaceWidgetInfo = [
|
||||
{
|
||||
@@ -87,7 +107,7 @@ export const namespaceWidgetInfo = [
|
||||
];
|
||||
|
||||
export const getNamespaceMetricsQueryPayload = (
|
||||
namespace: InframonitoringtypesNamespaceRecordDTO,
|
||||
namespace: K8sNamespacesData,
|
||||
start: number,
|
||||
end: number,
|
||||
dotMetricsEnabled: boolean,
|
||||
|
||||
@@ -1,46 +1,37 @@
|
||||
import { FilePenLine } from '@signozhq/icons';
|
||||
import { InframonitoringtypesNamespaceRecordDTO } from 'api/generated/services/sigNoz.schemas';
|
||||
import { Tooltip } from 'antd';
|
||||
import TanStackTable, { TableColumnDef } from 'components/TanStackTableView';
|
||||
import { ExpandButtonWrapper } from 'container/InfraMonitoringK8sV2/components';
|
||||
|
||||
import ColumnHeader from '../Base/ColumnHeader';
|
||||
import EntityGroupHeader from '../Base/EntityGroupHeader';
|
||||
import K8sGroupCell from '../Base/K8sGroupCell';
|
||||
import { formatBytes, getPodPhaseStatusItems } from '../commonUtils';
|
||||
import {
|
||||
CellValueTooltip,
|
||||
GroupedStatusCounts,
|
||||
ValidateColumnValueWrapper,
|
||||
} from '../components';
|
||||
import {
|
||||
INFRA_MONITORING_ATTR_KEYS,
|
||||
InfraMonitoringEntity,
|
||||
} from '../constants';
|
||||
import { formatBytes } from '../commonUtils';
|
||||
import { ValidateColumnValueWrapper } from '../components';
|
||||
import { InfraMonitoringEntity } from '../constants';
|
||||
import { K8sNamespacesData, K8sNamespacesListPayload } from './api';
|
||||
import { FilePenLine } from '@signozhq/icons';
|
||||
|
||||
export function getK8sNamespaceRowKey(
|
||||
namespace: InframonitoringtypesNamespaceRecordDTO,
|
||||
): string {
|
||||
return (
|
||||
namespace.namespaceName ||
|
||||
namespace.meta?.[INFRA_MONITORING_ATTR_KEYS.K8S_NAMESPACE_NAME] ||
|
||||
''
|
||||
);
|
||||
export function getK8sNamespaceRowKey(namespace: K8sNamespacesData): string {
|
||||
return namespace.namespaceName || namespace.meta.k8s_namespace_name;
|
||||
}
|
||||
|
||||
export function getK8sNamespaceItemKey(
|
||||
namespace: InframonitoringtypesNamespaceRecordDTO,
|
||||
): string {
|
||||
return namespace.namespaceName;
|
||||
export function getK8sNamespaceItemKey(namespace: K8sNamespacesData): string {
|
||||
return namespace.meta.k8s_namespace_name;
|
||||
}
|
||||
|
||||
export type NamespaceTableColumnConfig =
|
||||
TableColumnDef<InframonitoringtypesNamespaceRecordDTO>;
|
||||
export const k8sNamespacesColumnsConfig: NamespaceTableColumnConfig[] = [
|
||||
export const getK8sNamespacesListQuery = (): K8sNamespacesListPayload => ({
|
||||
filters: {
|
||||
items: [],
|
||||
op: 'and',
|
||||
},
|
||||
orderBy: { columnName: 'cpu', order: 'desc' },
|
||||
});
|
||||
|
||||
export const k8sNamespacesColumnsConfig: TableColumnDef<K8sNamespacesData>[] = [
|
||||
{
|
||||
id: 'namespaceGroup',
|
||||
header: (): React.ReactNode => <EntityGroupHeader title="Namespace Group" />,
|
||||
accessorFn: (row): string => row.namespaceName || '',
|
||||
width: { min: 290 },
|
||||
header: (): React.ReactNode => <EntityGroupHeader title="NAMESPACE GROUP" />,
|
||||
accessorFn: (row): string => row.meta.k8s_namespace_name || '',
|
||||
width: { min: 300 },
|
||||
enableSort: false,
|
||||
enableRemove: false,
|
||||
enableMove: false,
|
||||
@@ -63,7 +54,6 @@ export const k8sNamespacesColumnsConfig: NamespaceTableColumnConfig[] = [
|
||||
<EntityGroupHeader
|
||||
title="Namespace Name"
|
||||
icon={<FilePenLine data-hide-expanded="true" size={14} />}
|
||||
docPath="/infrastructure-monitoring/kubernetes/namespaces#namespace-name"
|
||||
/>
|
||||
),
|
||||
accessorFn: (row): string => row.namespaceName || '',
|
||||
@@ -76,85 +66,49 @@ export const k8sNamespacesColumnsConfig: NamespaceTableColumnConfig[] = [
|
||||
cell: ({ value }): React.ReactNode => {
|
||||
const namespaceName = value as string;
|
||||
return (
|
||||
<CellValueTooltip value={namespaceName}>
|
||||
<Tooltip title={namespaceName}>
|
||||
<TanStackTable.Text>{namespaceName}</TanStackTable.Text>
|
||||
</CellValueTooltip>
|
||||
</Tooltip>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'clusterName',
|
||||
header: (): React.ReactNode => (
|
||||
<ColumnHeader docPath="/infrastructure-monitoring/kubernetes/namespaces#cluster-name">
|
||||
Cluster Name
|
||||
</ColumnHeader>
|
||||
),
|
||||
accessorFn: (row): string =>
|
||||
row.meta?.[INFRA_MONITORING_ATTR_KEYS.K8S_CLUSTER_NAME] || '',
|
||||
header: 'Cluster Name',
|
||||
accessorFn: (row): string => row.meta.k8s_cluster_name || '',
|
||||
width: { default: 150 },
|
||||
enableSort: false,
|
||||
cell: ({ value }): React.ReactNode => (
|
||||
<TanStackTable.Text>{value as string}</TanStackTable.Text>
|
||||
),
|
||||
},
|
||||
{
|
||||
id: 'podCountsByPhase',
|
||||
header: (): React.ReactNode => (
|
||||
<ColumnHeader docPath="/infrastructure-monitoring/kubernetes/namespaces#pod-counts-by-phase">
|
||||
Pod Phases
|
||||
</ColumnHeader>
|
||||
),
|
||||
accessorFn: (
|
||||
row,
|
||||
): InframonitoringtypesNamespaceRecordDTO['podCountsByPhase'] =>
|
||||
row.podCountsByPhase,
|
||||
width: { min: 250 },
|
||||
enableSort: false,
|
||||
cell: ({ row }): React.ReactNode => {
|
||||
const podCountsByPhase = row.podCountsByPhase;
|
||||
if (!podCountsByPhase) {
|
||||
return <TanStackTable.Text>-</TanStackTable.Text>;
|
||||
}
|
||||
return (
|
||||
<GroupedStatusCounts items={getPodPhaseStatusItems(row.podCountsByPhase)} />
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'cpu',
|
||||
header: (): React.ReactNode => (
|
||||
<ColumnHeader docPath="/infrastructure-monitoring/kubernetes/namespaces#cpu-usage-cores">
|
||||
CPU Usage (cores)
|
||||
</ColumnHeader>
|
||||
),
|
||||
accessorFn: (row): number => row.namespaceCPU,
|
||||
width: { min: 190 },
|
||||
header: 'CPU Usage (cores)',
|
||||
accessorFn: (row): number => row.cpuUsage,
|
||||
width: { min: 220 },
|
||||
enableSort: true,
|
||||
cell: ({ value }): React.ReactNode => {
|
||||
const cpu = Number(value);
|
||||
const cpu = value as number;
|
||||
return (
|
||||
<ValidateColumnValueWrapper
|
||||
value={cpu}
|
||||
entity={InfraMonitoringEntity.NAMESPACES}
|
||||
attribute="CPU metric"
|
||||
>
|
||||
<TanStackTable.Text>{cpu.toFixed(2)}</TanStackTable.Text>
|
||||
<TanStackTable.Text>{cpu}</TanStackTable.Text>
|
||||
</ValidateColumnValueWrapper>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'memory',
|
||||
header: (): React.ReactNode => (
|
||||
<ColumnHeader docPath="/infrastructure-monitoring/kubernetes/namespaces#mem-usage-wss">
|
||||
Memory Usage (WSS)
|
||||
</ColumnHeader>
|
||||
),
|
||||
accessorFn: (row): number => row.namespaceMemory,
|
||||
header: 'Mem Usage (WSS)',
|
||||
accessorFn: (row): number => row.memoryUsage,
|
||||
width: { min: 220 },
|
||||
enableSort: true,
|
||||
cell: ({ value }): React.ReactNode => {
|
||||
const memory = Number(value);
|
||||
const memory = value as number;
|
||||
return (
|
||||
<ValidateColumnValueWrapper
|
||||
value={memory}
|
||||
|
||||
@@ -1,23 +1,20 @@
|
||||
import { useCallback } from 'react';
|
||||
import { listNodes } from 'api/generated/services/inframonitoring';
|
||||
import {
|
||||
InframonitoringtypesNodeRecordDTO,
|
||||
InframonitoringtypesResponseTypeDTO,
|
||||
Querybuildertypesv5OrderDirectionDTO,
|
||||
} from 'api/generated/services/sigNoz.schemas';
|
||||
import React, { useCallback } from 'react';
|
||||
import { InfraMonitoringEvents } from 'constants/events';
|
||||
import { FeatureKeys } from 'constants/features';
|
||||
import { useAppContext } from 'providers/App/App';
|
||||
|
||||
import K8sBaseDetails, { K8sDetailsFilters } from '../Base/K8sBaseDetails';
|
||||
import { K8sBaseList } from '../Base/K8sBaseList';
|
||||
import { K8sBaseFilters } from '../Base/types';
|
||||
import { InfraMonitoringEntity } from '../constants';
|
||||
import { getK8sNodesList, K8sNodeData } from './api';
|
||||
import {
|
||||
getNodeMetricsQueryPayload,
|
||||
k8sNodeDetailsMetadataConfig,
|
||||
k8sNodeGetEntityName,
|
||||
k8sNodeGetSelectedItemExpression,
|
||||
k8sNodeInitialEventsExpression,
|
||||
k8sNodeInitialLogTracesExpression,
|
||||
k8sNodeGetSelectedItemFilters,
|
||||
k8sNodeInitialEventsFilter,
|
||||
k8sNodeInitialLogTracesFilter,
|
||||
nodeWidgetInfo,
|
||||
} from './constants';
|
||||
import {
|
||||
@@ -31,93 +28,66 @@ function K8sNodesList({
|
||||
}: {
|
||||
controlListPrefix?: React.ReactNode;
|
||||
}): JSX.Element {
|
||||
const { featureFlags } = useAppContext();
|
||||
const dotMetricsEnabled =
|
||||
featureFlags?.find((flag) => flag.name === FeatureKeys.DOT_METRICS_ENABLED)
|
||||
?.active || false;
|
||||
|
||||
const fetchListData = useCallback(
|
||||
async (filters: K8sBaseFilters, signal?: AbortSignal) => {
|
||||
try {
|
||||
const response = await listNodes(
|
||||
{
|
||||
filter: { expression: filters.filter.expression },
|
||||
groupBy: filters.groupBy?.map((g) => ({ name: g.name })),
|
||||
offset: filters.offset,
|
||||
limit: filters.limit ?? 10,
|
||||
start: filters.start,
|
||||
end: filters.end,
|
||||
orderBy: filters.orderBy
|
||||
? {
|
||||
key: { name: filters.orderBy.key.name },
|
||||
direction:
|
||||
filters.orderBy.direction === 'asc'
|
||||
? Querybuildertypesv5OrderDirectionDTO.asc
|
||||
: Querybuildertypesv5OrderDirectionDTO.desc,
|
||||
}
|
||||
: undefined,
|
||||
},
|
||||
signal,
|
||||
);
|
||||
filters.orderBy ||= {
|
||||
columnName: 'cpu',
|
||||
order: 'desc',
|
||||
};
|
||||
|
||||
const data = response.data;
|
||||
return {
|
||||
type:
|
||||
data.type === InframonitoringtypesResponseTypeDTO.grouped_list
|
||||
? ('grouped_list' as const)
|
||||
: ('list' as const),
|
||||
records: data.records,
|
||||
total: data.total,
|
||||
endTimeBeforeRetention: data.endTimeBeforeRetention,
|
||||
warning: data.warning,
|
||||
};
|
||||
} catch (error) {
|
||||
const errMsg =
|
||||
error instanceof Error ? error.message : 'Failed to fetch nodes';
|
||||
return {
|
||||
type: 'list' as const,
|
||||
records: [] as InframonitoringtypesNodeRecordDTO[],
|
||||
total: 0,
|
||||
error: errMsg,
|
||||
};
|
||||
}
|
||||
const response = await getK8sNodesList(
|
||||
filters,
|
||||
signal,
|
||||
undefined,
|
||||
dotMetricsEnabled,
|
||||
);
|
||||
|
||||
return {
|
||||
data: response.payload?.data.records || [],
|
||||
total: response.payload?.data.total || 0,
|
||||
error: response.error,
|
||||
rawData: response.payload?.data,
|
||||
};
|
||||
},
|
||||
[],
|
||||
[dotMetricsEnabled],
|
||||
);
|
||||
|
||||
const fetchEntityData = useCallback(
|
||||
async (
|
||||
filters: K8sDetailsFilters,
|
||||
signal?: AbortSignal,
|
||||
): Promise<{
|
||||
data: InframonitoringtypesNodeRecordDTO | null;
|
||||
error?: string | null;
|
||||
}> => {
|
||||
try {
|
||||
const response = await listNodes(
|
||||
{
|
||||
filter: { expression: filters.filter.expression },
|
||||
start: filters.start,
|
||||
end: filters.end,
|
||||
limit: 1,
|
||||
offset: 0,
|
||||
},
|
||||
signal,
|
||||
);
|
||||
): Promise<{ data: K8sNodeData | null; error?: string | null }> => {
|
||||
const response = await getK8sNodesList(
|
||||
{
|
||||
filters: filters.filters,
|
||||
start: filters.start,
|
||||
end: filters.end,
|
||||
limit: 1,
|
||||
offset: 0,
|
||||
},
|
||||
signal,
|
||||
undefined,
|
||||
dotMetricsEnabled,
|
||||
);
|
||||
|
||||
return {
|
||||
data: response.data.records.length > 0 ? response.data.records[0] : null,
|
||||
};
|
||||
} catch (error) {
|
||||
const errMsg =
|
||||
error instanceof Error ? error.message : 'Failed to fetch node';
|
||||
return {
|
||||
data: null,
|
||||
error: errMsg,
|
||||
};
|
||||
}
|
||||
const records = response.payload?.data.records || [];
|
||||
|
||||
return {
|
||||
data: records.length > 0 ? records[0] : null,
|
||||
error: response.error,
|
||||
};
|
||||
},
|
||||
[],
|
||||
[dotMetricsEnabled],
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<K8sBaseList<InframonitoringtypesNodeRecordDTO>
|
||||
<K8sBaseList<K8sNodeData>
|
||||
controlListPrefix={controlListPrefix}
|
||||
entity={InfraMonitoringEntity.NODES}
|
||||
tableColumns={k8sNodesColumnsConfig}
|
||||
@@ -127,14 +97,14 @@ function K8sNodesList({
|
||||
eventCategory={InfraMonitoringEvents.Node}
|
||||
/>
|
||||
|
||||
<K8sBaseDetails<InframonitoringtypesNodeRecordDTO>
|
||||
<K8sBaseDetails<K8sNodeData>
|
||||
category={InfraMonitoringEntity.NODES}
|
||||
eventCategory={InfraMonitoringEvents.Node}
|
||||
getSelectedItemExpression={k8sNodeGetSelectedItemExpression}
|
||||
getSelectedItemFilters={k8sNodeGetSelectedItemFilters}
|
||||
fetchEntityData={fetchEntityData}
|
||||
getEntityName={k8sNodeGetEntityName}
|
||||
getInitialLogTracesExpression={k8sNodeInitialLogTracesExpression}
|
||||
getInitialEventsExpression={k8sNodeInitialEventsExpression}
|
||||
getInitialLogTracesFilters={k8sNodeInitialLogTracesFilter}
|
||||
getInitialEventsFilters={k8sNodeInitialEventsFilter}
|
||||
metadataConfig={k8sNodeDetailsMetadataConfig}
|
||||
entityWidgetInfo={nodeWidgetInfo}
|
||||
getEntityQueryPayload={getNodeMetricsQueryPayload}
|
||||
|
||||
129
frontend/src/container/InfraMonitoringK8sV2/Nodes/api.ts
Normal file
129
frontend/src/container/InfraMonitoringK8sV2/Nodes/api.ts
Normal file
@@ -0,0 +1,129 @@
|
||||
import axios from 'api';
|
||||
import { ErrorResponseHandler } from 'api/ErrorResponseHandler';
|
||||
import { UnderscoreToDotMap } from 'api/utils';
|
||||
import { AxiosError } from 'axios';
|
||||
import { ErrorResponse, SuccessResponse } from 'types/api';
|
||||
import { BaseAutocompleteData } from 'types/api/queryBuilder/queryAutocompleteResponse';
|
||||
import { TagFilter } from 'types/api/queryBuilder/queryBuilderData';
|
||||
|
||||
import { K8sBaseFilters } from '../Base/types';
|
||||
|
||||
export interface K8sNodesListPayload {
|
||||
filters: TagFilter;
|
||||
groupBy?: BaseAutocompleteData[];
|
||||
offset?: number;
|
||||
limit?: number;
|
||||
orderBy?: {
|
||||
columnName: string;
|
||||
order: 'asc' | 'desc';
|
||||
};
|
||||
}
|
||||
|
||||
export interface K8sNodeData {
|
||||
nodeUID: string;
|
||||
nodeCPUUsage: number;
|
||||
nodeCPUAllocatable: number;
|
||||
nodeMemoryUsage: number;
|
||||
nodeMemoryAllocatable: number;
|
||||
meta: {
|
||||
k8s_node_name: string;
|
||||
k8s_node_uid: string;
|
||||
k8s_cluster_name: string;
|
||||
};
|
||||
}
|
||||
|
||||
export interface K8sNodesListResponse {
|
||||
status: string;
|
||||
data: {
|
||||
type: string;
|
||||
records: K8sNodeData[];
|
||||
groups: null;
|
||||
total: number;
|
||||
sentAnyHostMetricsData: boolean;
|
||||
isSendingK8SAgentMetrics: boolean;
|
||||
};
|
||||
}
|
||||
|
||||
// TODO(H4ad): Erase this whole file when migrating to openapi
|
||||
export const nodesMetaMap = [
|
||||
{ dot: 'k8s.node.name', under: 'k8s_node_name' },
|
||||
{ dot: 'k8s.node.uid', under: 'k8s_node_uid' },
|
||||
{ dot: 'k8s.cluster.name', under: 'k8s_cluster_name' },
|
||||
] as const;
|
||||
|
||||
export function mapNodesMeta(
|
||||
raw: Record<string, unknown>,
|
||||
): K8sNodeData['meta'] {
|
||||
const out: Record<string, unknown> = { ...raw };
|
||||
nodesMetaMap.forEach(({ dot, under }) => {
|
||||
if (dot in raw) {
|
||||
const v = raw[dot];
|
||||
out[under] = typeof v === 'string' ? v : raw[under];
|
||||
}
|
||||
});
|
||||
return out as K8sNodeData['meta'];
|
||||
}
|
||||
|
||||
export const getK8sNodesList = async (
|
||||
props: K8sBaseFilters,
|
||||
signal?: AbortSignal,
|
||||
headers?: Record<string, string>,
|
||||
dotMetricsEnabled = false,
|
||||
): Promise<SuccessResponse<K8sNodesListResponse> | ErrorResponse> => {
|
||||
try {
|
||||
const requestProps =
|
||||
dotMetricsEnabled && Array.isArray(props.filters?.items)
|
||||
? {
|
||||
...props,
|
||||
filters: {
|
||||
...props.filters,
|
||||
items: props.filters.items.reduce<typeof props.filters.items>(
|
||||
(acc, item) => {
|
||||
if (item.value === undefined) {
|
||||
return acc;
|
||||
}
|
||||
if (
|
||||
item.key &&
|
||||
typeof item.key === 'object' &&
|
||||
'key' in item.key &&
|
||||
typeof item.key.key === 'string'
|
||||
) {
|
||||
const mappedKey = UnderscoreToDotMap[item.key.key] ?? item.key.key;
|
||||
acc.push({
|
||||
...item,
|
||||
key: { ...item.key, key: mappedKey },
|
||||
});
|
||||
} else {
|
||||
acc.push(item);
|
||||
}
|
||||
return acc;
|
||||
},
|
||||
[] as typeof props.filters.items,
|
||||
),
|
||||
},
|
||||
}
|
||||
: props;
|
||||
|
||||
const response = await axios.post('/nodes/list', requestProps, {
|
||||
signal,
|
||||
headers,
|
||||
});
|
||||
const payload: K8sNodesListResponse = response.data;
|
||||
|
||||
// one-liner to map dot→underscore
|
||||
payload.data.records = payload.data.records.map((record) => ({
|
||||
...record,
|
||||
meta: mapNodesMeta(record.meta as Record<string, unknown>),
|
||||
}));
|
||||
|
||||
return {
|
||||
statusCode: 200,
|
||||
error: null,
|
||||
message: 'Success',
|
||||
payload,
|
||||
params: requestProps,
|
||||
};
|
||||
} catch (error) {
|
||||
return ErrorResponseHandler(error as AxiosError);
|
||||
}
|
||||
};
|
||||
@@ -1,43 +1,67 @@
|
||||
import { InframonitoringtypesNodeRecordDTO } from 'api/generated/services/sigNoz.schemas';
|
||||
import { PANEL_TYPES } from 'constants/queryBuilder';
|
||||
import { GetQueryResultsProps } from 'lib/dashboard/getQueryResults';
|
||||
import { DataTypes } from 'types/api/queryBuilder/queryAutocompleteResponse';
|
||||
import { TagFilter } from 'types/api/queryBuilder/queryBuilderData';
|
||||
import { EQueryType } from 'types/common/dashboard';
|
||||
import { DataSource, ReduceOperators } from 'types/common/queryBuilder';
|
||||
import { v4 } from 'uuid';
|
||||
|
||||
import { formatValueForExpression } from 'components/QueryBuilderV2/utils';
|
||||
import {
|
||||
createFilterItem,
|
||||
K8sDetailsMetadataConfig,
|
||||
} from '../Base/K8sBaseDetails';
|
||||
import { QUERY_KEYS } from '../EntityDetailsUtils/utils';
|
||||
import { K8sNodeData } from './api';
|
||||
|
||||
import { K8sDetailsMetadataConfig } from '../Base/K8sBaseDetails';
|
||||
import { INFRA_MONITORING_ATTR_KEYS } from '../constants';
|
||||
|
||||
export const k8sNodeGetSelectedItemExpression = (
|
||||
export const k8sNodeGetSelectedItemFilters = (
|
||||
selectedItemId: string,
|
||||
): string => `k8s.node.name = ${formatValueForExpression(selectedItemId)}`;
|
||||
): TagFilter => {
|
||||
return {
|
||||
op: 'AND',
|
||||
items: [
|
||||
{
|
||||
id: 'k8s_node_name',
|
||||
key: {
|
||||
key: 'k8s_node_name',
|
||||
type: null,
|
||||
},
|
||||
op: '=',
|
||||
value: selectedItemId,
|
||||
},
|
||||
],
|
||||
};
|
||||
};
|
||||
|
||||
export const k8sNodeDetailsMetadataConfig: K8sDetailsMetadataConfig<InframonitoringtypesNodeRecordDTO>[] =
|
||||
export const k8sNodeDetailsMetadataConfig: K8sDetailsMetadataConfig<K8sNodeData>[] =
|
||||
[
|
||||
{ label: 'Node Name', getValue: (p): string => p.nodeName || '' },
|
||||
{ label: 'Node Name', getValue: (p): string => p.meta.k8s_node_name },
|
||||
{
|
||||
label: 'Cluster Name',
|
||||
getValue: (p): string =>
|
||||
p.meta?.[INFRA_MONITORING_ATTR_KEYS.K8S_CLUSTER_NAME] || '',
|
||||
getValue: (p): string => p.meta.k8s_cluster_name,
|
||||
},
|
||||
];
|
||||
|
||||
export const k8sNodeInitialEventsExpression = (
|
||||
item: InframonitoringtypesNodeRecordDTO,
|
||||
): string =>
|
||||
`${INFRA_MONITORING_ATTR_KEYS.K8S_OBJECT_KIND} = 'Node' AND ${INFRA_MONITORING_ATTR_KEYS.K8S_OBJECT_NAME} = ${formatValueForExpression(item.nodeName || '')}`;
|
||||
export const k8sNodeInitialFilters = [
|
||||
QUERY_KEYS.K8S_NODE_NAME,
|
||||
QUERY_KEYS.K8S_CLUSTER_NAME,
|
||||
];
|
||||
|
||||
export const k8sNodeInitialLogTracesExpression = (
|
||||
item: InframonitoringtypesNodeRecordDTO,
|
||||
): string =>
|
||||
`${INFRA_MONITORING_ATTR_KEYS.K8S_NODE_NAME} = ${formatValueForExpression(item.nodeName || '')} AND ${INFRA_MONITORING_ATTR_KEYS.K8S_CLUSTER_NAME} = ${formatValueForExpression(item.meta?.[INFRA_MONITORING_ATTR_KEYS.K8S_CLUSTER_NAME] || '')}`;
|
||||
export const k8sNodeInitialEventsFilter = (
|
||||
item: K8sNodeData,
|
||||
): ReturnType<typeof createFilterItem>[] => [
|
||||
createFilterItem(QUERY_KEYS.K8S_OBJECT_KIND, 'Node'),
|
||||
createFilterItem(QUERY_KEYS.K8S_OBJECT_NAME, item.meta.k8s_node_name),
|
||||
];
|
||||
|
||||
export const k8sNodeGetEntityName = (
|
||||
item: InframonitoringtypesNodeRecordDTO,
|
||||
): string => item.nodeName || '';
|
||||
export const k8sNodeInitialLogTracesFilter = (
|
||||
item: K8sNodeData,
|
||||
): ReturnType<typeof createFilterItem>[] => [
|
||||
createFilterItem(QUERY_KEYS.K8S_NODE_NAME, item.meta.k8s_node_name),
|
||||
createFilterItem(QUERY_KEYS.K8S_CLUSTER_NAME, item.meta.k8s_cluster_name),
|
||||
];
|
||||
|
||||
export const k8sNodeGetEntityName = (item: K8sNodeData): string =>
|
||||
item.meta.k8s_node_name;
|
||||
|
||||
export const nodeWidgetInfo = [
|
||||
{
|
||||
@@ -83,7 +107,7 @@ export const nodeWidgetInfo = [
|
||||
];
|
||||
|
||||
export const getNodeMetricsQueryPayload = (
|
||||
node: InframonitoringtypesNodeRecordDTO,
|
||||
node: K8sNodeData,
|
||||
start: number,
|
||||
end: number,
|
||||
dotMetricsEnabled: boolean,
|
||||
@@ -198,7 +222,7 @@ export const getNodeMetricsQueryPayload = (
|
||||
type: 'tag',
|
||||
},
|
||||
op: '=',
|
||||
value: node.nodeName,
|
||||
value: node.meta.k8s_node_name,
|
||||
},
|
||||
],
|
||||
op: 'AND',
|
||||
@@ -237,7 +261,7 @@ export const getNodeMetricsQueryPayload = (
|
||||
type: 'tag',
|
||||
},
|
||||
op: '=',
|
||||
value: node.nodeName,
|
||||
value: node.meta.k8s_node_name,
|
||||
},
|
||||
],
|
||||
op: 'AND',
|
||||
@@ -276,7 +300,7 @@ export const getNodeMetricsQueryPayload = (
|
||||
type: 'tag',
|
||||
},
|
||||
op: '=',
|
||||
value: node.nodeName,
|
||||
value: node.meta.k8s_node_name,
|
||||
},
|
||||
],
|
||||
op: 'AND',
|
||||
@@ -315,7 +339,7 @@ export const getNodeMetricsQueryPayload = (
|
||||
type: 'tag',
|
||||
},
|
||||
op: '=',
|
||||
value: node.nodeName,
|
||||
value: node.meta.k8s_node_name,
|
||||
},
|
||||
],
|
||||
op: 'AND',
|
||||
@@ -354,7 +378,7 @@ export const getNodeMetricsQueryPayload = (
|
||||
type: 'tag',
|
||||
},
|
||||
op: '=',
|
||||
value: node.nodeName,
|
||||
value: node.meta.k8s_node_name,
|
||||
},
|
||||
],
|
||||
op: 'AND',
|
||||
@@ -427,7 +451,7 @@ export const getNodeMetricsQueryPayload = (
|
||||
type: 'tag',
|
||||
},
|
||||
op: '=',
|
||||
value: node.nodeName,
|
||||
value: node.meta.k8s_node_name,
|
||||
},
|
||||
],
|
||||
op: 'AND',
|
||||
@@ -466,7 +490,7 @@ export const getNodeMetricsQueryPayload = (
|
||||
type: 'tag',
|
||||
},
|
||||
op: '=',
|
||||
value: node.nodeName,
|
||||
value: node.meta.k8s_node_name,
|
||||
},
|
||||
],
|
||||
op: 'AND',
|
||||
@@ -505,7 +529,7 @@ export const getNodeMetricsQueryPayload = (
|
||||
type: 'tag',
|
||||
},
|
||||
op: '=',
|
||||
value: node.nodeName,
|
||||
value: node.meta.k8s_node_name,
|
||||
},
|
||||
],
|
||||
op: 'AND',
|
||||
@@ -544,7 +568,7 @@ export const getNodeMetricsQueryPayload = (
|
||||
type: 'tag',
|
||||
},
|
||||
op: '=',
|
||||
value: node.nodeName,
|
||||
value: node.meta.k8s_node_name,
|
||||
},
|
||||
],
|
||||
op: 'AND',
|
||||
@@ -583,7 +607,7 @@ export const getNodeMetricsQueryPayload = (
|
||||
type: 'tag',
|
||||
},
|
||||
op: '=',
|
||||
value: node.nodeName,
|
||||
value: node.meta.k8s_node_name,
|
||||
},
|
||||
],
|
||||
op: 'AND',
|
||||
@@ -622,7 +646,7 @@ export const getNodeMetricsQueryPayload = (
|
||||
type: 'tag',
|
||||
},
|
||||
op: '=',
|
||||
value: node.nodeName,
|
||||
value: node.meta.k8s_node_name,
|
||||
},
|
||||
],
|
||||
op: 'AND',
|
||||
@@ -661,7 +685,7 @@ export const getNodeMetricsQueryPayload = (
|
||||
type: 'tag',
|
||||
},
|
||||
op: '=',
|
||||
value: node.nodeName,
|
||||
value: node.meta.k8s_node_name,
|
||||
},
|
||||
],
|
||||
op: 'AND',
|
||||
@@ -734,7 +758,7 @@ export const getNodeMetricsQueryPayload = (
|
||||
type: 'tag',
|
||||
},
|
||||
op: '=',
|
||||
value: node.nodeName,
|
||||
value: node.meta.k8s_node_name,
|
||||
},
|
||||
],
|
||||
op: 'AND',
|
||||
@@ -773,7 +797,7 @@ export const getNodeMetricsQueryPayload = (
|
||||
type: 'tag',
|
||||
},
|
||||
op: '=',
|
||||
value: node.nodeName,
|
||||
value: node.meta.k8s_node_name,
|
||||
},
|
||||
],
|
||||
op: 'AND',
|
||||
@@ -812,7 +836,7 @@ export const getNodeMetricsQueryPayload = (
|
||||
type: 'tag',
|
||||
},
|
||||
op: '=',
|
||||
value: node.nodeName,
|
||||
value: node.meta.k8s_node_name,
|
||||
},
|
||||
],
|
||||
op: 'AND',
|
||||
@@ -898,7 +922,7 @@ export const getNodeMetricsQueryPayload = (
|
||||
type: 'tag',
|
||||
},
|
||||
op: '=',
|
||||
value: node.nodeName,
|
||||
value: node.meta.k8s_node_name,
|
||||
},
|
||||
],
|
||||
op: 'AND',
|
||||
@@ -937,7 +961,7 @@ export const getNodeMetricsQueryPayload = (
|
||||
type: 'tag',
|
||||
},
|
||||
op: '=',
|
||||
value: node.nodeName,
|
||||
value: node.meta.k8s_node_name,
|
||||
},
|
||||
],
|
||||
op: 'AND',
|
||||
@@ -976,7 +1000,7 @@ export const getNodeMetricsQueryPayload = (
|
||||
type: 'tag',
|
||||
},
|
||||
op: '=',
|
||||
value: node.nodeName,
|
||||
value: node.meta.k8s_node_name,
|
||||
},
|
||||
],
|
||||
op: 'AND',
|
||||
@@ -1062,7 +1086,7 @@ export const getNodeMetricsQueryPayload = (
|
||||
type: 'tag',
|
||||
},
|
||||
op: '=',
|
||||
value: node.nodeName,
|
||||
value: node.meta.k8s_node_name,
|
||||
},
|
||||
],
|
||||
op: 'AND',
|
||||
@@ -1142,7 +1166,7 @@ export const getNodeMetricsQueryPayload = (
|
||||
type: 'tag',
|
||||
},
|
||||
op: '=',
|
||||
value: node.nodeName,
|
||||
value: node.meta.k8s_node_name,
|
||||
},
|
||||
],
|
||||
op: 'AND',
|
||||
@@ -1222,7 +1246,7 @@ export const getNodeMetricsQueryPayload = (
|
||||
type: 'tag',
|
||||
},
|
||||
op: '=',
|
||||
value: node.nodeName,
|
||||
value: node.meta.k8s_node_name,
|
||||
},
|
||||
],
|
||||
op: 'AND',
|
||||
@@ -1308,7 +1332,7 @@ export const getNodeMetricsQueryPayload = (
|
||||
type: 'tag',
|
||||
},
|
||||
op: '=',
|
||||
value: node.nodeName,
|
||||
value: node.meta.k8s_node_name,
|
||||
},
|
||||
],
|
||||
op: 'AND',
|
||||
@@ -1394,7 +1418,7 @@ export const getNodeMetricsQueryPayload = (
|
||||
type: 'tag',
|
||||
},
|
||||
op: '=',
|
||||
value: node.nodeName,
|
||||
value: node.meta.k8s_node_name,
|
||||
},
|
||||
],
|
||||
op: 'AND',
|
||||
@@ -1433,7 +1457,7 @@ export const getNodeMetricsQueryPayload = (
|
||||
type: 'tag',
|
||||
},
|
||||
op: '=',
|
||||
value: node.nodeName,
|
||||
value: node.meta.k8s_node_name,
|
||||
},
|
||||
],
|
||||
op: 'AND',
|
||||
@@ -1472,7 +1496,7 @@ export const getNodeMetricsQueryPayload = (
|
||||
type: 'tag',
|
||||
},
|
||||
op: '=',
|
||||
value: node.nodeName,
|
||||
value: node.meta.k8s_node_name,
|
||||
},
|
||||
],
|
||||
op: 'AND',
|
||||
@@ -1545,7 +1569,7 @@ export const getNodeMetricsQueryPayload = (
|
||||
type: 'tag',
|
||||
},
|
||||
op: '=',
|
||||
value: node.nodeName,
|
||||
value: node.meta.k8s_node_name,
|
||||
},
|
||||
],
|
||||
op: 'AND',
|
||||
@@ -1584,7 +1608,7 @@ export const getNodeMetricsQueryPayload = (
|
||||
type: 'tag',
|
||||
},
|
||||
op: '=',
|
||||
value: node.nodeName,
|
||||
value: node.meta.k8s_node_name,
|
||||
},
|
||||
],
|
||||
op: 'AND',
|
||||
|
||||
@@ -1,56 +1,38 @@
|
||||
import { Color } from '@signozhq/design-tokens';
|
||||
import { Badge, BadgeColor } from '@signozhq/ui/badge';
|
||||
import { InframonitoringtypesNodeRecordDTO } from 'api/generated/services/sigNoz.schemas';
|
||||
import TanStackTable, { TableColumnDef } from 'components/TanStackTableView';
|
||||
import { Tooltip } from 'antd';
|
||||
import { TableColumnDef } from 'components/TanStackTableView';
|
||||
import TanStackTable from 'components/TanStackTableView';
|
||||
import { ExpandButtonWrapper } from 'container/InfraMonitoringK8sV2/components';
|
||||
|
||||
import ColumnHeader from '../Base/ColumnHeader';
|
||||
import EntityGroupHeader from '../Base/EntityGroupHeader';
|
||||
import K8sGroupCell from '../Base/K8sGroupCell';
|
||||
import { formatBytes, getPodPhaseStatusItems } from '../commonUtils';
|
||||
import { INFRA_MONITORING_ATTR_KEYS } from '../constants';
|
||||
import {
|
||||
CellValueTooltip,
|
||||
GroupedStatusCounts,
|
||||
ValidateColumnValueWrapper,
|
||||
} from '../components';
|
||||
import { formatBytes } from '../commonUtils';
|
||||
import { ValidateColumnValueWrapper } from '../components';
|
||||
import { InfraMonitoringEntity } from '../constants';
|
||||
import { K8sNodeData, K8sNodesListPayload } from './api';
|
||||
import { Workflow } from '@signozhq/icons';
|
||||
|
||||
export function getK8sNodeRowKey(
|
||||
node: InframonitoringtypesNodeRecordDTO,
|
||||
): string {
|
||||
return (
|
||||
node.nodeName || node.meta?.[INFRA_MONITORING_ATTR_KEYS.K8S_NODE_UID] || ''
|
||||
);
|
||||
export function getK8sNodeRowKey(node: K8sNodeData): string {
|
||||
return node.nodeUID || node.meta.k8s_node_uid || node.meta.k8s_node_name;
|
||||
}
|
||||
|
||||
export function getK8sNodeItemKey(
|
||||
node: InframonitoringtypesNodeRecordDTO,
|
||||
): string {
|
||||
return node.nodeName;
|
||||
export function getK8sNodeItemKey(node: K8sNodeData): string {
|
||||
return node.meta.k8s_node_name;
|
||||
}
|
||||
|
||||
const NODE_CONDITION_COLORS: Record<string, BadgeColor> = {
|
||||
ready: 'forest',
|
||||
not_ready: 'amber',
|
||||
no_data: 'secondary',
|
||||
};
|
||||
export const getK8sNodesListQuery = (): K8sNodesListPayload => ({
|
||||
filters: {
|
||||
items: [],
|
||||
op: 'and',
|
||||
},
|
||||
orderBy: { columnName: 'cpu', order: 'desc' },
|
||||
});
|
||||
|
||||
const NODE_CONDITION_LABEL_MAP: Record<string, string> = {
|
||||
ready: 'Ready',
|
||||
not_ready: 'Not Ready',
|
||||
no_data: 'No Data',
|
||||
};
|
||||
|
||||
export type NodeTableColumnConfig =
|
||||
TableColumnDef<InframonitoringtypesNodeRecordDTO>;
|
||||
export const k8sNodesColumnsConfig: NodeTableColumnConfig[] = [
|
||||
export const k8sNodesColumnsConfig: TableColumnDef<K8sNodeData>[] = [
|
||||
{
|
||||
id: 'nodeGroup',
|
||||
header: (): React.ReactNode => <EntityGroupHeader title="Node Group" />,
|
||||
accessorFn: (row): string => row.nodeName || '',
|
||||
width: { min: 290 },
|
||||
header: (): React.ReactNode => <EntityGroupHeader title="NODE GROUP" />,
|
||||
accessorFn: (row): string => row.meta.k8s_node_name || '',
|
||||
width: { min: 300 },
|
||||
enableSort: false,
|
||||
enableRemove: false,
|
||||
enableMove: false,
|
||||
@@ -73,10 +55,9 @@ export const k8sNodesColumnsConfig: NodeTableColumnConfig[] = [
|
||||
<EntityGroupHeader
|
||||
title="Node Name"
|
||||
icon={<Workflow data-hide-expanded="true" size={14} />}
|
||||
docPath="/infrastructure-monitoring/kubernetes/nodes#node-name"
|
||||
/>
|
||||
),
|
||||
accessorFn: (row): string => row.nodeName || '',
|
||||
accessorFn: (row): string => row.meta.k8s_node_name || '',
|
||||
width: { min: 290 },
|
||||
enableSort: false,
|
||||
enableRemove: false,
|
||||
@@ -86,146 +67,65 @@ export const k8sNodesColumnsConfig: NodeTableColumnConfig[] = [
|
||||
cell: ({ value }): React.ReactNode => {
|
||||
const nodeName = value as string;
|
||||
return (
|
||||
<CellValueTooltip value={nodeName}>
|
||||
<Tooltip title={nodeName}>
|
||||
<TanStackTable.Text>{nodeName}</TanStackTable.Text>
|
||||
</CellValueTooltip>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'condition',
|
||||
header: (): React.ReactNode => (
|
||||
<ColumnHeader docPath="/infrastructure-monitoring/kubernetes/nodes#node-condition">
|
||||
Status
|
||||
</ColumnHeader>
|
||||
),
|
||||
accessorFn: (row): string => row.condition,
|
||||
width: { min: 120 },
|
||||
enableSort: false,
|
||||
cell: ({ row, groupMeta }): React.ReactNode => {
|
||||
if (!groupMeta) {
|
||||
const color =
|
||||
NODE_CONDITION_COLORS[row.condition] || NODE_CONDITION_COLORS.no_data;
|
||||
return (
|
||||
<Badge color={color} variant="outline">
|
||||
{NODE_CONDITION_LABEL_MAP[row.condition] || 'Unknown'}
|
||||
</Badge>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<GroupedStatusCounts
|
||||
items={[
|
||||
{
|
||||
value: row.nodeCountsByReadiness?.ready ?? 0,
|
||||
label: 'Ready',
|
||||
color: Color.BG_FOREST_500,
|
||||
},
|
||||
{
|
||||
value: row.nodeCountsByReadiness?.notReady ?? 0,
|
||||
label: 'Not Ready',
|
||||
color: Color.BG_AMBER_500,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'podCountsByPhase',
|
||||
header: (): React.ReactNode => (
|
||||
<ColumnHeader docPath="/infrastructure-monitoring/kubernetes/nodes#pod-counts-by-phase">
|
||||
Pod Phases
|
||||
</ColumnHeader>
|
||||
),
|
||||
accessorFn: (row): InframonitoringtypesNodeRecordDTO['podCountsByPhase'] =>
|
||||
row.podCountsByPhase,
|
||||
width: { min: 250 },
|
||||
enableSort: false,
|
||||
cell: ({ row }): React.ReactNode => {
|
||||
const podCountsByPhase = row.podCountsByPhase;
|
||||
if (!podCountsByPhase) {
|
||||
return <TanStackTable.Text>-</TanStackTable.Text>;
|
||||
}
|
||||
return (
|
||||
<GroupedStatusCounts items={getPodPhaseStatusItems(row.podCountsByPhase)} />
|
||||
</Tooltip>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'clusterName',
|
||||
header: (): React.ReactNode => (
|
||||
<ColumnHeader docPath="/infrastructure-monitoring/kubernetes/nodes#cluster-name">
|
||||
Cluster Name
|
||||
</ColumnHeader>
|
||||
),
|
||||
accessorFn: (row): string =>
|
||||
row.meta?.[INFRA_MONITORING_ATTR_KEYS.K8S_CLUSTER_NAME] || '',
|
||||
header: 'Cluster Name',
|
||||
accessorFn: (row): string => row.meta.k8s_cluster_name || '',
|
||||
width: { min: 150, default: 150 },
|
||||
enableSort: false,
|
||||
defaultVisibility: false,
|
||||
cell: ({ value }): React.ReactNode => (
|
||||
<TanStackTable.Text>{value as string}</TanStackTable.Text>
|
||||
),
|
||||
},
|
||||
{
|
||||
id: 'cpu',
|
||||
header: (): React.ReactNode => (
|
||||
<ColumnHeader docPath="/infrastructure-monitoring/kubernetes/nodes#cpu-usage-cores">
|
||||
CPU Usage
|
||||
<br /> (cores)
|
||||
</ColumnHeader>
|
||||
),
|
||||
accessorFn: (row): number => row.nodeCPU,
|
||||
width: { min: 160, default: 160 },
|
||||
header: 'CPU Usage (cores)',
|
||||
accessorFn: (row): number => row.nodeCPUUsage,
|
||||
width: { min: 200, default: 200 },
|
||||
enableSort: true,
|
||||
cell: ({ value }): React.ReactNode => {
|
||||
const cpu = Number(value);
|
||||
const cpu = value as number;
|
||||
return (
|
||||
<ValidateColumnValueWrapper
|
||||
value={cpu}
|
||||
entity={InfraMonitoringEntity.NODES}
|
||||
attribute="CPU metric"
|
||||
>
|
||||
<TanStackTable.Text>{cpu.toFixed(2)}</TanStackTable.Text>
|
||||
<TanStackTable.Text>{cpu}</TanStackTable.Text>
|
||||
</ValidateColumnValueWrapper>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'cpu_allocatable',
|
||||
header: (): React.ReactNode => (
|
||||
<ColumnHeader docPath="/infrastructure-monitoring/kubernetes/nodes#cpu-alloc-cores">
|
||||
CPU Allocatable
|
||||
<br /> (cores)
|
||||
</ColumnHeader>
|
||||
),
|
||||
header: 'CPU Alloc (cores)',
|
||||
accessorFn: (row): number => row.nodeCPUAllocatable,
|
||||
width: { min: 160, default: 190 },
|
||||
width: { min: 200, default: 200 },
|
||||
enableSort: true,
|
||||
cell: ({ value }): React.ReactNode => {
|
||||
const cpuAllocatable = Number(value);
|
||||
const cpuAllocatable = value as number;
|
||||
return (
|
||||
<ValidateColumnValueWrapper
|
||||
value={cpuAllocatable}
|
||||
entity={InfraMonitoringEntity.NODES}
|
||||
attribute="CPU allocatable metric"
|
||||
>
|
||||
<TanStackTable.Text>{cpuAllocatable.toFixed(2)}</TanStackTable.Text>
|
||||
<TanStackTable.Text>{cpuAllocatable}</TanStackTable.Text>
|
||||
</ValidateColumnValueWrapper>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'memory',
|
||||
header: (): React.ReactNode => (
|
||||
<ColumnHeader docPath="/infrastructure-monitoring/kubernetes/nodes#memory-usage-wss">
|
||||
Memory Usage
|
||||
<br /> (WSS)
|
||||
</ColumnHeader>
|
||||
),
|
||||
accessorFn: (row): number => row.nodeMemory,
|
||||
width: { min: 220, default: 220 },
|
||||
header: 'Memory Usage (WSS)',
|
||||
accessorFn: (row): number => row.nodeMemoryUsage,
|
||||
width: { min: 240, default: 240 },
|
||||
enableSort: true,
|
||||
cell: ({ value }): React.ReactNode => {
|
||||
const memory = value as number;
|
||||
@@ -242,14 +142,9 @@ export const k8sNodesColumnsConfig: NodeTableColumnConfig[] = [
|
||||
},
|
||||
{
|
||||
id: 'memory_allocatable',
|
||||
header: (): React.ReactNode => (
|
||||
<ColumnHeader docPath="/infrastructure-monitoring/kubernetes/nodes#memory-allocatable">
|
||||
Memory
|
||||
<br /> Allocatable
|
||||
</ColumnHeader>
|
||||
),
|
||||
header: 'Memory Allocatable',
|
||||
accessorFn: (row): number => row.nodeMemoryAllocatable,
|
||||
width: { min: 200, default: 200 },
|
||||
width: { min: 240, default: 240 },
|
||||
enableSort: true,
|
||||
cell: ({ value }): React.ReactNode => {
|
||||
const memoryAllocatable = value as number;
|
||||
|
||||
@@ -1,23 +1,20 @@
|
||||
import { useCallback } from 'react';
|
||||
import { listPods } from 'api/generated/services/inframonitoring';
|
||||
import {
|
||||
InframonitoringtypesPodRecordDTO,
|
||||
InframonitoringtypesResponseTypeDTO,
|
||||
Querybuildertypesv5OrderDirectionDTO,
|
||||
} from 'api/generated/services/sigNoz.schemas';
|
||||
import React, { useCallback } from 'react';
|
||||
import { InfraMonitoringEvents } from 'constants/events';
|
||||
import { FeatureKeys } from 'constants/features';
|
||||
import { useAppContext } from 'providers/App/App';
|
||||
|
||||
import K8sBaseDetails, { K8sDetailsFilters } from '../Base/K8sBaseDetails';
|
||||
import { K8sBaseList } from '../Base/K8sBaseList';
|
||||
import { K8sBaseFilters } from '../Base/types';
|
||||
import { InfraMonitoringEntity } from '../constants';
|
||||
import { getK8sPodsList, K8sPodsData } from './api';
|
||||
import {
|
||||
getPodMetricsQueryPayload,
|
||||
k8sPodDetailsMetadataConfig,
|
||||
k8sPodGetEntityName,
|
||||
k8sPodGetSelectedItemExpression,
|
||||
k8sPodInitialEventsExpression,
|
||||
k8sPodInitialLogTracesExpression,
|
||||
k8sPodGetSelectedItemFilters,
|
||||
k8sPodInitialEventsFilter,
|
||||
k8sPodInitialLogTracesFilter,
|
||||
podWidgetInfo,
|
||||
} from './constants';
|
||||
import {
|
||||
@@ -31,93 +28,66 @@ function K8sPodsList({
|
||||
}: {
|
||||
controlListPrefix?: React.ReactNode;
|
||||
}): JSX.Element {
|
||||
const { featureFlags } = useAppContext();
|
||||
const dotMetricsEnabled =
|
||||
featureFlags?.find((flag) => flag.name === FeatureKeys.DOT_METRICS_ENABLED)
|
||||
?.active || false;
|
||||
|
||||
const fetchListData = useCallback(
|
||||
async (filters: K8sBaseFilters, signal?: AbortSignal) => {
|
||||
try {
|
||||
const response = await listPods(
|
||||
{
|
||||
filter: { expression: filters.filter.expression },
|
||||
groupBy: filters.groupBy?.map((g) => ({ name: g.name })),
|
||||
offset: filters.offset,
|
||||
limit: filters.limit ?? 10,
|
||||
start: filters.start,
|
||||
end: filters.end,
|
||||
orderBy: filters.orderBy
|
||||
? {
|
||||
key: { name: filters.orderBy.key.name },
|
||||
direction:
|
||||
filters.orderBy.direction === 'asc'
|
||||
? Querybuildertypesv5OrderDirectionDTO.asc
|
||||
: Querybuildertypesv5OrderDirectionDTO.desc,
|
||||
}
|
||||
: undefined,
|
||||
},
|
||||
signal,
|
||||
);
|
||||
filters.orderBy ||= {
|
||||
columnName: 'cpu',
|
||||
order: 'desc',
|
||||
};
|
||||
|
||||
const data = response.data;
|
||||
return {
|
||||
type:
|
||||
data.type === InframonitoringtypesResponseTypeDTO.grouped_list
|
||||
? ('grouped_list' as const)
|
||||
: ('list' as const),
|
||||
records: data.records,
|
||||
total: data.total,
|
||||
endTimeBeforeRetention: data.endTimeBeforeRetention,
|
||||
warning: data.warning,
|
||||
};
|
||||
} catch (error) {
|
||||
const errMsg =
|
||||
error instanceof Error ? error.message : 'Failed to fetch pods';
|
||||
return {
|
||||
type: 'list' as const,
|
||||
records: [] as InframonitoringtypesPodRecordDTO[],
|
||||
total: 0,
|
||||
error: errMsg,
|
||||
};
|
||||
}
|
||||
const response = await getK8sPodsList(
|
||||
filters,
|
||||
signal,
|
||||
undefined,
|
||||
dotMetricsEnabled,
|
||||
);
|
||||
|
||||
return {
|
||||
data: response.payload?.data.records || [],
|
||||
total: response.payload?.data.total || 0,
|
||||
error: response.error,
|
||||
rawData: response.payload?.data,
|
||||
};
|
||||
},
|
||||
[],
|
||||
[dotMetricsEnabled],
|
||||
);
|
||||
|
||||
const fetchEntityData = useCallback(
|
||||
async (
|
||||
filters: K8sDetailsFilters,
|
||||
signal?: AbortSignal,
|
||||
): Promise<{
|
||||
data: InframonitoringtypesPodRecordDTO | null;
|
||||
error?: string | null;
|
||||
}> => {
|
||||
try {
|
||||
const response = await listPods(
|
||||
{
|
||||
filter: { expression: filters.filter.expression },
|
||||
start: filters.start,
|
||||
end: filters.end,
|
||||
limit: 1,
|
||||
offset: 0,
|
||||
},
|
||||
signal,
|
||||
);
|
||||
): Promise<{ data: K8sPodsData | null; error?: string | null }> => {
|
||||
const response = await getK8sPodsList(
|
||||
{
|
||||
filters: filters.filters,
|
||||
start: filters.start,
|
||||
end: filters.end,
|
||||
limit: 1,
|
||||
offset: 0,
|
||||
},
|
||||
signal,
|
||||
undefined,
|
||||
dotMetricsEnabled,
|
||||
);
|
||||
|
||||
return {
|
||||
data: response.data.records.length > 0 ? response.data.records[0] : null,
|
||||
};
|
||||
} catch (error) {
|
||||
const errMsg =
|
||||
error instanceof Error ? error.message : 'Failed to fetch pod';
|
||||
return {
|
||||
data: null,
|
||||
error: errMsg,
|
||||
};
|
||||
}
|
||||
const records = response.payload?.data.records || [];
|
||||
|
||||
return {
|
||||
data: records.length > 0 ? records[0] : null,
|
||||
error: response.error,
|
||||
};
|
||||
},
|
||||
[],
|
||||
[dotMetricsEnabled],
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<K8sBaseList<InframonitoringtypesPodRecordDTO>
|
||||
<K8sBaseList<K8sPodsData>
|
||||
controlListPrefix={controlListPrefix}
|
||||
entity={InfraMonitoringEntity.PODS}
|
||||
tableColumns={k8sPodColumnsConfig}
|
||||
@@ -127,14 +97,14 @@ function K8sPodsList({
|
||||
eventCategory={InfraMonitoringEvents.Pod}
|
||||
/>
|
||||
|
||||
<K8sBaseDetails<InframonitoringtypesPodRecordDTO>
|
||||
<K8sBaseDetails<K8sPodsData>
|
||||
category={InfraMonitoringEntity.PODS}
|
||||
eventCategory={InfraMonitoringEvents.Pod}
|
||||
getSelectedItemExpression={k8sPodGetSelectedItemExpression}
|
||||
getSelectedItemFilters={k8sPodGetSelectedItemFilters}
|
||||
fetchEntityData={fetchEntityData}
|
||||
getEntityName={k8sPodGetEntityName}
|
||||
getInitialLogTracesExpression={k8sPodInitialLogTracesExpression}
|
||||
getInitialEventsExpression={k8sPodInitialEventsExpression}
|
||||
getInitialLogTracesFilters={k8sPodInitialLogTracesFilter}
|
||||
getInitialEventsFilters={k8sPodInitialEventsFilter}
|
||||
metadataConfig={k8sPodDetailsMetadataConfig}
|
||||
entityWidgetInfo={podWidgetInfo}
|
||||
getEntityQueryPayload={getPodMetricsQueryPayload}
|
||||
|
||||
148
frontend/src/container/InfraMonitoringK8sV2/Pods/api.ts
generated
Normal file
148
frontend/src/container/InfraMonitoringK8sV2/Pods/api.ts
generated
Normal file
@@ -0,0 +1,148 @@
|
||||
import axios from 'api';
|
||||
import { ErrorResponseHandler } from 'api/ErrorResponseHandler';
|
||||
import { UnderscoreToDotMap } from 'api/utils';
|
||||
import { AxiosError } from 'axios';
|
||||
import { ErrorResponse, SuccessResponse } from 'types/api';
|
||||
|
||||
import { K8sBaseFilters } from '../Base/types';
|
||||
|
||||
// TODO(H4ad): Erase this whole file when migrating to openapi
|
||||
export const podsMetaMap = [
|
||||
{ dot: 'k8s.cronjob.name', under: 'k8s_cronjob_name' },
|
||||
{ dot: 'k8s.daemonset.name', under: 'k8s_daemonset_name' },
|
||||
{ dot: 'k8s.deployment.name', under: 'k8s_deployment_name' },
|
||||
{ dot: 'k8s.job.name', under: 'k8s_job_name' },
|
||||
{ dot: 'k8s.namespace.name', under: 'k8s_namespace_name' },
|
||||
{ dot: 'k8s.node.name', under: 'k8s_node_name' },
|
||||
{ dot: 'k8s.pod.name', under: 'k8s_pod_name' },
|
||||
{ dot: 'k8s.pod.uid', under: 'k8s_pod_uid' },
|
||||
{ dot: 'k8s.statefulset.name', under: 'k8s_statefulset_name' },
|
||||
{ dot: 'k8s.cluster.name', under: 'k8s_cluster_name' },
|
||||
] as const;
|
||||
|
||||
export function mapPodsMeta(raw: Record<string, unknown>): K8sPodsData['meta'] {
|
||||
// clone everything
|
||||
const out: Record<string, unknown> = { ...raw };
|
||||
// overlay only the dot→under mappings
|
||||
podsMetaMap.forEach(({ dot, under }) => {
|
||||
if (dot in raw) {
|
||||
const v = raw[dot];
|
||||
out[under] = typeof v === 'string' ? v : raw[under];
|
||||
}
|
||||
});
|
||||
return out as K8sPodsData['meta'];
|
||||
}
|
||||
|
||||
export interface TimeSeriesValue {
|
||||
timestamp: number;
|
||||
value: string;
|
||||
}
|
||||
|
||||
export interface TimeSeries {
|
||||
labels: Record<string, string>;
|
||||
labelsArray: Array<Record<string, string>>;
|
||||
values: TimeSeriesValue[];
|
||||
}
|
||||
|
||||
export interface K8sPodsData {
|
||||
podUID: string;
|
||||
podCPU: number;
|
||||
podCPURequest: number;
|
||||
podCPULimit: number;
|
||||
podMemory: number;
|
||||
podMemoryRequest: number;
|
||||
podMemoryLimit: number;
|
||||
restartCount: number;
|
||||
meta: {
|
||||
k8s_cronjob_name: string;
|
||||
k8s_daemonset_name: string;
|
||||
k8s_deployment_name: string;
|
||||
k8s_job_name: string;
|
||||
k8s_namespace_name: string;
|
||||
k8s_node_name: string;
|
||||
k8s_pod_name: string;
|
||||
k8s_pod_uid: string;
|
||||
k8s_statefulset_name: string;
|
||||
k8s_cluster_name: string;
|
||||
};
|
||||
countByPhase: {
|
||||
pending: number;
|
||||
running: number;
|
||||
succeeded: number;
|
||||
failed: number;
|
||||
unknown: number;
|
||||
};
|
||||
}
|
||||
|
||||
export interface K8sPodsListResponse {
|
||||
status: string;
|
||||
data: {
|
||||
type: string;
|
||||
records: K8sPodsData[];
|
||||
groups: null;
|
||||
total: number;
|
||||
sentAnyHostMetricsData: boolean;
|
||||
isSendingK8SAgentMetrics: boolean;
|
||||
};
|
||||
}
|
||||
|
||||
export const getK8sPodsList = async (
|
||||
props: K8sBaseFilters,
|
||||
signal?: AbortSignal,
|
||||
headers?: Record<string, string>,
|
||||
dotMetricsEnabled = false,
|
||||
): Promise<SuccessResponse<K8sPodsListResponse> | ErrorResponse> => {
|
||||
try {
|
||||
const requestProps = dotMetricsEnabled
|
||||
? {
|
||||
...props,
|
||||
filters: {
|
||||
...props.filters,
|
||||
items: props.filters?.items.reduce<typeof props.filters.items>(
|
||||
(acc, item) => {
|
||||
if (item.value === undefined) {
|
||||
return acc;
|
||||
}
|
||||
if (
|
||||
item.key &&
|
||||
typeof item.key === 'object' &&
|
||||
'key' in item.key &&
|
||||
typeof item.key.key === 'string'
|
||||
) {
|
||||
const mappedKey = UnderscoreToDotMap[item.key.key] ?? item.key.key;
|
||||
acc.push({
|
||||
...item,
|
||||
key: { ...item.key, key: mappedKey },
|
||||
});
|
||||
} else {
|
||||
acc.push(item);
|
||||
}
|
||||
return acc;
|
||||
},
|
||||
[] as typeof props.filters.items,
|
||||
),
|
||||
},
|
||||
}
|
||||
: props;
|
||||
|
||||
const response = await axios.post('/pods/list', requestProps, {
|
||||
signal,
|
||||
headers,
|
||||
});
|
||||
const payload: K8sPodsListResponse = response.data;
|
||||
payload.data.records = payload.data.records.map((record) => ({
|
||||
...record,
|
||||
meta: mapPodsMeta(record.meta as Record<string, unknown>),
|
||||
}));
|
||||
|
||||
return {
|
||||
statusCode: 200,
|
||||
error: null,
|
||||
message: 'Success',
|
||||
payload,
|
||||
params: requestProps,
|
||||
};
|
||||
} catch (error) {
|
||||
return ErrorResponseHandler(error as AxiosError);
|
||||
}
|
||||
};
|
||||
@@ -1,59 +1,62 @@
|
||||
import { InframonitoringtypesPodRecordDTO } from 'api/generated/services/sigNoz.schemas';
|
||||
import { PANEL_TYPES } from 'constants/queryBuilder';
|
||||
import { GetQueryResultsProps } from 'lib/dashboard/getQueryResults';
|
||||
import { DataTypes } from 'types/api/queryBuilder/queryAutocompleteResponse';
|
||||
import { TagFilter } from 'types/api/queryBuilder/queryBuilderData';
|
||||
import { EQueryType } from 'types/common/dashboard';
|
||||
import { DataSource, ReduceOperators } from 'types/common/queryBuilder';
|
||||
import { v4 } from 'uuid';
|
||||
|
||||
import { K8sDetailsMetadataConfig } from '../Base/K8sBaseDetails';
|
||||
import { formatValueForExpression } from 'components/QueryBuilderV2/utils';
|
||||
import { INFRA_MONITORING_ATTR_KEYS } from '../constants';
|
||||
import {
|
||||
createFilterItem,
|
||||
K8sDetailsMetadataConfig,
|
||||
} from '../Base/K8sBaseDetails';
|
||||
import { QUERY_KEYS } from '../EntityDetailsUtils/utils';
|
||||
import { K8sPodsData } from './api';
|
||||
|
||||
export const k8sPodGetSelectedItemExpression = (
|
||||
export const k8sPodGetSelectedItemFilters = (
|
||||
selectedItemId: string,
|
||||
): string => `k8s.pod.uid = ${formatValueForExpression(selectedItemId)}`;
|
||||
): TagFilter => {
|
||||
return {
|
||||
op: 'AND',
|
||||
items: [
|
||||
{
|
||||
id: 'k8s_pod_uid',
|
||||
key: {
|
||||
key: 'k8s_pod_uid',
|
||||
type: null,
|
||||
},
|
||||
op: '=',
|
||||
value: selectedItemId,
|
||||
},
|
||||
],
|
||||
};
|
||||
};
|
||||
|
||||
export const k8sPodDetailsMetadataConfig: K8sDetailsMetadataConfig<InframonitoringtypesPodRecordDTO>[] =
|
||||
export const k8sPodDetailsMetadataConfig: K8sDetailsMetadataConfig<K8sPodsData>[] =
|
||||
[
|
||||
{
|
||||
label: 'NAMESPACE',
|
||||
getValue: (p): string =>
|
||||
p.meta?.[INFRA_MONITORING_ATTR_KEYS.K8S_NAMESPACE_NAME] || '',
|
||||
},
|
||||
{ label: 'NAMESPACE', getValue: (p): string => p.meta.k8s_namespace_name },
|
||||
{
|
||||
label: 'Cluster Name',
|
||||
getValue: (p): string =>
|
||||
p.meta?.[INFRA_MONITORING_ATTR_KEYS.K8S_CLUSTER_NAME] || '',
|
||||
},
|
||||
{
|
||||
label: 'Node',
|
||||
getValue: (p): string =>
|
||||
p.meta?.[INFRA_MONITORING_ATTR_KEYS.K8S_NODE_NAME] || '',
|
||||
getValue: (p): string => p.meta.k8s_cluster_name,
|
||||
},
|
||||
{ label: 'Node', getValue: (p): string => p.meta.k8s_node_name },
|
||||
];
|
||||
|
||||
export const k8sPodInitialEventsExpression = (
|
||||
pod: InframonitoringtypesPodRecordDTO,
|
||||
): string => {
|
||||
const podName = formatValueForExpression(
|
||||
pod.meta?.[INFRA_MONITORING_ATTR_KEYS.K8S_POD_NAME] || '',
|
||||
);
|
||||
return `${INFRA_MONITORING_ATTR_KEYS.K8S_OBJECT_KIND} = 'Pod' AND ${INFRA_MONITORING_ATTR_KEYS.K8S_OBJECT_NAME} = ${podName}`;
|
||||
};
|
||||
export const k8sPodInitialEventsFilter = (
|
||||
pod: K8sPodsData,
|
||||
): ReturnType<typeof createFilterItem>[] => [
|
||||
createFilterItem(QUERY_KEYS.K8S_OBJECT_KIND, 'Pod'),
|
||||
createFilterItem(QUERY_KEYS.K8S_OBJECT_NAME, pod.meta.k8s_pod_name),
|
||||
];
|
||||
|
||||
export const k8sPodInitialLogTracesExpression = (
|
||||
pod: InframonitoringtypesPodRecordDTO,
|
||||
): string => {
|
||||
const podName = formatValueForExpression(
|
||||
pod.meta?.[INFRA_MONITORING_ATTR_KEYS.K8S_POD_NAME] || '',
|
||||
);
|
||||
return `${INFRA_MONITORING_ATTR_KEYS.K8S_POD_NAME} = ${podName}`;
|
||||
};
|
||||
export const k8sPodInitialLogTracesFilter = (
|
||||
pod: K8sPodsData,
|
||||
): ReturnType<typeof createFilterItem>[] => [
|
||||
createFilterItem(QUERY_KEYS.K8S_POD_NAME, pod.meta.k8s_pod_name),
|
||||
];
|
||||
|
||||
export const k8sPodGetEntityName = (
|
||||
pod: InframonitoringtypesPodRecordDTO,
|
||||
): string => pod.meta?.[INFRA_MONITORING_ATTR_KEYS.K8S_POD_NAME] || '';
|
||||
export const k8sPodGetEntityName = (pod: K8sPodsData): string =>
|
||||
pod.meta.k8s_pod_name;
|
||||
|
||||
export const podWidgetInfo = [
|
||||
{
|
||||
@@ -111,7 +114,7 @@ export const podWidgetInfo = [
|
||||
];
|
||||
|
||||
export const getPodMetricsQueryPayload = (
|
||||
pod: InframonitoringtypesPodRecordDTO,
|
||||
pod: K8sPodsData,
|
||||
start: number,
|
||||
end: number,
|
||||
dotMetricsEnabled: boolean,
|
||||
@@ -221,15 +224,9 @@ export const getPodMetricsQueryPayload = (
|
||||
'k8s_pod_filesystem_usage',
|
||||
);
|
||||
|
||||
const k8sPodNameKey = getKey(
|
||||
INFRA_MONITORING_ATTR_KEYS.K8S_POD_NAME,
|
||||
'k8s_pod_name',
|
||||
);
|
||||
const k8sPodNameKey = getKey('k8s.pod.name', 'k8s_pod_name');
|
||||
|
||||
const k8sNamespaceNameKey = getKey(
|
||||
INFRA_MONITORING_ATTR_KEYS.K8S_NAMESPACE_NAME,
|
||||
'k8s_namespace_name',
|
||||
);
|
||||
const k8sNamespaceNameKey = getKey('k8s.namespace.name', 'k8s_namespace_name');
|
||||
return [
|
||||
{
|
||||
selectedTime: 'GLOBAL_TIME',
|
||||
@@ -259,7 +256,7 @@ export const getPodMetricsQueryPayload = (
|
||||
type: 'tag',
|
||||
},
|
||||
op: '=',
|
||||
value: pod.meta?.[INFRA_MONITORING_ATTR_KEYS.K8S_POD_NAME] || '',
|
||||
value: pod.meta.k8s_pod_name,
|
||||
},
|
||||
{
|
||||
id: '067b2dc4',
|
||||
@@ -270,8 +267,7 @@ export const getPodMetricsQueryPayload = (
|
||||
type: 'tag',
|
||||
},
|
||||
op: '=',
|
||||
value:
|
||||
pod.meta?.[INFRA_MONITORING_ATTR_KEYS.K8S_NAMESPACE_NAME] || '',
|
||||
value: pod.meta.k8s_namespace_name,
|
||||
},
|
||||
],
|
||||
op: 'AND',
|
||||
@@ -310,7 +306,7 @@ export const getPodMetricsQueryPayload = (
|
||||
type: 'tag',
|
||||
},
|
||||
op: '=',
|
||||
value: pod.meta?.[INFRA_MONITORING_ATTR_KEYS.K8S_POD_NAME] || '',
|
||||
value: pod.meta.k8s_pod_name,
|
||||
},
|
||||
{
|
||||
id: '379af416',
|
||||
@@ -321,8 +317,7 @@ export const getPodMetricsQueryPayload = (
|
||||
type: 'tag',
|
||||
},
|
||||
op: '=',
|
||||
value:
|
||||
pod.meta?.[INFRA_MONITORING_ATTR_KEYS.K8S_NAMESPACE_NAME] || '',
|
||||
value: pod.meta.k8s_namespace_name,
|
||||
},
|
||||
],
|
||||
op: 'AND',
|
||||
@@ -361,7 +356,7 @@ export const getPodMetricsQueryPayload = (
|
||||
type: 'tag',
|
||||
},
|
||||
op: '=',
|
||||
value: pod.meta?.[INFRA_MONITORING_ATTR_KEYS.K8S_POD_NAME] || '',
|
||||
value: pod.meta.k8s_pod_name,
|
||||
},
|
||||
{
|
||||
id: '39ee0dbd',
|
||||
@@ -372,8 +367,7 @@ export const getPodMetricsQueryPayload = (
|
||||
type: 'tag',
|
||||
},
|
||||
op: '=',
|
||||
value:
|
||||
pod.meta?.[INFRA_MONITORING_ATTR_KEYS.K8S_NAMESPACE_NAME] || '',
|
||||
value: pod.meta.k8s_namespace_name,
|
||||
},
|
||||
],
|
||||
op: 'AND',
|
||||
@@ -446,8 +440,7 @@ export const getPodMetricsQueryPayload = (
|
||||
type: 'tag',
|
||||
},
|
||||
op: '=',
|
||||
value:
|
||||
pod.meta?.[INFRA_MONITORING_ATTR_KEYS.K8S_NAMESPACE_NAME] || '',
|
||||
value: pod.meta.k8s_namespace_name,
|
||||
},
|
||||
{
|
||||
id: '755c8a9d',
|
||||
@@ -458,7 +451,7 @@ export const getPodMetricsQueryPayload = (
|
||||
type: 'tag',
|
||||
},
|
||||
op: '=',
|
||||
value: pod.meta?.[INFRA_MONITORING_ATTR_KEYS.K8S_POD_NAME] || '',
|
||||
value: pod.meta.k8s_pod_name,
|
||||
},
|
||||
],
|
||||
op: 'AND',
|
||||
@@ -497,8 +490,7 @@ export const getPodMetricsQueryPayload = (
|
||||
type: 'tag',
|
||||
},
|
||||
op: '=',
|
||||
value:
|
||||
pod.meta?.[INFRA_MONITORING_ATTR_KEYS.K8S_NAMESPACE_NAME] || '',
|
||||
value: pod.meta.k8s_namespace_name,
|
||||
},
|
||||
{
|
||||
id: '1e3d01ee',
|
||||
@@ -509,7 +501,7 @@ export const getPodMetricsQueryPayload = (
|
||||
type: 'tag',
|
||||
},
|
||||
op: '=',
|
||||
value: pod.meta?.[INFRA_MONITORING_ATTR_KEYS.K8S_POD_NAME] || '',
|
||||
value: pod.meta.k8s_pod_name,
|
||||
},
|
||||
],
|
||||
op: 'AND',
|
||||
@@ -548,8 +540,7 @@ export const getPodMetricsQueryPayload = (
|
||||
type: 'tag',
|
||||
},
|
||||
op: '=',
|
||||
value:
|
||||
pod.meta?.[INFRA_MONITORING_ATTR_KEYS.K8S_NAMESPACE_NAME] || '',
|
||||
value: pod.meta.k8s_namespace_name,
|
||||
},
|
||||
{
|
||||
id: '0c8b2662',
|
||||
@@ -560,7 +551,7 @@ export const getPodMetricsQueryPayload = (
|
||||
type: 'tag',
|
||||
},
|
||||
op: '=',
|
||||
value: pod.meta?.[INFRA_MONITORING_ATTR_KEYS.K8S_POD_NAME] || '',
|
||||
value: pod.meta.k8s_pod_name,
|
||||
},
|
||||
],
|
||||
op: 'AND',
|
||||
@@ -599,8 +590,7 @@ export const getPodMetricsQueryPayload = (
|
||||
type: 'tag',
|
||||
},
|
||||
op: '=',
|
||||
value:
|
||||
pod.meta?.[INFRA_MONITORING_ATTR_KEYS.K8S_NAMESPACE_NAME] || '',
|
||||
value: pod.meta.k8s_namespace_name,
|
||||
},
|
||||
{
|
||||
id: 'e915da76',
|
||||
@@ -611,7 +601,7 @@ export const getPodMetricsQueryPayload = (
|
||||
type: 'tag',
|
||||
},
|
||||
op: '=',
|
||||
value: pod.meta?.[INFRA_MONITORING_ATTR_KEYS.K8S_POD_NAME] || '',
|
||||
value: pod.meta.k8s_pod_name,
|
||||
},
|
||||
],
|
||||
op: 'AND',
|
||||
@@ -650,8 +640,7 @@ export const getPodMetricsQueryPayload = (
|
||||
type: 'tag',
|
||||
},
|
||||
op: '=',
|
||||
value:
|
||||
pod.meta?.[INFRA_MONITORING_ATTR_KEYS.K8S_NAMESPACE_NAME] || '',
|
||||
value: pod.meta.k8s_namespace_name,
|
||||
},
|
||||
{
|
||||
id: '32c15c03',
|
||||
@@ -662,7 +651,7 @@ export const getPodMetricsQueryPayload = (
|
||||
type: 'tag',
|
||||
},
|
||||
op: '=',
|
||||
value: pod.meta?.[INFRA_MONITORING_ATTR_KEYS.K8S_POD_NAME] || '',
|
||||
value: pod.meta.k8s_pod_name,
|
||||
},
|
||||
],
|
||||
op: 'AND',
|
||||
@@ -701,8 +690,7 @@ export const getPodMetricsQueryPayload = (
|
||||
type: 'tag',
|
||||
},
|
||||
op: '=',
|
||||
value:
|
||||
pod.meta?.[INFRA_MONITORING_ATTR_KEYS.K8S_NAMESPACE_NAME] || '',
|
||||
value: pod.meta.k8s_namespace_name,
|
||||
},
|
||||
{
|
||||
id: '703fced1',
|
||||
@@ -713,7 +701,7 @@ export const getPodMetricsQueryPayload = (
|
||||
type: 'tag',
|
||||
},
|
||||
op: '=',
|
||||
value: pod.meta?.[INFRA_MONITORING_ATTR_KEYS.K8S_POD_NAME] || '',
|
||||
value: pod.meta.k8s_pod_name,
|
||||
},
|
||||
],
|
||||
op: 'AND',
|
||||
@@ -786,8 +774,7 @@ export const getPodMetricsQueryPayload = (
|
||||
type: 'tag',
|
||||
},
|
||||
op: '=',
|
||||
value:
|
||||
pod.meta?.[INFRA_MONITORING_ATTR_KEYS.K8S_NAMESPACE_NAME] || '',
|
||||
value: pod.meta.k8s_namespace_name,
|
||||
},
|
||||
{
|
||||
id: '5418405c',
|
||||
@@ -798,7 +785,7 @@ export const getPodMetricsQueryPayload = (
|
||||
type: 'tag',
|
||||
},
|
||||
op: '=',
|
||||
value: pod.meta?.[INFRA_MONITORING_ATTR_KEYS.K8S_POD_NAME] || '',
|
||||
value: pod.meta.k8s_pod_name,
|
||||
},
|
||||
],
|
||||
op: 'AND',
|
||||
@@ -837,8 +824,7 @@ export const getPodMetricsQueryPayload = (
|
||||
type: 'tag',
|
||||
},
|
||||
op: '=',
|
||||
value:
|
||||
pod.meta?.[INFRA_MONITORING_ATTR_KEYS.K8S_NAMESPACE_NAME] || '',
|
||||
value: pod.meta.k8s_namespace_name,
|
||||
},
|
||||
{
|
||||
id: '1ea5c602',
|
||||
@@ -849,7 +835,7 @@ export const getPodMetricsQueryPayload = (
|
||||
type: 'tag',
|
||||
},
|
||||
op: '=',
|
||||
value: pod.meta?.[INFRA_MONITORING_ATTR_KEYS.K8S_POD_NAME] || '',
|
||||
value: pod.meta.k8s_pod_name,
|
||||
},
|
||||
],
|
||||
op: 'AND',
|
||||
@@ -888,8 +874,7 @@ export const getPodMetricsQueryPayload = (
|
||||
type: 'tag',
|
||||
},
|
||||
op: '=',
|
||||
value:
|
||||
pod.meta?.[INFRA_MONITORING_ATTR_KEYS.K8S_NAMESPACE_NAME] || '',
|
||||
value: pod.meta.k8s_namespace_name,
|
||||
},
|
||||
{
|
||||
id: 'd7632974',
|
||||
@@ -900,7 +885,7 @@ export const getPodMetricsQueryPayload = (
|
||||
type: 'tag',
|
||||
},
|
||||
op: '=',
|
||||
value: pod.meta?.[INFRA_MONITORING_ATTR_KEYS.K8S_POD_NAME] || '',
|
||||
value: pod.meta.k8s_pod_name,
|
||||
},
|
||||
],
|
||||
op: 'AND',
|
||||
@@ -973,8 +958,7 @@ export const getPodMetricsQueryPayload = (
|
||||
type: 'tag',
|
||||
},
|
||||
op: '=',
|
||||
value:
|
||||
pod.meta?.[INFRA_MONITORING_ATTR_KEYS.K8S_NAMESPACE_NAME] || '',
|
||||
value: pod.meta.k8s_namespace_name,
|
||||
},
|
||||
{
|
||||
id: '5a822bec',
|
||||
@@ -985,7 +969,7 @@ export const getPodMetricsQueryPayload = (
|
||||
type: 'tag',
|
||||
},
|
||||
op: '=',
|
||||
value: pod.meta?.[INFRA_MONITORING_ATTR_KEYS.K8S_POD_NAME] || '',
|
||||
value: pod.meta.k8s_pod_name,
|
||||
},
|
||||
],
|
||||
op: 'AND',
|
||||
@@ -1024,8 +1008,7 @@ export const getPodMetricsQueryPayload = (
|
||||
type: 'tag',
|
||||
},
|
||||
op: '=',
|
||||
value:
|
||||
pod.meta?.[INFRA_MONITORING_ATTR_KEYS.K8S_NAMESPACE_NAME] || '',
|
||||
value: pod.meta.k8s_namespace_name,
|
||||
},
|
||||
{
|
||||
id: '3362c603',
|
||||
@@ -1036,7 +1019,7 @@ export const getPodMetricsQueryPayload = (
|
||||
type: 'tag',
|
||||
},
|
||||
op: '=',
|
||||
value: pod.meta?.[INFRA_MONITORING_ATTR_KEYS.K8S_POD_NAME] || '',
|
||||
value: pod.meta.k8s_pod_name,
|
||||
},
|
||||
],
|
||||
op: 'AND',
|
||||
@@ -1075,8 +1058,7 @@ export const getPodMetricsQueryPayload = (
|
||||
type: 'tag',
|
||||
},
|
||||
op: '=',
|
||||
value:
|
||||
pod.meta?.[INFRA_MONITORING_ATTR_KEYS.K8S_NAMESPACE_NAME] || '',
|
||||
value: pod.meta.k8s_namespace_name,
|
||||
},
|
||||
{
|
||||
id: 'ce88008b',
|
||||
@@ -1087,7 +1069,7 @@ export const getPodMetricsQueryPayload = (
|
||||
type: 'tag',
|
||||
},
|
||||
op: '=',
|
||||
value: pod.meta?.[INFRA_MONITORING_ATTR_KEYS.K8S_POD_NAME] || '',
|
||||
value: pod.meta.k8s_pod_name,
|
||||
},
|
||||
],
|
||||
op: 'AND',
|
||||
@@ -1126,8 +1108,7 @@ export const getPodMetricsQueryPayload = (
|
||||
type: 'tag',
|
||||
},
|
||||
op: '=',
|
||||
value:
|
||||
pod.meta?.[INFRA_MONITORING_ATTR_KEYS.K8S_NAMESPACE_NAME] || '',
|
||||
value: pod.meta.k8s_namespace_name,
|
||||
},
|
||||
{
|
||||
id: '53fc92fd',
|
||||
@@ -1138,7 +1119,7 @@ export const getPodMetricsQueryPayload = (
|
||||
type: 'tag',
|
||||
},
|
||||
op: '=',
|
||||
value: pod.meta?.[INFRA_MONITORING_ATTR_KEYS.K8S_POD_NAME] || '',
|
||||
value: pod.meta.k8s_pod_name,
|
||||
},
|
||||
],
|
||||
op: 'AND',
|
||||
@@ -1177,8 +1158,7 @@ export const getPodMetricsQueryPayload = (
|
||||
type: 'tag',
|
||||
},
|
||||
op: '=',
|
||||
value:
|
||||
pod.meta?.[INFRA_MONITORING_ATTR_KEYS.K8S_NAMESPACE_NAME] || '',
|
||||
value: pod.meta.k8s_namespace_name,
|
||||
},
|
||||
{
|
||||
id: '61dfa9f6',
|
||||
@@ -1189,7 +1169,7 @@ export const getPodMetricsQueryPayload = (
|
||||
type: 'tag',
|
||||
},
|
||||
op: '=',
|
||||
value: pod.meta?.[INFRA_MONITORING_ATTR_KEYS.K8S_POD_NAME] || '',
|
||||
value: pod.meta.k8s_pod_name,
|
||||
},
|
||||
],
|
||||
op: 'AND',
|
||||
@@ -1228,8 +1208,7 @@ export const getPodMetricsQueryPayload = (
|
||||
type: 'tag',
|
||||
},
|
||||
op: '=',
|
||||
value:
|
||||
pod.meta?.[INFRA_MONITORING_ATTR_KEYS.K8S_NAMESPACE_NAME] || '',
|
||||
value: pod.meta.k8s_namespace_name,
|
||||
},
|
||||
{
|
||||
id: '508cdf26',
|
||||
@@ -1240,7 +1219,7 @@ export const getPodMetricsQueryPayload = (
|
||||
type: 'tag',
|
||||
},
|
||||
op: '=',
|
||||
value: pod.meta?.[INFRA_MONITORING_ATTR_KEYS.K8S_POD_NAME] || '',
|
||||
value: pod.meta.k8s_pod_name,
|
||||
},
|
||||
],
|
||||
op: 'AND',
|
||||
@@ -1313,8 +1292,7 @@ export const getPodMetricsQueryPayload = (
|
||||
type: 'tag',
|
||||
},
|
||||
op: '=',
|
||||
value:
|
||||
pod.meta?.[INFRA_MONITORING_ATTR_KEYS.K8S_NAMESPACE_NAME] || '',
|
||||
value: pod.meta.k8s_namespace_name,
|
||||
},
|
||||
{
|
||||
id: '80528f79',
|
||||
@@ -1325,7 +1303,7 @@ export const getPodMetricsQueryPayload = (
|
||||
type: 'tag',
|
||||
},
|
||||
op: '=',
|
||||
value: pod.meta?.[INFRA_MONITORING_ATTR_KEYS.K8S_POD_NAME] || '',
|
||||
value: pod.meta.k8s_pod_name,
|
||||
},
|
||||
],
|
||||
op: 'AND',
|
||||
@@ -1364,8 +1342,7 @@ export const getPodMetricsQueryPayload = (
|
||||
type: 'tag',
|
||||
},
|
||||
op: '=',
|
||||
value:
|
||||
pod.meta?.[INFRA_MONITORING_ATTR_KEYS.K8S_NAMESPACE_NAME] || '',
|
||||
value: pod.meta.k8s_namespace_name,
|
||||
},
|
||||
{
|
||||
id: '780cc786',
|
||||
@@ -1376,7 +1353,7 @@ export const getPodMetricsQueryPayload = (
|
||||
type: 'tag',
|
||||
},
|
||||
op: '=',
|
||||
value: pod.meta?.[INFRA_MONITORING_ATTR_KEYS.K8S_POD_NAME] || '',
|
||||
value: pod.meta.k8s_pod_name,
|
||||
},
|
||||
],
|
||||
op: 'AND',
|
||||
@@ -1456,8 +1433,7 @@ export const getPodMetricsQueryPayload = (
|
||||
type: 'tag',
|
||||
},
|
||||
op: '=',
|
||||
value:
|
||||
pod.meta?.[INFRA_MONITORING_ATTR_KEYS.K8S_NAMESPACE_NAME] || '',
|
||||
value: pod.meta.k8s_namespace_name,
|
||||
},
|
||||
{
|
||||
id: '8b2a539b',
|
||||
@@ -1468,7 +1444,7 @@ export const getPodMetricsQueryPayload = (
|
||||
type: 'tag',
|
||||
},
|
||||
op: '=',
|
||||
value: pod.meta?.[INFRA_MONITORING_ATTR_KEYS.K8S_POD_NAME] || '',
|
||||
value: pod.meta.k8s_pod_name,
|
||||
},
|
||||
],
|
||||
op: 'AND',
|
||||
@@ -1541,8 +1517,7 @@ export const getPodMetricsQueryPayload = (
|
||||
type: 'tag',
|
||||
},
|
||||
op: '=',
|
||||
value:
|
||||
pod.meta?.[INFRA_MONITORING_ATTR_KEYS.K8S_NAMESPACE_NAME] || '',
|
||||
value: pod.meta.k8s_namespace_name,
|
||||
},
|
||||
{
|
||||
id: '4b4382be',
|
||||
@@ -1553,7 +1528,7 @@ export const getPodMetricsQueryPayload = (
|
||||
type: 'tag',
|
||||
},
|
||||
op: '=',
|
||||
value: pod.meta?.[INFRA_MONITORING_ATTR_KEYS.K8S_POD_NAME] || '',
|
||||
value: pod.meta.k8s_pod_name,
|
||||
},
|
||||
],
|
||||
op: 'AND',
|
||||
@@ -1633,8 +1608,7 @@ export const getPodMetricsQueryPayload = (
|
||||
type: 'tag',
|
||||
},
|
||||
op: '=',
|
||||
value:
|
||||
pod.meta?.[INFRA_MONITORING_ATTR_KEYS.K8S_NAMESPACE_NAME] || '',
|
||||
value: pod.meta.k8s_namespace_name,
|
||||
},
|
||||
{
|
||||
id: '9301d7c0',
|
||||
@@ -1645,7 +1619,7 @@ export const getPodMetricsQueryPayload = (
|
||||
type: 'tag',
|
||||
},
|
||||
op: '=',
|
||||
value: pod.meta?.[INFRA_MONITORING_ATTR_KEYS.K8S_POD_NAME] || '',
|
||||
value: pod.meta.k8s_pod_name,
|
||||
},
|
||||
],
|
||||
op: 'AND',
|
||||
@@ -1691,8 +1665,7 @@ export const getPodMetricsQueryPayload = (
|
||||
type: 'tag',
|
||||
},
|
||||
op: '=',
|
||||
value:
|
||||
pod.meta?.[INFRA_MONITORING_ATTR_KEYS.K8S_NAMESPACE_NAME] || '',
|
||||
value: pod.meta.k8s_namespace_name,
|
||||
},
|
||||
{
|
||||
id: '92b99374',
|
||||
@@ -1703,7 +1676,7 @@ export const getPodMetricsQueryPayload = (
|
||||
type: 'tag',
|
||||
},
|
||||
op: '=',
|
||||
value: pod.meta?.[INFRA_MONITORING_ATTR_KEYS.K8S_POD_NAME] || '',
|
||||
value: pod.meta.k8s_pod_name,
|
||||
},
|
||||
],
|
||||
op: 'AND',
|
||||
@@ -1749,8 +1722,7 @@ export const getPodMetricsQueryPayload = (
|
||||
type: 'tag',
|
||||
},
|
||||
op: '=',
|
||||
value:
|
||||
pod.meta?.[INFRA_MONITORING_ATTR_KEYS.K8S_NAMESPACE_NAME] || '',
|
||||
value: pod.meta.k8s_namespace_name,
|
||||
},
|
||||
{
|
||||
id: '1c7de95d',
|
||||
@@ -1761,7 +1733,7 @@ export const getPodMetricsQueryPayload = (
|
||||
type: 'tag',
|
||||
},
|
||||
op: '=',
|
||||
value: pod.meta?.[INFRA_MONITORING_ATTR_KEYS.K8S_POD_NAME] || '',
|
||||
value: pod.meta.k8s_pod_name,
|
||||
},
|
||||
],
|
||||
op: 'AND',
|
||||
@@ -1854,8 +1826,7 @@ export const getPodMetricsQueryPayload = (
|
||||
type: 'tag',
|
||||
},
|
||||
op: '=',
|
||||
value:
|
||||
pod.meta?.[INFRA_MONITORING_ATTR_KEYS.K8S_NAMESPACE_NAME] || '',
|
||||
value: pod.meta.k8s_namespace_name,
|
||||
},
|
||||
{
|
||||
id: '964fd905',
|
||||
@@ -1866,7 +1837,7 @@ export const getPodMetricsQueryPayload = (
|
||||
type: 'tag',
|
||||
},
|
||||
op: '=',
|
||||
value: pod.meta?.[INFRA_MONITORING_ATTR_KEYS.K8S_POD_NAME] || '',
|
||||
value: pod.meta.k8s_pod_name,
|
||||
},
|
||||
],
|
||||
op: 'AND',
|
||||
@@ -1912,8 +1883,7 @@ export const getPodMetricsQueryPayload = (
|
||||
type: 'tag',
|
||||
},
|
||||
op: '=',
|
||||
value:
|
||||
pod.meta?.[INFRA_MONITORING_ATTR_KEYS.K8S_NAMESPACE_NAME] || '',
|
||||
value: pod.meta.k8s_namespace_name,
|
||||
},
|
||||
{
|
||||
id: '76a586be',
|
||||
@@ -1924,7 +1894,7 @@ export const getPodMetricsQueryPayload = (
|
||||
type: 'tag',
|
||||
},
|
||||
op: '=',
|
||||
value: pod.meta?.[INFRA_MONITORING_ATTR_KEYS.K8S_POD_NAME] || '',
|
||||
value: pod.meta.k8s_pod_name,
|
||||
},
|
||||
],
|
||||
op: 'AND',
|
||||
@@ -1970,8 +1940,7 @@ export const getPodMetricsQueryPayload = (
|
||||
type: 'tag',
|
||||
},
|
||||
op: '=',
|
||||
value:
|
||||
pod.meta?.[INFRA_MONITORING_ATTR_KEYS.K8S_NAMESPACE_NAME] || '',
|
||||
value: pod.meta.k8s_namespace_name,
|
||||
},
|
||||
{
|
||||
id: '7c5f875b',
|
||||
@@ -1982,7 +1951,7 @@ export const getPodMetricsQueryPayload = (
|
||||
type: 'tag',
|
||||
},
|
||||
op: '=',
|
||||
value: pod.meta?.[INFRA_MONITORING_ATTR_KEYS.K8S_POD_NAME] || '',
|
||||
value: pod.meta.k8s_pod_name,
|
||||
},
|
||||
],
|
||||
op: 'AND',
|
||||
@@ -2062,8 +2031,7 @@ export const getPodMetricsQueryPayload = (
|
||||
type: 'tag',
|
||||
},
|
||||
op: '=',
|
||||
value:
|
||||
pod.meta?.[INFRA_MONITORING_ATTR_KEYS.K8S_NAMESPACE_NAME] || '',
|
||||
value: pod.meta.k8s_namespace_name,
|
||||
},
|
||||
{
|
||||
id: '80216712',
|
||||
@@ -2074,7 +2042,7 @@ export const getPodMetricsQueryPayload = (
|
||||
type: 'tag',
|
||||
},
|
||||
op: '=',
|
||||
value: pod.meta?.[INFRA_MONITORING_ATTR_KEYS.K8S_POD_NAME] || '',
|
||||
value: pod.meta.k8s_pod_name,
|
||||
},
|
||||
],
|
||||
op: 'AND',
|
||||
@@ -2120,8 +2088,7 @@ export const getPodMetricsQueryPayload = (
|
||||
type: 'tag',
|
||||
},
|
||||
op: '=',
|
||||
value:
|
||||
pod.meta?.[INFRA_MONITORING_ATTR_KEYS.K8S_NAMESPACE_NAME] || '',
|
||||
value: pod.meta.k8s_namespace_name,
|
||||
},
|
||||
{
|
||||
id: '84b59a9f',
|
||||
@@ -2132,7 +2099,7 @@ export const getPodMetricsQueryPayload = (
|
||||
type: 'tag',
|
||||
},
|
||||
op: '=',
|
||||
value: pod.meta?.[INFRA_MONITORING_ATTR_KEYS.K8S_POD_NAME] || '',
|
||||
value: pod.meta.k8s_pod_name,
|
||||
},
|
||||
],
|
||||
op: 'AND',
|
||||
@@ -2178,8 +2145,7 @@ export const getPodMetricsQueryPayload = (
|
||||
type: 'tag',
|
||||
},
|
||||
op: '=',
|
||||
value:
|
||||
pod.meta?.[INFRA_MONITORING_ATTR_KEYS.K8S_NAMESPACE_NAME] || '',
|
||||
value: pod.meta.k8s_namespace_name,
|
||||
},
|
||||
{
|
||||
id: 'd649ad0c',
|
||||
@@ -2190,7 +2156,7 @@ export const getPodMetricsQueryPayload = (
|
||||
type: 'tag',
|
||||
},
|
||||
op: '=',
|
||||
value: pod.meta?.[INFRA_MONITORING_ATTR_KEYS.K8S_POD_NAME] || '',
|
||||
value: pod.meta.k8s_pod_name,
|
||||
},
|
||||
],
|
||||
op: 'AND',
|
||||
@@ -2283,8 +2249,7 @@ export const getPodMetricsQueryPayload = (
|
||||
type: 'tag',
|
||||
},
|
||||
op: '=',
|
||||
value:
|
||||
pod.meta?.[INFRA_MONITORING_ATTR_KEYS.K8S_NAMESPACE_NAME] || '',
|
||||
value: pod.meta.k8s_namespace_name,
|
||||
},
|
||||
{
|
||||
id: 'bc9c5cf3',
|
||||
@@ -2295,7 +2260,7 @@ export const getPodMetricsQueryPayload = (
|
||||
type: 'tag',
|
||||
},
|
||||
op: '=',
|
||||
value: pod.meta?.[INFRA_MONITORING_ATTR_KEYS.K8S_POD_NAME] || '',
|
||||
value: pod.meta.k8s_pod_name,
|
||||
},
|
||||
],
|
||||
op: 'AND',
|
||||
@@ -2381,8 +2346,7 @@ export const getPodMetricsQueryPayload = (
|
||||
type: 'tag',
|
||||
},
|
||||
op: '=',
|
||||
value:
|
||||
pod.meta?.[INFRA_MONITORING_ATTR_KEYS.K8S_NAMESPACE_NAME] || '',
|
||||
value: pod.meta.k8s_namespace_name,
|
||||
},
|
||||
{
|
||||
id: 'b54a3d78',
|
||||
@@ -2393,7 +2357,7 @@ export const getPodMetricsQueryPayload = (
|
||||
type: 'tag',
|
||||
},
|
||||
op: '=',
|
||||
value: pod.meta?.[INFRA_MONITORING_ATTR_KEYS.K8S_POD_NAME] || '',
|
||||
value: pod.meta.k8s_pod_name,
|
||||
},
|
||||
],
|
||||
op: 'AND',
|
||||
@@ -2479,8 +2443,7 @@ export const getPodMetricsQueryPayload = (
|
||||
type: 'tag',
|
||||
},
|
||||
op: '=',
|
||||
value:
|
||||
pod.meta?.[INFRA_MONITORING_ATTR_KEYS.K8S_NAMESPACE_NAME] || '',
|
||||
value: pod.meta.k8s_namespace_name,
|
||||
},
|
||||
{
|
||||
id: '1a97cb95',
|
||||
@@ -2491,7 +2454,7 @@ export const getPodMetricsQueryPayload = (
|
||||
type: 'tag',
|
||||
},
|
||||
op: '=',
|
||||
value: pod.meta?.[INFRA_MONITORING_ATTR_KEYS.K8S_POD_NAME] || '',
|
||||
value: pod.meta.k8s_pod_name,
|
||||
},
|
||||
],
|
||||
op: 'AND',
|
||||
@@ -2530,8 +2493,7 @@ export const getPodMetricsQueryPayload = (
|
||||
type: 'tag',
|
||||
},
|
||||
op: '=',
|
||||
value:
|
||||
pod.meta?.[INFRA_MONITORING_ATTR_KEYS.K8S_NAMESPACE_NAME] || '',
|
||||
value: pod.meta.k8s_namespace_name,
|
||||
},
|
||||
{
|
||||
id: 'ddfbd379',
|
||||
@@ -2542,7 +2504,7 @@ export const getPodMetricsQueryPayload = (
|
||||
type: 'tag',
|
||||
},
|
||||
op: '=',
|
||||
value: pod.meta?.[INFRA_MONITORING_ATTR_KEYS.K8S_POD_NAME] || '',
|
||||
value: pod.meta.k8s_pod_name,
|
||||
},
|
||||
],
|
||||
op: 'AND',
|
||||
@@ -2581,8 +2543,7 @@ export const getPodMetricsQueryPayload = (
|
||||
type: 'tag',
|
||||
},
|
||||
op: '=',
|
||||
value:
|
||||
pod.meta?.[INFRA_MONITORING_ATTR_KEYS.K8S_NAMESPACE_NAME] || '',
|
||||
value: pod.meta.k8s_namespace_name,
|
||||
},
|
||||
{
|
||||
id: '1963fc96',
|
||||
@@ -2593,7 +2554,7 @@ export const getPodMetricsQueryPayload = (
|
||||
type: 'tag',
|
||||
},
|
||||
op: '=',
|
||||
value: pod.meta?.[INFRA_MONITORING_ATTR_KEYS.K8S_POD_NAME] || '',
|
||||
value: pod.meta.k8s_pod_name,
|
||||
},
|
||||
],
|
||||
op: 'AND',
|
||||
@@ -2601,7 +2562,7 @@ export const getPodMetricsQueryPayload = (
|
||||
functions: [],
|
||||
groupBy: [],
|
||||
having: [],
|
||||
legend: 'Usage',
|
||||
legend: 'Uage',
|
||||
limit: null,
|
||||
orderBy: [],
|
||||
queryName: 'C',
|
||||
|
||||
@@ -1,63 +1,30 @@
|
||||
import { Container } from '@signozhq/icons';
|
||||
import { Badge, BadgeColor } from '@signozhq/ui/badge';
|
||||
import { TooltipSimple } from '@signozhq/ui/tooltip';
|
||||
import {
|
||||
InframonitoringtypesPodPhaseDTO,
|
||||
InframonitoringtypesPodRecordDTO,
|
||||
} from 'api/generated/services/sigNoz.schemas';
|
||||
import TanStackTable, { TableColumnDef } from 'components/TanStackTableView';
|
||||
import { Tooltip } from 'antd';
|
||||
import { TableColumnDef } from 'components/TanStackTableView';
|
||||
import TanStackTable from 'components/TanStackTableView';
|
||||
import { ExpandButtonWrapper } from 'container/InfraMonitoringK8sV2/components';
|
||||
|
||||
import ColumnHeader from '../Base/ColumnHeader';
|
||||
import EntityGroupHeader from '../Base/EntityGroupHeader';
|
||||
import K8sGroupCell from '../Base/K8sGroupCell';
|
||||
import { formatBytes, getPodPhaseStatusItems } from '../commonUtils';
|
||||
import {
|
||||
CellValueTooltip,
|
||||
EntityProgressBar,
|
||||
GroupedStatusCounts,
|
||||
ValidateColumnValueWrapper,
|
||||
} from '../components';
|
||||
import {
|
||||
INFRA_MONITORING_ATTR_KEYS,
|
||||
InfraMonitoringEntity,
|
||||
} from '../constants';
|
||||
import { Typography } from '@signozhq/ui/typography';
|
||||
import { formatAge } from 'utils/timeUtils';
|
||||
import { formatBytes } from '../commonUtils';
|
||||
import { EntityProgressBar, ValidateColumnValueWrapper } from '../components';
|
||||
import { InfraMonitoringEntity } from '../constants';
|
||||
import { K8sPodsData } from './api';
|
||||
import { Container } from '@signozhq/icons';
|
||||
|
||||
export function getK8sPodRowKey(pod: InframonitoringtypesPodRecordDTO): string {
|
||||
return (
|
||||
pod.podUID ||
|
||||
pod.meta?.[INFRA_MONITORING_ATTR_KEYS.K8S_POD_UID] ||
|
||||
pod.meta?.[INFRA_MONITORING_ATTR_KEYS.K8S_POD_NAME] ||
|
||||
''
|
||||
);
|
||||
export function getK8sPodRowKey(pod: K8sPodsData): string {
|
||||
return pod.podUID || pod.meta.k8s_pod_uid || pod.meta.k8s_pod_name;
|
||||
}
|
||||
|
||||
export function getK8sPodItemKey(
|
||||
pod: InframonitoringtypesPodRecordDTO,
|
||||
): string {
|
||||
export function getK8sPodItemKey(pod: K8sPodsData): string {
|
||||
return pod.podUID;
|
||||
}
|
||||
|
||||
const POD_PHASE_COLORS: Record<string, BadgeColor> = {
|
||||
running: 'forest',
|
||||
pending: 'amber',
|
||||
succeeded: 'robin',
|
||||
failed: 'cherry',
|
||||
unknown: 'vanilla',
|
||||
no_data: 'vanilla',
|
||||
};
|
||||
|
||||
export type PodTableColumnConfig =
|
||||
TableColumnDef<InframonitoringtypesPodRecordDTO>;
|
||||
export const k8sPodColumnsConfig: PodTableColumnConfig[] = [
|
||||
export const k8sPodColumnsConfig: TableColumnDef<K8sPodsData>[] = [
|
||||
{
|
||||
id: 'podGroup',
|
||||
header: (): React.ReactNode => <EntityGroupHeader title="Pod Group" />,
|
||||
accessorFn: (row): string =>
|
||||
row.meta?.[INFRA_MONITORING_ATTR_KEYS.K8S_POD_NAME] || '',
|
||||
width: { min: 290 },
|
||||
header: (): React.ReactNode => <EntityGroupHeader title="POD GROUP" />,
|
||||
accessorFn: (row): string => row.meta.k8s_pod_name || '',
|
||||
width: { min: 300 },
|
||||
enableSort: false,
|
||||
enableRemove: false,
|
||||
enableMove: false,
|
||||
@@ -80,11 +47,9 @@ export const k8sPodColumnsConfig: PodTableColumnConfig[] = [
|
||||
<EntityGroupHeader
|
||||
title="Pod Name"
|
||||
icon={<Container data-hide-expanded="true" size={14} />}
|
||||
docPath="/infrastructure-monitoring/kubernetes/pods#pod-name"
|
||||
/>
|
||||
),
|
||||
accessorFn: (row): string =>
|
||||
row.meta?.[INFRA_MONITORING_ATTR_KEYS.K8S_POD_NAME] || '',
|
||||
accessorFn: (row): string => row.meta.k8s_pod_name || '',
|
||||
width: { min: 290 },
|
||||
enableSort: false,
|
||||
enableRemove: false,
|
||||
@@ -94,92 +59,18 @@ export const k8sPodColumnsConfig: PodTableColumnConfig[] = [
|
||||
cell: ({ value }): React.ReactNode => {
|
||||
const podName = value as string;
|
||||
return (
|
||||
<CellValueTooltip value={podName}>
|
||||
<Tooltip title={podName}>
|
||||
<TanStackTable.Text>{podName}</TanStackTable.Text>
|
||||
</CellValueTooltip>
|
||||
</Tooltip>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'podPhase',
|
||||
header: (): React.ReactNode => (
|
||||
<ColumnHeader docPath="/infrastructure-monitoring/kubernetes/pods#pod-phase">
|
||||
Phase
|
||||
</ColumnHeader>
|
||||
),
|
||||
accessorFn: (row): string => row.podPhase,
|
||||
width: { min: 120 },
|
||||
enableSort: false,
|
||||
visibilityBehavior: 'hidden-on-expand',
|
||||
cell: ({ row }): React.ReactNode => {
|
||||
if (!row.podPhase) {
|
||||
return <></>;
|
||||
}
|
||||
|
||||
const color = POD_PHASE_COLORS[row.podPhase] || POD_PHASE_COLORS.unknown;
|
||||
const label =
|
||||
row.podPhase === InframonitoringtypesPodPhaseDTO.no_data
|
||||
? 'No Data'
|
||||
: row.podPhase.charAt(0).toUpperCase() + row.podPhase.slice(1);
|
||||
return (
|
||||
<Badge color={color} variant="outline">
|
||||
{label}
|
||||
</Badge>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'podCountsByPhase',
|
||||
header: (): React.ReactNode => (
|
||||
<ColumnHeader docPath="/infrastructure-monitoring/kubernetes/pods#pod-phase">
|
||||
Phases
|
||||
</ColumnHeader>
|
||||
),
|
||||
accessorFn: (row): InframonitoringtypesPodRecordDTO['podCountsByPhase'] =>
|
||||
row.podCountsByPhase,
|
||||
width: { min: 250 },
|
||||
enableSort: false,
|
||||
visibilityBehavior: 'hidden-on-collapse',
|
||||
cell: ({ row }): React.ReactNode => {
|
||||
const podCountsByPhase = row.podCountsByPhase;
|
||||
if (!podCountsByPhase) {
|
||||
return <TanStackTable.Text>-</TanStackTable.Text>;
|
||||
}
|
||||
return (
|
||||
<GroupedStatusCounts items={getPodPhaseStatusItems(row.podCountsByPhase)} />
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'podAge',
|
||||
header: 'Age',
|
||||
accessorFn: (row): number => row.podAge,
|
||||
width: { min: 100 },
|
||||
enableSort: false,
|
||||
cell: ({ value }): React.ReactNode => {
|
||||
const age = value as number;
|
||||
if (age === -1) {
|
||||
return (
|
||||
<TooltipSimple title="No data">
|
||||
<Typography.Text>-</Typography.Text>
|
||||
</TooltipSimple>
|
||||
);
|
||||
}
|
||||
return <TanStackTable.Text>{formatAge(age)}</TanStackTable.Text>;
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'cpu_request',
|
||||
header: (): React.ReactNode => (
|
||||
<ColumnHeader docPath="/infrastructure-monitoring/kubernetes/pods#cpu-req-usage-">
|
||||
CPU Request
|
||||
<br /> Usage (%)
|
||||
</ColumnHeader>
|
||||
),
|
||||
header: 'CPU Req Usage (%)',
|
||||
accessorFn: (row): number => row.podCPURequest,
|
||||
width: { min: 210 },
|
||||
enableSort: true,
|
||||
defaultVisibility: false,
|
||||
cell: ({ value }): React.ReactNode => {
|
||||
const cpuRequest = value as number;
|
||||
return (
|
||||
@@ -195,14 +86,9 @@ export const k8sPodColumnsConfig: PodTableColumnConfig[] = [
|
||||
},
|
||||
{
|
||||
id: 'cpu_limit',
|
||||
header: (): React.ReactNode => (
|
||||
<ColumnHeader docPath="/infrastructure-monitoring/kubernetes/pods#cpu-limit-usage-">
|
||||
CPU Limit
|
||||
<br /> Usage (%)
|
||||
</ColumnHeader>
|
||||
),
|
||||
header: 'CPU Limit Usage (%)',
|
||||
accessorFn: (row): number => row.podCPULimit,
|
||||
width: { min: 220 },
|
||||
width: { min: 210 },
|
||||
enableSort: true,
|
||||
cell: ({ value }): React.ReactNode => {
|
||||
const cpuLimit = value as number;
|
||||
@@ -219,40 +105,29 @@ export const k8sPodColumnsConfig: PodTableColumnConfig[] = [
|
||||
},
|
||||
{
|
||||
id: 'cpu',
|
||||
header: (): React.ReactNode => (
|
||||
<ColumnHeader docPath="/infrastructure-monitoring/kubernetes/pods#cpu-usage-cores">
|
||||
CPU Usage
|
||||
<br /> (cores)
|
||||
</ColumnHeader>
|
||||
),
|
||||
header: 'CPU Usage (cores)',
|
||||
accessorFn: (row): number => row.podCPU,
|
||||
width: { min: 160 },
|
||||
width: { min: 210 },
|
||||
enableSort: true,
|
||||
cell: ({ value }): React.ReactNode => {
|
||||
const cpu = Number(value);
|
||||
const cpu = value as number;
|
||||
return (
|
||||
<ValidateColumnValueWrapper
|
||||
value={cpu}
|
||||
entity={InfraMonitoringEntity.PODS}
|
||||
attribute="CPU metric"
|
||||
>
|
||||
<TanStackTable.Text>{cpu.toFixed(2)}</TanStackTable.Text>
|
||||
<TanStackTable.Text>{cpu}</TanStackTable.Text>
|
||||
</ValidateColumnValueWrapper>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'memory_request',
|
||||
header: (): React.ReactNode => (
|
||||
<ColumnHeader docPath="/infrastructure-monitoring/kubernetes/pods#mem-req-usage-">
|
||||
Memory Request
|
||||
<br /> Usage (%)
|
||||
</ColumnHeader>
|
||||
),
|
||||
header: 'Mem Req Usage (%)',
|
||||
accessorFn: (row): number => row.podMemoryRequest,
|
||||
width: { min: 210 },
|
||||
enableSort: true,
|
||||
defaultVisibility: false,
|
||||
cell: ({ value }): React.ReactNode => {
|
||||
const memoryRequest = value as number;
|
||||
return (
|
||||
@@ -268,14 +143,9 @@ export const k8sPodColumnsConfig: PodTableColumnConfig[] = [
|
||||
},
|
||||
{
|
||||
id: 'memory_limit',
|
||||
header: (): React.ReactNode => (
|
||||
<ColumnHeader docPath="/infrastructure-monitoring/kubernetes/pods#mem-limit-usage-">
|
||||
Memory Limit
|
||||
<br /> Usage (%)
|
||||
</ColumnHeader>
|
||||
),
|
||||
header: 'Mem Limit Usage (%)',
|
||||
accessorFn: (row): number => row.podMemoryLimit,
|
||||
width: { min: 220 },
|
||||
width: { min: 210 },
|
||||
enableSort: true,
|
||||
cell: ({ value }): React.ReactNode => {
|
||||
const memoryLimit = value as number;
|
||||
@@ -292,12 +162,7 @@ export const k8sPodColumnsConfig: PodTableColumnConfig[] = [
|
||||
},
|
||||
{
|
||||
id: 'memory',
|
||||
header: (): React.ReactNode => (
|
||||
<ColumnHeader docPath="/infrastructure-monitoring/kubernetes/pods#mem-usage-wss">
|
||||
Memory Usage
|
||||
<br /> (WSS)
|
||||
</ColumnHeader>
|
||||
),
|
||||
header: 'Mem Usage (WSS)',
|
||||
accessorFn: (row): number => row.podMemory,
|
||||
width: { min: 210, default: '100%' },
|
||||
enableSort: true,
|
||||
@@ -317,8 +182,7 @@ export const k8sPodColumnsConfig: PodTableColumnConfig[] = [
|
||||
{
|
||||
id: 'namespace',
|
||||
header: 'Namespace',
|
||||
accessorFn: (row): string =>
|
||||
row.meta?.[INFRA_MONITORING_ATTR_KEYS.K8S_NAMESPACE_NAME] || '',
|
||||
accessorFn: (row): string => row.meta.k8s_namespace_name || '',
|
||||
width: { default: 100 },
|
||||
enableSort: false,
|
||||
defaultVisibility: false,
|
||||
@@ -329,8 +193,7 @@ export const k8sPodColumnsConfig: PodTableColumnConfig[] = [
|
||||
{
|
||||
id: 'node',
|
||||
header: 'Node',
|
||||
accessorFn: (row): string =>
|
||||
row.meta?.[INFRA_MONITORING_ATTR_KEYS.K8S_NODE_NAME] || '',
|
||||
accessorFn: (row): string => row.meta.k8s_node_name || '',
|
||||
width: { default: 100 },
|
||||
enableSort: false,
|
||||
defaultVisibility: false,
|
||||
@@ -341,8 +204,7 @@ export const k8sPodColumnsConfig: PodTableColumnConfig[] = [
|
||||
{
|
||||
id: 'cluster',
|
||||
header: 'Cluster',
|
||||
accessorFn: (row): string =>
|
||||
row.meta?.[INFRA_MONITORING_ATTR_KEYS.K8S_CLUSTER_NAME] || '',
|
||||
accessorFn: (row): string => row.meta.k8s_cluster_name || '',
|
||||
width: { default: 100 },
|
||||
enableSort: false,
|
||||
defaultVisibility: false,
|
||||
|
||||
@@ -1,23 +1,20 @@
|
||||
import { useCallback } from 'react';
|
||||
import { listStatefulSets } from 'api/generated/services/inframonitoring';
|
||||
import {
|
||||
InframonitoringtypesResponseTypeDTO,
|
||||
InframonitoringtypesStatefulSetRecordDTO,
|
||||
Querybuildertypesv5OrderDirectionDTO,
|
||||
} from 'api/generated/services/sigNoz.schemas';
|
||||
import React, { useCallback } from 'react';
|
||||
import { InfraMonitoringEvents } from 'constants/events';
|
||||
import { FeatureKeys } from 'constants/features';
|
||||
import { useAppContext } from 'providers/App/App';
|
||||
|
||||
import K8sBaseDetails, { K8sDetailsFilters } from '../Base/K8sBaseDetails';
|
||||
import { K8sBaseList } from '../Base/K8sBaseList';
|
||||
import { K8sBaseFilters } from '../Base/types';
|
||||
import { InfraMonitoringEntity } from '../constants';
|
||||
import { getK8sStatefulSetsList, K8sStatefulSetsData } from './api';
|
||||
import {
|
||||
getStatefulSetMetricsQueryPayload,
|
||||
k8sStatefulSetDetailsMetadataConfig,
|
||||
k8sStatefulSetGetEntityName,
|
||||
k8sStatefulSetGetSelectedItemExpression,
|
||||
k8sStatefulSetInitialEventsExpression,
|
||||
k8sStatefulSetInitialLogTracesExpression,
|
||||
k8sStatefulSetGetSelectedItemFilters,
|
||||
k8sStatefulSetInitialEventsFilter,
|
||||
k8sStatefulSetInitialLogTracesFilter,
|
||||
statefulSetWidgetInfo,
|
||||
} from './constants';
|
||||
import {
|
||||
@@ -31,93 +28,66 @@ function K8sStatefulSetsList({
|
||||
}: {
|
||||
controlListPrefix?: React.ReactNode;
|
||||
}): JSX.Element {
|
||||
const { featureFlags } = useAppContext();
|
||||
const dotMetricsEnabled =
|
||||
featureFlags?.find((flag) => flag.name === FeatureKeys.DOT_METRICS_ENABLED)
|
||||
?.active || false;
|
||||
|
||||
const fetchListData = useCallback(
|
||||
async (filters: K8sBaseFilters, signal?: AbortSignal) => {
|
||||
try {
|
||||
const response = await listStatefulSets(
|
||||
{
|
||||
filter: { expression: filters.filter.expression },
|
||||
groupBy: filters.groupBy?.map((g) => ({ name: g.name })),
|
||||
offset: filters.offset,
|
||||
limit: filters.limit ?? 10,
|
||||
start: filters.start,
|
||||
end: filters.end,
|
||||
orderBy: filters.orderBy
|
||||
? {
|
||||
key: { name: filters.orderBy.key.name },
|
||||
direction:
|
||||
filters.orderBy.direction === 'asc'
|
||||
? Querybuildertypesv5OrderDirectionDTO.asc
|
||||
: Querybuildertypesv5OrderDirectionDTO.desc,
|
||||
}
|
||||
: undefined,
|
||||
},
|
||||
signal,
|
||||
);
|
||||
filters.orderBy ||= {
|
||||
columnName: 'cpu',
|
||||
order: 'desc',
|
||||
};
|
||||
|
||||
const data = response.data;
|
||||
return {
|
||||
type:
|
||||
data.type === InframonitoringtypesResponseTypeDTO.grouped_list
|
||||
? ('grouped_list' as const)
|
||||
: ('list' as const),
|
||||
records: data.records,
|
||||
total: data.total,
|
||||
endTimeBeforeRetention: data.endTimeBeforeRetention,
|
||||
warning: data.warning,
|
||||
};
|
||||
} catch (error) {
|
||||
const errMsg =
|
||||
error instanceof Error ? error.message : 'Failed to fetch statefulsets';
|
||||
return {
|
||||
type: 'list' as const,
|
||||
records: [] as InframonitoringtypesStatefulSetRecordDTO[],
|
||||
total: 0,
|
||||
error: errMsg,
|
||||
};
|
||||
}
|
||||
const response = await getK8sStatefulSetsList(
|
||||
filters,
|
||||
signal,
|
||||
undefined,
|
||||
dotMetricsEnabled,
|
||||
);
|
||||
|
||||
return {
|
||||
data: response.payload?.data.records || [],
|
||||
total: response.payload?.data.total || 0,
|
||||
error: response.error,
|
||||
rawData: response.payload?.data,
|
||||
};
|
||||
},
|
||||
[],
|
||||
[dotMetricsEnabled],
|
||||
);
|
||||
|
||||
const fetchEntityData = useCallback(
|
||||
async (
|
||||
filters: K8sDetailsFilters,
|
||||
signal?: AbortSignal,
|
||||
): Promise<{
|
||||
data: InframonitoringtypesStatefulSetRecordDTO | null;
|
||||
error?: string | null;
|
||||
}> => {
|
||||
try {
|
||||
const response = await listStatefulSets(
|
||||
{
|
||||
filter: { expression: filters.filter.expression },
|
||||
start: filters.start,
|
||||
end: filters.end,
|
||||
limit: 1,
|
||||
offset: 0,
|
||||
},
|
||||
signal,
|
||||
);
|
||||
): Promise<{ data: K8sStatefulSetsData | null; error?: string | null }> => {
|
||||
const response = await getK8sStatefulSetsList(
|
||||
{
|
||||
filters: filters.filters,
|
||||
start: filters.start,
|
||||
end: filters.end,
|
||||
limit: 1,
|
||||
offset: 0,
|
||||
},
|
||||
signal,
|
||||
undefined,
|
||||
dotMetricsEnabled,
|
||||
);
|
||||
|
||||
return {
|
||||
data: response.data.records.length > 0 ? response.data.records[0] : null,
|
||||
};
|
||||
} catch (error) {
|
||||
const errMsg =
|
||||
error instanceof Error ? error.message : 'Failed to fetch statefulset';
|
||||
return {
|
||||
data: null,
|
||||
error: errMsg,
|
||||
};
|
||||
}
|
||||
const records = response.payload?.data.records || [];
|
||||
|
||||
return {
|
||||
data: records.length > 0 ? records[0] : null,
|
||||
error: response.error,
|
||||
};
|
||||
},
|
||||
[],
|
||||
[dotMetricsEnabled],
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<K8sBaseList<InframonitoringtypesStatefulSetRecordDTO>
|
||||
<K8sBaseList<K8sStatefulSetsData>
|
||||
controlListPrefix={controlListPrefix}
|
||||
entity={InfraMonitoringEntity.STATEFULSETS}
|
||||
tableColumns={k8sStatefulSetsColumnsConfig}
|
||||
@@ -127,14 +97,14 @@ function K8sStatefulSetsList({
|
||||
eventCategory={InfraMonitoringEvents.StatefulSet}
|
||||
/>
|
||||
|
||||
<K8sBaseDetails<InframonitoringtypesStatefulSetRecordDTO>
|
||||
<K8sBaseDetails<K8sStatefulSetsData>
|
||||
category={InfraMonitoringEntity.STATEFULSETS}
|
||||
eventCategory={InfraMonitoringEvents.StatefulSet}
|
||||
getSelectedItemExpression={k8sStatefulSetGetSelectedItemExpression}
|
||||
getSelectedItemFilters={k8sStatefulSetGetSelectedItemFilters}
|
||||
fetchEntityData={fetchEntityData}
|
||||
getEntityName={k8sStatefulSetGetEntityName}
|
||||
getInitialLogTracesExpression={k8sStatefulSetInitialLogTracesExpression}
|
||||
getInitialEventsExpression={k8sStatefulSetInitialEventsExpression}
|
||||
getInitialLogTracesFilters={k8sStatefulSetInitialLogTracesFilter}
|
||||
getInitialEventsFilters={k8sStatefulSetInitialEventsFilter}
|
||||
metadataConfig={k8sStatefulSetDetailsMetadataConfig}
|
||||
entityWidgetInfo={statefulSetWidgetInfo}
|
||||
getEntityQueryPayload={getStatefulSetMetricsQueryPayload}
|
||||
|
||||
127
frontend/src/container/InfraMonitoringK8sV2/StatefulSets/api.ts
Normal file
127
frontend/src/container/InfraMonitoringK8sV2/StatefulSets/api.ts
Normal file
@@ -0,0 +1,127 @@
|
||||
import axios from 'api';
|
||||
import { ErrorResponseHandler } from 'api/ErrorResponseHandler';
|
||||
import { UnderscoreToDotMap } from 'api/utils';
|
||||
import { AxiosError } from 'axios';
|
||||
import { ErrorResponse, SuccessResponse } from 'types/api';
|
||||
import { BaseAutocompleteData } from 'types/api/queryBuilder/queryAutocompleteResponse';
|
||||
import { TagFilter } from 'types/api/queryBuilder/queryBuilderData';
|
||||
|
||||
import { K8sBaseFilters } from '../Base/types';
|
||||
|
||||
export interface K8sStatefulSetsListPayload {
|
||||
filters: TagFilter;
|
||||
groupBy?: BaseAutocompleteData[];
|
||||
offset?: number;
|
||||
limit?: number;
|
||||
orderBy?: {
|
||||
columnName: string;
|
||||
order: 'asc' | 'desc';
|
||||
};
|
||||
}
|
||||
|
||||
export interface K8sStatefulSetsData {
|
||||
statefulSetName: string;
|
||||
cpuUsage: number;
|
||||
memoryUsage: number;
|
||||
desiredPods: number;
|
||||
availablePods: number;
|
||||
cpuRequest: number;
|
||||
memoryRequest: number;
|
||||
cpuLimit: number;
|
||||
memoryLimit: number;
|
||||
restarts: number;
|
||||
meta: {
|
||||
k8s_statefulset_name: string;
|
||||
k8s_namespace_name: string;
|
||||
};
|
||||
}
|
||||
|
||||
export interface K8sStatefulSetsListResponse {
|
||||
status: string;
|
||||
data: {
|
||||
type: string;
|
||||
records: K8sStatefulSetsData[];
|
||||
groups: null;
|
||||
total: number;
|
||||
sentAnyHostMetricsData: boolean;
|
||||
isSendingK8SAgentMetrics: boolean;
|
||||
};
|
||||
}
|
||||
|
||||
// TODO(H4ad): Erase this whole file when migrating to openapi
|
||||
export const statefulSetsMetaMap = [
|
||||
{ dot: 'k8s.statefulset.name', under: 'k8s_statefulset_name' },
|
||||
{ dot: 'k8s.namespace.name', under: 'k8s_namespace_name' },
|
||||
] as const;
|
||||
|
||||
export function mapStatefulSetsMeta(
|
||||
raw: Record<string, unknown>,
|
||||
): K8sStatefulSetsData['meta'] {
|
||||
const out: Record<string, unknown> = { ...raw };
|
||||
statefulSetsMetaMap.forEach(({ dot, under }) => {
|
||||
if (dot in raw) {
|
||||
const v = raw[dot];
|
||||
out[under] = typeof v === 'string' ? v : raw[under];
|
||||
}
|
||||
});
|
||||
return out as K8sStatefulSetsData['meta'];
|
||||
}
|
||||
|
||||
export const getK8sStatefulSetsList = async (
|
||||
props: K8sBaseFilters,
|
||||
signal?: AbortSignal,
|
||||
headers?: Record<string, string>,
|
||||
dotMetricsEnabled = false,
|
||||
): Promise<SuccessResponse<K8sStatefulSetsListResponse> | ErrorResponse> => {
|
||||
try {
|
||||
const requestProps = dotMetricsEnabled
|
||||
? {
|
||||
...props,
|
||||
filters: {
|
||||
...props.filters,
|
||||
items: props.filters.items.reduce<TagFilter['items']>(
|
||||
(acc, item) => {
|
||||
if (item.value === undefined) {
|
||||
return acc;
|
||||
}
|
||||
if (
|
||||
item.key &&
|
||||
typeof item.key === 'object' &&
|
||||
'key' in item.key &&
|
||||
typeof item.key.key === 'string'
|
||||
) {
|
||||
const mappedKey = UnderscoreToDotMap[item.key.key] ?? item.key.key;
|
||||
acc.push({ ...item, key: { ...item.key, key: mappedKey } });
|
||||
} else {
|
||||
acc.push(item);
|
||||
}
|
||||
return acc;
|
||||
},
|
||||
[] as TagFilter['items'],
|
||||
),
|
||||
},
|
||||
}
|
||||
: props;
|
||||
|
||||
const response = await axios.post('/statefulsets/list', requestProps, {
|
||||
signal,
|
||||
headers,
|
||||
});
|
||||
const payload: K8sStatefulSetsListResponse = response.data;
|
||||
|
||||
payload.data.records = payload.data.records.map((record) => ({
|
||||
...record,
|
||||
meta: mapStatefulSetsMeta(record.meta as Record<string, unknown>),
|
||||
}));
|
||||
|
||||
return {
|
||||
statusCode: 200,
|
||||
error: null,
|
||||
message: 'Success',
|
||||
payload,
|
||||
params: requestProps,
|
||||
};
|
||||
} catch (error) {
|
||||
return ErrorResponseHandler(error as AxiosError);
|
||||
}
|
||||
};
|
||||
@@ -1,58 +1,67 @@
|
||||
import { InframonitoringtypesStatefulSetRecordDTO } from 'api/generated/services/sigNoz.schemas';
|
||||
import { PANEL_TYPES } from 'constants/queryBuilder';
|
||||
import { GetQueryResultsProps } from 'lib/dashboard/getQueryResults';
|
||||
import { DataTypes } from 'types/api/queryBuilder/queryAutocompleteResponse';
|
||||
import { TagFilter } from 'types/api/queryBuilder/queryBuilderData';
|
||||
import { EQueryType } from 'types/common/dashboard';
|
||||
import { DataSource, ReduceOperators } from 'types/common/queryBuilder';
|
||||
import { v4 } from 'uuid';
|
||||
|
||||
import { K8sDetailsMetadataConfig } from '../Base/K8sBaseDetails';
|
||||
import { formatValueForExpression } from 'components/QueryBuilderV2/utils';
|
||||
import { INFRA_MONITORING_ATTR_KEYS } from '../constants';
|
||||
import {
|
||||
createFilterItem,
|
||||
K8sDetailsMetadataConfig,
|
||||
} from '../Base/K8sBaseDetails';
|
||||
import { QUERY_KEYS } from '../EntityDetailsUtils/utils';
|
||||
import { K8sStatefulSetsData } from './api';
|
||||
|
||||
export const k8sStatefulSetGetSelectedItemExpression = (
|
||||
export const k8sStatefulSetGetSelectedItemFilters = (
|
||||
selectedItemId: string,
|
||||
): string =>
|
||||
`${INFRA_MONITORING_ATTR_KEYS.K8S_STATEFULSET_NAME} = ${formatValueForExpression(selectedItemId)}`;
|
||||
): TagFilter => ({
|
||||
op: 'AND',
|
||||
items: [
|
||||
{
|
||||
id: 'k8s_statefulset_name',
|
||||
key: {
|
||||
key: 'k8s_statefulset_name',
|
||||
type: null,
|
||||
},
|
||||
op: '=',
|
||||
value: selectedItemId,
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
export const k8sStatefulSetDetailsMetadataConfig: K8sDetailsMetadataConfig<InframonitoringtypesStatefulSetRecordDTO>[] =
|
||||
export const k8sStatefulSetDetailsMetadataConfig: K8sDetailsMetadataConfig<K8sStatefulSetsData>[] =
|
||||
[
|
||||
{
|
||||
label: 'Statefulset Name',
|
||||
getValue: (p): string =>
|
||||
p.meta?.[INFRA_MONITORING_ATTR_KEYS.K8S_STATEFULSET_NAME] ?? '',
|
||||
getValue: (p): string => p.meta.k8s_statefulset_name,
|
||||
},
|
||||
{
|
||||
label: 'Namespace Name',
|
||||
getValue: (p): string =>
|
||||
p.meta?.[INFRA_MONITORING_ATTR_KEYS.K8S_NAMESPACE_NAME] ?? '',
|
||||
getValue: (p): string => p.meta.k8s_namespace_name,
|
||||
},
|
||||
];
|
||||
|
||||
export const k8sStatefulSetInitialEventsExpression = (
|
||||
item: InframonitoringtypesStatefulSetRecordDTO,
|
||||
): string => {
|
||||
const objectName = formatValueForExpression(
|
||||
item.meta?.[INFRA_MONITORING_ATTR_KEYS.K8S_STATEFULSET_NAME] ?? '',
|
||||
);
|
||||
return `${INFRA_MONITORING_ATTR_KEYS.K8S_OBJECT_KIND} = 'StatefulSet' AND ${INFRA_MONITORING_ATTR_KEYS.K8S_OBJECT_NAME} = ${objectName}`;
|
||||
};
|
||||
export const k8sStatefulSetInitialEventsFilter = (
|
||||
item: K8sStatefulSetsData,
|
||||
): ReturnType<typeof createFilterItem>[] => [
|
||||
createFilterItem(QUERY_KEYS.K8S_OBJECT_KIND, 'StatefulSet'),
|
||||
createFilterItem(QUERY_KEYS.K8S_OBJECT_NAME, item.meta.k8s_statefulset_name),
|
||||
];
|
||||
|
||||
export const k8sStatefulSetInitialLogTracesExpression = (
|
||||
item: InframonitoringtypesStatefulSetRecordDTO,
|
||||
): string => {
|
||||
const statefulSetName = formatValueForExpression(
|
||||
item.meta?.[INFRA_MONITORING_ATTR_KEYS.K8S_STATEFULSET_NAME] ?? '',
|
||||
);
|
||||
const namespaceName = formatValueForExpression(
|
||||
item.meta?.[INFRA_MONITORING_ATTR_KEYS.K8S_NAMESPACE_NAME] ?? '',
|
||||
);
|
||||
return `${INFRA_MONITORING_ATTR_KEYS.K8S_STATEFULSET_NAME} = ${statefulSetName} AND ${INFRA_MONITORING_ATTR_KEYS.K8S_NAMESPACE_NAME} = ${namespaceName}`;
|
||||
};
|
||||
export const k8sStatefulSetInitialLogTracesFilter = (
|
||||
item: K8sStatefulSetsData,
|
||||
): ReturnType<typeof createFilterItem>[] => [
|
||||
createFilterItem(
|
||||
QUERY_KEYS.K8S_STATEFUL_SET_NAME,
|
||||
item.meta.k8s_statefulset_name,
|
||||
),
|
||||
createFilterItem(QUERY_KEYS.K8S_NAMESPACE_NAME, item.meta.k8s_namespace_name),
|
||||
];
|
||||
|
||||
export const k8sStatefulSetGetEntityName = (
|
||||
item: InframonitoringtypesStatefulSetRecordDTO,
|
||||
): string => item.meta?.[INFRA_MONITORING_ATTR_KEYS.K8S_STATEFULSET_NAME] ?? '';
|
||||
item: K8sStatefulSetsData,
|
||||
): string => item.meta.k8s_statefulset_name;
|
||||
|
||||
export const statefulSetWidgetInfo = [
|
||||
{
|
||||
@@ -82,16 +91,16 @@ export const statefulSetWidgetInfo = [
|
||||
];
|
||||
|
||||
export const getStatefulSetMetricsQueryPayload = (
|
||||
statefulSet: InframonitoringtypesStatefulSetRecordDTO,
|
||||
statefulSet: K8sStatefulSetsData,
|
||||
start: number,
|
||||
end: number,
|
||||
dotMetricsEnabled: boolean,
|
||||
): GetQueryResultsProps[] => {
|
||||
const k8sStatefulSetNameKey = dotMetricsEnabled
|
||||
? INFRA_MONITORING_ATTR_KEYS.K8S_STATEFULSET_NAME
|
||||
? 'k8s.statefulset.name'
|
||||
: 'k8s_statefulset_name';
|
||||
const k8sNamespaceNameKey = dotMetricsEnabled
|
||||
? INFRA_MONITORING_ATTR_KEYS.K8S_NAMESPACE_NAME
|
||||
? 'k8s.namespace.name'
|
||||
: 'k8s_namespace_name';
|
||||
const k8sPodNameKey = dotMetricsEnabled ? 'k8s.pod.name' : 'k8s_pod_name';
|
||||
|
||||
@@ -163,10 +172,7 @@ export const getStatefulSetMetricsQueryPayload = (
|
||||
type: 'tag',
|
||||
},
|
||||
op: '=',
|
||||
value:
|
||||
statefulSet.meta?.[
|
||||
INFRA_MONITORING_ATTR_KEYS.K8S_STATEFULSET_NAME
|
||||
] ?? '',
|
||||
value: statefulSet.meta.k8s_statefulset_name,
|
||||
},
|
||||
{
|
||||
id: 'f2',
|
||||
@@ -177,9 +183,7 @@ export const getStatefulSetMetricsQueryPayload = (
|
||||
type: 'tag',
|
||||
},
|
||||
op: '=',
|
||||
value:
|
||||
statefulSet.meta?.[INFRA_MONITORING_ATTR_KEYS.K8S_NAMESPACE_NAME] ??
|
||||
'',
|
||||
value: statefulSet.meta.k8s_namespace_name,
|
||||
},
|
||||
],
|
||||
op: 'AND',
|
||||
@@ -218,10 +222,7 @@ export const getStatefulSetMetricsQueryPayload = (
|
||||
type: 'tag',
|
||||
},
|
||||
op: 'contains',
|
||||
value:
|
||||
statefulSet.meta?.[
|
||||
INFRA_MONITORING_ATTR_KEYS.K8S_STATEFULSET_NAME
|
||||
] ?? '',
|
||||
value: statefulSet.meta.k8s_statefulset_name,
|
||||
},
|
||||
{
|
||||
id: 'f2',
|
||||
@@ -232,9 +233,7 @@ export const getStatefulSetMetricsQueryPayload = (
|
||||
type: 'tag',
|
||||
},
|
||||
op: '=',
|
||||
value:
|
||||
statefulSet.meta?.[INFRA_MONITORING_ATTR_KEYS.K8S_NAMESPACE_NAME] ??
|
||||
'',
|
||||
value: statefulSet.meta.k8s_namespace_name,
|
||||
},
|
||||
],
|
||||
op: 'AND',
|
||||
@@ -273,10 +272,7 @@ export const getStatefulSetMetricsQueryPayload = (
|
||||
type: 'tag',
|
||||
},
|
||||
op: 'contains',
|
||||
value:
|
||||
statefulSet.meta?.[
|
||||
INFRA_MONITORING_ATTR_KEYS.K8S_STATEFULSET_NAME
|
||||
] ?? '',
|
||||
value: statefulSet.meta.k8s_statefulset_name,
|
||||
},
|
||||
{
|
||||
id: 'f2',
|
||||
@@ -287,9 +283,7 @@ export const getStatefulSetMetricsQueryPayload = (
|
||||
type: 'tag',
|
||||
},
|
||||
op: '=',
|
||||
value:
|
||||
statefulSet.meta?.[INFRA_MONITORING_ATTR_KEYS.K8S_NAMESPACE_NAME] ??
|
||||
'',
|
||||
value: statefulSet.meta.k8s_namespace_name,
|
||||
},
|
||||
],
|
||||
op: 'AND',
|
||||
@@ -348,10 +342,7 @@ export const getStatefulSetMetricsQueryPayload = (
|
||||
type: 'tag',
|
||||
},
|
||||
op: '=',
|
||||
value:
|
||||
statefulSet.meta?.[
|
||||
INFRA_MONITORING_ATTR_KEYS.K8S_STATEFULSET_NAME
|
||||
] ?? '',
|
||||
value: statefulSet.meta.k8s_statefulset_name,
|
||||
},
|
||||
{
|
||||
id: 'f2',
|
||||
@@ -362,9 +353,7 @@ export const getStatefulSetMetricsQueryPayload = (
|
||||
type: 'tag',
|
||||
},
|
||||
op: '=',
|
||||
value:
|
||||
statefulSet.meta?.[INFRA_MONITORING_ATTR_KEYS.K8S_NAMESPACE_NAME] ??
|
||||
'',
|
||||
value: statefulSet.meta.k8s_namespace_name,
|
||||
},
|
||||
],
|
||||
op: 'AND',
|
||||
@@ -403,10 +392,7 @@ export const getStatefulSetMetricsQueryPayload = (
|
||||
type: 'tag',
|
||||
},
|
||||
op: '=',
|
||||
value:
|
||||
statefulSet.meta?.[
|
||||
INFRA_MONITORING_ATTR_KEYS.K8S_STATEFULSET_NAME
|
||||
] ?? '',
|
||||
value: statefulSet.meta.k8s_statefulset_name,
|
||||
},
|
||||
{
|
||||
id: 'f2',
|
||||
@@ -417,9 +403,7 @@ export const getStatefulSetMetricsQueryPayload = (
|
||||
type: 'tag',
|
||||
},
|
||||
op: '=',
|
||||
value:
|
||||
statefulSet.meta?.[INFRA_MONITORING_ATTR_KEYS.K8S_NAMESPACE_NAME] ??
|
||||
'',
|
||||
value: statefulSet.meta.k8s_namespace_name,
|
||||
},
|
||||
],
|
||||
op: 'AND',
|
||||
@@ -478,10 +462,7 @@ export const getStatefulSetMetricsQueryPayload = (
|
||||
type: 'tag',
|
||||
},
|
||||
op: '=',
|
||||
value:
|
||||
statefulSet.meta?.[
|
||||
INFRA_MONITORING_ATTR_KEYS.K8S_STATEFULSET_NAME
|
||||
] ?? '',
|
||||
value: statefulSet.meta.k8s_statefulset_name,
|
||||
},
|
||||
{
|
||||
id: 'f2',
|
||||
@@ -492,9 +473,7 @@ export const getStatefulSetMetricsQueryPayload = (
|
||||
type: 'tag',
|
||||
},
|
||||
op: '=',
|
||||
value:
|
||||
statefulSet.meta?.[INFRA_MONITORING_ATTR_KEYS.K8S_NAMESPACE_NAME] ??
|
||||
'',
|
||||
value: statefulSet.meta.k8s_namespace_name,
|
||||
},
|
||||
],
|
||||
op: 'AND',
|
||||
@@ -533,10 +512,7 @@ export const getStatefulSetMetricsQueryPayload = (
|
||||
type: 'tag',
|
||||
},
|
||||
op: 'contains',
|
||||
value:
|
||||
statefulSet.meta?.[
|
||||
INFRA_MONITORING_ATTR_KEYS.K8S_STATEFULSET_NAME
|
||||
] ?? '',
|
||||
value: statefulSet.meta.k8s_statefulset_name,
|
||||
},
|
||||
{
|
||||
id: 'f2',
|
||||
@@ -547,9 +523,7 @@ export const getStatefulSetMetricsQueryPayload = (
|
||||
type: 'tag',
|
||||
},
|
||||
op: '=',
|
||||
value:
|
||||
statefulSet.meta?.[INFRA_MONITORING_ATTR_KEYS.K8S_NAMESPACE_NAME] ??
|
||||
'',
|
||||
value: statefulSet.meta.k8s_namespace_name,
|
||||
},
|
||||
],
|
||||
op: 'AND',
|
||||
@@ -588,10 +562,7 @@ export const getStatefulSetMetricsQueryPayload = (
|
||||
type: 'tag',
|
||||
},
|
||||
op: 'contains',
|
||||
value:
|
||||
statefulSet.meta?.[
|
||||
INFRA_MONITORING_ATTR_KEYS.K8S_STATEFULSET_NAME
|
||||
] ?? '',
|
||||
value: statefulSet.meta.k8s_statefulset_name,
|
||||
},
|
||||
{
|
||||
id: 'f2',
|
||||
@@ -602,9 +573,7 @@ export const getStatefulSetMetricsQueryPayload = (
|
||||
type: 'tag',
|
||||
},
|
||||
op: '=',
|
||||
value:
|
||||
statefulSet.meta?.[INFRA_MONITORING_ATTR_KEYS.K8S_NAMESPACE_NAME] ??
|
||||
'',
|
||||
value: statefulSet.meta.k8s_namespace_name,
|
||||
},
|
||||
],
|
||||
op: 'AND',
|
||||
@@ -663,10 +632,7 @@ export const getStatefulSetMetricsQueryPayload = (
|
||||
type: 'tag',
|
||||
},
|
||||
op: '=',
|
||||
value:
|
||||
statefulSet.meta?.[
|
||||
INFRA_MONITORING_ATTR_KEYS.K8S_STATEFULSET_NAME
|
||||
] ?? '',
|
||||
value: statefulSet.meta.k8s_statefulset_name,
|
||||
},
|
||||
{
|
||||
id: 'f2',
|
||||
@@ -677,9 +643,7 @@ export const getStatefulSetMetricsQueryPayload = (
|
||||
type: 'tag',
|
||||
},
|
||||
op: '=',
|
||||
value:
|
||||
statefulSet.meta?.[INFRA_MONITORING_ATTR_KEYS.K8S_NAMESPACE_NAME] ??
|
||||
'',
|
||||
value: statefulSet.meta.k8s_namespace_name,
|
||||
},
|
||||
],
|
||||
op: 'AND',
|
||||
@@ -718,10 +682,7 @@ export const getStatefulSetMetricsQueryPayload = (
|
||||
type: 'tag',
|
||||
},
|
||||
op: '=',
|
||||
value:
|
||||
statefulSet.meta?.[
|
||||
INFRA_MONITORING_ATTR_KEYS.K8S_STATEFULSET_NAME
|
||||
] ?? '',
|
||||
value: statefulSet.meta.k8s_statefulset_name,
|
||||
},
|
||||
{
|
||||
id: 'f2',
|
||||
@@ -732,9 +693,7 @@ export const getStatefulSetMetricsQueryPayload = (
|
||||
type: 'tag',
|
||||
},
|
||||
op: '=',
|
||||
value:
|
||||
statefulSet.meta?.[INFRA_MONITORING_ATTR_KEYS.K8S_NAMESPACE_NAME] ??
|
||||
'',
|
||||
value: statefulSet.meta.k8s_namespace_name,
|
||||
},
|
||||
],
|
||||
op: 'AND',
|
||||
@@ -793,10 +752,7 @@ export const getStatefulSetMetricsQueryPayload = (
|
||||
type: 'tag',
|
||||
},
|
||||
op: '=',
|
||||
value:
|
||||
statefulSet.meta?.[
|
||||
INFRA_MONITORING_ATTR_KEYS.K8S_STATEFULSET_NAME
|
||||
] ?? '',
|
||||
value: statefulSet.meta.k8s_statefulset_name,
|
||||
},
|
||||
{
|
||||
id: 'f2',
|
||||
@@ -807,9 +763,7 @@ export const getStatefulSetMetricsQueryPayload = (
|
||||
type: 'tag',
|
||||
},
|
||||
op: '=',
|
||||
value:
|
||||
statefulSet.meta?.[INFRA_MONITORING_ATTR_KEYS.K8S_NAMESPACE_NAME] ??
|
||||
'',
|
||||
value: statefulSet.meta.k8s_namespace_name,
|
||||
},
|
||||
],
|
||||
op: 'AND',
|
||||
@@ -881,10 +835,7 @@ export const getStatefulSetMetricsQueryPayload = (
|
||||
type: 'tag',
|
||||
},
|
||||
op: '=',
|
||||
value:
|
||||
statefulSet.meta?.[
|
||||
INFRA_MONITORING_ATTR_KEYS.K8S_STATEFULSET_NAME
|
||||
] ?? '',
|
||||
value: statefulSet.meta.k8s_statefulset_name,
|
||||
},
|
||||
{
|
||||
id: 'f2',
|
||||
@@ -895,9 +846,7 @@ export const getStatefulSetMetricsQueryPayload = (
|
||||
type: 'tag',
|
||||
},
|
||||
op: '=',
|
||||
value:
|
||||
statefulSet.meta?.[INFRA_MONITORING_ATTR_KEYS.K8S_NAMESPACE_NAME] ??
|
||||
'',
|
||||
value: statefulSet.meta.k8s_namespace_name,
|
||||
},
|
||||
],
|
||||
op: 'AND',
|
||||
|
||||
@@ -1,52 +1,39 @@
|
||||
import { Color } from '@signozhq/design-tokens';
|
||||
import { InframonitoringtypesStatefulSetRecordDTO } from 'api/generated/services/sigNoz.schemas';
|
||||
import TanStackTable, { TableColumnDef } from 'components/TanStackTableView';
|
||||
import { Tooltip } from 'antd';
|
||||
import { TableColumnDef } from 'components/TanStackTableView';
|
||||
import TanStackTable from 'components/TanStackTableView';
|
||||
import { ExpandButtonWrapper } from 'container/InfraMonitoringK8sV2/components';
|
||||
|
||||
import ColumnHeader from '../Base/ColumnHeader';
|
||||
import EntityGroupHeader from '../Base/EntityGroupHeader';
|
||||
import K8sGroupCell from '../Base/K8sGroupCell';
|
||||
import { formatBytes, getPodPhaseStatusItems } from '../commonUtils';
|
||||
import {
|
||||
CellValueTooltip,
|
||||
EntityProgressBar,
|
||||
GroupedStatusCounts,
|
||||
ValidateColumnValueWrapper,
|
||||
} from '../components';
|
||||
import {
|
||||
INFRA_MONITORING_ATTR_KEYS,
|
||||
InfraMonitoringEntity,
|
||||
} from '../constants';
|
||||
import { formatBytes } from '../commonUtils';
|
||||
import { EntityProgressBar, ValidateColumnValueWrapper } from '../components';
|
||||
import { InfraMonitoringEntity } from '../constants';
|
||||
import { K8sStatefulSetsData } from './api';
|
||||
import { ArrowUpDown } from '@signozhq/icons';
|
||||
|
||||
export function getK8sStatefulSetRowKey(
|
||||
statefulSet: InframonitoringtypesStatefulSetRecordDTO,
|
||||
statefulSet: K8sStatefulSetsData,
|
||||
): string {
|
||||
return (
|
||||
statefulSet.meta?.[INFRA_MONITORING_ATTR_KEYS.K8S_STATEFULSET_NAME] ||
|
||||
statefulSet.statefulSetName ||
|
||||
''
|
||||
statefulSet.statefulSetName || statefulSet.meta.k8s_statefulset_name || ''
|
||||
);
|
||||
}
|
||||
|
||||
export function getK8sStatefulSetItemKey(
|
||||
statefulSet: InframonitoringtypesStatefulSetRecordDTO,
|
||||
statefulSet: K8sStatefulSetsData,
|
||||
): string {
|
||||
return (
|
||||
statefulSet.meta?.[INFRA_MONITORING_ATTR_KEYS.K8S_STATEFULSET_NAME] || ''
|
||||
);
|
||||
return statefulSet.meta.k8s_statefulset_name;
|
||||
}
|
||||
|
||||
export const k8sStatefulSetsColumnsConfig: TableColumnDef<InframonitoringtypesStatefulSetRecordDTO>[] =
|
||||
export const k8sStatefulSetsColumnsConfig: TableColumnDef<K8sStatefulSetsData>[] =
|
||||
[
|
||||
{
|
||||
id: 'statefulSetGroup',
|
||||
header: (): React.ReactNode => (
|
||||
<EntityGroupHeader title="StatefulSet Group" />
|
||||
<EntityGroupHeader title="STATEFULSET GROUP" />
|
||||
),
|
||||
accessorFn: (row): string =>
|
||||
row.meta?.[INFRA_MONITORING_ATTR_KEYS.K8S_STATEFULSET_NAME] || '',
|
||||
width: { min: 290 },
|
||||
accessorFn: (row): string => row.meta.k8s_statefulset_name || '',
|
||||
width: { min: 210 },
|
||||
enableSort: false,
|
||||
enableRemove: false,
|
||||
enableMove: false,
|
||||
@@ -69,12 +56,10 @@ export const k8sStatefulSetsColumnsConfig: TableColumnDef<InframonitoringtypesSt
|
||||
<EntityGroupHeader
|
||||
title="StatefulSet Name"
|
||||
icon={<ArrowUpDown data-hide-expanded="true" size={14} />}
|
||||
docPath="/infrastructure-monitoring/kubernetes/statefulsets#statefulset-name"
|
||||
/>
|
||||
),
|
||||
accessorFn: (row): string =>
|
||||
row.meta?.[INFRA_MONITORING_ATTR_KEYS.K8S_STATEFULSET_NAME] || '',
|
||||
width: { min: 290 },
|
||||
accessorFn: (row): string => row.meta.k8s_statefulset_name || '',
|
||||
width: { min: 200 },
|
||||
enableSort: false,
|
||||
enableRemove: false,
|
||||
enableMove: false,
|
||||
@@ -83,98 +68,75 @@ export const k8sStatefulSetsColumnsConfig: TableColumnDef<InframonitoringtypesSt
|
||||
cell: ({ value }): React.ReactNode => {
|
||||
const statefulsetName = value as string;
|
||||
return (
|
||||
<CellValueTooltip value={statefulsetName}>
|
||||
<Tooltip title={statefulsetName}>
|
||||
<TanStackTable.Text>{statefulsetName}</TanStackTable.Text>
|
||||
</CellValueTooltip>
|
||||
</Tooltip>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'namespaceName',
|
||||
header: (): React.ReactNode => (
|
||||
<ColumnHeader docPath="/infrastructure-monitoring/kubernetes/statefulsets#namespace-name">
|
||||
Namespace
|
||||
</ColumnHeader>
|
||||
),
|
||||
accessorFn: (row): string =>
|
||||
row.meta?.[INFRA_MONITORING_ATTR_KEYS.K8S_NAMESPACE_NAME] || '',
|
||||
width: { min: 180 },
|
||||
header: 'Namespace Name',
|
||||
accessorFn: (row): string => row.meta.k8s_namespace_name || '',
|
||||
width: { default: 150 },
|
||||
enableSort: false,
|
||||
enableResize: true,
|
||||
cell: ({ value }): React.ReactNode => {
|
||||
const namespaceName = value as string;
|
||||
return (
|
||||
<CellValueTooltip value={namespaceName}>
|
||||
<Tooltip title={namespaceName}>
|
||||
<TanStackTable.Text>{namespaceName}</TanStackTable.Text>
|
||||
</CellValueTooltip>
|
||||
</Tooltip>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'pod_counts_by_phase',
|
||||
header: (): React.ReactNode => (
|
||||
<ColumnHeader docPath="/infrastructure-monitoring/kubernetes/statefulsets#pod-counts-by-phase">
|
||||
Pod Phases
|
||||
</ColumnHeader>
|
||||
),
|
||||
accessorFn: (
|
||||
row,
|
||||
): InframonitoringtypesStatefulSetRecordDTO['podCountsByPhase'] =>
|
||||
row.podCountsByPhase,
|
||||
width: { min: 250 },
|
||||
enableSort: false,
|
||||
id: 'available_pods',
|
||||
header: 'Available',
|
||||
accessorFn: (row): number => row.availablePods,
|
||||
width: { min: 100, default: 140 },
|
||||
enableSort: true,
|
||||
enableResize: true,
|
||||
cell: ({ row }): React.ReactNode => {
|
||||
const podCountsByPhase = row.podCountsByPhase;
|
||||
if (!podCountsByPhase) {
|
||||
return <TanStackTable.Text>-</TanStackTable.Text>;
|
||||
}
|
||||
cell: ({ value }): React.ReactNode => {
|
||||
const availablePods = value as number;
|
||||
return (
|
||||
<GroupedStatusCounts items={getPodPhaseStatusItems(podCountsByPhase)} />
|
||||
<ValidateColumnValueWrapper
|
||||
value={availablePods}
|
||||
entity={InfraMonitoringEntity.STATEFULSETS}
|
||||
attribute="available pod"
|
||||
>
|
||||
<TanStackTable.Text>{availablePods}</TanStackTable.Text>
|
||||
</ValidateColumnValueWrapper>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'pod_status',
|
||||
header: (): React.ReactNode => (
|
||||
<ColumnHeader docPath="/infrastructure-monitoring/kubernetes/statefulsets#pod-status">
|
||||
Pod Status
|
||||
</ColumnHeader>
|
||||
),
|
||||
accessorFn: (row): number => row.currentPods,
|
||||
width: { min: 140 },
|
||||
enableSort: false,
|
||||
id: 'desired_pods',
|
||||
header: 'Desired',
|
||||
accessorFn: (row): number => row.desiredPods,
|
||||
width: { min: 100, default: 140 },
|
||||
enableSort: true,
|
||||
enableResize: true,
|
||||
cell: ({ row }): React.ReactNode => (
|
||||
<GroupedStatusCounts
|
||||
items={[
|
||||
{
|
||||
value: row.currentPods,
|
||||
label: 'Current',
|
||||
color: Color.BG_FOREST_500,
|
||||
},
|
||||
{
|
||||
value: row.desiredPods,
|
||||
label: 'Desired',
|
||||
color: Color.BG_ROBIN_500,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
),
|
||||
cell: ({ value }): React.ReactNode => {
|
||||
const desiredPods = value as number;
|
||||
return (
|
||||
<ValidateColumnValueWrapper
|
||||
value={desiredPods}
|
||||
entity={InfraMonitoringEntity.STATEFULSETS}
|
||||
attribute="desired pod"
|
||||
>
|
||||
<TanStackTable.Text>{desiredPods}</TanStackTable.Text>
|
||||
</ValidateColumnValueWrapper>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'cpu_request',
|
||||
header: (): React.ReactNode => (
|
||||
<ColumnHeader docPath="/infrastructure-monitoring/kubernetes/statefulsets#cpu-req-usage-">
|
||||
CPU Request
|
||||
<br /> Usage (%)
|
||||
</ColumnHeader>
|
||||
),
|
||||
accessorFn: (row): number => row.statefulSetCPURequest,
|
||||
header: 'CPU Req Usage (%)',
|
||||
accessorFn: (row): number => row.cpuRequest,
|
||||
width: { min: 200, default: 200 },
|
||||
enableSort: true,
|
||||
enableResize: true,
|
||||
defaultVisibility: false,
|
||||
cell: ({ value }): React.ReactNode => {
|
||||
const cpuRequest = value as number;
|
||||
return (
|
||||
@@ -190,13 +152,8 @@ export const k8sStatefulSetsColumnsConfig: TableColumnDef<InframonitoringtypesSt
|
||||
},
|
||||
{
|
||||
id: 'cpu_limit',
|
||||
header: (): React.ReactNode => (
|
||||
<ColumnHeader docPath="/infrastructure-monitoring/kubernetes/statefulsets#cpu-limit-usage-">
|
||||
CPU Limit
|
||||
<br /> Usage (%)
|
||||
</ColumnHeader>
|
||||
),
|
||||
accessorFn: (row): number => row.statefulSetCPULimit,
|
||||
header: 'CPU Limit Usage (%)',
|
||||
accessorFn: (row): number => row.cpuLimit,
|
||||
width: { min: 200, default: 200 },
|
||||
enableSort: true,
|
||||
enableResize: true,
|
||||
@@ -215,43 +172,32 @@ export const k8sStatefulSetsColumnsConfig: TableColumnDef<InframonitoringtypesSt
|
||||
},
|
||||
{
|
||||
id: 'cpu',
|
||||
header: (): React.ReactNode => (
|
||||
<ColumnHeader docPath="/infrastructure-monitoring/kubernetes/statefulsets#cpu-usage-cores">
|
||||
CPU Usage
|
||||
<br /> (cores)
|
||||
</ColumnHeader>
|
||||
),
|
||||
accessorFn: (row): number => row.statefulSetCPU,
|
||||
width: { min: 160 },
|
||||
header: 'CPU Usage (cores)',
|
||||
accessorFn: (row): number => row.cpuUsage,
|
||||
width: { min: 190 },
|
||||
enableSort: true,
|
||||
enableResize: true,
|
||||
defaultVisibility: false,
|
||||
cell: ({ value }): React.ReactNode => {
|
||||
const cpu = Number(value);
|
||||
const cpu = value as number;
|
||||
return (
|
||||
<ValidateColumnValueWrapper
|
||||
value={cpu}
|
||||
entity={InfraMonitoringEntity.STATEFULSETS}
|
||||
attribute="CPU metric"
|
||||
>
|
||||
<TanStackTable.Text>{cpu.toFixed(2)}</TanStackTable.Text>
|
||||
<TanStackTable.Text>{cpu}</TanStackTable.Text>
|
||||
</ValidateColumnValueWrapper>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'memory_request',
|
||||
header: (): React.ReactNode => (
|
||||
<ColumnHeader docPath="/infrastructure-monitoring/kubernetes/statefulsets#mem-req-usage-">
|
||||
Memory Request
|
||||
<br /> Usage (%)
|
||||
</ColumnHeader>
|
||||
),
|
||||
accessorFn: (row): number => row.statefulSetMemoryRequest,
|
||||
header: 'Mem Req Usage (%)',
|
||||
accessorFn: (row): number => row.memoryRequest,
|
||||
width: { min: 190 },
|
||||
enableSort: true,
|
||||
enableResize: true,
|
||||
defaultVisibility: false,
|
||||
cell: ({ value }): React.ReactNode => {
|
||||
const memoryRequest = value as number;
|
||||
return (
|
||||
@@ -267,13 +213,8 @@ export const k8sStatefulSetsColumnsConfig: TableColumnDef<InframonitoringtypesSt
|
||||
},
|
||||
{
|
||||
id: 'memory_limit',
|
||||
header: (): React.ReactNode => (
|
||||
<ColumnHeader docPath="/infrastructure-monitoring/kubernetes/statefulsets#mem-limit-usage-">
|
||||
Memory Limit
|
||||
<br /> Usage (%)
|
||||
</ColumnHeader>
|
||||
),
|
||||
accessorFn: (row): number => row.statefulSetMemoryLimit,
|
||||
header: 'Mem Limit Usage (%)',
|
||||
accessorFn: (row): number => row.memoryLimit,
|
||||
width: { min: 180 },
|
||||
enableSort: true,
|
||||
enableResize: true,
|
||||
@@ -292,13 +233,8 @@ export const k8sStatefulSetsColumnsConfig: TableColumnDef<InframonitoringtypesSt
|
||||
},
|
||||
{
|
||||
id: 'memory',
|
||||
header: (): React.ReactNode => (
|
||||
<ColumnHeader docPath="/infrastructure-monitoring/kubernetes/statefulsets#mem-usage-wss">
|
||||
Memory Usage
|
||||
<br /> (WSS)
|
||||
</ColumnHeader>
|
||||
),
|
||||
accessorFn: (row): number => row.statefulSetMemory,
|
||||
header: 'Mem Usage (WSS)',
|
||||
accessorFn: (row): number => row.memoryUsage,
|
||||
width: { min: 160 },
|
||||
enableSort: true,
|
||||
enableResize: true,
|
||||
@@ -316,52 +252,4 @@ export const k8sStatefulSetsColumnsConfig: TableColumnDef<InframonitoringtypesSt
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'current_pods',
|
||||
header: (): React.ReactNode => (
|
||||
<ColumnHeader docPath="/infrastructure-monitoring/kubernetes/statefulsets#current">
|
||||
Current Pods
|
||||
</ColumnHeader>
|
||||
),
|
||||
accessorFn: (row): number => row.currentPods,
|
||||
width: { min: 120 },
|
||||
enableSort: true,
|
||||
defaultVisibility: false,
|
||||
cell: ({ value }): React.ReactNode => {
|
||||
const currentPods = value as number;
|
||||
return (
|
||||
<ValidateColumnValueWrapper
|
||||
value={currentPods}
|
||||
entity={InfraMonitoringEntity.STATEFULSETS}
|
||||
attribute="current pod"
|
||||
>
|
||||
<TanStackTable.Text>{currentPods}</TanStackTable.Text>
|
||||
</ValidateColumnValueWrapper>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'desired_pods',
|
||||
header: (): React.ReactNode => (
|
||||
<ColumnHeader docPath="/infrastructure-monitoring/kubernetes/statefulsets#desired">
|
||||
Desired Pods
|
||||
</ColumnHeader>
|
||||
),
|
||||
accessorFn: (row): number => row.desiredPods,
|
||||
width: { min: 120 },
|
||||
enableSort: true,
|
||||
defaultVisibility: false,
|
||||
cell: ({ value }): React.ReactNode => {
|
||||
const desiredPods = value as number;
|
||||
return (
|
||||
<ValidateColumnValueWrapper
|
||||
value={desiredPods}
|
||||
entity={InfraMonitoringEntity.STATEFULSETS}
|
||||
attribute="desired pod"
|
||||
>
|
||||
<TanStackTable.Text>{desiredPods}</TanStackTable.Text>
|
||||
</ValidateColumnValueWrapper>
|
||||
);
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
@@ -1,23 +1,20 @@
|
||||
import { useCallback } from 'react';
|
||||
import { listVolumes } from 'api/generated/services/inframonitoring';
|
||||
import {
|
||||
InframonitoringtypesResponseTypeDTO,
|
||||
InframonitoringtypesVolumeRecordDTO,
|
||||
Querybuildertypesv5OrderDirectionDTO,
|
||||
} from 'api/generated/services/sigNoz.schemas';
|
||||
import React, { useCallback } from 'react';
|
||||
import { InfraMonitoringEvents } from 'constants/events';
|
||||
import { FeatureKeys } from 'constants/features';
|
||||
import { useAppContext } from 'providers/App/App';
|
||||
|
||||
import K8sBaseDetails, { K8sDetailsFilters } from '../Base/K8sBaseDetails';
|
||||
import { K8sBaseList } from '../Base/K8sBaseList';
|
||||
import { K8sBaseFilters } from '../Base/types';
|
||||
import { InfraMonitoringEntity } from '../constants';
|
||||
import { getK8sVolumesList, K8sVolumesData } from './api';
|
||||
import {
|
||||
getVolumeMetricsQueryPayload,
|
||||
k8sVolumeDetailsMetadataConfig,
|
||||
k8sVolumeGetEntityName,
|
||||
k8sVolumeGetSelectedItemExpression,
|
||||
k8sVolumeInitialEventsExpression,
|
||||
k8sVolumeInitialLogTracesExpression,
|
||||
k8sVolumeGetSelectedItemFilters,
|
||||
k8sVolumeInitialEventsFilter,
|
||||
k8sVolumeInitialLogTracesFilter,
|
||||
volumeWidgetInfo,
|
||||
} from './constants';
|
||||
import {
|
||||
@@ -31,93 +28,66 @@ function K8sVolumesList({
|
||||
}: {
|
||||
controlListPrefix?: React.ReactNode;
|
||||
}): JSX.Element {
|
||||
const { featureFlags } = useAppContext();
|
||||
const dotMetricsEnabled =
|
||||
featureFlags?.find((flag) => flag.name === FeatureKeys.DOT_METRICS_ENABLED)
|
||||
?.active || false;
|
||||
|
||||
const fetchListData = useCallback(
|
||||
async (filters: K8sBaseFilters, signal?: AbortSignal) => {
|
||||
try {
|
||||
const response = await listVolumes(
|
||||
{
|
||||
filter: { expression: filters.filter.expression },
|
||||
groupBy: filters.groupBy?.map((g) => ({ name: g.name })),
|
||||
offset: filters.offset,
|
||||
limit: filters.limit ?? 10,
|
||||
start: filters.start,
|
||||
end: filters.end,
|
||||
orderBy: filters.orderBy
|
||||
? {
|
||||
key: { name: filters.orderBy.key.name },
|
||||
direction:
|
||||
filters.orderBy.direction === 'asc'
|
||||
? Querybuildertypesv5OrderDirectionDTO.asc
|
||||
: Querybuildertypesv5OrderDirectionDTO.desc,
|
||||
}
|
||||
: undefined,
|
||||
},
|
||||
signal,
|
||||
);
|
||||
filters.orderBy ||= {
|
||||
columnName: 'usage',
|
||||
order: 'desc',
|
||||
};
|
||||
|
||||
const data = response.data;
|
||||
return {
|
||||
type:
|
||||
data.type === InframonitoringtypesResponseTypeDTO.grouped_list
|
||||
? ('grouped_list' as const)
|
||||
: ('list' as const),
|
||||
records: data.records,
|
||||
total: data.total,
|
||||
endTimeBeforeRetention: data.endTimeBeforeRetention,
|
||||
warning: data.warning,
|
||||
};
|
||||
} catch (error) {
|
||||
const errMsg =
|
||||
error instanceof Error ? error.message : 'Failed to fetch volumes';
|
||||
return {
|
||||
type: 'list' as const,
|
||||
records: [] as InframonitoringtypesVolumeRecordDTO[],
|
||||
total: 0,
|
||||
error: errMsg,
|
||||
};
|
||||
}
|
||||
const response = await getK8sVolumesList(
|
||||
filters,
|
||||
signal,
|
||||
undefined,
|
||||
dotMetricsEnabled,
|
||||
);
|
||||
|
||||
return {
|
||||
data: response.payload?.data.records || [],
|
||||
total: response.payload?.data.total || 0,
|
||||
error: response.error,
|
||||
rawData: response.payload?.data,
|
||||
};
|
||||
},
|
||||
[],
|
||||
[dotMetricsEnabled],
|
||||
);
|
||||
|
||||
const fetchEntityData = useCallback(
|
||||
async (
|
||||
filters: K8sDetailsFilters,
|
||||
signal?: AbortSignal,
|
||||
): Promise<{
|
||||
data: InframonitoringtypesVolumeRecordDTO | null;
|
||||
error?: string | null;
|
||||
}> => {
|
||||
try {
|
||||
const response = await listVolumes(
|
||||
{
|
||||
filter: { expression: filters.filter.expression },
|
||||
start: filters.start,
|
||||
end: filters.end,
|
||||
limit: 1,
|
||||
offset: 0,
|
||||
},
|
||||
signal,
|
||||
);
|
||||
): Promise<{ data: K8sVolumesData | null; error?: string | null }> => {
|
||||
const response = await getK8sVolumesList(
|
||||
{
|
||||
filters: filters.filters,
|
||||
start: filters.start,
|
||||
end: filters.end,
|
||||
limit: 1,
|
||||
offset: 0,
|
||||
},
|
||||
signal,
|
||||
undefined,
|
||||
dotMetricsEnabled,
|
||||
);
|
||||
|
||||
return {
|
||||
data: response.data.records.length > 0 ? response.data.records[0] : null,
|
||||
};
|
||||
} catch (error) {
|
||||
const errMsg =
|
||||
error instanceof Error ? error.message : 'Failed to fetch volume';
|
||||
return {
|
||||
data: null,
|
||||
error: errMsg,
|
||||
};
|
||||
}
|
||||
const records = response.payload?.data.records || [];
|
||||
|
||||
return {
|
||||
data: records.length > 0 ? records[0] : null,
|
||||
error: response.error,
|
||||
};
|
||||
},
|
||||
[],
|
||||
[dotMetricsEnabled],
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<K8sBaseList<InframonitoringtypesVolumeRecordDTO>
|
||||
<K8sBaseList<K8sVolumesData>
|
||||
controlListPrefix={controlListPrefix}
|
||||
entity={InfraMonitoringEntity.VOLUMES}
|
||||
tableColumns={k8sVolumesColumnsConfig}
|
||||
@@ -127,14 +97,14 @@ function K8sVolumesList({
|
||||
eventCategory={InfraMonitoringEvents.Volumes}
|
||||
/>
|
||||
|
||||
<K8sBaseDetails<InframonitoringtypesVolumeRecordDTO>
|
||||
<K8sBaseDetails<K8sVolumesData>
|
||||
category={InfraMonitoringEntity.VOLUMES}
|
||||
eventCategory={InfraMonitoringEvents.Volume}
|
||||
getSelectedItemExpression={k8sVolumeGetSelectedItemExpression}
|
||||
getSelectedItemFilters={k8sVolumeGetSelectedItemFilters}
|
||||
fetchEntityData={fetchEntityData}
|
||||
getEntityName={k8sVolumeGetEntityName}
|
||||
getInitialLogTracesExpression={k8sVolumeInitialLogTracesExpression}
|
||||
getInitialEventsExpression={k8sVolumeInitialEventsExpression}
|
||||
getInitialLogTracesFilters={k8sVolumeInitialLogTracesFilter}
|
||||
getInitialEventsFilters={k8sVolumeInitialEventsFilter}
|
||||
metadataConfig={k8sVolumeDetailsMetadataConfig}
|
||||
entityWidgetInfo={volumeWidgetInfo}
|
||||
getEntityQueryPayload={getVolumeMetricsQueryPayload}
|
||||
|
||||
136
frontend/src/container/InfraMonitoringK8sV2/Volumes/api.ts
Normal file
136
frontend/src/container/InfraMonitoringK8sV2/Volumes/api.ts
Normal file
@@ -0,0 +1,136 @@
|
||||
import axios from 'api';
|
||||
import { ErrorResponseHandler } from 'api/ErrorResponseHandler';
|
||||
import { UnderscoreToDotMap } from 'api/utils';
|
||||
import { AxiosError } from 'axios';
|
||||
import { ErrorResponse, SuccessResponse } from 'types/api';
|
||||
|
||||
import { K8sBaseFilters } from '../Base/types';
|
||||
|
||||
export interface K8sVolumesData {
|
||||
persistentVolumeClaimName: string;
|
||||
volumeAvailable: number;
|
||||
volumeCapacity: number;
|
||||
volumeInodes: number;
|
||||
volumeInodesFree: number;
|
||||
volumeInodesUsed: number;
|
||||
volumeUsage: number;
|
||||
meta: {
|
||||
k8s_cluster_name: string;
|
||||
k8s_namespace_name: string;
|
||||
k8s_node_name: string;
|
||||
k8s_persistentvolumeclaim_name: string;
|
||||
k8s_pod_name: string;
|
||||
k8s_pod_uid: string;
|
||||
k8s_statefulset_name: string;
|
||||
};
|
||||
}
|
||||
|
||||
export interface K8sVolumesListResponse {
|
||||
status: string;
|
||||
data: {
|
||||
type: string;
|
||||
records: K8sVolumesData[];
|
||||
groups: null;
|
||||
total: number;
|
||||
sentAnyHostMetricsData: boolean;
|
||||
isSendingK8SAgentMetrics: boolean;
|
||||
};
|
||||
}
|
||||
|
||||
// TODO(H4ad): Erase this whole file when migrating to openapi
|
||||
export const volumesMetaMap: Array<{
|
||||
dot: keyof Record<string, unknown>;
|
||||
under: keyof K8sVolumesData['meta'];
|
||||
}> = [
|
||||
{ dot: 'k8s.cluster.name', under: 'k8s_cluster_name' },
|
||||
{ dot: 'k8s.namespace.name', under: 'k8s_namespace_name' },
|
||||
{ dot: 'k8s.node.name', under: 'k8s_node_name' },
|
||||
{
|
||||
dot: 'k8s.persistentvolumeclaim.name',
|
||||
under: 'k8s_persistentvolumeclaim_name',
|
||||
},
|
||||
{ dot: 'k8s.pod.name', under: 'k8s_pod_name' },
|
||||
{ dot: 'k8s.pod.uid', under: 'k8s_pod_uid' },
|
||||
{ dot: 'k8s.statefulset.name', under: 'k8s_statefulset_name' },
|
||||
];
|
||||
|
||||
export function mapVolumesMeta(
|
||||
rawMeta: Record<string, unknown>,
|
||||
): K8sVolumesData['meta'] {
|
||||
const out: Record<string, unknown> = { ...rawMeta };
|
||||
|
||||
volumesMetaMap.forEach(({ dot, under }) => {
|
||||
if (dot in rawMeta) {
|
||||
const val = rawMeta[dot];
|
||||
out[under] = typeof val === 'string' ? val : rawMeta[under];
|
||||
}
|
||||
});
|
||||
|
||||
return out as K8sVolumesData['meta'];
|
||||
}
|
||||
|
||||
export const getK8sVolumesList = async (
|
||||
props: K8sBaseFilters,
|
||||
signal?: AbortSignal,
|
||||
headers?: Record<string, string>,
|
||||
dotMetricsEnabled = false,
|
||||
): Promise<SuccessResponse<K8sVolumesListResponse> | ErrorResponse> => {
|
||||
try {
|
||||
const { orderBy, ...rest } = props;
|
||||
const basePayload = {
|
||||
...rest,
|
||||
filters: props.filters ?? { items: [], op: 'and' },
|
||||
...(orderBy != null ? { orderBy } : {}),
|
||||
};
|
||||
|
||||
const requestProps = dotMetricsEnabled
|
||||
? {
|
||||
...basePayload,
|
||||
filters: {
|
||||
...basePayload.filters,
|
||||
items: basePayload.filters.items.reduce<typeof basePayload.filters.items>(
|
||||
(acc, item) => {
|
||||
if (item.value === undefined) {
|
||||
return acc;
|
||||
}
|
||||
if (
|
||||
item.key &&
|
||||
typeof item.key === 'object' &&
|
||||
'key' in item.key &&
|
||||
typeof item.key.key === 'string'
|
||||
) {
|
||||
const mappedKey = UnderscoreToDotMap[item.key.key] ?? item.key.key;
|
||||
acc.push({ ...item, key: { ...item.key, key: mappedKey } });
|
||||
} else {
|
||||
acc.push(item);
|
||||
}
|
||||
return acc;
|
||||
},
|
||||
[] as typeof basePayload.filters.items,
|
||||
),
|
||||
},
|
||||
}
|
||||
: basePayload;
|
||||
|
||||
const response = await axios.post('/pvcs/list', requestProps, {
|
||||
signal,
|
||||
headers,
|
||||
});
|
||||
const payload: K8sVolumesListResponse = response.data;
|
||||
|
||||
payload.data.records = payload.data.records.map((record) => ({
|
||||
...record,
|
||||
meta: mapVolumesMeta(record.meta as Record<string, unknown>),
|
||||
}));
|
||||
|
||||
return {
|
||||
statusCode: 200,
|
||||
error: null,
|
||||
message: 'Success',
|
||||
payload,
|
||||
params: requestProps,
|
||||
};
|
||||
} catch (error) {
|
||||
return ErrorResponseHandler(error as AxiosError);
|
||||
}
|
||||
};
|
||||
@@ -1,61 +1,70 @@
|
||||
import { InframonitoringtypesVolumeRecordDTO } from 'api/generated/services/sigNoz.schemas';
|
||||
import { PANEL_TYPES } from 'constants/queryBuilder';
|
||||
import { GetQueryResultsProps } from 'lib/dashboard/getQueryResults';
|
||||
import { DataTypes } from 'types/api/queryBuilder/queryAutocompleteResponse';
|
||||
import { TagFilter } from 'types/api/queryBuilder/queryBuilderData';
|
||||
import { EQueryType } from 'types/common/dashboard';
|
||||
import { DataSource, ReduceOperators } from 'types/common/queryBuilder';
|
||||
import { v4 } from 'uuid';
|
||||
|
||||
import { formatValueForExpression } from 'components/QueryBuilderV2/utils';
|
||||
import {
|
||||
createFilterItem,
|
||||
K8sDetailsMetadataConfig,
|
||||
} from '../Base/K8sBaseDetails';
|
||||
import { QUERY_KEYS } from '../EntityDetailsUtils/utils';
|
||||
import { K8sVolumesData } from './api';
|
||||
|
||||
import { K8sDetailsMetadataConfig } from '../Base/K8sBaseDetails';
|
||||
import { INFRA_MONITORING_ATTR_KEYS } from '../constants';
|
||||
|
||||
export const k8sVolumeGetSelectedItemExpression = (
|
||||
export const k8sVolumeGetSelectedItemFilters = (
|
||||
selectedItemId: string,
|
||||
): string =>
|
||||
`${INFRA_MONITORING_ATTR_KEYS.K8S_PERSISTENT_VOLUME_CLAIM_NAME} = ${formatValueForExpression(selectedItemId)}`;
|
||||
): TagFilter => ({
|
||||
op: 'AND',
|
||||
items: [
|
||||
{
|
||||
id: 'k8s_persistentvolumeclaim_name',
|
||||
key: {
|
||||
key: 'k8s_persistentvolumeclaim_name',
|
||||
type: null,
|
||||
},
|
||||
op: '=',
|
||||
value: selectedItemId,
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
export const k8sVolumeDetailsMetadataConfig: K8sDetailsMetadataConfig<InframonitoringtypesVolumeRecordDTO>[] =
|
||||
export const k8sVolumeDetailsMetadataConfig: K8sDetailsMetadataConfig<K8sVolumesData>[] =
|
||||
[
|
||||
{
|
||||
label: 'PVC Name',
|
||||
getValue: (p): string => p.persistentVolumeClaimName || '',
|
||||
getValue: (p): string => p.persistentVolumeClaimName,
|
||||
},
|
||||
{
|
||||
label: 'Cluster Name',
|
||||
getValue: (p): string =>
|
||||
p.meta?.[INFRA_MONITORING_ATTR_KEYS.K8S_CLUSTER_NAME] || '',
|
||||
getValue: (p): string => p.meta.k8s_cluster_name,
|
||||
},
|
||||
{
|
||||
label: 'Namespace Name',
|
||||
getValue: (p): string =>
|
||||
p.meta?.[INFRA_MONITORING_ATTR_KEYS.K8S_NAMESPACE_NAME] || '',
|
||||
getValue: (p): string => p.meta.k8s_namespace_name,
|
||||
},
|
||||
];
|
||||
|
||||
export const k8sVolumeInitialEventsExpression = (
|
||||
item: InframonitoringtypesVolumeRecordDTO,
|
||||
): string => {
|
||||
const objectName = formatValueForExpression(
|
||||
item.persistentVolumeClaimName || '',
|
||||
);
|
||||
return `${INFRA_MONITORING_ATTR_KEYS.K8S_OBJECT_KIND} = 'PersistentVolumeClaim' AND ${INFRA_MONITORING_ATTR_KEYS.K8S_OBJECT_NAME} = ${objectName}`;
|
||||
};
|
||||
export const k8sVolumeInitialEventsFilter = (
|
||||
item: K8sVolumesData,
|
||||
): ReturnType<typeof createFilterItem>[] => [
|
||||
createFilterItem(QUERY_KEYS.K8S_OBJECT_KIND, 'PersistentVolumeClaim'),
|
||||
createFilterItem(QUERY_KEYS.K8S_OBJECT_NAME, item.persistentVolumeClaimName),
|
||||
];
|
||||
|
||||
export const k8sVolumeInitialLogTracesExpression = (
|
||||
item: InframonitoringtypesVolumeRecordDTO,
|
||||
): string => {
|
||||
const pvcName = formatValueForExpression(item.persistentVolumeClaimName || '');
|
||||
const namespaceName = formatValueForExpression(
|
||||
item.meta?.[INFRA_MONITORING_ATTR_KEYS.K8S_NAMESPACE_NAME] || '',
|
||||
);
|
||||
return `${INFRA_MONITORING_ATTR_KEYS.K8S_PERSISTENT_VOLUME_CLAIM_NAME} = ${pvcName} AND ${INFRA_MONITORING_ATTR_KEYS.K8S_NAMESPACE_NAME} = ${namespaceName}`;
|
||||
};
|
||||
export const k8sVolumeInitialLogTracesFilter = (
|
||||
item: K8sVolumesData,
|
||||
): ReturnType<typeof createFilterItem>[] => [
|
||||
createFilterItem(
|
||||
QUERY_KEYS.K8S_PERSISTENT_VOLUME_CLAIM_NAME,
|
||||
item.meta.k8s_persistentvolumeclaim_name,
|
||||
),
|
||||
createFilterItem(QUERY_KEYS.K8S_NAMESPACE_NAME, item.meta.k8s_namespace_name),
|
||||
];
|
||||
|
||||
export const k8sVolumeGetEntityName = (
|
||||
item: InframonitoringtypesVolumeRecordDTO,
|
||||
): string => item.persistentVolumeClaimName || '';
|
||||
export const k8sVolumeGetEntityName = (item: K8sVolumesData): string =>
|
||||
item.persistentVolumeClaimName;
|
||||
|
||||
export const volumeWidgetInfo = [
|
||||
{
|
||||
@@ -81,37 +90,37 @@ export const volumeWidgetInfo = [
|
||||
];
|
||||
|
||||
export const getVolumeMetricsQueryPayload = (
|
||||
volume: InframonitoringtypesVolumeRecordDTO,
|
||||
volume: K8sVolumesData,
|
||||
start: number,
|
||||
end: number,
|
||||
dotMetricsEnabled: boolean,
|
||||
): GetQueryResultsProps[] => {
|
||||
const k8sClusterNameKey = dotMetricsEnabled
|
||||
? INFRA_MONITORING_ATTR_KEYS.K8S_CLUSTER_NAME
|
||||
? 'k8s.cluster.name'
|
||||
: 'k8s_cluster_name';
|
||||
const k8sNamespaceNameKey = dotMetricsEnabled
|
||||
? INFRA_MONITORING_ATTR_KEYS.K8S_NAMESPACE_NAME
|
||||
? 'k8s.namespace.name'
|
||||
: 'k8s_namespace_name';
|
||||
const k8sVolumeAvailableKey = dotMetricsEnabled
|
||||
? INFRA_MONITORING_ATTR_KEYS.K8S_VOLUME_AVAILABLE
|
||||
? 'k8s.volume.available'
|
||||
: 'k8s_volume_available';
|
||||
const k8sVolumeCapacityKey = dotMetricsEnabled
|
||||
? INFRA_MONITORING_ATTR_KEYS.K8S_VOLUME_CAPACITY
|
||||
? 'k8s.volume.capacity'
|
||||
: 'k8s_volume_capacity';
|
||||
const k8sVolumeInodesUsedKey = dotMetricsEnabled
|
||||
? INFRA_MONITORING_ATTR_KEYS.K8S_VOLUME_INODES_USED
|
||||
? 'k8s.volume.inodes.used'
|
||||
: 'k8s_volume_inodes_used';
|
||||
const k8sVolumeInodesKey = dotMetricsEnabled
|
||||
? INFRA_MONITORING_ATTR_KEYS.K8S_VOLUME_INODES
|
||||
? 'k8s.volume.inodes'
|
||||
: 'k8s_volume_inodes';
|
||||
const k8sVolumeInodesFreeKey = dotMetricsEnabled
|
||||
? INFRA_MONITORING_ATTR_KEYS.K8S_VOLUME_INODES_FREE
|
||||
? 'k8s.volume.inodes.free'
|
||||
: 'k8s_volume_inodes_free';
|
||||
const k8sVolumeTypeKey = dotMetricsEnabled
|
||||
? INFRA_MONITORING_ATTR_KEYS.K8S_VOLUME_TYPE
|
||||
? 'k8s.volume.type'
|
||||
: 'k8s_volume_type';
|
||||
const k8sPVCNameKey = dotMetricsEnabled
|
||||
? INFRA_MONITORING_ATTR_KEYS.K8S_PERSISTENT_VOLUME_CLAIM_NAME
|
||||
? 'k8s.persistentvolumeclaim.name'
|
||||
: 'k8s_persistentvolumeclaim_name';
|
||||
|
||||
return [
|
||||
@@ -143,8 +152,7 @@ export const getVolumeMetricsQueryPayload = (
|
||||
type: 'tag',
|
||||
},
|
||||
op: '=',
|
||||
value:
|
||||
volume.meta?.[INFRA_MONITORING_ATTR_KEYS.K8S_CLUSTER_NAME] || '',
|
||||
value: volume.meta.k8s_cluster_name,
|
||||
},
|
||||
{
|
||||
id: 'c2',
|
||||
@@ -155,9 +163,7 @@ export const getVolumeMetricsQueryPayload = (
|
||||
type: 'tag',
|
||||
},
|
||||
op: 'in',
|
||||
value: [
|
||||
volume.meta?.[INFRA_MONITORING_ATTR_KEYS.K8S_NAMESPACE_NAME] || '',
|
||||
],
|
||||
value: [volume.meta.k8s_namespace_name],
|
||||
},
|
||||
{
|
||||
id: 'c3',
|
||||
@@ -238,8 +244,7 @@ export const getVolumeMetricsQueryPayload = (
|
||||
type: 'tag',
|
||||
},
|
||||
op: '=',
|
||||
value:
|
||||
volume.meta?.[INFRA_MONITORING_ATTR_KEYS.K8S_CLUSTER_NAME] || '',
|
||||
value: volume.meta.k8s_cluster_name,
|
||||
},
|
||||
{
|
||||
id: 'c2',
|
||||
@@ -250,9 +255,7 @@ export const getVolumeMetricsQueryPayload = (
|
||||
type: 'tag',
|
||||
},
|
||||
op: 'in',
|
||||
value: [
|
||||
volume.meta?.[INFRA_MONITORING_ATTR_KEYS.K8S_NAMESPACE_NAME] || '',
|
||||
],
|
||||
value: [volume.meta.k8s_namespace_name],
|
||||
},
|
||||
{
|
||||
id: 'c3',
|
||||
@@ -333,8 +336,7 @@ export const getVolumeMetricsQueryPayload = (
|
||||
type: 'tag',
|
||||
},
|
||||
op: '=',
|
||||
value:
|
||||
volume.meta?.[INFRA_MONITORING_ATTR_KEYS.K8S_CLUSTER_NAME] || '',
|
||||
value: volume.meta.k8s_cluster_name,
|
||||
},
|
||||
{
|
||||
id: 'c2',
|
||||
@@ -345,9 +347,7 @@ export const getVolumeMetricsQueryPayload = (
|
||||
type: 'tag',
|
||||
},
|
||||
op: 'in',
|
||||
value: [
|
||||
volume.meta?.[INFRA_MONITORING_ATTR_KEYS.K8S_NAMESPACE_NAME] || '',
|
||||
],
|
||||
value: [volume.meta.k8s_namespace_name],
|
||||
},
|
||||
{
|
||||
id: 'c3',
|
||||
@@ -428,8 +428,7 @@ export const getVolumeMetricsQueryPayload = (
|
||||
type: 'tag',
|
||||
},
|
||||
op: '=',
|
||||
value:
|
||||
volume.meta?.[INFRA_MONITORING_ATTR_KEYS.K8S_CLUSTER_NAME] || '',
|
||||
value: volume.meta.k8s_cluster_name,
|
||||
},
|
||||
{
|
||||
id: 'c2',
|
||||
@@ -440,9 +439,7 @@ export const getVolumeMetricsQueryPayload = (
|
||||
type: 'tag',
|
||||
},
|
||||
op: 'in',
|
||||
value: [
|
||||
volume.meta?.[INFRA_MONITORING_ATTR_KEYS.K8S_NAMESPACE_NAME] || '',
|
||||
],
|
||||
value: [volume.meta.k8s_namespace_name],
|
||||
},
|
||||
{
|
||||
id: 'c3',
|
||||
@@ -523,8 +520,7 @@ export const getVolumeMetricsQueryPayload = (
|
||||
type: 'tag',
|
||||
},
|
||||
op: '=',
|
||||
value:
|
||||
volume.meta?.[INFRA_MONITORING_ATTR_KEYS.K8S_CLUSTER_NAME] || '',
|
||||
value: volume.meta.k8s_cluster_name,
|
||||
},
|
||||
{
|
||||
id: 'c2',
|
||||
@@ -535,9 +531,7 @@ export const getVolumeMetricsQueryPayload = (
|
||||
type: 'tag',
|
||||
},
|
||||
op: 'in',
|
||||
value: [
|
||||
volume.meta?.[INFRA_MONITORING_ATTR_KEYS.K8S_NAMESPACE_NAME] || '',
|
||||
],
|
||||
value: [volume.meta.k8s_namespace_name],
|
||||
},
|
||||
{
|
||||
id: 'c3',
|
||||
|
||||
@@ -1,43 +1,34 @@
|
||||
import { Tooltip } from 'antd';
|
||||
import { TableColumnDef } from 'components/TanStackTableView';
|
||||
import { InframonitoringtypesVolumeRecordDTO } from 'api/generated/services/sigNoz.schemas';
|
||||
import TanStackTable from 'components/TanStackTableView';
|
||||
import { ExpandButtonWrapper } from 'container/InfraMonitoringK8sV2/components';
|
||||
|
||||
import ColumnHeader from '../Base/ColumnHeader';
|
||||
import EntityGroupHeader from '../Base/EntityGroupHeader';
|
||||
import K8sGroupCell from '../Base/K8sGroupCell';
|
||||
import { formatBytes } from '../commonUtils';
|
||||
import { CellValueTooltip, ValidateColumnValueWrapper } from '../components';
|
||||
import {
|
||||
INFRA_MONITORING_ATTR_KEYS,
|
||||
InfraMonitoringEntity,
|
||||
} from '../constants';
|
||||
import { ValidateColumnValueWrapper } from '../components';
|
||||
import { InfraMonitoringEntity } from '../constants';
|
||||
import { K8sVolumesData } from './api';
|
||||
import { HardDrive } from '@signozhq/icons';
|
||||
|
||||
export function getK8sVolumeRowKey(
|
||||
volume: InframonitoringtypesVolumeRecordDTO,
|
||||
): string {
|
||||
export function getK8sVolumeRowKey(volume: K8sVolumesData): string {
|
||||
return (
|
||||
volume.persistentVolumeClaimName ||
|
||||
volume.meta?.[INFRA_MONITORING_ATTR_KEYS.K8S_PERSISTENT_VOLUME_CLAIM_NAME] ||
|
||||
volume.meta.k8s_persistentvolumeclaim_name ||
|
||||
''
|
||||
);
|
||||
}
|
||||
|
||||
export function getK8sVolumeItemKey(
|
||||
volume: InframonitoringtypesVolumeRecordDTO,
|
||||
): string {
|
||||
export function getK8sVolumeItemKey(volume: K8sVolumesData): string {
|
||||
return volume.persistentVolumeClaimName;
|
||||
}
|
||||
|
||||
export type VolumeTableColumnConfig =
|
||||
TableColumnDef<InframonitoringtypesVolumeRecordDTO>;
|
||||
export const k8sVolumesColumnsConfig: VolumeTableColumnConfig[] = [
|
||||
export const k8sVolumesColumnsConfig: TableColumnDef<K8sVolumesData>[] = [
|
||||
{
|
||||
id: 'volumeGroup',
|
||||
header: (): React.ReactNode => <EntityGroupHeader title="Volume Group" />,
|
||||
header: (): React.ReactNode => <EntityGroupHeader title="VOLUME GROUP" />,
|
||||
accessorFn: (row): string => row.persistentVolumeClaimName || '',
|
||||
width: { min: 290 },
|
||||
width: { min: 300 },
|
||||
enableSort: false,
|
||||
enableRemove: false,
|
||||
enableMove: false,
|
||||
@@ -60,7 +51,6 @@ export const k8sVolumesColumnsConfig: VolumeTableColumnConfig[] = [
|
||||
<EntityGroupHeader
|
||||
title="PVC Name"
|
||||
icon={<HardDrive data-hide-expanded="true" size={14} />}
|
||||
docPath="/infrastructure-monitoring/kubernetes/volumes#pvc-name"
|
||||
/>
|
||||
),
|
||||
accessorFn: (row): string => row.persistentVolumeClaimName || '',
|
||||
@@ -73,39 +63,30 @@ export const k8sVolumesColumnsConfig: VolumeTableColumnConfig[] = [
|
||||
cell: ({ value }): React.ReactNode => {
|
||||
const pvcName = value as string;
|
||||
return (
|
||||
<CellValueTooltip value={pvcName}>
|
||||
<Tooltip title={pvcName}>
|
||||
<TanStackTable.Text>{pvcName}</TanStackTable.Text>
|
||||
</CellValueTooltip>
|
||||
</Tooltip>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'namespaceName',
|
||||
header: (): React.ReactNode => (
|
||||
<ColumnHeader docPath="/infrastructure-monitoring/kubernetes/volumes#namespace-name">
|
||||
Namespace
|
||||
</ColumnHeader>
|
||||
),
|
||||
accessorFn: (row): string =>
|
||||
row.meta?.[INFRA_MONITORING_ATTR_KEYS.K8S_NAMESPACE_NAME] || '',
|
||||
header: 'Namespace Name',
|
||||
accessorFn: (row): string => row.meta.k8s_namespace_name || '',
|
||||
width: { min: 220 },
|
||||
enableSort: false,
|
||||
cell: ({ value }): React.ReactNode => {
|
||||
const namespaceName = value as string;
|
||||
return (
|
||||
<CellValueTooltip value={namespaceName}>
|
||||
<Tooltip title={namespaceName}>
|
||||
<TanStackTable.Text>{namespaceName}</TanStackTable.Text>
|
||||
</CellValueTooltip>
|
||||
</Tooltip>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'capacity',
|
||||
header: (): React.ReactNode => (
|
||||
<ColumnHeader docPath="/infrastructure-monitoring/kubernetes/volumes#volume-capacity">
|
||||
Capacity
|
||||
</ColumnHeader>
|
||||
),
|
||||
header: 'Capacity',
|
||||
accessorFn: (row): number => row.volumeCapacity,
|
||||
width: { min: 140 },
|
||||
enableSort: true,
|
||||
@@ -124,11 +105,7 @@ export const k8sVolumesColumnsConfig: VolumeTableColumnConfig[] = [
|
||||
},
|
||||
{
|
||||
id: 'usage',
|
||||
header: (): React.ReactNode => (
|
||||
<ColumnHeader docPath="/infrastructure-monitoring/kubernetes/volumes#volume-used">
|
||||
Used
|
||||
</ColumnHeader>
|
||||
),
|
||||
header: 'Used',
|
||||
accessorFn: (row): number => row.volumeUsage,
|
||||
width: { min: 140 },
|
||||
enableSort: true,
|
||||
@@ -147,11 +124,7 @@ export const k8sVolumesColumnsConfig: VolumeTableColumnConfig[] = [
|
||||
},
|
||||
{
|
||||
id: 'available',
|
||||
header: (): React.ReactNode => (
|
||||
<ColumnHeader docPath="/infrastructure-monitoring/kubernetes/volumes#volume-available">
|
||||
Available
|
||||
</ColumnHeader>
|
||||
),
|
||||
header: 'Available',
|
||||
accessorFn: (row): number => row.volumeAvailable,
|
||||
width: { min: 140 },
|
||||
enableSort: true,
|
||||
@@ -170,11 +143,7 @@ export const k8sVolumesColumnsConfig: VolumeTableColumnConfig[] = [
|
||||
},
|
||||
{
|
||||
id: 'inodes',
|
||||
header: (): React.ReactNode => (
|
||||
<ColumnHeader docPath="/infrastructure-monitoring/kubernetes/volumes#volume-inodes">
|
||||
Inodes
|
||||
</ColumnHeader>
|
||||
),
|
||||
header: 'Inodes',
|
||||
accessorFn: (row): number => row.volumeInodes,
|
||||
width: { min: 140 },
|
||||
enableSort: true,
|
||||
@@ -193,11 +162,7 @@ export const k8sVolumesColumnsConfig: VolumeTableColumnConfig[] = [
|
||||
},
|
||||
{
|
||||
id: 'inodesUsed',
|
||||
header: (): React.ReactNode => (
|
||||
<ColumnHeader docPath="/infrastructure-monitoring/kubernetes/volumes#volume-inodes-used">
|
||||
Inodes Used
|
||||
</ColumnHeader>
|
||||
),
|
||||
header: 'Inodes Used',
|
||||
accessorFn: (row): number => row.volumeInodesUsed,
|
||||
width: { min: 160 },
|
||||
enableSort: true,
|
||||
@@ -216,11 +181,7 @@ export const k8sVolumesColumnsConfig: VolumeTableColumnConfig[] = [
|
||||
},
|
||||
{
|
||||
id: 'inodesFree',
|
||||
header: (): React.ReactNode => (
|
||||
<ColumnHeader docPath="/infrastructure-monitoring/kubernetes/volumes#volume-inodes-free">
|
||||
Inodes Free
|
||||
</ColumnHeader>
|
||||
),
|
||||
header: 'Inodes Free',
|
||||
accessorFn: (row): number => row.volumeInodesFree,
|
||||
width: { min: 160 },
|
||||
enableSort: true,
|
||||
|
||||
@@ -1,7 +1,4 @@
|
||||
import { Color } from '@signozhq/design-tokens';
|
||||
import { InframonitoringtypesPodCountsByPhaseDTO } from 'api/generated/services/sigNoz.schemas';
|
||||
|
||||
import { StatusCountItem } from './components/GroupedStatusCounts';
|
||||
|
||||
/**
|
||||
* Converts size in bytes to a human-readable string with appropriate units
|
||||
@@ -63,18 +60,3 @@ export function getStrokeColorForLimitUtilization(value: number): string {
|
||||
// Red
|
||||
return Color.BG_SAKURA_500;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds StatusCountItem[] for GroupedStatusCounts from pod phase counts.
|
||||
*/
|
||||
export function getPodPhaseStatusItems(
|
||||
counts: InframonitoringtypesPodCountsByPhaseDTO,
|
||||
): StatusCountItem[] {
|
||||
return [
|
||||
{ value: counts.running, label: 'Running', color: Color.BG_FOREST_500 },
|
||||
{ value: counts.pending, label: 'Pending', color: Color.BG_AMBER_500 },
|
||||
{ value: counts.succeeded, label: 'Succeeded', color: Color.BG_ROBIN_500 },
|
||||
{ value: counts.failed, label: 'Failed', color: Color.BG_CHERRY_500 },
|
||||
{ value: counts.unknown, label: 'Unknown', color: Color.BG_SLATE_400 },
|
||||
];
|
||||
}
|
||||
|
||||
@@ -1,54 +0,0 @@
|
||||
.tooltipContent {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
max-width: 280px;
|
||||
}
|
||||
|
||||
.actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--spacing-1);
|
||||
padding: var(--spacing-2);
|
||||
justify-content: flex-end;
|
||||
border-bottom-right-radius: 4px;
|
||||
border-bottom-left-radius: 4px;
|
||||
}
|
||||
|
||||
.actionButton {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 26px;
|
||||
height: 26px;
|
||||
padding: 0;
|
||||
border: none;
|
||||
border-radius: var(--radius-1);
|
||||
background: transparent;
|
||||
color: var(--l2-foreground);
|
||||
cursor: pointer;
|
||||
transition:
|
||||
background 0.15s ease,
|
||||
color 0.15s ease;
|
||||
|
||||
&:hover:not(:disabled) {
|
||||
background: var(--l2-background-hover);
|
||||
color: var(--l1-foreground);
|
||||
}
|
||||
|
||||
&:disabled {
|
||||
opacity: 0.4;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
}
|
||||
|
||||
.tooltipContentWrapper {
|
||||
--tooltip-background: var(--l3-background);
|
||||
--tooltip-border-radius: 4px;
|
||||
--tooltip-border-width: 1px;
|
||||
--tooltip-border-color: rgba(255, 255, 255, 0.14);
|
||||
--tooltip-padding: 0px;
|
||||
}
|
||||
|
||||
.divider {
|
||||
--divider-color: rgba(255, 255, 255, 0.14);
|
||||
}
|
||||
@@ -1,100 +0,0 @@
|
||||
import { useCallback, type ReactNode, type MouseEvent } from 'react';
|
||||
import { TooltipSimple } from '@signozhq/ui/tooltip';
|
||||
import { toast } from '@signozhq/ui/sonner';
|
||||
import { Copy, Minus, Plus } from '@signozhq/icons';
|
||||
import { useCopyToClipboard } from 'react-use';
|
||||
|
||||
import { useInfraMonitoringCellActionsStore } from './useInfraMonitoringCellActionsStore';
|
||||
|
||||
import styles from './CellValueTooltip.module.scss';
|
||||
import { Divider } from '@signozhq/ui/divider';
|
||||
|
||||
export interface CellValueTooltipProps {
|
||||
value: string;
|
||||
children: ReactNode;
|
||||
}
|
||||
|
||||
export function CellValueTooltip({
|
||||
value,
|
||||
children,
|
||||
}: CellValueTooltipProps): JSX.Element {
|
||||
const [, copyToClipboard] = useCopyToClipboard();
|
||||
const { lineClamp, increaseLineClamp, decreaseLineClamp } =
|
||||
useInfraMonitoringCellActionsStore();
|
||||
|
||||
const handleCopy = useCallback(
|
||||
(e: MouseEvent): void => {
|
||||
e.stopPropagation();
|
||||
copyToClipboard(value);
|
||||
toast.success('Copied to clipboard');
|
||||
},
|
||||
[copyToClipboard, value],
|
||||
);
|
||||
|
||||
const handleIncreaseLineClamp = useCallback(
|
||||
(e: MouseEvent): void => {
|
||||
e.stopPropagation();
|
||||
increaseLineClamp();
|
||||
},
|
||||
[increaseLineClamp],
|
||||
);
|
||||
|
||||
const handleDecreaseLineClamp = useCallback(
|
||||
(e: MouseEvent): void => {
|
||||
e.stopPropagation();
|
||||
decreaseLineClamp();
|
||||
},
|
||||
[decreaseLineClamp],
|
||||
);
|
||||
|
||||
const tooltipContent = (
|
||||
<div className={styles.tooltipContent}>
|
||||
<div className={styles.actions}>
|
||||
<button
|
||||
type="button"
|
||||
className={styles.actionButton}
|
||||
onClick={handleDecreaseLineClamp}
|
||||
disabled={lineClamp <= 1}
|
||||
data-testid="cell-value-decrease-line-height"
|
||||
title="Decrease line height"
|
||||
>
|
||||
<Minus size={14} />
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className={styles.actionButton}
|
||||
onClick={handleIncreaseLineClamp}
|
||||
disabled={lineClamp >= 10}
|
||||
data-testid="cell-value-increase-line-height"
|
||||
title="Increase line height"
|
||||
>
|
||||
<Plus size={14} />
|
||||
</button>
|
||||
|
||||
<Divider type="vertical" className={styles.divider} />
|
||||
|
||||
<button
|
||||
type="button"
|
||||
className={styles.actionButton}
|
||||
onClick={handleCopy}
|
||||
data-testid="cell-value-copy"
|
||||
title="Copy value"
|
||||
>
|
||||
<Copy size={14} />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
return (
|
||||
<TooltipSimple
|
||||
title={tooltipContent}
|
||||
arrow
|
||||
tooltipContentProps={{
|
||||
className: styles.tooltipContentWrapper,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</TooltipSimple>
|
||||
);
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
export {
|
||||
CellValueTooltip,
|
||||
type CellValueTooltipProps,
|
||||
} from './CellValueTooltip';
|
||||
export {
|
||||
useInfraMonitoringCellActionsStore,
|
||||
useInfraMonitoringLineClamp,
|
||||
} from './useInfraMonitoringCellActionsStore';
|
||||
@@ -1,39 +0,0 @@
|
||||
import { create } from 'zustand';
|
||||
import { persist } from 'zustand/middleware';
|
||||
|
||||
export interface IInfraMonitoringCellActionsStore {
|
||||
lineClamp: number;
|
||||
increaseLineClamp: () => void;
|
||||
decreaseLineClamp: () => void;
|
||||
}
|
||||
|
||||
const MIN_LINE_CLAMP = 1;
|
||||
const MAX_LINE_CLAMP = 10;
|
||||
const DEFAULT_LINE_CLAMP = 2;
|
||||
|
||||
export const useInfraMonitoringCellActionsStore =
|
||||
create<IInfraMonitoringCellActionsStore>()(
|
||||
persist(
|
||||
(set, get) => ({
|
||||
lineClamp: DEFAULT_LINE_CLAMP,
|
||||
increaseLineClamp: (): void => {
|
||||
const current = get().lineClamp;
|
||||
if (current < MAX_LINE_CLAMP) {
|
||||
set({ lineClamp: current + 1 });
|
||||
}
|
||||
},
|
||||
decreaseLineClamp: (): void => {
|
||||
const current = get().lineClamp;
|
||||
if (current > MIN_LINE_CLAMP) {
|
||||
set({ lineClamp: current - 1 });
|
||||
}
|
||||
},
|
||||
}),
|
||||
{
|
||||
name: '@signoz/infra-monitoring-cell-actions',
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
export const useInfraMonitoringLineClamp = (): number =>
|
||||
useInfraMonitoringCellActionsStore((s) => s.lineClamp);
|
||||
@@ -12,7 +12,7 @@ import {
|
||||
|
||||
import styles from './EntityProgressBar.module.scss';
|
||||
|
||||
type EntityProgressBarType = 'request' | 'limit' | 'cpu' | 'memory' | 'disk';
|
||||
type EntityProgressBarType = 'request' | 'limit' | 'cpu' | 'memory';
|
||||
|
||||
function getStrokeColor(type: EntityProgressBarType, value: number): string {
|
||||
switch (type) {
|
||||
@@ -24,8 +24,6 @@ function getStrokeColor(type: EntityProgressBarType, value: number): string {
|
||||
return getProgressColor(Number((value * 100).toFixed(1)));
|
||||
case 'memory':
|
||||
return getMemoryProgressColor(Number((value * 100).toFixed(1)));
|
||||
case 'disk':
|
||||
return getProgressColor(Number((value * 100).toFixed(1)));
|
||||
default:
|
||||
return getStrokeColorForRequestUtilization(value);
|
||||
}
|
||||
@@ -38,8 +36,9 @@ export function EntityProgressBar({
|
||||
value: number;
|
||||
type: EntityProgressBarType;
|
||||
}): JSX.Element {
|
||||
const isNoData = value === -1 || Number.isNaN(+value);
|
||||
const percentage = isNoData ? null : Number((value * 100).toFixed(1));
|
||||
const percentage = Number.isNaN(+value)
|
||||
? null
|
||||
: Number((value * 100).toFixed(1));
|
||||
|
||||
if (percentage === null) {
|
||||
return (
|
||||
|
||||
@@ -1,25 +0,0 @@
|
||||
.container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.itemWrapper {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.separator {
|
||||
width: 3px;
|
||||
height: 14px;
|
||||
border-radius: 1px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.value {
|
||||
font-variant-numeric: tabular-nums;
|
||||
min-width: 4ch;
|
||||
text-align: left;
|
||||
cursor: default;
|
||||
}
|
||||
@@ -1,47 +0,0 @@
|
||||
import { TooltipSimple } from '@signozhq/ui/tooltip';
|
||||
|
||||
import styles from './GroupedStatusCounts.module.scss';
|
||||
import TanStackTable from 'components/TanStackTableView';
|
||||
|
||||
export interface StatusCountItem {
|
||||
value: number;
|
||||
label: string;
|
||||
color: string;
|
||||
}
|
||||
|
||||
interface GroupedStatusCountsProps {
|
||||
items: StatusCountItem[];
|
||||
showZeroValues?: boolean;
|
||||
}
|
||||
|
||||
export function GroupedStatusCounts({
|
||||
items,
|
||||
showZeroValues = true,
|
||||
}: GroupedStatusCountsProps): JSX.Element {
|
||||
const visibleItems =
|
||||
showZeroValues === false ? items.filter((item) => item.value > 0) : items;
|
||||
|
||||
if (visibleItems.length === 0) {
|
||||
return <TanStackTable.Text>-</TanStackTable.Text>;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={styles.container}>
|
||||
{visibleItems.map((item) => (
|
||||
<div key={item.label} className={styles.itemWrapper}>
|
||||
<div
|
||||
className={styles.separator}
|
||||
style={{ backgroundColor: item.color }}
|
||||
/>
|
||||
<TooltipSimple title={`${item.label}: ${item.value}`}>
|
||||
<span>
|
||||
<TanStackTable.Text className={styles.value}>
|
||||
{item.value || '-'}
|
||||
</TanStackTable.Text>
|
||||
</span>
|
||||
</TooltipSimple>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -17,7 +17,7 @@ export function ValidateColumnValueWrapper({
|
||||
entity?: InfraMonitoringEntity;
|
||||
attribute?: string;
|
||||
}): JSX.Element {
|
||||
if (value === -1 || Number.isNaN(value)) {
|
||||
if (value === -1) {
|
||||
let element = <TanStackTable.Text>-</TanStackTable.Text>;
|
||||
if (entity && attribute) {
|
||||
element = (
|
||||
|
||||
@@ -1,12 +1,3 @@
|
||||
export { EntityProgressBar } from './EntityProgressBar';
|
||||
export { ValidateColumnValueWrapper } from './ValidateColumnValueWrapper';
|
||||
export { ExpandButtonWrapper } from './ExpandButtonWrapper';
|
||||
export {
|
||||
GroupedStatusCounts,
|
||||
type StatusCountItem,
|
||||
} from './GroupedStatusCounts';
|
||||
export {
|
||||
CellValueTooltip,
|
||||
useInfraMonitoringCellActionsStore,
|
||||
useInfraMonitoringLineClamp,
|
||||
} from './CellValueTooltip';
|
||||
|
||||
@@ -5,108 +5,6 @@ import {
|
||||
import { DataTypes } from 'types/api/queryBuilder/queryAutocompleteResponse';
|
||||
import { DataSource } from 'types/common/queryBuilder';
|
||||
|
||||
// TODO(backend): Find a way to generate this via openapi
|
||||
export const INFRA_MONITORING_ATTR_KEYS = {
|
||||
// Host
|
||||
HOST_NAME: 'host.name',
|
||||
|
||||
// Cluster
|
||||
K8S_CLUSTER_NAME: 'k8s.cluster.name',
|
||||
K8S_CLUSTER_UID: 'k8s.cluster.uid',
|
||||
|
||||
// Namespace
|
||||
K8S_NAMESPACE_NAME: 'k8s.namespace.name',
|
||||
|
||||
// Node
|
||||
K8S_NODE_NAME: 'k8s.node.name',
|
||||
K8S_NODE_UID: 'k8s.node.uid',
|
||||
K8S_NODE_CPU_USAGE: 'k8s.node.cpu.usage',
|
||||
K8S_NODE_ALLOCATABLE_CPU: 'k8s.node.allocatable_cpu',
|
||||
K8S_NODE_ALLOCATABLE_MEMORY: 'k8s.node.allocatable_memory',
|
||||
K8S_NODE_CONDITION_READY: 'k8s.node.condition_ready',
|
||||
K8S_NODE_MEMORY_RSS: 'k8s.node.memory.rss',
|
||||
K8S_NODE_MEMORY_USAGE: 'k8s.node.memory.usage',
|
||||
K8S_NODE_MEMORY_WORKING_SET: 'k8s.node.memory.working_set',
|
||||
K8S_NODE_FILESYSTEM_AVAILABLE: 'k8s.node.filesystem.available',
|
||||
K8S_NODE_FILESYSTEM_CAPACITY: 'k8s.node.filesystem.capacity',
|
||||
K8S_NODE_FILESYSTEM_USAGE: 'k8s.node.filesystem.usage',
|
||||
K8S_NODE_NETWORK_IO: 'k8s.node.network.io',
|
||||
K8S_NODE_NETWORK_ERRORS: 'k8s.node.network.errors',
|
||||
|
||||
// Pod
|
||||
K8S_POD_NAME: 'k8s.pod.name',
|
||||
K8S_POD_UID: 'k8s.pod.uid',
|
||||
K8S_POD_CPU_USAGE: 'k8s.pod.cpu.usage',
|
||||
K8S_POD_CPU_LIMIT_UTILIZATION: 'k8s.pod.cpu_limit_utilization',
|
||||
K8S_POD_CPU_REQUEST_UTILIZATION: 'k8s.pod.cpu_request_utilization',
|
||||
K8S_POD_MEMORY_USAGE: 'k8s.pod.memory.usage',
|
||||
K8S_POD_MEMORY_RSS: 'k8s.pod.memory.rss',
|
||||
K8S_POD_MEMORY_WORKING_SET: 'k8s.pod.memory.working_set',
|
||||
K8S_POD_MEMORY_MAJOR_PAGE_FAULTS: 'k8s.pod.memory.major_page_faults',
|
||||
K8S_POD_MEMORY_LIMIT_UTILIZATION: 'k8s.pod.memory_limit_utilization',
|
||||
K8S_POD_MEMORY_REQUEST_UTILIZATION: 'k8s.pod.memory_request_utilization',
|
||||
K8S_POD_FILESYSTEM_AVAILABLE: 'k8s.pod.filesystem.available',
|
||||
K8S_POD_FILESYSTEM_CAPACITY: 'k8s.pod.filesystem.capacity',
|
||||
K8S_POD_FILESYSTEM_USAGE: 'k8s.pod.filesystem.usage',
|
||||
K8S_POD_NETWORK_IO: 'k8s.pod.network.io',
|
||||
K8S_POD_NETWORK_ERRORS: 'k8s.pod.network.errors',
|
||||
|
||||
// Container
|
||||
K8S_CONTAINER_NAME: 'k8s.container.name',
|
||||
K8S_CONTAINER_CPU_REQUEST: 'k8s.container.cpu_request',
|
||||
K8S_CONTAINER_CPU_LIMIT: 'k8s.container.cpu_limit',
|
||||
K8S_CONTAINER_MEMORY_REQUEST: 'k8s.container.memory_request',
|
||||
K8S_CONTAINER_MEMORY_LIMIT: 'k8s.container.memory_limit',
|
||||
|
||||
// Deployment
|
||||
K8S_DEPLOYMENT_NAME: 'k8s.deployment.name',
|
||||
K8S_DEPLOYMENT_AVAILABLE: 'k8s.deployment.available',
|
||||
K8S_DEPLOYMENT_DESIRED: 'k8s.deployment.desired',
|
||||
|
||||
// StatefulSet
|
||||
K8S_STATEFULSET_NAME: 'k8s.statefulset.name',
|
||||
K8S_STATEFULSET_CURRENT_PODS: 'k8s.statefulset.current_pods',
|
||||
K8S_STATEFULSET_DESIRED_PODS: 'k8s.statefulset.desired_pods',
|
||||
K8S_STATEFULSET_READY_PODS: 'k8s.statefulset.ready_pods',
|
||||
K8S_STATEFULSET_UPDATED_PODS: 'k8s.statefulset.updated_pods',
|
||||
|
||||
// DaemonSet
|
||||
K8S_DAEMONSET_NAME: 'k8s.daemonset.name',
|
||||
K8S_DAEMONSET_CURRENT_SCHEDULED_NODES: 'k8s.daemonset.current_scheduled_nodes',
|
||||
K8S_DAEMONSET_DESIRED_SCHEDULED_NODES: 'k8s.daemonset.desired_scheduled_nodes',
|
||||
K8S_DAEMONSET_MISSCHEDULED_NODES: 'k8s.daemonset.misscheduled_nodes',
|
||||
K8S_DAEMONSET_READY_NODES: 'k8s.daemonset.ready_nodes',
|
||||
|
||||
// ReplicaSet
|
||||
K8S_REPLICASET_NAME: 'k8s.replicaset.name',
|
||||
K8S_REPLICASET_AVAILABLE: 'k8s.replicaset.available',
|
||||
K8S_REPLICASET_DESIRED: 'k8s.replicaset.desired',
|
||||
|
||||
// Job
|
||||
K8S_JOB_NAME: 'k8s.job.name',
|
||||
K8S_CRONJOB_NAME: 'k8s.cronjob.name',
|
||||
K8S_JOB_ACTIVE_PODS: 'k8s.job.active_pods',
|
||||
K8S_JOB_DESIRED_SUCCESSFUL_PODS: 'k8s.job.desired_successful_pods',
|
||||
K8S_JOB_FAILED_PODS: 'k8s.job.failed_pods',
|
||||
K8S_JOB_SUCCESSFUL_PODS: 'k8s.job.successful_pods',
|
||||
|
||||
// Volume
|
||||
K8S_PERSISTENT_VOLUME_CLAIM_NAME: 'k8s.persistentvolumeclaim.name',
|
||||
K8S_VOLUME_AVAILABLE: 'k8s.volume.available',
|
||||
K8S_VOLUME_CAPACITY: 'k8s.volume.capacity',
|
||||
K8S_VOLUME_INODES: 'k8s.volume.inodes',
|
||||
K8S_VOLUME_INODES_FREE: 'k8s.volume.inodes.free',
|
||||
K8S_VOLUME_INODES_USED: 'k8s.volume.inodes.used',
|
||||
K8S_VOLUME_TYPE: 'k8s.volume.type',
|
||||
|
||||
// K8s events
|
||||
K8S_OBJECT_KIND: 'k8s.object.kind',
|
||||
K8S_OBJECT_NAME: 'k8s.object.name',
|
||||
|
||||
// Environment
|
||||
DEPLOYMENT_ENVIRONMENT: 'deployment.environment',
|
||||
} as const;
|
||||
|
||||
export const DEFAULT_PAGE_SIZE = 10;
|
||||
|
||||
export enum InfraMonitoringEntity {
|
||||
@@ -153,7 +51,7 @@ export const K8sCategories = {
|
||||
VOLUMES: 'volumes',
|
||||
};
|
||||
|
||||
const underscoreMap = {
|
||||
export const underscoreMap = {
|
||||
[InfraMonitoringEntity.HOSTS]: 'system_cpu_load_average_15m',
|
||||
[InfraMonitoringEntity.PODS]: 'k8s_pod_cpu_usage',
|
||||
[InfraMonitoringEntity.NODES]: 'k8s_node_cpu_usage',
|
||||
@@ -167,7 +65,7 @@ const underscoreMap = {
|
||||
[InfraMonitoringEntity.VOLUMES]: 'k8s_volume_capacity',
|
||||
};
|
||||
|
||||
const dotMap = {
|
||||
export const dotMap = {
|
||||
[InfraMonitoringEntity.HOSTS]: 'system.cpu.load_average.15m',
|
||||
[InfraMonitoringEntity.PODS]: 'k8s.pod.cpu.usage',
|
||||
[InfraMonitoringEntity.NODES]: 'k8s.node.cpu.usage',
|
||||
@@ -854,63 +752,19 @@ export const getInvalidValueTooltipText = (
|
||||
attribute: string,
|
||||
): string => `Some ${entity} do not have ${attribute}s.`;
|
||||
|
||||
export const ENTITY_FILTER_PLACEHOLDERS: Record<InfraMonitoringEntity, string> =
|
||||
{
|
||||
[InfraMonitoringEntity.HOSTS]:
|
||||
"Enter your filter query (e.g., host.name = 'web-server-01' AND os.type = 'linux')",
|
||||
[InfraMonitoringEntity.PODS]:
|
||||
"Enter your filter query (e.g., k8s.namespace.name = 'production' AND k8s.deployment.name = 'api-server')",
|
||||
[InfraMonitoringEntity.NODES]:
|
||||
"Enter your filter query (e.g., k8s.node.name = 'node-01' AND k8s.cluster.name = 'prod-cluster')",
|
||||
[InfraMonitoringEntity.NAMESPACES]:
|
||||
"Enter your filter query (e.g., k8s.namespace.name = 'production' AND k8s.cluster.name = 'prod-cluster')",
|
||||
[InfraMonitoringEntity.CLUSTERS]:
|
||||
"Enter your filter query (e.g., k8s.cluster.name = 'prod-cluster' AND deployment.environment = 'production')",
|
||||
[InfraMonitoringEntity.DEPLOYMENTS]:
|
||||
"Enter your filter query (e.g., k8s.deployment.name = 'api-server' AND k8s.namespace.name = 'production')",
|
||||
[InfraMonitoringEntity.STATEFULSETS]:
|
||||
"Enter your filter query (e.g., k8s.statefulset.name = 'postgres' AND k8s.namespace.name = 'databases')",
|
||||
[InfraMonitoringEntity.DAEMONSETS]:
|
||||
"Enter your filter query (e.g., k8s.daemonset.name = 'fluentd' AND k8s.namespace.name = 'logging')",
|
||||
[InfraMonitoringEntity.CONTAINERS]:
|
||||
"Enter your filter query (e.g., k8s.container.name = 'nginx' AND k8s.namespace.name = 'production')",
|
||||
[InfraMonitoringEntity.JOBS]:
|
||||
"Enter your filter query (e.g., k8s.job.name = 'backup-job' AND k8s.namespace.name = 'cron-jobs')",
|
||||
[InfraMonitoringEntity.VOLUMES]:
|
||||
"Enter your filter query (e.g., k8s.persistentvolumeclaim.name = 'data-pvc' AND k8s.namespace.name = 'storage')",
|
||||
};
|
||||
|
||||
export const INFRA_MONITORING_K8S_PARAMS_KEYS = {
|
||||
CATEGORY: 'category',
|
||||
VIEW: 'view',
|
||||
FILTERS: 'filters',
|
||||
EXPRESSION: 'expression',
|
||||
GROUP_BY: 'groupBy',
|
||||
ORDER_BY: 'orderBy',
|
||||
LOG_FILTERS: 'logFilters',
|
||||
TRACES_FILTERS: 'tracesFilters',
|
||||
EVENTS_FILTERS: 'eventsFilters',
|
||||
HOSTS_FILTERS: 'hostsFilters',
|
||||
STATUS_FILTER: 'statusFilter',
|
||||
CURRENT_PAGE: 'currentPage',
|
||||
PAGE: 'page',
|
||||
PAGE_SIZE: 'pageSize',
|
||||
EXPANDED: 'expanded',
|
||||
SELECTED_ITEM: 'selectedItem',
|
||||
};
|
||||
|
||||
/** Metric namespace prefixes for /fields/keys and /fields/values APIs */
|
||||
export const METRIC_NAMESPACE_BY_ENTITY: Record<InfraMonitoringEntity, string> =
|
||||
{
|
||||
[InfraMonitoringEntity.HOSTS]: 'system.',
|
||||
[InfraMonitoringEntity.PODS]: 'k8s.pod.',
|
||||
[InfraMonitoringEntity.NODES]: 'k8s.node.',
|
||||
[InfraMonitoringEntity.NAMESPACES]: 'k8s.pod.',
|
||||
[InfraMonitoringEntity.CLUSTERS]: 'k8s.node.',
|
||||
[InfraMonitoringEntity.DEPLOYMENTS]: 'k8s.',
|
||||
[InfraMonitoringEntity.STATEFULSETS]: 'k8s.',
|
||||
[InfraMonitoringEntity.DAEMONSETS]: 'k8s.',
|
||||
[InfraMonitoringEntity.CONTAINERS]: 'k8s.pod.',
|
||||
[InfraMonitoringEntity.JOBS]: 'k8s.',
|
||||
[InfraMonitoringEntity.VOLUMES]: 'k8s.volume.',
|
||||
};
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import {
|
||||
createParser,
|
||||
Options,
|
||||
parseAsInteger,
|
||||
parseAsJson,
|
||||
@@ -7,6 +6,7 @@ import {
|
||||
useQueryState,
|
||||
UseQueryStateReturn,
|
||||
} from 'nuqs';
|
||||
import { BaseAutocompleteData } from 'types/api/queryBuilder/queryAutocompleteResponse';
|
||||
import {
|
||||
IBuilderQuery,
|
||||
TagFilter,
|
||||
@@ -20,19 +20,35 @@ import {
|
||||
} from './constants';
|
||||
import { orderBySchema, OrderBySchemaType } from './schemas';
|
||||
|
||||
export type StatusFilterValue = 'active' | 'inactive';
|
||||
|
||||
const defaultNuqsOptions: Options = {
|
||||
history: 'push',
|
||||
};
|
||||
|
||||
export const useInfraMonitoringCurrentPage = (): UseQueryStateReturn<
|
||||
number,
|
||||
number
|
||||
> =>
|
||||
useQueryState(
|
||||
INFRA_MONITORING_K8S_PARAMS_KEYS.CURRENT_PAGE,
|
||||
parseAsInteger.withDefault(1).withOptions(defaultNuqsOptions),
|
||||
);
|
||||
|
||||
export const useInfraMonitoringPageListing = (): UseQueryStateReturn<
|
||||
number,
|
||||
number | undefined
|
||||
number
|
||||
> =>
|
||||
useQueryState(
|
||||
INFRA_MONITORING_K8S_PARAMS_KEYS.PAGE,
|
||||
parseAsInteger.withOptions(defaultNuqsOptions),
|
||||
parseAsInteger.withDefault(1).withOptions(defaultNuqsOptions),
|
||||
);
|
||||
|
||||
export const useInfraMonitoringPageSizeListing = (): UseQueryStateReturn<
|
||||
number,
|
||||
number
|
||||
> =>
|
||||
useQueryState(
|
||||
INFRA_MONITORING_K8S_PARAMS_KEYS.PAGE_SIZE,
|
||||
parseAsInteger.withDefault(10).withOptions(defaultNuqsOptions),
|
||||
);
|
||||
|
||||
export const useInfraMonitoringOrderBy = (): UseQueryStateReturn<
|
||||
@@ -44,40 +60,15 @@ export const useInfraMonitoringOrderBy = (): UseQueryStateReturn<
|
||||
parseAsJson(orderBySchema).withOptions(defaultNuqsOptions),
|
||||
);
|
||||
|
||||
const parseAsGroupBy = createParser<string[]>({
|
||||
parse: (value: string): string[] | null => {
|
||||
try {
|
||||
const parsed = JSON.parse(value);
|
||||
if (!Array.isArray(parsed)) {
|
||||
return null;
|
||||
}
|
||||
return parsed
|
||||
.map((item: unknown) => {
|
||||
if (typeof item === 'string') {
|
||||
return item;
|
||||
}
|
||||
if (item && typeof item === 'object' && 'key' in item) {
|
||||
return String((item as { key: unknown }).key);
|
||||
}
|
||||
return '';
|
||||
})
|
||||
.filter(Boolean);
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
},
|
||||
serialize: (value: string[]): string => JSON.stringify(value),
|
||||
eq: (a: string[], b: string[]): boolean =>
|
||||
JSON.stringify(a) === JSON.stringify(b),
|
||||
});
|
||||
|
||||
export const useInfraMonitoringGroupBy = (): UseQueryStateReturn<
|
||||
string[],
|
||||
BaseAutocompleteData[],
|
||||
[]
|
||||
> =>
|
||||
useQueryState(
|
||||
INFRA_MONITORING_K8S_PARAMS_KEYS.GROUP_BY,
|
||||
parseAsGroupBy.withDefault([]).withOptions(defaultNuqsOptions),
|
||||
parseAsJsonNoValidate<IBuilderQuery['groupBy']>()
|
||||
.withDefault([])
|
||||
.withOptions(defaultNuqsOptions),
|
||||
);
|
||||
|
||||
export const useInfraMonitoringView = (): UseQueryStateReturn<string, string> =>
|
||||
@@ -128,6 +119,15 @@ export const useInfraMonitoringCategory = (): UseQueryStateReturn<
|
||||
parseAsString.withDefault(K8sCategories.PODS).withOptions(defaultNuqsOptions),
|
||||
);
|
||||
|
||||
export const useInfraMonitoringFiltersK8s = (): UseQueryStateReturn<
|
||||
TagFilter,
|
||||
undefined
|
||||
> =>
|
||||
useQueryState(
|
||||
INFRA_MONITORING_K8S_PARAMS_KEYS.FILTERS,
|
||||
parseAsJsonNoValidate<TagFilter>().withOptions(defaultNuqsOptions),
|
||||
);
|
||||
|
||||
export const useInfraMonitoringSelectedItem = (): UseQueryStateReturn<
|
||||
string,
|
||||
string | undefined
|
||||
@@ -137,12 +137,3 @@ export const useInfraMonitoringSelectedItem = (): UseQueryStateReturn<
|
||||
parseAsString,
|
||||
);
|
||||
};
|
||||
|
||||
export const useInfraMonitoringStatusFilter = (): UseQueryStateReturn<
|
||||
string,
|
||||
string
|
||||
> =>
|
||||
useQueryState(
|
||||
INFRA_MONITORING_K8S_PARAMS_KEYS.STATUS_FILTER,
|
||||
parseAsString.withDefault('').withOptions(defaultNuqsOptions),
|
||||
);
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user