release: v0.4.1 — code quality fixes and doc updates

Remove unsafe `as any` casts, fix MetricsPage fetch cancellation safety,
delete dead AppBarGpuBadge component, fix typo in data context, move
extractJsonData to module scope, resolve ESLint/Prettier indent conflict,
fix artifacthub-pkg.yml version mismatch and inaccurate description.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
DevContainer User
2026-03-04 13:05:58 +00:00
parent e451e3906e
commit 1ae6e2d355
8 changed files with 75 additions and 110 deletions
+5
View File
@@ -1,3 +1,8 @@
module.exports = { module.exports = {
extends: ['@headlamp-k8s/eslint-config'], extends: ['@headlamp-k8s/eslint-config'],
rules: {
// Prettier handles indentation; the shared config's indent rule
// conflicts with Prettier's JSX ternary formatting.
indent: 'off',
},
}; };
+2 -2
View File
@@ -35,7 +35,7 @@ src/
├── index.tsx # Plugin entry: registerRoute, registerSidebarEntry, registerDetailsViewSection, registerResourceTableColumnsProcessor ├── index.tsx # Plugin entry: registerRoute, registerSidebarEntry, registerDetailsViewSection, registerResourceTableColumnsProcessor
├── api/ ├── api/
│ ├── k8s.ts # Types + helpers (GpuDevicePlugin CRD, Nodes, Pods, type guards, formatters) │ ├── k8s.ts # Types + helpers (GpuDevicePlugin CRD, Nodes, Pods, type guards, formatters)
│ ├── k8s.test.ts # Tests for k8s helpers (70+ test cases) │ ├── k8s.test.ts # Tests for k8s helpers (48 test cases)
│ ├── metrics.ts # Prometheus GPU power metrics (node-exporter i915 hwmon) │ ├── metrics.ts # Prometheus GPU power metrics (node-exporter i915 hwmon)
│ └── IntelGpuDataContext.tsx # Shared React context provider with data fetching │ └── IntelGpuDataContext.tsx # Shared React context provider with data fetching
└── components/ └── components/
@@ -44,7 +44,7 @@ src/
├── NodesPage.tsx # Per-node GPU type, device count, allocation, workload pods ├── NodesPage.tsx # Per-node GPU type, device count, allocation, workload pods
├── PodsPage.tsx # All pods requesting Intel GPU resources with per-container detail ├── PodsPage.tsx # All pods requesting Intel GPU resources with per-container detail
├── MetricsPage.tsx # Real-time GPU power metrics from Prometheus ├── MetricsPage.tsx # Real-time GPU power metrics from Prometheus
├── NodeDetailSection.tsx # Injected into native Node detail page (capacity, utilization, pods) ├── NodeDetailSection.tsx # Injected into native Node detail page (capacity, utilization, pods)
├── PodDetailSection.tsx # Injected into native Pod detail page (GPU requests per container) ├── PodDetailSection.tsx # Injected into native Pod detail page (GPU requests per container)
└── integrations/ └── integrations/
└── NodeColumns.tsx # GPU Type and GPU Devices columns for native Nodes table └── NodeColumns.tsx # GPU Type and GPU Devices columns for native Nodes table
+18 -28
View File
@@ -1,4 +1,4 @@
version: "0.4.0" version: "0.4.1"
name: intel-gpu name: intel-gpu
displayName: Intel GPU displayName: Intel GPU
description: >- description: >-
@@ -8,14 +8,14 @@ description: >-
sections into native Node and Pod detail pages. Supports discrete (i915), sections into native Node and Pod detail pages. Supports discrete (i915),
Xe, and integrated GPU nodes with graceful degradation when the device Xe, and integrated GPU nodes with graceful degradation when the device
plugin operator is not installed. Includes a Metrics page showing real-time plugin operator is not installed. Includes a Metrics page showing real-time
engine utilization, GPU frequency, VRAM usage, and energy from the device GPU power draw and TDP from node-exporter i915 hwmon metrics (discrete GPU
plugin's Prometheus endpoint. nodes only).
createdAt: "2026-02-18T00:00:00Z" createdAt: "2026-02-18T00:00:00Z"
license: Apache-2.0 license: Apache-2.0
category: monitoring-logging category: monitoring-logging
homeURL: https://github.com/privilegedescalation/headlamp-intel-gpu-plugin homeURL: https://github.com/privilegedescalation/headlamp-intel-gpu-plugin
appVersion: "0.3.0" appVersion: "0.4.0"
keywords: keywords:
- headlamp - headlamp
@@ -45,33 +45,23 @@ links:
url: https://intel.github.io/intel-device-plugins-for-kubernetes/ url: https://intel.github.io/intel-device-plugins-for-kubernetes/
changes: changes:
- kind: added - kind: fixed
description: "Metrics page: document which metrics require what infrastructure (power via hwmon works out of the box; frequency and utilization need custom exporters)" description: "Remove unsafe `as any` casts in NodeDetailSection"
- kind: added - kind: fixed
description: "Metrics page: real-time GPU power draw (W) and TDP via node-exporter i915 hwmon metrics in kube-prometheus-stack" description: "Fix MetricsPage fetch cancellation safety (prevent setState on unmounted component)"
- kind: fixed
description: "Fix typo gpuPluinPods → gpuPluginPods in data context"
- kind: changed - kind: changed
description: "Sidebar label changed to intel-gpu" description: "Move extractJsonData utility to module scope to avoid recreation on every render"
- kind: removed - kind: removed
description: "Removed app bar health badge" description: "Remove dead AppBarGpuBadge component"
- kind: added - kind: fixed
description: "Overview dashboard: plugin health, GPU node summary, allocation bar, active GPU pods" description: "Fix appVersion mismatch and inaccurate metrics description in Artifact Hub metadata"
- kind: added - kind: fixed
description: "Device Plugins page: GpuDevicePlugin CRD instances with spec/status and daemon pods" description: "Resolve ESLint/Prettier indent conflict by disabling ESLint indent rule (Prettier is formatting authority)"
- kind: added
description: "GPU Nodes page: per-node GPU type, device count, allocation, workload pods"
- kind: added
description: "GPU Pods page: all pods requesting Intel GPU resources with per-container detail"
- kind: added
description: "Node detail injection: Intel GPU section on native Node detail pages (capacity, allocatable, utilization, active pods)"
- kind: added
description: "Pod detail injection: GPU resource requests/limits per container on native Pod detail pages"
- kind: added
description: "Nodes table: GPU Type and GPU Devices columns injected into native Nodes table"
- kind: added
description: "App bar health badge: hidden when no Intel GPU plugin detected"
annotations: annotations:
headlamp/plugin/archive-url: "https://github.com/privilegedescalation/headlamp-intel-gpu-plugin/releases/download/v0.4.0/intel-gpu-0.4.0.tar.gz" headlamp/plugin/archive-url: "https://github.com/privilegedescalation/headlamp-intel-gpu-plugin/releases/download/v0.4.1/intel-gpu-0.4.1.tar.gz"
headlamp/plugin/archive-checksum: sha256:f529794d7995b35b954fa32c10874fa8367f6f5cd8040600e47a3013373219df headlamp/plugin/archive-checksum: ""
headlamp/plugin/version-compat: ">=0.20.0" headlamp/plugin/version-compat: ">=0.20.0"
headlamp/plugin/distro-compat: "in-cluster,web,app" headlamp/plugin/distro-compat: "in-cluster,web,app"
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "intel-gpu", "name": "intel-gpu",
"version": "0.4.0", "version": "0.4.1",
"description": "Headlamp plugin for Intel GPU device plugin visibility and monitoring", "description": "Headlamp plugin for Intel GPU device plugin visibility and monitoring",
"repository": { "repository": {
"type": "git", "type": "git",
+14 -9
View File
@@ -65,6 +65,18 @@ export function useIntelGpuContext(): IntelGpuContextValue {
return ctx; return ctx;
} }
// ---------------------------------------------------------------------------
// Helpers
// ---------------------------------------------------------------------------
/** Extract raw Kubernetes JSON from Headlamp KubeObject wrappers. */
const extractJsonData = (items: unknown[]): unknown[] =>
items.map(item =>
item && typeof item === 'object' && 'jsonData' in item
? (item as { jsonData: unknown }).jsonData
: item
);
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
// Provider // Provider
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
@@ -129,8 +141,8 @@ export function IntelGpuDataProvider({ children }: { children: React.ReactNode }
try { try {
const list = await ApiProxy.request(url); const list = await ApiProxy.request(url);
if (!cancelled && isKubeList(list)) { if (!cancelled && isKubeList(list)) {
const gpuPluinPods = filterIntelGpuPluginPods(list.items); const gpuPluginPods = filterIntelGpuPluginPods(list.items);
foundPluginPods.push(...gpuPluinPods); foundPluginPods.push(...gpuPluginPods);
} }
} catch { } catch {
// Silently ignore — some selectors may not match // Silently ignore — some selectors may not match
@@ -170,13 +182,6 @@ export function IntelGpuDataProvider({ children }: { children: React.ReactNode }
// type helpers work correctly. // type helpers work correctly.
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
const extractJsonData = (items: unknown[]): unknown[] =>
items.map(item =>
item && typeof item === 'object' && 'jsonData' in item
? (item as { jsonData: unknown }).jsonData
: item
);
const gpuNodes = useMemo(() => { const gpuNodes = useMemo(() => {
if (!allNodes) return []; if (!allNodes) return [];
return filterIntelGpuNodes(extractJsonData(allNodes as unknown[])); return filterIntelGpuNodes(extractJsonData(allNodes as unknown[]));
-46
View File
@@ -1,46 +0,0 @@
/**
* AppBarGpuBadge — compact Intel GPU health indicator in the Headlamp app bar.
*
* Shows a status chip in the top navigation bar summarising GPU plugin health.
* Hides itself when no Intel GPU plugin is detected.
*/
import { StatusLabel } from '@kinvolk/headlamp-plugin/lib/CommonComponents';
import React from 'react';
import { useIntelGpuContext } from '../api/IntelGpuDataContext';
export default function AppBarGpuBadge() {
const { pluginInstalled, gpuNodes, devicePlugins, loading } = useIntelGpuContext();
// Hide when loading or no plugin present
if (loading || !pluginInstalled) return null;
const hasUnhealthyPlugin = devicePlugins.some(p => {
const desired = p.status?.desiredNumberScheduled ?? 0;
const ready = p.status?.numberReady ?? 0;
const unavailable = p.status?.numberUnavailable ?? 0;
return (desired > 0 && ready < desired) || unavailable > 0;
});
const status = hasUnhealthyPlugin ? 'warning' : 'success';
const nodeCount = gpuNodes.length;
return (
<div
style={{
display: 'flex',
alignItems: 'center',
gap: '4px',
padding: '0 8px',
cursor: 'default',
}}
title={`Intel GPU: ${nodeCount} node${nodeCount !== 1 ? 's' : ''}`}
>
<StatusLabel status={status}>
<span style={{ fontSize: '11px', fontWeight: 600 }}>
Intel GPU{nodeCount > 0 ? ` · ${nodeCount}N` : ''}
</span>
</StatusLabel>
</div>
);
}
+31 -20
View File
@@ -194,30 +194,41 @@ export default function MetricsPage() {
const [metrics, setMetrics] = useState<GpuMetrics | null>(null); const [metrics, setMetrics] = useState<GpuMetrics | null>(null);
const [fetchError, setFetchError] = useState<string | null>(null); const [fetchError, setFetchError] = useState<string | null>(null);
const [fetching, setFetching] = useState(false); const [fetching, setFetching] = useState(false);
const [fetchSeq, setFetchSeq] = useState(0);
const doFetch = useCallback(async () => { const doFetch = useCallback(() => {
setFetching(true); setFetchSeq(s => s + 1);
setFetchError(null);
try {
const result = await fetchGpuMetrics();
setMetrics(result);
if (!result) {
setFetchError(
'Could not reach Prometheus. Ensure kube-prometheus-stack is installed in the monitoring namespace.'
);
}
} catch (e: unknown) {
setFetchError(e instanceof Error ? e.message : String(e));
} finally {
setFetching(false);
}
}, []); }, []);
useEffect(() => { useEffect(() => {
if (!ctxLoading) { if (ctxLoading) return;
void doFetch();
} let cancelled = false;
}, [ctxLoading, doFetch]); setFetching(true);
setFetchError(null);
fetchGpuMetrics()
.then(result => {
if (cancelled) return;
setMetrics(result);
if (!result) {
setFetchError(
'Could not reach Prometheus. Ensure kube-prometheus-stack is installed in the monitoring namespace.'
);
}
})
.catch((e: unknown) => {
if (cancelled) return;
setFetchError(e instanceof Error ? e.message : String(e));
})
.finally(() => {
if (!cancelled) setFetching(false);
});
return () => {
cancelled = true;
};
}, [ctxLoading, fetchSeq]);
if (ctxLoading) { if (ctxLoading) {
return <Loader title="Loading Intel GPU data..." />; return <Loader title="Loading Intel GPU data..." />;
+4 -4
View File
@@ -52,11 +52,11 @@ export default function NodeDetailSection({ resource }: NodeDetailSectionProps)
metadata: { name: string; labels?: Record<string, string> }; metadata: { name: string; labels?: Record<string, string> };
}; };
const nodeName = (node as { metadata: { name: string } }).metadata.name; const nodeName = node.metadata.name;
const capacity = getGpuResources((node as any).status?.capacity); const capacity = getGpuResources(node.status?.capacity);
const allocatable = getGpuResources((node as any).status?.allocatable); const allocatable = getGpuResources(node.status?.allocatable);
const gpuType = getNodeGpuType(node as any); const gpuType = getNodeGpuType(node);
// Find GPU pods scheduled on this node // Find GPU pods scheduled on this node
const podsOnNode = loading ? [] : gpuPods.filter(p => p.spec?.nodeName === nodeName); const podsOnNode = loading ? [] : gpuPods.filter(p => p.spec?.nodeName === nodeName);