"use client"; import { useEffect, useState } from "react"; import { api } from "@/lib/api"; import DashboardLayout from "@/components/DashboardLayout"; import { Card, CardContent } from "@/components/ui/card"; import { Badge } from "@/components/ui/badge"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; import { Alert, AlertDescription } from "@/components/ui/alert"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from "@/components/ui/select"; import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, DialogTrigger, } from "@/components/ui/dialog"; import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table"; import { Rocket, Check, Plus, AlertTriangle } from "lucide-react"; interface Deployment { id: string; name: string; description: string | null; jobType: string; status: string; createdByName: string | null; totalTargets: number; completedTargets: number; failedTargets: number; createdAt: string; completedAt: string | null; } interface Device { id: string; name: string; status: string; } const statusVariant: Record = { draft: "outline", pending_approval: "warning", approved: "info", running: "warning", completed: "success", partially_completed: "warning", failed: "destructive", cancelled: "secondary", }; const JOB_TYPES = [ { value: "install_package", label: "Install Package", paramKey: "package_name", paramLabel: "Package Name" }, { value: "update_package", label: "Update Package", paramKey: "package_name", paramLabel: "Package Name" }, { value: "remove_package", label: "Remove Package", paramKey: "package_name", paramLabel: "Package Name" }, { value: "start_service", label: "Start Service", paramKey: "service_name", paramLabel: "Service Name" }, { value: "stop_service", label: "Stop Service", paramKey: "service_name", paramLabel: "Service Name" }, { value: "restart_service", label: "Restart Service", paramKey: "service_name", paramLabel: "Service Name" }, { value: "check_service_status", label: "Check Service Status", paramKey: "service_name", paramLabel: "Service Name" }, { value: "reboot_system", label: "Reboot System", paramKey: null, paramLabel: null }, ]; export default function DeploymentsPage() { const [deployments, setDeployments] = useState([]); const [total, setTotal] = useState(0); const [devices, setDevices] = useState([]); const [showCreate, setShowCreate] = useState(false); const [error, setError] = useState(""); const [form, setForm] = useState({ name: "", description: "", jobType: "install_package", packageName: "", serviceName: "", autoApprove: false, }); const [selectedDevices, setSelectedDevices] = useState([]); const loadDeployments = () => { api.get("/v1/deployments").then((res) => { if (res.success && res.data) { setDeployments(res.data); setTotal(res.meta?.total || 0); } }); }; const loadDevices = () => { api.get("/v1/devices").then((res) => { if (res.success && res.data) setDevices(res.data); }); }; useEffect(() => { loadDeployments(); loadDevices(); }, []); const selectedJobType = JOB_TYPES.find((j) => j.value === form.jobType); const needsPackageName = selectedJobType?.paramKey === "package_name"; const needsServiceName = selectedJobType?.paramKey === "service_name"; const handleCreate = async (e: React.FormEvent) => { e.preventDefault(); setError(""); if (selectedDevices.length === 0) { setError("Select at least one target device."); return; } const parameters: Record = {}; if (needsPackageName) { if (!form.packageName.trim()) { setError("Package name is required."); return; } parameters.package_name = form.packageName.trim(); } if (needsServiceName) { if (!form.serviceName.trim()) { setError("Service name is required."); return; } parameters.service_name = form.serviceName.trim(); } const res = await api.post("/v1/deployments", { name: form.name.trim(), description: form.description.trim() || undefined, jobType: form.jobType, parameters, deviceIds: selectedDevices, autoApprove: form.autoApprove, }); if (res.success) { setShowCreate(false); setForm({ name: "", description: "", jobType: "install_package", packageName: "", serviceName: "", autoApprove: false }); setSelectedDevices([]); loadDeployments(); } else { setError(res.error?.message || "Failed to create deployment"); } }; const handleApprove = async (id: string) => { await api.patch(`/v1/deployments/${id}/approve`); loadDeployments(); }; const handleCancel = async (id: string) => { await api.patch(`/v1/deployments/${id}/cancel`); loadDeployments(); }; const handleDelete = async (id: string) => { if (!confirm("Delete this deployment?")) return; await api.delete(`/v1/deployments/${id}`); loadDeployments(); }; const toggleDevice = (id: string) => { setSelectedDevices((prev) => prev.includes(id) ? prev.filter((d) => d !== id) : [...prev, id] ); }; return (

Deployments

{total} deployment(s)

Create Deployment Deploy packages, services, or commands to selected devices.
{error && ( {error} )}
setForm({ ...form, name: e.target.value })} placeholder="e.g. Install nginx on web servers" required />
setForm({ ...form, description: e.target.value })} placeholder="Optional description" />
{needsPackageName && (
setForm({ ...form, packageName: e.target.value })} placeholder="e.g. nginx" required />
)} {needsServiceName && (
setForm({ ...form, serviceName: e.target.value })} placeholder="e.g. nginx" required />
)}
{devices.length === 0 ? (

No devices enrolled yet.

) : ( devices.map((d) => ( )) )}

{selectedDevices.length} device(s) selected

{deployments.length === 0 ? (

No deployments created yet. Click "New Deployment" to get started.

) : ( Name Job Type Status Progress Created By Created Actions {deployments.map((d) => (
{d.name}
{d.description && (

{d.description}

)}
{d.jobType.replace(/_/g, " ")} {d.status.replace(/_/g, " ")}
{d.completedTargets} / {d.totalTargets} {d.failedTargets > 0 && ( ({d.failedTargets} failed) )}
{d.createdByName || "—"} {new Date(d.createdAt).toLocaleString()}
{d.status === "draft" && ( )} {["draft", "approved"].includes(d.status) && ( )} {d.status === "draft" && ( )}
))}
)}
); }