diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index 7746a40c..23e8debb 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -1,6 +1,6 @@ name: Bug Report description: Create a bug report -labels: ["bug"] +labels: ["needs-triage🔍"] body: - type: markdown attributes: @@ -62,6 +62,7 @@ body: - "Docker" - "Remote server" - "Local Development" + - "Cloud Version" validations: required: true - type: dropdown diff --git a/.github/workflows/dokploy.yml b/.github/workflows/dokploy.yml index 9914811e..adcb1bb5 100644 --- a/.github/workflows/dokploy.yml +++ b/.github/workflows/dokploy.yml @@ -2,7 +2,7 @@ name: Dokploy Docker Build on: push: - branches: [main, canary, "feat/monitoring"] + branches: [main, canary, "feat/better-auth-2"] env: IMAGE_NAME: dokploy/dokploy diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 19ee38dc..8584fdf6 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -138,11 +138,18 @@ curl -sSL https://nixpacks.com/install.sh -o install.sh \ && ./install.sh ``` +```bash +# Install Railpack +curl -sSL https://railpack.com/install.sh | sh +``` + ```bash # Install Buildpacks curl -sSL "https://github.com/buildpacks/pack/releases/download/v0.32.1/pack-v0.32.1-linux.tgz" | tar -C /usr/local/bin/ --no-same-owner -xzv pack ``` + + ## Pull Request - The `main` branch is the source of truth and should always reflect the latest stable release. diff --git a/Dockerfile b/Dockerfile index 48bbb877..a5bd7e5e 100644 --- a/Dockerfile +++ b/Dockerfile @@ -55,6 +55,10 @@ RUN curl -sSL https://nixpacks.com/install.sh -o install.sh \ && ./install.sh \ && pnpm install -g tsx +# Install Railpack +ARG RAILPACK_VERSION=0.0.37 +RUN curl -sSL https://railpack.com/install.sh | bash + # Install buildpacks COPY --from=buildpacksio/pack:0.35.0 /usr/local/bin/pack /usr/local/bin/pack diff --git a/apps/api/src/index.ts b/apps/api/src/index.ts index 4b405e9c..0db56599 100644 --- a/apps/api/src/index.ts +++ b/apps/api/src/index.ts @@ -28,7 +28,7 @@ app.use(async (c, next) => { app.post("/deploy", zValidator("json", deployJobSchema), (c) => { const data = c.req.valid("json"); - const res = queue.add(data, { groupName: data.serverId }); + queue.add(data, { groupName: data.serverId }); return c.json( { message: "Deployment Added", diff --git a/apps/api/src/utils.ts b/apps/api/src/utils.ts index d919f29e..3f3c9698 100644 --- a/apps/api/src/utils.ts +++ b/apps/api/src/utils.ts @@ -64,7 +64,7 @@ export const deploy = async (job: DeployJob) => { } } } - } catch (error) { + } catch (_) { if (job.applicationType === "application") { await updateApplicationStatus(job.applicationId, "error"); } else if (job.applicationType === "compose") { diff --git a/apps/dokploy/__test__/compose/config/config.test.ts b/apps/dokploy/__test__/compose/config/config.test.ts index 3f98525a..aed3350f 100644 --- a/apps/dokploy/__test__/compose/config/config.test.ts +++ b/apps/dokploy/__test__/compose/config/config.test.ts @@ -1,5 +1,5 @@ import { generateRandomHash } from "@dokploy/server"; -import { addSuffixToAllConfigs, addSuffixToConfigsRoot } from "@dokploy/server"; +import { addSuffixToAllConfigs } from "@dokploy/server"; import type { ComposeSpecification } from "@dokploy/server"; import { load } from "js-yaml"; import { expect, test } from "vitest"; diff --git a/apps/dokploy/__test__/compose/domain/labels.test.ts b/apps/dokploy/__test__/compose/domain/labels.test.ts index 8bc9fbcc..c5f45810 100644 --- a/apps/dokploy/__test__/compose/domain/labels.test.ts +++ b/apps/dokploy/__test__/compose/domain/labels.test.ts @@ -9,6 +9,7 @@ describe("createDomainLabels", () => { port: 8080, https: false, uniqueConfigKey: 1, + customCertResolver: null, certificateType: "none", applicationId: "", composeId: "", diff --git a/apps/dokploy/__test__/compose/network/network-root.test.ts b/apps/dokploy/__test__/compose/network/network-root.test.ts index 7e06a9f0..980502ff 100644 --- a/apps/dokploy/__test__/compose/network/network-root.test.ts +++ b/apps/dokploy/__test__/compose/network/network-root.test.ts @@ -293,29 +293,6 @@ networks: dokploy-network: `; -const expectedComposeFile7 = ` -version: "3.8" - -services: - web: - image: nginx:latest - networks: - - dokploy-network - -networks: - dokploy-network: - driver: bridge - driver_opts: - com.docker.network.driver.mtu: 1200 - - backend: - driver: bridge - attachable: true - - external_network: - external: true - name: dokploy-network -`; test("It shoudn't add suffix to dokploy-network", () => { const composeData = load(composeFile7) as ComposeSpecification; diff --git a/apps/dokploy/__test__/compose/secrets/secret-root.test.ts b/apps/dokploy/__test__/compose/secrets/secret-root.test.ts index 2bd91b58..1b1898c5 100644 --- a/apps/dokploy/__test__/compose/secrets/secret-root.test.ts +++ b/apps/dokploy/__test__/compose/secrets/secret-root.test.ts @@ -1,7 +1,7 @@ import { generateRandomHash } from "@dokploy/server"; import { addSuffixToSecretsRoot } from "@dokploy/server"; import type { ComposeSpecification } from "@dokploy/server"; -import { dump, load } from "js-yaml"; +import { load } from "js-yaml"; import { expect, test } from "vitest"; test("Generate random hash with 8 characters", () => { diff --git a/apps/dokploy/__test__/compose/volume/volume.test.ts b/apps/dokploy/__test__/compose/volume/volume.test.ts index d4623aeb..6c434476 100644 --- a/apps/dokploy/__test__/compose/volume/volume.test.ts +++ b/apps/dokploy/__test__/compose/volume/volume.test.ts @@ -1,8 +1,4 @@ -import { generateRandomHash } from "@dokploy/server"; -import { - addSuffixToAllVolumes, - addSuffixToVolumesInServices, -} from "@dokploy/server"; +import { addSuffixToAllVolumes } from "@dokploy/server"; import type { ComposeSpecification } from "@dokploy/server"; import { load } from "js-yaml"; import { expect, test } from "vitest"; diff --git a/apps/dokploy/__test__/drop/drop.test.test.ts b/apps/dokploy/__test__/drop/drop.test.test.ts index c4b2ba8d..f5b68c1d 100644 --- a/apps/dokploy/__test__/drop/drop.test.test.ts +++ b/apps/dokploy/__test__/drop/drop.test.test.ts @@ -27,6 +27,7 @@ if (typeof window === "undefined") { const baseApp: ApplicationNested = { applicationId: "", herokuVersion: "", + watchPaths: [], applicationStatus: "done", appName: "", autoDeploy: true, @@ -37,6 +38,7 @@ const baseApp: ApplicationNested = { isPreviewDeploymentsActive: false, previewBuildArgs: null, previewCertificateType: "none", + previewCustomCertResolver: null, previewEnv: null, previewHttps: false, previewPath: "/", @@ -45,7 +47,7 @@ const baseApp: ApplicationNested = { previewWildcard: "", project: { env: "", - adminId: "", + organizationId: "", name: "", description: "", createdAt: "", diff --git a/apps/dokploy/__test__/traefik/server/update-server-config.test.ts b/apps/dokploy/__test__/traefik/server/update-server-config.test.ts index e38c19e4..f33b37fd 100644 --- a/apps/dokploy/__test__/traefik/server/update-server-config.test.ts +++ b/apps/dokploy/__test__/traefik/server/update-server-config.test.ts @@ -5,7 +5,7 @@ vi.mock("node:fs", () => ({ default: fs, })); -import type { Admin, FileConfig } from "@dokploy/server"; +import type { FileConfig, User } from "@dokploy/server"; import { createDefaultServerTraefikConfig, loadOrCreateConfig, @@ -13,7 +13,7 @@ import { } from "@dokploy/server"; import { beforeEach, expect, test, vi } from "vitest"; -const baseAdmin: Admin = { +const baseAdmin: User = { enablePaidFeatures: false, metricsConfig: { containers: { @@ -40,19 +40,30 @@ const baseAdmin: Admin = { cleanupCacheApplications: false, cleanupCacheOnCompose: false, cleanupCacheOnPreviews: false, - createdAt: "", - authId: "", - adminId: "string", + createdAt: new Date(), serverIp: null, certificateType: "none", host: null, letsEncryptEmail: null, sshPrivateKey: null, enableDockerCleanup: false, - enableLogRotation: false, + logCleanupCron: null, serversQuantity: 0, stripeCustomerId: "", stripeSubscriptionId: "", + banExpires: new Date(), + banned: true, + banReason: "", + email: "", + expirationDate: "", + id: "", + isRegistered: false, + name: "", + createdAt2: new Date().toISOString(), + emailVerified: false, + image: "", + updatedAt: new Date(), + twoFactorEnabled: false, }; beforeEach(() => { @@ -103,8 +114,6 @@ test("Should not touch config without host", () => { }); test("Should remove websecure if https rollback to http", () => { - const originalConfig: FileConfig = loadOrCreateConfig("dokploy"); - updateServerTraefik( { ...baseAdmin, certificateType: "letsencrypt" }, "example.com", diff --git a/apps/dokploy/__test__/traefik/traefik.test.ts b/apps/dokploy/__test__/traefik/traefik.test.ts index d05dda81..a64103ff 100644 --- a/apps/dokploy/__test__/traefik/traefik.test.ts +++ b/apps/dokploy/__test__/traefik/traefik.test.ts @@ -14,6 +14,7 @@ const baseApp: ApplicationNested = { branch: null, dockerBuildStage: "", registryUrl: "", + watchPaths: [], buildArgs: null, isPreviewDeploymentsActive: false, previewBuildArgs: null, @@ -23,10 +24,11 @@ const baseApp: ApplicationNested = { previewPath: "/", previewPort: 3000, previewLimit: 0, + previewCustomCertResolver: null, previewWildcard: "", project: { env: "", - adminId: "", + organizationId: "", name: "", description: "", createdAt: "", @@ -103,6 +105,7 @@ const baseDomain: Domain = { port: null, serviceName: "", composeId: "", + customCertResolver: null, domainType: "application", uniqueConfigKey: 1, previewDeploymentId: "", diff --git a/apps/dokploy/components/auth/login-2fa.tsx b/apps/dokploy/components/auth/login-2fa.tsx deleted file mode 100644 index 6a11268e..00000000 --- a/apps/dokploy/components/auth/login-2fa.tsx +++ /dev/null @@ -1,132 +0,0 @@ -import { Button } from "@/components/ui/button"; -import { - Form, - FormControl, - FormDescription, - FormField, - FormItem, - FormLabel, - FormMessage, -} from "@/components/ui/form"; - -import { CardTitle } from "@/components/ui/card"; -import { - InputOTP, - InputOTPGroup, - InputOTPSeparator, - InputOTPSlot, -} from "@/components/ui/input-otp"; -import { api } from "@/utils/api"; -import { zodResolver } from "@hookform/resolvers/zod"; -import { REGEXP_ONLY_DIGITS } from "input-otp"; -import { AlertTriangle } from "lucide-react"; -import { useRouter } from "next/router"; -import { useEffect } from "react"; -import { useForm } from "react-hook-form"; -import { toast } from "sonner"; -import { z } from "zod"; - -const Login2FASchema = z.object({ - pin: z.string().min(6, { - message: "Pin is required", - }), -}); - -type Login2FA = z.infer; - -interface Props { - authId: string; -} - -export const Login2FA = ({ authId }: Props) => { - const { push } = useRouter(); - - const { mutateAsync, isLoading, isError, error } = - api.auth.verifyLogin2FA.useMutation(); - - const form = useForm({ - defaultValues: { - pin: "", - }, - resolver: zodResolver(Login2FASchema), - }); - - useEffect(() => { - form.reset({ - pin: "", - }); - }, [form, form.reset, form.formState.isSubmitSuccessful]); - - const onSubmit = async (data: Login2FA) => { - await mutateAsync({ - pin: data.pin, - id: authId, - }) - .then(() => { - toast.success("Signin successfully", { - duration: 2000, - }); - - push("/dashboard/projects"); - }) - .catch(() => { - toast.error("Signin failed", { - duration: 2000, - }); - }); - }; - return ( -
- - {isError && ( -
- - - {error?.message} - -
- )} - 2FA Login - - ( - - Pin - -
- - - - - - - - - - -
-
- - Please enter the 6 digits code provided by your authenticator - app. - - -
- )} - /> - - - - ); -}; diff --git a/apps/dokploy/components/dashboard/application/advanced/cluster/modify-swarm-settings.tsx b/apps/dokploy/components/dashboard/application/advanced/cluster/modify-swarm-settings.tsx index 9b71a042..95a559f6 100644 --- a/apps/dokploy/components/dashboard/application/advanced/cluster/modify-swarm-settings.tsx +++ b/apps/dokploy/components/dashboard/application/advanced/cluster/modify-swarm-settings.tsx @@ -130,7 +130,7 @@ const createStringToJSONSchema = (schema: z.ZodTypeAny) => { } try { return JSON.parse(str); - } catch (e) { + } catch (_e) { ctx.addIssue({ code: "custom", message: "Invalid JSON format" }); return z.NEVER; } diff --git a/apps/dokploy/components/dashboard/application/advanced/cluster/show-cluster-settings.tsx b/apps/dokploy/components/dashboard/application/advanced/cluster/show-cluster-settings.tsx index cf7314cf..1eadf8ba 100644 --- a/apps/dokploy/components/dashboard/application/advanced/cluster/show-cluster-settings.tsx +++ b/apps/dokploy/components/dashboard/application/advanced/cluster/show-cluster-settings.tsx @@ -29,7 +29,6 @@ import { api } from "@/utils/api"; import { zodResolver } from "@hookform/resolvers/zod"; import { Server } from "lucide-react"; import Link from "next/link"; -import React from "react"; import { useEffect } from "react"; import { useForm } from "react-hook-form"; import { toast } from "sonner"; diff --git a/apps/dokploy/components/dashboard/application/advanced/general/add-command.tsx b/apps/dokploy/components/dashboard/application/advanced/general/add-command.tsx index 4cd839a1..50e36ad7 100644 --- a/apps/dokploy/components/dashboard/application/advanced/general/add-command.tsx +++ b/apps/dokploy/components/dashboard/application/advanced/general/add-command.tsx @@ -17,7 +17,6 @@ import { import { Input } from "@/components/ui/input"; import { api } from "@/utils/api"; import { zodResolver } from "@hookform/resolvers/zod"; -import React from "react"; import { useEffect } from "react"; import { useForm } from "react-hook-form"; import { toast } from "sonner"; diff --git a/apps/dokploy/components/dashboard/application/advanced/ports/show-port.tsx b/apps/dokploy/components/dashboard/application/advanced/ports/show-port.tsx index a2c6ddcf..4cd29a36 100644 --- a/apps/dokploy/components/dashboard/application/advanced/ports/show-port.tsx +++ b/apps/dokploy/components/dashboard/application/advanced/ports/show-port.tsx @@ -10,7 +10,6 @@ import { } from "@/components/ui/card"; import { api } from "@/utils/api"; import { Rss, Trash2 } from "lucide-react"; -import React from "react"; import { toast } from "sonner"; import { HandlePorts } from "./handle-ports"; interface Props { diff --git a/apps/dokploy/components/dashboard/application/advanced/redirects/show-redirects.tsx b/apps/dokploy/components/dashboard/application/advanced/redirects/show-redirects.tsx index 4ee59791..5c2c5943 100644 --- a/apps/dokploy/components/dashboard/application/advanced/redirects/show-redirects.tsx +++ b/apps/dokploy/components/dashboard/application/advanced/redirects/show-redirects.tsx @@ -9,7 +9,6 @@ import { } from "@/components/ui/card"; import { api } from "@/utils/api"; import { Split, Trash2 } from "lucide-react"; -import React from "react"; import { toast } from "sonner"; import { HandleRedirect } from "./handle-redirect"; 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 33022c09..92439f51 100644 --- a/apps/dokploy/components/dashboard/application/advanced/security/show-security.tsx +++ b/apps/dokploy/components/dashboard/application/advanced/security/show-security.tsx @@ -9,7 +9,6 @@ import { } from "@/components/ui/card"; import { api } from "@/utils/api"; import { LockKeyhole, Trash2 } from "lucide-react"; -import React from "react"; import { toast } from "sonner"; import { HandleSecurity } from "./handle-security"; diff --git a/apps/dokploy/components/dashboard/application/advanced/show-resources.tsx b/apps/dokploy/components/dashboard/application/advanced/show-resources.tsx index 227bca55..3d26716f 100644 --- a/apps/dokploy/components/dashboard/application/advanced/show-resources.tsx +++ b/apps/dokploy/components/dashboard/application/advanced/show-resources.tsx @@ -25,7 +25,7 @@ import { import { api } from "@/utils/api"; import { zodResolver } from "@hookform/resolvers/zod"; import { InfoIcon } from "lucide-react"; -import React, { useEffect } from "react"; +import { useEffect } from "react"; import { useForm } from "react-hook-form"; import { toast } from "sonner"; import { z } from "zod"; diff --git a/apps/dokploy/components/dashboard/application/advanced/traefik/show-traefik-config.tsx b/apps/dokploy/components/dashboard/application/advanced/traefik/show-traefik-config.tsx index fb6fc0c1..58601fb4 100644 --- a/apps/dokploy/components/dashboard/application/advanced/traefik/show-traefik-config.tsx +++ b/apps/dokploy/components/dashboard/application/advanced/traefik/show-traefik-config.tsx @@ -8,7 +8,6 @@ import { } from "@/components/ui/card"; import { api } from "@/utils/api"; import { File, Loader2 } from "lucide-react"; -import React from "react"; import { UpdateTraefikConfig } from "./update-traefik-config"; interface Props { applicationId: string; diff --git a/apps/dokploy/components/dashboard/application/advanced/volumes/show-volumes.tsx b/apps/dokploy/components/dashboard/application/advanced/volumes/show-volumes.tsx index e0f842ce..2a2d2c03 100644 --- a/apps/dokploy/components/dashboard/application/advanced/volumes/show-volumes.tsx +++ b/apps/dokploy/components/dashboard/application/advanced/volumes/show-volumes.tsx @@ -10,7 +10,6 @@ import { } from "@/components/ui/card"; import { api } from "@/utils/api"; import { Package, Trash2 } from "lucide-react"; -import React from "react"; import { toast } from "sonner"; import type { ServiceType } from "../show-resources"; import { AddVolumes } from "./add-volumes"; diff --git a/apps/dokploy/components/dashboard/application/advanced/volumes/update-volume.tsx b/apps/dokploy/components/dashboard/application/advanced/volumes/update-volume.tsx index d8481d65..8da09b58 100644 --- a/apps/dokploy/components/dashboard/application/advanced/volumes/update-volume.tsx +++ b/apps/dokploy/components/dashboard/application/advanced/volumes/update-volume.tsx @@ -21,7 +21,7 @@ import { import { Input } from "@/components/ui/input"; import { api } from "@/utils/api"; import { zodResolver } from "@hookform/resolvers/zod"; -import { PenBoxIcon, Pencil } from "lucide-react"; +import { PenBoxIcon } from "lucide-react"; import { useEffect, useState } from "react"; import { useForm } from "react-hook-form"; import { toast } from "sonner"; @@ -77,7 +77,7 @@ export const UpdateVolume = ({ serviceType, }: Props) => { const [isOpen, setIsOpen] = useState(false); - const utils = api.useUtils(); + const _utils = api.useUtils(); const { data } = api.mounts.one.useQuery( { mountId, diff --git a/apps/dokploy/components/dashboard/application/build/show.tsx b/apps/dokploy/components/dashboard/application/build/show.tsx index ad83f456..5c6e044c 100644 --- a/apps/dokploy/components/dashboard/application/build/show.tsx +++ b/apps/dokploy/components/dashboard/application/build/show.tsx @@ -1,3 +1,4 @@ +import { Badge } from "@/components/ui/badge"; import { Button } from "@/components/ui/button"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { @@ -25,6 +26,7 @@ enum BuildType { paketo_buildpacks = "paketo_buildpacks", nixpacks = "nixpacks", static = "static", + railpack = "railpack", } const mySchema = z.discriminatedUnion("buildType", [ @@ -53,6 +55,9 @@ const mySchema = z.discriminatedUnion("buildType", [ z.object({ buildType: z.literal("static"), }), + z.object({ + buildType: z.literal("railpack"), + }), ]); type AddTemplate = z.infer; @@ -173,6 +178,15 @@ export const ShowBuildChooseForm = ({ applicationId }: Props) => { Dockerfile + + + + + + Railpack{" "} + New + + diff --git a/apps/dokploy/components/dashboard/application/deployments/refresh-token.tsx b/apps/dokploy/components/dashboard/application/deployments/refresh-token.tsx index c268e6d5..b80450f9 100644 --- a/apps/dokploy/components/dashboard/application/deployments/refresh-token.tsx +++ b/apps/dokploy/components/dashboard/application/deployments/refresh-token.tsx @@ -11,7 +11,6 @@ import { } from "@/components/ui/alert-dialog"; import { api } from "@/utils/api"; import { RefreshCcw } from "lucide-react"; -import React from "react"; import { toast } from "sonner"; interface Props { diff --git a/apps/dokploy/components/dashboard/application/deployments/show-deployments.tsx b/apps/dokploy/components/dashboard/application/deployments/show-deployments.tsx index d33936f5..76e5bb26 100644 --- a/apps/dokploy/components/dashboard/application/deployments/show-deployments.tsx +++ b/apps/dokploy/components/dashboard/application/deployments/show-deployments.tsx @@ -73,15 +73,14 @@ export const ShowDeployments = ({ applicationId }: Props) => { ) : (
- {deployments?.map((deployment) => ( + {deployments?.map((deployment, index) => (
- {deployment.status} - + {index + 1}. {deployment.status} ({ resolver: zodResolver(domain), + defaultValues: { + host: "", + path: undefined, + port: undefined, + https: false, + certificateType: undefined, + customCertResolver: undefined, + }, + mode: "onChange", }); + const certificateType = form.watch("certificateType"); + const https = form.watch("https"); + useEffect(() => { if (data) { form.reset({ @@ -94,13 +106,29 @@ export const AddDomain = ({ /* Convert null to undefined */ path: data?.path || undefined, port: data?.port || undefined, + certificateType: data?.certificateType || undefined, + customCertResolver: data?.customCertResolver || undefined, }); } if (!domainId) { - form.reset({}); + form.reset({ + host: "", + path: undefined, + port: undefined, + https: false, + certificateType: undefined, + customCertResolver: undefined, + }); } - }, [form, form.reset, data, isLoading]); + }, [form, data, isLoading, domainId]); + + // Separate effect for handling custom cert resolver validation + useEffect(() => { + if (certificateType === "custom") { + form.trigger("customCertResolver"); + } + }, [certificateType, form]); const dictionary = { success: domainId ? "Domain Updated" : "Domain Created", @@ -256,34 +284,73 @@ export const AddDomain = ({ )} /> - {form.getValues().https && ( - ( - - Certificate Provider - { + field.onChange(value); + if (value !== "custom") { + form.setValue( + "customCertResolver", + undefined, + ); + } + }} + value={field.value} + > + + + + + + + None + + Let's Encrypt + + Custom + + + + + ); + }} + /> - - None - - Let's Encrypt - - - - - + {certificateType === "custom" && ( + { + return ( + + Custom Certificate Resolver + + { + field.onChange(e); + form.trigger("customCertResolver"); + }} + /> + + + + ); + }} + /> )} - /> + )}
diff --git a/apps/dokploy/components/dashboard/application/environment/show-enviroment.tsx b/apps/dokploy/components/dashboard/application/environment/show-enviroment.tsx index b65a1816..8a78c274 100644 --- a/apps/dokploy/components/dashboard/application/environment/show-enviroment.tsx +++ b/apps/dokploy/components/dashboard/application/environment/show-enviroment.tsx @@ -18,7 +18,7 @@ import { Toggle } from "@/components/ui/toggle"; import { api } from "@/utils/api"; import { zodResolver } from "@hookform/resolvers/zod"; import { EyeIcon, EyeOffIcon } from "lucide-react"; -import React, { type CSSProperties, useEffect, useState } from "react"; +import { type CSSProperties, useEffect, useState } from "react"; import { useForm } from "react-hook-form"; import { toast } from "sonner"; import { z } from "zod"; @@ -71,15 +71,19 @@ export const ShowEnvironment = ({ id, type }: Props) => { resolver: zodResolver(addEnvironmentSchema), }); + // Watch form value + const currentEnvironment = form.watch("environment"); + const hasChanges = currentEnvironment !== (data?.env || ""); + useEffect(() => { if (data) { form.reset({ environment: data.env || "", }); } - }, [form.reset, data, form]); + }, [data, form]); - const onSubmit = async (data: EnvironmentSchema) => { + const onSubmit = async (formData: EnvironmentSchema) => { mutateAsync({ mongoId: id || "", postgresId: id || "", @@ -87,7 +91,7 @@ export const ShowEnvironment = ({ id, type }: Props) => { mysqlId: id || "", mariadbId: id || "", composeId: id || "", - env: data.environment, + env: formData.environment, }) .then(async () => { toast.success("Environments Added"); @@ -98,6 +102,12 @@ export const ShowEnvironment = ({ id, type }: Props) => { }); }; + const handleCancel = () => { + form.reset({ + environment: data?.env || "", + }); + }; + return (
@@ -106,6 +116,11 @@ export const ShowEnvironment = ({ id, type }: Props) => { Environment Settings You can add environment variables to your resource. + {hasChanges && ( + + (You have unsaved changes) + + )}
@@ -132,8 +147,8 @@ export const ShowEnvironment = ({ id, type }: Props) => { control={form.control} name="environment" render={({ field }) => ( - - + + { } language="properties" disabled={isEnvVisible} + className="font-mono" + wrapperClassName="compose-file-editor" placeholder={`NODE_ENV=production PORT=3000 -`} - className="h-96 font-mono" + `} {...field} /> - )} /> -
- + )} +
diff --git a/apps/dokploy/components/dashboard/application/environment/show.tsx b/apps/dokploy/components/dashboard/application/environment/show.tsx index 7200f2a7..b574ce09 100644 --- a/apps/dokploy/components/dashboard/application/environment/show.tsx +++ b/apps/dokploy/components/dashboard/application/environment/show.tsx @@ -1,5 +1,5 @@ import { Button } from "@/components/ui/button"; -import { Card, CardContent } from "@/components/ui/card"; +import { Card } from "@/components/ui/card"; import { Form } from "@/components/ui/form"; import { Secrets } from "@/components/ui/secrets"; import { api } from "@/utils/api"; @@ -7,6 +7,7 @@ import { zodResolver } from "@hookform/resolvers/zod"; import { useForm } from "react-hook-form"; import { toast } from "sonner"; import { z } from "zod"; +import { useEffect } from "react"; const addEnvironmentSchema = z.object({ env: z.string(), @@ -34,16 +35,32 @@ export const ShowEnvironment = ({ applicationId }: Props) => { const form = useForm({ defaultValues: { - env: data?.env || "", - buildArgs: data?.buildArgs || "", + env: "", + buildArgs: "", }, resolver: zodResolver(addEnvironmentSchema), }); - const onSubmit = async (data: EnvironmentSchema) => { + // Watch form values + const currentEnv = form.watch("env"); + const currentBuildArgs = form.watch("buildArgs"); + const hasChanges = + currentEnv !== (data?.env || "") || + currentBuildArgs !== (data?.buildArgs || ""); + + useEffect(() => { + if (data) { + form.reset({ + env: data.env || "", + buildArgs: data.buildArgs || "", + }); + } + }, [data, form]); + + const onSubmit = async (formData: EnvironmentSchema) => { mutateAsync({ - env: data.env, - buildArgs: data.buildArgs, + env: formData.env, + buildArgs: formData.buildArgs, applicationId, }) .then(async () => { @@ -55,6 +72,13 @@ export const ShowEnvironment = ({ applicationId }: Props) => { }); }; + const handleCancel = () => { + form.reset({ + env: data?.env || "", + buildArgs: data?.buildArgs || "", + }); + }; + return (
@@ -65,7 +89,16 @@ export const ShowEnvironment = ({ applicationId }: Props) => { + You can add environment variables to your resource. + {hasChanges && ( + + (You have unsaved changes) + + )} + + } placeholder={["NODE_ENV=production", "PORT=3000"].join("\n")} /> {data?.buildType === "dockerfile" && ( @@ -89,8 +122,18 @@ export const ShowEnvironment = ({ applicationId }: Props) => { placeholder="NPM_TOKEN=xyz" /> )} -
- + )} +
diff --git a/apps/dokploy/components/dashboard/application/general/generic/save-bitbucket-provider.tsx b/apps/dokploy/components/dashboard/application/general/generic/save-bitbucket-provider.tsx index 9b207d63..ca1bf823 100644 --- a/apps/dokploy/components/dashboard/application/general/generic/save-bitbucket-provider.tsx +++ b/apps/dokploy/components/dashboard/application/general/generic/save-bitbucket-provider.tsx @@ -29,14 +29,23 @@ import { SelectTrigger, SelectValue, } from "@/components/ui/select"; +import { + Tooltip, + TooltipContent, + TooltipProvider, + TooltipTrigger, +} from "@/components/ui/tooltip"; import { cn } from "@/lib/utils"; import { api } from "@/utils/api"; import { zodResolver } from "@hookform/resolvers/zod"; -import { CheckIcon, ChevronsUpDown } from "lucide-react"; +import { CheckIcon, ChevronsUpDown, X } from "lucide-react"; import { useEffect } from "react"; import { useForm } from "react-hook-form"; import { toast } from "sonner"; import { z } from "zod"; +import { Badge } from "@/components/ui/badge"; +import { BitbucketIcon } from "@/components/icons/data-tools-icons"; +import Link from "next/link"; const BitbucketProviderSchema = z.object({ buildPath: z.string().min(1, "Path is required").default("/"), @@ -48,6 +57,7 @@ const BitbucketProviderSchema = z.object({ .required(), branch: z.string().min(1, "Branch is required"), bitbucketId: z.string().min(1, "Bitbucket Provider is required"), + watchPaths: z.array(z.string()).optional(), }); type BitbucketProvider = z.infer; @@ -73,6 +83,7 @@ export const SaveBitbucketProvider = ({ applicationId }: Props) => { }, bitbucketId: "", branch: "", + watchPaths: [], }, resolver: zodResolver(BitbucketProviderSchema), }); @@ -84,7 +95,6 @@ export const SaveBitbucketProvider = ({ applicationId }: Props) => { data: repositories, isLoading: isLoadingRepositories, error, - isError, } = api.bitbucket.getBitbucketRepositories.useQuery( { bitbucketId, @@ -119,6 +129,7 @@ export const SaveBitbucketProvider = ({ applicationId }: Props) => { }, buildPath: data.bitbucketBuildPath || "/", bitbucketId: data.bitbucketId || "", + watchPaths: data.watchPaths || [], }); } }, [form.reset, data, form]); @@ -131,6 +142,7 @@ export const SaveBitbucketProvider = ({ applicationId }: Props) => { bitbucketBuildPath: data.buildPath, bitbucketId: data.bitbucketId, applicationId, + watchPaths: data.watchPaths || [], }) .then(async () => { toast.success("Service Provided Saved"); @@ -196,7 +208,20 @@ export const SaveBitbucketProvider = ({ applicationId }: Props) => { name="repository" render={({ field }) => ( - Repository +
+ Repository + {field.value.owner && field.value.repo && ( + + + View Repository + + )} +
@@ -364,6 +389,84 @@ export const SaveBitbucketProvider = ({ applicationId }: Props) => {
)} /> + ( + +
+ Watch Paths + + + +
+ ? +
+
+ +

+ Add paths to watch for changes. When files in these + paths change, a new deployment will be triggered. +

+
+
+
+
+
+ {field.value?.map((path, index) => ( + + {path} + { + const newPaths = [...(field.value || [])]; + newPaths.splice(index, 1); + form.setValue("watchPaths", newPaths); + }} + /> + + ))} +
+ +
+ { + if (e.key === "Enter") { + e.preventDefault(); + const input = e.currentTarget; + const value = input.value.trim(); + if (value) { + const newPaths = [...(field.value || []), value]; + form.setValue("watchPaths", newPaths); + input.value = ""; + } + } + }} + /> + +
+
+ +
+ )} + />
)}
- ( - - Branch - - - - - - )} - /> +
+ ( + + Branch + + + + + + )} + /> +
+ {
)} /> + ( + +
+ Watch Paths + + + +
+ ? +
+
+ +

+ Add paths to watch for changes. When files in these + paths change, a new deployment will be triggered. This + will work only when manual webhook is setup. +

+
+
+
+
+
+ {field.value?.map((path, index) => ( + + {path} + { + const newPaths = [...(field.value || [])]; + newPaths.splice(index, 1); + form.setValue("watchPaths", newPaths); + }} + /> + + ))} +
+ +
+ { + if (e.key === "Enter") { + e.preventDefault(); + const input = e.currentTarget; + const value = input.value.trim(); + if (value) { + const newPaths = [...(field.value || []), value]; + form.setValue("watchPaths", newPaths); + input.value = ""; + } + } + }} + /> + +
+
+ +
+ )} + />
diff --git a/apps/dokploy/components/dashboard/application/general/generic/save-github-provider.tsx b/apps/dokploy/components/dashboard/application/general/generic/save-github-provider.tsx index adb44575..257e48ea 100644 --- a/apps/dokploy/components/dashboard/application/general/generic/save-github-provider.tsx +++ b/apps/dokploy/components/dashboard/application/general/generic/save-github-provider.tsx @@ -28,14 +28,23 @@ import { SelectTrigger, SelectValue, } from "@/components/ui/select"; +import { + Tooltip, + TooltipContent, + TooltipProvider, + TooltipTrigger, +} from "@/components/ui/tooltip"; +import { Badge } from "@/components/ui/badge"; import { cn } from "@/lib/utils"; import { api } from "@/utils/api"; import { zodResolver } from "@hookform/resolvers/zod"; -import { CheckIcon, ChevronsUpDown } from "lucide-react"; +import { CheckIcon, ChevronsUpDown, HelpCircle, Plus, X } from "lucide-react"; import { useEffect } from "react"; import { useForm } from "react-hook-form"; import { toast } from "sonner"; import { z } from "zod"; +import Link from "next/link"; +import { GithubIcon } from "@/components/icons/data-tools-icons"; const GithubProviderSchema = z.object({ buildPath: z.string().min(1, "Path is required").default("/"), @@ -47,6 +56,7 @@ const GithubProviderSchema = z.object({ .required(), branch: z.string().min(1, "Branch is required"), githubId: z.string().min(1, "Github Provider is required"), + watchPaths: z.array(z.string()).optional(), }); type GithubProvider = z.infer; @@ -113,6 +123,7 @@ export const SaveGithubProvider = ({ applicationId }: Props) => { }, buildPath: data.buildPath || "/", githubId: data.githubId || "", + watchPaths: data.watchPaths || [], }); } }, [form.reset, data, form]); @@ -125,6 +136,7 @@ export const SaveGithubProvider = ({ applicationId }: Props) => { owner: data.repository.owner, buildPath: data.buildPath, githubId: data.githubId, + watchPaths: data.watchPaths || [], }) .then(async () => { toast.success("Service Provided Saved"); @@ -187,7 +199,20 @@ export const SaveGithubProvider = ({ applicationId }: Props) => { name="repository" render={({ field }) => ( - Repository +
+ Repository + {field.value.owner && field.value.repo && ( + + + View Repository + + )} +
@@ -350,7 +375,85 @@ export const SaveGithubProvider = ({ applicationId }: Props) => { - + +
+ )} + /> + ( + +
+ Watch Paths + + + + + + +

+ Add paths to watch for changes. When files in these + paths change, a new deployment will be triggered. +

+
+
+
+
+
+ {field.value?.map((path, index) => ( + + {path} + { + const newPaths = [...(field.value || [])]; + newPaths.splice(index, 1); + field.onChange(newPaths); + }} + /> + + ))} +
+
+ + { + if (e.key === "Enter") { + e.preventDefault(); + const input = e.currentTarget; + const path = input.value.trim(); + if (path) { + field.onChange([...(field.value || []), path]); + input.value = ""; + } + } + }} + /> + + +
)} @@ -365,6 +468,16 @@ export const SaveGithubProvider = ({ applicationId }: Props) => { Save
+ {/* create github link */} +
+ + Repository + +
diff --git a/apps/dokploy/components/dashboard/application/general/generic/save-gitlab-provider.tsx b/apps/dokploy/components/dashboard/application/general/generic/save-gitlab-provider.tsx index 298a9114..c0c90a01 100644 --- a/apps/dokploy/components/dashboard/application/general/generic/save-gitlab-provider.tsx +++ b/apps/dokploy/components/dashboard/application/general/generic/save-gitlab-provider.tsx @@ -29,14 +29,23 @@ import { SelectTrigger, SelectValue, } from "@/components/ui/select"; +import { + Tooltip, + TooltipContent, + TooltipProvider, + TooltipTrigger, +} from "@/components/ui/tooltip"; +import { Badge } from "@/components/ui/badge"; import { cn } from "@/lib/utils"; import { api } from "@/utils/api"; import { zodResolver } from "@hookform/resolvers/zod"; -import { CheckIcon, ChevronsUpDown } from "lucide-react"; +import { CheckIcon, ChevronsUpDown, HelpCircle, Plus, X } from "lucide-react"; import { useEffect } from "react"; import { useForm } from "react-hook-form"; import { toast } from "sonner"; import { z } from "zod"; +import Link from "next/link"; +import { GitlabIcon } from "@/components/icons/data-tools-icons"; const GitlabProviderSchema = z.object({ buildPath: z.string().min(1, "Path is required").default("/"), @@ -50,6 +59,7 @@ const GitlabProviderSchema = z.object({ .required(), branch: z.string().min(1, "Branch is required"), gitlabId: z.string().min(1, "Gitlab Provider is required"), + watchPaths: z.array(z.string()).optional(), }); type GitlabProvider = z.infer; @@ -124,6 +134,7 @@ export const SaveGitlabProvider = ({ applicationId }: Props) => { }, buildPath: data.gitlabBuildPath || "/", gitlabId: data.gitlabId || "", + watchPaths: data.watchPaths || [], }); } }, [form.reset, data, form]); @@ -138,6 +149,7 @@ export const SaveGitlabProvider = ({ applicationId }: Props) => { applicationId, gitlabProjectId: data.repository.id, gitlabPathNamespace: data.repository.gitlabPathNamespace, + watchPaths: data.watchPaths || [], }) .then(async () => { toast.success("Service Provided Saved"); @@ -203,7 +215,20 @@ export const SaveGitlabProvider = ({ applicationId }: Props) => { name="repository" render={({ field }) => ( - Repository +
+ Repository + {field.value.owner && field.value.repo && ( + + + View Repository + + )} +
@@ -375,7 +400,85 @@ export const SaveGitlabProvider = ({ applicationId }: Props) => { - + +
+ )} + /> + ( + +
+ Watch Paths + + + + + + +

+ Add paths to watch for changes. When files in these + paths change, a new deployment will be triggered. +

+
+
+
+
+
+ {field.value?.map((path, index) => ( + + {path} + { + const newPaths = [...(field.value || [])]; + newPaths.splice(index, 1); + field.onChange(newPaths); + }} + /> + + ))} +
+
+ + { + if (e.key === "Enter") { + e.preventDefault(); + const input = e.currentTarget; + const path = input.value.trim(); + if (path) { + field.onChange([...(field.value || []), path]); + input.value = ""; + } + } + }} + /> + + +
)} diff --git a/apps/dokploy/components/dashboard/application/general/generic/show.tsx b/apps/dokploy/components/dashboard/application/general/generic/show.tsx index 73f5e8f8..b00a3495 100644 --- a/apps/dokploy/components/dashboard/application/general/generic/show.tsx +++ b/apps/dokploy/components/dashboard/application/general/generic/show.tsx @@ -11,7 +11,7 @@ import { import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; import { api } from "@/utils/api"; -import { GitBranch, LockIcon, UploadCloud } from "lucide-react"; +import { GitBranch, UploadCloud } from "lucide-react"; import Link from "next/link"; import { useState } from "react"; import { SaveBitbucketProvider } from "./save-bitbucket-provider"; diff --git a/apps/dokploy/components/dashboard/application/general/show.tsx b/apps/dokploy/components/dashboard/application/general/show.tsx index 83e4b6f0..28a50a01 100644 --- a/apps/dokploy/components/dashboard/application/general/show.tsx +++ b/apps/dokploy/components/dashboard/application/general/show.tsx @@ -4,10 +4,23 @@ import { DialogAction } from "@/components/shared/dialog-action"; import { Button } from "@/components/ui/button"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { Switch } from "@/components/ui/switch"; +import { + Tooltip, + TooltipContent, + TooltipProvider, + TooltipTrigger, +} from "@/components/ui/tooltip"; +import * as TooltipPrimitive from "@radix-ui/react-tooltip"; import { api } from "@/utils/api"; -import { Ban, CheckCircle2, Hammer, RefreshCcw, Terminal } from "lucide-react"; +import { + Ban, + CheckCircle2, + Hammer, + HelpCircle, + RefreshCcw, + Terminal, +} from "lucide-react"; import { useRouter } from "next/router"; -import React from "react"; import { toast } from "sonner"; import { DockerTerminalModal } from "../../settings/web-server/docker-terminal-modal"; interface Props { @@ -28,8 +41,7 @@ export const ShowGeneralApplication = ({ applicationId }: Props) => { const { mutateAsync: stop, isLoading: isStopping } = api.application.stop.useMutation(); - const { mutateAsync: deploy, isLoading: isDeploying } = - api.application.deploy.useMutation(); + const { mutateAsync: deploy } = api.application.deploy.useMutation(); const { mutateAsync: reload, isLoading: isReloading } = api.application.reload.useMutation(); @@ -43,128 +55,188 @@ export const ShowGeneralApplication = ({ applicationId }: Props) => { Deploy Settings - { - await deploy({ - applicationId: applicationId, - }) - .then(() => { - toast.success("Application deployed successfully"); - refetch(); - router.push( - `/dashboard/project/${data?.projectId}/services/application/${applicationId}?tab=deployments`, - ); - }) - .catch(() => { - toast.error("Error deploying application"); - }); - }} - > - - - { - await reload({ - applicationId: applicationId, - appName: data?.appName || "", - }) - .then(() => { - toast.success("Application reloaded successfully"); - refetch(); - }) - .catch(() => { - toast.error("Error reloading application"); - }); - }} - > - - - { - await redeploy({ - applicationId: applicationId, - }) - .then(() => { - toast.success("Application rebuilt successfully"); - refetch(); - }) - .catch(() => { - toast.error("Error rebuilding application"); - }); - }} - > - - - - {data?.applicationStatus === "idle" ? ( + { - await start({ + await deploy({ applicationId: applicationId, }) .then(() => { - toast.success("Application started successfully"); + toast.success("Application deployed successfully"); refetch(); + router.push( + `/dashboard/project/${data?.projectId}/services/application/${applicationId}?tab=deployments`, + ); }) .catch(() => { - toast.error("Error starting application"); + toast.error("Error deploying application"); }); }} > - - ) : ( { - await stop({ + await reload({ applicationId: applicationId, + appName: data?.appName || "", }) .then(() => { - toast.success("Application stopped successfully"); + toast.success("Application reloaded successfully"); refetch(); }) .catch(() => { - toast.error("Error stopping application"); + toast.error("Error reloading application"); }); }} > - - )} + { + await redeploy({ + applicationId: applicationId, + }) + .then(() => { + toast.success("Application rebuilt successfully"); + refetch(); + }) + .catch(() => { + toast.error("Error rebuilding application"); + }); + }} + > + + + + {data?.applicationStatus === "idle" ? ( + { + await start({ + applicationId: applicationId, + }) + .then(() => { + toast.success("Application started successfully"); + refetch(); + }) + .catch(() => { + toast.error("Error starting application"); + }); + }} + > + + + ) : ( + { + await stop({ + applicationId: applicationId, + }) + .then(() => { + toast.success("Application stopped successfully"); + refetch(); + }) + .catch(() => { + toast.error("Error stopping application"); + }); + }} + > + + + )} + { + if ( + input.previewCertificateType === "custom" && + !input.previewCustomCertResolver + ) { + ctx.addIssue({ + code: z.ZodIssueCode.custom, + path: ["previewCustomCertResolver"], + message: "Required", + }); + } + }); type Schema = z.infer; @@ -90,6 +104,7 @@ export const ShowPreviewSettings = ({ applicationId }: Props) => { previewHttps: data.previewHttps || false, previewPath: data.previewPath || "/", previewCertificateType: data.previewCertificateType || "none", + previewCustomCertResolver: data.previewCustomCertResolver || "", }); } }, [data]); @@ -105,6 +120,7 @@ export const ShowPreviewSettings = ({ applicationId }: Props) => { previewHttps: formData.previewHttps, previewPath: formData.previewPath, previewCertificateType: formData.previewCertificateType, + previewCustomCertResolver: formData.previewCustomCertResolver, }) .then(() => { toast.success("Preview Deployments settings updated"); @@ -184,10 +200,6 @@ export const ShowPreviewSettings = ({ applicationId }: Props) => { render={({ field }) => ( Preview Limit - {/* - Set the limit of preview deployments that can be - created for this app. - */} @@ -238,6 +250,7 @@ export const ShowPreviewSettings = ({ applicationId }: Props) => { Let's Encrypt + Custom @@ -245,6 +258,25 @@ export const ShowPreviewSettings = ({ applicationId }: Props) => { )} /> )} + + {form.watch("previewCertificateType") === "custom" && ( + ( + + Certificate Provider + + + + + + )} + /> + )}
@@ -279,7 +311,7 @@ export const ShowPreviewSettings = ({ applicationId }: Props) => { ( + render={() => ( { compose: () => api.compose.one.useQuery({ composeId: id }, { enabled: !!id }), }; - const { data, refetch } = queryMap[type] + const { data } = queryMap[type] ? queryMap[type]() : api.mongo.one.useQuery({ mongoId: id }, { enabled: !!id }); diff --git a/apps/dokploy/components/dashboard/compose/deployments/refresh-token-compose.tsx b/apps/dokploy/components/dashboard/compose/deployments/refresh-token-compose.tsx index 95fafaab..b062b099 100644 --- a/apps/dokploy/components/dashboard/compose/deployments/refresh-token-compose.tsx +++ b/apps/dokploy/components/dashboard/compose/deployments/refresh-token-compose.tsx @@ -11,7 +11,6 @@ import { } from "@/components/ui/alert-dialog"; import { api } from "@/utils/api"; import { RefreshCcw } from "lucide-react"; -import React from "react"; import { toast } from "sonner"; interface Props { diff --git a/apps/dokploy/components/dashboard/compose/domains/add-domain.tsx b/apps/dokploy/components/dashboard/compose/domains/add-domain.tsx index e18d40d7..9b412c83 100644 --- a/apps/dokploy/components/dashboard/compose/domains/add-domain.tsx +++ b/apps/dokploy/components/dashboard/compose/domains/add-domain.tsx @@ -104,6 +104,15 @@ export const AddDomainCompose = ({ const form = useForm({ resolver: zodResolver(domainCompose), + defaultValues: { + host: "", + path: undefined, + port: undefined, + https: false, + certificateType: undefined, + customCertResolver: undefined, + serviceName: "", + }, }); const https = form.watch("https"); @@ -116,11 +125,21 @@ export const AddDomainCompose = ({ path: data?.path || undefined, port: data?.port || undefined, serviceName: data?.serviceName || undefined, + certificateType: data?.certificateType || undefined, + customCertResolver: data?.customCertResolver || undefined, }); } if (!domainId) { - form.reset({}); + form.reset({ + host: "", + path: undefined, + port: undefined, + https: false, + certificateType: undefined, + customCertResolver: undefined, + serviceName: "", + }); } }, [form, form.reset, data, isLoading]); @@ -393,33 +412,55 @@ export const AddDomainCompose = ({ /> {https && ( - ( - - Certificate Provider - + + + + + - - None - - Let's Encrypt - - - - - + + None + + Let's Encrypt + + Custom + + + + + )} + /> + + {form.getValues().certificateType === "custom" && ( + ( + + Custom Certificate Resolver + + + + + + )} + /> )} - /> + )}
diff --git a/apps/dokploy/components/dashboard/compose/domains/show-domains.tsx b/apps/dokploy/components/dashboard/compose/domains/show-domains.tsx index 7bc451e0..e6468d6f 100644 --- a/apps/dokploy/components/dashboard/compose/domains/show-domains.tsx +++ b/apps/dokploy/components/dashboard/compose/domains/show-domains.tsx @@ -118,7 +118,7 @@ export const ShowDomainsCompose = ({ composeId }: Props) => { await deleteDomain({ domainId: item.domainId, }) - .then((data) => { + .then((_data) => { refetch(); toast.success("Domain deleted successfully"); }) diff --git a/apps/dokploy/components/dashboard/compose/general/actions.tsx b/apps/dokploy/components/dashboard/compose/general/actions.tsx index a40cc345..f7761920 100644 --- a/apps/dokploy/components/dashboard/compose/general/actions.tsx +++ b/apps/dokploy/components/dashboard/compose/general/actions.tsx @@ -1,8 +1,15 @@ import { DialogAction } from "@/components/shared/dialog-action"; import { Button } from "@/components/ui/button"; import { Switch } from "@/components/ui/switch"; +import { + Tooltip, + TooltipContent, + TooltipProvider, + TooltipTrigger, +} from "@/components/ui/tooltip"; +import * as TooltipPrimitive from "@radix-ui/react-tooltip"; import { api } from "@/utils/api"; -import { Ban, CheckCircle2, Hammer, Terminal } from "lucide-react"; +import { Ban, CheckCircle2, Hammer, HelpCircle, Terminal } from "lucide-react"; import { useRouter } from "next/router"; import { toast } from "sonner"; import { DockerTerminalModal } from "../../settings/web-server/docker-terminal-modal"; @@ -27,103 +34,159 @@ export const ComposeActions = ({ composeId }: Props) => { api.compose.stop.useMutation(); return (
- { - await deploy({ - composeId: composeId, - }) - .then(() => { - toast.success("Compose deployed successfully"); - refetch(); - router.push( - `/dashboard/project/${data?.project.projectId}/services/compose/${composeId}?tab=deployments`, - ); - }) - .catch(() => { - toast.error("Error deploying compose"); - }); - }} - > - - - { - await redeploy({ - composeId: composeId, - }) - .then(() => { - toast.success("Compose rebuilt successfully"); - refetch(); - }) - .catch(() => { - toast.error("Error rebuilding compose"); - }); - }} - > - - - {data?.composeType === "docker-compose" && - data?.composeStatus === "idle" ? ( + { - await start({ + await deploy({ composeId: composeId, }) .then(() => { - toast.success("Compose started successfully"); + toast.success("Compose deployed successfully"); refetch(); + router.push( + `/dashboard/project/${data?.project.projectId}/services/compose/${composeId}?tab=deployments`, + ); }) .catch(() => { - toast.error("Error starting compose"); + toast.error("Error deploying compose"); }); }} > - - ) : ( { - await stop({ + await redeploy({ composeId: composeId, }) .then(() => { - toast.success("Compose stopped successfully"); + toast.success("Compose rebuilt successfully"); refetch(); }) .catch(() => { - toast.error("Error stopping compose"); + toast.error("Error rebuilding compose"); }); }} > - - )} - + {data?.composeType === "docker-compose" && + data?.composeStatus === "idle" ? ( + { + await start({ + composeId: composeId, + }) + .then(() => { + toast.success("Compose started successfully"); + refetch(); + }) + .catch(() => { + toast.error("Error starting compose"); + }); + }} + > + + + ) : ( + { + await stop({ + composeId: composeId, + }) + .then(() => { + toast.success("Compose stopped successfully"); + refetch(); + }) + .catch(() => { + toast.error("Error stopping compose"); + }); + }} + > + + + )} + { { enabled: !!composeId }, ); - const { mutateAsync, isLoading, error, isError } = - api.compose.update.useMutation(); + const { mutateAsync, isLoading } = api.compose.update.useMutation(); const form = useForm({ defaultValues: { @@ -76,7 +75,7 @@ export const ComposeFileEditor = ({ composeId }: Props) => { composeId, }); }) - .catch((e) => { + .catch((_e) => { toast.error("Error updating the Compose config"); }); }; diff --git a/apps/dokploy/components/dashboard/compose/general/generic/save-bitbucket-provider-compose.tsx b/apps/dokploy/components/dashboard/compose/general/generic/save-bitbucket-provider-compose.tsx index 1c06fe88..6dc99b26 100644 --- a/apps/dokploy/components/dashboard/compose/general/generic/save-bitbucket-provider-compose.tsx +++ b/apps/dokploy/components/dashboard/compose/general/generic/save-bitbucket-provider-compose.tsx @@ -29,14 +29,23 @@ import { SelectTrigger, SelectValue, } from "@/components/ui/select"; +import { + Tooltip, + TooltipContent, + TooltipProvider, + TooltipTrigger, +} from "@/components/ui/tooltip"; import { cn } from "@/lib/utils"; import { api } from "@/utils/api"; import { zodResolver } from "@hookform/resolvers/zod"; -import { CheckIcon, ChevronsUpDown } from "lucide-react"; +import { CheckIcon, ChevronsUpDown, X } from "lucide-react"; import { useEffect } from "react"; import { useForm } from "react-hook-form"; import { toast } from "sonner"; import { z } from "zod"; +import { Badge } from "@/components/ui/badge"; +import { BitbucketIcon } from "@/components/icons/data-tools-icons"; +import Link from "next/link"; const BitbucketProviderSchema = z.object({ composePath: z.string().min(1), @@ -48,6 +57,7 @@ const BitbucketProviderSchema = z.object({ .required(), branch: z.string().min(1, "Branch is required"), bitbucketId: z.string().min(1, "Bitbucket Provider is required"), + watchPaths: z.array(z.string()).optional(), }); type BitbucketProvider = z.infer; @@ -73,6 +83,7 @@ export const SaveBitbucketProviderCompose = ({ composeId }: Props) => { }, bitbucketId: "", branch: "", + watchPaths: [], }, resolver: zodResolver(BitbucketProviderSchema), }); @@ -84,7 +95,6 @@ export const SaveBitbucketProviderCompose = ({ composeId }: Props) => { data: repositories, isLoading: isLoadingRepositories, error, - isError, } = api.bitbucket.getBitbucketRepositories.useQuery( { bitbucketId, @@ -119,6 +129,7 @@ export const SaveBitbucketProviderCompose = ({ composeId }: Props) => { }, composePath: data.composePath, bitbucketId: data.bitbucketId || "", + watchPaths: data.watchPaths || [], }); } }, [form.reset, data, form]); @@ -133,6 +144,7 @@ export const SaveBitbucketProviderCompose = ({ composeId }: Props) => { composeId, sourceType: "bitbucket", composeStatus: "idle", + watchPaths: data.watchPaths, }) .then(async () => { toast.success("Service Provided Saved"); @@ -198,7 +210,20 @@ export const SaveBitbucketProviderCompose = ({ composeId }: Props) => { name="repository" render={({ field }) => ( - Repository +
+ Repository + {field.value.owner && field.value.repo && ( + + + View Repository + + )} +
@@ -366,6 +391,84 @@ export const SaveBitbucketProviderCompose = ({ composeId }: Props) => {
)} /> + ( + +
+ Watch Paths + + + +
+ ? +
+
+ +

+ Add paths to watch for changes. When files in these + paths change, a new deployment will be triggered. +

+
+
+
+
+
+ {field.value?.map((path, index) => ( + + {path} + { + const newPaths = [...(field.value || [])]; + newPaths.splice(index, 1); + form.setValue("watchPaths", newPaths); + }} + /> + + ))} +
+ +
+ { + if (e.key === "Enter") { + e.preventDefault(); + const input = e.currentTarget; + const value = input.value.trim(); + if (value) { + const newPaths = [...(field.value || []), value]; + form.setValue("watchPaths", newPaths); + input.value = ""; + } + } + }} + /> + +
+
+ +
+ )} + />
+
+ + +
+ )} + />
diff --git a/apps/dokploy/components/dashboard/compose/general/generic/save-github-provider-compose.tsx b/apps/dokploy/components/dashboard/compose/general/generic/save-github-provider-compose.tsx index 7787cb3c..b58347dc 100644 --- a/apps/dokploy/components/dashboard/compose/general/generic/save-github-provider-compose.tsx +++ b/apps/dokploy/components/dashboard/compose/general/generic/save-github-provider-compose.tsx @@ -1,3 +1,4 @@ +import { Badge } from "@/components/ui/badge"; import { Button } from "@/components/ui/button"; import { Command, @@ -28,14 +29,22 @@ import { SelectTrigger, SelectValue, } from "@/components/ui/select"; +import { + Tooltip, + TooltipContent, + TooltipProvider, + TooltipTrigger, +} from "@/components/ui/tooltip"; import { cn } from "@/lib/utils"; import { api } from "@/utils/api"; import { zodResolver } from "@hookform/resolvers/zod"; -import { CheckIcon, ChevronsUpDown } from "lucide-react"; +import { CheckIcon, ChevronsUpDown, X } from "lucide-react"; import { useEffect } from "react"; import { useForm } from "react-hook-form"; import { toast } from "sonner"; import { z } from "zod"; +import { GithubIcon } from "@/components/icons/data-tools-icons"; +import Link from "next/link"; const GithubProviderSchema = z.object({ composePath: z.string().min(1), @@ -47,6 +56,7 @@ const GithubProviderSchema = z.object({ .required(), branch: z.string().min(1, "Branch is required"), githubId: z.string().min(1, "Github Provider is required"), + watchPaths: z.array(z.string()).optional(), }); type GithubProvider = z.infer; @@ -71,6 +81,7 @@ export const SaveGithubProviderCompose = ({ composeId }: Props) => { }, githubId: "", branch: "", + watchPaths: [], }, resolver: zodResolver(GithubProviderSchema), }); @@ -113,6 +124,7 @@ export const SaveGithubProviderCompose = ({ composeId }: Props) => { }, composePath: data.composePath, githubId: data.githubId || "", + watchPaths: data.watchPaths || [], }); } }, [form.reset, data, form]); @@ -127,6 +139,7 @@ export const SaveGithubProviderCompose = ({ composeId }: Props) => { githubId: data.githubId, sourceType: "github", composeStatus: "idle", + watchPaths: data.watchPaths, }) .then(async () => { toast.success("Service Provided Saved"); @@ -183,13 +196,25 @@ export const SaveGithubProviderCompose = ({ composeId }: Props) => { )} /> - ( - Repository +
+ Repository + {field.value.owner && field.value.repo && ( + + + View Repository + + )} +
@@ -357,6 +382,84 @@ export const SaveGithubProviderCompose = ({ composeId }: Props) => {
)} /> + ( + +
+ Watch Paths + + + +
+ ? +
+
+ +

+ Add paths to watch for changes. When files in these + paths change, a new deployment will be triggered. +

+
+
+
+
+
+ {field.value?.map((path, index) => ( + + {path} + { + const newPaths = [...(field.value || [])]; + newPaths.splice(index, 1); + form.setValue("watchPaths", newPaths); + }} + /> + + ))} +
+ +
+ { + if (e.key === "Enter") { + e.preventDefault(); + const input = e.currentTarget; + const value = input.value.trim(); + if (value) { + const newPaths = [...(field.value || []), value]; + form.setValue("watchPaths", newPaths); + input.value = ""; + } + } + }} + /> + +
+
+ +
+ )} + />
+
+ + + + )} + />
- - { - await reload({ - mariadbId: mariadbId, - appName: data?.appName || "", - }) - .then(() => { - toast.success("Mariadb reloaded successfully"); - refetch(); - }) - .catch(() => { - toast.error("Error reloading Mariadb"); - }); - }} - > - - - {data?.applicationStatus === "idle" ? ( + { - await start({ - mariadbId: mariadbId, - }) - .then(() => { - toast.success("Mariadb started successfully"); - refetch(); - }) - .catch(() => { - toast.error("Error starting Mariadb"); - }); + setIsDeploying(true); + await new Promise((resolve) => setTimeout(resolve, 1000)); + refetch(); }} > - - ) : ( { - await stop({ + await reload({ mariadbId: mariadbId, + appName: data?.appName || "", }) .then(() => { - toast.success("Mariadb stopped successfully"); + toast.success("Mariadb reloaded successfully"); refetch(); }) .catch(() => { - toast.error("Error stopping Mariadb"); + toast.error("Error reloading Mariadb"); }); }} > - - )} + {data?.applicationStatus === "idle" ? ( + { + await start({ + mariadbId: mariadbId, + }) + .then(() => { + toast.success("Mariadb started successfully"); + refetch(); + }) + .catch(() => { + toast.error("Error starting Mariadb"); + }); + }} + > + + + ) : ( + { + await stop({ + mariadbId: mariadbId, + }) + .then(() => { + toast.success("Mariadb stopped successfully"); + refetch(); + }) + .catch(() => { + toast.error("Error stopping Mariadb"); + }); + }} + > + + + )} + { Deploy Settings - { - setIsDeploying(true); - await new Promise((resolve) => setTimeout(resolve, 1000)); - refetch(); - }} - > - - - { - await reload({ - mongoId: mongoId, - appName: data?.appName || "", - }) - .then(() => { - toast.success("Mongo reloaded successfully"); - refetch(); - }) - .catch(() => { - toast.error("Error reloading Mongo"); - }); - }} - > - - - {data?.applicationStatus === "idle" ? ( + { - await start({ + setIsDeploying(true); + await new Promise((resolve) => setTimeout(resolve, 1000)); + refetch(); + }} + > + + + { + await reload({ mongoId: mongoId, + appName: data?.appName || "", }) .then(() => { - toast.success("Mongo started successfully"); + toast.success("Mongo reloaded successfully"); refetch(); }) .catch(() => { - toast.error("Error starting Mongo"); + toast.error("Error reloading Mongo"); }); }} > - - ) : ( - { - await stop({ - mongoId: mongoId, - }) - .then(() => { - toast.success("Mongo stopped successfully"); - refetch(); + {data?.applicationStatus === "idle" ? ( + { + await start({ + mongoId: mongoId, }) - .catch(() => { - toast.error("Error stopping Mongo"); - }); - }} - > - - - )} + .then(() => { + toast.success("Mongo started successfully"); + refetch(); + }) + .catch(() => { + toast.error("Error starting Mongo"); + }); + }} + > + + + ) : ( + { + await stop({ + mongoId: mongoId, + }) + .then(() => { + toast.success("Mongo stopped successfully"); + refetch(); + }) + .catch(() => { + toast.error("Error stopping Mongo"); + }); + }} + > + + + )} + +

Monitoring

diff --git a/apps/dokploy/components/dashboard/monitoring/paid/container/container-block-chart.tsx b/apps/dokploy/components/dashboard/monitoring/paid/container/container-block-chart.tsx index 9150dbcd..12af6b91 100644 --- a/apps/dokploy/components/dashboard/monitoring/paid/container/container-block-chart.tsx +++ b/apps/dokploy/components/dashboard/monitoring/paid/container/container-block-chart.tsx @@ -29,14 +29,6 @@ interface Props { data: ContainerMetric[]; } -interface FormattedMetric { - timestamp: string; - read: number; - write: number; - readUnit: string; - writeUnit: string; -} - const chartConfig = { read: { label: "Read", diff --git a/apps/dokploy/components/dashboard/monitoring/paid/container/show-paid-container-monitoring.tsx b/apps/dokploy/components/dashboard/monitoring/paid/container/show-paid-container-monitoring.tsx index 3636a391..3b189c2a 100644 --- a/apps/dokploy/components/dashboard/monitoring/paid/container/show-paid-container-monitoring.tsx +++ b/apps/dokploy/components/dashboard/monitoring/paid/container/show-paid-container-monitoring.tsx @@ -79,7 +79,7 @@ export const ContainerPaidMonitoring = ({ appName, baseUrl, token }: Props) => { data, isLoading, error: queryError, - } = api.admin.getContainerMetrics.useQuery( + } = api.user.getContainerMetrics.useQuery( { url: baseUrl, token, diff --git a/apps/dokploy/components/dashboard/monitoring/paid/servers/show-paid-monitoring.tsx b/apps/dokploy/components/dashboard/monitoring/paid/servers/show-paid-monitoring.tsx index 043b5c62..e92ce03f 100644 --- a/apps/dokploy/components/dashboard/monitoring/paid/servers/show-paid-monitoring.tsx +++ b/apps/dokploy/components/dashboard/monitoring/paid/servers/show-paid-monitoring.tsx @@ -7,7 +7,6 @@ import { } from "@/components/ui/select"; import { api } from "@/utils/api"; import { Clock, Cpu, HardDrive, Loader2, MemoryStick } from "lucide-react"; -import type React from "react"; import { useEffect, useState } from "react"; import { CPUChart } from "./cpu-chart"; import { DiskChart } from "./disk-chart"; @@ -73,7 +72,7 @@ export const ShowPaidMonitoring = ({ data, isLoading, error: queryError, - } = api.admin.getServerMetrics.useQuery( + } = api.server.getServerMetrics.useQuery( { url: BASE_URL, token, diff --git a/apps/dokploy/components/dashboard/mysql/general/show-external-mysql-credentials.tsx b/apps/dokploy/components/dashboard/mysql/general/show-external-mysql-credentials.tsx index dc1ca3a7..7a0527b1 100644 --- a/apps/dokploy/components/dashboard/mysql/general/show-external-mysql-credentials.tsx +++ b/apps/dokploy/components/dashboard/mysql/general/show-external-mysql-credentials.tsx @@ -19,7 +19,7 @@ import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; import { api } from "@/utils/api"; import { zodResolver } from "@hookform/resolvers/zod"; -import React, { useEffect, useState } from "react"; +import { useEffect, useState } from "react"; import { useForm } from "react-hook-form"; import { toast } from "sonner"; import { z } from "zod"; diff --git a/apps/dokploy/components/dashboard/mysql/general/show-general-mysql.tsx b/apps/dokploy/components/dashboard/mysql/general/show-general-mysql.tsx index 56a191ce..3c8ae3ea 100644 --- a/apps/dokploy/components/dashboard/mysql/general/show-general-mysql.tsx +++ b/apps/dokploy/components/dashboard/mysql/general/show-general-mysql.tsx @@ -2,9 +2,22 @@ import { DialogAction } from "@/components/shared/dialog-action"; import { DrawerLogs } from "@/components/shared/drawer-logs"; import { Button } from "@/components/ui/button"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; +import { + Tooltip, + TooltipContent, + TooltipProvider, + TooltipTrigger, +} from "@/components/ui/tooltip"; +import * as TooltipPrimitive from "@radix-ui/react-tooltip"; import { api } from "@/utils/api"; -import { Ban, CheckCircle2, RefreshCcw, Terminal } from "lucide-react"; -import React, { useState } from "react"; +import { + Ban, + CheckCircle2, + HelpCircle, + RefreshCcw, + Terminal, +} from "lucide-react"; +import { useState } from "react"; import { toast } from "sonner"; import { type LogLine, parseLogs } from "../../docker/logs/utils"; import { DockerTerminalModal } from "../../settings/web-server/docker-terminal-modal"; @@ -62,93 +75,150 @@ export const ShowGeneralMysql = ({ mysqlId }: Props) => { Deploy Settings - { - setIsDeploying(true); - await new Promise((resolve) => setTimeout(resolve, 1000)); - refetch(); - }} - > - - - { - await reload({ - mysqlId: mysqlId, - appName: data?.appName || "", - }) - .then(() => { - toast.success("Mysql reloaded successfully"); - refetch(); - }) - .catch(() => { - toast.error("Error reloading Mysql"); - }); - }} - > - - - {data?.applicationStatus === "idle" ? ( + { - await start({ - mysqlId: mysqlId, - }) - .then(() => { - toast.success("Mysql started successfully"); - refetch(); - }) - .catch(() => { - toast.error("Error starting Mysql"); - }); + setIsDeploying(true); + await new Promise((resolve) => setTimeout(resolve, 1000)); + refetch(); }} > - - ) : ( { - await stop({ + await reload({ mysqlId: mysqlId, + appName: data?.appName || "", }) .then(() => { - toast.success("Mysql stopped successfully"); + toast.success("Mysql reloaded successfully"); refetch(); }) .catch(() => { - toast.error("Error stopping Mysql"); + toast.error("Error reloading Mysql"); }); }} > - - )} - + {data?.applicationStatus === "idle" ? ( + { + await start({ + mysqlId: mysqlId, + }) + .then(() => { + toast.success("Mysql started successfully"); + refetch(); + }) + .catch(() => { + toast.error("Error starting Mysql"); + }); + }} + > + + + ) : ( + { + await stop({ + mysqlId: mysqlId, + }) + .then(() => { + toast.success("Mysql stopped successfully"); + refetch(); + }) + .catch(() => { + toast.error("Error stopping Mysql"); + }); + }} + > + + + )} + ; + +interface Props { + organizationId?: string; + children?: React.ReactNode; +} + +export function AddOrganization({ organizationId }: Props) { + const [open, setOpen] = useState(false); + const utils = api.useUtils(); + const { data: organization } = api.organization.one.useQuery( + { + organizationId: organizationId ?? "", + }, + { + enabled: !!organizationId, + }, + ); + const { mutateAsync, isLoading } = organizationId + ? api.organization.update.useMutation() + : api.organization.create.useMutation(); + + const form = useForm({ + resolver: zodResolver(organizationSchema), + defaultValues: { + name: "", + logo: "", + }, + }); + + useEffect(() => { + if (organization) { + form.reset({ + name: organization.name, + logo: organization.logo || "", + }); + } + }, [organization, form]); + + const onSubmit = async (values: OrganizationFormValues) => { + await mutateAsync({ + name: values.name, + logo: values.logo, + organizationId: organizationId ?? "", + }) + .then(() => { + form.reset(); + toast.success( + `Organization ${organizationId ? "updated" : "created"} successfully`, + ); + utils.organization.all.invalidate(); + setOpen(false); + }) + .catch((error) => { + console.error(error); + toast.error( + `Failed to ${organizationId ? "update" : "create"} organization`, + ); + }); + }; + + return ( + + + {organizationId ? ( + e.preventDefault()} + > + + + ) : ( + e.preventDefault()} + > +
+ +
+
+ Add organization +
+
+ )} +
+ + + + {organizationId ? "Update organization" : "Add organization"} + + + {organizationId + ? "Update the organization name and logo" + : "Create a new organization to manage your projects."} + + +
+ + ( + + Name + + + + + + )} + /> + ( + + Logo URL + + + + + + )} + /> + + + + + +
+
+ ); +} diff --git a/apps/dokploy/components/dashboard/postgres/advanced/show-custom-command.tsx b/apps/dokploy/components/dashboard/postgres/advanced/show-custom-command.tsx index 6e912db9..40e84844 100644 --- a/apps/dokploy/components/dashboard/postgres/advanced/show-custom-command.tsx +++ b/apps/dokploy/components/dashboard/postgres/advanced/show-custom-command.tsx @@ -11,7 +11,7 @@ import { import { Input } from "@/components/ui/input"; import { api } from "@/utils/api"; import { zodResolver } from "@hookform/resolvers/zod"; -import React, { useEffect } from "react"; +import { useEffect } from "react"; import { useForm } from "react-hook-form"; import { toast } from "sonner"; import { z } from "zod"; @@ -53,7 +53,7 @@ export const ShowCustomCommand = ({ id, type }: Props) => { mongo: () => api.mongo.update.useMutation(), }; - const { mutateAsync, isLoading } = mutationMap[type] + const { mutateAsync } = mutationMap[type] ? mutationMap[type]() : api.mongo.update.useMutation(); diff --git a/apps/dokploy/components/dashboard/postgres/general/show-external-postgres-credentials.tsx b/apps/dokploy/components/dashboard/postgres/general/show-external-postgres-credentials.tsx index e8fff7dc..dbd57d0b 100644 --- a/apps/dokploy/components/dashboard/postgres/general/show-external-postgres-credentials.tsx +++ b/apps/dokploy/components/dashboard/postgres/general/show-external-postgres-credentials.tsx @@ -19,7 +19,7 @@ import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; import { api } from "@/utils/api"; import { zodResolver } from "@hookform/resolvers/zod"; -import React, { useEffect, useState } from "react"; +import { useEffect, useState } from "react"; import { useForm } from "react-hook-form"; import { toast } from "sonner"; import { z } from "zod"; diff --git a/apps/dokploy/components/dashboard/postgres/general/show-general-postgres.tsx b/apps/dokploy/components/dashboard/postgres/general/show-general-postgres.tsx index 43c3f432..a2215545 100644 --- a/apps/dokploy/components/dashboard/postgres/general/show-general-postgres.tsx +++ b/apps/dokploy/components/dashboard/postgres/general/show-general-postgres.tsx @@ -2,12 +2,26 @@ import { DialogAction } from "@/components/shared/dialog-action"; import { DrawerLogs } from "@/components/shared/drawer-logs"; import { Button } from "@/components/ui/button"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; +import { + Tooltip, + TooltipContent, + TooltipProvider, + TooltipTrigger, +} from "@/components/ui/tooltip"; +import * as TooltipPrimitive from "@radix-ui/react-tooltip"; import { api } from "@/utils/api"; -import { Ban, CheckCircle2, RefreshCcw, Terminal } from "lucide-react"; -import React, { useState } from "react"; +import { + Ban, + CheckCircle2, + HelpCircle, + RefreshCcw, + Terminal, +} from "lucide-react"; +import { useState } from "react"; import { toast } from "sonner"; import { type LogLine, parseLogs } from "../../docker/logs/utils"; import { DockerTerminalModal } from "../../settings/web-server/docker-terminal-modal"; + interface Props { postgresId: string; } @@ -57,122 +71,179 @@ export const ShowGeneralPostgres = ({ postgresId }: Props) => { ); return ( -
- - - General - - - { - setIsDeploying(true); - - await new Promise((resolve) => setTimeout(resolve, 1000)); - refetch(); - }} - > - - - - { - await reload({ - postgresId: postgresId, - appName: data?.appName || "", - }) - .then(() => { - toast.success("Postgres reloaded successfully"); + <> +
+ + + Deploy Settings + + + + { + setIsDeploying(true); + await new Promise((resolve) => setTimeout(resolve, 1000)); refetch(); - }) - .catch(() => { - toast.error("Error reloading Postgres"); - }); - }} - > - - - {data?.applicationStatus === "idle" ? ( - { - await start({ - postgresId: postgresId, - }) - .then(() => { - toast.success("Postgres started successfully"); - refetch(); + }} + > + + + { + await reload({ + postgresId: postgresId, + appName: data?.appName || "", }) - .catch(() => { - toast.error("Error starting Postgres"); - }); - }} + .then(() => { + toast.success("Postgres reloaded successfully"); + refetch(); + }) + .catch(() => { + toast.error("Error reloading Postgres"); + }); + }} + > + + + {data?.applicationStatus === "idle" ? ( + { + await start({ + postgresId: postgresId, + }) + .then(() => { + toast.success("Postgres started successfully"); + refetch(); + }) + .catch(() => { + toast.error("Error starting Postgres"); + }); + }} + > + + + ) : ( + { + await stop({ + postgresId: postgresId, + }) + .then(() => { + toast.success("Postgres stopped successfully"); + refetch(); + }) + .catch(() => { + toast.error("Error stopping Postgres"); + }); + }} + > + + + )} + + - - - ) : ( - { - await stop({ - postgresId: postgresId, - }) - .then(() => { - toast.success("Postgres stopped successfully"); - refetch(); - }) - .catch(() => { - toast.error("Error stopping Postgres"); - }); - }} - > - - - )} - - - - - - - { - setIsDrawerOpen(false); - setFilteredLogs([]); - setIsDeploying(false); - refetch(); - }} - filteredLogs={filteredLogs} - /> -
+ +
+
+ { + setIsDrawerOpen(false); + setFilteredLogs([]); + setIsDeploying(false); + refetch(); + }} + filteredLogs={filteredLogs} + /> +
+ ); }; diff --git a/apps/dokploy/components/dashboard/postgres/general/show-internal-postgres-credentials.tsx b/apps/dokploy/components/dashboard/postgres/general/show-internal-postgres-credentials.tsx index e0122610..545150f8 100644 --- a/apps/dokploy/components/dashboard/postgres/general/show-internal-postgres-credentials.tsx +++ b/apps/dokploy/components/dashboard/postgres/general/show-internal-postgres-credentials.tsx @@ -3,7 +3,6 @@ import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; import { api } from "@/utils/api"; -import React from "react"; interface Props { postgresId: string; diff --git a/apps/dokploy/components/dashboard/postgres/update-postgres.tsx b/apps/dokploy/components/dashboard/postgres/update-postgres.tsx index 54ad5bce..7be6908f 100644 --- a/apps/dokploy/components/dashboard/postgres/update-postgres.tsx +++ b/apps/dokploy/components/dashboard/postgres/update-postgres.tsx @@ -21,7 +21,7 @@ import { Input } from "@/components/ui/input"; import { Textarea } from "@/components/ui/textarea"; import { api } from "@/utils/api"; import { zodResolver } from "@hookform/resolvers/zod"; -import { AlertTriangle, PenBoxIcon, SquarePen } from "lucide-react"; +import { PenBoxIcon } from "lucide-react"; import { useEffect, useState } from "react"; import { useForm } from "react-hook-form"; import { toast } from "sonner"; diff --git a/apps/dokploy/components/dashboard/project/add-ai-assistant.tsx b/apps/dokploy/components/dashboard/project/add-ai-assistant.tsx new file mode 100644 index 00000000..2bb47618 --- /dev/null +++ b/apps/dokploy/components/dashboard/project/add-ai-assistant.tsx @@ -0,0 +1,10 @@ +import { TemplateGenerator } from "@/components/dashboard/project/ai/template-generator"; + +interface Props { + projectId: string; + projectName?: string; +} + +export const AddAiAssistant = ({ projectId }: Props) => { + return ; +}; diff --git a/apps/dokploy/components/dashboard/project/add-application.tsx b/apps/dokploy/components/dashboard/project/add-application.tsx index da30cfee..16c56917 100644 --- a/apps/dokploy/components/dashboard/project/add-application.tsx +++ b/apps/dokploy/components/dashboard/project/add-application.tsx @@ -103,7 +103,7 @@ export const AddApplication = ({ projectId, projectName }: Props) => { projectId, }); }) - .catch((e) => { + .catch((_e) => { toast.error("Error creating the service"); }); }; diff --git a/apps/dokploy/components/dashboard/project/add-database.tsx b/apps/dokploy/components/dashboard/project/add-database.tsx index 1ca0d6a5..a58aef2c 100644 --- a/apps/dokploy/components/dashboard/project/add-database.tsx +++ b/apps/dokploy/components/dashboard/project/add-database.tsx @@ -18,7 +18,6 @@ import { DropdownMenuItem } from "@/components/ui/dropdown-menu"; import { Form, FormControl, - FormDescription, FormField, FormItem, FormLabel, @@ -49,7 +48,6 @@ import { z } from "zod"; type DbType = typeof mySchema._type.type; -// TODO: Change to a real docker images const dockerImageDefaultPlaceholder: Record = { mongo: "mongo:6", mariadb: "mariadb:11", @@ -496,7 +494,7 @@ export const AddDatabase = ({ projectId, projectName }: Props) => { diff --git a/apps/dokploy/components/dashboard/project/add-template.tsx b/apps/dokploy/components/dashboard/project/add-template.tsx index c28e2332..43e6f0d9 100644 --- a/apps/dokploy/components/dashboard/project/add-template.tsx +++ b/apps/dokploy/components/dashboard/project/add-template.tsx @@ -57,7 +57,6 @@ import { BookText, CheckIcon, ChevronsUpDown, - Code, Github, Globe, HelpCircle, @@ -418,7 +417,7 @@ export const AddTemplate = ({ projectId }: Props) => { side="top" > - If ot server is selected, the application + If no server is selected, the application will be deployed on the server where the user is logged in. @@ -469,7 +468,7 @@ export const AddTemplate = ({ projectId }: Props) => { }); toast.promise(promise, { loading: "Setting up...", - success: (data) => { + success: (_data) => { utils.project.one.invalidate({ projectId, }); diff --git a/apps/dokploy/components/dashboard/project/ai/step-one.tsx b/apps/dokploy/components/dashboard/project/ai/step-one.tsx new file mode 100644 index 00000000..109ad441 --- /dev/null +++ b/apps/dokploy/components/dashboard/project/ai/step-one.tsx @@ -0,0 +1,102 @@ +"use client"; + +import { Button } from "@/components/ui/button"; +import { Label } from "@/components/ui/label"; +import { + Select, + SelectContent, + SelectGroup, + SelectItem, + SelectLabel, + SelectTrigger, + SelectValue, +} from "@/components/ui/select"; +import { Textarea } from "@/components/ui/textarea"; +import { api } from "@/utils/api"; +import { useState } from "react"; + +const examples = [ + "Make a personal blog", + "Add a photo studio portfolio", + "Create a personal ad blocker", + "Build a social media dashboard", + "Sendgrid service opensource analogue", +]; + +export const StepOne = ({ nextStep, setTemplateInfo, templateInfo }: any) => { + // Get servers from the API + const { data: servers } = api.server.withSSHKey.useQuery(); + + const handleExampleClick = (example: string) => { + setTemplateInfo({ ...templateInfo, userInput: example }); + }; + return ( +
+
+
+

Step 1: Describe Your Needs

+
+ +