mirror of
https://github.com/hyperdxio/hyperdx
synced 2026-04-21 13:37:15 +00:00
feat: Add Pods table to Infra dashboard (#196)

This commit is contained in:
parent
725d7b7b30
commit
ea9acde1f4
3 changed files with 206 additions and 23 deletions
5
.changeset/spicy-parents-count.md
Normal file
5
.changeset/spicy-parents-count.md
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
'@hyperdx/app': patch
|
||||
---
|
||||
|
||||
Add Pods table to Infra dashboard
|
||||
|
|
@ -1,8 +1,19 @@
|
|||
import * as React from 'react';
|
||||
import Head from 'next/head';
|
||||
import { formatDistanceStrict } from 'date-fns';
|
||||
import { StringParam, useQueryParam, withDefault } from 'use-query-params';
|
||||
import { Card, Grid, Group, Select, Tabs } from '@mantine/core';
|
||||
import {
|
||||
Badge,
|
||||
Card,
|
||||
Grid,
|
||||
Group,
|
||||
Select,
|
||||
Skeleton,
|
||||
Table,
|
||||
Tabs,
|
||||
} from '@mantine/core';
|
||||
|
||||
import api from './api';
|
||||
import AppNav from './AppNav';
|
||||
import {
|
||||
convertDateRangeToGranularityString,
|
||||
|
|
@ -23,6 +34,183 @@ import { LogTableWithSidePanel } from './LogTableWithSidePanel';
|
|||
import SearchInput from './SearchInput';
|
||||
import SearchTimeRangePicker from './SearchTimeRangePicker';
|
||||
import { parseTimeQuery, useTimeQuery } from './timeQuery';
|
||||
import { formatNumber } from './utils';
|
||||
|
||||
const FormatPodStatus = ({ status }: { status?: number }) => {
|
||||
// based on
|
||||
// https://github.com/open-telemetry/opentelemetry-collector-contrib/blob/main/receiver/k8sclusterreceiver/documentation.md#k8spodphase
|
||||
// Current phase of the pod (1 - Pending, 2 - Running, 3 - Succeeded, 4 - Failed, 5 - Unknown)
|
||||
switch (status) {
|
||||
case 1:
|
||||
return (
|
||||
<Badge color="yellow" fw="normal" tt="none" size="md">
|
||||
Pending
|
||||
</Badge>
|
||||
);
|
||||
case 2:
|
||||
return (
|
||||
<Badge color="green" fw="normal" tt="none" size="md">
|
||||
Running
|
||||
</Badge>
|
||||
);
|
||||
case 3:
|
||||
return (
|
||||
<Badge color="indigo" fw="normal" tt="none" size="md">
|
||||
Succeeded
|
||||
</Badge>
|
||||
);
|
||||
case 4:
|
||||
return (
|
||||
<Badge color="red" fw="normal" tt="none" size="md">
|
||||
Failed
|
||||
</Badge>
|
||||
);
|
||||
case 5:
|
||||
return (
|
||||
<Badge color="gray" fw="normal" tt="none" size="md">
|
||||
Unknown
|
||||
</Badge>
|
||||
);
|
||||
default:
|
||||
return (
|
||||
<Badge color="gray" fw="normal" tt="none" size="md">
|
||||
Unknown
|
||||
</Badge>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
const InfraPodsStatusTable = ({
|
||||
dateRange,
|
||||
where,
|
||||
}: {
|
||||
dateRange: [Date, Date];
|
||||
where: string;
|
||||
}) => {
|
||||
const { data, isError, isLoading } = api.useMultiSeriesChart({
|
||||
series: [
|
||||
{
|
||||
table: 'metrics',
|
||||
field: 'k8s.container.restarts - Gauge',
|
||||
type: 'table',
|
||||
aggFn: 'max', // TODO
|
||||
where,
|
||||
groupBy: ['k8s.pod.name'],
|
||||
},
|
||||
{
|
||||
table: 'metrics',
|
||||
field: 'k8s.pod.uptime - Sum',
|
||||
type: 'table',
|
||||
aggFn: 'sum',
|
||||
where,
|
||||
groupBy: ['k8s.pod.name'],
|
||||
},
|
||||
{
|
||||
table: 'metrics',
|
||||
field: 'k8s.pod.cpu.utilization - Gauge',
|
||||
type: 'table',
|
||||
aggFn: 'avg',
|
||||
where,
|
||||
groupBy: ['k8s.pod.name'],
|
||||
},
|
||||
{
|
||||
table: 'metrics',
|
||||
field: 'k8s.pod.memory.usage - Gauge',
|
||||
type: 'table',
|
||||
aggFn: 'avg',
|
||||
where,
|
||||
groupBy: ['k8s.pod.name'],
|
||||
},
|
||||
{
|
||||
table: 'metrics',
|
||||
field: 'k8s.pod.phase - Gauge',
|
||||
type: 'table',
|
||||
aggFn: 'max', // TODO latest
|
||||
where,
|
||||
groupBy: ['k8s.pod.name'],
|
||||
},
|
||||
],
|
||||
endDate: dateRange[1] ?? new Date(),
|
||||
startDate: dateRange[0] ?? new Date(),
|
||||
seriesReturnType: 'column',
|
||||
});
|
||||
|
||||
return (
|
||||
<Card p="md">
|
||||
<Card.Section p="md" py="xs" withBorder>
|
||||
Pods
|
||||
</Card.Section>
|
||||
<Card.Section>
|
||||
{isError ? (
|
||||
<div className="p-4 text-center text-slate-500 fs-8">
|
||||
Unable to load pod metrics
|
||||
</div>
|
||||
) : (
|
||||
<Table horizontalSpacing="md" highlightOnHover>
|
||||
<thead className="muted-thead">
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th style={{ width: 100 }}>Restarts</th>
|
||||
{/* <th style={{ width: 120 }}>Age</th> */}
|
||||
<th style={{ width: 100 }}>CPU Avg</th>
|
||||
<th style={{ width: 100 }}>Mem Avg</th>
|
||||
<th style={{ width: 130 }}>Status</th>
|
||||
</tr>
|
||||
</thead>
|
||||
{isLoading ? (
|
||||
<tbody>
|
||||
{Array.from({ length: 4 }).map((_, index) => (
|
||||
<tr key={index}>
|
||||
<td>
|
||||
<Skeleton height={8} my={6} />
|
||||
</td>
|
||||
<td>
|
||||
<Skeleton height={8} />
|
||||
</td>
|
||||
<td>
|
||||
<Skeleton height={8} />
|
||||
</td>
|
||||
<td>
|
||||
<Skeleton height={8} />
|
||||
</td>
|
||||
<td>
|
||||
<Skeleton height={8} />
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
) : (
|
||||
<tbody>
|
||||
{data?.data?.map((row: any) => (
|
||||
<tr key={row.group}>
|
||||
<td>{row.group}</td>
|
||||
<td>{row['series_0.data']}</td>
|
||||
{/* <td>{formatDistanceStrict(row['series_1.data'] * 1000, 0)}</td> */}
|
||||
<td>
|
||||
{formatNumber(
|
||||
row['series_2.data'],
|
||||
K8S_CPU_PERCENTAGE_NUMBER_FORMAT,
|
||||
)}
|
||||
</td>
|
||||
<td>
|
||||
{formatNumber(
|
||||
row['series_3.data'],
|
||||
K8S_MEM_NUMBER_FORMAT,
|
||||
)}
|
||||
</td>
|
||||
<td>
|
||||
<FormatPodStatus status={row['series_4.data']} />
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
)}
|
||||
</Table>
|
||||
)}
|
||||
</Card.Section>
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
|
||||
const defaultTimeRange = parseTimeQuery('Past 1h', false);
|
||||
|
||||
|
|
@ -245,6 +433,12 @@ export default function ServiceDashboardPage() {
|
|||
</Card.Section>
|
||||
</Card>
|
||||
</Grid.Col>
|
||||
<Grid.Col span={12}>
|
||||
<InfraPodsStatusTable
|
||||
dateRange={dateRange}
|
||||
where={whereClause}
|
||||
/>
|
||||
</Grid.Col>
|
||||
<Grid.Col span={12}>
|
||||
<Card p="md">
|
||||
<Card.Section p="md" py="xs" withBorder>
|
||||
|
|
@ -264,28 +458,6 @@ export default function ServiceDashboardPage() {
|
|||
</Card.Section>
|
||||
</Card>
|
||||
</Grid.Col>
|
||||
<Grid.Col span={12}>
|
||||
<Card p="md">
|
||||
<Card.Section p="md" py="xs" withBorder>
|
||||
Debug
|
||||
</Card.Section>
|
||||
<Card.Section p="md" py="sm">
|
||||
<pre>
|
||||
{JSON.stringify(
|
||||
{
|
||||
dateRange,
|
||||
searchQuery,
|
||||
service,
|
||||
podNames,
|
||||
whereClause,
|
||||
},
|
||||
null,
|
||||
4,
|
||||
)}
|
||||
</pre>
|
||||
</Card.Section>
|
||||
</Card>
|
||||
</Grid.Col>
|
||||
</Grid>
|
||||
</Tabs.Panel>
|
||||
<Tabs.Panel value="http">
|
||||
|
|
|
|||
|
|
@ -780,3 +780,9 @@ div.react-datepicker {
|
|||
100% {
|
||||
}
|
||||
}
|
||||
|
||||
.muted-thead tr th {
|
||||
font-weight: normal !important;
|
||||
font-size: 0.6em;
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue