diff --git a/.vscode/extensions.json b/.vscode/extensions.json new file mode 100644 index 000000000..16e8e6664 --- /dev/null +++ b/.vscode/extensions.json @@ -0,0 +1,3 @@ +{ + "recommendations": ["biomejs.biome"] +} diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 000000000..99357f236 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,8 @@ +{ + "editor.formatOnSave": true, + "editor.defaultFormatter": "biomejs.biome", + "editor.codeActionsOnSave": { + "source.fixAll.biome": "explicit", + "source.organizeImports.biome": "explicit" + } +} diff --git a/apps/dokploy/components/dashboard/application/advanced/security/handle-security.tsx b/apps/dokploy/components/dashboard/application/advanced/security/handle-security.tsx index 1808f7873..2fc50c7c7 100644 --- a/apps/dokploy/components/dashboard/application/advanced/security/handle-security.tsx +++ b/apps/dokploy/components/dashboard/application/advanced/security/handle-security.tsx @@ -151,7 +151,7 @@ export const HandleSecurity = ({ Password - + diff --git a/apps/dokploy/components/dashboard/application/advanced/security/show-security.tsx b/apps/dokploy/components/dashboard/application/advanced/security/show-security.tsx index 92439f511..552a186a1 100644 --- a/apps/dokploy/components/dashboard/application/advanced/security/show-security.tsx +++ b/apps/dokploy/components/dashboard/application/advanced/security/show-security.tsx @@ -7,6 +7,9 @@ import { CardHeader, CardTitle, } from "@/components/ui/card"; +import { Label } from "@/components/ui/label"; +import { ToggleVisibilityInput } from "@/components/shared/toggle-visibility-input"; +import { Input } from "@/components/ui/input"; import { api } from "@/utils/api"; import { LockKeyhole, Trash2 } from "lucide-react"; import { toast } from "sonner"; @@ -58,19 +61,18 @@ export const ShowSecurity = ({ applicationId }: Props) => {
{data?.security.map((security) => (
-
-
-
- Username - - {security.username} - +
+
+
+ +
-
- Password - - {security.password} - +
+ +
diff --git a/apps/dokploy/components/dashboard/project/add-application.tsx b/apps/dokploy/components/dashboard/project/add-application.tsx index 9ed31464a..137f75a51 100644 --- a/apps/dokploy/components/dashboard/project/add-application.tsx +++ b/apps/dokploy/components/dashboard/project/add-application.tsx @@ -1,3 +1,9 @@ +import { zodResolver } from "@hookform/resolvers/zod"; +import { Folder, HelpCircle } from "lucide-react"; +import { useState } from "react"; +import { useForm } from "react-hook-form"; +import { toast } from "sonner"; +import { z } from "zod"; import { AlertBlock } from "@/components/shared/alert-block"; import { Button } from "@/components/ui/button"; import { @@ -37,12 +43,6 @@ import { } from "@/components/ui/tooltip"; import { slugify } from "@/lib/slug"; import { api } from "@/utils/api"; -import { zodResolver } from "@hookform/resolvers/zod"; -import { Folder, HelpCircle } from "lucide-react"; -import { useState } from "react"; -import { useForm } from "react-hook-form"; -import { toast } from "sonner"; -import { z } from "zod"; const AddTemplateSchema = z.object({ name: z.string().min(1, { @@ -75,6 +75,8 @@ export const AddApplication = ({ projectId, projectName }: Props) => { const slug = slugify(projectName); const { data: servers } = api.server.withSSHKey.useQuery(); + const hasServers = servers && servers.length > 0; + const { mutateAsync, isLoading, error, isError } = api.application.create.useMutation(); @@ -155,68 +157,84 @@ export const AddApplication = ({ projectId, projectName }: Props) => { )} /> - ( - - - - - - Select a Server {!isCloud ? "(Optional)" : ""} - - - - - - If no server is selected, the application will be - deployed on the server where the user is logged in. - - - - + {hasServers && ( + ( + + + + + + Select a Server {!isCloud ? "(Optional)" : ""} + + + + + + If no server is selected, the application will be + deployed on the server where the user is logged in. + + + + - + + + + + + {servers?.map((server) => ( + + + {server.name} + + {server.ipAddress} + - - - ))} - Servers ({servers?.length}) - - - - - - )} - /> + + ))} + Servers ({servers?.length}) + + + + + + )} + /> + )} ( - App Name + + App Name + + + + + + +

+ This will be the name of the Docker Swarm service +

+
+
+
+
diff --git a/apps/dokploy/components/dashboard/project/add-compose.tsx b/apps/dokploy/components/dashboard/project/add-compose.tsx index a60bfdd70..c32e55c16 100644 --- a/apps/dokploy/components/dashboard/project/add-compose.tsx +++ b/apps/dokploy/components/dashboard/project/add-compose.tsx @@ -1,3 +1,9 @@ +import { zodResolver } from "@hookform/resolvers/zod"; +import { CircuitBoard, HelpCircle } from "lucide-react"; +import { useEffect, useState } from "react"; +import { useForm } from "react-hook-form"; +import { toast } from "sonner"; +import { z } from "zod"; import { AlertBlock } from "@/components/shared/alert-block"; import { Button } from "@/components/ui/button"; import { @@ -37,12 +43,6 @@ import { } from "@/components/ui/tooltip"; import { slugify } from "@/lib/slug"; import { api } from "@/utils/api"; -import { zodResolver } from "@hookform/resolvers/zod"; -import { CircuitBoard, HelpCircle } from "lucide-react"; -import { useEffect, useState } from "react"; -import { useForm } from "react-hook-form"; -import { toast } from "sonner"; -import { z } from "zod"; const AddComposeSchema = z.object({ composeType: z.enum(["docker-compose", "stack"]).optional(), @@ -78,6 +78,8 @@ export const AddCompose = ({ projectId, projectName }: Props) => { const { mutateAsync, isLoading, error, isError } = api.compose.create.useMutation(); + const hasServers = servers && servers.length > 0; + const form = useForm({ defaultValues: { name: "", @@ -163,62 +165,64 @@ export const AddCompose = ({ projectId, projectName }: Props) => { )} />
- ( - - - - - - Select a Server {!isCloud ? "(Optional)" : ""} - - - - - - If no server is selected, the application will be - deployed on the server where the user is logged in. - - - - + {hasServers && ( + ( + + + + + + Select a Server {!isCloud ? "(Optional)" : ""} + + + + + + If no server is selected, the application will be + deployed on the server where the user is logged in. + + + + - + + + + + + {servers?.map((server) => ( + + + {server.name} + + {server.ipAddress} + - - - ))} - Servers ({servers?.length}) - - - - - - )} - /> + + ))} + Servers ({servers?.length}) + + + + + + )} + /> + )} { const mariadbMutation = api.mariadb.create.useMutation(); const mysqlMutation = api.mysql.create.useMutation(); + const hasServers = servers && servers.length > 0; + const form = useForm({ defaultValues: { type: "postgres", @@ -374,45 +382,62 @@ export const AddDatabase = ({ projectId, projectName }: Props) => { )} /> - ( - - Select a Server - - - - )} - /> + {hasServers && ( + ( + + Select a Server + + + + )} + /> + )} ( - App Name + + App Name + + + + + + +

+ This will be the name of the Docker Swarm + service +

+
+
+
+
diff --git a/apps/dokploy/components/dashboard/project/add-template.tsx b/apps/dokploy/components/dashboard/project/add-template.tsx index 53ae30141..5eb994fc4 100644 --- a/apps/dokploy/components/dashboard/project/add-template.tsx +++ b/apps/dokploy/components/dashboard/project/add-template.tsx @@ -1,3 +1,18 @@ +import { + BookText, + CheckIcon, + ChevronsUpDown, + Globe, + HelpCircle, + LayoutGrid, + List, + Loader2, + PuzzleIcon, + SearchIcon, +} from "lucide-react"; +import Link from "next/link"; +import { useEffect, useState } from "react"; +import { toast } from "sonner"; import { GithubIcon } from "@/components/icons/data-tools-icons"; import { AlertBlock } from "@/components/shared/alert-block"; import { @@ -54,21 +69,6 @@ import { } from "@/components/ui/tooltip"; import { cn } from "@/lib/utils"; import { api } from "@/utils/api"; -import { - BookText, - CheckIcon, - ChevronsUpDown, - Globe, - HelpCircle, - LayoutGrid, - List, - Loader2, - PuzzleIcon, - SearchIcon, -} from "lucide-react"; -import Link from "next/link"; -import { useEffect, useState } from "react"; -import { toast } from "sonner"; const TEMPLATE_BASE_URL_KEY = "dokploy_template_base_url"; @@ -137,6 +137,8 @@ export const AddTemplate = ({ projectId, baseUrl }: Props) => { return matchesTags && matchesQuery; }) || []; + const hasServers = servers && servers.length > 0; + return ( @@ -425,60 +427,62 @@ export const AddTemplate = ({ projectId, baseUrl }: Props) => { project. -
- - - - - - - - If no server is selected, the application - will be deployed on the server where the - user is logged in. - - - - + {hasServers && ( +
+ + + + + + + + If no server is selected, the + application will be deployed on the + server where the user is logged in. + + + + - { + setServerId(e); + }} + > + + + + + + {servers?.map((server) => ( + + + {server.name} + + {server.ipAddress} + - - - ))} - - Servers ({servers?.length}) - - - - -
+ + ))} + + Servers ({servers?.length}) + + + + +
+ )} Cancel diff --git a/apps/dokploy/components/dashboard/project/ai/step-one.tsx b/apps/dokploy/components/dashboard/project/ai/step-one.tsx index e2a6795fe..29cf90305 100644 --- a/apps/dokploy/components/dashboard/project/ai/step-one.tsx +++ b/apps/dokploy/components/dashboard/project/ai/step-one.tsx @@ -25,6 +25,7 @@ const examples = [ export const StepOne = ({ setTemplateInfo, templateInfo }: any) => { // Get servers from the API const { data: servers } = api.server.withSSHKey.useQuery(); + const hasServers = servers && servers.length > 0; const handleExampleClick = (example: string) => { setTemplateInfo({ ...templateInfo, userInput: example }); @@ -47,37 +48,39 @@ export const StepOne = ({ setTemplateInfo, templateInfo }: any) => { />
-
- - -
+ {hasServers && ( +
+ + +
+ )}
diff --git a/apps/dokploy/components/dashboard/project/ai/step-two.tsx b/apps/dokploy/components/dashboard/project/ai/step-two.tsx index 7b4deb30e..fc64638f1 100644 --- a/apps/dokploy/components/dashboard/project/ai/step-two.tsx +++ b/apps/dokploy/components/dashboard/project/ai/step-two.tsx @@ -199,7 +199,7 @@ export const StepTwo = ({ templateInfo, setTemplateInfo }: StepProps) => {

Generating template suggestions based on your input...

-
{templateInfo.userInput}
+
{templateInfo.userInput}
); } diff --git a/apps/dokploy/components/dashboard/settings/git/github/add-github-provider.tsx b/apps/dokploy/components/dashboard/settings/git/github/add-github-provider.tsx index af7d58544..83a4c8b42 100644 --- a/apps/dokploy/components/dashboard/settings/git/github/add-github-provider.tsx +++ b/apps/dokploy/components/dashboard/settings/git/github/add-github-provider.tsx @@ -24,12 +24,14 @@ export const AddGithubProvider = () => { const [isOrganization, setIsOrganization] = useState(false); const [organizationName, setOrganization] = useState(""); + const randomString = () => Math.random().toString(36).slice(2, 8); + useEffect(() => { const url = document.location.origin; const manifest = JSON.stringify( { redirect_url: `${origin}/api/providers/github/setup?organizationId=${activeOrganization?.id}&userId=${session?.user?.id}`, - name: `Dokploy-${format(new Date(), "yyyy-MM-dd")}`, + name: `Dokploy-${format(new Date(), "yyyy-MM-dd")}-${randomString()}`, url: origin, hook_attributes: { url: `${url}/api/deploy/github`, diff --git a/apps/dokploy/components/dashboard/settings/git/show-git-providers.tsx b/apps/dokploy/components/dashboard/settings/git/show-git-providers.tsx index 05273ca2a..eda9d8afb 100644 --- a/apps/dokploy/components/dashboard/settings/git/show-git-providers.tsx +++ b/apps/dokploy/components/dashboard/settings/git/show-git-providers.tsx @@ -33,6 +33,7 @@ import { AddGithubProvider } from "./github/add-github-provider"; import { EditGithubProvider } from "./github/edit-github-provider"; import { AddGitlabProvider } from "./gitlab/add-gitlab-provider"; import { EditGitlabProvider } from "./gitlab/edit-gitlab-provider"; +import { Badge } from "@/components/ui/badge"; export const ShowGitProviders = () => { const { data, isLoading, refetch } = api.gitProvider.getAll.useQuery(); @@ -158,7 +159,13 @@ export const ShowGitProviders = () => {
{!haveGithubRequirements && isGithub && ( -
+
+ + Action Required + {
)} {!haveGitlabRequirements && isGitlab && ( -
+
+ + Action Required + { image: data?.user?.image || "", currentPassword: "", allowImpersonation: data?.user?.allowImpersonation || false, + name: data?.user?.name || "", }, resolver: zodResolver(profileSchema), }); @@ -97,6 +99,7 @@ export const ProfileForm = () => { image: data?.user?.image || "", currentPassword: form.getValues("currentPassword") || "", allowImpersonation: data?.user?.allowImpersonation, + name: data?.user?.name || "", }, { keepValues: true, @@ -119,6 +122,7 @@ export const ProfileForm = () => { image: values.image, currentPassword: values.currentPassword || undefined, allowImpersonation: values.allowImpersonation, + name: values.name || undefined, }) .then(async () => { await refetch(); @@ -128,6 +132,7 @@ export const ProfileForm = () => { password: "", image: values.image, currentPassword: "", + name: values.name || "", }); }) .catch(() => { @@ -167,6 +172,19 @@ export const ProfileForm = () => { className="grid gap-4" >
+ ( + + Name + + + + + + )} + /> {
{!canCreateMoreServers && ( - + You cannot create more servers,{" "} Please upgrade your plan diff --git a/apps/dokploy/components/dashboard/settings/servers/show-servers.tsx b/apps/dokploy/components/dashboard/settings/servers/show-servers.tsx index d6465cf09..191aab9ce 100644 --- a/apps/dokploy/components/dashboard/settings/servers/show-servers.tsx +++ b/apps/dokploy/components/dashboard/settings/servers/show-servers.tsx @@ -1,3 +1,9 @@ +import { format } from "date-fns"; +import { KeyIcon, Loader2, MoreHorizontal, ServerIcon } from "lucide-react"; +import Link from "next/link"; +import { useRouter } from "next/router"; +import { useTranslation } from "next-i18next"; +import { toast } from "sonner"; import { AlertBlock } from "@/components/shared/alert-block"; import { DialogAction } from "@/components/shared/dialog-action"; import { Badge } from "@/components/ui/badge"; @@ -27,12 +33,6 @@ import { TableRow, } from "@/components/ui/table"; import { api } from "@/utils/api"; -import { format } from "date-fns"; -import { KeyIcon, Loader2, MoreHorizontal, ServerIcon } from "lucide-react"; -import { useTranslation } from "next-i18next"; -import Link from "next/link"; -import { useRouter } from "next/router"; -import { toast } from "sonner"; import { ShowNodesModal } from "../cluster/nodes/show-nodes-modal"; import { TerminalModal } from "../web-server/terminal-modal"; import { ShowServerActions } from "./actions/show-server-actions"; @@ -115,24 +115,6 @@ export const ShowServers = () => {
) : (
- {!canCreateMoreServers && ( - -
- -
- You cannot create more servers,{" "} - - Please upgrade your plan - -
-
-
-
- )} -
diff --git a/apps/dokploy/components/dashboard/settings/servers/welcome-stripe/create-server.tsx b/apps/dokploy/components/dashboard/settings/servers/welcome-stripe/create-server.tsx index 3a112af20..b895f385b 100644 --- a/apps/dokploy/components/dashboard/settings/servers/welcome-stripe/create-server.tsx +++ b/apps/dokploy/components/dashboard/settings/servers/welcome-stripe/create-server.tsx @@ -1,3 +1,9 @@ +import { zodResolver } from "@hookform/resolvers/zod"; +import Link from "next/link"; +import { useEffect, useState } from "react"; +import { useForm } from "react-hook-form"; +import { toast } from "sonner"; +import { z } from "zod"; import { AlertBlock } from "@/components/shared/alert-block"; import { Button } from "@/components/ui/button"; import { Card, CardContent } from "@/components/ui/card"; @@ -22,12 +28,6 @@ import { } from "@/components/ui/select"; import { Textarea } from "@/components/ui/textarea"; import { api } from "@/utils/api"; -import { zodResolver } from "@hookform/resolvers/zod"; -import Link from "next/link"; -import { useEffect, useState } from "react"; -import { useForm } from "react-hook-form"; -import { toast } from "sonner"; -import { z } from "zod"; const Schema = z.object({ name: z.string().min(1, { @@ -108,7 +108,7 @@ export const CreateServer = ({ stepper }: Props) => {
{!canCreateMoreServers && ( - + You cannot create more servers,{" "} Please upgrade your plan diff --git a/apps/dokploy/components/dashboard/settings/servers/welcome-stripe/create-ssh-key.tsx b/apps/dokploy/components/dashboard/settings/servers/welcome-stripe/create-ssh-key.tsx index d7ab5e329..ad386cc49 100644 --- a/apps/dokploy/components/dashboard/settings/servers/welcome-stripe/create-ssh-key.tsx +++ b/apps/dokploy/components/dashboard/settings/servers/welcome-stripe/create-ssh-key.tsx @@ -1,18 +1,22 @@ +import copy from "copy-to-clipboard"; +import { CopyIcon, ExternalLinkIcon, Loader2 } from "lucide-react"; +import Link from "next/link"; +import { useEffect, useRef, useState } from "react"; +import { toast } from "sonner"; import { CodeEditor } from "@/components/shared/code-editor"; import { Card, CardContent } from "@/components/ui/card"; +import { Label } from "@/components/ui/label"; +import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group"; import { api } from "@/utils/api"; -import copy from "copy-to-clipboard"; -import { ExternalLinkIcon, Loader2 } from "lucide-react"; -import { CopyIcon } from "lucide-react"; -import Link from "next/link"; -import { useEffect, useRef } from "react"; -import { toast } from "sonner"; export const CreateSSHKey = () => { const { data, refetch } = api.sshKey.all.useQuery(); const generateMutation = api.sshKey.generate.useMutation(); const { mutateAsync, isLoading } = api.sshKey.create.useMutation(); const hasCreatedKey = useRef(false); + const [selectedOption, setSelectedOption] = useState<"manual" | "provider">( + "manual", + ); const cloudSSHKey = data?.find( (sshKey) => sshKey.name === "dokploy-cloud-ssh-key", @@ -60,89 +64,122 @@ export const CreateSSHKey = () => {
) : ( <> -
+

- You have two options to add SSH Keys to your server: + Choose how to add SSH Keys to your server:

-
    -
  • 1. Add The SSH Key to Server Manually
  • + {/* Radio button options */} +
    + { + setSelectedOption(value as "manual" | "provider"); + }} + className="grid gap-3" + > +
    + + +
    -
  • - 2. Add the public SSH Key when you create a server in your - preffered provider (Hostinger, Digital Ocean, Hetzner, etc){" "} -
  • -
- -
- - Option 1 - -
    -
  • - 1. Login to your server{" "} -
  • -
  • - 2. When you are logged in run the following command -
    - > ~/.ssh/authorized_keys`} - readOnly - className="font-mono opacity-60" - /> - -
    -
  • -
  • - 3. You're done, follow the next step to insert the details - of your server. -
  • -
+
+ + +
+
-
- - Option 2 - -
-
-
- Copy Public Key - + + {/* Content based on selected option */} + {selectedOption === "manual" && ( +
+ + Manual Setup Instructions + +
    +
  • + 1. Login to your server +
  • +
  • + 2. When you are logged in run the following command +
    + > ~/.ssh/authorized_keys`} + readOnly + className="font-mono opacity-60" + /> + +
    +
  • +
  • + 3. You're done, follow the next step to insert the + details of your server. +
  • +
+
+ )} + + {selectedOption === "provider" && ( +
+ + Provider Setup Instructions + +
+
+
+ Copy Public Key + +
+

+ Use this public key when creating a server in your + preferred provider (Hostinger, Digital Ocean, Hetzner, + etc.) +

+ + View Tutorial +
- - View Tutorial - -
+ )}
)} diff --git a/apps/dokploy/components/dashboard/swarm/applications/show-applications.tsx b/apps/dokploy/components/dashboard/swarm/applications/show-applications.tsx index 626e2a282..6734991f1 100644 --- a/apps/dokploy/components/dashboard/swarm/applications/show-applications.tsx +++ b/apps/dokploy/components/dashboard/swarm/applications/show-applications.tsx @@ -1,3 +1,4 @@ +import { Layers, Loader2 } from "lucide-react"; import { Button } from "@/components/ui/button"; import { Dialog, @@ -8,7 +9,6 @@ import { DialogTrigger, } from "@/components/ui/dialog"; import { api } from "@/utils/api"; -import { Layers, Loader2 } from "lucide-react"; import { type ApplicationList, columns } from "./columns"; import { DataTable } from "./data-table"; @@ -20,10 +20,10 @@ export const ShowNodeApplications = ({ serverId }: Props) => { const { data: NodeApps, isLoading: NodeAppsLoading } = api.swarm.getNodeApps.useQuery({ serverId }); - let applicationList = ""; + let applicationList: string[] = []; if (NodeApps && NodeApps.length > 0) { - applicationList = NodeApps.map((app) => app.Name).join(" "); + applicationList = NodeApps.map((app) => app.Name); } const { data: NodeAppDetails, isLoading: NodeAppDetailsLoading } = diff --git a/apps/dokploy/package.json b/apps/dokploy/package.json index ec1219500..2418644be 100644 --- a/apps/dokploy/package.json +++ b/apps/dokploy/package.json @@ -1,6 +1,6 @@ { "name": "dokploy", - "version": "v0.24.4", + "version": "v0.24.5", "private": true, "license": "Apache-2.0", "type": "module", diff --git a/apps/dokploy/server/api/routers/swarm.ts b/apps/dokploy/server/api/routers/swarm.ts index 997eba310..1c3abdf37 100644 --- a/apps/dokploy/server/api/routers/swarm.ts +++ b/apps/dokploy/server/api/routers/swarm.ts @@ -1,10 +1,10 @@ import { + findServerById, getApplicationInfo, getNodeApplications, getNodeInfo, getSwarmNodes, } from "@dokploy/server"; -import { findServerById } from "@dokploy/server"; import { TRPCError } from "@trpc/server"; import { z } from "zod"; import { createTRPCRouter, protectedProcedure } from "../trpc"; @@ -55,7 +55,12 @@ export const swarmRouter = createTRPCRouter({ getAppInfos: protectedProcedure .input( z.object({ - appName: z.string().min(1).regex(containerIdRegex, "Invalid app name."), + appName: z + .string() + .min(1) + .regex(containerIdRegex, "Invalid app name.") + .array() + .min(1), serverId: z.string().optional(), }), ) diff --git a/biome.json b/biome.json index 3309d3a86..a26024c86 100644 --- a/biome.json +++ b/biome.json @@ -19,7 +19,8 @@ }, "complexity": { "noUselessCatch": "off", - "noBannedTypes": "off" + "noBannedTypes": "off", + "noUselessFragments": "off" }, "correctness": { "useExhaustiveDependencies": "off", diff --git a/packages/server/src/db/schema/user.ts b/packages/server/src/db/schema/user.ts index d1620ef16..e7fb4cbf4 100644 --- a/packages/server/src/db/schema/user.ts +++ b/packages/server/src/db/schema/user.ts @@ -323,6 +323,7 @@ export const apiUpdateWebServerMonitoring = z.object({ export const apiUpdateUser = createSchema.partial().extend({ password: z.string().optional(), currentPassword: z.string().optional(), + name: z.string().optional(), metricsConfig: z .object({ server: z.object({ diff --git a/packages/server/src/services/application.ts b/packages/server/src/services/application.ts index 179820b8f..a8effba98 100644 --- a/packages/server/src/services/application.ts +++ b/packages/server/src/services/application.ts @@ -237,6 +237,7 @@ export const deployApplication = async ({ } catch (error) { await updateDeploymentStatus(deployment.deploymentId, "error"); await updateApplicationStatus(applicationId, "error"); + await sendBuildErrorNotifications({ projectName: application.project.name, applicationName: application.name, @@ -370,8 +371,9 @@ export const deployRemoteApplication = async ({ domains: application.domains, }); } catch (error) { - // @ts-ignore - const encodedContent = encodeBase64(error?.message); + const errorMessage = error instanceof Error ? error.message : String(error); + + const encodedContent = encodeBase64(errorMessage); await execAsyncRemote( application.serverId, @@ -383,12 +385,12 @@ export const deployRemoteApplication = async ({ await updateDeploymentStatus(deployment.deploymentId, "error"); await updateApplicationStatus(applicationId, "error"); + await sendBuildErrorNotifications({ projectName: application.project.name, applicationName: application.name, applicationType: "application", - // @ts-ignore - errorMessage: error?.message || "Error building", + errorMessage: `Please check the logs for details: ${errorMessage}`, buildLink, organizationId: application.project.organizationId, }); diff --git a/packages/server/src/services/compose.ts b/packages/server/src/services/compose.ts index 8b3bd3b0f..bb1b2e8a0 100644 --- a/packages/server/src/services/compose.ts +++ b/packages/server/src/services/compose.ts @@ -1,8 +1,12 @@ import { join } from "node:path"; import { paths } from "@dokploy/server/constants"; import { db } from "@dokploy/server/db"; -import { type apiCreateCompose, compose } from "@dokploy/server/db/schema"; -import { buildAppName, cleanAppName } from "@dokploy/server/db/schema"; +import { + type apiCreateCompose, + buildAppName, + cleanAppName, + compose, +} from "@dokploy/server/db/schema"; import { buildCompose, getBuildComposeCommand, @@ -516,19 +520,20 @@ export const startCompose = async (composeId: string) => { const compose = await findComposeById(composeId); try { const { COMPOSE_PATH } = paths(!!compose.serverId); + + const projectPath = join(COMPOSE_PATH, compose.appName, "code"); + const path = + compose.sourceType === "raw" ? "docker-compose.yml" : compose.composePath; + const baseCommand = `docker compose -p ${compose.appName} -f ${path} up -d`; if (compose.composeType === "docker-compose") { if (compose.serverId) { await execAsyncRemote( compose.serverId, - `cd ${join( - COMPOSE_PATH, - compose.appName, - "code", - )} && docker compose -p ${compose.appName} up -d`, + `cd ${projectPath} && ${baseCommand}`, ); } else { - await execAsync(`docker compose -p ${compose.appName} up -d`, { - cwd: join(COMPOSE_PATH, compose.appName, "code"), + await execAsync(baseCommand, { + cwd: projectPath, }); } } diff --git a/packages/server/src/services/docker.ts b/packages/server/src/services/docker.ts index 2e315d008..2194c89c6 100644 --- a/packages/server/src/services/docker.ts +++ b/packages/server/src/services/docker.ts @@ -441,13 +441,13 @@ export const getNodeApplications = async (serverId?: string) => { }; export const getApplicationInfo = async ( - appName: string, + appNames: string[], serverId?: string, ) => { try { let stdout = ""; let stderr = ""; - const command = `docker service ps ${appName} --format '{{json .}}' --no-trunc`; + const command = `docker service ps ${appNames.join(" ")} --format '{{json .}}' --no-trunc`; if (serverId) { const result = await execAsyncRemote(serverId, command); diff --git a/packages/server/src/utils/notifications/build-error.ts b/packages/server/src/utils/notifications/build-error.ts index c873c9ab5..47fa4de77 100644 --- a/packages/server/src/utils/notifications/build-error.ts +++ b/packages/server/src/utils/notifications/build-error.ts @@ -65,6 +65,8 @@ export const sendBuildErrorNotifications = async ({ const decorate = (decoration: string, text: string) => `${discord.decoration ? decoration : ""} ${text}`.trim(); + const limitCharacter = 800; + const truncatedErrorMessage = errorMessage.substring(0, limitCharacter); await sendDiscordNotification(discord, { title: decorate(">", "`⚠️` Build Failed"), color: 0xed4245, @@ -101,7 +103,7 @@ export const sendBuildErrorNotifications = async ({ }, { name: decorate("`⚠️`", "Error Message"), - value: `\`\`\`${errorMessage}\`\`\``, + value: `\`\`\`${truncatedErrorMessage}\`\`\``, }, { name: decorate("`🧷`", "Build Link"), diff --git a/packages/server/src/utils/volume-backups/backup.ts b/packages/server/src/utils/volume-backups/backup.ts index 3b074f998..cc613ffa9 100644 --- a/packages/server/src/utils/volume-backups/backup.ts +++ b/packages/server/src/utils/volume-backups/backup.ts @@ -2,8 +2,7 @@ import path from "node:path"; import { paths } from "@dokploy/server/constants"; import { findComposeById } from "@dokploy/server/services/compose"; import type { findVolumeBackupById } from "@dokploy/server/services/volume-backups"; -import { normalizeS3Path } from "../backups/utils"; -import { getS3Credentials } from "../backups/utils"; +import { getS3Credentials, normalizeS3Path } from "../backups/utils"; export const backupVolume = async ( volumeBackup: Awaited>, @@ -37,6 +36,9 @@ export const backupVolume = async ( echo "Starting upload to S3..." ${rcloneCommand} echo "Upload to S3 done ✅" + echo "Cleaning up local backup file..." + rm "${volumeBackupPath}/${backupFileName}" + echo "Local backup file cleaned up ✅" `; if (!turnOff) {