diff --git a/.claude/settings.local.json b/.claude/settings.local.json index 374b014..d5fe83d 100644 --- a/.claude/settings.local.json +++ b/.claude/settings.local.json @@ -3,7 +3,14 @@ "allow": [ "Bash(done)", "Bash(npm install:*)", - "Bash(git add:*)" + "Bash(git add:*)", + "Bash(git commit:*)", + "Bash(git push:*)", + "Bash(gh workflow:*)", + "Bash(gh run:*)", + "Bash(npm run:*)", + "Bash(npm ci:*)", + "Bash(npm test:*)" ] }, "enabledMcpjsonServers": [ diff --git a/src/api/IntelGpuDataContext.tsx b/src/api/IntelGpuDataContext.tsx index 62f4425..c947f91 100644 --- a/src/api/IntelGpuDataContext.tsx +++ b/src/api/IntelGpuDataContext.tsx @@ -116,9 +116,11 @@ export function IntelGpuDataProvider({ children }: { children: React.ReactNode } // Intel device plugins operator deployment `/api/v1/pods?labelSelector=${encodeURIComponent('app=intel-gpu-plugin')}`, // Alternative: by component label - `/api/v1/pods?labelSelector=${encodeURIComponent('app.kubernetes.io/name=intel-gpu-plugin')}`, + `/api/v1/pods?labelSelector=${encodeURIComponent( + 'app.kubernetes.io/name=intel-gpu-plugin' + )}`, // Intel device plugins from inteldeviceplugins-system namespace - `/api/v1/namespaces/inteldeviceplugins-system/pods`, + '/api/v1/namespaces/inteldeviceplugins-system/pods', ]; const foundPluginPods: IntelGpuPod[] = []; @@ -155,7 +157,9 @@ export function IntelGpuDataProvider({ children }: { children: React.ReactNode } } void fetchAsync(); - return () => { cancelled = true; }; + return () => { + cancelled = true; + }; }, [refreshKey]); // --------------------------------------------------------------------------- diff --git a/src/api/k8s.test.ts b/src/api/k8s.test.ts index 4a0e622..bba5be7 100644 --- a/src/api/k8s.test.ts +++ b/src/api/k8s.test.ts @@ -12,18 +12,18 @@ import { getNodeGpuCount, getNodeGpuType, getPodGpuRequests, + type GpuDevicePlugin, INTEL_GPU_NODE_LABEL, INTEL_GPU_RESOURCE, INTEL_GPU_XE_RESOURCE, + type IntelGpuNode, + type IntelGpuPod, isGpuRequestingPod, isIntelGpuNode, isKubeList, isNodeReady, pluginStatusText, pluginStatusToStatus, - type GpuDevicePlugin, - type IntelGpuNode, - type IntelGpuPod, } from './k8s'; // --------------------------------------------------------------------------- @@ -413,11 +413,7 @@ describe('formatGpuType', () => { // --------------------------------------------------------------------------- describe('pluginStatusToStatus', () => { - function makePlugin( - desired: number, - ready: number, - unavailable = 0 - ): GpuDevicePlugin { + function makePlugin(desired: number, ready: number, unavailable = 0): GpuDevicePlugin { return { apiVersion: 'deviceplugin.intel.com/v1', kind: 'GpuDevicePlugin', diff --git a/src/api/k8s.ts b/src/api/k8s.ts index 3b0ab2f..5b5ca51 100644 --- a/src/api/k8s.ts +++ b/src/api/k8s.ts @@ -28,8 +28,7 @@ export const INTEL_DISCRETE_GPU_NODE_ROLE = 'node-role.kubernetes.io/gpu'; export const INTEL_INTEGRATED_GPU_NODE_ROLE = 'node-role.kubernetes.io/igpu'; /** Label selector for Intel GPU device plugin DaemonSet pods */ -export const INTEL_GPU_PLUGIN_LABEL_SELECTOR = - 'app=intel-gpu-plugin'; +export const INTEL_GPU_PLUGIN_LABEL_SELECTOR = 'app=intel-gpu-plugin'; // --------------------------------------------------------------------------- // Generic Kubernetes object base shapes @@ -194,9 +193,12 @@ export function getNodeGpuType(node: IntelGpuNode): GpuType { export function formatGpuType(type: GpuType): string { switch (type) { - case 'discrete': return 'Discrete'; - case 'integrated': return 'Integrated'; - default: return 'Unknown'; + case 'discrete': + return 'Discrete'; + case 'integrated': + return 'Integrated'; + default: + return 'Unknown'; } } @@ -272,9 +274,11 @@ export function isIntelGpuPluginPod(pod: unknown): pod is IntelGpuPod { const meta = obj['metadata'] as Record | undefined; const labels = meta?.['labels'] as Record | undefined; if (!labels) return false; - return labels['app'] === 'intel-gpu-plugin' || - (labels['app.kubernetes.io/name'] === 'intel-gpu-plugin') || - (labels['component'] === 'intel-gpu-plugin'); + return ( + labels['app'] === 'intel-gpu-plugin' || + labels['app.kubernetes.io/name'] === 'intel-gpu-plugin' || + labels['component'] === 'intel-gpu-plugin' + ); } export function filterIntelGpuPluginPods(items: unknown[]): IntelGpuPod[] { @@ -284,10 +288,7 @@ export function filterIntelGpuPluginPods(items: unknown[]): IntelGpuPod[] { /** Get total GPU requests from a pod's containers */ export function getPodGpuRequests(pod: IntelGpuPod): Record { const totals: Record = {}; - const allContainers = [ - ...(pod.spec?.containers ?? []), - ...(pod.spec?.initContainers ?? []), - ]; + const allContainers = [...(pod.spec?.containers ?? []), ...(pod.spec?.initContainers ?? [])]; for (const c of allContainers) { const requests = c.resources?.requests ?? {}; for (const [key, value] of Object.entries(requests)) { @@ -300,15 +301,11 @@ export function getPodGpuRequests(pod: IntelGpuPod): Record { } export function isPodReady(pod: IntelGpuPod): boolean { - return ( - pod.status?.conditions?.some(c => c.type === 'Ready' && c.status === 'True') ?? false - ); + return pod.status?.conditions?.some(c => c.type === 'Ready' && c.status === 'True') ?? false; } export function getPodRestarts(pod: IntelGpuPod): number { - return ( - pod.status?.containerStatuses?.reduce((sum, c) => sum + c.restartCount, 0) ?? 0 - ); + return pod.status?.containerStatuses?.reduce((sum, c) => sum + c.restartCount, 0) ?? 0; } // --------------------------------------------------------------------------- @@ -330,9 +327,7 @@ export function isKubeList(value: unknown): value is KubeList { // --------------------------------------------------------------------------- export function isNodeReady(node: IntelGpuNode): boolean { - return ( - node.status?.conditions?.some(c => c.type === 'Ready' && c.status === 'True') ?? false - ); + return node.status?.conditions?.some(c => c.type === 'Ready' && c.status === 'True') ?? false; } // --------------------------------------------------------------------------- @@ -359,11 +354,11 @@ export function formatAge(timestamp: string | undefined): string { export function formatGpuResourceName(resourceKey: string): string { const name = resourceKey.replace(INTEL_GPU_RESOURCE_PREFIX, ''); const map: Record = { - 'i915': 'GPU (i915)', - 'xe': 'GPU (Xe)', - 'millicores': 'GPU Millicores', + i915: 'GPU (i915)', + xe: 'GPU (Xe)', + millicores: 'GPU Millicores', 'memory.max': 'GPU Memory (max)', - 'tiles': 'GPU Tiles', + tiles: 'GPU Tiles', }; return map[name] ?? name; } @@ -372,9 +367,7 @@ export function formatGpuResourceName(resourceKey: string): string { // Status helpers // --------------------------------------------------------------------------- -export function pluginStatusToStatus( - plugin: GpuDevicePlugin -): 'success' | 'warning' | 'error' { +export function pluginStatusToStatus(plugin: GpuDevicePlugin): 'success' | 'warning' | 'error' { const desired = plugin.status?.desiredNumberScheduled ?? 0; const ready = plugin.status?.numberReady ?? 0; const unavailable = plugin.status?.numberUnavailable ?? 0; diff --git a/src/api/metrics.ts b/src/api/metrics.ts index f51ca24..3e07628 100644 --- a/src/api/metrics.ts +++ b/src/api/metrics.ts @@ -64,14 +64,11 @@ const PROMETHEUS_SERVICES = [ { namespace: 'monitoring', service: 'prometheus', port: '9090' }, ]; -async function queryPrometheus( - query: string, - prometheusPath: string -): Promise { +async function queryPrometheus(query: string, prometheusPath: string): Promise { const encoded = encodeURIComponent(query); const path = `${prometheusPath}/api/v1/query?query=${encoded}`; - const raw = await ApiProxy.request(path, { method: 'GET' }) as PrometheusResponse; + const raw = (await ApiProxy.request(path, { method: 'GET' })) as PrometheusResponse; if (raw?.status !== 'success') return []; return raw.data?.result ?? []; @@ -81,7 +78,9 @@ async function findPrometheusPath(): Promise { for (const { namespace, service, port } of PROMETHEUS_SERVICES) { const basePath = `/api/v1/namespaces/${namespace}/services/${service}:${port}/proxy`; try { - const raw = await ApiProxy.request(`${basePath}/api/v1/query?query=1`, { method: 'GET' }) as PrometheusResponse; + const raw = (await ApiProxy.request(`${basePath}/api/v1/query?query=1`, { + method: 'GET', + })) as PrometheusResponse; if (raw?.status === 'success') return basePath; } catch { // try next diff --git a/src/components/DevicePluginsPage.tsx b/src/components/DevicePluginsPage.tsx index 3b9d731..5d3a5b1 100644 --- a/src/components/DevicePluginsPage.tsx +++ b/src/components/DevicePluginsPage.tsx @@ -18,8 +18,7 @@ import { useIntelGpuContext } from '../api/IntelGpuDataContext'; import { formatAge, isPodReady, pluginStatusText, pluginStatusToStatus } from '../api/k8s'; export default function DevicePluginsPage() { - const { devicePlugins, pluginPods, crdAvailable, loading, error, refresh } = - useIntelGpuContext(); + const { devicePlugins, pluginPods, crdAvailable, loading, error, refresh } = useIntelGpuContext(); if (loading) { return ; @@ -27,7 +26,14 @@ export default function DevicePluginsPage() { return ( <> -
+