mirror of
https://github.com/Dokploy/dokploy.git
synced 2026-07-01 12:05:23 +02:00
218 lines
6.6 KiB
TypeScript
218 lines
6.6 KiB
TypeScript
"use client";
|
|
|
|
import type { inferRouterOutputs } from "@trpc/server";
|
|
import { ArrowRight, ListTodo, Loader2, XCircle } from "lucide-react";
|
|
import Link from "next/link";
|
|
import { Badge } from "@/components/ui/badge";
|
|
import { Button } from "@/components/ui/button";
|
|
import {
|
|
Table,
|
|
TableBody,
|
|
TableCell,
|
|
TableHead,
|
|
TableHeader,
|
|
TableRow,
|
|
} from "@/components/ui/table";
|
|
import type { AppRouter } from "@/server/api/root";
|
|
import { api } from "@/utils/api";
|
|
|
|
type QueueRow =
|
|
inferRouterOutputs<AppRouter>["deployment"]["queueList"][number];
|
|
|
|
const stateVariants: Record<
|
|
string,
|
|
| "default"
|
|
| "secondary"
|
|
| "destructive"
|
|
| "outline"
|
|
| "yellow"
|
|
| "green"
|
|
| "red"
|
|
> = {
|
|
pending: "secondary",
|
|
waiting: "secondary",
|
|
active: "yellow",
|
|
delayed: "outline",
|
|
completed: "green",
|
|
failed: "destructive",
|
|
cancelled: "outline",
|
|
paused: "outline",
|
|
};
|
|
|
|
function formatTs(ts?: number): string {
|
|
if (ts == null) return "—";
|
|
const d = new Date(ts);
|
|
return d.toLocaleString();
|
|
}
|
|
|
|
function getJobLabel(row: QueueRow): string {
|
|
const d = row.data as {
|
|
applicationType?: string;
|
|
applicationId?: string;
|
|
composeId?: string;
|
|
previewDeploymentId?: string;
|
|
titleLog?: string;
|
|
type?: string;
|
|
};
|
|
if (!d) return String(row.id);
|
|
const type = d.applicationType ?? "job";
|
|
const title = d.titleLog ?? "";
|
|
if (title) return title;
|
|
if (d.applicationId) return `Application ${d.applicationId.slice(0, 8)}…`;
|
|
if (d.composeId) return `Compose ${d.composeId.slice(0, 8)}…`;
|
|
if (d.previewDeploymentId)
|
|
return `Preview ${d.previewDeploymentId.slice(0, 8)}…`;
|
|
return `${type} ${String(row.id)}`;
|
|
}
|
|
|
|
export function ShowQueueTable(props: { embedded?: boolean }) {
|
|
const { embedded: _embedded = false } = props;
|
|
const { data: queueList, isLoading } = api.deployment.queueList.useQuery(
|
|
undefined,
|
|
{ refetchInterval: 3000 },
|
|
);
|
|
const { data: isCloud } = api.settings.isCloud.useQuery();
|
|
const utils = api.useUtils();
|
|
const {
|
|
mutateAsync: cancelApplicationDeployment,
|
|
isPending: isCancellingApp,
|
|
} = api.application.cancelDeployment.useMutation({
|
|
onSuccess: () => void utils.deployment.queueList.invalidate(),
|
|
});
|
|
const {
|
|
mutateAsync: cancelComposeDeployment,
|
|
isPending: isCancellingCompose,
|
|
} = api.compose.cancelDeployment.useMutation({
|
|
onSuccess: () => void utils.deployment.queueList.invalidate(),
|
|
});
|
|
const isCancelling = isCancellingApp || isCancellingCompose;
|
|
|
|
return (
|
|
<div className="px-0">
|
|
{isLoading ? (
|
|
<div className="flex gap-4 w-full items-center justify-center min-h-[30vh] text-muted-foreground">
|
|
<Loader2 className="size-4 animate-spin" />
|
|
<span>Loading queue...</span>
|
|
</div>
|
|
) : (
|
|
<div className="rounded-md border overflow-x-auto">
|
|
<Table>
|
|
<TableHeader>
|
|
<TableRow>
|
|
<TableHead>Job ID</TableHead>
|
|
<TableHead>Label</TableHead>
|
|
<TableHead>Type</TableHead>
|
|
<TableHead>State</TableHead>
|
|
<TableHead>Added</TableHead>
|
|
<TableHead>Processed</TableHead>
|
|
<TableHead>Finished</TableHead>
|
|
<TableHead>Error</TableHead>
|
|
<TableHead className="w-[100px]">Actions</TableHead>
|
|
</TableRow>
|
|
</TableHeader>
|
|
<TableBody>
|
|
{queueList?.length ? (
|
|
queueList.map((row) => {
|
|
const d = row.data as Record<string, unknown>;
|
|
const appType = d?.applicationType as string | undefined;
|
|
const pathInfo = row.servicePath;
|
|
const hasLink = pathInfo?.href != null;
|
|
return (
|
|
<TableRow key={String(row.id)}>
|
|
<TableCell className="font-mono text-xs">
|
|
{String(row.id)}
|
|
</TableCell>
|
|
<TableCell className="max-w-[200px] truncate">
|
|
{getJobLabel(row)}
|
|
</TableCell>
|
|
<TableCell>{appType ?? row.name ?? "—"}</TableCell>
|
|
<TableCell>
|
|
<Badge variant={stateVariants[row.state] ?? "outline"}>
|
|
{row.state}
|
|
</Badge>
|
|
</TableCell>
|
|
<TableCell className="text-muted-foreground text-xs">
|
|
{formatTs(row.timestamp)}
|
|
</TableCell>
|
|
<TableCell className="text-muted-foreground text-xs">
|
|
{formatTs(row.processedOn)}
|
|
</TableCell>
|
|
<TableCell className="text-muted-foreground text-xs">
|
|
{formatTs(row.finishedOn)}
|
|
</TableCell>
|
|
<TableCell className="max-w-[180px] truncate text-xs text-destructive">
|
|
{row.failedReason ?? "—"}
|
|
</TableCell>
|
|
<TableCell>
|
|
<div className="flex items-center gap-1">
|
|
{hasLink ? (
|
|
<Button variant="ghost" size="sm" asChild>
|
|
<Link href={pathInfo!.href!}>
|
|
<ArrowRight className="size-4 mr-1" />
|
|
Service
|
|
</Link>
|
|
</Button>
|
|
) : (
|
|
<span className="text-muted-foreground text-xs">
|
|
—
|
|
</span>
|
|
)}
|
|
{isCloud &&
|
|
row.state === "active" &&
|
|
(d?.applicationId != null ||
|
|
d?.composeId != null) && (
|
|
<Button
|
|
variant="ghost"
|
|
size="sm"
|
|
className="text-destructive hover:text-destructive"
|
|
disabled={isCancelling}
|
|
onClick={() => {
|
|
const appId =
|
|
typeof d.applicationId === "string"
|
|
? d.applicationId
|
|
: undefined;
|
|
const compId =
|
|
typeof d.composeId === "string"
|
|
? d.composeId
|
|
: undefined;
|
|
if (appId) {
|
|
void cancelApplicationDeployment({
|
|
applicationId: appId,
|
|
});
|
|
} else if (compId) {
|
|
void cancelComposeDeployment({
|
|
composeId: compId,
|
|
});
|
|
}
|
|
}}
|
|
>
|
|
<XCircle className="size-4 mr-1" />
|
|
Cancel
|
|
</Button>
|
|
)}
|
|
</div>
|
|
</TableCell>
|
|
</TableRow>
|
|
);
|
|
})
|
|
) : (
|
|
<TableRow>
|
|
<TableCell colSpan={9} className="text-center py-12">
|
|
<div className="flex flex-col items-center justify-center gap-2 text-muted-foreground min-h-[30vh]">
|
|
<ListTodo className="size-8" />
|
|
<p className="font-medium">Queue is empty</p>
|
|
<p className="text-sm">
|
|
Deployment jobs will appear here when they are queued.
|
|
</p>
|
|
</div>
|
|
</TableCell>
|
|
</TableRow>
|
|
)}
|
|
</TableBody>
|
|
</Table>
|
|
</div>
|
|
)}
|
|
</div>
|
|
);
|
|
}
|