mirror of
https://github.com/Dokploy/dokploy
synced 2025-06-26 18:27:59 +00:00
Compare commits
3 Commits
v0.16.0
...
804-servic
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
159584c5df | ||
|
|
bffd8c3e9c | ||
|
|
5e7486859f |
@@ -99,14 +99,14 @@ workflows:
|
|||||||
only:
|
only:
|
||||||
- main
|
- main
|
||||||
- canary
|
- canary
|
||||||
- fix/nixpacks-version
|
- 804-services-deployed-via-dokploy-inaccessible-after-customizing-traefik-ports-via-environment-variables
|
||||||
- build-arm64:
|
- build-arm64:
|
||||||
filters:
|
filters:
|
||||||
branches:
|
branches:
|
||||||
only:
|
only:
|
||||||
- main
|
- main
|
||||||
- canary
|
- canary
|
||||||
- fix/nixpacks-version
|
- 804-services-deployed-via-dokploy-inaccessible-after-customizing-traefik-ports-via-environment-variables
|
||||||
- combine-manifests:
|
- combine-manifests:
|
||||||
requires:
|
requires:
|
||||||
- build-amd64
|
- build-amd64
|
||||||
@@ -116,4 +116,4 @@ workflows:
|
|||||||
only:
|
only:
|
||||||
- main
|
- main
|
||||||
- canary
|
- canary
|
||||||
- fix/nixpacks-version
|
- 804-services-deployed-via-dokploy-inaccessible-after-customizing-traefik-ports-via-environment-variables
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import { Badge } from "@/components/ui/badge";
|
|
||||||
import {
|
import {
|
||||||
Card,
|
Card,
|
||||||
CardContent,
|
CardContent,
|
||||||
@@ -16,7 +15,6 @@ 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 { api } from "@/utils/api";
|
||||||
import { Loader2 } from "lucide-react";
|
import { Loader2 } from "lucide-react";
|
||||||
import dynamic from "next/dynamic";
|
import dynamic from "next/dynamic";
|
||||||
@@ -31,67 +29,28 @@ export const DockerLogs = dynamic(
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
export const badgeStateColor = (state: string) => {
|
|
||||||
switch (state) {
|
|
||||||
case "running":
|
|
||||||
return "green";
|
|
||||||
case "exited":
|
|
||||||
case "shutdown":
|
|
||||||
return "red";
|
|
||||||
case "accepted":
|
|
||||||
case "created":
|
|
||||||
return "blue";
|
|
||||||
default:
|
|
||||||
return "default";
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
appName: string;
|
appName: string;
|
||||||
serverId?: string;
|
serverId?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const ShowDockerLogs = ({ appName, serverId }: Props) => {
|
export const ShowDockerLogs = ({ appName, serverId }: Props) => {
|
||||||
|
const { data, isLoading } = api.docker.getContainersByAppNameMatch.useQuery(
|
||||||
|
{
|
||||||
|
appName,
|
||||||
|
serverId,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
enabled: !!appName,
|
||||||
|
},
|
||||||
|
);
|
||||||
const [containerId, setContainerId] = useState<string | undefined>();
|
const [containerId, setContainerId] = useState<string | undefined>();
|
||||||
const [option, setOption] = useState<"swarm" | "native">("native");
|
|
||||||
|
|
||||||
const { data: services, isLoading: servicesLoading } =
|
|
||||||
api.docker.getServiceContainersByAppName.useQuery(
|
|
||||||
{
|
|
||||||
appName,
|
|
||||||
serverId,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
enabled: !!appName && option === "swarm",
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
const { data: containers, isLoading: containersLoading } =
|
|
||||||
api.docker.getContainersByAppNameMatch.useQuery(
|
|
||||||
{
|
|
||||||
appName,
|
|
||||||
serverId,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
enabled: !!appName && option === "native",
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (option === "native") {
|
if (data && data?.length > 0) {
|
||||||
if (containers && containers?.length > 0) {
|
setContainerId(data[0]?.containerId);
|
||||||
setContainerId(containers[0]?.containerId);
|
|
||||||
}
|
}
|
||||||
} else {
|
}, [data]);
|
||||||
if (services && services?.length > 0) {
|
|
||||||
setContainerId(services[0]?.containerId);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, [option, services, containers]);
|
|
||||||
|
|
||||||
const isLoading = option === "native" ? containersLoading : servicesLoading;
|
|
||||||
const containersLenght =
|
|
||||||
option === "native" ? containers?.length : services?.length;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card className="bg-background">
|
<Card className="bg-background">
|
||||||
@@ -103,21 +62,7 @@ export const ShowDockerLogs = ({ appName, serverId }: Props) => {
|
|||||||
</CardHeader>
|
</CardHeader>
|
||||||
|
|
||||||
<CardContent className="flex flex-col gap-4">
|
<CardContent className="flex flex-col gap-4">
|
||||||
<div className="flex flex-row justify-between items-center gap-2">
|
|
||||||
<Label>Select a container to view logs</Label>
|
<Label>Select a container to view logs</Label>
|
||||||
<div className="flex flex-row gap-2 items-center">
|
|
||||||
<span className="text-sm text-muted-foreground">
|
|
||||||
{option === "native" ? "Native" : "Swarm"}
|
|
||||||
</span>
|
|
||||||
<Switch
|
|
||||||
checked={option === "native"}
|
|
||||||
onCheckedChange={(checked) => {
|
|
||||||
setOption(checked ? "native" : "swarm");
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<Select onValueChange={setContainerId} value={containerId}>
|
<Select onValueChange={setContainerId} value={containerId}>
|
||||||
<SelectTrigger>
|
<SelectTrigger>
|
||||||
{isLoading ? (
|
{isLoading ? (
|
||||||
@@ -131,45 +76,21 @@ export const ShowDockerLogs = ({ appName, serverId }: Props) => {
|
|||||||
</SelectTrigger>
|
</SelectTrigger>
|
||||||
<SelectContent>
|
<SelectContent>
|
||||||
<SelectGroup>
|
<SelectGroup>
|
||||||
{option === "native" ? (
|
{data?.map((container) => (
|
||||||
<div>
|
|
||||||
{containers?.map((container) => (
|
|
||||||
<SelectItem
|
<SelectItem
|
||||||
key={container.containerId}
|
key={container.containerId}
|
||||||
value={container.containerId}
|
value={container.containerId}
|
||||||
>
|
>
|
||||||
{container.name} ({container.containerId}){" "}
|
{container.name} ({container.containerId}) {container.state}
|
||||||
<Badge variant={badgeStateColor(container.state)}>
|
|
||||||
{container.state}
|
|
||||||
</Badge>
|
|
||||||
</SelectItem>
|
</SelectItem>
|
||||||
))}
|
))}
|
||||||
</div>
|
<SelectLabel>Containers ({data?.length})</SelectLabel>
|
||||||
) : (
|
|
||||||
<>
|
|
||||||
{services?.map((container) => (
|
|
||||||
<SelectItem
|
|
||||||
key={container.containerId}
|
|
||||||
value={container.containerId}
|
|
||||||
>
|
|
||||||
{container.name} ({container.containerId}@{container.node}
|
|
||||||
)
|
|
||||||
<Badge variant={badgeStateColor(container.state)}>
|
|
||||||
{container.state}
|
|
||||||
</Badge>
|
|
||||||
</SelectItem>
|
|
||||||
))}
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<SelectLabel>Containers ({containersLenght})</SelectLabel>
|
|
||||||
</SelectGroup>
|
</SelectGroup>
|
||||||
</SelectContent>
|
</SelectContent>
|
||||||
</Select>
|
</Select>
|
||||||
<DockerLogs
|
<DockerLogs
|
||||||
serverId={serverId || ""}
|
serverId={serverId || ""}
|
||||||
containerId={containerId || "select-a-container"}
|
containerId={containerId || "select-a-container"}
|
||||||
runType={option}
|
|
||||||
/>
|
/>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
|
|||||||
@@ -1,165 +0,0 @@
|
|||||||
import { badgeStateColor } from "@/components/dashboard/application/logs/show";
|
|
||||||
import { Badge } from "@/components/ui/badge";
|
|
||||||
import {
|
|
||||||
Card,
|
|
||||||
CardContent,
|
|
||||||
CardDescription,
|
|
||||||
CardHeader,
|
|
||||||
CardTitle,
|
|
||||||
} from "@/components/ui/card";
|
|
||||||
import { Label } from "@/components/ui/label";
|
|
||||||
import {
|
|
||||||
Select,
|
|
||||||
SelectContent,
|
|
||||||
SelectGroup,
|
|
||||||
SelectItem,
|
|
||||||
SelectLabel,
|
|
||||||
SelectTrigger,
|
|
||||||
SelectValue,
|
|
||||||
} from "@/components/ui/select";
|
|
||||||
import { Switch } from "@/components/ui/switch";
|
|
||||||
import { api } from "@/utils/api";
|
|
||||||
import { Loader2 } from "lucide-react";
|
|
||||||
import dynamic from "next/dynamic";
|
|
||||||
import { useEffect, useState } from "react";
|
|
||||||
export const DockerLogs = dynamic(
|
|
||||||
() =>
|
|
||||||
import("@/components/dashboard/docker/logs/docker-logs-id").then(
|
|
||||||
(e) => e.DockerLogsId,
|
|
||||||
),
|
|
||||||
{
|
|
||||||
ssr: false,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
interface Props {
|
|
||||||
appName: string;
|
|
||||||
serverId?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
badgeStateColor;
|
|
||||||
|
|
||||||
export const ShowDockerLogsStack = ({ appName, serverId }: Props) => {
|
|
||||||
const [option, setOption] = useState<"swarm" | "native">("native");
|
|
||||||
const [containerId, setContainerId] = useState<string | undefined>();
|
|
||||||
|
|
||||||
const { data: services, isLoading: servicesLoading } =
|
|
||||||
api.docker.getStackContainersByAppName.useQuery(
|
|
||||||
{
|
|
||||||
appName,
|
|
||||||
serverId,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
enabled: !!appName && option === "swarm",
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
const { data: containers, isLoading: containersLoading } =
|
|
||||||
api.docker.getContainersByAppNameMatch.useQuery(
|
|
||||||
{
|
|
||||||
appName,
|
|
||||||
appType: "stack",
|
|
||||||
serverId,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
enabled: !!appName && option === "native",
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (option === "native") {
|
|
||||||
if (containers && containers?.length > 0) {
|
|
||||||
setContainerId(containers[0]?.containerId);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (services && services?.length > 0) {
|
|
||||||
setContainerId(services[0]?.containerId);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, [option, services, containers]);
|
|
||||||
|
|
||||||
const isLoading = option === "native" ? containersLoading : servicesLoading;
|
|
||||||
const containersLenght =
|
|
||||||
option === "native" ? containers?.length : services?.length;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Card className="bg-background">
|
|
||||||
<CardHeader>
|
|
||||||
<CardTitle className="text-xl">Logs</CardTitle>
|
|
||||||
<CardDescription>
|
|
||||||
Watch the logs of the application in real time
|
|
||||||
</CardDescription>
|
|
||||||
</CardHeader>
|
|
||||||
|
|
||||||
<CardContent className="flex flex-col gap-4">
|
|
||||||
<div className="flex flex-row justify-between items-center gap-2">
|
|
||||||
<Label>Select a container to view logs</Label>
|
|
||||||
<div className="flex flex-row gap-2 items-center">
|
|
||||||
<span className="text-sm text-muted-foreground">
|
|
||||||
{option === "native" ? "Native" : "Swarm"}
|
|
||||||
</span>
|
|
||||||
<Switch
|
|
||||||
checked={option === "native"}
|
|
||||||
onCheckedChange={(checked) => {
|
|
||||||
setOption(checked ? "native" : "swarm");
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<Select onValueChange={setContainerId} value={containerId}>
|
|
||||||
<SelectTrigger>
|
|
||||||
{isLoading ? (
|
|
||||||
<div className="flex flex-row gap-2 items-center justify-center text-sm text-muted-foreground">
|
|
||||||
<span>Loading...</span>
|
|
||||||
<Loader2 className="animate-spin size-4" />
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
<SelectValue placeholder="Select a container" />
|
|
||||||
)}
|
|
||||||
</SelectTrigger>
|
|
||||||
<SelectContent>
|
|
||||||
<SelectGroup>
|
|
||||||
{option === "native" ? (
|
|
||||||
<div>
|
|
||||||
{containers?.map((container) => (
|
|
||||||
<SelectItem
|
|
||||||
key={container.containerId}
|
|
||||||
value={container.containerId}
|
|
||||||
>
|
|
||||||
{container.name} ({container.containerId}){" "}
|
|
||||||
<Badge variant={badgeStateColor(container.state)}>
|
|
||||||
{container.state}
|
|
||||||
</Badge>
|
|
||||||
</SelectItem>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
<>
|
|
||||||
{services?.map((container) => (
|
|
||||||
<SelectItem
|
|
||||||
key={container.containerId}
|
|
||||||
value={container.containerId}
|
|
||||||
>
|
|
||||||
{container.name} ({container.containerId}@{container.node}
|
|
||||||
)
|
|
||||||
<Badge variant={badgeStateColor(container.state)}>
|
|
||||||
{container.state}
|
|
||||||
</Badge>
|
|
||||||
</SelectItem>
|
|
||||||
))}
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<SelectLabel>Containers ({containersLenght})</SelectLabel>
|
|
||||||
</SelectGroup>
|
|
||||||
</SelectContent>
|
|
||||||
</Select>
|
|
||||||
<DockerLogs
|
|
||||||
serverId={serverId || ""}
|
|
||||||
containerId={containerId || "select-a-container"}
|
|
||||||
runType={option}
|
|
||||||
/>
|
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@@ -1,5 +1,3 @@
|
|||||||
import { badgeStateColor } from "@/components/dashboard/application/logs/show";
|
|
||||||
import { Badge } from "@/components/ui/badge";
|
|
||||||
import {
|
import {
|
||||||
Card,
|
Card,
|
||||||
CardContent,
|
CardContent,
|
||||||
@@ -89,10 +87,7 @@ export const ShowDockerLogsCompose = ({
|
|||||||
key={container.containerId}
|
key={container.containerId}
|
||||||
value={container.containerId}
|
value={container.containerId}
|
||||||
>
|
>
|
||||||
{container.name} ({container.containerId}){" "}
|
{container.name} ({container.containerId}) {container.state}
|
||||||
<Badge variant={badgeStateColor(container.state)}>
|
|
||||||
{container.state}
|
|
||||||
</Badge>
|
|
||||||
</SelectItem>
|
</SelectItem>
|
||||||
))}
|
))}
|
||||||
<SelectLabel>Containers ({data?.length})</SelectLabel>
|
<SelectLabel>Containers ({data?.length})</SelectLabel>
|
||||||
@@ -102,7 +97,6 @@ export const ShowDockerLogsCompose = ({
|
|||||||
<DockerLogs
|
<DockerLogs
|
||||||
serverId={serverId || ""}
|
serverId={serverId || ""}
|
||||||
containerId={containerId || "select-a-container"}
|
containerId={containerId || "select-a-container"}
|
||||||
runType="native"
|
|
||||||
/>
|
/>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
|
|||||||
@@ -12,7 +12,6 @@ import { type LogLine, getLogType, parseLogs } from "./utils";
|
|||||||
interface Props {
|
interface Props {
|
||||||
containerId: string;
|
containerId: string;
|
||||||
serverId?: string | null;
|
serverId?: string | null;
|
||||||
runType: "swarm" | "native";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const priorities = [
|
export const priorities = [
|
||||||
@@ -38,11 +37,7 @@ export const priorities = [
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
export const DockerLogsId: React.FC<Props> = ({
|
export const DockerLogsId: React.FC<Props> = ({ containerId, serverId }) => {
|
||||||
containerId,
|
|
||||||
serverId,
|
|
||||||
runType,
|
|
||||||
}) => {
|
|
||||||
const { data } = api.docker.getConfig.useQuery(
|
const { data } = api.docker.getConfig.useQuery(
|
||||||
{
|
{
|
||||||
containerId,
|
containerId,
|
||||||
@@ -109,7 +104,6 @@ export const DockerLogsId: React.FC<Props> = ({
|
|||||||
tail: lines.toString(),
|
tail: lines.toString(),
|
||||||
since,
|
since,
|
||||||
search,
|
search,
|
||||||
runType,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
if (serverId) {
|
if (serverId) {
|
||||||
|
|||||||
@@ -46,11 +46,7 @@ export const ShowDockerModalLogs = ({
|
|||||||
<DialogDescription>View the logs for {containerId}</DialogDescription>
|
<DialogDescription>View the logs for {containerId}</DialogDescription>
|
||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
<div className="flex flex-col gap-4 pt-2.5">
|
<div className="flex flex-col gap-4 pt-2.5">
|
||||||
<DockerLogsId
|
<DockerLogsId containerId={containerId || ""} serverId={serverId} />
|
||||||
containerId={containerId || ""}
|
|
||||||
serverId={serverId}
|
|
||||||
runType="native"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
|
|||||||
@@ -1,58 +0,0 @@
|
|||||||
import {
|
|
||||||
Dialog,
|
|
||||||
DialogContent,
|
|
||||||
DialogDescription,
|
|
||||||
DialogHeader,
|
|
||||||
DialogTitle,
|
|
||||||
DialogTrigger,
|
|
||||||
} from "@/components/ui/dialog";
|
|
||||||
import { DropdownMenuItem } from "@/components/ui/dropdown-menu";
|
|
||||||
import dynamic from "next/dynamic";
|
|
||||||
import type React from "react";
|
|
||||||
export const DockerLogsId = dynamic(
|
|
||||||
() =>
|
|
||||||
import("@/components/dashboard/docker/logs/docker-logs-id").then(
|
|
||||||
(e) => e.DockerLogsId,
|
|
||||||
),
|
|
||||||
{
|
|
||||||
ssr: false,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
interface Props {
|
|
||||||
containerId: string;
|
|
||||||
children?: React.ReactNode;
|
|
||||||
serverId?: string | null;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const ShowDockerModalStackLogs = ({
|
|
||||||
containerId,
|
|
||||||
children,
|
|
||||||
serverId,
|
|
||||||
}: Props) => {
|
|
||||||
return (
|
|
||||||
<Dialog>
|
|
||||||
<DialogTrigger asChild>
|
|
||||||
<DropdownMenuItem
|
|
||||||
className="w-full cursor-pointer space-x-3"
|
|
||||||
onSelect={(e) => e.preventDefault()}
|
|
||||||
>
|
|
||||||
{children}
|
|
||||||
</DropdownMenuItem>
|
|
||||||
</DialogTrigger>
|
|
||||||
<DialogContent className="max-h-screen overflow-y-auto sm:max-w-7xl">
|
|
||||||
<DialogHeader>
|
|
||||||
<DialogTitle>View Logs</DialogTitle>
|
|
||||||
<DialogDescription>View the logs for {containerId}</DialogDescription>
|
|
||||||
</DialogHeader>
|
|
||||||
<div className="flex flex-col gap-4 pt-2.5">
|
|
||||||
<DockerLogsId
|
|
||||||
containerId={containerId || ""}
|
|
||||||
serverId={serverId}
|
|
||||||
runType="swarm"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</DialogContent>
|
|
||||||
</Dialog>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@@ -18,7 +18,6 @@ import { DropdownMenuItem } from "@/components/ui/dropdown-menu";
|
|||||||
import {
|
import {
|
||||||
Form,
|
Form,
|
||||||
FormControl,
|
FormControl,
|
||||||
FormDescription,
|
|
||||||
FormField,
|
FormField,
|
||||||
FormItem,
|
FormItem,
|
||||||
FormLabel,
|
FormLabel,
|
||||||
@@ -36,7 +35,6 @@ import {
|
|||||||
SelectTrigger,
|
SelectTrigger,
|
||||||
SelectValue,
|
SelectValue,
|
||||||
} from "@/components/ui/select";
|
} from "@/components/ui/select";
|
||||||
import { Switch } from "@/components/ui/switch";
|
|
||||||
import { Textarea } from "@/components/ui/textarea";
|
import { Textarea } from "@/components/ui/textarea";
|
||||||
import { slugify } from "@/lib/slug";
|
import { slugify } from "@/lib/slug";
|
||||||
import { api } from "@/utils/api";
|
import { api } from "@/utils/api";
|
||||||
@@ -97,7 +95,6 @@ const mySchema = z.discriminatedUnion("type", [
|
|||||||
.object({
|
.object({
|
||||||
type: z.literal("mongo"),
|
type: z.literal("mongo"),
|
||||||
databaseUser: z.string().default("mongo"),
|
databaseUser: z.string().default("mongo"),
|
||||||
replicaSets: z.boolean().default(false),
|
|
||||||
})
|
})
|
||||||
.merge(baseDatabaseSchema),
|
.merge(baseDatabaseSchema),
|
||||||
z
|
z
|
||||||
@@ -219,7 +216,6 @@ export const AddDatabase = ({ projectId, projectName }: Props) => {
|
|||||||
databaseUser:
|
databaseUser:
|
||||||
data.databaseUser || databasesUserDefaultPlaceholder[data.type],
|
data.databaseUser || databasesUserDefaultPlaceholder[data.type],
|
||||||
serverId: data.serverId,
|
serverId: data.serverId,
|
||||||
replicaSets: data.replicaSets,
|
|
||||||
});
|
});
|
||||||
} else if (data.type === "redis") {
|
} else if (data.type === "redis") {
|
||||||
promise = redisMutation.mutateAsync({
|
promise = redisMutation.mutateAsync({
|
||||||
@@ -546,30 +542,6 @@ export const AddDatabase = ({ projectId, projectName }: Props) => {
|
|||||||
);
|
);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{type === "mongo" && (
|
|
||||||
<FormField
|
|
||||||
control={form.control}
|
|
||||||
name="replicaSets"
|
|
||||||
render={({ field }) => {
|
|
||||||
return (
|
|
||||||
<FormItem className="flex flex-row items-center justify-between p-3 mt-4 border rounded-lg shadow-sm">
|
|
||||||
<div className="space-y-0.5">
|
|
||||||
<FormLabel>Use Replica Sets</FormLabel>
|
|
||||||
</div>
|
|
||||||
<FormControl>
|
|
||||||
<Switch
|
|
||||||
checked={field.value}
|
|
||||||
onCheckedChange={field.onChange}
|
|
||||||
/>
|
|
||||||
</FormControl>
|
|
||||||
|
|
||||||
<FormMessage />
|
|
||||||
</FormItem>
|
|
||||||
);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|||||||
@@ -91,11 +91,7 @@ export const ShowModalLogs = ({ appName, children, serverId }: Props) => {
|
|||||||
</SelectGroup>
|
</SelectGroup>
|
||||||
</SelectContent>
|
</SelectContent>
|
||||||
</Select>
|
</Select>
|
||||||
<DockerLogsId
|
<DockerLogsId containerId={containerId || ""} serverId={serverId} />
|
||||||
containerId={containerId || ""}
|
|
||||||
serverId={serverId}
|
|
||||||
runType="native"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
|
|||||||
@@ -11,7 +11,11 @@ import {
|
|||||||
} from "@/components/ui/dropdown-menu";
|
} from "@/components/ui/dropdown-menu";
|
||||||
|
|
||||||
import { Badge } from "@/components/ui/badge";
|
import { Badge } from "@/components/ui/badge";
|
||||||
import { ShowDockerModalStackLogs } from "../../docker/logs/show-docker-modal-stack-logs";
|
import { ShowNodeConfig } from "../details/show-node-config";
|
||||||
|
// import { ShowContainerConfig } from "../config/show-container-config";
|
||||||
|
// import { ShowDockerModalLogs } from "../logs/show-docker-modal-logs";
|
||||||
|
// import { DockerTerminalModal } from "../terminal/docker-terminal-modal";
|
||||||
|
// import type { Container } from "./show-containers";
|
||||||
|
|
||||||
export interface ApplicationList {
|
export interface ApplicationList {
|
||||||
ID: string;
|
ID: string;
|
||||||
@@ -24,7 +28,6 @@ export interface ApplicationList {
|
|||||||
DesiredState: string;
|
DesiredState: string;
|
||||||
Error: string;
|
Error: string;
|
||||||
Node: string;
|
Node: string;
|
||||||
serverId: string;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const columns: ColumnDef<ApplicationList>[] = [
|
export const columns: ColumnDef<ApplicationList>[] = [
|
||||||
@@ -209,37 +212,7 @@ export const columns: ColumnDef<ApplicationList>[] = [
|
|||||||
);
|
);
|
||||||
},
|
},
|
||||||
cell: ({ row }) => {
|
cell: ({ row }) => {
|
||||||
return <div className="w-[10rem]">{row.getValue("Errors")}</div>;
|
return <div>{row.getValue("Errors")}</div>;
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
accessorKey: "Logs",
|
|
||||||
accessorFn: (row) => row.Error,
|
|
||||||
header: ({ column }) => {
|
|
||||||
return <span>Logs</span>;
|
|
||||||
},
|
|
||||||
cell: ({ row }) => {
|
|
||||||
return (
|
|
||||||
<span className="w-[10rem]">
|
|
||||||
<DropdownMenu>
|
|
||||||
<DropdownMenuTrigger asChild>
|
|
||||||
<Button variant="ghost" className="h-8 w-8 p-0">
|
|
||||||
<span className="sr-only">Open menu</span>
|
|
||||||
<MoreHorizontal className="h-4 w-4" />
|
|
||||||
</Button>
|
|
||||||
</DropdownMenuTrigger>
|
|
||||||
<DropdownMenuContent align="end">
|
|
||||||
<DropdownMenuLabel>Actions</DropdownMenuLabel>
|
|
||||||
<ShowDockerModalStackLogs
|
|
||||||
containerId={row.original.ID}
|
|
||||||
serverId={row.original.serverId}
|
|
||||||
>
|
|
||||||
View Logs
|
|
||||||
</ShowDockerModalStackLogs>
|
|
||||||
</DropdownMenuContent>
|
|
||||||
</DropdownMenu>
|
|
||||||
</span>
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -10,13 +10,26 @@ import {
|
|||||||
import { api } from "@/utils/api";
|
import { api } from "@/utils/api";
|
||||||
import { Layers, Loader2 } from "lucide-react";
|
import { Layers, Loader2 } from "lucide-react";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { type ApplicationList, columns } from "./columns";
|
import { columns } from "./columns";
|
||||||
import { DataTable } from "./data-table";
|
import { DataTable } from "./data-table";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
serverId?: string;
|
serverId?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface ApplicationList {
|
||||||
|
ID: string;
|
||||||
|
Image: string;
|
||||||
|
Mode: string;
|
||||||
|
Name: string;
|
||||||
|
Ports: string;
|
||||||
|
Replicas: string;
|
||||||
|
CurrentState: string;
|
||||||
|
DesiredState: string;
|
||||||
|
Error: string;
|
||||||
|
Node: string;
|
||||||
|
}
|
||||||
|
|
||||||
export const ShowNodeApplications = ({ serverId }: Props) => {
|
export const ShowNodeApplications = ({ serverId }: Props) => {
|
||||||
const { data: NodeApps, isLoading: NodeAppsLoading } =
|
const { data: NodeApps, isLoading: NodeAppsLoading } =
|
||||||
api.swarm.getNodeApps.useQuery({ serverId });
|
api.swarm.getNodeApps.useQuery({ serverId });
|
||||||
@@ -76,7 +89,6 @@ export const ShowNodeApplications = ({ serverId }: Props) => {
|
|||||||
Error: detail.Error,
|
Error: detail.Error,
|
||||||
Node: detail.Node,
|
Node: detail.Node,
|
||||||
Ports: detail.Ports || app.Ports,
|
Ports: detail.Ports || app.Ports,
|
||||||
serverId: serverId || "",
|
|
||||||
}));
|
}));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -1 +0,0 @@
|
|||||||
ALTER TABLE "mongo" ADD COLUMN "replicaSets" boolean DEFAULT false;
|
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -372,13 +372,6 @@
|
|||||||
"when": 1734809337308,
|
"when": 1734809337308,
|
||||||
"tag": "0052_bumpy_luckman",
|
"tag": "0052_bumpy_luckman",
|
||||||
"breakpoints": true
|
"breakpoints": true
|
||||||
},
|
|
||||||
{
|
|
||||||
"idx": 53,
|
|
||||||
"version": "6",
|
|
||||||
"when": 1735118844878,
|
|
||||||
"tag": "0053_broken_kulan_gath",
|
|
||||||
"breakpoints": true
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@@ -13,8 +13,6 @@ export enum Languages {
|
|||||||
Portuguese = "pt-br",
|
Portuguese = "pt-br",
|
||||||
Italian = "it",
|
Italian = "it",
|
||||||
Japanese = "ja",
|
Japanese = "ja",
|
||||||
Spanish = "es",
|
|
||||||
Norwegian = "no",
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export type Language = keyof typeof Languages;
|
export type Language = keyof typeof Languages;
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "dokploy",
|
"name": "dokploy",
|
||||||
"version": "v0.16.0",
|
"version": "v0.15.1",
|
||||||
"private": true,
|
"private": true,
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ import { ShowDomainsCompose } from "@/components/dashboard/compose/domains/show-
|
|||||||
import { ShowEnvironmentCompose } from "@/components/dashboard/compose/enviroment/show";
|
import { ShowEnvironmentCompose } from "@/components/dashboard/compose/enviroment/show";
|
||||||
import { ShowGeneralCompose } from "@/components/dashboard/compose/general/show";
|
import { ShowGeneralCompose } from "@/components/dashboard/compose/general/show";
|
||||||
import { ShowDockerLogsCompose } from "@/components/dashboard/compose/logs/show";
|
import { ShowDockerLogsCompose } from "@/components/dashboard/compose/logs/show";
|
||||||
import { ShowDockerLogsStack } from "@/components/dashboard/compose/logs/show-stack";
|
|
||||||
import { ShowMonitoringCompose } from "@/components/dashboard/compose/monitoring/show";
|
import { ShowMonitoringCompose } from "@/components/dashboard/compose/monitoring/show";
|
||||||
import { UpdateCompose } from "@/components/dashboard/compose/update-compose";
|
import { UpdateCompose } from "@/components/dashboard/compose/update-compose";
|
||||||
import { ProjectLayout } from "@/components/layouts/project-layout";
|
import { ProjectLayout } from "@/components/layouts/project-layout";
|
||||||
@@ -252,18 +251,11 @@ const Service = (
|
|||||||
|
|
||||||
<TabsContent value="logs">
|
<TabsContent value="logs">
|
||||||
<div className="flex flex-col gap-4 pt-2.5">
|
<div className="flex flex-col gap-4 pt-2.5">
|
||||||
{data?.composeType === "docker-compose" ? (
|
|
||||||
<ShowDockerLogsCompose
|
<ShowDockerLogsCompose
|
||||||
serverId={data?.serverId || ""}
|
serverId={data?.serverId || ""}
|
||||||
appName={data?.appName || ""}
|
appName={data?.appName || ""}
|
||||||
appType={data?.composeType || "docker-compose"}
|
appType={data?.composeType || "docker-compose"}
|
||||||
/>
|
/>
|
||||||
) : (
|
|
||||||
<ShowDockerLogsStack
|
|
||||||
serverId={data?.serverId || ""}
|
|
||||||
appName={data?.appName || ""}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
</TabsContent>
|
</TabsContent>
|
||||||
|
|
||||||
|
|||||||
@@ -1 +0,0 @@
|
|||||||
{}
|
|
||||||
@@ -1,52 +0,0 @@
|
|||||||
{
|
|
||||||
"settings.common.save": "Guardar",
|
|
||||||
"settings.server.domain.title": "Dominio del Servidor",
|
|
||||||
"settings.server.domain.description": "Añade un dominio a tu aplicación de servidor.",
|
|
||||||
"settings.server.domain.form.domain": "Dominio",
|
|
||||||
"settings.server.domain.form.letsEncryptEmail": "Correo de Let's Encrypt",
|
|
||||||
"settings.server.domain.form.certificate.label": "Proveedor de Certificado",
|
|
||||||
"settings.server.domain.form.certificate.placeholder": "Selecciona un certificado",
|
|
||||||
"settings.server.domain.form.certificateOptions.none": "Ninguno",
|
|
||||||
"settings.server.domain.form.certificateOptions.letsencrypt": "Let's Encrypt",
|
|
||||||
|
|
||||||
"settings.server.webServer.title": "Servidor Web",
|
|
||||||
"settings.server.webServer.description": "Recarga o limpia el servidor web.",
|
|
||||||
"settings.server.webServer.actions": "Acciones",
|
|
||||||
"settings.server.webServer.reload": "Recargar",
|
|
||||||
"settings.server.webServer.watchLogs": "Ver registros",
|
|
||||||
"settings.server.webServer.updateServerIp": "Actualizar IP del Servidor",
|
|
||||||
"settings.server.webServer.server.label": "Servidor",
|
|
||||||
"settings.server.webServer.traefik.label": "Traefik",
|
|
||||||
"settings.server.webServer.traefik.modifyEnv": "Modificar Entorno",
|
|
||||||
"settings.server.webServer.traefik.managePorts": "Asignación Adicional de Puertos",
|
|
||||||
"settings.server.webServer.traefik.managePortsDescription": "Añadir o eliminar puertos adicionales para Traefik",
|
|
||||||
"settings.server.webServer.traefik.targetPort": "Puerto de Destino",
|
|
||||||
"settings.server.webServer.traefik.publishedPort": "Puerto Publicado",
|
|
||||||
"settings.server.webServer.traefik.addPort": "Añadir Puerto",
|
|
||||||
"settings.server.webServer.traefik.portsUpdated": "Puertos actualizados correctamente",
|
|
||||||
"settings.server.webServer.traefik.portsUpdateError": "Error al actualizar los puertos",
|
|
||||||
"settings.server.webServer.traefik.publishMode": "Modo de Publicación",
|
|
||||||
"settings.server.webServer.storage.label": "Espacio",
|
|
||||||
"settings.server.webServer.storage.cleanUnusedImages": "Limpiar imágenes no utilizadas",
|
|
||||||
"settings.server.webServer.storage.cleanUnusedVolumes": "Limpiar volúmenes no utilizados",
|
|
||||||
"settings.server.webServer.storage.cleanStoppedContainers": "Limpiar contenedores detenidos",
|
|
||||||
"settings.server.webServer.storage.cleanDockerBuilder": "Limpiar Constructor de Docker y Sistema",
|
|
||||||
"settings.server.webServer.storage.cleanMonitoring": "Limpiar Monitoreo",
|
|
||||||
"settings.server.webServer.storage.cleanAll": "Limpiar todo",
|
|
||||||
|
|
||||||
"settings.profile.title": "Cuenta",
|
|
||||||
"settings.profile.description": "Cambia los detalles de tu perfil aquí.",
|
|
||||||
"settings.profile.email": "Correo electrónico",
|
|
||||||
"settings.profile.password": "Contraseña",
|
|
||||||
"settings.profile.avatar": "Avatar",
|
|
||||||
|
|
||||||
"settings.appearance.title": "Apariencia",
|
|
||||||
"settings.appearance.description": "Personaliza el tema de tu panel.",
|
|
||||||
"settings.appearance.theme": "Tema",
|
|
||||||
"settings.appearance.themeDescription": "Selecciona un tema para tu panel",
|
|
||||||
"settings.appearance.themes.light": "Claro",
|
|
||||||
"settings.appearance.themes.dark": "Oscuro",
|
|
||||||
"settings.appearance.themes.system": "Sistema",
|
|
||||||
"settings.appearance.language": "Idioma",
|
|
||||||
"settings.appearance.languageDescription": "Selecciona un idioma para tu panel"
|
|
||||||
}
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
{}
|
|
||||||
@@ -1,52 +0,0 @@
|
|||||||
{
|
|
||||||
"settings.common.save": "Lagre",
|
|
||||||
"settings.server.domain.title": "Serverdomene",
|
|
||||||
"settings.server.domain.description": "Legg til et domene i serverapplikasjonen din.",
|
|
||||||
"settings.server.domain.form.domain": "Domene",
|
|
||||||
"settings.server.domain.form.letsEncryptEmail": "Let's Encrypt Epost",
|
|
||||||
"settings.server.domain.form.certificate.label": "Sertifikatleverandør",
|
|
||||||
"settings.server.domain.form.certificate.placeholder": "Velg et sertifikat",
|
|
||||||
"settings.server.domain.form.certificateOptions.none": "Ingen",
|
|
||||||
"settings.server.domain.form.certificateOptions.letsencrypt": "Let's Encrypt",
|
|
||||||
|
|
||||||
"settings.server.webServer.title": "Webserver",
|
|
||||||
"settings.server.webServer.description": "Last på nytt eller rens webserveren.",
|
|
||||||
"settings.server.webServer.actions": "Handlinger",
|
|
||||||
"settings.server.webServer.reload": "Last på nytt",
|
|
||||||
"settings.server.webServer.watchLogs": "Se logger",
|
|
||||||
"settings.server.webServer.updateServerIp": "Oppdater server-IP",
|
|
||||||
"settings.server.webServer.server.label": "Server",
|
|
||||||
"settings.server.webServer.traefik.label": "Traefik",
|
|
||||||
"settings.server.webServer.traefik.modifyEnv": "Endre miljø",
|
|
||||||
"settings.server.webServer.traefik.managePorts": "Ytterligere portkartlegginger",
|
|
||||||
"settings.server.webServer.traefik.managePortsDescription": "Legg til eller fjern flere porter for Traefik",
|
|
||||||
"settings.server.webServer.traefik.targetPort": "Målport",
|
|
||||||
"settings.server.webServer.traefik.publishedPort": "Publisert port",
|
|
||||||
"settings.server.webServer.traefik.addPort": "Legg til port",
|
|
||||||
"settings.server.webServer.traefik.portsUpdated": "Portene ble oppdatert",
|
|
||||||
"settings.server.webServer.traefik.portsUpdateError": "Kunne ikke oppdatere portene",
|
|
||||||
"settings.server.webServer.traefik.publishMode": "Publiseringsmodus",
|
|
||||||
"settings.server.webServer.storage.label": "Lagring",
|
|
||||||
"settings.server.webServer.storage.cleanUnusedImages": "Rens ubrukte bilder",
|
|
||||||
"settings.server.webServer.storage.cleanUnusedVolumes": "Rens ubrukte volumer",
|
|
||||||
"settings.server.webServer.storage.cleanStoppedContainers": "Rens stoppete containere",
|
|
||||||
"settings.server.webServer.storage.cleanDockerBuilder": "Rens Docker Builder og System",
|
|
||||||
"settings.server.webServer.storage.cleanMonitoring": "Rens overvåking",
|
|
||||||
"settings.server.webServer.storage.cleanAll": "Rens alt",
|
|
||||||
|
|
||||||
"settings.profile.title": "Konto",
|
|
||||||
"settings.profile.description": "Endre detaljene for profilen din her.",
|
|
||||||
"settings.profile.email": "Epost",
|
|
||||||
"settings.profile.password": "Passord",
|
|
||||||
"settings.profile.avatar": "Avatar",
|
|
||||||
|
|
||||||
"settings.appearance.title": "Utseende",
|
|
||||||
"settings.appearance.description": "Tilpass temaet for dashbordet ditt.",
|
|
||||||
"settings.appearance.theme": "Tema",
|
|
||||||
"settings.appearance.themeDescription": "Velg et tema for dashbordet ditt",
|
|
||||||
"settings.appearance.themes.light": "Lys",
|
|
||||||
"settings.appearance.themes.dark": "Mørk",
|
|
||||||
"settings.appearance.themes.system": "System",
|
|
||||||
"settings.appearance.language": "Språk",
|
|
||||||
"settings.appearance.languageDescription": "Velg et språk for dashbordet ditt"
|
|
||||||
}
|
|
||||||
@@ -555,9 +555,9 @@ export const applicationRouter = createTRPCRouter({
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
await updateApplication(input.applicationId as string, {
|
updateApplication(input.applicationId as string, {
|
||||||
sourceType: "drop",
|
sourceType: "drop",
|
||||||
dropBuildPath: input.dropBuildPath || "",
|
dropBuildPath: input.dropBuildPath,
|
||||||
});
|
});
|
||||||
|
|
||||||
await unzipDrop(zipFile, app);
|
await unzipDrop(zipFile, app);
|
||||||
|
|||||||
@@ -4,8 +4,6 @@ import {
|
|||||||
getContainers,
|
getContainers,
|
||||||
getContainersByAppLabel,
|
getContainersByAppLabel,
|
||||||
getContainersByAppNameMatch,
|
getContainersByAppNameMatch,
|
||||||
getServiceContainersByAppName,
|
|
||||||
getStackContainersByAppName,
|
|
||||||
} from "@dokploy/server";
|
} from "@dokploy/server";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import { createTRPCRouter, protectedProcedure } from "../trpc";
|
import { createTRPCRouter, protectedProcedure } from "../trpc";
|
||||||
@@ -70,26 +68,4 @@ export const dockerRouter = createTRPCRouter({
|
|||||||
.query(async ({ input }) => {
|
.query(async ({ input }) => {
|
||||||
return await getContainersByAppLabel(input.appName, input.serverId);
|
return await getContainersByAppLabel(input.appName, input.serverId);
|
||||||
}),
|
}),
|
||||||
|
|
||||||
getStackContainersByAppName: protectedProcedure
|
|
||||||
.input(
|
|
||||||
z.object({
|
|
||||||
appName: z.string().min(1),
|
|
||||||
serverId: z.string().optional(),
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
.query(async ({ input }) => {
|
|
||||||
return await getStackContainersByAppName(input.appName, input.serverId);
|
|
||||||
}),
|
|
||||||
|
|
||||||
getServiceContainersByAppName: protectedProcedure
|
|
||||||
.input(
|
|
||||||
z.object({
|
|
||||||
appName: z.string().min(1),
|
|
||||||
serverId: z.string().optional(),
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
.query(async ({ input }) => {
|
|
||||||
return await getServiceContainersByAppName(input.appName, input.serverId);
|
|
||||||
}),
|
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -34,7 +34,6 @@ export const setupDockerContainerLogsWebSocketServer = (
|
|||||||
const search = url.searchParams.get("search");
|
const search = url.searchParams.get("search");
|
||||||
const since = url.searchParams.get("since");
|
const since = url.searchParams.get("since");
|
||||||
const serverId = url.searchParams.get("serverId");
|
const serverId = url.searchParams.get("serverId");
|
||||||
const runType = url.searchParams.get("runType");
|
|
||||||
const { user, session } = await validateWebSocketRequest(req);
|
const { user, session } = await validateWebSocketRequest(req);
|
||||||
|
|
||||||
if (!containerId) {
|
if (!containerId) {
|
||||||
@@ -54,9 +53,7 @@ export const setupDockerContainerLogsWebSocketServer = (
|
|||||||
const client = new Client();
|
const client = new Client();
|
||||||
client
|
client
|
||||||
.once("ready", () => {
|
.once("ready", () => {
|
||||||
const baseCommand = `docker ${runType === "swarm" ? "service" : "container"} logs --timestamps ${
|
const baseCommand = `docker container logs --timestamps --tail ${tail} ${
|
||||||
runType === "swarm" ? "--raw" : ""
|
|
||||||
} --tail ${tail} ${
|
|
||||||
since === "all" ? "" : `--since ${since}`
|
since === "all" ? "" : `--since ${since}`
|
||||||
} --follow ${containerId}`;
|
} --follow ${containerId}`;
|
||||||
const escapedSearch = search ? search.replace(/'/g, "'\\''") : "";
|
const escapedSearch = search ? search.replace(/'/g, "'\\''") : "";
|
||||||
@@ -100,9 +97,7 @@ export const setupDockerContainerLogsWebSocketServer = (
|
|||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
const shell = getShell();
|
const shell = getShell();
|
||||||
const baseCommand = `docker ${runType === "swarm" ? "service" : "container"} logs --timestamps ${
|
const baseCommand = `docker container logs --timestamps --tail ${tail} ${
|
||||||
runType === "swarm" ? "--raw" : ""
|
|
||||||
} --tail ${tail} ${
|
|
||||||
since === "all" ? "" : `--since ${since}`
|
since === "all" ? "" : `--since ${since}`
|
||||||
} --follow ${containerId}`;
|
} --follow ${containerId}`;
|
||||||
const command = search
|
const command = search
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { relations } from "drizzle-orm";
|
import { relations } from "drizzle-orm";
|
||||||
import { boolean, integer, pgTable, text } from "drizzle-orm/pg-core";
|
import { integer, pgTable, text } from "drizzle-orm/pg-core";
|
||||||
import { createInsertSchema } from "drizzle-zod";
|
import { createInsertSchema } from "drizzle-zod";
|
||||||
import { nanoid } from "nanoid";
|
import { nanoid } from "nanoid";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
@@ -43,7 +43,6 @@ export const mongo = pgTable("mongo", {
|
|||||||
serverId: text("serverId").references(() => server.serverId, {
|
serverId: text("serverId").references(() => server.serverId, {
|
||||||
onDelete: "cascade",
|
onDelete: "cascade",
|
||||||
}),
|
}),
|
||||||
replicaSets: boolean("replicaSets").default(false),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
export const mongoRelations = relations(mongo, ({ one, many }) => ({
|
export const mongoRelations = relations(mongo, ({ one, many }) => ({
|
||||||
@@ -78,7 +77,6 @@ const createSchema = createInsertSchema(mongo, {
|
|||||||
externalPort: z.number(),
|
externalPort: z.number(),
|
||||||
description: z.string().optional(),
|
description: z.string().optional(),
|
||||||
serverId: z.string().optional(),
|
serverId: z.string().optional(),
|
||||||
replicaSets: z.boolean().default(false),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
export const apiCreateMongo = createSchema
|
export const apiCreateMongo = createSchema
|
||||||
@@ -91,7 +89,6 @@ export const apiCreateMongo = createSchema
|
|||||||
databaseUser: true,
|
databaseUser: true,
|
||||||
databasePassword: true,
|
databasePassword: true,
|
||||||
serverId: true,
|
serverId: true,
|
||||||
replicaSets: true,
|
|
||||||
})
|
})
|
||||||
.required();
|
.required();
|
||||||
|
|
||||||
|
|||||||
@@ -157,124 +157,6 @@ export const getContainersByAppNameMatch = async (
|
|||||||
return [];
|
return [];
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getStackContainersByAppName = async (
|
|
||||||
appName: string,
|
|
||||||
serverId?: string,
|
|
||||||
) => {
|
|
||||||
try {
|
|
||||||
let result: string[] = [];
|
|
||||||
|
|
||||||
const command = `docker stack ps ${appName} --format 'CONTAINER ID : {{.ID}} | Name: {{.Name}} | State: {{.DesiredState}} | Node: {{.Node}}'`;
|
|
||||||
if (serverId) {
|
|
||||||
const { stdout, stderr } = await execAsyncRemote(serverId, command);
|
|
||||||
|
|
||||||
if (stderr) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!stdout) return [];
|
|
||||||
result = stdout.trim().split("\n");
|
|
||||||
} else {
|
|
||||||
const { stdout, stderr } = await execAsync(command);
|
|
||||||
|
|
||||||
if (stderr) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!stdout) return [];
|
|
||||||
|
|
||||||
result = stdout.trim().split("\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
const containers = result.map((line) => {
|
|
||||||
const parts = line.split(" | ");
|
|
||||||
const containerId = parts[0]
|
|
||||||
? parts[0].replace("CONTAINER ID : ", "").trim()
|
|
||||||
: "No container id";
|
|
||||||
const name = parts[1]
|
|
||||||
? parts[1].replace("Name: ", "").trim()
|
|
||||||
: "No container name";
|
|
||||||
|
|
||||||
const state = parts[2]
|
|
||||||
? parts[2].replace("State: ", "").trim().toLowerCase()
|
|
||||||
: "No state";
|
|
||||||
const node = parts[3]
|
|
||||||
? parts[3].replace("Node: ", "").trim()
|
|
||||||
: "No specific node";
|
|
||||||
return {
|
|
||||||
containerId,
|
|
||||||
name,
|
|
||||||
state,
|
|
||||||
node,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
return containers || [];
|
|
||||||
} catch (error) {}
|
|
||||||
|
|
||||||
return [];
|
|
||||||
};
|
|
||||||
|
|
||||||
export const getServiceContainersByAppName = async (
|
|
||||||
appName: string,
|
|
||||||
serverId?: string,
|
|
||||||
) => {
|
|
||||||
try {
|
|
||||||
let result: string[] = [];
|
|
||||||
|
|
||||||
const command = `docker service ps ${appName} --format 'CONTAINER ID : {{.ID}} | Name: {{.Name}} | State: {{.DesiredState}} | Node: {{.Node}}'`;
|
|
||||||
|
|
||||||
if (serverId) {
|
|
||||||
const { stdout, stderr } = await execAsyncRemote(serverId, command);
|
|
||||||
|
|
||||||
if (stderr) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!stdout) return [];
|
|
||||||
result = stdout.trim().split("\n");
|
|
||||||
} else {
|
|
||||||
const { stdout, stderr } = await execAsync(command);
|
|
||||||
|
|
||||||
if (stderr) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!stdout) return [];
|
|
||||||
|
|
||||||
result = stdout.trim().split("\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
const containers = result.map((line) => {
|
|
||||||
const parts = line.split(" | ");
|
|
||||||
const containerId = parts[0]
|
|
||||||
? parts[0].replace("CONTAINER ID : ", "").trim()
|
|
||||||
: "No container id";
|
|
||||||
const name = parts[1]
|
|
||||||
? parts[1].replace("Name: ", "").trim()
|
|
||||||
: "No container name";
|
|
||||||
|
|
||||||
const state = parts[2]
|
|
||||||
? parts[2].replace("State: ", "").trim().toLowerCase()
|
|
||||||
: "No state";
|
|
||||||
|
|
||||||
const node = parts[3]
|
|
||||||
? parts[3].replace("Node: ", "").trim()
|
|
||||||
: "No specific node";
|
|
||||||
return {
|
|
||||||
containerId,
|
|
||||||
name,
|
|
||||||
state,
|
|
||||||
node,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
return containers || [];
|
|
||||||
} catch (error) {}
|
|
||||||
|
|
||||||
return [];
|
|
||||||
};
|
|
||||||
|
|
||||||
export const getContainersByAppLabel = async (
|
export const getContainersByAppLabel = async (
|
||||||
appName: string,
|
appName: string,
|
||||||
serverId?: string,
|
serverId?: string,
|
||||||
@@ -364,6 +246,8 @@ export const getSwarmNodes = async (serverId?: string) => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const nodes = JSON.parse(stdout);
|
||||||
|
|
||||||
const nodesArray = stdout
|
const nodesArray = stdout
|
||||||
.trim()
|
.trim()
|
||||||
.split("\n")
|
.split("\n")
|
||||||
@@ -423,8 +307,7 @@ export const getNodeApplications = async (serverId?: string) => {
|
|||||||
const appArray = stdout
|
const appArray = stdout
|
||||||
.trim()
|
.trim()
|
||||||
.split("\n")
|
.split("\n")
|
||||||
.map((line) => JSON.parse(line))
|
.map((line) => JSON.parse(line));
|
||||||
.filter((service) => !service.Name.startsWith("dokploy-"));
|
|
||||||
|
|
||||||
return appArray;
|
return appArray;
|
||||||
} catch (error) {}
|
} catch (error) {}
|
||||||
@@ -437,7 +320,7 @@ export const getApplicationInfo = async (
|
|||||||
try {
|
try {
|
||||||
let stdout = "";
|
let stdout = "";
|
||||||
let stderr = "";
|
let stderr = "";
|
||||||
const command = `docker service ps ${appName} --format '{{json .}}' --no-trunc`;
|
const command = `docker service ps ${appName} --format '{{json .}}'`;
|
||||||
|
|
||||||
if (serverId) {
|
if (serverId) {
|
||||||
const result = await execAsyncRemote(serverId, command);
|
const result = await execAsyncRemote(serverId, command);
|
||||||
|
|||||||
@@ -68,9 +68,9 @@ export const initializeTraefik = async ({
|
|||||||
Replicas: 1,
|
Replicas: 1,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Labels: {
|
// Labels: {
|
||||||
"traefik.enable": "true",
|
// "traefik.enable": "true",
|
||||||
},
|
// },
|
||||||
EndpointSpec: {
|
EndpointSpec: {
|
||||||
Ports: [
|
Ports: [
|
||||||
{
|
{
|
||||||
@@ -172,7 +172,7 @@ export const createDefaultServerTraefikConfig = () => {
|
|||||||
writeFileSync(
|
writeFileSync(
|
||||||
path.join(DYNAMIC_TRAEFIK_PATH, `${appName}.yml`),
|
path.join(DYNAMIC_TRAEFIK_PATH, `${appName}.yml`),
|
||||||
yamlStr,
|
yamlStr,
|
||||||
"utf8",
|
"utf8"
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -27,9 +27,7 @@ export const unzipDrop = async (zipFile: File, application: Application) => {
|
|||||||
const buffer = Buffer.from(arrayBuffer);
|
const buffer = Buffer.from(arrayBuffer);
|
||||||
|
|
||||||
const zip = new AdmZip(buffer);
|
const zip = new AdmZip(buffer);
|
||||||
const zipEntries = zip
|
const zipEntries = zip.getEntries();
|
||||||
.getEntries()
|
|
||||||
.filter((entry) => !entry.entryName.startsWith("__MACOSX"));
|
|
||||||
|
|
||||||
const rootEntries = zipEntries.filter(
|
const rootEntries = zipEntries.filter(
|
||||||
(entry) =>
|
(entry) =>
|
||||||
@@ -61,22 +59,14 @@ export const unzipDrop = async (zipFile: File, application: Application) => {
|
|||||||
|
|
||||||
if (!filePath) continue;
|
if (!filePath) continue;
|
||||||
|
|
||||||
const fullPath = path.join(outputPath, filePath).replace(/\\/g, "/");
|
const fullPath = path.join(outputPath, filePath);
|
||||||
|
|
||||||
if (application.serverId) {
|
if (application.serverId) {
|
||||||
if (!entry.isDirectory) {
|
if (entry.isDirectory) {
|
||||||
|
await execAsyncRemote(application.serverId, `mkdir -p ${fullPath}`);
|
||||||
|
} else {
|
||||||
if (sftp === null) throw new Error("No SFTP connection available");
|
if (sftp === null) throw new Error("No SFTP connection available");
|
||||||
try {
|
|
||||||
const dirPath = path.dirname(fullPath);
|
|
||||||
await execAsyncRemote(
|
|
||||||
application.serverId,
|
|
||||||
`mkdir -p "${dirPath}"`,
|
|
||||||
);
|
|
||||||
await uploadFileToServer(sftp, entry.getData(), fullPath);
|
await uploadFileToServer(sftp, entry.getData(), fullPath);
|
||||||
} catch (err) {
|
|
||||||
console.error(`Error uploading file ${fullPath}:`, err);
|
|
||||||
throw err;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (entry.isDirectory) {
|
if (entry.isDirectory) {
|
||||||
@@ -113,6 +103,7 @@ const getSFTPConnection = async (serverId: string): Promise<SFTPWrapper> => {
|
|||||||
port: server.port,
|
port: server.port,
|
||||||
username: server.username,
|
username: server.username,
|
||||||
privateKey: server.sshKey?.privateKey,
|
privateKey: server.sshKey?.privateKey,
|
||||||
|
timeout: 99999,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
@@ -124,10 +115,7 @@ const uploadFileToServer = (
|
|||||||
): Promise<void> => {
|
): Promise<void> => {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
sftp.writeFile(remotePath, data, (err) => {
|
sftp.writeFile(remotePath, data, (err) => {
|
||||||
if (err) {
|
if (err) return reject(err);
|
||||||
console.error(`SFTP write error for ${remotePath}:`, err);
|
|
||||||
return reject(err);
|
|
||||||
}
|
|
||||||
resolve();
|
resolve();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -28,66 +28,17 @@ export const buildMongo = async (mongo: MongoNested) => {
|
|||||||
databasePassword,
|
databasePassword,
|
||||||
command,
|
command,
|
||||||
mounts,
|
mounts,
|
||||||
replicaSets,
|
|
||||||
} = mongo;
|
} = mongo;
|
||||||
|
|
||||||
const startupScript = `
|
const defaultMongoEnv = `MONGO_INITDB_ROOT_USERNAME=${databaseUser}\nMONGO_INITDB_ROOT_PASSWORD=${databasePassword}${
|
||||||
#!/bin/bash
|
|
||||||
${
|
|
||||||
replicaSets
|
|
||||||
? `
|
|
||||||
mongod --port 27017 --replSet rs0 --bind_ip_all &
|
|
||||||
MONGOD_PID=$!
|
|
||||||
|
|
||||||
# Wait for MongoDB to be ready
|
|
||||||
while ! mongosh --eval "db.adminCommand('ping')" > /dev/null 2>&1; do
|
|
||||||
sleep 2
|
|
||||||
done
|
|
||||||
|
|
||||||
# Check if replica set is already initialized
|
|
||||||
REPLICA_STATUS=$(mongosh --quiet --eval "rs.status().ok || 0")
|
|
||||||
|
|
||||||
if [ "$REPLICA_STATUS" != "1" ]; then
|
|
||||||
echo "Initializing replica set..."
|
|
||||||
mongosh --eval '
|
|
||||||
rs.initiate({
|
|
||||||
_id: "rs0",
|
|
||||||
members: [{ _id: 0, host: "localhost:27017", priority: 1 }]
|
|
||||||
});
|
|
||||||
|
|
||||||
// Wait for the replica set to initialize
|
|
||||||
while (!rs.isMaster().ismaster) {
|
|
||||||
sleep(1000);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create root user after replica set is initialized and we are primary
|
|
||||||
db.getSiblingDB("admin").createUser({
|
|
||||||
user: "${databaseUser}",
|
|
||||||
pwd: "${databasePassword}",
|
|
||||||
roles: ["root"]
|
|
||||||
});
|
|
||||||
'
|
|
||||||
|
|
||||||
else
|
|
||||||
echo "Replica set already initialized."
|
|
||||||
fi
|
|
||||||
`
|
|
||||||
: ""
|
|
||||||
}
|
|
||||||
|
|
||||||
${command ?? "wait $MONGOD_PID"}`;
|
|
||||||
|
|
||||||
const defaultMongoEnv = `MONGO_INITDB_ROOT_USERNAME=${databaseUser}\nMONGO_INITDB_ROOT_PASSWORD=${databasePassword}${replicaSets ? "\nMONGO_INITDB_DATABASE=admin" : ""}${
|
|
||||||
env ? `\n${env}` : ""
|
env ? `\n${env}` : ""
|
||||||
}`;
|
}`;
|
||||||
|
|
||||||
const resources = calculateResources({
|
const resources = calculateResources({
|
||||||
memoryLimit,
|
memoryLimit,
|
||||||
memoryReservation,
|
memoryReservation,
|
||||||
cpuLimit,
|
cpuLimit,
|
||||||
cpuReservation,
|
cpuReservation,
|
||||||
});
|
});
|
||||||
|
|
||||||
const envVariables = prepareEnvironmentVariables(
|
const envVariables = prepareEnvironmentVariables(
|
||||||
defaultMongoEnv,
|
defaultMongoEnv,
|
||||||
mongo.project.env,
|
mongo.project.env,
|
||||||
@@ -105,17 +56,12 @@ ${command ?? "wait $MONGOD_PID"}`;
|
|||||||
Image: dockerImage,
|
Image: dockerImage,
|
||||||
Env: envVariables,
|
Env: envVariables,
|
||||||
Mounts: [...volumesMount, ...bindsMount, ...filesMount],
|
Mounts: [...volumesMount, ...bindsMount, ...filesMount],
|
||||||
...(replicaSets
|
...(command
|
||||||
? {
|
? {
|
||||||
Command: ["/bin/bash"],
|
Command: ["/bin/sh"],
|
||||||
Args: ["-c", startupScript],
|
|
||||||
}
|
|
||||||
: {
|
|
||||||
...(command && {
|
|
||||||
Command: ["/bin/bash"],
|
|
||||||
Args: ["-c", command],
|
Args: ["-c", command],
|
||||||
}),
|
}
|
||||||
}),
|
: {}),
|
||||||
},
|
},
|
||||||
Networks: [{ Target: "dokploy-network" }],
|
Networks: [{ Target: "dokploy-network" }],
|
||||||
Resources: {
|
Resources: {
|
||||||
@@ -144,7 +90,6 @@ ${command ?? "wait $MONGOD_PID"}`;
|
|||||||
: [],
|
: [],
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const service = docker.getService(appName);
|
const service = docker.getService(appName);
|
||||||
const inspect = await service.inspect();
|
const inspect = await service.inspect();
|
||||||
|
|||||||
Reference in New Issue
Block a user