File size: 7,773 Bytes
e9d5b7d |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 |
"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>
</>
);
}
|