Files
headlamp-tns-csi-plugin/src/components/DriverPodDetailSection.tsx
T
Chris Farhood f1feb5c2f7 feat: native Headlamp integration, TrueNAS API, docs, and CI for v0.2.0
Native Headlamp integrations:
- registerResourceTableColumnsProcessor: add Protocol/Pool/Server columns to
  native StorageClass table and Protocol/Volume Handle to PV table
- registerDetailsViewSection: inject TNS-CSI section into PV detail pages
- registerDetailsViewSection: inject driver role/status into tns-csi Pod pages
- registerDetailsViewHeaderAction: Benchmark shortcut on StorageClass detail
- registerAppBarAction: driver health badge (N/Nc M/Mn, color-coded)
- Trim sidebar from 6 → 4 entries (Overview, Snapshots, Metrics, Benchmark)

TrueNAS API integration:
- src/api/truenas.ts: ConfigStore-backed settings, WebSocket JSON-RPC client
  for pool.query (auth.login_with_api_key + pool.query)
- src/components/TnsCsiSettings.tsx: API key + server override settings UI
  with connection test button
- TnsCsiDataContext: fetch real pool stats (size/allocated/free/status)
- OverviewPage: three-tier pool capacity display (real data → error → metrics
  fallback)

Documentation:
- README, CHANGELOG, CONTRIBUTING, SECURITY
- docs/: architecture, deployment (Helm), getting-started, user-guide,
  troubleshooting

CI:
- .github/workflows/ci.yaml: lint + type-check + test on PR/push
- .github/workflows/release.yaml: workflow_dispatch versioned release

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>
2026-02-18 16:37:56 -05:00

144 lines
4.7 KiB
TypeScript

/**
* DriverPodDetailSection — injected into Headlamp's Pod detail view.
*
* Shown only for tns-csi driver pods (identified by
* app.kubernetes.io/name=tns-csi-driver label). Returns null for all other pods.
* Uses registerDetailsViewSection in index.tsx.
*/
import {
NameValueTable,
SectionBox,
} from '@kinvolk/headlamp-plugin/lib/CommonComponents';
import React from 'react';
import { formatAge, isPodReady, getPodRestarts, TnsCsiPod } from '../api/k8s';
interface DriverPodDetailSectionProps {
resource: {
kind?: string;
metadata?: {
name?: string;
namespace?: string;
labels?: Record<string, string>;
creationTimestamp?: string;
};
spec?: { nodeName?: string };
status?: {
phase?: string;
conditions?: Array<{ type: string; status: string }>;
containerStatuses?: Array<{
name: string;
ready: boolean;
restartCount: number;
image?: string;
state?: {
running?: { startedAt?: string };
waiting?: { reason?: string };
terminated?: { exitCode?: number; reason?: string };
};
}>;
};
// KubeObject instance: raw JSON lives under jsonData;
// metadata here only exposes what the class getter provides (labels, creationTimestamp).
// The jsonData.metadata has the full shape.
jsonData?: {
metadata?: {
name?: string;
namespace?: string;
labels?: Record<string, string>;
creationTimestamp?: string;
};
spec?: { nodeName?: string };
status?: {
phase?: string;
conditions?: Array<{ type: string; status: string }>;
containerStatuses?: Array<{
name: string;
ready: boolean;
restartCount: number;
image?: string;
state?: {
running?: { startedAt?: string };
waiting?: { reason?: string };
terminated?: { exitCode?: number; reason?: string };
};
}>;
};
};
};
}
export default function DriverPodDetailSection({ resource }: DriverPodDetailSectionProps) {
// Extract from jsonData (KubeObject instance) or fall back to direct props.
// jsonData.metadata has the full shape including name/namespace; resource.metadata
// only exposes fields that the Headlamp class getter provides (labels, creationTimestamp).
const meta = (resource?.jsonData?.metadata ?? resource?.metadata) as {
name?: string;
namespace?: string;
labels?: Record<string, string>;
creationTimestamp?: string;
} | undefined;
const spec = resource?.jsonData?.spec ?? resource?.spec;
const status = resource?.jsonData?.status ?? resource?.status;
const labels = meta?.labels ?? {};
// Guard: only tns-csi driver pods
if (labels['app.kubernetes.io/name'] !== 'tns-csi-driver') {
return null;
}
const component = labels['app.kubernetes.io/component'] ?? 'unknown';
const roleLabel = component === 'controller' ? 'Controller' : component === 'node' ? 'Node' : component;
// Build a minimal pod shape that isPodReady / getPodRestarts can consume
const podShape: TnsCsiPod = {
metadata: {
name: meta?.name ?? '',
namespace: meta?.namespace,
creationTimestamp: meta?.creationTimestamp,
labels,
},
spec: { nodeName: spec?.nodeName },
status: status as TnsCsiPod['status'],
};
const ready = isPodReady(podShape);
const restarts = getPodRestarts(podShape);
const phase = status?.phase ?? '—';
const nodeName = spec?.nodeName ?? '—';
const age = formatAge(meta?.creationTimestamp);
// Container statuses
const containerStatuses = status?.containerStatuses ?? [];
const containerRows = containerStatuses.map(cs => {
let stateText = 'Unknown';
if (cs.state?.running) {
stateText = `Running since ${cs.state.running.startedAt ? formatAge(cs.state.running.startedAt) : '?'} ago`;
} else if (cs.state?.waiting) {
stateText = `Waiting: ${cs.state.waiting.reason ?? 'unknown'}`;
} else if (cs.state?.terminated) {
stateText = `Terminated (exit ${cs.state.terminated.exitCode ?? '?'}): ${cs.state.terminated.reason ?? ''}`;
}
return {
name: cs.name,
value: `${cs.ready ? '✓ Ready' : '✗ Not Ready'}${stateText}${cs.restartCount} restart(s)`,
};
});
return (
<SectionBox title="TNS-CSI Driver Info">
<NameValueTable
rows={[
{ name: 'Role', value: roleLabel },
{ name: 'Phase', value: phase },
{ name: 'Ready', value: ready ? 'Yes' : 'No' },
{ name: 'Restarts', value: String(restarts) },
{ name: 'Node', value: nodeName },
{ name: 'Age', value: age },
...containerRows,
]}
/>
</SectionBox>
);
}