tftsr-devops_investigation/src/components/Kubernetes/PodDetail.tsx
Shaun Arman 3f4869af01
Some checks failed
PR Review Automation / review (pull_request) Has been cancelled
Test / frontend-typecheck (pull_request) Has been cancelled
Test / rust-clippy (pull_request) Has been cancelled
Test / frontend-tests (pull_request) Has been cancelled
Test / rust-fmt-check (pull_request) Has been cancelled
Test / rust-tests (pull_request) Has been cancelled
feat(kubernetes): implement Lens Desktop v5 feature-parity UI
Complete overhaul of the Kubernetes management page from a basic config
panel into a full Lens-style IDE shell with 26 resource types, real-time
data, and a comprehensive test suite.

Layout & navigation:
- Rewrite KubernetesPage as a Lens v5-style shell: collapsible sidebar
  (Workloads / Services & Networking / Config & Storage / Access Control /
  Cluster), top hotbar with cluster+namespace selectors, Ctrl+K command
  palette
- All 26 resource types now accessible via sidebar navigation (previously 5)

New resource types (Rust + TypeScript + React):
- StorageClasses, NetworkPolicies, ResourceQuotas, LimitRanges
- 4 new Tauri commands registered in generate_handler![]

Component implementations (replacing stubs with real IPC):
- Terminal: full xterm.js with multi-tab sessions and exec_pod IPC
- YamlEditor: Monaco editor with YAML syntax highlighting
- MetricsChart: recharts LineChart/BarChart
- ClusterOverview: live node/pod/deployment/namespace counts
- ClusterDetails: real kubeconfig + node data
- PodDetail, DeploymentDetail, ServiceDetail, ConfigMapDetail, SecretDetail:
  all connected to real IPC data, zero hardcoded values
- CreateResourceModal, EditResourceModal: wired to createResourceCmd /
  editResourceCmd
- RbacViewer: live data from 4 RBAC IPC commands
- RbacEditor: create roles/cluster-roles via YAML editor
- CommandPalette: 12 real navigation commands, keyboard nav

Dependencies added: xterm@5, xterm-addon-fit, xterm-addon-web-links,
@monaco-editor/react@4, recharts@2

Tooling:
- Replace eslint-plugin-react (incompatible with ESLint 10) with
  @eslint-react/eslint-plugin; fix eslint.config.js for flat config
- Fix pre-existing hoisting lint errors in Security.tsx, PortForwardForm.tsx
- Fix eventBus.ts: replace all `any` generics with `unknown`

Tests: 251 passing across 35 test files (was 94/19)
- 16 new test files covering all new and fixed components (TDD)
- npx tsc --noEmit: 0 errors
- cargo clippy -- -D warnings: 0 warnings
- cargo fmt --check: passes
- eslint src/ --max-warnings 0: 0 issues
2026-06-07 16:41:28 -05:00

199 lines
7.7 KiB
TypeScript

