mirror of
https://github.com/Dokploy/dokploy
synced 2025-06-26 18:27:59 +00:00
Add ShowDeploymentsModal component and refactor ShowDeployments for enhanced deployment handling
- Introduced `ShowDeploymentsModal` component to manage deployment logs and details in a modal interface. - Updated `ShowDeployments` to accept `serverId` and `refreshToken` props, allowing for more flexible deployment data handling. - Refactored API queries in `ShowDeployments` to utilize a unified query for fetching deployments by type, improving code efficiency. - Replaced instances of `ShowSchedulesLogs` with `ShowDeploymentsModal` in relevant components to streamline deployment log access.
This commit is contained in:
parent
2fa0c7dfd2
commit
e09447d4b4
@ -0,0 +1,63 @@
|
|||||||
|
import { Button } from "@/components/ui/button";
|
||||||
|
import { Dialog, DialogContent, DialogTrigger } from "@/components/ui/dialog";
|
||||||
|
|
||||||
|
import type { RouterOutputs } from "@/utils/api";
|
||||||
|
import { useState } from "react";
|
||||||
|
import { ShowDeployment } from "../deployments/show-deployment";
|
||||||
|
import { ShowDeployments } from "./show-deployments";
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
id: string;
|
||||||
|
type: "application" | "compose" | "schedule" | "server" | "backup";
|
||||||
|
serverId?: string;
|
||||||
|
refreshToken?: string;
|
||||||
|
children?: React.ReactNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const formatDuration = (seconds: number) => {
|
||||||
|
if (seconds < 60) return `${seconds}s`;
|
||||||
|
const minutes = Math.floor(seconds / 60);
|
||||||
|
const remainingSeconds = seconds % 60;
|
||||||
|
return `${minutes}m ${remainingSeconds}s`;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const ShowDeploymentsModal = ({
|
||||||
|
id,
|
||||||
|
type,
|
||||||
|
serverId,
|
||||||
|
refreshToken,
|
||||||
|
children,
|
||||||
|
}: Props) => {
|
||||||
|
const [activeLog, setActiveLog] = useState<
|
||||||
|
RouterOutputs["deployment"]["all"][number] | null
|
||||||
|
>(null);
|
||||||
|
const [isOpen, setIsOpen] = useState(false);
|
||||||
|
return (
|
||||||
|
<Dialog open={isOpen} onOpenChange={setIsOpen}>
|
||||||
|
<DialogTrigger asChild>
|
||||||
|
{children ? (
|
||||||
|
children
|
||||||
|
) : (
|
||||||
|
<Button className="sm:w-auto w-full" size="sm" variant="outline">
|
||||||
|
View Logs
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</DialogTrigger>
|
||||||
|
<DialogContent className="max-h-screen overflow-y-auto sm:max-w-5xl p-0">
|
||||||
|
<ShowDeployments
|
||||||
|
id={id}
|
||||||
|
type={type}
|
||||||
|
serverId={serverId}
|
||||||
|
refreshToken={refreshToken}
|
||||||
|
/>
|
||||||
|
</DialogContent>
|
||||||
|
<ShowDeployment
|
||||||
|
serverId={serverId || ""}
|
||||||
|
open={Boolean(activeLog && activeLog.logPath !== null)}
|
||||||
|
onClose={() => setActiveLog(null)}
|
||||||
|
logPath={activeLog?.logPath || ""}
|
||||||
|
errorMessage={activeLog?.errorMessage || ""}
|
||||||
|
/>
|
||||||
|
</Dialog>
|
||||||
|
);
|
||||||
|
};
|
@ -16,35 +16,34 @@ import { RefreshToken } from "./refresh-token";
|
|||||||
import { ShowDeployment } from "./show-deployment";
|
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 {
|
||||||
id: string;
|
id: string;
|
||||||
type: "application" | "compose";
|
type: "application" | "compose" | "schedule" | "server" | "backup";
|
||||||
|
refreshToken?: string;
|
||||||
|
serverId?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const ShowDeployments = ({ id, type }: Props) => {
|
export const ShowDeployments = ({
|
||||||
|
id,
|
||||||
|
type,
|
||||||
|
refreshToken,
|
||||||
|
serverId,
|
||||||
|
}: Props) => {
|
||||||
const [activeLog, setActiveLog] = useState<
|
const [activeLog, setActiveLog] = useState<
|
||||||
RouterOutputs["deployment"]["all"][number] | null
|
RouterOutputs["deployment"]["all"][number] | null
|
||||||
>(null);
|
>(null);
|
||||||
const { data } =
|
|
||||||
type === "application"
|
|
||||||
? api.application.one.useQuery({ applicationId: id })
|
|
||||||
: api.compose.one.useQuery({ composeId: id });
|
|
||||||
const { data: deployments, isLoading: isLoadingDeployments } =
|
const { data: deployments, isLoading: isLoadingDeployments } =
|
||||||
type === "application"
|
api.deployment.allByType.useQuery(
|
||||||
? api.deployment.all.useQuery(
|
{
|
||||||
{ applicationId: id },
|
id,
|
||||||
{
|
type,
|
||||||
enabled: !!id,
|
},
|
||||||
refetchInterval: 1000,
|
{
|
||||||
},
|
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(() => {
|
||||||
@ -52,7 +51,7 @@ export const ShowDeployments = ({ id, type }: Props) => {
|
|||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card className="bg-background">
|
<Card className="bg-background border-none">
|
||||||
<CardHeader className="flex flex-row items-center justify-between flex-wrap gap-2">
|
<CardHeader className="flex flex-row items-center justify-between flex-wrap gap-2">
|
||||||
<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>
|
||||||
@ -60,24 +59,27 @@ export const ShowDeployments = ({ id, type }: Props) => {
|
|||||||
See all the 10 last deployments for this {type}
|
See all the 10 last deployments for this {type}
|
||||||
</CardDescription>
|
</CardDescription>
|
||||||
</div>
|
</div>
|
||||||
<CancelQueues id={id} type={type} />
|
{refreshToken && <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">
|
{refreshToken && (
|
||||||
<span>
|
<div className="flex flex-col gap-2 text-sm">
|
||||||
If you want to re-deploy this application use this URL in the config
|
<span>
|
||||||
of your git provider or docker
|
If you want to re-deploy this application use this URL in the
|
||||||
</span>
|
config of your git provider or docker
|
||||||
<div className="flex flex-row items-center gap-2 flex-wrap">
|
</span>
|
||||||
<span>Webhook URL: </span>
|
<div className="flex flex-row items-center gap-2 flex-wrap">
|
||||||
<div className="flex flex-row items-center gap-2">
|
<span>Webhook URL: </span>
|
||||||
<span className="break-all text-muted-foreground">
|
<div className="flex flex-row items-center gap-2">
|
||||||
{`${url}/api/deploy/${data?.refreshToken}`}
|
<span className="break-all text-muted-foreground">
|
||||||
</span>
|
{`${url}/api/deploy/${refreshToken}`}
|
||||||
<RefreshToken id={id} type={type} />
|
</span>
|
||||||
|
<RefreshToken id={id} type={type} />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
)}
|
||||||
|
|
||||||
{isLoadingDeployments ? (
|
{isLoadingDeployments ? (
|
||||||
<div className="flex w-full flex-row items-center justify-center gap-3 pt-10 min-h-[25vh]">
|
<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" />
|
<Loader2 className="size-6 text-muted-foreground animate-spin" />
|
||||||
@ -85,7 +87,7 @@ export const ShowDeployments = ({ id, type }: Props) => {
|
|||||||
Loading deployments...
|
Loading deployments...
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
) : data?.deployments?.length === 0 ? (
|
) : deployments?.length === 0 ? (
|
||||||
<div className="flex w-full flex-col items-center justify-center gap-3 pt-10 min-h-[25vh]">
|
<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">
|
||||||
@ -149,7 +151,7 @@ export const ShowDeployments = ({ id, type }: Props) => {
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<ShowDeployment
|
<ShowDeployment
|
||||||
serverId={data?.serverId || ""}
|
serverId={serverId}
|
||||||
open={Boolean(activeLog && activeLog.logPath !== null)}
|
open={Boolean(activeLog && activeLog.logPath !== null)}
|
||||||
onClose={() => setActiveLog(null)}
|
onClose={() => setActiveLog(null)}
|
||||||
logPath={activeLog?.logPath || ""}
|
logPath={activeLog?.logPath || ""}
|
||||||
|
@ -18,7 +18,6 @@ import {
|
|||||||
} from "@/components/ui/card";
|
} from "@/components/ui/card";
|
||||||
import { Badge } from "@/components/ui/badge";
|
import { Badge } from "@/components/ui/badge";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
import { ShowSchedulesLogs } from "./show-schedules-logs";
|
|
||||||
import {
|
import {
|
||||||
Tooltip,
|
Tooltip,
|
||||||
TooltipContent,
|
TooltipContent,
|
||||||
@ -26,6 +25,7 @@ import {
|
|||||||
TooltipTrigger,
|
TooltipTrigger,
|
||||||
} from "@/components/ui/tooltip";
|
} from "@/components/ui/tooltip";
|
||||||
import { DialogAction } from "@/components/shared/dialog-action";
|
import { DialogAction } from "@/components/shared/dialog-action";
|
||||||
|
import { ShowDeploymentsModal } from "../deployments/show-deployments-modal";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
id: string;
|
id: string;
|
||||||
@ -88,7 +88,6 @@ export const ShowSchedules = ({ id, scheduleType = "application" }: Props) => {
|
|||||||
schedule.serverId ||
|
schedule.serverId ||
|
||||||
schedule.application?.serverId ||
|
schedule.application?.serverId ||
|
||||||
schedule.compose?.serverId;
|
schedule.compose?.serverId;
|
||||||
const deployments = schedule.deployments;
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
key={schedule.scheduleId}
|
key={schedule.scheduleId}
|
||||||
@ -144,14 +143,15 @@ export const ShowSchedules = ({ id, scheduleType = "application" }: Props) => {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex items-center gap-1.5">
|
<div className="flex items-center gap-1.5">
|
||||||
<ShowSchedulesLogs
|
<ShowDeploymentsModal
|
||||||
deployments={deployments || []}
|
id={schedule.scheduleId}
|
||||||
|
type="schedule"
|
||||||
serverId={serverId || undefined}
|
serverId={serverId || undefined}
|
||||||
>
|
>
|
||||||
<Button variant="ghost" size="icon">
|
<Button variant="ghost" size="icon">
|
||||||
<ClipboardList className="size-4 transition-colors " />
|
<ClipboardList className="size-4 transition-colors " />
|
||||||
</Button>
|
</Button>
|
||||||
</ShowSchedulesLogs>
|
</ShowDeploymentsModal>
|
||||||
|
|
||||||
<TooltipProvider delayDuration={0}>
|
<TooltipProvider delayDuration={0}>
|
||||||
<Tooltip>
|
<Tooltip>
|
||||||
|
@ -35,7 +35,7 @@ import {
|
|||||||
PostgresqlIcon,
|
PostgresqlIcon,
|
||||||
} from "@/components/icons/data-tools-icons";
|
} from "@/components/icons/data-tools-icons";
|
||||||
import { AlertBlock } from "@/components/shared/alert-block";
|
import { AlertBlock } from "@/components/shared/alert-block";
|
||||||
import { ShowSchedulesLogs } from "../../application/schedules/show-schedules-logs";
|
import { ShowDeploymentsModal } from "../../application/deployments/show-deployments-modal";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
id: string;
|
id: string;
|
||||||
@ -179,12 +179,6 @@ export const ShowBackups = ({
|
|||||||
)}
|
)}
|
||||||
<div className="flex flex-col gap-6">
|
<div className="flex flex-col gap-6">
|
||||||
{postgres?.backups.map((backup) => {
|
{postgres?.backups.map((backup) => {
|
||||||
const orderedDeployments = backup.deployments.sort(
|
|
||||||
(a, b) =>
|
|
||||||
new Date(b.createdAt).getTime() -
|
|
||||||
new Date(a.createdAt).getTime(),
|
|
||||||
);
|
|
||||||
|
|
||||||
const serverId =
|
const serverId =
|
||||||
"serverId" in postgres ? postgres.serverId : undefined;
|
"serverId" in postgres ? postgres.serverId : undefined;
|
||||||
|
|
||||||
@ -285,8 +279,9 @@ export const ShowBackups = ({
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex flex-row md:flex-col gap-1.5">
|
<div className="flex flex-row md:flex-col gap-1.5">
|
||||||
<ShowSchedulesLogs
|
<ShowDeploymentsModal
|
||||||
deployments={orderedDeployments}
|
id={backup.backupId}
|
||||||
|
type="backup"
|
||||||
serverId={serverId || undefined}
|
serverId={serverId || undefined}
|
||||||
>
|
>
|
||||||
<Button
|
<Button
|
||||||
@ -296,7 +291,7 @@ export const ShowBackups = ({
|
|||||||
>
|
>
|
||||||
<ClipboardList className="size-4 transition-colors " />
|
<ClipboardList className="size-4 transition-colors " />
|
||||||
</Button>
|
</Button>
|
||||||
</ShowSchedulesLogs>
|
</ShowDeploymentsModal>
|
||||||
<TooltipProvider delayDuration={0}>
|
<TooltipProvider delayDuration={0}>
|
||||||
<Tooltip>
|
<Tooltip>
|
||||||
<TooltipTrigger asChild>
|
<TooltipTrigger asChild>
|
||||||
|
@ -318,9 +318,14 @@ const Service = (
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</TabsContent>
|
</TabsContent>
|
||||||
<TabsContent value="deployments" className="w-full">
|
<TabsContent value="deployments" className="w-full pt-2.5">
|
||||||
<div className="flex flex-col gap-4 pt-2.5">
|
<div className="flex flex-col gap-4 border rounded-lg">
|
||||||
<ShowDeployments id={applicationId} type="application" />
|
<ShowDeployments
|
||||||
|
id={applicationId}
|
||||||
|
type="application"
|
||||||
|
serverId={data?.serverId || ""}
|
||||||
|
refreshToken={data?.refreshToken || ""}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</TabsContent>
|
</TabsContent>
|
||||||
<TabsContent value="preview-deployments" className="w-full">
|
<TabsContent value="preview-deployments" className="w-full">
|
||||||
|
@ -6,7 +6,6 @@ import { ShowEnvironment } from "@/components/dashboard/application/environment/
|
|||||||
import { ShowSchedules } from "@/components/dashboard/application/schedules/show-schedules";
|
import { ShowSchedules } from "@/components/dashboard/application/schedules/show-schedules";
|
||||||
import { AddCommandCompose } from "@/components/dashboard/compose/advanced/add-command";
|
import { AddCommandCompose } from "@/components/dashboard/compose/advanced/add-command";
|
||||||
import { DeleteService } from "@/components/dashboard/compose/delete-service";
|
import { DeleteService } from "@/components/dashboard/compose/delete-service";
|
||||||
import { ShowDeploymentsCompose } from "@/components/dashboard/compose/deployments/show-deployments-compose";
|
|
||||||
import { ShowGeneralCompose } from "@/components/dashboard/compose/general/show";
|
import { ShowGeneralCompose } from "@/components/dashboard/compose/general/show";
|
||||||
import { ShowDockerLogsCompose } from "@/components/dashboard/compose/logs/show";
|
import { ShowDockerLogsCompose } from "@/components/dashboard/compose/logs/show";
|
||||||
import { ShowDockerLogsStack } from "@/components/dashboard/compose/logs/show-stack";
|
import { ShowDockerLogsStack } from "@/components/dashboard/compose/logs/show-stack";
|
||||||
@ -334,7 +333,12 @@ 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">
|
||||||
<ShowDeployments id={composeId} type="compose" />
|
<ShowDeployments
|
||||||
|
id={composeId}
|
||||||
|
type="compose"
|
||||||
|
serverId={data?.serverId || ""}
|
||||||
|
refreshToken={data?.refreshToken || ""}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</TabsContent>
|
</TabsContent>
|
||||||
|
|
||||||
|
@ -2,6 +2,8 @@ import {
|
|||||||
apiFindAllByApplication,
|
apiFindAllByApplication,
|
||||||
apiFindAllByCompose,
|
apiFindAllByCompose,
|
||||||
apiFindAllByServer,
|
apiFindAllByServer,
|
||||||
|
apiFindAllByType,
|
||||||
|
deployments,
|
||||||
} from "@/server/db/schema";
|
} from "@/server/db/schema";
|
||||||
import {
|
import {
|
||||||
findAllDeploymentsByApplicationId,
|
findAllDeploymentsByApplicationId,
|
||||||
@ -12,7 +14,9 @@ import {
|
|||||||
findServerById,
|
findServerById,
|
||||||
} from "@dokploy/server";
|
} from "@dokploy/server";
|
||||||
import { TRPCError } from "@trpc/server";
|
import { TRPCError } from "@trpc/server";
|
||||||
|
import { desc, eq } from "drizzle-orm";
|
||||||
import { createTRPCRouter, protectedProcedure } from "../trpc";
|
import { createTRPCRouter, protectedProcedure } from "../trpc";
|
||||||
|
import { db } from "@/server/db";
|
||||||
|
|
||||||
export const deploymentRouter = createTRPCRouter({
|
export const deploymentRouter = createTRPCRouter({
|
||||||
all: protectedProcedure
|
all: protectedProcedure
|
||||||
@ -54,4 +58,14 @@ export const deploymentRouter = createTRPCRouter({
|
|||||||
}
|
}
|
||||||
return await findAllDeploymentsByServerId(input.serverId);
|
return await findAllDeploymentsByServerId(input.serverId);
|
||||||
}),
|
}),
|
||||||
|
|
||||||
|
allByType: protectedProcedure
|
||||||
|
.input(apiFindAllByType)
|
||||||
|
.query(async ({ input }) => {
|
||||||
|
const deploymentsList = await db.query.deployments.findMany({
|
||||||
|
where: eq(deployments[`${input.type}Id`], input.id),
|
||||||
|
orderBy: desc(deployments.createdAt),
|
||||||
|
});
|
||||||
|
return deploymentsList;
|
||||||
|
}),
|
||||||
});
|
});
|
||||||
|
@ -195,3 +195,17 @@ export const apiFindAllByServer = schema
|
|||||||
serverId: z.string().min(1),
|
serverId: z.string().min(1),
|
||||||
})
|
})
|
||||||
.required();
|
.required();
|
||||||
|
|
||||||
|
export const apiFindAllByType = z
|
||||||
|
.object({
|
||||||
|
id: z.string().min(1),
|
||||||
|
type: z.enum([
|
||||||
|
"application",
|
||||||
|
"compose",
|
||||||
|
"server",
|
||||||
|
"schedule",
|
||||||
|
"previewDeployment",
|
||||||
|
"backup",
|
||||||
|
]),
|
||||||
|
})
|
||||||
|
.required();
|
||||||
|
Loading…
Reference in New Issue
Block a user