feat(cluster-settings): refactor cluster settings components to support multiple database types and add new swarm configuration fields

This commit is contained in:
Mauricio Siu
2025-08-03 16:36:14 -06:00
parent 2318fb062a
commit 6e79183f6a
14 changed files with 6992 additions and 272 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,
@@ -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" && "registryId" in data
? {
registryId: data?.registryId || "",
}
: {}),
replicas: data?.replicas || 1, replicas: data?.replicas || 1,
}, },
resolver: zodResolver(AddRedirectchema), resolver: zodResolver(AddRedirectchema),
@@ -71,26 +92,39 @@ export const ShowClusterSettings = ({ applicationId }: Props) => {
useEffect(() => { useEffect(() => {
if (data?.command) { if (data?.command) {
form.reset({ form.reset({
registryId: data?.registryId || "", ...(type === "application" && "registryId" in data
? {
registryId: data?.registryId || "",
}
: {}),
replicas: data?.replicas || 1, replicas: data?.replicas || 1,
}); });
} }
}, [form, form.reset, form.formState.isSubmitSuccessful, data?.command]); }, [form, form.reset, form.formState.isSubmitSuccessful, data?.command]);
console.log(form.formState.errors);
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");
@@ -106,7 +140,7 @@ export const ShowClusterSettings = ({ applicationId }: Props) => {
Add the registry and the replicas of the application Add the registry and the replicas of the application
</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 +178,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());