diff --git a/packages/app/src/KubernetesDashboardPage.tsx b/packages/app/src/KubernetesDashboardPage.tsx index 4cff0f8b0..48ae73f4d 100644 --- a/packages/app/src/KubernetesDashboardPage.tsx +++ b/packages/app/src/KubernetesDashboardPage.tsx @@ -30,6 +30,7 @@ import HDXLineChart from './HDXLineChart'; import { withAppNav } from './layout'; import { LogTableWithSidePanel } from './LogTableWithSidePanel'; import MetricTagValueSelect from './MetricTagValueSelect'; +import NodeDetailsSidePanel from './NodeDetailsSidePanel'; import PodDetailsSidePanel from './PodDetailsSidePanel'; import HdxSearchInput from './SearchInput'; import SearchTimeRangePicker from './SearchTimeRangePicker'; @@ -410,6 +411,12 @@ const NodesTable = ({ seriesReturnType: 'column', }); + const getLink = React.useCallback((nodeName: string) => { + const searchParams = new URLSearchParams(window.location.search); + searchParams.set('nodeName', `${nodeName}`); + return window.location.pathname + '?' + searchParams.toString(); + }, []); + const nodesList = React.useMemo(() => { if (!data) { return []; @@ -472,30 +479,37 @@ const NodesTable = ({ ) : ( {nodesList.map(node => ( - - {node.name || 'N/A'} - - {node.ready === 1 ? ( - - Ready - - ) : ( - - Not Ready - - )} - - - {formatNumber( - node.cpuAvg, - K8S_CPU_PERCENTAGE_NUMBER_FORMAT, - )} - - - {formatNumber(node.memAvg, K8S_MEM_NUMBER_FORMAT)} - - {node.uptime ? formatUptime(node.uptime) : '–'} - + + + {node.name || 'N/A'} + + {node.ready === 1 ? ( + + Ready + + ) : ( + + Not Ready + + )} + + + {formatNumber( + node.cpuAvg, + K8S_CPU_PERCENTAGE_NUMBER_FORMAT, + )} + + + {formatNumber(node.memAvg, K8S_MEM_NUMBER_FORMAT)} + + {node.uptime ? formatUptime(node.uptime) : '–'} + + ))} )} @@ -593,6 +607,7 @@ export default function KubernetesDashboardPage() { Kubernetes Dashboard +
{ + if (!value) return null; + return ( +
+ + {label} + + + {value} + +
+ ); + }, +); + +const NodeDetails = ({ + name, + dateRange, +}: { + name: string; + dateRange: [Date, Date]; +}) => { + const where = `k8s.node.name:"${name}"`; + const groupBy = ['k8s.node.name']; + + const { data } = api.useMultiSeriesChart({ + series: [ + { + table: 'metrics', + field: 'k8s.node.condition_ready - Gauge', + type: 'table', + aggFn: 'last_value', + where, + groupBy, + }, + { + table: 'metrics', + field: 'k8s.node.uptime - Sum', + type: 'table', + aggFn: 'last_value', + where, + groupBy, + }, + ], + endDate: dateRange[1] ?? new Date(), + startDate: dateRange[0] ?? new Date(), + seriesReturnType: 'column', + }); + + const properties = React.useMemo(() => { + const series: Record = data?.data?.[0] || {}; + return { + ready: series['series_0.data'], + uptime: series['series_1.data'], + }; + }, [data?.data]); + + return ( + +
+ + + Ready + + ) : ( + + Not Ready + + ) + } + /> + +
+
+ ); +}; + +function NodeLogs({ + where, + dateRange, +}: { + where: string; + dateRange: [Date, Date]; +}) { + const [resultType, setResultType] = React.useState<'all' | 'error'>('all'); + + const _where = where + (resultType === 'error' ? ' level:err' : ''); + + return ( + + + + Latest Node Logs & Spans + + { + if (value === 'all' || value === 'error') { + setResultType(value); + } + }} + data={[ + { label: 'All', value: 'all' }, + { label: 'Errors', value: 'error' }, + ]} + /> + + + Search + + + + + + + {}} + onPropertySearchClick={() => {}} + /> + + + ); +} + +export default function NodeDetailsSidePanel() { + const [nodeName, setNodeName] = useQueryParam( + 'nodeName', + withDefault(StringParam, ''), + { + updateType: 'replaceIn', + }, + ); + + const contextZIndex = useZIndex(); + const drawerZIndex = contextZIndex + 10; + + const where = React.useMemo(() => { + return `k8s.node.name:"${nodeName}"`; + }, [nodeName]); + + const { searchedTimeRange: dateRange } = useTimeQuery({ + isUTC: false, + defaultValue: 'Past 1h', + defaultTimeRange: [ + defaultTimeRange?.[0]?.getTime() ?? -1, + defaultTimeRange?.[1]?.getTime() ?? -1, + ], + }); + + const handleClose = React.useCallback(() => { + setNodeName(undefined); + }, [setNodeName]); + + if (!nodeName) { + return null; + } + + return ( + + +
+ + + + + + + + CPU Usage by Pod + + + + + + + + + + Memory Usage by Pod + + + + + + + + + + + + + + +
+
+
+ ); +} diff --git a/packages/app/src/PodDetailsSidePanel.tsx b/packages/app/src/PodDetailsSidePanel.tsx index b962163ff..a932435e5 100644 --- a/packages/app/src/PodDetailsSidePanel.tsx +++ b/packages/app/src/PodDetailsSidePanel.tsx @@ -162,8 +162,13 @@ export default function PodDetailsSidePanel() { }, ); + // If we're in a nested side panel, we need to use a higher z-index + // TODO: This is a hack + const [nodeName] = useQueryParam('nodeName', StringParam); + const [namespaceName] = useQueryParam('namespaceName', StringParam); + const isNested = !!nodeName || !!namespaceName; const contextZIndex = useZIndex(); - const drawerZIndex = contextZIndex + 10; + const drawerZIndex = contextZIndex + 10 + (isNested ? 100 : 0); const where = React.useMemo(() => { return `k8s.pod.name:"${podName}"`; @@ -194,7 +199,7 @@ export default function PodDetailsSidePanel() { open={!!podName} onClose={handleClose} direction="right" - size={'80vw'} + size={isNested ? '70vw' : '80vw'} zIndex={drawerZIndex} >