mywork / src /components /deployment /DeploymentControls.tsx
DeeCeeXxx's picture
Upload 114 files
e9d5b7d verified
"use client";
import { Button } from "@/components/ui/button";
import {
AlertDialog,
AlertDialogAction,
AlertDialogCancel,
AlertDialogContent,
AlertDialogDescription,
AlertDialogFooter,
AlertDialogHeader,
AlertDialogTitle,
} from "@/components/ui/alert-dialog";
import { Play, StopCircle, RefreshCw, Loader2, AlertTriangle, Trash2 } from "lucide-react";
import { useToast } from "@/hooks/use-toast";
import { useState } from "react";
import { controlDeployment, deleteDeployment } from "@/lib/actions/deployment"; // Server Actions
import type { DeploymentStatus } from "@/lib/types";
import { useRouter } from "next/navigation";
interface DeploymentControlsProps {
deploymentId: string;
currentStatus: DeploymentStatus;
onStatusChange: (newStatus: DeploymentStatus) => void;
}
export function DeploymentControls({ deploymentId, currentStatus, onStatusChange }: DeploymentControlsProps) {
const { toast } = useToast();
const router = useRouter();
const [loadingAction, setLoadingAction] = useState<"start" | "stop" | "restart" | "delete" | null>(null);
const [actionToConfirm, setActionToConfirm] = useState<"start" | "stop" | "restart" | "delete" | null>(null);
const [isConfirmOpen, setIsConfirmOpen] = useState(false);
const handleOpenConfirmDialog = (action: "start" | "stop" | "restart" | "delete") => {
setActionToConfirm(action);
setIsConfirmOpen(true);
};
const handleConfirmAction = async () => {
if (!actionToConfirm) return;
setLoadingAction(actionToConfirm);
setIsConfirmOpen(false);
try {
let result;
if (actionToConfirm === "delete") {
result = await deleteDeployment(deploymentId);
if (result.success) {
toast({
title: "Deletion Successful",
description: result.message,
});
router.push('/dashboard'); // Redirect to dashboard after successful deletion
} else {
toast({
title: "Deletion Failed",
description: result.message,
variant: "destructive",
});
}
} else { // Handle start, stop, restart
result = await controlDeployment(deploymentId, actionToConfirm as "start" | "stop" | "restart");
if (result.success) {
toast({
title: "Action Successful",
description: result.message,
});
let newStatus: DeploymentStatus = currentStatus;
if (result.newStatus) {
newStatus = result.newStatus;
} else {
if (actionToConfirm === "start") newStatus = "deploying";
if (actionToConfirm === "stop") newStatus = "stopped";
if (actionToConfirm === "restart") newStatus = "deploying";
}
onStatusChange(newStatus);
} else {
toast({
title: "Action Failed",
description: result.message,
variant: "destructive",
});
}
}
} catch (error) {
toast({
title: "Error",
description: `An unexpected error occurred while performing ${actionToConfirm}.`,
variant: "destructive",
});
} finally {
setLoadingAction(null);
setActionToConfirm(null);
}
};
const canStart = currentStatus === 'stopped' || currentStatus === 'failed';
const canStop = currentStatus === 'succeeded' || currentStatus === 'deploying' || currentStatus === 'pending';
const canRestart = currentStatus === 'succeeded' || currentStatus === 'failed' || currentStatus === 'stopped';
// Deletion can be attempted in most states, Heroku API will handle specific restrictions if any.
const getDialogTexts = () => {
switch (actionToConfirm) {
case "start": return { title: "Start Deployment?", description: "Are you sure you want to start this deployment? This may incur costs or use resources.", confirmText: "Yes, Start"};
case "stop": return { title: "Stop Deployment?", description: "Are you sure you want to stop this deployment? The application will become unavailable.", confirmText: "Yes, Stop", variant: "destructive" as const};
case "restart": return { title: "Restart Deployment?", description: "Are you sure you want to restart this deployment? This will stop and then start the application.", confirmText: "Yes, Restart"};
case "delete": return { title: "Delete Deployment?", description: "This action is irreversible. The Heroku app and all its data will be permanently deleted. Are you sure?", confirmText: "Yes, Delete Permanently", variant: "destructive" as const};
default: return { title: "", description: "", confirmText: ""};
}
};
const dialogContent = getDialogTexts();
return (
<>
<div className="flex flex-wrap gap-2 pt-4 border-t mt-4">
<Button
onClick={() => handleOpenConfirmDialog("start")}
disabled={loadingAction !== null || !canStart}
variant="outline"
className="text-green-600 border-green-600 hover:bg-green-50 hover:text-green-700"
>
{loadingAction === "start" ? <Loader2 className="mr-2 h-4 w-4 animate-spin" /> : <Play className="mr-2 h-4 w-4" />}
Start
</Button>
<Button
onClick={() => handleOpenConfirmDialog("stop")}
disabled={loadingAction !== null || !canStop}
variant="outline"
className="text-orange-600 border-orange-600 hover:bg-orange-50 hover:text-orange-700" // Changed color for stop
>
{loadingAction === "stop" ? <Loader2 className="mr-2 h-4 w-4 animate-spin" /> : <StopCircle className="mr-2 h-4 w-4" />}
Stop
</Button>
<Button
onClick={() => handleOpenConfirmDialog("restart")}
disabled={loadingAction !== null || !canRestart}
variant="outline"
className="text-blue-600 border-blue-600 hover:bg-blue-50 hover:text-blue-700"
>
{loadingAction === "restart" ? <Loader2 className="mr-2 h-4 w-4 animate-spin" /> : <RefreshCw className="mr-2 h-4 w-4" />}
Restart
</Button>
<Button
onClick={() => handleOpenConfirmDialog("delete")}
disabled={loadingAction !== null}
variant="destructive" // Destructive variant for delete
className="ml-auto" // Pushes delete button to the right if space allows
>
{loadingAction === "delete" ? <Loader2 className="mr-2 h-4 w-4 animate-spin" /> : <Trash2 className="mr-2 h-4 w-4" />}
Delete
</Button>
</div>
<AlertDialog open={isConfirmOpen} onOpenChange={setIsConfirmOpen}>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle className="flex items-center">
{(actionToConfirm === "stop" || actionToConfirm === "delete") && <AlertTriangle className="mr-2 h-5 w-5 text-destructive" /> }
{dialogContent.title}
</AlertDialogTitle>
<AlertDialogDescription>
{dialogContent.description}
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel onClick={() => setActionToConfirm(null)}>Cancel</AlertDialogCancel>
<AlertDialogAction
onClick={handleConfirmAction}
className={dialogContent.variant === "destructive" ? "bg-destructive text-destructive-foreground hover:bg-destructive/90" : ""}
>
{loadingAction === actionToConfirm ? <Loader2 className="mr-2 h-4 w-4 animate-spin" /> : null}
{dialogContent.confirmText}
</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
</>
);
}