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>
This commit is contained in:
@@ -0,0 +1,62 @@
|
||||
/**
|
||||
* PVCDetailSection — injected into Headlamp's PVC detail view.
|
||||
*
|
||||
* Shown only when the bound PV uses a Rook-Ceph CSI driver.
|
||||
* Uses registerDetailsViewSection in index.tsx.
|
||||
*/
|
||||
|
||||
import {
|
||||
NameValueTable,
|
||||
SectionBox,
|
||||
} from '@kinvolk/headlamp-plugin/lib/CommonComponents';
|
||||
import React from 'react';
|
||||
import { useRookCephContext } from '../api/RookCephDataContext';
|
||||
import { findBoundPv, formatStorageType, storageClassType } 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 } = useRookCephContext();
|
||||
|
||||
if (loading) return null;
|
||||
|
||||
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) return null;
|
||||
|
||||
const boundPv = findBoundPv(matchedPvc, persistentVolumes);
|
||||
if (!boundPv) return null;
|
||||
|
||||
const attrs = boundPv.spec.csi?.volumeAttributes ?? {};
|
||||
|
||||
// Determine storage type from driver name
|
||||
const driver = boundPv.spec.csi?.driver ?? '';
|
||||
const type = driver.includes('.rbd.') ? 'rbd' : driver.includes('.cephfs.') ? 'cephfs' : 'unknown';
|
||||
|
||||
return (
|
||||
<SectionBox title="Rook-Ceph Storage Details">
|
||||
<NameValueTable
|
||||
rows={[
|
||||
{ name: 'Driver', value: driver || '—' },
|
||||
{ name: 'Type', value: formatStorageType(type) },
|
||||
{ name: 'Pool', value: attrs['pool'] ?? '—' },
|
||||
{ name: 'Storage Class', value: boundPv.spec.storageClassName ?? '—' },
|
||||
{ name: 'Volume Handle', value: boundPv.spec.csi?.volumeHandle ?? '—' },
|
||||
{ name: 'PV Name', value: boundPv.metadata.name },
|
||||
...Object.entries(attrs)
|
||||
.filter(([k]) => k !== 'pool')
|
||||
.map(([k, v]) => ({ name: k, value: v ?? '—' })),
|
||||
]}
|
||||
/>
|
||||
</SectionBox>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user