diff --git a/.circleci/config.yml b/.circleci/config.yml index ff3a65c93..ba405c22e 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -53,7 +53,7 @@ jobs: combine-manifests: docker: - - image: cimg/node:18.18.0 + - image: cimg/node:20.9.0 steps: - checkout - setup_remote_docker @@ -99,14 +99,14 @@ workflows: only: - main - canary - - fix/nixpacks-version + - feat/add-sidebar - build-arm64: filters: branches: only: - main - canary - - fix/nixpacks-version + - feat/add-sidebar - combine-manifests: requires: - build-amd64 @@ -116,4 +116,4 @@ workflows: only: - main - canary - - fix/nixpacks-version + - feat/add-sidebar diff --git a/.github/sponsors/its.png b/.github/sponsors/its.png new file mode 100644 index 000000000..85e7ad71a Binary files /dev/null and b/.github/sponsors/its.png differ diff --git a/.github/sponsors/light-node.webp b/.github/sponsors/light-node.webp new file mode 100644 index 000000000..56729452c Binary files /dev/null and b/.github/sponsors/light-node.webp differ diff --git a/.github/workflows/pull-request.yml b/.github/workflows/pull-request.yml index ad409e69f..2ac542296 100644 --- a/.github/workflows/pull-request.yml +++ b/.github/workflows/pull-request.yml @@ -12,7 +12,7 @@ jobs: - uses: pnpm/action-setup@v4 - uses: actions/setup-node@v4 with: - node-version: 18.18.0 + node-version: 20.9.0 cache: "pnpm" - run: pnpm install --frozen-lockfile - run: pnpm run server:build @@ -26,7 +26,7 @@ jobs: - uses: pnpm/action-setup@v4 - uses: actions/setup-node@v4 with: - node-version: 18.18.0 + node-version: 20.9.0 cache: "pnpm" - run: pnpm install --frozen-lockfile - run: pnpm run server:build @@ -39,7 +39,7 @@ jobs: - uses: pnpm/action-setup@v4 - uses: actions/setup-node@v4 with: - node-version: 18.18.0 + node-version: 20.9.0 cache: "pnpm" - run: pnpm install --frozen-lockfile - run: pnpm run server:build diff --git a/.nvmrc b/.nvmrc index 67a228a44..43bff1f8c 100644 --- a/.nvmrc +++ b/.nvmrc @@ -1 +1 @@ -18.18.0 \ No newline at end of file +20.9.0 \ No newline at end of file diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 91bb70494..19ee38dc5 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -14,12 +14,10 @@ We have a few guidelines to follow when contributing to this project: ## Commit Convention - Before you create a Pull Request, please make sure your commit message follows the [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/) specification. ### Commit Message Format - ``` [optional scope]: @@ -54,6 +52,8 @@ feat: add new feature Before you start, please make the clone based on the `canary` branch, since the `main` branch is the source of truth and should always reflect the latest stable release, also the PRs will be merged to the `canary` branch. +We use Node v20.9.0 + ```bash git clone https://github.com/dokploy/dokploy.git cd dokploy @@ -73,9 +73,10 @@ Run the command that will spin up all the required services and files. pnpm run dokploy:setup ``` -Run this script +Run this script + ```bash -pnpm run server:script +pnpm run server:script ``` Now run the development server. @@ -169,6 +170,7 @@ Let's take the example of `plausible` template. ```typescript // EXAMPLE import { + generateBase64, generateHash, generateRandomDomain, type Template, @@ -200,8 +202,8 @@ export function generate(schema: Schema): Template { const mounts: Template["mounts"] = [ { - mountPath: "./clickhouse/clickhouse-config.xml", - content: `some content......`, + filePath: "./clickhouse/clickhouse-config.xml", + content: "some content......", }, ]; @@ -247,4 +249,3 @@ export function generate(schema: Schema): Template { ## Docs & Website To contribute to the Dokploy docs or website, please go to this [repository](https://github.com/Dokploy/website). - diff --git a/Dockerfile b/Dockerfile index 51be6469f..986ceb595 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM node:18-slim AS base +FROM node:20-slim AS base ENV PNPM_HOME="/pnpm" ENV PATH="$PNPM_HOME:$PATH" RUN corepack enable @@ -7,7 +7,7 @@ FROM base AS build COPY . /usr/src/app WORKDIR /usr/src/app -RUN apt-get update && apt-get install -y python3 make g++ git && rm -rf /var/lib/apt/lists/* +RUN apt-get update && apt-get install -y python3 make g++ git python3-pip pkg-config libsecret-1-dev && rm -rf /var/lib/apt/lists/* # Install dependencies RUN --mount=type=cache,id=pnpm,target=/pnpm/store pnpm install --frozen-lockfile @@ -29,7 +29,7 @@ WORKDIR /app # Set production ENV NODE_ENV=production -RUN apt-get update && apt-get install -y curl unzip apache2-utils && rm -rf /var/lib/apt/lists/* +RUN apt-get update && apt-get install -y curl unzip apache2-utils iproute2 && rm -rf /var/lib/apt/lists/* # Copy only the necessary files COPY --from=build /prod/dokploy/.next ./.next diff --git a/Dockerfile.cloud b/Dockerfile.cloud index 020ea3d69..2cc050021 100644 --- a/Dockerfile.cloud +++ b/Dockerfile.cloud @@ -1,4 +1,4 @@ -FROM node:18-slim AS base +FROM node:20-slim AS base ENV PNPM_HOME="/pnpm" ENV PATH="$PNPM_HOME:$PATH" RUN corepack enable @@ -7,7 +7,7 @@ FROM base AS build COPY . /usr/src/app WORKDIR /usr/src/app -RUN apt-get update && apt-get install -y python3 make g++ git && rm -rf /var/lib/apt/lists/* +RUN apt-get update && apt-get install -y python3 make g++ git python3-pip pkg-config libsecret-1-dev && rm -rf /var/lib/apt/lists/* # Install dependencies RUN --mount=type=cache,id=pnpm,target=/pnpm/store pnpm --filter=@dokploy/server --filter=./apps/dokploy install --frozen-lockfile diff --git a/Dockerfile.schedule b/Dockerfile.schedule index 5eca3420e..d04af70f2 100644 --- a/Dockerfile.schedule +++ b/Dockerfile.schedule @@ -1,4 +1,4 @@ -FROM node:18-slim AS base +FROM node:20-slim AS base ENV PNPM_HOME="/pnpm" ENV PATH="$PNPM_HOME:$PATH" RUN corepack enable @@ -7,7 +7,7 @@ FROM base AS build COPY . /usr/src/app WORKDIR /usr/src/app -RUN apt-get update && apt-get install -y python3 make g++ git && rm -rf /var/lib/apt/lists/* +RUN apt-get update && apt-get install -y python3 make g++ git python3-pip pkg-config libsecret-1-dev && rm -rf /var/lib/apt/lists/* # Install dependencies RUN --mount=type=cache,id=pnpm,target=/pnpm/store pnpm --filter=@dokploy/server --filter=./apps/schedules install --frozen-lockfile diff --git a/Dockerfile.server b/Dockerfile.server index a25b22e52..98f74966f 100644 --- a/Dockerfile.server +++ b/Dockerfile.server @@ -1,4 +1,4 @@ -FROM node:18-slim AS base +FROM node:20-slim AS base ENV PNPM_HOME="/pnpm" ENV PATH="$PNPM_HOME:$PATH" RUN corepack enable @@ -7,7 +7,7 @@ FROM base AS build COPY . /usr/src/app WORKDIR /usr/src/app -RUN apt-get update && apt-get install -y python3 make g++ git && rm -rf /var/lib/apt/lists/* +RUN apt-get update && apt-get install -y python3 make g++ git python3-pip pkg-config libsecret-1-dev && rm -rf /var/lib/apt/lists/* # Install dependencies RUN --mount=type=cache,id=pnpm,target=/pnpm/store pnpm --filter=@dokploy/server --filter=./apps/api install --frozen-lockfile diff --git a/README.md b/README.md index ff25f7d0d..ca48852c3 100644 --- a/README.md +++ b/README.md @@ -71,6 +71,9 @@ For detailed documentation, visit [docs.dokploy.com](https://docs.dokploy.com). Mandarin + + Lightnode + ### Premium Supporters šŸ„‡ @@ -89,6 +92,7 @@ For detailed documentation, visit [docs.dokploy.com](https://docs.dokploy.com). Lightspeed.run Cloudblast.io Startupfame +Itsdb-center ### Community Backers šŸ¤ diff --git a/apps/api/package.json b/apps/api/package.json index 0da226afa..56ea56952 100644 --- a/apps/api/package.json +++ b/apps/api/package.json @@ -5,7 +5,7 @@ "scripts": { "dev": "PORT=4000 tsx watch src/index.ts", "build": "tsc --project tsconfig.json", - "start": "node --experimental-specifier-resolution=node dist/index.js", + "start": "node dist/index.js", "typecheck": "tsc --noEmit" }, "dependencies": { diff --git a/apps/api/src/index.ts b/apps/api/src/index.ts index bf91b0400..4b405e9c7 100644 --- a/apps/api/src/index.ts +++ b/apps/api/src/index.ts @@ -4,9 +4,9 @@ import "dotenv/config"; import { zValidator } from "@hono/zod-validator"; import { Queue } from "@nerimity/mimiqueue"; import { createClient } from "redis"; -import { logger } from "./logger"; -import { type DeployJob, deployJobSchema } from "./schema"; -import { deploy } from "./utils"; +import { logger } from "./logger.js"; +import { type DeployJob, deployJobSchema } from "./schema.js"; +import { deploy } from "./utils.js"; const app = new Hono(); const redisClient = createClient({ diff --git a/apps/dokploy/.nvmrc b/apps/dokploy/.nvmrc index 67a228a44..43bff1f8c 100644 --- a/apps/dokploy/.nvmrc +++ b/apps/dokploy/.nvmrc @@ -1 +1 @@ -18.18.0 \ No newline at end of file +20.9.0 \ No newline at end of file diff --git a/apps/dokploy/components/auth/login-2fa.tsx b/apps/dokploy/components/auth/login-2fa.tsx index dcb004f1d..6a11268e0 100644 --- a/apps/dokploy/components/auth/login-2fa.tsx +++ b/apps/dokploy/components/auth/login-2fa.tsx @@ -13,10 +13,12 @@ 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"; @@ -93,19 +95,25 @@ export const Login2FA = ({ authId }: Props) => { control={form.control} name="pin" render={({ field }) => ( - + Pin - - - - - - - - - - +
+ + + + + + + + + + +
Please enter the 6 digits code provided by your authenticator 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 f9e70b422..4cd839a11 100644 --- a/apps/dokploy/components/dashboard/application/advanced/general/add-command.tsx +++ b/apps/dokploy/components/dashboard/application/advanced/general/add-command.tsx @@ -81,7 +81,8 @@ export const AddCommand = ({ applicationId }: Props) => {
Run Command - Run a custom command in the container + Run a custom command in the container after the application + initialized
diff --git a/apps/dokploy/components/dashboard/application/advanced/ports/delete-port.tsx b/apps/dokploy/components/dashboard/application/advanced/ports/delete-port.tsx deleted file mode 100644 index e1723f6eb..000000000 --- a/apps/dokploy/components/dashboard/application/advanced/ports/delete-port.tsx +++ /dev/null @@ -1,63 +0,0 @@ -import { - AlertDialog, - AlertDialogAction, - AlertDialogCancel, - AlertDialogContent, - AlertDialogDescription, - AlertDialogFooter, - AlertDialogHeader, - AlertDialogTitle, - AlertDialogTrigger, -} from "@/components/ui/alert-dialog"; -import { Button } from "@/components/ui/button"; -import { api } from "@/utils/api"; -import { TrashIcon } from "lucide-react"; -import { toast } from "sonner"; - -interface Props { - portId: string; -} - -export const DeletePort = ({ portId }: Props) => { - const utils = api.useUtils(); - const { mutateAsync, isLoading } = api.port.delete.useMutation(); - return ( - - - - - - - Are you absolutely sure? - - This action cannot be undone. This will permanently delete the port - - - - Cancel - { - await mutateAsync({ - portId, - }) - .then((data) => { - utils.application.one.invalidate({ - applicationId: data?.applicationId, - }); - - toast.success("Port delete successfully"); - }) - .catch(() => { - toast.error("Error deleting the port"); - }); - }} - > - Confirm - - - - - ); -}; diff --git a/apps/dokploy/components/dashboard/application/advanced/ports/add-port.tsx b/apps/dokploy/components/dashboard/application/advanced/ports/handle-ports.tsx similarity index 82% rename from apps/dokploy/components/dashboard/application/advanced/ports/add-port.tsx rename to apps/dokploy/components/dashboard/application/advanced/ports/handle-ports.tsx index 1fc34cac5..c9758e37f 100644 --- a/apps/dokploy/components/dashboard/application/advanced/ports/add-port.tsx +++ b/apps/dokploy/components/dashboard/application/advanced/ports/handle-ports.tsx @@ -27,7 +27,7 @@ import { } from "@/components/ui/select"; import { api } from "@/utils/api"; import { zodResolver } from "@hookform/resolvers/zod"; -import { PlusIcon } from "lucide-react"; +import { PenBoxIcon, PlusIcon } from "lucide-react"; import { useEffect, useState } from "react"; import { useForm } from "react-hook-form"; import { toast } from "sonner"; @@ -45,18 +45,29 @@ type AddPort = z.infer; interface Props { applicationId: string; + portId?: string; children?: React.ReactNode; } -export const AddPort = ({ +export const HandlePorts = ({ applicationId, + portId, children = , }: Props) => { const [isOpen, setIsOpen] = useState(false); const utils = api.useUtils(); - const { mutateAsync, isLoading, error, isError } = - api.port.create.useMutation(); + const { data } = api.port.one.useQuery( + { + portId: portId ?? "", + }, + { + enabled: !!portId, + }, + ); + const { mutateAsync, isLoading, error, isError } = portId + ? api.port.update.useMutation() + : api.port.create.useMutation(); const form = useForm({ defaultValues: { @@ -68,32 +79,46 @@ export const AddPort = ({ useEffect(() => { form.reset({ - publishedPort: 0, - targetPort: 0, + publishedPort: data?.publishedPort ?? 0, + targetPort: data?.targetPort ?? 0, + protocol: data?.protocol ?? "tcp", }); - }, [form, form.reset, form.formState.isSubmitSuccessful]); + }, [form, form.reset, form.formState.isSubmitSuccessful, data]); const onSubmit = async (data: AddPort) => { await mutateAsync({ applicationId, ...data, + portId: portId || "", }) .then(async () => { - toast.success("Port Created"); + toast.success(portId ? "Port Updated" : "Port Created"); await utils.application.one.invalidate({ applicationId, }); setIsOpen(false); }) .catch(() => { - toast.error("Error creating the port"); + toast.error( + portId ? "Error updating the port" : "Error creating the port", + ); }); }; return ( - + {portId ? ( + + ) : ( + + )} @@ -204,7 +229,7 @@ export const AddPort = ({ form="hook-form-add-port" type="submit" > - Create + {portId ? "Update" : "Create"} 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 1ab804fb4..a2c6ddcf1 100644 --- a/apps/dokploy/components/dashboard/application/advanced/ports/show-port.tsx +++ b/apps/dokploy/components/dashboard/application/advanced/ports/show-port.tsx @@ -1,4 +1,6 @@ import { AlertBlock } from "@/components/shared/alert-block"; +import { DialogAction } from "@/components/shared/dialog-action"; +import { Button } from "@/components/ui/button"; import { Card, CardContent, @@ -7,23 +9,25 @@ import { CardTitle, } from "@/components/ui/card"; import { api } from "@/utils/api"; -import { Rss } from "lucide-react"; +import { Rss, Trash2 } from "lucide-react"; import React from "react"; -import { AddPort } from "./add-port"; -import { DeletePort } from "./delete-port"; -import { UpdatePort } from "./update-port"; +import { toast } from "sonner"; +import { HandlePorts } from "./handle-ports"; interface Props { applicationId: string; } export const ShowPorts = ({ applicationId }: Props) => { - const { data } = api.application.one.useQuery( + const { data, refetch } = api.application.one.useQuery( { applicationId, }, { enabled: !!applicationId }, ); + const { mutateAsync: deletePort, isLoading: isRemoving } = + api.port.delete.useMutation(); + return ( @@ -35,7 +39,7 @@ export const ShowPorts = ({ applicationId }: Props) => { {data && data?.ports.length > 0 && ( - Add Port + Add Port )} @@ -45,7 +49,7 @@ export const ShowPorts = ({ applicationId }: Props) => { No ports configured - Add Port + Add Port ) : (
@@ -78,8 +82,36 @@ export const ShowPorts = ({ applicationId }: Props) => {
- - + + { + await deletePort({ + portId: port.portId, + }) + .then(() => { + refetch(); + toast.success("Port deleted successfully"); + }) + .catch(() => { + toast.error("Error deleting port"); + }); + }} + > + +
diff --git a/apps/dokploy/components/dashboard/application/advanced/ports/update-port.tsx b/apps/dokploy/components/dashboard/application/advanced/ports/update-port.tsx deleted file mode 100644 index e37a3c5bd..000000000 --- a/apps/dokploy/components/dashboard/application/advanced/ports/update-port.tsx +++ /dev/null @@ -1,195 +0,0 @@ -import { AlertBlock } from "@/components/shared/alert-block"; -import { Button } from "@/components/ui/button"; -import { - Dialog, - DialogContent, - DialogDescription, - DialogFooter, - DialogHeader, - DialogTitle, - DialogTrigger, -} from "@/components/ui/dialog"; -import { - Form, - FormControl, - FormField, - FormItem, - FormLabel, - FormMessage, -} from "@/components/ui/form"; -import { Input, NumberInput } from "@/components/ui/input"; -import { - Select, - SelectContent, - SelectItem, - SelectTrigger, - SelectValue, -} from "@/components/ui/select"; -import { api } from "@/utils/api"; -import { zodResolver } from "@hookform/resolvers/zod"; -import { PenBoxIcon, Pencil } from "lucide-react"; -import { useEffect, useState } from "react"; -import { useForm } from "react-hook-form"; -import { toast } from "sonner"; -import { z } from "zod"; - -const UpdatePortSchema = z.object({ - publishedPort: z.number().int().min(1).max(65535), - targetPort: z.number().int().min(1).max(65535), - protocol: z.enum(["tcp", "udp"], { - required_error: "Protocol is required", - invalid_type_error: "Protocol must be a valid protocol", - }), -}); - -type UpdatePort = z.infer; - -interface Props { - portId: string; -} - -export const UpdatePort = ({ portId }: Props) => { - const [isOpen, setIsOpen] = useState(false); - const utils = api.useUtils(); - const { data } = api.port.one.useQuery( - { - portId, - }, - { - enabled: !!portId, - }, - ); - - const { mutateAsync, isLoading, error, isError } = - api.port.update.useMutation(); - - const form = useForm({ - defaultValues: {}, - resolver: zodResolver(UpdatePortSchema), - }); - - useEffect(() => { - if (data) { - form.reset({ - publishedPort: data.publishedPort, - targetPort: data.targetPort, - protocol: data.protocol, - }); - } - }, [form, form.reset, data]); - - const onSubmit = async (data: UpdatePort) => { - await mutateAsync({ - portId, - publishedPort: data.publishedPort, - targetPort: data.targetPort, - protocol: data.protocol, - }) - .then(async (response) => { - toast.success("Port Updated"); - await utils.application.one.invalidate({ - applicationId: response?.applicationId, - }); - setIsOpen(false); - }) - .catch(() => { - toast.error("Error updating the port"); - }); - }; - - return ( - - - - - - - Update - Update the port - - {isError && {error?.message}} - -
- -
- ( - - Published Port - - - - - - - )} - /> - - ( - - Target Port - - - - - - - )} - /> - { - return ( - - Protocol - - - - ); - }} - /> -
-
- - - - - -
-
- ); -}; diff --git a/apps/dokploy/components/dashboard/application/advanced/redirects/delete-redirect.tsx b/apps/dokploy/components/dashboard/application/advanced/redirects/delete-redirect.tsx deleted file mode 100644 index 49f289b29..000000000 --- a/apps/dokploy/components/dashboard/application/advanced/redirects/delete-redirect.tsx +++ /dev/null @@ -1,66 +0,0 @@ -import { - AlertDialog, - AlertDialogAction, - AlertDialogCancel, - AlertDialogContent, - AlertDialogDescription, - AlertDialogFooter, - AlertDialogHeader, - AlertDialogTitle, - AlertDialogTrigger, -} from "@/components/ui/alert-dialog"; -import { Button } from "@/components/ui/button"; -import { api } from "@/utils/api"; -import { TrashIcon } from "lucide-react"; -import { toast } from "sonner"; - -interface Props { - redirectId: string; -} - -export const DeleteRedirect = ({ redirectId }: Props) => { - const utils = api.useUtils(); - const { mutateAsync, isLoading } = api.redirects.delete.useMutation(); - return ( - - - - - - - Are you absolutely sure? - - This action cannot be undone. This will permanently delete the - redirect - - - - Cancel - { - await mutateAsync({ - redirectId, - }) - .then((data) => { - utils.application.one.invalidate({ - applicationId: data?.applicationId, - }); - utils.application.readTraefikConfig.invalidate({ - applicationId: data?.applicationId, - }); - toast.success("Redirect delete successfully"); - }) - .catch(() => { - toast.error("Error deleting the redirect"); - }); - }} - > - Confirm - - - - - ); -}; diff --git a/apps/dokploy/components/dashboard/application/advanced/redirects/add-redirect.tsx b/apps/dokploy/components/dashboard/application/advanced/redirects/handle-redirect.tsx similarity index 83% rename from apps/dokploy/components/dashboard/application/advanced/redirects/add-redirect.tsx rename to apps/dokploy/components/dashboard/application/advanced/redirects/handle-redirect.tsx index abeb21089..5d91d580d 100644 --- a/apps/dokploy/components/dashboard/application/advanced/redirects/add-redirect.tsx +++ b/apps/dokploy/components/dashboard/application/advanced/redirects/handle-redirect.tsx @@ -31,7 +31,7 @@ import { Separator } from "@/components/ui/separator"; import { Switch } from "@/components/ui/switch"; import { api } from "@/utils/api"; import { zodResolver } from "@hookform/resolvers/zod"; -import { PlusIcon } from "lucide-react"; +import { PenBoxIcon, PlusIcon } from "lucide-react"; import { useEffect, useState } from "react"; import { useForm } from "react-hook-form"; import { toast } from "sonner"; @@ -77,19 +77,32 @@ const redirectPresets = [ interface Props { applicationId: string; + redirectId?: string; children?: React.ReactNode; } -export const AddRedirect = ({ +export const HandleRedirect = ({ applicationId, + redirectId, children = , }: Props) => { const [isOpen, setIsOpen] = useState(false); const [presetSelected, setPresetSelected] = useState(""); + + const { data, refetch } = api.redirects.one.useQuery( + { + redirectId: redirectId || "", + }, + { + enabled: !!redirectId, + }, + ); + const utils = api.useUtils(); - const { mutateAsync, isLoading, error, isError } = - api.redirects.create.useMutation(); + const { mutateAsync, isLoading, error, isError } = redirectId + ? api.redirects.update.useMutation() + : api.redirects.create.useMutation(); const form = useForm({ defaultValues: { @@ -102,29 +115,35 @@ export const AddRedirect = ({ useEffect(() => { form.reset({ - permanent: false, - regex: "", - replacement: "", + permanent: data?.permanent || false, + regex: data?.regex || "", + replacement: data?.replacement || "", }); - }, [form, form.reset, form.formState.isSubmitSuccessful]); + }, [form, form.reset, form.formState.isSubmitSuccessful, data]); const onSubmit = async (data: AddRedirect) => { await mutateAsync({ applicationId, ...data, + redirectId: redirectId || "", }) .then(async () => { - toast.success("Redirect Created"); + toast.success(redirectId ? "Redirect Updated" : "Redirect Created"); await utils.application.one.invalidate({ applicationId, }); + refetch(); await utils.application.readTraefikConfig.invalidate({ applicationId, }); onDialogToggle(false); }) .catch(() => { - toast.error("Error creating the redirect"); + toast.error( + redirectId + ? "Error updating the redirect" + : "Error creating the redirect", + ); }); }; @@ -148,7 +167,17 @@ export const AddRedirect = ({ return ( - + {redirectId ? ( + + ) : ( + + )} @@ -243,7 +272,7 @@ export const AddRedirect = ({ form="hook-form-add-redirect" type="submit" > - Create + {redirectId ? "Update" : "Create"} 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 9a8325fc8..4ee597917 100644 --- a/apps/dokploy/components/dashboard/application/advanced/redirects/show-redirects.tsx +++ b/apps/dokploy/components/dashboard/application/advanced/redirects/show-redirects.tsx @@ -1,3 +1,5 @@ +import { DialogAction } from "@/components/shared/dialog-action"; +import { Button } from "@/components/ui/button"; import { Card, CardContent, @@ -6,23 +8,28 @@ import { CardTitle, } from "@/components/ui/card"; import { api } from "@/utils/api"; -import { Split } from "lucide-react"; +import { Split, Trash2 } from "lucide-react"; import React from "react"; -import { AddRedirect } from "./add-redirect"; -import { DeleteRedirect } from "./delete-redirect"; -import { UpdateRedirect } from "./update-redirect"; +import { toast } from "sonner"; +import { HandleRedirect } from "./handle-redirect"; + interface Props { applicationId: string; } export const ShowRedirects = ({ applicationId }: Props) => { - const { data } = api.application.one.useQuery( + const { data, refetch } = api.application.one.useQuery( { applicationId, }, { enabled: !!applicationId }, ); + const { mutateAsync: deleteRedirect, isLoading: isRemoving } = + api.redirects.delete.useMutation(); + + const utils = api.useUtils(); + return ( @@ -35,7 +42,9 @@ export const ShowRedirects = ({ applicationId }: Props) => { {data && data?.redirects.length > 0 && ( - Add Redirect + + Add Redirect + )} @@ -45,9 +54,9 @@ export const ShowRedirects = ({ applicationId }: Props) => { No redirects configured - + Add Redirect - + ) : (
@@ -76,8 +85,40 @@ export const ShowRedirects = ({ applicationId }: Props) => {
- - + + + { + await deleteRedirect({ + redirectId: redirect.redirectId, + }) + .then(() => { + refetch(); + utils.application.readTraefikConfig.invalidate({ + applicationId, + }); + toast.success("Redirect deleted successfully"); + }) + .catch(() => { + toast.error("Error deleting redirect"); + }); + }} + > + +
diff --git a/apps/dokploy/components/dashboard/application/advanced/redirects/update-redirect.tsx b/apps/dokploy/components/dashboard/application/advanced/redirects/update-redirect.tsx deleted file mode 100644 index c41258466..000000000 --- a/apps/dokploy/components/dashboard/application/advanced/redirects/update-redirect.tsx +++ /dev/null @@ -1,182 +0,0 @@ -import { AlertBlock } from "@/components/shared/alert-block"; -import { Button } from "@/components/ui/button"; -import { - Dialog, - DialogContent, - DialogDescription, - DialogFooter, - DialogHeader, - DialogTitle, - DialogTrigger, -} from "@/components/ui/dialog"; -import { - Form, - FormControl, - FormDescription, - FormField, - FormItem, - FormLabel, - FormMessage, -} from "@/components/ui/form"; -import { Input } from "@/components/ui/input"; -import { Switch } from "@/components/ui/switch"; -import { api } from "@/utils/api"; -import { zodResolver } from "@hookform/resolvers/zod"; -import { PenBoxIcon, Pencil } from "lucide-react"; -import { useEffect, useState } from "react"; -import { useForm } from "react-hook-form"; -import { toast } from "sonner"; -import { z } from "zod"; -const UpdateRedirectSchema = z.object({ - regex: z.string().min(1, "Regex required"), - permanent: z.boolean().default(false), - replacement: z.string().min(1, "Replacement required"), -}); - -type UpdateRedirect = z.infer; - -interface Props { - redirectId: string; -} - -export const UpdateRedirect = ({ redirectId }: Props) => { - const utils = api.useUtils(); - const [isOpen, setIsOpen] = useState(false); - const { data } = api.redirects.one.useQuery( - { - redirectId, - }, - { - enabled: !!redirectId, - }, - ); - - const { mutateAsync, isLoading, error, isError } = - api.redirects.update.useMutation(); - - const form = useForm({ - defaultValues: { - permanent: false, - regex: "", - replacement: "", - }, - resolver: zodResolver(UpdateRedirectSchema), - }); - - useEffect(() => { - if (data) { - form.reset({ - permanent: data.permanent || false, - regex: data.regex || "", - replacement: data.replacement || "", - }); - } - }, [form, form.reset, data]); - - const onSubmit = async (data: UpdateRedirect) => { - await mutateAsync({ - redirectId, - permanent: data.permanent, - regex: data.regex, - replacement: data.replacement, - }) - .then(async (response) => { - toast.success("Redirect Updated"); - await utils.application.one.invalidate({ - applicationId: response?.applicationId, - }); - setIsOpen(false); - }) - .catch(() => { - toast.error("Error updating the redirect"); - }); - }; - - return ( - - - - - - - Update - Update the redirect - - {isError && {error?.message}} - -
- -
- ( - - Regex - - - - - - - )} - /> - ( - - Replacement - - - - - - - )} - /> - - ( - -
- Permanent - - Set the permanent option to true to apply a permanent - redirection. - -
- - - -
- )} - /> -
-
- - - - - -
-
- ); -}; diff --git a/apps/dokploy/components/dashboard/application/advanced/security/delete-security.tsx b/apps/dokploy/components/dashboard/application/advanced/security/delete-security.tsx deleted file mode 100644 index b44b2ff82..000000000 --- a/apps/dokploy/components/dashboard/application/advanced/security/delete-security.tsx +++ /dev/null @@ -1,66 +0,0 @@ -import { - AlertDialog, - AlertDialogAction, - AlertDialogCancel, - AlertDialogContent, - AlertDialogDescription, - AlertDialogFooter, - AlertDialogHeader, - AlertDialogTitle, - AlertDialogTrigger, -} from "@/components/ui/alert-dialog"; -import { Button } from "@/components/ui/button"; -import { api } from "@/utils/api"; -import { TrashIcon } from "lucide-react"; -import { toast } from "sonner"; - -interface Props { - securityId: string; -} - -export const DeleteSecurity = ({ securityId }: Props) => { - const utils = api.useUtils(); - const { mutateAsync, isLoading } = api.security.delete.useMutation(); - return ( - - - - - - - Are you absolutely sure? - - This action cannot be undone. This will permanently delete the - security - - - - Cancel - { - await mutateAsync({ - securityId, - }) - .then((data) => { - utils.application.one.invalidate({ - applicationId: data?.applicationId, - }); - utils.application.readTraefikConfig.invalidate({ - applicationId: data?.applicationId, - }); - toast.success("Security delete successfully"); - }) - .catch(() => { - toast.error("Error deleting the security"); - }); - }} - > - Confirm - - - - - ); -}; diff --git a/apps/dokploy/components/dashboard/application/advanced/security/add-security.tsx b/apps/dokploy/components/dashboard/application/advanced/security/handle-security.tsx similarity index 72% rename from apps/dokploy/components/dashboard/application/advanced/security/add-security.tsx rename to apps/dokploy/components/dashboard/application/advanced/security/handle-security.tsx index 56a1a6a49..e7bc0cd1f 100644 --- a/apps/dokploy/components/dashboard/application/advanced/security/add-security.tsx +++ b/apps/dokploy/components/dashboard/application/advanced/security/handle-security.tsx @@ -20,7 +20,7 @@ import { import { Input } from "@/components/ui/input"; import { api } from "@/utils/api"; import { zodResolver } from "@hookform/resolvers/zod"; -import { PlusIcon } from "lucide-react"; +import { PenBoxIcon, PlusIcon } from "lucide-react"; import { useEffect, useState } from "react"; import { useForm } from "react-hook-form"; import { toast } from "sonner"; @@ -35,17 +35,29 @@ type AddSecurity = z.infer; interface Props { applicationId: string; + securityId?: string; children?: React.ReactNode; } -export const AddSecurity = ({ +export const HandleSecurity = ({ applicationId, + securityId, children = , }: Props) => { const utils = api.useUtils(); const [isOpen, setIsOpen] = useState(false); - const { mutateAsync, isLoading, error, isError } = - api.security.create.useMutation(); + const { data } = api.security.one.useQuery( + { + securityId: securityId ?? "", + }, + { + enabled: !!securityId, + }, + ); + + const { mutateAsync, isLoading, error, isError } = securityId + ? api.security.update.useMutation() + : api.security.create.useMutation(); const form = useForm({ defaultValues: { @@ -56,16 +68,20 @@ export const AddSecurity = ({ }); useEffect(() => { - form.reset(); - }, [form, form.reset, form.formState.isSubmitSuccessful]); + form.reset({ + username: data?.username || "", + password: data?.password || "", + }); + }, [form, form.reset, form.formState.isSubmitSuccessful, data]); const onSubmit = async (data: AddSecurity) => { await mutateAsync({ applicationId, ...data, + securityId: securityId || "", }) .then(async () => { - toast.success("Security Created"); + toast.success(securityId ? "Security Updated" : "Security Created"); await utils.application.one.invalidate({ applicationId, }); @@ -75,20 +91,34 @@ export const AddSecurity = ({ setIsOpen(false); }) .catch(() => { - toast.error("Error creating security"); + toast.error( + securityId + ? "Error updating the security" + : "Error creating security", + ); }); }; return ( - + {securityId ? ( + + ) : ( + + )} Security - Add security to your application + {securityId ? "Update" : "Add"} security to your application {isError && {error?.message}} @@ -137,7 +167,7 @@ export const AddSecurity = ({ form="hook-form-add-security" type="submit" > - Create + {securityId ? "Update" : "Create"} 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 5c02bf76d..33022c097 100644 --- a/apps/dokploy/components/dashboard/application/advanced/security/show-security.tsx +++ b/apps/dokploy/components/dashboard/application/advanced/security/show-security.tsx @@ -1,3 +1,5 @@ +import { DialogAction } from "@/components/shared/dialog-action"; +import { Button } from "@/components/ui/button"; import { Card, CardContent, @@ -6,23 +8,27 @@ import { CardTitle, } from "@/components/ui/card"; import { api } from "@/utils/api"; -import { LockKeyhole } from "lucide-react"; +import { LockKeyhole, Trash2 } from "lucide-react"; import React from "react"; -import { AddSecurity } from "./add-security"; -import { DeleteSecurity } from "./delete-security"; -import { UpdateSecurity } from "./update-security"; +import { toast } from "sonner"; +import { HandleSecurity } from "./handle-security"; + interface Props { applicationId: string; } export const ShowSecurity = ({ applicationId }: Props) => { - const { data } = api.application.one.useQuery( + const { data, refetch } = api.application.one.useQuery( { applicationId, }, { enabled: !!applicationId }, ); + const { mutateAsync: deleteSecurity, isLoading: isRemoving } = + api.security.delete.useMutation(); + + const utils = api.useUtils(); return ( @@ -32,7 +38,9 @@ export const ShowSecurity = ({ applicationId }: Props) => { {data && data?.security.length > 0 && ( - Add Security + + Add Security + )} @@ -42,9 +50,9 @@ export const ShowSecurity = ({ applicationId }: Props) => { No security configured - + Add Security - + ) : (
@@ -67,8 +75,39 @@ export const ShowSecurity = ({ applicationId }: Props) => {
- - + + { + await deleteSecurity({ + securityId: security.securityId, + }) + .then(() => { + refetch(); + utils.application.readTraefikConfig.invalidate({ + applicationId, + }); + toast.success("Security deleted successfully"); + }) + .catch(() => { + toast.error("Error deleting security"); + }); + }} + > + +
diff --git a/apps/dokploy/components/dashboard/application/advanced/security/update-security.tsx b/apps/dokploy/components/dashboard/application/advanced/security/update-security.tsx deleted file mode 100644 index cec1db0d7..000000000 --- a/apps/dokploy/components/dashboard/application/advanced/security/update-security.tsx +++ /dev/null @@ -1,155 +0,0 @@ -import { AlertBlock } from "@/components/shared/alert-block"; -import { Button } from "@/components/ui/button"; -import { - Dialog, - DialogContent, - DialogDescription, - DialogFooter, - DialogHeader, - DialogTitle, - DialogTrigger, -} from "@/components/ui/dialog"; -import { - Form, - FormControl, - FormField, - FormItem, - FormLabel, - FormMessage, -} from "@/components/ui/form"; -import { Input } from "@/components/ui/input"; -import { api } from "@/utils/api"; -import { zodResolver } from "@hookform/resolvers/zod"; -import { PenBoxIcon, Pencil } from "lucide-react"; -import { useEffect, useState } from "react"; -import { useForm } from "react-hook-form"; -import { toast } from "sonner"; -import { z } from "zod"; - -const UpdateSecuritySchema = z.object({ - username: z.string().min(1, "Username is required"), - password: z.string().min(1, "Password is required"), -}); - -type UpdateSecurity = z.infer; - -interface Props { - securityId: string; -} - -export const UpdateSecurity = ({ securityId }: Props) => { - const [isOpen, setIsOpen] = useState(false); - const utils = api.useUtils(); - const { data } = api.security.one.useQuery( - { - securityId, - }, - { - enabled: !!securityId, - }, - ); - - const { mutateAsync, isLoading, error, isError } = - api.security.update.useMutation(); - - const form = useForm({ - defaultValues: { - username: "", - password: "", - }, - resolver: zodResolver(UpdateSecuritySchema), - }); - - useEffect(() => { - if (data) { - form.reset({ - username: data.username || "", - password: data.password || "", - }); - } - }, [form, form.reset, data]); - - const onSubmit = async (data: UpdateSecurity) => { - await mutateAsync({ - securityId, - username: data.username, - password: data.password, - }) - .then(async (response) => { - toast.success("Security Updated"); - await utils.application.one.invalidate({ - applicationId: response?.applicationId, - }); - setIsOpen(false); - }) - .catch(() => { - toast.error("Error updating the security"); - }); - }; - - return ( - - - - - - - Update - Update the security - - {isError && {error?.message}} - -
- -
- ( - - Username - - - - - - - )} - /> - ( - - Password - - - - - - - )} - /> -
-
- - - - - -
-
- ); -}; diff --git a/apps/dokploy/components/dashboard/application/advanced/show-application-advanced-settings.tsx b/apps/dokploy/components/dashboard/application/advanced/show-application-advanced-settings.tsx deleted file mode 100644 index c603ea327..000000000 --- a/apps/dokploy/components/dashboard/application/advanced/show-application-advanced-settings.tsx +++ /dev/null @@ -1,297 +0,0 @@ -import { AlertBlock } from "@/components/shared/alert-block"; -import { Button } from "@/components/ui/button"; -import { - Card, - CardContent, - CardDescription, - CardHeader, - CardTitle, -} from "@/components/ui/card"; -import { - Form, - FormControl, - FormField, - FormItem, - FormLabel, - FormMessage, -} from "@/components/ui/form"; -import { Input } from "@/components/ui/input"; -import { api } from "@/utils/api"; -import { zodResolver } from "@hookform/resolvers/zod"; -import React, { useEffect } from "react"; -import { useForm } from "react-hook-form"; -import { toast } from "sonner"; -import { z } from "zod"; -import { - Tooltip, - TooltipContent, - TooltipProvider, - TooltipTrigger, -} from "@/components/ui/tooltip"; -import { InfoIcon } from "lucide-react"; - -const addResourcesApplication = z.object({ - memoryReservation: z.number().nullable().optional(), - cpuLimit: z.number().nullable().optional(), - memoryLimit: z.number().nullable().optional(), - cpuReservation: z.number().nullable().optional(), -}); -interface Props { - applicationId: string; -} - -type AddResourcesApplication = z.infer; - -export const ShowApplicationResources = ({ applicationId }: Props) => { - const { data, refetch } = api.application.one.useQuery( - { - applicationId, - }, - { enabled: !!applicationId }, - ); - const { mutateAsync, isLoading } = api.application.update.useMutation(); - const form = useForm({ - defaultValues: {}, - resolver: zodResolver(addResourcesApplication), - }); - - useEffect(() => { - if (data) { - form.reset({ - cpuLimit: data?.cpuLimit || undefined, - cpuReservation: data?.cpuReservation || undefined, - memoryLimit: data?.memoryLimit || undefined, - memoryReservation: data?.memoryReservation || undefined, - }); - } - }, [data, form, form.reset]); - - const onSubmit = async (formData: AddResourcesApplication) => { - await mutateAsync({ - applicationId, - cpuLimit: formData.cpuLimit || null, - cpuReservation: formData.cpuReservation || null, - memoryLimit: formData.memoryLimit || null, - memoryReservation: formData.memoryReservation || null, - }) - .then(async () => { - toast.success("Resources Updated"); - await refetch(); - }) - .catch(() => { - toast.error("Error updating the resources"); - }); - }; - return ( - - - Resources - - If you want to decrease or increase the resources to a specific. - application or database - - - - - Please remember to click Redeploy after modify the resources to apply - the changes. - -
- -
- ( - -
- Memory Reservation - - - - - - -

- Memory soft limit in bytes. Example: 256MB = - 268435456 bytes -

-
-
-
-
- - { - const value = e.target.value; - if (value === "") { - field.onChange(null); - } else { - const number = Number.parseInt(value, 10); - if (!Number.isNaN(number)) { - field.onChange(number); - } - } - }} - /> - - -
- )} - /> - - { - return ( - -
- Memory Limit - - - - - - -

- Memory hard limit in bytes. Example: 1GB = - 1073741824 bytes -

-
-
-
-
- - { - const value = e.target.value; - if (value === "") { - field.onChange(null); - } else { - const number = Number.parseInt(value, 10); - if (!Number.isNaN(number)) { - field.onChange(number); - } - } - }} - /> - - -
- ); - }} - /> - - { - return ( - -
- CPU Limit - - - - - - -

- CPU quota in units of 10^-9 CPUs. Example: 2 - CPUs = 2000000000 -

-
-
-
-
- - { - const value = e.target.value; - if (value === "") { - field.onChange(null); - } else { - const number = Number.parseInt(value, 10); - if (!Number.isNaN(number)) { - field.onChange(number); - } - } - }} - /> - - -
- ); - }} - /> - { - return ( - -
- CPU Reservation - - - - - - -

- CPU shares (relative weight). Example: 1 CPU = - 1000000000 -

-
-
-
-
- - { - const value = e.target.value; - if (value === "") { - field.onChange(null); - } else { - const number = Number.parseInt(value, 10); - if (!Number.isNaN(number)) { - field.onChange(number); - } - } - }} - /> - - -
- ); - }} - /> -
-
- -
-
- -
-
- ); -}; diff --git a/apps/dokploy/components/dashboard/mysql/advanced/show-mysql-resources.tsx b/apps/dokploy/components/dashboard/application/advanced/show-resources.tsx similarity index 71% rename from apps/dokploy/components/dashboard/mysql/advanced/show-mysql-resources.tsx rename to apps/dokploy/components/dashboard/application/advanced/show-resources.tsx index bcf5a0b90..bcf0ccbd6 100644 --- a/apps/dokploy/components/dashboard/mysql/advanced/show-mysql-resources.tsx +++ b/apps/dokploy/components/dashboard/application/advanced/show-resources.tsx @@ -16,42 +16,78 @@ import { FormMessage, } from "@/components/ui/form"; import { Input } from "@/components/ui/input"; -import { api } from "@/utils/api"; -import { zodResolver } from "@hookform/resolvers/zod"; -import React, { useEffect } from "react"; -import { useForm } from "react-hook-form"; import { + Tooltip, + TooltipContent, TooltipProvider, TooltipTrigger, - TooltipContent, - Tooltip, } from "@/components/ui/tooltip"; +import { api } from "@/utils/api"; +import { zodResolver } from "@hookform/resolvers/zod"; import { InfoIcon } from "lucide-react"; +import React, { useEffect } from "react"; +import { useForm } from "react-hook-form"; import { toast } from "sonner"; import { z } from "zod"; -const addResourcesMysql = z.object({ - memoryReservation: z.number().nullable().optional(), - cpuLimit: z.number().nullable().optional(), - memoryLimit: z.number().nullable().optional(), - cpuReservation: z.number().nullable().optional(), +const addResourcesSchema = z.object({ + memoryReservation: z.string().optional(), + cpuLimit: z.string().optional(), + memoryLimit: z.string().optional(), + cpuReservation: z.string().optional(), }); + +export type ServiceType = + | "postgres" + | "mongo" + | "redis" + | "mysql" + | "mariadb" + | "application"; + interface Props { - mysqlId: string; + id: string; + type: ServiceType | "application"; } -type AddResourcesMysql = z.infer; -export const ShowMysqlResources = ({ mysqlId }: Props) => { - const { data, refetch } = api.mysql.one.useQuery( - { - mysqlId, +type AddResources = z.infer; +export const ShowResources = ({ id, type }: Props) => { + const queryMap = { + postgres: () => + api.postgres.one.useQuery({ postgresId: id }, { enabled: !!id }), + redis: () => api.redis.one.useQuery({ redisId: id }, { enabled: !!id }), + mysql: () => api.mysql.one.useQuery({ mysqlId: id }, { enabled: !!id }), + mariadb: () => + api.mariadb.one.useQuery({ mariadbId: id }, { enabled: !!id }), + application: () => + api.application.one.useQuery({ applicationId: id }, { enabled: !!id }), + mongo: () => api.mongo.one.useQuery({ mongoId: id }, { enabled: !!id }), + }; + const { data, refetch } = queryMap[type] + ? queryMap[type]() + : api.mongo.one.useQuery({ mongoId: id }, { enabled: !!id }); + + const mutationMap = { + postgres: () => api.postgres.update.useMutation(), + redis: () => api.redis.update.useMutation(), + mysql: () => api.mysql.update.useMutation(), + mariadb: () => api.mariadb.update.useMutation(), + application: () => api.application.update.useMutation(), + mongo: () => api.mongo.update.useMutation(), + }; + + const { mutateAsync, isLoading } = mutationMap[type] + ? mutationMap[type]() + : api.mongo.update.useMutation(); + + const form = useForm({ + defaultValues: { + cpuLimit: "", + cpuReservation: "", + memoryLimit: "", + memoryReservation: "", }, - { enabled: !!mysqlId }, - ); - const { mutateAsync, isLoading } = api.mysql.update.useMutation(); - const form = useForm({ - defaultValues: {}, - resolver: zodResolver(addResourcesMysql), + resolver: zodResolver(addResourcesSchema), }); useEffect(() => { @@ -65,9 +101,14 @@ export const ShowMysqlResources = ({ mysqlId }: Props) => { } }, [data, form, form.reset]); - const onSubmit = async (formData: AddResourcesMysql) => { + const onSubmit = async (formData: AddResources) => { await mutateAsync({ - mysqlId, + mongoId: id || "", + postgresId: id || "", + redisId: id || "", + mysqlId: id || "", + mariadbId: id || "", + applicationId: id || "", cpuLimit: formData.cpuLimit || null, cpuReservation: formData.cpuReservation || null, memoryLimit: formData.memoryLimit || null, @@ -81,6 +122,7 @@ export const ShowMysqlResources = ({ mysqlId }: Props) => { toast.error("Error updating the resources"); }); }; + return ( @@ -127,18 +169,6 @@ export const ShowMysqlResources = ({ mysqlId }: Props) => { { - const value = e.target.value; - if (value === "") { - field.onChange(null); - } else { - const number = Number.parseInt(value, 10); - if (!Number.isNaN(number)) { - field.onChange(number); - } - } - }} /> @@ -172,18 +202,6 @@ export const ShowMysqlResources = ({ mysqlId }: Props) => { { - const value = e.target.value; - if (value === "") { - field.onChange(null); - } else { - const number = Number.parseInt(value, 10); - if (!Number.isNaN(number)) { - field.onChange(number); - } - } - }} /> @@ -219,17 +237,6 @@ export const ShowMysqlResources = ({ mysqlId }: Props) => { placeholder="2000000000 (2 CPUs)" {...field} value={field.value?.toString() || ""} - onChange={(e) => { - const value = e.target.value; - if (value === "") { - field.onChange(null); - } else { - const number = Number.parseInt(value, 10); - if (!Number.isNaN(number)) { - field.onChange(number); - } - } - }} /> @@ -260,22 +267,7 @@ export const ShowMysqlResources = ({ mysqlId }: Props) => { - { - const value = e.target.value; - if (value === "") { - field.onChange(null); - } else { - const number = Number.parseInt(value, 10); - if (!Number.isNaN(number)) { - field.onChange(number); - } - } - }} - /> +
diff --git a/apps/dokploy/components/dashboard/application/advanced/volumes/delete-volume.tsx b/apps/dokploy/components/dashboard/application/advanced/volumes/delete-volume.tsx deleted file mode 100644 index 4945bb4bf..000000000 --- a/apps/dokploy/components/dashboard/application/advanced/volumes/delete-volume.tsx +++ /dev/null @@ -1,61 +0,0 @@ -import { - AlertDialog, - AlertDialogAction, - AlertDialogCancel, - AlertDialogContent, - AlertDialogDescription, - AlertDialogFooter, - AlertDialogHeader, - AlertDialogTitle, - AlertDialogTrigger, -} from "@/components/ui/alert-dialog"; -import { Button } from "@/components/ui/button"; -import { api } from "@/utils/api"; -import { TrashIcon } from "lucide-react"; -import React from "react"; -import { toast } from "sonner"; - -interface Props { - mountId: string; - refetch: () => void; -} -export const DeleteVolume = ({ mountId, refetch }: Props) => { - const { mutateAsync, isLoading } = api.mounts.remove.useMutation(); - - return ( - - - - - - - Are you absolutely sure? - - This action cannot be undone. This will permanently delete the mount - - - - Cancel - { - await mutateAsync({ - mountId, - }) - .then(() => { - refetch(); - toast.success("Mount deleted successfully"); - }) - .catch(() => { - toast.error("Error deleting the mount"); - }); - }} - > - Confirm - - - - - ); -}; 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 c24d87812..9575c59c4 100644 --- a/apps/dokploy/components/dashboard/application/advanced/volumes/show-volumes.tsx +++ b/apps/dokploy/components/dashboard/application/advanced/volumes/show-volumes.tsx @@ -1,4 +1,6 @@ import { AlertBlock } from "@/components/shared/alert-block"; +import { DialogAction } from "@/components/shared/dialog-action"; +import { Button } from "@/components/ui/button"; import { Card, CardContent, @@ -7,40 +9,49 @@ import { CardTitle, } from "@/components/ui/card"; import { api } from "@/utils/api"; -import { Package } from "lucide-react"; +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"; -import { DeleteVolume } from "./delete-volume"; import { UpdateVolume } from "./update-volume"; interface Props { - applicationId: string; + id: string; + type: ServiceType | "compose"; } -export const ShowVolumes = ({ applicationId }: Props) => { - const { data, refetch } = api.application.one.useQuery( - { - applicationId, - }, - { enabled: !!applicationId }, - ); - +export const ShowVolumes = ({ id, type }: Props) => { + const queryMap = { + postgres: () => + api.postgres.one.useQuery({ postgresId: id }, { enabled: !!id }), + redis: () => api.redis.one.useQuery({ redisId: id }, { enabled: !!id }), + mysql: () => api.mysql.one.useQuery({ mysqlId: id }, { enabled: !!id }), + mariadb: () => + api.mariadb.one.useQuery({ mariadbId: id }, { enabled: !!id }), + application: () => + api.application.one.useQuery({ applicationId: id }, { enabled: !!id }), + mongo: () => api.mongo.one.useQuery({ mongoId: id }, { enabled: !!id }), + compose: () => + api.compose.one.useQuery({ composeId: id }, { enabled: !!id }), + }; + const { data, refetch } = queryMap[type] + ? queryMap[type]() + : api.mongo.one.useQuery({ mongoId: id }, { enabled: !!id }); + const { mutateAsync: deleteVolume, isLoading: isRemoving } = + api.mounts.remove.useMutation(); return (
Volumes - If you want to persist data in this application use the following - config to setup the volumes + If you want to persist data in this postgres database use the + following config to setup the volumes
{data && data?.mounts.length > 0 && ( - + Add Volume )} @@ -52,17 +63,13 @@ export const ShowVolumes = ({ applicationId }: Props) => { No volumes/mounts configured - + Add Volume ) : (
- + Please remember to click Redeploy after adding, editing, or deleting a mount to apply the changes. @@ -73,7 +80,8 @@ export const ShowVolumes = ({ applicationId }: Props) => { key={mount.mountId} className="flex w-full flex-col sm:flex-row sm:items-center justify-between gap-4 sm:gap-10 border rounded-lg p-4" > -
+ {/* */} +
Mount Type @@ -90,21 +98,12 @@ export const ShowVolumes = ({ applicationId }: Props) => { )} {mount.type === "file" && ( - <> -
- Content - - {mount.content} - -
- -
- File Path - - {mount.filePath} - -
- +
+ Content + + {mount.content} + +
)} {mount.type === "bind" && (
@@ -126,9 +125,34 @@ export const ShowVolumes = ({ applicationId }: Props) => { mountId={mount.mountId} type={mount.type} refetch={refetch} - serviceType="application" + serviceType={type} /> - + { + await deleteVolume({ + mountId: mount.mountId, + }) + .then(() => { + refetch(); + toast.success("Volume deleted successfully"); + }) + .catch(() => { + toast.error("Error deleting volume"); + }); + }} + > + +
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 55ea21202..d8481d652 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 { Pencil } from "lucide-react"; +import { PenBoxIcon, Pencil } from "lucide-react"; import { useEffect, useState } from "react"; import { useForm } from "react-hook-form"; import { toast } from "sonner"; @@ -177,8 +177,13 @@ export const UpdateVolume = ({ return ( - diff --git a/apps/dokploy/components/dashboard/application/delete-application.tsx b/apps/dokploy/components/dashboard/application/delete-application.tsx deleted file mode 100644 index f34d29a78..000000000 --- a/apps/dokploy/components/dashboard/application/delete-application.tsx +++ /dev/null @@ -1,161 +0,0 @@ -import { Badge } from "@/components/ui/badge"; -import { Button } from "@/components/ui/button"; -import { - Dialog, - DialogContent, - DialogDescription, - DialogFooter, - DialogHeader, - DialogTitle, - DialogTrigger, -} from "@/components/ui/dialog"; -import { - Form, - FormControl, - FormField, - FormItem, - FormLabel, - FormMessage, -} from "@/components/ui/form"; -import { Input } from "@/components/ui/input"; -import { api } from "@/utils/api"; -import { zodResolver } from "@hookform/resolvers/zod"; -import { Copy, TrashIcon } from "lucide-react"; -import { useRouter } from "next/router"; -import { useState } from "react"; -import { useForm } from "react-hook-form"; -import { toast } from "sonner"; -import { z } from "zod"; - -const deleteApplicationSchema = z.object({ - projectName: z.string().min(1, { - message: "Application name is required", - }), -}); - -type DeleteApplication = z.infer; - -interface Props { - applicationId: string; -} - -export const DeleteApplication = ({ applicationId }: Props) => { - const [isOpen, setIsOpen] = useState(false); - const { mutateAsync, isLoading } = api.application.delete.useMutation(); - const { data } = api.application.one.useQuery( - { applicationId }, - { enabled: !!applicationId }, - ); - const { push } = useRouter(); - const form = useForm({ - defaultValues: { - projectName: "", - }, - resolver: zodResolver(deleteApplicationSchema), - }); - - const onSubmit = async (formData: DeleteApplication) => { - const expectedName = `${data?.name}/${data?.appName}`; - if (formData.projectName === expectedName) { - await mutateAsync({ - applicationId, - }) - .then((data) => { - push(`/dashboard/project/${data?.projectId}`); - toast.success("Application deleted successfully"); - setIsOpen(false); - }) - .catch(() => { - toast.error("Error deleting the application"); - }); - } else { - form.setError("projectName", { - message: "Project name does not match", - }); - } - }; - - return ( - - - - - - - Are you absolutely sure? - - This action cannot be undone. This will permanently delete the - application. If you are sure please enter the application name to - delete this application. - - -
-
- - ( - - - - To confirm, type{" "} - { - if (data?.name && data?.appName) { - navigator.clipboard.writeText( - `${data.name}/${data.appName}`, - ); - toast.success("Copied to clipboard. Be careful!"); - } - }} - > - {data?.name}/{data?.appName}  - - {" "} - in the box below: - - - - - - - - )} - /> - - -
- - - - -
-
- ); -}; diff --git a/apps/dokploy/components/dashboard/application/deployments/cancel-queues.tsx b/apps/dokploy/components/dashboard/application/deployments/cancel-queues.tsx index 110e168f5..5fe7ffb07 100644 --- a/apps/dokploy/components/dashboard/application/deployments/cancel-queues.tsx +++ b/apps/dokploy/components/dashboard/application/deployments/cancel-queues.tsx @@ -20,6 +20,12 @@ interface Props { export const CancelQueues = ({ applicationId }: Props) => { const { mutateAsync, isLoading } = api.application.cleanQueues.useMutation(); + const { data: isCloud } = api.settings.isCloud.useQuery(); + + if (isCloud) { + return null; + } + return ( diff --git a/apps/dokploy/components/dashboard/application/domains/add-domain.tsx b/apps/dokploy/components/dashboard/application/domains/add-domain.tsx index 22eaf1d3c..611689439 100644 --- a/apps/dokploy/components/dashboard/application/domains/add-domain.tsx +++ b/apps/dokploy/components/dashboard/application/domains/add-domain.tsx @@ -104,9 +104,7 @@ export const AddDomain = ({ const dictionary = { success: domainId ? "Domain Updated" : "Domain Created", - error: domainId - ? "Error updating the domain" - : "Error creating the domain", + error: domainId ? "Error updating the domain" : "Error creating the domain", submit: domainId ? "Update" : "Create", dialogDescription: domainId ? "In this section you can edit a domain" diff --git a/apps/dokploy/components/dashboard/application/domains/delete-domain.tsx b/apps/dokploy/components/dashboard/application/domains/delete-domain.tsx deleted file mode 100644 index 3d4425df1..000000000 --- a/apps/dokploy/components/dashboard/application/domains/delete-domain.tsx +++ /dev/null @@ -1,73 +0,0 @@ -import { - AlertDialog, - AlertDialogAction, - AlertDialogCancel, - AlertDialogContent, - AlertDialogDescription, - AlertDialogFooter, - AlertDialogHeader, - AlertDialogTitle, - AlertDialogTrigger, -} from "@/components/ui/alert-dialog"; -import { Button } from "@/components/ui/button"; -import { api } from "@/utils/api"; -import { TrashIcon } from "lucide-react"; -import React from "react"; -import { toast } from "sonner"; - -interface Props { - domainId: string; -} -export const DeleteDomain = ({ domainId }: Props) => { - const { mutateAsync, isLoading } = api.domain.delete.useMutation(); - const utils = api.useUtils(); - return ( - - - - - - - Are you absolutely sure? - - This action cannot be undone. This will permanently delete the - domain - - - - Cancel - { - await mutateAsync({ - domainId, - }) - .then((data) => { - if (data?.applicationId) { - utils.domain.byApplicationId.invalidate({ - applicationId: data?.applicationId, - }); - utils.application.readTraefikConfig.invalidate({ - applicationId: data?.applicationId, - }); - } else if (data?.composeId) { - utils.domain.byComposeId.invalidate({ - composeId: data?.composeId, - }); - } - - toast.success("Domain delete successfully"); - }) - .catch(() => { - toast.error("Error deleting the Domain"); - }); - }} - > - Confirm - - - - - ); -}; diff --git a/apps/dokploy/components/dashboard/application/domains/show-domains.tsx b/apps/dokploy/components/dashboard/application/domains/show-domains.tsx index 8ca59061e..0b775c226 100644 --- a/apps/dokploy/components/dashboard/application/domains/show-domains.tsx +++ b/apps/dokploy/components/dashboard/application/domains/show-domains.tsx @@ -1,3 +1,4 @@ +import { DialogAction } from "@/components/shared/dialog-action"; import { Button } from "@/components/ui/button"; import { Card, @@ -8,17 +9,17 @@ import { } from "@/components/ui/card"; import { Input } from "@/components/ui/input"; import { api } from "@/utils/api"; -import { ExternalLink, GlobeIcon, PenBoxIcon } from "lucide-react"; +import { ExternalLink, GlobeIcon, PenBoxIcon, Trash2 } from "lucide-react"; import Link from "next/link"; +import { toast } from "sonner"; import { AddDomain } from "./add-domain"; -import { DeleteDomain } from "./delete-domain"; interface Props { applicationId: string; } export const ShowDomains = ({ applicationId }: Props) => { - const { data } = api.domain.byApplicationId.useQuery( + const { data, refetch } = api.domain.byApplicationId.useQuery( { applicationId, }, @@ -26,6 +27,10 @@ export const ShowDomains = ({ applicationId }: Props) => { enabled: !!applicationId, }, ); + + const { mutateAsync: deleteDomain, isLoading: isRemoving } = + api.domain.delete.useMutation(); + return (
@@ -97,7 +102,32 @@ export const ShowDomains = ({ applicationId }: Props) => { - + { + await deleteDomain({ + domainId: item.domainId, + }) + .then((data) => { + refetch(); + toast.success("Domain deleted successfully"); + }) + .catch(() => { + toast.error("Error deleting domain"); + }); + }} + > + +
); diff --git a/apps/dokploy/components/dashboard/mongo/environment/show-mongo-environment.tsx b/apps/dokploy/components/dashboard/application/environment/show-enviroment.tsx similarity index 64% rename from apps/dokploy/components/dashboard/mongo/environment/show-mongo-environment.tsx rename to apps/dokploy/components/dashboard/application/environment/show-enviroment.tsx index 0aab9c8da..b65a18161 100644 --- a/apps/dokploy/components/dashboard/mongo/environment/show-mongo-environment.tsx +++ b/apps/dokploy/components/dashboard/application/environment/show-enviroment.tsx @@ -18,10 +18,11 @@ 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, { useEffect, useState } from "react"; +import React, { type CSSProperties, useEffect, useState } from "react"; import { useForm } from "react-hook-form"; import { toast } from "sonner"; import { z } from "zod"; +import type { ServiceType } from "../advanced/show-resources"; const addEnvironmentSchema = z.object({ environment: z.string(), @@ -30,21 +31,39 @@ const addEnvironmentSchema = z.object({ type EnvironmentSchema = z.infer; interface Props { - mongoId: string; + id: string; + type: Exclude; } -export const ShowMongoEnvironment = ({ mongoId }: Props) => { +export const ShowEnvironment = ({ id, type }: Props) => { + const queryMap = { + postgres: () => + api.postgres.one.useQuery({ postgresId: id }, { enabled: !!id }), + redis: () => api.redis.one.useQuery({ redisId: id }, { enabled: !!id }), + mysql: () => api.mysql.one.useQuery({ mysqlId: id }, { enabled: !!id }), + mariadb: () => + api.mariadb.one.useQuery({ mariadbId: id }, { enabled: !!id }), + mongo: () => api.mongo.one.useQuery({ mongoId: id }, { enabled: !!id }), + compose: () => + api.compose.one.useQuery({ composeId: id }, { enabled: !!id }), + }; + const { data, refetch } = queryMap[type] + ? queryMap[type]() + : api.mongo.one.useQuery({ mongoId: id }, { enabled: !!id }); const [isEnvVisible, setIsEnvVisible] = useState(true); - const { mutateAsync, isLoading } = api.mongo.saveEnvironment.useMutation(); - const { data, refetch } = api.mongo.one.useQuery( - { - mongoId, - }, - { - enabled: !!mongoId, - }, - ); + const mutationMap = { + postgres: () => api.postgres.update.useMutation(), + redis: () => api.redis.update.useMutation(), + mysql: () => api.mysql.update.useMutation(), + mariadb: () => api.mariadb.update.useMutation(), + mongo: () => api.mongo.update.useMutation(), + compose: () => api.compose.update.useMutation(), + }; + const { mutateAsync, isLoading } = mutationMap[type] + ? mutationMap[type]() + : api.mongo.update.useMutation(); + const form = useForm({ defaultValues: { environment: "", @@ -62,8 +81,13 @@ export const ShowMongoEnvironment = ({ mongoId }: Props) => { const onSubmit = async (data: EnvironmentSchema) => { mutateAsync({ + mongoId: id || "", + postgresId: id || "", + redisId: id || "", + mysqlId: id || "", + mariadbId: id || "", + composeId: id || "", env: data.environment, - mongoId, }) .then(async () => { toast.success("Environments Added"); @@ -111,6 +135,11 @@ export const ShowMongoEnvironment = ({ mongoId }: Props) => { { }; return ( -
- - + + + { placeholder="NPM_TOKEN=xyz" /> )} - -
- -
-
-
- - +
+ +
+ + +
); }; diff --git a/apps/dokploy/components/dashboard/application/general/deploy-application.tsx b/apps/dokploy/components/dashboard/application/general/deploy-application.tsx deleted file mode 100644 index c1ab84572..000000000 --- a/apps/dokploy/components/dashboard/application/general/deploy-application.tsx +++ /dev/null @@ -1,74 +0,0 @@ -import { - AlertDialog, - AlertDialogAction, - AlertDialogCancel, - AlertDialogContent, - AlertDialogDescription, - AlertDialogFooter, - AlertDialogHeader, - AlertDialogTitle, - AlertDialogTrigger, -} from "@/components/ui/alert-dialog"; -import { Button } from "@/components/ui/button"; -import { api } from "@/utils/api"; -import { useRouter } from "next/router"; -import { toast } from "sonner"; - -interface Props { - applicationId: string; -} - -export const DeployApplication = ({ applicationId }: Props) => { - const router = useRouter(); - const { data, refetch } = api.application.one.useQuery( - { - applicationId, - }, - { enabled: !!applicationId }, - ); - - const { mutateAsync: deploy } = api.application.deploy.useMutation(); - - return ( - - - - - - - Are you absolutely sure? - - This will deploy the application - - - - Cancel - { - await deploy({ - applicationId, - }) - .then(async () => { - toast.success("Application deployed successfully"); - await refetch(); - router.push( - `/dashboard/project/${data?.projectId}/services/application/${applicationId}?tab=deployments`, - ); - }) - - .catch(() => { - toast.error("Error deploying the Application"); - }); - - await refetch(); - }} - > - Confirm - - - - - ); -}; diff --git a/apps/dokploy/components/dashboard/application/general/reset-application.tsx b/apps/dokploy/components/dashboard/application/general/reset-application.tsx deleted file mode 100644 index 5be2a185e..000000000 --- a/apps/dokploy/components/dashboard/application/general/reset-application.tsx +++ /dev/null @@ -1,70 +0,0 @@ -import { - AlertDialog, - AlertDialogAction, - AlertDialogCancel, - AlertDialogContent, - AlertDialogDescription, - AlertDialogFooter, - AlertDialogHeader, - AlertDialogTitle, - AlertDialogTrigger, -} from "@/components/ui/alert-dialog"; -import { Button } from "@/components/ui/button"; -import { api } from "@/utils/api"; -import { RefreshCcw } from "lucide-react"; -import { toast } from "sonner"; - -interface Props { - applicationId: string; - appName: string; -} - -export const ResetApplication = ({ applicationId, appName }: Props) => { - const { refetch } = api.application.one.useQuery( - { - applicationId, - }, - { enabled: !!applicationId }, - ); - const { mutateAsync: reload, isLoading } = - api.application.reload.useMutation(); - - return ( - - - - - - - Are you absolutely sure? - - This will reload the application - - - - Cancel - { - await reload({ - applicationId, - appName, - }) - .then(() => { - toast.success("Service Reloaded"); - }) - .catch(() => { - toast.error("Error reloading the service"); - }); - await refetch(); - }} - > - Confirm - - - - - ); -}; diff --git a/apps/dokploy/components/dashboard/application/general/show.tsx b/apps/dokploy/components/dashboard/application/general/show.tsx index 46f185d55..83e4b6f06 100644 --- a/apps/dokploy/components/dashboard/application/general/show.tsx +++ b/apps/dokploy/components/dashboard/application/general/show.tsx @@ -1,23 +1,21 @@ import { ShowBuildChooseForm } from "@/components/dashboard/application/build/show"; import { ShowProviderForm } from "@/components/dashboard/application/general/generic/show"; +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 { api } from "@/utils/api"; -import { Terminal } from "lucide-react"; +import { Ban, CheckCircle2, Hammer, 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"; -import { RedbuildApplication } from "../rebuild-application"; -import { StartApplication } from "../start-application"; -import { StopApplication } from "../stop-application"; -import { DeployApplication } from "./deploy-application"; -import { ResetApplication } from "./reset-application"; interface Props { applicationId: string; } export const ShowGeneralApplication = ({ applicationId }: Props) => { + const router = useRouter(); const { data, refetch } = api.application.one.useQuery( { applicationId, @@ -25,6 +23,18 @@ export const ShowGeneralApplication = ({ applicationId }: Props) => { { enabled: !!applicationId }, ); const { mutateAsync: update } = api.application.update.useMutation(); + const { mutateAsync: start, isLoading: isStarting } = + api.application.start.useMutation(); + const { mutateAsync: stop, isLoading: isStopping } = + api.application.stop.useMutation(); + + const { mutateAsync: deploy, isLoading: isDeploying } = + api.application.deploy.useMutation(); + + const { mutateAsync: reload, isLoading: isReloading } = + api.application.reload.useMutation(); + + const { mutateAsync: redeploy } = api.application.redeploy.useMutation(); return ( <> @@ -33,17 +43,127 @@ 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({ + 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"); + }); + }} + > + + )} { "PORT=3000", ].join("\n")} /> - {/* */}
diff --git a/apps/dokploy/components/dashboard/application/rebuild-application.tsx b/apps/dokploy/components/dashboard/application/rebuild-application.tsx deleted file mode 100644 index 66ed33974..000000000 --- a/apps/dokploy/components/dashboard/application/rebuild-application.tsx +++ /dev/null @@ -1,76 +0,0 @@ -import { - AlertDialog, - AlertDialogAction, - AlertDialogCancel, - AlertDialogContent, - AlertDialogDescription, - AlertDialogFooter, - AlertDialogHeader, - AlertDialogTitle, - AlertDialogTrigger, -} from "@/components/ui/alert-dialog"; -import { Button } from "@/components/ui/button"; -import { api } from "@/utils/api"; -import { Hammer } from "lucide-react"; -import { toast } from "sonner"; - -interface Props { - applicationId: string; -} - -export const RedbuildApplication = ({ applicationId }: Props) => { - const { data } = api.application.one.useQuery( - { - applicationId, - }, - { enabled: !!applicationId }, - ); - - const { mutateAsync } = api.application.redeploy.useMutation(); - const utils = api.useUtils(); - return ( - - - - - - - - Are you sure to rebuild the application? - - - Is required to deploy at least 1 time in order to reuse the same - code - - - - Cancel - { - toast.success("Redeploying Application...."); - await mutateAsync({ - applicationId, - }) - .then(async () => { - await utils.application.one.invalidate({ - applicationId, - }); - }) - .catch(() => { - toast.error("Error rebuilding the application"); - }); - }} - > - Confirm - - - - - ); -}; diff --git a/apps/dokploy/components/dashboard/application/start-application.tsx b/apps/dokploy/components/dashboard/application/start-application.tsx deleted file mode 100644 index 9a65397b3..000000000 --- a/apps/dokploy/components/dashboard/application/start-application.tsx +++ /dev/null @@ -1,65 +0,0 @@ -import { - AlertDialog, - AlertDialogAction, - AlertDialogCancel, - AlertDialogContent, - AlertDialogDescription, - AlertDialogFooter, - AlertDialogHeader, - AlertDialogTitle, - AlertDialogTrigger, -} from "@/components/ui/alert-dialog"; -import { Button } from "@/components/ui/button"; -import { api } from "@/utils/api"; -import { CheckCircle2 } from "lucide-react"; -import { toast } from "sonner"; - -interface Props { - applicationId: string; -} - -export const StartApplication = ({ applicationId }: Props) => { - const { mutateAsync, isLoading } = api.application.start.useMutation(); - const utils = api.useUtils(); - return ( - - - - - - - - Are you sure to start the application? - - - This will start the application - - - - Cancel - { - await mutateAsync({ - applicationId, - }) - .then(async () => { - await utils.application.one.invalidate({ - applicationId, - }); - toast.success("Application started successfully"); - }) - .catch(() => { - toast.error("Error starting the Application"); - }); - }} - > - Confirm - - - - - ); -}; diff --git a/apps/dokploy/components/dashboard/application/stop-application.tsx b/apps/dokploy/components/dashboard/application/stop-application.tsx deleted file mode 100644 index ea052fabd..000000000 --- a/apps/dokploy/components/dashboard/application/stop-application.tsx +++ /dev/null @@ -1,65 +0,0 @@ -import { - AlertDialog, - AlertDialogAction, - AlertDialogCancel, - AlertDialogContent, - AlertDialogDescription, - AlertDialogFooter, - AlertDialogHeader, - AlertDialogTitle, - AlertDialogTrigger, -} from "@/components/ui/alert-dialog"; -import { Button } from "@/components/ui/button"; -import { api } from "@/utils/api"; -import { Ban } from "lucide-react"; -import { toast } from "sonner"; - -interface Props { - applicationId: string; -} - -export const StopApplication = ({ applicationId }: Props) => { - const { mutateAsync, isLoading } = api.application.stop.useMutation(); - const utils = api.useUtils(); - return ( - - - - - - - - Are you absolutely sure to stop the application? - - - This will stop the application - - - - Cancel - { - await mutateAsync({ - applicationId, - }) - .then(async () => { - await utils.application.one.invalidate({ - applicationId, - }); - toast.success("Application stopped successfully"); - }) - .catch(() => { - toast.error("Error stopping the Application"); - }); - }} - > - Confirm - - - - - ); -}; diff --git a/apps/dokploy/components/dashboard/application/update-application.tsx b/apps/dokploy/components/dashboard/application/update-application.tsx index 8f8b63fe8..a49fc5383 100644 --- a/apps/dokploy/components/dashboard/application/update-application.tsx +++ b/apps/dokploy/components/dashboard/application/update-application.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, SquarePen } from "lucide-react"; +import { AlertTriangle, PenBoxIcon, SquarePen } from "lucide-react"; import { useEffect, useState } from "react"; import { useForm } from "react-hook-form"; import { toast } from "sonner"; @@ -91,8 +91,12 @@ export const UpdateApplication = ({ applicationId }: Props) => { return ( - diff --git a/apps/dokploy/components/dashboard/compose/advanced/show-volumes.tsx b/apps/dokploy/components/dashboard/compose/advanced/show-volumes.tsx deleted file mode 100644 index 08e0213c5..000000000 --- a/apps/dokploy/components/dashboard/compose/advanced/show-volumes.tsx +++ /dev/null @@ -1,142 +0,0 @@ -import { AlertBlock } from "@/components/shared/alert-block"; -import { - Card, - CardContent, - CardDescription, - CardHeader, - CardTitle, -} from "@/components/ui/card"; -import { api } from "@/utils/api"; -import { Package } from "lucide-react"; -import React from "react"; -import { AddVolumes } from "../../application/advanced/volumes/add-volumes"; -import { DeleteVolume } from "../../application/advanced/volumes/delete-volume"; -import { UpdateVolume } from "../../application/advanced/volumes/update-volume"; -interface Props { - composeId: string; -} - -export const ShowVolumesCompose = ({ composeId }: Props) => { - const { data, refetch } = api.compose.one.useQuery( - { - composeId, - }, - { enabled: !!composeId }, - ); - - return ( - - -
- Volumes - - If you want to persist data in this compose use the following config - to setup the volumes - -
- - {data && data?.mounts.length > 0 && ( - - Add Volume - - )} -
- - {data?.mounts.length === 0 ? ( -
- - - No volumes/mounts configured - - - Add Volume - -
- ) : ( -
- - Please remember to click Redeploy after adding, editing, or - deleting a mount to apply the changes. - - -
- {data?.mounts.map((mount) => ( -
-
-
-
- Mount Type - - {mount.type.toUpperCase()} - -
- {mount.type === "volume" && ( -
- Volume Name - - {mount.volumeName} - -
- )} - - {mount.type === "file" && ( - <> -
- Content - - {mount.content} - -
-
- File Path - - {mount.filePath} - -
- - )} - {mount.type === "bind" && ( -
- Host Path - - {mount.hostPath} - -
- )} -
- Mount Path - - {mount.mountPath} - -
-
-
- - -
-
-
- ))} -
-
- )} -
-
- ); -}; diff --git a/apps/dokploy/components/dashboard/compose/delete-compose.tsx b/apps/dokploy/components/dashboard/compose/delete-compose.tsx index f263bc7fa..764de95e8 100644 --- a/apps/dokploy/components/dashboard/compose/delete-compose.tsx +++ b/apps/dokploy/components/dashboard/compose/delete-compose.tsx @@ -13,7 +13,6 @@ import { import { Form, FormControl, - FormDescription, FormField, FormItem, FormLabel, @@ -22,7 +21,7 @@ import { import { Input } from "@/components/ui/input"; import { api } from "@/utils/api"; import { zodResolver } from "@hookform/resolvers/zod"; -import { Copy } from "lucide-react"; +import { Copy, Trash2 } from "lucide-react"; import { TrashIcon } from "lucide-react"; import { useRouter } from "next/router"; import { useState } from "react"; @@ -82,8 +81,13 @@ export const DeleteCompose = ({ composeId }: Props) => { return ( - diff --git a/apps/dokploy/components/dashboard/compose/deployments/cancel-queues-compose.tsx b/apps/dokploy/components/dashboard/compose/deployments/cancel-queues-compose.tsx index c84167c4d..a430ae18f 100644 --- a/apps/dokploy/components/dashboard/compose/deployments/cancel-queues-compose.tsx +++ b/apps/dokploy/components/dashboard/compose/deployments/cancel-queues-compose.tsx @@ -20,6 +20,11 @@ interface Props { export const CancelQueuesCompose = ({ composeId }: Props) => { const { mutateAsync, isLoading } = api.compose.cleanQueues.useMutation(); + const { data: isCloud } = api.settings.isCloud.useQuery(); + + if (isCloud) { + return null; + } return ( diff --git a/apps/dokploy/components/dashboard/compose/domains/add-domain.tsx b/apps/dokploy/components/dashboard/compose/domains/add-domain.tsx index b9ae9a2ed..e18d40d73 100644 --- a/apps/dokploy/components/dashboard/compose/domains/add-domain.tsx +++ b/apps/dokploy/components/dashboard/compose/domains/add-domain.tsx @@ -126,9 +126,7 @@ export const AddDomainCompose = ({ const dictionary = { success: domainId ? "Domain Updated" : "Domain Created", - error: domainId - ? "Error updating the domain" - : "Error creating the domain", + error: domainId ? "Error updating the domain" : "Error creating the domain", submit: domainId ? "Update" : "Create", dialogDescription: domainId ? "In this section you can edit a domain" diff --git a/apps/dokploy/components/dashboard/compose/domains/show-domains.tsx b/apps/dokploy/components/dashboard/compose/domains/show-domains.tsx index dc798f80a..e95270672 100644 --- a/apps/dokploy/components/dashboard/compose/domains/show-domains.tsx +++ b/apps/dokploy/components/dashboard/compose/domains/show-domains.tsx @@ -1,3 +1,4 @@ +import { DialogAction } from "@/components/shared/dialog-action"; import { Button } from "@/components/ui/button"; import { Card, @@ -8,9 +9,9 @@ import { } from "@/components/ui/card"; import { Input } from "@/components/ui/input"; import { api } from "@/utils/api"; -import { ExternalLink, GlobeIcon, PenBoxIcon } from "lucide-react"; +import { ExternalLink, GlobeIcon, PenBoxIcon, Trash2 } from "lucide-react"; import Link from "next/link"; -import { DeleteDomain } from "../../application/domains/delete-domain"; +import { toast } from "sonner"; import { AddDomainCompose } from "./add-domain"; interface Props { @@ -18,7 +19,7 @@ interface Props { } export const ShowDomainsCompose = ({ composeId }: Props) => { - const { data } = api.domain.byComposeId.useQuery( + const { data, refetch } = api.domain.byComposeId.useQuery( { composeId, }, @@ -27,6 +28,9 @@ export const ShowDomainsCompose = ({ composeId }: Props) => { }, ); + const { mutateAsync: deleteDomain, isLoading: isRemoving } = + api.domain.delete.useMutation(); + return (
@@ -97,7 +101,32 @@ export const ShowDomainsCompose = ({ composeId }: Props) => { - + { + await deleteDomain({ + domainId: item.domainId, + }) + .then((data) => { + refetch(); + toast.success("Domain deleted successfully"); + }) + .catch(() => { + toast.error("Error deleting domain"); + }); + }} + > + +
); diff --git a/apps/dokploy/components/dashboard/compose/environment/show.tsx b/apps/dokploy/components/dashboard/compose/environment/show.tsx deleted file mode 100644 index 8fa1f4445..000000000 --- a/apps/dokploy/components/dashboard/compose/environment/show.tsx +++ /dev/null @@ -1,167 +0,0 @@ -import { CodeEditor } from "@/components/shared/code-editor"; -import { Button } from "@/components/ui/button"; -import { - Card, - CardContent, - CardDescription, - CardHeader, - CardTitle, -} from "@/components/ui/card"; -import { - Form, - FormControl, - FormField, - FormItem, - FormMessage, -} from "@/components/ui/form"; -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, { useEffect, useState } from "react"; -import { useForm } from "react-hook-form"; -import { toast } from "sonner"; -import { z } from "zod"; - -const addEnvironmentSchema = z.object({ - environment: z.string(), -}); - -type EnvironmentSchema = z.infer; - -interface Props { - composeId: string; -} - -export const ShowEnvironmentCompose = ({ composeId }: Props) => { - const [isEnvVisible, setIsEnvVisible] = useState(true); - const { mutateAsync, isLoading } = api.compose.update.useMutation(); - - const { data, refetch } = api.compose.one.useQuery( - { - composeId, - }, - { - enabled: !!composeId, - }, - ); - const form = useForm({ - defaultValues: { - environment: "", - }, - resolver: zodResolver(addEnvironmentSchema), - }); - - useEffect(() => { - if (data) { - form.reset({ - environment: data.env || "", - }); - } - }, [form.reset, data, form]); - - const onSubmit = async (data: EnvironmentSchema) => { - mutateAsync({ - env: data.environment, - composeId, - }) - .then(async () => { - toast.success("Environments Added"); - await refetch(); - }) - .catch(() => { - toast.error("Error adding environment"); - }); - }; - - useEffect(() => { - if (isEnvVisible) { - if (data?.env) { - const maskedLines = data.env - .split("\n") - .map((line) => "*".repeat(line.length)) - .join("\n"); - form.reset({ - environment: maskedLines, - }); - } else { - form.reset({ - environment: "", - }); - } - } else { - form.reset({ - environment: data?.env || "", - }); - } - }, [form.reset, data, form, isEnvVisible]); - - return ( -
- - -
- Environment Settings - - You can add environment variables to your resource. - -
- - - {isEnvVisible ? ( - - ) : ( - - )} - -
- -
- - ( - - - - - - - - )} - /> - -
- -
- - -
-
-
- ); -}; diff --git a/apps/dokploy/components/dashboard/compose/general/actions.tsx b/apps/dokploy/components/dashboard/compose/general/actions.tsx index 215e7e42f..a40cc3453 100644 --- a/apps/dokploy/components/dashboard/compose/general/actions.tsx +++ b/apps/dokploy/components/dashboard/compose/general/actions.tsx @@ -1,28 +1,17 @@ +import { DialogAction } from "@/components/shared/dialog-action"; import { Button } from "@/components/ui/button"; -import { - DropdownMenu, - DropdownMenuContent, - DropdownMenuGroup, - DropdownMenuItem, - DropdownMenuLabel, - DropdownMenuSeparator, - DropdownMenuTrigger, -} from "@/components/ui/dropdown-menu"; import { Switch } from "@/components/ui/switch"; import { api } from "@/utils/api"; -import { CheckCircle2, ExternalLink, Globe, Terminal } from "lucide-react"; -import Link from "next/link"; +import { Ban, CheckCircle2, Hammer, Terminal } from "lucide-react"; +import { useRouter } from "next/router"; import { toast } from "sonner"; import { DockerTerminalModal } from "../../settings/web-server/docker-terminal-modal"; -import { StartCompose } from "../start-compose"; -import { DeployCompose } from "./deploy-compose"; -import { RedbuildCompose } from "./rebuild-compose"; -import { StopCompose } from "./stop-compose"; interface Props { composeId: string; } export const ComposeActions = ({ composeId }: Props) => { + const router = useRouter(); const { data, refetch } = api.compose.one.useQuery( { composeId, @@ -30,33 +19,109 @@ export const ComposeActions = ({ composeId }: Props) => { { enabled: !!composeId }, ); const { mutateAsync: update } = api.compose.update.useMutation(); - - const extractDomains = (env: string) => { - const lines = env.split("\n"); - const hostLines = lines.filter((line) => { - const [key, value] = line.split("="); - return key?.trim().endsWith("_HOST"); - }); - - const hosts = hostLines.map((line) => { - const [key, value] = line.split("="); - return value ? value.trim() : ""; - }); - - return hosts; - }; - - const domains = extractDomains(data?.env || ""); - + const { mutateAsync: deploy } = api.compose.deploy.useMutation(); + const { mutateAsync: redeploy } = api.compose.redeploy.useMutation(); + const { mutateAsync: start, isLoading: isStarting } = + api.compose.start.useMutation(); + const { mutateAsync: stop, isLoading: isStopping } = + 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({ + 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"); + }); + }} + > + + )} { className="flex flex-row gap-2 items-center" />
- {domains.length > 0 && ( - - - - - - Domains detected - - - {domains.map((host, index) => { - const url = - host.startsWith("http://") || host.startsWith("https://") - ? host - : `http://${host}`; - - return ( - - - {host} - - - - ); - })} - - - - )} ); }; diff --git a/apps/dokploy/components/dashboard/compose/general/deploy-compose.tsx b/apps/dokploy/components/dashboard/compose/general/deploy-compose.tsx deleted file mode 100644 index 2c6f13e04..000000000 --- a/apps/dokploy/components/dashboard/compose/general/deploy-compose.tsx +++ /dev/null @@ -1,72 +0,0 @@ -import { - AlertDialog, - AlertDialogAction, - AlertDialogCancel, - AlertDialogContent, - AlertDialogDescription, - AlertDialogFooter, - AlertDialogHeader, - AlertDialogTitle, - AlertDialogTrigger, -} from "@/components/ui/alert-dialog"; -import { Button } from "@/components/ui/button"; -import { api } from "@/utils/api"; -import { useRouter } from "next/router"; -import { toast } from "sonner"; - -interface Props { - composeId: string; -} - -export const DeployCompose = ({ composeId }: Props) => { - const router = useRouter(); - const { data, refetch } = api.compose.one.useQuery( - { - composeId, - }, - { enabled: !!composeId }, - ); - - const { mutateAsync: deploy } = api.compose.deploy.useMutation(); - - return ( - - - - - - - Are you absolutely sure? - - This will deploy the compose - - - - Cancel - { - toast.success("Deploying Compose...."); - - await refetch(); - await deploy({ - composeId, - }) - .then(async () => { - router.push( - `/dashboard/project/${data?.project.projectId}/services/compose/${composeId}?tab=deployments`, - ); - }) - .catch(() => { - toast.error("Error deploying Compose"); - }); - - await refetch(); - }} - > - Confirm - - - - - ); -}; diff --git a/apps/dokploy/components/dashboard/compose/general/rebuild-compose.tsx b/apps/dokploy/components/dashboard/compose/general/rebuild-compose.tsx deleted file mode 100644 index c2f504145..000000000 --- a/apps/dokploy/components/dashboard/compose/general/rebuild-compose.tsx +++ /dev/null @@ -1,75 +0,0 @@ -import { - AlertDialog, - AlertDialogAction, - AlertDialogCancel, - AlertDialogContent, - AlertDialogDescription, - AlertDialogFooter, - AlertDialogHeader, - AlertDialogTitle, - AlertDialogTrigger, -} from "@/components/ui/alert-dialog"; -import { Button } from "@/components/ui/button"; -import { api } from "@/utils/api"; -import { Hammer } from "lucide-react"; -import { toast } from "sonner"; - -interface Props { - composeId: string; -} - -export const RedbuildCompose = ({ composeId }: Props) => { - const { data } = api.compose.one.useQuery( - { - composeId, - }, - { enabled: !!composeId }, - ); - const { mutateAsync } = api.compose.redeploy.useMutation(); - const utils = api.useUtils(); - return ( - - - - - - - - Are you sure to rebuild the compose? - - - Is required to deploy at least 1 time in order to reuse the same - code - - - - Cancel - { - toast.success("Redeploying Compose...."); - await mutateAsync({ - composeId, - }) - .then(async () => { - await utils.compose.one.invalidate({ - composeId, - }); - }) - .catch(() => { - toast.error("Error rebuilding the compose"); - }); - }} - > - Confirm - - - - - ); -}; diff --git a/apps/dokploy/components/dashboard/compose/general/stop-compose.tsx b/apps/dokploy/components/dashboard/compose/general/stop-compose.tsx deleted file mode 100644 index f405481bf..000000000 --- a/apps/dokploy/components/dashboard/compose/general/stop-compose.tsx +++ /dev/null @@ -1,69 +0,0 @@ -import { - AlertDialog, - AlertDialogAction, - AlertDialogCancel, - AlertDialogContent, - AlertDialogDescription, - AlertDialogFooter, - AlertDialogHeader, - AlertDialogTitle, - AlertDialogTrigger, -} from "@/components/ui/alert-dialog"; -import { Button } from "@/components/ui/button"; -import { api } from "@/utils/api"; -import { Ban } from "lucide-react"; -import { toast } from "sonner"; - -interface Props { - composeId: string; -} - -export const StopCompose = ({ composeId }: Props) => { - const { data } = api.compose.one.useQuery( - { - composeId, - }, - { enabled: !!composeId }, - ); - const { mutateAsync, isLoading } = api.compose.stop.useMutation(); - const utils = api.useUtils(); - return ( - - - - - - - Are you sure to stop the compose? - - This will stop the compose services - - - - Cancel - { - await mutateAsync({ - composeId, - }) - .then(async () => { - await utils.compose.one.invalidate({ - composeId, - }); - toast.success("Compose stopped successfully"); - }) - .catch(() => { - toast.error("Error stopping the compose"); - }); - }} - > - Confirm - - - - - ); -}; diff --git a/apps/dokploy/components/dashboard/compose/start-compose.tsx b/apps/dokploy/components/dashboard/compose/start-compose.tsx deleted file mode 100644 index fae978e2c..000000000 --- a/apps/dokploy/components/dashboard/compose/start-compose.tsx +++ /dev/null @@ -1,65 +0,0 @@ -import { - AlertDialog, - AlertDialogAction, - AlertDialogCancel, - AlertDialogContent, - AlertDialogDescription, - AlertDialogFooter, - AlertDialogHeader, - AlertDialogTitle, - AlertDialogTrigger, -} from "@/components/ui/alert-dialog"; -import { Button } from "@/components/ui/button"; -import { api } from "@/utils/api"; -import { CheckCircle2 } from "lucide-react"; -import { toast } from "sonner"; - -interface Props { - composeId: string; -} - -export const StartCompose = ({ composeId }: Props) => { - const { mutateAsync, isLoading } = api.compose.start.useMutation(); - const utils = api.useUtils(); - return ( - - - - - - - - Are you sure to start the compose? - - - This will start the compose - - - - Cancel - { - await mutateAsync({ - composeId, - }) - .then(async () => { - await utils.compose.one.invalidate({ - composeId, - }); - toast.success("Compose started successfully"); - }) - .catch(() => { - toast.error("Error starting the Compose"); - }); - }} - > - Confirm - - - - - ); -}; diff --git a/apps/dokploy/components/dashboard/compose/stop-compose.tsx b/apps/dokploy/components/dashboard/compose/stop-compose.tsx deleted file mode 100644 index da3e34125..000000000 --- a/apps/dokploy/components/dashboard/compose/stop-compose.tsx +++ /dev/null @@ -1,65 +0,0 @@ -import { - AlertDialog, - AlertDialogAction, - AlertDialogCancel, - AlertDialogContent, - AlertDialogDescription, - AlertDialogFooter, - AlertDialogHeader, - AlertDialogTitle, - AlertDialogTrigger, -} from "@/components/ui/alert-dialog"; -import { Button } from "@/components/ui/button"; -import { api } from "@/utils/api"; -import { Ban } from "lucide-react"; -import { toast } from "sonner"; - -interface Props { - composeId: string; -} - -export const StopCompose = ({ composeId }: Props) => { - const { mutateAsync, isLoading } = api.compose.stop.useMutation(); - const utils = api.useUtils(); - return ( - - - - - - - - Are you absolutely sure to stop the compose? - - - This will stop the compose - - - - Cancel - { - await mutateAsync({ - composeId, - }) - .then(async () => { - await utils.compose.one.invalidate({ - composeId, - }); - toast.success("Compose stopped successfully"); - }) - .catch(() => { - toast.error("Error stopping the Compose"); - }); - }} - > - Confirm - - - - - ); -}; diff --git a/apps/dokploy/components/dashboard/compose/update-compose.tsx b/apps/dokploy/components/dashboard/compose/update-compose.tsx index 807f5b26e..3120f2d4c 100644 --- a/apps/dokploy/components/dashboard/compose/update-compose.tsx +++ b/apps/dokploy/components/dashboard/compose/update-compose.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 { SquarePen } from "lucide-react"; +import { PenBoxIcon } from "lucide-react"; import { useEffect, useState } from "react"; import { useForm } from "react-hook-form"; import { toast } from "sonner"; @@ -91,8 +91,12 @@ export const UpdateCompose = ({ composeId }: Props) => { return ( - diff --git a/apps/dokploy/components/dashboard/database/backups/delete-backup.tsx b/apps/dokploy/components/dashboard/database/backups/delete-backup.tsx deleted file mode 100644 index 63c6677d4..000000000 --- a/apps/dokploy/components/dashboard/database/backups/delete-backup.tsx +++ /dev/null @@ -1,62 +0,0 @@ -import { - AlertDialog, - AlertDialogAction, - AlertDialogCancel, - AlertDialogContent, - AlertDialogDescription, - AlertDialogFooter, - AlertDialogHeader, - AlertDialogTitle, - AlertDialogTrigger, -} from "@/components/ui/alert-dialog"; -import { Button } from "@/components/ui/button"; -import { api } from "@/utils/api"; -import { TrashIcon } from "lucide-react"; -import { toast } from "sonner"; - -interface Props { - backupId: string; - refetch: () => void; -} - -export const DeleteBackup = ({ backupId, refetch }: Props) => { - const { mutateAsync, isLoading } = api.backup.remove.useMutation(); - return ( - - - - - - - Are you absolutely sure? - - This action cannot be undone. This will permanently delete the - backup - - - - Cancel - { - await mutateAsync({ - backupId, - }) - .then(() => { - refetch(); - - toast.success("Backup deleted successfully"); - }) - .catch(() => { - toast.error("Error deleting the backup"); - }); - }} - > - Confirm - - - - - ); -}; diff --git a/apps/dokploy/components/dashboard/postgres/backups/show-backup-postgres.tsx b/apps/dokploy/components/dashboard/database/backups/show-backups.tsx similarity index 66% rename from apps/dokploy/components/dashboard/postgres/backups/show-backup-postgres.tsx rename to apps/dokploy/components/dashboard/database/backups/show-backups.tsx index dad55d1eb..6ecdbe021 100644 --- a/apps/dokploy/components/dashboard/postgres/backups/show-backup-postgres.tsx +++ b/apps/dokploy/components/dashboard/database/backups/show-backups.tsx @@ -1,3 +1,4 @@ +import { DialogAction } from "@/components/shared/dialog-action"; import { Button } from "@/components/ui/button"; import { Card, @@ -13,31 +14,47 @@ import { TooltipTrigger, } from "@/components/ui/tooltip"; import { api } from "@/utils/api"; -import { DatabaseBackup, Play } from "lucide-react"; +import { DatabaseBackup, Play, Trash2 } from "lucide-react"; import Link from "next/link"; import React from "react"; import { toast } from "sonner"; -import { AddBackup } from "../../database/backups/add-backup"; -import { DeleteBackup } from "../../database/backups/delete-backup"; -import { UpdateBackup } from "../../database/backups/update-backup"; +import type { ServiceType } from "../../application/advanced/show-resources"; +import { AddBackup } from "./add-backup"; +import { UpdateBackup } from "./update-backup"; + interface Props { - postgresId: string; + id: string; + type: Exclude; } - -export const ShowBackupPostgres = ({ postgresId }: Props) => { +export const ShowBackups = ({ id, type }: Props) => { + const queryMap = { + postgres: () => + api.postgres.one.useQuery({ postgresId: id }, { enabled: !!id }), + mysql: () => api.mysql.one.useQuery({ mysqlId: id }, { enabled: !!id }), + mariadb: () => + api.mariadb.one.useQuery({ mariadbId: id }, { enabled: !!id }), + mongo: () => api.mongo.one.useQuery({ mongoId: id }, { enabled: !!id }), + }; const { data } = api.destination.all.useQuery(); - const { data: postgres, refetch: refetchPostgres } = - api.postgres.one.useQuery( - { - postgresId, - }, - { - enabled: !!postgresId, - }, - ); + const { data: postgres, refetch } = queryMap[type] + ? queryMap[type]() + : api.mongo.one.useQuery({ mongoId: id }, { enabled: !!id }); + + const mutationMap = { + postgres: () => api.backup.manualBackupPostgres.useMutation(), + mysql: () => api.backup.manualBackupMySql.useMutation(), + mariadb: () => api.backup.manualBackupMariadb.useMutation(), + mongo: () => api.backup.manualBackupMongo.useMutation(), + }; - const { mutateAsync: manualBackup, isLoading: isManualBackup } = - api.backup.manualBackupPostgres.useMutation(); + const { mutateAsync: manualBackup, isLoading: isManualBackup } = mutationMap[ + type + ] + ? mutationMap[type]() + : api.backup.manualBackupMongo.useMutation(); + + const { mutateAsync: deleteBackup, isLoading: isRemoving } = + api.backup.remove.useMutation(); return ( @@ -51,11 +68,7 @@ export const ShowBackupPostgres = ({ postgresId }: Props) => { {postgres && postgres?.backups?.length > 0 && ( - + )}
@@ -83,9 +96,9 @@ export const ShowBackupPostgres = ({ postgresId }: Props) => { No backups configured ) : ( @@ -158,12 +171,34 @@ export const ShowBackupPostgres = ({ postgresId }: Props) => { - + { + await deleteBackup({ + backupId: backup.backupId, + }) + .then(() => { + refetch(); + toast.success("Backup deleted successfully"); + }) + .catch(() => { + toast.error("Error deleting backup"); + }); + }} + > + + diff --git a/apps/dokploy/components/dashboard/database/backups/update-backup.tsx b/apps/dokploy/components/dashboard/database/backups/update-backup.tsx index 55754e9d1..0083bb1df 100644 --- a/apps/dokploy/components/dashboard/database/backups/update-backup.tsx +++ b/apps/dokploy/components/dashboard/database/backups/update-backup.tsx @@ -116,8 +116,12 @@ export const UpdateBackup = ({ backupId, refetch }: Props) => { return ( - diff --git a/apps/dokploy/components/dashboard/docker/logs/docker-logs-id.tsx b/apps/dokploy/components/dashboard/docker/logs/docker-logs-id.tsx index 1fd8cea48..b638991cf 100644 --- a/apps/dokploy/components/dashboard/docker/logs/docker-logs-id.tsx +++ b/apps/dokploy/components/dashboard/docker/logs/docker-logs-id.tsx @@ -226,7 +226,7 @@ export const DockerLogsId: React.FC = ({ return (
-
+
diff --git a/apps/dokploy/components/dashboard/docker/show/show-containers.tsx b/apps/dokploy/components/dashboard/docker/show/show-containers.tsx index e55e6271f..8c1acfe01 100644 --- a/apps/dokploy/components/dashboard/docker/show/show-containers.tsx +++ b/apps/dokploy/components/dashboard/docker/show/show-containers.tsx @@ -9,10 +9,18 @@ import { getSortedRowModel, useReactTable, } from "@tanstack/react-table"; -import { ChevronDown } from "lucide-react"; +import { ChevronDown, Container } from "lucide-react"; import * as React from "react"; +import { AlertBlock } from "@/components/shared/alert-block"; import { Button } from "@/components/ui/button"; +import { + Card, + CardContent, + CardDescription, + CardHeader, + CardTitle, +} from "@/components/ui/card"; import { DropdownMenu, DropdownMenuCheckboxItem, @@ -71,139 +79,164 @@ export const ShowContainers = ({ serverId }: Props) => { }); return ( -
-
-
- - table.getColumn("name")?.setFilterValue(event.target.value) - } - className="md:max-w-sm" - /> - - - - - - {table - .getAllColumns() - .filter((column) => column.getCanHide()) - .map((column) => { - return ( - - column.toggleVisibility(!!value) - } - > - {column.id} - - ); - })} - - -
-
- {isLoading ? ( -
- - Loading... - -
- ) : data?.length === 0 ? ( -
- - No results. - -
- ) : ( - - - {table.getHeaderGroups().map((headerGroup) => ( - - {headerGroup.headers.map((header) => { - return ( - - {header.isPlaceholder - ? null - : flexRender( - header.column.columnDef.header, - header.getContext(), +
+ +
+ + + + Docker Containers + + + See all the containers of your dokploy server + + + +
+
+
+ + table + .getColumn("name") + ?.setFilterValue(event.target.value) + } + className="md:max-w-sm" + /> + + + + + + {table + .getAllColumns() + .filter((column) => column.getCanHide()) + .map((column) => { + return ( + + column.toggleVisibility(!!value) + } + > + {column.id} + + ); + })} + + +
+
+ {isLoading ? ( +
+ + Loading... + +
+ ) : data?.length === 0 ? ( +
+ + No results. + +
+ ) : ( +
+ + {table.getHeaderGroups().map((headerGroup) => ( + + {headerGroup.headers.map((header) => { + return ( + + {header.isPlaceholder + ? null + : flexRender( + header.column.columnDef.header, + header.getContext(), + )} + + ); + })} + + ))} + + + {table?.getRowModel()?.rows?.length ? ( + table.getRowModel().rows.map((row) => ( + + {row.getVisibleCells().map((cell) => ( + + {flexRender( + cell.column.columnDef.cell, + cell.getContext(), + )} + + ))} + + )) + ) : ( + + + {isLoading ? ( +
+ + Loading... + +
+ ) : ( + <>No results. )} - - ); - })} -
- ))} - - - {table?.getRowModel()?.rows?.length ? ( - table.getRowModel().rows.map((row) => ( - - {row.getVisibleCells().map((cell) => ( - - {flexRender( - cell.column.columnDef.cell, - cell.getContext(), - )} - - ))} - - )) - ) : ( - - - {isLoading ? ( -
- - Loading... - -
- ) : ( - <>No results. - )} -
-
+ + + )} +
+
+ )} +
+ {data && data?.length > 0 && ( +
+
+ + +
+
)} - - - )} -
- {data && data?.length > 0 && ( -
-
- - +
-
- )} -
+ +
+
); }; diff --git a/apps/dokploy/components/dashboard/file-system/show-traefik-system.tsx b/apps/dokploy/components/dashboard/file-system/show-traefik-system.tsx index 0aaf9990b..d2263f747 100644 --- a/apps/dokploy/components/dashboard/file-system/show-traefik-system.tsx +++ b/apps/dokploy/components/dashboard/file-system/show-traefik-system.tsx @@ -1,8 +1,15 @@ import { AlertBlock } from "@/components/shared/alert-block"; +import { + Card, + CardContent, + CardDescription, + CardHeader, + CardTitle, +} from "@/components/ui/card"; import { Tree } from "@/components/ui/file-tree"; import { cn } from "@/lib/utils"; import { api } from "@/utils/api"; -import { FileIcon, Folder, Loader2, Workflow } from "lucide-react"; +import { FileIcon, Folder, Link, Loader2, Workflow } from "lucide-react"; import React from "react"; import { ShowTraefikFile } from "./show-traefik-file"; @@ -27,53 +34,77 @@ export const ShowTraefikSystem = ({ serverId }: Props) => { ); return ( -
-
- {isError && ( - - {error?.message} - - )} - {isLoading && ( -
- - Loading... - - -
- )} - {directories?.length === 0 && ( -
- - No directories or files detected in {"'/etc/dokploy/traefik'"} - - -
- )} - {directories && directories?.length > 0 && ( - <> - setFile(item?.id || null)} - folderIcon={Folder} - itemIcon={Workflow} - /> -
- {file ? ( - - ) : ( -
- - No file selected - - -
- )} +
+ +
+ + + + Traefik File System + + + Manage all the files and directories in {"'/etc/dokploy/traefik'"} + . + + + + Adding invalid configuration to existing files, can break your + Traefik instance, preventing access to your applications. + + + +
+
+ {isError && ( + + {error?.message} + + )} + {isLoading && ( +
+ + Loading... + + +
+ )} + {directories?.length === 0 && ( +
+ + No directories or files detected in{" "} + {"'/etc/dokploy/traefik'"} + + +
+ )} + {directories && directories?.length > 0 && ( + <> + setFile(item?.id || null)} + folderIcon={Folder} + itemIcon={Workflow} + /> +
+ {file ? ( + + ) : ( +
+ + No file selected + + +
+ )} +
+ + )} +
- - )} -
+ +
+
); }; diff --git a/apps/dokploy/components/dashboard/mariadb/advanced/show-mariadb-advanced-settings.tsx b/apps/dokploy/components/dashboard/mariadb/advanced/show-mariadb-advanced-settings.tsx deleted file mode 100644 index b4c0129dd..000000000 --- a/apps/dokploy/components/dashboard/mariadb/advanced/show-mariadb-advanced-settings.tsx +++ /dev/null @@ -1,129 +0,0 @@ -import { Button } from "@/components/ui/button"; -import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; -import { - Form, - FormControl, - FormField, - FormItem, - FormLabel, - FormMessage, -} from "@/components/ui/form"; -import { Input } from "@/components/ui/input"; -import { api } from "@/utils/api"; -import { zodResolver } from "@hookform/resolvers/zod"; -import React, { useEffect } from "react"; -import { useForm } from "react-hook-form"; -import { toast } from "sonner"; -import { z } from "zod"; -import { ShowVolumes } from "../volumes/show-volumes"; -import { ShowMariadbResources } from "./show-mariadb-resources"; - -const addDockerImage = z.object({ - dockerImage: z.string().min(1, "Docker image is required"), - command: z.string(), -}); - -interface Props { - mariadbId: string; -} - -type AddDockerImage = z.infer; -export const ShowAdvancedMariadb = ({ mariadbId }: Props) => { - const { data, refetch } = api.mariadb.one.useQuery( - { - mariadbId, - }, - { enabled: !!mariadbId }, - ); - const { mutateAsync } = api.mariadb.update.useMutation(); - - const form = useForm({ - defaultValues: { - dockerImage: "", - command: "", - }, - resolver: zodResolver(addDockerImage), - }); - - useEffect(() => { - if (data) { - form.reset({ - dockerImage: data.dockerImage, - command: data.command || "", - }); - } - }, [data, form, form.formState.isSubmitSuccessful, form.reset]); - - const onSubmit = async (formData: AddDockerImage) => { - await mutateAsync({ - mariadbId, - dockerImage: formData?.dockerImage, - command: formData?.command, - }) - .then(async () => { - toast.success("Docker Image Updated"); - await refetch(); - }) - .catch(() => { - toast.error("Error updating the resources"); - }); - }; - return ( - <> -
- - - Advanced Settings - - -
- -
- ( - - Docker Image - - - - - - - )} - /> - - ( - - Command - - - - - - - )} - /> -
-
- -
-
- -
-
- - -
- - ); -}; diff --git a/apps/dokploy/components/dashboard/mariadb/advanced/show-mariadb-resources.tsx b/apps/dokploy/components/dashboard/mariadb/advanced/show-mariadb-resources.tsx deleted file mode 100644 index 7624441ea..000000000 --- a/apps/dokploy/components/dashboard/mariadb/advanced/show-mariadb-resources.tsx +++ /dev/null @@ -1,296 +0,0 @@ -import { AlertBlock } from "@/components/shared/alert-block"; -import { Button } from "@/components/ui/button"; -import { - Card, - CardContent, - CardDescription, - CardHeader, - CardTitle, -} from "@/components/ui/card"; -import { - Form, - FormControl, - FormField, - FormItem, - FormLabel, - FormMessage, -} from "@/components/ui/form"; -import { Input } from "@/components/ui/input"; -import { api } from "@/utils/api"; -import { zodResolver } from "@hookform/resolvers/zod"; -import { - TooltipProvider, - TooltipTrigger, - TooltipContent, - Tooltip, -} from "@/components/ui/tooltip"; -import { InfoIcon } from "lucide-react"; -import React, { useEffect } from "react"; -import { useForm } from "react-hook-form"; -import { toast } from "sonner"; -import { z } from "zod"; - -const addResourcesMariadb = z.object({ - memoryReservation: z.number().nullable().optional(), - cpuLimit: z.number().nullable().optional(), - memoryLimit: z.number().nullable().optional(), - cpuReservation: z.number().nullable().optional(), -}); -interface Props { - mariadbId: string; -} - -type AddResourcesMariadb = z.infer; -export const ShowMariadbResources = ({ mariadbId }: Props) => { - const { data, refetch } = api.mariadb.one.useQuery( - { - mariadbId, - }, - { enabled: !!mariadbId }, - ); - const { mutateAsync, isLoading } = api.mariadb.update.useMutation(); - const form = useForm({ - defaultValues: {}, - resolver: zodResolver(addResourcesMariadb), - }); - - useEffect(() => { - if (data) { - form.reset({ - cpuLimit: data?.cpuLimit || undefined, - cpuReservation: data?.cpuReservation || undefined, - memoryLimit: data?.memoryLimit || undefined, - memoryReservation: data?.memoryReservation || undefined, - }); - } - }, [data, form, form.formState.isSubmitSuccessful, form.reset]); - - const onSubmit = async (formData: AddResourcesMariadb) => { - await mutateAsync({ - mariadbId, - cpuLimit: formData.cpuLimit || null, - cpuReservation: formData.cpuReservation || null, - memoryLimit: formData.memoryLimit || null, - memoryReservation: formData.memoryReservation || null, - }) - .then(async () => { - toast.success("Resources Updated"); - await refetch(); - }) - .catch(() => { - toast.error("Error updating the resources"); - }); - }; - return ( - - - Resources - - If you want to decrease or increase the resources to a specific. - application or database - - - - - Please remember to click Redeploy after modify the resources to apply - the changes. - -
- -
- ( - -
- Memory Reservation - - - - - - -

- Memory soft limit in bytes. Example: 256MB = - 268435456 bytes -

-
-
-
-
- - { - const value = e.target.value; - if (value === "") { - field.onChange(null); - } else { - const number = Number.parseInt(value, 10); - if (!Number.isNaN(number)) { - field.onChange(number); - } - } - }} - /> - - -
- )} - /> - - { - return ( - -
- Memory Limit - - - - - - -

- Memory hard limit in bytes. Example: 1GB = - 1073741824 bytes -

-
-
-
-
- - { - const value = e.target.value; - if (value === "") { - field.onChange(null); - } else { - const number = Number.parseInt(value, 10); - if (!Number.isNaN(number)) { - field.onChange(number); - } - } - }} - /> - - -
- ); - }} - /> - - { - return ( - -
- CPU Limit - - - - - - -

- CPU quota in units of 10^-9 CPUs. Example: 2 - CPUs = 2000000000 -

-
-
-
-
- - { - const value = e.target.value; - if (value === "") { - field.onChange(null); - } else { - const number = Number.parseInt(value, 10); - if (!Number.isNaN(number)) { - field.onChange(number); - } - } - }} - /> - - -
- ); - }} - /> - { - return ( - -
- CPU Reservation - - - - - - -

- CPU shares (relative weight). Example: 1 CPU = - 1000000000 -

-
-
-
-
- - { - const value = e.target.value; - if (value === "") { - field.onChange(null); - } else { - const number = Number.parseInt(value, 10); - if (!Number.isNaN(number)) { - field.onChange(number); - } - } - }} - /> - - -
- ); - }} - /> -
-
- -
-
- -
-
- ); -}; diff --git a/apps/dokploy/components/dashboard/mariadb/backups/show-backup-mariadb.tsx b/apps/dokploy/components/dashboard/mariadb/backups/show-backup-mariadb.tsx deleted file mode 100644 index cac2820f9..000000000 --- a/apps/dokploy/components/dashboard/mariadb/backups/show-backup-mariadb.tsx +++ /dev/null @@ -1,178 +0,0 @@ -import { Button } from "@/components/ui/button"; -import { - Card, - CardContent, - CardDescription, - CardHeader, - CardTitle, -} from "@/components/ui/card"; -import { - Tooltip, - TooltipContent, - TooltipProvider, - TooltipTrigger, -} from "@/components/ui/tooltip"; -import { api } from "@/utils/api"; -import { DatabaseBackup, Play } from "lucide-react"; -import Link from "next/link"; -import React from "react"; -import { toast } from "sonner"; -import { AddBackup } from "../../database/backups/add-backup"; -import { DeleteBackup } from "../../database/backups/delete-backup"; -import { UpdateBackup } from "../../database/backups/update-backup"; -interface Props { - mariadbId: string; -} - -export const ShowBackupMariadb = ({ mariadbId }: Props) => { - const { data } = api.destination.all.useQuery(); - const { data: mariadb, refetch: refetchMariadb } = api.mariadb.one.useQuery( - { - mariadbId, - }, - { - enabled: !!mariadbId, - }, - ); - - const { mutateAsync: manualBackup, isLoading: isManualBackup } = - api.backup.manualBackupMariadb.useMutation(); - - return ( - - -
- Backups - - Add backups to your database to save the data to a different - providers. - -
- - {mariadb && mariadb?.backups?.length > 0 && ( - - )} -
- - {data?.length === 0 ? ( -
- - - To create a backup it is required to set at least 1 provider. - Please, go to{" "} - - Settings - {" "} - to do so. - -
- ) : ( -
- {mariadb?.backups.length === 0 ? ( -
- - - No backups configured - - -
- ) : ( -
-
- {mariadb?.backups.map((backup) => ( -
-
-
-
- Destination - - {backup.destination.name} - -
-
- Database - - {backup.database} - -
-
- Scheduled - - {backup.schedule} - -
-
- Prefix Storage - - {backup.prefix} - -
-
- Enabled - - {backup.enabled ? "Yes" : "No"} - -
-
-
- - - - - - Run Manual Backup - - - - -
-
-
- ))} -
-
- )} -
- )} -
-
- ); -}; diff --git a/apps/dokploy/components/dashboard/mariadb/delete-mariadb.tsx b/apps/dokploy/components/dashboard/mariadb/delete-mariadb.tsx deleted file mode 100644 index 1956954a0..000000000 --- a/apps/dokploy/components/dashboard/mariadb/delete-mariadb.tsx +++ /dev/null @@ -1,158 +0,0 @@ -import { Badge } from "@/components/ui/badge"; -import { Button } from "@/components/ui/button"; -import { - Dialog, - DialogContent, - DialogDescription, - DialogFooter, - DialogHeader, - DialogTitle, - DialogTrigger, -} from "@/components/ui/dialog"; -import { - Form, - FormControl, - FormField, - FormItem, - FormLabel, - FormMessage, -} from "@/components/ui/form"; -import { Input } from "@/components/ui/input"; -import { api } from "@/utils/api"; -import { zodResolver } from "@hookform/resolvers/zod"; -import { Copy, TrashIcon } from "lucide-react"; -import { useRouter } from "next/router"; -import { useState } from "react"; -import { useForm } from "react-hook-form"; -import { toast } from "sonner"; -import { z } from "zod"; - -const deleteMariadbSchema = z.object({ - projectName: z.string().min(1, { - message: "Database name is required", - }), -}); - -type DeleteMariadb = z.infer; - -interface Props { - mariadbId: string; -} -export const DeleteMariadb = ({ mariadbId }: Props) => { - const [isOpen, setIsOpen] = useState(false); - const { mutateAsync, isLoading } = api.mariadb.remove.useMutation(); - const { data } = api.mariadb.one.useQuery( - { mariadbId }, - { enabled: !!mariadbId }, - ); - const { push } = useRouter(); - const form = useForm({ - defaultValues: { - projectName: "", - }, - resolver: zodResolver(deleteMariadbSchema), - }); - - const onSubmit = async (formData: DeleteMariadb) => { - const expectedName = `${data?.name}/${data?.appName}`; - if (formData.projectName === expectedName) { - await mutateAsync({ mariadbId }) - .then((data) => { - push(`/dashboard/project/${data?.projectId}`); - toast.success("Database deleted successfully"); - setIsOpen(false); - }) - .catch(() => { - toast.error("Error deleting the database"); - }); - } else { - form.setError("projectName", { - message: "Database name does not match", - }); - } - }; - - return ( - - - - - - - Are you absolutely sure? - - This action cannot be undone. This will permanently delete the - database. If you are sure please enter the database name to delete - this database. - - -
-
- - ( - - - - To confirm, type{" "} - { - if (data?.name && data?.appName) { - navigator.clipboard.writeText( - `${data.name}/${data.appName}`, - ); - toast.success("Copied to clipboard. Be careful!"); - } - }} - > - {data?.name}/{data?.appName}  - - {" "} - in the box below: - - - - - - - - )} - /> - - -
- - - - -
-
- ); -}; diff --git a/apps/dokploy/components/dashboard/mariadb/environment/show-mariadb-environment.tsx b/apps/dokploy/components/dashboard/mariadb/environment/show-mariadb-environment.tsx deleted file mode 100644 index 418ae001c..000000000 --- a/apps/dokploy/components/dashboard/mariadb/environment/show-mariadb-environment.tsx +++ /dev/null @@ -1,141 +0,0 @@ -import { CodeEditor } from "@/components/shared/code-editor"; -import { Button } from "@/components/ui/button"; -import { - Card, - CardContent, - CardDescription, - CardHeader, - CardTitle, -} from "@/components/ui/card"; -import { - Form, - FormControl, - FormField, - FormItem, - FormMessage, -} from "@/components/ui/form"; -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, { useEffect, useState } from "react"; -import { useForm } from "react-hook-form"; -import { toast } from "sonner"; -import { z } from "zod"; - -const addEnvironmentSchema = z.object({ - environment: z.string(), -}); - -type EnvironmentSchema = z.infer; - -interface Props { - mariadbId: string; -} - -export const ShowMariadbEnvironment = ({ mariadbId }: Props) => { - const [isEnvVisible, setIsEnvVisible] = useState(true); - const { mutateAsync, isLoading } = api.mariadb.saveEnvironment.useMutation(); - - const { data, refetch } = api.mariadb.one.useQuery( - { - mariadbId, - }, - { - enabled: !!mariadbId, - }, - ); - const form = useForm({ - defaultValues: { - environment: "", - }, - resolver: zodResolver(addEnvironmentSchema), - }); - - useEffect(() => { - if (data) { - form.reset({ - environment: data.env || "", - }); - } - }, [form.reset, data, form]); - - const onSubmit = async (data: EnvironmentSchema) => { - mutateAsync({ - env: data.environment, - mariadbId, - }) - .then(async () => { - toast.success("Environments Added"); - await refetch(); - }) - .catch(() => { - toast.error("Error adding environment"); - }); - }; - - return ( -
- - {" "} - -
- Environment Settings - - You can add environment variables to your resource. - -
- - - {isEnvVisible ? ( - - ) : ( - - )} - -
- -
- - ( - - - - - - - - )} - /> - -
- -
- - -
-
-
- ); -}; diff --git a/apps/dokploy/components/dashboard/mariadb/general/deploy-mariadb.tsx b/apps/dokploy/components/dashboard/mariadb/general/deploy-mariadb.tsx deleted file mode 100644 index 50bf58196..000000000 --- a/apps/dokploy/components/dashboard/mariadb/general/deploy-mariadb.tsx +++ /dev/null @@ -1,73 +0,0 @@ -import { - AlertDialog, - AlertDialogAction, - AlertDialogCancel, - AlertDialogContent, - AlertDialogDescription, - AlertDialogFooter, - AlertDialogHeader, - AlertDialogTitle, - AlertDialogTrigger, -} from "@/components/ui/alert-dialog"; -import { Button } from "@/components/ui/button"; -import { api } from "@/utils/api"; -import { toast } from "sonner"; - -interface Props { - mariadbId: string; -} - -export const DeployMariadb = ({ mariadbId }: Props) => { - const { data, refetch } = api.mariadb.one.useQuery( - { - mariadbId, - }, - { enabled: !!mariadbId }, - ); - const { mutateAsync: deploy } = api.mariadb.deploy.useMutation(); - const { mutateAsync: changeStatus } = api.mariadb.changeStatus.useMutation(); - - return ( - - - - - - - Are you absolutely sure? - - This will deploy the mariadb database - - - - Cancel - { - await changeStatus({ - mariadbId, - applicationStatus: "running", - }) - .then(async () => { - toast.success("Deploying Database...."); - await refetch(); - await deploy({ - mariadbId, - }).catch(() => { - toast.error("Error deploying Database"); - }); - await refetch(); - }) - .catch((e) => { - toast.error(e.message || "Error deploying Database"); - }); - }} - > - Confirm - - - - - ); -}; diff --git a/apps/dokploy/components/dashboard/mariadb/general/reset-mariadb.tsx b/apps/dokploy/components/dashboard/mariadb/general/reset-mariadb.tsx deleted file mode 100644 index bee3d8ba9..000000000 --- a/apps/dokploy/components/dashboard/mariadb/general/reset-mariadb.tsx +++ /dev/null @@ -1,69 +0,0 @@ -import { - AlertDialog, - AlertDialogAction, - AlertDialogCancel, - AlertDialogContent, - AlertDialogDescription, - AlertDialogFooter, - AlertDialogHeader, - AlertDialogTitle, - AlertDialogTrigger, -} from "@/components/ui/alert-dialog"; -import { Button } from "@/components/ui/button"; -import { api } from "@/utils/api"; -import { RefreshCcw } from "lucide-react"; -import { toast } from "sonner"; - -interface Props { - mariadbId: string; - appName: string; -} - -export const ResetMariadb = ({ mariadbId, appName }: Props) => { - const { refetch } = api.mariadb.one.useQuery( - { - mariadbId, - }, - { enabled: !!mariadbId }, - ); - const { mutateAsync: reload, isLoading } = api.mariadb.reload.useMutation(); - - return ( - - - - - - - Are you absolutely sure? - - This will reload the service - - - - Cancel - { - await reload({ - mariadbId, - appName, - }) - .then(() => { - toast.success("Service Reloaded"); - }) - .catch(() => { - toast.error("Error reloading the service"); - }); - await refetch(); - }} - > - Confirm - - - - - ); -}; diff --git a/apps/dokploy/components/dashboard/mariadb/general/show-general-mariadb.tsx b/apps/dokploy/components/dashboard/mariadb/general/show-general-mariadb.tsx index 925e213d8..98773685f 100644 --- a/apps/dokploy/components/dashboard/mariadb/general/show-general-mariadb.tsx +++ b/apps/dokploy/components/dashboard/mariadb/general/show-general-mariadb.tsx @@ -1,26 +1,62 @@ +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 { api } from "@/utils/api"; -import { Terminal } from "lucide-react"; -import React from "react"; +import { Ban, CheckCircle2, RefreshCcw, Terminal } from "lucide-react"; +import React, { useState } from "react"; +import { toast } from "sonner"; +import { type LogLine, parseLogs } from "../../docker/logs/utils"; import { DockerTerminalModal } from "../../settings/web-server/docker-terminal-modal"; -import { StartMariadb } from "../start-mariadb"; -import { DeployMariadb } from "./deploy-mariadb"; -import { ResetMariadb } from "./reset-mariadb"; -import { StopMariadb } from "./stop-mariadb"; interface Props { mariadbId: string; } export const ShowGeneralMariadb = ({ mariadbId }: Props) => { - const { data } = api.mariadb.one.useQuery( + const { data, refetch } = api.mariadb.one.useQuery( { mariadbId, }, { enabled: !!mariadbId }, ); + const { mutateAsync: reload, isLoading: isReloading } = + api.mariadb.reload.useMutation(); + + const { mutateAsync: start, isLoading: isStarting } = + api.mariadb.start.useMutation(); + + const { mutateAsync: stop, isLoading: isStopping } = + api.mariadb.stop.useMutation(); + + const [isDrawerOpen, setIsDrawerOpen] = useState(false); + const [filteredLogs, setFilteredLogs] = useState([]); + const [isDeploying, setIsDeploying] = useState(false); + api.mariadb.deployWithLogs.useSubscription( + { + mariadbId: mariadbId, + }, + { + enabled: isDeploying, + onData(log) { + if (!isDrawerOpen) { + setIsDrawerOpen(true); + } + + if (log === "Deployment completed successfully!") { + setIsDeploying(false); + } + const parsedLogs = parseLogs(log); + setFilteredLogs((prev) => [...prev, ...parsedLogs]); + }, + onError(error) { + console.error("Deployment logs error:", error); + setIsDeploying(false); + }, + }, + ); + return ( <>
@@ -29,12 +65,91 @@ export const ShowGeneralMariadb = ({ mariadbId }: Props) => { Deploy Settings - - + { + setIsDeploying(true); + await new Promise((resolve) => setTimeout(resolve, 1000)); + refetch(); + }} + > + + + { + 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"); + }); + }} + > + + ) : ( - + { + await stop({ + mariadbId: mariadbId, + }) + .then(() => { + toast.success("Mariadb stopped successfully"); + refetch(); + }) + .catch(() => { + toast.error("Error stopping Mariadb"); + }); + }} + > + + )} { + { + setIsDrawerOpen(false); + setFilteredLogs([]); + setIsDeploying(false); + refetch(); + }} + filteredLogs={filteredLogs} + />
); diff --git a/apps/dokploy/components/dashboard/mariadb/general/stop-mariadb.tsx b/apps/dokploy/components/dashboard/mariadb/general/stop-mariadb.tsx deleted file mode 100644 index 55dc13670..000000000 --- a/apps/dokploy/components/dashboard/mariadb/general/stop-mariadb.tsx +++ /dev/null @@ -1,65 +0,0 @@ -import { - AlertDialog, - AlertDialogAction, - AlertDialogCancel, - AlertDialogContent, - AlertDialogDescription, - AlertDialogFooter, - AlertDialogHeader, - AlertDialogTitle, - AlertDialogTrigger, -} from "@/components/ui/alert-dialog"; -import { Button } from "@/components/ui/button"; -import { api } from "@/utils/api"; -import { Ban } from "lucide-react"; -import { toast } from "sonner"; - -interface Props { - mariadbId: string; -} - -export const StopMariadb = ({ mariadbId }: Props) => { - const { mutateAsync, isLoading } = api.mariadb.stop.useMutation(); - const utils = api.useUtils(); - return ( - - - - - - - - Are you absolutely sure to stop the database? - - - This will stop the database - - - - Cancel - { - await mutateAsync({ - mariadbId, - }) - .then(async () => { - await utils.mariadb.one.invalidate({ - mariadbId, - }); - toast.success("Application stopped successfully"); - }) - .catch(() => { - toast.error("Error stopping the Application"); - }); - }} - > - Confirm - - - - - ); -}; diff --git a/apps/dokploy/components/dashboard/mariadb/start-mariadb.tsx b/apps/dokploy/components/dashboard/mariadb/start-mariadb.tsx deleted file mode 100644 index 4c62f0a0c..000000000 --- a/apps/dokploy/components/dashboard/mariadb/start-mariadb.tsx +++ /dev/null @@ -1,65 +0,0 @@ -import { - AlertDialog, - AlertDialogAction, - AlertDialogCancel, - AlertDialogContent, - AlertDialogDescription, - AlertDialogFooter, - AlertDialogHeader, - AlertDialogTitle, - AlertDialogTrigger, -} from "@/components/ui/alert-dialog"; -import { Button } from "@/components/ui/button"; -import { api } from "@/utils/api"; -import { CheckCircle2 } from "lucide-react"; -import { toast } from "sonner"; - -interface Props { - mariadbId: string; -} - -export const StartMariadb = ({ mariadbId }: Props) => { - const { mutateAsync, isLoading } = api.mariadb.start.useMutation(); - const utils = api.useUtils(); - return ( - - - - - - - - Are you sure to start the database? - - - This will start the database - - - - Cancel - { - await mutateAsync({ - mariadbId, - }) - .then(async () => { - await utils.mariadb.one.invalidate({ - mariadbId, - }); - toast.success("Database started successfully"); - }) - .catch(() => { - toast.error("Error starting the Database"); - }); - }} - > - Confirm - - - - - ); -}; diff --git a/apps/dokploy/components/dashboard/mariadb/update-mariadb.tsx b/apps/dokploy/components/dashboard/mariadb/update-mariadb.tsx index 9b7c258ee..4c9be0903 100644 --- a/apps/dokploy/components/dashboard/mariadb/update-mariadb.tsx +++ b/apps/dokploy/components/dashboard/mariadb/update-mariadb.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, SquarePen } from "lucide-react"; +import { AlertTriangle, PenBoxIcon, SquarePen } from "lucide-react"; import { useEffect } from "react"; import { useForm } from "react-hook-form"; import { toast } from "sonner"; @@ -89,8 +89,12 @@ export const UpdateMariadb = ({ mariadbId }: Props) => { return ( - diff --git a/apps/dokploy/components/dashboard/mariadb/volumes/show-volumes.tsx b/apps/dokploy/components/dashboard/mariadb/volumes/show-volumes.tsx deleted file mode 100644 index a4a88bbe5..000000000 --- a/apps/dokploy/components/dashboard/mariadb/volumes/show-volumes.tsx +++ /dev/null @@ -1,133 +0,0 @@ -import { AlertBlock } from "@/components/shared/alert-block"; -import { - Card, - CardContent, - CardDescription, - CardHeader, - CardTitle, -} from "@/components/ui/card"; -import { api } from "@/utils/api"; -import { AlertTriangle, Package } from "lucide-react"; -import React from "react"; -import { AddVolumes } from "../../application/advanced/volumes/add-volumes"; -import { DeleteVolume } from "../../application/advanced/volumes/delete-volume"; -import { UpdateVolume } from "../../application/advanced/volumes/update-volume"; -interface Props { - mariadbId: string; -} - -export const ShowVolumes = ({ mariadbId }: Props) => { - const { data, refetch } = api.mariadb.one.useQuery( - { - mariadbId, - }, - { enabled: !!mariadbId }, - ); - - return ( - - -
- Volumes - - If you want to persist data in this mariadb use the following config - to setup the volumes - -
- - {data && data?.mounts.length > 0 && ( - - Add Volume - - )} -
- - {data?.mounts.length === 0 ? ( -
- - - No volumes/mounts configured - - - Add Volume - -
- ) : ( -
- - Please remember to click Redeploy after adding, editing, or - deleting a mount to apply the changes. - -
- {data?.mounts.map((mount) => ( -
-
-
-
- Mount Type - - {mount.type.toUpperCase()} - -
- {mount.type === "volume" && ( -
- Volume Name - - {mount.volumeName} - -
- )} - - {mount.type === "file" && ( -
- Content - - {mount.content} - -
- )} - {mount.type === "bind" && ( -
- Host Path - - {mount.hostPath} - -
- )} -
- Mount Path - - {mount.mountPath} - -
-
-
- - -
-
-
- ))} -
-
- )} -
-
- ); -}; diff --git a/apps/dokploy/components/dashboard/mongo/advanced/show-mongo-advanced-settings.tsx b/apps/dokploy/components/dashboard/mongo/advanced/show-mongo-advanced-settings.tsx deleted file mode 100644 index d3a308f82..000000000 --- a/apps/dokploy/components/dashboard/mongo/advanced/show-mongo-advanced-settings.tsx +++ /dev/null @@ -1,128 +0,0 @@ -import { Button } from "@/components/ui/button"; -import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; -import { - Form, - FormControl, - FormField, - FormItem, - FormLabel, - FormMessage, -} from "@/components/ui/form"; -import { Input } from "@/components/ui/input"; -import { api } from "@/utils/api"; -import { zodResolver } from "@hookform/resolvers/zod"; -import React, { useEffect } from "react"; -import { useForm } from "react-hook-form"; -import { toast } from "sonner"; -import { z } from "zod"; -import { ShowVolumes } from "../volumes/show-volumes"; -import { ShowMongoResources } from "./show-mongo-resources"; - -const addDockerImage = z.object({ - dockerImage: z.string().min(1, "Docker image is required"), - command: z.string(), -}); - -interface Props { - mongoId: string; -} - -type AddDockerImage = z.infer; -export const ShowAdvancedMongo = ({ mongoId }: Props) => { - const { data, refetch } = api.mongo.one.useQuery( - { - mongoId, - }, - { enabled: !!mongoId }, - ); - const { mutateAsync } = api.mongo.update.useMutation(); - - const form = useForm({ - defaultValues: { - dockerImage: "", - command: "", - }, - resolver: zodResolver(addDockerImage), - }); - - useEffect(() => { - if (data) { - form.reset({ - dockerImage: data.dockerImage, - command: data.command || "", - }); - } - }, [data, form, form.formState.isSubmitSuccessful, form.reset]); - - const onSubmit = async (formData: AddDockerImage) => { - await mutateAsync({ - mongoId, - dockerImage: formData?.dockerImage, - command: formData?.command, - }) - .then(async () => { - toast.success("Resources Updated"); - await refetch(); - }) - .catch(() => { - toast.error("Error updating the resources"); - }); - }; - return ( - <> -
- - - Advanced Settings - - -
- -
- ( - - Docker Image - - - - - - - )} - /> - ( - - Command - - - - - - - )} - /> -
-
- -
-
- -
-
- - -
- - ); -}; diff --git a/apps/dokploy/components/dashboard/mongo/advanced/show-mongo-resources.tsx b/apps/dokploy/components/dashboard/mongo/advanced/show-mongo-resources.tsx deleted file mode 100644 index d4824f526..000000000 --- a/apps/dokploy/components/dashboard/mongo/advanced/show-mongo-resources.tsx +++ /dev/null @@ -1,296 +0,0 @@ -import { AlertBlock } from "@/components/shared/alert-block"; -import { Button } from "@/components/ui/button"; -import { - Card, - CardContent, - CardDescription, - CardHeader, - CardTitle, -} from "@/components/ui/card"; -import { - Form, - FormControl, - FormField, - FormItem, - FormLabel, - FormMessage, -} from "@/components/ui/form"; -import { Input } from "@/components/ui/input"; -import { api } from "@/utils/api"; -import { zodResolver } from "@hookform/resolvers/zod"; -import React, { useEffect } from "react"; -import { - TooltipProvider, - TooltipTrigger, - TooltipContent, - Tooltip, -} from "@/components/ui/tooltip"; -import { InfoIcon } from "lucide-react"; -import { useForm } from "react-hook-form"; -import { toast } from "sonner"; -import { z } from "zod"; - -const addResourcesMongo = z.object({ - memoryReservation: z.number().nullable().optional(), - cpuLimit: z.number().nullable().optional(), - memoryLimit: z.number().nullable().optional(), - cpuReservation: z.number().nullable().optional(), -}); -interface Props { - mongoId: string; -} - -type AddResourcesMongo = z.infer; -export const ShowMongoResources = ({ mongoId }: Props) => { - const { data, refetch } = api.mongo.one.useQuery( - { - mongoId, - }, - { enabled: !!mongoId }, - ); - const { mutateAsync, isLoading } = api.mongo.update.useMutation(); - const form = useForm({ - defaultValues: {}, - resolver: zodResolver(addResourcesMongo), - }); - - useEffect(() => { - if (data) { - form.reset({ - cpuLimit: data?.cpuLimit || undefined, - cpuReservation: data?.cpuReservation || undefined, - memoryLimit: data?.memoryLimit || undefined, - memoryReservation: data?.memoryReservation || undefined, - }); - } - }, [data, form, form.reset]); - - const onSubmit = async (formData: AddResourcesMongo) => { - await mutateAsync({ - mongoId, - cpuLimit: formData.cpuLimit || null, - cpuReservation: formData.cpuReservation || null, - memoryLimit: formData.memoryLimit || null, - memoryReservation: formData.memoryReservation || null, - }) - .then(async () => { - toast.success("Resources Updated"); - await refetch(); - }) - .catch(() => { - toast.error("Error updating the resources"); - }); - }; - return ( - - - Resources - - If you want to decrease or increase the resources to a specific. - application or database - - - - - Please remember to click Redeploy after modify the resources to apply - the changes. - -
- -
- ( - -
- Memory Reservation - - - - - - -

- Memory soft limit in bytes. Example: 256MB = - 268435456 bytes -

-
-
-
-
- - { - const value = e.target.value; - if (value === "") { - field.onChange(null); - } else { - const number = Number.parseInt(value, 10); - if (!Number.isNaN(number)) { - field.onChange(number); - } - } - }} - /> - - -
- )} - /> - - { - return ( - -
- Memory Limit - - - - - - -

- Memory hard limit in bytes. Example: 1GB = - 1073741824 bytes -

-
-
-
-
- - { - const value = e.target.value; - if (value === "") { - field.onChange(null); - } else { - const number = Number.parseInt(value, 10); - if (!Number.isNaN(number)) { - field.onChange(number); - } - } - }} - /> - - -
- ); - }} - /> - - { - return ( - -
- CPU Limit - - - - - - -

- CPU quota in units of 10^-9 CPUs. Example: 2 - CPUs = 2000000000 -

-
-
-
-
- - { - const value = e.target.value; - if (value === "") { - field.onChange(null); - } else { - const number = Number.parseInt(value, 10); - if (!Number.isNaN(number)) { - field.onChange(number); - } - } - }} - /> - - -
- ); - }} - /> - { - return ( - -
- CPU Reservation - - - - - - -

- CPU shares (relative weight). Example: 1 CPU = - 1000000000 -

-
-
-
-
- - { - const value = e.target.value; - if (value === "") { - field.onChange(null); - } else { - const number = Number.parseInt(value, 10); - if (!Number.isNaN(number)) { - field.onChange(number); - } - } - }} - /> - - -
- ); - }} - /> -
-
- -
-
- -
-
- ); -}; diff --git a/apps/dokploy/components/dashboard/mongo/backups/show-backup-mongo.tsx b/apps/dokploy/components/dashboard/mongo/backups/show-backup-mongo.tsx deleted file mode 100644 index bc1b4c613..000000000 --- a/apps/dokploy/components/dashboard/mongo/backups/show-backup-mongo.tsx +++ /dev/null @@ -1,178 +0,0 @@ -import { Button } from "@/components/ui/button"; -import { - Card, - CardContent, - CardDescription, - CardHeader, - CardTitle, -} from "@/components/ui/card"; -import { - Tooltip, - TooltipContent, - TooltipProvider, - TooltipTrigger, -} from "@/components/ui/tooltip"; -import { api } from "@/utils/api"; -import { DatabaseBackup, Play } from "lucide-react"; -import Link from "next/link"; -import React from "react"; -import { toast } from "sonner"; -import { AddBackup } from "../../database/backups/add-backup"; -import { DeleteBackup } from "../../database/backups/delete-backup"; -import { UpdateBackup } from "../../database/backups/update-backup"; -interface Props { - mongoId: string; -} - -export const ShowBackupMongo = ({ mongoId }: Props) => { - const { data } = api.destination.all.useQuery(); - const { data: mongo, refetch: refetchMongo } = api.mongo.one.useQuery( - { - mongoId, - }, - { - enabled: !!mongoId, - }, - ); - - const { mutateAsync: manualBackup, isLoading: isManualBackup } = - api.backup.manualBackupMongo.useMutation(); - - return ( - - -
- Backups - - Add backups to your database to save the data to a different - provider. - -
- - {mongo && mongo?.backups?.length > 0 && ( - - )} -
- - {data?.length === 0 ? ( -
- - - To create a backup it is required to set at least 1 provider. - Please, go to{" "} - - Settings - {" "} - to do so. - -
- ) : ( -
- {mongo?.backups.length === 0 ? ( -
- - - No backups configured - - -
- ) : ( -
-
- {mongo?.backups.map((backup) => ( -
-
-
-
- Destination - - {backup.destination.name} - -
-
- Database - - {backup.database} - -
-
- Scheduled - - {backup.schedule} - -
-
- Prefix Storage - - {backup.prefix} - -
-
- Enabled - - {backup.enabled ? "Yes" : "No"} - -
-
-
- - - - - - Run Manual Backup - - - - -
-
-
- ))} -
-
- )} -
- )} -
-
- ); -}; diff --git a/apps/dokploy/components/dashboard/mongo/delete-mongo.tsx b/apps/dokploy/components/dashboard/mongo/delete-mongo.tsx deleted file mode 100644 index b5ba82260..000000000 --- a/apps/dokploy/components/dashboard/mongo/delete-mongo.tsx +++ /dev/null @@ -1,157 +0,0 @@ -import { Badge } from "@/components/ui/badge"; -import { Button } from "@/components/ui/button"; -import { - Dialog, - DialogContent, - DialogDescription, - DialogFooter, - DialogHeader, - DialogTitle, - DialogTrigger, -} from "@/components/ui/dialog"; -import { - Form, - FormControl, - FormField, - FormItem, - FormLabel, - FormMessage, -} from "@/components/ui/form"; -import { Input } from "@/components/ui/input"; -import { api } from "@/utils/api"; -import { zodResolver } from "@hookform/resolvers/zod"; -import { Copy, TrashIcon } from "lucide-react"; -import { useRouter } from "next/router"; -import { useState } from "react"; -import { useForm } from "react-hook-form"; -import { toast } from "sonner"; -import { z } from "zod"; - -const deleteMongoSchema = z.object({ - projectName: z.string().min(1, { - message: "Database name is required", - }), -}); - -type DeleteMongo = z.infer; - -interface Props { - mongoId: string; -} - -// commen - -export const DeleteMongo = ({ mongoId }: Props) => { - const [isOpen, setIsOpen] = useState(false); - const { mutateAsync, isLoading } = api.mongo.remove.useMutation(); - const { data } = api.mongo.one.useQuery({ mongoId }, { enabled: !!mongoId }); - const { push } = useRouter(); - const form = useForm({ - defaultValues: { - projectName: "", - }, - resolver: zodResolver(deleteMongoSchema), - }); - - const onSubmit = async (formData: DeleteMongo) => { - const expectedName = `${data?.name}/${data?.appName}`; - if (formData.projectName === expectedName) { - await mutateAsync({ mongoId }) - .then((data) => { - push(`/dashboard/project/${data?.projectId}`); - toast.success("Database deleted successfully"); - setIsOpen(false); - }) - .catch(() => { - toast.error("Error deleting the database"); - }); - } else { - form.setError("projectName", { - message: "Database name does not match", - }); - } - }; - return ( - - - - - - - Are you absolutely sure? - - This action cannot be undone. This will permanently delete the - database. If you are sure please enter the database name to delete - this database. - - -
-
- - ( - - - - To confirm, type{" "} - { - if (data?.name && data?.appName) { - navigator.clipboard.writeText( - `${data.name}/${data.appName}`, - ); - toast.success("Copied to clipboard. Be careful!"); - } - }} - > - {data?.name}/{data?.appName}  - - {" "} - in the box below: - - - - - - - - )} - /> - - -
- - - - -
-
- ); -}; diff --git a/apps/dokploy/components/dashboard/mongo/general/deploy-mongo.tsx b/apps/dokploy/components/dashboard/mongo/general/deploy-mongo.tsx deleted file mode 100644 index 61f7e5d2a..000000000 --- a/apps/dokploy/components/dashboard/mongo/general/deploy-mongo.tsx +++ /dev/null @@ -1,73 +0,0 @@ -import { - AlertDialog, - AlertDialogAction, - AlertDialogCancel, - AlertDialogContent, - AlertDialogDescription, - AlertDialogFooter, - AlertDialogHeader, - AlertDialogTitle, - AlertDialogTrigger, -} from "@/components/ui/alert-dialog"; -import { Button } from "@/components/ui/button"; -import { api } from "@/utils/api"; -import { toast } from "sonner"; - -interface Props { - mongoId: string; -} - -export const DeployMongo = ({ mongoId }: Props) => { - const { data, refetch } = api.mongo.one.useQuery( - { - mongoId, - }, - { enabled: !!mongoId }, - ); - const { mutateAsync: deploy } = api.mongo.deploy.useMutation(); - const { mutateAsync: changeStatus } = api.mongo.changeStatus.useMutation(); - - return ( - - - - - - - Are you absolutely sure? - - This will deploy the mongo database - - - - Cancel - { - await changeStatus({ - mongoId, - applicationStatus: "running", - }) - .then(async () => { - toast.success("Deploying Database...."); - await refetch(); - await deploy({ - mongoId, - }).catch(() => { - toast.error("Error deploying Database"); - }); - await refetch(); - }) - .catch((e) => { - toast.error(e.message || "Error deploying Database"); - }); - }} - > - Confirm - - - - - ); -}; diff --git a/apps/dokploy/components/dashboard/mongo/general/reset-mongo.tsx b/apps/dokploy/components/dashboard/mongo/general/reset-mongo.tsx deleted file mode 100644 index 1c297799e..000000000 --- a/apps/dokploy/components/dashboard/mongo/general/reset-mongo.tsx +++ /dev/null @@ -1,69 +0,0 @@ -import { - AlertDialog, - AlertDialogAction, - AlertDialogCancel, - AlertDialogContent, - AlertDialogDescription, - AlertDialogFooter, - AlertDialogHeader, - AlertDialogTitle, - AlertDialogTrigger, -} from "@/components/ui/alert-dialog"; -import { Button } from "@/components/ui/button"; -import { api } from "@/utils/api"; -import { RefreshCcw } from "lucide-react"; -import { toast } from "sonner"; - -interface Props { - mongoId: string; - appName: string; -} - -export const ResetMongo = ({ mongoId, appName }: Props) => { - const { refetch } = api.mongo.one.useQuery( - { - mongoId, - }, - { enabled: !!mongoId }, - ); - const { mutateAsync: reload, isLoading } = api.mongo.reload.useMutation(); - - return ( - - - - - - - Are you absolutely sure? - - This will reload the service - - - - Cancel - { - await reload({ - mongoId, - appName, - }) - .then(() => { - toast.success("Service Reloaded"); - }) - .catch(() => { - toast.error("Error reloading the service"); - }); - await refetch(); - }} - > - Confirm - - - - - ); -}; diff --git a/apps/dokploy/components/dashboard/mongo/general/show-general-mongo.tsx b/apps/dokploy/components/dashboard/mongo/general/show-general-mongo.tsx index c8ae007af..df01e36d4 100644 --- a/apps/dokploy/components/dashboard/mongo/general/show-general-mongo.tsx +++ b/apps/dokploy/components/dashboard/mongo/general/show-general-mongo.tsx @@ -1,24 +1,61 @@ +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 { api } from "@/utils/api"; -import { Terminal } from "lucide-react"; -import React from "react"; +import { Ban, CheckCircle2, RefreshCcw, Terminal } from "lucide-react"; +import React, { useState } from "react"; +import { toast } from "sonner"; +import { type LogLine, parseLogs } from "../../docker/logs/utils"; import { DockerTerminalModal } from "../../settings/web-server/docker-terminal-modal"; -import { StartMongo } from "../start-mongo"; -import { DeployMongo } from "./deploy-mongo"; -import { ResetMongo } from "./reset-mongo"; -import { StopMongo } from "./stop-mongo"; interface Props { mongoId: string; } export const ShowGeneralMongo = ({ mongoId }: Props) => { - const { data } = api.mongo.one.useQuery( + const { data, refetch } = api.mongo.one.useQuery( { mongoId, }, { enabled: !!mongoId }, ); + + const { mutateAsync: reload, isLoading: isReloading } = + api.mongo.reload.useMutation(); + + const { mutateAsync: start, isLoading: isStarting } = + api.mongo.start.useMutation(); + + const { mutateAsync: stop, isLoading: isStopping } = + api.mongo.stop.useMutation(); + + const [isDrawerOpen, setIsDrawerOpen] = useState(false); + const [filteredLogs, setFilteredLogs] = useState([]); + const [isDeploying, setIsDeploying] = useState(false); + api.mongo.deployWithLogs.useSubscription( + { + mongoId: mongoId, + }, + { + enabled: isDeploying, + onData(log) { + if (!isDrawerOpen) { + setIsDrawerOpen(true); + } + + if (log === "Deployment completed successfully!") { + setIsDeploying(false); + } + + const parsedLogs = parseLogs(log); + setFilteredLogs((prev) => [...prev, ...parsedLogs]); + }, + onError(error) { + console.error("Deployment logs error:", error); + setIsDeploying(false); + }, + }, + ); return ( <>
@@ -27,12 +64,92 @@ export const ShowGeneralMongo = ({ mongoId }: Props) => { 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({ + mongoId: mongoId, + }) + .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"); + }); + }} + > + + )} { + { + setIsDrawerOpen(false); + setFilteredLogs([]); + setIsDeploying(false); + refetch(); + }} + filteredLogs={filteredLogs} + />
); diff --git a/apps/dokploy/components/dashboard/mongo/general/stop-mongo.tsx b/apps/dokploy/components/dashboard/mongo/general/stop-mongo.tsx deleted file mode 100644 index f146145f4..000000000 --- a/apps/dokploy/components/dashboard/mongo/general/stop-mongo.tsx +++ /dev/null @@ -1,65 +0,0 @@ -import { - AlertDialog, - AlertDialogAction, - AlertDialogCancel, - AlertDialogContent, - AlertDialogDescription, - AlertDialogFooter, - AlertDialogHeader, - AlertDialogTitle, - AlertDialogTrigger, -} from "@/components/ui/alert-dialog"; -import { Button } from "@/components/ui/button"; -import { api } from "@/utils/api"; -import { Ban } from "lucide-react"; -import { toast } from "sonner"; - -interface Props { - mongoId: string; -} - -export const StopMongo = ({ mongoId }: Props) => { - const { mutateAsync, isLoading } = api.mongo.stop.useMutation(); - const utils = api.useUtils(); - return ( - - - - - - - - Are you absolutely sure to stop the database? - - - This will stop the database - - - - Cancel - { - await mutateAsync({ - mongoId, - }) - .then(async () => { - await utils.mongo.one.invalidate({ - mongoId, - }); - toast.success("Application stopped successfully"); - }) - .catch(() => { - toast.error("Error stopping the Application"); - }); - }} - > - Confirm - - - - - ); -}; diff --git a/apps/dokploy/components/dashboard/mongo/start-mongo.tsx b/apps/dokploy/components/dashboard/mongo/start-mongo.tsx deleted file mode 100644 index 56675cb39..000000000 --- a/apps/dokploy/components/dashboard/mongo/start-mongo.tsx +++ /dev/null @@ -1,65 +0,0 @@ -import { - AlertDialog, - AlertDialogAction, - AlertDialogCancel, - AlertDialogContent, - AlertDialogDescription, - AlertDialogFooter, - AlertDialogHeader, - AlertDialogTitle, - AlertDialogTrigger, -} from "@/components/ui/alert-dialog"; -import { Button } from "@/components/ui/button"; -import { api } from "@/utils/api"; -import { CheckCircle2 } from "lucide-react"; -import { toast } from "sonner"; - -interface Props { - mongoId: string; -} - -export const StartMongo = ({ mongoId }: Props) => { - const { mutateAsync, isLoading } = api.mongo.start.useMutation(); - const utils = api.useUtils(); - return ( - - - - - - - - Are you sure to start the database? - - - This will start the database - - - - Cancel - { - await mutateAsync({ - mongoId, - }) - .then(async () => { - await utils.mongo.one.invalidate({ - mongoId, - }); - toast.success("Database started successfully"); - }) - .catch(() => { - toast.error("Error starting the Database"); - }); - }} - > - Confirm - - - - - ); -}; diff --git a/apps/dokploy/components/dashboard/mongo/update-mongo.tsx b/apps/dokploy/components/dashboard/mongo/update-mongo.tsx index 95ff9e3ea..c2e3616c4 100644 --- a/apps/dokploy/components/dashboard/mongo/update-mongo.tsx +++ b/apps/dokploy/components/dashboard/mongo/update-mongo.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, SquarePen } from "lucide-react"; +import { PenBoxIcon } from "lucide-react"; import { useEffect, useState } from "react"; import { useForm } from "react-hook-form"; import { toast } from "sonner"; @@ -91,8 +91,12 @@ export const UpdateMongo = ({ mongoId }: Props) => { return ( - diff --git a/apps/dokploy/components/dashboard/mongo/volumes/show-volumes.tsx b/apps/dokploy/components/dashboard/mongo/volumes/show-volumes.tsx deleted file mode 100644 index c30f2df88..000000000 --- a/apps/dokploy/components/dashboard/mongo/volumes/show-volumes.tsx +++ /dev/null @@ -1,129 +0,0 @@ -import { AlertBlock } from "@/components/shared/alert-block"; -import { - Card, - CardContent, - CardDescription, - CardHeader, - CardTitle, -} from "@/components/ui/card"; -import { api } from "@/utils/api"; -import { AlertTriangle, Package } from "lucide-react"; -import React from "react"; -import { AddVolumes } from "../../application/advanced/volumes/add-volumes"; -import { DeleteVolume } from "../../application/advanced/volumes/delete-volume"; -import { UpdateVolume } from "../../application/advanced/volumes/update-volume"; -interface Props { - mongoId: string; -} - -export const ShowVolumes = ({ mongoId }: Props) => { - const { data, refetch } = api.mongo.one.useQuery( - { - mongoId, - }, - { enabled: !!mongoId }, - ); - - return ( - - -
- Volumes - - If you want to persist data in this mongo use the following config. - to setup the volumes - -
- - {data && data?.mounts.length > 0 && ( - - Add Volume - - )} -
- - {data?.mounts.length === 0 ? ( -
- - - No volumes/mounts configured - - - Add Volume - -
- ) : ( -
- - Please remember to click Redeploy after adding, editing, or - deleting a mount to apply the changes. - -
- {data?.mounts.map((mount) => ( -
-
-
-
- Mount Type - - {mount.type.toUpperCase()} - -
- {mount.type === "volume" && ( -
- Volume Name - - {mount.volumeName} - -
- )} - - {mount.type === "file" && ( -
- Content - - {mount.content} - -
- )} - {mount.type === "bind" && ( -
- Host Path - - {mount.hostPath} - -
- )} -
- Mount Path - - {mount.mountPath} - -
-
-
- - -
-
-
- ))} -
-
- )} -
-
- ); -}; diff --git a/apps/dokploy/components/dashboard/monitoring/docker/show.tsx b/apps/dokploy/components/dashboard/monitoring/docker/show.tsx index d9deaa351..2e5eecef0 100644 --- a/apps/dokploy/components/dashboard/monitoring/docker/show.tsx +++ b/apps/dokploy/components/dashboard/monitoring/docker/show.tsx @@ -187,81 +187,127 @@ export const DockerMonitoring = ({ return (
- - - Monitoring - - Watch the usage of your server in the current app. - - - -
-
-
- CPU - - Used: {currentData.cpu.value.toFixed(2)}% - - - -
-
- Memory - - {`Used: ${(currentData.memory.value.used / 1024 ** 3).toFixed(2)} GB / Limit: ${(currentData.memory.value.total / 1024 ** 3).toFixed(2)} GB`} - - - -
- {appName === "dokploy" && ( -
- Space + +
+
+
+

+ Monitoring +

+

+ Watch the usage of your server in the current app +

+
+
+ +
+ + + CPU Usage + + +
+ + Used: {currentData.cpu.value.toFixed(2)}% + + + +
+
+
+ + + + + Memory Usage + + + +
- {`Used: ${currentData.disk.value.diskUsage} GB / Limit: ${currentData.disk.value.diskTotal} GB`} + {`Used: ${(currentData.memory.value.used / 1024 ** 3).toFixed(2)} GB / Limit: ${(currentData.memory.value.total / 1024 ** 3).toFixed(2)} GB`} -
- )} -
- Block I/O - - {`Read: ${currentData.block.value.readMb.toFixed( - 2, - )} MB / Write: ${currentData.block.value.writeMb.toFixed( - 3, - )} MB`} - - -
-
- Network - - {`In MB: ${currentData.network.value.inputMb.toFixed( - 2, - )} MB / Out MB: ${currentData.network.value.outputMb.toFixed( - 2, - )} MB`} - - -
-
+ + + + {appName === "dokploy" && ( + + + + Disk Space + + + +
+ + {`Used: ${currentData.disk.value.diskUsage} GB / Limit: ${currentData.disk.value.diskTotal} GB`} + + + +
+
+
+ )} + + + + Block I/O + + +
+ + {`Read: ${currentData.block.value.readMb.toFixed( + 2, + )} MB / Write: ${currentData.block.value.writeMb.toFixed( + 3, + )} MB`} + + +
+
+
+ + + + + Network I/O + + + +
+ + {`In MB: ${currentData.network.value.inputMb.toFixed( + 2, + )} MB / Out MB: ${currentData.network.value.outputMb.toFixed( + 2, + )} MB`} + + +
+
+
- +
); diff --git a/apps/dokploy/components/dashboard/mysql/advanced/show-mysql-advanced-settings.tsx b/apps/dokploy/components/dashboard/mysql/advanced/show-mysql-advanced-settings.tsx deleted file mode 100644 index 02256967d..000000000 --- a/apps/dokploy/components/dashboard/mysql/advanced/show-mysql-advanced-settings.tsx +++ /dev/null @@ -1,128 +0,0 @@ -import { Button } from "@/components/ui/button"; -import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; -import { - Form, - FormControl, - FormField, - FormItem, - FormLabel, - FormMessage, -} from "@/components/ui/form"; -import { Input } from "@/components/ui/input"; -import { api } from "@/utils/api"; -import { zodResolver } from "@hookform/resolvers/zod"; -import React, { useEffect } from "react"; -import { useForm } from "react-hook-form"; -import { toast } from "sonner"; -import { z } from "zod"; -import { ShowVolumes } from "../volumes/show-volumes"; -import { ShowMysqlResources } from "./show-mysql-resources"; - -const addDockerImage = z.object({ - dockerImage: z.string().min(1, "Docker image is required"), - command: z.string(), -}); - -interface Props { - mysqlId: string; -} - -type AddDockerImage = z.infer; -export const ShowAdvancedMysql = ({ mysqlId }: Props) => { - const { data, refetch } = api.mysql.one.useQuery( - { - mysqlId, - }, - { enabled: !!mysqlId }, - ); - const { mutateAsync } = api.mysql.update.useMutation(); - - const form = useForm({ - defaultValues: { - dockerImage: "", - command: "", - }, - resolver: zodResolver(addDockerImage), - }); - - useEffect(() => { - if (data) { - form.reset({ - dockerImage: data.dockerImage, - command: data.command || "", - }); - } - }, [data, form, form.reset]); - - const onSubmit = async (formData: AddDockerImage) => { - await mutateAsync({ - mysqlId, - dockerImage: formData?.dockerImage, - command: formData?.command, - }) - .then(async () => { - toast.success("Resources Updated"); - await refetch(); - }) - .catch(() => { - toast.error("Error updating the resources"); - }); - }; - return ( - <> -
- - - Advanced Settings - - -
- -
- ( - - Docker Image - - - - - - - )} - /> - ( - - Command - - - - - - - )} - /> -
-
- -
-
- -
-
- - -
- - ); -}; diff --git a/apps/dokploy/components/dashboard/mysql/backups/show-backup-mysql.tsx b/apps/dokploy/components/dashboard/mysql/backups/show-backup-mysql.tsx deleted file mode 100644 index de443c687..000000000 --- a/apps/dokploy/components/dashboard/mysql/backups/show-backup-mysql.tsx +++ /dev/null @@ -1,178 +0,0 @@ -import { Button } from "@/components/ui/button"; -import { - Card, - CardContent, - CardDescription, - CardHeader, - CardTitle, -} from "@/components/ui/card"; -import { - Tooltip, - TooltipContent, - TooltipProvider, - TooltipTrigger, -} from "@/components/ui/tooltip"; -import { api } from "@/utils/api"; -import { DatabaseBackup, Play } from "lucide-react"; -import Link from "next/link"; -import React from "react"; -import { toast } from "sonner"; -import { AddBackup } from "../../database/backups/add-backup"; -import { DeleteBackup } from "../../database/backups/delete-backup"; -import { UpdateBackup } from "../../database/backups/update-backup"; -interface Props { - mysqlId: string; -} - -export const ShowBackupMySql = ({ mysqlId }: Props) => { - const { data } = api.destination.all.useQuery(); - const { data: mysql, refetch: refetchMySql } = api.mysql.one.useQuery( - { - mysqlId, - }, - { - enabled: !!mysqlId, - }, - ); - - const { mutateAsync: manualBackup, isLoading: isManualBackup } = - api.backup.manualBackupMySql.useMutation(); - - return ( - - -
- Backups - - Add backups to your database to save the data to a different - provider. - -
- - {mysql && mysql?.backups?.length > 0 && ( - - )} -
- - {data?.length === 0 ? ( -
- - - To create a backup it is required to set at least 1 provider. - Please, go to{" "} - - Settings - {" "} - to do so. - -
- ) : ( -
- {mysql?.backups.length === 0 ? ( -
- - - No backups configured - - -
- ) : ( -
-
- {mysql?.backups.map((backup) => ( -
-
-
-
- Destination - - {backup.destination.name} - -
-
- Database - - {backup.database} - -
-
- Scheduled - - {backup.schedule} - -
-
- Prefix Storage - - {backup.prefix} - -
-
- Enabled - - {backup.enabled ? "Yes" : "No"} - -
-
-
- - - - - - Run Manual Backup - - - - -
-
-
- ))} -
-
- )} -
- )} -
-
- ); -}; diff --git a/apps/dokploy/components/dashboard/mysql/delete-mysql.tsx b/apps/dokploy/components/dashboard/mysql/delete-mysql.tsx deleted file mode 100644 index b9ce6cbce..000000000 --- a/apps/dokploy/components/dashboard/mysql/delete-mysql.tsx +++ /dev/null @@ -1,156 +0,0 @@ -import { Badge } from "@/components/ui/badge"; -import { Button } from "@/components/ui/button"; -import { - Dialog, - DialogContent, - DialogDescription, - DialogFooter, - DialogHeader, - DialogTitle, - DialogTrigger, -} from "@/components/ui/dialog"; -import { - Form, - FormControl, - FormField, - FormItem, - FormLabel, - FormMessage, -} from "@/components/ui/form"; -import { Input } from "@/components/ui/input"; -import { api } from "@/utils/api"; -import { zodResolver } from "@hookform/resolvers/zod"; -import { Copy, TrashIcon } from "lucide-react"; -import { useRouter } from "next/router"; -import { useState } from "react"; -import { useForm } from "react-hook-form"; -import { toast } from "sonner"; -import { z } from "zod"; - -const deleteMysqlSchema = z.object({ - projectName: z.string().min(1, { - message: "Database name is required", - }), -}); - -type DeleteMysql = z.infer; - -interface Props { - mysqlId: string; -} - -export const DeleteMysql = ({ mysqlId }: Props) => { - const [isOpen, setIsOpen] = useState(false); - const { mutateAsync, isLoading } = api.mysql.remove.useMutation(); - const { data } = api.mysql.one.useQuery({ mysqlId }, { enabled: !!mysqlId }); - const { push } = useRouter(); - const form = useForm({ - defaultValues: { - projectName: "", - }, - resolver: zodResolver(deleteMysqlSchema), - }); - - const onSubmit = async (formData: DeleteMysql) => { - const expectedName = `${data?.name}/${data?.appName}`; - if (formData.projectName === expectedName) { - await mutateAsync({ mysqlId }) - .then((data) => { - push(`/dashboard/project/${data?.projectId}`); - toast.success("Database deleted successfully"); - setIsOpen(false); - }) - .catch(() => { - toast.error("Error deleting the database"); - }); - } else { - form.setError("projectName", { - message: "Database name does not match", - }); - } - }; - - return ( - - - - - - - Are you absolutely sure? - - This action cannot be undone. This will permanently delete the - database. If you are sure please enter the database name to delete - this database. - - -
-
- - ( - - - - To confirm, type{" "} - { - if (data?.name && data?.appName) { - navigator.clipboard.writeText( - `${data.name}/${data.appName}`, - ); - toast.success("Copied to clipboard. Be careful!"); - } - }} - > - {data?.name}/{data?.appName}  - - {" "} - in the box below: - - - - - - - - )} - /> - - -
- - - - -
-
- ); -}; diff --git a/apps/dokploy/components/dashboard/mysql/environment/show-mysql-environment.tsx b/apps/dokploy/components/dashboard/mysql/environment/show-mysql-environment.tsx deleted file mode 100644 index 7dfa6aed6..000000000 --- a/apps/dokploy/components/dashboard/mysql/environment/show-mysql-environment.tsx +++ /dev/null @@ -1,140 +0,0 @@ -import { CodeEditor } from "@/components/shared/code-editor"; -import { Button } from "@/components/ui/button"; -import { - Card, - CardContent, - CardDescription, - CardHeader, - CardTitle, -} from "@/components/ui/card"; -import { - Form, - FormControl, - FormField, - FormItem, - FormMessage, -} from "@/components/ui/form"; -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, { useEffect, useState } from "react"; -import { useForm } from "react-hook-form"; -import { toast } from "sonner"; -import { z } from "zod"; - -const addEnvironmentSchema = z.object({ - environment: z.string(), -}); - -type EnvironmentSchema = z.infer; - -interface Props { - mysqlId: string; -} - -export const ShowMysqlEnvironment = ({ mysqlId }: Props) => { - const [isEnvVisible, setIsEnvVisible] = useState(true); - const { mutateAsync, isLoading } = api.mysql.saveEnvironment.useMutation(); - - const { data, refetch } = api.mysql.one.useQuery( - { - mysqlId, - }, - { - enabled: !!mysqlId, - }, - ); - const form = useForm({ - defaultValues: { - environment: "", - }, - resolver: zodResolver(addEnvironmentSchema), - }); - - useEffect(() => { - if (data) { - form.reset({ - environment: data.env || "", - }); - } - }, [form.reset, data, form]); - - const onSubmit = async (data: EnvironmentSchema) => { - mutateAsync({ - env: data.environment, - mysqlId, - }) - .then(async () => { - toast.success("Environments Added"); - await refetch(); - }) - .catch(() => { - toast.error("Error adding environment"); - }); - }; - - return ( -
- - -
- Environment Settings - - You can add environment variables to your resource. - -
- - - {isEnvVisible ? ( - - ) : ( - - )} - -
- -
- - ( - - - - - - - - )} - /> - -
- -
- - -
-
-
- ); -}; diff --git a/apps/dokploy/components/dashboard/mysql/general/deploy-mysql.tsx b/apps/dokploy/components/dashboard/mysql/general/deploy-mysql.tsx deleted file mode 100644 index c56c20d54..000000000 --- a/apps/dokploy/components/dashboard/mysql/general/deploy-mysql.tsx +++ /dev/null @@ -1,73 +0,0 @@ -import { - AlertDialog, - AlertDialogAction, - AlertDialogCancel, - AlertDialogContent, - AlertDialogDescription, - AlertDialogFooter, - AlertDialogHeader, - AlertDialogTitle, - AlertDialogTrigger, -} from "@/components/ui/alert-dialog"; -import { Button } from "@/components/ui/button"; -import { api } from "@/utils/api"; -import { toast } from "sonner"; - -interface Props { - mysqlId: string; -} - -export const DeployMysql = ({ mysqlId }: Props) => { - const { data, refetch } = api.mysql.one.useQuery( - { - mysqlId, - }, - { enabled: !!mysqlId }, - ); - const { mutateAsync: deploy } = api.mysql.deploy.useMutation(); - const { mutateAsync: changeStatus } = api.mysql.changeStatus.useMutation(); - - return ( - - - - - - - Are you absolutely sure? - - This will deploy the mysql database - - - - Cancel - { - await changeStatus({ - mysqlId, - applicationStatus: "running", - }) - .then(async () => { - toast.success("Deploying Database...."); - await refetch(); - await deploy({ - mysqlId, - }).catch(() => { - toast.error("Error deploying Database"); - }); - await refetch(); - }) - .catch((e) => { - toast.error(e.message || "Error deploying Database"); - }); - }} - > - Confirm - - - - - ); -}; diff --git a/apps/dokploy/components/dashboard/mysql/general/reset-mysql.tsx b/apps/dokploy/components/dashboard/mysql/general/reset-mysql.tsx deleted file mode 100644 index 5e9896711..000000000 --- a/apps/dokploy/components/dashboard/mysql/general/reset-mysql.tsx +++ /dev/null @@ -1,69 +0,0 @@ -import { - AlertDialog, - AlertDialogAction, - AlertDialogCancel, - AlertDialogContent, - AlertDialogDescription, - AlertDialogFooter, - AlertDialogHeader, - AlertDialogTitle, - AlertDialogTrigger, -} from "@/components/ui/alert-dialog"; -import { Button } from "@/components/ui/button"; -import { api } from "@/utils/api"; -import { RefreshCcw } from "lucide-react"; -import { toast } from "sonner"; - -interface Props { - mysqlId: string; - appName: string; -} - -export const ResetMysql = ({ mysqlId, appName }: Props) => { - const { refetch } = api.mysql.one.useQuery( - { - mysqlId, - }, - { enabled: !!mysqlId }, - ); - const { mutateAsync: reload, isLoading } = api.mysql.reload.useMutation(); - - return ( - - - - - - - Are you absolutely sure? - - This will reload the service - - - - Cancel - { - await reload({ - mysqlId, - appName, - }) - .then(() => { - toast.success("Service Reloaded"); - }) - .catch(() => { - toast.error("Error reloading the service"); - }); - await refetch(); - }} - > - Confirm - - - - - ); -}; 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 f9928cb1d..56a191ceb 100644 --- a/apps/dokploy/components/dashboard/mysql/general/show-general-mysql.tsx +++ b/apps/dokploy/components/dashboard/mysql/general/show-general-mysql.tsx @@ -1,24 +1,59 @@ +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 { api } from "@/utils/api"; -import { Terminal } from "lucide-react"; -import React from "react"; +import { Ban, CheckCircle2, RefreshCcw, Terminal } from "lucide-react"; +import React, { useState } from "react"; +import { toast } from "sonner"; +import { type LogLine, parseLogs } from "../../docker/logs/utils"; import { DockerTerminalModal } from "../../settings/web-server/docker-terminal-modal"; -import { StartMysql } from "../start-mysql"; -import { DeployMysql } from "./deploy-mysql"; -import { ResetMysql } from "./reset-mysql"; -import { StopMysql } from "./stop-mysql"; interface Props { mysqlId: string; } export const ShowGeneralMysql = ({ mysqlId }: Props) => { - const { data } = api.mysql.one.useQuery( + const { data, refetch } = api.mysql.one.useQuery( { mysqlId, }, { enabled: !!mysqlId }, ); + + const { mutateAsync: reload, isLoading: isReloading } = + api.mysql.reload.useMutation(); + const { mutateAsync: start, isLoading: isStarting } = + api.mysql.start.useMutation(); + + const { mutateAsync: stop, isLoading: isStopping } = + api.mysql.stop.useMutation(); + + const [isDrawerOpen, setIsDrawerOpen] = useState(false); + const [filteredLogs, setFilteredLogs] = useState([]); + const [isDeploying, setIsDeploying] = useState(false); + api.mysql.deployWithLogs.useSubscription( + { + mysqlId: mysqlId, + }, + { + enabled: isDeploying, + onData(log) { + if (!isDrawerOpen) { + setIsDrawerOpen(true); + } + + if (log === "Deployment completed successfully!") { + setIsDeploying(false); + } + const parsedLogs = parseLogs(log); + setFilteredLogs((prev) => [...prev, ...parsedLogs]); + }, + onError(error) { + console.error("Deployment logs error:", error); + setIsDeploying(false); + }, + }, + ); return ( <>
@@ -27,12 +62,91 @@ 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"); + }); + }} + > + + ) : ( - + { + await stop({ + mysqlId: mysqlId, + }) + .then(() => { + toast.success("Mysql stopped successfully"); + refetch(); + }) + .catch(() => { + toast.error("Error stopping Mysql"); + }); + }} + > + + )} { + { + setIsDrawerOpen(false); + setFilteredLogs([]); + setIsDeploying(false); + refetch(); + }} + filteredLogs={filteredLogs} + />
); diff --git a/apps/dokploy/components/dashboard/mysql/general/stop-mysql.tsx b/apps/dokploy/components/dashboard/mysql/general/stop-mysql.tsx deleted file mode 100644 index 31d4c0f69..000000000 --- a/apps/dokploy/components/dashboard/mysql/general/stop-mysql.tsx +++ /dev/null @@ -1,65 +0,0 @@ -import { - AlertDialog, - AlertDialogAction, - AlertDialogCancel, - AlertDialogContent, - AlertDialogDescription, - AlertDialogFooter, - AlertDialogHeader, - AlertDialogTitle, - AlertDialogTrigger, -} from "@/components/ui/alert-dialog"; -import { Button } from "@/components/ui/button"; -import { api } from "@/utils/api"; -import { Ban } from "lucide-react"; -import { toast } from "sonner"; - -interface Props { - mysqlId: string; -} - -export const StopMysql = ({ mysqlId }: Props) => { - const { mutateAsync, isLoading } = api.mysql.stop.useMutation(); - const utils = api.useUtils(); - return ( - - - - - - - - Are you absolutely sure to stop the database? - - - This will stop the database - - - - Cancel - { - await mutateAsync({ - mysqlId, - }) - .then(async () => { - await utils.mysql.one.invalidate({ - mysqlId, - }); - toast.success("MySQL stopped successfully"); - }) - .catch(() => { - toast.error("Error stopping MySQL"); - }); - }} - > - Confirm - - - - - ); -}; diff --git a/apps/dokploy/components/dashboard/mysql/start-mysql.tsx b/apps/dokploy/components/dashboard/mysql/start-mysql.tsx deleted file mode 100644 index dcf4db221..000000000 --- a/apps/dokploy/components/dashboard/mysql/start-mysql.tsx +++ /dev/null @@ -1,65 +0,0 @@ -import { - AlertDialog, - AlertDialogAction, - AlertDialogCancel, - AlertDialogContent, - AlertDialogDescription, - AlertDialogFooter, - AlertDialogHeader, - AlertDialogTitle, - AlertDialogTrigger, -} from "@/components/ui/alert-dialog"; -import { Button } from "@/components/ui/button"; -import { api } from "@/utils/api"; -import { CheckCircle2 } from "lucide-react"; -import { toast } from "sonner"; - -interface Props { - mysqlId: string; -} - -export const StartMysql = ({ mysqlId }: Props) => { - const { mutateAsync, isLoading } = api.mysql.start.useMutation(); - const utils = api.useUtils(); - return ( - - - - - - - - Are you sure to start the database? - - - This will start the database - - - - Cancel - { - await mutateAsync({ - mysqlId, - }) - .then(async () => { - await utils.mysql.one.invalidate({ - mysqlId, - }); - toast.success("Database started successfully"); - }) - .catch(() => { - toast.error("Error starting the Database"); - }); - }} - > - Confirm - - - - - ); -}; diff --git a/apps/dokploy/components/dashboard/mysql/update-mysql.tsx b/apps/dokploy/components/dashboard/mysql/update-mysql.tsx index 5c1af50cf..645575cdc 100644 --- a/apps/dokploy/components/dashboard/mysql/update-mysql.tsx +++ b/apps/dokploy/components/dashboard/mysql/update-mysql.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, SquarePen } from "lucide-react"; +import { AlertTriangle, PenBoxIcon, SquarePen } from "lucide-react"; import { useEffect } from "react"; import { useForm } from "react-hook-form"; import { toast } from "sonner"; @@ -89,8 +89,12 @@ export const UpdateMysql = ({ mysqlId }: Props) => { return ( - diff --git a/apps/dokploy/components/dashboard/mysql/volumes/show-volumes.tsx b/apps/dokploy/components/dashboard/mysql/volumes/show-volumes.tsx deleted file mode 100644 index 530a0f6a1..000000000 --- a/apps/dokploy/components/dashboard/mysql/volumes/show-volumes.tsx +++ /dev/null @@ -1,129 +0,0 @@ -import { AlertBlock } from "@/components/shared/alert-block"; -import { - Card, - CardContent, - CardDescription, - CardHeader, - CardTitle, -} from "@/components/ui/card"; -import { api } from "@/utils/api"; -import { AlertTriangle, Package } from "lucide-react"; -import React from "react"; -import { AddVolumes } from "../../application/advanced/volumes/add-volumes"; -import { DeleteVolume } from "../../application/advanced/volumes/delete-volume"; -import { UpdateVolume } from "../../application/advanced/volumes/update-volume"; -interface Props { - mysqlId: string; -} - -export const ShowVolumes = ({ mysqlId }: Props) => { - const { data, refetch } = api.mysql.one.useQuery( - { - mysqlId, - }, - { enabled: !!mysqlId }, - ); - - return ( - - -
- Volumes - - If you want to persist data in this mysql use the following config - to setup the volumes - -
- - {data && data?.mounts.length > 0 && ( - - Add Volume - - )} -
- - {data?.mounts.length === 0 ? ( -
- - - No volumes/mounts configured - - - Add Volume - -
- ) : ( -
- - Please remember to click Redeploy after adding, editing, or - deleting a mount to apply the changes. - -
- {data?.mounts.map((mount) => ( -
-
-
-
- Mount Type - - {mount.type.toUpperCase()} - -
- {mount.type === "volume" && ( -
- Volume Name - - {mount.volumeName} - -
- )} - - {mount.type === "file" && ( -
- Content - - {mount.content} - -
- )} - {mount.type === "bind" && ( -
- Host Path - - {mount.hostPath} - -
- )} -
- Mount Path - - {mount.mountPath} - -
-
-
- - -
-
-
- ))} -
-
- )} -
-
- ); -}; diff --git a/apps/dokploy/components/dashboard/postgres/advanced/show-postgres-advanced-settings.tsx b/apps/dokploy/components/dashboard/postgres/advanced/show-custom-command.tsx similarity index 64% rename from apps/dokploy/components/dashboard/postgres/advanced/show-postgres-advanced-settings.tsx rename to apps/dokploy/components/dashboard/postgres/advanced/show-custom-command.tsx index d3ad7213f..6e912db95 100644 --- a/apps/dokploy/components/dashboard/postgres/advanced/show-postgres-advanced-settings.tsx +++ b/apps/dokploy/components/dashboard/postgres/advanced/show-custom-command.tsx @@ -15,8 +15,7 @@ import React, { useEffect } from "react"; import { useForm } from "react-hook-form"; import { toast } from "sonner"; import { z } from "zod"; -import { ShowVolumes } from "../volumes/show-volumes"; -import { ShowPostgresResources } from "./show-postgres-resources"; +import type { ServiceType } from "../../application/advanced/show-resources"; const addDockerImage = z.object({ dockerImage: z.string().min(1, "Docker image is required"), @@ -24,18 +23,39 @@ const addDockerImage = z.object({ }); interface Props { - postgresId: string; + id: string; + type: Exclude; } type AddDockerImage = z.infer; -export const ShowAdvancedPostgres = ({ postgresId }: Props) => { - const { data, refetch } = api.postgres.one.useQuery( - { - postgresId, - }, - { enabled: !!postgresId }, - ); - const { mutateAsync } = api.postgres.update.useMutation(); +export const ShowCustomCommand = ({ id, type }: Props) => { + const queryMap = { + postgres: () => + api.postgres.one.useQuery({ postgresId: id }, { enabled: !!id }), + redis: () => api.redis.one.useQuery({ redisId: id }, { enabled: !!id }), + mysql: () => api.mysql.one.useQuery({ mysqlId: id }, { enabled: !!id }), + mariadb: () => + api.mariadb.one.useQuery({ mariadbId: id }, { enabled: !!id }), + application: () => + api.application.one.useQuery({ applicationId: id }, { enabled: !!id }), + mongo: () => api.mongo.one.useQuery({ mongoId: id }, { enabled: !!id }), + }; + const { data, refetch } = queryMap[type] + ? queryMap[type]() + : api.mongo.one.useQuery({ mongoId: id }, { enabled: !!id }); + + const mutationMap = { + postgres: () => api.postgres.update.useMutation(), + redis: () => api.redis.update.useMutation(), + mysql: () => api.mysql.update.useMutation(), + mariadb: () => api.mariadb.update.useMutation(), + application: () => api.application.update.useMutation(), + mongo: () => api.mongo.update.useMutation(), + }; + + const { mutateAsync, isLoading } = mutationMap[type] + ? mutationMap[type]() + : api.mongo.update.useMutation(); const form = useForm({ defaultValues: { @@ -56,16 +76,20 @@ export const ShowAdvancedPostgres = ({ postgresId }: Props) => { const onSubmit = async (formData: AddDockerImage) => { await mutateAsync({ - postgresId, + mongoId: id || "", + postgresId: id || "", + redisId: id || "", + mysqlId: id || "", + mariadbId: id || "", dockerImage: formData?.dockerImage, command: formData?.command, }) .then(async () => { - toast.success("Resources Updated"); + toast.success("Custom Command Updated"); await refetch(); }) .catch(() => { - toast.error("Error updating the resources"); + toast.error("Error updating the custom command"); }); }; return ( @@ -120,8 +144,6 @@ export const ShowAdvancedPostgres = ({ postgresId }: Props) => { - -
); diff --git a/apps/dokploy/components/dashboard/postgres/advanced/show-postgres-resources.tsx b/apps/dokploy/components/dashboard/postgres/advanced/show-postgres-resources.tsx deleted file mode 100644 index 678aec29f..000000000 --- a/apps/dokploy/components/dashboard/postgres/advanced/show-postgres-resources.tsx +++ /dev/null @@ -1,296 +0,0 @@ -import { AlertBlock } from "@/components/shared/alert-block"; -import { Button } from "@/components/ui/button"; -import { - Card, - CardContent, - CardDescription, - CardHeader, - CardTitle, -} from "@/components/ui/card"; -import { - Form, - FormControl, - FormField, - FormItem, - FormLabel, - FormMessage, -} from "@/components/ui/form"; -import { Input } from "@/components/ui/input"; -import { api } from "@/utils/api"; -import { zodResolver } from "@hookform/resolvers/zod"; -import React, { useEffect } from "react"; -import { useForm } from "react-hook-form"; -import { toast } from "sonner"; -import { - TooltipProvider, - TooltipTrigger, - TooltipContent, - Tooltip, -} from "@/components/ui/tooltip"; -import { InfoIcon } from "lucide-react"; -import { z } from "zod"; - -const addResourcesPostgres = z.object({ - memoryReservation: z.number().nullable().optional(), - cpuLimit: z.number().nullable().optional(), - memoryLimit: z.number().nullable().optional(), - cpuReservation: z.number().nullable().optional(), -}); -interface Props { - postgresId: string; -} - -type AddResourcesPostgres = z.infer; -export const ShowPostgresResources = ({ postgresId }: Props) => { - const { data, refetch } = api.postgres.one.useQuery( - { - postgresId, - }, - { enabled: !!postgresId }, - ); - const { mutateAsync, isLoading } = api.postgres.update.useMutation(); - const form = useForm({ - defaultValues: {}, - resolver: zodResolver(addResourcesPostgres), - }); - - useEffect(() => { - if (data) { - form.reset({ - cpuLimit: data?.cpuLimit || undefined, - cpuReservation: data?.cpuReservation || undefined, - memoryLimit: data?.memoryLimit || undefined, - memoryReservation: data?.memoryReservation || undefined, - }); - } - }, [data, form, form.reset]); - - const onSubmit = async (formData: AddResourcesPostgres) => { - await mutateAsync({ - postgresId, - cpuLimit: formData.cpuLimit || null, - cpuReservation: formData.cpuReservation || null, - memoryLimit: formData.memoryLimit || null, - memoryReservation: formData.memoryReservation || null, - }) - .then(async () => { - toast.success("Resources Updated"); - await refetch(); - }) - .catch(() => { - toast.error("Error updating the resources"); - }); - }; - return ( - - - Resources - - If you want to decrease or increase the resources to a specific. - application or database - - - - - Please remember to click Redeploy after modify the resources to apply - the changes. - -
- -
- ( - -
- Memory Reservation - - - - - - -

- Memory soft limit in bytes. Example: 256MB = - 268435456 bytes -

-
-
-
-
- - { - const value = e.target.value; - if (value === "") { - field.onChange(null); - } else { - const number = Number.parseInt(value, 10); - if (!Number.isNaN(number)) { - field.onChange(number); - } - } - }} - /> - - -
- )} - /> - - { - return ( - -
- Memory Limit - - - - - - -

- Memory hard limit in bytes. Example: 1GB = - 1073741824 bytes -

-
-
-
-
- - { - const value = e.target.value; - if (value === "") { - field.onChange(null); - } else { - const number = Number.parseInt(value, 10); - if (!Number.isNaN(number)) { - field.onChange(number); - } - } - }} - /> - - -
- ); - }} - /> - - { - return ( - -
- CPU Limit - - - - - - -

- CPU quota in units of 10^-9 CPUs. Example: 2 - CPUs = 2000000000 -

-
-
-
-
- - { - const value = e.target.value; - if (value === "") { - field.onChange(null); - } else { - const number = Number.parseInt(value, 10); - if (!Number.isNaN(number)) { - field.onChange(number); - } - } - }} - /> - - -
- ); - }} - /> - { - return ( - -
- CPU Reservation - - - - - - -

- CPU shares (relative weight). Example: 1 CPU = - 1000000000 -

-
-
-
-
- - { - const value = e.target.value; - if (value === "") { - field.onChange(null); - } else { - const number = Number.parseInt(value, 10); - if (!Number.isNaN(number)) { - field.onChange(number); - } - } - }} - /> - - -
- ); - }} - /> -
-
- -
-
- -
-
- ); -}; diff --git a/apps/dokploy/components/dashboard/postgres/delete-postgres.tsx b/apps/dokploy/components/dashboard/postgres/delete-postgres.tsx deleted file mode 100644 index e3388b178..000000000 --- a/apps/dokploy/components/dashboard/postgres/delete-postgres.tsx +++ /dev/null @@ -1,159 +0,0 @@ -import { Badge } from "@/components/ui/badge"; -import { Button } from "@/components/ui/button"; -import { - Dialog, - DialogContent, - DialogDescription, - DialogFooter, - DialogHeader, - DialogTitle, - DialogTrigger, -} from "@/components/ui/dialog"; -import { - Form, - FormControl, - FormField, - FormItem, - FormLabel, - FormMessage, -} from "@/components/ui/form"; -import { Input } from "@/components/ui/input"; -import { api } from "@/utils/api"; -import { zodResolver } from "@hookform/resolvers/zod"; -import { Copy, TrashIcon } from "lucide-react"; -import { useRouter } from "next/router"; -import { useState } from "react"; -import { useForm } from "react-hook-form"; -import { toast } from "sonner"; -import { z } from "zod"; - -const deletePostgresSchema = z.object({ - projectName: z.string().min(1, { - message: "Database name is required", - }), -}); - -type DeletePostgres = z.infer; - -interface Props { - postgresId: string; -} - -export const DeletePostgres = ({ postgresId }: Props) => { - const [isOpen, setIsOpen] = useState(false); - const { mutateAsync, isLoading } = api.postgres.remove.useMutation(); - const { data } = api.postgres.one.useQuery( - { postgresId }, - { enabled: !!postgresId }, - ); - const { push } = useRouter(); - const form = useForm({ - defaultValues: { - projectName: "", - }, - resolver: zodResolver(deletePostgresSchema), - }); - - const onSubmit = async (formData: DeletePostgres) => { - const expectedName = `${data?.name}/${data?.appName}`; - if (formData.projectName === expectedName) { - await mutateAsync({ postgresId }) - .then((data) => { - push(`/dashboard/project/${data?.projectId}`); - toast.success("Database deleted successfully"); - setIsOpen(false); - }) - .catch(() => { - toast.error("Error deleting the database"); - }); - } else { - form.setError("projectName", { - message: "Database name does not match", - }); - } - }; - - return ( - - - - - - - Are you absolutely sure? - - This action cannot be undone. This will permanently delete the - database. If you are sure please enter the database name to delete - this database. - - -
-
- - ( - - - - To confirm, type{" "} - { - if (data?.name && data?.appName) { - navigator.clipboard.writeText( - `${data.name}/${data.appName}`, - ); - toast.success("Copied to clipboard. Be careful!"); - } - }} - > - {data?.name}/{data?.appName}  - - {" "} - in the box below: - - - - - - - - )} - /> - - -
- - - - -
-
- ); -}; diff --git a/apps/dokploy/components/dashboard/postgres/environment/show-postgres-environment.tsx b/apps/dokploy/components/dashboard/postgres/environment/show-postgres-environment.tsx deleted file mode 100644 index c2361831c..000000000 --- a/apps/dokploy/components/dashboard/postgres/environment/show-postgres-environment.tsx +++ /dev/null @@ -1,140 +0,0 @@ -import { CodeEditor } from "@/components/shared/code-editor"; -import { Button } from "@/components/ui/button"; -import { - Card, - CardContent, - CardDescription, - CardHeader, - CardTitle, -} from "@/components/ui/card"; -import { - Form, - FormControl, - FormField, - FormItem, - FormMessage, -} from "@/components/ui/form"; -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, { useEffect, useState } from "react"; -import { useForm } from "react-hook-form"; -import { toast } from "sonner"; -import { z } from "zod"; - -const addEnvironmentSchema = z.object({ - environment: z.string(), -}); - -type EnvironmentSchema = z.infer; - -interface Props { - postgresId: string; -} - -export const ShowPostgresEnvironment = ({ postgresId }: Props) => { - const [isEnvVisible, setIsEnvVisible] = useState(true); - const { mutateAsync, isLoading } = api.postgres.saveEnvironment.useMutation(); - - const { data, refetch } = api.postgres.one.useQuery( - { - postgresId, - }, - { - enabled: !!postgresId, - }, - ); - const form = useForm({ - defaultValues: { - environment: "", - }, - resolver: zodResolver(addEnvironmentSchema), - }); - - useEffect(() => { - if (data) { - form.reset({ - environment: data.env || "", - }); - } - }, [form.reset, data, form]); - - const onSubmit = async (data: EnvironmentSchema) => { - mutateAsync({ - env: data.environment, - postgresId, - }) - .then(async () => { - toast.success("Environments Added"); - await refetch(); - }) - .catch(() => { - toast.error("Error adding environment"); - }); - }; - - return ( -
- - -
- Environment Settings - - You can add environment variables to your resource. - -
- - - {isEnvVisible ? ( - - ) : ( - - )} - -
- -
- - ( - - - - - - - - )} - /> - -
- -
- - -
-
-
- ); -}; diff --git a/apps/dokploy/components/dashboard/postgres/general/deploy-postgres.tsx b/apps/dokploy/components/dashboard/postgres/general/deploy-postgres.tsx deleted file mode 100644 index 1d78d17e9..000000000 --- a/apps/dokploy/components/dashboard/postgres/general/deploy-postgres.tsx +++ /dev/null @@ -1,73 +0,0 @@ -import { - AlertDialog, - AlertDialogAction, - AlertDialogCancel, - AlertDialogContent, - AlertDialogDescription, - AlertDialogFooter, - AlertDialogHeader, - AlertDialogTitle, - AlertDialogTrigger, -} from "@/components/ui/alert-dialog"; -import { Button } from "@/components/ui/button"; -import { api } from "@/utils/api"; -import { toast } from "sonner"; - -interface Props { - postgresId: string; -} - -export const DeployPostgres = ({ postgresId }: Props) => { - const { data, refetch } = api.postgres.one.useQuery( - { - postgresId, - }, - { enabled: !!postgresId }, - ); - const { mutateAsync: deploy } = api.postgres.deploy.useMutation(); - const { mutateAsync: changeStatus } = api.postgres.changeStatus.useMutation(); - - return ( - - - - - - - Are you absolutely sure? - - This will deploy the postgres database - - - - Cancel - { - await changeStatus({ - postgresId, - applicationStatus: "running", - }) - .then(async () => { - toast.success("Deploying Database...."); - await refetch(); - await deploy({ - postgresId, - }).catch(() => { - toast.error("Error deploying Database"); - }); - await refetch(); - }) - .catch((e) => { - toast.error(e.message || "Error deploying Database"); - }); - }} - > - Confirm - - - - - ); -}; diff --git a/apps/dokploy/components/dashboard/postgres/general/reset-postgres.tsx b/apps/dokploy/components/dashboard/postgres/general/reset-postgres.tsx deleted file mode 100644 index 51108f93e..000000000 --- a/apps/dokploy/components/dashboard/postgres/general/reset-postgres.tsx +++ /dev/null @@ -1,69 +0,0 @@ -import { - AlertDialog, - AlertDialogAction, - AlertDialogCancel, - AlertDialogContent, - AlertDialogDescription, - AlertDialogFooter, - AlertDialogHeader, - AlertDialogTitle, - AlertDialogTrigger, -} from "@/components/ui/alert-dialog"; -import { Button } from "@/components/ui/button"; -import { api } from "@/utils/api"; -import { RefreshCcw } from "lucide-react"; -import { toast } from "sonner"; - -interface Props { - postgresId: string; - appName: string; -} - -export const ResetPostgres = ({ postgresId, appName }: Props) => { - const { refetch } = api.postgres.one.useQuery( - { - postgresId, - }, - { enabled: !!postgresId }, - ); - const { mutateAsync: reload, isLoading } = api.postgres.reload.useMutation(); - - return ( - - - - - - - Are you absolutely sure? - - This will reload the service - - - - Cancel - { - await reload({ - postgresId, - appName, - }) - .then(() => { - toast.success("Service Reloaded"); - }) - .catch(() => { - toast.error("Error reloading the service"); - }); - await refetch(); - }} - > - Confirm - - - - - ); -}; 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 781080ee5..43c3f4322 100644 --- a/apps/dokploy/components/dashboard/postgres/general/show-general-postgres.tsx +++ b/apps/dokploy/components/dashboard/postgres/general/show-general-postgres.tsx @@ -1,41 +1,155 @@ +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 { api } from "@/utils/api"; -import { Terminal } from "lucide-react"; -import React from "react"; +import { Ban, CheckCircle2, RefreshCcw, Terminal } from "lucide-react"; +import React, { useState } from "react"; +import { toast } from "sonner"; +import { type LogLine, parseLogs } from "../../docker/logs/utils"; import { DockerTerminalModal } from "../../settings/web-server/docker-terminal-modal"; -import { StartPostgres } from "../start-postgres"; -import { DeployPostgres } from "./deploy-postgres"; -import { ResetPostgres } from "./reset-postgres"; -import { StopPostgres } from "./stop-postgres"; interface Props { postgresId: string; } export const ShowGeneralPostgres = ({ postgresId }: Props) => { - const { data } = api.postgres.one.useQuery( + const { data, refetch } = api.postgres.one.useQuery( { - postgresId, + postgresId: postgresId, }, { enabled: !!postgresId }, ); + const { mutateAsync: reload, isLoading: isReloading } = + api.postgres.reload.useMutation(); + + const { mutateAsync: stop, isLoading: isStopping } = + api.postgres.stop.useMutation(); + + const { mutateAsync: start, isLoading: isStarting } = + api.postgres.start.useMutation(); + + const [isDrawerOpen, setIsDrawerOpen] = useState(false); + const [filteredLogs, setFilteredLogs] = useState([]); + const [isDeploying, setIsDeploying] = useState(false); + api.postgres.deployWithLogs.useSubscription( + { + postgresId: postgresId, + }, + { + enabled: isDeploying, + onData(log) { + if (!isDrawerOpen) { + setIsDrawerOpen(true); + } + + if (log === "Deployment completed successfully!") { + setIsDeploying(false); + } + const parsedLogs = parseLogs(log); + setFilteredLogs((prev) => [...prev, ...parsedLogs]); + }, + onError(error) { + console.error("Deployment logs error:", error); + setIsDeploying(false); + }, + }, + ); + return (
- - Deploy Settings + + General - - - + + { + setIsDeploying(true); + + await new Promise((resolve) => setTimeout(resolve, 1000)); + refetch(); + }} + > + + + + { + await reload({ + postgresId: postgresId, + appName: data?.appName || "", + }) + .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"); + }); + }} + > + + )} { + { + setIsDrawerOpen(false); + setFilteredLogs([]); + setIsDeploying(false); + refetch(); + }} + filteredLogs={filteredLogs} + />
); }; diff --git a/apps/dokploy/components/dashboard/postgres/general/stop-postgres.tsx b/apps/dokploy/components/dashboard/postgres/general/stop-postgres.tsx deleted file mode 100644 index 6e2b45573..000000000 --- a/apps/dokploy/components/dashboard/postgres/general/stop-postgres.tsx +++ /dev/null @@ -1,65 +0,0 @@ -import { - AlertDialog, - AlertDialogAction, - AlertDialogCancel, - AlertDialogContent, - AlertDialogDescription, - AlertDialogFooter, - AlertDialogHeader, - AlertDialogTitle, - AlertDialogTrigger, -} from "@/components/ui/alert-dialog"; -import { Button } from "@/components/ui/button"; -import { api } from "@/utils/api"; -import { Ban } from "lucide-react"; -import { toast } from "sonner"; - -interface Props { - postgresId: string; -} - -export const StopPostgres = ({ postgresId }: Props) => { - const { mutateAsync, isLoading } = api.postgres.stop.useMutation(); - const utils = api.useUtils(); - return ( - - - - - - - - Are you absolutely sure to stop the database? - - - This will stop the database - - - - Cancel - { - await mutateAsync({ - postgresId, - }) - .then(async () => { - await utils.postgres.one.invalidate({ - postgresId, - }); - toast.success("Postgres stopped successfully"); - }) - .catch(() => { - toast.error("Error stopping Postgres"); - }); - }} - > - Confirm - - - - - ); -}; diff --git a/apps/dokploy/components/dashboard/postgres/start-postgres.tsx b/apps/dokploy/components/dashboard/postgres/start-postgres.tsx deleted file mode 100644 index 4b1c7475f..000000000 --- a/apps/dokploy/components/dashboard/postgres/start-postgres.tsx +++ /dev/null @@ -1,65 +0,0 @@ -import { - AlertDialog, - AlertDialogAction, - AlertDialogCancel, - AlertDialogContent, - AlertDialogDescription, - AlertDialogFooter, - AlertDialogHeader, - AlertDialogTitle, - AlertDialogTrigger, -} from "@/components/ui/alert-dialog"; -import { Button } from "@/components/ui/button"; -import { api } from "@/utils/api"; -import { CheckCircle2 } from "lucide-react"; -import { toast } from "sonner"; - -interface Props { - postgresId: string; -} - -export const StartPostgres = ({ postgresId }: Props) => { - const { mutateAsync, isLoading } = api.postgres.start.useMutation(); - const utils = api.useUtils(); - return ( - - - - - - - - Are you sure to start the database? - - - This will start the database - - - - Cancel - { - await mutateAsync({ - postgresId, - }) - .then(async () => { - await utils.postgres.one.invalidate({ - postgresId, - }); - toast.success("Database started successfully"); - }) - .catch(() => { - toast.error("Error starting the Database"); - }); - }} - > - Confirm - - - - - ); -}; diff --git a/apps/dokploy/components/dashboard/postgres/update-postgres.tsx b/apps/dokploy/components/dashboard/postgres/update-postgres.tsx index 566fd1d6e..54ad5bce0 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, SquarePen } from "lucide-react"; +import { AlertTriangle, PenBoxIcon, SquarePen } from "lucide-react"; import { useEffect, useState } from "react"; import { useForm } from "react-hook-form"; import { toast } from "sonner"; @@ -91,8 +91,12 @@ export const UpdatePostgres = ({ postgresId }: Props) => { return ( - diff --git a/apps/dokploy/components/dashboard/postgres/volumes/show-volumes.tsx b/apps/dokploy/components/dashboard/postgres/volumes/show-volumes.tsx deleted file mode 100644 index 9fd33c505..000000000 --- a/apps/dokploy/components/dashboard/postgres/volumes/show-volumes.tsx +++ /dev/null @@ -1,134 +0,0 @@ -import { AlertBlock } from "@/components/shared/alert-block"; -import { - Card, - CardContent, - CardDescription, - CardHeader, - CardTitle, -} from "@/components/ui/card"; -import { api } from "@/utils/api"; -import { AlertTriangle, Package } from "lucide-react"; -import React from "react"; -import { AddVolumes } from "../../application/advanced/volumes/add-volumes"; -import { DeleteVolume } from "../../application/advanced/volumes/delete-volume"; -import { UpdateVolume } from "../../application/advanced/volumes/update-volume"; -interface Props { - postgresId: string; -} - -export const ShowVolumes = ({ postgresId }: Props) => { - const { data, refetch } = api.postgres.one.useQuery( - { - postgresId, - }, - { enabled: !!postgresId }, - ); - - return ( - - -
- Volumes - - If you want to persist data in this postgres database use the - following config to setup the volumes - -
- - {data && data?.mounts.length > 0 && ( - - Add Volume - - )} -
- - {data?.mounts.length === 0 ? ( -
- - - No volumes/mounts configured - - - Add Volume - -
- ) : ( -
- - Please remember to click Redeploy after adding, editing, or - deleting a mount to apply the changes. - -
- {data?.mounts.map((mount) => ( -
-
- {/* */} -
-
- Mount Type - - {mount.type.toUpperCase()} - -
- {mount.type === "volume" && ( -
- Volume Name - - {mount.volumeName} - -
- )} - - {mount.type === "file" && ( -
- Content - - {mount.content} - -
- )} - {mount.type === "bind" && ( -
- Host Path - - {mount.hostPath} - -
- )} -
- Mount Path - - {mount.mountPath} - -
-
-
- - -
-
-
- ))} -
-
- )} -
-
- ); -}; diff --git a/apps/dokploy/components/dashboard/project/add-template.tsx b/apps/dokploy/components/dashboard/project/add-template.tsx index ae93069a6..068fc984d 100644 --- a/apps/dokploy/components/dashboard/project/add-template.tsx +++ b/apps/dokploy/components/dashboard/project/add-template.tsx @@ -35,6 +35,7 @@ import { PopoverContent, PopoverTrigger, } from "@/components/ui/popover"; +import { ScrollArea } from "@/components/ui/scroll-area"; import { Select, SelectContent, @@ -52,7 +53,6 @@ import { } from "@/components/ui/tooltip"; import { cn } from "@/lib/utils"; import { api } from "@/utils/api"; -import { ScrollArea } from "@radix-ui/react-scroll-area"; import { BookText, CheckIcon, @@ -61,12 +61,15 @@ import { Github, Globe, HelpCircle, + LayoutGrid, + List, PuzzleIcon, SearchIcon, } from "lucide-react"; import Link from "next/link"; import { useState } from "react"; import { toast } from "sonner"; + interface Props { projectId: string; } @@ -74,8 +77,9 @@ interface Props { export const AddTemplate = ({ projectId }: Props) => { const [query, setQuery] = useState(""); const [open, setOpen] = useState(false); - const { data } = api.compose.templates.useQuery(); + const [viewMode, setViewMode] = useState<"detailed" | "icon">("detailed"); const [selectedTags, setSelectedTags] = useState([]); + const { data } = api.compose.templates.useQuery(); const { data: servers } = api.server.withSSHKey.useQuery(); const { data: tags, isLoading: isLoadingTags } = api.compose.getTags.useQuery(); @@ -108,279 +112,340 @@ export const AddTemplate = ({ projectId }: Props) => { Template - -
- - Create from Template - - Create an open source application from a template - - - {isError && {error?.message}} -
- setQuery(e.target.value)} - className="w-full" - value={query} - /> - - + + +
+
+
+ Create from Template + + Create an open source application from a template + +
+
+ setQuery(e.target.value)} + className="w-[200px]" + value={query} + /> + + + + + + + + {isLoadingTags && ( + + Loading Tags.... + + )} + No tags found. + + + {tags?.map((tag) => ( + { + if (selectedTags.includes(tag)) { + setSelectedTags( + selectedTags.filter((t) => t !== tag), + ); + return; + } + setSelectedTags([...selectedTags, tag]); + }} + > + {tag} + + + ))} + + + + + - - - - - {isLoadingTags && ( - - Loading Tags.... - + {viewMode === "detailed" ? ( + + ) : ( + )} - No tags found. - - - {tags?.map((tag) => { - return ( - { - if (selectedTags.includes(tag)) { - setSelectedTags( - selectedTags.filter((t) => t !== tag), - ); - return; - } - setSelectedTags([...selectedTags, tag]); - }} - > - {tag} - - - ); - })} - - - - - -
-
-
- {templates.length === 0 ? ( -
- -
- No templates found +
- ) : ( -
- {templates?.map((template, index) => ( -
-
0 && ( +
+ {selectedTags.map((tag) => ( + + setSelectedTags(selectedTags.filter((t) => t !== tag)) + } > -
-
- -
+ {tag} Ɨ + + ))} +
+ )} +
+ -
-
-
-
- - {template.name} - - {template.version} -
+ +
+ {isError && {error?.message}} -
- - - - {template.links.website && ( - - - - )} - {template.links.docs && ( - - - - )} - - - -
-
+ {templates.length === 0 ? ( +
+ +
+ No templates found +
+
+ ) : ( +
+ {templates?.map((template, index) => ( +
+ + {template.version} + + {/* Template Header */} +
+ {template.name} +
+ + {template.name} + + {viewMode === "detailed" && + template.tags.length > 0 && ( +
{template.tags.map((tag) => ( - + {tag} ))}
-
+ )} +
+
- - - - - - - - Are you absolutely sure? - - - This will create an application from the{" "} - {template.name} template and add it to your - project. - + {/* Template Content */} + {viewMode === "detailed" && ( + +
+ {template.description} +
+
+ )} -
- - - - - - - - If ot server is selected, the - application will be deployed on the - server where the user is logged in. - - - - + {/* Create Button */} +
+ {viewMode === "detailed" && ( +
+ + + + {template.links.website && ( + + + + )} + {template.links.docs && ( + + + + )} +
+ )} + + + + + + + + Are you absolutely sure? + + + This will create an application from the{" "} + {template.name} template and add it to your + project. + - -
- - - Cancel - { - const promise = mutateAsync({ + + If ot server is selected, the application + will be deployed on the server where the + user is logged in. + + + + + + +
+
+ + Cancel + { + const promise = mutateAsync({ + projectId, + serverId: serverId || undefined, + id: template.id, + }); + toast.promise(promise, { + loading: "Setting up...", + success: (data) => { + utils.project.one.invalidate({ projectId, - serverId: serverId || undefined, - id: template.id, - }); - toast.promise(promise, { - loading: "Setting up...", - success: (data) => { - utils.project.one.invalidate({ - projectId, - }); - setOpen(false); - return `${template.name} template created successfully`; - }, - error: (err) => { - return `An error ocurred deploying ${template.name} template`; - }, }); - }} - > - Confirm - - -
-
-
- -

- {template.description} -

-
+ setOpen(false); + return `${template.name} template created successfully`; + }, + error: (err) => { + return `An error ocurred deploying ${template.name} template`; + }, + }); + }} + > + Confirm + + + +
-
- ))} -
- )} -
+ ))} +
+ )} +
+
); diff --git a/apps/dokploy/components/dashboard/projects/add.tsx b/apps/dokploy/components/dashboard/projects/handle-project.tsx similarity index 70% rename from apps/dokploy/components/dashboard/projects/add.tsx rename to apps/dokploy/components/dashboard/projects/handle-project.tsx index d1b8cebd4..cf38c57c9 100644 --- a/apps/dokploy/components/dashboard/projects/add.tsx +++ b/apps/dokploy/components/dashboard/projects/handle-project.tsx @@ -9,6 +9,7 @@ import { DialogTitle, DialogTrigger, } from "@/components/ui/dialog"; +import { DropdownMenuItem } from "@/components/ui/dropdown-menu"; import { Form, FormControl, @@ -22,7 +23,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 { PlusIcon } from "lucide-react"; +import { PlusIcon, SquarePen } from "lucide-react"; import { useRouter } from "next/router"; import { useEffect, useState } from "react"; import { useForm } from "react-hook-form"; @@ -38,10 +39,26 @@ const AddProjectSchema = z.object({ type AddProject = z.infer; -export const AddProject = () => { +interface Props { + projectId?: string; +} + +export const HandleProject = ({ projectId }: Props) => { const utils = api.useUtils(); const [isOpen, setIsOpen] = useState(false); - const { mutateAsync, error, isError } = api.project.create.useMutation(); + + const { mutateAsync, error, isError } = projectId + ? api.project.update.useMutation() + : api.project.create.useMutation(); + + const { data, refetch } = api.project.one.useQuery( + { + projectId: projectId || "", + }, + { + enabled: !!projectId, + }, + ); const router = useRouter(); const form = useForm({ defaultValues: { @@ -53,34 +70,51 @@ export const AddProject = () => { useEffect(() => { form.reset({ - description: "", - name: "", + description: data?.description ?? "", + name: data?.name ?? "", }); - }, [form, form.reset, form.formState.isSubmitSuccessful]); + }, [form, form.reset, form.formState.isSubmitSuccessful, data]); const onSubmit = async (data: AddProject) => { await mutateAsync({ name: data.name, description: data.description, + projectId: projectId || "", }) .then(async (data) => { await utils.project.all.invalidate(); - toast.success("Project Created"); + toast.success(projectId ? "Project Updated" : "Project Created"); setIsOpen(false); - router.push(`/dashboard/project/${data.projectId}`); + if (!projectId) { + router.push(`/dashboard/project/${data?.projectId}`); + } else { + refetch(); + } }) .catch(() => { - toast.error("Error creating a project"); + toast.error( + projectId ? "Error updating a project" : "Error creating a project", + ); }); }; return ( - + {projectId ? ( + e.preventDefault()} + > + + Update + + ) : ( + + )} @@ -137,7 +171,7 @@ export const AddProject = () => { form="hook-form-add-project" type="submit" > - Create + {projectId ? "Update" : "Create"} diff --git a/apps/dokploy/components/dashboard/projects/project.tsx b/apps/dokploy/components/dashboard/projects/project.tsx deleted file mode 100644 index d1fdc5b89..000000000 --- a/apps/dokploy/components/dashboard/projects/project.tsx +++ /dev/null @@ -1,57 +0,0 @@ -// import { ComponentProps } from "react"; -// import { formatDistanceToNow } from "date-fns"; -// import { cn } from "@/lib/utils"; -// import { Badge } from "@/components/ui/badge"; - -// interface Props { -// item: ProjectType; -// } - -// export const Project = ({ item }: Props) => { -// return ( -// -// ); -// }; - -// const getBadgeVariantFromLabel = ( -// label: string, -// ): ComponentProps["variant"] => { -// if (["work"].includes(label.toLowerCase())) { -// return "default"; -// } - -// if (["personal"].includes(label.toLowerCase())) { -// return "outline"; -// } - -// return "secondary"; -// }; diff --git a/apps/dokploy/components/dashboard/projects/show.tsx b/apps/dokploy/components/dashboard/projects/show.tsx index 592621835..1f6c7ab4d 100644 --- a/apps/dokploy/components/dashboard/projects/show.tsx +++ b/apps/dokploy/components/dashboard/projects/show.tsx @@ -1,3 +1,4 @@ +import { BreadcrumbSidebar } from "@/components/shared/breadcrumb-sidebar"; import { DateTooltip } from "@/components/shared/date-tooltip"; import { AlertDialog, @@ -11,35 +12,42 @@ import { AlertDialogTrigger, } from "@/components/ui/alert-dialog"; import { Button } from "@/components/ui/button"; -import { Card, CardFooter, CardHeader, CardTitle } from "@/components/ui/card"; +import { + Card, + CardContent, + CardDescription, + CardFooter, + CardHeader, + CardTitle, +} from "@/components/ui/card"; import { DropdownMenu, DropdownMenuContent, - DropdownMenuGroup, DropdownMenuItem, DropdownMenuLabel, - DropdownMenuSeparator, DropdownMenuTrigger, } from "@/components/ui/dropdown-menu"; +import { Input } from "@/components/ui/input"; import { api } from "@/utils/api"; import { AlertTriangle, BookIcon, - ExternalLink, ExternalLinkIcon, FolderInput, + Loader2, MoreHorizontalIcon, + Search, TrashIcon, } from "lucide-react"; import Link from "next/link"; -import { Fragment } from "react"; +import { useMemo, useState } from "react"; import { toast } from "sonner"; +import { HandleProject } from "./handle-project"; import { ProjectEnvironment } from "./project-environment"; -import { UpdateProject } from "./update"; export const ShowProjects = () => { const utils = api.useUtils(); - const { data } = api.project.all.useQuery(); + const { data, isLoading } = api.project.all.useQuery(); const { data: auth } = api.auth.get.useQuery(); const { data: user } = api.user.byAuthId.useQuery( { @@ -50,244 +58,247 @@ export const ShowProjects = () => { }, ); const { mutateAsync } = api.project.remove.useMutation(); + const [searchQuery, setSearchQuery] = useState(""); + + const filteredProjects = useMemo(() => { + if (!data) return []; + return data.filter( + (project) => + project.name.toLowerCase().includes(searchQuery.toLowerCase()) || + project.description?.toLowerCase().includes(searchQuery.toLowerCase()), + ); + }, [data, searchQuery]); return ( <> - {data?.length === 0 && ( -
- - - No projects added yet. Click on Create project. - -
- )} -
- {data?.map((project) => { - const emptyServices = - project?.mariadb.length === 0 && - project?.mongo.length === 0 && - project?.mysql.length === 0 && - project?.postgres.length === 0 && - project?.redis.length === 0 && - project?.applications.length === 0 && - project?.compose.length === 0; + +
+ +
+
+ + + + Projects + + + Create and manage your projects + + +
+ +
+
- const totalServices = - project?.mariadb.length + - project?.mongo.length + - project?.mysql.length + - project?.postgres.length + - project?.redis.length + - project?.applications.length + - project?.compose.length; + + {isLoading ? ( +
+ Loading... + +
+ ) : ( + <> +
+ setSearchQuery(e.target.value)} + className="pr-10" + /> + +
+ {filteredProjects?.length === 0 && ( +
+ + + No projects found + +
+ )} +
+ {filteredProjects?.map((project) => { + const emptyServices = + project?.mariadb.length === 0 && + project?.mongo.length === 0 && + project?.mysql.length === 0 && + project?.postgres.length === 0 && + project?.redis.length === 0 && + project?.applications.length === 0 && + project?.compose.length === 0; - const flattedDomains = [ - ...project.applications.flatMap((a) => a.domains), - ...project.compose.flatMap((a) => a.domains), - ]; + const totalServices = + project?.mariadb.length + + project?.mongo.length + + project?.mysql.length + + project?.postgres.length + + project?.redis.length + + project?.applications.length + + project?.compose.length; - const renderDomainsDropdown = ( - item: typeof project.compose | typeof project.applications, - ) => - item[0] ? ( - - - {"applicationId" in item[0] ? "Applications" : "Compose"} - - {item.map((a) => ( - - - - - {a.name} - - - {a.domains.map((domain) => ( - + return ( +
- {domain.host} - - - - ))} - - - ))} - - ) : null; - - return ( -
- - - {flattedDomains.length > 1 ? ( - - - - - e.stopPropagation()} - > - {renderDomainsDropdown(project.applications)} - {renderDomainsDropdown(project.compose)} - - - ) : flattedDomains[0] ? ( - - ) : null} + + - - - -
- - - {project.name} - -
+ + + +
+ + + {project.name} + +
- - {project.description} - -
-
- - - - - - - Actions - -
e.stopPropagation()}> - -
-
e.stopPropagation()}> - -
+ + {project.description} + + +
+ + + + + + + Actions + +
e.stopPropagation()} + > + +
+
e.stopPropagation()} + > + +
-
e.stopPropagation()}> - {(auth?.rol === "admin" || - user?.canDeleteProjects) && ( - - - e.preventDefault()} - > - - Delete - - - - - - Are you sure to delete this project? - - {!emptyServices ? ( -
- - - You have active services, please - delete them first - +
e.stopPropagation()} + > + {(auth?.rol === "admin" || + user?.canDeleteProjects) && ( + + + + e.preventDefault() + } + > + + Delete + + + + + + Are you sure to delete this + project? + + {!emptyServices ? ( +
+ + + You have active + services, please delete + them first + +
+ ) : ( + + This action cannot be + undone + + )} +
+ + + Cancel + + { + await mutateAsync({ + projectId: + project.projectId, + }) + .then(() => { + toast.success( + "Project deleted successfully", + ); + }) + .catch(() => { + toast.error( + "Error deleting this project", + ); + }) + .finally(() => { + utils.project.all.invalidate(); + }); + }} + > + Delete + + +
+
+ )}
- ) : ( - - This action cannot be undone - - )} - - - - Cancel - - { - await mutateAsync({ - projectId: project.projectId, - }) - .then(() => { - toast.success( - "Project deleted successfully", - ); - }) - .catch(() => { - toast.error( - "Error deleting this project", - ); - }) - .finally(() => { - utils.project.all.invalidate(); - }); - }} - > - Delete - - - - - )} -
- - -
- - - -
- - Created - - - {totalServices}{" "} - {totalServices === 1 ? "service" : "services"} - -
-
- - -
- ); - })} +
+
+
+
+
+ +
+ + Created + + + {totalServices}{" "} + {totalServices === 1 + ? "service" + : "services"} + +
+
+
+ +
+ ); + })} +
+ + )} + +
+
); diff --git a/apps/dokploy/components/dashboard/projects/update.tsx b/apps/dokploy/components/dashboard/projects/update.tsx deleted file mode 100644 index 79665c717..000000000 --- a/apps/dokploy/components/dashboard/projects/update.tsx +++ /dev/null @@ -1,161 +0,0 @@ -import { AlertBlock } from "@/components/shared/alert-block"; -import { Button } from "@/components/ui/button"; -import { - Dialog, - DialogContent, - DialogDescription, - DialogFooter, - DialogHeader, - DialogTitle, - DialogTrigger, -} from "@/components/ui/dialog"; -import { DropdownMenuItem } from "@/components/ui/dropdown-menu"; -import { - Form, - FormControl, - FormField, - FormItem, - FormLabel, - FormMessage, -} from "@/components/ui/form"; -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, SquarePen } from "lucide-react"; -import { useEffect, useState } from "react"; -import { useForm } from "react-hook-form"; -import { toast } from "sonner"; -import { z } from "zod"; - -const updateProjectSchema = z.object({ - name: z.string().min(1, { - message: "Name is required", - }), - description: z.string().optional(), -}); - -type UpdateProject = z.infer; - -interface Props { - projectId: string; -} - -export const UpdateProject = ({ projectId }: Props) => { - const [isOpen, setIsOpen] = useState(false); - const utils = api.useUtils(); - const { mutateAsync, error, isError } = api.project.update.useMutation(); - const { data } = api.project.one.useQuery( - { - projectId, - }, - { - enabled: !!projectId, - }, - ); - const form = useForm({ - defaultValues: { - description: data?.description ?? "", - name: data?.name ?? "", - }, - resolver: zodResolver(updateProjectSchema), - }); - useEffect(() => { - if (data) { - form.reset({ - description: data.description ?? "", - name: data.name, - }); - } - }, [data, form, form.reset]); - - const onSubmit = async (formData: UpdateProject) => { - await mutateAsync({ - name: formData.name, - projectId: projectId, - description: formData.description || "", - }) - .then(() => { - toast.success("Project updated successfully"); - utils.project.all.invalidate(); - setIsOpen(false); - }) - .catch(() => { - toast.error("Error updating the Project"); - }) - .finally(() => {}); - }; - - return ( - - - e.preventDefault()} - > - - Update - - - - - Modify project - - Update the details of the project - - - {isError && {error?.message}} - -
-
-
- - ( - - Name - - - - - - - )} - /> - ( - - Description - -