From 4b1f359cb6c7533eaf7c1a07b84a0060b7fdd949 Mon Sep 17 00:00:00 2001 From: Oliver Geneser Date: Sat, 13 Sep 2025 10:11:43 +0200 Subject: [PATCH 001/273] feat: add libsql database --- README.md | 12 +- .../cluster/modify-swarm-settings.tsx | 38 +- .../cluster/show-cluster-settings.tsx | 38 +- .../application/advanced/show-resources.tsx | 38 +- .../advanced/volumes/add-volumes.tsx | 10 +- .../advanced/volumes/show-volumes.tsx | 16 +- .../advanced/volumes/update-volume.tsx | 12 +- .../dashboard/compose/delete-service.tsx | 3 + .../show-external-libsql-credentials.tsx | 258 +++++++++++ .../libsql/general/show-general-libsql.tsx | 268 +++++++++++ .../show-internal-libsql-credentials.tsx | 92 ++++ .../dashboard/libsql/update-libsql.tsx | 163 +++++++ .../postgres/advanced/show-custom-command.tsx | 3 + .../dashboard/project/add-database.tsx | 189 ++++++-- .../dashboard/project/duplicate-project.tsx | 11 +- .../components/dashboard/projects/show.tsx | 17 +- .../dashboard/shared/rebuild-database.tsx | 12 +- .../show-database-advanced-settings.tsx | 2 +- .../components/icons/data-tools-icons.tsx | 55 +++ apps/dokploy/hooks/use-keyboard-nav.tsx | 14 +- .../environment/[environmentId].tsx | 288 +++++++----- .../services/libsql/[libsqlId].tsx | 361 +++++++++++++++ apps/dokploy/server/api/root.ts | 60 +-- .../dokploy/server/api/routers/environment.ts | 9 +- apps/dokploy/server/api/routers/libsql.ts | 437 ++++++++++++++++++ apps/dokploy/server/api/routers/mariadb.ts | 2 +- apps/dokploy/server/api/routers/mongo.ts | 2 +- apps/dokploy/server/api/routers/project.ts | 154 +++--- packages/server/src/db/schema/application.ts | 1 - packages/server/src/db/schema/compose.ts | 1 - packages/server/src/db/schema/environment.ts | 8 +- packages/server/src/db/schema/index.ts | 1 + packages/server/src/db/schema/libsql.ts | 222 +++++++++ packages/server/src/db/schema/mount.ts | 42 +- packages/server/src/db/schema/redis.ts | 1 - packages/server/src/db/schema/server.ts | 18 +- packages/server/src/db/schema/shared.ts | 2 + .../server/src/db/schema/volume-backups.ts | 8 + packages/server/src/index.ts | 2 +- packages/server/src/services/environment.ts | 10 +- packages/server/src/services/libsql.ts | 160 +++++++ packages/server/src/services/mount.ts | 79 ++-- packages/server/src/services/project.ts | 8 +- packages/server/src/services/server.ts | 8 +- .../server/src/services/volume-backups.ts | 9 +- packages/server/src/utils/backups/mariadb.ts | 2 +- packages/server/src/utils/backups/mongo.ts | 2 +- packages/server/src/utils/backups/mysql.ts | 2 +- packages/server/src/utils/backups/postgres.ts | 2 +- packages/server/src/utils/databases/libsql.ts | 138 ++++++ .../server/src/utils/databases/rebuild.ts | 50 +- packages/server/src/utils/docker/utils.ts | 2 + .../server/src/utils/volume-backups/utils.ts | 6 +- 53 files changed, 2902 insertions(+), 446 deletions(-) create mode 100644 apps/dokploy/components/dashboard/libsql/general/show-external-libsql-credentials.tsx create mode 100644 apps/dokploy/components/dashboard/libsql/general/show-general-libsql.tsx create mode 100644 apps/dokploy/components/dashboard/libsql/general/show-internal-libsql-credentials.tsx create mode 100644 apps/dokploy/components/dashboard/libsql/update-libsql.tsx create mode 100644 apps/dokploy/pages/dashboard/project/[projectId]/environment/[environmentId]/services/libsql/[libsqlId].tsx create mode 100644 apps/dokploy/server/api/routers/libsql.ts create mode 100644 packages/server/src/db/schema/libsql.ts create mode 100644 packages/server/src/services/libsql.ts create mode 100644 packages/server/src/utils/databases/libsql.ts diff --git a/README.md b/README.md index 8faf22a356..3c9bfd68b7 100644 --- a/README.md +++ b/README.md @@ -11,8 +11,6 @@
- -
Special thanks to:
@@ -22,20 +20,19 @@ ### [Tuple, the premier screen sharing app for developers](https://tuple.app/dokploy) + [Available for MacOS & Windows](https://tuple.app/dokploy)
- Dokploy is a free, self-hostable Platform as a Service (PaaS) that simplifies the deployment and management of applications and databases. - ## ✨ Features Dokploy includes multiple features to make your life easier. - **Applications**: Deploy any type of application (Node.js, PHP, Python, Go, Ruby, etc.). -- **Databases**: Create and manage databases with support for MySQL, PostgreSQL, MongoDB, MariaDB, and Redis. +- **Databases**: Create and manage databases with support for MySQL, PostgreSQL, MongoDB, MariaDB, libsql, and Redis. - **Backups**: Automate backups for databases to an external storage destination. - **Docker Compose**: Native support for Docker Compose to manage complex applications. - **Multi Node**: Scale applications to multiple nodes using Docker Swarm to manage the cluster. @@ -105,9 +102,10 @@ For detailed documentation, visit [docs.dokploy.com](https://docs.dokploy.com).
- Cloudblast.io +Cloudblast.io + +Synexa - Synexa
### Community Backers 🤝 diff --git a/apps/dokploy/components/dashboard/application/advanced/cluster/modify-swarm-settings.tsx b/apps/dokploy/components/dashboard/application/advanced/cluster/modify-swarm-settings.tsx index 9e10f43ec4..c8a4616ca9 100644 --- a/apps/dokploy/components/dashboard/application/advanced/cluster/modify-swarm-settings.tsx +++ b/apps/dokploy/components/dashboard/application/advanced/cluster/modify-swarm-settings.tsx @@ -182,32 +182,41 @@ type AddSwarmSettings = z.infer; interface Props { id: string; - type: "postgres" | "mariadb" | "mongo" | "mysql" | "redis" | "application"; + type: + | "application" + | "libsql" + | "mariadb" + | "mongo" + | "mysql" + | "postgres" + | "redis"; } export const AddSwarmSettings = ({ 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 }), + libsql: () => api.libsql.one.useQuery({ libsqlId: id }, { enabled: !!id }), + mariadb: () => + api.mariadb.one.useQuery({ mariadbId: id }, { enabled: !!id }), mongo: () => api.mongo.one.useQuery({ mongoId: id }, { enabled: !!id }), + mysql: () => api.mysql.one.useQuery({ mysqlId: id }, { enabled: !!id }), + postgres: () => + api.postgres.one.useQuery({ postgresId: id }, { enabled: !!id }), + redis: () => api.redis.one.useQuery({ redisId: 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(), + libsql: () => api.libsql.update.useMutation(), + mariadb: () => api.mariadb.update.useMutation(), mongo: () => api.mongo.update.useMutation(), + mysql: () => api.mysql.update.useMutation(), + postgres: () => api.postgres.update.useMutation(), + redis: () => api.redis.update.useMutation(), }; const { mutateAsync, isError, error, isLoading } = mutationMap[type] @@ -262,11 +271,12 @@ export const AddSwarmSettings = ({ id, type }: Props) => { const onSubmit = async (data: AddSwarmSettings) => { await mutateAsync({ applicationId: id || "", - postgresId: id || "", - redisId: id || "", - mysqlId: id || "", + libsqlId: id || "", mariadbId: id || "", mongoId: id || "", + mysqlId: id || "", + postgresId: id || "", + redisId: id || "", healthCheckSwarm: data.healthCheckSwarm, restartPolicySwarm: data.restartPolicySwarm, placementSwarm: data.placementSwarm, diff --git a/apps/dokploy/components/dashboard/application/advanced/cluster/show-cluster-settings.tsx b/apps/dokploy/components/dashboard/application/advanced/cluster/show-cluster-settings.tsx index a3bc8079a2..962666fa9a 100644 --- a/apps/dokploy/components/dashboard/application/advanced/cluster/show-cluster-settings.tsx +++ b/apps/dokploy/components/dashboard/application/advanced/cluster/show-cluster-settings.tsx @@ -37,7 +37,14 @@ import { AddSwarmSettings } from "./modify-swarm-settings"; interface Props { id: string; - type: "postgres" | "mariadb" | "mongo" | "mysql" | "redis" | "application"; + type: + | "application" + | "libsql" + | "mariadb" + | "mongo" + | "mysql" + | "postgres" + | "redis"; } const AddRedirectchema = z.object({ @@ -49,15 +56,16 @@ type AddCommand = z.infer; export const ShowClusterSettings = ({ 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 }), + libsql: () => api.libsql.one.useQuery({ libsqlId: id }, { enabled: !!id }), + mariadb: () => + api.mariadb.one.useQuery({ mariadbId: id }, { enabled: !!id }), mongo: () => api.mongo.one.useQuery({ mongoId: id }, { enabled: !!id }), + mysql: () => api.mysql.one.useQuery({ mysqlId: id }, { enabled: !!id }), + postgres: () => + api.postgres.one.useQuery({ postgresId: id }, { enabled: !!id }), + redis: () => api.redis.one.useQuery({ redisId: id }, { enabled: !!id }), }; const { data, refetch } = queryMap[type] ? queryMap[type]() @@ -65,12 +73,13 @@ export const ShowClusterSettings = ({ id, type }: Props) => { const { data: registries } = api.registry.all.useQuery(); 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(), + libsql: () => api.libsql.update.useMutation(), + mariadb: () => api.mariadb.update.useMutation(), mongo: () => api.mongo.update.useMutation(), + mysql: () => api.mysql.update.useMutation(), + postgres: () => api.postgres.update.useMutation(), + redis: () => api.redis.update.useMutation(), }; const { mutateAsync, isLoading } = mutationMap[type] @@ -105,11 +114,12 @@ export const ShowClusterSettings = ({ id, type }: Props) => { const onSubmit = async (data: AddCommand) => { await mutateAsync({ applicationId: id || "", - postgresId: id || "", - redisId: id || "", - mysqlId: id || "", + libsqlId: id || "", mariadbId: id || "", mongoId: id || "", + mysqlId: id || "", + postgresId: id || "", + redisId: id || "", ...(type === "application" ? { registryId: diff --git a/apps/dokploy/components/dashboard/application/advanced/show-resources.tsx b/apps/dokploy/components/dashboard/application/advanced/show-resources.tsx index 25040067b4..7f69760c51 100644 --- a/apps/dokploy/components/dashboard/application/advanced/show-resources.tsx +++ b/apps/dokploy/components/dashboard/application/advanced/show-resources.tsx @@ -38,12 +38,13 @@ const addResourcesSchema = z.object({ }); export type ServiceType = - | "postgres" + | "application" + | "libsql" + | "mariadb" | "mongo" - | "redis" | "mysql" - | "mariadb" - | "application"; + | "postgres" + | "redis"; interface Props { id: string; @@ -53,27 +54,29 @@ interface Props { 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 }), + libsql: () => api.libsql.one.useQuery({ libsqlId: id }, { enabled: !!id }), + mariadb: () => + api.mariadb.one.useQuery({ mariadbId: id }, { enabled: !!id }), mongo: () => api.mongo.one.useQuery({ mongoId: id }, { enabled: !!id }), + mysql: () => api.mysql.one.useQuery({ mysqlId: id }, { enabled: !!id }), + postgres: () => + api.postgres.one.useQuery({ postgresId: id }, { enabled: !!id }), + redis: () => api.redis.one.useQuery({ redisId: 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(), + libsql: () => api.libsql.update.useMutation(), + mariadb: () => api.mariadb.update.useMutation(), mongo: () => api.mongo.update.useMutation(), + mysql: () => api.mysql.update.useMutation(), + postgres: () => api.postgres.update.useMutation(), + redis: () => api.redis.update.useMutation(), }; const { mutateAsync, isLoading } = mutationMap[type] @@ -103,12 +106,13 @@ export const ShowResources = ({ id, type }: Props) => { const onSubmit = async (formData: AddResources) => { await mutateAsync({ + applicationId: id || "", + libsqlId: id || "", + mariadbId: id || "", mongoId: id || "", + mysqlId: id || "", postgresId: id || "", redisId: id || "", - mysqlId: id || "", - mariadbId: id || "", - applicationId: id || "", cpuLimit: formData.cpuLimit || null, cpuReservation: formData.cpuReservation || null, memoryLimit: formData.memoryLimit || null, diff --git a/apps/dokploy/components/dashboard/application/advanced/volumes/add-volumes.tsx b/apps/dokploy/components/dashboard/application/advanced/volumes/add-volumes.tsx index 00be8a1e1b..a94db51534 100644 --- a/apps/dokploy/components/dashboard/application/advanced/volumes/add-volumes.tsx +++ b/apps/dokploy/components/dashboard/application/advanced/volumes/add-volumes.tsx @@ -34,13 +34,13 @@ interface Props { serviceId: string; serviceType: | "application" - | "postgres" - | "redis" + | "compose" + | "libsql" + | "mariadb" | "mongo" - | "redis" | "mysql" - | "mariadb" - | "compose"; + | "postgres" + | "redis"; refetch: () => void; children?: React.ReactNode; } 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 d3803c42ab..38737bf163 100644 --- a/apps/dokploy/components/dashboard/application/advanced/volumes/show-volumes.tsx +++ b/apps/dokploy/components/dashboard/application/advanced/volumes/show-volumes.tsx @@ -22,23 +22,25 @@ interface Props { 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 }), + libsql: () => api.libsql.one.useQuery({ libsqlId: id }, { enabled: !!id }), + mariadb: () => + api.mariadb.one.useQuery({ mariadbId: id }, { enabled: !!id }), + mongo: () => api.mongo.one.useQuery({ mongoId: id }, { enabled: !!id }), + mysql: () => api.mysql.one.useQuery({ mysqlId: id }, { enabled: !!id }), + postgres: () => + api.postgres.one.useQuery({ postgresId: id }, { enabled: !!id }), + redis: () => api.redis.one.useQuery({ redisId: 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 ( 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 38d02ec90a..a75292b070 100644 --- a/apps/dokploy/components/dashboard/application/advanced/volumes/update-volume.tsx +++ b/apps/dokploy/components/dashboard/application/advanced/volumes/update-volume.tsx @@ -61,13 +61,13 @@ interface Props { refetch: () => void; serviceType: | "application" - | "postgres" - | "redis" + | "compose" + | "libsql" + | "mariadb" | "mongo" - | "redis" | "mysql" - | "mariadb" - | "compose"; + | "postgres" + | "redis"; } export const UpdateVolume = ({ @@ -247,7 +247,7 @@ export const UpdateVolume = ({ control={form.control} name="content" render={({ field }) => ( - + Content diff --git a/apps/dokploy/components/dashboard/compose/delete-service.tsx b/apps/dokploy/components/dashboard/compose/delete-service.tsx index e75aad5e5e..c1d03dedeb 100644 --- a/apps/dokploy/components/dashboard/compose/delete-service.tsx +++ b/apps/dokploy/components/dashboard/compose/delete-service.tsx @@ -55,6 +55,7 @@ export const DeleteService = ({ id, type }: Props) => { mysql: () => api.mysql.one.useQuery({ mysqlId: id }, { enabled: !!id }), mariadb: () => api.mariadb.one.useQuery({ mariadbId: id }, { enabled: !!id }), + libsql: () => api.libsql.one.useQuery({ libsqlId: id }, { enabled: !!id }), application: () => api.application.one.useQuery({ applicationId: id }, { enabled: !!id }), mongo: () => api.mongo.one.useQuery({ mongoId: id }, { enabled: !!id }), @@ -70,6 +71,7 @@ export const DeleteService = ({ id, type }: Props) => { redis: () => api.redis.remove.useMutation(), mysql: () => api.mysql.remove.useMutation(), mariadb: () => api.mariadb.remove.useMutation(), + libsql: () => api.libsql.remove.useMutation(), application: () => api.application.delete.useMutation(), mongo: () => api.mongo.remove.useMutation(), compose: () => api.compose.delete.useMutation(), @@ -96,6 +98,7 @@ export const DeleteService = ({ id, type }: Props) => { redisId: id || "", mysqlId: id || "", mariadbId: id || "", + libsqlId: id || "", applicationId: id || "", composeId: id || "", deleteVolumes, diff --git a/apps/dokploy/components/dashboard/libsql/general/show-external-libsql-credentials.tsx b/apps/dokploy/components/dashboard/libsql/general/show-external-libsql-credentials.tsx new file mode 100644 index 0000000000..562f8271e7 --- /dev/null +++ b/apps/dokploy/components/dashboard/libsql/general/show-external-libsql-credentials.tsx @@ -0,0 +1,258 @@ +import { zodResolver } from "@hookform/resolvers/zod"; +import Link from "next/link"; +import { useEffect, useState } from "react"; +import { useForm } from "react-hook-form"; +import { toast } from "sonner"; +import { z } from "zod"; +import { AlertBlock } from "@/components/shared/alert-block"; +import { ToggleVisibilityInput } from "@/components/shared/toggle-visibility-input"; +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 { Label } from "@/components/ui/label"; +import { api } from "@/utils/api"; + +const createDockerProviderSchema = (sqldNode?: string) => + z + .object({ + externalPort: z.preprocess((a) => { + if (a !== null) { + const parsed = Number.parseInt(z.string().parse(a), 10); + return Number.isNaN(parsed) ? null : parsed; + } + return null; + }, z + .number() + .gte(0, "Range must be 0 - 65535") + .lte(65535, "Range must be 0 - 65535") + .nullable()), + externalGRPCPort: z.preprocess((a) => { + if (a !== null) { + const parsed = Number.parseInt(z.string().parse(a), 10); + return Number.isNaN(parsed) ? null : parsed; + } + return null; + }, z + .number() + .gte(0, "Range must be 0 - 65535") + .lte(65535, "Range must be 0 - 65535") + .nullable()), + }) + .superRefine((data, ctx) => { + if (data.externalPort === null && data.externalGRPCPort === null) { + ctx.addIssue({ + code: z.ZodIssueCode.custom, + message: "Either externalPort or externalGRPCPort must be provided.", + path: ["externalPort", "externalGRPCPort"], + }); + } + if (sqldNode === "replica" && data.externalGRPCPort !== null) { + ctx.addIssue({ + code: z.ZodIssueCode.custom, + message: "externalGRPCPort cannot be set when sqldNode is 'replica'", + path: ["externalGRPCPort"], + }); + } + }); + +interface Props { + libsqlId: string; +} +export const ShowExternalLibsqlCredentials = ({ libsqlId }: Props) => { + const { data: ip } = api.settings.getIp.useQuery(); + const { data, refetch } = api.libsql.one.useQuery({ libsqlId }); + const { mutateAsync, isLoading } = api.libsql.saveExternalPorts.useMutation(); + const [connectionUrl, setConnectionUrl] = useState(""); + const [connectionGRPCUrl, setGRPCConnectionUrl] = useState(""); + const getIp = data?.server?.ipAddress || ip; + + const DockerProviderSchema = createDockerProviderSchema(data?.sqldNode); + type DockerProvider = z.infer; + + const form = useForm({ + defaultValues: {}, + resolver: zodResolver(DockerProviderSchema), + }); + + useEffect(() => { + const fieldsToUpdate: Partial = {}; + + if (data?.externalGRPCPort !== undefined) { + fieldsToUpdate.externalGRPCPort = data.externalGRPCPort; + } + + if (data?.externalPort !== undefined) { + fieldsToUpdate.externalPort = data.externalPort; + } + + if (Object.keys(fieldsToUpdate).length > 0) { + form.reset(fieldsToUpdate); + } + }, [form.reset, data, form]); + + const onSubmit = async (values: DockerProvider) => { + await mutateAsync({ + externalPort: values.externalPort, + externalGRPCPort: values.externalGRPCPort, + libsqlId, + }) + .then(async () => { + toast.success("External port/ports updated"); + await refetch(); + }) + .catch(() => { + toast.error("Error saving the external port/ports"); + }); + }; + + useEffect(() => { + const buildConnectionUrl = () => { + const port = form.watch("externalPort") || data?.externalPort; + + return `https://${data?.databaseUser}:${data?.databasePassword}@${getIp}:${port}`; + }; + + setConnectionUrl(buildConnectionUrl()); + + const buildGRPCConnectionUrl = () => { + if (data?.sqldNode === "replica") return ""; + const port = form.watch("externalGRPCPort") || data?.externalGRPCPort; + + return `https://${data?.databaseUser}:${data?.databasePassword}@${getIp}:${port}`; + }; + + setGRPCConnectionUrl(buildGRPCConnectionUrl()); + }, [ + data?.appName, + data?.externalGRPCPort, + data?.databasePassword, + form, + data?.databaseUser, + getIp, + ]); + + return ( + <> +
+ + + External Credentials + + In order to make the database reachable through the internet, you + must set a port and ensure that the port is not being used by + another application or database + + + + {!getIp && ( + + You need to set an IP address in your{" "} + + {data?.serverId + ? "Remote Servers -> Server -> Edit Server -> Update IP Address" + : "Web Server -> Server -> Update Server IP"} + {" "} + to fix the database url connection. + + )} +
+ +
+
+ { + return ( + + External Port (Internet) + + + + + + ); + }} + /> +
+ {!!data?.externalPort && ( +
+ + +
+ )} + {data?.sqldNode !== "replica" && ( + <> +
+ { + return ( + + + External GRPC Port (Internet) + + + + + + + ); + }} + /> +
+ {!!data?.externalGRPCPort && ( +
+ + +
+ )} + + )} +
+ +
+ +
+
+ +
+
+
+ + ); +}; diff --git a/apps/dokploy/components/dashboard/libsql/general/show-general-libsql.tsx b/apps/dokploy/components/dashboard/libsql/general/show-general-libsql.tsx new file mode 100644 index 0000000000..f905d0d77f --- /dev/null +++ b/apps/dokploy/components/dashboard/libsql/general/show-general-libsql.tsx @@ -0,0 +1,268 @@ +import * as TooltipPrimitive from "@radix-ui/react-tooltip"; +import { Ban, CheckCircle2, RefreshCcw, Rocket, Terminal } from "lucide-react"; +import { useState } from "react"; +import { toast } from "sonner"; +import { DialogAction } from "@/components/shared/dialog-action"; +import { DrawerLogs } from "@/components/shared/drawer-logs"; +import { Button } from "@/components/ui/button"; +import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; +import { + Tooltip, + TooltipContent, + TooltipProvider, + TooltipTrigger, +} from "@/components/ui/tooltip"; +import { api } from "@/utils/api"; +import { type LogLine, parseLogs } from "../../docker/logs/utils"; +import { DockerTerminalModal } from "../../settings/web-server/docker-terminal-modal"; + +interface Props { + libsqlId: string; +} + +export const ShowGeneralLibsql = ({ libsqlId }: Props) => { + const { data, refetch } = api.libsql.one.useQuery( + { + libsqlId, + }, + { enabled: !!libsqlId }, + ); + + const { mutateAsync: reload, isLoading: isReloading } = + api.libsql.reload.useMutation(); + + const { mutateAsync: start, isLoading: isStarting } = + api.libsql.start.useMutation(); + + const { mutateAsync: stop, isLoading: isStopping } = + api.libsql.stop.useMutation(); + + const [isDrawerOpen, setIsDrawerOpen] = useState(false); + const [filteredLogs, setFilteredLogs] = useState([]); + const [isDeploying, setIsDeploying] = useState(false); + api.libsql.deployWithLogs.useSubscription( + { + libsqlId: libsqlId, + }, + { + 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 + + + + { + setIsDeploying(true); + await new Promise((resolve) => setTimeout(resolve, 1000)); + refetch(); + }} + > + + + + + { + await reload({ + libsqlId: libsqlId, + appName: data?.appName || "", + }) + .then(() => { + toast.success("Libsql reloaded successfully"); + refetch(); + }) + .catch(() => { + toast.error("Error reloading Libsql"); + }); + }} + > + + + + {data?.applicationStatus === "idle" ? ( + + { + await start({ + libsqlId: libsqlId, + }) + .then(() => { + toast.success("Libsql started successfully"); + refetch(); + }) + .catch(() => { + toast.error("Error starting Libsql"); + }); + }} + > + + + + ) : ( + + { + await stop({ + libsqlId: libsqlId, + }) + .then(() => { + toast.success("Libsql stopped successfully"); + refetch(); + }) + .catch(() => { + toast.error("Error stopping Libsql"); + }); + }} + > + + + + )} + + + + + + { + setIsDrawerOpen(false); + setFilteredLogs([]); + setIsDeploying(false); + refetch(); + }} + filteredLogs={filteredLogs} + /> +
+ + ); +}; diff --git a/apps/dokploy/components/dashboard/libsql/general/show-internal-libsql-credentials.tsx b/apps/dokploy/components/dashboard/libsql/general/show-internal-libsql-credentials.tsx new file mode 100644 index 0000000000..9a56125287 --- /dev/null +++ b/apps/dokploy/components/dashboard/libsql/general/show-internal-libsql-credentials.tsx @@ -0,0 +1,92 @@ +import { ToggleVisibilityInput } from "@/components/shared/toggle-visibility-input"; +import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; +import { Input } from "@/components/ui/input"; +import { Label } from "@/components/ui/label"; +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "@/components/ui/select"; +import { api } from "@/utils/api"; + +interface Props { + libsqlId: string; +} +export const ShowInternalLibsqlCredentials = ({ libsqlId }: Props) => { + const { data } = api.libsql.one.useQuery({ libsqlId }); + return ( + <> +
+ + + Internal Credentials + + +
+
+ + +
+
+ + +
+
+ +
+ +
+
+ +
+
+ + +
+
+ + +
+
+
+ + +
+ +
+ + +
+
+ + +
+
+
+
+
+ + ); +}; diff --git a/apps/dokploy/components/dashboard/libsql/update-libsql.tsx b/apps/dokploy/components/dashboard/libsql/update-libsql.tsx new file mode 100644 index 0000000000..2e09049570 --- /dev/null +++ b/apps/dokploy/components/dashboard/libsql/update-libsql.tsx @@ -0,0 +1,163 @@ +import { zodResolver } from "@hookform/resolvers/zod"; +import { PenBoxIcon } from "lucide-react"; +import { useEffect } from "react"; +import { useForm } from "react-hook-form"; +import { toast } from "sonner"; +import { z } from "zod"; +import { AlertBlock } from "@/components/shared/alert-block"; +import { Button } from "@/components/ui/button"; +import { + 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 { Textarea } from "@/components/ui/textarea"; +import { api } from "@/utils/api"; + +const updateLibsqlSchema = z.object({ + name: z.string().min(1, { + message: "Name is required", + }), + description: z.string().optional(), +}); + +type UpdateLibsql = z.infer; + +interface Props { + libsqlId: string; +} + +export const UpdateLibsql = ({ libsqlId }: Props) => { + const utils = api.useUtils(); + const { mutateAsync, error, isError, isLoading } = + api.libsql.update.useMutation(); + const { data } = api.libsql.one.useQuery( + { + libsqlId, + }, + { + enabled: !!libsqlId, + }, + ); + const form = useForm({ + defaultValues: { + description: data?.description ?? "", + name: data?.name ?? "", + }, + resolver: zodResolver(updateLibsqlSchema), + }); + useEffect(() => { + if (data) { + form.reset({ + description: data.description ?? "", + name: data.name, + }); + } + }, [data, form, form.reset]); + + const onSubmit = async (formData: UpdateLibsql) => { + await mutateAsync({ + name: formData.name, + libsqlId: libsqlId, + description: formData.description || "", + }) + .then(() => { + toast.success("Libsql updated successfully"); + utils.libsql.one.invalidate({ + libsqlId: libsqlId, + }); + }) + .catch(() => { + toast.error("Error updating the Libsql"); + }) + .finally(() => {}); + }; + + return ( + + + + + + + Modify Libsql + Update the Libsql data + + {isError && {error?.message}} + +
+
+
+ + ( + + Name + + + + + + + )} + /> + ( + + Description + +