From 7cb299a4bb06a071352051436c7702ed69aa314e Mon Sep 17 00:00:00 2001 From: Mauricio Siu <47042324+Siumauricio@users.noreply.github.com> Date: Wed, 29 May 2024 21:05:22 -0600 Subject: [PATCH] v0.1.0 (#112) * feat: add schema for registry and routes * feat: add docker registry upload * feat: add show cluster * refactor: set the registry url in image in case we have a registry asociated * feat: add update registry and fix the docker url markup * chore: remove --advertise-ip on swarm script * refactor: remove listen address of swarm initialize * feat: add table to show nodes and add dropdown to add manager & workers * refactor: improve interface for cluster * refactor: improve UI * feat: add experimental swarm settings * refactor: remove comments * refactor: prettify json of each setting * refactor: add interface tooltip * refactor: delete static form self registry * refactor: allow to se a empty registry * fix: remove text area warnings * feat: add network swarm json * refactor: update ui * revert: go back to swarm init config * refactor: remove initialization on server, only on setup script * Update LICENSE.MD * feat: appearance theme support system config * refactor: remove logs * fix(README-ru): hyperlink-ed docs url * feat: (#107) webhook listener filter docker events based on image tag. Fixes #107 * refactor: simplify comparison docker tags * refactor: remove return in res status * refactor: prevent to updates download automatically * feat: support code editor (#105) * feat: support code editor * Update codeblock * refactor: remove unused class --------- Co-authored-by: Mauricio Siu <47042324+Siumauricio@users.noreply.github.com> * fix: select the right image from sourcetype (#109) * chore: bump minor version --------- Co-authored-by: hehehai Co-authored-by: Bayram Tagiev Co-authored-by: Paulo Santana <30875229+hikinine@users.noreply.github.com> --- Dockerfile | 3 +- LICENSE.MD | 11 +- README-ru.md | 2 +- .../cluster/modify-swarm-settings.tsx | 756 ++++++ .../cluster/show-cluster-settings.tsx | 203 ++ .../advanced/ports/update-port.tsx | 4 +- .../advanced/redirects/update-redirect.tsx | 4 +- .../advanced/security/update-security.tsx | 4 +- .../advanced/traefik/show-traefik-config.tsx | 13 +- .../traefik/update-traefik-config.tsx | 8 +- .../application/delete-application.tsx | 4 +- .../application/domains/delete-domain.tsx | 4 +- .../application/domains/show-domains.tsx | 14 +- .../application/domains/update-domain.tsx | 4 +- .../application/update-application.tsx | 2 +- .../database/backups/update-backup.tsx | 4 +- .../file-system/show-traefik-file.tsx | 12 +- .../file-system/show-traefik-system.tsx | 4 +- .../dashboard/mariadb/delete-mariadb.tsx | 98 +- .../dashboard/mariadb/update-mariadb.tsx | 2 +- components/dashboard/mongo/delete-mongo.tsx | 98 +- components/dashboard/mongo/update-mongo.tsx | 2 +- components/dashboard/mysql/delete-mysql.tsx | 98 +- components/dashboard/mysql/update-mysql.tsx | 2 +- .../dashboard/postgres/delete-postgres.tsx | 98 +- .../dashboard/postgres/update-postgres.tsx | 2 +- components/dashboard/projects/show.tsx | 4 +- components/dashboard/redis/delete-redis.tsx | 98 +- components/dashboard/redis/update-redis.tsx | 2 +- .../dashboard/settings/appearance-form.tsx | 58 +- .../certificates/show-certificates.tsx | 30 +- .../settings/cluster/nodes/add-node.tsx | 66 + .../cluster/nodes/manager/add-manager.tsx | 63 + .../settings/cluster/nodes/show-node-data.tsx | 43 + .../settings/cluster/nodes/show-nodes.tsx | 162 ++ .../cluster/nodes/workers/add-worker.tsx | 61 + .../cluster/nodes/workers/delete-worker.tsx | 62 + .../cluster/registry/add-docker-registry.tsx | 251 ++ .../registry/add-self-docker-registry.tsx | 181 ++ .../cluster/registry/delete-registry.tsx | 61 + .../cluster/registry/show-registry.tsx | 76 + .../registry/update-docker-registry.tsx | 275 ++ .../destination/show-destinations.tsx | 6 +- .../settings/github/github-setup.tsx | 16 +- .../settings/profile/profile-form.tsx | 6 +- .../settings/users/add-permissions.tsx | 11 +- .../dashboard/settings/users/add-user.tsx | 5 +- .../dashboard/settings/users/delete-user.tsx | 10 +- .../dashboard/settings/users/show-users.tsx | 172 +- components/dashboard/settings/web-server.tsx | 12 +- .../web-server/show-main-traefik-config.tsx | 6 +- .../show-server-middleware-config.tsx | 8 +- .../web-server/show-server-traefik-config.tsx | 8 +- .../settings/web-server/terminal-modal.tsx | 2 +- .../settings/web-server/update-server.tsx | 98 + .../settings/web-server/update-webserver.tsx | 6 +- components/layouts/project-layout.tsx | 2 +- components/layouts/settings-layout.tsx | 10 +- components/shared/code-editor.tsx | 45 + components/shared/date-tooltip.tsx | 11 +- components/ui/file-tree.tsx | 3 +- components/ui/form.tsx | 1 - docker/prod.sh | 2 +- drizzle/0005_cute_terror.sql | 22 + drizzle/0006_oval_jimmy_woo.sql | 6 + drizzle/0007_cute_guardsmen.sql | 1 + drizzle/0008_lazy_sage.sql | 7 + drizzle/0009_majestic_spencer_smythe.sql | 1 + drizzle/0010_lean_black_widow.sql | 1 + drizzle/0011_petite_calypso.sql | 1 + drizzle/0012_chubby_umar.sql | 7 + drizzle/0013_blushing_starjammers.sql | 1 + drizzle/meta/0000_snapshot.json | 394 +-- drizzle/meta/0001_snapshot.json | 394 +-- drizzle/meta/0002_snapshot.json | 394 +-- drizzle/meta/0003_snapshot.json | 394 +-- drizzle/meta/0004_snapshot.json | 394 +-- drizzle/meta/0005_snapshot.json | 2295 ++++++++++++++++ drizzle/meta/0006_snapshot.json | 2331 ++++++++++++++++ drizzle/meta/0007_snapshot.json | 2338 ++++++++++++++++ drizzle/meta/0008_snapshot.json | 2338 ++++++++++++++++ drizzle/meta/0009_snapshot.json | 2338 ++++++++++++++++ drizzle/meta/0010_snapshot.json | 2344 ++++++++++++++++ drizzle/meta/0011_snapshot.json | 2344 ++++++++++++++++ drizzle/meta/0012_snapshot.json | 2386 ++++++++++++++++ drizzle/meta/0013_snapshot.json | 2392 +++++++++++++++++ drizzle/meta/_journal.json | 63 + package.json | 18 +- pages/_error.tsx | 4 +- pages/api/deploy/[refreshToken].ts | 50 +- pages/dashboard/project/[projectId].tsx | 2 +- .../services/application/[applicationId].tsx | 4 +- .../services/mariadb/[mariadbId].tsx | 2 +- .../[projectId]/services/mongo/[mongoId].tsx | 2 +- .../[projectId]/services/mysql/[mysqlId].tsx | 2 +- .../services/postgres/[postgresId].tsx | 2 +- .../[projectId]/services/redis/[redisId].tsx | 2 +- pages/dashboard/settings/cluster.tsx | 43 + pages/index.tsx | 2 +- pnpm-lock.yaml | 284 +- public/images/theme-dark.svg | 1 + public/images/theme-light.svg | 1 + public/images/theme-system.svg | 1 + server/api/root.ts | 4 + server/api/routers/cluster.ts | 48 + server/api/routers/registry.ts | 92 + server/api/routers/settings.ts | 3 +- server/api/services/application.ts | 1 + server/api/services/cluster.ts | 41 + server/api/services/registry.ts | 113 + server/constants/index.ts | 1 + server/db/drizzle.config.ts | 17 +- server/db/schema/admin.ts | 2 + server/db/schema/application.ts | 183 +- server/db/schema/index.ts | 2 +- server/db/schema/registry.ts | 98 + server/server.ts | 3 +- server/setup/registry-setup.ts | 89 + server/utils/builders/index.ts | 88 +- server/utils/cluster/upload.ts | 65 + server/utils/docker/utils.ts | 81 +- server/utils/traefik/domain.ts | 5 +- server/utils/traefik/registry.ts | 71 + styles/globals.css | 41 +- 124 files changed, 26519 insertions(+), 1524 deletions(-) create mode 100644 components/dashboard/application/advanced/cluster/modify-swarm-settings.tsx create mode 100644 components/dashboard/application/advanced/cluster/show-cluster-settings.tsx create mode 100644 components/dashboard/settings/cluster/nodes/add-node.tsx create mode 100644 components/dashboard/settings/cluster/nodes/manager/add-manager.tsx create mode 100644 components/dashboard/settings/cluster/nodes/show-node-data.tsx create mode 100644 components/dashboard/settings/cluster/nodes/show-nodes.tsx create mode 100644 components/dashboard/settings/cluster/nodes/workers/add-worker.tsx create mode 100644 components/dashboard/settings/cluster/nodes/workers/delete-worker.tsx create mode 100644 components/dashboard/settings/cluster/registry/add-docker-registry.tsx create mode 100644 components/dashboard/settings/cluster/registry/add-self-docker-registry.tsx create mode 100644 components/dashboard/settings/cluster/registry/delete-registry.tsx create mode 100644 components/dashboard/settings/cluster/registry/show-registry.tsx create mode 100644 components/dashboard/settings/cluster/registry/update-docker-registry.tsx create mode 100644 components/dashboard/settings/web-server/update-server.tsx create mode 100644 components/shared/code-editor.tsx create mode 100644 drizzle/0005_cute_terror.sql create mode 100644 drizzle/0006_oval_jimmy_woo.sql create mode 100644 drizzle/0007_cute_guardsmen.sql create mode 100644 drizzle/0008_lazy_sage.sql create mode 100644 drizzle/0009_majestic_spencer_smythe.sql create mode 100644 drizzle/0010_lean_black_widow.sql create mode 100644 drizzle/0011_petite_calypso.sql create mode 100644 drizzle/0012_chubby_umar.sql create mode 100644 drizzle/0013_blushing_starjammers.sql create mode 100644 drizzle/meta/0005_snapshot.json create mode 100644 drizzle/meta/0006_snapshot.json create mode 100644 drizzle/meta/0007_snapshot.json create mode 100644 drizzle/meta/0008_snapshot.json create mode 100644 drizzle/meta/0009_snapshot.json create mode 100644 drizzle/meta/0010_snapshot.json create mode 100644 drizzle/meta/0011_snapshot.json create mode 100644 drizzle/meta/0012_snapshot.json create mode 100644 drizzle/meta/0013_snapshot.json create mode 100644 pages/dashboard/settings/cluster.tsx create mode 100644 public/images/theme-dark.svg create mode 100644 public/images/theme-light.svg create mode 100644 public/images/theme-system.svg create mode 100644 server/api/routers/cluster.ts create mode 100644 server/api/routers/registry.ts create mode 100644 server/api/services/cluster.ts create mode 100644 server/api/services/registry.ts create mode 100644 server/db/schema/registry.ts create mode 100644 server/setup/registry-setup.ts create mode 100644 server/utils/cluster/upload.ts create mode 100644 server/utils/traefik/registry.ts diff --git a/Dockerfile b/Dockerfile index 97d7b26a7..6d3e58758 100644 --- a/Dockerfile +++ b/Dockerfile @@ -26,7 +26,7 @@ FROM node:18-slim AS production # Install dependencies only for production ENV PNPM_HOME="/pnpm" ENV PATH="$PNPM_HOME:$PATH" -RUN corepack enable && apt-get update && apt-get install -y curl && rm -rf /var/lib/apt/lists/* +RUN corepack enable && apt-get update && apt-get install -y curl && apt-get install -y apache2-utils && rm -rf /var/lib/apt/lists/* WORKDIR /app @@ -47,7 +47,6 @@ RUN --mount=type=cache,id=pnpm,target=/pnpm/store pnpm install --prod --frozen-l # Install docker RUN curl -fsSL https://get.docker.com -o get-docker.sh && sh get-docker.sh && rm get-docker.sh - # Install Nixpacks and tsx # | VERBOSE=1 VERSION=1.21.0 bash RUN curl -sSL https://nixpacks.com/install.sh -o install.sh \ diff --git a/LICENSE.MD b/LICENSE.MD index 9c53a3bc0..9031c94b9 100644 --- a/LICENSE.MD +++ b/LICENSE.MD @@ -1,5 +1,7 @@ # License +## Core License (Apache License 2.0) + Copyright 2024 Mauricio Siu. Licensed under the Apache License, Version 2.0 (the "License"); @@ -13,11 +15,12 @@ distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. -## Appendix +## Additional Terms for Specific Features -In the event of a conflict, the provisions in this appendix shall take precedence over those in the Apache License. +The following additional terms apply to the multi-node support and Docker Compose file support features of Dokploy. In the event of a conflict, these provisions shall take precedence over those in the Apache License: -- **Modification Distribution:** Any modifications to the software must be distributed freely. -- **Future Paid Features:** Any future paid features of Dokploy cannot be sold or offered as a service by any party other than the copyright holder without prior written consent. +- **Self-Hosted Version Free**: All features of Dokploy, including multi-node support and Docker Compose file support, will always be free to use in the self-hosted version. +- **Restriction on Resale**: The multi-node support and Docker Compose file support features cannot be sold or offered as a service by any party other than the copyright holder without prior written consent. +- **Modification Distribution**: Any modifications to the multi-node support and Docker Compose file support features must be distributed freely and cannot be sold or offered as a service. For further inquiries or permissions, please contact us directly. diff --git a/README-ru.md b/README-ru.md index a63e76f09..5f07d38d5 100644 --- a/README-ru.md +++ b/README-ru.md @@ -46,4 +46,4 @@ curl -sSL https://dokploy.com/install.sh | sh - Centos 9 ## πŸ“„ ДокумСнтация -Для ΠΏΠΎΠ΄Ρ€ΠΎΠ±Π½ΠΎΠΉ Π΄ΠΎΠΊΡƒΠΌΠ΅Π½Ρ‚Π°Ρ†ΠΈΠΈ посСтитС docs.dokploy.com/docs. +Для ΠΏΠΎΠ΄Ρ€ΠΎΠ±Π½ΠΎΠΉ Π΄ΠΎΠΊΡƒΠΌΠ΅Π½Ρ‚Π°Ρ†ΠΈΠΈ посСтитС [docs.dokploy.com/docs](https://docs.dokploy.com). diff --git a/components/dashboard/application/advanced/cluster/modify-swarm-settings.tsx b/components/dashboard/application/advanced/cluster/modify-swarm-settings.tsx new file mode 100644 index 000000000..e556650b8 --- /dev/null +++ b/components/dashboard/application/advanced/cluster/modify-swarm-settings.tsx @@ -0,0 +1,756 @@ +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 { api } from "@/utils/api"; +import { AlertBlock } from "@/components/shared/alert-block"; +import { zodResolver } from "@hookform/resolvers/zod"; +import { useEffect } from "react"; +import { useForm } from "react-hook-form"; +import { toast } from "sonner"; +import { z } from "zod"; +import { Textarea } from "@/components/ui/textarea"; +import { HelpCircle, Settings } from "lucide-react"; +import { + Tooltip, + TooltipContent, + TooltipProvider, + TooltipTrigger, +} from "@/components/ui/tooltip"; + +const HealthCheckSwarmSchema = z + .object({ + Test: z.array(z.string()).optional(), + Interval: z.number().optional(), + Timeout: z.number().optional(), + StartPeriod: z.number().optional(), + Retries: z.number().optional(), + }) + .strict(); + +const RestartPolicySwarmSchema = z + .object({ + Condition: z.string().optional(), + Delay: z.number().optional(), + MaxAttempts: z.number().optional(), + Window: z.number().optional(), + }) + .strict(); + +const PreferenceSchema = z + .object({ + Spread: z.object({ + SpreadDescriptor: z.string(), + }), + }) + .strict(); + +const PlatformSchema = z + .object({ + Architecture: z.string(), + OS: z.string(), + }) + .strict(); + +const PlacementSwarmSchema = z + .object({ + Constraints: z.array(z.string()).optional(), + Preferences: z.array(PreferenceSchema).optional(), + MaxReplicas: z.number().optional(), + Platforms: z.array(PlatformSchema).optional(), + }) + .strict(); + +const UpdateConfigSwarmSchema = z + .object({ + Parallelism: z.number(), + Delay: z.number().optional(), + FailureAction: z.string().optional(), + Monitor: z.number().optional(), + MaxFailureRatio: z.number().optional(), + Order: z.string(), + }) + .strict(); + +const ReplicatedSchema = z + .object({ + Replicas: z.number().optional(), + }) + .strict(); + +const ReplicatedJobSchema = z + .object({ + MaxConcurrent: z.number().optional(), + TotalCompletions: z.number().optional(), + }) + .strict(); + +const ServiceModeSwarmSchema = z + .object({ + Replicated: ReplicatedSchema.optional(), + Global: z.object({}).optional(), + ReplicatedJob: ReplicatedJobSchema.optional(), + GlobalJob: z.object({}).optional(), + }) + .strict(); + +const NetworkSwarmSchema = z.array( + z + .object({ + Target: z.string().optional(), + Aliases: z.array(z.string()).optional(), + DriverOpts: z.object({}).optional(), + }) + .strict(), +); + +const LabelsSwarmSchema = z.record(z.string()); + +const createStringToJSONSchema = (schema: z.ZodTypeAny) => { + return z + .string() + .transform((str, ctx) => { + if (str === null || str === "") { + return null; + } + try { + return JSON.parse(str); + } catch (e) { + ctx.addIssue({ code: "custom", message: "Invalid JSON format" }); + return z.NEVER; + } + }) + .superRefine((data, ctx) => { + if (data === null) { + return; + } + + if (Object.keys(data).length === 0) { + ctx.addIssue({ + code: z.ZodIssueCode.custom, + message: "Object cannot be empty", + }); + return; + } + + const parseResult = schema.safeParse(data); + if (!parseResult.success) { + for (const error of parseResult.error.issues) { + const path = error.path.join("."); + ctx.addIssue({ + code: z.ZodIssueCode.custom, + message: `${path} ${error.message}`, + }); + } + } + }); +}; + +const addSwarmSettings = z.object({ + healthCheckSwarm: createStringToJSONSchema(HealthCheckSwarmSchema).nullable(), + restartPolicySwarm: createStringToJSONSchema( + RestartPolicySwarmSchema, + ).nullable(), + placementSwarm: createStringToJSONSchema(PlacementSwarmSchema).nullable(), + updateConfigSwarm: createStringToJSONSchema( + UpdateConfigSwarmSchema, + ).nullable(), + rollbackConfigSwarm: createStringToJSONSchema( + UpdateConfigSwarmSchema, + ).nullable(), + modeSwarm: createStringToJSONSchema(ServiceModeSwarmSchema).nullable(), + labelsSwarm: createStringToJSONSchema(LabelsSwarmSchema).nullable(), + networkSwarm: createStringToJSONSchema(NetworkSwarmSchema).nullable(), +}); + +type AddSwarmSettings = z.infer; + +interface Props { + applicationId: string; +} + +export const AddSwarmSettings = ({ applicationId }: Props) => { + const { data, refetch } = api.application.one.useQuery( + { + applicationId, + }, + { + enabled: !!applicationId, + }, + ); + + const { mutateAsync, isError, error, isLoading } = + api.application.update.useMutation(); + + const form = useForm({ + defaultValues: { + healthCheckSwarm: null, + restartPolicySwarm: null, + placementSwarm: null, + updateConfigSwarm: null, + rollbackConfigSwarm: null, + modeSwarm: null, + labelsSwarm: null, + networkSwarm: null, + }, + resolver: zodResolver(addSwarmSettings), + }); + + useEffect(() => { + if (data) { + form.reset({ + healthCheckSwarm: data.healthCheckSwarm + ? JSON.stringify(data.healthCheckSwarm, null, 2) + : null, + restartPolicySwarm: data.restartPolicySwarm + ? JSON.stringify(data.restartPolicySwarm, null, 2) + : null, + placementSwarm: data.placementSwarm + ? JSON.stringify(data.placementSwarm, null, 2) + : null, + updateConfigSwarm: data.updateConfigSwarm + ? JSON.stringify(data.updateConfigSwarm, null, 2) + : null, + rollbackConfigSwarm: data.rollbackConfigSwarm + ? JSON.stringify(data.rollbackConfigSwarm, null, 2) + : null, + modeSwarm: data.modeSwarm + ? JSON.stringify(data.modeSwarm, null, 2) + : null, + labelsSwarm: data.labelsSwarm + ? JSON.stringify(data.labelsSwarm, null, 2) + : null, + networkSwarm: data.networkSwarm + ? JSON.stringify(data.networkSwarm, null, 2) + : null, + }); + } + }, [form, form.reset, data]); + + const onSubmit = async (data: AddSwarmSettings) => { + await mutateAsync({ + applicationId, + healthCheckSwarm: data.healthCheckSwarm, + restartPolicySwarm: data.restartPolicySwarm, + placementSwarm: data.placementSwarm, + updateConfigSwarm: data.updateConfigSwarm, + rollbackConfigSwarm: data.rollbackConfigSwarm, + modeSwarm: data.modeSwarm, + labelsSwarm: data.labelsSwarm, + networkSwarm: data.networkSwarm, + }) + .then(async () => { + toast.success("Swarm settings updated"); + refetch(); + }) + .catch(() => { + toast.error("Error to update the swarm settings"); + }); + }; + return ( + + + + + + + Swarm Settings + + Update certain settings using a json object. + + + {isError && {error?.message}} + +
+ + ( + + Health Check + + + + + Check the interface + + + + + +
+														{`{
+	Test?: string[] | undefined;
+	Interval?: number | undefined;
+	Timeout?: number | undefined;
+	StartPeriod?: number | undefined;
+	Retries?: number | undefined;
+}`}
+													
+
+
+
+
+ + +