mirror of
https://github.com/LukeHagar/dokploy.git
synced 2025-12-10 04:19:48 +00:00
refactor: lint
This commit is contained in:
@@ -1,3 +1,4 @@
|
|||||||
|
import { Badge } from "@/components/ui/badge";
|
||||||
import {
|
import {
|
||||||
Dialog,
|
Dialog,
|
||||||
DialogContent,
|
DialogContent,
|
||||||
@@ -5,11 +6,10 @@ import {
|
|||||||
DialogHeader,
|
DialogHeader,
|
||||||
DialogTitle,
|
DialogTitle,
|
||||||
} from "@/components/ui/dialog";
|
} from "@/components/ui/dialog";
|
||||||
|
import { Loader2 } from "lucide-react";
|
||||||
import { useEffect, useRef, useState } from "react";
|
import { useEffect, useRef, useState } from "react";
|
||||||
import { TerminalLine } from "../../docker/logs/terminal-line";
|
import { TerminalLine } from "../../docker/logs/terminal-line";
|
||||||
import { LogLine, parseLogs } from "../../docker/logs/utils";
|
import { type LogLine, parseLogs } from "../../docker/logs/utils";
|
||||||
import { Badge } from "@/components/ui/badge";
|
|
||||||
import { Loader2 } from "lucide-react";
|
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
logPath: string | null;
|
logPath: string | null;
|
||||||
@@ -24,21 +24,20 @@ export const ShowDeployment = ({ logPath, open, onClose, serverId }: Props) => {
|
|||||||
const [autoScroll, setAutoScroll] = useState(true);
|
const [autoScroll, setAutoScroll] = useState(true);
|
||||||
const scrollRef = useRef<HTMLDivElement>(null);
|
const scrollRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
|
|
||||||
const scrollToBottom = () => {
|
const scrollToBottom = () => {
|
||||||
if (autoScroll && scrollRef.current) {
|
if (autoScroll && scrollRef.current) {
|
||||||
scrollRef.current.scrollTop = scrollRef.current.scrollHeight;
|
scrollRef.current.scrollTop = scrollRef.current.scrollHeight;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleScroll = () => {
|
const handleScroll = () => {
|
||||||
if (!scrollRef.current) return;
|
if (!scrollRef.current) return;
|
||||||
|
|
||||||
const { scrollTop, scrollHeight, clientHeight } = scrollRef.current;
|
const { scrollTop, scrollHeight, clientHeight } = scrollRef.current;
|
||||||
const isAtBottom = Math.abs(scrollHeight - scrollTop - clientHeight) < 10;
|
const isAtBottom = Math.abs(scrollHeight - scrollTop - clientHeight) < 10;
|
||||||
setAutoScroll(isAtBottom);
|
setAutoScroll(isAtBottom);
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!open || !logPath) return;
|
if (!open || !logPath) return;
|
||||||
|
|
||||||
@@ -69,7 +68,6 @@ export const ShowDeployment = ({ logPath, open, onClose, serverId }: Props) => {
|
|||||||
};
|
};
|
||||||
}, [logPath, open]);
|
}, [logPath, open]);
|
||||||
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const logs = parseLogs(data);
|
const logs = parseLogs(data);
|
||||||
setFilteredLogs(logs);
|
setFilteredLogs(logs);
|
||||||
@@ -77,12 +75,11 @@ export const ShowDeployment = ({ logPath, open, onClose, serverId }: Props) => {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
scrollToBottom();
|
scrollToBottom();
|
||||||
|
|
||||||
if (autoScroll && scrollRef.current) {
|
|
||||||
scrollRef.current.scrollTop = scrollRef.current.scrollHeight;
|
|
||||||
}
|
|
||||||
}, [filteredLogs, autoScroll]);
|
|
||||||
|
|
||||||
|
if (autoScroll && scrollRef.current) {
|
||||||
|
scrollRef.current.scrollTop = scrollRef.current.scrollHeight;
|
||||||
|
}
|
||||||
|
}, [filteredLogs, autoScroll]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Dialog
|
<Dialog
|
||||||
@@ -104,27 +101,28 @@ export const ShowDeployment = ({ logPath, open, onClose, serverId }: Props) => {
|
|||||||
<DialogHeader>
|
<DialogHeader>
|
||||||
<DialogTitle>Deployment</DialogTitle>
|
<DialogTitle>Deployment</DialogTitle>
|
||||||
<DialogDescription>
|
<DialogDescription>
|
||||||
See all the details of this deployment | <Badge variant="blank" className="text-xs">{filteredLogs.length} lines</Badge>
|
See all the details of this deployment |{" "}
|
||||||
|
<Badge variant="blank" className="text-xs">
|
||||||
|
{filteredLogs.length} lines
|
||||||
|
</Badge>
|
||||||
</DialogDescription>
|
</DialogDescription>
|
||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
ref={scrollRef}
|
ref={scrollRef}
|
||||||
onScroll={handleScroll}
|
onScroll={handleScroll}
|
||||||
className="h-[720px] overflow-y-auto space-y-0 border p-4 bg-[#fafafa] dark:bg-[#050506] rounded custom-logs-scrollbar"
|
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
|
{filteredLogs.length > 0 ? (
|
||||||
key={index}
|
filteredLogs.map((log: LogLine, index: number) => (
|
||||||
log={log}
|
<TerminalLine key={index} log={log} noTimestamp />
|
||||||
noTimestamp
|
))
|
||||||
/>
|
) : (
|
||||||
)) :
|
<div className="flex justify-center items-center h-full text-muted-foreground">
|
||||||
(
|
<Loader2 className="h-6 w-6 animate-spin" />
|
||||||
<div className="flex justify-center items-center h-full text-muted-foreground">
|
</div>
|
||||||
<Loader2 className="h-6 w-6 animate-spin" />
|
)}
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
|
|||||||
@@ -26,7 +26,9 @@ export const ShowPreviewBuilds = ({ deployments, serverId }: Props) => {
|
|||||||
return (
|
return (
|
||||||
<Dialog open={isOpen} onOpenChange={setIsOpen}>
|
<Dialog open={isOpen} onOpenChange={setIsOpen}>
|
||||||
<DialogTrigger asChild>
|
<DialogTrigger asChild>
|
||||||
<Button className="sm:w-auto w-full" size="sm" variant="outline">View Builds</Button>
|
<Button className="sm:w-auto w-full" size="sm" variant="outline">
|
||||||
|
View Builds
|
||||||
|
</Button>
|
||||||
</DialogTrigger>
|
</DialogTrigger>
|
||||||
<DialogContent className="max-h-screen overflow-y-auto sm:max-w-5xl">
|
<DialogContent className="max-h-screen overflow-y-auto sm:max-w-5xl">
|
||||||
<DialogHeader>
|
<DialogHeader>
|
||||||
|
|||||||
@@ -1,237 +1,237 @@
|
|||||||
import React from "react";
|
import { DateTooltip } from "@/components/shared/date-tooltip";
|
||||||
|
import { DialogAction } from "@/components/shared/dialog-action";
|
||||||
|
import { StatusTooltip } from "@/components/shared/status-tooltip";
|
||||||
import { Badge } from "@/components/ui/badge";
|
import { Badge } from "@/components/ui/badge";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { Separator } from "@/components/ui/separator";
|
|
||||||
import {
|
import {
|
||||||
Clock,
|
Card,
|
||||||
GitBranch,
|
CardContent,
|
||||||
GitPullRequest,
|
CardDescription,
|
||||||
Pencil,
|
CardHeader,
|
||||||
RocketIcon,
|
CardTitle,
|
||||||
|
} from "@/components/ui/card";
|
||||||
|
import { Separator } from "@/components/ui/separator";
|
||||||
|
import { api } from "@/utils/api";
|
||||||
|
import {
|
||||||
|
Clock,
|
||||||
|
GitBranch,
|
||||||
|
GitPullRequest,
|
||||||
|
Pencil,
|
||||||
|
RocketIcon,
|
||||||
} from "lucide-react";
|
} from "lucide-react";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { ShowModalLogs } from "../../settings/web-server/show-modal-logs";
|
import React from "react";
|
||||||
import { DialogAction } from "@/components/shared/dialog-action";
|
|
||||||
import { api } from "@/utils/api";
|
|
||||||
import { ShowPreviewBuilds } from "./show-preview-builds";
|
|
||||||
import { DateTooltip } from "@/components/shared/date-tooltip";
|
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
import { StatusTooltip } from "@/components/shared/status-tooltip";
|
import { ShowModalLogs } from "../../settings/web-server/show-modal-logs";
|
||||||
import { AddPreviewDomain } from "./add-preview-domain";
|
import { AddPreviewDomain } from "./add-preview-domain";
|
||||||
import {
|
import { ShowPreviewBuilds } from "./show-preview-builds";
|
||||||
Card,
|
|
||||||
CardContent,
|
|
||||||
CardDescription,
|
|
||||||
CardHeader,
|
|
||||||
CardTitle,
|
|
||||||
} from "@/components/ui/card";
|
|
||||||
import { ShowPreviewSettings } from "./show-preview-settings";
|
import { ShowPreviewSettings } from "./show-preview-settings";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
applicationId: string;
|
applicationId: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const ShowPreviewDeployments = ({ applicationId }: Props) => {
|
export const ShowPreviewDeployments = ({ applicationId }: Props) => {
|
||||||
const { data } = api.application.one.useQuery({ applicationId });
|
const { data } = api.application.one.useQuery({ applicationId });
|
||||||
|
|
||||||
const { mutateAsync: deletePreviewDeployment, isLoading } =
|
const { mutateAsync: deletePreviewDeployment, isLoading } =
|
||||||
api.previewDeployment.delete.useMutation();
|
api.previewDeployment.delete.useMutation();
|
||||||
|
|
||||||
const { data: previewDeployments, refetch: refetchPreviewDeployments } =
|
const { data: previewDeployments, refetch: refetchPreviewDeployments } =
|
||||||
api.previewDeployment.all.useQuery(
|
api.previewDeployment.all.useQuery(
|
||||||
{ applicationId },
|
{ applicationId },
|
||||||
{
|
{
|
||||||
enabled: !!applicationId,
|
enabled: !!applicationId,
|
||||||
}
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleDeletePreviewDeployment = async (previewDeploymentId: string) => {
|
const handleDeletePreviewDeployment = async (previewDeploymentId: string) => {
|
||||||
deletePreviewDeployment({
|
deletePreviewDeployment({
|
||||||
previewDeploymentId: previewDeploymentId,
|
previewDeploymentId: previewDeploymentId,
|
||||||
})
|
})
|
||||||
.then(() => {
|
.then(() => {
|
||||||
refetchPreviewDeployments();
|
refetchPreviewDeployments();
|
||||||
toast.success("Preview deployment deleted");
|
toast.success("Preview deployment deleted");
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
toast.error(error.message);
|
toast.error(error.message);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card className="bg-background">
|
<Card className="bg-background">
|
||||||
<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">Preview Deployments</CardTitle>
|
<CardTitle className="text-xl">Preview Deployments</CardTitle>
|
||||||
<CardDescription>See all the preview deployments</CardDescription>
|
<CardDescription>See all the preview deployments</CardDescription>
|
||||||
</div>
|
</div>
|
||||||
{data?.isPreviewDeploymentsActive && (
|
{data?.isPreviewDeploymentsActive && (
|
||||||
<ShowPreviewSettings applicationId={applicationId} />
|
<ShowPreviewSettings applicationId={applicationId} />
|
||||||
)}
|
)}
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent className="flex flex-col gap-4">
|
<CardContent className="flex flex-col gap-4">
|
||||||
{data?.isPreviewDeploymentsActive ? (
|
{data?.isPreviewDeploymentsActive ? (
|
||||||
<>
|
<>
|
||||||
<div className="flex flex-col gap-2 text-sm">
|
<div className="flex flex-col gap-2 text-sm">
|
||||||
<span>
|
<span>
|
||||||
Preview deployments are a way to test your application before it
|
Preview deployments are a way to test your application before it
|
||||||
is deployed to production. It will create a new deployment for
|
is deployed to production. It will create a new deployment for
|
||||||
each pull request you create.
|
each pull request you create.
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
{!previewDeployments?.length ? (
|
{!previewDeployments?.length ? (
|
||||||
<div className="flex w-full flex-col items-center justify-center gap-3 pt-10">
|
<div className="flex w-full flex-col items-center justify-center gap-3 pt-10">
|
||||||
<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 preview deployments found
|
No preview deployments found
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div className="flex flex-col gap-4">
|
<div className="flex flex-col gap-4">
|
||||||
{previewDeployments.map((previewDeployment) => (
|
{previewDeployments.map((previewDeployment) => (
|
||||||
<div
|
<div
|
||||||
key={previewDeployment.previewDeploymentId}
|
key={previewDeployment.previewDeploymentId}
|
||||||
className="w-full border rounded-xl"
|
className="w-full border rounded-xl"
|
||||||
>
|
>
|
||||||
<div className="md:p-6 p-2 md:pb-3 flex flex-row items-center justify-between">
|
<div className="md:p-6 p-2 md:pb-3 flex flex-row items-center justify-between">
|
||||||
<span className="text-lg font-bold">
|
<span className="text-lg font-bold">
|
||||||
{previewDeployment.pullRequestTitle}
|
{previewDeployment.pullRequestTitle}
|
||||||
</span>
|
</span>
|
||||||
<Badge
|
<Badge
|
||||||
variant="outline"
|
variant="outline"
|
||||||
className="text-sm font-medium gap-x-2"
|
className="text-sm font-medium gap-x-2"
|
||||||
>
|
>
|
||||||
<StatusTooltip
|
<StatusTooltip
|
||||||
status={previewDeployment.previewStatus}
|
status={previewDeployment.previewStatus}
|
||||||
className="size-2.5"
|
className="size-2.5"
|
||||||
/>
|
/>
|
||||||
{previewDeployment.previewStatus
|
{previewDeployment.previewStatus
|
||||||
?.replace("running", "Running")
|
?.replace("running", "Running")
|
||||||
.replace("done", "Done")
|
.replace("done", "Done")
|
||||||
.replace("error", "Error")
|
.replace("error", "Error")
|
||||||
.replace("idle", "Idle") || "Idle"}
|
.replace("idle", "Idle") || "Idle"}
|
||||||
</Badge>
|
</Badge>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="md:p-6 p-2 md:pt-0 space-y-4">
|
<div className="md:p-6 p-2 md:pt-0 space-y-4">
|
||||||
<div className="flex sm:flex-row flex-col items-center gap-2">
|
<div className="flex sm:flex-row flex-col items-center gap-2">
|
||||||
<Link
|
<Link
|
||||||
href={`http://${previewDeployment.domain?.host}`}
|
href={`http://${previewDeployment.domain?.host}`}
|
||||||
target="_blank"
|
target="_blank"
|
||||||
className="text-sm text-blue-500/95 hover:underline gap-2 flex w-full sm:flex-row flex-col items-center justify-between rounded-lg border p-2"
|
className="text-sm text-blue-500/95 hover:underline gap-2 flex w-full sm:flex-row flex-col items-center justify-between rounded-lg border p-2"
|
||||||
>
|
>
|
||||||
{previewDeployment.domain?.host}
|
{previewDeployment.domain?.host}
|
||||||
</Link>
|
</Link>
|
||||||
|
|
||||||
<AddPreviewDomain
|
<AddPreviewDomain
|
||||||
previewDeploymentId={
|
previewDeploymentId={
|
||||||
previewDeployment.previewDeploymentId
|
previewDeployment.previewDeploymentId
|
||||||
}
|
}
|
||||||
domainId={previewDeployment.domain?.domainId}
|
domainId={previewDeployment.domain?.domainId}
|
||||||
>
|
>
|
||||||
<Button
|
<Button
|
||||||
className="sm:w-auto w-full"
|
className="sm:w-auto w-full"
|
||||||
size="sm"
|
size="sm"
|
||||||
variant="outline"
|
variant="outline"
|
||||||
>
|
>
|
||||||
<Pencil className="size-4" />
|
<Pencil className="size-4" />
|
||||||
Edit
|
Edit
|
||||||
</Button>
|
</Button>
|
||||||
</AddPreviewDomain>
|
</AddPreviewDomain>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex sm:flex-row text-sm flex-col items-center justify-between">
|
<div className="flex sm:flex-row text-sm flex-col items-center justify-between">
|
||||||
<div className="flex items-center space-x-2">
|
<div className="flex items-center space-x-2">
|
||||||
<GitBranch className="size-5 text-gray-400" />
|
<GitBranch className="size-5 text-gray-400" />
|
||||||
<span>Branch:</span>
|
<span>Branch:</span>
|
||||||
<Badge className="p-2" variant="blank">
|
<Badge className="p-2" variant="blank">
|
||||||
{previewDeployment.branch}
|
{previewDeployment.branch}
|
||||||
</Badge>
|
</Badge>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center space-x-2">
|
<div className="flex items-center space-x-2">
|
||||||
<Clock className="size-5 text-gray-400" />
|
<Clock className="size-5 text-gray-400" />
|
||||||
<span>Deployed:</span>
|
<span>Deployed:</span>
|
||||||
<Badge className="p-2" variant="blank">
|
<Badge className="p-2" variant="blank">
|
||||||
<DateTooltip date={previewDeployment.createdAt} />
|
<DateTooltip date={previewDeployment.createdAt} />
|
||||||
</Badge>
|
</Badge>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Separator />
|
<Separator />
|
||||||
|
|
||||||
<div className="rounded-lg bg-muted p-4">
|
<div className="rounded-lg bg-muted p-4">
|
||||||
<h3 className="mb-2 text-sm font-medium">
|
<h3 className="mb-2 text-sm font-medium">
|
||||||
Pull Request
|
Pull Request
|
||||||
</h3>
|
</h3>
|
||||||
<div className="flex items-center space-x-2 text-sm text-muted-foreground">
|
<div className="flex items-center space-x-2 text-sm text-muted-foreground">
|
||||||
<GitPullRequest className="size-5 text-gray-400" />
|
<GitPullRequest className="size-5 text-gray-400" />
|
||||||
<Link
|
<Link
|
||||||
className="hover:text-blue-500/95 hover:underline"
|
className="hover:text-blue-500/95 hover:underline"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
href={previewDeployment.pullRequestURL}
|
href={previewDeployment.pullRequestURL}
|
||||||
>
|
>
|
||||||
{previewDeployment.pullRequestTitle}
|
{previewDeployment.pullRequestTitle}
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="justify-center flex-wrap md:p-6 p-2 md:pt-0">
|
<div className="justify-center flex-wrap md:p-6 p-2 md:pt-0">
|
||||||
<div className="flex flex-wrap justify-end gap-2">
|
<div className="flex flex-wrap justify-end gap-2">
|
||||||
<ShowModalLogs
|
<ShowModalLogs
|
||||||
appName={previewDeployment.appName}
|
appName={previewDeployment.appName}
|
||||||
serverId={data?.serverId || ""}
|
serverId={data?.serverId || ""}
|
||||||
>
|
>
|
||||||
<Button
|
<Button
|
||||||
className="sm:w-auto w-full"
|
className="sm:w-auto w-full"
|
||||||
variant="outline"
|
variant="outline"
|
||||||
size="sm"
|
size="sm"
|
||||||
>
|
>
|
||||||
View Logs
|
View Logs
|
||||||
</Button>
|
</Button>
|
||||||
</ShowModalLogs>
|
</ShowModalLogs>
|
||||||
|
|
||||||
<ShowPreviewBuilds
|
<ShowPreviewBuilds
|
||||||
deployments={previewDeployment.deployments || []}
|
deployments={previewDeployment.deployments || []}
|
||||||
serverId={data?.serverId || ""}
|
serverId={data?.serverId || ""}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<DialogAction
|
<DialogAction
|
||||||
title="Delete Preview"
|
title="Delete Preview"
|
||||||
description="Are you sure you want to delete this preview?"
|
description="Are you sure you want to delete this preview?"
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
handleDeletePreviewDeployment(
|
handleDeletePreviewDeployment(
|
||||||
previewDeployment.previewDeploymentId
|
previewDeployment.previewDeploymentId,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<Button
|
<Button
|
||||||
className="sm:w-auto w-full"
|
className="sm:w-auto w-full"
|
||||||
variant="destructive"
|
variant="destructive"
|
||||||
isLoading={isLoading}
|
isLoading={isLoading}
|
||||||
size="sm"
|
size="sm"
|
||||||
>
|
>
|
||||||
Delete Preview
|
Delete Preview
|
||||||
</Button>
|
</Button>
|
||||||
</DialogAction>
|
</DialogAction>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
<div className="flex w-full flex-col items-center justify-center gap-3 pt-10">
|
<div className="flex w-full flex-col items-center justify-center gap-3 pt-10">
|
||||||
<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">
|
||||||
Preview deployments are disabled for this application, please
|
Preview deployments are disabled for this application, please
|
||||||
enable it
|
enable it
|
||||||
</span>
|
</span>
|
||||||
<ShowPreviewSettings applicationId={applicationId} />
|
<ShowPreviewSettings applicationId={applicationId} />
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
import { api } from "@/utils/api";
|
|
||||||
import { useEffect, useState } from "react";
|
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import {
|
import {
|
||||||
Dialog,
|
Dialog,
|
||||||
@@ -20,12 +18,7 @@ import {
|
|||||||
FormMessage,
|
FormMessage,
|
||||||
} from "@/components/ui/form";
|
} from "@/components/ui/form";
|
||||||
import { Input, NumberInput } from "@/components/ui/input";
|
import { Input, NumberInput } from "@/components/ui/input";
|
||||||
import { zodResolver } from "@hookform/resolvers/zod";
|
|
||||||
import { useForm } from "react-hook-form";
|
|
||||||
import { z } from "zod";
|
|
||||||
import { Secrets } from "@/components/ui/secrets";
|
import { Secrets } from "@/components/ui/secrets";
|
||||||
import { toast } from "sonner";
|
|
||||||
import { Switch } from "@/components/ui/switch";
|
|
||||||
import {
|
import {
|
||||||
Select,
|
Select,
|
||||||
SelectContent,
|
SelectContent,
|
||||||
@@ -33,6 +26,13 @@ import {
|
|||||||
SelectTrigger,
|
SelectTrigger,
|
||||||
SelectValue,
|
SelectValue,
|
||||||
} from "@/components/ui/select";
|
} from "@/components/ui/select";
|
||||||
|
import { Switch } from "@/components/ui/switch";
|
||||||
|
import { api } from "@/utils/api";
|
||||||
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
|
import { useEffect, useState } from "react";
|
||||||
|
import { useForm } from "react-hook-form";
|
||||||
|
import { toast } from "sonner";
|
||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
const schema = z.object({
|
const schema = z.object({
|
||||||
env: z.string(),
|
env: z.string(),
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { Badge } from "@/components/ui/badge";
|
||||||
import {
|
import {
|
||||||
Dialog,
|
Dialog,
|
||||||
DialogContent,
|
DialogContent,
|
||||||
@@ -5,12 +6,10 @@ import {
|
|||||||
DialogHeader,
|
DialogHeader,
|
||||||
DialogTitle,
|
DialogTitle,
|
||||||
} from "@/components/ui/dialog";
|
} from "@/components/ui/dialog";
|
||||||
|
import { Loader2 } from "lucide-react";
|
||||||
import { useEffect, useRef, useState } from "react";
|
import { useEffect, useRef, useState } from "react";
|
||||||
import { TerminalLine } from "../../docker/logs/terminal-line";
|
import { TerminalLine } from "../../docker/logs/terminal-line";
|
||||||
import { LogLine, parseLogs } from "../../docker/logs/utils";
|
import { type LogLine, parseLogs } from "../../docker/logs/utils";
|
||||||
import { Badge } from "@/components/ui/badge";
|
|
||||||
import { Loader2 } from "lucide-react";
|
|
||||||
|
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
logPath: string | null;
|
logPath: string | null;
|
||||||
@@ -32,19 +31,18 @@ export const ShowDeploymentCompose = ({
|
|||||||
|
|
||||||
const scrollToBottom = () => {
|
const scrollToBottom = () => {
|
||||||
if (autoScroll && scrollRef.current) {
|
if (autoScroll && scrollRef.current) {
|
||||||
scrollRef.current.scrollTop = scrollRef.current.scrollHeight;
|
scrollRef.current.scrollTop = scrollRef.current.scrollHeight;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleScroll = () => {
|
const handleScroll = () => {
|
||||||
if (!scrollRef.current) return;
|
if (!scrollRef.current) return;
|
||||||
|
|
||||||
const { scrollTop, scrollHeight, clientHeight } = scrollRef.current;
|
const { scrollTop, scrollHeight, clientHeight } = scrollRef.current;
|
||||||
const isAtBottom = Math.abs(scrollHeight - scrollTop - clientHeight) < 10;
|
const isAtBottom = Math.abs(scrollHeight - scrollTop - clientHeight) < 10;
|
||||||
setAutoScroll(isAtBottom);
|
setAutoScroll(isAtBottom);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!open || !logPath) return;
|
if (!open || !logPath) return;
|
||||||
|
|
||||||
@@ -76,7 +74,6 @@ export const ShowDeploymentCompose = ({
|
|||||||
};
|
};
|
||||||
}, [logPath, open]);
|
}, [logPath, open]);
|
||||||
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const logs = parseLogs(data);
|
const logs = parseLogs(data);
|
||||||
setFilteredLogs(logs);
|
setFilteredLogs(logs);
|
||||||
@@ -84,11 +81,11 @@ export const ShowDeploymentCompose = ({
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
scrollToBottom();
|
scrollToBottom();
|
||||||
|
|
||||||
if (autoScroll && scrollRef.current) {
|
if (autoScroll && scrollRef.current) {
|
||||||
scrollRef.current.scrollTop = scrollRef.current.scrollHeight;
|
scrollRef.current.scrollTop = scrollRef.current.scrollHeight;
|
||||||
}
|
}
|
||||||
}, [filteredLogs, autoScroll]);
|
}, [filteredLogs, autoScroll]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Dialog
|
<Dialog
|
||||||
@@ -110,31 +107,27 @@ export const ShowDeploymentCompose = ({
|
|||||||
<DialogHeader>
|
<DialogHeader>
|
||||||
<DialogTitle>Deployment</DialogTitle>
|
<DialogTitle>Deployment</DialogTitle>
|
||||||
<DialogDescription>
|
<DialogDescription>
|
||||||
See all the details of this deployment | <Badge variant="blank" className="text-xs">{filteredLogs.length} lines</Badge>
|
See all the details of this deployment |{" "}
|
||||||
|
<Badge variant="blank" className="text-xs">
|
||||||
|
{filteredLogs.length} lines
|
||||||
|
</Badge>
|
||||||
</DialogDescription>
|
</DialogDescription>
|
||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
ref={scrollRef}
|
ref={scrollRef}
|
||||||
onScroll={handleScroll}
|
onScroll={handleScroll}
|
||||||
className="h-[720px] overflow-y-auto space-y-0 border p-4 bg-[#fafafa] dark:bg-[#050506] rounded custom-logs-scrollbar"
|
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 />
|
||||||
filteredLogs.length > 0 ? filteredLogs.map((log: LogLine, index: number) => (
|
))
|
||||||
<TerminalLine
|
) : (
|
||||||
key={index}
|
|
||||||
log={log}
|
|
||||||
noTimestamp
|
|
||||||
/>
|
|
||||||
)) :
|
|
||||||
(
|
|
||||||
<div className="flex justify-center items-center h-full text-muted-foreground">
|
<div className="flex justify-center items-center h-full text-muted-foreground">
|
||||||
<Loader2 className="h-6 w-6 animate-spin" />
|
<Loader2 className="h-6 w-6 animate-spin" />
|
||||||
</div>
|
</div>
|
||||||
)
|
)}
|
||||||
}
|
|
||||||
</div>
|
</div>
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
|
|||||||
@@ -53,7 +53,7 @@ export const DeployCompose = ({ composeId }: Props) => {
|
|||||||
})
|
})
|
||||||
.then(async () => {
|
.then(async () => {
|
||||||
router.push(
|
router.push(
|
||||||
`/dashboard/project/${data?.project.projectId}/services/compose/${composeId}?tab=deployments`
|
`/dashboard/project/${data?.project.projectId}/services/compose/${composeId}?tab=deployments`,
|
||||||
);
|
);
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.catch(() => {
|
||||||
|
|||||||
@@ -59,7 +59,7 @@ export const DockerTerminalModal = ({
|
|||||||
{children}
|
{children}
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
</DialogTrigger>
|
</DialogTrigger>
|
||||||
<DialogContent
|
<DialogContent
|
||||||
className="max-h-screen overflow-y-auto sm:max-w-7xl"
|
className="max-h-screen overflow-y-auto sm:max-w-7xl"
|
||||||
onEscapeKeyDown={(event) => event.preventDefault()}
|
onEscapeKeyDown={(event) => event.preventDefault()}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -1,35 +1,35 @@
|
|||||||
import { DateTooltip } from "@/components/shared/date-tooltip";
|
import { DateTooltip } from "@/components/shared/date-tooltip";
|
||||||
import {
|
import {
|
||||||
AlertDialog,
|
AlertDialog,
|
||||||
AlertDialogAction,
|
AlertDialogAction,
|
||||||
AlertDialogCancel,
|
AlertDialogCancel,
|
||||||
AlertDialogContent,
|
AlertDialogContent,
|
||||||
AlertDialogDescription,
|
AlertDialogDescription,
|
||||||
AlertDialogFooter,
|
AlertDialogFooter,
|
||||||
AlertDialogHeader,
|
AlertDialogHeader,
|
||||||
AlertDialogTitle,
|
AlertDialogTitle,
|
||||||
AlertDialogTrigger,
|
AlertDialogTrigger,
|
||||||
} from "@/components/ui/alert-dialog";
|
} from "@/components/ui/alert-dialog";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { Card, CardFooter, CardHeader, CardTitle } from "@/components/ui/card";
|
import { Card, CardFooter, CardHeader, CardTitle } from "@/components/ui/card";
|
||||||
import {
|
import {
|
||||||
DropdownMenu,
|
DropdownMenu,
|
||||||
DropdownMenuContent,
|
DropdownMenuContent,
|
||||||
DropdownMenuGroup,
|
DropdownMenuGroup,
|
||||||
DropdownMenuItem,
|
DropdownMenuItem,
|
||||||
DropdownMenuLabel,
|
DropdownMenuLabel,
|
||||||
DropdownMenuSeparator,
|
DropdownMenuSeparator,
|
||||||
DropdownMenuTrigger,
|
DropdownMenuTrigger,
|
||||||
} from "@/components/ui/dropdown-menu";
|
} from "@/components/ui/dropdown-menu";
|
||||||
import { api } from "@/utils/api";
|
import { api } from "@/utils/api";
|
||||||
import {
|
import {
|
||||||
AlertTriangle,
|
AlertTriangle,
|
||||||
BookIcon,
|
BookIcon,
|
||||||
ExternalLink,
|
ExternalLink,
|
||||||
ExternalLinkIcon,
|
ExternalLinkIcon,
|
||||||
FolderInput,
|
FolderInput,
|
||||||
MoreHorizontalIcon,
|
MoreHorizontalIcon,
|
||||||
TrashIcon,
|
TrashIcon,
|
||||||
} from "lucide-react";
|
} from "lucide-react";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { Fragment } from "react";
|
import { Fragment } from "react";
|
||||||
@@ -38,257 +38,257 @@ import { ProjectEnviroment } from "./project-enviroment";
|
|||||||
import { UpdateProject } from "./update";
|
import { UpdateProject } from "./update";
|
||||||
|
|
||||||
export const ShowProjects = () => {
|
export const ShowProjects = () => {
|
||||||
const utils = api.useUtils();
|
const utils = api.useUtils();
|
||||||
const { data } = api.project.all.useQuery();
|
const { data } = api.project.all.useQuery();
|
||||||
const { data: auth } = api.auth.get.useQuery();
|
const { data: auth } = api.auth.get.useQuery();
|
||||||
const { data: user } = api.user.byAuthId.useQuery(
|
const { data: user } = api.user.byAuthId.useQuery(
|
||||||
{
|
{
|
||||||
authId: auth?.id || "",
|
authId: auth?.id || "",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
enabled: !!auth?.id && auth?.rol === "user",
|
enabled: !!auth?.id && auth?.rol === "user",
|
||||||
}
|
},
|
||||||
);
|
);
|
||||||
const { mutateAsync } = api.project.remove.useMutation();
|
const { mutateAsync } = api.project.remove.useMutation();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{data?.length === 0 && (
|
{data?.length === 0 && (
|
||||||
<div className="mt-6 flex h-[50vh] w-full flex-col items-center justify-center space-y-4">
|
<div className="mt-6 flex h-[50vh] w-full flex-col items-center justify-center space-y-4">
|
||||||
<FolderInput className="size-10 md:size-28 text-muted-foreground" />
|
<FolderInput className="size-10 md:size-28 text-muted-foreground" />
|
||||||
<span className="text-center font-medium text-muted-foreground">
|
<span className="text-center font-medium text-muted-foreground">
|
||||||
No projects added yet. Click on Create project.
|
No projects added yet. Click on Create project.
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<div className="mt-6 w-full grid sm:grid-cols-2 lg:grid-cols-3 flex-wrap gap-5 pb-10">
|
<div className="mt-6 w-full grid sm:grid-cols-2 lg:grid-cols-3 flex-wrap gap-5 pb-10">
|
||||||
{data?.map((project) => {
|
{data?.map((project) => {
|
||||||
const emptyServices =
|
const emptyServices =
|
||||||
project?.mariadb.length === 0 &&
|
project?.mariadb.length === 0 &&
|
||||||
project?.mongo.length === 0 &&
|
project?.mongo.length === 0 &&
|
||||||
project?.mysql.length === 0 &&
|
project?.mysql.length === 0 &&
|
||||||
project?.postgres.length === 0 &&
|
project?.postgres.length === 0 &&
|
||||||
project?.redis.length === 0 &&
|
project?.redis.length === 0 &&
|
||||||
project?.applications.length === 0 &&
|
project?.applications.length === 0 &&
|
||||||
project?.compose.length === 0;
|
project?.compose.length === 0;
|
||||||
|
|
||||||
const totalServices =
|
const totalServices =
|
||||||
project?.mariadb.length +
|
project?.mariadb.length +
|
||||||
project?.mongo.length +
|
project?.mongo.length +
|
||||||
project?.mysql.length +
|
project?.mysql.length +
|
||||||
project?.postgres.length +
|
project?.postgres.length +
|
||||||
project?.redis.length +
|
project?.redis.length +
|
||||||
project?.applications.length +
|
project?.applications.length +
|
||||||
project?.compose.length;
|
project?.compose.length;
|
||||||
|
|
||||||
const flattedDomains = [
|
const flattedDomains = [
|
||||||
...project.applications.flatMap((a) => a.domains),
|
...project.applications.flatMap((a) => a.domains),
|
||||||
...project.compose.flatMap((a) => a.domains),
|
...project.compose.flatMap((a) => a.domains),
|
||||||
];
|
];
|
||||||
|
|
||||||
const renderDomainsDropdown = (
|
const renderDomainsDropdown = (
|
||||||
item: typeof project.compose | typeof project.applications
|
item: typeof project.compose | typeof project.applications,
|
||||||
) =>
|
) =>
|
||||||
item[0] ? (
|
item[0] ? (
|
||||||
<DropdownMenuGroup>
|
<DropdownMenuGroup>
|
||||||
<DropdownMenuLabel>
|
<DropdownMenuLabel>
|
||||||
{"applicationId" in item[0] ? "Applications" : "Compose"}
|
{"applicationId" in item[0] ? "Applications" : "Compose"}
|
||||||
</DropdownMenuLabel>
|
</DropdownMenuLabel>
|
||||||
{item.map((a) => (
|
{item.map((a) => (
|
||||||
<Fragment
|
<Fragment
|
||||||
key={"applicationId" in a ? a.applicationId : a.composeId}
|
key={"applicationId" in a ? a.applicationId : a.composeId}
|
||||||
>
|
>
|
||||||
<DropdownMenuSeparator />
|
<DropdownMenuSeparator />
|
||||||
<DropdownMenuGroup>
|
<DropdownMenuGroup>
|
||||||
<DropdownMenuLabel className="font-normal capitalize text-xs ">
|
<DropdownMenuLabel className="font-normal capitalize text-xs ">
|
||||||
{a.name}
|
{a.name}
|
||||||
</DropdownMenuLabel>
|
</DropdownMenuLabel>
|
||||||
<DropdownMenuSeparator />
|
<DropdownMenuSeparator />
|
||||||
{a.domains.map((domain) => (
|
{a.domains.map((domain) => (
|
||||||
<DropdownMenuItem key={domain.domainId} asChild>
|
<DropdownMenuItem key={domain.domainId} asChild>
|
||||||
<Link
|
<Link
|
||||||
className="space-x-4 text-xs cursor-pointer justify-between"
|
className="space-x-4 text-xs cursor-pointer justify-between"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
href={`${domain.https ? "https" : "http"}://${
|
href={`${domain.https ? "https" : "http"}://${
|
||||||
domain.host
|
domain.host
|
||||||
}${domain.path}`}
|
}${domain.path}`}
|
||||||
>
|
>
|
||||||
<span>{domain.host}</span>
|
<span>{domain.host}</span>
|
||||||
<ExternalLink className="size-4 shrink-0" />
|
<ExternalLink className="size-4 shrink-0" />
|
||||||
</Link>
|
</Link>
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
))}
|
))}
|
||||||
</DropdownMenuGroup>
|
</DropdownMenuGroup>
|
||||||
</Fragment>
|
</Fragment>
|
||||||
))}
|
))}
|
||||||
</DropdownMenuGroup>
|
</DropdownMenuGroup>
|
||||||
) : null;
|
) : null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div key={project.projectId} className="w-full lg:max-w-md">
|
<div key={project.projectId} className="w-full lg:max-w-md">
|
||||||
<Link href={`/dashboard/project/${project.projectId}`}>
|
<Link href={`/dashboard/project/${project.projectId}`}>
|
||||||
<Card className="group relative w-full bg-transparent transition-colors hover:bg-card">
|
<Card className="group relative w-full bg-transparent transition-colors hover:bg-card">
|
||||||
{flattedDomains.length > 1 ? (
|
{flattedDomains.length > 1 ? (
|
||||||
<DropdownMenu>
|
<DropdownMenu>
|
||||||
<DropdownMenuTrigger asChild>
|
<DropdownMenuTrigger asChild>
|
||||||
<Button
|
<Button
|
||||||
className="absolute -right-3 -top-3 size-9 translate-y-1 rounded-full p-0 opacity-0 transition-all duration-200 group-hover:translate-y-0 group-hover:opacity-100"
|
className="absolute -right-3 -top-3 size-9 translate-y-1 rounded-full p-0 opacity-0 transition-all duration-200 group-hover:translate-y-0 group-hover:opacity-100"
|
||||||
size="sm"
|
size="sm"
|
||||||
variant="default"
|
variant="default"
|
||||||
>
|
>
|
||||||
<ExternalLinkIcon className="size-3.5" />
|
<ExternalLinkIcon className="size-3.5" />
|
||||||
</Button>
|
</Button>
|
||||||
</DropdownMenuTrigger>
|
</DropdownMenuTrigger>
|
||||||
<DropdownMenuContent
|
<DropdownMenuContent
|
||||||
className="w-[200px] space-y-2"
|
className="w-[200px] space-y-2"
|
||||||
onClick={(e) => e.stopPropagation()}
|
onClick={(e) => e.stopPropagation()}
|
||||||
>
|
>
|
||||||
{renderDomainsDropdown(project.applications)}
|
{renderDomainsDropdown(project.applications)}
|
||||||
{renderDomainsDropdown(project.compose)}
|
{renderDomainsDropdown(project.compose)}
|
||||||
</DropdownMenuContent>
|
</DropdownMenuContent>
|
||||||
</DropdownMenu>
|
</DropdownMenu>
|
||||||
) : flattedDomains[0] ? (
|
) : flattedDomains[0] ? (
|
||||||
<Button
|
<Button
|
||||||
className="absolute -right-3 -top-3 size-9 translate-y-1 rounded-full p-0 opacity-0 transition-all duration-200 group-hover:translate-y-0 group-hover:opacity-100"
|
className="absolute -right-3 -top-3 size-9 translate-y-1 rounded-full p-0 opacity-0 transition-all duration-200 group-hover:translate-y-0 group-hover:opacity-100"
|
||||||
size="sm"
|
size="sm"
|
||||||
variant="default"
|
variant="default"
|
||||||
onClick={(e) => e.stopPropagation()}
|
onClick={(e) => e.stopPropagation()}
|
||||||
>
|
>
|
||||||
<Link
|
<Link
|
||||||
href={`${
|
href={`${
|
||||||
flattedDomains[0].https ? "https" : "http"
|
flattedDomains[0].https ? "https" : "http"
|
||||||
}://${flattedDomains[0].host}${flattedDomains[0].path}`}
|
}://${flattedDomains[0].host}${flattedDomains[0].path}`}
|
||||||
target="_blank"
|
target="_blank"
|
||||||
>
|
>
|
||||||
<ExternalLinkIcon className="size-3.5" />
|
<ExternalLinkIcon className="size-3.5" />
|
||||||
</Link>
|
</Link>
|
||||||
</Button>
|
</Button>
|
||||||
) : null}
|
) : null}
|
||||||
|
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
<CardTitle className="flex items-center justify-between gap-2">
|
<CardTitle className="flex items-center justify-between gap-2">
|
||||||
<span className="flex flex-col gap-1.5">
|
<span className="flex flex-col gap-1.5">
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<BookIcon className="size-4 text-muted-foreground" />
|
<BookIcon className="size-4 text-muted-foreground" />
|
||||||
<span className="text-base font-medium leading-none">
|
<span className="text-base font-medium leading-none">
|
||||||
{project.name}
|
{project.name}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<span className="text-sm font-medium text-muted-foreground">
|
<span className="text-sm font-medium text-muted-foreground">
|
||||||
{project.description}
|
{project.description}
|
||||||
</span>
|
</span>
|
||||||
</span>
|
</span>
|
||||||
<div className="flex self-start space-x-1">
|
<div className="flex self-start space-x-1">
|
||||||
<DropdownMenu>
|
<DropdownMenu>
|
||||||
<DropdownMenuTrigger asChild>
|
<DropdownMenuTrigger asChild>
|
||||||
<Button
|
<Button
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
size="icon"
|
size="icon"
|
||||||
className="px-2"
|
className="px-2"
|
||||||
>
|
>
|
||||||
<MoreHorizontalIcon className="size-5" />
|
<MoreHorizontalIcon className="size-5" />
|
||||||
</Button>
|
</Button>
|
||||||
</DropdownMenuTrigger>
|
</DropdownMenuTrigger>
|
||||||
<DropdownMenuContent className="w-[200px] space-y-2">
|
<DropdownMenuContent className="w-[200px] space-y-2">
|
||||||
<DropdownMenuLabel className="font-normal">
|
<DropdownMenuLabel className="font-normal">
|
||||||
Actions
|
Actions
|
||||||
</DropdownMenuLabel>
|
</DropdownMenuLabel>
|
||||||
<div onClick={(e) => e.stopPropagation()}>
|
<div onClick={(e) => e.stopPropagation()}>
|
||||||
<ProjectEnviroment
|
<ProjectEnviroment
|
||||||
projectId={project.projectId}
|
projectId={project.projectId}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div onClick={(e) => e.stopPropagation()}>
|
<div onClick={(e) => e.stopPropagation()}>
|
||||||
<UpdateProject projectId={project.projectId} />
|
<UpdateProject projectId={project.projectId} />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div onClick={(e) => e.stopPropagation()}>
|
<div onClick={(e) => e.stopPropagation()}>
|
||||||
{(auth?.rol === "admin" ||
|
{(auth?.rol === "admin" ||
|
||||||
user?.canDeleteProjects) && (
|
user?.canDeleteProjects) && (
|
||||||
<AlertDialog>
|
<AlertDialog>
|
||||||
<AlertDialogTrigger className="w-full">
|
<AlertDialogTrigger className="w-full">
|
||||||
<DropdownMenuItem
|
<DropdownMenuItem
|
||||||
className="w-full cursor-pointer space-x-3"
|
className="w-full cursor-pointer space-x-3"
|
||||||
onSelect={(e) => e.preventDefault()}
|
onSelect={(e) => e.preventDefault()}
|
||||||
>
|
>
|
||||||
<TrashIcon className="size-4" />
|
<TrashIcon className="size-4" />
|
||||||
<span>Delete</span>
|
<span>Delete</span>
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
</AlertDialogTrigger>
|
</AlertDialogTrigger>
|
||||||
<AlertDialogContent>
|
<AlertDialogContent>
|
||||||
<AlertDialogHeader>
|
<AlertDialogHeader>
|
||||||
<AlertDialogTitle>
|
<AlertDialogTitle>
|
||||||
Are you sure to delete this project?
|
Are you sure to delete this project?
|
||||||
</AlertDialogTitle>
|
</AlertDialogTitle>
|
||||||
{!emptyServices ? (
|
{!emptyServices ? (
|
||||||
<div className="flex flex-row gap-4 rounded-lg bg-yellow-50 p-2 dark:bg-yellow-950">
|
<div className="flex flex-row gap-4 rounded-lg bg-yellow-50 p-2 dark:bg-yellow-950">
|
||||||
<AlertTriangle className="text-yellow-600 dark:text-yellow-400" />
|
<AlertTriangle className="text-yellow-600 dark:text-yellow-400" />
|
||||||
<span className="text-sm text-yellow-600 dark:text-yellow-400">
|
<span className="text-sm text-yellow-600 dark:text-yellow-400">
|
||||||
You have active services, please
|
You have active services, please
|
||||||
delete them first
|
delete them first
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<AlertDialogDescription>
|
<AlertDialogDescription>
|
||||||
This action cannot be undone
|
This action cannot be undone
|
||||||
</AlertDialogDescription>
|
</AlertDialogDescription>
|
||||||
)}
|
)}
|
||||||
</AlertDialogHeader>
|
</AlertDialogHeader>
|
||||||
<AlertDialogFooter>
|
<AlertDialogFooter>
|
||||||
<AlertDialogCancel>
|
<AlertDialogCancel>
|
||||||
Cancel
|
Cancel
|
||||||
</AlertDialogCancel>
|
</AlertDialogCancel>
|
||||||
<AlertDialogAction
|
<AlertDialogAction
|
||||||
disabled={!emptyServices}
|
disabled={!emptyServices}
|
||||||
onClick={async () => {
|
onClick={async () => {
|
||||||
await mutateAsync({
|
await mutateAsync({
|
||||||
projectId: project.projectId,
|
projectId: project.projectId,
|
||||||
})
|
})
|
||||||
.then(() => {
|
.then(() => {
|
||||||
toast.success(
|
toast.success(
|
||||||
"Project delete succesfully"
|
"Project delete succesfully",
|
||||||
);
|
);
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.catch(() => {
|
||||||
toast.error(
|
toast.error(
|
||||||
"Error to delete this project"
|
"Error to delete this project",
|
||||||
);
|
);
|
||||||
})
|
})
|
||||||
.finally(() => {
|
.finally(() => {
|
||||||
utils.project.all.invalidate();
|
utils.project.all.invalidate();
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Delete
|
Delete
|
||||||
</AlertDialogAction>
|
</AlertDialogAction>
|
||||||
</AlertDialogFooter>
|
</AlertDialogFooter>
|
||||||
</AlertDialogContent>
|
</AlertDialogContent>
|
||||||
</AlertDialog>
|
</AlertDialog>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</DropdownMenuContent>
|
</DropdownMenuContent>
|
||||||
</DropdownMenu>
|
</DropdownMenu>
|
||||||
</div>
|
</div>
|
||||||
</CardTitle>
|
</CardTitle>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardFooter className="pt-4">
|
<CardFooter className="pt-4">
|
||||||
<div className="space-y-1 text-sm flex flex-row justify-between max-sm:flex-wrap w-full gap-2 sm:gap-4">
|
<div className="space-y-1 text-sm flex flex-row justify-between max-sm:flex-wrap w-full gap-2 sm:gap-4">
|
||||||
<DateTooltip date={project.createdAt}>
|
<DateTooltip date={project.createdAt}>
|
||||||
Created
|
Created
|
||||||
</DateTooltip>
|
</DateTooltip>
|
||||||
<span>
|
<span>
|
||||||
{totalServices}{" "}
|
{totalServices}{" "}
|
||||||
{totalServices === 1 ? "service" : "services"}
|
{totalServices === 1 ? "service" : "services"}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</CardFooter>
|
</CardFooter>
|
||||||
</Card>
|
</Card>
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,189 +1,189 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import React from "react";
|
|
||||||
import {
|
import {
|
||||||
Command,
|
MariadbIcon,
|
||||||
CommandEmpty,
|
MongodbIcon,
|
||||||
CommandList,
|
MysqlIcon,
|
||||||
CommandGroup,
|
PostgresqlIcon,
|
||||||
CommandInput,
|
RedisIcon,
|
||||||
CommandItem,
|
} from "@/components/icons/data-tools-icons";
|
||||||
CommandDialog,
|
import { Badge } from "@/components/ui/badge";
|
||||||
CommandSeparator,
|
import {
|
||||||
|
Command,
|
||||||
|
CommandDialog,
|
||||||
|
CommandEmpty,
|
||||||
|
CommandGroup,
|
||||||
|
CommandInput,
|
||||||
|
CommandItem,
|
||||||
|
CommandList,
|
||||||
|
CommandSeparator,
|
||||||
} from "@/components/ui/command";
|
} from "@/components/ui/command";
|
||||||
import { useRouter } from "next/router";
|
|
||||||
import {
|
import {
|
||||||
extractServices,
|
type Services,
|
||||||
type Services,
|
extractServices,
|
||||||
} from "@/pages/dashboard/project/[projectId]";
|
} from "@/pages/dashboard/project/[projectId]";
|
||||||
|
import { api } from "@/utils/api";
|
||||||
import type { findProjectById } from "@dokploy/server/services/project";
|
import type { findProjectById } from "@dokploy/server/services/project";
|
||||||
import { BookIcon, CircuitBoard, GlobeIcon } from "lucide-react";
|
import { BookIcon, CircuitBoard, GlobeIcon } from "lucide-react";
|
||||||
import {
|
import { useRouter } from "next/router";
|
||||||
MariadbIcon,
|
import React from "react";
|
||||||
MongodbIcon,
|
|
||||||
MysqlIcon,
|
|
||||||
PostgresqlIcon,
|
|
||||||
RedisIcon,
|
|
||||||
} from "@/components/icons/data-tools-icons";
|
|
||||||
import { api } from "@/utils/api";
|
|
||||||
import { Badge } from "@/components/ui/badge";
|
|
||||||
import { StatusTooltip } from "../shared/status-tooltip";
|
import { StatusTooltip } from "../shared/status-tooltip";
|
||||||
|
|
||||||
type Project = Awaited<ReturnType<typeof findProjectById>>;
|
type Project = Awaited<ReturnType<typeof findProjectById>>;
|
||||||
|
|
||||||
export const SearchCommand = () => {
|
export const SearchCommand = () => {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const [open, setOpen] = React.useState(false);
|
const [open, setOpen] = React.useState(false);
|
||||||
const [search, setSearch] = React.useState("");
|
const [search, setSearch] = React.useState("");
|
||||||
|
|
||||||
const { data } = api.project.all.useQuery();
|
const { data } = api.project.all.useQuery();
|
||||||
const { data: isCloud, isLoading } = api.settings.isCloud.useQuery();
|
const { data: isCloud, isLoading } = api.settings.isCloud.useQuery();
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
const down = (e: KeyboardEvent) => {
|
const down = (e: KeyboardEvent) => {
|
||||||
if (e.key === "j" && (e.metaKey || e.ctrlKey)) {
|
if (e.key === "j" && (e.metaKey || e.ctrlKey)) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
setOpen((open) => !open);
|
setOpen((open) => !open);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
document.addEventListener("keydown", down);
|
document.addEventListener("keydown", down);
|
||||||
return () => document.removeEventListener("keydown", down);
|
return () => document.removeEventListener("keydown", down);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<CommandDialog open={open} onOpenChange={setOpen}>
|
<CommandDialog open={open} onOpenChange={setOpen}>
|
||||||
<CommandInput
|
<CommandInput
|
||||||
placeholder={"Search projects or settings"}
|
placeholder={"Search projects or settings"}
|
||||||
value={search}
|
value={search}
|
||||||
onValueChange={setSearch}
|
onValueChange={setSearch}
|
||||||
/>
|
/>
|
||||||
<CommandList>
|
<CommandList>
|
||||||
<CommandEmpty>
|
<CommandEmpty>
|
||||||
No projects added yet. Click on Create project.
|
No projects added yet. Click on Create project.
|
||||||
</CommandEmpty>
|
</CommandEmpty>
|
||||||
<CommandGroup heading={"Projects"}>
|
<CommandGroup heading={"Projects"}>
|
||||||
<CommandList>
|
<CommandList>
|
||||||
{data?.map((project) => (
|
{data?.map((project) => (
|
||||||
<CommandItem
|
<CommandItem
|
||||||
key={project.projectId}
|
key={project.projectId}
|
||||||
onSelect={() => {
|
onSelect={() => {
|
||||||
router.push(`/dashboard/project/${project.projectId}`);
|
router.push(`/dashboard/project/${project.projectId}`);
|
||||||
setOpen(false);
|
setOpen(false);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<BookIcon className="size-4 text-muted-foreground mr-2" />
|
<BookIcon className="size-4 text-muted-foreground mr-2" />
|
||||||
{project.name}
|
{project.name}
|
||||||
</CommandItem>
|
</CommandItem>
|
||||||
))}
|
))}
|
||||||
</CommandList>
|
</CommandList>
|
||||||
</CommandGroup>
|
</CommandGroup>
|
||||||
<CommandSeparator />
|
<CommandSeparator />
|
||||||
<CommandGroup heading={"Services"}>
|
<CommandGroup heading={"Services"}>
|
||||||
<CommandList>
|
<CommandList>
|
||||||
{data?.map((project) => {
|
{data?.map((project) => {
|
||||||
const applications: Services[] = extractServices(project);
|
const applications: Services[] = extractServices(project);
|
||||||
return applications.map((application) => (
|
return applications.map((application) => (
|
||||||
<CommandItem
|
<CommandItem
|
||||||
key={application.id}
|
key={application.id}
|
||||||
onSelect={() => {
|
onSelect={() => {
|
||||||
router.push(
|
router.push(
|
||||||
`/dashboard/project/${project.projectId}/services/${application.type}/${application.id}`
|
`/dashboard/project/${project.projectId}/services/${application.type}/${application.id}`,
|
||||||
);
|
);
|
||||||
setOpen(false);
|
setOpen(false);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{application.type === "postgres" && (
|
{application.type === "postgres" && (
|
||||||
<PostgresqlIcon className="h-6 w-6 mr-2" />
|
<PostgresqlIcon className="h-6 w-6 mr-2" />
|
||||||
)}
|
)}
|
||||||
{application.type === "redis" && (
|
{application.type === "redis" && (
|
||||||
<RedisIcon className="h-6 w-6 mr-2" />
|
<RedisIcon className="h-6 w-6 mr-2" />
|
||||||
)}
|
)}
|
||||||
{application.type === "mariadb" && (
|
{application.type === "mariadb" && (
|
||||||
<MariadbIcon className="h-6 w-6 mr-2" />
|
<MariadbIcon className="h-6 w-6 mr-2" />
|
||||||
)}
|
)}
|
||||||
{application.type === "mongo" && (
|
{application.type === "mongo" && (
|
||||||
<MongodbIcon className="h-6 w-6 mr-2" />
|
<MongodbIcon className="h-6 w-6 mr-2" />
|
||||||
)}
|
)}
|
||||||
{application.type === "mysql" && (
|
{application.type === "mysql" && (
|
||||||
<MysqlIcon className="h-6 w-6 mr-2" />
|
<MysqlIcon className="h-6 w-6 mr-2" />
|
||||||
)}
|
)}
|
||||||
{application.type === "application" && (
|
{application.type === "application" && (
|
||||||
<GlobeIcon className="h-6 w-6 mr-2" />
|
<GlobeIcon className="h-6 w-6 mr-2" />
|
||||||
)}
|
)}
|
||||||
{application.type === "compose" && (
|
{application.type === "compose" && (
|
||||||
<CircuitBoard className="h-6 w-6 mr-2" />
|
<CircuitBoard className="h-6 w-6 mr-2" />
|
||||||
)}
|
)}
|
||||||
<span className="flex-grow">
|
<span className="flex-grow">
|
||||||
{project.name} / {application.name}{" "}
|
{project.name} / {application.name}{" "}
|
||||||
<div style={{ display: "none" }}>{application.id}</div>
|
<div style={{ display: "none" }}>{application.id}</div>
|
||||||
</span>
|
</span>
|
||||||
<div>
|
<div>
|
||||||
<StatusTooltip status={application.status} />
|
<StatusTooltip status={application.status} />
|
||||||
</div>
|
</div>
|
||||||
</CommandItem>
|
</CommandItem>
|
||||||
));
|
));
|
||||||
})}
|
})}
|
||||||
</CommandList>
|
</CommandList>
|
||||||
</CommandGroup>
|
</CommandGroup>
|
||||||
<CommandSeparator />
|
<CommandSeparator />
|
||||||
<CommandGroup heading={"Application"} hidden={true}>
|
<CommandGroup heading={"Application"} hidden={true}>
|
||||||
<CommandItem
|
<CommandItem
|
||||||
onSelect={() => {
|
onSelect={() => {
|
||||||
router.push("/dashboard/projects");
|
router.push("/dashboard/projects");
|
||||||
setOpen(false);
|
setOpen(false);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Projects
|
Projects
|
||||||
</CommandItem>
|
</CommandItem>
|
||||||
{!isCloud && (
|
{!isCloud && (
|
||||||
<>
|
<>
|
||||||
<CommandItem
|
<CommandItem
|
||||||
onSelect={() => {
|
onSelect={() => {
|
||||||
router.push("/dashboard/monitoring");
|
router.push("/dashboard/monitoring");
|
||||||
setOpen(false);
|
setOpen(false);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Monitoring
|
Monitoring
|
||||||
</CommandItem>
|
</CommandItem>
|
||||||
<CommandItem
|
<CommandItem
|
||||||
onSelect={() => {
|
onSelect={() => {
|
||||||
router.push("/dashboard/traefik");
|
router.push("/dashboard/traefik");
|
||||||
setOpen(false);
|
setOpen(false);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Traefik
|
Traefik
|
||||||
</CommandItem>
|
</CommandItem>
|
||||||
<CommandItem
|
<CommandItem
|
||||||
onSelect={() => {
|
onSelect={() => {
|
||||||
router.push("/dashboard/docker");
|
router.push("/dashboard/docker");
|
||||||
setOpen(false);
|
setOpen(false);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Docker
|
Docker
|
||||||
</CommandItem>
|
</CommandItem>
|
||||||
<CommandItem
|
<CommandItem
|
||||||
onSelect={() => {
|
onSelect={() => {
|
||||||
router.push("/dashboard/requests");
|
router.push("/dashboard/requests");
|
||||||
setOpen(false);
|
setOpen(false);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Requests
|
Requests
|
||||||
</CommandItem>
|
</CommandItem>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
<CommandItem
|
<CommandItem
|
||||||
onSelect={() => {
|
onSelect={() => {
|
||||||
router.push("/dashboard/settings/server");
|
router.push("/dashboard/settings/server");
|
||||||
setOpen(false);
|
setOpen(false);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Settings
|
Settings
|
||||||
</CommandItem>
|
</CommandItem>
|
||||||
</CommandGroup>
|
</CommandGroup>
|
||||||
</CommandList>
|
</CommandList>
|
||||||
</CommandDialog>
|
</CommandDialog>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -24,12 +24,12 @@ export const DeleteNotification = ({ notificationId }: Props) => {
|
|||||||
return (
|
return (
|
||||||
<AlertDialog>
|
<AlertDialog>
|
||||||
<AlertDialogTrigger asChild>
|
<AlertDialogTrigger asChild>
|
||||||
<Button
|
<Button
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
size="icon"
|
size="icon"
|
||||||
className="h-9 w-9 group hover:bg-red-500/10"
|
className="h-9 w-9 group hover:bg-red-500/10"
|
||||||
isLoading={isLoading}
|
isLoading={isLoading}
|
||||||
>
|
>
|
||||||
<Trash2 className="size-4 text-muted-foreground group-hover:text-red-500" />
|
<Trash2 className="size-4 text-muted-foreground group-hover:text-red-500" />
|
||||||
</Button>
|
</Button>
|
||||||
</AlertDialogTrigger>
|
</AlertDialogTrigger>
|
||||||
|
|||||||
@@ -40,58 +40,60 @@ export const ShowNotifications = () => {
|
|||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div className="flex flex-col gap-4">
|
<div className="flex flex-col gap-4">
|
||||||
<div className="grid lg:grid-cols-1 xl:grid-cols-2 gap-4">
|
<div className="grid lg:grid-cols-1 xl:grid-cols-2 gap-4">
|
||||||
{data?.map((notification, index) => (
|
{data?.map((notification, index) => (
|
||||||
<div
|
<div
|
||||||
key={notification.notificationId}
|
key={notification.notificationId}
|
||||||
className="flex items-center justify-between rounded-xl p-4 transition-colors dark:bg-zinc-900/50 bg-gray-200/50 border border-card"
|
className="flex items-center justify-between rounded-xl p-4 transition-colors dark:bg-zinc-900/50 bg-gray-200/50 border border-card"
|
||||||
>
|
>
|
||||||
<div className="flex items-center gap-4">
|
<div className="flex items-center gap-4">
|
||||||
{notification.notificationType === "slack" && (
|
{notification.notificationType === "slack" && (
|
||||||
<div className="flex h-12 w-12 items-center justify-center rounded-lg bg-indigo-500/10">
|
<div className="flex h-12 w-12 items-center justify-center rounded-lg bg-indigo-500/10">
|
||||||
<SlackIcon className="h-6 w-6 text-indigo-400" />
|
<SlackIcon className="h-6 w-6 text-indigo-400" />
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{notification.notificationType === "telegram" && (
|
{notification.notificationType === "telegram" && (
|
||||||
<div className="flex h-12 w-12 items-center justify-center rounded-lg bg-cyan-500/10">
|
<div className="flex h-12 w-12 items-center justify-center rounded-lg bg-cyan-500/10">
|
||||||
<TelegramIcon className="h-6 w-6 text-indigo-400" />
|
<TelegramIcon className="h-6 w-6 text-indigo-400" />
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{notification.notificationType === "discord" && (
|
{notification.notificationType === "discord" && (
|
||||||
<div className="flex h-12 w-12 items-center justify-center rounded-lg bg-indigo-500/10">
|
<div className="flex h-12 w-12 items-center justify-center rounded-lg bg-indigo-500/10">
|
||||||
<DiscordIcon className="h-6 w-6 text-indigo-400" />
|
<DiscordIcon className="h-6 w-6 text-indigo-400" />
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{notification.notificationType === "email" && (
|
{notification.notificationType === "email" && (
|
||||||
<div className="flex h-12 w-12 items-center justify-center rounded-lg bg-zinc-500/10">
|
<div className="flex h-12 w-12 items-center justify-center rounded-lg bg-zinc-500/10">
|
||||||
<Mail className="h-6 w-6 text-indigo-400" />
|
<Mail className="h-6 w-6 text-indigo-400" />
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<div className="flex flex-col">
|
<div className="flex flex-col">
|
||||||
<span className="text-sm font-medium dark:text-zinc-300 text-zinc-800">
|
<span className="text-sm font-medium dark:text-zinc-300 text-zinc-800">
|
||||||
{notification.name}
|
{notification.name}
|
||||||
</span>
|
</span>
|
||||||
<span className="text-xs font-medium text-muted-foreground">
|
<span className="text-xs font-medium text-muted-foreground">
|
||||||
{notification.notificationType?.[0]?.toUpperCase() + notification.notificationType?.slice(1)} notification
|
{notification.notificationType?.[0]?.toUpperCase() +
|
||||||
</span>
|
notification.notificationType?.slice(1)}{" "}
|
||||||
</div>
|
notification
|
||||||
</div>
|
</span>
|
||||||
<div className="flex items-center gap-2">
|
</div>
|
||||||
<UpdateNotification
|
</div>
|
||||||
notificationId={notification.notificationId}
|
<div className="flex items-center gap-2">
|
||||||
/>
|
<UpdateNotification
|
||||||
<DeleteNotification
|
notificationId={notification.notificationId}
|
||||||
notificationId={notification.notificationId}
|
/>
|
||||||
/>
|
<DeleteNotification
|
||||||
</div>
|
notificationId={notification.notificationId}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex flex-col gap-4 justify-end w-full items-end">
|
||||||
|
<AddNotification />
|
||||||
</div>
|
</div>
|
||||||
))}
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex flex-col gap-4 justify-end w-full items-end">
|
|
||||||
<AddNotification />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
)}
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
|
|||||||
@@ -218,9 +218,11 @@ export const UpdateNotification = ({ notificationId }: Props) => {
|
|||||||
return (
|
return (
|
||||||
<Dialog open={isOpen} onOpenChange={setIsOpen}>
|
<Dialog open={isOpen} onOpenChange={setIsOpen}>
|
||||||
<DialogTrigger className="" asChild>
|
<DialogTrigger className="" asChild>
|
||||||
<Button variant="ghost"
|
<Button
|
||||||
size="icon"
|
variant="ghost"
|
||||||
className="h-9 w-9 dark:hover:bg-zinc-900/80 hover:bg-gray-200/80">
|
size="icon"
|
||||||
|
className="h-9 w-9 dark:hover:bg-zinc-900/80 hover:bg-gray-200/80"
|
||||||
|
>
|
||||||
<Pen className="size-4 text-muted-foreground" />
|
<Pen className="size-4 text-muted-foreground" />
|
||||||
</Button>
|
</Button>
|
||||||
</DialogTrigger>
|
</DialogTrigger>
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { AlertBlock } from "@/components/shared/alert-block";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import {
|
import {
|
||||||
Card,
|
Card,
|
||||||
@@ -26,7 +27,6 @@ import { toast } from "sonner";
|
|||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import { Disable2FA } from "./disable-2fa";
|
import { Disable2FA } from "./disable-2fa";
|
||||||
import { Enable2FA } from "./enable-2fa";
|
import { Enable2FA } from "./enable-2fa";
|
||||||
import { AlertBlock } from "@/components/shared/alert-block";
|
|
||||||
|
|
||||||
const profileSchema = z.object({
|
const profileSchema = z.object({
|
||||||
email: z.string(),
|
email: z.string(),
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import { AlertBlock } from "@/components/shared/alert-block";
|
||||||
|
import { DialogAction } from "@/components/shared/dialog-action";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import {
|
import {
|
||||||
Card,
|
Card,
|
||||||
@@ -18,13 +20,11 @@ import { Input } from "@/components/ui/input";
|
|||||||
import { api } from "@/utils/api";
|
import { api } from "@/utils/api";
|
||||||
import { zodResolver } from "@hookform/resolvers/zod";
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
import { useTranslation } from "next-i18next";
|
import { useTranslation } from "next-i18next";
|
||||||
|
import { useRouter } from "next/router";
|
||||||
import { useEffect } from "react";
|
import { useEffect } from "react";
|
||||||
import { useForm } from "react-hook-form";
|
import { useForm } from "react-hook-form";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import { DialogAction } from "@/components/shared/dialog-action";
|
|
||||||
import { AlertBlock } from "@/components/shared/alert-block";
|
|
||||||
import { useRouter } from "next/router";
|
|
||||||
|
|
||||||
const profileSchema = z.object({
|
const profileSchema = z.object({
|
||||||
password: z.string().min(1, {
|
password: z.string().min(1, {
|
||||||
|
|||||||
@@ -25,8 +25,8 @@ import { toast } from "sonner";
|
|||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
import { useTranslation } from "next-i18next";
|
import { useTranslation } from "next-i18next";
|
||||||
import { EditTraefikEnv } from "../../web-server/edit-traefik-env";
|
import { EditTraefikEnv } from "../../web-server/edit-traefik-env";
|
||||||
import { ShowModalLogs } from "../../web-server/show-modal-logs";
|
|
||||||
import { ManageTraefikPorts } from "../../web-server/manage-traefik-ports";
|
import { ManageTraefikPorts } from "../../web-server/manage-traefik-ports";
|
||||||
|
import { ShowModalLogs } from "../../web-server/show-modal-logs";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
serverId?: string;
|
serverId?: string;
|
||||||
|
|||||||
@@ -108,7 +108,8 @@ export const EditScript = ({ serverId }: Props) => {
|
|||||||
</DialogDescription>
|
</DialogDescription>
|
||||||
|
|
||||||
<AlertBlock type="warning">
|
<AlertBlock type="warning">
|
||||||
We recommend not modifying this script unless you know what you are doing.
|
We recommend not modifying this script unless you know what you are
|
||||||
|
doing.
|
||||||
</AlertBlock>
|
</AlertBlock>
|
||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
<div className="grid gap-4">
|
<div className="grid gap-4">
|
||||||
|
|||||||
@@ -34,8 +34,8 @@ import { toast } from "sonner";
|
|||||||
import { ShowDeployment } from "../../application/deployments/show-deployment";
|
import { ShowDeployment } from "../../application/deployments/show-deployment";
|
||||||
import { EditScript } from "./edit-script";
|
import { EditScript } from "./edit-script";
|
||||||
import { GPUSupport } from "./gpu-support";
|
import { GPUSupport } from "./gpu-support";
|
||||||
import { ValidateServer } from "./validate-server";
|
|
||||||
import { SecurityAudit } from "./security-audit";
|
import { SecurityAudit } from "./security-audit";
|
||||||
|
import { ValidateServer } from "./validate-server";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
serverId: string;
|
serverId: string;
|
||||||
|
|||||||
@@ -23,17 +23,17 @@ import { api } from "@/utils/api";
|
|||||||
import { format } from "date-fns";
|
import { format } from "date-fns";
|
||||||
import { KeyIcon, MoreHorizontal, ServerIcon } from "lucide-react";
|
import { KeyIcon, MoreHorizontal, ServerIcon } from "lucide-react";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
|
import { useRouter } from "next/router";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
import { TerminalModal } from "../web-server/terminal-modal";
|
import { TerminalModal } from "../web-server/terminal-modal";
|
||||||
import { ShowServerActions } from "./actions/show-server-actions";
|
import { ShowServerActions } from "./actions/show-server-actions";
|
||||||
import { AddServer } from "./add-server";
|
import { AddServer } from "./add-server";
|
||||||
import { SetupServer } from "./setup-server";
|
import { SetupServer } from "./setup-server";
|
||||||
import { ShowDockerContainersModal } from "./show-docker-containers-modal";
|
import { ShowDockerContainersModal } from "./show-docker-containers-modal";
|
||||||
|
import { ShowSwarmOverviewModal } from "./show-swarm-overview-modal";
|
||||||
import { ShowTraefikFileSystemModal } from "./show-traefik-file-system-modal";
|
import { ShowTraefikFileSystemModal } from "./show-traefik-file-system-modal";
|
||||||
import { UpdateServer } from "./update-server";
|
import { UpdateServer } from "./update-server";
|
||||||
import { useRouter } from "next/router";
|
|
||||||
import { WelcomeSuscription } from "./welcome-stripe/welcome-suscription";
|
import { WelcomeSuscription } from "./welcome-stripe/welcome-suscription";
|
||||||
import { ShowSwarmOverviewModal } from "./show-swarm-overview-modal";
|
|
||||||
|
|
||||||
export const ShowServers = () => {
|
export const ShowServers = () => {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
|
import { CodeEditor } from "@/components/shared/code-editor";
|
||||||
import { Card, CardContent } from "@/components/ui/card";
|
import { Card, CardContent } from "@/components/ui/card";
|
||||||
import { api } from "@/utils/api";
|
import { api } from "@/utils/api";
|
||||||
import { ExternalLinkIcon, Loader2 } from "lucide-react";
|
|
||||||
import copy from "copy-to-clipboard";
|
import copy from "copy-to-clipboard";
|
||||||
|
import { ExternalLinkIcon, Loader2 } from "lucide-react";
|
||||||
import { CopyIcon } from "lucide-react";
|
import { CopyIcon } from "lucide-react";
|
||||||
|
import Link from "next/link";
|
||||||
import { useEffect, useRef } from "react";
|
import { useEffect, useRef } from "react";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
import { CodeEditor } from "@/components/shared/code-editor";
|
|
||||||
import Link from "next/link";
|
|
||||||
|
|
||||||
export const CreateSSHKey = () => {
|
export const CreateSSHKey = () => {
|
||||||
const { data, refetch } = api.sshKey.all.useQuery();
|
const { data, refetch } = api.sshKey.all.useQuery();
|
||||||
|
|||||||
@@ -5,26 +5,26 @@ import { StatusTooltip } from "@/components/shared/status-tooltip";
|
|||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import {
|
import {
|
||||||
Card,
|
Card,
|
||||||
|
CardContent,
|
||||||
|
CardDescription,
|
||||||
CardHeader,
|
CardHeader,
|
||||||
CardTitle,
|
CardTitle,
|
||||||
CardDescription,
|
|
||||||
CardContent,
|
|
||||||
} from "@/components/ui/card";
|
} from "@/components/ui/card";
|
||||||
import { RocketIcon } from "lucide-react";
|
|
||||||
import { toast } from "sonner";
|
|
||||||
import { EditScript } from "../edit-script";
|
|
||||||
import { api } from "@/utils/api";
|
|
||||||
import { useState } from "react";
|
|
||||||
import { Label } from "@/components/ui/label";
|
import { Label } from "@/components/ui/label";
|
||||||
import {
|
import {
|
||||||
Select,
|
Select,
|
||||||
SelectTrigger,
|
|
||||||
SelectValue,
|
|
||||||
SelectContent,
|
SelectContent,
|
||||||
SelectGroup,
|
SelectGroup,
|
||||||
SelectItem,
|
SelectItem,
|
||||||
SelectLabel,
|
SelectLabel,
|
||||||
|
SelectTrigger,
|
||||||
|
SelectValue,
|
||||||
} from "@/components/ui/select";
|
} from "@/components/ui/select";
|
||||||
|
import { api } from "@/utils/api";
|
||||||
|
import { RocketIcon } from "lucide-react";
|
||||||
|
import { useState } from "react";
|
||||||
|
import { toast } from "sonner";
|
||||||
|
import { EditScript } from "../edit-script";
|
||||||
|
|
||||||
export const Setup = () => {
|
export const Setup = () => {
|
||||||
const { data: servers } = api.server.all.useQuery();
|
const { data: servers } = api.server.all.useQuery();
|
||||||
|
|||||||
@@ -1,27 +1,27 @@
|
|||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import {
|
import {
|
||||||
Card,
|
Card,
|
||||||
|
CardContent,
|
||||||
|
CardDescription,
|
||||||
CardHeader,
|
CardHeader,
|
||||||
CardTitle,
|
CardTitle,
|
||||||
CardDescription,
|
|
||||||
CardContent,
|
|
||||||
} from "@/components/ui/card";
|
} from "@/components/ui/card";
|
||||||
import { Loader2, PcCase, RefreshCw } from "lucide-react";
|
|
||||||
import { api } from "@/utils/api";
|
|
||||||
import { useState } from "react";
|
|
||||||
import { Label } from "@/components/ui/label";
|
import { Label } from "@/components/ui/label";
|
||||||
|
import { api } from "@/utils/api";
|
||||||
|
import { Loader2, PcCase, RefreshCw } from "lucide-react";
|
||||||
|
import { useState } from "react";
|
||||||
|
|
||||||
|
import { AlertBlock } from "@/components/shared/alert-block";
|
||||||
import {
|
import {
|
||||||
Select,
|
Select,
|
||||||
SelectTrigger,
|
|
||||||
SelectValue,
|
|
||||||
SelectContent,
|
SelectContent,
|
||||||
SelectGroup,
|
SelectGroup,
|
||||||
SelectItem,
|
SelectItem,
|
||||||
SelectLabel,
|
SelectLabel,
|
||||||
|
SelectTrigger,
|
||||||
|
SelectValue,
|
||||||
} from "@/components/ui/select";
|
} from "@/components/ui/select";
|
||||||
import { StatusRow } from "../gpu-support";
|
import { StatusRow } from "../gpu-support";
|
||||||
import { AlertBlock } from "@/components/shared/alert-block";
|
|
||||||
|
|
||||||
export const Verify = () => {
|
export const Verify = () => {
|
||||||
const { data: servers } = api.server.all.useQuery();
|
const { data: servers } = api.server.all.useQuery();
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import { GithubIcon } from "@/components/icons/data-tools-icons";
|
||||||
|
import { AlertBlock } from "@/components/shared/alert-block";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import {
|
import {
|
||||||
Dialog,
|
Dialog,
|
||||||
@@ -7,21 +9,19 @@ import {
|
|||||||
DialogHeader,
|
DialogHeader,
|
||||||
DialogTitle,
|
DialogTitle,
|
||||||
} from "@/components/ui/dialog";
|
} from "@/components/ui/dialog";
|
||||||
|
import { Separator } from "@/components/ui/separator";
|
||||||
|
import { defineStepper } from "@stepperize/react";
|
||||||
import { BookIcon, Puzzle } from "lucide-react";
|
import { BookIcon, Puzzle } from "lucide-react";
|
||||||
|
import { Code2, Database, GitMerge, Globe, Plug, Users } from "lucide-react";
|
||||||
|
import Link from "next/link";
|
||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { defineStepper } from "@stepperize/react";
|
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { Separator } from "@/components/ui/separator";
|
import ConfettiExplosion from "react-confetti-explosion";
|
||||||
import { AlertBlock } from "@/components/shared/alert-block";
|
|
||||||
import { CreateServer } from "./create-server";
|
import { CreateServer } from "./create-server";
|
||||||
import { CreateSSHKey } from "./create-ssh-key";
|
import { CreateSSHKey } from "./create-ssh-key";
|
||||||
import { Setup } from "./setup";
|
import { Setup } from "./setup";
|
||||||
import { Verify } from "./verify";
|
import { Verify } from "./verify";
|
||||||
import { Database, Globe, GitMerge, Users, Code2, Plug } from "lucide-react";
|
|
||||||
import ConfettiExplosion from "react-confetti-explosion";
|
|
||||||
import Link from "next/link";
|
|
||||||
import { GithubIcon } from "@/components/icons/data-tools-icons";
|
|
||||||
|
|
||||||
export const { useStepper, steps, Scoped } = defineStepper(
|
export const { useStepper, steps, Scoped } = defineStepper(
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -80,7 +80,7 @@ export const DockerTerminalModal = ({ children, appName, serverId }: Props) => {
|
|||||||
return (
|
return (
|
||||||
<Dialog open={mainDialogOpen} onOpenChange={handleMainDialogOpenChange}>
|
<Dialog open={mainDialogOpen} onOpenChange={handleMainDialogOpenChange}>
|
||||||
<DialogTrigger asChild>{children}</DialogTrigger>
|
<DialogTrigger asChild>{children}</DialogTrigger>
|
||||||
<DialogContent
|
<DialogContent
|
||||||
className="max-h-[85vh] overflow-y-auto sm:max-w-7xl"
|
className="max-h-[85vh] overflow-y-auto sm:max-w-7xl"
|
||||||
onEscapeKeyDown={(event) => event.preventDefault()}
|
onEscapeKeyDown={(event) => event.preventDefault()}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import {
|
|||||||
DialogHeader,
|
DialogHeader,
|
||||||
DialogTitle,
|
DialogTitle,
|
||||||
} from "@/components/ui/dialog";
|
} from "@/components/ui/dialog";
|
||||||
|
import { Label } from "@/components/ui/label";
|
||||||
import {
|
import {
|
||||||
Select,
|
Select,
|
||||||
SelectContent,
|
SelectContent,
|
||||||
@@ -13,7 +14,6 @@ import {
|
|||||||
SelectTrigger,
|
SelectTrigger,
|
||||||
SelectValue,
|
SelectValue,
|
||||||
} from "@/components/ui/select";
|
} from "@/components/ui/select";
|
||||||
import { Label } from "@/components/ui/label";
|
|
||||||
import { api } from "@/utils/api";
|
import { api } from "@/utils/api";
|
||||||
import { useTranslation } from "next-i18next";
|
import { useTranslation } from "next-i18next";
|
||||||
import type React from "react";
|
import type React from "react";
|
||||||
|
|||||||
@@ -3,19 +3,19 @@ import { applications, compose, github } from "@/server/db/schema";
|
|||||||
import type { DeploymentJob } from "@/server/queues/queue-types";
|
import type { DeploymentJob } from "@/server/queues/queue-types";
|
||||||
import { myQueue } from "@/server/queues/queueSetup";
|
import { myQueue } from "@/server/queues/queueSetup";
|
||||||
import { deploy } from "@/server/utils/deploy";
|
import { deploy } from "@/server/utils/deploy";
|
||||||
|
import { generateRandomDomain } from "@/templates/utils";
|
||||||
import {
|
import {
|
||||||
createPreviewDeployment,
|
|
||||||
type Domain,
|
type Domain,
|
||||||
|
IS_CLOUD,
|
||||||
|
createPreviewDeployment,
|
||||||
findPreviewDeploymentByApplicationId,
|
findPreviewDeploymentByApplicationId,
|
||||||
findPreviewDeploymentsByPullRequestId,
|
findPreviewDeploymentsByPullRequestId,
|
||||||
IS_CLOUD,
|
|
||||||
removePreviewDeployment,
|
removePreviewDeployment,
|
||||||
} from "@dokploy/server";
|
} from "@dokploy/server";
|
||||||
import { Webhooks } from "@octokit/webhooks";
|
import { Webhooks } from "@octokit/webhooks";
|
||||||
import { and, eq } from "drizzle-orm";
|
import { and, eq } from "drizzle-orm";
|
||||||
import type { NextApiRequest, NextApiResponse } from "next";
|
import type { NextApiRequest, NextApiResponse } from "next";
|
||||||
import { extractCommitMessage, extractHash } from "./[refreshToken]";
|
import { extractCommitMessage, extractHash } from "./[refreshToken]";
|
||||||
import { generateRandomDomain } from "@/templates/utils";
|
|
||||||
|
|
||||||
export default async function handler(
|
export default async function handler(
|
||||||
req: NextApiRequest,
|
req: NextApiRequest,
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
{}
|
{}
|
||||||
|
|||||||
@@ -1,44 +1,44 @@
|
|||||||
{
|
{
|
||||||
"settings.common.save": "Salva",
|
"settings.common.save": "Salva",
|
||||||
"settings.server.domain.title": "Dominio del server",
|
"settings.server.domain.title": "Dominio del server",
|
||||||
"settings.server.domain.description": "Aggiungi un dominio alla tua applicazione server.",
|
"settings.server.domain.description": "Aggiungi un dominio alla tua applicazione server.",
|
||||||
"settings.server.domain.form.domain": "Dominio",
|
"settings.server.domain.form.domain": "Dominio",
|
||||||
"settings.server.domain.form.letsEncryptEmail": "Email di Let's Encrypt",
|
"settings.server.domain.form.letsEncryptEmail": "Email di Let's Encrypt",
|
||||||
"settings.server.domain.form.certificate.label": "Certificato",
|
"settings.server.domain.form.certificate.label": "Certificato",
|
||||||
"settings.server.domain.form.certificate.placeholder": "Seleziona un certificato",
|
"settings.server.domain.form.certificate.placeholder": "Seleziona un certificato",
|
||||||
"settings.server.domain.form.certificateOptions.none": "Nessuno",
|
"settings.server.domain.form.certificateOptions.none": "Nessuno",
|
||||||
"settings.server.domain.form.certificateOptions.letsencrypt": "Let's Encrypt (Predefinito)",
|
"settings.server.domain.form.certificateOptions.letsencrypt": "Let's Encrypt (Predefinito)",
|
||||||
|
|
||||||
"settings.server.webServer.title": "Server Web",
|
"settings.server.webServer.title": "Server Web",
|
||||||
"settings.server.webServer.description": "Ricarica o pulisci il server web.",
|
"settings.server.webServer.description": "Ricarica o pulisci il server web.",
|
||||||
"settings.server.webServer.actions": "Azioni",
|
"settings.server.webServer.actions": "Azioni",
|
||||||
"settings.server.webServer.reload": "Ricarica",
|
"settings.server.webServer.reload": "Ricarica",
|
||||||
"settings.server.webServer.watchLogs": "Guarda i log",
|
"settings.server.webServer.watchLogs": "Guarda i log",
|
||||||
"settings.server.webServer.updateServerIp": "Aggiorna IP del server",
|
"settings.server.webServer.updateServerIp": "Aggiorna IP del server",
|
||||||
"settings.server.webServer.server.label": "Server",
|
"settings.server.webServer.server.label": "Server",
|
||||||
"settings.server.webServer.traefik.label": "Traefik",
|
"settings.server.webServer.traefik.label": "Traefik",
|
||||||
"settings.server.webServer.traefik.modifyEnv": "Modifica Env",
|
"settings.server.webServer.traefik.modifyEnv": "Modifica Env",
|
||||||
"settings.server.webServer.storage.label": "Spazio",
|
"settings.server.webServer.storage.label": "Spazio",
|
||||||
"settings.server.webServer.storage.cleanUnusedImages": "Pulisci immagini inutilizzate",
|
"settings.server.webServer.storage.cleanUnusedImages": "Pulisci immagini inutilizzate",
|
||||||
"settings.server.webServer.storage.cleanUnusedVolumes": "Pulisci volumi inutilizzati",
|
"settings.server.webServer.storage.cleanUnusedVolumes": "Pulisci volumi inutilizzati",
|
||||||
"settings.server.webServer.storage.cleanStoppedContainers": "Pulisci container fermati",
|
"settings.server.webServer.storage.cleanStoppedContainers": "Pulisci container fermati",
|
||||||
"settings.server.webServer.storage.cleanDockerBuilder": "Pulisci Docker Builder e sistema",
|
"settings.server.webServer.storage.cleanDockerBuilder": "Pulisci Docker Builder e sistema",
|
||||||
"settings.server.webServer.storage.cleanMonitoring": "Pulisci monitoraggio",
|
"settings.server.webServer.storage.cleanMonitoring": "Pulisci monitoraggio",
|
||||||
"settings.server.webServer.storage.cleanAll": "Pulisci tutto",
|
"settings.server.webServer.storage.cleanAll": "Pulisci tutto",
|
||||||
|
|
||||||
"settings.profile.title": "Account",
|
"settings.profile.title": "Account",
|
||||||
"settings.profile.description": "Modifica i dettagli del tuo profilo qui.",
|
"settings.profile.description": "Modifica i dettagli del tuo profilo qui.",
|
||||||
"settings.profile.email": "Email",
|
"settings.profile.email": "Email",
|
||||||
"settings.profile.password": "Password",
|
"settings.profile.password": "Password",
|
||||||
"settings.profile.avatar": "Avatar",
|
"settings.profile.avatar": "Avatar",
|
||||||
|
|
||||||
"settings.appearance.title": "Aspetto",
|
"settings.appearance.title": "Aspetto",
|
||||||
"settings.appearance.description": "Personalizza il tema della tua dashboard.",
|
"settings.appearance.description": "Personalizza il tema della tua dashboard.",
|
||||||
"settings.appearance.theme": "Tema",
|
"settings.appearance.theme": "Tema",
|
||||||
"settings.appearance.themeDescription": "Seleziona un tema per la tua dashboard",
|
"settings.appearance.themeDescription": "Seleziona un tema per la tua dashboard",
|
||||||
"settings.appearance.themes.light": "Chiaro",
|
"settings.appearance.themes.light": "Chiaro",
|
||||||
"settings.appearance.themes.dark": "Scuro",
|
"settings.appearance.themes.dark": "Scuro",
|
||||||
"settings.appearance.themes.system": "Sistema",
|
"settings.appearance.themes.system": "Sistema",
|
||||||
"settings.appearance.language": "Lingua",
|
"settings.appearance.language": "Lingua",
|
||||||
"settings.appearance.languageDescription": "Seleziona una lingua per la tua dashboard"
|
"settings.appearance.languageDescription": "Seleziona una lingua per la tua dashboard"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,22 +1,22 @@
|
|||||||
import {
|
import {
|
||||||
type DomainSchema,
|
type DomainSchema,
|
||||||
type Schema,
|
type Schema,
|
||||||
type Template,
|
type Template,
|
||||||
generateRandomDomain,
|
generateRandomDomain,
|
||||||
} from "../utils";
|
} from "../utils";
|
||||||
|
|
||||||
export function generate(schema: Schema): Template {
|
export function generate(schema: Schema): Template {
|
||||||
const randomDomain = generateRandomDomain(schema);
|
const randomDomain = generateRandomDomain(schema);
|
||||||
|
|
||||||
const domains: DomainSchema[] = [
|
const domains: DomainSchema[] = [
|
||||||
{
|
{
|
||||||
host: randomDomain,
|
host: randomDomain,
|
||||||
port: 6610,
|
port: 6610,
|
||||||
serviceName: "onedev",
|
serviceName: "onedev",
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
return {
|
return {
|
||||||
domains,
|
domains,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,44 +1,44 @@
|
|||||||
import {
|
import {
|
||||||
generateHash,
|
type DomainSchema,
|
||||||
generateRandomDomain,
|
type Schema,
|
||||||
generateBase64,
|
type Template,
|
||||||
type Template,
|
generateBase64,
|
||||||
type Schema,
|
generateHash,
|
||||||
type DomainSchema,
|
generateRandomDomain,
|
||||||
} from "../utils";
|
} from "../utils";
|
||||||
|
|
||||||
export function generate(schema: Schema): Template {
|
export function generate(schema: Schema): Template {
|
||||||
const mainDomain = generateRandomDomain(schema);
|
const mainDomain = generateRandomDomain(schema);
|
||||||
const secretBase = generateBase64(64);
|
const secretBase = generateBase64(64);
|
||||||
|
|
||||||
const domains: DomainSchema[] = [
|
const domains: DomainSchema[] = [
|
||||||
{
|
{
|
||||||
host: mainDomain,
|
host: mainDomain,
|
||||||
port: 3000,
|
port: 3000,
|
||||||
serviceName: "unsend",
|
serviceName: "unsend",
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
const envs = [
|
const envs = [
|
||||||
"REDIS_URL=redis://unsend-redis-prod:6379",
|
"REDIS_URL=redis://unsend-redis-prod:6379",
|
||||||
"POSTGRES_USER=postgres",
|
"POSTGRES_USER=postgres",
|
||||||
"POSTGRES_PASSWORD=postgres",
|
"POSTGRES_PASSWORD=postgres",
|
||||||
"POSTGRES_DB=unsend",
|
"POSTGRES_DB=unsend",
|
||||||
"DATABASE_URL=postgresql://postgres:postgres@unsend-db-prod:5432/unsend",
|
"DATABASE_URL=postgresql://postgres:postgres@unsend-db-prod:5432/unsend",
|
||||||
"NEXTAUTH_URL=http://localhost:3000",
|
"NEXTAUTH_URL=http://localhost:3000",
|
||||||
`NEXTAUTH_SECRET=${secretBase}`,
|
`NEXTAUTH_SECRET=${secretBase}`,
|
||||||
"GITHUB_ID='Fill'",
|
"GITHUB_ID='Fill'",
|
||||||
"GITHUB_SECRET='Fill'",
|
"GITHUB_SECRET='Fill'",
|
||||||
"AWS_DEFAULT_REGION=us-east-1",
|
"AWS_DEFAULT_REGION=us-east-1",
|
||||||
"AWS_SECRET_KEY='Fill'",
|
"AWS_SECRET_KEY='Fill'",
|
||||||
"AWS_ACCESS_KEY='Fill'",
|
"AWS_ACCESS_KEY='Fill'",
|
||||||
"DOCKER_OUTPUT=1",
|
"DOCKER_OUTPUT=1",
|
||||||
"API_RATE_LIMIT=1",
|
"API_RATE_LIMIT=1",
|
||||||
"DISCORD_WEBHOOK_URL=",
|
"DISCORD_WEBHOOK_URL=",
|
||||||
];
|
];
|
||||||
|
|
||||||
return {
|
return {
|
||||||
envs,
|
envs,
|
||||||
domains,
|
domains,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
import { relations } from "drizzle-orm";
|
import { relations } from "drizzle-orm";
|
||||||
import {
|
import {
|
||||||
boolean,
|
boolean,
|
||||||
integer,
|
integer,
|
||||||
json,
|
json,
|
||||||
pgEnum,
|
pgEnum,
|
||||||
pgTable,
|
pgTable,
|
||||||
text,
|
text,
|
||||||
} from "drizzle-orm/pg-core";
|
} from "drizzle-orm/pg-core";
|
||||||
import { createInsertSchema } from "drizzle-zod";
|
import { createInsertSchema } from "drizzle-zod";
|
||||||
import { nanoid } from "nanoid";
|
import { nanoid } from "nanoid";
|
||||||
@@ -28,493 +28,493 @@ import { sshKeys } from "./ssh-key";
|
|||||||
import { generateAppName } from "./utils";
|
import { generateAppName } from "./utils";
|
||||||
|
|
||||||
export const sourceType = pgEnum("sourceType", [
|
export const sourceType = pgEnum("sourceType", [
|
||||||
"docker",
|
"docker",
|
||||||
"git",
|
"git",
|
||||||
"github",
|
"github",
|
||||||
"gitlab",
|
"gitlab",
|
||||||
"bitbucket",
|
"bitbucket",
|
||||||
"drop",
|
"drop",
|
||||||
]);
|
]);
|
||||||
|
|
||||||
export const buildType = pgEnum("buildType", [
|
export const buildType = pgEnum("buildType", [
|
||||||
"dockerfile",
|
"dockerfile",
|
||||||
"heroku_buildpacks",
|
"heroku_buildpacks",
|
||||||
"paketo_buildpacks",
|
"paketo_buildpacks",
|
||||||
"nixpacks",
|
"nixpacks",
|
||||||
"static",
|
"static",
|
||||||
]);
|
]);
|
||||||
|
|
||||||
// TODO: refactor this types
|
// TODO: refactor this types
|
||||||
export interface HealthCheckSwarm {
|
export interface HealthCheckSwarm {
|
||||||
Test?: string[] | undefined;
|
Test?: string[] | undefined;
|
||||||
Interval?: number | undefined;
|
Interval?: number | undefined;
|
||||||
Timeout?: number | undefined;
|
Timeout?: number | undefined;
|
||||||
StartPeriod?: number | undefined;
|
StartPeriod?: number | undefined;
|
||||||
Retries?: number | undefined;
|
Retries?: number | undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface RestartPolicySwarm {
|
export interface RestartPolicySwarm {
|
||||||
Condition?: string | undefined;
|
Condition?: string | undefined;
|
||||||
Delay?: number | undefined;
|
Delay?: number | undefined;
|
||||||
MaxAttempts?: number | undefined;
|
MaxAttempts?: number | undefined;
|
||||||
Window?: number | undefined;
|
Window?: number | undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface PlacementSwarm {
|
export interface PlacementSwarm {
|
||||||
Constraints?: string[] | undefined;
|
Constraints?: string[] | undefined;
|
||||||
Preferences?: Array<{ Spread: { SpreadDescriptor: string } }> | undefined;
|
Preferences?: Array<{ Spread: { SpreadDescriptor: string } }> | undefined;
|
||||||
MaxReplicas?: number | undefined;
|
MaxReplicas?: number | undefined;
|
||||||
Platforms?:
|
Platforms?:
|
||||||
| Array<{
|
| Array<{
|
||||||
Architecture: string;
|
Architecture: string;
|
||||||
OS: string;
|
OS: string;
|
||||||
}>
|
}>
|
||||||
| undefined;
|
| undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface UpdateConfigSwarm {
|
export interface UpdateConfigSwarm {
|
||||||
Parallelism: number;
|
Parallelism: number;
|
||||||
Delay?: number | undefined;
|
Delay?: number | undefined;
|
||||||
FailureAction?: string | undefined;
|
FailureAction?: string | undefined;
|
||||||
Monitor?: number | undefined;
|
Monitor?: number | undefined;
|
||||||
MaxFailureRatio?: number | undefined;
|
MaxFailureRatio?: number | undefined;
|
||||||
Order: string;
|
Order: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ServiceModeSwarm {
|
export interface ServiceModeSwarm {
|
||||||
Replicated?: { Replicas?: number | undefined } | undefined;
|
Replicated?: { Replicas?: number | undefined } | undefined;
|
||||||
Global?: {} | undefined;
|
Global?: {} | undefined;
|
||||||
ReplicatedJob?:
|
ReplicatedJob?:
|
||||||
| {
|
| {
|
||||||
MaxConcurrent?: number | undefined;
|
MaxConcurrent?: number | undefined;
|
||||||
TotalCompletions?: number | undefined;
|
TotalCompletions?: number | undefined;
|
||||||
}
|
}
|
||||||
| undefined;
|
| undefined;
|
||||||
GlobalJob?: {} | undefined;
|
GlobalJob?: {} | undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface NetworkSwarm {
|
export interface NetworkSwarm {
|
||||||
Target?: string | undefined;
|
Target?: string | undefined;
|
||||||
Aliases?: string[] | undefined;
|
Aliases?: string[] | undefined;
|
||||||
DriverOpts?: { [key: string]: string } | undefined;
|
DriverOpts?: { [key: string]: string } | undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface LabelsSwarm {
|
export interface LabelsSwarm {
|
||||||
[name: string]: string;
|
[name: string]: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const applications = pgTable("application", {
|
export const applications = pgTable("application", {
|
||||||
applicationId: text("applicationId")
|
applicationId: text("applicationId")
|
||||||
.notNull()
|
.notNull()
|
||||||
.primaryKey()
|
.primaryKey()
|
||||||
.$defaultFn(() => nanoid()),
|
.$defaultFn(() => nanoid()),
|
||||||
name: text("name").notNull(),
|
name: text("name").notNull(),
|
||||||
appName: text("appName")
|
appName: text("appName")
|
||||||
.notNull()
|
.notNull()
|
||||||
.$defaultFn(() => generateAppName("app"))
|
.$defaultFn(() => generateAppName("app"))
|
||||||
.unique(),
|
.unique(),
|
||||||
description: text("description"),
|
description: text("description"),
|
||||||
env: text("env"),
|
env: text("env"),
|
||||||
previewEnv: text("previewEnv"),
|
previewEnv: text("previewEnv"),
|
||||||
previewBuildArgs: text("previewBuildArgs"),
|
previewBuildArgs: text("previewBuildArgs"),
|
||||||
previewWildcard: text("previewWildcard"),
|
previewWildcard: text("previewWildcard"),
|
||||||
previewPort: integer("previewPort").default(3000),
|
previewPort: integer("previewPort").default(3000),
|
||||||
previewHttps: boolean("previewHttps").notNull().default(false),
|
previewHttps: boolean("previewHttps").notNull().default(false),
|
||||||
previewPath: text("previewPath").default("/"),
|
previewPath: text("previewPath").default("/"),
|
||||||
previewCertificateType: certificateType("certificateType")
|
previewCertificateType: certificateType("certificateType")
|
||||||
.notNull()
|
.notNull()
|
||||||
.default("none"),
|
.default("none"),
|
||||||
previewLimit: integer("previewLimit").default(3),
|
previewLimit: integer("previewLimit").default(3),
|
||||||
isPreviewDeploymentsActive: boolean("isPreviewDeploymentsActive").default(
|
isPreviewDeploymentsActive: boolean("isPreviewDeploymentsActive").default(
|
||||||
false
|
false,
|
||||||
),
|
),
|
||||||
buildArgs: text("buildArgs"),
|
buildArgs: text("buildArgs"),
|
||||||
memoryReservation: integer("memoryReservation"),
|
memoryReservation: integer("memoryReservation"),
|
||||||
memoryLimit: integer("memoryLimit"),
|
memoryLimit: integer("memoryLimit"),
|
||||||
cpuReservation: integer("cpuReservation"),
|
cpuReservation: integer("cpuReservation"),
|
||||||
cpuLimit: integer("cpuLimit"),
|
cpuLimit: integer("cpuLimit"),
|
||||||
title: text("title"),
|
title: text("title"),
|
||||||
enabled: boolean("enabled"),
|
enabled: boolean("enabled"),
|
||||||
subtitle: text("subtitle"),
|
subtitle: text("subtitle"),
|
||||||
command: text("command"),
|
command: text("command"),
|
||||||
refreshToken: text("refreshToken").$defaultFn(() => nanoid()),
|
refreshToken: text("refreshToken").$defaultFn(() => nanoid()),
|
||||||
sourceType: sourceType("sourceType").notNull().default("github"),
|
sourceType: sourceType("sourceType").notNull().default("github"),
|
||||||
// Github
|
// Github
|
||||||
repository: text("repository"),
|
repository: text("repository"),
|
||||||
owner: text("owner"),
|
owner: text("owner"),
|
||||||
branch: text("branch"),
|
branch: text("branch"),
|
||||||
buildPath: text("buildPath").default("/"),
|
buildPath: text("buildPath").default("/"),
|
||||||
autoDeploy: boolean("autoDeploy").$defaultFn(() => true),
|
autoDeploy: boolean("autoDeploy").$defaultFn(() => true),
|
||||||
// Gitlab
|
// Gitlab
|
||||||
gitlabProjectId: integer("gitlabProjectId"),
|
gitlabProjectId: integer("gitlabProjectId"),
|
||||||
gitlabRepository: text("gitlabRepository"),
|
gitlabRepository: text("gitlabRepository"),
|
||||||
gitlabOwner: text("gitlabOwner"),
|
gitlabOwner: text("gitlabOwner"),
|
||||||
gitlabBranch: text("gitlabBranch"),
|
gitlabBranch: text("gitlabBranch"),
|
||||||
gitlabBuildPath: text("gitlabBuildPath").default("/"),
|
gitlabBuildPath: text("gitlabBuildPath").default("/"),
|
||||||
gitlabPathNamespace: text("gitlabPathNamespace"),
|
gitlabPathNamespace: text("gitlabPathNamespace"),
|
||||||
// Bitbucket
|
// Bitbucket
|
||||||
bitbucketRepository: text("bitbucketRepository"),
|
bitbucketRepository: text("bitbucketRepository"),
|
||||||
bitbucketOwner: text("bitbucketOwner"),
|
bitbucketOwner: text("bitbucketOwner"),
|
||||||
bitbucketBranch: text("bitbucketBranch"),
|
bitbucketBranch: text("bitbucketBranch"),
|
||||||
bitbucketBuildPath: text("bitbucketBuildPath").default("/"),
|
bitbucketBuildPath: text("bitbucketBuildPath").default("/"),
|
||||||
// Docker
|
// Docker
|
||||||
username: text("username"),
|
username: text("username"),
|
||||||
password: text("password"),
|
password: text("password"),
|
||||||
dockerImage: text("dockerImage"),
|
dockerImage: text("dockerImage"),
|
||||||
registryUrl: text("registryUrl"),
|
registryUrl: text("registryUrl"),
|
||||||
// Git
|
// Git
|
||||||
customGitUrl: text("customGitUrl"),
|
customGitUrl: text("customGitUrl"),
|
||||||
customGitBranch: text("customGitBranch"),
|
customGitBranch: text("customGitBranch"),
|
||||||
customGitBuildPath: text("customGitBuildPath"),
|
customGitBuildPath: text("customGitBuildPath"),
|
||||||
customGitSSHKeyId: text("customGitSSHKeyId").references(
|
customGitSSHKeyId: text("customGitSSHKeyId").references(
|
||||||
() => sshKeys.sshKeyId,
|
() => sshKeys.sshKeyId,
|
||||||
{
|
{
|
||||||
onDelete: "set null",
|
onDelete: "set null",
|
||||||
}
|
},
|
||||||
),
|
),
|
||||||
dockerfile: text("dockerfile"),
|
dockerfile: text("dockerfile"),
|
||||||
dockerContextPath: text("dockerContextPath"),
|
dockerContextPath: text("dockerContextPath"),
|
||||||
dockerBuildStage: text("dockerBuildStage"),
|
dockerBuildStage: text("dockerBuildStage"),
|
||||||
// Drop
|
// Drop
|
||||||
dropBuildPath: text("dropBuildPath"),
|
dropBuildPath: text("dropBuildPath"),
|
||||||
// Docker swarm json
|
// Docker swarm json
|
||||||
healthCheckSwarm: json("healthCheckSwarm").$type<HealthCheckSwarm>(),
|
healthCheckSwarm: json("healthCheckSwarm").$type<HealthCheckSwarm>(),
|
||||||
restartPolicySwarm: json("restartPolicySwarm").$type<RestartPolicySwarm>(),
|
restartPolicySwarm: json("restartPolicySwarm").$type<RestartPolicySwarm>(),
|
||||||
placementSwarm: json("placementSwarm").$type<PlacementSwarm>(),
|
placementSwarm: json("placementSwarm").$type<PlacementSwarm>(),
|
||||||
updateConfigSwarm: json("updateConfigSwarm").$type<UpdateConfigSwarm>(),
|
updateConfigSwarm: json("updateConfigSwarm").$type<UpdateConfigSwarm>(),
|
||||||
rollbackConfigSwarm: json("rollbackConfigSwarm").$type<UpdateConfigSwarm>(),
|
rollbackConfigSwarm: json("rollbackConfigSwarm").$type<UpdateConfigSwarm>(),
|
||||||
modeSwarm: json("modeSwarm").$type<ServiceModeSwarm>(),
|
modeSwarm: json("modeSwarm").$type<ServiceModeSwarm>(),
|
||||||
labelsSwarm: json("labelsSwarm").$type<LabelsSwarm>(),
|
labelsSwarm: json("labelsSwarm").$type<LabelsSwarm>(),
|
||||||
networkSwarm: json("networkSwarm").$type<NetworkSwarm[]>(),
|
networkSwarm: json("networkSwarm").$type<NetworkSwarm[]>(),
|
||||||
//
|
//
|
||||||
replicas: integer("replicas").default(1).notNull(),
|
replicas: integer("replicas").default(1).notNull(),
|
||||||
applicationStatus: applicationStatus("applicationStatus")
|
applicationStatus: applicationStatus("applicationStatus")
|
||||||
.notNull()
|
.notNull()
|
||||||
.default("idle"),
|
.default("idle"),
|
||||||
buildType: buildType("buildType").notNull().default("nixpacks"),
|
buildType: buildType("buildType").notNull().default("nixpacks"),
|
||||||
herokuVersion: text("herokuVersion").default("24"),
|
herokuVersion: text("herokuVersion").default("24"),
|
||||||
publishDirectory: text("publishDirectory"),
|
publishDirectory: text("publishDirectory"),
|
||||||
createdAt: text("createdAt")
|
createdAt: text("createdAt")
|
||||||
.notNull()
|
.notNull()
|
||||||
.$defaultFn(() => new Date().toISOString()),
|
.$defaultFn(() => new Date().toISOString()),
|
||||||
registryId: text("registryId").references(() => registry.registryId, {
|
registryId: text("registryId").references(() => registry.registryId, {
|
||||||
onDelete: "set null",
|
onDelete: "set null",
|
||||||
}),
|
}),
|
||||||
projectId: text("projectId")
|
projectId: text("projectId")
|
||||||
.notNull()
|
.notNull()
|
||||||
.references(() => projects.projectId, { onDelete: "cascade" }),
|
.references(() => projects.projectId, { onDelete: "cascade" }),
|
||||||
githubId: text("githubId").references(() => github.githubId, {
|
githubId: text("githubId").references(() => github.githubId, {
|
||||||
onDelete: "set null",
|
onDelete: "set null",
|
||||||
}),
|
}),
|
||||||
gitlabId: text("gitlabId").references(() => gitlab.gitlabId, {
|
gitlabId: text("gitlabId").references(() => gitlab.gitlabId, {
|
||||||
onDelete: "set null",
|
onDelete: "set null",
|
||||||
}),
|
}),
|
||||||
bitbucketId: text("bitbucketId").references(() => bitbucket.bitbucketId, {
|
bitbucketId: text("bitbucketId").references(() => bitbucket.bitbucketId, {
|
||||||
onDelete: "set null",
|
onDelete: "set null",
|
||||||
}),
|
}),
|
||||||
serverId: text("serverId").references(() => server.serverId, {
|
serverId: text("serverId").references(() => server.serverId, {
|
||||||
onDelete: "cascade",
|
onDelete: "cascade",
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
export const applicationsRelations = relations(
|
export const applicationsRelations = relations(
|
||||||
applications,
|
applications,
|
||||||
({ one, many }) => ({
|
({ one, many }) => ({
|
||||||
project: one(projects, {
|
project: one(projects, {
|
||||||
fields: [applications.projectId],
|
fields: [applications.projectId],
|
||||||
references: [projects.projectId],
|
references: [projects.projectId],
|
||||||
}),
|
}),
|
||||||
deployments: many(deployments),
|
deployments: many(deployments),
|
||||||
customGitSSHKey: one(sshKeys, {
|
customGitSSHKey: one(sshKeys, {
|
||||||
fields: [applications.customGitSSHKeyId],
|
fields: [applications.customGitSSHKeyId],
|
||||||
references: [sshKeys.sshKeyId],
|
references: [sshKeys.sshKeyId],
|
||||||
}),
|
}),
|
||||||
domains: many(domains),
|
domains: many(domains),
|
||||||
mounts: many(mounts),
|
mounts: many(mounts),
|
||||||
redirects: many(redirects),
|
redirects: many(redirects),
|
||||||
security: many(security),
|
security: many(security),
|
||||||
ports: many(ports),
|
ports: many(ports),
|
||||||
registry: one(registry, {
|
registry: one(registry, {
|
||||||
fields: [applications.registryId],
|
fields: [applications.registryId],
|
||||||
references: [registry.registryId],
|
references: [registry.registryId],
|
||||||
}),
|
}),
|
||||||
github: one(github, {
|
github: one(github, {
|
||||||
fields: [applications.githubId],
|
fields: [applications.githubId],
|
||||||
references: [github.githubId],
|
references: [github.githubId],
|
||||||
}),
|
}),
|
||||||
gitlab: one(gitlab, {
|
gitlab: one(gitlab, {
|
||||||
fields: [applications.gitlabId],
|
fields: [applications.gitlabId],
|
||||||
references: [gitlab.gitlabId],
|
references: [gitlab.gitlabId],
|
||||||
}),
|
}),
|
||||||
bitbucket: one(bitbucket, {
|
bitbucket: one(bitbucket, {
|
||||||
fields: [applications.bitbucketId],
|
fields: [applications.bitbucketId],
|
||||||
references: [bitbucket.bitbucketId],
|
references: [bitbucket.bitbucketId],
|
||||||
}),
|
}),
|
||||||
server: one(server, {
|
server: one(server, {
|
||||||
fields: [applications.serverId],
|
fields: [applications.serverId],
|
||||||
references: [server.serverId],
|
references: [server.serverId],
|
||||||
}),
|
}),
|
||||||
previewDeployments: many(previewDeployments),
|
previewDeployments: many(previewDeployments),
|
||||||
})
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
const HealthCheckSwarmSchema = z
|
const HealthCheckSwarmSchema = z
|
||||||
.object({
|
.object({
|
||||||
Test: z.array(z.string()).optional(),
|
Test: z.array(z.string()).optional(),
|
||||||
Interval: z.number().optional(),
|
Interval: z.number().optional(),
|
||||||
Timeout: z.number().optional(),
|
Timeout: z.number().optional(),
|
||||||
StartPeriod: z.number().optional(),
|
StartPeriod: z.number().optional(),
|
||||||
Retries: z.number().optional(),
|
Retries: z.number().optional(),
|
||||||
})
|
})
|
||||||
.strict();
|
.strict();
|
||||||
|
|
||||||
const RestartPolicySwarmSchema = z
|
const RestartPolicySwarmSchema = z
|
||||||
.object({
|
.object({
|
||||||
Condition: z.string().optional(),
|
Condition: z.string().optional(),
|
||||||
Delay: z.number().optional(),
|
Delay: z.number().optional(),
|
||||||
MaxAttempts: z.number().optional(),
|
MaxAttempts: z.number().optional(),
|
||||||
Window: z.number().optional(),
|
Window: z.number().optional(),
|
||||||
})
|
})
|
||||||
.strict();
|
.strict();
|
||||||
|
|
||||||
const PreferenceSchema = z
|
const PreferenceSchema = z
|
||||||
.object({
|
.object({
|
||||||
Spread: z.object({
|
Spread: z.object({
|
||||||
SpreadDescriptor: z.string(),
|
SpreadDescriptor: z.string(),
|
||||||
}),
|
}),
|
||||||
})
|
})
|
||||||
.strict();
|
.strict();
|
||||||
|
|
||||||
const PlatformSchema = z
|
const PlatformSchema = z
|
||||||
.object({
|
.object({
|
||||||
Architecture: z.string(),
|
Architecture: z.string(),
|
||||||
OS: z.string(),
|
OS: z.string(),
|
||||||
})
|
})
|
||||||
.strict();
|
.strict();
|
||||||
|
|
||||||
const PlacementSwarmSchema = z
|
const PlacementSwarmSchema = z
|
||||||
.object({
|
.object({
|
||||||
Constraints: z.array(z.string()).optional(),
|
Constraints: z.array(z.string()).optional(),
|
||||||
Preferences: z.array(PreferenceSchema).optional(),
|
Preferences: z.array(PreferenceSchema).optional(),
|
||||||
MaxReplicas: z.number().optional(),
|
MaxReplicas: z.number().optional(),
|
||||||
Platforms: z.array(PlatformSchema).optional(),
|
Platforms: z.array(PlatformSchema).optional(),
|
||||||
})
|
})
|
||||||
.strict();
|
.strict();
|
||||||
|
|
||||||
const UpdateConfigSwarmSchema = z
|
const UpdateConfigSwarmSchema = z
|
||||||
.object({
|
.object({
|
||||||
Parallelism: z.number(),
|
Parallelism: z.number(),
|
||||||
Delay: z.number().optional(),
|
Delay: z.number().optional(),
|
||||||
FailureAction: z.string().optional(),
|
FailureAction: z.string().optional(),
|
||||||
Monitor: z.number().optional(),
|
Monitor: z.number().optional(),
|
||||||
MaxFailureRatio: z.number().optional(),
|
MaxFailureRatio: z.number().optional(),
|
||||||
Order: z.string(),
|
Order: z.string(),
|
||||||
})
|
})
|
||||||
.strict();
|
.strict();
|
||||||
|
|
||||||
const ReplicatedSchema = z
|
const ReplicatedSchema = z
|
||||||
.object({
|
.object({
|
||||||
Replicas: z.number().optional(),
|
Replicas: z.number().optional(),
|
||||||
})
|
})
|
||||||
.strict();
|
.strict();
|
||||||
|
|
||||||
const ReplicatedJobSchema = z
|
const ReplicatedJobSchema = z
|
||||||
.object({
|
.object({
|
||||||
MaxConcurrent: z.number().optional(),
|
MaxConcurrent: z.number().optional(),
|
||||||
TotalCompletions: z.number().optional(),
|
TotalCompletions: z.number().optional(),
|
||||||
})
|
})
|
||||||
.strict();
|
.strict();
|
||||||
|
|
||||||
const ServiceModeSwarmSchema = z
|
const ServiceModeSwarmSchema = z
|
||||||
.object({
|
.object({
|
||||||
Replicated: ReplicatedSchema.optional(),
|
Replicated: ReplicatedSchema.optional(),
|
||||||
Global: z.object({}).optional(),
|
Global: z.object({}).optional(),
|
||||||
ReplicatedJob: ReplicatedJobSchema.optional(),
|
ReplicatedJob: ReplicatedJobSchema.optional(),
|
||||||
GlobalJob: z.object({}).optional(),
|
GlobalJob: z.object({}).optional(),
|
||||||
})
|
})
|
||||||
.strict();
|
.strict();
|
||||||
|
|
||||||
const NetworkSwarmSchema = z.array(
|
const NetworkSwarmSchema = z.array(
|
||||||
z
|
z
|
||||||
.object({
|
.object({
|
||||||
Target: z.string().optional(),
|
Target: z.string().optional(),
|
||||||
Aliases: z.array(z.string()).optional(),
|
Aliases: z.array(z.string()).optional(),
|
||||||
DriverOpts: z.object({}).optional(),
|
DriverOpts: z.object({}).optional(),
|
||||||
})
|
})
|
||||||
.strict()
|
.strict(),
|
||||||
);
|
);
|
||||||
|
|
||||||
const LabelsSwarmSchema = z.record(z.string());
|
const LabelsSwarmSchema = z.record(z.string());
|
||||||
|
|
||||||
const createSchema = createInsertSchema(applications, {
|
const createSchema = createInsertSchema(applications, {
|
||||||
appName: z.string(),
|
appName: z.string(),
|
||||||
createdAt: z.string(),
|
createdAt: z.string(),
|
||||||
applicationId: z.string(),
|
applicationId: z.string(),
|
||||||
autoDeploy: z.boolean(),
|
autoDeploy: z.boolean(),
|
||||||
env: z.string().optional(),
|
env: z.string().optional(),
|
||||||
buildArgs: z.string().optional(),
|
buildArgs: z.string().optional(),
|
||||||
name: z.string().min(1),
|
name: z.string().min(1),
|
||||||
description: z.string().optional(),
|
description: z.string().optional(),
|
||||||
memoryReservation: z.number().optional(),
|
memoryReservation: z.number().optional(),
|
||||||
memoryLimit: z.number().optional(),
|
memoryLimit: z.number().optional(),
|
||||||
cpuReservation: z.number().optional(),
|
cpuReservation: z.number().optional(),
|
||||||
cpuLimit: z.number().optional(),
|
cpuLimit: z.number().optional(),
|
||||||
title: z.string().optional(),
|
title: z.string().optional(),
|
||||||
enabled: z.boolean().optional(),
|
enabled: z.boolean().optional(),
|
||||||
subtitle: z.string().optional(),
|
subtitle: z.string().optional(),
|
||||||
dockerImage: z.string().optional(),
|
dockerImage: z.string().optional(),
|
||||||
username: z.string().optional(),
|
username: z.string().optional(),
|
||||||
isPreviewDeploymentsActive: z.boolean().optional(),
|
isPreviewDeploymentsActive: z.boolean().optional(),
|
||||||
password: z.string().optional(),
|
password: z.string().optional(),
|
||||||
registryUrl: z.string().optional(),
|
registryUrl: z.string().optional(),
|
||||||
customGitSSHKeyId: z.string().optional(),
|
customGitSSHKeyId: z.string().optional(),
|
||||||
repository: z.string().optional(),
|
repository: z.string().optional(),
|
||||||
dockerfile: z.string().optional(),
|
dockerfile: z.string().optional(),
|
||||||
branch: z.string().optional(),
|
branch: z.string().optional(),
|
||||||
customGitBranch: z.string().optional(),
|
customGitBranch: z.string().optional(),
|
||||||
customGitBuildPath: z.string().optional(),
|
customGitBuildPath: z.string().optional(),
|
||||||
customGitUrl: z.string().optional(),
|
customGitUrl: z.string().optional(),
|
||||||
buildPath: z.string().optional(),
|
buildPath: z.string().optional(),
|
||||||
projectId: z.string(),
|
projectId: z.string(),
|
||||||
sourceType: z.enum(["github", "docker", "git"]).optional(),
|
sourceType: z.enum(["github", "docker", "git"]).optional(),
|
||||||
applicationStatus: z.enum(["idle", "running", "done", "error"]),
|
applicationStatus: z.enum(["idle", "running", "done", "error"]),
|
||||||
buildType: z.enum([
|
buildType: z.enum([
|
||||||
"dockerfile",
|
"dockerfile",
|
||||||
"heroku_buildpacks",
|
"heroku_buildpacks",
|
||||||
"paketo_buildpacks",
|
"paketo_buildpacks",
|
||||||
"nixpacks",
|
"nixpacks",
|
||||||
"static",
|
"static",
|
||||||
]),
|
]),
|
||||||
herokuVersion: z.string().optional(),
|
herokuVersion: z.string().optional(),
|
||||||
publishDirectory: z.string().optional(),
|
publishDirectory: z.string().optional(),
|
||||||
owner: z.string(),
|
owner: z.string(),
|
||||||
healthCheckSwarm: HealthCheckSwarmSchema.nullable(),
|
healthCheckSwarm: HealthCheckSwarmSchema.nullable(),
|
||||||
restartPolicySwarm: RestartPolicySwarmSchema.nullable(),
|
restartPolicySwarm: RestartPolicySwarmSchema.nullable(),
|
||||||
placementSwarm: PlacementSwarmSchema.nullable(),
|
placementSwarm: PlacementSwarmSchema.nullable(),
|
||||||
updateConfigSwarm: UpdateConfigSwarmSchema.nullable(),
|
updateConfigSwarm: UpdateConfigSwarmSchema.nullable(),
|
||||||
rollbackConfigSwarm: UpdateConfigSwarmSchema.nullable(),
|
rollbackConfigSwarm: UpdateConfigSwarmSchema.nullable(),
|
||||||
modeSwarm: ServiceModeSwarmSchema.nullable(),
|
modeSwarm: ServiceModeSwarmSchema.nullable(),
|
||||||
labelsSwarm: LabelsSwarmSchema.nullable(),
|
labelsSwarm: LabelsSwarmSchema.nullable(),
|
||||||
networkSwarm: NetworkSwarmSchema.nullable(),
|
networkSwarm: NetworkSwarmSchema.nullable(),
|
||||||
previewPort: z.number().optional(),
|
previewPort: z.number().optional(),
|
||||||
previewEnv: z.string().optional(),
|
previewEnv: z.string().optional(),
|
||||||
previewBuildArgs: z.string().optional(),
|
previewBuildArgs: z.string().optional(),
|
||||||
previewWildcard: z.string().optional(),
|
previewWildcard: z.string().optional(),
|
||||||
previewLimit: z.number().optional(),
|
previewLimit: z.number().optional(),
|
||||||
previewHttps: z.boolean().optional(),
|
previewHttps: z.boolean().optional(),
|
||||||
previewPath: z.string().optional(),
|
previewPath: z.string().optional(),
|
||||||
previewCertificateType: z.enum(["letsencrypt", "none"]).optional(),
|
previewCertificateType: z.enum(["letsencrypt", "none"]).optional(),
|
||||||
});
|
});
|
||||||
|
|
||||||
export const apiCreateApplication = createSchema.pick({
|
export const apiCreateApplication = createSchema.pick({
|
||||||
name: true,
|
name: true,
|
||||||
appName: true,
|
appName: true,
|
||||||
description: true,
|
description: true,
|
||||||
projectId: true,
|
projectId: true,
|
||||||
serverId: true,
|
serverId: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
export const apiFindOneApplication = createSchema
|
export const apiFindOneApplication = createSchema
|
||||||
.pick({
|
.pick({
|
||||||
applicationId: true,
|
applicationId: true,
|
||||||
})
|
})
|
||||||
.required();
|
.required();
|
||||||
|
|
||||||
export const apiReloadApplication = createSchema
|
export const apiReloadApplication = createSchema
|
||||||
.pick({
|
.pick({
|
||||||
appName: true,
|
appName: true,
|
||||||
applicationId: true,
|
applicationId: true,
|
||||||
})
|
})
|
||||||
.required();
|
.required();
|
||||||
|
|
||||||
export const apiSaveBuildType = createSchema
|
export const apiSaveBuildType = createSchema
|
||||||
.pick({
|
.pick({
|
||||||
applicationId: true,
|
applicationId: true,
|
||||||
buildType: true,
|
buildType: true,
|
||||||
dockerfile: true,
|
dockerfile: true,
|
||||||
dockerContextPath: true,
|
dockerContextPath: true,
|
||||||
dockerBuildStage: true,
|
dockerBuildStage: true,
|
||||||
herokuVersion: true,
|
herokuVersion: true,
|
||||||
})
|
})
|
||||||
.required()
|
.required()
|
||||||
.merge(createSchema.pick({ publishDirectory: true }));
|
.merge(createSchema.pick({ publishDirectory: true }));
|
||||||
|
|
||||||
export const apiSaveGithubProvider = createSchema
|
export const apiSaveGithubProvider = createSchema
|
||||||
.pick({
|
.pick({
|
||||||
applicationId: true,
|
applicationId: true,
|
||||||
repository: true,
|
repository: true,
|
||||||
branch: true,
|
branch: true,
|
||||||
owner: true,
|
owner: true,
|
||||||
buildPath: true,
|
buildPath: true,
|
||||||
githubId: true,
|
githubId: true,
|
||||||
})
|
})
|
||||||
.required();
|
.required();
|
||||||
|
|
||||||
export const apiSaveGitlabProvider = createSchema
|
export const apiSaveGitlabProvider = createSchema
|
||||||
.pick({
|
.pick({
|
||||||
applicationId: true,
|
applicationId: true,
|
||||||
gitlabBranch: true,
|
gitlabBranch: true,
|
||||||
gitlabBuildPath: true,
|
gitlabBuildPath: true,
|
||||||
gitlabOwner: true,
|
gitlabOwner: true,
|
||||||
gitlabRepository: true,
|
gitlabRepository: true,
|
||||||
gitlabId: true,
|
gitlabId: true,
|
||||||
gitlabProjectId: true,
|
gitlabProjectId: true,
|
||||||
gitlabPathNamespace: true,
|
gitlabPathNamespace: true,
|
||||||
})
|
})
|
||||||
.required();
|
.required();
|
||||||
|
|
||||||
export const apiSaveBitbucketProvider = createSchema
|
export const apiSaveBitbucketProvider = createSchema
|
||||||
.pick({
|
.pick({
|
||||||
bitbucketBranch: true,
|
bitbucketBranch: true,
|
||||||
bitbucketBuildPath: true,
|
bitbucketBuildPath: true,
|
||||||
bitbucketOwner: true,
|
bitbucketOwner: true,
|
||||||
bitbucketRepository: true,
|
bitbucketRepository: true,
|
||||||
bitbucketId: true,
|
bitbucketId: true,
|
||||||
applicationId: true,
|
applicationId: true,
|
||||||
})
|
})
|
||||||
.required();
|
.required();
|
||||||
|
|
||||||
export const apiSaveDockerProvider = createSchema
|
export const apiSaveDockerProvider = createSchema
|
||||||
.pick({
|
.pick({
|
||||||
dockerImage: true,
|
dockerImage: true,
|
||||||
applicationId: true,
|
applicationId: true,
|
||||||
username: true,
|
username: true,
|
||||||
password: true,
|
password: true,
|
||||||
registryUrl: true,
|
registryUrl: true,
|
||||||
})
|
})
|
||||||
.required();
|
.required();
|
||||||
|
|
||||||
export const apiSaveGitProvider = createSchema
|
export const apiSaveGitProvider = createSchema
|
||||||
.pick({
|
.pick({
|
||||||
customGitBranch: true,
|
customGitBranch: true,
|
||||||
applicationId: true,
|
applicationId: true,
|
||||||
customGitBuildPath: true,
|
customGitBuildPath: true,
|
||||||
customGitUrl: true,
|
customGitUrl: true,
|
||||||
})
|
})
|
||||||
.required()
|
.required()
|
||||||
.merge(
|
.merge(
|
||||||
createSchema.pick({
|
createSchema.pick({
|
||||||
customGitSSHKeyId: true,
|
customGitSSHKeyId: true,
|
||||||
})
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
export const apiSaveEnvironmentVariables = createSchema
|
export const apiSaveEnvironmentVariables = createSchema
|
||||||
.pick({
|
.pick({
|
||||||
applicationId: true,
|
applicationId: true,
|
||||||
env: true,
|
env: true,
|
||||||
buildArgs: true,
|
buildArgs: true,
|
||||||
})
|
})
|
||||||
.required();
|
.required();
|
||||||
|
|
||||||
export const apiFindMonitoringStats = createSchema
|
export const apiFindMonitoringStats = createSchema
|
||||||
.pick({
|
.pick({
|
||||||
appName: true,
|
appName: true,
|
||||||
})
|
})
|
||||||
.required();
|
.required();
|
||||||
|
|
||||||
export const apiUpdateApplication = createSchema
|
export const apiUpdateApplication = createSchema
|
||||||
.partial()
|
.partial()
|
||||||
.extend({
|
.extend({
|
||||||
applicationId: z.string().min(1),
|
applicationId: z.string().min(1),
|
||||||
})
|
})
|
||||||
.omit({ serverId: true });
|
.omit({ serverId: true });
|
||||||
|
|||||||
@@ -11,8 +11,8 @@ import { nanoid } from "nanoid";
|
|||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import { applications } from "./application";
|
import { applications } from "./application";
|
||||||
import { compose } from "./compose";
|
import { compose } from "./compose";
|
||||||
import { server } from "./server";
|
|
||||||
import { previewDeployments } from "./preview-deployments";
|
import { previewDeployments } from "./preview-deployments";
|
||||||
|
import { server } from "./server";
|
||||||
|
|
||||||
export const deploymentStatus = pgEnum("deploymentStatus", [
|
export const deploymentStatus = pgEnum("deploymentStatus", [
|
||||||
"running",
|
"running",
|
||||||
|
|||||||
@@ -14,8 +14,8 @@ import { z } from "zod";
|
|||||||
import { domain } from "../validations/domain";
|
import { domain } from "../validations/domain";
|
||||||
import { applications } from "./application";
|
import { applications } from "./application";
|
||||||
import { compose } from "./compose";
|
import { compose } from "./compose";
|
||||||
import { certificateType } from "./shared";
|
|
||||||
import { previewDeployments } from "./preview-deployments";
|
import { previewDeployments } from "./preview-deployments";
|
||||||
|
import { certificateType } from "./shared";
|
||||||
|
|
||||||
export const domainType = pgEnum("domainType", [
|
export const domainType = pgEnum("domainType", [
|
||||||
"compose",
|
"compose",
|
||||||
|
|||||||
@@ -29,4 +29,4 @@ export * from "./github";
|
|||||||
export * from "./gitlab";
|
export * from "./gitlab";
|
||||||
export * from "./server";
|
export * from "./server";
|
||||||
export * from "./utils";
|
export * from "./utils";
|
||||||
export * from "./preview-deployments";
|
export * from "./preview-deployments";
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
import { relations } from "drizzle-orm";
|
import { relations } from "drizzle-orm";
|
||||||
import { pgTable, text } from "drizzle-orm/pg-core";
|
import { pgTable, text } from "drizzle-orm/pg-core";
|
||||||
import { nanoid } from "nanoid";
|
|
||||||
import { applications } from "./application";
|
|
||||||
import { domains } from "./domain";
|
|
||||||
import { deployments } from "./deployment";
|
|
||||||
import { createInsertSchema } from "drizzle-zod";
|
import { createInsertSchema } from "drizzle-zod";
|
||||||
|
import { nanoid } from "nanoid";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import { generateAppName } from "./utils";
|
import { applications } from "./application";
|
||||||
|
import { deployments } from "./deployment";
|
||||||
|
import { domains } from "./domain";
|
||||||
import { applicationStatus } from "./shared";
|
import { applicationStatus } from "./shared";
|
||||||
|
import { generateAppName } from "./utils";
|
||||||
|
|
||||||
export const previewDeployments = pgTable("preview_deployments", {
|
export const previewDeployments = pgTable("preview_deployments", {
|
||||||
previewDeploymentId: text("previewDeploymentId")
|
previewDeploymentId: text("previewDeploymentId")
|
||||||
|
|||||||
@@ -7,12 +7,12 @@ import {
|
|||||||
cleanUpSystemPrune,
|
cleanUpSystemPrune,
|
||||||
cleanUpUnusedImages,
|
cleanUpUnusedImages,
|
||||||
} from "../docker/utils";
|
} from "../docker/utils";
|
||||||
|
import { sendDatabaseBackupNotifications } from "../notifications/database-backup";
|
||||||
|
import { sendDockerCleanupNotifications } from "../notifications/docker-cleanup";
|
||||||
import { runMariadbBackup } from "./mariadb";
|
import { runMariadbBackup } from "./mariadb";
|
||||||
import { runMongoBackup } from "./mongo";
|
import { runMongoBackup } from "./mongo";
|
||||||
import { runMySqlBackup } from "./mysql";
|
import { runMySqlBackup } from "./mysql";
|
||||||
import { runPostgresBackup } from "./postgres";
|
import { runPostgresBackup } from "./postgres";
|
||||||
import { sendDockerCleanupNotifications } from "../notifications/docker-cleanup";
|
|
||||||
import { sendDatabaseBackupNotifications } from "../notifications/database-backup";
|
|
||||||
|
|
||||||
export const initCronJobs = async () => {
|
export const initCronJobs = async () => {
|
||||||
console.log("Setting up cron jobs....");
|
console.log("Setting up cron jobs....");
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import { createWriteStream } from "node:fs";
|
|||||||
import { join } from "node:path";
|
import { join } from "node:path";
|
||||||
import type { InferResultType } from "@dokploy/server/types/with";
|
import type { InferResultType } from "@dokploy/server/types/with";
|
||||||
import type { CreateServiceOptions } from "dockerode";
|
import type { CreateServiceOptions } from "dockerode";
|
||||||
|
import { nanoid } from "nanoid";
|
||||||
import { uploadImage, uploadImageRemoteCommand } from "../cluster/upload";
|
import { uploadImage, uploadImageRemoteCommand } from "../cluster/upload";
|
||||||
import {
|
import {
|
||||||
calculateResources,
|
calculateResources,
|
||||||
@@ -17,7 +18,6 @@ import { buildHeroku, getHerokuCommand } from "./heroku";
|
|||||||
import { buildNixpacks, getNixpacksCommand } from "./nixpacks";
|
import { buildNixpacks, getNixpacksCommand } from "./nixpacks";
|
||||||
import { buildPaketo, getPaketoCommand } from "./paketo";
|
import { buildPaketo, getPaketoCommand } from "./paketo";
|
||||||
import { buildStatic, getStaticCommand } from "./static";
|
import { buildStatic, getStaticCommand } from "./static";
|
||||||
import { nanoid } from "nanoid";
|
|
||||||
|
|
||||||
// NIXPACKS codeDirectory = where is the path of the code directory
|
// NIXPACKS codeDirectory = where is the path of the code directory
|
||||||
// HEROKU codeDirectory = where is the path of the code directory
|
// HEROKU codeDirectory = where is the path of the code directory
|
||||||
|
|||||||
@@ -15,528 +15,528 @@ import { spawnAsync } from "../process/spawnAsync";
|
|||||||
import { getRemoteDocker } from "../servers/remote-docker";
|
import { getRemoteDocker } from "../servers/remote-docker";
|
||||||
|
|
||||||
interface RegistryAuth {
|
interface RegistryAuth {
|
||||||
username: string;
|
username: string;
|
||||||
password: string;
|
password: string;
|
||||||
registryUrl: string;
|
registryUrl: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const pullImage = async (
|
export const pullImage = async (
|
||||||
dockerImage: string,
|
dockerImage: string,
|
||||||
onData?: (data: any) => void,
|
onData?: (data: any) => void,
|
||||||
authConfig?: Partial<RegistryAuth>
|
authConfig?: Partial<RegistryAuth>,
|
||||||
): Promise<void> => {
|
): Promise<void> => {
|
||||||
try {
|
try {
|
||||||
if (!dockerImage) {
|
if (!dockerImage) {
|
||||||
throw new Error("Docker image not found");
|
throw new Error("Docker image not found");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (authConfig?.username && authConfig?.password) {
|
if (authConfig?.username && authConfig?.password) {
|
||||||
await spawnAsync(
|
await spawnAsync(
|
||||||
"docker",
|
"docker",
|
||||||
[
|
[
|
||||||
"login",
|
"login",
|
||||||
authConfig.registryUrl || "",
|
authConfig.registryUrl || "",
|
||||||
"-u",
|
"-u",
|
||||||
authConfig.username,
|
authConfig.username,
|
||||||
"-p",
|
"-p",
|
||||||
authConfig.password,
|
authConfig.password,
|
||||||
],
|
],
|
||||||
onData
|
onData,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
await spawnAsync("docker", ["pull", dockerImage], onData);
|
await spawnAsync("docker", ["pull", dockerImage], onData);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const pullRemoteImage = async (
|
export const pullRemoteImage = async (
|
||||||
dockerImage: string,
|
dockerImage: string,
|
||||||
serverId: string,
|
serverId: string,
|
||||||
onData?: (data: any) => void,
|
onData?: (data: any) => void,
|
||||||
authConfig?: Partial<RegistryAuth>
|
authConfig?: Partial<RegistryAuth>,
|
||||||
): Promise<void> => {
|
): Promise<void> => {
|
||||||
try {
|
try {
|
||||||
if (!dockerImage) {
|
if (!dockerImage) {
|
||||||
throw new Error("Docker image not found");
|
throw new Error("Docker image not found");
|
||||||
}
|
}
|
||||||
|
|
||||||
const remoteDocker = await getRemoteDocker(serverId);
|
const remoteDocker = await getRemoteDocker(serverId);
|
||||||
|
|
||||||
await new Promise((resolve, reject) => {
|
await new Promise((resolve, reject) => {
|
||||||
remoteDocker.pull(
|
remoteDocker.pull(
|
||||||
dockerImage,
|
dockerImage,
|
||||||
{ authconfig: authConfig },
|
{ authconfig: authConfig },
|
||||||
(err, stream) => {
|
(err, stream) => {
|
||||||
if (err) {
|
if (err) {
|
||||||
reject(err);
|
reject(err);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
remoteDocker.modem.followProgress(
|
remoteDocker.modem.followProgress(
|
||||||
stream as Readable,
|
stream as Readable,
|
||||||
(err: Error | null, res) => {
|
(err: Error | null, res) => {
|
||||||
if (!err) {
|
if (!err) {
|
||||||
resolve(res);
|
resolve(res);
|
||||||
}
|
}
|
||||||
if (err) {
|
if (err) {
|
||||||
reject(err);
|
reject(err);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
(event) => {
|
(event) => {
|
||||||
onData?.(event);
|
onData?.(event);
|
||||||
}
|
},
|
||||||
);
|
);
|
||||||
}
|
},
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const containerExists = async (containerName: string) => {
|
export const containerExists = async (containerName: string) => {
|
||||||
const container = docker.getContainer(containerName);
|
const container = docker.getContainer(containerName);
|
||||||
try {
|
try {
|
||||||
await container.inspect();
|
await container.inspect();
|
||||||
return true;
|
return true;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const stopService = async (appName: string) => {
|
export const stopService = async (appName: string) => {
|
||||||
try {
|
try {
|
||||||
await execAsync(`docker service scale ${appName}=0 `);
|
await execAsync(`docker service scale ${appName}=0 `);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(error);
|
console.error(error);
|
||||||
return error;
|
return error;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const stopServiceRemote = async (serverId: string, appName: string) => {
|
export const stopServiceRemote = async (serverId: string, appName: string) => {
|
||||||
try {
|
try {
|
||||||
await execAsyncRemote(serverId, `docker service scale ${appName}=0 `);
|
await execAsyncRemote(serverId, `docker service scale ${appName}=0 `);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(error);
|
console.error(error);
|
||||||
return error;
|
return error;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getContainerByName = (name: string): Promise<ContainerInfo> => {
|
export const getContainerByName = (name: string): Promise<ContainerInfo> => {
|
||||||
const opts = {
|
const opts = {
|
||||||
limit: 1,
|
limit: 1,
|
||||||
filters: {
|
filters: {
|
||||||
name: [name],
|
name: [name],
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
docker.listContainers(opts, (err, containers) => {
|
docker.listContainers(opts, (err, containers) => {
|
||||||
if (err) {
|
if (err) {
|
||||||
reject(err);
|
reject(err);
|
||||||
} else if (containers?.length === 0) {
|
} else if (containers?.length === 0) {
|
||||||
reject(new Error(`No container found with name: ${name}`));
|
reject(new Error(`No container found with name: ${name}`));
|
||||||
} else if (containers && containers?.length > 0 && containers[0]) {
|
} else if (containers && containers?.length > 0 && containers[0]) {
|
||||||
resolve(containers[0]);
|
resolve(containers[0]);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
export const cleanUpUnusedImages = async (serverId?: string) => {
|
export const cleanUpUnusedImages = async (serverId?: string) => {
|
||||||
try {
|
try {
|
||||||
if (serverId) {
|
if (serverId) {
|
||||||
await execAsyncRemote(serverId, "docker image prune --all --force");
|
await execAsyncRemote(serverId, "docker image prune --all --force");
|
||||||
} else {
|
} else {
|
||||||
await execAsync("docker image prune --all --force");
|
await execAsync("docker image prune --all --force");
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(error);
|
console.error(error);
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const cleanStoppedContainers = async (serverId?: string) => {
|
export const cleanStoppedContainers = async (serverId?: string) => {
|
||||||
try {
|
try {
|
||||||
if (serverId) {
|
if (serverId) {
|
||||||
await execAsyncRemote(serverId, "docker container prune --force");
|
await execAsyncRemote(serverId, "docker container prune --force");
|
||||||
} else {
|
} else {
|
||||||
await execAsync("docker container prune --force");
|
await execAsync("docker container prune --force");
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(error);
|
console.error(error);
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const cleanUpUnusedVolumes = async (serverId?: string) => {
|
export const cleanUpUnusedVolumes = async (serverId?: string) => {
|
||||||
try {
|
try {
|
||||||
if (serverId) {
|
if (serverId) {
|
||||||
await execAsyncRemote(serverId, "docker volume prune --all --force");
|
await execAsyncRemote(serverId, "docker volume prune --all --force");
|
||||||
} else {
|
} else {
|
||||||
await execAsync("docker volume prune --all --force");
|
await execAsync("docker volume prune --all --force");
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(error);
|
console.error(error);
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const cleanUpInactiveContainers = async () => {
|
export const cleanUpInactiveContainers = async () => {
|
||||||
try {
|
try {
|
||||||
const containers = await docker.listContainers({ all: true });
|
const containers = await docker.listContainers({ all: true });
|
||||||
const inactiveContainers = containers.filter(
|
const inactiveContainers = containers.filter(
|
||||||
(container) => container.State !== "running"
|
(container) => container.State !== "running",
|
||||||
);
|
);
|
||||||
|
|
||||||
for (const container of inactiveContainers) {
|
for (const container of inactiveContainers) {
|
||||||
await docker.getContainer(container.Id).remove({ force: true });
|
await docker.getContainer(container.Id).remove({ force: true });
|
||||||
console.log(`Cleaning up inactive container: ${container.Id}`);
|
console.log(`Cleaning up inactive container: ${container.Id}`);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error cleaning up inactive containers:", error);
|
console.error("Error cleaning up inactive containers:", error);
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const cleanUpDockerBuilder = async (serverId?: string) => {
|
export const cleanUpDockerBuilder = async (serverId?: string) => {
|
||||||
if (serverId) {
|
if (serverId) {
|
||||||
await execAsyncRemote(serverId, "docker builder prune --all --force");
|
await execAsyncRemote(serverId, "docker builder prune --all --force");
|
||||||
} else {
|
} else {
|
||||||
await execAsync("docker builder prune --all --force");
|
await execAsync("docker builder prune --all --force");
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const cleanUpSystemPrune = async (serverId?: string) => {
|
export const cleanUpSystemPrune = async (serverId?: string) => {
|
||||||
if (serverId) {
|
if (serverId) {
|
||||||
await execAsyncRemote(
|
await execAsyncRemote(
|
||||||
serverId,
|
serverId,
|
||||||
"docker system prune --all --force --volumes"
|
"docker system prune --all --force --volumes",
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
await execAsync("docker system prune --all --force --volumes");
|
await execAsync("docker system prune --all --force --volumes");
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const startService = async (appName: string) => {
|
export const startService = async (appName: string) => {
|
||||||
try {
|
try {
|
||||||
await execAsync(`docker service scale ${appName}=1 `);
|
await execAsync(`docker service scale ${appName}=1 `);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(error);
|
console.error(error);
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const startServiceRemote = async (serverId: string, appName: string) => {
|
export const startServiceRemote = async (serverId: string, appName: string) => {
|
||||||
try {
|
try {
|
||||||
await execAsyncRemote(serverId, `docker service scale ${appName}=1 `);
|
await execAsyncRemote(serverId, `docker service scale ${appName}=1 `);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(error);
|
console.error(error);
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const removeService = async (
|
export const removeService = async (
|
||||||
appName: string,
|
appName: string,
|
||||||
serverId?: string | null,
|
serverId?: string | null,
|
||||||
deleteVolumes = false
|
deleteVolumes = false,
|
||||||
) => {
|
) => {
|
||||||
try {
|
try {
|
||||||
let command: string;
|
let command: string;
|
||||||
|
|
||||||
if (deleteVolumes) {
|
if (deleteVolumes) {
|
||||||
command = `docker service rm --force ${appName}`;
|
command = `docker service rm --force ${appName}`;
|
||||||
} else {
|
} else {
|
||||||
command = `docker service rm ${appName}`;
|
command = `docker service rm ${appName}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (serverId) {
|
if (serverId) {
|
||||||
await execAsyncRemote(serverId, command);
|
await execAsyncRemote(serverId, command);
|
||||||
} else {
|
} else {
|
||||||
await execAsync(command);
|
await execAsync(command);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return error;
|
return error;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const prepareEnvironmentVariables = (
|
export const prepareEnvironmentVariables = (
|
||||||
serviceEnv: string | null,
|
serviceEnv: string | null,
|
||||||
projectEnv?: string | null
|
projectEnv?: string | null,
|
||||||
) => {
|
) => {
|
||||||
const projectVars = parse(projectEnv ?? "");
|
const projectVars = parse(projectEnv ?? "");
|
||||||
const serviceVars = parse(serviceEnv ?? "");
|
const serviceVars = parse(serviceEnv ?? "");
|
||||||
|
|
||||||
const resolvedVars = Object.entries(serviceVars).map(([key, value]) => {
|
const resolvedVars = Object.entries(serviceVars).map(([key, value]) => {
|
||||||
let resolvedValue = value;
|
let resolvedValue = value;
|
||||||
if (projectVars) {
|
if (projectVars) {
|
||||||
resolvedValue = value.replace(/\$\{\{project\.(.*?)\}\}/g, (_, ref) => {
|
resolvedValue = value.replace(/\$\{\{project\.(.*?)\}\}/g, (_, ref) => {
|
||||||
if (projectVars[ref] !== undefined) {
|
if (projectVars[ref] !== undefined) {
|
||||||
return projectVars[ref];
|
return projectVars[ref];
|
||||||
}
|
}
|
||||||
throw new Error(`Invalid project environment variable: project.${ref}`);
|
throw new Error(`Invalid project environment variable: project.${ref}`);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
return `${key}=${resolvedValue}`;
|
return `${key}=${resolvedValue}`;
|
||||||
});
|
});
|
||||||
|
|
||||||
return resolvedVars;
|
return resolvedVars;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const prepareBuildArgs = (input: string | null) => {
|
export const prepareBuildArgs = (input: string | null) => {
|
||||||
const pairs = (input ?? "").split("\n");
|
const pairs = (input ?? "").split("\n");
|
||||||
|
|
||||||
const jsonObject: Record<string, string> = {};
|
const jsonObject: Record<string, string> = {};
|
||||||
|
|
||||||
for (const pair of pairs) {
|
for (const pair of pairs) {
|
||||||
const [key, value] = pair.split("=");
|
const [key, value] = pair.split("=");
|
||||||
if (key && value) {
|
if (key && value) {
|
||||||
jsonObject[key] = value;
|
jsonObject[key] = value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return jsonObject;
|
return jsonObject;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const generateVolumeMounts = (mounts: ApplicationNested["mounts"]) => {
|
export const generateVolumeMounts = (mounts: ApplicationNested["mounts"]) => {
|
||||||
if (!mounts || mounts.length === 0) {
|
if (!mounts || mounts.length === 0) {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
return mounts
|
return mounts
|
||||||
.filter((mount) => mount.type === "volume")
|
.filter((mount) => mount.type === "volume")
|
||||||
.map((mount) => ({
|
.map((mount) => ({
|
||||||
Type: "volume" as const,
|
Type: "volume" as const,
|
||||||
Source: mount.volumeName || "",
|
Source: mount.volumeName || "",
|
||||||
Target: mount.mountPath,
|
Target: mount.mountPath,
|
||||||
}));
|
}));
|
||||||
};
|
};
|
||||||
|
|
||||||
type Resources = {
|
type Resources = {
|
||||||
memoryLimit: number | null;
|
memoryLimit: number | null;
|
||||||
memoryReservation: number | null;
|
memoryReservation: number | null;
|
||||||
cpuLimit: number | null;
|
cpuLimit: number | null;
|
||||||
cpuReservation: number | null;
|
cpuReservation: number | null;
|
||||||
};
|
};
|
||||||
export const calculateResources = ({
|
export const calculateResources = ({
|
||||||
memoryLimit,
|
memoryLimit,
|
||||||
memoryReservation,
|
memoryReservation,
|
||||||
cpuLimit,
|
cpuLimit,
|
||||||
cpuReservation,
|
cpuReservation,
|
||||||
}: Resources): ResourceRequirements => {
|
}: Resources): ResourceRequirements => {
|
||||||
return {
|
return {
|
||||||
Limits: {
|
Limits: {
|
||||||
MemoryBytes: memoryLimit ?? undefined,
|
MemoryBytes: memoryLimit ?? undefined,
|
||||||
NanoCPUs: cpuLimit ?? undefined,
|
NanoCPUs: cpuLimit ?? undefined,
|
||||||
},
|
},
|
||||||
Reservations: {
|
Reservations: {
|
||||||
MemoryBytes: memoryReservation ?? undefined,
|
MemoryBytes: memoryReservation ?? undefined,
|
||||||
NanoCPUs: cpuReservation ?? undefined,
|
NanoCPUs: cpuReservation ?? undefined,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export const generateConfigContainer = (application: ApplicationNested) => {
|
export const generateConfigContainer = (application: ApplicationNested) => {
|
||||||
const {
|
const {
|
||||||
healthCheckSwarm,
|
healthCheckSwarm,
|
||||||
restartPolicySwarm,
|
restartPolicySwarm,
|
||||||
placementSwarm,
|
placementSwarm,
|
||||||
updateConfigSwarm,
|
updateConfigSwarm,
|
||||||
rollbackConfigSwarm,
|
rollbackConfigSwarm,
|
||||||
modeSwarm,
|
modeSwarm,
|
||||||
labelsSwarm,
|
labelsSwarm,
|
||||||
replicas,
|
replicas,
|
||||||
mounts,
|
mounts,
|
||||||
networkSwarm,
|
networkSwarm,
|
||||||
} = application;
|
} = application;
|
||||||
|
|
||||||
const haveMounts = mounts.length > 0;
|
const haveMounts = mounts.length > 0;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...(healthCheckSwarm && {
|
...(healthCheckSwarm && {
|
||||||
HealthCheck: healthCheckSwarm,
|
HealthCheck: healthCheckSwarm,
|
||||||
}),
|
}),
|
||||||
...(restartPolicySwarm
|
...(restartPolicySwarm
|
||||||
? {
|
? {
|
||||||
RestartPolicy: restartPolicySwarm,
|
RestartPolicy: restartPolicySwarm,
|
||||||
}
|
}
|
||||||
: {}),
|
: {}),
|
||||||
...(placementSwarm
|
...(placementSwarm
|
||||||
? {
|
? {
|
||||||
Placement: placementSwarm,
|
Placement: placementSwarm,
|
||||||
}
|
}
|
||||||
: {
|
: {
|
||||||
// if app have mounts keep manager as constraint
|
// if app have mounts keep manager as constraint
|
||||||
Placement: {
|
Placement: {
|
||||||
Constraints: haveMounts ? ["node.role==manager"] : [],
|
Constraints: haveMounts ? ["node.role==manager"] : [],
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
...(labelsSwarm && {
|
...(labelsSwarm && {
|
||||||
Labels: labelsSwarm,
|
Labels: labelsSwarm,
|
||||||
}),
|
}),
|
||||||
...(modeSwarm
|
...(modeSwarm
|
||||||
? {
|
? {
|
||||||
Mode: modeSwarm,
|
Mode: modeSwarm,
|
||||||
}
|
}
|
||||||
: {
|
: {
|
||||||
// use replicas value if no modeSwarm provided
|
// use replicas value if no modeSwarm provided
|
||||||
Mode: {
|
Mode: {
|
||||||
Replicated: {
|
Replicated: {
|
||||||
Replicas: replicas,
|
Replicas: replicas,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
...(rollbackConfigSwarm && {
|
...(rollbackConfigSwarm && {
|
||||||
RollbackConfig: rollbackConfigSwarm,
|
RollbackConfig: rollbackConfigSwarm,
|
||||||
}),
|
}),
|
||||||
...(updateConfigSwarm
|
...(updateConfigSwarm
|
||||||
? { UpdateConfig: updateConfigSwarm }
|
? { UpdateConfig: updateConfigSwarm }
|
||||||
: {
|
: {
|
||||||
// default config if no updateConfigSwarm provided
|
// default config if no updateConfigSwarm provided
|
||||||
UpdateConfig: {
|
UpdateConfig: {
|
||||||
Parallelism: 1,
|
Parallelism: 1,
|
||||||
Order: "start-first",
|
Order: "start-first",
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
...(networkSwarm
|
...(networkSwarm
|
||||||
? {
|
? {
|
||||||
Networks: networkSwarm,
|
Networks: networkSwarm,
|
||||||
}
|
}
|
||||||
: {
|
: {
|
||||||
Networks: [{ Target: "dokploy-network" }],
|
Networks: [{ Target: "dokploy-network" }],
|
||||||
}),
|
}),
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export const generateBindMounts = (mounts: ApplicationNested["mounts"]) => {
|
export const generateBindMounts = (mounts: ApplicationNested["mounts"]) => {
|
||||||
if (!mounts || mounts.length === 0) {
|
if (!mounts || mounts.length === 0) {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
return mounts
|
return mounts
|
||||||
.filter((mount) => mount.type === "bind")
|
.filter((mount) => mount.type === "bind")
|
||||||
.map((mount) => ({
|
.map((mount) => ({
|
||||||
Type: "bind" as const,
|
Type: "bind" as const,
|
||||||
Source: mount.hostPath || "",
|
Source: mount.hostPath || "",
|
||||||
Target: mount.mountPath,
|
Target: mount.mountPath,
|
||||||
}));
|
}));
|
||||||
};
|
};
|
||||||
|
|
||||||
export const generateFileMounts = (
|
export const generateFileMounts = (
|
||||||
appName: string,
|
appName: string,
|
||||||
service:
|
service:
|
||||||
| ApplicationNested
|
| ApplicationNested
|
||||||
| MongoNested
|
| MongoNested
|
||||||
| MariadbNested
|
| MariadbNested
|
||||||
| MysqlNested
|
| MysqlNested
|
||||||
| PostgresNested
|
| PostgresNested
|
||||||
| RedisNested
|
| RedisNested,
|
||||||
) => {
|
) => {
|
||||||
const { mounts } = service;
|
const { mounts } = service;
|
||||||
const { APPLICATIONS_PATH } = paths(!!service.serverId);
|
const { APPLICATIONS_PATH } = paths(!!service.serverId);
|
||||||
if (!mounts || mounts.length === 0) {
|
if (!mounts || mounts.length === 0) {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
return mounts
|
return mounts
|
||||||
.filter((mount) => mount.type === "file")
|
.filter((mount) => mount.type === "file")
|
||||||
.map((mount) => {
|
.map((mount) => {
|
||||||
const fileName = mount.filePath;
|
const fileName = mount.filePath;
|
||||||
const absoluteBasePath = path.resolve(APPLICATIONS_PATH);
|
const absoluteBasePath = path.resolve(APPLICATIONS_PATH);
|
||||||
const directory = path.join(absoluteBasePath, appName, "files");
|
const directory = path.join(absoluteBasePath, appName, "files");
|
||||||
const sourcePath = path.join(directory, fileName || "");
|
const sourcePath = path.join(directory, fileName || "");
|
||||||
return {
|
return {
|
||||||
Type: "bind" as const,
|
Type: "bind" as const,
|
||||||
Source: sourcePath,
|
Source: sourcePath,
|
||||||
Target: mount.mountPath,
|
Target: mount.mountPath,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
export const createFile = async (
|
export const createFile = async (
|
||||||
outputPath: string,
|
outputPath: string,
|
||||||
filePath: string,
|
filePath: string,
|
||||||
content: string
|
content: string,
|
||||||
) => {
|
) => {
|
||||||
try {
|
try {
|
||||||
const fullPath = path.join(outputPath, filePath);
|
const fullPath = path.join(outputPath, filePath);
|
||||||
if (fullPath.endsWith(path.sep) || filePath.endsWith("/")) {
|
if (fullPath.endsWith(path.sep) || filePath.endsWith("/")) {
|
||||||
fs.mkdirSync(fullPath, { recursive: true });
|
fs.mkdirSync(fullPath, { recursive: true });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const directory = path.dirname(fullPath);
|
const directory = path.dirname(fullPath);
|
||||||
fs.mkdirSync(directory, { recursive: true });
|
fs.mkdirSync(directory, { recursive: true });
|
||||||
fs.writeFileSync(fullPath, content || "");
|
fs.writeFileSync(fullPath, content || "");
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
export const encodeBase64 = (content: string) =>
|
export const encodeBase64 = (content: string) =>
|
||||||
Buffer.from(content, "utf-8").toString("base64");
|
Buffer.from(content, "utf-8").toString("base64");
|
||||||
|
|
||||||
export const getCreateFileCommand = (
|
export const getCreateFileCommand = (
|
||||||
outputPath: string,
|
outputPath: string,
|
||||||
filePath: string,
|
filePath: string,
|
||||||
content: string
|
content: string,
|
||||||
) => {
|
) => {
|
||||||
const fullPath = path.join(outputPath, filePath);
|
const fullPath = path.join(outputPath, filePath);
|
||||||
if (fullPath.endsWith(path.sep) || filePath.endsWith("/")) {
|
if (fullPath.endsWith(path.sep) || filePath.endsWith("/")) {
|
||||||
return `mkdir -p ${fullPath};`;
|
return `mkdir -p ${fullPath};`;
|
||||||
}
|
}
|
||||||
|
|
||||||
const directory = path.dirname(fullPath);
|
const directory = path.dirname(fullPath);
|
||||||
const encodedContent = encodeBase64(content);
|
const encodedContent = encodeBase64(content);
|
||||||
return `
|
return `
|
||||||
mkdir -p ${directory};
|
mkdir -p ${directory};
|
||||||
echo "${encodedContent}" | base64 -d > "${fullPath}";
|
echo "${encodedContent}" | base64 -d > "${fullPath}";
|
||||||
`;
|
`;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getServiceContainer = async (appName: string) => {
|
export const getServiceContainer = async (appName: string) => {
|
||||||
try {
|
try {
|
||||||
const filter = {
|
const filter = {
|
||||||
status: ["running"],
|
status: ["running"],
|
||||||
label: [`com.docker.swarm.service.name=${appName}`],
|
label: [`com.docker.swarm.service.name=${appName}`],
|
||||||
};
|
};
|
||||||
|
|
||||||
const containers = await docker.listContainers({
|
const containers = await docker.listContainers({
|
||||||
filters: JSON.stringify(filter),
|
filters: JSON.stringify(filter),
|
||||||
});
|
});
|
||||||
|
|
||||||
if (containers.length === 0 || !containers[0]) {
|
if (containers.length === 0 || !containers[0]) {
|
||||||
throw new Error(`No container found with name: ${appName}`);
|
throw new Error(`No container found with name: ${appName}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
const container = containers[0];
|
const container = containers[0];
|
||||||
|
|
||||||
return container;
|
return container;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getRemoteServiceContainer = async (
|
export const getRemoteServiceContainer = async (
|
||||||
serverId: string,
|
serverId: string,
|
||||||
appName: string
|
appName: string,
|
||||||
) => {
|
) => {
|
||||||
try {
|
try {
|
||||||
const filter = {
|
const filter = {
|
||||||
status: ["running"],
|
status: ["running"],
|
||||||
label: [`com.docker.swarm.service.name=${appName}`],
|
label: [`com.docker.swarm.service.name=${appName}`],
|
||||||
};
|
};
|
||||||
const remoteDocker = await getRemoteDocker(serverId);
|
const remoteDocker = await getRemoteDocker(serverId);
|
||||||
const containers = await remoteDocker.listContainers({
|
const containers = await remoteDocker.listContainers({
|
||||||
filters: JSON.stringify(filter),
|
filters: JSON.stringify(filter),
|
||||||
});
|
});
|
||||||
|
|
||||||
if (containers.length === 0 || !containers[0]) {
|
if (containers.length === 0 || !containers[0]) {
|
||||||
throw new Error(`No container found with name: ${appName}`);
|
throw new Error(`No container found with name: ${appName}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
const container = containers[0];
|
const container = containers[0];
|
||||||
|
|
||||||
return container;
|
return container;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user