feat: initial release of headlamp-intel-gpu-plugin v0.1.0

Adds a Headlamp plugin for Intel GPU device plugin visibility:

- Dedicated sidebar section: Overview, Device Plugins, GPU Nodes, GPU Pods
- Native Node detail page injection: GPU capacity, allocatable, utilization, active pods
- Native Pod detail page injection: per-container GPU resource requests/limits
- Native Nodes table: GPU Type and GPU Devices columns
- App bar health badge (hidden when plugin not installed)
- GpuDevicePlugin CRD monitoring (deviceplugin.intel.com/v1) with graceful
  degradation when CRD is not present
- Supports discrete (i915), Xe, and integrated GPU nodes via node labels
- 48 unit tests, TypeScript clean, 28 kB production bundle

Generated with [Claude Code](https://claude.ai/code)
via [Happy](https://happy.engineering)

Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Happy <yesreply@happy.engineering>
This commit is contained in:
2026-02-18 17:58:49 -05:00
commit 41bf2aead4
18 changed files with 21053 additions and 0 deletions
+118
View File
@@ -0,0 +1,118 @@
/**
* PodDetailSection — injected into Headlamp's native Pod detail page.
*
* Shows Intel GPU resource requests and limits per container, plus
* a link to the node's GPU summary.
* Returns null for pods that don't request Intel GPU resources.
*/
import {
NameValueTable,
SectionBox,
StatusLabel,
} from '@kinvolk/headlamp-plugin/lib/CommonComponents';
import React from 'react';
import { formatGpuResourceName, INTEL_GPU_RESOURCE_PREFIX, isGpuRequestingPod } from '../api/k8s';
interface PodDetailSectionProps {
resource: {
kind?: string;
metadata?: { name?: string; namespace?: string };
jsonData?: unknown;
};
}
export default function PodDetailSection({ resource }: PodDetailSectionProps) {
// Extract raw Kubernetes JSON
const rawPod =
resource.jsonData && typeof resource.jsonData === 'object'
? resource.jsonData
: resource;
// Only render for pods that request Intel GPU resources
if (!isGpuRequestingPod(rawPod)) return null;
const pod = rawPod as {
metadata: { name: string; namespace?: string };
spec?: {
nodeName?: string;
containers?: Array<{
name: string;
resources?: {
requests?: Record<string, string>;
limits?: Record<string, string>;
};
}>;
};
status?: { phase?: string };
};
const containers = pod.spec?.containers ?? [];
const gpuContainers = containers.filter(c => {
const all = { ...c.resources?.requests, ...c.resources?.limits };
return Object.keys(all).some(k => k.startsWith(INTEL_GPU_RESOURCE_PREFIX));
});
if (gpuContainers.length === 0) return null;
// Build rows: one per container per GPU resource
const rows: Array<{ name: string; value: React.ReactNode }> = [];
for (const c of gpuContainers) {
const requests = c.resources?.requests ?? {};
const limits = c.resources?.limits ?? {};
const allGpuKeys = new Set([
...Object.keys(requests).filter(k => k.startsWith(INTEL_GPU_RESOURCE_PREFIX)),
...Object.keys(limits).filter(k => k.startsWith(INTEL_GPU_RESOURCE_PREFIX)),
]);
for (const key of allGpuKeys) {
const req = requests[key];
const lim = limits[key];
const resourceName = formatGpuResourceName(key);
rows.push({
name: `${c.name}${resourceName} request`,
value: req ?? '—',
});
if (lim && lim !== req) {
rows.push({
name: `${c.name}${resourceName} limit`,
value: lim,
});
}
}
}
const phase = pod.status?.phase;
const phaseStatus: 'success' | 'warning' | 'error' =
phase === 'Running' || phase === 'Succeeded'
? 'success'
: phase === 'Pending'
? 'warning'
: 'error';
return (
<SectionBox title="Intel GPU Resources">
<NameValueTable
rows={[
{
name: 'Phase',
value: (
<StatusLabel status={phaseStatus}>{phase ?? 'Unknown'}</StatusLabel>
),
},
{
name: 'Scheduled Node',
value: pod.spec?.nodeName ?? '—',
},
{
name: 'GPU Containers',
value: String(gpuContainers.length),
},
...rows,
]}
/>
</SectionBox>
);
}