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
+14 -9
View File
@@ -65,6 +65,18 @@ export function useIntelGpuContext(): IntelGpuContextValue {
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
// ---------------------------------------------------------------------------
@@ -129,8 +141,8 @@ export function IntelGpuDataProvider({ children }: { children: React.ReactNode }
try {
const list = await ApiProxy.request(url);
if (!cancelled && isKubeList(list)) {
const gpuPluinPods = filterIntelGpuPluginPods(list.items);
foundPluginPods.push(...gpuPluinPods);
const gpuPluginPods = filterIntelGpuPluginPods(list.items);
foundPluginPods.push(...gpuPluginPods);
}
} catch {
// Silently ignore — some selectors may not match
@@ -170,13 +182,6 @@ export function IntelGpuDataProvider({ children }: { children: React.ReactNode }
// 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(() => {
if (!allNodes) return [];
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 [fetchError, setFetchError] = useState<string | null>(null);
const [fetching, setFetching] = useState(false);
const [fetchSeq, setFetchSeq] = useState(0);
const doFetch = useCallback(async () => {
setFetching(true);
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);
}
const doFetch = useCallback(() => {
setFetchSeq(s => s + 1);
}, []);
useEffect(() => {
if (!ctxLoading) {
void doFetch();
}
}, [ctxLoading, doFetch]);
if (ctxLoading) return;
let cancelled = false;
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) {
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> };
};
const nodeName = (node as { metadata: { name: string } }).metadata.name;
const capacity = getGpuResources((node as any).status?.capacity);
const allocatable = getGpuResources((node as any).status?.allocatable);
const nodeName = node.metadata.name;
const capacity = getGpuResources(node.status?.capacity);
const allocatable = getGpuResources(node.status?.allocatable);
const gpuType = getNodeGpuType(node as any);
const gpuType = getNodeGpuType(node);
// Find GPU pods scheduled on this node
const podsOnNode = loading ? [] : gpuPods.filter(p => p.spec?.nodeName === nodeName);