- Register AppBarClusterBadge via registerAppBarAction (was dead code) - Add Rook 1.12+ CSI pod labels to CephPodDetailSection alongside legacy labels - Add sidebar entries for Storage Classes and Volumes pages - Add role="dialog", aria-modal, aria-labelledby, and Escape key to all detail drawers - Replace hardcoded hex colors with CSS custom properties for dark/light theme compat - Remove duplicate parseStorageToBytes from OverviewPage (import from k8s.ts) - Add endpoints field to CephObjectStoreStatus interface (remove unsafe cast) - Use ROOK_CEPH_API_GROUP/VERSION constants in API URL construction - Hoist extractJsonData to module level - Remove dead extractPoolFromVolumeHandle function - Fix redundant storageClasses.length guard in OverviewPage - Fix lint indent warnings - Update CLAUDE.md and CHANGELOG.md Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
4.6 KiB
CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
Project
Headlamp plugin for Rook-Ceph cluster visibility.
- Plugin name:
rook - Rook-Ceph API group:
ceph.rook.io/v1 - Default namespace:
rook-ceph - Reference plugin:
../headlamp-tns-csi-plugin
Commands
npm start # dev server with hot reload
npm run build # production build
npm run package # package for headlamp
npm run tsc # TypeScript type check (no emit)
npm run lint # ESLint
npm run lint:fix # ESLint with auto-fix
npm run format # Prettier write
npm run format:check # Prettier check
npm test # vitest run (all tests)
npm run test:watch # vitest watch mode
npx vitest run src/api/k8s.test.ts # run a single test file
All tests and tsc must pass before committing.
Architecture
src/
├── index.tsx # Plugin entry: registerRoute, registerSidebarEntry, registerAppBarAction, etc.
├── api/
│ ├── k8s.ts # Types + filtering helpers (ceph.rook.io)
│ └── RookCephDataContext.tsx # Shared React context provider
└── components/
├── OverviewPage.tsx
├── BlockPoolsPage.tsx
├── StorageClassesPage.tsx
├── VolumesPage.tsx
├── PodsPage.tsx
├── FilesystemsPage.tsx
├── ObjectStoresPage.tsx
├── ClusterStatusCard.tsx
├── AppBarClusterBadge.tsx # Cluster health badge in Headlamp top nav bar
├── PVCDetailSection.tsx # Injected into Headlamp PVC detail view
├── PVDetailSection.tsx # Injected into Headlamp PV detail view
├── CephPodDetailSection.tsx # Injected into Headlamp Pod detail view
└── integrations/
└── StorageClassColumns.tsx # Column processors for SC + PV tables
Data flow
RookCephDataContext.tsx uses two fetching strategies:
-
Headlamp hooks (
K8s.ResourceClasses.*.useList()) — for StorageClasses, PVs, PVCs. These return HeadlampKubeObjectclass instances whose raw JSON is stored under.jsonData. TheextractJsonData()helper in the context provider unwraps them to plain objects before passing to the type-guard filters ink8s.ts. -
ApiProxy.request()— for Rook CRDs (cephclusters,cephblockpools,cephfilesystems,cephobjectstores) and daemon pods, since these aren't in Headlamp's built-in resource classes. Fetched in a singleuseEffectkeyed onrefreshKey.
All pages consume data exclusively via useRookCephContext(). The provider is re-wrapped per route and per detail-section registration in index.tsx.
Key constants (src/api/k8s.ts)
- Namespace:
rook-ceph - API group:
ceph.rook.io/v1 - RBD provisioner:
rook-ceph.rbd.csi.ceph.com - CephFS provisioner:
rook-ceph.cephfs.csi.ceph.com - Custom namespace provisioners: any string ending in
.rbd.csi.ceph.comor.cephfs.csi.ceph.com - Pod selectors:
app=rook-ceph-operator,app=rook-ceph-mon,app=rook-ceph-osd,app=rook-ceph-mgr,app=rook-ceph-mds,app=rook-ceph-rgw - CSI pod selectors (Rook 1.12+):
app=rook-ceph.rbd.csi.ceph.com-ctrlplugin,app=rook-ceph.cephfs.csi.ceph.com-ctrlplugin - CSI pod selectors (legacy):
app=csi-rbdplugin-provisioner,app=csi-cephfsplugin-provisioner,app=csi-rbdplugin,app=csi-cephfsplugin
Code conventions
- Functional React components only — no class components
- All imports from
@kinvolk/headlamp-plugin/liband@kinvolk/headlamp-plugin/lib/CommonComponents - No additional UI libraries (no MUI direct imports, no Ant Design, etc.)
- TypeScript strict mode — no
any, useunknown+ type guards at API boundaries - Context provider (
RookCephDataProvider) wraps each route component inindex.tsx - Tests: vitest + @testing-library/react, mock with
vi.mock('@kinvolk/headlamp-plugin/lib', ...) vitest.setup.tsprovides a spec-compliantlocalStorageshim for Node 22+ compatibility
Testing
Mock pattern for headlamp APIs:
vi.mock('@kinvolk/headlamp-plugin/lib', () => ({
ApiProxy: { request: vi.fn().mockResolvedValue({ items: [] }) },
K8s: {
ResourceClasses: {
StorageClass: { useList: vi.fn(() => [[], null]) },
PersistentVolume: { useList: vi.fn(() => [[], null]) },
PersistentVolumeClaim: { useList: vi.fn(() => [[], null]) },
},
},
}));
The mock import must appear before the module under test is imported (see RookCephDataContext.test.tsx).