From 69598821ed33da85e0fab18c930dc6c638fa88cc Mon Sep 17 00:00:00 2001 From: Mauricio Siu Date: Sat, 21 Feb 2026 14:53:27 -0600 Subject: [PATCH 001/585] feat: implement Docker network management functionality - Added components for handling and displaying Docker networks, including creation, editing, and listing of networks. - Introduced a new API router for network operations, integrating with the database schema for network management. - Updated the sidebar layout to include a link to the networks dashboard, ensuring user access to network features. - Created necessary database migrations for the network table and its associated types. - Enhanced the dashboard layout to support the new network management interface. --- .../dashboard/networks/handle-network.tsx | 566 ++ .../dashboard/networks/show-networks.tsx | 116 + apps/dokploy/components/layouts/side.tsx | 15 + .../drizzle/0146_stormy_ender_wiggin.sql | 20 + apps/dokploy/drizzle/meta/0146_snapshot.json | 7604 +++++++++++++++++ apps/dokploy/drizzle/meta/_journal.json | 7 + apps/dokploy/pages/dashboard/networks.tsx | 84 + apps/dokploy/server/api/root.ts | 2 + apps/dokploy/server/api/routers/network.ts | 71 + packages/server/src/db/schema/account.ts | 2 + packages/server/src/db/schema/index.ts | 1 + packages/server/src/db/schema/network.ts | 137 + packages/server/src/db/schema/server.ts | 2 + packages/server/src/index.ts | 1 + packages/server/src/services/network.ts | 114 + 15 files changed, 8742 insertions(+) create mode 100644 apps/dokploy/components/dashboard/networks/handle-network.tsx create mode 100644 apps/dokploy/components/dashboard/networks/show-networks.tsx create mode 100644 apps/dokploy/drizzle/0146_stormy_ender_wiggin.sql create mode 100644 apps/dokploy/drizzle/meta/0146_snapshot.json create mode 100644 apps/dokploy/pages/dashboard/networks.tsx create mode 100644 apps/dokploy/server/api/routers/network.ts create mode 100644 packages/server/src/db/schema/network.ts create mode 100644 packages/server/src/services/network.ts diff --git a/apps/dokploy/components/dashboard/networks/handle-network.tsx b/apps/dokploy/components/dashboard/networks/handle-network.tsx new file mode 100644 index 000000000..8af7db795 --- /dev/null +++ b/apps/dokploy/components/dashboard/networks/handle-network.tsx @@ -0,0 +1,566 @@ +"use client"; + +import { zodResolver } from "@hookform/resolvers/zod"; +import { Network, Pencil, Plus } from "lucide-react"; +import { useEffect, useState } from "react"; +import { useFieldArray, useForm } from "react-hook-form"; +import { toast } from "sonner"; +import { z } from "zod"; +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 { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "@/components/ui/select"; +import { Switch } from "@/components/ui/switch"; +import { api } from "@/utils/api"; + +const networkDriverEnum = [ + "bridge", + "host", + "overlay", + "macvlan", + "none", + "ipvlan", +] as const; + +/** Sentinel for "no scope" */ +const SCOPE_EMPTY = "__scope_none__"; + +/** Value for "Dokploy server" (local / no specific server). Not used in cloud. */ +const DOKPLOY_SERVER_VALUE = "__dokploy_server__"; + +const ipamConfigEntrySchema = z.object({ + subnet: z.string().optional(), + ipRange: z.string().optional(), + gateway: z.string().optional(), +}); + +const networkFormSchema = z.object({ + name: z.string().min(1, "Name is required"), + driver: z.enum(networkDriverEnum).default("bridge"), + scope: z.string().optional(), + serverId: z.string().optional(), + internal: z.boolean().default(false), + attachable: z.boolean().default(false), + ingress: z.boolean().default(false), + configOnly: z.boolean().default(false), + enableIPv4: z.boolean().default(true), + enableIPv6: z.boolean().default(false), + ipamDriver: z.string().optional(), + ipamConfig: z.array(ipamConfigEntrySchema).default([]), +}); + +type NetworkFormValues = z.infer; + +const defaultValues: NetworkFormValues = { + name: "", + driver: "bridge", + scope: SCOPE_EMPTY, + serverId: DOKPLOY_SERVER_VALUE, + internal: false, + attachable: false, + ingress: false, + configOnly: false, + enableIPv4: true, + enableIPv6: false, + ipamDriver: "", + ipamConfig: [], +}; + +interface HandleNetworkProps { + networkId?: string; + children?: React.ReactNode; +} + +export const HandleNetwork = ({ networkId, children }: HandleNetworkProps) => { + const [isOpen, setIsOpen] = useState(false); + const { data: isCloud } = api.settings.isCloud.useQuery(); + const utils = api.useUtils(); + const isEdit = !!networkId; + + const { data: servers } = api.server.all.useQuery(); + const { data: network, isLoading: isLoadingNetwork } = + api.network.one.useQuery( + { networkId: networkId! }, + { enabled: isEdit && !!networkId }, + ); + + const { mutateAsync, isLoading: isPending } = networkId + ? api.network.update.useMutation() + : api.network.create.useMutation(); + + const form = useForm({ + resolver: zodResolver(networkFormSchema), + defaultValues, + }); + + const ipamConfigFieldArray = useFieldArray({ + control: form.control, + name: "ipamConfig", + }); + + useEffect(() => { + if (isEdit && network && isOpen) { + const ipam = network.ipam ?? {}; + const ipamConfigArr = (ipam.config ?? []).map((c) => ({ + subnet: c.subnet ?? "", + ipRange: c.ipRange ?? "", + gateway: c.gateway ?? "", + })); + form.reset({ + ...defaultValues, + name: network.name, + driver: network.driver, + scope: network.scope ?? SCOPE_EMPTY, + serverId: network.serverId ?? DOKPLOY_SERVER_VALUE, + internal: network.internal, + attachable: network.attachable, + enableIPv4: network.enableIPv4, + enableIPv6: network.enableIPv6, + ipamDriver: ipam.driver ?? "", + ipamConfig: ipamConfigArr, + ingress: network.ingress, + configOnly: network.configOnly, + }); + } + }, [isEdit, isOpen, network, form]); + + const onSubmit = async (data: NetworkFormValues) => { + const scope = + data.scope && data.scope !== SCOPE_EMPTY ? data.scope : undefined; + + try { + await mutateAsync({ + networkId: networkId ?? "", + name: data.name, + driver: data.driver, + scope, + serverId: data.serverId ?? undefined, + internal: data.internal, + attachable: data.attachable, + ingress: data.ingress, + configOnly: data.configOnly, + enableIPv4: data.enableIPv4, + enableIPv6: data.enableIPv6, + ipam: { + driver: data.ipamDriver, + config: data.ipamConfig, + }, + }); + + await utils.network.all.invalidate(); + if (networkId) await utils.network.one.invalidate({ networkId }); + setIsOpen(false); + form.reset(defaultValues); + } catch { + toast.error(isEdit ? "Error updating network" : "Error creating network"); + } + }; + + const trigger = + children ?? + (isEdit ? ( + + ) : ( + + )); + + return ( + + {trigger} + + + + + {isEdit ? "Edit network" : "Add network"} + + + {isEdit + ? "Update this Docker network. Changes apply to name, driver, and server assignment." + : "Create a new Docker network for your organization. You can optionally assign it to a server."} + + + {isEdit && isLoadingNetwork ? ( +
+ Loading network… +
+ ) : ( +
+ +
+ ( + + Name + + + + + + )} + /> + ( + + Driver + + + + )} + /> +
+ ( + + Server + + + {isCloud + ? "Server where this network will be created." + : "Dokploy server is the default local server; or choose a specific server."} + + + + )} + /> + ( + + Scope (optional) + + + + )} + /> +
+ ( + +
+ Internal + + Restrict external access; containers on this network + cannot reach external networks. + +
+ + + +
+ )} + /> + ( + +
+ Attachable + + Allow standalone containers to attach to this network + (e.g. in Swarm, not only services). + +
+ + + +
+ )} + /> + ( + +
+ Enable IPv4 + + Enable IPv4 addressing on the network. + +
+ + + +
+ )} + /> + ( + +
+ Enable IPv6 + + Enable IPv6 addressing on the network. + +
+ + + +
+ )} + /> + ( + +
+ Ingress + + Use as the routing-mesh network in Swarm mode (load + balancing between nodes). + +
+ + + +
+ )} + /> + ( + +
+ Config only + + Create a placeholder network whose config is reused by + other networks; cannot run containers on it. + +
+ + + +
+ )} + /> +
+
+ IPAM + ( + + + Driver (optional) + + + + + + + )} + /> +
+ + Config (subnet / gateway / IP range) + + {ipamConfigFieldArray.fields.map((field, index) => ( +
+ ( + + + + + + + )} + /> + ( + + + + + + + )} + /> + ( + + + + + + + )} + /> + +
+ ))} + +
+
+ + + + + + + )} +
+
+ ); +}; diff --git a/apps/dokploy/components/dashboard/networks/show-networks.tsx b/apps/dokploy/components/dashboard/networks/show-networks.tsx new file mode 100644 index 000000000..6319ebe39 --- /dev/null +++ b/apps/dokploy/components/dashboard/networks/show-networks.tsx @@ -0,0 +1,116 @@ +"use client"; + +import { Loader2, Network } from "lucide-react"; +import { HandleNetwork } from "@/components/dashboard/networks/handle-network"; +import { Button } from "@/components/ui/button"; +import { + Card, + CardContent, + CardDescription, + CardHeader, + CardTitle, +} from "@/components/ui/card"; +import { + Table, + TableBody, + TableCell, + TableHead, + TableHeader, + TableRow, +} from "@/components/ui/table"; +import { api } from "@/utils/api"; + +export const ShowNetworks = () => { + const { data: networks, isLoading } = api.network.all.useQuery(); + + return ( +
+ +
+
+ + + + Networks + + + Manage Docker networks for your organization. Networks can be + scoped to a server (optional). + + + {networks && networks?.length > 0 && } +
+ + +
+
+
+ {isLoading ? ( +
+ Loading... + +
+ ) : !networks?.length ? ( +
+
+ +
+
+

No networks yet

+

+ Create Docker networks for your organization and + optionally attach them to a server. Add your first + network to get started. +

+
+ +
+ ) : ( + + + + Name + Driver + Scope + Internal + Attachable + Server + Created + Actions + + + + {networks.map((n) => ( + + + {n.name} + + {n.driver} + {n.scope ?? "—"} + {n.internal ? "Yes" : "No"} + {n.attachable ? "Yes" : "No"} + {n.serverId ?? "Dokploy server"} + + {new Date(n.createdAt).toLocaleDateString()} + + + + + + + + ))} + +
+ )} +
+
+
+
+
+
+
+ ); +}; diff --git a/apps/dokploy/components/layouts/side.tsx b/apps/dokploy/components/layouts/side.tsx index 4b3354ed8..3fbf8b313 100644 --- a/apps/dokploy/components/layouts/side.tsx +++ b/apps/dokploy/components/layouts/side.tsx @@ -23,6 +23,7 @@ import { Loader2, LogIn, type LucideIcon, + Network, Package, PieChart, Server, @@ -204,6 +205,20 @@ const MENU: Menu = { !isCloud ), }, + { + isSingle: true, + title: "Networks", + url: "/dashboard/networks", + icon: Network, + // Only enabled for admins and users with access to Docker in non-cloud environments + isEnabled: ({ auth, isCloud }) => + !!( + (auth?.role === "owner" || + auth?.role === "admin" || + auth?.canAccessToDocker) && + !isCloud + ), + }, { isSingle: true, title: "Requests", diff --git a/apps/dokploy/drizzle/0146_stormy_ender_wiggin.sql b/apps/dokploy/drizzle/0146_stormy_ender_wiggin.sql new file mode 100644 index 000000000..405e9323e --- /dev/null +++ b/apps/dokploy/drizzle/0146_stormy_ender_wiggin.sql @@ -0,0 +1,20 @@ +CREATE TYPE "public"."networkDriver" AS ENUM('bridge', 'host', 'overlay', 'macvlan', 'none', 'ipvlan');--> statement-breakpoint +CREATE TABLE "network" ( + "networkId" text PRIMARY KEY NOT NULL, + "name" text NOT NULL, + "driver" "networkDriver" DEFAULT 'bridge' NOT NULL, + "scope" text, + "internal" boolean DEFAULT false NOT NULL, + "attachable" boolean DEFAULT false NOT NULL, + "ingress" boolean DEFAULT false NOT NULL, + "configOnly" boolean DEFAULT false NOT NULL, + "enableIPv4" boolean DEFAULT true NOT NULL, + "enableIPv6" boolean DEFAULT false NOT NULL, + "ipam" jsonb DEFAULT '{}'::jsonb, + "createdAt" text NOT NULL, + "organizationId" text NOT NULL, + "serverId" text +); +--> statement-breakpoint +ALTER TABLE "network" ADD CONSTRAINT "network_organizationId_organization_id_fk" FOREIGN KEY ("organizationId") REFERENCES "public"."organization"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "network" ADD CONSTRAINT "network_serverId_server_serverId_fk" FOREIGN KEY ("serverId") REFERENCES "public"."server"("serverId") ON DELETE cascade ON UPDATE no action; \ No newline at end of file diff --git a/apps/dokploy/drizzle/meta/0146_snapshot.json b/apps/dokploy/drizzle/meta/0146_snapshot.json new file mode 100644 index 000000000..4489bee53 --- /dev/null +++ b/apps/dokploy/drizzle/meta/0146_snapshot.json @@ -0,0 +1,7604 @@ +{ + "id": "95c21059-b9f0-420d-a452-a2926bc3bd34", + "prevId": "ec86a5ad-8776-483e-a003-346d32f65089", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.account": { + "name": "account", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "account_id": { + "name": "account_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider_id": { + "name": "provider_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "access_token": { + "name": "access_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "refresh_token": { + "name": "refresh_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "id_token": { + "name": "id_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "access_token_expires_at": { + "name": "access_token_expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "refresh_token_expires_at": { + "name": "refresh_token_expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "scope": { + "name": "scope", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "password": { + "name": "password", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "is2FAEnabled": { + "name": "is2FAEnabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "resetPasswordToken": { + "name": "resetPasswordToken", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "resetPasswordExpiresAt": { + "name": "resetPasswordExpiresAt", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "confirmationToken": { + "name": "confirmationToken", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "confirmationExpiresAt": { + "name": "confirmationExpiresAt", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "account_user_id_user_id_fk": { + "name": "account_user_id_user_id_fk", + "tableFrom": "account", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.apikey": { + "name": "apikey", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "start": { + "name": "start", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "prefix": { + "name": "prefix", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "key": { + "name": "key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "refill_interval": { + "name": "refill_interval", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "refill_amount": { + "name": "refill_amount", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "last_refill_at": { + "name": "last_refill_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "rate_limit_enabled": { + "name": "rate_limit_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "rate_limit_time_window": { + "name": "rate_limit_time_window", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "rate_limit_max": { + "name": "rate_limit_max", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "request_count": { + "name": "request_count", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "remaining": { + "name": "remaining", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "last_request": { + "name": "last_request", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "permissions": { + "name": "permissions", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "apikey_user_id_user_id_fk": { + "name": "apikey_user_id_user_id_fk", + "tableFrom": "apikey", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.invitation": { + "name": "invitation", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "inviter_id": { + "name": "inviter_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "team_id": { + "name": "team_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "invitation_organization_id_organization_id_fk": { + "name": "invitation_organization_id_organization_id_fk", + "tableFrom": "invitation", + "tableTo": "organization", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "invitation_inviter_id_user_id_fk": { + "name": "invitation_inviter_id_user_id_fk", + "tableFrom": "invitation", + "tableTo": "user", + "columnsFrom": [ + "inviter_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.member": { + "name": "member", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "team_id": { + "name": "team_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "is_default": { + "name": "is_default", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "canCreateProjects": { + "name": "canCreateProjects", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "canAccessToSSHKeys": { + "name": "canAccessToSSHKeys", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "canCreateServices": { + "name": "canCreateServices", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "canDeleteProjects": { + "name": "canDeleteProjects", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "canDeleteServices": { + "name": "canDeleteServices", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "canAccessToDocker": { + "name": "canAccessToDocker", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "canAccessToAPI": { + "name": "canAccessToAPI", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "canAccessToGitProviders": { + "name": "canAccessToGitProviders", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "canAccessToTraefikFiles": { + "name": "canAccessToTraefikFiles", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "canDeleteEnvironments": { + "name": "canDeleteEnvironments", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "canCreateEnvironments": { + "name": "canCreateEnvironments", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "accesedProjects": { + "name": "accesedProjects", + "type": "text[]", + "primaryKey": false, + "notNull": true, + "default": "ARRAY[]::text[]" + }, + "accessedEnvironments": { + "name": "accessedEnvironments", + "type": "text[]", + "primaryKey": false, + "notNull": true, + "default": "ARRAY[]::text[]" + }, + "accesedServices": { + "name": "accesedServices", + "type": "text[]", + "primaryKey": false, + "notNull": true, + "default": "ARRAY[]::text[]" + } + }, + "indexes": {}, + "foreignKeys": { + "member_organization_id_organization_id_fk": { + "name": "member_organization_id_organization_id_fk", + "tableFrom": "member", + "tableTo": "organization", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "member_user_id_user_id_fk": { + "name": "member_user_id_user_id_fk", + "tableFrom": "member", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.organization": { + "name": "organization", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "slug": { + "name": "slug", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "logo": { + "name": "logo", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "metadata": { + "name": "metadata", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "owner_id": { + "name": "owner_id", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "organization_owner_id_user_id_fk": { + "name": "organization_owner_id_user_id_fk", + "tableFrom": "organization", + "tableTo": "user", + "columnsFrom": [ + "owner_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "organization_slug_unique": { + "name": "organization_slug_unique", + "nullsNotDistinct": false, + "columns": [ + "slug" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.two_factor": { + "name": "two_factor", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "secret": { + "name": "secret", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "backup_codes": { + "name": "backup_codes", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "two_factor_user_id_user_id_fk": { + "name": "two_factor_user_id_user_id_fk", + "tableFrom": "two_factor", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.verification": { + "name": "verification", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "identifier": { + "name": "identifier", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "value": { + "name": "value", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.ai": { + "name": "ai", + "schema": "", + "columns": { + "aiId": { + "name": "aiId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "apiUrl": { + "name": "apiUrl", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "apiKey": { + "name": "apiKey", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "model": { + "name": "model", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "isEnabled": { + "name": "isEnabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "organizationId": { + "name": "organizationId", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "ai_organizationId_organization_id_fk": { + "name": "ai_organizationId_organization_id_fk", + "tableFrom": "ai", + "tableTo": "organization", + "columnsFrom": [ + "organizationId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.application": { + "name": "application", + "schema": "", + "columns": { + "applicationId": { + "name": "applicationId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "appName": { + "name": "appName", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "env": { + "name": "env", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "previewEnv": { + "name": "previewEnv", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "watchPaths": { + "name": "watchPaths", + "type": "text[]", + "primaryKey": false, + "notNull": false + }, + "previewBuildArgs": { + "name": "previewBuildArgs", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "previewBuildSecrets": { + "name": "previewBuildSecrets", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "previewLabels": { + "name": "previewLabels", + "type": "text[]", + "primaryKey": false, + "notNull": false + }, + "previewWildcard": { + "name": "previewWildcard", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "previewPort": { + "name": "previewPort", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 3000 + }, + "previewHttps": { + "name": "previewHttps", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "previewPath": { + "name": "previewPath", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "'/'" + }, + "certificateType": { + "name": "certificateType", + "type": "certificateType", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'none'" + }, + "previewCustomCertResolver": { + "name": "previewCustomCertResolver", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "previewLimit": { + "name": "previewLimit", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 3 + }, + "isPreviewDeploymentsActive": { + "name": "isPreviewDeploymentsActive", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "previewRequireCollaboratorPermissions": { + "name": "previewRequireCollaboratorPermissions", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": true + }, + "rollbackActive": { + "name": "rollbackActive", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "buildArgs": { + "name": "buildArgs", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "buildSecrets": { + "name": "buildSecrets", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "memoryReservation": { + "name": "memoryReservation", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "memoryLimit": { + "name": "memoryLimit", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "cpuReservation": { + "name": "cpuReservation", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "cpuLimit": { + "name": "cpuLimit", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "subtitle": { + "name": "subtitle", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "command": { + "name": "command", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "args": { + "name": "args", + "type": "text[]", + "primaryKey": false, + "notNull": false + }, + "refreshToken": { + "name": "refreshToken", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "sourceType": { + "name": "sourceType", + "type": "sourceType", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'github'" + }, + "cleanCache": { + "name": "cleanCache", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "repository": { + "name": "repository", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "owner": { + "name": "owner", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "branch": { + "name": "branch", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "buildPath": { + "name": "buildPath", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "'/'" + }, + "triggerType": { + "name": "triggerType", + "type": "triggerType", + "typeSchema": "public", + "primaryKey": false, + "notNull": false, + "default": "'push'" + }, + "autoDeploy": { + "name": "autoDeploy", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "gitlabProjectId": { + "name": "gitlabProjectId", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "gitlabRepository": { + "name": "gitlabRepository", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "gitlabOwner": { + "name": "gitlabOwner", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "gitlabBranch": { + "name": "gitlabBranch", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "gitlabBuildPath": { + "name": "gitlabBuildPath", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "'/'" + }, + "gitlabPathNamespace": { + "name": "gitlabPathNamespace", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "giteaRepository": { + "name": "giteaRepository", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "giteaOwner": { + "name": "giteaOwner", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "giteaBranch": { + "name": "giteaBranch", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "giteaBuildPath": { + "name": "giteaBuildPath", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "'/'" + }, + "bitbucketRepository": { + "name": "bitbucketRepository", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "bitbucketRepositorySlug": { + "name": "bitbucketRepositorySlug", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "bitbucketOwner": { + "name": "bitbucketOwner", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "bitbucketBranch": { + "name": "bitbucketBranch", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "bitbucketBuildPath": { + "name": "bitbucketBuildPath", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "'/'" + }, + "username": { + "name": "username", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "password": { + "name": "password", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "dockerImage": { + "name": "dockerImage", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "registryUrl": { + "name": "registryUrl", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "customGitUrl": { + "name": "customGitUrl", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "customGitBranch": { + "name": "customGitBranch", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "customGitBuildPath": { + "name": "customGitBuildPath", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "customGitSSHKeyId": { + "name": "customGitSSHKeyId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "enableSubmodules": { + "name": "enableSubmodules", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "dockerfile": { + "name": "dockerfile", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "dockerContextPath": { + "name": "dockerContextPath", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "dockerBuildStage": { + "name": "dockerBuildStage", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "dropBuildPath": { + "name": "dropBuildPath", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "healthCheckSwarm": { + "name": "healthCheckSwarm", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "restartPolicySwarm": { + "name": "restartPolicySwarm", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "placementSwarm": { + "name": "placementSwarm", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "updateConfigSwarm": { + "name": "updateConfigSwarm", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "rollbackConfigSwarm": { + "name": "rollbackConfigSwarm", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "modeSwarm": { + "name": "modeSwarm", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "labelsSwarm": { + "name": "labelsSwarm", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "networkSwarm": { + "name": "networkSwarm", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "stopGracePeriodSwarm": { + "name": "stopGracePeriodSwarm", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "endpointSpecSwarm": { + "name": "endpointSpecSwarm", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "ulimitsSwarm": { + "name": "ulimitsSwarm", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "replicas": { + "name": "replicas", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "applicationStatus": { + "name": "applicationStatus", + "type": "applicationStatus", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'idle'" + }, + "buildType": { + "name": "buildType", + "type": "buildType", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'nixpacks'" + }, + "railpackVersion": { + "name": "railpackVersion", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "'0.15.4'" + }, + "herokuVersion": { + "name": "herokuVersion", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "'24'" + }, + "publishDirectory": { + "name": "publishDirectory", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "isStaticSpa": { + "name": "isStaticSpa", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "createEnvFile": { + "name": "createEnvFile", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "createdAt": { + "name": "createdAt", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "registryId": { + "name": "registryId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "rollbackRegistryId": { + "name": "rollbackRegistryId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "environmentId": { + "name": "environmentId", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "githubId": { + "name": "githubId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "gitlabId": { + "name": "gitlabId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "giteaId": { + "name": "giteaId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "bitbucketId": { + "name": "bitbucketId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "serverId": { + "name": "serverId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "buildServerId": { + "name": "buildServerId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "buildRegistryId": { + "name": "buildRegistryId", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "application_customGitSSHKeyId_ssh-key_sshKeyId_fk": { + "name": "application_customGitSSHKeyId_ssh-key_sshKeyId_fk", + "tableFrom": "application", + "tableTo": "ssh-key", + "columnsFrom": [ + "customGitSSHKeyId" + ], + "columnsTo": [ + "sshKeyId" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "application_registryId_registry_registryId_fk": { + "name": "application_registryId_registry_registryId_fk", + "tableFrom": "application", + "tableTo": "registry", + "columnsFrom": [ + "registryId" + ], + "columnsTo": [ + "registryId" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "application_rollbackRegistryId_registry_registryId_fk": { + "name": "application_rollbackRegistryId_registry_registryId_fk", + "tableFrom": "application", + "tableTo": "registry", + "columnsFrom": [ + "rollbackRegistryId" + ], + "columnsTo": [ + "registryId" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "application_environmentId_environment_environmentId_fk": { + "name": "application_environmentId_environment_environmentId_fk", + "tableFrom": "application", + "tableTo": "environment", + "columnsFrom": [ + "environmentId" + ], + "columnsTo": [ + "environmentId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "application_githubId_github_githubId_fk": { + "name": "application_githubId_github_githubId_fk", + "tableFrom": "application", + "tableTo": "github", + "columnsFrom": [ + "githubId" + ], + "columnsTo": [ + "githubId" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "application_gitlabId_gitlab_gitlabId_fk": { + "name": "application_gitlabId_gitlab_gitlabId_fk", + "tableFrom": "application", + "tableTo": "gitlab", + "columnsFrom": [ + "gitlabId" + ], + "columnsTo": [ + "gitlabId" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "application_giteaId_gitea_giteaId_fk": { + "name": "application_giteaId_gitea_giteaId_fk", + "tableFrom": "application", + "tableTo": "gitea", + "columnsFrom": [ + "giteaId" + ], + "columnsTo": [ + "giteaId" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "application_bitbucketId_bitbucket_bitbucketId_fk": { + "name": "application_bitbucketId_bitbucket_bitbucketId_fk", + "tableFrom": "application", + "tableTo": "bitbucket", + "columnsFrom": [ + "bitbucketId" + ], + "columnsTo": [ + "bitbucketId" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "application_serverId_server_serverId_fk": { + "name": "application_serverId_server_serverId_fk", + "tableFrom": "application", + "tableTo": "server", + "columnsFrom": [ + "serverId" + ], + "columnsTo": [ + "serverId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "application_buildServerId_server_serverId_fk": { + "name": "application_buildServerId_server_serverId_fk", + "tableFrom": "application", + "tableTo": "server", + "columnsFrom": [ + "buildServerId" + ], + "columnsTo": [ + "serverId" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "application_buildRegistryId_registry_registryId_fk": { + "name": "application_buildRegistryId_registry_registryId_fk", + "tableFrom": "application", + "tableTo": "registry", + "columnsFrom": [ + "buildRegistryId" + ], + "columnsTo": [ + "registryId" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "application_appName_unique": { + "name": "application_appName_unique", + "nullsNotDistinct": false, + "columns": [ + "appName" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.backup": { + "name": "backup", + "schema": "", + "columns": { + "backupId": { + "name": "backupId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "appName": { + "name": "appName", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "schedule": { + "name": "schedule", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "database": { + "name": "database", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "prefix": { + "name": "prefix", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "serviceName": { + "name": "serviceName", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "destinationId": { + "name": "destinationId", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "keepLatestCount": { + "name": "keepLatestCount", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "backupType": { + "name": "backupType", + "type": "backupType", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'database'" + }, + "databaseType": { + "name": "databaseType", + "type": "databaseType", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "composeId": { + "name": "composeId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "postgresId": { + "name": "postgresId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "mariadbId": { + "name": "mariadbId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "mysqlId": { + "name": "mysqlId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "mongoId": { + "name": "mongoId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "userId": { + "name": "userId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "backup_destinationId_destination_destinationId_fk": { + "name": "backup_destinationId_destination_destinationId_fk", + "tableFrom": "backup", + "tableTo": "destination", + "columnsFrom": [ + "destinationId" + ], + "columnsTo": [ + "destinationId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "backup_composeId_compose_composeId_fk": { + "name": "backup_composeId_compose_composeId_fk", + "tableFrom": "backup", + "tableTo": "compose", + "columnsFrom": [ + "composeId" + ], + "columnsTo": [ + "composeId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "backup_postgresId_postgres_postgresId_fk": { + "name": "backup_postgresId_postgres_postgresId_fk", + "tableFrom": "backup", + "tableTo": "postgres", + "columnsFrom": [ + "postgresId" + ], + "columnsTo": [ + "postgresId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "backup_mariadbId_mariadb_mariadbId_fk": { + "name": "backup_mariadbId_mariadb_mariadbId_fk", + "tableFrom": "backup", + "tableTo": "mariadb", + "columnsFrom": [ + "mariadbId" + ], + "columnsTo": [ + "mariadbId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "backup_mysqlId_mysql_mysqlId_fk": { + "name": "backup_mysqlId_mysql_mysqlId_fk", + "tableFrom": "backup", + "tableTo": "mysql", + "columnsFrom": [ + "mysqlId" + ], + "columnsTo": [ + "mysqlId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "backup_mongoId_mongo_mongoId_fk": { + "name": "backup_mongoId_mongo_mongoId_fk", + "tableFrom": "backup", + "tableTo": "mongo", + "columnsFrom": [ + "mongoId" + ], + "columnsTo": [ + "mongoId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "backup_userId_user_id_fk": { + "name": "backup_userId_user_id_fk", + "tableFrom": "backup", + "tableTo": "user", + "columnsFrom": [ + "userId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "backup_appName_unique": { + "name": "backup_appName_unique", + "nullsNotDistinct": false, + "columns": [ + "appName" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.bitbucket": { + "name": "bitbucket", + "schema": "", + "columns": { + "bitbucketId": { + "name": "bitbucketId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "bitbucketUsername": { + "name": "bitbucketUsername", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "bitbucketEmail": { + "name": "bitbucketEmail", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "appPassword": { + "name": "appPassword", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "apiToken": { + "name": "apiToken", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "bitbucketWorkspaceName": { + "name": "bitbucketWorkspaceName", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "gitProviderId": { + "name": "gitProviderId", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "bitbucket_gitProviderId_git_provider_gitProviderId_fk": { + "name": "bitbucket_gitProviderId_git_provider_gitProviderId_fk", + "tableFrom": "bitbucket", + "tableTo": "git_provider", + "columnsFrom": [ + "gitProviderId" + ], + "columnsTo": [ + "gitProviderId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.certificate": { + "name": "certificate", + "schema": "", + "columns": { + "certificateId": { + "name": "certificateId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "certificateData": { + "name": "certificateData", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "privateKey": { + "name": "privateKey", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "certificatePath": { + "name": "certificatePath", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "autoRenew": { + "name": "autoRenew", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "organizationId": { + "name": "organizationId", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "serverId": { + "name": "serverId", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "certificate_organizationId_organization_id_fk": { + "name": "certificate_organizationId_organization_id_fk", + "tableFrom": "certificate", + "tableTo": "organization", + "columnsFrom": [ + "organizationId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "certificate_serverId_server_serverId_fk": { + "name": "certificate_serverId_server_serverId_fk", + "tableFrom": "certificate", + "tableTo": "server", + "columnsFrom": [ + "serverId" + ], + "columnsTo": [ + "serverId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "certificate_certificatePath_unique": { + "name": "certificate_certificatePath_unique", + "nullsNotDistinct": false, + "columns": [ + "certificatePath" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.compose": { + "name": "compose", + "schema": "", + "columns": { + "composeId": { + "name": "composeId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "appName": { + "name": "appName", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "env": { + "name": "env", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "composeFile": { + "name": "composeFile", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "refreshToken": { + "name": "refreshToken", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "sourceType": { + "name": "sourceType", + "type": "sourceTypeCompose", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'github'" + }, + "composeType": { + "name": "composeType", + "type": "composeType", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'docker-compose'" + }, + "repository": { + "name": "repository", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "owner": { + "name": "owner", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "branch": { + "name": "branch", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "autoDeploy": { + "name": "autoDeploy", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "gitlabProjectId": { + "name": "gitlabProjectId", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "gitlabRepository": { + "name": "gitlabRepository", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "gitlabOwner": { + "name": "gitlabOwner", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "gitlabBranch": { + "name": "gitlabBranch", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "gitlabPathNamespace": { + "name": "gitlabPathNamespace", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "bitbucketRepository": { + "name": "bitbucketRepository", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "bitbucketRepositorySlug": { + "name": "bitbucketRepositorySlug", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "bitbucketOwner": { + "name": "bitbucketOwner", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "bitbucketBranch": { + "name": "bitbucketBranch", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "giteaRepository": { + "name": "giteaRepository", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "giteaOwner": { + "name": "giteaOwner", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "giteaBranch": { + "name": "giteaBranch", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "customGitUrl": { + "name": "customGitUrl", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "customGitBranch": { + "name": "customGitBranch", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "customGitSSHKeyId": { + "name": "customGitSSHKeyId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "command": { + "name": "command", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "enableSubmodules": { + "name": "enableSubmodules", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "composePath": { + "name": "composePath", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'./docker-compose.yml'" + }, + "suffix": { + "name": "suffix", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "randomize": { + "name": "randomize", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "isolatedDeployment": { + "name": "isolatedDeployment", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "isolatedDeploymentsVolume": { + "name": "isolatedDeploymentsVolume", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "triggerType": { + "name": "triggerType", + "type": "triggerType", + "typeSchema": "public", + "primaryKey": false, + "notNull": false, + "default": "'push'" + }, + "composeStatus": { + "name": "composeStatus", + "type": "applicationStatus", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'idle'" + }, + "environmentId": { + "name": "environmentId", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "watchPaths": { + "name": "watchPaths", + "type": "text[]", + "primaryKey": false, + "notNull": false + }, + "githubId": { + "name": "githubId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "gitlabId": { + "name": "gitlabId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "bitbucketId": { + "name": "bitbucketId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "giteaId": { + "name": "giteaId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "serverId": { + "name": "serverId", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "compose_customGitSSHKeyId_ssh-key_sshKeyId_fk": { + "name": "compose_customGitSSHKeyId_ssh-key_sshKeyId_fk", + "tableFrom": "compose", + "tableTo": "ssh-key", + "columnsFrom": [ + "customGitSSHKeyId" + ], + "columnsTo": [ + "sshKeyId" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "compose_environmentId_environment_environmentId_fk": { + "name": "compose_environmentId_environment_environmentId_fk", + "tableFrom": "compose", + "tableTo": "environment", + "columnsFrom": [ + "environmentId" + ], + "columnsTo": [ + "environmentId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "compose_githubId_github_githubId_fk": { + "name": "compose_githubId_github_githubId_fk", + "tableFrom": "compose", + "tableTo": "github", + "columnsFrom": [ + "githubId" + ], + "columnsTo": [ + "githubId" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "compose_gitlabId_gitlab_gitlabId_fk": { + "name": "compose_gitlabId_gitlab_gitlabId_fk", + "tableFrom": "compose", + "tableTo": "gitlab", + "columnsFrom": [ + "gitlabId" + ], + "columnsTo": [ + "gitlabId" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "compose_bitbucketId_bitbucket_bitbucketId_fk": { + "name": "compose_bitbucketId_bitbucket_bitbucketId_fk", + "tableFrom": "compose", + "tableTo": "bitbucket", + "columnsFrom": [ + "bitbucketId" + ], + "columnsTo": [ + "bitbucketId" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "compose_giteaId_gitea_giteaId_fk": { + "name": "compose_giteaId_gitea_giteaId_fk", + "tableFrom": "compose", + "tableTo": "gitea", + "columnsFrom": [ + "giteaId" + ], + "columnsTo": [ + "giteaId" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "compose_serverId_server_serverId_fk": { + "name": "compose_serverId_server_serverId_fk", + "tableFrom": "compose", + "tableTo": "server", + "columnsFrom": [ + "serverId" + ], + "columnsTo": [ + "serverId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.deployment": { + "name": "deployment", + "schema": "", + "columns": { + "deploymentId": { + "name": "deploymentId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "deploymentStatus", + "typeSchema": "public", + "primaryKey": false, + "notNull": false, + "default": "'running'" + }, + "logPath": { + "name": "logPath", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "pid": { + "name": "pid", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "applicationId": { + "name": "applicationId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "composeId": { + "name": "composeId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "serverId": { + "name": "serverId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "isPreviewDeployment": { + "name": "isPreviewDeployment", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "previewDeploymentId": { + "name": "previewDeploymentId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "createdAt": { + "name": "createdAt", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "startedAt": { + "name": "startedAt", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "finishedAt": { + "name": "finishedAt", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "errorMessage": { + "name": "errorMessage", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "scheduleId": { + "name": "scheduleId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "backupId": { + "name": "backupId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "rollbackId": { + "name": "rollbackId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "volumeBackupId": { + "name": "volumeBackupId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "buildServerId": { + "name": "buildServerId", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "deployment_applicationId_application_applicationId_fk": { + "name": "deployment_applicationId_application_applicationId_fk", + "tableFrom": "deployment", + "tableTo": "application", + "columnsFrom": [ + "applicationId" + ], + "columnsTo": [ + "applicationId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "deployment_composeId_compose_composeId_fk": { + "name": "deployment_composeId_compose_composeId_fk", + "tableFrom": "deployment", + "tableTo": "compose", + "columnsFrom": [ + "composeId" + ], + "columnsTo": [ + "composeId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "deployment_serverId_server_serverId_fk": { + "name": "deployment_serverId_server_serverId_fk", + "tableFrom": "deployment", + "tableTo": "server", + "columnsFrom": [ + "serverId" + ], + "columnsTo": [ + "serverId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "deployment_previewDeploymentId_preview_deployments_previewDeploymentId_fk": { + "name": "deployment_previewDeploymentId_preview_deployments_previewDeploymentId_fk", + "tableFrom": "deployment", + "tableTo": "preview_deployments", + "columnsFrom": [ + "previewDeploymentId" + ], + "columnsTo": [ + "previewDeploymentId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "deployment_scheduleId_schedule_scheduleId_fk": { + "name": "deployment_scheduleId_schedule_scheduleId_fk", + "tableFrom": "deployment", + "tableTo": "schedule", + "columnsFrom": [ + "scheduleId" + ], + "columnsTo": [ + "scheduleId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "deployment_backupId_backup_backupId_fk": { + "name": "deployment_backupId_backup_backupId_fk", + "tableFrom": "deployment", + "tableTo": "backup", + "columnsFrom": [ + "backupId" + ], + "columnsTo": [ + "backupId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "deployment_rollbackId_rollback_rollbackId_fk": { + "name": "deployment_rollbackId_rollback_rollbackId_fk", + "tableFrom": "deployment", + "tableTo": "rollback", + "columnsFrom": [ + "rollbackId" + ], + "columnsTo": [ + "rollbackId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "deployment_volumeBackupId_volume_backup_volumeBackupId_fk": { + "name": "deployment_volumeBackupId_volume_backup_volumeBackupId_fk", + "tableFrom": "deployment", + "tableTo": "volume_backup", + "columnsFrom": [ + "volumeBackupId" + ], + "columnsTo": [ + "volumeBackupId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "deployment_buildServerId_server_serverId_fk": { + "name": "deployment_buildServerId_server_serverId_fk", + "tableFrom": "deployment", + "tableTo": "server", + "columnsFrom": [ + "buildServerId" + ], + "columnsTo": [ + "serverId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.destination": { + "name": "destination", + "schema": "", + "columns": { + "destinationId": { + "name": "destinationId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "accessKey": { + "name": "accessKey", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "secretAccessKey": { + "name": "secretAccessKey", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "bucket": { + "name": "bucket", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "region": { + "name": "region", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "endpoint": { + "name": "endpoint", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "organizationId": { + "name": "organizationId", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "destination_organizationId_organization_id_fk": { + "name": "destination_organizationId_organization_id_fk", + "tableFrom": "destination", + "tableTo": "organization", + "columnsFrom": [ + "organizationId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.domain": { + "name": "domain", + "schema": "", + "columns": { + "domainId": { + "name": "domainId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "host": { + "name": "host", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "https": { + "name": "https", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "port": { + "name": "port", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 3000 + }, + "path": { + "name": "path", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "'/'" + }, + "serviceName": { + "name": "serviceName", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "domainType": { + "name": "domainType", + "type": "domainType", + "typeSchema": "public", + "primaryKey": false, + "notNull": false, + "default": "'application'" + }, + "uniqueConfigKey": { + "name": "uniqueConfigKey", + "type": "serial", + "primaryKey": false, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "composeId": { + "name": "composeId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "customCertResolver": { + "name": "customCertResolver", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "applicationId": { + "name": "applicationId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "previewDeploymentId": { + "name": "previewDeploymentId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "certificateType": { + "name": "certificateType", + "type": "certificateType", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'none'" + }, + "internalPath": { + "name": "internalPath", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "'/'" + }, + "stripPath": { + "name": "stripPath", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + } + }, + "indexes": {}, + "foreignKeys": { + "domain_composeId_compose_composeId_fk": { + "name": "domain_composeId_compose_composeId_fk", + "tableFrom": "domain", + "tableTo": "compose", + "columnsFrom": [ + "composeId" + ], + "columnsTo": [ + "composeId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "domain_applicationId_application_applicationId_fk": { + "name": "domain_applicationId_application_applicationId_fk", + "tableFrom": "domain", + "tableTo": "application", + "columnsFrom": [ + "applicationId" + ], + "columnsTo": [ + "applicationId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "domain_previewDeploymentId_preview_deployments_previewDeploymentId_fk": { + "name": "domain_previewDeploymentId_preview_deployments_previewDeploymentId_fk", + "tableFrom": "domain", + "tableTo": "preview_deployments", + "columnsFrom": [ + "previewDeploymentId" + ], + "columnsTo": [ + "previewDeploymentId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.environment": { + "name": "environment", + "schema": "", + "columns": { + "environmentId": { + "name": "environmentId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "createdAt": { + "name": "createdAt", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "env": { + "name": "env", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "projectId": { + "name": "projectId", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "isDefault": { + "name": "isDefault", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + } + }, + "indexes": {}, + "foreignKeys": { + "environment_projectId_project_projectId_fk": { + "name": "environment_projectId_project_projectId_fk", + "tableFrom": "environment", + "tableTo": "project", + "columnsFrom": [ + "projectId" + ], + "columnsTo": [ + "projectId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.git_provider": { + "name": "git_provider", + "schema": "", + "columns": { + "gitProviderId": { + "name": "gitProviderId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "providerType": { + "name": "providerType", + "type": "gitProviderType", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'github'" + }, + "createdAt": { + "name": "createdAt", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "organizationId": { + "name": "organizationId", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "userId": { + "name": "userId", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "git_provider_organizationId_organization_id_fk": { + "name": "git_provider_organizationId_organization_id_fk", + "tableFrom": "git_provider", + "tableTo": "organization", + "columnsFrom": [ + "organizationId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "git_provider_userId_user_id_fk": { + "name": "git_provider_userId_user_id_fk", + "tableFrom": "git_provider", + "tableTo": "user", + "columnsFrom": [ + "userId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.gitea": { + "name": "gitea", + "schema": "", + "columns": { + "giteaId": { + "name": "giteaId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "giteaUrl": { + "name": "giteaUrl", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'https://gitea.com'" + }, + "giteaInternalUrl": { + "name": "giteaInternalUrl", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "redirect_uri": { + "name": "redirect_uri", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "client_id": { + "name": "client_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "client_secret": { + "name": "client_secret", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "gitProviderId": { + "name": "gitProviderId", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "access_token": { + "name": "access_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "refresh_token": { + "name": "refresh_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "expires_at": { + "name": "expires_at", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "scopes": { + "name": "scopes", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "'repo,repo:status,read:user,read:org'" + }, + "last_authenticated_at": { + "name": "last_authenticated_at", + "type": "integer", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "gitea_gitProviderId_git_provider_gitProviderId_fk": { + "name": "gitea_gitProviderId_git_provider_gitProviderId_fk", + "tableFrom": "gitea", + "tableTo": "git_provider", + "columnsFrom": [ + "gitProviderId" + ], + "columnsTo": [ + "gitProviderId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.github": { + "name": "github", + "schema": "", + "columns": { + "githubId": { + "name": "githubId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "githubAppName": { + "name": "githubAppName", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "githubAppId": { + "name": "githubAppId", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "githubClientId": { + "name": "githubClientId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "githubClientSecret": { + "name": "githubClientSecret", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "githubInstallationId": { + "name": "githubInstallationId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "githubPrivateKey": { + "name": "githubPrivateKey", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "githubWebhookSecret": { + "name": "githubWebhookSecret", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "gitProviderId": { + "name": "gitProviderId", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "github_gitProviderId_git_provider_gitProviderId_fk": { + "name": "github_gitProviderId_git_provider_gitProviderId_fk", + "tableFrom": "github", + "tableTo": "git_provider", + "columnsFrom": [ + "gitProviderId" + ], + "columnsTo": [ + "gitProviderId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.gitlab": { + "name": "gitlab", + "schema": "", + "columns": { + "gitlabId": { + "name": "gitlabId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "gitlabUrl": { + "name": "gitlabUrl", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'https://gitlab.com'" + }, + "gitlabInternalUrl": { + "name": "gitlabInternalUrl", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "application_id": { + "name": "application_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "redirect_uri": { + "name": "redirect_uri", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "secret": { + "name": "secret", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "access_token": { + "name": "access_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "refresh_token": { + "name": "refresh_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "group_name": { + "name": "group_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "expires_at": { + "name": "expires_at", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "gitProviderId": { + "name": "gitProviderId", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "gitlab_gitProviderId_git_provider_gitProviderId_fk": { + "name": "gitlab_gitProviderId_git_provider_gitProviderId_fk", + "tableFrom": "gitlab", + "tableTo": "git_provider", + "columnsFrom": [ + "gitProviderId" + ], + "columnsTo": [ + "gitProviderId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.mariadb": { + "name": "mariadb", + "schema": "", + "columns": { + "mariadbId": { + "name": "mariadbId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "appName": { + "name": "appName", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "databaseName": { + "name": "databaseName", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "databaseUser": { + "name": "databaseUser", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "databasePassword": { + "name": "databasePassword", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "rootPassword": { + "name": "rootPassword", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "dockerImage": { + "name": "dockerImage", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "command": { + "name": "command", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "args": { + "name": "args", + "type": "text[]", + "primaryKey": false, + "notNull": false + }, + "env": { + "name": "env", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "memoryReservation": { + "name": "memoryReservation", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "memoryLimit": { + "name": "memoryLimit", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "cpuReservation": { + "name": "cpuReservation", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "cpuLimit": { + "name": "cpuLimit", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "externalPort": { + "name": "externalPort", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "applicationStatus": { + "name": "applicationStatus", + "type": "applicationStatus", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'idle'" + }, + "healthCheckSwarm": { + "name": "healthCheckSwarm", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "restartPolicySwarm": { + "name": "restartPolicySwarm", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "placementSwarm": { + "name": "placementSwarm", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "updateConfigSwarm": { + "name": "updateConfigSwarm", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "rollbackConfigSwarm": { + "name": "rollbackConfigSwarm", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "modeSwarm": { + "name": "modeSwarm", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "labelsSwarm": { + "name": "labelsSwarm", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "networkSwarm": { + "name": "networkSwarm", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "stopGracePeriodSwarm": { + "name": "stopGracePeriodSwarm", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "endpointSpecSwarm": { + "name": "endpointSpecSwarm", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "ulimitsSwarm": { + "name": "ulimitsSwarm", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "replicas": { + "name": "replicas", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "createdAt": { + "name": "createdAt", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "environmentId": { + "name": "environmentId", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "serverId": { + "name": "serverId", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "mariadb_environmentId_environment_environmentId_fk": { + "name": "mariadb_environmentId_environment_environmentId_fk", + "tableFrom": "mariadb", + "tableTo": "environment", + "columnsFrom": [ + "environmentId" + ], + "columnsTo": [ + "environmentId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "mariadb_serverId_server_serverId_fk": { + "name": "mariadb_serverId_server_serverId_fk", + "tableFrom": "mariadb", + "tableTo": "server", + "columnsFrom": [ + "serverId" + ], + "columnsTo": [ + "serverId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "mariadb_appName_unique": { + "name": "mariadb_appName_unique", + "nullsNotDistinct": false, + "columns": [ + "appName" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.mongo": { + "name": "mongo", + "schema": "", + "columns": { + "mongoId": { + "name": "mongoId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "appName": { + "name": "appName", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "databaseUser": { + "name": "databaseUser", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "databasePassword": { + "name": "databasePassword", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "dockerImage": { + "name": "dockerImage", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "command": { + "name": "command", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "args": { + "name": "args", + "type": "text[]", + "primaryKey": false, + "notNull": false + }, + "env": { + "name": "env", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "memoryReservation": { + "name": "memoryReservation", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "memoryLimit": { + "name": "memoryLimit", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "cpuReservation": { + "name": "cpuReservation", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "cpuLimit": { + "name": "cpuLimit", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "externalPort": { + "name": "externalPort", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "applicationStatus": { + "name": "applicationStatus", + "type": "applicationStatus", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'idle'" + }, + "healthCheckSwarm": { + "name": "healthCheckSwarm", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "restartPolicySwarm": { + "name": "restartPolicySwarm", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "placementSwarm": { + "name": "placementSwarm", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "updateConfigSwarm": { + "name": "updateConfigSwarm", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "rollbackConfigSwarm": { + "name": "rollbackConfigSwarm", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "modeSwarm": { + "name": "modeSwarm", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "labelsSwarm": { + "name": "labelsSwarm", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "networkSwarm": { + "name": "networkSwarm", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "stopGracePeriodSwarm": { + "name": "stopGracePeriodSwarm", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "endpointSpecSwarm": { + "name": "endpointSpecSwarm", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "ulimitsSwarm": { + "name": "ulimitsSwarm", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "replicas": { + "name": "replicas", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "createdAt": { + "name": "createdAt", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "environmentId": { + "name": "environmentId", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "serverId": { + "name": "serverId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "replicaSets": { + "name": "replicaSets", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + } + }, + "indexes": {}, + "foreignKeys": { + "mongo_environmentId_environment_environmentId_fk": { + "name": "mongo_environmentId_environment_environmentId_fk", + "tableFrom": "mongo", + "tableTo": "environment", + "columnsFrom": [ + "environmentId" + ], + "columnsTo": [ + "environmentId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "mongo_serverId_server_serverId_fk": { + "name": "mongo_serverId_server_serverId_fk", + "tableFrom": "mongo", + "tableTo": "server", + "columnsFrom": [ + "serverId" + ], + "columnsTo": [ + "serverId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "mongo_appName_unique": { + "name": "mongo_appName_unique", + "nullsNotDistinct": false, + "columns": [ + "appName" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.mount": { + "name": "mount", + "schema": "", + "columns": { + "mountId": { + "name": "mountId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "type": { + "name": "type", + "type": "mountType", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "hostPath": { + "name": "hostPath", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "volumeName": { + "name": "volumeName", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "filePath": { + "name": "filePath", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "content": { + "name": "content", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "serviceType": { + "name": "serviceType", + "type": "serviceType", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'application'" + }, + "mountPath": { + "name": "mountPath", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "applicationId": { + "name": "applicationId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "postgresId": { + "name": "postgresId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "mariadbId": { + "name": "mariadbId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "mongoId": { + "name": "mongoId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "mysqlId": { + "name": "mysqlId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "redisId": { + "name": "redisId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "composeId": { + "name": "composeId", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "mount_applicationId_application_applicationId_fk": { + "name": "mount_applicationId_application_applicationId_fk", + "tableFrom": "mount", + "tableTo": "application", + "columnsFrom": [ + "applicationId" + ], + "columnsTo": [ + "applicationId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "mount_postgresId_postgres_postgresId_fk": { + "name": "mount_postgresId_postgres_postgresId_fk", + "tableFrom": "mount", + "tableTo": "postgres", + "columnsFrom": [ + "postgresId" + ], + "columnsTo": [ + "postgresId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "mount_mariadbId_mariadb_mariadbId_fk": { + "name": "mount_mariadbId_mariadb_mariadbId_fk", + "tableFrom": "mount", + "tableTo": "mariadb", + "columnsFrom": [ + "mariadbId" + ], + "columnsTo": [ + "mariadbId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "mount_mongoId_mongo_mongoId_fk": { + "name": "mount_mongoId_mongo_mongoId_fk", + "tableFrom": "mount", + "tableTo": "mongo", + "columnsFrom": [ + "mongoId" + ], + "columnsTo": [ + "mongoId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "mount_mysqlId_mysql_mysqlId_fk": { + "name": "mount_mysqlId_mysql_mysqlId_fk", + "tableFrom": "mount", + "tableTo": "mysql", + "columnsFrom": [ + "mysqlId" + ], + "columnsTo": [ + "mysqlId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "mount_redisId_redis_redisId_fk": { + "name": "mount_redisId_redis_redisId_fk", + "tableFrom": "mount", + "tableTo": "redis", + "columnsFrom": [ + "redisId" + ], + "columnsTo": [ + "redisId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "mount_composeId_compose_composeId_fk": { + "name": "mount_composeId_compose_composeId_fk", + "tableFrom": "mount", + "tableTo": "compose", + "columnsFrom": [ + "composeId" + ], + "columnsTo": [ + "composeId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.network": { + "name": "network", + "schema": "", + "columns": { + "networkId": { + "name": "networkId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "driver": { + "name": "driver", + "type": "networkDriver", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'bridge'" + }, + "scope": { + "name": "scope", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "internal": { + "name": "internal", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "attachable": { + "name": "attachable", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "ingress": { + "name": "ingress", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "configOnly": { + "name": "configOnly", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "enableIPv4": { + "name": "enableIPv4", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "enableIPv6": { + "name": "enableIPv6", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "ipam": { + "name": "ipam", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "'{}'::jsonb" + }, + "createdAt": { + "name": "createdAt", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "organizationId": { + "name": "organizationId", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "serverId": { + "name": "serverId", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "network_organizationId_organization_id_fk": { + "name": "network_organizationId_organization_id_fk", + "tableFrom": "network", + "tableTo": "organization", + "columnsFrom": [ + "organizationId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "network_serverId_server_serverId_fk": { + "name": "network_serverId_server_serverId_fk", + "tableFrom": "network", + "tableTo": "server", + "columnsFrom": [ + "serverId" + ], + "columnsTo": [ + "serverId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.mysql": { + "name": "mysql", + "schema": "", + "columns": { + "mysqlId": { + "name": "mysqlId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "appName": { + "name": "appName", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "databaseName": { + "name": "databaseName", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "databaseUser": { + "name": "databaseUser", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "databasePassword": { + "name": "databasePassword", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "rootPassword": { + "name": "rootPassword", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "dockerImage": { + "name": "dockerImage", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "command": { + "name": "command", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "args": { + "name": "args", + "type": "text[]", + "primaryKey": false, + "notNull": false + }, + "env": { + "name": "env", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "memoryReservation": { + "name": "memoryReservation", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "memoryLimit": { + "name": "memoryLimit", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "cpuReservation": { + "name": "cpuReservation", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "cpuLimit": { + "name": "cpuLimit", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "externalPort": { + "name": "externalPort", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "applicationStatus": { + "name": "applicationStatus", + "type": "applicationStatus", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'idle'" + }, + "healthCheckSwarm": { + "name": "healthCheckSwarm", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "restartPolicySwarm": { + "name": "restartPolicySwarm", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "placementSwarm": { + "name": "placementSwarm", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "updateConfigSwarm": { + "name": "updateConfigSwarm", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "rollbackConfigSwarm": { + "name": "rollbackConfigSwarm", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "modeSwarm": { + "name": "modeSwarm", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "labelsSwarm": { + "name": "labelsSwarm", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "networkSwarm": { + "name": "networkSwarm", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "stopGracePeriodSwarm": { + "name": "stopGracePeriodSwarm", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "endpointSpecSwarm": { + "name": "endpointSpecSwarm", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "ulimitsSwarm": { + "name": "ulimitsSwarm", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "replicas": { + "name": "replicas", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "createdAt": { + "name": "createdAt", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "environmentId": { + "name": "environmentId", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "serverId": { + "name": "serverId", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "mysql_environmentId_environment_environmentId_fk": { + "name": "mysql_environmentId_environment_environmentId_fk", + "tableFrom": "mysql", + "tableTo": "environment", + "columnsFrom": [ + "environmentId" + ], + "columnsTo": [ + "environmentId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "mysql_serverId_server_serverId_fk": { + "name": "mysql_serverId_server_serverId_fk", + "tableFrom": "mysql", + "tableTo": "server", + "columnsFrom": [ + "serverId" + ], + "columnsTo": [ + "serverId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "mysql_appName_unique": { + "name": "mysql_appName_unique", + "nullsNotDistinct": false, + "columns": [ + "appName" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.custom": { + "name": "custom", + "schema": "", + "columns": { + "customId": { + "name": "customId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "endpoint": { + "name": "endpoint", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "headers": { + "name": "headers", + "type": "jsonb", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.discord": { + "name": "discord", + "schema": "", + "columns": { + "discordId": { + "name": "discordId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "webhookUrl": { + "name": "webhookUrl", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "decoration": { + "name": "decoration", + "type": "boolean", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.email": { + "name": "email", + "schema": "", + "columns": { + "emailId": { + "name": "emailId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "smtpServer": { + "name": "smtpServer", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "smtpPort": { + "name": "smtpPort", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "username": { + "name": "username", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "password": { + "name": "password", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "fromAddress": { + "name": "fromAddress", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "toAddress": { + "name": "toAddress", + "type": "text[]", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.gotify": { + "name": "gotify", + "schema": "", + "columns": { + "gotifyId": { + "name": "gotifyId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "serverUrl": { + "name": "serverUrl", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "appToken": { + "name": "appToken", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "priority": { + "name": "priority", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 5 + }, + "decoration": { + "name": "decoration", + "type": "boolean", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.lark": { + "name": "lark", + "schema": "", + "columns": { + "larkId": { + "name": "larkId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "webhookUrl": { + "name": "webhookUrl", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.notification": { + "name": "notification", + "schema": "", + "columns": { + "notificationId": { + "name": "notificationId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "appDeploy": { + "name": "appDeploy", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "appBuildError": { + "name": "appBuildError", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "databaseBackup": { + "name": "databaseBackup", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "volumeBackup": { + "name": "volumeBackup", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "dokployRestart": { + "name": "dokployRestart", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "dockerCleanup": { + "name": "dockerCleanup", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "serverThreshold": { + "name": "serverThreshold", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "notificationType": { + "name": "notificationType", + "type": "notificationType", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "slackId": { + "name": "slackId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "telegramId": { + "name": "telegramId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "discordId": { + "name": "discordId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "emailId": { + "name": "emailId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "resendId": { + "name": "resendId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "gotifyId": { + "name": "gotifyId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "ntfyId": { + "name": "ntfyId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "customId": { + "name": "customId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "larkId": { + "name": "larkId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "pushoverId": { + "name": "pushoverId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "teamsId": { + "name": "teamsId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "organizationId": { + "name": "organizationId", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "notification_slackId_slack_slackId_fk": { + "name": "notification_slackId_slack_slackId_fk", + "tableFrom": "notification", + "tableTo": "slack", + "columnsFrom": [ + "slackId" + ], + "columnsTo": [ + "slackId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "notification_telegramId_telegram_telegramId_fk": { + "name": "notification_telegramId_telegram_telegramId_fk", + "tableFrom": "notification", + "tableTo": "telegram", + "columnsFrom": [ + "telegramId" + ], + "columnsTo": [ + "telegramId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "notification_discordId_discord_discordId_fk": { + "name": "notification_discordId_discord_discordId_fk", + "tableFrom": "notification", + "tableTo": "discord", + "columnsFrom": [ + "discordId" + ], + "columnsTo": [ + "discordId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "notification_emailId_email_emailId_fk": { + "name": "notification_emailId_email_emailId_fk", + "tableFrom": "notification", + "tableTo": "email", + "columnsFrom": [ + "emailId" + ], + "columnsTo": [ + "emailId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "notification_resendId_resend_resendId_fk": { + "name": "notification_resendId_resend_resendId_fk", + "tableFrom": "notification", + "tableTo": "resend", + "columnsFrom": [ + "resendId" + ], + "columnsTo": [ + "resendId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "notification_gotifyId_gotify_gotifyId_fk": { + "name": "notification_gotifyId_gotify_gotifyId_fk", + "tableFrom": "notification", + "tableTo": "gotify", + "columnsFrom": [ + "gotifyId" + ], + "columnsTo": [ + "gotifyId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "notification_ntfyId_ntfy_ntfyId_fk": { + "name": "notification_ntfyId_ntfy_ntfyId_fk", + "tableFrom": "notification", + "tableTo": "ntfy", + "columnsFrom": [ + "ntfyId" + ], + "columnsTo": [ + "ntfyId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "notification_customId_custom_customId_fk": { + "name": "notification_customId_custom_customId_fk", + "tableFrom": "notification", + "tableTo": "custom", + "columnsFrom": [ + "customId" + ], + "columnsTo": [ + "customId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "notification_larkId_lark_larkId_fk": { + "name": "notification_larkId_lark_larkId_fk", + "tableFrom": "notification", + "tableTo": "lark", + "columnsFrom": [ + "larkId" + ], + "columnsTo": [ + "larkId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "notification_pushoverId_pushover_pushoverId_fk": { + "name": "notification_pushoverId_pushover_pushoverId_fk", + "tableFrom": "notification", + "tableTo": "pushover", + "columnsFrom": [ + "pushoverId" + ], + "columnsTo": [ + "pushoverId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "notification_teamsId_teams_teamsId_fk": { + "name": "notification_teamsId_teams_teamsId_fk", + "tableFrom": "notification", + "tableTo": "teams", + "columnsFrom": [ + "teamsId" + ], + "columnsTo": [ + "teamsId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "notification_organizationId_organization_id_fk": { + "name": "notification_organizationId_organization_id_fk", + "tableFrom": "notification", + "tableTo": "organization", + "columnsFrom": [ + "organizationId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.ntfy": { + "name": "ntfy", + "schema": "", + "columns": { + "ntfyId": { + "name": "ntfyId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "serverUrl": { + "name": "serverUrl", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "topic": { + "name": "topic", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "accessToken": { + "name": "accessToken", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "priority": { + "name": "priority", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 3 + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.pushover": { + "name": "pushover", + "schema": "", + "columns": { + "pushoverId": { + "name": "pushoverId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "userKey": { + "name": "userKey", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "apiToken": { + "name": "apiToken", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "priority": { + "name": "priority", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "retry": { + "name": "retry", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "expire": { + "name": "expire", + "type": "integer", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.resend": { + "name": "resend", + "schema": "", + "columns": { + "resendId": { + "name": "resendId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "apiKey": { + "name": "apiKey", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "fromAddress": { + "name": "fromAddress", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "toAddress": { + "name": "toAddress", + "type": "text[]", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.slack": { + "name": "slack", + "schema": "", + "columns": { + "slackId": { + "name": "slackId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "webhookUrl": { + "name": "webhookUrl", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "channel": { + "name": "channel", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.teams": { + "name": "teams", + "schema": "", + "columns": { + "teamsId": { + "name": "teamsId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "webhookUrl": { + "name": "webhookUrl", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.telegram": { + "name": "telegram", + "schema": "", + "columns": { + "telegramId": { + "name": "telegramId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "botToken": { + "name": "botToken", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "chatId": { + "name": "chatId", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "messageThreadId": { + "name": "messageThreadId", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.patch": { + "name": "patch", + "schema": "", + "columns": { + "patchId": { + "name": "patchId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "type": { + "name": "type", + "type": "patchType", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'update'" + }, + "filePath": { + "name": "filePath", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "content": { + "name": "content", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "updatedAt": { + "name": "updatedAt", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "applicationId": { + "name": "applicationId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "composeId": { + "name": "composeId", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "patch_applicationId_application_applicationId_fk": { + "name": "patch_applicationId_application_applicationId_fk", + "tableFrom": "patch", + "tableTo": "application", + "columnsFrom": [ + "applicationId" + ], + "columnsTo": [ + "applicationId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "patch_composeId_compose_composeId_fk": { + "name": "patch_composeId_compose_composeId_fk", + "tableFrom": "patch", + "tableTo": "compose", + "columnsFrom": [ + "composeId" + ], + "columnsTo": [ + "composeId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "patch_filepath_application_unique": { + "name": "patch_filepath_application_unique", + "nullsNotDistinct": false, + "columns": [ + "filePath", + "applicationId" + ] + }, + "patch_filepath_compose_unique": { + "name": "patch_filepath_compose_unique", + "nullsNotDistinct": false, + "columns": [ + "filePath", + "composeId" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.port": { + "name": "port", + "schema": "", + "columns": { + "portId": { + "name": "portId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "publishedPort": { + "name": "publishedPort", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "publishMode": { + "name": "publishMode", + "type": "publishModeType", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'host'" + }, + "targetPort": { + "name": "targetPort", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "protocol": { + "name": "protocol", + "type": "protocolType", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "applicationId": { + "name": "applicationId", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "port_applicationId_application_applicationId_fk": { + "name": "port_applicationId_application_applicationId_fk", + "tableFrom": "port", + "tableTo": "application", + "columnsFrom": [ + "applicationId" + ], + "columnsTo": [ + "applicationId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.postgres": { + "name": "postgres", + "schema": "", + "columns": { + "postgresId": { + "name": "postgresId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "appName": { + "name": "appName", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "databaseName": { + "name": "databaseName", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "databaseUser": { + "name": "databaseUser", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "databasePassword": { + "name": "databasePassword", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "dockerImage": { + "name": "dockerImage", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "command": { + "name": "command", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "args": { + "name": "args", + "type": "text[]", + "primaryKey": false, + "notNull": false + }, + "env": { + "name": "env", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "memoryReservation": { + "name": "memoryReservation", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "externalPort": { + "name": "externalPort", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "memoryLimit": { + "name": "memoryLimit", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "cpuReservation": { + "name": "cpuReservation", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "cpuLimit": { + "name": "cpuLimit", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "applicationStatus": { + "name": "applicationStatus", + "type": "applicationStatus", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'idle'" + }, + "healthCheckSwarm": { + "name": "healthCheckSwarm", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "restartPolicySwarm": { + "name": "restartPolicySwarm", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "placementSwarm": { + "name": "placementSwarm", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "updateConfigSwarm": { + "name": "updateConfigSwarm", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "rollbackConfigSwarm": { + "name": "rollbackConfigSwarm", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "modeSwarm": { + "name": "modeSwarm", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "labelsSwarm": { + "name": "labelsSwarm", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "networkSwarm": { + "name": "networkSwarm", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "stopGracePeriodSwarm": { + "name": "stopGracePeriodSwarm", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "endpointSpecSwarm": { + "name": "endpointSpecSwarm", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "ulimitsSwarm": { + "name": "ulimitsSwarm", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "replicas": { + "name": "replicas", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "createdAt": { + "name": "createdAt", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "environmentId": { + "name": "environmentId", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "serverId": { + "name": "serverId", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "postgres_environmentId_environment_environmentId_fk": { + "name": "postgres_environmentId_environment_environmentId_fk", + "tableFrom": "postgres", + "tableTo": "environment", + "columnsFrom": [ + "environmentId" + ], + "columnsTo": [ + "environmentId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "postgres_serverId_server_serverId_fk": { + "name": "postgres_serverId_server_serverId_fk", + "tableFrom": "postgres", + "tableTo": "server", + "columnsFrom": [ + "serverId" + ], + "columnsTo": [ + "serverId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "postgres_appName_unique": { + "name": "postgres_appName_unique", + "nullsNotDistinct": false, + "columns": [ + "appName" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.preview_deployments": { + "name": "preview_deployments", + "schema": "", + "columns": { + "previewDeploymentId": { + "name": "previewDeploymentId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "branch": { + "name": "branch", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "pullRequestId": { + "name": "pullRequestId", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "pullRequestNumber": { + "name": "pullRequestNumber", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "pullRequestURL": { + "name": "pullRequestURL", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "pullRequestTitle": { + "name": "pullRequestTitle", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "pullRequestCommentId": { + "name": "pullRequestCommentId", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "previewStatus": { + "name": "previewStatus", + "type": "applicationStatus", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'idle'" + }, + "appName": { + "name": "appName", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "applicationId": { + "name": "applicationId", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "domainId": { + "name": "domainId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "createdAt": { + "name": "createdAt", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "expiresAt": { + "name": "expiresAt", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "preview_deployments_applicationId_application_applicationId_fk": { + "name": "preview_deployments_applicationId_application_applicationId_fk", + "tableFrom": "preview_deployments", + "tableTo": "application", + "columnsFrom": [ + "applicationId" + ], + "columnsTo": [ + "applicationId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "preview_deployments_domainId_domain_domainId_fk": { + "name": "preview_deployments_domainId_domain_domainId_fk", + "tableFrom": "preview_deployments", + "tableTo": "domain", + "columnsFrom": [ + "domainId" + ], + "columnsTo": [ + "domainId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "preview_deployments_appName_unique": { + "name": "preview_deployments_appName_unique", + "nullsNotDistinct": false, + "columns": [ + "appName" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.project": { + "name": "project", + "schema": "", + "columns": { + "projectId": { + "name": "projectId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "createdAt": { + "name": "createdAt", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "organizationId": { + "name": "organizationId", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "env": { + "name": "env", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + } + }, + "indexes": {}, + "foreignKeys": { + "project_organizationId_organization_id_fk": { + "name": "project_organizationId_organization_id_fk", + "tableFrom": "project", + "tableTo": "organization", + "columnsFrom": [ + "organizationId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.redirect": { + "name": "redirect", + "schema": "", + "columns": { + "redirectId": { + "name": "redirectId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "regex": { + "name": "regex", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "replacement": { + "name": "replacement", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "permanent": { + "name": "permanent", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "uniqueConfigKey": { + "name": "uniqueConfigKey", + "type": "serial", + "primaryKey": false, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "applicationId": { + "name": "applicationId", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "redirect_applicationId_application_applicationId_fk": { + "name": "redirect_applicationId_application_applicationId_fk", + "tableFrom": "redirect", + "tableTo": "application", + "columnsFrom": [ + "applicationId" + ], + "columnsTo": [ + "applicationId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.redis": { + "name": "redis", + "schema": "", + "columns": { + "redisId": { + "name": "redisId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "appName": { + "name": "appName", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "password": { + "name": "password", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "dockerImage": { + "name": "dockerImage", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "command": { + "name": "command", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "args": { + "name": "args", + "type": "text[]", + "primaryKey": false, + "notNull": false + }, + "env": { + "name": "env", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "memoryReservation": { + "name": "memoryReservation", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "memoryLimit": { + "name": "memoryLimit", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "cpuReservation": { + "name": "cpuReservation", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "cpuLimit": { + "name": "cpuLimit", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "externalPort": { + "name": "externalPort", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "createdAt": { + "name": "createdAt", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "applicationStatus": { + "name": "applicationStatus", + "type": "applicationStatus", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'idle'" + }, + "healthCheckSwarm": { + "name": "healthCheckSwarm", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "restartPolicySwarm": { + "name": "restartPolicySwarm", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "placementSwarm": { + "name": "placementSwarm", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "updateConfigSwarm": { + "name": "updateConfigSwarm", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "rollbackConfigSwarm": { + "name": "rollbackConfigSwarm", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "modeSwarm": { + "name": "modeSwarm", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "labelsSwarm": { + "name": "labelsSwarm", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "networkSwarm": { + "name": "networkSwarm", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "stopGracePeriodSwarm": { + "name": "stopGracePeriodSwarm", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "endpointSpecSwarm": { + "name": "endpointSpecSwarm", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "ulimitsSwarm": { + "name": "ulimitsSwarm", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "replicas": { + "name": "replicas", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "environmentId": { + "name": "environmentId", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "serverId": { + "name": "serverId", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "redis_environmentId_environment_environmentId_fk": { + "name": "redis_environmentId_environment_environmentId_fk", + "tableFrom": "redis", + "tableTo": "environment", + "columnsFrom": [ + "environmentId" + ], + "columnsTo": [ + "environmentId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "redis_serverId_server_serverId_fk": { + "name": "redis_serverId_server_serverId_fk", + "tableFrom": "redis", + "tableTo": "server", + "columnsFrom": [ + "serverId" + ], + "columnsTo": [ + "serverId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "redis_appName_unique": { + "name": "redis_appName_unique", + "nullsNotDistinct": false, + "columns": [ + "appName" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.registry": { + "name": "registry", + "schema": "", + "columns": { + "registryId": { + "name": "registryId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "registryName": { + "name": "registryName", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "imagePrefix": { + "name": "imagePrefix", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "username": { + "name": "username", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "password": { + "name": "password", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "registryUrl": { + "name": "registryUrl", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "createdAt": { + "name": "createdAt", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "selfHosted": { + "name": "selfHosted", + "type": "RegistryType", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'cloud'" + }, + "organizationId": { + "name": "organizationId", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "registry_organizationId_organization_id_fk": { + "name": "registry_organizationId_organization_id_fk", + "tableFrom": "registry", + "tableTo": "organization", + "columnsFrom": [ + "organizationId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.rollback": { + "name": "rollback", + "schema": "", + "columns": { + "rollbackId": { + "name": "rollbackId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "deploymentId": { + "name": "deploymentId", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "version": { + "name": "version", + "type": "serial", + "primaryKey": false, + "notNull": true + }, + "image": { + "name": "image", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "createdAt": { + "name": "createdAt", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "fullContext": { + "name": "fullContext", + "type": "jsonb", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "rollback_deploymentId_deployment_deploymentId_fk": { + "name": "rollback_deploymentId_deployment_deploymentId_fk", + "tableFrom": "rollback", + "tableTo": "deployment", + "columnsFrom": [ + "deploymentId" + ], + "columnsTo": [ + "deploymentId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.schedule": { + "name": "schedule", + "schema": "", + "columns": { + "scheduleId": { + "name": "scheduleId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "cronExpression": { + "name": "cronExpression", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "appName": { + "name": "appName", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "serviceName": { + "name": "serviceName", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "shellType": { + "name": "shellType", + "type": "shellType", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'bash'" + }, + "scheduleType": { + "name": "scheduleType", + "type": "scheduleType", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'application'" + }, + "command": { + "name": "command", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "script": { + "name": "script", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "applicationId": { + "name": "applicationId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "composeId": { + "name": "composeId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "serverId": { + "name": "serverId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "userId": { + "name": "userId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "timezone": { + "name": "timezone", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "createdAt": { + "name": "createdAt", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "schedule_applicationId_application_applicationId_fk": { + "name": "schedule_applicationId_application_applicationId_fk", + "tableFrom": "schedule", + "tableTo": "application", + "columnsFrom": [ + "applicationId" + ], + "columnsTo": [ + "applicationId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "schedule_composeId_compose_composeId_fk": { + "name": "schedule_composeId_compose_composeId_fk", + "tableFrom": "schedule", + "tableTo": "compose", + "columnsFrom": [ + "composeId" + ], + "columnsTo": [ + "composeId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "schedule_serverId_server_serverId_fk": { + "name": "schedule_serverId_server_serverId_fk", + "tableFrom": "schedule", + "tableTo": "server", + "columnsFrom": [ + "serverId" + ], + "columnsTo": [ + "serverId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "schedule_userId_user_id_fk": { + "name": "schedule_userId_user_id_fk", + "tableFrom": "schedule", + "tableTo": "user", + "columnsFrom": [ + "userId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.security": { + "name": "security", + "schema": "", + "columns": { + "securityId": { + "name": "securityId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "username": { + "name": "username", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "password": { + "name": "password", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "applicationId": { + "name": "applicationId", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "security_applicationId_application_applicationId_fk": { + "name": "security_applicationId_application_applicationId_fk", + "tableFrom": "security", + "tableTo": "application", + "columnsFrom": [ + "applicationId" + ], + "columnsTo": [ + "applicationId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "security_username_applicationId_unique": { + "name": "security_username_applicationId_unique", + "nullsNotDistinct": false, + "columns": [ + "username", + "applicationId" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.server": { + "name": "server", + "schema": "", + "columns": { + "serverId": { + "name": "serverId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "ipAddress": { + "name": "ipAddress", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "port": { + "name": "port", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "username": { + "name": "username", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'root'" + }, + "appName": { + "name": "appName", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "enableDockerCleanup": { + "name": "enableDockerCleanup", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "createdAt": { + "name": "createdAt", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "organizationId": { + "name": "organizationId", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "serverStatus": { + "name": "serverStatus", + "type": "serverStatus", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "serverType": { + "name": "serverType", + "type": "serverType", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'deploy'" + }, + "command": { + "name": "command", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "sshKeyId": { + "name": "sshKeyId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "metricsConfig": { + "name": "metricsConfig", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{\"server\":{\"type\":\"Remote\",\"refreshRate\":60,\"port\":4500,\"token\":\"\",\"urlCallback\":\"\",\"cronJob\":\"\",\"retentionDays\":2,\"thresholds\":{\"cpu\":0,\"memory\":0}},\"containers\":{\"refreshRate\":60,\"services\":{\"include\":[],\"exclude\":[]}}}'::jsonb" + } + }, + "indexes": {}, + "foreignKeys": { + "server_organizationId_organization_id_fk": { + "name": "server_organizationId_organization_id_fk", + "tableFrom": "server", + "tableTo": "organization", + "columnsFrom": [ + "organizationId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "server_sshKeyId_ssh-key_sshKeyId_fk": { + "name": "server_sshKeyId_ssh-key_sshKeyId_fk", + "tableFrom": "server", + "tableTo": "ssh-key", + "columnsFrom": [ + "sshKeyId" + ], + "columnsTo": [ + "sshKeyId" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.session_temp": { + "name": "session_temp", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "ip_address": { + "name": "ip_address", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_agent": { + "name": "user_agent", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "impersonated_by": { + "name": "impersonated_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "active_organization_id": { + "name": "active_organization_id", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "session_temp_user_id_user_id_fk": { + "name": "session_temp_user_id_user_id_fk", + "tableFrom": "session_temp", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "session_temp_token_unique": { + "name": "session_temp_token_unique", + "nullsNotDistinct": false, + "columns": [ + "token" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.ssh-key": { + "name": "ssh-key", + "schema": "", + "columns": { + "sshKeyId": { + "name": "sshKeyId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "privateKey": { + "name": "privateKey", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "publicKey": { + "name": "publicKey", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "createdAt": { + "name": "createdAt", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "lastUsedAt": { + "name": "lastUsedAt", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "organizationId": { + "name": "organizationId", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "ssh-key_organizationId_organization_id_fk": { + "name": "ssh-key_organizationId_organization_id_fk", + "tableFrom": "ssh-key", + "tableTo": "organization", + "columnsFrom": [ + "organizationId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.sso_provider": { + "name": "sso_provider", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "issuer": { + "name": "issuer", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "oidc_config": { + "name": "oidc_config", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "saml_config": { + "name": "saml_config", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "provider_id": { + "name": "provider_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "domain": { + "name": "domain", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "sso_provider_user_id_user_id_fk": { + "name": "sso_provider_user_id_user_id_fk", + "tableFrom": "sso_provider", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "sso_provider_organization_id_organization_id_fk": { + "name": "sso_provider_organization_id_organization_id_fk", + "tableFrom": "sso_provider", + "tableTo": "organization", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "sso_provider_provider_id_unique": { + "name": "sso_provider_provider_id_unique", + "nullsNotDistinct": false, + "columns": [ + "provider_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.user": { + "name": "user", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "firstName": { + "name": "firstName", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "lastName": { + "name": "lastName", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "isRegistered": { + "name": "isRegistered", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "expirationDate": { + "name": "expirationDate", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "two_factor_enabled": { + "name": "two_factor_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "email_verified": { + "name": "email_verified", + "type": "boolean", + "primaryKey": false, + "notNull": true + }, + "image": { + "name": "image", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "banned": { + "name": "banned", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "ban_reason": { + "name": "ban_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "ban_expires": { + "name": "ban_expires", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'user'" + }, + "enablePaidFeatures": { + "name": "enablePaidFeatures", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "allowImpersonation": { + "name": "allowImpersonation", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "enableEnterpriseFeatures": { + "name": "enableEnterpriseFeatures", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "licenseKey": { + "name": "licenseKey", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "isValidEnterpriseLicense": { + "name": "isValidEnterpriseLicense", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "stripeCustomerId": { + "name": "stripeCustomerId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "stripeSubscriptionId": { + "name": "stripeSubscriptionId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "serversQuantity": { + "name": "serversQuantity", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "trustedOrigins": { + "name": "trustedOrigins", + "type": "text[]", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "user_email_unique": { + "name": "user_email_unique", + "nullsNotDistinct": false, + "columns": [ + "email" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.volume_backup": { + "name": "volume_backup", + "schema": "", + "columns": { + "volumeBackupId": { + "name": "volumeBackupId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "volumeName": { + "name": "volumeName", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "prefix": { + "name": "prefix", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "serviceType": { + "name": "serviceType", + "type": "serviceType", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'application'" + }, + "appName": { + "name": "appName", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "serviceName": { + "name": "serviceName", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "turnOff": { + "name": "turnOff", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "cronExpression": { + "name": "cronExpression", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "keepLatestCount": { + "name": "keepLatestCount", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "applicationId": { + "name": "applicationId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "postgresId": { + "name": "postgresId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "mariadbId": { + "name": "mariadbId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "mongoId": { + "name": "mongoId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "mysqlId": { + "name": "mysqlId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "redisId": { + "name": "redisId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "composeId": { + "name": "composeId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "createdAt": { + "name": "createdAt", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "destinationId": { + "name": "destinationId", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "volume_backup_applicationId_application_applicationId_fk": { + "name": "volume_backup_applicationId_application_applicationId_fk", + "tableFrom": "volume_backup", + "tableTo": "application", + "columnsFrom": [ + "applicationId" + ], + "columnsTo": [ + "applicationId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "volume_backup_postgresId_postgres_postgresId_fk": { + "name": "volume_backup_postgresId_postgres_postgresId_fk", + "tableFrom": "volume_backup", + "tableTo": "postgres", + "columnsFrom": [ + "postgresId" + ], + "columnsTo": [ + "postgresId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "volume_backup_mariadbId_mariadb_mariadbId_fk": { + "name": "volume_backup_mariadbId_mariadb_mariadbId_fk", + "tableFrom": "volume_backup", + "tableTo": "mariadb", + "columnsFrom": [ + "mariadbId" + ], + "columnsTo": [ + "mariadbId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "volume_backup_mongoId_mongo_mongoId_fk": { + "name": "volume_backup_mongoId_mongo_mongoId_fk", + "tableFrom": "volume_backup", + "tableTo": "mongo", + "columnsFrom": [ + "mongoId" + ], + "columnsTo": [ + "mongoId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "volume_backup_mysqlId_mysql_mysqlId_fk": { + "name": "volume_backup_mysqlId_mysql_mysqlId_fk", + "tableFrom": "volume_backup", + "tableTo": "mysql", + "columnsFrom": [ + "mysqlId" + ], + "columnsTo": [ + "mysqlId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "volume_backup_redisId_redis_redisId_fk": { + "name": "volume_backup_redisId_redis_redisId_fk", + "tableFrom": "volume_backup", + "tableTo": "redis", + "columnsFrom": [ + "redisId" + ], + "columnsTo": [ + "redisId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "volume_backup_composeId_compose_composeId_fk": { + "name": "volume_backup_composeId_compose_composeId_fk", + "tableFrom": "volume_backup", + "tableTo": "compose", + "columnsFrom": [ + "composeId" + ], + "columnsTo": [ + "composeId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "volume_backup_destinationId_destination_destinationId_fk": { + "name": "volume_backup_destinationId_destination_destinationId_fk", + "tableFrom": "volume_backup", + "tableTo": "destination", + "columnsFrom": [ + "destinationId" + ], + "columnsTo": [ + "destinationId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.webServerSettings": { + "name": "webServerSettings", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "serverIp": { + "name": "serverIp", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "certificateType": { + "name": "certificateType", + "type": "certificateType", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'none'" + }, + "https": { + "name": "https", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "host": { + "name": "host", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "letsEncryptEmail": { + "name": "letsEncryptEmail", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "sshPrivateKey": { + "name": "sshPrivateKey", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "enableDockerCleanup": { + "name": "enableDockerCleanup", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "logCleanupCron": { + "name": "logCleanupCron", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "'0 0 * * *'" + }, + "metricsConfig": { + "name": "metricsConfig", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{\"server\":{\"type\":\"Dokploy\",\"refreshRate\":60,\"port\":4500,\"token\":\"\",\"retentionDays\":2,\"cronJob\":\"\",\"urlCallback\":\"\",\"thresholds\":{\"cpu\":0,\"memory\":0}},\"containers\":{\"refreshRate\":60,\"services\":{\"include\":[],\"exclude\":[]}}}'::jsonb" + }, + "cleanupCacheApplications": { + "name": "cleanupCacheApplications", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "cleanupCacheOnPreviews": { + "name": "cleanupCacheOnPreviews", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "cleanupCacheOnCompose": { + "name": "cleanupCacheOnCompose", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + } + }, + "enums": { + "public.buildType": { + "name": "buildType", + "schema": "public", + "values": [ + "dockerfile", + "heroku_buildpacks", + "paketo_buildpacks", + "nixpacks", + "static", + "railpack" + ] + }, + "public.sourceType": { + "name": "sourceType", + "schema": "public", + "values": [ + "docker", + "git", + "github", + "gitlab", + "bitbucket", + "gitea", + "drop" + ] + }, + "public.backupType": { + "name": "backupType", + "schema": "public", + "values": [ + "database", + "compose" + ] + }, + "public.databaseType": { + "name": "databaseType", + "schema": "public", + "values": [ + "postgres", + "mariadb", + "mysql", + "mongo", + "web-server" + ] + }, + "public.composeType": { + "name": "composeType", + "schema": "public", + "values": [ + "docker-compose", + "stack" + ] + }, + "public.sourceTypeCompose": { + "name": "sourceTypeCompose", + "schema": "public", + "values": [ + "git", + "github", + "gitlab", + "bitbucket", + "gitea", + "raw" + ] + }, + "public.deploymentStatus": { + "name": "deploymentStatus", + "schema": "public", + "values": [ + "running", + "done", + "error", + "cancelled" + ] + }, + "public.domainType": { + "name": "domainType", + "schema": "public", + "values": [ + "compose", + "application", + "preview" + ] + }, + "public.gitProviderType": { + "name": "gitProviderType", + "schema": "public", + "values": [ + "github", + "gitlab", + "bitbucket", + "gitea" + ] + }, + "public.mountType": { + "name": "mountType", + "schema": "public", + "values": [ + "bind", + "volume", + "file" + ] + }, + "public.serviceType": { + "name": "serviceType", + "schema": "public", + "values": [ + "application", + "postgres", + "mysql", + "mariadb", + "mongo", + "redis", + "compose" + ] + }, + "public.networkDriver": { + "name": "networkDriver", + "schema": "public", + "values": [ + "bridge", + "host", + "overlay", + "macvlan", + "none", + "ipvlan" + ] + }, + "public.notificationType": { + "name": "notificationType", + "schema": "public", + "values": [ + "slack", + "telegram", + "discord", + "email", + "resend", + "gotify", + "ntfy", + "pushover", + "custom", + "lark", + "teams" + ] + }, + "public.patchType": { + "name": "patchType", + "schema": "public", + "values": [ + "create", + "update", + "delete" + ] + }, + "public.protocolType": { + "name": "protocolType", + "schema": "public", + "values": [ + "tcp", + "udp" + ] + }, + "public.publishModeType": { + "name": "publishModeType", + "schema": "public", + "values": [ + "ingress", + "host" + ] + }, + "public.RegistryType": { + "name": "RegistryType", + "schema": "public", + "values": [ + "selfHosted", + "cloud" + ] + }, + "public.scheduleType": { + "name": "scheduleType", + "schema": "public", + "values": [ + "application", + "compose", + "server", + "dokploy-server" + ] + }, + "public.shellType": { + "name": "shellType", + "schema": "public", + "values": [ + "bash", + "sh" + ] + }, + "public.serverStatus": { + "name": "serverStatus", + "schema": "public", + "values": [ + "active", + "inactive" + ] + }, + "public.serverType": { + "name": "serverType", + "schema": "public", + "values": [ + "deploy", + "build" + ] + }, + "public.applicationStatus": { + "name": "applicationStatus", + "schema": "public", + "values": [ + "idle", + "running", + "done", + "error" + ] + }, + "public.certificateType": { + "name": "certificateType", + "schema": "public", + "values": [ + "letsencrypt", + "none", + "custom" + ] + }, + "public.triggerType": { + "name": "triggerType", + "schema": "public", + "values": [ + "push", + "tag" + ] + } + }, + "schemas": {}, + "sequences": {}, + "roles": {}, + "policies": {}, + "views": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} \ No newline at end of file diff --git a/apps/dokploy/drizzle/meta/_journal.json b/apps/dokploy/drizzle/meta/_journal.json index b09199e7f..0c893edbb 100644 --- a/apps/dokploy/drizzle/meta/_journal.json +++ b/apps/dokploy/drizzle/meta/_journal.json @@ -1023,6 +1023,13 @@ "when": 1771447229358, "tag": "0145_remarkable_titania", "breakpoints": true + }, + { + "idx": 146, + "version": "7", + "when": 1771621786767, + "tag": "0146_stormy_ender_wiggin", + "breakpoints": true } ] } \ No newline at end of file diff --git a/apps/dokploy/pages/dashboard/networks.tsx b/apps/dokploy/pages/dashboard/networks.tsx new file mode 100644 index 000000000..d9d65cd8e --- /dev/null +++ b/apps/dokploy/pages/dashboard/networks.tsx @@ -0,0 +1,84 @@ +import { IS_CLOUD } from "@dokploy/server/constants"; +import { validateRequest } from "@dokploy/server/lib/auth"; +import { createServerSideHelpers } from "@trpc/react-query/server"; +import type { GetServerSidePropsContext } from "next"; +import type { ReactElement } from "react"; +import superjson from "superjson"; +import { ShowNetworks } from "@/components/dashboard/networks/show-networks"; +import { DashboardLayout } from "@/components/layouts/dashboard-layout"; +import { appRouter } from "@/server/api/root"; + +const Dashboard = () => { + return ; +}; + +export default Dashboard; + +Dashboard.getLayout = (page: ReactElement) => { + return {page}; +}; + +export async function getServerSideProps( + ctx: GetServerSidePropsContext<{ serviceId: string }>, +) { + if (IS_CLOUD) { + return { + redirect: { + permanent: true, + destination: "/dashboard/projects", + }, + }; + } + const { user, session } = await validateRequest(ctx.req); + if (!user) { + return { + redirect: { + permanent: true, + destination: "/", + }, + }; + } + const { req } = ctx; + + const helpers = createServerSideHelpers({ + router: appRouter, + ctx: { + req: req as any, + res: ctx.res as any, + db: null as any, + session: session as any, + user: user as any, + }, + transformer: superjson, + }); + try { + await helpers.project.all.prefetch(); + + if (user.role === "member") { + const userR = await helpers.user.one.fetch({ + userId: user.id, + }); + + if (!userR?.canAccessToDocker) { + return { + redirect: { + permanent: true, + destination: "/", + }, + }; + } + } + + await helpers.network.all.prefetch(); + + return { + props: { + trpcState: helpers.dehydrate(), + }, + }; + } catch { + return { + props: {}, + }; + } +} diff --git a/apps/dokploy/server/api/root.ts b/apps/dokploy/server/api/root.ts index f123fde85..c4deb3593 100644 --- a/apps/dokploy/server/api/root.ts +++ b/apps/dokploy/server/api/root.ts @@ -20,6 +20,7 @@ import { mariadbRouter } from "./routers/mariadb"; import { mongoRouter } from "./routers/mongo"; import { mountRouter } from "./routers/mount"; import { mysqlRouter } from "./routers/mysql"; +import { networkRouter } from "./routers/network"; import { notificationRouter } from "./routers/notification"; import { organizationRouter } from "./routers/organization"; import { patchRouter } from "./routers/patch"; @@ -66,6 +67,7 @@ export const appRouter = createTRPCRouter({ deployment: deploymentRouter, previewDeployment: previewDeploymentRouter, mounts: mountRouter, + network: networkRouter, certificates: certificateRouter, settings: settingsRouter, security: securityRouter, diff --git a/apps/dokploy/server/api/routers/network.ts b/apps/dokploy/server/api/routers/network.ts new file mode 100644 index 000000000..46da4d79d --- /dev/null +++ b/apps/dokploy/server/api/routers/network.ts @@ -0,0 +1,71 @@ +import { + createNetwork, + findNetworkById, + removeNetwork, + updateNetwork, +} from "@dokploy/server"; +import { TRPCError } from "@trpc/server"; +import { desc, eq } from "drizzle-orm"; +import { createTRPCRouter, protectedProcedure } from "@/server/api/trpc"; +import { db } from "@/server/db"; +import { + apiCreateNetwork, + apiFindOneNetwork, + apiRemoveNetwork, + apiUpdateNetwork, + network as networkTable, +} from "@/server/db/schema"; + +export const networkRouter = createTRPCRouter({ + all: protectedProcedure.query(async ({ ctx }) => { + const rows = await db + .select() + .from(networkTable) + .where(eq(networkTable.organizationId, ctx.session.activeOrganizationId)) + .orderBy(desc(networkTable.createdAt)); + return rows; + }), + + one: protectedProcedure + .input(apiFindOneNetwork) + .query(async ({ ctx, input }) => { + const row = await findNetworkById(input.networkId); + if (row.organizationId !== ctx.session.activeOrganizationId) { + throw new TRPCError({ + code: "NOT_FOUND", + message: "Network not found", + }); + } + return row; + }), + create: protectedProcedure + .input(apiCreateNetwork) + .mutation(async ({ ctx, input }) => { + return createNetwork(input, ctx.session.activeOrganizationId); + }), + update: protectedProcedure + .input(apiUpdateNetwork) + .mutation(async ({ ctx, input }) => { + const network = await findNetworkById(input.networkId); + if (network.organizationId !== ctx.session.activeOrganizationId) { + throw new TRPCError({ + code: "UNAUTHORIZED", + message: "Not authorized to update this network", + }); + } + return updateNetwork(input); + }), + + remove: protectedProcedure + .input(apiRemoveNetwork) + .mutation(async ({ ctx, input }) => { + const network = await findNetworkById(input.networkId); + if (network.organizationId !== ctx.session.activeOrganizationId) { + throw new TRPCError({ + code: "UNAUTHORIZED", + message: "Not authorized to delete this network", + }); + } + return removeNetwork(input.networkId); + }), +}); diff --git a/packages/server/src/db/schema/account.ts b/packages/server/src/db/schema/account.ts index 6814613e5..0680c76ff 100644 --- a/packages/server/src/db/schema/account.ts +++ b/packages/server/src/db/schema/account.ts @@ -7,6 +7,7 @@ import { timestamp, } from "drizzle-orm/pg-core"; import { nanoid } from "nanoid"; +import { network } from "./network"; import { projects } from "./project"; import { server } from "./server"; import { ssoProvider } from "./sso"; @@ -77,6 +78,7 @@ export const organizationRelations = relations( references: [user.id], }), servers: many(server), + networks: many(network), projects: many(projects), members: many(member), ssoProviders: many(ssoProvider), diff --git a/packages/server/src/db/schema/index.ts b/packages/server/src/db/schema/index.ts index fece17f02..42330d308 100644 --- a/packages/server/src/db/schema/index.ts +++ b/packages/server/src/db/schema/index.ts @@ -16,6 +16,7 @@ export * from "./gitlab"; export * from "./mariadb"; export * from "./mongo"; export * from "./mount"; +export * from "./network"; export * from "./mysql"; export * from "./notification"; export * from "./patch"; diff --git a/packages/server/src/db/schema/network.ts b/packages/server/src/db/schema/network.ts new file mode 100644 index 000000000..2341f3cdb --- /dev/null +++ b/packages/server/src/db/schema/network.ts @@ -0,0 +1,137 @@ +import { relations } from "drizzle-orm"; +import { boolean, jsonb, pgEnum, pgTable, text } from "drizzle-orm/pg-core"; +import { createInsertSchema } from "drizzle-zod"; +import { nanoid } from "nanoid"; +import { z } from "zod"; +import { organization } from "./account"; +import { server } from "./server"; + +/** Docker network driver types */ +export const networkDriver = pgEnum("networkDriver", [ + "bridge", + "host", + "overlay", + "macvlan", + "none", + "ipvlan", +]); + +export const network = pgTable("network", { + networkId: text("networkId") + .notNull() + .primaryKey() + .$defaultFn(() => nanoid()), + name: text("name").notNull(), + driver: networkDriver("driver").notNull().default("bridge"), + scope: text("scope"), // e.g. "local", "swarm" + internal: boolean("internal").notNull().default(false), + attachable: boolean("attachable").notNull().default(false), + ingress: boolean("ingress").notNull().default(false), + configOnly: boolean("configOnly").notNull().default(false), + enableIPv4: boolean("enableIPv4").notNull().default(true), + enableIPv6: boolean("enableIPv6").notNull().default(false), + ipam: jsonb("ipam") + .$type<{ + driver?: string; + config?: Array<{ subnet?: string; gateway?: string; ipRange?: string }>; + }>() + .default({}), + createdAt: text("createdAt") + .notNull() + .$defaultFn(() => new Date().toISOString()), + organizationId: text("organizationId") + .notNull() + .references(() => organization.id, { onDelete: "cascade" }), + serverId: text("serverId").references(() => server.serverId, { + onDelete: "cascade", + }), +}); + +export const networkRelations = relations(network, ({ one }) => ({ + organization: one(organization, { + fields: [network.organizationId], + references: [organization.id], + }), + server: one(server, { + fields: [network.serverId], + references: [server.serverId], + }), +})); + +const createSchema = createInsertSchema(network, { + networkId: z.string().min(1), + name: z.string().min(1), + driver: z + .enum(["bridge", "host", "overlay", "macvlan", "none", "ipvlan"]) + .optional(), + scope: z.string().optional(), + internal: z.boolean().optional(), + attachable: z.boolean().optional(), + ingress: z.boolean().optional(), + configOnly: z.boolean().optional(), + enableIPv4: z.boolean().optional(), + enableIPv6: z.boolean().optional(), + ipam: z + .object({ + driver: z.string().optional(), + config: z + .array( + z.object({ + subnet: z.string().optional(), + gateway: z.string().optional(), + ipRange: z.string().optional(), + }), + ) + .optional(), + }) + .optional(), + organizationId: z.string().min(1), + serverId: z.string().optional().nullable(), +}); + +export const apiCreateNetwork = createSchema + .pick({ + name: true, + driver: true, + scope: true, + internal: true, + attachable: true, + ingress: true, + configOnly: true, + enableIPv4: true, + enableIPv6: true, + ipam: true, + serverId: true, + }) + .partial() + .required({ name: true }); + +export const apiFindOneNetwork = createSchema + .pick({ + networkId: true, + }) + .required(); + +export const apiRemoveNetwork = createSchema + .pick({ + networkId: true, + }) + .required(); + +export const apiUpdateNetwork = createSchema + .pick({ + networkId: true, + name: true, + driver: true, + scope: true, + internal: true, + attachable: true, + ingress: true, + configOnly: true, + enableIPv4: true, + enableIPv6: true, + ipam: true, + serverId: true, + }) + .partial() + .required({ networkId: true }); diff --git a/packages/server/src/db/schema/server.ts b/packages/server/src/db/schema/server.ts index 176f35948..be95e74f7 100644 --- a/packages/server/src/db/schema/server.ts +++ b/packages/server/src/db/schema/server.ts @@ -18,6 +18,7 @@ import { deployments } from "./deployment"; import { mariadb } from "./mariadb"; import { mongo } from "./mongo"; import { mysql } from "./mysql"; +import { network } from "./network"; import { postgres } from "./postgres"; import { redis } from "./redis"; import { schedules } from "./schedule"; @@ -122,6 +123,7 @@ export const serverRelations = relations(server, ({ one, many }) => ({ mysql: many(mysql), postgres: many(postgres), certificates: many(certificates), + networks: many(network), organization: one(organization, { fields: [server.organizationId], references: [organization.id], diff --git a/packages/server/src/index.ts b/packages/server/src/index.ts index 9adc9081e..1f2abbc98 100644 --- a/packages/server/src/index.ts +++ b/packages/server/src/index.ts @@ -26,6 +26,7 @@ export * from "./services/mariadb"; export * from "./services/mongo"; export * from "./services/mount"; export * from "./services/mysql"; +export * from "./services/network"; export * from "./services/notification"; export * from "./services/patch"; export * from "./services/patch-repo"; diff --git a/packages/server/src/services/network.ts b/packages/server/src/services/network.ts new file mode 100644 index 000000000..a6879bed9 --- /dev/null +++ b/packages/server/src/services/network.ts @@ -0,0 +1,114 @@ +import { db } from "@dokploy/server/db"; +import { + type apiCreateNetwork, + type apiUpdateNetwork, + network, +} from "@dokploy/server/db/schema"; +import { TRPCError } from "@trpc/server"; +import { eq } from "drizzle-orm"; +import { getRemoteDocker } from "../utils/servers/remote-docker"; + +export const findNetworkById = async (networkId: string) => { + const [row] = await db + .select() + .from(network) + .where(eq(network.networkId, networkId)) + .limit(1); + + if (!row) { + throw new TRPCError({ + code: "NOT_FOUND", + message: "Network not found", + }); + } + + return row; +}; + +export const createNetwork = async ( + input: typeof apiCreateNetwork._type, + organizationId: string, +) => { + const created = await db.transaction(async (tx) => { + const [row] = await tx + .insert(network) + .values({ + ...input, + organizationId, + }) + .returning(); + + if (!row) { + throw new TRPCError({ + code: "INTERNAL_SERVER_ERROR", + message: "Failed to create network", + }); + } + + const ipam = row.ipam ?? {}; + const ipamConfig = (ipam.config ?? []) + .map((c) => { + const entry: Record = {}; + if (c.subnet) entry.Subnet = c.subnet; + if (c.gateway) entry.Gateway = c.gateway; + if (c.ipRange) entry.IPRange = c.ipRange; + return entry; + }) + .filter((e) => Object.keys(e).length > 0); + + const docker = await getRemoteDocker(input.serverId ?? null); + await docker.createNetwork({ + Name: row.name, + Driver: row.driver, + Internal: row.internal, + Attachable: row.attachable, + Ingress: row.ingress, + EnableIPv6: row.enableIPv6, + IPAM: + ipamConfig.length > 0 || ipam.driver + ? { + Driver: ipam.driver ?? "default", + Config: ipamConfig.length > 0 ? ipamConfig : undefined, + } + : undefined, + }); + + return row; + }); + + return created; +}; + +export const updateNetwork = async (input: typeof apiUpdateNetwork._type) => { + const { networkId, ...rest } = input; + const [updated] = await db + .update(network) + .set(rest) + .where(eq(network.networkId, networkId)) + .returning(); + + if (!updated) { + throw new TRPCError({ + code: "NOT_FOUND", + message: "Network not found", + }); + } + + return updated; +}; + +export const removeNetwork = async (networkId: string) => { + const [deleted] = await db + .delete(network) + .where(eq(network.networkId, networkId)) + .returning(); + + if (!deleted) { + throw new TRPCError({ + code: "NOT_FOUND", + message: "Network not found", + }); + } + + return deleted; +}; From a0566cdbd777f0d3e14052b18d1800196bbd6687 Mon Sep 17 00:00:00 2001 From: Mauricio Siu Date: Sat, 21 Feb 2026 14:59:50 -0600 Subject: [PATCH 002/585] refactor: remove unused server value and update network handling logic - Eliminated the unused DOKPLOY_SERVER_VALUE constant from the network handling component. - Updated the default serverId to undefined in the network form values and adjusted related logic to ensure compatibility with cloud environments. - Enhanced server validation in the createNetwork function to require a serverId when in cloud mode, improving error handling for network creation. --- .../dashboard/networks/handle-network.tsx | 13 +++++-------- packages/server/src/services/network.ts | 19 ++++++++++++------- 2 files changed, 17 insertions(+), 15 deletions(-) diff --git a/apps/dokploy/components/dashboard/networks/handle-network.tsx b/apps/dokploy/components/dashboard/networks/handle-network.tsx index 8af7db795..86d328b8e 100644 --- a/apps/dokploy/components/dashboard/networks/handle-network.tsx +++ b/apps/dokploy/components/dashboard/networks/handle-network.tsx @@ -48,9 +48,6 @@ const networkDriverEnum = [ /** Sentinel for "no scope" */ const SCOPE_EMPTY = "__scope_none__"; -/** Value for "Dokploy server" (local / no specific server). Not used in cloud. */ -const DOKPLOY_SERVER_VALUE = "__dokploy_server__"; - const ipamConfigEntrySchema = z.object({ subnet: z.string().optional(), ipRange: z.string().optional(), @@ -78,7 +75,7 @@ const defaultValues: NetworkFormValues = { name: "", driver: "bridge", scope: SCOPE_EMPTY, - serverId: DOKPLOY_SERVER_VALUE, + serverId: undefined, internal: false, attachable: false, ingress: false, @@ -134,7 +131,7 @@ export const HandleNetwork = ({ networkId, children }: HandleNetworkProps) => { name: network.name, driver: network.driver, scope: network.scope ?? SCOPE_EMPTY, - serverId: network.serverId ?? DOKPLOY_SERVER_VALUE, + serverId: network.serverId || undefined, internal: network.internal, attachable: network.attachable, enableIPv4: network.enableIPv4, @@ -157,7 +154,7 @@ export const HandleNetwork = ({ networkId, children }: HandleNetworkProps) => { name: data.name, driver: data.driver, scope, - serverId: data.serverId ?? undefined, + serverId: data.serverId || undefined, internal: data.internal, attachable: data.attachable, ingress: data.ingress, @@ -268,7 +265,7 @@ export const HandleNetwork = ({ networkId, children }: HandleNetworkProps) => { Server + + + + + {providers?.map((p) => ( + + {p.name} ({p.model}) + + ))} + + + + + ) : ( + <> +
+
+ {data.analysis} +
+
+
+ + +
+ + )} + + + + ); +} 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 59b939008..ace20b343 100644 --- a/apps/dokploy/components/dashboard/docker/logs/docker-logs-id.tsx +++ b/apps/dokploy/components/dashboard/docker/logs/docker-logs-id.tsx @@ -12,6 +12,7 @@ import { AlertBlock } from "@/components/shared/alert-block"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { api } from "@/utils/api"; +import { AnalyzeLogs } from "./analyze-logs"; import { LineCountFilter } from "./line-count-filter"; import { SinceLogsFilter, type TimeFilter } from "./since-logs-filter"; import { StatusLogsFilter } from "./status-logs-filter"; @@ -377,6 +378,7 @@ export const DockerLogsId: React.FC = ({ Download logs + {isPaused && ( diff --git a/apps/dokploy/components/dashboard/project/ai/template-generator.tsx b/apps/dokploy/components/dashboard/project/ai/template-generator.tsx index 76a491aa0..64d4b0776 100644 --- a/apps/dokploy/components/dashboard/project/ai/template-generator.tsx +++ b/apps/dokploy/components/dashboard/project/ai/template-generator.tsx @@ -298,7 +298,19 @@ export const TemplateGenerator = ({ environmentId }: Props) => {
+ + + - {field.value - ? (selectedModel?.id ?? field.value) - : "Select a model"} - - - - - - - + + + + {modelSearch ? ( + + ) : ( + "No models found." + )} + + {displayModels.map((model) => { + const isSelected = field.value === model.id; + return ( + { + field.onChange(model.id); + setModelPopoverOpen(false); + setModelSearch(""); + }} + > + + {model.id} + + ); + })} + + + + + ) : ( + + - - No models found. - {displayModels.map((model) => { - const isSelected = field.value === model.id; - return ( - { - field.onChange(model.id); - setModelPopoverOpen(false); - setModelSearch(""); - }} - > - - {model.id} - - ); - })} - - - - - - Select an AI model to use - - - - ); - }} - /> - )} + + )} +
+
+ + Select a model from the list or type a custom model name + + + + ); + }} + /> { )} /> -
+
+ @@ -383,3 +475,42 @@ export const HandleAi = ({ aiId }: Props) => { ); }; + +function TestConnectionButton({ + apiUrl, + apiKey, + model, +}: { + apiUrl: string; + apiKey: string; + model: string; +}) { + const { mutate, isPending } = api.ai.testConnection.useMutation({ + onSuccess: () => { + toast.success("Connection successful"); + }, + onError: (error) => { + toast.error("Connection failed", { + description: error.message, + }); + }, + }); + + const isDisabled = !apiUrl || !model; + + return ( + + ); +} diff --git a/apps/dokploy/server/api/routers/ai.ts b/apps/dokploy/server/api/routers/ai.ts index a4527497d..139227221 100644 --- a/apps/dokploy/server/api/routers/ai.ts +++ b/apps/dokploy/server/api/routers/ai.ts @@ -25,9 +25,11 @@ import { findProjectById } from "@dokploy/server/services/project"; import { getProviderHeaders, getProviderName, + selectAIProvider, type Model, } from "@dokploy/server/utils/ai/select-ai-provider"; import { TRPCError } from "@trpc/server"; +import { generateText } from "ai"; import { z } from "zod"; import { slugify } from "@/lib/slug"; import { @@ -95,6 +97,30 @@ export const aiRouter = createTRPCRouter({ owned_by: "perplexity", }, ] as Model[]; + case "zai": + return [ + { + id: "glm-5", + object: "model", + created: Date.now(), + owned_by: "zai", + }, + { + id: "glm-4.7", + object: "model", + created: Date.now(), + owned_by: "zai", + }, + ] as Model[]; + case "minimax": + return [ + { + id: "MiniMax-M2.7", + object: "model", + created: Date.now(), + owned_by: "minimax", + }, + ] as Model[]; default: if (!input.apiKey) throw new TRPCError({ @@ -174,6 +200,96 @@ export const aiRouter = createTRPCRouter({ return await deleteAiSettings(input.aiId); }), + getEnabledProviders: protectedProcedure.query(async ({ ctx }) => { + const settings = await getAiSettingsByOrganizationId( + ctx.session.activeOrganizationId, + ); + return settings + .filter((s) => s.isEnabled) + .map((s) => ({ aiId: s.aiId, name: s.name, model: s.model })); + }), + + analyzeLogs: protectedProcedure + .input( + z.object({ + aiId: z.string().min(1), + logs: z.string().min(1), + context: z.enum(["build", "runtime"]), + }), + ) + .mutation(async ({ input }) => { + try { + const aiSettings = await getAiSettingById(input.aiId); + if (!aiSettings?.isEnabled) { + throw new TRPCError({ + code: "BAD_REQUEST", + message: "AI provider is not enabled", + }); + } + + const provider = selectAIProvider(aiSettings); + const model = provider(aiSettings.model); + + const contextLabel = + input.context === "build" ? "build/deployment" : "runtime/container"; + + const result = await generateText({ + model, + prompt: `You are a DevOps engineer analyzing ${contextLabel} logs. Analyze the following logs and provide: + +1. **Summary**: A brief summary of what's happening +2. **Issues Found**: Any errors, warnings, or problems detected +3. **Root Cause**: The most likely root cause if there are errors +4. **Suggested Fix**: Actionable steps to resolve the issues + +Be concise and practical. Focus on the most important issues. If the logs look healthy, say so briefly. + +Logs: +${input.logs}`, + }); + + return { analysis: result.text }; + } catch (error) { + throw new TRPCError({ + code: "BAD_REQUEST", + message: + error instanceof Error ? error.message : `Analysis failed: ${error}`, + }); + } + }), + + testConnection: protectedProcedure + .input( + z.object({ + apiUrl: z.string().min(1), + apiKey: z.string(), + model: z.string().min(1), + }), + ) + .mutation(async ({ input }) => { + try { + const provider = selectAIProvider({ + apiUrl: input.apiUrl, + apiKey: input.apiKey, + }); + const model = provider(input.model); + const result = await generateText({ + model, + prompt: "Reply with 'ok'", + }); + if (!result.text) { + throw new Error("No response received from the model"); + } + return { success: true, message: "Connection successful" }; + } catch (error) { + throw new TRPCError({ + code: "BAD_REQUEST", + message: + error instanceof Error ? error.message : `Connection failed: ${error}`, + }); + } + }), + suggest: protectedProcedure .input( z.object({ diff --git a/packages/server/src/services/ai.ts b/packages/server/src/services/ai.ts index ccfb31fc1..6e90d82d0 100644 --- a/packages/server/src/services/ai.ts +++ b/packages/server/src/services/ai.ts @@ -108,22 +108,45 @@ export const suggestVariants = async ({ ip = "127.0.0.1"; } - const suggestionsSchema = z.object({ + const fullSchema = z.object({ suggestions: z.array( z.object({ id: z.string(), name: z.string(), shortDescription: z.string(), description: z.string(), + dockerCompose: z.string(), + envVariables: z.array( + z.object({ + name: z.string(), + value: z.string(), + }), + ), + domains: z.array( + z.object({ + host: z.string(), + port: z.number(), + serviceName: z.string(), + }), + ), + configFiles: z + .array( + z.object({ + content: z.string(), + filePath: z.string(), + }), + ) + .optional(), }), ), }); - const suggestionsResult = await generateText({ + + const result = await generateText({ model, // @ts-ignore - Zod + AI SDK Output.object() causes excessively deep instantiation - output: Output.object({ schema: suggestionsSchema }), + output: Output.object({ schema: fullSchema }), prompt: ` - Act as advanced DevOps engineer and analyze the user's request to determine the appropriate suggestions (up to 3 items). + Act as advanced DevOps engineer. Analyze the user's request and generate up to 3 deployment suggestions, each with a complete docker compose configuration. CRITICAL - Read the user's request carefully and follow the appropriate strategy: @@ -139,163 +162,94 @@ export const suggestVariants = async ({ - Example: For "personal blog" → "WordPress", "Ghost", "Hugo with Nginx" - The name should be the actual project name - Return your response as a JSON object with the following structure: + Return your response as a JSON object with this structure: { "suggestions": [ { "id": "project-or-variant-slug", "name": "Project Name or Variant Name", "shortDescription": "Brief one-line description", - "description": "Detailed description" + "description": "Detailed description of the project/variant", + "dockerCompose": "yaml string here", + "envVariables": [{"name": "VAR_NAME", "value": "example_value"}], + "domains": [{"host": "domain.com", "port": 3000, "serviceName": "service"}], + "configFiles": [{"content": "file content", "filePath": "path/to/file"}] } ] } - Important rules for the response: + Suggestion Rules: 1. Use slug format for the id field (lowercase, hyphenated) - 2. Determine which strategy to use based on whether the user specified a particular application or described a general need - 3. For Strategy A (specific app): The name must include the app name and describe the variant configuration - 4. For Strategy B (general need): The name should be the actual project/tool name that fulfills the need - 5. The description field should ONLY contain a plain text description of the project or variant, its features, and use cases - 6. Do NOT include any code snippets, configuration examples, or installation instructions in the description - 7. The shortDescription should be a single-line summary focusing on key technologies or differentiators - 8. All suggestions should be installable in docker and have docker compose support - 9. Provide variety in your suggestions - different complexity levels, tech stacks, or approaches + 2. The description field should ONLY contain plain text — no code snippets or installation instructions + 3. The shortDescription should be a single-line summary focusing on key technologies or differentiators + 4. All suggestions should be installable in docker and have docker compose support + 5. Provide variety in your suggestions - different complexity levels, tech stacks, or approaches - User wants to create a new project with the following details: + Docker Compose Rules: + 1. Use placeholder like \${VARIABLE_NAME-default} for generated variables in the docker-compose.yml + 2. Use complex values for passwords/secrets variables + 3. Don't set container_name field in services + 4. Don't set version field in the docker compose + 5. Don't set ports like 'ports: 3000:3000', use 'ports: "3000"' instead + 6. If a service depends on a database or other service, INCLUDE that service in the docker-compose + 7. Make sure all required services are defined in the docker-compose - ${input} + Docker Image Rules (CRITICAL): + 1. ALWAYS use 'image:' field, NEVER use 'build:' field + 2. NEVER use 'build: .' or any build directive - we don't have local Dockerfiles + 3. Use images from Docker Hub or other public registries (e.g., docker.io, ghcr.io, quay.io) + 4. For dependencies (databases, redis, etc.), use official images (e.g., postgres:16, redis:7, etc.) + 5. Always specify image tags - avoid using 'latest' tag, use specific versions when possible + 6. Examples of correct image usage: + - image: sendingtk/chatwoot:develop + - image: postgres:16-alpine + - image: redis:7-alpine + 7. Examples of INCORRECT usage (DO NOT USE): + - build: . + - build: ./app + - build: + context: . + dockerfile: Dockerfile + + Volume Mounting and Configuration Rules: + 1. DO NOT create configuration files unless the service CANNOT work without them + 2. Most services can work with just environment variables - USE THEM FIRST + 3. If and ONLY IF a config file is absolutely required: + - Keep it minimal with only critical settings + - Use "../files/" prefix for all mounts + - Format: "../files/folder:/container/path" + 4. DO NOT add configuration files for default configs, env-configurable settings, or proxy/routing configs + + Environment Variables Rules: + 1. For the envVariables array, provide ACTUAL example values, not placeholders + 2. Use realistic example values (e.g., "admin@example.com" for emails, "mypassword123" for passwords) + 3. DO NOT use \${VARIABLE_NAME-default} syntax in the envVariables values + 4. ONLY include environment variables that are actually used in the docker-compose + 5. Every environment variable referenced in the docker-compose MUST have a corresponding entry in envVariables + + Domain Rules - For each service that needs to be exposed to the internet: + 1. Define a domain with: + - host: {service-name}-{random-3-chars-hex}-${ip ? ip.replaceAll(".", "-") : ""}.traefik.me + - port: the internal port the service runs on + - serviceName: the name of the service in the docker-compose + 2. Make sure the service is properly configured to work with the specified port + + User's request: ${input} `, }); - const object = suggestionsResult.output as SuggestionsOutput | undefined; - if (object?.suggestions?.length) { - const dockerSchema = z.object({ - dockerCompose: z.string(), - envVariables: z.array( - z.object({ - name: z.string(), - value: z.string(), - }), - ), - domains: z.array( - z.object({ - host: z.string(), - port: z.number(), - serviceName: z.string(), - }), - ), - configFiles: z - .array( - z.object({ - content: z.string(), - filePath: z.string(), - }), - ) - .optional(), + const output = result.output as + | { suggestions: (SuggestionItem & DockerOutput)[] } + | undefined; + + if (!output?.suggestions?.length) { + throw new TRPCError({ + code: "NOT_FOUND", + message: "No suggestions found", }); - const result = []; - for (const suggestion of object.suggestions) { - try { - const dockerResult = await generateText({ - model, - // @ts-ignore - Zod + AI SDK Output.object() causes excessively deep instantiation - output: Output.object({ schema: dockerSchema }), - prompt: ` - Act as advanced DevOps engineer and generate docker compose with environment variables and domain configurations needed to install the following project. - - Return your response as a JSON object with this structure: - { - "dockerCompose": "yaml string here", - "envVariables": [{"name": "VAR_NAME", "value": "example_value"}], - "domains": [{"host": "domain.com", "port": 3000, "serviceName": "service"}], - "configFiles": [{"content": "file content", "filePath": "path/to/file"}] - } - - Note: configFiles is optional - only include it if configuration files are absolutely required. - - Follow these rules: - - Docker Compose Rules: - 1. Use placeholder like \${VARIABLE_NAME-default} for generated variables in the docker-compose.yml - 2. Use complex values for passwords/secrets variables - 3. Don't set container_name field in services - 4. Don't set version field in the docker compose - 5. Don't set ports like 'ports: 3000:3000', use 'ports: "3000"' instead - 6. If a service depends on a database or other service, INCLUDE that service in the docker-compose - 7. Make sure all required services are defined in the docker-compose - - Docker Image Rules (CRITICAL): - 1. ALWAYS use 'image:' field, NEVER use 'build:' field - 2. NEVER use 'build: .' or any build directive - we don't have local Dockerfiles - 3. Use images from Docker Hub or other public registries (e.g., docker.io, ghcr.io, quay.io) - 4. For dependencies (databases, redis, etc.), use official images (e.g., postgres:16, redis:7, etc.) - 5. Always specify image tags - avoid using 'latest' tag, use specific versions when possible - 6. Examples of correct image usage: - - image: sendingtk/chatwoot:develop - - image: postgres:16-alpine - - image: redis:7-alpine - - image: chatwoot/chatwoot:latest - 7. Examples of INCORRECT usage (DO NOT USE): - - build: . - - build: ./app - - build: - context: . - dockerfile: Dockerfile - - Volume Mounting and Configuration Rules: - 1. DO NOT create configuration files unless the service CANNOT work without them - 2. Most services can work with just environment variables - USE THEM FIRST - 3. Ask yourself: "Can this be configured with an environment variable instead?" - 4. If and ONLY IF a config file is absolutely required: - - Keep it minimal with only critical settings - - Use "../files/" prefix for all mounts - - Format: "../files/folder:/container/path" - 5. DO NOT add configuration files for: - - Default configurations that work out of the box - - Settings that can be handled by environment variables - - Proxy or routing configurations (these are handled elsewhere) - - Environment Variables Rules: - 1. For the envVariables array, provide ACTUAL example values, not placeholders - 2. Use realistic example values (e.g., "admin@example.com" for emails, "mypassword123" for passwords) - 3. DO NOT use \${VARIABLE_NAME-default} syntax in the envVariables values - 4. ONLY include environment variables that are actually used in the docker-compose - 5. Every environment variable referenced in the docker-compose MUST have a corresponding entry in envVariables - 6. Do not include environment variables for services that don't exist in the docker-compose - - For each service that needs to be exposed to the internet: - 1. Define a domain configuration with: - - host: the domain name for the service in format: {service-name}-{random-3-chars-hex}-${ip ? ip.replaceAll(".", "-") : ""}.traefik.me - - port: the internal port the service runs on - - serviceName: the name of the service in the docker-compose - 2. Make sure the service is properly configured to work with the specified port - - User's original request: ${input} - - Project details: - ${suggestion?.description} - `, - }); - const docker = dockerResult.output as DockerOutput | undefined; - if (docker?.dockerCompose) { - result.push({ - ...suggestion, - ...docker, - }); - } - } catch (error) { - console.error("Error in docker compose generation:", error); - } - } - - return result; } - throw new TRPCError({ - code: "NOT_FOUND", - message: "No suggestions found", - }); + return output.suggestions.filter((s) => s.dockerCompose); } catch (error) { console.error("Error in suggestVariants:", error); throw error; diff --git a/packages/server/src/utils/ai/select-ai-provider.ts b/packages/server/src/utils/ai/select-ai-provider.ts index b6477c573..52d7c6224 100644 --- a/packages/server/src/utils/ai/select-ai-provider.ts +++ b/packages/server/src/utils/ai/select-ai-provider.ts @@ -17,6 +17,9 @@ export function getProviderName(apiUrl: string) { if (apiUrl.includes(":11434") || apiUrl.includes("ollama")) return "ollama"; if (apiUrl.includes("api.deepinfra.com")) return "deepinfra"; if (apiUrl.includes("generativelanguage.googleapis.com")) return "gemini"; + if (apiUrl.includes("openrouter.ai")) return "openrouter"; + if (apiUrl.includes("api.z.ai")) return "zai"; + if (apiUrl.includes("api.minimax.io")) return "minimax"; return "custom"; } @@ -87,6 +90,30 @@ export function selectAIProvider(config: { apiUrl: string; apiKey: string }) { Authorization: `Bearer ${config.apiKey}`, }, }); + case "openrouter": + return createOpenAICompatible({ + name: "openrouter", + baseURL: config.apiUrl, + headers: { + Authorization: `Bearer ${config.apiKey}`, + }, + }); + case "zai": + return createOpenAICompatible({ + name: "zai", + baseURL: config.apiUrl, + headers: { + Authorization: `Bearer ${config.apiKey}`, + }, + }); + case "minimax": + return createOpenAICompatible({ + name: "minimax", + baseURL: config.apiUrl, + headers: { + Authorization: `Bearer ${config.apiKey}`, + }, + }); case "custom": return createOpenAICompatible({ name: "custom", From fbde5be02ce317dfe0e99d2b4fef9053c1324693 Mon Sep 17 00:00:00 2001 From: "autofix-ci[bot]" <114827586+autofix-ci[bot]@users.noreply.github.com> Date: Thu, 9 Apr 2026 17:20:44 +0000 Subject: [PATCH 019/585] [autofix.ci] apply automated fixes --- .../dashboard/docker/logs/analyze-logs.tsx | 21 +++++++++++-------- .../dashboard/docker/logs/docker-logs-id.tsx | 2 +- apps/dokploy/server/api/routers/ai.ts | 8 +++++-- 3 files changed, 19 insertions(+), 12 deletions(-) diff --git a/apps/dokploy/components/dashboard/docker/logs/analyze-logs.tsx b/apps/dokploy/components/dashboard/docker/logs/analyze-logs.tsx index f330c3fe2..c26ce5bfe 100644 --- a/apps/dokploy/components/dashboard/docker/logs/analyze-logs.tsx +++ b/apps/dokploy/components/dashboard/docker/logs/analyze-logs.tsx @@ -32,14 +32,13 @@ export function AnalyzeLogs({ logs, context }: Props) { const { data: providers } = api.ai.getEnabledProviders.useQuery(undefined, { enabled: open, }); - const { mutate, isPending, data, reset } = - api.ai.analyzeLogs.useMutation({ - onError: (error) => { - toast.error("Analysis failed", { - description: error.message, - }); - }, - }); + const { mutate, isPending, data, reset } = api.ai.analyzeLogs.useMutation({ + onError: (error) => { + toast.error("Analysis failed", { + description: error.message, + }); + }, + }); const handleAnalyze = () => { if (!aiId || logs.length === 0) return; @@ -119,7 +118,11 @@ export function AnalyzeLogs({ logs, context }: Props) { ) : ( <> - Analyze {logs.length > MAX_LOG_LINES ? `last ${MAX_LOG_LINES}` : logs.length} lines + Analyze{" "} + {logs.length > MAX_LOG_LINES + ? `last ${MAX_LOG_LINES}` + : logs.length}{" "} + lines )} 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 ace20b343..8d8842ac0 100644 --- a/apps/dokploy/components/dashboard/docker/logs/docker-logs-id.tsx +++ b/apps/dokploy/components/dashboard/docker/logs/docker-logs-id.tsx @@ -378,7 +378,7 @@ export const DockerLogsId: React.FC = ({ Download logs - +
{isPaused && ( diff --git a/apps/dokploy/server/api/routers/ai.ts b/apps/dokploy/server/api/routers/ai.ts index 139227221..48bacfcba 100644 --- a/apps/dokploy/server/api/routers/ai.ts +++ b/apps/dokploy/server/api/routers/ai.ts @@ -253,7 +253,9 @@ ${input.logs}`, throw new TRPCError({ code: "BAD_REQUEST", message: - error instanceof Error ? error.message : `Analysis failed: ${error}`, + error instanceof Error + ? error.message + : `Analysis failed: ${error}`, }); } }), @@ -285,7 +287,9 @@ ${input.logs}`, throw new TRPCError({ code: "BAD_REQUEST", message: - error instanceof Error ? error.message : `Connection failed: ${error}`, + error instanceof Error + ? error.message + : `Connection failed: ${error}`, }); } }), From 8d8658a478b0bbe6e2ef796c797f3a0750247639 Mon Sep 17 00:00:00 2001 From: Mauricio Siu Date: Thu, 9 Apr 2026 11:27:19 -0600 Subject: [PATCH 020/585] fix: update Z.AI API URL and enhance AI router access control - Corrected the API URL for Z.AI by removing the trailing slash. - Modified the AI router mutation to include context and added access control to ensure users can only access their organization's AI settings. These changes improve the accuracy of the API integration and enhance security by enforcing organizational access restrictions. --- apps/dokploy/components/dashboard/settings/handle-ai.tsx | 2 +- apps/dokploy/server/api/routers/ai.ts | 9 ++++++++- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/apps/dokploy/components/dashboard/settings/handle-ai.tsx b/apps/dokploy/components/dashboard/settings/handle-ai.tsx index db225bb58..18915609e 100644 --- a/apps/dokploy/components/dashboard/settings/handle-ai.tsx +++ b/apps/dokploy/components/dashboard/settings/handle-ai.tsx @@ -68,7 +68,7 @@ const AI_PROVIDERS = [ { name: "DeepInfra", apiUrl: "https://api.deepinfra.com/v1/openai" }, { name: "Ollama", apiUrl: "http://localhost:11434" }, { name: "OpenRouter", apiUrl: "https://openrouter.ai/api/v1" }, - { name: "Z.AI", apiUrl: "https://api.z.ai/api/paas/v4/" }, + { name: "Z.AI", apiUrl: "https://api.z.ai/api/paas/v4" }, { name: "MiniMax", apiUrl: "https://api.minimax.io/v1" }, ] as const; diff --git a/apps/dokploy/server/api/routers/ai.ts b/apps/dokploy/server/api/routers/ai.ts index 48bacfcba..3a299235a 100644 --- a/apps/dokploy/server/api/routers/ai.ts +++ b/apps/dokploy/server/api/routers/ai.ts @@ -217,7 +217,7 @@ export const aiRouter = createTRPCRouter({ context: z.enum(["build", "runtime"]), }), ) - .mutation(async ({ input }) => { + .mutation(async ({ input, ctx }) => { try { const aiSettings = await getAiSettingById(input.aiId); if (!aiSettings?.isEnabled) { @@ -227,6 +227,13 @@ export const aiRouter = createTRPCRouter({ }); } + if (aiSettings.organizationId !== ctx.session.activeOrganizationId) { + throw new TRPCError({ + code: "FORBIDDEN", + message: "Access denied", + }); + } + const provider = selectAIProvider(aiSettings); const model = provider(aiSettings.model); From 7c10610a5ac175c806c0a8e5904ba1e8145d3198 Mon Sep 17 00:00:00 2001 From: Mauricio Siu Date: Thu, 9 Apr 2026 11:40:02 -0600 Subject: [PATCH 021/585] feat: add readLogs procedure to multiple routers for container log retrieval - Implemented a new `readLogs` procedure across various routers (application, compose, libsql, mariadb, mongo, mysql, postgres, redis) to enable users to retrieve logs from containers. - Each procedure includes input validation for parameters such as `tail`, `since`, and `search`, ensuring robust access control and authorization checks. - Enhanced the `getContainerLogs` service to support fetching logs from both Docker containers and services, improving the logging capabilities of the application. This feature enhances observability and troubleshooting for users by providing direct access to container logs. --- .../dokploy/server/api/routers/application.ts | 38 ++++++++- apps/dokploy/server/api/routers/compose.ts | 42 +++++++++- apps/dokploy/server/api/routers/libsql.ts | 36 +++++++++ apps/dokploy/server/api/routers/mariadb.ts | 38 ++++++++- apps/dokploy/server/api/routers/mongo.ts | 36 +++++++++ apps/dokploy/server/api/routers/mysql.ts | 36 +++++++++ apps/dokploy/server/api/routers/postgres.ts | 36 +++++++++ apps/dokploy/server/api/routers/redis.ts | 36 +++++++++ packages/server/src/services/docker.ts | 81 +++++++++++++++++++ 9 files changed, 376 insertions(+), 3 deletions(-) diff --git a/apps/dokploy/server/api/routers/application.ts b/apps/dokploy/server/api/routers/application.ts index c00514715..228c98656 100644 --- a/apps/dokploy/server/api/routers/application.ts +++ b/apps/dokploy/server/api/routers/application.ts @@ -6,7 +6,9 @@ import { findEnvironmentById, findGitProviderById, findProjectById, + getAccessibleServerIds, getApplicationStats, + getContainerLogs, IS_CLOUD, mechanizeDockerContainer, readConfig, @@ -26,7 +28,6 @@ import { updateDeploymentStatus, writeConfig, writeConfigRemote, - getAccessibleServerIds, } from "@dokploy/server"; import { db } from "@dokploy/server/db"; import { @@ -1101,4 +1102,39 @@ export const applicationRouter = createTRPCRouter({ total: countResult[0]?.count ?? 0, }; }), + + readLogs: protectedProcedure + .input( + apiFindOneApplication.extend({ + tail: z.number().int().min(1).max(10000).default(100), + since: z + .string() + .regex(/^(all|\d+[smhd])$/, "Invalid since format") + .default("all"), + search: z + .string() + .regex(/^[a-zA-Z0-9 ._-]{0,500}$/) + .optional(), + }), + ) + .query(async ({ input, ctx }) => { + await checkServiceAccess(ctx, input.applicationId, "read"); + const application = await findApplicationById(input.applicationId); + if ( + application.environment.project.organizationId !== + ctx.session.activeOrganizationId + ) { + throw new TRPCError({ + code: "UNAUTHORIZED", + message: "You are not authorized to access this application", + }); + } + return await getContainerLogs( + application.appName, + input.tail, + input.since, + input.search, + application.serverId, + ); + }), }); diff --git a/apps/dokploy/server/api/routers/compose.ts b/apps/dokploy/server/api/routers/compose.ts index be5a836d7..dd632466b 100644 --- a/apps/dokploy/server/api/routers/compose.ts +++ b/apps/dokploy/server/api/routers/compose.ts @@ -16,7 +16,9 @@ import { findGitProviderById, findProjectById, findServerById, + getAccessibleServerIds, getComposeContainer, + getContainerLogs, getWebServerSettings, IS_CLOUD, loadServices, @@ -30,7 +32,6 @@ import { stopCompose, updateCompose, updateDeploymentStatus, - getAccessibleServerIds, } from "@dokploy/server"; import { db } from "@dokploy/server/db"; import { @@ -1130,4 +1131,43 @@ export const composeRouter = createTRPCRouter({ total: countResult[0]?.count ?? 0, }; }), + + readLogs: protectedProcedure + .input( + apiFindCompose.extend({ + containerId: z + .string() + .min(1) + .regex(/^[a-zA-Z0-9.\-_]+$/, "Invalid container id."), + tail: z.number().int().min(1).max(10000).default(100), + since: z + .string() + .regex(/^(all|\d+[smhd])$/, "Invalid since format") + .default("all"), + search: z + .string() + .regex(/^[a-zA-Z0-9 ._-]{0,500}$/) + .optional(), + }), + ) + .query(async ({ input, ctx }) => { + await checkServiceAccess(ctx, input.composeId, "read"); + const compose = await findComposeById(input.composeId); + if ( + compose.environment.project.organizationId !== + ctx.session.activeOrganizationId + ) { + throw new TRPCError({ + code: "UNAUTHORIZED", + message: "You are not authorized to access this compose", + }); + } + return await getContainerLogs( + input.containerId, + input.tail, + input.since, + input.search, + compose.serverId, + ); + }), }); diff --git a/apps/dokploy/server/api/routers/libsql.ts b/apps/dokploy/server/api/routers/libsql.ts index 65052097e..47798393e 100644 --- a/apps/dokploy/server/api/routers/libsql.ts +++ b/apps/dokploy/server/api/routers/libsql.ts @@ -6,6 +6,7 @@ import { findEnvironmentById, findLibsqlById, findProjectById, + getContainerLogs, IS_CLOUD, rebuildDatabase, removeLibsqlById, @@ -466,4 +467,39 @@ export const libsqlRouter = createTRPCRouter({ }); return true; }), + + readLogs: protectedProcedure + .input( + apiFindOneLibsql.extend({ + tail: z.number().int().min(1).max(10000).default(100), + since: z + .string() + .regex(/^(all|\d+[smhd])$/, "Invalid since format") + .default("all"), + search: z + .string() + .regex(/^[a-zA-Z0-9 ._-]{0,500}$/) + .optional(), + }), + ) + .query(async ({ input, ctx }) => { + await checkServiceAccess(ctx, input.libsqlId, "read"); + const libsql = await findLibsqlById(input.libsqlId); + if ( + libsql.environment.project.organizationId !== + ctx.session.activeOrganizationId + ) { + throw new TRPCError({ + code: "UNAUTHORIZED", + message: "You are not authorized to access this LibSQL", + }); + } + return await getContainerLogs( + libsql.appName, + input.tail, + input.since, + input.search, + libsql.serverId, + ); + }), }); diff --git a/apps/dokploy/server/api/routers/mariadb.ts b/apps/dokploy/server/api/routers/mariadb.ts index a31554bec..a58a33a9c 100644 --- a/apps/dokploy/server/api/routers/mariadb.ts +++ b/apps/dokploy/server/api/routers/mariadb.ts @@ -9,6 +9,8 @@ import { findEnvironmentById, findMariadbById, findProjectById, + getAccessibleServerIds, + getContainerLogs, getServiceContainerCommand, IS_CLOUD, rebuildDatabase, @@ -19,7 +21,6 @@ import { stopService, stopServiceRemote, updateMariadbById, - getAccessibleServerIds, } from "@dokploy/server"; import { db } from "@dokploy/server/db"; import { @@ -590,4 +591,39 @@ export const mariadbRouter = createTRPCRouter({ ]); return { items, total: countResult[0]?.count ?? 0 }; }), + + readLogs: protectedProcedure + .input( + apiFindOneMariaDB.extend({ + tail: z.number().int().min(1).max(10000).default(100), + since: z + .string() + .regex(/^(all|\d+[smhd])$/, "Invalid since format") + .default("all"), + search: z + .string() + .regex(/^[a-zA-Z0-9 ._-]{0,500}$/) + .optional(), + }), + ) + .query(async ({ input, ctx }) => { + await checkServiceAccess(ctx, input.mariadbId, "read"); + const mariadb = await findMariadbById(input.mariadbId); + if ( + mariadb.environment.project.organizationId !== + ctx.session.activeOrganizationId + ) { + throw new TRPCError({ + code: "UNAUTHORIZED", + message: "You are not authorized to access this MariaDB", + }); + } + return await getContainerLogs( + mariadb.appName, + input.tail, + input.since, + input.search, + mariadb.serverId, + ); + }), }); diff --git a/apps/dokploy/server/api/routers/mongo.ts b/apps/dokploy/server/api/routers/mongo.ts index f0f5c593a..222917b9f 100644 --- a/apps/dokploy/server/api/routers/mongo.ts +++ b/apps/dokploy/server/api/routers/mongo.ts @@ -10,6 +10,7 @@ import { findMongoById, findProjectById, getAccessibleServerIds, + getContainerLogs, getServiceContainerCommand, IS_CLOUD, rebuildDatabase, @@ -601,4 +602,39 @@ export const mongoRouter = createTRPCRouter({ ]); return { items, total: countResult[0]?.count ?? 0 }; }), + + readLogs: protectedProcedure + .input( + apiFindOneMongo.extend({ + tail: z.number().int().min(1).max(10000).default(100), + since: z + .string() + .regex(/^(all|\d+[smhd])$/, "Invalid since format") + .default("all"), + search: z + .string() + .regex(/^[a-zA-Z0-9 ._-]{0,500}$/) + .optional(), + }), + ) + .query(async ({ input, ctx }) => { + await checkServiceAccess(ctx, input.mongoId, "read"); + const mongo = await findMongoById(input.mongoId); + if ( + mongo.environment.project.organizationId !== + ctx.session.activeOrganizationId + ) { + throw new TRPCError({ + code: "UNAUTHORIZED", + message: "You are not authorized to access this MongoDB", + }); + } + return await getContainerLogs( + mongo.appName, + input.tail, + input.since, + input.search, + mongo.serverId, + ); + }), }); diff --git a/apps/dokploy/server/api/routers/mysql.ts b/apps/dokploy/server/api/routers/mysql.ts index f2a750c04..263fa53f0 100644 --- a/apps/dokploy/server/api/routers/mysql.ts +++ b/apps/dokploy/server/api/routers/mysql.ts @@ -9,6 +9,7 @@ import { findEnvironmentById, findMySqlById, findProjectById, + getContainerLogs, getServiceContainerCommand, IS_CLOUD, rebuildDatabase, @@ -604,4 +605,39 @@ export const mysqlRouter = createTRPCRouter({ ]); return { items, total: countResult[0]?.count ?? 0 }; }), + + readLogs: protectedProcedure + .input( + apiFindOneMySql.extend({ + tail: z.number().int().min(1).max(10000).default(100), + since: z + .string() + .regex(/^(all|\d+[smhd])$/, "Invalid since format") + .default("all"), + search: z + .string() + .regex(/^[a-zA-Z0-9 ._-]{0,500}$/) + .optional(), + }), + ) + .query(async ({ input, ctx }) => { + await checkServiceAccess(ctx, input.mysqlId, "read"); + const mysql = await findMySqlById(input.mysqlId); + if ( + mysql.environment.project.organizationId !== + ctx.session.activeOrganizationId + ) { + throw new TRPCError({ + code: "UNAUTHORIZED", + message: "You are not authorized to access this MySQL", + }); + } + return await getContainerLogs( + mysql.appName, + input.tail, + input.since, + input.search, + mysql.serverId, + ); + }), }); diff --git a/apps/dokploy/server/api/routers/postgres.ts b/apps/dokploy/server/api/routers/postgres.ts index 0baeb3f0e..33d8fd3f4 100644 --- a/apps/dokploy/server/api/routers/postgres.ts +++ b/apps/dokploy/server/api/routers/postgres.ts @@ -9,6 +9,7 @@ import { findEnvironmentById, findPostgresById, findProjectById, + getContainerLogs, getMountPath, getServiceContainerCommand, IS_CLOUD, @@ -614,4 +615,39 @@ export const postgresRouter = createTRPCRouter({ ]); return { items, total: countResult[0]?.count ?? 0 }; }), + + readLogs: protectedProcedure + .input( + apiFindOnePostgres.extend({ + tail: z.number().int().min(1).max(10000).default(100), + since: z + .string() + .regex(/^(all|\d+[smhd])$/, "Invalid since format") + .default("all"), + search: z + .string() + .regex(/^[a-zA-Z0-9 ._-]{0,500}$/) + .optional(), + }), + ) + .query(async ({ input, ctx }) => { + await checkServiceAccess(ctx, input.postgresId, "read"); + const postgres = await findPostgresById(input.postgresId); + if ( + postgres.environment.project.organizationId !== + ctx.session.activeOrganizationId + ) { + throw new TRPCError({ + code: "UNAUTHORIZED", + message: "You are not authorized to access this Postgres", + }); + } + return await getContainerLogs( + postgres.appName, + input.tail, + input.since, + input.search, + postgres.serverId, + ); + }), }); diff --git a/apps/dokploy/server/api/routers/redis.ts b/apps/dokploy/server/api/routers/redis.ts index 2d7b20674..a1e912e0b 100644 --- a/apps/dokploy/server/api/routers/redis.ts +++ b/apps/dokploy/server/api/routers/redis.ts @@ -8,6 +8,7 @@ import { findEnvironmentById, findProjectById, findRedisById, + getContainerLogs, getServiceContainerCommand, IS_CLOUD, rebuildDatabase, @@ -587,4 +588,39 @@ export const redisRouter = createTRPCRouter({ ]); return { items, total: countResult[0]?.count ?? 0 }; }), + + readLogs: protectedProcedure + .input( + apiFindOneRedis.extend({ + tail: z.number().int().min(1).max(10000).default(100), + since: z + .string() + .regex(/^(all|\d+[smhd])$/, "Invalid since format") + .default("all"), + search: z + .string() + .regex(/^[a-zA-Z0-9 ._-]{0,500}$/) + .optional(), + }), + ) + .query(async ({ input, ctx }) => { + await checkServiceAccess(ctx, input.redisId, "read"); + const redis = await findRedisById(input.redisId); + if ( + redis.environment.project.organizationId !== + ctx.session.activeOrganizationId + ) { + throw new TRPCError({ + code: "UNAUTHORIZED", + message: "You are not authorized to access this Redis", + }); + } + return await getContainerLogs( + redis.appName, + input.tail, + input.since, + input.search, + redis.serverId, + ); + }), }); diff --git a/packages/server/src/services/docker.ts b/packages/server/src/services/docker.ts index 1205e62c2..2b14d0b23 100644 --- a/packages/server/src/services/docker.ts +++ b/packages/server/src/services/docker.ts @@ -354,6 +354,87 @@ export const getContainersByAppLabel = async ( return []; }; +export const getContainerLogs = async ( + appName: string, + tail = 100, + since = "all", + search?: string, + serverId?: string | null, +): Promise => { + // First, find the real container ID by appName filter + const findCommand = `docker ps -q --filter "name=^${appName}" | head -1`; + const findResult = serverId + ? await execAsyncRemote(serverId, findCommand) + : await execAsync(findCommand); + + const containerId = findResult.stdout.trim(); + if (!containerId) { + // Fallback: try as a swarm service + const svcCommand = `docker service ls -q --filter "name=${appName}" | head -1`; + const svcResult = serverId + ? await execAsyncRemote(serverId, svcCommand) + : await execAsync(svcCommand); + + const serviceId = svcResult.stdout.trim(); + if (!serviceId) { + throw new Error(`No container or service found for: ${appName}`); + } + + // Use docker service logs for swarm + const sinceFlag = since === "all" ? "" : `--since ${since}`; + const baseCommand = `docker service logs --timestamps --raw --tail ${tail} ${sinceFlag} ${appName}`; + const escapedSearch = search?.replace(/'/g, "'\\''") ?? ""; + const command = search + ? `${baseCommand} 2>&1 | grep -iF '${escapedSearch}'` + : `${baseCommand} 2>&1`; + + try { + const result = serverId + ? await execAsyncRemote(serverId, command) + : await execAsync(command); + return result.stdout; + } catch (error: unknown) { + if ( + error && + typeof error === "object" && + "stdout" in error && + typeof (error as { stdout: string }).stdout === "string" && + (error as { stdout: string }).stdout.length > 0 + ) { + return (error as { stdout: string }).stdout; + } + throw error; + } + } + + const sinceFlag = since === "all" ? "" : `--since ${since}`; + const baseCommand = `docker container logs --timestamps --tail ${tail} ${sinceFlag} ${containerId}`; + + const escapedSearch = search?.replace(/'/g, "'\\''") ?? ""; + const command = search + ? `${baseCommand} 2>&1 | grep -iF '${escapedSearch}'` + : `${baseCommand} 2>&1`; + + try { + const result = serverId + ? await execAsyncRemote(serverId, command) + : await execAsync(command); + + return result.stdout; + } catch (error: unknown) { + if ( + error && + typeof error === "object" && + "stdout" in error && + typeof (error as { stdout: string }).stdout === "string" && + (error as { stdout: string }).stdout.length > 0 + ) { + return (error as { stdout: string }).stdout; + } + throw error; + } +}; + export const containerRestart = async (containerId: string) => { try { const { stdout, stderr } = await execAsync( From b8db120432c966810f88ee66cf638f0be7fd0c9d Mon Sep 17 00:00:00 2001 From: Mauricio Siu Date: Thu, 9 Apr 2026 11:41:01 -0600 Subject: [PATCH 022/585] refactor: enhance getContainerLogs function to support app name or ID - Updated the `getContainerLogs` function to accept either an application name or container ID, improving flexibility in log retrieval. - Simplified the command execution logic by consolidating the remote and local execution paths. - Added a new parameter to directly use container IDs, streamlining the process for users. These changes enhance the usability of the logging feature, allowing for more efficient access to container logs. --- apps/dokploy/server/api/routers/compose.ts | 1 + packages/server/src/services/docker.ts | 72 ++++++++-------------- 2 files changed, 28 insertions(+), 45 deletions(-) diff --git a/apps/dokploy/server/api/routers/compose.ts b/apps/dokploy/server/api/routers/compose.ts index dd632466b..d395bdffc 100644 --- a/apps/dokploy/server/api/routers/compose.ts +++ b/apps/dokploy/server/api/routers/compose.ts @@ -1168,6 +1168,7 @@ export const composeRouter = createTRPCRouter({ input.since, input.search, compose.serverId, + true, ); }), }); diff --git a/packages/server/src/services/docker.ts b/packages/server/src/services/docker.ts index 2b14d0b23..eb218b1e1 100644 --- a/packages/server/src/services/docker.ts +++ b/packages/server/src/services/docker.ts @@ -355,60 +355,45 @@ export const getContainersByAppLabel = async ( }; export const getContainerLogs = async ( - appName: string, + appNameOrId: string, tail = 100, since = "all", search?: string, serverId?: string | null, + useContainerIdDirectly = false, ): Promise => { - // First, find the real container ID by appName filter - const findCommand = `docker ps -q --filter "name=^${appName}" | head -1`; - const findResult = serverId - ? await execAsyncRemote(serverId, findCommand) - : await execAsync(findCommand); + const exec = (cmd: string) => + serverId ? execAsyncRemote(serverId, cmd) : execAsync(cmd); - const containerId = findResult.stdout.trim(); - if (!containerId) { - // Fallback: try as a swarm service - const svcCommand = `docker service ls -q --filter "name=${appName}" | head -1`; - const svcResult = serverId - ? await execAsyncRemote(serverId, svcCommand) - : await execAsync(svcCommand); + let target = appNameOrId; + let isService = false; - const serviceId = svcResult.stdout.trim(); - if (!serviceId) { - throw new Error(`No container or service found for: ${appName}`); - } + if (!useContainerIdDirectly) { + // Find the real container ID by appName filter + const findResult = await exec( + `docker ps -q --filter "name=^${appNameOrId}" | head -1`, + ); + const containerId = findResult.stdout.trim(); - // Use docker service logs for swarm - const sinceFlag = since === "all" ? "" : `--since ${since}`; - const baseCommand = `docker service logs --timestamps --raw --tail ${tail} ${sinceFlag} ${appName}`; - const escapedSearch = search?.replace(/'/g, "'\\''") ?? ""; - const command = search - ? `${baseCommand} 2>&1 | grep -iF '${escapedSearch}'` - : `${baseCommand} 2>&1`; - - try { - const result = serverId - ? await execAsyncRemote(serverId, command) - : await execAsync(command); - return result.stdout; - } catch (error: unknown) { - if ( - error && - typeof error === "object" && - "stdout" in error && - typeof (error as { stdout: string }).stdout === "string" && - (error as { stdout: string }).stdout.length > 0 - ) { - return (error as { stdout: string }).stdout; + if (!containerId) { + // Fallback: try as a swarm service + const svcResult = await exec( + `docker service ls -q --filter "name=${appNameOrId}" | head -1`, + ); + const serviceId = svcResult.stdout.trim(); + if (!serviceId) { + throw new Error(`No container or service found for: ${appNameOrId}`); } - throw error; + isService = true; + } else { + target = containerId; } } const sinceFlag = since === "all" ? "" : `--since ${since}`; - const baseCommand = `docker container logs --timestamps --tail ${tail} ${sinceFlag} ${containerId}`; + const baseCommand = isService + ? `docker service logs --timestamps --raw --tail ${tail} ${sinceFlag} ${target}` + : `docker container logs --timestamps --tail ${tail} ${sinceFlag} ${target}`; const escapedSearch = search?.replace(/'/g, "'\\''") ?? ""; const command = search @@ -416,10 +401,7 @@ export const getContainerLogs = async ( : `${baseCommand} 2>&1`; try { - const result = serverId - ? await execAsyncRemote(serverId, command) - : await execAsync(command); - + const result = await exec(command); return result.stdout; } catch (error: unknown) { if ( From 6c3578a475f0479d2ed80b5253cd31c23308bb60 Mon Sep 17 00:00:00 2001 From: Mauricio Siu Date: Thu, 9 Apr 2026 11:44:55 -0600 Subject: [PATCH 023/585] feat: enhance AnalyzeLogs component with AI provider configuration prompt - Updated the AnalyzeLogs component to display a message and button for configuring AI providers when none are available, improving user guidance. - Added a link to the settings page for easy access to AI provider configuration. - Integrated new icon for the configuration button to enhance UI clarity. These changes improve the user experience by ensuring users are informed about the need to set up AI providers for log analysis. --- .../dashboard/docker/logs/analyze-logs.tsx | 90 +++++++++++-------- 1 file changed, 53 insertions(+), 37 deletions(-) diff --git a/apps/dokploy/components/dashboard/docker/logs/analyze-logs.tsx b/apps/dokploy/components/dashboard/docker/logs/analyze-logs.tsx index c26ce5bfe..267735eac 100644 --- a/apps/dokploy/components/dashboard/docker/logs/analyze-logs.tsx +++ b/apps/dokploy/components/dashboard/docker/logs/analyze-logs.tsx @@ -1,5 +1,6 @@ "use client"; -import { Bot, Loader2, RotateCcw, X } from "lucide-react"; +import { Bot, Loader2, RotateCcw, Settings, X } from "lucide-react"; +import Link from "next/link"; import { useState } from "react"; import ReactMarkdown from "react-markdown"; import { toast } from "sonner"; @@ -91,42 +92,57 @@ export function AnalyzeLogs({ logs, context }: Props) {
{!data?.analysis ? ( - <> - - - + providers && providers.length === 0 ? ( +
+

+ No AI providers configured. Set up a provider to start + analyzing logs. +

+ +
+ ) : ( + <> + + + + ) ) : ( <>
From 825e6b654c11f302f4353e2166e566b6e2859c5a Mon Sep 17 00:00:00 2001 From: Mauricio Siu Date: Thu, 9 Apr 2026 16:25:36 -0600 Subject: [PATCH 024/585] fix: prevent orphaned containers when deleting compose services MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Commands were chained with && so if the project directory was missing, cd would fail and docker compose down would never execute — leaving containers and volumes running. Use semicolons to run each command independently, matching the existing stack deletion pattern. Closes #4064 --- packages/server/src/services/compose.ts | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/packages/server/src/services/compose.ts b/packages/server/src/services/compose.ts index 5db6526a6..0cec3418b 100644 --- a/packages/server/src/services/compose.ts +++ b/packages/server/src/services/compose.ts @@ -440,17 +440,16 @@ export const removeCompose = async ( } } else { const command = ` - docker network disconnect ${compose.appName} dokploy-traefik; - cd ${projectPath} && env -i PATH="$PATH" docker compose -p ${compose.appName} down ${ + docker network disconnect ${compose.appName} dokploy-traefik; + env -i PATH="$PATH" docker compose -p ${compose.appName} down ${ deleteVolumes ? "--volumes" : "" - } && rm -rf ${projectPath}`; + }; + rm -rf ${projectPath}`; if (compose.serverId) { await execAsyncRemote(compose.serverId, command); } else { - await execAsync(command, { - cwd: projectPath, - }); + await execAsync(command); } } } catch (error) { From cb64482649cba0717a55da30c181ecf4bc45527c Mon Sep 17 00:00:00 2001 From: Mauricio Siu Date: Thu, 9 Apr 2026 17:06:09 -0600 Subject: [PATCH 025/585] fix: inject COMPOSE_PROJECT_NAME to prevent orphaned containers on redeploy When users set a custom docker compose command without the -p flag, Docker Compose defaults to using the directory name (code) as the project name. If the custom command is later removed, Dokploy uses -p appName, creating a new stack while the old one remains running. Injecting COMPOSE_PROJECT_NAME=appName into the .env ensures the project name is always consistent regardless of the command used. Closes #4019 --- packages/server/src/utils/builders/compose.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/server/src/utils/builders/compose.ts b/packages/server/src/utils/builders/compose.ts index 316570626..8da1b6678 100644 --- a/packages/server/src/utils/builders/compose.ts +++ b/packages/server/src/utils/builders/compose.ts @@ -106,6 +106,7 @@ export const getCreateEnvFileCommand = (compose: ComposeNested) => { const envFilePath = join(dirname(composeFilePath), ".env"); let envContent = `APP_NAME=${appName}\n`; + envContent += `COMPOSE_PROJECT_NAME=${appName}\n`; envContent += env || ""; if (!envContent.includes("DOCKER_CONFIG")) { envContent += "\nDOCKER_CONFIG=/root/.docker"; From b079cbd427940d9370aa5b103fbc8eff6b16f2ae Mon Sep 17 00:00:00 2001 From: Mauricio Siu Date: Thu, 9 Apr 2026 17:25:04 -0600 Subject: [PATCH 026/585] fix: add runtime type guard for cpu.value in monitoring tab Closes #4062 --- .../free/container/show-free-container-monitoring.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/dokploy/components/dashboard/monitoring/free/container/show-free-container-monitoring.tsx b/apps/dokploy/components/dashboard/monitoring/free/container/show-free-container-monitoring.tsx index fd666255c..96cd41bdd 100644 --- a/apps/dokploy/components/dashboard/monitoring/free/container/show-free-container-monitoring.tsx +++ b/apps/dokploy/components/dashboard/monitoring/free/container/show-free-container-monitoring.tsx @@ -220,11 +220,11 @@ export const ContainerFreeMonitoring = ({
- Used: {currentData.cpu.value} + Used: {String(currentData.cpu.value ?? "0%")} Date: Thu, 9 Apr 2026 17:35:42 -0600 Subject: [PATCH 027/585] fix: swap stripPrefix and addPrefix middleware order in Traefik domain config When both stripPath and internalPath are configured, addPrefix was pushed before stripPrefix causing incorrect path rewriting (e.g. /app/v2/public/api instead of /app/v2/api). Traefik executes middlewares in array order, so stripPrefix must come first. Closes #4061 --- apps/dokploy/__test__/traefik/traefik.test.ts | 20 +++++++++++++++++++ packages/server/src/utils/traefik/domain.ts | 12 ++++++----- 2 files changed, 27 insertions(+), 5 deletions(-) diff --git a/apps/dokploy/__test__/traefik/traefik.test.ts b/apps/dokploy/__test__/traefik/traefik.test.ts index fe7d0ff9d..14d45f76c 100644 --- a/apps/dokploy/__test__/traefik/traefik.test.ts +++ b/apps/dokploy/__test__/traefik/traefik.test.ts @@ -424,6 +424,26 @@ test("Custom entrypoint with internalPath adds addprefix middleware", async () = expect(router.entryPoints).toEqual(["custom"]); }); +test("stripPath and internalPath together: stripprefix must come before addprefix", async () => { + const router = await createRouterConfig( + baseApp, + { + ...baseDomain, + path: "/public", + stripPath: true, + internalPath: "/app/v2", + }, + "web", + ); + + const stripIndex = router.middlewares?.indexOf("stripprefix--1") ?? -1; + const addIndex = router.middlewares?.indexOf("addprefix--1") ?? -1; + + expect(stripIndex).toBeGreaterThanOrEqual(0); + expect(addIndex).toBeGreaterThanOrEqual(0); + expect(stripIndex).toBeLessThan(addIndex); +}); + test("Custom entrypoint with https and custom cert resolver", async () => { const router = await createRouterConfig( baseApp, diff --git a/packages/server/src/utils/traefik/domain.ts b/packages/server/src/utils/traefik/domain.ts index c1745ddab..596758b33 100644 --- a/packages/server/src/utils/traefik/domain.ts +++ b/packages/server/src/utils/traefik/domain.ts @@ -151,16 +151,18 @@ export const createRouterConfig = async ( routerConfig.middlewares?.push("redirect-to-https"); } else { // Add path rewriting middleware if needed - if (internalPath && internalPath !== "/" && internalPath !== path) { - const pathMiddleware = `addprefix-${appName}-${uniqueConfigKey}`; - routerConfig.middlewares?.push(pathMiddleware); - } - + // stripPrefix must come before addPrefix so Traefik strips the + // public path first, then prepends the internal path. if (stripPath && path && path !== "/") { const stripMiddleware = `stripprefix-${appName}-${uniqueConfigKey}`; routerConfig.middlewares?.push(stripMiddleware); } + if (internalPath && internalPath !== "/" && internalPath !== path) { + const pathMiddleware = `addprefix-${appName}-${uniqueConfigKey}`; + routerConfig.middlewares?.push(pathMiddleware); + } + // redirects - skip for preview deployments as wildcard subdomains // should not inherit parent redirect rules (e.g., www-redirect) if (domain.domainType !== "preview") { From 9687ed0d831671f468706c0fec7b650899d5e080 Mon Sep 17 00:00:00 2001 From: Mauricio Siu Date: Sat, 11 Apr 2026 00:18:23 -0600 Subject: [PATCH 028/585] feat: add invoice notification settings and email notifications for payments - Introduced a new feature allowing users to enable or disable invoice email notifications in the billing settings. - Implemented email notifications for successful invoice payments and payment failures, enhancing user communication regarding billing. - Updated the database schema to include a new column for storing user preferences on invoice notifications. - Added corresponding email templates for invoice notifications and payment failure alerts. These changes improve user experience by keeping users informed about their billing status and actions required. --- .../settings/billing/show-billing.tsx | 83 +- .../welcome-stripe/welcome-subscription.tsx | 16 +- .../drizzle/0165_abnormal_greymalkin.sql | 1 + apps/dokploy/drizzle/meta/0165_snapshot.json | 8312 +++++++++++++++++ apps/dokploy/drizzle/meta/_journal.json | 7 + apps/dokploy/pages/api/stripe/webhook.ts | 14 +- apps/dokploy/server/api/routers/stripe.ts | 16 + .../server/utils/stripe-notifications.ts | 119 + packages/server/src/db/schema/user.ts | 3 + .../emails/emails/invoice-notification.tsx | 171 + .../src/emails/emails/payment-failed.tsx | 175 + .../server/src/utils/notifications/utils.ts | 2 + .../verification/send-verification-email.tsx | 3 + 13 files changed, 8911 insertions(+), 11 deletions(-) create mode 100644 apps/dokploy/drizzle/0165_abnormal_greymalkin.sql create mode 100644 apps/dokploy/drizzle/meta/0165_snapshot.json create mode 100644 apps/dokploy/server/utils/stripe-notifications.ts create mode 100644 packages/server/src/emails/emails/invoice-notification.tsx create mode 100644 packages/server/src/emails/emails/payment-failed.tsx diff --git a/apps/dokploy/components/dashboard/settings/billing/show-billing.tsx b/apps/dokploy/components/dashboard/settings/billing/show-billing.tsx index c740f8211..cbc363dcd 100644 --- a/apps/dokploy/components/dashboard/settings/billing/show-billing.tsx +++ b/apps/dokploy/components/dashboard/settings/billing/show-billing.tsx @@ -2,6 +2,7 @@ import { loadStripe } from "@stripe/stripe-js"; import clsx from "clsx"; import { AlertTriangle, + Bell, CheckIcon, CreditCard, FileText, @@ -25,7 +26,17 @@ import { CardTitle, } from "@/components/ui/card"; import { NumberInput } from "@/components/ui/input"; +import { + Dialog, + DialogContent, + DialogDescription, + DialogHeader, + DialogTitle, + DialogTrigger, +} from "@/components/ui/dialog"; +import { Label } from "@/components/ui/label"; import { Progress } from "@/components/ui/progress"; +import { Switch } from "@/components/ui/switch"; import { Tabs, TabsList, TabsTrigger } from "@/components/ui/tabs"; import { cn } from "@/lib/utils"; import { api } from "@/utils/api"; @@ -90,6 +101,8 @@ export const ShowBilling = () => { api.stripe.createCustomerPortalSession.useMutation(); const { mutateAsync: upgradeSubscription, isPending: isUpgrading } = api.stripe.upgradeSubscription.useMutation(); + const { mutateAsync: updateInvoiceNotifications } = + api.stripe.updateInvoiceNotifications.useMutation(); const utils = api.useUtils(); const [hobbyServerQuantity, setHobbyServerQuantity] = useState(1); @@ -151,14 +164,68 @@ export const ShowBilling = () => {
- - - - Billing - - - Manage your subscription and invoices - + +
+ + + Billing + + + Manage your subscription and invoices + +
+ {(admin?.user.stripeSubscriptionId || isEnterpriseCloud) && ( + + + + + + + Notification Settings + + Configure your billing email notifications. + + +
+
+ +

+ Receive email notifications for payments and failed + charges. +

+
+ { + await updateInvoiceNotifications({ + enabled: checked, + }) + .then(() => { + utils.user.get.invalidate(); + toast.success( + checked + ? "Invoice notifications enabled" + : "Invoice notifications disabled", + ); + }) + .catch(() => { + toast.error( + "Failed to update invoice notifications", + ); + }); + }} + /> +
+
+
+ )}
)} + {permissions?.docker.read && ( + +
+ +
+
+ )} + {permissions?.monitoring.read && (
diff --git a/apps/dokploy/server/api/routers/docker.ts b/apps/dokploy/server/api/routers/docker.ts index dd322e6c3..838ab83a0 100644 --- a/apps/dokploy/server/api/routers/docker.ts +++ b/apps/dokploy/server/api/routers/docker.ts @@ -1,6 +1,9 @@ import { + containerKill, containerRemove, containerRestart, + containerStart, + containerStop, findServerById, getConfig, getContainers, @@ -42,17 +45,101 @@ export const dockerRouter = createTRPCRouter({ .string() .min(1) .regex(containerIdRegex, "Invalid container id."), + serverId: z.string().optional(), }), ) .mutation(async ({ input, ctx }) => { - const result = await containerRestart(input.containerId); + if (input.serverId) { + const server = await findServerById(input.serverId); + if (server.organizationId !== ctx.session?.activeOrganizationId) { + throw new TRPCError({ code: "UNAUTHORIZED" }); + } + } + await containerRestart(input.containerId, input.serverId); await audit(ctx, { action: "start", resourceType: "docker", resourceId: input.containerId, resourceName: input.containerId, }); - return result; + }), + + startContainer: withPermission("docker", "read") + .input( + z.object({ + containerId: z + .string() + .min(1) + .regex(containerIdRegex, "Invalid container id."), + serverId: z.string().optional(), + }), + ) + .mutation(async ({ input, ctx }) => { + if (input.serverId) { + const server = await findServerById(input.serverId); + if (server.organizationId !== ctx.session?.activeOrganizationId) { + throw new TRPCError({ code: "UNAUTHORIZED" }); + } + } + await containerStart(input.containerId, input.serverId); + await audit(ctx, { + action: "start", + resourceType: "docker", + resourceId: input.containerId, + resourceName: input.containerId, + }); + }), + + stopContainer: withPermission("docker", "read") + .input( + z.object({ + containerId: z + .string() + .min(1) + .regex(containerIdRegex, "Invalid container id."), + serverId: z.string().optional(), + }), + ) + .mutation(async ({ input, ctx }) => { + if (input.serverId) { + const server = await findServerById(input.serverId); + if (server.organizationId !== ctx.session?.activeOrganizationId) { + throw new TRPCError({ code: "UNAUTHORIZED" }); + } + } + await containerStop(input.containerId, input.serverId); + await audit(ctx, { + action: "stop", + resourceType: "docker", + resourceId: input.containerId, + resourceName: input.containerId, + }); + }), + + killContainer: withPermission("docker", "read") + .input( + z.object({ + containerId: z + .string() + .min(1) + .regex(containerIdRegex, "Invalid container id."), + serverId: z.string().optional(), + }), + ) + .mutation(async ({ input, ctx }) => { + if (input.serverId) { + const server = await findServerById(input.serverId); + if (server.organizationId !== ctx.session?.activeOrganizationId) { + throw new TRPCError({ code: "UNAUTHORIZED" }); + } + } + await containerKill(input.containerId, input.serverId); + await audit(ctx, { + action: "stop", + resourceType: "docker", + resourceId: input.containerId, + resourceName: input.containerId, + }); }), removeContainer: withPermission("docker", "read") diff --git a/packages/server/src/services/docker.ts b/packages/server/src/services/docker.ts index eb218b1e1..6c7e379c0 100644 --- a/packages/server/src/services/docker.ts +++ b/packages/server/src/services/docker.ts @@ -417,21 +417,64 @@ export const getContainerLogs = async ( } }; -export const containerRestart = async (containerId: string) => { - try { - const { stdout, stderr } = await execAsync( - `docker container restart ${containerId}`, - ); +export const containerRestart = async ( + containerId: string, + serverId?: string, +) => { + const command = `docker container restart ${containerId}`; + const { stderr } = serverId + ? await execAsyncRemote(serverId, command) + : await execAsync(command); - if (stderr) { - console.error(`Error: ${stderr}`); - return; - } + if (stderr) { + console.error(`Error: ${stderr}`); + throw new Error(stderr); + } +}; - const config = JSON.parse(stdout); +export const containerStart = async ( + containerId: string, + serverId?: string, +) => { + const command = `docker container start ${containerId}`; + const { stderr } = serverId + ? await execAsyncRemote(serverId, command) + : await execAsync(command); - return config; - } catch {} + if (stderr) { + console.error(`Error: ${stderr}`); + throw new Error(stderr); + } +}; + +export const containerStop = async ( + containerId: string, + serverId?: string, +) => { + const command = `docker container stop ${containerId}`; + const { stderr } = serverId + ? await execAsyncRemote(serverId, command) + : await execAsync(command); + + if (stderr) { + console.error(`Error: ${stderr}`); + throw new Error(stderr); + } +}; + +export const containerKill = async ( + containerId: string, + serverId?: string, +) => { + const command = `docker container kill ${containerId}`; + const { stderr } = serverId + ? await execAsyncRemote(serverId, command) + : await execAsync(command); + + if (stderr) { + console.error(`Error: ${stderr}`); + throw new Error(stderr); + } }; export const containerRemove = async ( From ddf570a8078d7b4a54ffbf21ad6d5d60b7c110ed Mon Sep 17 00:00:00 2001 From: "autofix-ci[bot]" <114827586+autofix-ci[bot]@users.noreply.github.com> Date: Tue, 14 Apr 2026 02:15:37 +0000 Subject: [PATCH 036/585] [autofix.ci] apply automated fixes --- .../compose/containers/show-compose-containers.tsx | 8 ++------ .../[environmentId]/services/compose/[composeId].tsx | 4 +--- packages/server/src/services/docker.ts | 10 ++-------- 3 files changed, 5 insertions(+), 17 deletions(-) diff --git a/apps/dokploy/components/dashboard/compose/containers/show-compose-containers.tsx b/apps/dokploy/components/dashboard/compose/containers/show-compose-containers.tsx index 4372efdbd..d156e8e1f 100644 --- a/apps/dokploy/components/dashboard/compose/containers/show-compose-containers.tsx +++ b/apps/dokploy/components/dashboard/compose/containers/show-compose-containers.tsx @@ -86,9 +86,7 @@ export const ShowComposeContainers = ({ onClick={() => refetch()} disabled={isPending} > - + @@ -253,9 +251,7 @@ const ContainerRow = ({ View Logs - - Logs for {container.name} - + Logs for {container.name}
)} {permissions?.docker.read && ( - - Containers - + Containers )} {permissions?.service.create && ( Backups diff --git a/packages/server/src/services/docker.ts b/packages/server/src/services/docker.ts index 6c7e379c0..e49adbb94 100644 --- a/packages/server/src/services/docker.ts +++ b/packages/server/src/services/docker.ts @@ -447,10 +447,7 @@ export const containerStart = async ( } }; -export const containerStop = async ( - containerId: string, - serverId?: string, -) => { +export const containerStop = async (containerId: string, serverId?: string) => { const command = `docker container stop ${containerId}`; const { stderr } = serverId ? await execAsyncRemote(serverId, command) @@ -462,10 +459,7 @@ export const containerStop = async ( } }; -export const containerKill = async ( - containerId: string, - serverId?: string, -) => { +export const containerKill = async (containerId: string, serverId?: string) => { const command = `docker container kill ${containerId}`; const { stderr } = serverId ? await execAsyncRemote(serverId, command) From 00c708483ed31030e1b0a9d6b1b28fccbf886525 Mon Sep 17 00:00:00 2001 From: Mauricio Siu Date: Mon, 13 Apr 2026 20:31:58 -0600 Subject: [PATCH 037/585] fix: use service.read permission for compose container actions Change restartContainer, startContainer, stopContainer, and killContainer endpoints to use service.read instead of docker.read so members with access to the compose can use container lifecycle actions. --- .../[environmentId]/services/compose/[composeId].tsx | 4 ++-- apps/dokploy/server/api/routers/docker.ts | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/apps/dokploy/pages/dashboard/project/[projectId]/environment/[environmentId]/services/compose/[composeId].tsx b/apps/dokploy/pages/dashboard/project/[projectId]/environment/[environmentId]/services/compose/[composeId].tsx index 5f8fa3f44..b6652db84 100644 --- a/apps/dokploy/pages/dashboard/project/[projectId]/environment/[environmentId]/services/compose/[composeId].tsx +++ b/apps/dokploy/pages/dashboard/project/[projectId]/environment/[environmentId]/services/compose/[composeId].tsx @@ -233,7 +233,7 @@ const Service = ( Deployments )} - {permissions?.docker.read && ( + {permissions?.service.read && ( Containers )} {permissions?.service.create && ( @@ -303,7 +303,7 @@ const Service = (
)} - {permissions?.docker.read && ( + {permissions?.service.read && (
Date: Mon, 13 Apr 2026 20:32:11 -0600 Subject: [PATCH 038/585] refactor: remove duplicate import of ShowComposeContainers component Eliminate redundant import statement for ShowComposeContainers in the compose service page, streamlining the code and improving readability. --- .../[environmentId]/services/compose/[composeId].tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/dokploy/pages/dashboard/project/[projectId]/environment/[environmentId]/services/compose/[composeId].tsx b/apps/dokploy/pages/dashboard/project/[projectId]/environment/[environmentId]/services/compose/[composeId].tsx index b6652db84..ed116de43 100644 --- a/apps/dokploy/pages/dashboard/project/[projectId]/environment/[environmentId]/services/compose/[composeId].tsx +++ b/apps/dokploy/pages/dashboard/project/[projectId]/environment/[environmentId]/services/compose/[composeId].tsx @@ -22,9 +22,9 @@ import { ShowSchedules } from "@/components/dashboard/application/schedules/show import { ShowVolumeBackups } from "@/components/dashboard/application/volume-backups/show-volume-backups"; import { AddCommandCompose } from "@/components/dashboard/compose/advanced/add-command"; import { IsolatedDeploymentTab } from "@/components/dashboard/compose/advanced/add-isolation"; +import { ShowComposeContainers } from "@/components/dashboard/compose/containers/show-compose-containers"; import { DeleteService } from "@/components/dashboard/compose/delete-service"; import { ShowGeneralCompose } from "@/components/dashboard/compose/general/show"; -import { ShowComposeContainers } from "@/components/dashboard/compose/containers/show-compose-containers"; import { ShowDockerLogsCompose } from "@/components/dashboard/compose/logs/show"; import { ShowDockerLogsStack } from "@/components/dashboard/compose/logs/show-stack"; import { UpdateCompose } from "@/components/dashboard/compose/update-compose"; From a48306a2c67190fcf42d0b985ec71bc0b2b1c2c0 Mon Sep 17 00:00:00 2001 From: Mauricio Siu Date: Mon, 13 Apr 2026 20:34:06 -0600 Subject: [PATCH 039/585] fix: address PR review feedback - Use "kill" audit action for killContainer instead of "stop" - Pass undefined instead of empty string for optional serverId --- .../[environmentId]/services/compose/[composeId].tsx | 2 +- apps/dokploy/server/api/routers/docker.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/dokploy/pages/dashboard/project/[projectId]/environment/[environmentId]/services/compose/[composeId].tsx b/apps/dokploy/pages/dashboard/project/[projectId]/environment/[environmentId]/services/compose/[composeId].tsx index ed116de43..ba575706d 100644 --- a/apps/dokploy/pages/dashboard/project/[projectId]/environment/[environmentId]/services/compose/[composeId].tsx +++ b/apps/dokploy/pages/dashboard/project/[projectId]/environment/[environmentId]/services/compose/[composeId].tsx @@ -307,7 +307,7 @@ const Service = (
diff --git a/apps/dokploy/server/api/routers/docker.ts b/apps/dokploy/server/api/routers/docker.ts index d319e5a16..cadda0286 100644 --- a/apps/dokploy/server/api/routers/docker.ts +++ b/apps/dokploy/server/api/routers/docker.ts @@ -135,7 +135,7 @@ export const dockerRouter = createTRPCRouter({ } await containerKill(input.containerId, input.serverId); await audit(ctx, { - action: "stop", + action: "kill", resourceType: "docker", resourceId: input.containerId, resourceName: input.containerId, From 385850f354a997a0470105520d1e66b82539027d Mon Sep 17 00:00:00 2001 From: Mauricio Siu Date: Mon, 13 Apr 2026 20:36:04 -0600 Subject: [PATCH 040/585] fix: update audit action for container termination Change the audit action from "kill" to "stop" for the containerKill function to better reflect the operation being performed. This aligns the logging with the intended action and improves clarity in audit records. --- apps/dokploy/server/api/routers/docker.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/dokploy/server/api/routers/docker.ts b/apps/dokploy/server/api/routers/docker.ts index cadda0286..d319e5a16 100644 --- a/apps/dokploy/server/api/routers/docker.ts +++ b/apps/dokploy/server/api/routers/docker.ts @@ -135,7 +135,7 @@ export const dockerRouter = createTRPCRouter({ } await containerKill(input.containerId, input.serverId); await audit(ctx, { - action: "kill", + action: "stop", resourceType: "docker", resourceId: input.containerId, resourceName: input.containerId, From 638b3dd54632b156f05b53f49b8466caa12d9fd5 Mon Sep 17 00:00:00 2001 From: Mauricio Siu Date: Mon, 13 Apr 2026 20:48:17 -0600 Subject: [PATCH 041/585] feat: add context menu to service cards Right-click on service cards to quickly Start, Deploy, Stop, or Delete a service without navigating into it. Uses shadcn/ui ContextMenu component built on @radix-ui/react-context-menu. Delete action shows a confirmation dialog. LibSQL services are excluded since they lack standard mutation endpoints. --- apps/dokploy/components/ui/context-menu.tsx | 198 +++++++++ apps/dokploy/package.json | 1 + .../environment/[environmentId].tsx | 375 +++++++++++++----- pnpm-lock.yaml | 30 ++ 4 files changed, 503 insertions(+), 101 deletions(-) create mode 100644 apps/dokploy/components/ui/context-menu.tsx diff --git a/apps/dokploy/components/ui/context-menu.tsx b/apps/dokploy/components/ui/context-menu.tsx new file mode 100644 index 000000000..2ff9f812e --- /dev/null +++ b/apps/dokploy/components/ui/context-menu.tsx @@ -0,0 +1,198 @@ +import * as ContextMenuPrimitive from "@radix-ui/react-context-menu"; +import { Check, ChevronRight, Circle } from "lucide-react"; +import * as React from "react"; + +import { cn } from "@/lib/utils"; + +const ContextMenu = ContextMenuPrimitive.Root; + +const ContextMenuTrigger = ContextMenuPrimitive.Trigger; + +const ContextMenuGroup = ContextMenuPrimitive.Group; + +const ContextMenuPortal = ContextMenuPrimitive.Portal; + +const ContextMenuSub = ContextMenuPrimitive.Sub; + +const ContextMenuRadioGroup = ContextMenuPrimitive.RadioGroup; + +const ContextMenuSubTrigger = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef & { + inset?: boolean; + } +>(({ className, inset, children, ...props }, ref) => ( + + {children} + + +)); +ContextMenuSubTrigger.displayName = ContextMenuPrimitive.SubTrigger.displayName; + +const ContextMenuSubContent = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +ContextMenuSubContent.displayName = ContextMenuPrimitive.SubContent.displayName; + +const ContextMenuContent = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + + + +)); +ContextMenuContent.displayName = ContextMenuPrimitive.Content.displayName; + +const ContextMenuItem = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef & { + inset?: boolean; + } +>(({ className, inset, ...props }, ref) => ( + +)); +ContextMenuItem.displayName = ContextMenuPrimitive.Item.displayName; + +const ContextMenuCheckboxItem = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, children, checked, ...props }, ref) => ( + + + + + + + {children} + +)); +ContextMenuCheckboxItem.displayName = + ContextMenuPrimitive.CheckboxItem.displayName; + +const ContextMenuRadioItem = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, children, ...props }, ref) => ( + + + + + + + {children} + +)); +ContextMenuRadioItem.displayName = ContextMenuPrimitive.RadioItem.displayName; + +const ContextMenuLabel = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef & { + inset?: boolean; + } +>(({ className, inset, ...props }, ref) => ( + +)); +ContextMenuLabel.displayName = ContextMenuPrimitive.Label.displayName; + +const ContextMenuSeparator = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +ContextMenuSeparator.displayName = ContextMenuPrimitive.Separator.displayName; + +const ContextMenuShortcut = ({ + className, + ...props +}: React.HTMLAttributes) => { + return ( + + ); +}; +ContextMenuShortcut.displayName = "ContextMenuShortcut"; + +export { + ContextMenu, + ContextMenuTrigger, + ContextMenuContent, + ContextMenuItem, + ContextMenuCheckboxItem, + ContextMenuRadioItem, + ContextMenuLabel, + ContextMenuSeparator, + ContextMenuShortcut, + ContextMenuGroup, + ContextMenuPortal, + ContextMenuSub, + ContextMenuSubContent, + ContextMenuSubTrigger, + ContextMenuRadioGroup, +}; diff --git a/apps/dokploy/package.json b/apps/dokploy/package.json index f7eff0aba..eadbe989d 100644 --- a/apps/dokploy/package.json +++ b/apps/dokploy/package.json @@ -67,6 +67,7 @@ "@radix-ui/react-avatar": "^1.1.10", "@radix-ui/react-checkbox": "^1.3.2", "@radix-ui/react-collapsible": "^1.1.11", + "@radix-ui/react-context-menu": "^2.2.16", "@radix-ui/react-dialog": "^1.1.14", "@radix-ui/react-dropdown-menu": "^2.1.15", "@radix-ui/react-label": "^2.1.7", diff --git a/apps/dokploy/pages/dashboard/project/[projectId]/environment/[environmentId].tsx b/apps/dokploy/pages/dashboard/project/[projectId]/environment/[environmentId].tsx index a91138f66..08b130b7c 100644 --- a/apps/dokploy/pages/dashboard/project/[projectId]/environment/[environmentId].tsx +++ b/apps/dokploy/pages/dashboard/project/[projectId]/environment/[environmentId].tsx @@ -12,6 +12,7 @@ import { Loader2, Play, PlusIcon, + RefreshCw, Search, ServerIcon, SquareTerminal, @@ -68,6 +69,14 @@ import { CommandInput, CommandItem, } from "@/components/ui/command"; +import { + ContextMenu, + ContextMenuContent, + ContextMenuItem, + ContextMenuLabel, + ContextMenuSeparator, + ContextMenuTrigger, +} from "@/components/ui/context-menu"; import { Dialog, DialogContent, @@ -424,6 +433,7 @@ const EnvironmentPage = ( const [isBulkDeleteDialogOpen, setIsBulkDeleteDialogOpen] = useState(false); const [deleteVolumes, setDeleteVolumes] = useState(false); const [selectedServerId, setSelectedServerId] = useState("all"); + const [serviceToDelete, setServiceToDelete] = useState(null); const handleSelectAll = () => { if (selectedServices.length === filteredServices.length) { @@ -814,6 +824,91 @@ const EnvironmentPage = ( setIsBulkActionLoading(false); }; + const getServiceActions = (service: Services) => { + switch (service.type) { + case "application": + return applicationActions; + case "compose": + return composeActions; + case "postgres": + return postgresActions; + case "mysql": + return mysqlActions; + case "mariadb": + return mariadbActions; + case "redis": + return redisActions; + case "mongo": + return mongoActions; + default: + return null; + } + }; + + const getServiceIdKey = (service: Services) => { + switch (service.type) { + case "application": + return "applicationId"; + case "compose": + return "composeId"; + case "postgres": + return "postgresId"; + case "mysql": + return "mysqlId"; + case "mariadb": + return "mariadbId"; + case "redis": + return "redisId"; + case "mongo": + return "mongoId"; + default: + return null; + } + }; + + const handleServiceAction = async ( + service: Services, + action: "start" | "stop" | "deploy", + ) => { + const actions = getServiceActions(service); + const idKey = getServiceIdKey(service); + if (!actions || !idKey) return; + + try { + await actions[action].mutateAsync({ + [idKey]: service.id, + } as any); + toast.success( + `Service ${service.name} ${action === "deploy" ? "queued for deployment" : `${action}ed`} successfully`, + ); + refetch(); + } catch (error) { + toast.error( + `Error ${action === "stop" ? "stopp" : action}ing service ${service.name}: ${error instanceof Error ? error.message : "Unknown error"}`, + ); + } + }; + + const handleServiceDelete = async (service: Services) => { + const actions = getServiceActions(service); + const idKey = getServiceIdKey(service); + if (!actions || !idKey) return; + + try { + await actions.delete.mutateAsync({ + [idKey]: service.id, + } as any); + await utils.environment.one.invalidate({ environmentId }); + toast.success(`Service ${service.name} deleted successfully`); + refetch(); + } catch (error) { + toast.error( + `Error deleting service ${service.name}: ${error instanceof Error ? error.message : "Unknown error"}`, + ); + } + setServiceToDelete(null); + }; + // Get unique servers from services const availableServers = useMemo(() => { if (!applications) return []; @@ -1472,110 +1567,156 @@ const EnvironmentPage = (
{filteredServices?.map((service) => ( - - - {service.serverId && ( -
- -
- )} -
- -
- -
- handleServiceSelect(service.id, e) - } + + + -
- -
-
- - - -
-
- - {service.name} - - {service.description && ( - - {service.description} - - )} -
- - - {service.type === "postgres" && ( - - )} - {service.type === "redis" && ( - - )} - {service.type === "mariadb" && ( - - )} - {service.type === "mongo" && ( - - )} - {service.type === "mysql" && ( - - )} - {service.type === "application" && - (service.icon ? ( - // biome-ignore lint/performance/noImgElement: application icon is data URL - {service.name} - ) : ( - - ))} - {service.type === "compose" && ( - - )} - {service.type === "libsql" && ( - - )} - -
-
-
- -
- {service.serverName && ( -
- - - {service.serverName} - + + {service.serverId && ( +
+
)} - - Created - -
- - - +
+ +
+ +
+ handleServiceSelect(service.id, e) + } + > +
+ +
+
+ + + +
+
+ + {service.name} + + {service.description && ( + + {service.description} + + )} +
+ + + {service.type === "postgres" && ( + + )} + {service.type === "redis" && ( + + )} + {service.type === "mariadb" && ( + + )} + {service.type === "mongo" && ( + + )} + {service.type === "mysql" && ( + + )} + {service.type === "application" && + (service.icon ? ( + // biome-ignore lint/performance/noImgElement: application icon is data URL + {service.name} + ) : ( + + ))} + {service.type === "compose" && ( + + )} + {service.type === "libsql" && ( + + )} + +
+
+
+ +
+ {service.serverName && ( +
+ + + {service.serverName} + +
+ )} + + Created + +
+
+ + + + {service.type !== "libsql" && ( + + + {service.name} + + + + handleServiceAction(service, "start") + } + > + + Start + + + handleServiceAction(service, "deploy") + } + > + + Deploy + + + handleServiceAction(service, "stop") + } + > + + Stop + + + setServiceToDelete(service)} + > + + Delete + + + )} + ))}
@@ -1586,6 +1727,38 @@ const EnvironmentPage = (
+ + {/* Single Service Delete Dialog */} + !open && setServiceToDelete(null)} + > + + + Delete Service + + Are you sure you want to delete{" "} + {serviceToDelete?.name}? + This action cannot be undone. + + + + + + + +
); }; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 4f962c675..02c06680b 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -176,6 +176,9 @@ importers: '@radix-ui/react-collapsible': specifier: ^1.1.11 version: 1.1.12(@types/react-dom@18.3.0)(@types/react@18.3.5)(react-dom@18.2.0(react@18.2.0))(react@18.2.0) + '@radix-ui/react-context-menu': + specifier: ^2.2.16 + version: 2.2.16(@types/react-dom@18.3.0)(@types/react@18.3.5)(react-dom@18.2.0(react@18.2.0))(react@18.2.0) '@radix-ui/react-dialog': specifier: ^1.1.14 version: 1.1.15(@types/react-dom@18.3.0)(@types/react@18.3.5)(react-dom@18.2.0(react@18.2.0))(react@18.2.0) @@ -2801,6 +2804,19 @@ packages: '@types/react': optional: true + '@radix-ui/react-context-menu@2.2.16': + resolution: {integrity: sha512-O8morBEW+HsVG28gYDZPTrT9UUovQUlJue5YO836tiTJhuIWBm/zQHc7j388sHWtdH/xUZurK9olD2+pcqx5ww==} + peerDependencies: + '@types/react': 18.3.5 + '@types/react-dom': 18.3.0 + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + '@radix-ui/react-context@1.0.0': resolution: {integrity: sha512-1pVM9RfOQ+n/N5PJK33kRSKsr1glNxomxONs5c49MliinBY6Yw2Q995qfBUUo0/Mbg05B/sGA0gkgPI7kmSHBg==} peerDependencies: @@ -10633,6 +10649,20 @@ snapshots: optionalDependencies: '@types/react': 18.3.5 + '@radix-ui/react-context-menu@2.2.16(@types/react-dom@18.3.0)(@types/react@18.3.5)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)': + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-context': 1.1.2(@types/react@18.3.5)(react@18.2.0) + '@radix-ui/react-menu': 2.1.16(@types/react-dom@18.3.0)(@types/react@18.3.5)(react-dom@18.2.0(react@18.2.0))(react@18.2.0) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.0)(@types/react@18.3.5)(react-dom@18.2.0(react@18.2.0))(react@18.2.0) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@18.3.5)(react@18.2.0) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@18.3.5)(react@18.2.0) + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + optionalDependencies: + '@types/react': 18.3.5 + '@types/react-dom': 18.3.0 + '@radix-ui/react-context@1.0.0(react@18.2.0)': dependencies: '@babel/runtime': 7.28.6 From 7f25ddca44ccb70827bfeac05c79822fa5bc4a8e Mon Sep 17 00:00:00 2001 From: Mauricio Siu Date: Mon, 13 Apr 2026 20:51:22 -0600 Subject: [PATCH 042/585] fix: add loading feedback and invalidation to context menu actions Use toast.promise for loading/success/error states and invalidate environment query after actions complete to update service status. --- .../environment/[environmentId].tsx | 69 ++++++++++++------- 1 file changed, 44 insertions(+), 25 deletions(-) diff --git a/apps/dokploy/pages/dashboard/project/[projectId]/environment/[environmentId].tsx b/apps/dokploy/pages/dashboard/project/[projectId]/environment/[environmentId].tsx index 08b130b7c..051415a74 100644 --- a/apps/dokploy/pages/dashboard/project/[projectId]/environment/[environmentId].tsx +++ b/apps/dokploy/pages/dashboard/project/[projectId]/environment/[environmentId].tsx @@ -874,19 +874,34 @@ const EnvironmentPage = ( const idKey = getServiceIdKey(service); if (!actions || !idKey) return; - try { - await actions[action].mutateAsync({ - [idKey]: service.id, - } as any); - toast.success( - `Service ${service.name} ${action === "deploy" ? "queued for deployment" : `${action}ed`} successfully`, - ); - refetch(); - } catch (error) { - toast.error( - `Error ${action === "stop" ? "stopp" : action}ing service ${service.name}: ${error instanceof Error ? error.message : "Unknown error"}`, - ); - } + const actionLabels = { + start: { loading: "Starting", success: "started", error: "starting" }, + stop: { loading: "Stopping", success: "stopped", error: "stopping" }, + deploy: { + loading: "Deploying", + success: "queued for deployment", + error: "deploying", + }, + }; + + const labels = actionLabels[action]; + + toast.promise( + (async () => { + await actions[action].mutateAsync({ + [idKey]: service.id, + } as any); + })(), + { + loading: `${labels.loading} ${service.name}...`, + success: () => { + utils.environment.one.invalidate({ environmentId }); + return `${service.name} ${labels.success} successfully`; + }, + error: (error) => + `Error ${labels.error} ${service.name}: ${error instanceof Error ? error.message : "Unknown error"}`, + }, + ); }; const handleServiceDelete = async (service: Services) => { @@ -894,18 +909,22 @@ const EnvironmentPage = ( const idKey = getServiceIdKey(service); if (!actions || !idKey) return; - try { - await actions.delete.mutateAsync({ - [idKey]: service.id, - } as any); - await utils.environment.one.invalidate({ environmentId }); - toast.success(`Service ${service.name} deleted successfully`); - refetch(); - } catch (error) { - toast.error( - `Error deleting service ${service.name}: ${error instanceof Error ? error.message : "Unknown error"}`, - ); - } + toast.promise( + (async () => { + await actions.delete.mutateAsync({ + [idKey]: service.id, + } as any); + })(), + { + loading: `Deleting ${service.name}...`, + success: () => { + utils.environment.one.invalidate({ environmentId }); + return `${service.name} deleted successfully`; + }, + error: (error) => + `Error deleting ${service.name}: ${error instanceof Error ? error.message : "Unknown error"}`, + }, + ); setServiceToDelete(null); }; From 9af745ce6701f712461635ca50e2d2a442661b0d Mon Sep 17 00:00:00 2001 From: Mauricio Siu Date: Mon, 13 Apr 2026 21:56:53 -0600 Subject: [PATCH 043/585] feat: add view mounts, view config, and terminal to container actions Add a new "View Mounts" action to the container dropdown that displays volume and bind mounts in a formatted table (type, source, destination, mode, read/write). Also add "View Config" and "Terminal" actions to the compose containers tab which previously only had logs and lifecycle actions. --- .../containers/show-compose-containers.tsx | 17 +++ .../docker/mounts/show-container-mounts.tsx | 112 ++++++++++++++++++ .../dashboard/docker/show/columns.tsx | 5 + 3 files changed, 134 insertions(+) create mode 100644 apps/dokploy/components/dashboard/docker/mounts/show-container-mounts.tsx diff --git a/apps/dokploy/components/dashboard/compose/containers/show-compose-containers.tsx b/apps/dokploy/components/dashboard/compose/containers/show-compose-containers.tsx index d156e8e1f..68260bc2d 100644 --- a/apps/dokploy/components/dashboard/compose/containers/show-compose-containers.tsx +++ b/apps/dokploy/components/dashboard/compose/containers/show-compose-containers.tsx @@ -36,6 +36,9 @@ import { TableRow, } from "@/components/ui/table"; import { api } from "@/utils/api"; +import { ShowContainerConfig } from "@/components/dashboard/docker/config/show-container-config"; +import { ShowContainerMounts } from "@/components/dashboard/docker/mounts/show-container-mounts"; +import { DockerTerminalModal } from "@/components/dashboard/docker/terminal/docker-terminal-modal"; const DockerLogsId = dynamic( () => @@ -217,6 +220,20 @@ const ContainerRow = ({ View Logs + + + + Terminal + { + const { data } = api.docker.getConfig.useQuery( + { + containerId, + serverId, + }, + { + enabled: !!containerId, + }, + ); + + const mounts: Mount[] = data?.Mounts ?? []; + + return ( + + + e.preventDefault()} + > + View Mounts + + + + + Container Mounts + + Volume and bind mounts for this container + + +
+ {mounts.length === 0 ? ( +
+ No mounts found for this container. +
+ ) : ( + + + + Type + Source + Destination + Mode + Read/Write + + + + {mounts.map((mount, index) => ( + + + {mount.Type} + + + {mount.Name || mount.Source} + + + {mount.Destination} + + + {mount.Mode || "-"} + + + + {mount.RW ? "RW" : "RO"} + + + + ))} + +
+ )} +
+
+
+ ); +}; diff --git a/apps/dokploy/components/dashboard/docker/show/columns.tsx b/apps/dokploy/components/dashboard/docker/show/columns.tsx index 33c104d97..a51cb8c62 100644 --- a/apps/dokploy/components/dashboard/docker/show/columns.tsx +++ b/apps/dokploy/components/dashboard/docker/show/columns.tsx @@ -10,6 +10,7 @@ import { } from "@/components/ui/dropdown-menu"; import { ShowContainerConfig } from "../config/show-container-config"; import { ShowDockerModalLogs } from "../logs/show-docker-modal-logs"; +import { ShowContainerMounts } from "../mounts/show-container-mounts"; import { RemoveContainerDialog } from "../remove/remove-container"; import { DockerTerminalModal } from "../terminal/docker-terminal-modal"; import { UploadFileModal } from "../upload/upload-file-modal"; @@ -123,6 +124,10 @@ export const columns: ColumnDef[] = [ containerId={container.containerId} serverId={container.serverId || ""} /> + Date: Mon, 13 Apr 2026 22:04:46 -0600 Subject: [PATCH 044/585] feat: add container networks view to dashboard Integrate a new component, ShowContainerNetworks, to display network details for each container in the dashboard. This includes a dialog that shows network information such as IP address, gateway, and MAC address, enhancing the container management capabilities. --- .../containers/show-compose-containers.tsx | 5 + .../networks/show-container-networks.tsx | 119 ++++++++++++++++++ .../dashboard/docker/show/columns.tsx | 5 + 3 files changed, 129 insertions(+) create mode 100644 apps/dokploy/components/dashboard/docker/networks/show-container-networks.tsx diff --git a/apps/dokploy/components/dashboard/compose/containers/show-compose-containers.tsx b/apps/dokploy/components/dashboard/compose/containers/show-compose-containers.tsx index 68260bc2d..7787d00e7 100644 --- a/apps/dokploy/components/dashboard/compose/containers/show-compose-containers.tsx +++ b/apps/dokploy/components/dashboard/compose/containers/show-compose-containers.tsx @@ -38,6 +38,7 @@ import { import { api } from "@/utils/api"; import { ShowContainerConfig } from "@/components/dashboard/docker/config/show-container-config"; import { ShowContainerMounts } from "@/components/dashboard/docker/mounts/show-container-mounts"; +import { ShowContainerNetworks } from "@/components/dashboard/docker/networks/show-container-networks"; import { DockerTerminalModal } from "@/components/dashboard/docker/terminal/docker-terminal-modal"; const DockerLogsId = dynamic( @@ -228,6 +229,10 @@ const ContainerRow = ({ containerId={container.containerId} serverId={serverId || ""} /> + { + const { data } = api.docker.getConfig.useQuery( + { + containerId, + serverId, + }, + { + enabled: !!containerId, + }, + ); + + const networks: Record = + data?.NetworkSettings?.Networks ?? {}; + const entries = Object.entries(networks); + + return ( + + + e.preventDefault()} + > + View Networks + + + + + Container Networks + + Networks attached to this container + + +
+ {entries.length === 0 ? ( +
+ No networks found for this container. +
+ ) : ( + + + + Network + IP Address + Gateway + MAC Address + Aliases + + + + {entries.map(([name, network]) => ( + + + {name} + + + {network.IPAddress + ? `${network.IPAddress}/${network.IPPrefixLen}` + : "-"} + + + {network.Gateway || "-"} + + + {network.MacAddress || "-"} + + + {network.Aliases?.join(", ") || "-"} + + + ))} + +
+ )} +
+
+
+ ); +}; diff --git a/apps/dokploy/components/dashboard/docker/show/columns.tsx b/apps/dokploy/components/dashboard/docker/show/columns.tsx index a51cb8c62..fa06f4d70 100644 --- a/apps/dokploy/components/dashboard/docker/show/columns.tsx +++ b/apps/dokploy/components/dashboard/docker/show/columns.tsx @@ -11,6 +11,7 @@ import { import { ShowContainerConfig } from "../config/show-container-config"; import { ShowDockerModalLogs } from "../logs/show-docker-modal-logs"; import { ShowContainerMounts } from "../mounts/show-container-mounts"; +import { ShowContainerNetworks } from "../networks/show-container-networks"; import { RemoveContainerDialog } from "../remove/remove-container"; import { DockerTerminalModal } from "../terminal/docker-terminal-modal"; import { UploadFileModal } from "../upload/upload-file-modal"; @@ -128,6 +129,10 @@ export const columns: ColumnDef[] = [ containerId={container.containerId} serverId={container.serverId || ""} /> + Date: Tue, 14 Apr 2026 11:58:21 +0200 Subject: [PATCH 045/585] fix(ui): update favicon dynamically based on app theme Add icon-light.svg and icon-dark.svg with fixed fills (black/white) instead of relying on CSS prefers-color-scheme media query. Update WhitelabelingProvider to use useTheme() and switch favicon based on resolvedTheme (dark/light/unknown). When custom faviconUrl is set, it takes precedence. Closes #4100. --- .../proprietary/whitelabeling/whitelabeling-provider.tsx | 9 ++++++++- apps/dokploy/public/icon-dark.svg | 5 +++++ apps/dokploy/public/icon-light.svg | 5 +++++ 3 files changed, 18 insertions(+), 1 deletion(-) create mode 100644 apps/dokploy/public/icon-dark.svg create mode 100644 apps/dokploy/public/icon-light.svg diff --git a/apps/dokploy/components/proprietary/whitelabeling/whitelabeling-provider.tsx b/apps/dokploy/components/proprietary/whitelabeling/whitelabeling-provider.tsx index a1a327561..23c99a78e 100644 --- a/apps/dokploy/components/proprietary/whitelabeling/whitelabeling-provider.tsx +++ b/apps/dokploy/components/proprietary/whitelabeling/whitelabeling-provider.tsx @@ -1,21 +1,28 @@ "use client"; import Head from "next/head"; +import { useTheme } from "next-themes"; import { api } from "@/utils/api"; export function WhitelabelingProvider() { + const { resolvedTheme } = useTheme(); const { data: config } = api.whitelabeling.getPublic.useQuery(undefined, { staleTime: 5 * 60 * 1000, refetchOnWindowFocus: false, }); + const faviconHref = config?.faviconUrl + ?? (resolvedTheme === "dark" ? "/icon-dark.svg" + : resolvedTheme === "light" ? "/icon-light.svg" + : "/icon.svg"); + if (!config) return null; return ( <> {config.metaTitle && {config.metaTitle}} - {config.faviconUrl && } + {config.customCss && ( diff --git a/apps/dokploy/public/icon-dark.svg b/apps/dokploy/public/icon-dark.svg new file mode 100644 index 000000000..5faafdb37 --- /dev/null +++ b/apps/dokploy/public/icon-dark.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/apps/dokploy/public/icon-light.svg b/apps/dokploy/public/icon-light.svg new file mode 100644 index 000000000..6121a89dd --- /dev/null +++ b/apps/dokploy/public/icon-light.svg @@ -0,0 +1,5 @@ + + + + + From 689ae51ee75db7704f977d8e66b77687d095a867 Mon Sep 17 00:00:00 2001 From: Guillaume LECOMTE Date: Tue, 14 Apr 2026 12:16:57 +0200 Subject: [PATCH 046/585] fix(ui): remove early return to render theme-aware favicon immediately The previous implementation returned null when config was not yet loaded, preventing the theme-aware favicon from rendering during initial page load. Now uses optional chaining (config?.metaTitle, config?.customCss) throughout so the always renders with the correct theme-aware favicon regardless of config loading state. Fixes P1 issue from code review. --- .../proprietary/whitelabeling/whitelabeling-provider.tsx | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/apps/dokploy/components/proprietary/whitelabeling/whitelabeling-provider.tsx b/apps/dokploy/components/proprietary/whitelabeling/whitelabeling-provider.tsx index 23c99a78e..7fce2a935 100644 --- a/apps/dokploy/components/proprietary/whitelabeling/whitelabeling-provider.tsx +++ b/apps/dokploy/components/proprietary/whitelabeling/whitelabeling-provider.tsx @@ -16,16 +16,14 @@ export function WhitelabelingProvider() { : resolvedTheme === "light" ? "/icon-light.svg" : "/icon.svg"); - if (!config) return null; - return ( <> - {config.metaTitle && {config.metaTitle}} + {config?.metaTitle && {config.metaTitle}} - {config.customCss && ( + {config?.customCss && (