import React from "react";
import { Card, CardHeader, CardTitle, CardContent } from "@/components/ui";
import { Badge } from "@/components/ui";
import { Tabs, TabsList, TabsTrigger, TabsContent } from "@/components/ui";
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui";
import { Button } from "@/components/ui";
import { Copy, X } from "lucide-react";
import { Loader2 } from "lucide-react";
import { YamlEditor } from "./YamlEditor";
import { getPodLogsCmd } from "@/lib/tauriCommands";
import type { PodInfo } from "@/lib/tauriCommands";
interface PodDetailProps {
clusterId: string;
namespace: string;
pod: PodInfo;
onClose?: () => void;
}
export function PodDetail({ clusterId, namespace, pod, onClose }: PodDetailProps) {
const [activeTab, setActiveTab] = React.useState("overview");
const [selectedContainer, setSelectedContainer] = React.useState(pod.containers[0] ?? "");
const [logs, setLogs] = React.useState<string | null>(null);
const [logsLoading, setLogsLoading] = React.useState(false);
const [logsError, setLogsError] = React.useState<string | null>(null);
const fetchLogs = React.useCallback(
async (containerName: string) => {
if (!containerName) return;
setLogsLoading(true);
setLogsError(null);
setLogs(null);
try {
const response = await getPodLogsCmd(clusterId, namespace, pod.name, containerName);
setLogs(response.logs);
} catch (err) {
setLogsError(err instanceof Error ? err.message : String(err));
} finally {
setLogsLoading(false);
}
},
[clusterId, namespace, pod.name]
);
const handleTabChange = (tab: string) => {
setActiveTab(tab);
if (tab === "logs" && logs === null && !logsLoading && !logsError) {
void fetchLogs(selectedContainer);
}
};
const handleContainerChange = (e: React.ChangeEvent<HTMLSelectElement>) => {
const name = e.target.value;
setSelectedContainer(name);
void fetchLogs(name);
};
const copyLogs = () => {
if (logs) void navigator.clipboard.writeText(logs);
};
return (
<div className="h-full flex flex-col">
<div className="flex items-center justify-between mb-4">
<div className="flex items-center gap-2">
<h2 className="text-xl font-semibold">Pod: {pod.name}</h2>
<Badge variant="outline">{namespace}</Badge>
</div>
<Button variant="ghost" size="sm" onClick={onClose}>
<X className="w-4 h-4" />
</Button>
</div>
<Tabs value={activeTab} onValueChange={handleTabChange}>
<TabsList className="grid grid-cols-3 mb-4">
<TabsTrigger value="overview">Overview</TabsTrigger>
<TabsTrigger value="logs">Logs</TabsTrigger>
<TabsTrigger value="yaml">YAML</TabsTrigger>
</TabsList>
<div className="flex-1 overflow-hidden">
<TabsContent value="overview" className="h-full overflow-y-auto">
<div className="grid grid-cols-1 lg:grid-cols-2 gap-4">
<Card>
<CardHeader>
<CardTitle>Pod Information</CardTitle>
</CardHeader>
<CardContent className="space-y-2">
<div className="flex items-center justify-between">
<span className="text-sm text-muted-foreground">Name</span>
<span className="font-mono">{pod.name}</span>
</div>
<div className="flex items-center justify-between">
<span className="text-sm text-muted-foreground">Namespace</span>
<span className="font-mono">{namespace}</span>
</div>
<div className="flex items-center justify-between">
<span className="text-sm text-muted-foreground">Status</span>
<Badge variant={pod.status === "Running" ? "default" : "secondary"}>
{pod.status}
</Badge>
</div>
<div className="flex items-center justify-between">
<span className="text-sm text-muted-foreground">Ready</span>
<span className="font-mono">{pod.ready}</span>
</div>
<div className="flex items-center justify-between">
<span className="text-sm text-muted-foreground">Age</span>
<span className="text-sm">{pod.age}</span>
</div>
</CardContent>
</Card>
<Card>
<CardHeader>
<CardTitle>Containers</CardTitle>
</CardHeader>
<CardContent>
<Table>
<TableHeader>
<TableRow>
<TableHead>Name</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{pod.containers.map((c) => (
<TableRow key={c}>
<TableCell className="font-mono">{c}</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</CardContent>
</Card>
</div>
</TabsContent>
<TabsContent value="logs" className="h-full">
<Card className="h-full flex flex-col">
<CardHeader className="flex flex-row items-center justify-between">
<CardTitle>Container Logs</CardTitle>
<div className="flex items-center gap-2">
{pod.containers.length > 1 && (
<select
value={selectedContainer}
onChange={handleContainerChange}
className="text-sm border rounded px-2 py-1 bg-background"
>
{pod.containers.map((c) => (
<option key={c} value={c}>
{c}
</option>
))}
</select>
)}
<Button variant="outline" size="sm" onClick={copyLogs} disabled={!logs}>
<Copy className="w-4 h-4 mr-2" />
Copy
</Button>
</div>
</CardHeader>
<CardContent className="flex-1 bg-slate-900 rounded-md p-4 overflow-auto font-mono text-sm">
{logsLoading && (
<div
data-testid="logs-loading"
className="flex items-center gap-2 text-muted-foreground"
>
<Loader2 className="w-4 h-4 animate-spin" />
Loading logs
</div>
)}
{logsError && (
<div data-testid="logs-error" className="text-red-400">
Failed to load logs: {logsError}
</div>
)}
{!logsLoading && !logsError && logs !== null && (
<pre className="text-green-400 whitespace-pre-wrap break-words">{logs}</pre>
)}
{!logsLoading && !logsError && logs === null && (
<span className="text-muted-foreground">Select a container to view logs.</span>
)}
</CardContent>
</Card>
</TabsContent>
<TabsContent value="yaml" className="h-full">
<YamlEditor
readOnly
showControls={false}
content={JSON.stringify(pod, null, 2)}
/>
</TabsContent>
</div>
</Tabs>
</div>
);
}