e5d1fcb11c
Full plugin implementation with 6 pages, K8s resource filtering, Prometheus metrics parsing, kbench benchmark runner, and 67 unit tests. ## Pages - Overview: driver health, storage summary, protocol distribution chart, non-Bound PVC alerts - Storage Classes: tns-csi SC table with slide-in detail panel + protocol notes - Volumes: PV table with full CSI attribute detail panel - Snapshots: VolumeSnapshot CRDs with graceful degradation if not installed - Metrics: Prometheus text format parser + WebSocket/Volume/CSI operation cards - Benchmark: kbench Job+PVC lifecycle, FIO log parser, past benchmarks list ## API modules - k8s.ts: typed resource shapes, filtering helpers, formatting utilities - metrics.ts: Prometheus text format parser, tns-csi metric extraction - kbench.ts: Job/PVC manifests, lifecycle management, FIO summary parser - TnsCsiDataContext.tsx: shared React context with memoized filtered resources ## Quality - TypeScript strict mode, zero any, discriminated union for benchmark state - 67 tests passing (vitest + @testing-library/react) - registerDetailsViewSection injects TNS-CSI details on PVC pages - Graceful degradation for missing CSIDriver and VolumeSnapshot CRDs 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>
68 lines
2.1 KiB
TypeScript
68 lines
2.1 KiB
TypeScript
/**
|
|
* PVCDetailSection — injected into Headlamp's PVC detail view.
|
|
*
|
|
* Shown only when the bound PV uses tns.csi.io as the CSI driver.
|
|
* Uses registerDetailsViewSection in index.tsx.
|
|
*/
|
|
|
|
import {
|
|
NameValueTable,
|
|
SectionBox,
|
|
} from '@kinvolk/headlamp-plugin/lib/CommonComponents';
|
|
import React from 'react';
|
|
import { useTnsCsiContext } from '../api/TnsCsiDataContext';
|
|
import { findBoundPv, formatProtocol } from '../api/k8s';
|
|
|
|
interface PVCDetailSectionProps {
|
|
resource: {
|
|
metadata?: { name?: string; namespace?: string };
|
|
spec?: { volumeName?: string; storageClassName?: string };
|
|
};
|
|
}
|
|
|
|
export default function PVCDetailSection({ resource }: PVCDetailSectionProps) {
|
|
const { persistentVolumes, persistentVolumeClaims, loading } = useTnsCsiContext();
|
|
|
|
if (loading) return null;
|
|
|
|
// Find this PVC in our filtered list
|
|
const pvcName = resource.metadata?.name;
|
|
const pvcNamespace = resource.metadata?.namespace;
|
|
const matchedPvc = persistentVolumeClaims.find(
|
|
pvc => pvc.metadata.name === pvcName && pvc.metadata.namespace === pvcNamespace
|
|
);
|
|
|
|
if (!matchedPvc) {
|
|
// Not a tns-csi PVC — render nothing
|
|
return null;
|
|
}
|
|
|
|
const boundPv = findBoundPv(matchedPvc, persistentVolumes);
|
|
if (!boundPv) return null;
|
|
|
|
const attrs = boundPv.spec.csi?.volumeAttributes ?? {};
|
|
const protocol = formatProtocol(attrs['protocol']);
|
|
|
|
return (
|
|
<SectionBox title="TNS-CSI Storage Details">
|
|
<NameValueTable
|
|
rows={[
|
|
{ name: 'Driver', value: 'tns.csi.io' },
|
|
{ name: 'Protocol', value: protocol },
|
|
{ name: 'Server', value: attrs['server'] ?? '—' },
|
|
{ name: 'Storage Class', value: boundPv.spec.storageClassName ?? '—' },
|
|
{ name: 'Volume Handle', value: boundPv.spec.csi?.volumeHandle ?? '—' },
|
|
...(Object.entries(attrs)
|
|
.filter(([k]) => !['protocol', 'server'].includes(k))
|
|
.map(([k, v]) => ({ name: k, value: v ?? '—' }))
|
|
),
|
|
{
|
|
name: 'PV Name',
|
|
value: boundPv.metadata.name,
|
|
},
|
|
]}
|
|
/>
|
|
</SectionBox>
|
|
);
|
|
}
|