62c24e3857
- 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>
132 lines
3.9 KiB
TypeScript
132 lines
3.9 KiB
TypeScript
/**
|
|
* CephPodDetailSection — injected into Headlamp's Pod detail view.
|
|
*
|
|
* Shown only for Rook-Ceph daemon pods (operator, mon, osd, mgr, csi).
|
|
* Guards on rook-ceph label presence.
|
|
*/
|
|
|
|
import {
|
|
NameValueTable,
|
|
SectionBox,
|
|
StatusLabel,
|
|
} from '@kinvolk/headlamp-plugin/lib/CommonComponents';
|
|
import React from 'react';
|
|
import { formatAge } from '../api/k8s';
|
|
|
|
interface CephPodDetailSectionProps {
|
|
resource: {
|
|
metadata?: {
|
|
name?: string;
|
|
namespace?: string;
|
|
labels?: Record<string, string>;
|
|
creationTimestamp?: string;
|
|
};
|
|
spec?: { nodeName?: string; containers?: Array<{ name: string; image?: string }> };
|
|
status?: {
|
|
phase?: string;
|
|
conditions?: Array<{ type: string; status: string }>;
|
|
containerStatuses?: Array<{
|
|
name: string;
|
|
ready: boolean;
|
|
restartCount: number;
|
|
state?: {
|
|
running?: { startedAt?: string };
|
|
waiting?: { reason?: string };
|
|
terminated?: { reason?: string; exitCode?: number };
|
|
};
|
|
}>;
|
|
};
|
|
jsonData?: unknown;
|
|
};
|
|
}
|
|
|
|
const ROOK_APP_LABELS = new Set([
|
|
'rook-ceph-operator',
|
|
'rook-ceph-mon',
|
|
'rook-ceph-osd',
|
|
'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> = {
|
|
'rook-ceph-operator': 'Operator',
|
|
'rook-ceph-mon': 'Monitor (MON)',
|
|
'rook-ceph-osd': 'OSD',
|
|
'rook-ceph-mgr': 'Manager (MGR)',
|
|
'rook-ceph-mds': 'MDS (CephFS)',
|
|
'rook-ceph-rgw': 'RGW (Object Gateway)',
|
|
'csi-rbdplugin-provisioner': 'CSI RBD Provisioner',
|
|
'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) {
|
|
const raw =
|
|
resource.jsonData && typeof resource.jsonData === 'object'
|
|
? (resource.jsonData as typeof resource)
|
|
: resource;
|
|
|
|
const labels = raw.metadata?.labels ?? {};
|
|
const appLabel = labels['app'] ?? '';
|
|
|
|
if (!ROOK_APP_LABELS.has(appLabel)) return null;
|
|
|
|
const role = ROLE_LABELS[appLabel] ?? appLabel;
|
|
const phase = raw.status?.phase ?? 'Unknown';
|
|
const isReady =
|
|
raw.status?.conditions?.some(c => c.type === 'Ready' && c.status === 'True') ?? false;
|
|
const restarts = raw.status?.containerStatuses?.reduce((s, c) => s + c.restartCount, 0) ?? 0;
|
|
|
|
const containerRows = (raw.status?.containerStatuses ?? []).map(cs => {
|
|
let stateStr = 'Unknown';
|
|
if (cs.state?.running) stateStr = 'Running';
|
|
else if (cs.state?.waiting) stateStr = `Waiting: ${cs.state.waiting.reason ?? ''}`;
|
|
else if (cs.state?.terminated)
|
|
stateStr = `Terminated: ${cs.state.terminated.reason ?? ''} (exit ${
|
|
cs.state.terminated.exitCode ?? ''
|
|
})`;
|
|
|
|
return {
|
|
name: cs.name,
|
|
value: (
|
|
<StatusLabel status={cs.ready ? 'success' : 'warning'}>
|
|
{stateStr} | Restarts: {cs.restartCount}
|
|
</StatusLabel>
|
|
),
|
|
};
|
|
});
|
|
|
|
return (
|
|
<SectionBox title="Rook-Ceph Daemon Info">
|
|
<NameValueTable
|
|
rows={[
|
|
{
|
|
name: 'Role',
|
|
value: <StatusLabel status="success">{role}</StatusLabel>,
|
|
},
|
|
{
|
|
name: 'Phase',
|
|
value: <StatusLabel status={isReady ? 'success' : 'error'}>{phase}</StatusLabel>,
|
|
},
|
|
{ name: 'Node', value: raw.spec?.nodeName ?? '—' },
|
|
{ name: 'Restarts', value: String(restarts) },
|
|
{ name: 'Age', value: formatAge(raw.metadata?.creationTimestamp) },
|
|
...containerRows,
|
|
]}
|
|
/>
|
|
</SectionBox>
|
|
);
|
|
}
|