fix: register AppBarClusterBadge, fix CSI label mismatch, improve accessibility and theme support

- 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>
This commit is contained in:
DevContainer User
2026-03-04 12:55:37 +00:00
parent fea6df6719
commit 62c24e3857
14 changed files with 185 additions and 100 deletions
+4 -4
View File
@@ -15,13 +15,13 @@ import { useRookCephContext } from '../api/RookCephDataContext';
function getHealthColor(health: string | undefined): string {
switch (health) {
case 'HEALTH_OK':
return '#4caf50';
return 'var(--mui-palette-success-main, #4caf50)';
case 'HEALTH_WARN':
return '#ff9800';
return 'var(--mui-palette-warning-main, #ff9800)';
case 'HEALTH_ERR':
return '#f44336';
return 'var(--mui-palette-error-main, #f44336)';
default:
return '#9e9e9e';
return 'var(--mui-palette-action-disabled, #9e9e9e)';
}
}
+7 -1
View File
@@ -17,6 +17,12 @@ import { useRookCephContext } from '../api/RookCephDataContext';
function BlockPoolDetail({ pool, onClose }: { pool: CephBlockPool; onClose: () => void }) {
return (
<div
role="dialog"
aria-modal="true"
aria-labelledby="drawer-title-blockpool"
onKeyDown={e => {
if (e.key === 'Escape') onClose();
}}
style={{
position: 'fixed',
top: 0,
@@ -38,7 +44,7 @@ function BlockPoolDetail({ pool, onClose }: { pool: CephBlockPool; onClose: () =
marginBottom: '16px',
}}
>
<strong>{pool.metadata.name}</strong>
<strong id="drawer-title-blockpool">{pool.metadata.name}</strong>
<button
onClick={onClose}
aria-label="Close"
+6
View File
@@ -47,10 +47,14 @@ const ROOK_APP_LABELS = new Set([
'rook-ceph-mgr',
'rook-ceph-mds',
'rook-ceph-rgw',
// Legacy CSI labels (pre-Rook 1.12)
'csi-rbdplugin-provisioner',
'csi-cephfsplugin-provisioner',
'csi-rbdplugin',
'csi-cephfsplugin',
// New CSI labels (Rook 1.12+)
'rook-ceph.rbd.csi.ceph.com-ctrlplugin',
'rook-ceph.cephfs.csi.ceph.com-ctrlplugin',
]);
const ROLE_LABELS: Record<string, string> = {
@@ -64,6 +68,8 @@ const ROLE_LABELS: Record<string, string> = {
'csi-cephfsplugin-provisioner': 'CSI CephFS Provisioner',
'csi-rbdplugin': 'CSI RBD Node Plugin',
'csi-cephfsplugin': 'CSI CephFS Node Plugin',
'rook-ceph.rbd.csi.ceph.com-ctrlplugin': 'CSI RBD Provisioner',
'rook-ceph.cephfs.csi.ceph.com-ctrlplugin': 'CSI CephFS Provisioner',
};
export default function CephPodDetailSection({ resource }: CephPodDetailSectionProps) {
+9 -2
View File
@@ -110,9 +110,16 @@ export default function ClusterStatusCard({
{
name: 'Used',
value: bytesUsed,
fill: usedPct > 80 ? '#f44336' : '#1976d2',
fill:
usedPct > 80
? 'var(--mui-palette-error-main, #f44336)'
: 'var(--mui-palette-primary-main, #1976d2)',
},
{
name: 'Free',
value: bytesAvail,
fill: 'var(--mui-palette-action-disabledBackground, #e0e0e0)',
},
{ name: 'Free', value: bytesAvail, fill: '#e0e0e0' },
]}
total={bytesTotal}
/>
+7 -1
View File
@@ -17,6 +17,12 @@ import { useRookCephContext } from '../api/RookCephDataContext';
function FilesystemDetail({ fs, onClose }: { fs: CephFilesystem; onClose: () => void }) {
return (
<div
role="dialog"
aria-modal="true"
aria-labelledby="drawer-title-filesystem"
onKeyDown={e => {
if (e.key === 'Escape') onClose();
}}
style={{
position: 'fixed',
top: 0,
@@ -38,7 +44,7 @@ function FilesystemDetail({ fs, onClose }: { fs: CephFilesystem; onClose: () =>
marginBottom: '16px',
}}
>
<strong>{fs.metadata.name}</strong>
<strong id="drawer-title-filesystem">{fs.metadata.name}</strong>
<button
onClick={onClose}
aria-label="Close"
+8 -4
View File
@@ -15,12 +15,16 @@ import { CephObjectStore, formatAge, phaseToStatus } from '../api/k8s';
import { useRookCephContext } from '../api/RookCephDataContext';
function ObjectStoreDetail({ store, onClose }: { store: CephObjectStore; onClose: () => void }) {
const endpoints = (store.status as unknown as Record<string, unknown>)?.endpoints as
| { insecure?: string[]; secure?: string[] }
| undefined;
const endpoints = store.status?.endpoints;
return (
<div
role="dialog"
aria-modal="true"
aria-labelledby="drawer-title-objectstore"
onKeyDown={e => {
if (e.key === 'Escape') onClose();
}}
style={{
position: 'fixed',
top: 0,
@@ -42,7 +46,7 @@ function ObjectStoreDetail({ store, onClose }: { store: CephObjectStore; onClose
marginBottom: '16px',
}}
>
<strong>{store.metadata.name}</strong>
<strong id="drawer-title-objectstore">{store.metadata.name}</strong>
<button
onClick={onClose}
aria-label="Close"
+34 -50
View File
@@ -19,6 +19,7 @@ import {
formatAge,
formatBytes,
healthToStatus,
parseStorageToBytes,
phaseToStatus,
storageClassType,
} from '../api/k8s';
@@ -162,36 +163,40 @@ export default function OverviewPage() {
{/* Storage type distribution */}
{storageClasses.length > 0 && (
<SectionBox title="Storage Summary">
{storageClasses.length > 0 && (
<div style={{ marginBottom: '16px' }}>
<div
style={{
marginBottom: '8px',
fontSize: '14px',
color: 'var(--mui-palette-text-secondary)',
}}
>
StorageClass Type Distribution
</div>
<PercentageBar
data={[
...(rbdClasses.length > 0
? [{ name: 'Block (RBD)', value: rbdClasses.length, fill: '#1976d2' }]
: []),
...(cephfsClasses.length > 0
? [
{
name: 'Filesystem (CephFS)',
value: cephfsClasses.length,
fill: '#9c27b0',
},
]
: []),
]}
total={storageClasses.length}
/>
<div style={{ marginBottom: '16px' }}>
<div
style={{
marginBottom: '8px',
fontSize: '14px',
color: 'var(--mui-palette-text-secondary)',
}}
>
StorageClass Type Distribution
</div>
)}
<PercentageBar
data={[
...(rbdClasses.length > 0
? [
{
name: 'Block (RBD)',
value: rbdClasses.length,
fill: 'var(--mui-palette-primary-main, #1976d2)',
},
]
: []),
...(cephfsClasses.length > 0
? [
{
name: 'Filesystem (CephFS)',
value: cephfsClasses.length,
fill: 'var(--mui-palette-secondary-main, #9c27b0)',
},
]
: []),
]}
total={storageClasses.length}
/>
</div>
<NameValueTable
rows={[
{
@@ -334,24 +339,3 @@ export default function OverviewPage() {
</>
);
}
function parseStorageToBytes(storage: string): number {
const match = /^(\d+(?:\.\d+)?)\s*(Ki|Mi|Gi|Ti|Pi|K|M|G|T|P)?$/.exec(storage.trim());
if (!match) return 0;
const value = parseFloat(match[1]);
const suffix = match[2] ?? '';
const multipliers: Record<string, number> = {
'': 1,
K: 1e3,
Ki: 1024,
M: 1e6,
Mi: 1024 ** 2,
G: 1e9,
Gi: 1024 ** 3,
T: 1e12,
Ti: 1024 ** 4,
P: 1e15,
Pi: 1024 ** 5,
};
return value * (multipliers[suffix] ?? 1);
}
+7 -1
View File
@@ -26,6 +26,12 @@ function StorageClassDetail({
const type = storageClassType(sc);
return (
<div
role="dialog"
aria-modal="true"
aria-labelledby="drawer-title-storageclass"
onKeyDown={e => {
if (e.key === 'Escape') onClose();
}}
style={{
position: 'fixed',
top: 0,
@@ -47,7 +53,7 @@ function StorageClassDetail({
marginBottom: '16px',
}}
>
<strong>{sc.metadata.name}</strong>
<strong id="drawer-title-storageclass">{sc.metadata.name}</strong>
<button
onClick={onClose}
aria-label="Close"
+7 -1
View File
@@ -18,6 +18,12 @@ function PVDetail({ pv, onClose }: { pv: RookCephPersistentVolume; onClose: () =
const attrs = pv.spec.csi?.volumeAttributes ?? {};
return (
<div
role="dialog"
aria-modal="true"
aria-labelledby="drawer-title-pv"
onKeyDown={e => {
if (e.key === 'Escape') onClose();
}}
style={{
position: 'fixed',
top: 0,
@@ -39,7 +45,7 @@ function PVDetail({ pv, onClose }: { pv: RookCephPersistentVolume; onClose: () =
marginBottom: '16px',
}}
>
<strong>{pv.metadata.name}</strong>
<strong id="drawer-title-pv">{pv.metadata.name}</strong>
<button
onClick={onClose}
aria-label="Close"