mirror of
https://github.com/Dokploy/dokploy
synced 2025-06-26 18:27:59 +00:00
Refactor deployment components to unify application and compose handling
- Updated `CancelQueues`, `RefreshToken`, and `ShowDeployments` components to accept a unified `id` and `type` prop, allowing for streamlined handling of both applications and compose deployments. - Removed redundant compose-specific components (`CancelQueuesCompose`, `RefreshTokenCompose`, `ShowDeploymentCompose`, and `ShowDeploymentsCompose`) to simplify the codebase. - Enhanced loading state management in `ShowDeployments` to improve user experience during data fetching.
This commit is contained in:
@@ -15,11 +15,15 @@ import { Paintbrush } from "lucide-react";
|
|||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
applicationId: string;
|
id: string;
|
||||||
|
type: "application" | "compose";
|
||||||
}
|
}
|
||||||
|
|
||||||
export const CancelQueues = ({ applicationId }: Props) => {
|
export const CancelQueues = ({ id, type }: Props) => {
|
||||||
const { mutateAsync, isLoading } = api.application.cleanQueues.useMutation();
|
const { mutateAsync, isLoading } =
|
||||||
|
type === "application"
|
||||||
|
? api.application.cleanQueues.useMutation()
|
||||||
|
: api.compose.cleanQueues.useMutation();
|
||||||
const { data: isCloud } = api.settings.isCloud.useQuery();
|
const { data: isCloud } = api.settings.isCloud.useQuery();
|
||||||
|
|
||||||
if (isCloud) {
|
if (isCloud) {
|
||||||
@@ -48,7 +52,8 @@ export const CancelQueues = ({ applicationId }: Props) => {
|
|||||||
<AlertDialogAction
|
<AlertDialogAction
|
||||||
onClick={async () => {
|
onClick={async () => {
|
||||||
await mutateAsync({
|
await mutateAsync({
|
||||||
applicationId,
|
applicationId: id || "",
|
||||||
|
composeId: id || "",
|
||||||
})
|
})
|
||||||
.then(() => {
|
.then(() => {
|
||||||
toast.success("Queues are being cleaned");
|
toast.success("Queues are being cleaned");
|
||||||
|
|||||||
@@ -14,10 +14,14 @@ import { RefreshCcw } from "lucide-react";
|
|||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
applicationId: string;
|
id: string;
|
||||||
|
type: "application" | "compose";
|
||||||
}
|
}
|
||||||
export const RefreshToken = ({ applicationId }: Props) => {
|
export const RefreshToken = ({ id, type }: Props) => {
|
||||||
const { mutateAsync } = api.application.refreshToken.useMutation();
|
const { mutateAsync } =
|
||||||
|
type === "application"
|
||||||
|
? api.application.refreshToken.useMutation()
|
||||||
|
: api.compose.refreshToken.useMutation();
|
||||||
const utils = api.useUtils();
|
const utils = api.useUtils();
|
||||||
return (
|
return (
|
||||||
<AlertDialog>
|
<AlertDialog>
|
||||||
@@ -37,12 +41,19 @@ export const RefreshToken = ({ applicationId }: Props) => {
|
|||||||
<AlertDialogAction
|
<AlertDialogAction
|
||||||
onClick={async () => {
|
onClick={async () => {
|
||||||
await mutateAsync({
|
await mutateAsync({
|
||||||
applicationId,
|
applicationId: id || "",
|
||||||
|
composeId: id || "",
|
||||||
})
|
})
|
||||||
.then(() => {
|
.then(() => {
|
||||||
utils.application.one.invalidate({
|
if (type === "application") {
|
||||||
applicationId,
|
utils.application.one.invalidate({
|
||||||
});
|
applicationId: id,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
utils.compose.one.invalidate({
|
||||||
|
composeId: id,
|
||||||
|
});
|
||||||
|
}
|
||||||
toast.success("Refresh updated");
|
toast.success("Refresh updated");
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.catch(() => {
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import {
|
|||||||
CardTitle,
|
CardTitle,
|
||||||
} from "@/components/ui/card";
|
} from "@/components/ui/card";
|
||||||
import { type RouterOutputs, api } from "@/utils/api";
|
import { type RouterOutputs, api } from "@/utils/api";
|
||||||
import { RocketIcon, Clock } from "lucide-react";
|
import { RocketIcon, Clock, Loader2 } from "lucide-react";
|
||||||
import React, { useEffect, useState } from "react";
|
import React, { useEffect, useState } from "react";
|
||||||
import { CancelQueues } from "./cancel-queues";
|
import { CancelQueues } from "./cancel-queues";
|
||||||
import { RefreshToken } from "./refresh-token";
|
import { RefreshToken } from "./refresh-token";
|
||||||
@@ -17,21 +17,34 @@ import { ShowDeployment } from "./show-deployment";
|
|||||||
import { Badge } from "@/components/ui/badge";
|
import { Badge } from "@/components/ui/badge";
|
||||||
import { formatDuration } from "../schedules/show-schedules-logs";
|
import { formatDuration } from "../schedules/show-schedules-logs";
|
||||||
interface Props {
|
interface Props {
|
||||||
applicationId: string;
|
id: string;
|
||||||
|
type: "application" | "compose";
|
||||||
}
|
}
|
||||||
|
|
||||||
export const ShowDeployments = ({ applicationId }: Props) => {
|
export const ShowDeployments = ({ id, type }: Props) => {
|
||||||
const [activeLog, setActiveLog] = useState<
|
const [activeLog, setActiveLog] = useState<
|
||||||
RouterOutputs["deployment"]["all"][number] | null
|
RouterOutputs["deployment"]["all"][number] | null
|
||||||
>(null);
|
>(null);
|
||||||
const { data } = api.application.one.useQuery({ applicationId });
|
const { data } =
|
||||||
const { data: deployments } = api.deployment.all.useQuery(
|
type === "application"
|
||||||
{ applicationId },
|
? api.application.one.useQuery({ applicationId: id })
|
||||||
{
|
: api.compose.one.useQuery({ composeId: id });
|
||||||
enabled: !!applicationId,
|
const { data: deployments, isLoading: isLoadingDeployments } =
|
||||||
refetchInterval: 1000,
|
type === "application"
|
||||||
},
|
? api.deployment.all.useQuery(
|
||||||
);
|
{ applicationId: id },
|
||||||
|
{
|
||||||
|
enabled: !!id,
|
||||||
|
refetchInterval: 1000,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
: api.deployment.allByCompose.useQuery(
|
||||||
|
{ composeId: id },
|
||||||
|
{
|
||||||
|
enabled: !!id,
|
||||||
|
refetchInterval: 1000,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
const [url, setUrl] = React.useState("");
|
const [url, setUrl] = React.useState("");
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -44,10 +57,10 @@ export const ShowDeployments = ({ applicationId }: Props) => {
|
|||||||
<div className="flex flex-col gap-2">
|
<div className="flex flex-col gap-2">
|
||||||
<CardTitle className="text-xl">Deployments</CardTitle>
|
<CardTitle className="text-xl">Deployments</CardTitle>
|
||||||
<CardDescription>
|
<CardDescription>
|
||||||
See all the 10 last deployments for this application
|
See all the 10 last deployments for this {type}
|
||||||
</CardDescription>
|
</CardDescription>
|
||||||
</div>
|
</div>
|
||||||
<CancelQueues applicationId={applicationId} />
|
<CancelQueues id={id} type={type} />
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent className="flex flex-col gap-4">
|
<CardContent className="flex flex-col gap-4">
|
||||||
<div className="flex flex-col gap-2 text-sm">
|
<div className="flex flex-col gap-2 text-sm">
|
||||||
@@ -61,12 +74,19 @@ export const ShowDeployments = ({ applicationId }: Props) => {
|
|||||||
<span className="break-all text-muted-foreground">
|
<span className="break-all text-muted-foreground">
|
||||||
{`${url}/api/deploy/${data?.refreshToken}`}
|
{`${url}/api/deploy/${data?.refreshToken}`}
|
||||||
</span>
|
</span>
|
||||||
<RefreshToken applicationId={applicationId} />
|
<RefreshToken id={id} type={type} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{data?.deployments?.length === 0 ? (
|
{isLoadingDeployments ? (
|
||||||
<div className="flex w-full flex-col items-center justify-center gap-3 pt-10">
|
<div className="flex w-full flex-row items-center justify-center gap-3 pt-10 min-h-[25vh]">
|
||||||
|
<Loader2 className="size-6 text-muted-foreground animate-spin" />
|
||||||
|
<span className="text-base text-muted-foreground">
|
||||||
|
Loading deployments...
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
) : data?.deployments?.length === 0 ? (
|
||||||
|
<div className="flex w-full flex-col items-center justify-center gap-3 pt-10 min-h-[25vh]">
|
||||||
<RocketIcon className="size-8 text-muted-foreground" />
|
<RocketIcon className="size-8 text-muted-foreground" />
|
||||||
<span className="text-base text-muted-foreground">
|
<span className="text-base text-muted-foreground">
|
||||||
No deployments found
|
No deployments found
|
||||||
|
|||||||
@@ -1,66 +0,0 @@
|
|||||||
import {
|
|
||||||
AlertDialog,
|
|
||||||
AlertDialogAction,
|
|
||||||
AlertDialogCancel,
|
|
||||||
AlertDialogContent,
|
|
||||||
AlertDialogDescription,
|
|
||||||
AlertDialogFooter,
|
|
||||||
AlertDialogHeader,
|
|
||||||
AlertDialogTitle,
|
|
||||||
AlertDialogTrigger,
|
|
||||||
} from "@/components/ui/alert-dialog";
|
|
||||||
import { Button } from "@/components/ui/button";
|
|
||||||
import { api } from "@/utils/api";
|
|
||||||
import { Paintbrush } from "lucide-react";
|
|
||||||
import { toast } from "sonner";
|
|
||||||
|
|
||||||
interface Props {
|
|
||||||
composeId: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const CancelQueuesCompose = ({ composeId }: Props) => {
|
|
||||||
const { mutateAsync, isLoading } = api.compose.cleanQueues.useMutation();
|
|
||||||
const { data: isCloud } = api.settings.isCloud.useQuery();
|
|
||||||
|
|
||||||
if (isCloud) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return (
|
|
||||||
<AlertDialog>
|
|
||||||
<AlertDialogTrigger asChild>
|
|
||||||
<Button variant="destructive" className="w-fit" isLoading={isLoading}>
|
|
||||||
Cancel Queues
|
|
||||||
<Paintbrush className="size-4" />
|
|
||||||
</Button>
|
|
||||||
</AlertDialogTrigger>
|
|
||||||
<AlertDialogContent>
|
|
||||||
<AlertDialogHeader>
|
|
||||||
<AlertDialogTitle>
|
|
||||||
Are you sure to cancel the incoming deployments?
|
|
||||||
</AlertDialogTitle>
|
|
||||||
<AlertDialogDescription>
|
|
||||||
This will cancel all the incoming deployments
|
|
||||||
</AlertDialogDescription>
|
|
||||||
</AlertDialogHeader>
|
|
||||||
<AlertDialogFooter>
|
|
||||||
<AlertDialogCancel>Cancel</AlertDialogCancel>
|
|
||||||
<AlertDialogAction
|
|
||||||
onClick={async () => {
|
|
||||||
await mutateAsync({
|
|
||||||
composeId,
|
|
||||||
})
|
|
||||||
.then(() => {
|
|
||||||
toast.success("Queues are being cleaned");
|
|
||||||
})
|
|
||||||
.catch((err) => {
|
|
||||||
toast.error(err.message);
|
|
||||||
});
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Confirm
|
|
||||||
</AlertDialogAction>
|
|
||||||
</AlertDialogFooter>
|
|
||||||
</AlertDialogContent>
|
|
||||||
</AlertDialog>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@@ -1,59 +0,0 @@
|
|||||||
import {
|
|
||||||
AlertDialog,
|
|
||||||
AlertDialogAction,
|
|
||||||
AlertDialogCancel,
|
|
||||||
AlertDialogContent,
|
|
||||||
AlertDialogDescription,
|
|
||||||
AlertDialogFooter,
|
|
||||||
AlertDialogHeader,
|
|
||||||
AlertDialogTitle,
|
|
||||||
AlertDialogTrigger,
|
|
||||||
} from "@/components/ui/alert-dialog";
|
|
||||||
import { api } from "@/utils/api";
|
|
||||||
import { RefreshCcw } from "lucide-react";
|
|
||||||
import { toast } from "sonner";
|
|
||||||
|
|
||||||
interface Props {
|
|
||||||
composeId: string;
|
|
||||||
}
|
|
||||||
export const RefreshTokenCompose = ({ composeId }: Props) => {
|
|
||||||
const { mutateAsync } = api.compose.refreshToken.useMutation();
|
|
||||||
const utils = api.useUtils();
|
|
||||||
return (
|
|
||||||
<AlertDialog>
|
|
||||||
<AlertDialogTrigger>
|
|
||||||
<RefreshCcw className="h-4 w-4 cursor-pointer text-muted-foreground" />
|
|
||||||
</AlertDialogTrigger>
|
|
||||||
<AlertDialogContent>
|
|
||||||
<AlertDialogHeader>
|
|
||||||
<AlertDialogTitle>Are you absolutely sure?</AlertDialogTitle>
|
|
||||||
<AlertDialogDescription>
|
|
||||||
This action cannot be undone. This will permanently change the token
|
|
||||||
and all the previous tokens will be invalidated
|
|
||||||
</AlertDialogDescription>
|
|
||||||
</AlertDialogHeader>
|
|
||||||
<AlertDialogFooter>
|
|
||||||
<AlertDialogCancel>Cancel</AlertDialogCancel>
|
|
||||||
<AlertDialogAction
|
|
||||||
onClick={async () => {
|
|
||||||
await mutateAsync({
|
|
||||||
composeId,
|
|
||||||
})
|
|
||||||
.then(() => {
|
|
||||||
utils.compose.one.invalidate({
|
|
||||||
composeId,
|
|
||||||
});
|
|
||||||
toast.success("Refresh Token updated");
|
|
||||||
})
|
|
||||||
.catch(() => {
|
|
||||||
toast.error("Error updating the refresh token");
|
|
||||||
});
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Confirm
|
|
||||||
</AlertDialogAction>
|
|
||||||
</AlertDialogFooter>
|
|
||||||
</AlertDialogContent>
|
|
||||||
</AlertDialog>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@@ -1,184 +0,0 @@
|
|||||||
import { Badge } from "@/components/ui/badge";
|
|
||||||
import { Checkbox } from "@/components/ui/checkbox";
|
|
||||||
import {
|
|
||||||
Dialog,
|
|
||||||
DialogContent,
|
|
||||||
DialogDescription,
|
|
||||||
DialogHeader,
|
|
||||||
DialogTitle,
|
|
||||||
} from "@/components/ui/dialog";
|
|
||||||
import { Loader2 } from "lucide-react";
|
|
||||||
import { useEffect, useRef, useState } from "react";
|
|
||||||
import { TerminalLine } from "../../docker/logs/terminal-line";
|
|
||||||
import { type LogLine, parseLogs } from "../../docker/logs/utils";
|
|
||||||
|
|
||||||
interface Props {
|
|
||||||
logPath: string | null;
|
|
||||||
serverId?: string;
|
|
||||||
open: boolean;
|
|
||||||
onClose: () => void;
|
|
||||||
errorMessage?: string;
|
|
||||||
}
|
|
||||||
export const ShowDeploymentCompose = ({
|
|
||||||
logPath,
|
|
||||||
open,
|
|
||||||
onClose,
|
|
||||||
serverId,
|
|
||||||
errorMessage,
|
|
||||||
}: Props) => {
|
|
||||||
const [data, setData] = useState("");
|
|
||||||
const [filteredLogs, setFilteredLogs] = useState<LogLine[]>([]);
|
|
||||||
const [showExtraLogs, setShowExtraLogs] = useState(false);
|
|
||||||
const wsRef = useRef<WebSocket | null>(null); // Ref to hold WebSocket instance
|
|
||||||
const [autoScroll, setAutoScroll] = useState(true);
|
|
||||||
const scrollRef = useRef<HTMLDivElement>(null);
|
|
||||||
|
|
||||||
const scrollToBottom = () => {
|
|
||||||
if (autoScroll && scrollRef.current) {
|
|
||||||
scrollRef.current.scrollTop = scrollRef.current.scrollHeight;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleScroll = () => {
|
|
||||||
if (!scrollRef.current) return;
|
|
||||||
|
|
||||||
const { scrollTop, scrollHeight, clientHeight } = scrollRef.current;
|
|
||||||
const isAtBottom = Math.abs(scrollHeight - scrollTop - clientHeight) < 10;
|
|
||||||
setAutoScroll(isAtBottom);
|
|
||||||
};
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (!open || !logPath) return;
|
|
||||||
|
|
||||||
setData("");
|
|
||||||
const protocol = window.location.protocol === "https:" ? "wss:" : "ws:";
|
|
||||||
|
|
||||||
const wsUrl = `${protocol}//${window.location.host}/listen-deployment?logPath=${logPath}&serverId=${serverId}`;
|
|
||||||
const ws = new WebSocket(wsUrl);
|
|
||||||
|
|
||||||
wsRef.current = ws; // Store WebSocket instance in ref
|
|
||||||
|
|
||||||
ws.onmessage = (e) => {
|
|
||||||
setData((currentData) => currentData + e.data);
|
|
||||||
};
|
|
||||||
|
|
||||||
ws.onerror = (error) => {
|
|
||||||
console.error("WebSocket error: ", error);
|
|
||||||
};
|
|
||||||
|
|
||||||
ws.onclose = () => {
|
|
||||||
wsRef.current = null;
|
|
||||||
};
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
if (wsRef.current?.readyState === WebSocket.OPEN) {
|
|
||||||
ws.close();
|
|
||||||
wsRef.current = null;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}, [logPath, open]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const logs = parseLogs(data);
|
|
||||||
let filteredLogsResult = logs;
|
|
||||||
if (serverId) {
|
|
||||||
let hideSubsequentLogs = false;
|
|
||||||
filteredLogsResult = logs.filter((log) => {
|
|
||||||
if (
|
|
||||||
log.message.includes(
|
|
||||||
"===================================EXTRA LOGS============================================",
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
hideSubsequentLogs = true;
|
|
||||||
return showExtraLogs;
|
|
||||||
}
|
|
||||||
return showExtraLogs ? true : !hideSubsequentLogs;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
setFilteredLogs(filteredLogsResult);
|
|
||||||
}, [data, showExtraLogs]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
scrollToBottom();
|
|
||||||
|
|
||||||
if (autoScroll && scrollRef.current) {
|
|
||||||
scrollRef.current.scrollTop = scrollRef.current.scrollHeight;
|
|
||||||
}
|
|
||||||
}, [filteredLogs, autoScroll]);
|
|
||||||
|
|
||||||
const optionalErrors = parseLogs(errorMessage || "");
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Dialog
|
|
||||||
open={open}
|
|
||||||
onOpenChange={(e) => {
|
|
||||||
onClose();
|
|
||||||
if (!e) {
|
|
||||||
setData("");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (wsRef.current) {
|
|
||||||
if (wsRef.current.readyState === WebSocket.OPEN) {
|
|
||||||
wsRef.current.close();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<DialogContent className={"sm:max-w-5xl max-h-screen"}>
|
|
||||||
<DialogHeader>
|
|
||||||
<DialogTitle>Deployment</DialogTitle>
|
|
||||||
<DialogDescription className="flex items-center gap-2">
|
|
||||||
<span>
|
|
||||||
See all the details of this deployment |{" "}
|
|
||||||
<Badge variant="blank" className="text-xs">
|
|
||||||
{filteredLogs.length} lines
|
|
||||||
</Badge>
|
|
||||||
</span>
|
|
||||||
{serverId && (
|
|
||||||
<div className="flex items-center space-x-2">
|
|
||||||
<Checkbox
|
|
||||||
id="show-extra-logs"
|
|
||||||
checked={showExtraLogs}
|
|
||||||
onCheckedChange={(checked) =>
|
|
||||||
setShowExtraLogs(checked as boolean)
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
<label
|
|
||||||
htmlFor="show-extra-logs"
|
|
||||||
className="text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
|
|
||||||
>
|
|
||||||
Show Extra Logs
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</DialogDescription>
|
|
||||||
</DialogHeader>
|
|
||||||
|
|
||||||
<div
|
|
||||||
ref={scrollRef}
|
|
||||||
onScroll={handleScroll}
|
|
||||||
className="h-[720px] overflow-y-auto space-y-0 border p-4 bg-[#fafafa] dark:bg-[#050506] rounded custom-logs-scrollbar"
|
|
||||||
>
|
|
||||||
{filteredLogs.length > 0 ? (
|
|
||||||
filteredLogs.map((log: LogLine, index: number) => (
|
|
||||||
<TerminalLine key={index} log={log} noTimestamp />
|
|
||||||
))
|
|
||||||
) : (
|
|
||||||
<>
|
|
||||||
{optionalErrors.length > 0 ? (
|
|
||||||
optionalErrors.map((log: LogLine, index: number) => (
|
|
||||||
<TerminalLine key={`extra-${index}`} log={log} noTimestamp />
|
|
||||||
))
|
|
||||||
) : (
|
|
||||||
<div className="flex justify-center items-center h-full text-muted-foreground">
|
|
||||||
<Loader2 className="h-6 w-6 animate-spin" />
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</DialogContent>
|
|
||||||
</Dialog>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@@ -1,141 +0,0 @@
|
|||||||
import { DateTooltip } from "@/components/shared/date-tooltip";
|
|
||||||
import { StatusTooltip } from "@/components/shared/status-tooltip";
|
|
||||||
import { Button } from "@/components/ui/button";
|
|
||||||
import {
|
|
||||||
Card,
|
|
||||||
CardContent,
|
|
||||||
CardDescription,
|
|
||||||
CardHeader,
|
|
||||||
CardTitle,
|
|
||||||
} from "@/components/ui/card";
|
|
||||||
import { type RouterOutputs, api } from "@/utils/api";
|
|
||||||
import { RocketIcon, Clock } from "lucide-react";
|
|
||||||
import React, { useEffect, useState } from "react";
|
|
||||||
import { CancelQueuesCompose } from "./cancel-queues-compose";
|
|
||||||
import { RefreshTokenCompose } from "./refresh-token-compose";
|
|
||||||
import { ShowDeploymentCompose } from "./show-deployment-compose";
|
|
||||||
import { Badge } from "@/components/ui/badge";
|
|
||||||
import { formatDuration } from "@/components/dashboard/application/schedules/show-schedules-logs";
|
|
||||||
interface Props {
|
|
||||||
composeId: string;
|
|
||||||
}
|
|
||||||
export const ShowDeploymentsCompose = ({ composeId }: Props) => {
|
|
||||||
const [activeLog, setActiveLog] = useState<
|
|
||||||
RouterOutputs["deployment"]["all"][number] | null
|
|
||||||
>(null);
|
|
||||||
const { data } = api.compose.one.useQuery({ composeId });
|
|
||||||
const { data: deployments } = api.deployment.allByCompose.useQuery(
|
|
||||||
{ composeId },
|
|
||||||
{
|
|
||||||
enabled: !!composeId,
|
|
||||||
refetchInterval: 5000,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
const [url, setUrl] = React.useState("");
|
|
||||||
useEffect(() => {
|
|
||||||
setUrl(document.location.origin);
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Card className="bg-background">
|
|
||||||
<CardHeader className="flex flex-row items-center justify-between flex-wrap gap-2">
|
|
||||||
<div className="flex flex-col gap-2">
|
|
||||||
<CardTitle className="text-xl">Deployments</CardTitle>
|
|
||||||
<CardDescription>
|
|
||||||
See all the 10 last deployments for this compose
|
|
||||||
</CardDescription>
|
|
||||||
</div>
|
|
||||||
<CancelQueuesCompose composeId={composeId} />
|
|
||||||
{/* <CancelQueues applicationId={applicationId} /> */}
|
|
||||||
</CardHeader>
|
|
||||||
<CardContent className="flex flex-col gap-4">
|
|
||||||
<div className="flex flex-col gap-2 text-sm">
|
|
||||||
<span>
|
|
||||||
If you want to re-deploy this application use this URL in the config
|
|
||||||
of your git provider or docker
|
|
||||||
</span>
|
|
||||||
<div className="flex flex-row items-center gap-2 flex-wrap">
|
|
||||||
<span>Webhook URL: </span>
|
|
||||||
<div className="flex flex-row items-center gap-2">
|
|
||||||
<span className="text-muted-foreground">
|
|
||||||
{`${url}/api/deploy/compose/${data?.refreshToken}`}
|
|
||||||
</span>
|
|
||||||
<RefreshTokenCompose composeId={composeId} />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{data?.deployments?.length === 0 ? (
|
|
||||||
<div className="flex w-full flex-col items-center justify-center gap-3 pt-10">
|
|
||||||
<RocketIcon className="size-8 text-muted-foreground" />
|
|
||||||
<span className="text-base text-muted-foreground">
|
|
||||||
No deployments found
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
<div className="flex flex-col gap-4">
|
|
||||||
{deployments?.map((deployment) => (
|
|
||||||
<div
|
|
||||||
key={deployment.deploymentId}
|
|
||||||
className="flex items-center justify-between rounded-lg border p-4"
|
|
||||||
>
|
|
||||||
<div className="flex flex-col">
|
|
||||||
<span className="flex items-center gap-4 font-medium capitalize text-foreground">
|
|
||||||
{deployment.status}
|
|
||||||
|
|
||||||
<StatusTooltip
|
|
||||||
status={deployment?.status}
|
|
||||||
className="size-2.5"
|
|
||||||
/>
|
|
||||||
</span>
|
|
||||||
<span className="text-sm text-muted-foreground">
|
|
||||||
{deployment.title}
|
|
||||||
</span>
|
|
||||||
{deployment.description && (
|
|
||||||
<span className="text-sm text-muted-foreground">
|
|
||||||
{deployment.description}
|
|
||||||
</span>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
<div className="flex flex-col items-end gap-2">
|
|
||||||
<div className="text-sm capitalize text-muted-foreground flex items-center gap-2">
|
|
||||||
<DateTooltip date={deployment.createdAt} />
|
|
||||||
{deployment.startedAt && deployment.finishedAt && (
|
|
||||||
<Badge
|
|
||||||
variant="outline"
|
|
||||||
className="text-[10px] gap-1 flex items-center"
|
|
||||||
>
|
|
||||||
<Clock className="size-3" />
|
|
||||||
{formatDuration(
|
|
||||||
Math.floor(
|
|
||||||
(new Date(deployment.finishedAt).getTime() -
|
|
||||||
new Date(deployment.startedAt).getTime()) /
|
|
||||||
1000,
|
|
||||||
),
|
|
||||||
)}
|
|
||||||
</Badge>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<Button
|
|
||||||
onClick={() => {
|
|
||||||
setActiveLog(deployment);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
View
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
<ShowDeploymentCompose
|
|
||||||
serverId={data?.serverId || ""}
|
|
||||||
open={Boolean(activeLog && activeLog.logPath !== null)}
|
|
||||||
onClose={() => setActiveLog(null)}
|
|
||||||
logPath={activeLog?.logPath || ""}
|
|
||||||
errorMessage={activeLog?.errorMessage || ""}
|
|
||||||
/>
|
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@@ -320,7 +320,7 @@ const Service = (
|
|||||||
</TabsContent>
|
</TabsContent>
|
||||||
<TabsContent value="deployments" className="w-full">
|
<TabsContent value="deployments" className="w-full">
|
||||||
<div className="flex flex-col gap-4 pt-2.5">
|
<div className="flex flex-col gap-4 pt-2.5">
|
||||||
<ShowDeployments applicationId={applicationId} />
|
<ShowDeployments id={applicationId} type="application" />
|
||||||
</div>
|
</div>
|
||||||
</TabsContent>
|
</TabsContent>
|
||||||
<TabsContent value="preview-deployments" className="w-full">
|
<TabsContent value="preview-deployments" className="w-full">
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { ShowImport } from "@/components/dashboard/application/advanced/import/show-import";
|
import { ShowImport } from "@/components/dashboard/application/advanced/import/show-import";
|
||||||
import { ShowVolumes } from "@/components/dashboard/application/advanced/volumes/show-volumes";
|
import { ShowVolumes } from "@/components/dashboard/application/advanced/volumes/show-volumes";
|
||||||
|
import { ShowDeployments } from "@/components/dashboard/application/deployments/show-deployments";
|
||||||
import { ShowDomains } from "@/components/dashboard/application/domains/show-domains";
|
import { ShowDomains } from "@/components/dashboard/application/domains/show-domains";
|
||||||
import { ShowEnvironment } from "@/components/dashboard/application/environment/show-enviroment";
|
import { ShowEnvironment } from "@/components/dashboard/application/environment/show-enviroment";
|
||||||
import { ShowSchedules } from "@/components/dashboard/application/schedules/show-schedules";
|
import { ShowSchedules } from "@/components/dashboard/application/schedules/show-schedules";
|
||||||
@@ -333,7 +334,7 @@ const Service = (
|
|||||||
|
|
||||||
<TabsContent value="deployments">
|
<TabsContent value="deployments">
|
||||||
<div className="flex flex-col gap-4 pt-2.5">
|
<div className="flex flex-col gap-4 pt-2.5">
|
||||||
<ShowDeploymentsCompose composeId={composeId} />
|
<ShowDeployments id={composeId} type="compose" />
|
||||||
</div>
|
</div>
|
||||||
</TabsContent>
|
</TabsContent>
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user