/**
* NodesPage — lists all nodes with Intel GPU capabilities.
*
* Shows GPU type, device count, resource allocation, and pod assignments
* for each GPU-capable node in the cluster.
*/
import {
Loader,
NameValueTable,
SectionBox,
SectionHeader,
SimpleTable,
StatusLabel,
} from '@kinvolk/headlamp-plugin/lib/CommonComponents';
import React from 'react';
import { useIntelGpuContext } from '../api/IntelGpuDataContext';
import {
formatAge,
formatGpuResourceName,
formatGpuType,
getGpuResources,
getNodeGpuCount,
getNodeGpuType,
INTEL_GPU_RESOURCE,
INTEL_GPU_XE_RESOURCE,
IntelGpuNode,
isNodeReady,
} from '../api/k8s';
// ---------------------------------------------------------------------------
// GPU allocation bar component
// ---------------------------------------------------------------------------
function GpuAllocationBar({ used, allocatable }: { used: number; allocatable: number }) {
if (allocatable === 0) return —;
const pct = Math.min(100, Math.round((used / allocatable) * 100));
const color = pct >= 90 ? '#d32f2f' : pct >= 70 ? '#f57c00' : '#0071c5';
return (
{`${used}/${allocatable} (${pct}%)`}
);
}
// ---------------------------------------------------------------------------
// Node detail card
// ---------------------------------------------------------------------------
function NodeDetailCard({
node,
podsByNode,
}: {
node: IntelGpuNode;
podsByNode: Map;
}) {
const gpuType = getNodeGpuType(node);
const gpuCount = getNodeGpuCount(node);
const ready = isNodeReady(node);
const capacityResources = getGpuResources(node.status?.capacity);
const allocatableResources = getGpuResources(node.status?.allocatable);
const podsOnNode = podsByNode.get(node.metadata.name) ?? [];
return (
{ready ? 'Ready' : 'Not Ready'}
),
},
{
name: 'GPU Type',
value: formatGpuType(gpuType),
},
...(gpuCount > 0 ? [{ name: 'GPU Devices (i915/xe)', value: String(gpuCount) }] : []),
...Object.entries(capacityResources).map(([key, cap]) => {
const total = parseInt(cap, 10);
return {
name: `${formatGpuResourceName(key)} (capacity)`,
value: String(total),
};
}),
...Object.entries(allocatableResources).map(([key, value]) => {
return {
name: `${formatGpuResourceName(key)} (allocatable)`,
value: value ?? '0',
};
}),
{
name: 'GPU Workload Pods',
value: podsOnNode.length > 0 ? podsOnNode.join(', ') : '—',
},
{
name: 'OS Image',
value: node.status?.nodeInfo?.osImage ?? '—',
},
{
name: 'Kernel',
value: node.status?.nodeInfo?.kernelVersion ?? '—',
},
{
name: 'Kubelet',
value: node.status?.nodeInfo?.kubeletVersion ?? '—',
},
{
name: 'Age',
value: formatAge(node.metadata.creationTimestamp),
},
]}
/>
);
}
// ---------------------------------------------------------------------------
// Main component
// ---------------------------------------------------------------------------
export default function NodesPage() {
const { gpuNodes, gpuPods, loading, error, refresh } = useIntelGpuContext();
if (loading) {
return ;
}
// Build map: nodeName → list of GPU pod names
const podsByNode = new Map();
for (const pod of gpuPods) {
if (!pod.spec?.nodeName) continue;
const existing = podsByNode.get(pod.spec.nodeName) ?? [];
existing.push(pod.metadata.name);
podsByNode.set(pod.spec.nodeName, existing);
}
// Build table data for summary
const tableData = gpuNodes.map(node => {
const gpuType = getNodeGpuType(node);
const gpuCount = getNodeGpuCount(node);
const ready = isNodeReady(node);
const capacity = node.status?.capacity ?? {};
const allocatable = node.status?.allocatable ?? {};
let totalCapacity = 0;
let totalAllocatable = 0;
for (const key of Object.keys(capacity)) {
if (key === INTEL_GPU_RESOURCE || key === INTEL_GPU_XE_RESOURCE) {
totalCapacity += parseInt(capacity[key] ?? '0', 10);
totalAllocatable += parseInt(allocatable[key] ?? '0', 10);
}
}
const podsOnNode = podsByNode.get(node.metadata.name) ?? [];
return {
node,
gpuType,
gpuCount,
ready,
totalCapacity,
totalAllocatable,
podsOnNode,
};
});
return (
<>
{error && (
{error} }]}
/>
)}
{gpuNodes.length === 0 && (
No nodes with Intel GPU resources or labels were found
),
},
{
name: 'Note',
value:
'Nodes appear here when they have gpu.intel.com/* resources or Intel GPU node labels. ' +
'Ensure the Intel GPU device plugin and Node Feature Discovery are installed.',
},
]}
/>
)}
{/* Summary table */}
{gpuNodes.length > 0 && (
d.node.metadata.name },
{
label: 'Ready',
getter: d => (
{d.ready ? 'Ready' : 'Not Ready'}
),
},
{ label: 'GPU Type', getter: d => formatGpuType(d.gpuType) },
{ label: 'GPU Devices', getter: d => String(d.gpuCount || '—') },
{
label: 'Allocation',
getter: d => (
),
},
{ label: 'GPU Pods', getter: d => String(d.podsOnNode.length) },
{ label: 'Age', getter: d => formatAge(d.node.metadata.creationTimestamp) },
]}
data={tableData}
/>
)}
{/* Per-node detail cards */}
{gpuNodes.map(node => (
))}
>
);
}