diff --git a/CHANGELOG.md b/CHANGELOG.md index 250fcda..8ea519d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [0.2.1] - 2026-02-19 + +### Fixed + +- **Duplicate columns** — Protocol and Pool columns on mixed-driver clusters (rook-ceph + tns-csi) are now merged into a single shared column rather than duplicated; whichever plugin loads first owns the column and the second merges into it + +### Changed + +- **Sidebar label** — top-level navigation entry renamed from `Rook-Ceph` to `Rook` + ## [0.2.0] - 2026-02-19 ### Changed @@ -56,7 +66,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - TypeScript strict mode with zero `any` types - ESLint + Prettier code quality tooling -[Unreleased]: https://github.com/cpfarhood/headlamp-rook-plugin/compare/v0.2.0...HEAD +[Unreleased]: https://github.com/cpfarhood/headlamp-rook-plugin/compare/v0.2.1...HEAD +[0.2.1]: https://github.com/cpfarhood/headlamp-rook-plugin/compare/v0.2.0...v0.2.1 [0.2.0]: https://github.com/cpfarhood/headlamp-rook-plugin/compare/v0.1.3...v0.2.0 [0.1.3]: https://github.com/cpfarhood/headlamp-rook-plugin/compare/v0.1.2...v0.1.3 [0.1.2]: https://github.com/cpfarhood/headlamp-rook-plugin/compare/v0.1.1...v0.1.2 diff --git a/artifacthub-pkg.yml b/artifacthub-pkg.yml index a1e6b93..10ebbbe 100644 --- a/artifacthub-pkg.yml +++ b/artifacthub-pkg.yml @@ -1,4 +1,4 @@ -version: "0.2.0" +version: "0.2.1" name: headlamp-rook-plugin displayName: Rook Plugin createdAt: "2026-02-18T00:00:00Z" @@ -22,8 +22,14 @@ maintainers: email: privilegedescalation@users.noreply.github.com provider: name: privilegedescalation +changes: + - kind: fixed + description: "Duplicate Protocol/Pool columns on mixed-driver clusters now merged into a single shared column" + - kind: changed + description: "Sidebar navigation label renamed from Rook-Ceph to Rook" + annotations: - headlamp/plugin/archive-url: "https://github.com/privilegedescalation/headlamp-rook-plugin/releases/download/v0.2.0/headlamp-rook-plugin-0.2.0.tar.gz" - headlamp/plugin/archive-checksum: "sha256:2ef2efd810a512c13914a9ce74af23f6ec8ab0937d30cac1593a42dce5e9fe17" + headlamp/plugin/archive-url: "https://github.com/privilegedescalation/headlamp-rook-plugin/releases/download/v0.2.1/headlamp-rook-plugin-0.2.1.tar.gz" + headlamp/plugin/archive-checksum: "sha256:6cfe751bc06cf77cf1b96013fdb83d4e1db9ac3793e9aca82316546debd3e846" headlamp/plugin/distro-compat: "" headlamp/plugin/version-compat: ">=0.20" diff --git a/package.json b/package.json index 56e0d57..4dfc2f9 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "headlamp-rook-plugin", - "version": "0.2.0", + "version": "0.2.1", "description": "Headlamp plugin for Rook-Ceph cluster visibility and CSI driver monitoring", "repository": { "type": "git", diff --git a/src/index.tsx b/src/index.tsx index 7f269d8..a5c3967 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -32,7 +32,7 @@ import VolumesPage from './components/VolumesPage'; registerSidebarEntry({ parent: null, name: 'rook-ceph', - label: 'Rook-Ceph', + label: 'Rook', url: '/rook-ceph', icon: 'mdi:database-cog', }); @@ -202,12 +202,40 @@ registerDetailsViewSection(({ resource }) => { // Table column processors — native StorageClass and PV tables // --------------------------------------------------------------------------- +// Merges incoming columns into existing ones by label. +// If a column with the same label already exists, the incoming getValue/render +// takes priority and falls back to the existing one (for mixed-driver tables). +function mergeColumns( + existing: T[], + incoming: Array<{ label: string; getValue: (r: unknown) => unknown; render: (r: unknown) => React.ReactNode }> +): T[] { + type ObjCol = { label: string; getValue: (r: unknown) => unknown; render: (r: unknown) => React.ReactNode }; + const isObjCol = (c: unknown): c is ObjCol => + typeof c === 'object' && c !== null && 'label' in c; + const result = [...existing]; + const toAppend: typeof incoming = []; + for (const col of incoming) { + const idx = result.findIndex(c => isObjCol(c) && (c as ObjCol).label === col.label); + if (idx !== -1) { + const prev = result[idx] as ObjCol; + result[idx] = { + label: col.label, + getValue: (r: unknown) => col.getValue(r) ?? prev.getValue(r), + render: (r: unknown) => col.getValue(r) !== null ? col.render(r) : prev.render(r), + } as unknown as T; + } else { + toAppend.push(col); + } + } + return [...result, ...(toAppend as unknown as T[])]; +} + registerResourceTableColumnsProcessor(({ id, columns }) => { if (id === 'headlamp-storageclasses') { - return [...columns, ...buildStorageClassColumns()]; + return mergeColumns(columns, buildStorageClassColumns()); } if (id === 'headlamp-persistentvolumes') { - return [...columns, ...buildPVColumns()]; + return mergeColumns(columns, buildPVColumns()); } return columns; });