Merge pull request #2321 from Dokploy/1738-database-placement-constraints

feat(cluster-settings): Add swarm settings for databases
This commit is contained in:
Mauricio Siu
2025-08-03 17:41:08 -06:00
committed by GitHub
20 changed files with 7128 additions and 333 deletions
@@ -1,3 +1,9 @@
import { zodResolver } from "@hookform/resolvers/zod";
import { HelpCircle, Settings } from "lucide-react";
import { useEffect } from "react";
import { useForm } from "react-hook-form";
import { toast } from "sonner";
import { z } from "zod";
import { AlertBlock } from "@/components/shared/alert-block"; import { AlertBlock } from "@/components/shared/alert-block";
import { CodeEditor } from "@/components/shared/code-editor"; import { CodeEditor } from "@/components/shared/code-editor";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
@@ -26,12 +32,6 @@ import {
TooltipTrigger, TooltipTrigger,
} from "@/components/ui/tooltip"; } from "@/components/ui/tooltip";
import { api } from "@/utils/api"; import { api } from "@/utils/api";
import { zodResolver } from "@hookform/resolvers/zod";
import { HelpCircle, Settings } from "lucide-react";
import { useEffect } from "react";
import { useForm } from "react-hook-form";
import { toast } from "sonner";
import { z } from "zod";
const HealthCheckSwarmSchema = z const HealthCheckSwarmSchema = z
.object({ .object({
@@ -181,21 +181,38 @@ const addSwarmSettings = z.object({
type AddSwarmSettings = z.infer<typeof addSwarmSettings>; type AddSwarmSettings = z.infer<typeof addSwarmSettings>;
interface Props { interface Props {
applicationId: string; id: string;
type: "postgres" | "mariadb" | "mongo" | "mysql" | "redis" | "application";
} }
export const AddSwarmSettings = ({ applicationId }: Props) => { export const AddSwarmSettings = ({ id, type }: Props) => {
const { data, refetch } = api.application.one.useQuery( const queryMap = {
{ postgres: () =>
applicationId, api.postgres.one.useQuery({ postgresId: id }, { enabled: !!id }),
}, redis: () => api.redis.one.useQuery({ redisId: id }, { enabled: !!id }),
{ mysql: () => api.mysql.one.useQuery({ mysqlId: id }, { enabled: !!id }),
enabled: !!applicationId, mariadb: () =>
}, api.mariadb.one.useQuery({ mariadbId: id }, { enabled: !!id }),
); application: () =>
api.application.one.useQuery({ applicationId: id }, { enabled: !!id }),
mongo: () => api.mongo.one.useQuery({ mongoId: id }, { enabled: !!id }),
};
const { data, refetch } = queryMap[type]
? queryMap[type]()
: api.mongo.one.useQuery({ mongoId: id }, { enabled: !!id });
const { mutateAsync, isError, error, isLoading } = const mutationMap = {
api.application.update.useMutation(); postgres: () => api.postgres.update.useMutation(),
redis: () => api.redis.update.useMutation(),
mysql: () => api.mysql.update.useMutation(),
mariadb: () => api.mariadb.update.useMutation(),
application: () => api.application.update.useMutation(),
mongo: () => api.mongo.update.useMutation(),
};
const { mutateAsync, isError, error, isLoading } = mutationMap[type]
? mutationMap[type]()
: api.mongo.update.useMutation();
const form = useForm<AddSwarmSettings>({ const form = useForm<AddSwarmSettings>({
defaultValues: { defaultValues: {
@@ -244,7 +261,12 @@ export const AddSwarmSettings = ({ applicationId }: Props) => {
const onSubmit = async (data: AddSwarmSettings) => { const onSubmit = async (data: AddSwarmSettings) => {
await mutateAsync({ await mutateAsync({
applicationId, applicationId: id || "",
postgresId: id || "",
redisId: id || "",
mysqlId: id || "",
mariadbId: id || "",
mongoId: id || "",
healthCheckSwarm: data.healthCheckSwarm, healthCheckSwarm: data.healthCheckSwarm,
restartPolicySwarm: data.restartPolicySwarm, restartPolicySwarm: data.restartPolicySwarm,
placementSwarm: data.placementSwarm, placementSwarm: data.placementSwarm,
@@ -270,7 +292,7 @@ export const AddSwarmSettings = ({ applicationId }: Props) => {
Swarm Settings Swarm Settings
</Button> </Button>
</DialogTrigger> </DialogTrigger>
<DialogContent className="sm:max-w-5xl p-0"> <DialogContent className="sm:max-w-5xl">
<DialogHeader> <DialogHeader>
<DialogTitle>Swarm Settings</DialogTitle> <DialogTitle>Swarm Settings</DialogTitle>
<DialogDescription> <DialogDescription>
@@ -278,10 +300,10 @@ export const AddSwarmSettings = ({ applicationId }: Props) => {
</DialogDescription> </DialogDescription>
</DialogHeader> </DialogHeader>
{isError && <AlertBlock type="error">{error?.message}</AlertBlock>} {isError && <AlertBlock type="error">{error?.message}</AlertBlock>}
<div className="px-4"> <div>
<AlertBlock type="info"> <AlertBlock type="info">
Changing settings such as placements may cause the logs/monitoring Changing settings such as placements may cause the logs/monitoring,
to be unavailable. backups and other features to be unavailable.
</AlertBlock> </AlertBlock>
</div> </div>
@@ -289,13 +311,13 @@ export const AddSwarmSettings = ({ applicationId }: Props) => {
<form <form
id="hook-form-add-permissions" id="hook-form-add-permissions"
onSubmit={form.handleSubmit(onSubmit)} onSubmit={form.handleSubmit(onSubmit)}
className="grid grid-cols-1 md:grid-cols-2 w-full gap-4 relative" className="grid grid-cols-1 md:grid-cols-2 w-full gap-4 relative mt-4"
> >
<FormField <FormField
control={form.control} control={form.control}
name="healthCheckSwarm" name="healthCheckSwarm"
render={({ field }) => ( render={({ field }) => (
<FormItem className="relative max-lg:px-4 lg:pl-6 "> <FormItem className="relative ">
<FormLabel>Health Check</FormLabel> <FormLabel>Health Check</FormLabel>
<TooltipProvider delayDuration={0}> <TooltipProvider delayDuration={0}>
<Tooltip> <Tooltip>
@@ -351,7 +373,7 @@ export const AddSwarmSettings = ({ applicationId }: Props) => {
control={form.control} control={form.control}
name="restartPolicySwarm" name="restartPolicySwarm"
render={({ field }) => ( render={({ field }) => (
<FormItem className="relative max-lg:px-4 lg:pr-6 "> <FormItem className="relative ">
<FormLabel>Restart Policy</FormLabel> <FormLabel>Restart Policy</FormLabel>
<TooltipProvider delayDuration={0}> <TooltipProvider delayDuration={0}>
<Tooltip> <Tooltip>
@@ -405,7 +427,7 @@ export const AddSwarmSettings = ({ applicationId }: Props) => {
control={form.control} control={form.control}
name="placementSwarm" name="placementSwarm"
render={({ field }) => ( render={({ field }) => (
<FormItem className="relative max-lg:px-4 lg:pl-6 "> <FormItem className="relative ">
<FormLabel>Placement</FormLabel> <FormLabel>Placement</FormLabel>
<TooltipProvider delayDuration={0}> <TooltipProvider delayDuration={0}>
<Tooltip> <Tooltip>
@@ -471,7 +493,7 @@ export const AddSwarmSettings = ({ applicationId }: Props) => {
control={form.control} control={form.control}
name="updateConfigSwarm" name="updateConfigSwarm"
render={({ field }) => ( render={({ field }) => (
<FormItem className="relative max-lg:px-4 lg:pr-6 "> <FormItem className="relative ">
<FormLabel>Update Config</FormLabel> <FormLabel>Update Config</FormLabel>
<TooltipProvider delayDuration={0}> <TooltipProvider delayDuration={0}>
<Tooltip> <Tooltip>
@@ -529,7 +551,7 @@ export const AddSwarmSettings = ({ applicationId }: Props) => {
control={form.control} control={form.control}
name="rollbackConfigSwarm" name="rollbackConfigSwarm"
render={({ field }) => ( render={({ field }) => (
<FormItem className="relative max-lg:px-4 lg:pl-6 "> <FormItem className="relative ">
<FormLabel>Rollback Config</FormLabel> <FormLabel>Rollback Config</FormLabel>
<TooltipProvider delayDuration={0}> <TooltipProvider delayDuration={0}>
<Tooltip> <Tooltip>
@@ -587,7 +609,7 @@ export const AddSwarmSettings = ({ applicationId }: Props) => {
control={form.control} control={form.control}
name="modeSwarm" name="modeSwarm"
render={({ field }) => ( render={({ field }) => (
<FormItem className="relative max-lg:px-4 lg:pr-6 "> <FormItem className="relative ">
<FormLabel>Mode</FormLabel> <FormLabel>Mode</FormLabel>
<TooltipProvider delayDuration={0}> <TooltipProvider delayDuration={0}>
<Tooltip> <Tooltip>
@@ -650,7 +672,7 @@ export const AddSwarmSettings = ({ applicationId }: Props) => {
control={form.control} control={form.control}
name="networkSwarm" name="networkSwarm"
render={({ field }) => ( render={({ field }) => (
<FormItem className="relative max-lg:px-4 lg:pl-6 "> <FormItem className="relative ">
<FormLabel>Network</FormLabel> <FormLabel>Network</FormLabel>
<TooltipProvider delayDuration={0}> <TooltipProvider delayDuration={0}>
<Tooltip> <Tooltip>
@@ -709,7 +731,7 @@ export const AddSwarmSettings = ({ applicationId }: Props) => {
control={form.control} control={form.control}
name="labelsSwarm" name="labelsSwarm"
render={({ field }) => ( render={({ field }) => (
<FormItem className="relative max-lg:px-4 lg:pr-6 "> <FormItem className="relative ">
<FormLabel>Labels</FormLabel> <FormLabel>Labels</FormLabel>
<TooltipProvider delayDuration={0}> <TooltipProvider delayDuration={0}>
<Tooltip> <Tooltip>
@@ -1,3 +1,10 @@
import { zodResolver } from "@hookform/resolvers/zod";
import { Server } from "lucide-react";
import Link from "next/link";
import { useEffect } from "react";
import { useForm } from "react-hook-form";
import { toast } from "sonner";
import { z } from "zod";
import { AlertBlock } from "@/components/shared/alert-block"; import { AlertBlock } from "@/components/shared/alert-block";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { import {
@@ -26,43 +33,57 @@ import {
SelectValue, SelectValue,
} from "@/components/ui/select"; } from "@/components/ui/select";
import { api } from "@/utils/api"; import { api } from "@/utils/api";
import { zodResolver } from "@hookform/resolvers/zod";
import { Server } from "lucide-react";
import Link from "next/link";
import { useEffect } from "react";
import { useForm } from "react-hook-form";
import { toast } from "sonner";
import { z } from "zod";
import { AddSwarmSettings } from "./modify-swarm-settings"; import { AddSwarmSettings } from "./modify-swarm-settings";
interface Props { interface Props {
applicationId: string; id: string;
type: "postgres" | "mariadb" | "mongo" | "mysql" | "redis" | "application";
} }
const AddRedirectchema = z.object({ const AddRedirectchema = z.object({
replicas: z.number().min(1, "Replicas must be at least 1"), replicas: z.number().min(1, "Replicas must be at least 1"),
registryId: z.string(), registryId: z.string().optional(),
}); });
type AddCommand = z.infer<typeof AddRedirectchema>; type AddCommand = z.infer<typeof AddRedirectchema>;
export const ShowClusterSettings = ({ applicationId }: Props) => { export const ShowClusterSettings = ({ id, type }: Props) => {
const { data } = api.application.one.useQuery( const queryMap = {
{ postgres: () =>
applicationId, api.postgres.one.useQuery({ postgresId: id }, { enabled: !!id }),
}, redis: () => api.redis.one.useQuery({ redisId: id }, { enabled: !!id }),
{ enabled: !!applicationId }, mysql: () => api.mysql.one.useQuery({ mysqlId: id }, { enabled: !!id }),
); mariadb: () =>
api.mariadb.one.useQuery({ mariadbId: id }, { enabled: !!id }),
application: () =>
api.application.one.useQuery({ applicationId: id }, { enabled: !!id }),
mongo: () => api.mongo.one.useQuery({ mongoId: id }, { enabled: !!id }),
};
const { data, refetch } = queryMap[type]
? queryMap[type]()
: api.mongo.one.useQuery({ mongoId: id }, { enabled: !!id });
const { data: registries } = api.registry.all.useQuery(); const { data: registries } = api.registry.all.useQuery();
const utils = api.useUtils(); const mutationMap = {
postgres: () => api.postgres.update.useMutation(),
redis: () => api.redis.update.useMutation(),
mysql: () => api.mysql.update.useMutation(),
mariadb: () => api.mariadb.update.useMutation(),
application: () => api.application.update.useMutation(),
mongo: () => api.mongo.update.useMutation(),
};
const { mutateAsync, isLoading } = api.application.update.useMutation(); const { mutateAsync, isLoading } = mutationMap[type]
? mutationMap[type]()
: api.mongo.update.useMutation();
const form = useForm<AddCommand>({ const form = useForm<AddCommand>({
defaultValues: { defaultValues: {
registryId: data?.registryId || "", ...(type === "application" && data && "registryId" in data
? {
registryId: data?.registryId || "",
}
: {}),
replicas: data?.replicas || 1, replicas: data?.replicas || 1,
}, },
resolver: zodResolver(AddRedirectchema), resolver: zodResolver(AddRedirectchema),
@@ -71,7 +92,11 @@ export const ShowClusterSettings = ({ applicationId }: Props) => {
useEffect(() => { useEffect(() => {
if (data?.command) { if (data?.command) {
form.reset({ form.reset({
registryId: data?.registryId || "", ...(type === "application" && data && "registryId" in data
? {
registryId: data?.registryId || "",
}
: {}),
replicas: data?.replicas || 1, replicas: data?.replicas || 1,
}); });
} }
@@ -79,18 +104,25 @@ export const ShowClusterSettings = ({ applicationId }: Props) => {
const onSubmit = async (data: AddCommand) => { const onSubmit = async (data: AddCommand) => {
await mutateAsync({ await mutateAsync({
applicationId, applicationId: id || "",
registryId: postgresId: id || "",
data?.registryId === "none" || !data?.registryId redisId: id || "",
? null mysqlId: id || "",
: data?.registryId, mariadbId: id || "",
mongoId: id || "",
...(type === "application"
? {
registryId:
data?.registryId === "none" || !data?.registryId
? null
: data?.registryId,
}
: {}),
replicas: data?.replicas, replicas: data?.replicas,
}) })
.then(async () => { .then(async () => {
toast.success("Command Updated"); toast.success("Command Updated");
await utils.application.one.invalidate({ await refetch();
applicationId,
});
}) })
.catch(() => { .catch(() => {
toast.error("Error updating the command"); toast.error("Error updating the command");
@@ -103,10 +135,10 @@ export const ShowClusterSettings = ({ applicationId }: Props) => {
<div> <div>
<CardTitle className="text-xl">Cluster Settings</CardTitle> <CardTitle className="text-xl">Cluster Settings</CardTitle>
<CardDescription> <CardDescription>
Add the registry and the replicas of the application Modify swarm settings for the service.
</CardDescription> </CardDescription>
</div> </div>
<AddSwarmSettings applicationId={applicationId} /> <AddSwarmSettings id={id} type={type} />
</CardHeader> </CardHeader>
<CardContent className="flex flex-col gap-4"> <CardContent className="flex flex-col gap-4">
<AlertBlock type="info"> <AlertBlock type="info">
@@ -144,58 +176,62 @@ export const ShowClusterSettings = ({ applicationId }: Props) => {
/> />
</div> </div>
{registries && registries?.length === 0 ? ( {type === "application" && (
<div className="pt-10">
<div className="flex flex-col items-center gap-3">
<Server className="size-8 text-muted-foreground" />
<span className="text-base text-muted-foreground">
To use a cluster feature, you need to configure at least a
registry first. Please, go to{" "}
<Link
href="/dashboard/settings/cluster"
className="text-foreground"
>
Settings
</Link>{" "}
to do so.
</span>
</div>
</div>
) : (
<> <>
<FormField {registries && registries?.length === 0 ? (
control={form.control} <div className="pt-10">
name="registryId" <div className="flex flex-col items-center gap-3">
render={({ field }) => ( <Server className="size-8 text-muted-foreground" />
<FormItem> <span className="text-base text-muted-foreground">
<FormLabel>Select a registry</FormLabel> To use a cluster feature, you need to configure at least
<Select a registry first. Please, go to{" "}
onValueChange={field.onChange} <Link
defaultValue={field.value} href="/dashboard/settings/cluster"
> className="text-foreground"
<SelectTrigger> >
<SelectValue placeholder="Select a registry" /> Settings
</SelectTrigger> </Link>{" "}
<SelectContent> to do so.
<SelectGroup> </span>
{registries?.map((registry) => ( </div>
<SelectItem </div>
key={registry.registryId} ) : (
value={registry.registryId} <>
> <FormField
{registry.registryName} control={form.control}
</SelectItem> name="registryId"
))} render={({ field }) => (
<SelectItem value={"none"}>None</SelectItem> <FormItem>
<SelectLabel> <FormLabel>Select a registry</FormLabel>
Registries ({registries?.length}) <Select
</SelectLabel> onValueChange={field.onChange}
</SelectGroup> defaultValue={field.value}
</SelectContent> >
</Select> <SelectTrigger>
</FormItem> <SelectValue placeholder="Select a registry" />
)} </SelectTrigger>
/> <SelectContent>
<SelectGroup>
{registries?.map((registry) => (
<SelectItem
key={registry.registryId}
value={registry.registryId}
>
{registry.registryName}
</SelectItem>
))}
<SelectItem value={"none"}>None</SelectItem>
<SelectLabel>
Registries ({registries?.length})
</SelectLabel>
</SelectGroup>
</SelectContent>
</Select>
</FormItem>
)}
/>
</>
)}
</> </>
)} )}
@@ -1,6 +1,7 @@
import { ShowResources } from "@/components/dashboard/application/advanced/show-resources"; import { ShowResources } from "@/components/dashboard/application/advanced/show-resources";
import { ShowVolumes } from "@/components/dashboard/application/advanced/volumes/show-volumes"; import { ShowVolumes } from "@/components/dashboard/application/advanced/volumes/show-volumes";
import { ShowCustomCommand } from "@/components/dashboard/postgres/advanced/show-custom-command"; import { ShowCustomCommand } from "@/components/dashboard/postgres/advanced/show-custom-command";
import { ShowClusterSettings } from "../application/advanced/cluster/show-cluster-settings";
import { RebuildDatabase } from "./rebuild-database"; import { RebuildDatabase } from "./rebuild-database";
interface Props { interface Props {
@@ -12,6 +13,7 @@ export const ShowDatabaseAdvancedSettings = ({ id, type }: Props) => {
return ( return (
<div className="flex w-full flex-col gap-5"> <div className="flex w-full flex-col gap-5">
<ShowCustomCommand id={id} type={type} /> <ShowCustomCommand id={id} type={type} />
<ShowClusterSettings id={id} type={type} />
<ShowVolumes id={id} type={type} /> <ShowVolumes id={id} type={type} />
<ShowResources id={id} type={type} /> <ShowResources id={id} type={type} />
<RebuildDatabase id={id} type={type} /> <RebuildDatabase id={id} type={type} />
@@ -0,0 +1,45 @@
ALTER TABLE "postgres" ADD COLUMN "healthCheckSwarm" json;--> statement-breakpoint
ALTER TABLE "postgres" ADD COLUMN "restartPolicySwarm" json;--> statement-breakpoint
ALTER TABLE "postgres" ADD COLUMN "placementSwarm" json;--> statement-breakpoint
ALTER TABLE "postgres" ADD COLUMN "updateConfigSwarm" json;--> statement-breakpoint
ALTER TABLE "postgres" ADD COLUMN "rollbackConfigSwarm" json;--> statement-breakpoint
ALTER TABLE "postgres" ADD COLUMN "modeSwarm" json;--> statement-breakpoint
ALTER TABLE "postgres" ADD COLUMN "labelsSwarm" json;--> statement-breakpoint
ALTER TABLE "postgres" ADD COLUMN "networkSwarm" json;--> statement-breakpoint
ALTER TABLE "postgres" ADD COLUMN "replicas" integer DEFAULT 1 NOT NULL;--> statement-breakpoint
ALTER TABLE "mariadb" ADD COLUMN "healthCheckSwarm" json;--> statement-breakpoint
ALTER TABLE "mariadb" ADD COLUMN "restartPolicySwarm" json;--> statement-breakpoint
ALTER TABLE "mariadb" ADD COLUMN "placementSwarm" json;--> statement-breakpoint
ALTER TABLE "mariadb" ADD COLUMN "updateConfigSwarm" json;--> statement-breakpoint
ALTER TABLE "mariadb" ADD COLUMN "rollbackConfigSwarm" json;--> statement-breakpoint
ALTER TABLE "mariadb" ADD COLUMN "modeSwarm" json;--> statement-breakpoint
ALTER TABLE "mariadb" ADD COLUMN "labelsSwarm" json;--> statement-breakpoint
ALTER TABLE "mariadb" ADD COLUMN "networkSwarm" json;--> statement-breakpoint
ALTER TABLE "mariadb" ADD COLUMN "replicas" integer DEFAULT 1 NOT NULL;--> statement-breakpoint
ALTER TABLE "mongo" ADD COLUMN "healthCheckSwarm" json;--> statement-breakpoint
ALTER TABLE "mongo" ADD COLUMN "restartPolicySwarm" json;--> statement-breakpoint
ALTER TABLE "mongo" ADD COLUMN "placementSwarm" json;--> statement-breakpoint
ALTER TABLE "mongo" ADD COLUMN "updateConfigSwarm" json;--> statement-breakpoint
ALTER TABLE "mongo" ADD COLUMN "rollbackConfigSwarm" json;--> statement-breakpoint
ALTER TABLE "mongo" ADD COLUMN "modeSwarm" json;--> statement-breakpoint
ALTER TABLE "mongo" ADD COLUMN "labelsSwarm" json;--> statement-breakpoint
ALTER TABLE "mongo" ADD COLUMN "networkSwarm" json;--> statement-breakpoint
ALTER TABLE "mongo" ADD COLUMN "replicas" integer DEFAULT 1 NOT NULL;--> statement-breakpoint
ALTER TABLE "mysql" ADD COLUMN "healthCheckSwarm" json;--> statement-breakpoint
ALTER TABLE "mysql" ADD COLUMN "restartPolicySwarm" json;--> statement-breakpoint
ALTER TABLE "mysql" ADD COLUMN "placementSwarm" json;--> statement-breakpoint
ALTER TABLE "mysql" ADD COLUMN "updateConfigSwarm" json;--> statement-breakpoint
ALTER TABLE "mysql" ADD COLUMN "rollbackConfigSwarm" json;--> statement-breakpoint
ALTER TABLE "mysql" ADD COLUMN "modeSwarm" json;--> statement-breakpoint
ALTER TABLE "mysql" ADD COLUMN "labelsSwarm" json;--> statement-breakpoint
ALTER TABLE "mysql" ADD COLUMN "networkSwarm" json;--> statement-breakpoint
ALTER TABLE "mysql" ADD COLUMN "replicas" integer DEFAULT 1 NOT NULL;--> statement-breakpoint
ALTER TABLE "redis" ADD COLUMN "healthCheckSwarm" json;--> statement-breakpoint
ALTER TABLE "redis" ADD COLUMN "restartPolicySwarm" json;--> statement-breakpoint
ALTER TABLE "redis" ADD COLUMN "placementSwarm" json;--> statement-breakpoint
ALTER TABLE "redis" ADD COLUMN "updateConfigSwarm" json;--> statement-breakpoint
ALTER TABLE "redis" ADD COLUMN "rollbackConfigSwarm" json;--> statement-breakpoint
ALTER TABLE "redis" ADD COLUMN "modeSwarm" json;--> statement-breakpoint
ALTER TABLE "redis" ADD COLUMN "labelsSwarm" json;--> statement-breakpoint
ALTER TABLE "redis" ADD COLUMN "networkSwarm" json;--> statement-breakpoint
ALTER TABLE "redis" ADD COLUMN "replicas" integer DEFAULT 1 NOT NULL;
File diff suppressed because it is too large Load Diff
+7
View File
@@ -736,6 +736,13 @@
"when": 1754207407121, "when": 1754207407121,
"tag": "0104_omniscient_randall", "tag": "0104_omniscient_randall",
"breakpoints": true "breakpoints": true
},
{
"idx": 105,
"version": "7",
"when": 1754259281559,
"tag": "0105_clumsy_quicksilver",
"breakpoints": true
} }
] ]
} }
@@ -1,3 +1,17 @@
import { validateRequest } from "@dokploy/server/lib/auth";
import { createServerSideHelpers } from "@trpc/react-query/server";
import copy from "copy-to-clipboard";
import { GlobeIcon, HelpCircle, ServerOff } from "lucide-react";
import type {
GetServerSidePropsContext,
InferGetServerSidePropsType,
} from "next";
import Head from "next/head";
import Link from "next/link";
import { useRouter } from "next/router";
import { type ReactElement, useEffect, useState } from "react";
import { toast } from "sonner";
import superjson from "superjson";
import { ShowClusterSettings } from "@/components/dashboard/application/advanced/cluster/show-cluster-settings"; import { ShowClusterSettings } from "@/components/dashboard/application/advanced/cluster/show-cluster-settings";
import { AddCommand } from "@/components/dashboard/application/advanced/general/add-command"; import { AddCommand } from "@/components/dashboard/application/advanced/general/add-command";
import { ShowPorts } from "@/components/dashboard/application/advanced/ports/show-port"; import { ShowPorts } from "@/components/dashboard/application/advanced/ports/show-port";
@@ -39,20 +53,6 @@ import {
} from "@/components/ui/tooltip"; } from "@/components/ui/tooltip";
import { appRouter } from "@/server/api/root"; import { appRouter } from "@/server/api/root";
import { api } from "@/utils/api"; import { api } from "@/utils/api";
import { validateRequest } from "@dokploy/server/lib/auth";
import { createServerSideHelpers } from "@trpc/react-query/server";
import copy from "copy-to-clipboard";
import { GlobeIcon, HelpCircle, ServerOff } from "lucide-react";
import type {
GetServerSidePropsContext,
InferGetServerSidePropsType,
} from "next";
import Head from "next/head";
import Link from "next/link";
import { useRouter } from "next/router";
import { type ReactElement, useEffect, useState } from "react";
import { toast } from "sonner";
import superjson from "superjson";
type TabState = type TabState =
| "projects" | "projects"
@@ -345,7 +345,10 @@ const Service = (
<TabsContent value="advanced"> <TabsContent value="advanced">
<div className="flex flex-col gap-4 pt-2.5"> <div className="flex flex-col gap-4 pt-2.5">
<AddCommand applicationId={applicationId} /> <AddCommand applicationId={applicationId} />
<ShowClusterSettings applicationId={applicationId} /> <ShowClusterSettings
id={applicationId}
type="application"
/>
<ShowResources id={applicationId} type="application" /> <ShowResources id={applicationId} type="application" />
<ShowVolumes id={applicationId} type="application" /> <ShowVolumes id={applicationId} type="application" />
+19 -147
View File
@@ -24,7 +24,25 @@ import { redirects } from "./redirects";
import { registry } from "./registry"; import { registry } from "./registry";
import { security } from "./security"; import { security } from "./security";
import { server } from "./server"; import { server } from "./server";
import { applicationStatus, certificateType, triggerType } from "./shared"; import {
applicationStatus,
certificateType,
type HealthCheckSwarm,
HealthCheckSwarmSchema,
type LabelsSwarm,
LabelsSwarmSchema,
type NetworkSwarm,
NetworkSwarmSchema,
type PlacementSwarm,
PlacementSwarmSchema,
type RestartPolicySwarm,
RestartPolicySwarmSchema,
type ServiceModeSwarm,
ServiceModeSwarmSchema,
triggerType,
type UpdateConfigSwarm,
UpdateConfigSwarmSchema,
} from "./shared";
import { sshKeys } from "./ssh-key"; import { sshKeys } from "./ssh-key";
import { generateAppName } from "./utils"; import { generateAppName } from "./utils";
export const sourceType = pgEnum("sourceType", [ export const sourceType = pgEnum("sourceType", [
@@ -46,64 +64,6 @@ export const buildType = pgEnum("buildType", [
"railpack", "railpack",
]); ]);
export interface HealthCheckSwarm {
Test?: string[] | undefined;
Interval?: number | undefined;
Timeout?: number | undefined;
StartPeriod?: number | undefined;
Retries?: number | undefined;
}
export interface RestartPolicySwarm {
Condition?: string | undefined;
Delay?: number | undefined;
MaxAttempts?: number | undefined;
Window?: number | undefined;
}
export interface PlacementSwarm {
Constraints?: string[] | undefined;
Preferences?: Array<{ Spread: { SpreadDescriptor: string } }> | undefined;
MaxReplicas?: number | undefined;
Platforms?:
| Array<{
Architecture: string;
OS: string;
}>
| undefined;
}
export interface UpdateConfigSwarm {
Parallelism: number;
Delay?: number | undefined;
FailureAction?: string | undefined;
Monitor?: number | undefined;
MaxFailureRatio?: number | undefined;
Order: string;
}
export interface ServiceModeSwarm {
Replicated?: { Replicas?: number | undefined } | undefined;
Global?: {} | undefined;
ReplicatedJob?:
| {
MaxConcurrent?: number | undefined;
TotalCompletions?: number | undefined;
}
| undefined;
GlobalJob?: {} | undefined;
}
export interface NetworkSwarm {
Target?: string | undefined;
Aliases?: string[] | undefined;
DriverOpts?: { [key: string]: string } | undefined;
}
export interface LabelsSwarm {
[name: string]: string;
}
export const applications = pgTable("application", { export const applications = pgTable("application", {
applicationId: text("applicationId") applicationId: text("applicationId")
.notNull() .notNull()
@@ -283,94 +243,6 @@ export const applicationsRelations = relations(
}), }),
); );
const HealthCheckSwarmSchema = z
.object({
Test: z.array(z.string()).optional(),
Interval: z.number().optional(),
Timeout: z.number().optional(),
StartPeriod: z.number().optional(),
Retries: z.number().optional(),
})
.strict();
const RestartPolicySwarmSchema = z
.object({
Condition: z.string().optional(),
Delay: z.number().optional(),
MaxAttempts: z.number().optional(),
Window: z.number().optional(),
})
.strict();
const PreferenceSchema = z
.object({
Spread: z.object({
SpreadDescriptor: z.string(),
}),
})
.strict();
const PlatformSchema = z
.object({
Architecture: z.string(),
OS: z.string(),
})
.strict();
const PlacementSwarmSchema = z
.object({
Constraints: z.array(z.string()).optional(),
Preferences: z.array(PreferenceSchema).optional(),
MaxReplicas: z.number().optional(),
Platforms: z.array(PlatformSchema).optional(),
})
.strict();
const UpdateConfigSwarmSchema = z
.object({
Parallelism: z.number(),
Delay: z.number().optional(),
FailureAction: z.string().optional(),
Monitor: z.number().optional(),
MaxFailureRatio: z.number().optional(),
Order: z.string(),
})
.strict();
const ReplicatedSchema = z
.object({
Replicas: z.number().optional(),
})
.strict();
const ReplicatedJobSchema = z
.object({
MaxConcurrent: z.number().optional(),
TotalCompletions: z.number().optional(),
})
.strict();
const ServiceModeSwarmSchema = z
.object({
Replicated: ReplicatedSchema.optional(),
Global: z.object({}).optional(),
ReplicatedJob: ReplicatedJobSchema.optional(),
GlobalJob: z.object({}).optional(),
})
.strict();
const NetworkSwarmSchema = z.array(
z
.object({
Target: z.string().optional(),
Aliases: z.array(z.string()).optional(),
DriverOpts: z.object({}).optional(),
})
.strict(),
);
const LabelsSwarmSchema = z.record(z.string());
const createSchema = createInsertSchema(applications, { const createSchema = createInsertSchema(applications, {
appName: z.string(), appName: z.string(),
createdAt: z.string(), createdAt: z.string(),
+35 -2
View File
@@ -1,5 +1,5 @@
import { relations } from "drizzle-orm"; import { relations } from "drizzle-orm";
import { integer, pgTable, text } from "drizzle-orm/pg-core"; import { integer, json, pgTable, text } from "drizzle-orm/pg-core";
import { createInsertSchema } from "drizzle-zod"; import { createInsertSchema } from "drizzle-zod";
import { nanoid } from "nanoid"; import { nanoid } from "nanoid";
import { z } from "zod"; import { z } from "zod";
@@ -7,7 +7,23 @@ import { backups } from "./backups";
import { mounts } from "./mount"; import { mounts } from "./mount";
import { projects } from "./project"; import { projects } from "./project";
import { server } from "./server"; import { server } from "./server";
import { applicationStatus } from "./shared"; import {
applicationStatus,
type HealthCheckSwarm,
HealthCheckSwarmSchema,
type LabelsSwarm,
LabelsSwarmSchema,
type NetworkSwarm,
NetworkSwarmSchema,
type PlacementSwarm,
PlacementSwarmSchema,
type RestartPolicySwarm,
RestartPolicySwarmSchema,
type ServiceModeSwarm,
ServiceModeSwarmSchema,
type UpdateConfigSwarm,
UpdateConfigSwarmSchema,
} from "./shared";
import { generateAppName } from "./utils"; import { generateAppName } from "./utils";
export const mariadb = pgTable("mariadb", { export const mariadb = pgTable("mariadb", {
@@ -38,6 +54,15 @@ export const mariadb = pgTable("mariadb", {
applicationStatus: applicationStatus("applicationStatus") applicationStatus: applicationStatus("applicationStatus")
.notNull() .notNull()
.default("idle"), .default("idle"),
healthCheckSwarm: json("healthCheckSwarm").$type<HealthCheckSwarm>(),
restartPolicySwarm: json("restartPolicySwarm").$type<RestartPolicySwarm>(),
placementSwarm: json("placementSwarm").$type<PlacementSwarm>(),
updateConfigSwarm: json("updateConfigSwarm").$type<UpdateConfigSwarm>(),
rollbackConfigSwarm: json("rollbackConfigSwarm").$type<UpdateConfigSwarm>(),
modeSwarm: json("modeSwarm").$type<ServiceModeSwarm>(),
labelsSwarm: json("labelsSwarm").$type<LabelsSwarm>(),
networkSwarm: json("networkSwarm").$type<NetworkSwarm[]>(),
replicas: integer("replicas").default(1).notNull(),
createdAt: text("createdAt") createdAt: text("createdAt")
.notNull() .notNull()
.$defaultFn(() => new Date().toISOString()), .$defaultFn(() => new Date().toISOString()),
@@ -83,6 +108,14 @@ const createSchema = createInsertSchema(mariadb, {
externalPort: z.number(), externalPort: z.number(),
description: z.string().optional(), description: z.string().optional(),
serverId: z.string().optional(), serverId: z.string().optional(),
healthCheckSwarm: HealthCheckSwarmSchema.nullable(),
restartPolicySwarm: RestartPolicySwarmSchema.nullable(),
placementSwarm: PlacementSwarmSchema.nullable(),
updateConfigSwarm: UpdateConfigSwarmSchema.nullable(),
rollbackConfigSwarm: UpdateConfigSwarmSchema.nullable(),
modeSwarm: ServiceModeSwarmSchema.nullable(),
labelsSwarm: LabelsSwarmSchema.nullable(),
networkSwarm: NetworkSwarmSchema.nullable(),
}); });
export const apiCreateMariaDB = createSchema export const apiCreateMariaDB = createSchema
+35 -2
View File
@@ -1,5 +1,5 @@
import { relations } from "drizzle-orm"; import { relations } from "drizzle-orm";
import { boolean, integer, pgTable, text } from "drizzle-orm/pg-core"; import { boolean, integer, json, pgTable, text } from "drizzle-orm/pg-core";
import { createInsertSchema } from "drizzle-zod"; import { createInsertSchema } from "drizzle-zod";
import { nanoid } from "nanoid"; import { nanoid } from "nanoid";
import { z } from "zod"; import { z } from "zod";
@@ -7,7 +7,23 @@ import { backups } from "./backups";
import { mounts } from "./mount"; import { mounts } from "./mount";
import { projects } from "./project"; import { projects } from "./project";
import { server } from "./server"; import { server } from "./server";
import { applicationStatus } from "./shared"; import {
applicationStatus,
type HealthCheckSwarm,
HealthCheckSwarmSchema,
type LabelsSwarm,
LabelsSwarmSchema,
type NetworkSwarm,
NetworkSwarmSchema,
type PlacementSwarm,
PlacementSwarmSchema,
type RestartPolicySwarm,
RestartPolicySwarmSchema,
type ServiceModeSwarm,
ServiceModeSwarmSchema,
type UpdateConfigSwarm,
UpdateConfigSwarmSchema,
} from "./shared";
import { generateAppName } from "./utils"; import { generateAppName } from "./utils";
export const mongo = pgTable("mongo", { export const mongo = pgTable("mongo", {
@@ -34,6 +50,15 @@ export const mongo = pgTable("mongo", {
applicationStatus: applicationStatus("applicationStatus") applicationStatus: applicationStatus("applicationStatus")
.notNull() .notNull()
.default("idle"), .default("idle"),
healthCheckSwarm: json("healthCheckSwarm").$type<HealthCheckSwarm>(),
restartPolicySwarm: json("restartPolicySwarm").$type<RestartPolicySwarm>(),
placementSwarm: json("placementSwarm").$type<PlacementSwarm>(),
updateConfigSwarm: json("updateConfigSwarm").$type<UpdateConfigSwarm>(),
rollbackConfigSwarm: json("rollbackConfigSwarm").$type<UpdateConfigSwarm>(),
modeSwarm: json("modeSwarm").$type<ServiceModeSwarm>(),
labelsSwarm: json("labelsSwarm").$type<LabelsSwarm>(),
networkSwarm: json("networkSwarm").$type<NetworkSwarm[]>(),
replicas: integer("replicas").default(1).notNull(),
createdAt: text("createdAt") createdAt: text("createdAt")
.notNull() .notNull()
.$defaultFn(() => new Date().toISOString()), .$defaultFn(() => new Date().toISOString()),
@@ -79,6 +104,14 @@ const createSchema = createInsertSchema(mongo, {
description: z.string().optional(), description: z.string().optional(),
serverId: z.string().optional(), serverId: z.string().optional(),
replicaSets: z.boolean().default(false), replicaSets: z.boolean().default(false),
healthCheckSwarm: HealthCheckSwarmSchema.nullable(),
restartPolicySwarm: RestartPolicySwarmSchema.nullable(),
placementSwarm: PlacementSwarmSchema.nullable(),
updateConfigSwarm: UpdateConfigSwarmSchema.nullable(),
rollbackConfigSwarm: UpdateConfigSwarmSchema.nullable(),
modeSwarm: ServiceModeSwarmSchema.nullable(),
labelsSwarm: LabelsSwarmSchema.nullable(),
networkSwarm: NetworkSwarmSchema.nullable(),
}); });
export const apiCreateMongo = createSchema export const apiCreateMongo = createSchema
+35 -2
View File
@@ -1,5 +1,5 @@
import { relations } from "drizzle-orm"; import { relations } from "drizzle-orm";
import { integer, pgTable, text } from "drizzle-orm/pg-core"; import { integer, json, pgTable, text } from "drizzle-orm/pg-core";
import { createInsertSchema } from "drizzle-zod"; import { createInsertSchema } from "drizzle-zod";
import { nanoid } from "nanoid"; import { nanoid } from "nanoid";
import { z } from "zod"; import { z } from "zod";
@@ -7,7 +7,23 @@ import { backups } from "./backups";
import { mounts } from "./mount"; import { mounts } from "./mount";
import { projects } from "./project"; import { projects } from "./project";
import { server } from "./server"; import { server } from "./server";
import { applicationStatus } from "./shared"; import {
applicationStatus,
type HealthCheckSwarm,
HealthCheckSwarmSchema,
type LabelsSwarm,
LabelsSwarmSchema,
type NetworkSwarm,
NetworkSwarmSchema,
type PlacementSwarm,
PlacementSwarmSchema,
type RestartPolicySwarm,
RestartPolicySwarmSchema,
type ServiceModeSwarm,
ServiceModeSwarmSchema,
type UpdateConfigSwarm,
UpdateConfigSwarmSchema,
} from "./shared";
import { generateAppName } from "./utils"; import { generateAppName } from "./utils";
export const mysql = pgTable("mysql", { export const mysql = pgTable("mysql", {
@@ -36,6 +52,15 @@ export const mysql = pgTable("mysql", {
applicationStatus: applicationStatus("applicationStatus") applicationStatus: applicationStatus("applicationStatus")
.notNull() .notNull()
.default("idle"), .default("idle"),
healthCheckSwarm: json("healthCheckSwarm").$type<HealthCheckSwarm>(),
restartPolicySwarm: json("restartPolicySwarm").$type<RestartPolicySwarm>(),
placementSwarm: json("placementSwarm").$type<PlacementSwarm>(),
updateConfigSwarm: json("updateConfigSwarm").$type<UpdateConfigSwarm>(),
rollbackConfigSwarm: json("rollbackConfigSwarm").$type<UpdateConfigSwarm>(),
modeSwarm: json("modeSwarm").$type<ServiceModeSwarm>(),
labelsSwarm: json("labelsSwarm").$type<LabelsSwarm>(),
networkSwarm: json("networkSwarm").$type<NetworkSwarm[]>(),
replicas: integer("replicas").default(1).notNull(),
createdAt: text("createdAt") createdAt: text("createdAt")
.notNull() .notNull()
.$defaultFn(() => new Date().toISOString()), .$defaultFn(() => new Date().toISOString()),
@@ -81,6 +106,14 @@ const createSchema = createInsertSchema(mysql, {
externalPort: z.number(), externalPort: z.number(),
description: z.string().optional(), description: z.string().optional(),
serverId: z.string().optional(), serverId: z.string().optional(),
healthCheckSwarm: HealthCheckSwarmSchema.nullable(),
restartPolicySwarm: RestartPolicySwarmSchema.nullable(),
placementSwarm: PlacementSwarmSchema.nullable(),
updateConfigSwarm: UpdateConfigSwarmSchema.nullable(),
rollbackConfigSwarm: UpdateConfigSwarmSchema.nullable(),
modeSwarm: ServiceModeSwarmSchema.nullable(),
labelsSwarm: LabelsSwarmSchema.nullable(),
networkSwarm: NetworkSwarmSchema.nullable(),
}); });
export const apiCreateMySql = createSchema export const apiCreateMySql = createSchema
+36 -2
View File
@@ -1,5 +1,5 @@
import { relations } from "drizzle-orm"; import { relations } from "drizzle-orm";
import { integer, pgTable, text } from "drizzle-orm/pg-core"; import { integer, json, pgTable, text } from "drizzle-orm/pg-core";
import { createInsertSchema } from "drizzle-zod"; import { createInsertSchema } from "drizzle-zod";
import { nanoid } from "nanoid"; import { nanoid } from "nanoid";
import { z } from "zod"; import { z } from "zod";
@@ -7,7 +7,23 @@ import { backups } from "./backups";
import { mounts } from "./mount"; import { mounts } from "./mount";
import { projects } from "./project"; import { projects } from "./project";
import { server } from "./server"; import { server } from "./server";
import { applicationStatus } from "./shared"; import {
applicationStatus,
type HealthCheckSwarm,
HealthCheckSwarmSchema,
type LabelsSwarm,
LabelsSwarmSchema,
type NetworkSwarm,
NetworkSwarmSchema,
type PlacementSwarm,
PlacementSwarmSchema,
type RestartPolicySwarm,
RestartPolicySwarmSchema,
type ServiceModeSwarm,
ServiceModeSwarmSchema,
type UpdateConfigSwarm,
UpdateConfigSwarmSchema,
} from "./shared";
import { generateAppName } from "./utils"; import { generateAppName } from "./utils";
export const postgres = pgTable("postgres", { export const postgres = pgTable("postgres", {
@@ -35,6 +51,16 @@ export const postgres = pgTable("postgres", {
applicationStatus: applicationStatus("applicationStatus") applicationStatus: applicationStatus("applicationStatus")
.notNull() .notNull()
.default("idle"), .default("idle"),
healthCheckSwarm: json("healthCheckSwarm").$type<HealthCheckSwarm>(),
restartPolicySwarm: json("restartPolicySwarm").$type<RestartPolicySwarm>(),
placementSwarm: json("placementSwarm").$type<PlacementSwarm>(),
updateConfigSwarm: json("updateConfigSwarm").$type<UpdateConfigSwarm>(),
rollbackConfigSwarm: json("rollbackConfigSwarm").$type<UpdateConfigSwarm>(),
modeSwarm: json("modeSwarm").$type<ServiceModeSwarm>(),
labelsSwarm: json("labelsSwarm").$type<LabelsSwarm>(),
networkSwarm: json("networkSwarm").$type<NetworkSwarm[]>(),
replicas: integer("replicas").default(1).notNull(),
createdAt: text("createdAt") createdAt: text("createdAt")
.notNull() .notNull()
.$defaultFn(() => new Date().toISOString()), .$defaultFn(() => new Date().toISOString()),
@@ -78,6 +104,14 @@ const createSchema = createInsertSchema(postgres, {
createdAt: z.string(), createdAt: z.string(),
description: z.string().optional(), description: z.string().optional(),
serverId: z.string().optional(), serverId: z.string().optional(),
healthCheckSwarm: HealthCheckSwarmSchema.nullable(),
restartPolicySwarm: RestartPolicySwarmSchema.nullable(),
placementSwarm: PlacementSwarmSchema.nullable(),
updateConfigSwarm: UpdateConfigSwarmSchema.nullable(),
rollbackConfigSwarm: UpdateConfigSwarmSchema.nullable(),
modeSwarm: ServiceModeSwarmSchema.nullable(),
labelsSwarm: LabelsSwarmSchema.nullable(),
networkSwarm: NetworkSwarmSchema.nullable(),
}); });
export const apiCreatePostgres = createSchema export const apiCreatePostgres = createSchema
+35 -2
View File
@@ -1,12 +1,28 @@
import { relations } from "drizzle-orm"; import { relations } from "drizzle-orm";
import { integer, pgTable, text } from "drizzle-orm/pg-core"; import { integer, json, pgTable, text } from "drizzle-orm/pg-core";
import { createInsertSchema } from "drizzle-zod"; import { createInsertSchema } from "drizzle-zod";
import { nanoid } from "nanoid"; import { nanoid } from "nanoid";
import { z } from "zod"; import { z } from "zod";
import { mounts } from "./mount"; import { mounts } from "./mount";
import { projects } from "./project"; import { projects } from "./project";
import { server } from "./server"; import { server } from "./server";
import { applicationStatus } from "./shared"; import {
applicationStatus,
type HealthCheckSwarm,
HealthCheckSwarmSchema,
type LabelsSwarm,
LabelsSwarmSchema,
type NetworkSwarm,
NetworkSwarmSchema,
type PlacementSwarm,
PlacementSwarmSchema,
type RestartPolicySwarm,
RestartPolicySwarmSchema,
type ServiceModeSwarm,
ServiceModeSwarmSchema,
type UpdateConfigSwarm,
UpdateConfigSwarmSchema,
} from "./shared";
import { generateAppName } from "./utils"; import { generateAppName } from "./utils";
export const redis = pgTable("redis", { export const redis = pgTable("redis", {
@@ -35,6 +51,15 @@ export const redis = pgTable("redis", {
applicationStatus: applicationStatus("applicationStatus") applicationStatus: applicationStatus("applicationStatus")
.notNull() .notNull()
.default("idle"), .default("idle"),
healthCheckSwarm: json("healthCheckSwarm").$type<HealthCheckSwarm>(),
restartPolicySwarm: json("restartPolicySwarm").$type<RestartPolicySwarm>(),
placementSwarm: json("placementSwarm").$type<PlacementSwarm>(),
updateConfigSwarm: json("updateConfigSwarm").$type<UpdateConfigSwarm>(),
rollbackConfigSwarm: json("rollbackConfigSwarm").$type<UpdateConfigSwarm>(),
modeSwarm: json("modeSwarm").$type<ServiceModeSwarm>(),
labelsSwarm: json("labelsSwarm").$type<LabelsSwarm>(),
networkSwarm: json("networkSwarm").$type<NetworkSwarm[]>(),
replicas: integer("replicas").default(1).notNull(),
projectId: text("projectId") projectId: text("projectId")
.notNull() .notNull()
.references(() => projects.projectId, { onDelete: "cascade" }), .references(() => projects.projectId, { onDelete: "cascade" }),
@@ -73,6 +98,14 @@ const createSchema = createInsertSchema(redis, {
externalPort: z.number(), externalPort: z.number(),
description: z.string().optional(), description: z.string().optional(),
serverId: z.string().optional(), serverId: z.string().optional(),
healthCheckSwarm: HealthCheckSwarmSchema.nullable(),
restartPolicySwarm: RestartPolicySwarmSchema.nullable(),
placementSwarm: PlacementSwarmSchema.nullable(),
updateConfigSwarm: UpdateConfigSwarmSchema.nullable(),
rollbackConfigSwarm: UpdateConfigSwarmSchema.nullable(),
modeSwarm: ServiceModeSwarmSchema.nullable(),
labelsSwarm: LabelsSwarmSchema.nullable(),
networkSwarm: NetworkSwarmSchema.nullable(),
}); });
export const apiCreateRedis = createSchema export const apiCreateRedis = createSchema
+147
View File
@@ -1,4 +1,5 @@
import { pgEnum } from "drizzle-orm/pg-core"; import { pgEnum } from "drizzle-orm/pg-core";
import { z } from "zod";
export const applicationStatus = pgEnum("applicationStatus", [ export const applicationStatus = pgEnum("applicationStatus", [
"idle", "idle",
@@ -14,3 +15,149 @@ export const certificateType = pgEnum("certificateType", [
]); ]);
export const triggerType = pgEnum("triggerType", ["push", "tag"]); export const triggerType = pgEnum("triggerType", ["push", "tag"]);
export interface HealthCheckSwarm {
Test?: string[] | undefined;
Interval?: number | undefined;
Timeout?: number | undefined;
StartPeriod?: number | undefined;
Retries?: number | undefined;
}
export interface RestartPolicySwarm {
Condition?: string | undefined;
Delay?: number | undefined;
MaxAttempts?: number | undefined;
Window?: number | undefined;
}
export interface PlacementSwarm {
Constraints?: string[] | undefined;
Preferences?: Array<{ Spread: { SpreadDescriptor: string } }> | undefined;
MaxReplicas?: number | undefined;
Platforms?:
| Array<{
Architecture: string;
OS: string;
}>
| undefined;
}
export interface UpdateConfigSwarm {
Parallelism: number;
Delay?: number | undefined;
FailureAction?: string | undefined;
Monitor?: number | undefined;
MaxFailureRatio?: number | undefined;
Order: string;
}
export interface ServiceModeSwarm {
Replicated?: { Replicas?: number | undefined } | undefined;
Global?: {} | undefined;
ReplicatedJob?:
| {
MaxConcurrent?: number | undefined;
TotalCompletions?: number | undefined;
}
| undefined;
GlobalJob?: {} | undefined;
}
export interface NetworkSwarm {
Target?: string | undefined;
Aliases?: string[] | undefined;
DriverOpts?: { [key: string]: string } | undefined;
}
export interface LabelsSwarm {
[name: string]: string;
}
export const HealthCheckSwarmSchema = z
.object({
Test: z.array(z.string()).optional(),
Interval: z.number().optional(),
Timeout: z.number().optional(),
StartPeriod: z.number().optional(),
Retries: z.number().optional(),
})
.strict();
export const RestartPolicySwarmSchema = z
.object({
Condition: z.string().optional(),
Delay: z.number().optional(),
MaxAttempts: z.number().optional(),
Window: z.number().optional(),
})
.strict();
export const PreferenceSchema = z
.object({
Spread: z.object({
SpreadDescriptor: z.string(),
}),
})
.strict();
export const PlatformSchema = z
.object({
Architecture: z.string(),
OS: z.string(),
})
.strict();
export const PlacementSwarmSchema = z
.object({
Constraints: z.array(z.string()).optional(),
Preferences: z.array(PreferenceSchema).optional(),
MaxReplicas: z.number().optional(),
Platforms: z.array(PlatformSchema).optional(),
})
.strict();
export const UpdateConfigSwarmSchema = z
.object({
Parallelism: z.number(),
Delay: z.number().optional(),
FailureAction: z.string().optional(),
Monitor: z.number().optional(),
MaxFailureRatio: z.number().optional(),
Order: z.string(),
})
.strict();
export const ReplicatedSchema = z
.object({
Replicas: z.number().optional(),
})
.strict();
export const ReplicatedJobSchema = z
.object({
MaxConcurrent: z.number().optional(),
TotalCompletions: z.number().optional(),
})
.strict();
export const ServiceModeSwarmSchema = z
.object({
Replicated: ReplicatedSchema.optional(),
Global: z.object({}).optional(),
ReplicatedJob: ReplicatedJobSchema.optional(),
GlobalJob: z.object({}).optional(),
})
.strict();
export const NetworkSwarmSchema = z.array(
z
.object({
Target: z.string().optional(),
Aliases: z.array(z.string()).optional(),
DriverOpts: z.object({}).optional(),
})
.strict(),
);
export const LabelsSwarmSchema = z.record(z.string());
+24 -9
View File
@@ -3,6 +3,7 @@ import type { CreateServiceOptions } from "dockerode";
import { import {
calculateResources, calculateResources,
generateBindMounts, generateBindMounts,
generateConfigContainer,
generateFileMounts, generateFileMounts,
generateVolumeMounts, generateVolumeMounts,
prepareEnvironmentVariables, prepareEnvironmentVariables,
@@ -34,6 +35,17 @@ export const buildMariadb = async (mariadb: MariadbNested) => {
const defaultMariadbEnv = `MARIADB_DATABASE="${databaseName}"\nMARIADB_USER="${databaseUser}"\nMARIADB_PASSWORD="${databasePassword}"\nMARIADB_ROOT_PASSWORD="${databaseRootPassword}"${ const defaultMariadbEnv = `MARIADB_DATABASE="${databaseName}"\nMARIADB_USER="${databaseUser}"\nMARIADB_PASSWORD="${databasePassword}"\nMARIADB_ROOT_PASSWORD="${databaseRootPassword}"${
env ? `\n${env}` : "" env ? `\n${env}` : ""
}`; }`;
const {
HealthCheck,
RestartPolicy,
Placement,
Labels,
Mode,
RollbackConfig,
UpdateConfig,
Networks,
} = generateConfigContainer(mariadb);
const resources = calculateResources({ const resources = calculateResources({
memoryLimit, memoryLimit,
memoryReservation, memoryReservation,
@@ -54,6 +66,7 @@ export const buildMariadb = async (mariadb: MariadbNested) => {
Name: appName, Name: appName,
TaskTemplate: { TaskTemplate: {
ContainerSpec: { ContainerSpec: {
HealthCheck,
Image: dockerImage, Image: dockerImage,
Env: envVariables, Env: envVariables,
Mounts: [...volumesMount, ...bindsMount, ...filesMount], Mounts: [...volumesMount, ...bindsMount, ...filesMount],
@@ -63,20 +76,17 @@ export const buildMariadb = async (mariadb: MariadbNested) => {
Args: ["-c", command], Args: ["-c", command],
} }
: {}), : {}),
Labels,
}, },
Networks: [{ Target: "dokploy-network" }], Networks,
RestartPolicy,
Placement,
Resources: { Resources: {
...resources, ...resources,
}, },
Placement: {
Constraints: ["node.role==manager"],
},
},
Mode: {
Replicated: {
Replicas: 1,
},
}, },
Mode,
RollbackConfig,
EndpointSpec: { EndpointSpec: {
Mode: "dnsrr", Mode: "dnsrr",
Ports: externalPort Ports: externalPort
@@ -90,6 +100,7 @@ export const buildMariadb = async (mariadb: MariadbNested) => {
] ]
: [], : [],
}, },
UpdateConfig,
}; };
try { try {
const service = docker.getService(appName); const service = docker.getService(appName);
@@ -97,6 +108,10 @@ export const buildMariadb = async (mariadb: MariadbNested) => {
await service.update({ await service.update({
version: Number.parseInt(inspect.Version.Index), version: Number.parseInt(inspect.Version.Index),
...settings, ...settings,
TaskTemplate: {
...settings.TaskTemplate,
ForceUpdate: inspect.Spec.TaskTemplate.ForceUpdate + 1,
},
}); });
} catch { } catch {
await docker.createService(settings); await docker.createService(settings);
+24 -9
View File
@@ -3,6 +3,7 @@ import type { CreateServiceOptions } from "dockerode";
import { import {
calculateResources, calculateResources,
generateBindMounts, generateBindMounts,
generateConfigContainer,
generateFileMounts, generateFileMounts,
generateVolumeMounts, generateVolumeMounts,
prepareEnvironmentVariables, prepareEnvironmentVariables,
@@ -81,6 +82,17 @@ ${command ?? "wait $MONGOD_PID"}`;
env ? `\n${env}` : "" env ? `\n${env}` : ""
}`; }`;
const {
HealthCheck,
RestartPolicy,
Placement,
Labels,
Mode,
RollbackConfig,
UpdateConfig,
Networks,
} = generateConfigContainer(mongo);
const resources = calculateResources({ const resources = calculateResources({
memoryLimit, memoryLimit,
memoryReservation, memoryReservation,
@@ -102,6 +114,7 @@ ${command ?? "wait $MONGOD_PID"}`;
Name: appName, Name: appName,
TaskTemplate: { TaskTemplate: {
ContainerSpec: { ContainerSpec: {
HealthCheck,
Image: dockerImage, Image: dockerImage,
Env: envVariables, Env: envVariables,
Mounts: [...volumesMount, ...bindsMount, ...filesMount], Mounts: [...volumesMount, ...bindsMount, ...filesMount],
@@ -116,20 +129,17 @@ ${command ?? "wait $MONGOD_PID"}`;
Args: ["-c", command], Args: ["-c", command],
}), }),
}), }),
Labels,
}, },
Networks: [{ Target: "dokploy-network" }], Networks,
RestartPolicy,
Placement,
Resources: { Resources: {
...resources, ...resources,
}, },
Placement: {
Constraints: ["node.role==manager"],
},
},
Mode: {
Replicated: {
Replicas: 1,
},
}, },
Mode,
RollbackConfig,
EndpointSpec: { EndpointSpec: {
Mode: "dnsrr", Mode: "dnsrr",
Ports: externalPort Ports: externalPort
@@ -143,6 +153,7 @@ ${command ?? "wait $MONGOD_PID"}`;
] ]
: [], : [],
}, },
UpdateConfig,
}; };
try { try {
@@ -151,6 +162,10 @@ ${command ?? "wait $MONGOD_PID"}`;
await service.update({ await service.update({
version: Number.parseInt(inspect.Version.Index), version: Number.parseInt(inspect.Version.Index),
...settings, ...settings,
TaskTemplate: {
...settings.TaskTemplate,
ForceUpdate: inspect.Spec.TaskTemplate.ForceUpdate + 1,
},
}); });
} catch { } catch {
await docker.createService(settings); await docker.createService(settings);
+24 -9
View File
@@ -3,6 +3,7 @@ import type { CreateServiceOptions } from "dockerode";
import { import {
calculateResources, calculateResources,
generateBindMounts, generateBindMounts,
generateConfigContainer,
generateFileMounts, generateFileMounts,
generateVolumeMounts, generateVolumeMounts,
prepareEnvironmentVariables, prepareEnvironmentVariables,
@@ -40,6 +41,17 @@ export const buildMysql = async (mysql: MysqlNested) => {
: `MYSQL_DATABASE="${databaseName}"\nMYSQL_ROOT_PASSWORD="${databaseRootPassword}"${ : `MYSQL_DATABASE="${databaseName}"\nMYSQL_ROOT_PASSWORD="${databaseRootPassword}"${
env ? `\n${env}` : "" env ? `\n${env}` : ""
}`; }`;
const {
HealthCheck,
RestartPolicy,
Placement,
Labels,
Mode,
RollbackConfig,
UpdateConfig,
Networks,
} = generateConfigContainer(mysql);
const resources = calculateResources({ const resources = calculateResources({
memoryLimit, memoryLimit,
memoryReservation, memoryReservation,
@@ -60,6 +72,7 @@ export const buildMysql = async (mysql: MysqlNested) => {
Name: appName, Name: appName,
TaskTemplate: { TaskTemplate: {
ContainerSpec: { ContainerSpec: {
HealthCheck,
Image: dockerImage, Image: dockerImage,
Env: envVariables, Env: envVariables,
Mounts: [...volumesMount, ...bindsMount, ...filesMount], Mounts: [...volumesMount, ...bindsMount, ...filesMount],
@@ -69,20 +82,17 @@ export const buildMysql = async (mysql: MysqlNested) => {
Args: ["-c", command], Args: ["-c", command],
} }
: {}), : {}),
Labels,
}, },
Networks: [{ Target: "dokploy-network" }], Networks,
RestartPolicy,
Placement,
Resources: { Resources: {
...resources, ...resources,
}, },
Placement: {
Constraints: ["node.role==manager"],
},
},
Mode: {
Replicated: {
Replicas: 1,
},
}, },
Mode,
RollbackConfig,
EndpointSpec: { EndpointSpec: {
Mode: "dnsrr", Mode: "dnsrr",
Ports: externalPort Ports: externalPort
@@ -96,6 +106,7 @@ export const buildMysql = async (mysql: MysqlNested) => {
] ]
: [], : [],
}, },
UpdateConfig,
}; };
try { try {
const service = docker.getService(appName); const service = docker.getService(appName);
@@ -103,6 +114,10 @@ export const buildMysql = async (mysql: MysqlNested) => {
await service.update({ await service.update({
version: Number.parseInt(inspect.Version.Index), version: Number.parseInt(inspect.Version.Index),
...settings, ...settings,
TaskTemplate: {
...settings.TaskTemplate,
ForceUpdate: inspect.Spec.TaskTemplate.ForceUpdate + 1,
},
}); });
} catch { } catch {
await docker.createService(settings); await docker.createService(settings);
@@ -3,6 +3,7 @@ import type { CreateServiceOptions } from "dockerode";
import { import {
calculateResources, calculateResources,
generateBindMounts, generateBindMounts,
generateConfigContainer,
generateFileMounts, generateFileMounts,
generateVolumeMounts, generateVolumeMounts,
prepareEnvironmentVariables, prepareEnvironmentVariables,
@@ -33,6 +34,17 @@ export const buildPostgres = async (postgres: PostgresNested) => {
const defaultPostgresEnv = `POSTGRES_DB="${databaseName}"\nPOSTGRES_USER="${databaseUser}"\nPOSTGRES_PASSWORD="${databasePassword}"${ const defaultPostgresEnv = `POSTGRES_DB="${databaseName}"\nPOSTGRES_USER="${databaseUser}"\nPOSTGRES_PASSWORD="${databasePassword}"${
env ? `\n${env}` : "" env ? `\n${env}` : ""
}`; }`;
const {
HealthCheck,
RestartPolicy,
Placement,
Labels,
Mode,
RollbackConfig,
UpdateConfig,
Networks,
} = generateConfigContainer(postgres);
const resources = calculateResources({ const resources = calculateResources({
memoryLimit, memoryLimit,
memoryReservation, memoryReservation,
@@ -53,6 +65,7 @@ export const buildPostgres = async (postgres: PostgresNested) => {
Name: appName, Name: appName,
TaskTemplate: { TaskTemplate: {
ContainerSpec: { ContainerSpec: {
HealthCheck,
Image: dockerImage, Image: dockerImage,
Env: envVariables, Env: envVariables,
Mounts: [...volumesMount, ...bindsMount, ...filesMount], Mounts: [...volumesMount, ...bindsMount, ...filesMount],
@@ -62,20 +75,17 @@ export const buildPostgres = async (postgres: PostgresNested) => {
Args: ["-c", command], Args: ["-c", command],
} }
: {}), : {}),
Labels,
}, },
Networks: [{ Target: "dokploy-network" }], Networks,
RestartPolicy,
Placement,
Resources: { Resources: {
...resources, ...resources,
}, },
Placement: {
Constraints: ["node.role==manager"],
},
},
Mode: {
Replicated: {
Replicas: 1,
},
}, },
Mode,
RollbackConfig,
EndpointSpec: { EndpointSpec: {
Mode: "dnsrr", Mode: "dnsrr",
Ports: externalPort Ports: externalPort
@@ -89,6 +99,7 @@ export const buildPostgres = async (postgres: PostgresNested) => {
] ]
: [], : [],
}, },
UpdateConfig,
}; };
try { try {
const service = docker.getService(appName); const service = docker.getService(appName);
@@ -96,6 +107,10 @@ export const buildPostgres = async (postgres: PostgresNested) => {
await service.update({ await service.update({
version: Number.parseInt(inspect.Version.Index), version: Number.parseInt(inspect.Version.Index),
...settings, ...settings,
TaskTemplate: {
...settings.TaskTemplate,
ForceUpdate: inspect.Spec.TaskTemplate.ForceUpdate + 1,
},
}); });
} catch (error) { } catch (error) {
console.log("error", error); console.log("error", error);
+24 -9
View File
@@ -3,6 +3,7 @@ import type { CreateServiceOptions } from "dockerode";
import { import {
calculateResources, calculateResources,
generateBindMounts, generateBindMounts,
generateConfigContainer,
generateFileMounts, generateFileMounts,
generateVolumeMounts, generateVolumeMounts,
prepareEnvironmentVariables, prepareEnvironmentVariables,
@@ -31,6 +32,17 @@ export const buildRedis = async (redis: RedisNested) => {
const defaultRedisEnv = `REDIS_PASSWORD="${databasePassword}"${ const defaultRedisEnv = `REDIS_PASSWORD="${databasePassword}"${
env ? `\n${env}` : "" env ? `\n${env}` : ""
}`; }`;
const {
HealthCheck,
RestartPolicy,
Placement,
Labels,
Mode,
RollbackConfig,
UpdateConfig,
Networks,
} = generateConfigContainer(redis);
const resources = calculateResources({ const resources = calculateResources({
memoryLimit, memoryLimit,
memoryReservation, memoryReservation,
@@ -51,6 +63,7 @@ export const buildRedis = async (redis: RedisNested) => {
Name: appName, Name: appName,
TaskTemplate: { TaskTemplate: {
ContainerSpec: { ContainerSpec: {
HealthCheck,
Image: dockerImage, Image: dockerImage,
Env: envVariables, Env: envVariables,
Mounts: [...volumesMount, ...bindsMount, ...filesMount], Mounts: [...volumesMount, ...bindsMount, ...filesMount],
@@ -59,20 +72,17 @@ export const buildRedis = async (redis: RedisNested) => {
"-c", "-c",
command ? command : `redis-server --requirepass ${databasePassword}`, command ? command : `redis-server --requirepass ${databasePassword}`,
], ],
Labels,
}, },
Networks: [{ Target: "dokploy-network" }], Networks,
RestartPolicy,
Placement,
Resources: { Resources: {
...resources, ...resources,
}, },
Placement: {
Constraints: ["node.role==manager"],
},
},
Mode: {
Replicated: {
Replicas: 1,
},
}, },
Mode,
RollbackConfig,
EndpointSpec: { EndpointSpec: {
Mode: "dnsrr", Mode: "dnsrr",
Ports: externalPort Ports: externalPort
@@ -86,6 +96,7 @@ export const buildRedis = async (redis: RedisNested) => {
] ]
: [], : [],
}, },
UpdateConfig,
}; };
try { try {
@@ -94,6 +105,10 @@ export const buildRedis = async (redis: RedisNested) => {
await service.update({ await service.update({
version: Number.parseInt(inspect.Version.Index), version: Number.parseInt(inspect.Version.Index),
...settings, ...settings,
TaskTemplate: {
...settings.TaskTemplate,
ForceUpdate: inspect.Spec.TaskTemplate.ForceUpdate + 1,
},
}); });
} catch { } catch {
await docker.createService(settings); await docker.createService(settings);
+4 -2
View File
@@ -348,7 +348,9 @@ export const calculateResources = ({
}; };
}; };
export const generateConfigContainer = (application: ApplicationNested) => { export const generateConfigContainer = (
application: Partial<ApplicationNested>,
) => {
const { const {
healthCheckSwarm, healthCheckSwarm,
restartPolicySwarm, restartPolicySwarm,
@@ -362,7 +364,7 @@ export const generateConfigContainer = (application: ApplicationNested) => {
networkSwarm, networkSwarm,
} = application; } = application;
const haveMounts = mounts.length > 0; const haveMounts = mounts && mounts.length > 0;
return { return {
...(healthCheckSwarm && { ...(healthCheckSwarm && {