f1feb5c2f7
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>
144 lines
4.7 KiB
TypeScript
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>
|
|
);
|
|
}
|