tftsr-devops_investigation/src/components/Kubernetes/DeploymentList.tsx
Shaun Arman 7dfda91cd8
Some checks failed
PR Review Automation / review (pull_request) Has been cancelled
Test / rust-fmt-check (pull_request) Has been cancelled
Test / rust-clippy (pull_request) Has been cancelled
Test / frontend-typecheck (pull_request) Has been cancelled
Test / rust-tests (pull_request) Has been cancelled
Test / frontend-tests (pull_request) Has been cancelled
fix(kube): workload list actions use item.namespace not filter prop
Deployment/StatefulSet/DaemonSet action handlers were passing
namespace='all' to kubectl when All Namespaces was selected.
Actions now use the resource's own .namespace field for openEdit,
handleRestart, handleRollback, handleDelete, ScaleModal, and
EditResourceModal.

Adds 21 TDD tests in WorkloadListActions.test.tsx covering all
action handlers across DeploymentList, StatefulSetList, DaemonSetList,
ReplicaSetList, JobList, and CronJobList. Tests verify IPC calls
receive the item's actual namespace even when the filter prop is 'all'.
2026-06-08 22:02:00 -05:00

225 lines
7.7 KiB
TypeScript

import React, { useState } from "react";
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui";
import { Scale, RotateCcw, Undo2, Pencil, Trash2 } from "lucide-react";
import type { DeploymentInfo } from "@/lib/tauriCommands";
import {
scaleDeploymentCmd,
restartDeploymentCmd,
rollbackDeploymentCmd,
deleteResourceCmd,
getResourceYamlCmd,
} from "@/lib/tauriCommands";
import { ResourceActionMenu } from "./ResourceActionMenu";
import { ConfirmDeleteDialog } from "./ConfirmDeleteDialog";
import { ScaleModal } from "./ScaleModal";
import { EditResourceModal } from "./EditResourceModal";
interface DeploymentListProps {
deployments: DeploymentInfo[];
clusterId: string;
namespace: string;
onRefresh?: () => void;
}
type ActiveModal =
| { type: "scale"; deployment: DeploymentInfo }
| { type: "restart"; deployment: DeploymentInfo }
| { type: "rollback"; deployment: DeploymentInfo }
| { type: "edit"; deployment: DeploymentInfo; yaml: string }
| { type: "delete"; deployment: DeploymentInfo }
| null;
export function DeploymentList({ deployments, clusterId, namespace: _namespace, onRefresh }: DeploymentListProps) {
const [activeModal, setActiveModal] = useState<ActiveModal>(null);
const [isActing, setIsActing] = useState(false);
const [actionError, setActionError] = useState<string | null>(null);
const openEdit = async (deployment: DeploymentInfo) => {
setActionError(null);
try {
const yaml = await getResourceYamlCmd(clusterId, "deployments", deployment.namespace, deployment.name);
setActiveModal({ type: "edit", deployment, yaml });
} catch (err) {
setActionError(err instanceof Error ? err.message : String(err));
}
};
const handleRestart = async () => {
if (activeModal?.type !== "restart") return;
setIsActing(true);
try {
await restartDeploymentCmd(clusterId, activeModal.deployment.namespace, activeModal.deployment.name);
setActiveModal(null);
onRefresh?.();
} catch (err) {
setActionError(err instanceof Error ? err.message : String(err));
} finally {
setIsActing(false);
}
};
const handleRollback = async () => {
if (activeModal?.type !== "rollback") return;
setIsActing(true);
try {
await rollbackDeploymentCmd(clusterId, activeModal.deployment.namespace, activeModal.deployment.name);
setActiveModal(null);
onRefresh?.();
} catch (err) {
setActionError(err instanceof Error ? err.message : String(err));
} finally {
setIsActing(false);
}
};
const handleDelete = async () => {
if (activeModal?.type !== "delete") return;
setIsActing(true);
try {
await deleteResourceCmd(clusterId, "deployments", activeModal.deployment.namespace, activeModal.deployment.name);
setActiveModal(null);
onRefresh?.();
} finally {
setIsActing(false);
}
};
return (
<>
{actionError && (
<p className="mb-2 text-sm text-destructive">{actionError}</p>
)}
<div className="overflow-x-auto">
<Table>
<TableHeader>
<TableRow>
<TableHead>Name</TableHead>
<TableHead>Ready</TableHead>
<TableHead>Up-to-date</TableHead>
<TableHead>Available</TableHead>
<TableHead>Replicas</TableHead>
<TableHead>Age</TableHead>
<TableHead className="text-right">Actions</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{deployments.length === 0 ? (
<TableRow>
<TableCell colSpan={7} className="text-center text-muted-foreground">
No deployments found
</TableCell>
</TableRow>
) : (
deployments.map((deployment) => (
<TableRow key={deployment.name}>
<TableCell className="font-medium">{deployment.name}</TableCell>
<TableCell>{deployment.ready}</TableCell>
<TableCell>{deployment.up_to_date}</TableCell>
<TableCell>{deployment.available}</TableCell>
<TableCell>{deployment.replicas}</TableCell>
<TableCell className="text-muted-foreground">{deployment.age}</TableCell>
<TableCell className="text-right">
<ResourceActionMenu
actions={[
{
label: "Scale",
icon: Scale,
onClick: () => setActiveModal({ type: "scale", deployment }),
},
{
label: "Restart",
icon: RotateCcw,
onClick: () => setActiveModal({ type: "restart", deployment }),
},
{
label: "Rollback",
icon: Undo2,
onClick: () => setActiveModal({ type: "rollback", deployment }),
},
{
label: "Edit",
icon: Pencil,
onClick: () => openEdit(deployment),
},
{
label: "Delete",
icon: Trash2,
variant: "destructive",
onClick: () => setActiveModal({ type: "delete", deployment }),
},
]}
/>
</TableCell>
</TableRow>
))
)}
</TableBody>
</Table>
</div>
{activeModal?.type === "scale" && (
<ScaleModal
open
onOpenChange={(o) => { if (!o) setActiveModal(null); }}
resourceType="Deployment"
resourceName={activeModal.deployment.name}
currentReplicas={activeModal.deployment.replicas}
onScale={(replicas) =>
scaleDeploymentCmd(clusterId, activeModal.deployment.namespace, activeModal.deployment.name, replicas).then(() => {
setActiveModal(null);
onRefresh?.();
})
}
/>
)}
{activeModal?.type === "restart" && (
<ConfirmDeleteDialog
open
onOpenChange={(o) => { if (!o) setActiveModal(null); }}
resourceType="Deployment"
resourceName={activeModal.deployment.name}
isLoading={isActing}
onConfirm={handleRestart}
variant="delete"
/>
)}
{activeModal?.type === "rollback" && (
<ConfirmDeleteDialog
open
onOpenChange={(o) => { if (!o) setActiveModal(null); }}
resourceType="Deployment"
resourceName={activeModal.deployment.name}
isLoading={isActing}
onConfirm={handleRollback}
variant="delete"
/>
)}
{activeModal?.type === "edit" && (
<EditResourceModal
isOpen
clusterId={clusterId}
namespace={activeModal.deployment.namespace}
resourceType="deployments"
resourceName={activeModal.deployment.name}
initialYaml={activeModal.yaml}
onClose={() => { setActiveModal(null); onRefresh?.(); }}
/>
)}
{activeModal?.type === "delete" && (
<ConfirmDeleteDialog
open
onOpenChange={(o) => { if (!o) setActiveModal(null); }}
resourceType="Deployment"
resourceName={activeModal.deployment.name}
isLoading={isActing}
onConfirm={handleDelete}
/>
)}
</>
);
}