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>
This commit is contained in:
2026-02-18 16:37:56 -05:00
parent f2f3c3a87e
commit f1feb5c2f7
30 changed files with 3540 additions and 44 deletions
+73
View File
@@ -0,0 +1,73 @@
/**
* PVDetailSection — injected into Headlamp's PersistentVolume detail view.
*
* Shown only when the PV uses tns.csi.io as the CSI driver.
* Uses registerDetailsViewSection in index.tsx.
*/
import {
NameValueTable,
SectionBox,
} from '@kinvolk/headlamp-plugin/lib/CommonComponents';
import React from 'react';
import { formatProtocol, TNS_CSI_PROVISIONER } from '../api/k8s';
interface PVDetailSectionProps {
resource: {
kind?: string;
metadata?: { name?: string; namespace?: string };
spec?: {
csi?: {
driver?: string;
volumeHandle?: string;
volumeAttributes?: Record<string, string>;
};
storageClassName?: string;
capacity?: { storage?: string };
persistentVolumeReclaimPolicy?: string;
};
// KubeObject instance — raw JSON lives under jsonData
jsonData?: {
spec?: {
csi?: {
driver?: string;
volumeHandle?: string;
volumeAttributes?: Record<string, string>;
};
storageClassName?: string;
};
};
};
}
export default function PVDetailSection({ resource }: PVDetailSectionProps) {
// Extract from jsonData (KubeObject instance) or fall back to direct properties
const spec = resource?.jsonData?.spec ?? resource?.spec;
const csi = spec?.csi;
if (!csi || csi.driver !== TNS_CSI_PROVISIONER) {
return null;
}
const attrs = csi.volumeAttributes ?? {};
const protocol = formatProtocol(attrs['protocol']);
const otherAttrs = Object.entries(attrs).filter(
([k]) => !['protocol', 'server', 'pool'].includes(k)
);
return (
<SectionBox title="TNS-CSI Storage Details">
<NameValueTable
rows={[
{ name: 'Driver', value: TNS_CSI_PROVISIONER },
{ name: 'Protocol', value: protocol },
{ name: 'Server', value: attrs['server'] ?? '—' },
{ name: 'Pool', value: attrs['pool'] ?? '—' },
{ name: 'Volume Handle', value: csi.volumeHandle ?? '—' },
{ name: 'Storage Class', value: spec?.storageClassName ?? '—' },
...otherAttrs.map(([k, v]) => ({ name: k, value: v ?? '—' })),
]}
/>
</SectionBox>
);
}