Files
headlamp-rook-plugin/src/components/PVDetailSection.tsx
T
Chris Farhood 25175b65b8 feat: initial release of headlamp-rook-ceph-plugin v0.1.0
Headlamp plugin for Rook-Ceph cluster visibility.

Pages:
- Overview dashboard: CephCluster health, capacity bar, resource counts
  (block pools, filesystems, object stores, PVs, PVCs), daemon pod
  health summary, non-Bound PVC alerts
- Block Pools: CephBlockPool table with replication, failure domain,
  mirroring; slide-in detail panel
- Pods: all Rook-Ceph daemon pods grouped by role with ready/total counts

Native Headlamp integrations:
- StorageClass table: Rook Type, Pool, Cluster ID columns
- PV table: Rook Type, Pool columns
- PVC detail injection: driver, type, pool, volume handle
- PV detail injection: CSI volume attributes
- Pod detail injection: Ceph daemon role badge
- App bar badge: cluster health (HEALTH_OK/WARN/ERR), color-coded

API / architecture:
- src/api/k8s.ts: types + filters for ceph.rook.io/v1 CRDs; handles
  both default rook-ceph.* and custom-namespace provisioner strings
- src/api/RookCephDataContext.tsx: shared context provider; fetches
  CephCluster, CephBlockPool, CephFilesystem, CephObjectStore CRDs
  plus daemon pods via label selectors
- 37 unit tests (vitest + @testing-library/react)
- TypeScript strict mode, zero any types
- CI + release GitHub Actions workflows

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>
2026-02-18 16:55:39 -05:00

56 lines
1.8 KiB
TypeScript

/**
* PVDetailSection — injected into Headlamp's PV detail view.
*
* Shown only when the PV uses a Rook-Ceph CSI driver.
*/
import {
NameValueTable,
SectionBox,
} from '@kinvolk/headlamp-plugin/lib/CommonComponents';
import React from 'react';
import { formatStorageType, isRookCephPersistentVolume } from '../api/k8s';
interface PVDetailSectionProps {
resource: {
metadata?: { name?: string };
spec?: { csi?: { driver?: string; volumeHandle?: string; volumeAttributes?: Record<string, string> }; storageClassName?: string };
jsonData?: unknown;
};
}
export default function PVDetailSection({ resource }: PVDetailSectionProps) {
// Accept both KubeObject instances (jsonData) and plain objects
const raw =
resource.jsonData && typeof resource.jsonData === 'object'
? (resource.jsonData as typeof resource)
: resource;
const spec = raw.spec;
const driver = spec?.csi?.driver ?? '';
if (!isRookCephPersistentVolume({ metadata: raw.metadata ?? { name: '' }, spec: spec ?? {} })) {
return null;
}
const attrs = spec?.csi?.volumeAttributes ?? {};
const type = driver.includes('.rbd.') ? 'rbd' : driver.includes('.cephfs.') ? 'cephfs' : 'unknown';
return (
<SectionBox title="Rook-Ceph Volume Details">
<NameValueTable
rows={[
{ name: 'Driver', value: driver || '—' },
{ name: 'Type', value: formatStorageType(type) },
{ name: 'Volume Handle', value: spec?.csi?.volumeHandle ?? '—' },
{ name: 'Pool', value: attrs['pool'] ?? '—' },
{ name: 'Storage Class', value: spec?.storageClassName ?? '—' },
...Object.entries(attrs)
.filter(([k]) => k !== 'pool')
.map(([k, v]) => ({ name: k, value: v ?? '—' })),
]}
/>
</SectionBox>
);
}