Merge pull request #96 from Dokploy/feat/multi-node-support

Feat/multi node support
This commit is contained in:
Mauricio Siu
2024-05-26 03:13:00 -06:00
committed by GitHub
102 changed files with 26060 additions and 1426 deletions

View File

@@ -26,7 +26,7 @@ FROM node:18-slim AS production
# Install dependencies only for production # Install dependencies only for production
ENV PNPM_HOME="/pnpm" ENV PNPM_HOME="/pnpm"
ENV PATH="$PNPM_HOME:$PATH" ENV PATH="$PNPM_HOME:$PATH"
RUN corepack enable && apt-get update && apt-get install -y curl && rm -rf /var/lib/apt/lists/* RUN corepack enable && apt-get update && apt-get install -y curl && apt-get install -y apache2-utils && rm -rf /var/lib/apt/lists/*
WORKDIR /app WORKDIR /app
@@ -47,7 +47,6 @@ RUN --mount=type=cache,id=pnpm,target=/pnpm/store pnpm install --prod --frozen-l
# Install docker # Install docker
RUN curl -fsSL https://get.docker.com -o get-docker.sh && sh get-docker.sh && rm get-docker.sh RUN curl -fsSL https://get.docker.com -o get-docker.sh && sh get-docker.sh && rm get-docker.sh
# Install Nixpacks and tsx # Install Nixpacks and tsx
# | VERBOSE=1 VERSION=1.21.0 bash # | VERBOSE=1 VERSION=1.21.0 bash
RUN curl -sSL https://nixpacks.com/install.sh -o install.sh \ RUN curl -sSL https://nixpacks.com/install.sh -o install.sh \

View File

@@ -0,0 +1,756 @@
import { Button } from "@/components/ui/button";
import {
Dialog,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "@/components/ui/dialog";
import {
Form,
FormControl,
FormDescription,
FormField,
FormItem,
FormLabel,
FormMessage,
} from "@/components/ui/form";
import { api } from "@/utils/api";
import { AlertBlock } from "@/components/shared/alert-block";
import { zodResolver } from "@hookform/resolvers/zod";
import { useEffect } from "react";
import { useForm } from "react-hook-form";
import { toast } from "sonner";
import { z } from "zod";
import { Textarea } from "@/components/ui/textarea";
import { HelpCircle, Settings } from "lucide-react";
import {
Tooltip,
TooltipContent,
TooltipProvider,
TooltipTrigger,
} from "@/components/ui/tooltip";
const HealthCheckSwarmSchema = z
.object({
Test: z.array(z.string()).optional(),
Interval: z.number().optional(),
Timeout: z.number().optional(),
StartPeriod: z.number().optional(),
Retries: z.number().optional(),
})
.strict();
const RestartPolicySwarmSchema = z
.object({
Condition: z.string().optional(),
Delay: z.number().optional(),
MaxAttempts: z.number().optional(),
Window: z.number().optional(),
})
.strict();
const PreferenceSchema = z
.object({
Spread: z.object({
SpreadDescriptor: z.string(),
}),
})
.strict();
const PlatformSchema = z
.object({
Architecture: z.string(),
OS: z.string(),
})
.strict();
const PlacementSwarmSchema = z
.object({
Constraints: z.array(z.string()).optional(),
Preferences: z.array(PreferenceSchema).optional(),
MaxReplicas: z.number().optional(),
Platforms: z.array(PlatformSchema).optional(),
})
.strict();
const UpdateConfigSwarmSchema = z
.object({
Parallelism: z.number(),
Delay: z.number().optional(),
FailureAction: z.string().optional(),
Monitor: z.number().optional(),
MaxFailureRatio: z.number().optional(),
Order: z.string(),
})
.strict();
const ReplicatedSchema = z
.object({
Replicas: z.number().optional(),
})
.strict();
const ReplicatedJobSchema = z
.object({
MaxConcurrent: z.number().optional(),
TotalCompletions: z.number().optional(),
})
.strict();
const ServiceModeSwarmSchema = z
.object({
Replicated: ReplicatedSchema.optional(),
Global: z.object({}).optional(),
ReplicatedJob: ReplicatedJobSchema.optional(),
GlobalJob: z.object({}).optional(),
})
.strict();
const NetworkSwarmSchema = z.array(
z
.object({
Target: z.string().optional(),
Aliases: z.array(z.string()).optional(),
DriverOpts: z.object({}).optional(),
})
.strict(),
);
const LabelsSwarmSchema = z.record(z.string());
const createStringToJSONSchema = (schema: z.ZodTypeAny) => {
return z
.string()
.transform((str, ctx) => {
if (str === null || str === "") {
return null;
}
try {
return JSON.parse(str);
} catch (e) {
ctx.addIssue({ code: "custom", message: "Invalid JSON format" });
return z.NEVER;
}
})
.superRefine((data, ctx) => {
if (data === null) {
return;
}
if (Object.keys(data).length === 0) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
message: "Object cannot be empty",
});
return;
}
const parseResult = schema.safeParse(data);
if (!parseResult.success) {
for (const error of parseResult.error.issues) {
const path = error.path.join(".");
ctx.addIssue({
code: z.ZodIssueCode.custom,
message: `${path} ${error.message}`,
});
}
}
});
};
const addSwarmSettings = z.object({
healthCheckSwarm: createStringToJSONSchema(HealthCheckSwarmSchema).nullable(),
restartPolicySwarm: createStringToJSONSchema(
RestartPolicySwarmSchema,
).nullable(),
placementSwarm: createStringToJSONSchema(PlacementSwarmSchema).nullable(),
updateConfigSwarm: createStringToJSONSchema(
UpdateConfigSwarmSchema,
).nullable(),
rollbackConfigSwarm: createStringToJSONSchema(
UpdateConfigSwarmSchema,
).nullable(),
modeSwarm: createStringToJSONSchema(ServiceModeSwarmSchema).nullable(),
labelsSwarm: createStringToJSONSchema(LabelsSwarmSchema).nullable(),
networkSwarm: createStringToJSONSchema(NetworkSwarmSchema).nullable(),
});
type AddSwarmSettings = z.infer<typeof addSwarmSettings>;
interface Props {
applicationId: string;
}
export const AddSwarmSettings = ({ applicationId }: Props) => {
const { data, refetch } = api.application.one.useQuery(
{
applicationId,
},
{
enabled: !!applicationId,
},
);
const { mutateAsync, isError, error, isLoading } =
api.application.update.useMutation();
const form = useForm<AddSwarmSettings>({
defaultValues: {
healthCheckSwarm: null,
restartPolicySwarm: null,
placementSwarm: null,
updateConfigSwarm: null,
rollbackConfigSwarm: null,
modeSwarm: null,
labelsSwarm: null,
networkSwarm: null,
},
resolver: zodResolver(addSwarmSettings),
});
useEffect(() => {
if (data) {
form.reset({
healthCheckSwarm: data.healthCheckSwarm
? JSON.stringify(data.healthCheckSwarm, null, 2)
: null,
restartPolicySwarm: data.restartPolicySwarm
? JSON.stringify(data.restartPolicySwarm, null, 2)
: null,
placementSwarm: data.placementSwarm
? JSON.stringify(data.placementSwarm, null, 2)
: null,
updateConfigSwarm: data.updateConfigSwarm
? JSON.stringify(data.updateConfigSwarm, null, 2)
: null,
rollbackConfigSwarm: data.rollbackConfigSwarm
? JSON.stringify(data.rollbackConfigSwarm, null, 2)
: null,
modeSwarm: data.modeSwarm
? JSON.stringify(data.modeSwarm, null, 2)
: null,
labelsSwarm: data.labelsSwarm
? JSON.stringify(data.labelsSwarm, null, 2)
: null,
networkSwarm: data.networkSwarm
? JSON.stringify(data.networkSwarm, null, 2)
: null,
});
}
}, [form, form.reset, data]);
const onSubmit = async (data: AddSwarmSettings) => {
await mutateAsync({
applicationId,
healthCheckSwarm: data.healthCheckSwarm,
restartPolicySwarm: data.restartPolicySwarm,
placementSwarm: data.placementSwarm,
updateConfigSwarm: data.updateConfigSwarm,
rollbackConfigSwarm: data.rollbackConfigSwarm,
modeSwarm: data.modeSwarm,
labelsSwarm: data.labelsSwarm,
networkSwarm: data.networkSwarm,
})
.then(async () => {
toast.success("Swarm settings updated");
refetch();
})
.catch(() => {
toast.error("Error to update the swarm settings");
});
};
return (
<Dialog>
<DialogTrigger asChild>
<Button variant="secondary" className="cursor-pointer w-fit">
<Settings className="size-4 text-muted-foreground" />
Swarm Settings
</Button>
</DialogTrigger>
<DialogContent className="max-h-[85vh] overflow-y-auto sm:max-w-5xl p-0">
<DialogHeader className="p-6">
<DialogTitle>Swarm Settings</DialogTitle>
<DialogDescription>
Update certain settings using a json object.
</DialogDescription>
</DialogHeader>
{isError && <AlertBlock type="error">{error?.message}</AlertBlock>}
<Form {...form}>
<form
id="hook-form-add-permissions"
onSubmit={form.handleSubmit(onSubmit)}
className="grid grid-cols-1 md:grid-cols-2 w-full gap-4 relative"
>
<FormField
control={form.control}
name="healthCheckSwarm"
render={({ field }) => (
<FormItem className="relative max-lg:px-4 lg:pl-6 ">
<FormLabel>Health Check</FormLabel>
<TooltipProvider delayDuration={0}>
<Tooltip>
<TooltipTrigger asChild>
<FormDescription className="break-all w-fit flex flex-row gap-1 items-center">
Check the interface
<HelpCircle className="size-4 text-muted-foreground" />
</FormDescription>
</TooltipTrigger>
<TooltipContent
className="w-full z-[999]"
align="start"
side="bottom"
>
<code>
<pre>
{`{
Test?: string[] | undefined;
Interval?: number | undefined;
Timeout?: number | undefined;
StartPeriod?: number | undefined;
Retries?: number | undefined;
}`}
</pre>
</code>
</TooltipContent>
</Tooltip>
</TooltipProvider>
<FormControl>
<Textarea
className="font-mono [field-sizing:content;] min-h-[11.2rem]"
placeholder={`{
"Test" : ["CMD-SHELL", "curl -f http://localhost:3000/health"],
"Interval" : 10000,
"Timeout" : 10000,
"StartPeriod" : 10000,
"Retries" : 10
}`}
{...field}
value={field?.value || ""}
/>
</FormControl>
<pre>
<FormMessage />
</pre>
</FormItem>
)}
/>
<FormField
control={form.control}
name="restartPolicySwarm"
render={({ field }) => (
<FormItem className="relative max-lg:px-4 lg:pr-6 ">
<FormLabel>Restart Policy</FormLabel>
<TooltipProvider delayDuration={0}>
<Tooltip>
<TooltipTrigger asChild>
<FormDescription className="break-all w-fit flex flex-row gap-1 items-center">
Check the interface
<HelpCircle className="size-4 text-muted-foreground" />
</FormDescription>
</TooltipTrigger>
<TooltipContent
className="w-full z-[999]"
align="start"
side="bottom"
>
<code>
<pre>
{`{
Condition?: string | undefined;
Delay?: number | undefined;
MaxAttempts?: number | undefined;
Window?: number | undefined;
}`}
</pre>
</code>
</TooltipContent>
</Tooltip>
</TooltipProvider>
<FormControl>
<Textarea
className="font-mono [field-sizing:content;] min-h-[11.2rem]"
placeholder={`{
"Condition" : "on-failure",
"Delay" : 10000,
"MaxAttempts" : 10,
"Window" : 10000
} `}
{...field}
value={field?.value || ""}
/>
</FormControl>
<pre>
<FormMessage />
</pre>
</FormItem>
)}
/>
<FormField
control={form.control}
name="placementSwarm"
render={({ field }) => (
<FormItem className="relative max-lg:px-4 lg:pl-6 ">
<FormLabel>Placement</FormLabel>
<TooltipProvider delayDuration={0}>
<Tooltip>
<TooltipTrigger asChild>
<FormDescription className="break-all w-fit flex flex-row gap-1 items-center">
Check the interface
<HelpCircle className="size-4 text-muted-foreground" />
</FormDescription>
</TooltipTrigger>
<TooltipContent
className="w-full z-[999]"
align="start"
side="bottom"
>
<code>
<pre>
{`{
Constraints?: string[] | undefined;
Preferences?: Array<{ Spread: { SpreadDescriptor: string } }> | undefined;
MaxReplicas?: number | undefined;
Platforms?:
| Array<{
Architecture: string;
OS: string;
}>
| undefined;
}`}
</pre>
</code>
</TooltipContent>
</Tooltip>
</TooltipProvider>
<FormControl>
<Textarea
className="font-mono [field-sizing:content;] min-h-[18.7rem]"
placeholder={`{
"Constraints" : ["node.role==manager"],
"Preferences" : [{
"Spread" : {
"SpreadDescriptor" : "node.labels.region"
}
}],
"MaxReplicas" : 10,
"Platforms" : [{
"Architecture" : "amd64",
"OS" : "linux"
}]
} `}
{...field}
value={field?.value || ""}
/>
</FormControl>
<pre>
<FormMessage />
</pre>
</FormItem>
)}
/>
<FormField
control={form.control}
name="updateConfigSwarm"
render={({ field }) => (
<FormItem className="relative max-lg:px-4 lg:pr-6 ">
<FormLabel>Update Config</FormLabel>
<TooltipProvider delayDuration={0}>
<Tooltip>
<TooltipTrigger asChild>
<FormDescription className="break-all w-fit flex flex-row gap-1 items-center">
Check the interface
<HelpCircle className="size-4 text-muted-foreground" />
</FormDescription>
</TooltipTrigger>
<TooltipContent
className="w-full z-[999]"
align="start"
side="bottom"
>
<code>
<pre>
{`{
Parallelism?: number;
Delay?: number | undefined;
FailureAction?: string | undefined;
Monitor?: number | undefined;
MaxFailureRatio?: number | undefined;
Order: string;
}`}
</pre>
</code>
</TooltipContent>
</Tooltip>
</TooltipProvider>
<FormControl>
<Textarea
className="font-mono [field-sizing:content;] min-h-[18.7rem]"
placeholder={`{
"Parallelism" : 1,
"Delay" : 10000,
"FailureAction" : "continue",
"Monitor" : 10000,
"MaxFailureRatio" : 10,
"Order" : "start-first"
}`}
{...field}
value={field?.value || ""}
/>
</FormControl>
<pre>
<FormMessage />
</pre>
</FormItem>
)}
/>
<FormField
control={form.control}
name="rollbackConfigSwarm"
render={({ field }) => (
<FormItem className="relative max-lg:px-4 lg:pl-6 ">
<FormLabel>Rollback Config</FormLabel>
<TooltipProvider delayDuration={0}>
<Tooltip>
<TooltipTrigger asChild>
<FormDescription className="break-all w-fit flex flex-row gap-1 items-center">
Check the interface
<HelpCircle className="size-4 text-muted-foreground" />
</FormDescription>
</TooltipTrigger>
<TooltipContent
className="w-full z-[999]"
align="start"
side="bottom"
>
<code>
<pre>
{`{
Parallelism?: number;
Delay?: number | undefined;
FailureAction?: string | undefined;
Monitor?: number | undefined;
MaxFailureRatio?: number | undefined;
Order: string;
}`}
</pre>
</code>
</TooltipContent>
</Tooltip>
</TooltipProvider>
<FormControl>
<Textarea
className="font-mono [field-sizing:content;] min-h-[14.8rem]"
placeholder={`{
"Parallelism" : 1,
"Delay" : 10000,
"FailureAction" : "continue",
"Monitor" : 10000,
"MaxFailureRatio" : 10,
"Order" : "start-first"
}`}
{...field}
value={field?.value || ""}
/>
</FormControl>
<pre>
<FormMessage />
</pre>
</FormItem>
)}
/>
<FormField
control={form.control}
name="modeSwarm"
render={({ field }) => (
<FormItem className="relative max-lg:px-4 lg:pr-6 ">
<FormLabel>Mode</FormLabel>
<TooltipProvider delayDuration={0}>
<Tooltip>
<TooltipTrigger asChild>
<FormDescription className="break-all w-fit flex flex-row gap-1 items-center">
Check the interface
<HelpCircle className="size-4 text-muted-foreground" />
</FormDescription>
</TooltipTrigger>
<TooltipContent
className="w-full z-[999]"
align="center"
side="bottom"
>
<code>
<pre>
{`{
Replicated?: { Replicas?: number | undefined } | undefined;
Global?: {} | undefined;
ReplicatedJob?:
| {
MaxConcurrent?: number | undefined;
TotalCompletions?: number | undefined;
}
| undefined;
GlobalJob?: {} | undefined;
}`}
</pre>
</code>
</TooltipContent>
</Tooltip>
</TooltipProvider>
<FormControl>
<Textarea
className="font-mono [field-sizing:content;] min-h-[14.8rem]"
placeholder={`{
"Replicated" : {
"Replicas" : 1
},
"Global" : {},
"ReplicatedJob" : {
"MaxConcurrent" : 1,
"TotalCompletions" : 1
},
"GlobalJob" : {}
}`}
{...field}
value={field?.value || ""}
/>
</FormControl>
<pre>
<FormMessage />
</pre>
</FormItem>
)}
/>
<FormField
control={form.control}
name="networkSwarm"
render={({ field }) => (
<FormItem className="relative max-lg:px-4 lg:pl-6 ">
<FormLabel>Network</FormLabel>
<TooltipProvider delayDuration={0}>
<Tooltip>
<TooltipTrigger asChild>
<FormDescription className="break-all w-fit flex flex-row gap-1 items-center">
Check the interface
<HelpCircle className="size-4 text-muted-foreground" />
</FormDescription>
</TooltipTrigger>
<TooltipContent
className="w-full z-[999]"
align="start"
side="bottom"
>
<code>
<pre>
{`[
{
"Target" : string | undefined;
"Aliases" : string[] | undefined;
"DriverOpts" : { [key: string]: string } | undefined;
}
]`}
</pre>
</code>
</TooltipContent>
</Tooltip>
</TooltipProvider>
<FormControl>
<Textarea
className="font-mono [field-sizing:content;] min-h-[18.5rem]"
placeholder={`[
{
"Target" : "dokploy-network",
"Aliases" : ["dokploy-network"],
"DriverOpts" : {
"com.docker.network.driver.mtu" : "1500",
"com.docker.network.driver.host_binding" : "true",
"com.docker.network.driver.mtu" : "1500",
"com.docker.network.driver.host_binding" : "true"
}
}
]`}
{...field}
value={field?.value || ""}
/>
</FormControl>
<pre>
<FormMessage />
</pre>
</FormItem>
)}
/>
<FormField
control={form.control}
name="labelsSwarm"
render={({ field }) => (
<FormItem className="relative max-lg:px-4 lg:pr-6 ">
<FormLabel>Labels</FormLabel>
<TooltipProvider delayDuration={0}>
<Tooltip>
<TooltipTrigger asChild>
<FormDescription className="break-all w-fit flex flex-row gap-1 items-center">
Check the interface
<HelpCircle className="size-4 text-muted-foreground" />
</FormDescription>
</TooltipTrigger>
<TooltipContent
className="w-full z-[999]"
align="start"
side="bottom"
>
<code>
<pre>
{`{
[name: string]: string;
}`}
</pre>
</code>
</TooltipContent>
</Tooltip>
</TooltipProvider>
<FormControl>
<Textarea
className="font-mono [field-sizing:content;] min-h-[18.5rem]"
placeholder={`{
"com.example.app.name" : "my-app",
"com.example.app.version" : "1.0.0"
}`}
{...field}
value={field?.value || ""}
/>
</FormControl>
<pre>
<FormMessage />
</pre>
</FormItem>
)}
/>
<DialogFooter className="flex w-full flex-row justify-end md:col-span-2 m-0 sticky bottom-0 right-0 bg-muted border p-2 ">
<Button
isLoading={isLoading}
form="hook-form-add-permissions"
type="submit"
>
Update
</Button>
</DialogFooter>
</form>
</Form>
</DialogContent>
</Dialog>
);
};

View File

@@ -0,0 +1,203 @@
import React from "react";
import {
Card,
CardContent,
CardDescription,
CardHeader,
CardTitle,
} from "@/components/ui/card";
import { api } from "@/utils/api";
import { z } from "zod";
import {
Form,
FormControl,
FormField,
FormItem,
FormLabel,
FormMessage,
} from "@/components/ui/form";
import { toast } from "sonner";
import { zodResolver } from "@hookform/resolvers/zod";
import { useForm } from "react-hook-form";
import { useEffect } from "react";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import {
Select,
SelectContent,
SelectGroup,
SelectItem,
SelectLabel,
SelectTrigger,
SelectValue,
} from "@/components/ui/select";
import Link from "next/link";
import { Server } from "lucide-react";
import { AddSwarmSettings } from "./modify-swarm-settings";
interface Props {
applicationId: string;
}
const AddRedirectchema = z.object({
replicas: z.number(),
registryId: z.string(),
});
type AddCommand = z.infer<typeof AddRedirectchema>;
export const ShowClusterSettings = ({ applicationId }: Props) => {
const { data } = api.application.one.useQuery(
{
applicationId,
},
{ enabled: !!applicationId },
);
const { data: registries } = api.registry.all.useQuery();
const utils = api.useUtils();
const { mutateAsync, isLoading } = api.application.update.useMutation();
const form = useForm<AddCommand>({
defaultValues: {
registryId: data?.registryId || "",
replicas: data?.replicas || 1,
},
resolver: zodResolver(AddRedirectchema),
});
useEffect(() => {
if (data?.command) {
form.reset({
registryId: data?.registryId || "",
replicas: data?.replicas || 1,
});
}
}, [form, form.reset, form.formState.isSubmitSuccessful, data?.command]);
const onSubmit = async (data: AddCommand) => {
await mutateAsync({
applicationId,
registryId: data?.registryId === "none" ? null : data?.registryId,
replicas: data?.replicas,
})
.then(async () => {
toast.success("Command Updated");
await utils.application.one.invalidate({
applicationId,
});
})
.catch(() => {
toast.error("Error to update the command");
});
};
return (
<Card className="bg-background">
<CardHeader className="flex flex-row justify-between">
<div>
<CardTitle className="text-xl">Cluster Settings</CardTitle>
<CardDescription>
Add the registry and the replicas of the application
</CardDescription>
</div>
<AddSwarmSettings applicationId={applicationId} />
</CardHeader>
<CardContent className="flex flex-col gap-4">
<Form {...form}>
<form
onSubmit={form.handleSubmit(onSubmit)}
className="grid w-full gap-4"
>
<div className="flex flex-col gap-4">
<FormField
control={form.control}
name="replicas"
render={({ field }) => (
<FormItem>
<FormLabel>Replicas</FormLabel>
<FormControl>
<Input
placeholder="1"
{...field}
onChange={(e) => {
field.onChange(Number(e.target.value));
}}
type="number"
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
</div>
{registries && registries?.length === 0 ? (
<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
control={form.control}
name="registryId"
render={({ field }) => (
<FormItem>
<FormLabel>Select a registry</FormLabel>
<Select
onValueChange={field.onChange}
defaultValue={field.value}
>
<SelectTrigger>
<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>
)}
/>
</>
)}
<div className="flex justify-end">
<Button isLoading={isLoading} type="submit" className="w-fit">
Save
</Button>
</div>
</form>
</Form>
</CardContent>
</Card>
);
};

View File

@@ -20,7 +20,7 @@ import { Input } from "@/components/ui/input";
import { api } from "@/utils/api"; import { api } from "@/utils/api";
import { AlertBlock } from "@/components/shared/alert-block"; import { AlertBlock } from "@/components/shared/alert-block";
import { zodResolver } from "@hookform/resolvers/zod"; import { zodResolver } from "@hookform/resolvers/zod";
import { Pencil } from "lucide-react"; import { PenBoxIcon, Pencil } from "lucide-react";
import { useEffect } from "react"; import { useEffect } from "react";
import { useForm } from "react-hook-form"; import { useForm } from "react-hook-form";
import { toast } from "sonner"; import { toast } from "sonner";
@@ -99,7 +99,7 @@ export const UpdatePort = ({ portId }: Props) => {
<Dialog> <Dialog>
<DialogTrigger asChild> <DialogTrigger asChild>
<Button variant="ghost" isLoading={isLoading}> <Button variant="ghost" isLoading={isLoading}>
<Pencil className="size-4 text-muted-foreground" /> <PenBoxIcon className="size-4 text-muted-foreground" />
</Button> </Button>
</DialogTrigger> </DialogTrigger>
<DialogContent className="max-h-screen overflow-y-auto sm:max-w-lg"> <DialogContent className="max-h-screen overflow-y-auto sm:max-w-lg">

View File

@@ -21,7 +21,7 @@ import { Input } from "@/components/ui/input";
import { api } from "@/utils/api"; import { api } from "@/utils/api";
import { AlertBlock } from "@/components/shared/alert-block"; import { AlertBlock } from "@/components/shared/alert-block";
import { zodResolver } from "@hookform/resolvers/zod"; import { zodResolver } from "@hookform/resolvers/zod";
import { Pencil } from "lucide-react"; import { PenBoxIcon, Pencil } from "lucide-react";
import { useEffect } from "react"; import { useEffect } from "react";
import { useForm } from "react-hook-form"; import { useForm } from "react-hook-form";
import { toast } from "sonner"; import { toast } from "sonner";
@@ -94,7 +94,7 @@ export const UpdateRedirect = ({ redirectId }: Props) => {
<Dialog> <Dialog>
<DialogTrigger asChild> <DialogTrigger asChild>
<Button variant="ghost" isLoading={isLoading}> <Button variant="ghost" isLoading={isLoading}>
<Pencil className="size-4 text-muted-foreground" /> <PenBoxIcon className="size-4 text-muted-foreground" />
</Button> </Button>
</DialogTrigger> </DialogTrigger>
<DialogContent className="max-h-screen overflow-y-auto sm:max-w-lg"> <DialogContent className="max-h-screen overflow-y-auto sm:max-w-lg">

View File

@@ -20,7 +20,7 @@ import { Input } from "@/components/ui/input";
import { api } from "@/utils/api"; import { api } from "@/utils/api";
import { AlertBlock } from "@/components/shared/alert-block"; import { AlertBlock } from "@/components/shared/alert-block";
import { zodResolver } from "@hookform/resolvers/zod"; import { zodResolver } from "@hookform/resolvers/zod";
import { Pencil } from "lucide-react"; import { PenBoxIcon, Pencil } from "lucide-react";
import { useEffect } from "react"; import { useEffect } from "react";
import { useForm } from "react-hook-form"; import { useForm } from "react-hook-form";
import { toast } from "sonner"; import { toast } from "sonner";
@@ -89,7 +89,7 @@ export const UpdateSecurity = ({ securityId }: Props) => {
<Dialog> <Dialog>
<DialogTrigger asChild> <DialogTrigger asChild>
<Button variant="ghost" isLoading={isLoading}> <Button variant="ghost" isLoading={isLoading}>
<Pencil className="size-4 text-muted-foreground" /> <PenBoxIcon className="size-4 text-muted-foreground" />
</Button> </Button>
</DialogTrigger> </DialogTrigger>
<DialogContent className="max-h-screen overflow-y-auto sm:max-w-lg"> <DialogContent className="max-h-screen overflow-y-auto sm:max-w-lg">

View File

@@ -25,8 +25,8 @@ export const DeleteApplication = ({ applicationId }: Props) => {
return ( return (
<AlertDialog> <AlertDialog>
<AlertDialogTrigger asChild> <AlertDialogTrigger asChild>
<Button variant="destructive" isLoading={isLoading}> <Button variant="ghost" isLoading={isLoading}>
<TrashIcon className="size-4 " /> <TrashIcon className="size-4 text-muted-foreground" />
</Button> </Button>
</AlertDialogTrigger> </AlertDialogTrigger>
<AlertDialogContent> <AlertDialogContent>

View File

@@ -24,8 +24,8 @@ export const DeleteDomain = ({ domainId }: Props) => {
return ( return (
<AlertDialog> <AlertDialog>
<AlertDialogTrigger asChild> <AlertDialogTrigger asChild>
<Button variant="destructive" isLoading={isLoading}> <Button variant="ghost" isLoading={isLoading}>
<TrashIcon className="size-4 " /> <TrashIcon className="size-4 text-muted-foreground " />
</Button> </Button>
</AlertDialogTrigger> </AlertDialogTrigger>
<AlertDialogContent> <AlertDialogContent>

View File

@@ -40,7 +40,9 @@ export const ShowDomains = ({ applicationId }: Props) => {
</div> </div>
{data && data?.length > 0 && ( {data && data?.length > 0 && (
<AddDomain applicationId={applicationId} /> <AddDomain applicationId={applicationId}>
<GlobeIcon className="size-4" /> Add Domain
</AddDomain>
)} )}
</CardHeader> </CardHeader>
<CardContent className="flex w-full flex-row gap-4"> <CardContent className="flex w-full flex-row gap-4">
@@ -51,7 +53,9 @@ export const ShowDomains = ({ applicationId }: Props) => {
To access to the application is required to set at least 1 To access to the application is required to set at least 1
domain domain
</span> </span>
<AddDomain applicationId={applicationId}>Add Domain</AddDomain> <AddDomain applicationId={applicationId}>
<GlobeIcon className="size-4" /> Add Domain
</AddDomain>
</div> </div>
) : ( ) : (
<div className="flex w-full flex-col gap-4"> <div className="flex w-full flex-col gap-4">
@@ -75,8 +79,10 @@ export const ShowDomains = ({ applicationId }: Props) => {
<Button variant="outline" disabled> <Button variant="outline" disabled>
{item.https ? "HTTPS" : "HTTP"} {item.https ? "HTTPS" : "HTTP"}
</Button> </Button>
<UpdateDomain domainId={item.domainId} /> <div className="flex flex-row gap-1">
<DeleteDomain domainId={item.domainId} /> <UpdateDomain domainId={item.domainId} />
<DeleteDomain domainId={item.domainId} />
</div>
</div> </div>
); );
})} })}

View File

@@ -115,8 +115,8 @@ export const UpdateDomain = ({ domainId }: Props) => {
return ( return (
<Dialog> <Dialog>
<DialogTrigger className="" asChild> <DialogTrigger className="" asChild>
<Button> <Button variant="ghost">
<PenBoxIcon className="size-4" /> <PenBoxIcon className="size-4 text-muted-foreground" />
</Button> </Button>
</DialogTrigger> </DialogTrigger>
<DialogContent className="max-h-screen overflow-y-auto sm:max-w-2xl"> <DialogContent className="max-h-screen overflow-y-auto sm:max-w-2xl">

View File

@@ -90,7 +90,7 @@ export const UpdateApplication = ({ applicationId }: Props) => {
<Dialog> <Dialog>
<DialogTrigger asChild> <DialogTrigger asChild>
<Button variant="ghost"> <Button variant="ghost">
<SquarePen className="size-4" /> <SquarePen className="size-4 text-muted-foreground" />
</Button> </Button>
</DialogTrigger> </DialogTrigger>
<DialogContent className="max-h-screen overflow-y-auto sm:max-w-lg"> <DialogContent className="max-h-screen overflow-y-auto sm:max-w-lg">

View File

@@ -20,7 +20,7 @@ import {
import { Input } from "@/components/ui/input"; import { Input } from "@/components/ui/input";
import { api } from "@/utils/api"; import { api } from "@/utils/api";
import { zodResolver } from "@hookform/resolvers/zod"; import { zodResolver } from "@hookform/resolvers/zod";
import { Pencil, CheckIcon, ChevronsUpDown } from "lucide-react"; import { Pencil, CheckIcon, ChevronsUpDown, PenBoxIcon } from "lucide-react";
import { useEffect } from "react"; import { useEffect } from "react";
import { useForm } from "react-hook-form"; import { useForm } from "react-hook-form";
import { toast } from "sonner"; import { toast } from "sonner";
@@ -115,7 +115,7 @@ export const UpdateBackup = ({ backupId, refetch }: Props) => {
<Dialog> <Dialog>
<DialogTrigger asChild> <DialogTrigger asChild>
<Button variant="ghost"> <Button variant="ghost">
<Pencil className="size-4 text-muted-foreground" /> <PenBoxIcon className="size-4 text-muted-foreground" />
</Button> </Button>
</DialogTrigger> </DialogTrigger>
<DialogContent className="max-h-screen overflow-y-auto sm:max-w-lg"> <DialogContent className="max-h-screen overflow-y-auto sm:max-w-lg">

View File

@@ -1,13 +1,13 @@
import { import {
AlertDialog, AlertDialog,
AlertDialogAction, AlertDialogAction,
AlertDialogCancel, AlertDialogCancel,
AlertDialogContent, AlertDialogContent,
AlertDialogDescription, AlertDialogDescription,
AlertDialogFooter, AlertDialogFooter,
AlertDialogHeader, AlertDialogHeader,
AlertDialogTitle, AlertDialogTitle,
AlertDialogTrigger, AlertDialogTrigger,
} from "@/components/ui/alert-dialog"; } from "@/components/ui/alert-dialog";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { api } from "@/utils/api"; import { api } from "@/utils/api";
@@ -16,47 +16,47 @@ import { useRouter } from "next/router";
import { toast } from "sonner"; import { toast } from "sonner";
interface Props { interface Props {
mariadbId: string; mariadbId: string;
} }
export const DeleteMariadb = ({ mariadbId }: Props) => { export const DeleteMariadb = ({ mariadbId }: Props) => {
const { mutateAsync, isLoading } = api.mariadb.remove.useMutation(); const { mutateAsync, isLoading } = api.mariadb.remove.useMutation();
const { push } = useRouter(); const { push } = useRouter();
return ( return (
<AlertDialog> <AlertDialog>
<AlertDialogTrigger asChild> <AlertDialogTrigger asChild>
<Button variant="destructive" isLoading={isLoading}> <Button variant="ghost" isLoading={isLoading}>
<TrashIcon className="size-4 " /> <TrashIcon className="size-4 text-muted-foreground " />
</Button> </Button>
</AlertDialogTrigger> </AlertDialogTrigger>
<AlertDialogContent> <AlertDialogContent>
<AlertDialogHeader> <AlertDialogHeader>
<AlertDialogTitle>Are you absolutely sure?</AlertDialogTitle> <AlertDialogTitle>Are you absolutely sure?</AlertDialogTitle>
<AlertDialogDescription> <AlertDialogDescription>
This action cannot be undone. This will permanently delete the This action cannot be undone. This will permanently delete the
database database
</AlertDialogDescription> </AlertDialogDescription>
</AlertDialogHeader> </AlertDialogHeader>
<AlertDialogFooter> <AlertDialogFooter>
<AlertDialogCancel>Cancel</AlertDialogCancel> <AlertDialogCancel>Cancel</AlertDialogCancel>
<AlertDialogAction <AlertDialogAction
onClick={async () => { onClick={async () => {
await mutateAsync({ await mutateAsync({
mariadbId, mariadbId,
}) })
.then((data) => { .then((data) => {
push(`/dashboard/project/${data?.projectId}`); push(`/dashboard/project/${data?.projectId}`);
toast.success("Database delete succesfully"); toast.success("Database delete succesfully");
}) })
.catch(() => { .catch(() => {
toast.error("Error to delete the database"); toast.error("Error to delete the database");
}); });
}} }}
> >
Confirm Confirm
</AlertDialogAction> </AlertDialogAction>
</AlertDialogFooter> </AlertDialogFooter>
</AlertDialogContent> </AlertDialogContent>
</AlertDialog> </AlertDialog>
); );
}; };

View File

@@ -90,7 +90,7 @@ export const UpdateMariadb = ({ mariadbId }: Props) => {
<Dialog> <Dialog>
<DialogTrigger asChild> <DialogTrigger asChild>
<Button variant="ghost"> <Button variant="ghost">
<SquarePen className="size-4" /> <SquarePen className="size-4 text-muted-foreground" />
</Button> </Button>
</DialogTrigger> </DialogTrigger>
<DialogContent className="max-h-screen overflow-y-auto sm:max-w-lg"> <DialogContent className="max-h-screen overflow-y-auto sm:max-w-lg">

View File

@@ -1,13 +1,13 @@
import { import {
AlertDialog, AlertDialog,
AlertDialogAction, AlertDialogAction,
AlertDialogCancel, AlertDialogCancel,
AlertDialogContent, AlertDialogContent,
AlertDialogDescription, AlertDialogDescription,
AlertDialogFooter, AlertDialogFooter,
AlertDialogHeader, AlertDialogHeader,
AlertDialogTitle, AlertDialogTitle,
AlertDialogTrigger, AlertDialogTrigger,
} from "@/components/ui/alert-dialog"; } from "@/components/ui/alert-dialog";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { api } from "@/utils/api"; import { api } from "@/utils/api";
@@ -16,47 +16,47 @@ import { useRouter } from "next/router";
import { toast } from "sonner"; import { toast } from "sonner";
interface Props { interface Props {
mongoId: string; mongoId: string;
} }
export const DeleteMongo = ({ mongoId }: Props) => { export const DeleteMongo = ({ mongoId }: Props) => {
const { mutateAsync, isLoading } = api.mongo.remove.useMutation(); const { mutateAsync, isLoading } = api.mongo.remove.useMutation();
const { push } = useRouter(); const { push } = useRouter();
return ( return (
<AlertDialog> <AlertDialog>
<AlertDialogTrigger asChild> <AlertDialogTrigger asChild>
<Button variant="destructive" isLoading={isLoading}> <Button variant="ghost" isLoading={isLoading}>
<TrashIcon className="size-4 " /> <TrashIcon className="size-4 text-muted-foreground " />
</Button> </Button>
</AlertDialogTrigger> </AlertDialogTrigger>
<AlertDialogContent> <AlertDialogContent>
<AlertDialogHeader> <AlertDialogHeader>
<AlertDialogTitle>Are you absolutely sure?</AlertDialogTitle> <AlertDialogTitle>Are you absolutely sure?</AlertDialogTitle>
<AlertDialogDescription> <AlertDialogDescription>
This action cannot be undone. This will permanently delete the This action cannot be undone. This will permanently delete the
database database
</AlertDialogDescription> </AlertDialogDescription>
</AlertDialogHeader> </AlertDialogHeader>
<AlertDialogFooter> <AlertDialogFooter>
<AlertDialogCancel>Cancel</AlertDialogCancel> <AlertDialogCancel>Cancel</AlertDialogCancel>
<AlertDialogAction <AlertDialogAction
onClick={async () => { onClick={async () => {
await mutateAsync({ await mutateAsync({
mongoId, mongoId,
}) })
.then((data) => { .then((data) => {
push(`/dashboard/project/${data?.projectId}`); push(`/dashboard/project/${data?.projectId}`);
toast.success("Database delete succesfully"); toast.success("Database delete succesfully");
}) })
.catch(() => { .catch(() => {
toast.error("Error to delete the database"); toast.error("Error to delete the database");
}); });
}} }}
> >
Confirm Confirm
</AlertDialogAction> </AlertDialogAction>
</AlertDialogFooter> </AlertDialogFooter>
</AlertDialogContent> </AlertDialogContent>
</AlertDialog> </AlertDialog>
); );
}; };

View File

@@ -90,7 +90,7 @@ export const UpdateMongo = ({ mongoId }: Props) => {
<Dialog> <Dialog>
<DialogTrigger asChild> <DialogTrigger asChild>
<Button variant="ghost"> <Button variant="ghost">
<SquarePen className="size-4" /> <SquarePen className="size-4 text-muted-foreground" />
</Button> </Button>
</DialogTrigger> </DialogTrigger>
<DialogContent className="max-h-screen overflow-y-auto sm:max-w-lg"> <DialogContent className="max-h-screen overflow-y-auto sm:max-w-lg">

View File

@@ -1,13 +1,13 @@
import { import {
AlertDialog, AlertDialog,
AlertDialogAction, AlertDialogAction,
AlertDialogCancel, AlertDialogCancel,
AlertDialogContent, AlertDialogContent,
AlertDialogDescription, AlertDialogDescription,
AlertDialogFooter, AlertDialogFooter,
AlertDialogHeader, AlertDialogHeader,
AlertDialogTitle, AlertDialogTitle,
AlertDialogTrigger, AlertDialogTrigger,
} from "@/components/ui/alert-dialog"; } from "@/components/ui/alert-dialog";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { api } from "@/utils/api"; import { api } from "@/utils/api";
@@ -16,47 +16,47 @@ import { useRouter } from "next/router";
import { toast } from "sonner"; import { toast } from "sonner";
interface Props { interface Props {
mysqlId: string; mysqlId: string;
} }
export const DeleteMysql = ({ mysqlId }: Props) => { export const DeleteMysql = ({ mysqlId }: Props) => {
const { mutateAsync, isLoading } = api.mysql.remove.useMutation(); const { mutateAsync, isLoading } = api.mysql.remove.useMutation();
const { push } = useRouter(); const { push } = useRouter();
return ( return (
<AlertDialog> <AlertDialog>
<AlertDialogTrigger asChild> <AlertDialogTrigger asChild>
<Button variant="destructive" isLoading={isLoading}> <Button variant="ghost" isLoading={isLoading}>
<TrashIcon className="size-4 " /> <TrashIcon className="size-4 text-muted-foreground " />
</Button> </Button>
</AlertDialogTrigger> </AlertDialogTrigger>
<AlertDialogContent> <AlertDialogContent>
<AlertDialogHeader> <AlertDialogHeader>
<AlertDialogTitle>Are you absolutely sure?</AlertDialogTitle> <AlertDialogTitle>Are you absolutely sure?</AlertDialogTitle>
<AlertDialogDescription> <AlertDialogDescription>
This action cannot be undone. This will permanently delete the This action cannot be undone. This will permanently delete the
database database
</AlertDialogDescription> </AlertDialogDescription>
</AlertDialogHeader> </AlertDialogHeader>
<AlertDialogFooter> <AlertDialogFooter>
<AlertDialogCancel>Cancel</AlertDialogCancel> <AlertDialogCancel>Cancel</AlertDialogCancel>
<AlertDialogAction <AlertDialogAction
onClick={async () => { onClick={async () => {
await mutateAsync({ await mutateAsync({
mysqlId, mysqlId,
}) })
.then((data) => { .then((data) => {
push(`/dashboard/project/${data?.projectId}`); push(`/dashboard/project/${data?.projectId}`);
toast.success("Database delete succesfully"); toast.success("Database delete succesfully");
}) })
.catch(() => { .catch(() => {
toast.error("Error to delete the database"); toast.error("Error to delete the database");
}); });
}} }}
> >
Confirm Confirm
</AlertDialogAction> </AlertDialogAction>
</AlertDialogFooter> </AlertDialogFooter>
</AlertDialogContent> </AlertDialogContent>
</AlertDialog> </AlertDialog>
); );
}; };

View File

@@ -90,7 +90,7 @@ export const UpdateMysql = ({ mysqlId }: Props) => {
<Dialog> <Dialog>
<DialogTrigger asChild> <DialogTrigger asChild>
<Button variant="ghost"> <Button variant="ghost">
<SquarePen className="size-4" /> <SquarePen className="size-4 text-muted-foreground" />
</Button> </Button>
</DialogTrigger> </DialogTrigger>
<DialogContent className="max-h-screen overflow-y-auto sm:max-w-lg"> <DialogContent className="max-h-screen overflow-y-auto sm:max-w-lg">

View File

@@ -1,13 +1,13 @@
import { import {
AlertDialog, AlertDialog,
AlertDialogAction, AlertDialogAction,
AlertDialogCancel, AlertDialogCancel,
AlertDialogContent, AlertDialogContent,
AlertDialogDescription, AlertDialogDescription,
AlertDialogFooter, AlertDialogFooter,
AlertDialogHeader, AlertDialogHeader,
AlertDialogTitle, AlertDialogTitle,
AlertDialogTrigger, AlertDialogTrigger,
} from "@/components/ui/alert-dialog"; } from "@/components/ui/alert-dialog";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { api } from "@/utils/api"; import { api } from "@/utils/api";
@@ -16,47 +16,47 @@ import { useRouter } from "next/router";
import { toast } from "sonner"; import { toast } from "sonner";
interface Props { interface Props {
postgresId: string; postgresId: string;
} }
export const DeletePostgres = ({ postgresId }: Props) => { export const DeletePostgres = ({ postgresId }: Props) => {
const { mutateAsync, isLoading } = api.postgres.remove.useMutation(); const { mutateAsync, isLoading } = api.postgres.remove.useMutation();
const { push } = useRouter(); const { push } = useRouter();
return ( return (
<AlertDialog> <AlertDialog>
<AlertDialogTrigger asChild> <AlertDialogTrigger asChild>
<Button variant="destructive" isLoading={isLoading}> <Button variant="ghost" isLoading={isLoading}>
<TrashIcon className="size-4 " /> <TrashIcon className="size-4 text-muted-foreground " />
</Button> </Button>
</AlertDialogTrigger> </AlertDialogTrigger>
<AlertDialogContent> <AlertDialogContent>
<AlertDialogHeader> <AlertDialogHeader>
<AlertDialogTitle>Are you absolutely sure?</AlertDialogTitle> <AlertDialogTitle>Are you absolutely sure?</AlertDialogTitle>
<AlertDialogDescription> <AlertDialogDescription>
This action cannot be undone. This will permanently delete the This action cannot be undone. This will permanently delete the
database database
</AlertDialogDescription> </AlertDialogDescription>
</AlertDialogHeader> </AlertDialogHeader>
<AlertDialogFooter> <AlertDialogFooter>
<AlertDialogCancel>Cancel</AlertDialogCancel> <AlertDialogCancel>Cancel</AlertDialogCancel>
<AlertDialogAction <AlertDialogAction
onClick={async () => { onClick={async () => {
await mutateAsync({ await mutateAsync({
postgresId, postgresId,
}) })
.then((data) => { .then((data) => {
push(`/dashboard/project/${data?.projectId}`); push(`/dashboard/project/${data?.projectId}`);
toast.success("Database delete succesfully"); toast.success("Database delete succesfully");
}) })
.catch(() => { .catch(() => {
toast.error("Error to delete the database"); toast.error("Error to delete the database");
}); });
}} }}
> >
Confirm Confirm
</AlertDialogAction> </AlertDialogAction>
</AlertDialogFooter> </AlertDialogFooter>
</AlertDialogContent> </AlertDialogContent>
</AlertDialog> </AlertDialog>
); );
}; };

View File

@@ -90,7 +90,7 @@ export const UpdatePostgres = ({ postgresId }: Props) => {
<Dialog> <Dialog>
<DialogTrigger asChild> <DialogTrigger asChild>
<Button variant="ghost"> <Button variant="ghost">
<SquarePen className="size-4" /> <SquarePen className="size-4 text-muted-foreground" />
</Button> </Button>
</DialogTrigger> </DialogTrigger>
<DialogContent className="max-h-screen overflow-y-auto sm:max-w-lg"> <DialogContent className="max-h-screen overflow-y-auto sm:max-w-lg">

View File

@@ -1,13 +1,13 @@
import { import {
AlertDialog, AlertDialog,
AlertDialogAction, AlertDialogAction,
AlertDialogCancel, AlertDialogCancel,
AlertDialogContent, AlertDialogContent,
AlertDialogDescription, AlertDialogDescription,
AlertDialogFooter, AlertDialogFooter,
AlertDialogHeader, AlertDialogHeader,
AlertDialogTitle, AlertDialogTitle,
AlertDialogTrigger, AlertDialogTrigger,
} from "@/components/ui/alert-dialog"; } from "@/components/ui/alert-dialog";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { api } from "@/utils/api"; import { api } from "@/utils/api";
@@ -16,47 +16,47 @@ import { useRouter } from "next/router";
import { toast } from "sonner"; import { toast } from "sonner";
interface Props { interface Props {
redisId: string; redisId: string;
} }
export const DeleteRedis = ({ redisId }: Props) => { export const DeleteRedis = ({ redisId }: Props) => {
const { mutateAsync, isLoading } = api.redis.remove.useMutation(); const { mutateAsync, isLoading } = api.redis.remove.useMutation();
const { push } = useRouter(); const { push } = useRouter();
return ( return (
<AlertDialog> <AlertDialog>
<AlertDialogTrigger asChild> <AlertDialogTrigger asChild>
<Button variant="destructive" isLoading={isLoading}> <Button variant="ghost" isLoading={isLoading}>
<TrashIcon className="size-4 " /> <TrashIcon className="size-4 text-muted-foreground " />
</Button> </Button>
</AlertDialogTrigger> </AlertDialogTrigger>
<AlertDialogContent> <AlertDialogContent>
<AlertDialogHeader> <AlertDialogHeader>
<AlertDialogTitle>Are you absolutely sure?</AlertDialogTitle> <AlertDialogTitle>Are you absolutely sure?</AlertDialogTitle>
<AlertDialogDescription> <AlertDialogDescription>
This action cannot be undone. This will permanently delete the This action cannot be undone. This will permanently delete the
database database
</AlertDialogDescription> </AlertDialogDescription>
</AlertDialogHeader> </AlertDialogHeader>
<AlertDialogFooter> <AlertDialogFooter>
<AlertDialogCancel>Cancel</AlertDialogCancel> <AlertDialogCancel>Cancel</AlertDialogCancel>
<AlertDialogAction <AlertDialogAction
onClick={async () => { onClick={async () => {
await mutateAsync({ await mutateAsync({
redisId, redisId,
}) })
.then((data) => { .then((data) => {
push(`/dashboard/project/${data?.projectId}`); push(`/dashboard/project/${data?.projectId}`);
toast.success("Database delete succesfully"); toast.success("Database delete succesfully");
}) })
.catch(() => { .catch(() => {
toast.error("Error to delete the database"); toast.error("Error to delete the database");
}); });
}} }}
> >
Confirm Confirm
</AlertDialogAction> </AlertDialogAction>
</AlertDialogFooter> </AlertDialogFooter>
</AlertDialogContent> </AlertDialogContent>
</AlertDialog> </AlertDialog>
); );
}; };

View File

@@ -90,7 +90,7 @@ export const UpdateRedis = ({ redisId }: Props) => {
<Dialog> <Dialog>
<DialogTrigger asChild> <DialogTrigger asChild>
<Button variant="ghost"> <Button variant="ghost">
<SquarePen className="size-4" /> <SquarePen className="size-4 text-muted-foreground" />
</Button> </Button>
</DialogTrigger> </DialogTrigger>
<DialogContent className="max-h-screen overflow-y-auto sm:max-w-lg"> <DialogContent className="max-h-screen overflow-y-auto sm:max-w-lg">

View File

@@ -33,21 +33,23 @@ export const ShowCertificates = () => {
</div> </div>
) : ( ) : (
<div className="flex flex-col gap-6"> <div className="flex flex-col gap-6">
{data?.map((destination, index) => ( <div className="grid grid-cols-1 sm:grid-cols-2 gap-6">
<div {data?.map((destination, index) => (
key={destination.certificateId} <div
className="flex items-center justify-between" key={destination.certificateId}
> className="flex items-center justify-between border p-4 rounded-lg"
<span className="text-sm text-muted-foreground"> >
{index + 1}. {destination.name} <span className="text-sm text-muted-foreground">
</span> {index + 1}. {destination.name}
<div className="flex flex-row gap-3"> </span>
<DeleteCertificate <div className="flex flex-row gap-3">
certificateId={destination.certificateId} <DeleteCertificate
/> certificateId={destination.certificateId}
/>
</div>
</div> </div>
</div> ))}
))} </div>
<div> <div>
<AddCertificate /> <AddCertificate />
</div> </div>

View File

@@ -0,0 +1,66 @@
import {
Dialog,
DialogContent,
DialogDescription,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "@/components/ui/dialog";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
import { Button } from "@/components/ui/button";
import { ExternalLink, PlusIcon } from "lucide-react";
import { AddWorker } from "./workers/add-worker";
import { AddManager } from "./manager/add-manager";
import Link from "next/link";
export const AddNode = () => {
return (
<Dialog>
<DialogTrigger asChild>
<Button className="w-full cursor-pointer space-x-3">
<PlusIcon className="h-4 w-4" />
Add Node
</Button>
</DialogTrigger>
<DialogContent className="max-h-screen overflow-y-auto sm:max-w-4xl">
<DialogHeader>
<DialogTitle>Add Node</DialogTitle>
<DialogDescription className="flex flex-col gap-2">
Follow the steps to add a new node to your cluster, before you start
using this feature, you need to understand how docker swarm works.{" "}
<Link
href="https://docs.docker.com/engine/swarm/"
target="_blank"
className="text-primary flex flex-row gap-2 items-center"
>
Docker Swarm
<ExternalLink className="h-4 w-4" />
</Link>
<Link
href="https://docs.docker.com/engine/swarm/how-swarm-mode-works/nodes/"
target="_blank"
className="text-primary flex flex-row gap-2 items-center"
>
Architecture
<ExternalLink className="h-4 w-4" />
</Link>
</DialogDescription>
</DialogHeader>
<div className="flex flex-col gap-2">
<Tabs defaultValue="worker">
<TabsList>
<TabsTrigger value="worker">Worker</TabsTrigger>
<TabsTrigger value="manager">Manager</TabsTrigger>
</TabsList>
<TabsContent value="worker" className="pt-4">
<AddWorker />
</TabsContent>
<TabsContent value="manager" className="pt-4">
<AddManager />
</TabsContent>
</Tabs>
</div>
</DialogContent>
</Dialog>
);
};

View File

@@ -0,0 +1,63 @@
import {
DialogDescription,
DialogHeader,
DialogTitle,
} from "@/components/ui/dialog";
import { api } from "@/utils/api";
import { CardContent } from "@/components/ui/card";
import { CopyIcon } from "lucide-react";
import copy from "copy-to-clipboard";
import { toast } from "sonner";
export const AddManager = () => {
const { data } = api.cluster.addManager.useQuery();
return (
<>
<div>
<CardContent className="sm:max-w-4xl max-h-screen overflow-y-auto flex flex-col gap-4 px-0">
<DialogHeader>
<DialogTitle>Add a new manager</DialogTitle>
<DialogDescription>Add a new manager</DialogDescription>
</DialogHeader>
<div className="flex flex-col gap-2.5 text-sm">
<span>1. Go to your new server and run the following command</span>
<span className="bg-muted rounded-lg p-2 flex justify-between">
curl https://get.docker.com | sh -s -- --version 24.0
<button
type="button"
className="self-center"
onClick={() => {
copy("curl https://get.docker.com | sh -s -- --version 24.0");
toast.success("Copied to clipboard");
}}
>
<CopyIcon className="h-4 w-4 cursor-pointer" />
</button>
</span>
</div>
<div className="flex flex-col gap-2.5 text-sm">
<span>
2. Run the following command to add the node(manager) to your
cluster
</span>
<span className="bg-muted rounded-lg p-2 flex">
{data}
<button
type="button"
className="self-start"
onClick={() => {
copy(data || "");
toast.success("Copied to clipboard");
}}
>
<CopyIcon className="h-4 w-4 cursor-pointer" />
</button>
</span>
</div>
</CardContent>
</div>
</>
);
};

View File

@@ -0,0 +1,43 @@
import {
Dialog,
DialogContent,
DialogDescription,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "@/components/ui/dialog";
import { DropdownMenuItem } from "@/components/ui/dropdown-menu";
interface Props {
data: unknown;
}
export const ShowNodeData = ({ data }: Props) => {
return (
<Dialog>
<DialogTrigger asChild>
<DropdownMenuItem
className="w-full cursor-pointer"
onSelect={(e) => e.preventDefault()}
>
View Config
</DropdownMenuItem>
</DialogTrigger>
<DialogContent className={"sm:max-w-5xl overflow-y-auto max-h-screen"}>
<DialogHeader>
<DialogTitle>Node Config</DialogTitle>
<DialogDescription>
See in detail the metadata of this node
</DialogDescription>
</DialogHeader>
<div className="text-wrap rounded-lg border p-4 text-sm sm:max-w-[59rem] bg-card">
<code>
<pre className="whitespace-pre-wrap break-words">
{JSON.stringify(data, null, 2)}
</pre>
</code>
</div>
</DialogContent>
</Dialog>
);
};

View File

@@ -0,0 +1,162 @@
import React from "react";
import {
Card,
CardContent,
CardDescription,
CardHeader,
CardTitle,
} from "@/components/ui/card";
import { api } from "@/utils/api";
import { DateTooltip } from "@/components/shared/date-tooltip";
import { Badge } from "@/components/ui/badge";
import { DeleteWorker } from "./workers/delete-worker";
import {
Table,
TableBody,
TableCaption,
TableCell,
TableHead,
TableHeader,
TableRow,
} from "@/components/ui/table";
import { HelpCircle, LockIcon, MoreHorizontal } from "lucide-react";
import { Button } from "@/components/ui/button";
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuLabel,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";
import { ShowNodeData } from "./show-node-data";
import { AddNode } from "./add-node";
import {
Tooltip,
TooltipContent,
TooltipProvider,
TooltipTrigger,
} from "@/components/ui/tooltip";
export const ShowNodes = () => {
const { data, isLoading } = api.cluster.getNodes.useQuery();
const { data: registry } = api.registry.all.useQuery();
const haveAtLeastOneRegistry = !!(registry && registry?.length > 0);
return (
<Card className="bg-transparent h-full">
<CardHeader className="flex flex-row gap-2 justify-between w-full items-center flex-wrap">
<div className="flex flex-col gap-2">
<CardTitle className="text-xl">Cluster</CardTitle>
<CardDescription>Add nodes to your cluster</CardDescription>
</div>
{haveAtLeastOneRegistry && (
<div className="flex flex-row gap-2">
<AddNode />
</div>
)}
</CardHeader>
<CardContent className="flex flex-col gap-4">
{haveAtLeastOneRegistry ? (
<div className="grid md:grid-cols-1 gap-4">
{isLoading && <div>Loading...</div>}
<Table>
<TableCaption>A list of your managers / workers.</TableCaption>
<TableHeader>
<TableRow>
<TableHead className="w-[100px]">Hostname</TableHead>
<TableHead className="text-right">Status</TableHead>
<TableHead className="text-right">Role</TableHead>
<TableHead className="text-right">Availability</TableHead>
<TableHead className="text-right">Engine Version</TableHead>
<TableHead className="text-right">Created</TableHead>
<TableHead className="text-right">Actions</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{data?.map((node) => {
const isManager = node.Spec.Role === "manager";
return (
<TableRow key={node.ID}>
<TableCell className="w-[100px]">
{node.Description.Hostname}
</TableCell>
<TableCell className="text-right">
{node.Status.State}
</TableCell>
<TableCell className="text-right">
<Badge variant={isManager ? "default" : "secondary"}>
{node?.Spec?.Role}
</Badge>
</TableCell>
<TableCell className="text-right">
{node.Spec.Availability}
</TableCell>
<TableCell className="text-right">
{node?.Description.Engine.EngineVersion}
</TableCell>
<TableCell className="text-right">
<DateTooltip date={node.CreatedAt} className="text-sm">
Created{" "}
</DateTooltip>
</TableCell>
<TableCell className="text-right flex justify-end">
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="ghost" className="h-8 w-8 p-0">
<span className="sr-only">Open menu</span>
<MoreHorizontal className="h-4 w-4" />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
<DropdownMenuLabel>Actions</DropdownMenuLabel>
<ShowNodeData data={node} />
{!node?.ManagerStatus?.Leader && (
<DeleteWorker nodeId={node.ID} />
)}
</DropdownMenuContent>
</DropdownMenu>
</TableCell>
</TableRow>
);
})}
</TableBody>
</Table>
</div>
) : (
<div className="flex flex-col items-center gap-3">
<LockIcon className="size-8 text-muted-foreground" />
<div className="flex flex-row gap-2">
<span className="text-base text-muted-foreground ">
To add nodes to your cluster, you need to configure at least one
registry.
</span>
<TooltipProvider delayDuration={0}>
<Tooltip>
<TooltipTrigger className="self-center">
<HelpCircle className="size-5 text-muted-foreground " />
</TooltipTrigger>
<TooltipContent>
Nodes need a registry to pull images from.
</TooltipContent>
</Tooltip>
</TooltipProvider>
</div>
<ul className="list-disc list-inside text-sm text-muted-foreground border p-4 rounded-lg flex flex-col gap-1.5 mt-2.5">
<li>
<strong>Docker Registry:</strong> Use custom registries like
Docker Hub, DigitalOcean Registry, etc.
</li>
<li>
<strong>Self-Hosted Docker Registry:</strong> Automatically set
up a local registry to store all images.
</li>
</ul>
</div>
)}
</CardContent>
</Card>
);
};

View File

@@ -0,0 +1,61 @@
import { CardContent } from "@/components/ui/card";
import {
DialogDescription,
DialogHeader,
DialogTitle,
} from "@/components/ui/dialog";
import { api } from "@/utils/api";
import copy from "copy-to-clipboard";
import { CopyIcon } from "lucide-react";
import { toast } from "sonner";
export const AddWorker = () => {
const { data } = api.cluster.addWorker.useQuery();
return (
<div>
<CardContent className="sm:max-w-4xl max-h-screen overflow-y-auto flex flex-col gap-4 px-0">
<DialogHeader>
<DialogTitle>Add a new worker</DialogTitle>
<DialogDescription>Add a new worker</DialogDescription>
</DialogHeader>
<div className="flex flex-col gap-2.5 text-sm">
<span>1. Go to your new server and run the following command</span>
<span className="bg-muted rounded-lg p-2 flex justify-between">
curl https://get.docker.com | sh -s -- --version 24.0
<button
type="button"
className="self-center"
onClick={() => {
copy("curl https://get.docker.com | sh -s -- --version 24.0");
toast.success("Copied to clipboard");
}}
>
<CopyIcon className="h-4 w-4 cursor-pointer" />
</button>
</span>
</div>
<div className="flex flex-col gap-2.5 text-sm">
<span>
2. Run the following command to add the node(worker) to your cluster
</span>
<span className="bg-muted rounded-lg p-2 flex">
{data}
<button
type="button"
className="self-start"
onClick={() => {
copy(data || "");
toast.success("Copied to clipboard");
}}
>
<CopyIcon className="h-4 w-4 cursor-pointer" />
</button>
</span>
</div>
</CardContent>
</div>
);
};

View File

@@ -0,0 +1,62 @@
import React from "react";
import {
AlertDialog,
AlertDialogAction,
AlertDialogCancel,
AlertDialogContent,
AlertDialogDescription,
AlertDialogFooter,
AlertDialogHeader,
AlertDialogTitle,
AlertDialogTrigger,
} from "@/components/ui/alert-dialog";
import { Button } from "@/components/ui/button";
import { api } from "@/utils/api";
import { TrashIcon } from "lucide-react";
import { toast } from "sonner";
import { DropdownMenuItem } from "@/components/ui/dropdown-menu";
interface Props {
nodeId: string;
}
export const DeleteWorker = ({ nodeId }: Props) => {
const { mutateAsync, isLoading } = api.cluster.removeWorker.useMutation();
const utils = api.useUtils();
return (
<AlertDialog>
<AlertDialogTrigger asChild>
<DropdownMenuItem onSelect={(e) => e.preventDefault()}>
Delete
</DropdownMenuItem>
</AlertDialogTrigger>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle>Are you absolutely sure?</AlertDialogTitle>
<AlertDialogDescription>
This action cannot be undone. This will permanently delete the
worker.
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel>Cancel</AlertDialogCancel>
<AlertDialogAction
onClick={async () => {
await mutateAsync({
nodeId,
})
.then(async () => {
utils.cluster.getNodes.invalidate();
toast.success("Worker deleted succesfully");
})
.catch(() => {
toast.error("Error to delete the worker");
});
}}
>
Confirm
</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
);
};

View File

@@ -0,0 +1,251 @@
import { Button } from "@/components/ui/button";
import {
Dialog,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "@/components/ui/dialog";
import {
Form,
FormControl,
FormField,
FormItem,
FormLabel,
FormMessage,
} from "@/components/ui/form";
import { Input } from "@/components/ui/input";
import { api } from "@/utils/api";
import { zodResolver } from "@hookform/resolvers/zod";
import { AlertTriangle, Container } from "lucide-react";
import { useRouter } from "next/router";
import { useEffect, useState } from "react";
import { useForm } from "react-hook-form";
import { toast } from "sonner";
import { z } from "zod";
const AddRegistrySchema = z.object({
registryName: z.string().min(1, {
message: "Registry name is required",
}),
username: z.string().min(1, {
message: "Username is required",
}),
password: z.string().min(1, {
message: "Password is required",
}),
registryUrl: z.string().min(1, {
message: "Registry URL is required",
}),
imagePrefix: z.string(),
});
type AddRegistry = z.infer<typeof AddRegistrySchema>;
export const AddRegistry = () => {
const utils = api.useUtils();
const [isOpen, setIsOpen] = useState(false);
const { mutateAsync, error, isError } = api.registry.create.useMutation();
const { mutateAsync: testRegistry, isLoading } =
api.registry.testRegistry.useMutation();
const router = useRouter();
const form = useForm<AddRegistry>({
defaultValues: {
username: "",
password: "",
registryUrl: "",
imagePrefix: "",
registryName: "",
},
resolver: zodResolver(AddRegistrySchema),
});
const password = form.watch("password");
const username = form.watch("username");
const registryUrl = form.watch("registryUrl");
const registryName = form.watch("registryName");
const imagePrefix = form.watch("imagePrefix");
useEffect(() => {
form.reset({
username: "",
password: "",
registryUrl: "",
imagePrefix: "",
});
}, [form, form.reset, form.formState.isSubmitSuccessful]);
const onSubmit = async (data: AddRegistry) => {
await mutateAsync({
password: data.password,
registryName: data.registryName,
username: data.username,
registryUrl: data.registryUrl,
registryType: "cloud",
imagePrefix: data.imagePrefix,
})
.then(async (data) => {
await utils.registry.all.invalidate();
toast.success("Registry added");
setIsOpen(false);
})
.catch(() => {
toast.error("Error to add a registry");
});
};
return (
<Dialog open={isOpen} onOpenChange={setIsOpen}>
<DialogTrigger asChild>
<Button>
<Container className="h-4 w-4" />
Create Registry
</Button>
</DialogTrigger>
<DialogContent className="sm:m:max-w-lg ">
<DialogHeader>
<DialogTitle>Add a external registry</DialogTitle>
<DialogDescription>
Fill the next fields to add a external registry.
</DialogDescription>
</DialogHeader>
{isError && (
<div className="flex flex-row gap-4 rounded-lg bg-red-50 p-2 dark:bg-red-950">
<AlertTriangle className="text-red-600 dark:text-red-400" />
<span className="text-sm text-red-600 dark:text-red-400">
{error?.message}
</span>
</div>
)}
<Form {...form}>
<form
onSubmit={form.handleSubmit(onSubmit)}
className="grid w-full gap-4"
>
<div className="flex flex-col gap-4">
<FormField
control={form.control}
name="registryName"
render={({ field }) => (
<FormItem>
<FormLabel>Registry Name</FormLabel>
<FormControl>
<Input placeholder="Registry Name" {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
</div>
<div className="flex flex-col gap-4">
<FormField
control={form.control}
name="username"
render={({ field }) => (
<FormItem>
<FormLabel>Username</FormLabel>
<FormControl>
<Input placeholder="Username" {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
</div>
<div className="flex flex-col gap-4">
<FormField
control={form.control}
name="password"
render={({ field }) => (
<FormItem>
<FormLabel>Password</FormLabel>
<FormControl>
<Input
placeholder="Password"
{...field}
type="password"
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
</div>
<div className="flex flex-col gap-4">
<FormField
control={form.control}
name="imagePrefix"
render={({ field }) => (
<FormItem>
<FormLabel>Image Prefix</FormLabel>
<FormControl>
<Input {...field} placeholder="Image Prefix" />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
</div>
<div className="flex flex-col gap-4">
<FormField
control={form.control}
name="registryUrl"
render={({ field }) => (
<FormItem>
<FormLabel>Registry URL</FormLabel>
<FormControl>
<Input
placeholder="aws_account_id.dkr.ecr.us-west-2.amazonaws.com"
{...field}
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
</div>
<DialogFooter className="flex flex-row w-full sm:justify-between gap-4 flex-wrap">
<Button
type="button"
variant={"secondary"}
isLoading={isLoading}
onClick={async () => {
await testRegistry({
username: username,
password: password,
registryUrl: registryUrl,
registryName: registryName,
registryType: "cloud",
imagePrefix: imagePrefix,
})
.then((data) => {
if (data) {
toast.success("Registry Tested Successfully");
} else {
toast.error("Registry Test Failed");
}
})
.catch(() => {
toast.error("Error to test the registry");
});
}}
>
Test Registry
</Button>
<Button isLoading={form.formState.isSubmitting} type="submit">
Create
</Button>
</DialogFooter>
</form>
</Form>
</DialogContent>
</Dialog>
);
};

View File

@@ -0,0 +1,181 @@
import { Button } from "@/components/ui/button";
import {
Dialog,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "@/components/ui/dialog";
import {
Form,
FormControl,
FormDescription,
FormField,
FormItem,
FormLabel,
FormMessage,
} from "@/components/ui/form";
import { Input } from "@/components/ui/input";
import { api } from "@/utils/api";
import { zodResolver } from "@hookform/resolvers/zod";
import { AlertTriangle, Container } from "lucide-react";
import { useRouter } from "next/router";
import { useEffect, useState } from "react";
import { useForm } from "react-hook-form";
import { toast } from "sonner";
import { z } from "zod";
const AddRegistrySchema = z.object({
username: z
.string()
.min(1, {
message: "Username is required",
})
.regex(/^[a-zA-Z0-9]+$/, {
message: "Username can only contain letters and numbers",
}),
password: z.string().min(1, {
message: "Password is required",
}),
registryUrl: z.string().min(1, {
message: "Registry URL is required",
}),
});
type AddRegistry = z.infer<typeof AddRegistrySchema>;
export const AddSelfHostedRegistry = () => {
const utils = api.useUtils();
const [isOpen, setIsOpen] = useState(false);
const { mutateAsync, error, isError, isLoading } =
api.registry.enableSelfHostedRegistry.useMutation();
const router = useRouter();
const form = useForm<AddRegistry>({
defaultValues: {
username: "",
password: "",
registryUrl: "",
},
resolver: zodResolver(AddRegistrySchema),
});
useEffect(() => {
form.reset({
registryUrl: "",
username: "",
password: "",
});
}, [form, form.reset, form.formState.isSubmitSuccessful]);
const onSubmit = async (data: AddRegistry) => {
await mutateAsync({
registryUrl: data.registryUrl,
username: data.username,
password: data.password,
})
.then(async (data) => {
await utils.registry.all.invalidate();
toast.success("Self Hosted Registry Created");
setIsOpen(false);
})
.catch(() => {
toast.error("Error to create a self hosted registry");
});
};
return (
<Dialog open={isOpen} onOpenChange={setIsOpen}>
<DialogTrigger asChild>
<Button>
<Container className="h-4 w-4" />
Enable Self Hosted Registry
</Button>
</DialogTrigger>
<DialogContent className="sm:m:max-w-lg ">
<DialogHeader>
<DialogTitle>Add a self hosted registry</DialogTitle>
<DialogDescription>
Fill the next fields to add a self hosted registry.
</DialogDescription>
</DialogHeader>
{isError && (
<div className="flex flex-row gap-4 rounded-lg bg-red-50 p-2 dark:bg-red-950">
<AlertTriangle className="text-red-600 dark:text-red-400" />
<span className="text-sm text-red-600 dark:text-red-400">
{error?.message}
</span>
</div>
)}
<Form {...form}>
<form
onSubmit={form.handleSubmit(onSubmit)}
className="grid w-full gap-4"
>
<div className="flex flex-col gap-4">
<FormField
control={form.control}
name="username"
render={({ field }) => (
<FormItem>
<FormLabel>Username</FormLabel>
<FormControl>
<Input placeholder="Username" {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
</div>
<div className="flex flex-col gap-4">
<FormField
control={form.control}
name="password"
render={({ field }) => (
<FormItem>
<FormLabel>Password</FormLabel>
<FormControl>
<Input
placeholder="Password"
{...field}
type="password"
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
</div>
<div className="flex flex-col gap-4">
<FormField
control={form.control}
name="registryUrl"
render={({ field }) => (
<FormItem>
<FormLabel>Registry URL</FormLabel>
<FormControl>
<Input placeholder="registry.dokploy.com" {...field} />
</FormControl>
<FormDescription>
Point a DNS record to the VPS IP address.
</FormDescription>
<FormMessage />
</FormItem>
)}
/>
</div>
<DialogFooter>
<Button isLoading={isLoading} type="submit">
Create
</Button>
</DialogFooter>
</form>
</Form>
</DialogContent>
</Dialog>
);
};

View File

@@ -0,0 +1,61 @@
import React from "react";
import {
AlertDialog,
AlertDialogAction,
AlertDialogCancel,
AlertDialogContent,
AlertDialogDescription,
AlertDialogFooter,
AlertDialogHeader,
AlertDialogTitle,
AlertDialogTrigger,
} from "@/components/ui/alert-dialog";
import { Button } from "@/components/ui/button";
import { api } from "@/utils/api";
import { TrashIcon } from "lucide-react";
import { toast } from "sonner";
interface Props {
registryId: string;
}
export const DeleteRegistry = ({ registryId }: Props) => {
const { mutateAsync, isLoading } = api.registry.remove.useMutation();
const utils = api.useUtils();
return (
<AlertDialog>
<AlertDialogTrigger asChild>
<Button variant="ghost" isLoading={isLoading}>
<TrashIcon className="size-4 text-muted-foreground " />
</Button>
</AlertDialogTrigger>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle>Are you absolutely sure?</AlertDialogTitle>
<AlertDialogDescription>
This action cannot be undone. This will permanently delete the
registry.
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel>Cancel</AlertDialogCancel>
<AlertDialogAction
onClick={async () => {
await mutateAsync({
registryId,
})
.then(async () => {
utils.registry.all.invalidate();
toast.success("Registry deleted");
})
.catch(() => {
toast.error("Error to delete the registry");
});
}}
>
Confirm
</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
);
};

View File

@@ -0,0 +1,76 @@
import {
Card,
CardContent,
CardDescription,
CardHeader,
CardTitle,
} from "@/components/ui/card";
import { api } from "@/utils/api";
import { Server } from "lucide-react";
import { AddRegistry } from "./add-docker-registry";
import { AddSelfHostedRegistry } from "./add-self-docker-registry";
import { DeleteRegistry } from "./delete-registry";
import { UpdateDockerRegistry } from "./update-docker-registry";
export const ShowRegistry = () => {
const { data } = api.registry.all.useQuery();
const haveSelfHostedRegistry = data?.some(
(registry) => registry.registryType === "selfHosted",
);
return (
<div className="h-full">
<Card className="bg-transparent h-full">
<CardHeader className="flex flex-row gap-2 flex-wrap justify-between w-full items-center">
<div className="flex flex-col gap-2">
<CardTitle className="text-xl">Registry</CardTitle>
<CardDescription>Add registry to your application.</CardDescription>
</div>
<div className="flex flex-row gap-2">
{data && data?.length > 0 && (
<>
{!haveSelfHostedRegistry && <AddSelfHostedRegistry />}
<AddRegistry />
</>
)}
</div>
</CardHeader>
<CardContent className="space-y-2 pt-4 h-full">
{data?.length === 0 ? (
<div className="flex flex-col items-center gap-3">
<Server className="size-8 self-center text-muted-foreground" />
<span className="text-base text-muted-foreground">
To create a cluster is required to set a registry.
</span>
<div className="flex flex-row gap-2">
<AddSelfHostedRegistry />
<AddRegistry />
</div>
</div>
) : (
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-1 gap-6">
{data?.map((registry, index) => (
<div
key={registry.registryId}
className="flex items-center justify-between border p-4 rounded-lg"
>
<span className="text-sm text-muted-foreground">
{index + 1}. {registry.registryName}
</span>
<div className="flex flex-row gap-1">
<UpdateDockerRegistry registryId={registry.registryId} />
<DeleteRegistry registryId={registry.registryId} />
</div>
</div>
))}
</div>
)}
</CardContent>
</Card>
</div>
);
};

View File

@@ -0,0 +1,275 @@
import { Button } from "@/components/ui/button";
import {
Dialog,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "@/components/ui/dialog";
import {
Form,
FormControl,
FormField,
FormItem,
FormLabel,
FormMessage,
} from "@/components/ui/form";
import { Input } from "@/components/ui/input";
import { cn } from "@/lib/utils";
import { api } from "@/utils/api";
import { zodResolver } from "@hookform/resolvers/zod";
import { AlertTriangle, PenBoxIcon } from "lucide-react";
import { useEffect } from "react";
import { useForm } from "react-hook-form";
import { toast } from "sonner";
import { z } from "zod";
const updateRegistry = z.object({
registryName: z.string().min(1, {
message: "Registry name is required",
}),
username: z.string().min(1, {
message: "Username is required",
}),
password: z.string(),
registryUrl: z.string().min(1, {
message: "Registry URL is required",
}),
imagePrefix: z.string(),
});
type UpdateRegistry = z.infer<typeof updateRegistry>;
interface Props {
registryId: string;
}
export const UpdateDockerRegistry = ({ registryId }: Props) => {
const utils = api.useUtils();
const { mutateAsync: testRegistry, isLoading } =
api.registry.testRegistry.useMutation();
const { data, refetch } = api.registry.one.useQuery(
{
registryId,
},
{
enabled: !!registryId,
},
);
const isCloud = data?.registryType === "cloud";
const { mutateAsync, isError, error } = api.registry.update.useMutation();
const form = useForm<UpdateRegistry>({
defaultValues: {
imagePrefix: "",
registryName: "",
username: "",
password: "",
registryUrl: "",
},
resolver: zodResolver(updateRegistry),
});
const password = form.watch("password");
const username = form.watch("username");
const registryUrl = form.watch("registryUrl");
const registryName = form.watch("registryName");
const imagePrefix = form.watch("imagePrefix");
useEffect(() => {
if (data) {
form.reset({
imagePrefix: data.imagePrefix || "",
registryName: data.registryName || "",
username: data.username || "",
password: "",
registryUrl: data.registryUrl || "",
});
}
}, [form, form.reset, data]);
const onSubmit = async (data: UpdateRegistry) => {
await mutateAsync({
registryId,
...(data.password ? { password: data.password } : {}),
registryName: data.registryName,
username: data.username,
registryUrl: data.registryUrl,
imagePrefix: data.imagePrefix,
})
.then(async (data) => {
toast.success("Registry Updated");
await refetch();
await utils.registry.all.invalidate();
})
.catch(() => {
toast.error("Error to update the registry");
});
};
return (
<Dialog>
<DialogTrigger className="" asChild>
<Button variant="ghost">
<PenBoxIcon className="size-4 text-muted-foreground" />
</Button>
</DialogTrigger>
<DialogContent className="max-h-screen overflow-y-auto sm:max-w-2xl">
<DialogHeader>
<DialogTitle>Registry</DialogTitle>
<DialogDescription>Update the registry information</DialogDescription>
</DialogHeader>
{isError && (
<div className="flex flex-row gap-4 rounded-lg bg-red-50 p-2 dark:bg-red-950">
<AlertTriangle className="text-red-600 dark:text-red-400" />
<span className="text-sm text-red-600 dark:text-red-400">
{error?.message}
</span>
</div>
)}
<Form {...form}>
<form
id="hook-form"
onSubmit={form.handleSubmit(onSubmit)}
className="grid w-full gap-8 "
>
<div className="flex flex-col gap-4">
<div className="flex flex-col gap-2">
<FormField
control={form.control}
name="registryName"
render={({ field }) => (
<FormItem>
<FormLabel>Registry Name</FormLabel>
<FormControl>
<Input placeholder="Registry Name" {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="username"
render={({ field }) => (
<FormItem>
<FormLabel>Username</FormLabel>
<FormControl>
<Input placeholder="Username" {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="password"
render={({ field }) => (
<FormItem>
<FormLabel>Password</FormLabel>
<FormControl>
<Input
placeholder="Password"
{...field}
type="password"
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
{isCloud && (
<FormField
control={form.control}
name="imagePrefix"
render={({ field }) => (
<FormItem>
<FormLabel>Image Prefix</FormLabel>
<FormControl>
<Input {...field} placeholder="Image Prefix" />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
)}
<FormField
control={form.control}
name="registryUrl"
render={({ field }) => (
<FormItem>
<FormLabel>Registry URL</FormLabel>
<FormControl>
<Input
placeholder="https://aws_account_id.dkr.ecr.us-west-2.amazonaws.com"
{...field}
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
</div>
</div>
</form>
<DialogFooter
className={cn(
isCloud ? "sm:justify-between " : "",
"flex flex-row w-full gap-4 flex-wrap",
)}
>
{isCloud && (
<Button
type="button"
variant={"secondary"}
isLoading={isLoading}
onClick={async () => {
await testRegistry({
username: username,
password: password,
registryUrl: registryUrl,
registryName: registryName,
registryType: "cloud",
imagePrefix: imagePrefix,
})
.then((data) => {
if (data) {
toast.success("Registry Tested Successfully");
} else {
toast.error("Registry Test Failed");
}
})
.catch(() => {
toast.error("Error to test the registry");
});
}}
>
Test Registry
</Button>
)}
<Button
isLoading={form.formState.isSubmitting}
form="hook-form"
type="submit"
>
Update
</Button>
</DialogFooter>
</Form>
</DialogContent>
</Dialog>
);
};

View File

@@ -34,16 +34,16 @@ export const ShowDestinations = () => {
<AddDestination /> <AddDestination />
</div> </div>
) : ( ) : (
<div className="flex flex-col gap-6"> <div className="flex flex-col gap-4">
{data?.map((destination, index) => ( {data?.map((destination, index) => (
<div <div
key={destination.destinationId} key={destination.destinationId}
className="flex items-center justify-between" className="flex items-center justify-between border p-3.5 rounded-lg"
> >
<span className="text-sm text-muted-foreground"> <span className="text-sm text-muted-foreground">
{index + 1}. {destination.name} {index + 1}. {destination.name}
</span> </span>
<div className="flex flex-row gap-3"> <div className="flex flex-row gap-1">
<UpdateDestination <UpdateDestination
destinationId={destination.destinationId} destinationId={destination.destinationId}
/> />

View File

@@ -87,10 +87,10 @@ export const GithubSetup = () => {
{haveGithubConfigured ? ( {haveGithubConfigured ? (
<div className="flex flex-col gap-2"> <div className="flex flex-col gap-2">
<div className="flex items-center gap-4"> <div className="flex items-center gap-4">
<span className="text-muted-foreground"> <span className="text-muted-foreground text-sm">
Github account configured succesfully. Github account configured succesfully.
</span> </span>
<BadgeCheck className="size-5 text-green-700" /> <BadgeCheck className="size-4 text-green-700" />
</div> </div>
<div className="flex items-end"> <div className="flex items-end">
<RemoveGithubApp /> <RemoveGithubApp />
@@ -100,10 +100,10 @@ export const GithubSetup = () => {
<> <>
{data?.githubAppName ? ( {data?.githubAppName ? (
<div className="flex w-fit flex-col gap-4"> <div className="flex w-fit flex-col gap-4">
<span className="text-muted-foreground text-sm"> <span className="text-muted-foreground">
Youve successfully created a GitHub app named You've successfully created a github app named{" "}
{data.githubAppName}! The next step is to install this app in <strong>{data.githubAppName}</strong>! The next step is to
your GitHub account. install this app in your GitHub account.
</span> </span>
<div className="flex flex-row gap-4"> <div className="flex flex-row gap-4">
@@ -121,12 +121,12 @@ export const GithubSetup = () => {
) : ( ) : (
<div> <div>
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<span className="text-muted-foreground text-sm"> <p className="text-muted-foreground text-sm">
To integrate your GitHub account with our services, youll To integrate your GitHub account with our services, you'll
need to create and install a GitHub app. This process is need to create and install a GitHub app. This process is
straightforward and only takes a few minutes. Click the straightforward and only takes a few minutes. Click the
button below to get started. button below to get started.
</span> </p>
</div> </div>
<div className="mt-4 flex flex-col gap-4"> <div className="mt-4 flex flex-col gap-4">

View File

@@ -23,11 +23,11 @@ import { extractServices } from "@/pages/dashboard/project/[projectId]";
import { api } from "@/utils/api"; import { api } from "@/utils/api";
import { AlertBlock } from "@/components/shared/alert-block"; import { AlertBlock } from "@/components/shared/alert-block";
import { zodResolver } from "@hookform/resolvers/zod"; import { zodResolver } from "@hookform/resolvers/zod";
import { ListTodo } from "lucide-react";
import { useEffect } from "react"; import { useEffect } from "react";
import { useForm } from "react-hook-form"; import { useForm } from "react-hook-form";
import { toast } from "sonner"; import { toast } from "sonner";
import { z } from "zod"; import { z } from "zod";
import { DropdownMenuItem } from "@/components/ui/dropdown-menu";
const addPermissions = z.object({ const addPermissions = z.object({
accesedProjects: z.array(z.string()).optional(), accesedProjects: z.array(z.string()).optional(),
@@ -107,9 +107,12 @@ export const AddUserPermissions = ({ userId }: Props) => {
return ( return (
<Dialog> <Dialog>
<DialogTrigger className="" asChild> <DialogTrigger className="" asChild>
<Button variant="ghost"> <DropdownMenuItem
<ListTodo className="size-4 text-muted-foreground " /> className="w-full cursor-pointer"
</Button> onSelect={(e) => e.preventDefault()}
>
Add Permissions
</DropdownMenuItem>
</DialogTrigger> </DialogTrigger>
<DialogContent className="max-h-[85vh] overflow-y-auto sm:max-w-4xl"> <DialogContent className="max-h-[85vh] overflow-y-auto sm:max-w-4xl">
<DialogHeader> <DialogHeader>

View File

@@ -25,6 +25,7 @@ import { useEffect } from "react";
import { useForm } from "react-hook-form"; import { useForm } from "react-hook-form";
import { toast } from "sonner"; import { toast } from "sonner";
import { z } from "zod"; import { z } from "zod";
import { PlusIcon } from "lucide-react";
const addUser = z.object({ const addUser = z.object({
email: z email: z
@@ -66,7 +67,9 @@ export const AddUser = () => {
return ( return (
<Dialog> <Dialog>
<DialogTrigger className="" asChild> <DialogTrigger className="" asChild>
<Button>Add User</Button> <Button>
<PlusIcon className="h-4 w-4" /> Add User
</Button>
</DialogTrigger> </DialogTrigger>
<DialogContent className="max-h-screen overflow-y-auto sm:max-w-2xl"> <DialogContent className="max-h-screen overflow-y-auto sm:max-w-2xl">
<DialogHeader> <DialogHeader>

View File

@@ -14,6 +14,7 @@ import { Button } from "@/components/ui/button";
import { api } from "@/utils/api"; import { api } from "@/utils/api";
import { TrashIcon } from "lucide-react"; import { TrashIcon } from "lucide-react";
import { toast } from "sonner"; import { toast } from "sonner";
import { DropdownMenuItem } from "@/components/ui/dropdown-menu";
interface Props { interface Props {
authId: string; authId: string;
@@ -24,9 +25,12 @@ export const DeleteUser = ({ authId }: Props) => {
return ( return (
<AlertDialog> <AlertDialog>
<AlertDialogTrigger asChild> <AlertDialogTrigger asChild>
<Button variant="ghost" isLoading={isLoading}> <DropdownMenuItem
<TrashIcon className="size-4 text-muted-foreground " /> className="w-full cursor-pointer text-red-500 hover:!text-red-600"
</Button> onSelect={(e) => e.preventDefault()}
>
Delete User
</DropdownMenuItem>
</AlertDialogTrigger> </AlertDialogTrigger>
<AlertDialogContent> <AlertDialogContent>
<AlertDialogHeader> <AlertDialogHeader>

View File

@@ -6,7 +6,7 @@ import {
CardTitle, CardTitle,
} from "@/components/ui/card"; } from "@/components/ui/card";
import { api } from "@/utils/api"; import { api } from "@/utils/api";
import { CopyIcon, Users } from "lucide-react"; import { MoreHorizontal, Users } from "lucide-react";
import { AddUser } from "./add-user"; import { AddUser } from "./add-user";
import { DeleteUser } from "./delete-user"; import { DeleteUser } from "./delete-user";
import { format } from "date-fns"; import { format } from "date-fns";
@@ -14,7 +14,24 @@ import { useEffect, useState } from "react";
import { AddUserPermissions } from "./add-permissions"; import { AddUserPermissions } from "./add-permissions";
import copy from "copy-to-clipboard"; import copy from "copy-to-clipboard";
import { toast } from "sonner"; import { toast } from "sonner";
import { UpdateUser } from "./update-user"; import {
Table,
TableBody,
TableCaption,
TableCell,
TableHead,
TableHeader,
TableRow,
} from "@/components/ui/table";
import { Badge } from "@/components/ui/badge";
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuLabel,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";
import { Button } from "@/components/ui/button";
export const ShowUsers = () => { export const ShowUsers = () => {
const { data } = api.user.all.useQuery(); const { data } = api.user.all.useQuery();
@@ -25,82 +42,109 @@ export const ShowUsers = () => {
return ( return (
<div className="h-full col-span-2"> <div className="h-full col-span-2">
<Card className="bg-transparent h-full border-none"> <Card className="bg-transparent h-full ">
<CardHeader> <CardHeader className="flex flex-row gap-2 justify-between w-full flex-wrap">
<CardTitle className="text-xl">Users</CardTitle> <div className="flex flex-col gap-2">
<CardDescription>Add, manage and delete users.</CardDescription> <CardTitle className="text-xl">Users</CardTitle>
<CardDescription>Add, manage and delete users.</CardDescription>
</div>
{data && data.length > 0 && (
<div className="flex flex-col gap-3 items-end">
<AddUser />
</div>
)}
</CardHeader> </CardHeader>
<CardContent className="space-y-2 h-full"> <CardContent className="space-y-2 h-full">
{data?.length === 0 ? ( {data?.length === 0 ? (
<div className="flex flex-col items-center gap-3"> <div className="flex flex-col items-center gap-3">
<Users className="size-8 self-center text-muted-foreground" /> <Users className="size-8 self-center text-muted-foreground" />
<span className="text-base text-muted-foreground"> <span className="text-base text-muted-foreground">
To create a user is required to add To create a user, you need to add:
</span> </span>
<AddUser /> <AddUser />
</div> </div>
) : ( ) : (
<div className="flex flex-col gap-6"> <div className="flex flex-col gap-6">
{data?.map((user) => { <Table>
return ( <TableCaption>See all users</TableCaption>
<div <TableHeader>
key={user.userId} <TableRow>
className="flex gap-2 flex-col justify-start border p-4 rounded-lg" <TableHead className="w-[100px]">Email</TableHead>
> <TableHead className="text-center">Status</TableHead>
<span className="text-sm text-foreground"> <TableHead className="text-center">2FA</TableHead>
{user.auth.email} <TableHead className="text-center">Expiration</TableHead>
</span> <TableHead className="text-right">Actions</TableHead>
{!user.isRegistered && ( </TableRow>
<span className="text-sm text-muted-foreground"> </TableHeader>
Expire In{" "} <TableBody>
{format(new Date(user.expirationDate), "PPpp")} {data?.map((user) => {
</span> return (
)} <TableRow key={user.userId}>
<TableCell className="w-[100px]">
<span className="text-sm text-muted-foreground"> {user.auth.email}
{user.isRegistered ? "Registered" : "Not Registered"} </TableCell>
</span> <TableCell className="text-center">
{user.auth.is2FAEnabled && ( <Badge
<span className="text-sm text-muted-foreground"> variant={
{user.auth.is2FAEnabled user.isRegistered ? "default" : "secondary"
? "2FA Enabled" }
: "2FA Not Enabled"}
</span>
)}
<div className="flex flex-wrap flex-row gap-3">
{!user.isRegistered && (
<div className="overflow-x-auto flex flex-row gap-4 items-center">
<div className="overflow-x-auto">
<span className="text-sm text-muted-foreground ">
{`${url}/invitation?token=${user.token}`}
</span>
</div>
<button
type="button"
// className="absolute right-2 top-2"
onClick={() => {
copy(`${url}/invitation?token=${user.token}`);
toast.success("Invitation Copied to clipboard");
}}
> >
<CopyIcon className="size-4 text-muted-foreground" /> {user.isRegistered
</button> ? "Registered"
</div> : "Not Registered"}
)} </Badge>
</TableCell>
<TableCell className="text-center">
{user.auth.is2FAEnabled
? "2FA Enabled"
: "2FA Not Enabled"}
</TableCell>
<TableCell className="text-right">
<span className="text-sm text-muted-foreground">
{format(new Date(user.expirationDate), "PPpp")}
</span>
</TableCell>
{user.isRegistered && ( <TableCell className="text-right flex justify-end">
<AddUserPermissions userId={user.userId} /> <DropdownMenu>
)} <DropdownMenuTrigger asChild>
{user.isRegistered && <UpdateUser authId={user.authId} />} <Button variant="ghost" className="h-8 w-8 p-0">
<DeleteUser authId={user.authId} /> <span className="sr-only">Open menu</span>
</div> <MoreHorizontal className="h-4 w-4" />
</div> </Button>
); </DropdownMenuTrigger>
})} <DropdownMenuContent align="end">
<div className="flex flex-col justify-end gap-3 w-full items-end"> <DropdownMenuLabel>Actions</DropdownMenuLabel>
<AddUser /> {!user.isRegistered && (
</div> <DropdownMenuItem
className="w-full cursor-pointer"
onSelect={(e) => {
copy(
`${origin}/invitation?token=${user.token}`,
);
toast.success(
"Invitation Copied to clipboard",
);
}}
>
Copy Invitation
</DropdownMenuItem>
)}
{user.isRegistered && (
<AddUserPermissions userId={user.userId} />
)}
<DeleteUser authId={user.authId} />
</DropdownMenuContent>
</DropdownMenu>
</TableCell>
</TableRow>
);
})}
</TableBody>
</Table>
</div> </div>
)} )}
</CardContent> </CardContent>

View File

@@ -27,10 +27,9 @@ import { DockerTerminalModal } from "./web-server/docker-terminal-modal";
import { ShowMainTraefikConfig } from "./web-server/show-main-traefik-config"; import { ShowMainTraefikConfig } from "./web-server/show-main-traefik-config";
import { ShowServerTraefikConfig } from "./web-server/show-server-traefik-config"; import { ShowServerTraefikConfig } from "./web-server/show-server-traefik-config";
import { ShowServerMiddlewareConfig } from "./web-server/show-server-middleware-config"; import { ShowServerMiddlewareConfig } from "./web-server/show-server-middleware-config";
import { UpdateWebServer } from "./web-server/update-webserver"; import { UpdateServer } from "./web-server/update-server";
export const WebServer = () => { export const WebServer = () => {
const [fetchAfterFirstRender, setFetchAfterFirstRender] = useState(false);
const { data, refetch } = api.admin.one.useQuery(); const { data, refetch } = api.admin.one.useQuery();
const { mutateAsync: reloadServer, isLoading } = const { mutateAsync: reloadServer, isLoading } =
api.settings.reloadServer.useMutation(); api.settings.reloadServer.useMutation();
@@ -61,13 +60,6 @@ export const WebServer = () => {
const { mutateAsync: updateDockerCleanup } = const { mutateAsync: updateDockerCleanup } =
api.settings.updateDockerCleanup.useMutation(); api.settings.updateDockerCleanup.useMutation();
const { data: query } = api.settings.checkAndUpdateImage.useQuery(void 0, {
enabled: fetchAfterFirstRender,
});
useEffect(() => {
setFetchAfterFirstRender(true);
}, []);
return ( return (
<Card className="rounded-lg w-full bg-transparent"> <Card className="rounded-lg w-full bg-transparent">
@@ -279,7 +271,7 @@ export const WebServer = () => {
</DropdownMenuContent> </DropdownMenuContent>
</DropdownMenu> </DropdownMenu>
{query ? <UpdateWebServer /> : null} <UpdateServer />
</div> </div>
<div className="flex items-center flex-wrap justify-between gap-4"> <div className="flex items-center flex-wrap justify-between gap-4">

View File

@@ -0,0 +1,98 @@
import { Button } from "@/components/ui/button";
import {
Dialog,
DialogContent,
DialogDescription,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "@/components/ui/dialog";
import { api } from "@/utils/api";
import { AlertBlock } from "@/components/shared/alert-block";
import { RefreshCcw } from "lucide-react";
import { useState } from "react";
import { toast } from "sonner";
import Link from "next/link";
import { UpdateWebServer } from "./update-webserver";
export const UpdateServer = () => {
const [isUpdateAvailable, setIsUpdateAvailable] = useState<null | boolean>(
null,
);
const { mutateAsync: checkAndUpdateImage, isLoading } =
api.settings.checkAndUpdateImage.useMutation();
const [isOpen, setIsOpen] = useState(false);
return (
<Dialog open={isOpen} onOpenChange={setIsOpen}>
<DialogTrigger asChild>
<Button variant="secondary">
<RefreshCcw className="h-4 w-4" />
Updates
</Button>
</DialogTrigger>
<DialogContent className="sm:m:max-w-lg ">
<DialogHeader>
<DialogTitle>Web Server Update</DialogTitle>
<DialogDescription>
Check new releases and update your dokploy
</DialogDescription>
</DialogHeader>
<div className="flex flex-col gap-4">
<span className="text-sm text-muted-foreground">
We suggest to update your dokploy to the latest version only if you:
</span>
<ul className="list-disc list-inside text-sm text-muted-foreground">
<li>Want to try the latest features</li>
<li>Some bug that is blocking to use some features</li>
</ul>
<AlertBlock type="info">
Please we recommend to see the latest version to see if there are
any breaking changes before updating. Go to{" "}
<Link
href="https://github.com/Dokploy/dokploy/releases"
target="_blank"
className="text-foreground"
>
Dokploy Releases
</Link>{" "}
to check the latest version.
</AlertBlock>
<div className="w-full flex flex-col gap-4">
{isUpdateAvailable === false && (
<div className="flex flex-col items-center gap-3">
<RefreshCcw className="size-6 self-center text-muted-foreground" />
<span className="text-sm text-muted-foreground">
You are using the latest version
</span>
</div>
)}
{isUpdateAvailable ? (
<UpdateWebServer />
) : (
<Button
className="w-full"
onClick={async () => {
await checkAndUpdateImage()
.then(async (e) => {
setIsUpdateAvailable(e);
})
.catch(() => {
setIsUpdateAvailable(false);
toast.error("Error to check updates");
});
toast.success("Check updates");
}}
isLoading={isLoading}
>
Check updates
</Button>
)}
</div>
</div>
</DialogContent>
</Dialog>
);
};

View File

@@ -19,7 +19,11 @@ export const UpdateWebServer = () => {
return ( return (
<AlertDialog> <AlertDialog>
<AlertDialogTrigger asChild> <AlertDialogTrigger asChild>
<Button className="relative" variant="secondary" isLoading={isLoading}> <Button
className="relative w-full"
variant="secondary"
isLoading={isLoading}
>
<span className="absolute -right-1 -top-2 flex h-3 w-3"> <span className="absolute -right-1 -top-2 flex h-3 w-3">
<span className="animate-ping absolute inline-flex h-full w-full rounded-full bg-green-400 opacity-75" /> <span className="animate-ping absolute inline-flex h-full w-full rounded-full bg-green-400 opacity-75" />
<span className="relative inline-flex rounded-full h-3 w-3 bg-green-500" /> <span className="relative inline-flex rounded-full h-3 w-3 bg-green-500" />

View File

@@ -59,6 +59,12 @@ export const SettingsLayout = ({ children }: Props) => {
icon: Users, icon: Users,
href: "/dashboard/settings/users", href: "/dashboard/settings/users",
}, },
{
title: "Cluster",
label: "",
icon: Server,
href: "/dashboard/settings/cluster",
},
] ]
: []), : []),
]} ]}
@@ -75,6 +81,7 @@ import {
Activity, Activity,
Database, Database,
Route, Route,
Server,
ShieldCheck, ShieldCheck,
User2, User2,
Users, Users,

View File

@@ -4,19 +4,26 @@ import {
TooltipProvider, TooltipProvider,
TooltipTrigger, TooltipTrigger,
} from "@/components/ui/tooltip"; } from "@/components/ui/tooltip";
import { cn } from "@/lib/utils";
import { format, formatDistanceToNow } from "date-fns"; import { format, formatDistanceToNow } from "date-fns";
interface Props { interface Props {
date: string; date: string;
children?: React.ReactNode; children?: React.ReactNode;
className?: string;
} }
export const DateTooltip = ({ date, children }: Props) => { export const DateTooltip = ({ date, children, className }: Props) => {
return ( return (
<TooltipProvider delayDuration={0}> <TooltipProvider delayDuration={0}>
<Tooltip> <Tooltip>
<TooltipTrigger> <TooltipTrigger>
<span className="flex items-center text-muted-foreground text-left"> <span
className={cn(
"flex items-center text-muted-foreground text-left",
className,
)}
>
{children}{" "} {children}{" "}
{formatDistanceToNow(new Date(date), { {formatDistanceToNow(new Date(date), {
addSuffix: true, addSuffix: true,

View File

@@ -147,7 +147,6 @@ const FormMessage = React.forwardRef<
>(({ className, children, ...props }, ref) => { >(({ className, children, ...props }, ref) => {
const { error, formMessageId } = useFormField(); const { error, formMessageId } = useFormField();
const body = error ? String(error?.message) : children; const body = error ? String(error?.message) : children;
if (!body) { if (!body) {
return null; return null;
} }

View File

@@ -46,7 +46,7 @@ else
fi fi
docker swarm leave --force 2>/dev/null docker swarm leave --force 2>/dev/null
docker swarm init --advertise-addr 127.0.0.1 --listen-addr 0.0.0.0; docker swarm init;
echo "Swarm initialized" echo "Swarm initialized"

View File

@@ -0,0 +1,22 @@
DO $$ BEGIN
CREATE TYPE "RegistryType" AS ENUM('selfHosted', 'cloud');
EXCEPTION
WHEN duplicate_object THEN null;
END $$;
--> statement-breakpoint
CREATE TABLE IF NOT EXISTS "registry" (
"registryId" text PRIMARY KEY NOT NULL,
"registryName" text NOT NULL,
"username" text NOT NULL,
"password" text NOT NULL,
"registryUrl" text NOT NULL,
"createdAt" text NOT NULL,
"selfHosted" "RegistryType" DEFAULT 'cloud' NOT NULL,
"adminId" text NOT NULL
);
--> statement-breakpoint
DO $$ BEGIN
ALTER TABLE "registry" ADD CONSTRAINT "registry_adminId_admin_adminId_fk" FOREIGN KEY ("adminId") REFERENCES "admin"("adminId") ON DELETE cascade ON UPDATE no action;
EXCEPTION
WHEN duplicate_object THEN null;
END $$;

View File

@@ -0,0 +1,6 @@
ALTER TABLE "application" ADD COLUMN "registryId" text;--> statement-breakpoint
DO $$ BEGIN
ALTER TABLE "application" ADD CONSTRAINT "application_registryId_registry_registryId_fk" FOREIGN KEY ("registryId") REFERENCES "public"."registry"("registryId") ON DELETE cascade ON UPDATE no action;
EXCEPTION
WHEN duplicate_object THEN null;
END $$;

View File

@@ -0,0 +1 @@
ALTER TABLE "application" ADD COLUMN "replicas" integer DEFAULT 1;

View File

@@ -0,0 +1,7 @@
ALTER TABLE "application" DROP CONSTRAINT "application_registryId_registry_registryId_fk";
--> statement-breakpoint
DO $$ BEGIN
ALTER TABLE "application" ADD CONSTRAINT "application_registryId_registry_registryId_fk" FOREIGN KEY ("registryId") REFERENCES "public"."registry"("registryId") ON DELETE set null ON UPDATE no action;
EXCEPTION
WHEN duplicate_object THEN null;
END $$;

View File

@@ -0,0 +1 @@
ALTER TABLE "application" ALTER COLUMN "replicas" SET NOT NULL;

View File

@@ -0,0 +1 @@
ALTER TABLE "registry" ADD COLUMN "imagePrefix" text NOT NULL;

View File

@@ -0,0 +1 @@
ALTER TABLE "registry" ALTER COLUMN "imagePrefix" DROP NOT NULL;

View File

@@ -0,0 +1,7 @@
ALTER TABLE "application" ADD COLUMN "healthCheckSwarm" json;--> statement-breakpoint
ALTER TABLE "application" ADD COLUMN "restartPolicySwarm" json;--> statement-breakpoint
ALTER TABLE "application" ADD COLUMN "placementSwarm" json;--> statement-breakpoint
ALTER TABLE "application" ADD COLUMN "updateConfigSwarm" json;--> statement-breakpoint
ALTER TABLE "application" ADD COLUMN "rollbackConfigSwarm" json;--> statement-breakpoint
ALTER TABLE "application" ADD COLUMN "modeSwarm" json;--> statement-breakpoint
ALTER TABLE "application" ADD COLUMN "labelsSwarm" json;

View File

@@ -0,0 +1 @@
ALTER TABLE "application" ADD COLUMN "networkSwarm" json;

View File

@@ -1,10 +1,8 @@
{ {
"id": "c6215051-7cd1-412d-b8df-b50d58acacff", "version": "6",
"prevId": "00000000-0000-0000-0000-000000000000", "dialect": "postgresql",
"version": "5",
"dialect": "pg",
"tables": { "tables": {
"application": { "public.application": {
"name": "application", "name": "application",
"schema": "", "schema": "",
"columns": { "columns": {
@@ -234,29 +232,29 @@
"application_projectId_project_projectId_fk": { "application_projectId_project_projectId_fk": {
"name": "application_projectId_project_projectId_fk", "name": "application_projectId_project_projectId_fk",
"tableFrom": "application", "tableFrom": "application",
"tableTo": "project",
"columnsFrom": [ "columnsFrom": [
"projectId" "projectId"
], ],
"tableTo": "project",
"columnsTo": [ "columnsTo": [
"projectId" "projectId"
], ],
"onDelete": "cascade", "onUpdate": "no action",
"onUpdate": "no action" "onDelete": "cascade"
} }
}, },
"compositePrimaryKeys": {}, "compositePrimaryKeys": {},
"uniqueConstraints": { "uniqueConstraints": {
"application_appName_unique": { "application_appName_unique": {
"name": "application_appName_unique", "name": "application_appName_unique",
"nullsNotDistinct": false,
"columns": [ "columns": [
"appName" "appName"
] ],
"nullsNotDistinct": false
} }
} }
}, },
"postgres": { "public.postgres": {
"name": "postgres", "name": "postgres",
"schema": "", "schema": "",
"columns": { "columns": {
@@ -375,29 +373,29 @@
"postgres_projectId_project_projectId_fk": { "postgres_projectId_project_projectId_fk": {
"name": "postgres_projectId_project_projectId_fk", "name": "postgres_projectId_project_projectId_fk",
"tableFrom": "postgres", "tableFrom": "postgres",
"tableTo": "project",
"columnsFrom": [ "columnsFrom": [
"projectId" "projectId"
], ],
"tableTo": "project",
"columnsTo": [ "columnsTo": [
"projectId" "projectId"
], ],
"onDelete": "cascade", "onUpdate": "no action",
"onUpdate": "no action" "onDelete": "cascade"
} }
}, },
"compositePrimaryKeys": {}, "compositePrimaryKeys": {},
"uniqueConstraints": { "uniqueConstraints": {
"postgres_appName_unique": { "postgres_appName_unique": {
"name": "postgres_appName_unique", "name": "postgres_appName_unique",
"nullsNotDistinct": false,
"columns": [ "columns": [
"appName" "appName"
] ],
"nullsNotDistinct": false
} }
} }
}, },
"user": { "public.user": {
"name": "user", "name": "user",
"schema": "", "schema": "",
"columns": { "columns": {
@@ -499,34 +497,34 @@
"user_adminId_admin_adminId_fk": { "user_adminId_admin_adminId_fk": {
"name": "user_adminId_admin_adminId_fk", "name": "user_adminId_admin_adminId_fk",
"tableFrom": "user", "tableFrom": "user",
"tableTo": "admin",
"columnsFrom": [ "columnsFrom": [
"adminId" "adminId"
], ],
"tableTo": "admin",
"columnsTo": [ "columnsTo": [
"adminId" "adminId"
], ],
"onDelete": "cascade", "onUpdate": "no action",
"onUpdate": "no action" "onDelete": "cascade"
}, },
"user_authId_auth_id_fk": { "user_authId_auth_id_fk": {
"name": "user_authId_auth_id_fk", "name": "user_authId_auth_id_fk",
"tableFrom": "user", "tableFrom": "user",
"tableTo": "auth",
"columnsFrom": [ "columnsFrom": [
"authId" "authId"
], ],
"tableTo": "auth",
"columnsTo": [ "columnsTo": [
"id" "id"
], ],
"onDelete": "cascade", "onUpdate": "no action",
"onUpdate": "no action" "onDelete": "cascade"
} }
}, },
"compositePrimaryKeys": {}, "compositePrimaryKeys": {},
"uniqueConstraints": {} "uniqueConstraints": {}
}, },
"admin": { "public.admin": {
"name": "admin", "name": "admin",
"schema": "", "schema": "",
"columns": { "columns": {
@@ -628,21 +626,21 @@
"admin_authId_auth_id_fk": { "admin_authId_auth_id_fk": {
"name": "admin_authId_auth_id_fk", "name": "admin_authId_auth_id_fk",
"tableFrom": "admin", "tableFrom": "admin",
"tableTo": "auth",
"columnsFrom": [ "columnsFrom": [
"authId" "authId"
], ],
"tableTo": "auth",
"columnsTo": [ "columnsTo": [
"id" "id"
], ],
"onDelete": "cascade", "onUpdate": "no action",
"onUpdate": "no action" "onDelete": "cascade"
} }
}, },
"compositePrimaryKeys": {}, "compositePrimaryKeys": {},
"uniqueConstraints": {} "uniqueConstraints": {}
}, },
"auth": { "public.auth": {
"name": "auth", "name": "auth",
"schema": "", "schema": "",
"columns": { "columns": {
@@ -689,14 +687,14 @@
"uniqueConstraints": { "uniqueConstraints": {
"auth_email_unique": { "auth_email_unique": {
"name": "auth_email_unique", "name": "auth_email_unique",
"nullsNotDistinct": false,
"columns": [ "columns": [
"email" "email"
] ],
"nullsNotDistinct": false
} }
} }
}, },
"project": { "public.project": {
"name": "project", "name": "project",
"schema": "", "schema": "",
"columns": { "columns": {
@@ -736,21 +734,21 @@
"project_adminId_admin_adminId_fk": { "project_adminId_admin_adminId_fk": {
"name": "project_adminId_admin_adminId_fk", "name": "project_adminId_admin_adminId_fk",
"tableFrom": "project", "tableFrom": "project",
"tableTo": "admin",
"columnsFrom": [ "columnsFrom": [
"adminId" "adminId"
], ],
"tableTo": "admin",
"columnsTo": [ "columnsTo": [
"adminId" "adminId"
], ],
"onDelete": "cascade", "onUpdate": "no action",
"onUpdate": "no action" "onDelete": "cascade"
} }
}, },
"compositePrimaryKeys": {}, "compositePrimaryKeys": {},
"uniqueConstraints": {} "uniqueConstraints": {}
}, },
"domain": { "public.domain": {
"name": "domain", "name": "domain",
"schema": "", "schema": "",
"columns": { "columns": {
@@ -818,21 +816,21 @@
"domain_applicationId_application_applicationId_fk": { "domain_applicationId_application_applicationId_fk": {
"name": "domain_applicationId_application_applicationId_fk", "name": "domain_applicationId_application_applicationId_fk",
"tableFrom": "domain", "tableFrom": "domain",
"tableTo": "application",
"columnsFrom": [ "columnsFrom": [
"applicationId" "applicationId"
], ],
"tableTo": "application",
"columnsTo": [ "columnsTo": [
"applicationId" "applicationId"
], ],
"onDelete": "cascade", "onUpdate": "no action",
"onUpdate": "no action" "onDelete": "cascade"
} }
}, },
"compositePrimaryKeys": {}, "compositePrimaryKeys": {},
"uniqueConstraints": {} "uniqueConstraints": {}
}, },
"mariadb": { "public.mariadb": {
"name": "mariadb", "name": "mariadb",
"schema": "", "schema": "",
"columns": { "columns": {
@@ -957,29 +955,29 @@
"mariadb_projectId_project_projectId_fk": { "mariadb_projectId_project_projectId_fk": {
"name": "mariadb_projectId_project_projectId_fk", "name": "mariadb_projectId_project_projectId_fk",
"tableFrom": "mariadb", "tableFrom": "mariadb",
"tableTo": "project",
"columnsFrom": [ "columnsFrom": [
"projectId" "projectId"
], ],
"tableTo": "project",
"columnsTo": [ "columnsTo": [
"projectId" "projectId"
], ],
"onDelete": "cascade", "onUpdate": "no action",
"onUpdate": "no action" "onDelete": "cascade"
} }
}, },
"compositePrimaryKeys": {}, "compositePrimaryKeys": {},
"uniqueConstraints": { "uniqueConstraints": {
"mariadb_appName_unique": { "mariadb_appName_unique": {
"name": "mariadb_appName_unique", "name": "mariadb_appName_unique",
"nullsNotDistinct": false,
"columns": [ "columns": [
"appName" "appName"
] ],
"nullsNotDistinct": false
} }
} }
}, },
"mongo": { "public.mongo": {
"name": "mongo", "name": "mongo",
"schema": "", "schema": "",
"columns": { "columns": {
@@ -1092,29 +1090,29 @@
"mongo_projectId_project_projectId_fk": { "mongo_projectId_project_projectId_fk": {
"name": "mongo_projectId_project_projectId_fk", "name": "mongo_projectId_project_projectId_fk",
"tableFrom": "mongo", "tableFrom": "mongo",
"tableTo": "project",
"columnsFrom": [ "columnsFrom": [
"projectId" "projectId"
], ],
"tableTo": "project",
"columnsTo": [ "columnsTo": [
"projectId" "projectId"
], ],
"onDelete": "cascade", "onUpdate": "no action",
"onUpdate": "no action" "onDelete": "cascade"
} }
}, },
"compositePrimaryKeys": {}, "compositePrimaryKeys": {},
"uniqueConstraints": { "uniqueConstraints": {
"mongo_appName_unique": { "mongo_appName_unique": {
"name": "mongo_appName_unique", "name": "mongo_appName_unique",
"nullsNotDistinct": false,
"columns": [ "columns": [
"appName" "appName"
] ],
"nullsNotDistinct": false
} }
} }
}, },
"mysql": { "public.mysql": {
"name": "mysql", "name": "mysql",
"schema": "", "schema": "",
"columns": { "columns": {
@@ -1239,29 +1237,29 @@
"mysql_projectId_project_projectId_fk": { "mysql_projectId_project_projectId_fk": {
"name": "mysql_projectId_project_projectId_fk", "name": "mysql_projectId_project_projectId_fk",
"tableFrom": "mysql", "tableFrom": "mysql",
"tableTo": "project",
"columnsFrom": [ "columnsFrom": [
"projectId" "projectId"
], ],
"tableTo": "project",
"columnsTo": [ "columnsTo": [
"projectId" "projectId"
], ],
"onDelete": "cascade", "onUpdate": "no action",
"onUpdate": "no action" "onDelete": "cascade"
} }
}, },
"compositePrimaryKeys": {}, "compositePrimaryKeys": {},
"uniqueConstraints": { "uniqueConstraints": {
"mysql_appName_unique": { "mysql_appName_unique": {
"name": "mysql_appName_unique", "name": "mysql_appName_unique",
"nullsNotDistinct": false,
"columns": [ "columns": [
"appName" "appName"
] ],
"nullsNotDistinct": false
} }
} }
}, },
"backup": { "public.backup": {
"name": "backup", "name": "backup",
"schema": "", "schema": "",
"columns": { "columns": {
@@ -1337,73 +1335,73 @@
"backup_destinationId_destination_destinationId_fk": { "backup_destinationId_destination_destinationId_fk": {
"name": "backup_destinationId_destination_destinationId_fk", "name": "backup_destinationId_destination_destinationId_fk",
"tableFrom": "backup", "tableFrom": "backup",
"tableTo": "destination",
"columnsFrom": [ "columnsFrom": [
"destinationId" "destinationId"
], ],
"tableTo": "destination",
"columnsTo": [ "columnsTo": [
"destinationId" "destinationId"
], ],
"onDelete": "cascade", "onUpdate": "no action",
"onUpdate": "no action" "onDelete": "cascade"
}, },
"backup_postgresId_postgres_postgresId_fk": { "backup_postgresId_postgres_postgresId_fk": {
"name": "backup_postgresId_postgres_postgresId_fk", "name": "backup_postgresId_postgres_postgresId_fk",
"tableFrom": "backup", "tableFrom": "backup",
"tableTo": "postgres",
"columnsFrom": [ "columnsFrom": [
"postgresId" "postgresId"
], ],
"tableTo": "postgres",
"columnsTo": [ "columnsTo": [
"postgresId" "postgresId"
], ],
"onDelete": "cascade", "onUpdate": "no action",
"onUpdate": "no action" "onDelete": "cascade"
}, },
"backup_mariadbId_mariadb_mariadbId_fk": { "backup_mariadbId_mariadb_mariadbId_fk": {
"name": "backup_mariadbId_mariadb_mariadbId_fk", "name": "backup_mariadbId_mariadb_mariadbId_fk",
"tableFrom": "backup", "tableFrom": "backup",
"tableTo": "mariadb",
"columnsFrom": [ "columnsFrom": [
"mariadbId" "mariadbId"
], ],
"tableTo": "mariadb",
"columnsTo": [ "columnsTo": [
"mariadbId" "mariadbId"
], ],
"onDelete": "cascade", "onUpdate": "no action",
"onUpdate": "no action" "onDelete": "cascade"
}, },
"backup_mysqlId_mysql_mysqlId_fk": { "backup_mysqlId_mysql_mysqlId_fk": {
"name": "backup_mysqlId_mysql_mysqlId_fk", "name": "backup_mysqlId_mysql_mysqlId_fk",
"tableFrom": "backup", "tableFrom": "backup",
"tableTo": "mysql",
"columnsFrom": [ "columnsFrom": [
"mysqlId" "mysqlId"
], ],
"tableTo": "mysql",
"columnsTo": [ "columnsTo": [
"mysqlId" "mysqlId"
], ],
"onDelete": "cascade", "onUpdate": "no action",
"onUpdate": "no action" "onDelete": "cascade"
}, },
"backup_mongoId_mongo_mongoId_fk": { "backup_mongoId_mongo_mongoId_fk": {
"name": "backup_mongoId_mongo_mongoId_fk", "name": "backup_mongoId_mongo_mongoId_fk",
"tableFrom": "backup", "tableFrom": "backup",
"tableTo": "mongo",
"columnsFrom": [ "columnsFrom": [
"mongoId" "mongoId"
], ],
"tableTo": "mongo",
"columnsTo": [ "columnsTo": [
"mongoId" "mongoId"
], ],
"onDelete": "cascade", "onUpdate": "no action",
"onUpdate": "no action" "onDelete": "cascade"
} }
}, },
"compositePrimaryKeys": {}, "compositePrimaryKeys": {},
"uniqueConstraints": {} "uniqueConstraints": {}
}, },
"destination": { "public.destination": {
"name": "destination", "name": "destination",
"schema": "", "schema": "",
"columns": { "columns": {
@@ -1461,21 +1459,21 @@
"destination_adminId_admin_adminId_fk": { "destination_adminId_admin_adminId_fk": {
"name": "destination_adminId_admin_adminId_fk", "name": "destination_adminId_admin_adminId_fk",
"tableFrom": "destination", "tableFrom": "destination",
"tableTo": "admin",
"columnsFrom": [ "columnsFrom": [
"adminId" "adminId"
], ],
"tableTo": "admin",
"columnsTo": [ "columnsTo": [
"adminId" "adminId"
], ],
"onDelete": "cascade", "onUpdate": "no action",
"onUpdate": "no action" "onDelete": "cascade"
} }
}, },
"compositePrimaryKeys": {}, "compositePrimaryKeys": {},
"uniqueConstraints": {} "uniqueConstraints": {}
}, },
"deployment": { "public.deployment": {
"name": "deployment", "name": "deployment",
"schema": "", "schema": "",
"columns": { "columns": {
@@ -1522,21 +1520,21 @@
"deployment_applicationId_application_applicationId_fk": { "deployment_applicationId_application_applicationId_fk": {
"name": "deployment_applicationId_application_applicationId_fk", "name": "deployment_applicationId_application_applicationId_fk",
"tableFrom": "deployment", "tableFrom": "deployment",
"tableTo": "application",
"columnsFrom": [ "columnsFrom": [
"applicationId" "applicationId"
], ],
"tableTo": "application",
"columnsTo": [ "columnsTo": [
"applicationId" "applicationId"
], ],
"onDelete": "cascade", "onUpdate": "no action",
"onUpdate": "no action" "onDelete": "cascade"
} }
}, },
"compositePrimaryKeys": {}, "compositePrimaryKeys": {},
"uniqueConstraints": {} "uniqueConstraints": {}
}, },
"mount": { "public.mount": {
"name": "mount", "name": "mount",
"schema": "", "schema": "",
"columns": { "columns": {
@@ -1625,86 +1623,86 @@
"mount_applicationId_application_applicationId_fk": { "mount_applicationId_application_applicationId_fk": {
"name": "mount_applicationId_application_applicationId_fk", "name": "mount_applicationId_application_applicationId_fk",
"tableFrom": "mount", "tableFrom": "mount",
"tableTo": "application",
"columnsFrom": [ "columnsFrom": [
"applicationId" "applicationId"
], ],
"tableTo": "application",
"columnsTo": [ "columnsTo": [
"applicationId" "applicationId"
], ],
"onDelete": "cascade", "onUpdate": "no action",
"onUpdate": "no action" "onDelete": "cascade"
}, },
"mount_postgresId_postgres_postgresId_fk": { "mount_postgresId_postgres_postgresId_fk": {
"name": "mount_postgresId_postgres_postgresId_fk", "name": "mount_postgresId_postgres_postgresId_fk",
"tableFrom": "mount", "tableFrom": "mount",
"tableTo": "postgres",
"columnsFrom": [ "columnsFrom": [
"postgresId" "postgresId"
], ],
"tableTo": "postgres",
"columnsTo": [ "columnsTo": [
"postgresId" "postgresId"
], ],
"onDelete": "cascade", "onUpdate": "no action",
"onUpdate": "no action" "onDelete": "cascade"
}, },
"mount_mariadbId_mariadb_mariadbId_fk": { "mount_mariadbId_mariadb_mariadbId_fk": {
"name": "mount_mariadbId_mariadb_mariadbId_fk", "name": "mount_mariadbId_mariadb_mariadbId_fk",
"tableFrom": "mount", "tableFrom": "mount",
"tableTo": "mariadb",
"columnsFrom": [ "columnsFrom": [
"mariadbId" "mariadbId"
], ],
"tableTo": "mariadb",
"columnsTo": [ "columnsTo": [
"mariadbId" "mariadbId"
], ],
"onDelete": "cascade", "onUpdate": "no action",
"onUpdate": "no action" "onDelete": "cascade"
}, },
"mount_mongoId_mongo_mongoId_fk": { "mount_mongoId_mongo_mongoId_fk": {
"name": "mount_mongoId_mongo_mongoId_fk", "name": "mount_mongoId_mongo_mongoId_fk",
"tableFrom": "mount", "tableFrom": "mount",
"tableTo": "mongo",
"columnsFrom": [ "columnsFrom": [
"mongoId" "mongoId"
], ],
"tableTo": "mongo",
"columnsTo": [ "columnsTo": [
"mongoId" "mongoId"
], ],
"onDelete": "cascade", "onUpdate": "no action",
"onUpdate": "no action" "onDelete": "cascade"
}, },
"mount_mysqlId_mysql_mysqlId_fk": { "mount_mysqlId_mysql_mysqlId_fk": {
"name": "mount_mysqlId_mysql_mysqlId_fk", "name": "mount_mysqlId_mysql_mysqlId_fk",
"tableFrom": "mount", "tableFrom": "mount",
"tableTo": "mysql",
"columnsFrom": [ "columnsFrom": [
"mysqlId" "mysqlId"
], ],
"tableTo": "mysql",
"columnsTo": [ "columnsTo": [
"mysqlId" "mysqlId"
], ],
"onDelete": "cascade", "onUpdate": "no action",
"onUpdate": "no action" "onDelete": "cascade"
}, },
"mount_redisId_redis_redisId_fk": { "mount_redisId_redis_redisId_fk": {
"name": "mount_redisId_redis_redisId_fk", "name": "mount_redisId_redis_redisId_fk",
"tableFrom": "mount", "tableFrom": "mount",
"tableTo": "redis",
"columnsFrom": [ "columnsFrom": [
"redisId" "redisId"
], ],
"tableTo": "redis",
"columnsTo": [ "columnsTo": [
"redisId" "redisId"
], ],
"onDelete": "cascade", "onUpdate": "no action",
"onUpdate": "no action" "onDelete": "cascade"
} }
}, },
"compositePrimaryKeys": {}, "compositePrimaryKeys": {},
"uniqueConstraints": {} "uniqueConstraints": {}
}, },
"certificate": { "public.certificate": {
"name": "certificate", "name": "certificate",
"schema": "", "schema": "",
"columns": { "columns": {
@@ -1751,14 +1749,14 @@
"uniqueConstraints": { "uniqueConstraints": {
"certificate_certificatePath_unique": { "certificate_certificatePath_unique": {
"name": "certificate_certificatePath_unique", "name": "certificate_certificatePath_unique",
"nullsNotDistinct": false,
"columns": [ "columns": [
"certificatePath" "certificatePath"
] ],
"nullsNotDistinct": false
} }
} }
}, },
"session": { "public.session": {
"name": "session", "name": "session",
"schema": "", "schema": "",
"columns": { "columns": {
@@ -1786,21 +1784,21 @@
"session_user_id_auth_id_fk": { "session_user_id_auth_id_fk": {
"name": "session_user_id_auth_id_fk", "name": "session_user_id_auth_id_fk",
"tableFrom": "session", "tableFrom": "session",
"tableTo": "auth",
"columnsFrom": [ "columnsFrom": [
"user_id" "user_id"
], ],
"tableTo": "auth",
"columnsTo": [ "columnsTo": [
"id" "id"
], ],
"onDelete": "cascade", "onUpdate": "no action",
"onUpdate": "no action" "onDelete": "cascade"
} }
}, },
"compositePrimaryKeys": {}, "compositePrimaryKeys": {},
"uniqueConstraints": {} "uniqueConstraints": {}
}, },
"redirect": { "public.redirect": {
"name": "redirect", "name": "redirect",
"schema": "", "schema": "",
"columns": { "columns": {
@@ -1853,21 +1851,21 @@
"redirect_applicationId_application_applicationId_fk": { "redirect_applicationId_application_applicationId_fk": {
"name": "redirect_applicationId_application_applicationId_fk", "name": "redirect_applicationId_application_applicationId_fk",
"tableFrom": "redirect", "tableFrom": "redirect",
"tableTo": "application",
"columnsFrom": [ "columnsFrom": [
"applicationId" "applicationId"
], ],
"tableTo": "application",
"columnsTo": [ "columnsTo": [
"applicationId" "applicationId"
], ],
"onDelete": "cascade", "onUpdate": "no action",
"onUpdate": "no action" "onDelete": "cascade"
} }
}, },
"compositePrimaryKeys": {}, "compositePrimaryKeys": {},
"uniqueConstraints": {} "uniqueConstraints": {}
}, },
"security": { "public.security": {
"name": "security", "name": "security",
"schema": "", "schema": "",
"columns": { "columns": {
@@ -1907,30 +1905,30 @@
"security_applicationId_application_applicationId_fk": { "security_applicationId_application_applicationId_fk": {
"name": "security_applicationId_application_applicationId_fk", "name": "security_applicationId_application_applicationId_fk",
"tableFrom": "security", "tableFrom": "security",
"tableTo": "application",
"columnsFrom": [ "columnsFrom": [
"applicationId" "applicationId"
], ],
"tableTo": "application",
"columnsTo": [ "columnsTo": [
"applicationId" "applicationId"
], ],
"onDelete": "cascade", "onUpdate": "no action",
"onUpdate": "no action" "onDelete": "cascade"
} }
}, },
"compositePrimaryKeys": {}, "compositePrimaryKeys": {},
"uniqueConstraints": { "uniqueConstraints": {
"security_username_applicationId_unique": { "security_username_applicationId_unique": {
"name": "security_username_applicationId_unique", "name": "security_username_applicationId_unique",
"nullsNotDistinct": false,
"columns": [ "columns": [
"username", "username",
"applicationId" "applicationId"
] ],
"nullsNotDistinct": false
} }
} }
}, },
"port": { "public.port": {
"name": "port", "name": "port",
"schema": "", "schema": "",
"columns": { "columns": {
@@ -1970,21 +1968,21 @@
"port_applicationId_application_applicationId_fk": { "port_applicationId_application_applicationId_fk": {
"name": "port_applicationId_application_applicationId_fk", "name": "port_applicationId_application_applicationId_fk",
"tableFrom": "port", "tableFrom": "port",
"tableTo": "application",
"columnsFrom": [ "columnsFrom": [
"applicationId" "applicationId"
], ],
"tableTo": "application",
"columnsTo": [ "columnsTo": [
"applicationId" "applicationId"
], ],
"onDelete": "cascade", "onUpdate": "no action",
"onUpdate": "no action" "onDelete": "cascade"
} }
}, },
"compositePrimaryKeys": {}, "compositePrimaryKeys": {},
"uniqueConstraints": {} "uniqueConstraints": {}
}, },
"redis": { "public.redis": {
"name": "redis", "name": "redis",
"schema": "", "schema": "",
"columns": { "columns": {
@@ -2091,118 +2089,130 @@
"redis_projectId_project_projectId_fk": { "redis_projectId_project_projectId_fk": {
"name": "redis_projectId_project_projectId_fk", "name": "redis_projectId_project_projectId_fk",
"tableFrom": "redis", "tableFrom": "redis",
"tableTo": "project",
"columnsFrom": [ "columnsFrom": [
"projectId" "projectId"
], ],
"tableTo": "project",
"columnsTo": [ "columnsTo": [
"projectId" "projectId"
], ],
"onDelete": "cascade", "onUpdate": "no action",
"onUpdate": "no action" "onDelete": "cascade"
} }
}, },
"compositePrimaryKeys": {}, "compositePrimaryKeys": {},
"uniqueConstraints": { "uniqueConstraints": {
"redis_appName_unique": { "redis_appName_unique": {
"name": "redis_appName_unique", "name": "redis_appName_unique",
"nullsNotDistinct": false,
"columns": [ "columns": [
"appName" "appName"
] ],
"nullsNotDistinct": false
} }
} }
} }
}, },
"enums": { "enums": {
"buildType": { "public.buildType": {
"name": "buildType", "name": "buildType",
"values": { "schema": "public",
"dockerfile": "dockerfile", "values": [
"heroku_buildpacks": "heroku_buildpacks", "dockerfile",
"paketo_buildpacks": "paketo_buildpacks", "heroku_buildpacks",
"nixpacks": "nixpacks" "paketo_buildpacks",
} "nixpacks"
]
}, },
"sourceType": { "public.sourceType": {
"name": "sourceType", "name": "sourceType",
"values": { "schema": "public",
"docker": "docker", "values": [
"git": "git", "docker",
"github": "github" "git",
} "github"
]
}, },
"Roles": { "public.Roles": {
"name": "Roles", "name": "Roles",
"values": { "schema": "public",
"admin": "admin", "values": [
"user": "user" "admin",
} "user"
]
}, },
"databaseType": { "public.databaseType": {
"name": "databaseType", "name": "databaseType",
"values": { "schema": "public",
"postgres": "postgres", "values": [
"mariadb": "mariadb", "postgres",
"mysql": "mysql", "mariadb",
"mongo": "mongo" "mysql",
} "mongo"
]
}, },
"deploymentStatus": { "public.deploymentStatus": {
"name": "deploymentStatus", "name": "deploymentStatus",
"values": { "schema": "public",
"running": "running", "values": [
"done": "done", "running",
"error": "error" "done",
} "error"
]
}, },
"mountType": { "public.mountType": {
"name": "mountType", "name": "mountType",
"values": { "schema": "public",
"bind": "bind", "values": [
"volume": "volume", "bind",
"file": "file" "volume",
} "file"
]
}, },
"serviceType": { "public.serviceType": {
"name": "serviceType", "name": "serviceType",
"values": { "schema": "public",
"application": "application", "values": [
"postgres": "postgres", "application",
"mysql": "mysql", "postgres",
"mariadb": "mariadb", "mysql",
"mongo": "mongo", "mariadb",
"redis": "redis" "mongo",
} "redis"
]
}, },
"protocolType": { "public.protocolType": {
"name": "protocolType", "name": "protocolType",
"values": { "schema": "public",
"tcp": "tcp", "values": [
"udp": "udp" "tcp",
} "udp"
]
}, },
"applicationStatus": { "public.applicationStatus": {
"name": "applicationStatus", "name": "applicationStatus",
"values": { "schema": "public",
"idle": "idle", "values": [
"running": "running", "idle",
"done": "done", "running",
"error": "error" "done",
} "error"
]
}, },
"certificateType": { "public.certificateType": {
"name": "certificateType", "name": "certificateType",
"values": { "schema": "public",
"letsencrypt": "letsencrypt", "values": [
"none": "none" "letsencrypt",
} "none"
]
} }
}, },
"schemas": {}, "schemas": {},
"_meta": { "_meta": {
"columns": {},
"schemas": {}, "schemas": {},
"tables": {} "tables": {},
} "columns": {}
},
"id": "c6215051-7cd1-412d-b8df-b50d58acacff",
"prevId": "00000000-0000-0000-0000-000000000000"
} }

View File

@@ -1,10 +1,8 @@
{ {
"id": "3a4dfad7-ae33-4ae3-b60e-4f40f44f5652", "version": "6",
"prevId": "c6215051-7cd1-412d-b8df-b50d58acacff", "dialect": "postgresql",
"version": "5",
"dialect": "pg",
"tables": { "tables": {
"application": { "public.application": {
"name": "application", "name": "application",
"schema": "", "schema": "",
"columns": { "columns": {
@@ -234,29 +232,29 @@
"application_projectId_project_projectId_fk": { "application_projectId_project_projectId_fk": {
"name": "application_projectId_project_projectId_fk", "name": "application_projectId_project_projectId_fk",
"tableFrom": "application", "tableFrom": "application",
"tableTo": "project",
"columnsFrom": [ "columnsFrom": [
"projectId" "projectId"
], ],
"tableTo": "project",
"columnsTo": [ "columnsTo": [
"projectId" "projectId"
], ],
"onDelete": "cascade", "onUpdate": "no action",
"onUpdate": "no action" "onDelete": "cascade"
} }
}, },
"compositePrimaryKeys": {}, "compositePrimaryKeys": {},
"uniqueConstraints": { "uniqueConstraints": {
"application_appName_unique": { "application_appName_unique": {
"name": "application_appName_unique", "name": "application_appName_unique",
"nullsNotDistinct": false,
"columns": [ "columns": [
"appName" "appName"
] ],
"nullsNotDistinct": false
} }
} }
}, },
"postgres": { "public.postgres": {
"name": "postgres", "name": "postgres",
"schema": "", "schema": "",
"columns": { "columns": {
@@ -375,29 +373,29 @@
"postgres_projectId_project_projectId_fk": { "postgres_projectId_project_projectId_fk": {
"name": "postgres_projectId_project_projectId_fk", "name": "postgres_projectId_project_projectId_fk",
"tableFrom": "postgres", "tableFrom": "postgres",
"tableTo": "project",
"columnsFrom": [ "columnsFrom": [
"projectId" "projectId"
], ],
"tableTo": "project",
"columnsTo": [ "columnsTo": [
"projectId" "projectId"
], ],
"onDelete": "cascade", "onUpdate": "no action",
"onUpdate": "no action" "onDelete": "cascade"
} }
}, },
"compositePrimaryKeys": {}, "compositePrimaryKeys": {},
"uniqueConstraints": { "uniqueConstraints": {
"postgres_appName_unique": { "postgres_appName_unique": {
"name": "postgres_appName_unique", "name": "postgres_appName_unique",
"nullsNotDistinct": false,
"columns": [ "columns": [
"appName" "appName"
] ],
"nullsNotDistinct": false
} }
} }
}, },
"user": { "public.user": {
"name": "user", "name": "user",
"schema": "", "schema": "",
"columns": { "columns": {
@@ -499,34 +497,34 @@
"user_adminId_admin_adminId_fk": { "user_adminId_admin_adminId_fk": {
"name": "user_adminId_admin_adminId_fk", "name": "user_adminId_admin_adminId_fk",
"tableFrom": "user", "tableFrom": "user",
"tableTo": "admin",
"columnsFrom": [ "columnsFrom": [
"adminId" "adminId"
], ],
"tableTo": "admin",
"columnsTo": [ "columnsTo": [
"adminId" "adminId"
], ],
"onDelete": "cascade", "onUpdate": "no action",
"onUpdate": "no action" "onDelete": "cascade"
}, },
"user_authId_auth_id_fk": { "user_authId_auth_id_fk": {
"name": "user_authId_auth_id_fk", "name": "user_authId_auth_id_fk",
"tableFrom": "user", "tableFrom": "user",
"tableTo": "auth",
"columnsFrom": [ "columnsFrom": [
"authId" "authId"
], ],
"tableTo": "auth",
"columnsTo": [ "columnsTo": [
"id" "id"
], ],
"onDelete": "cascade", "onUpdate": "no action",
"onUpdate": "no action" "onDelete": "cascade"
} }
}, },
"compositePrimaryKeys": {}, "compositePrimaryKeys": {},
"uniqueConstraints": {} "uniqueConstraints": {}
}, },
"admin": { "public.admin": {
"name": "admin", "name": "admin",
"schema": "", "schema": "",
"columns": { "columns": {
@@ -628,21 +626,21 @@
"admin_authId_auth_id_fk": { "admin_authId_auth_id_fk": {
"name": "admin_authId_auth_id_fk", "name": "admin_authId_auth_id_fk",
"tableFrom": "admin", "tableFrom": "admin",
"tableTo": "auth",
"columnsFrom": [ "columnsFrom": [
"authId" "authId"
], ],
"tableTo": "auth",
"columnsTo": [ "columnsTo": [
"id" "id"
], ],
"onDelete": "cascade", "onUpdate": "no action",
"onUpdate": "no action" "onDelete": "cascade"
} }
}, },
"compositePrimaryKeys": {}, "compositePrimaryKeys": {},
"uniqueConstraints": {} "uniqueConstraints": {}
}, },
"auth": { "public.auth": {
"name": "auth", "name": "auth",
"schema": "", "schema": "",
"columns": { "columns": {
@@ -695,14 +693,14 @@
"uniqueConstraints": { "uniqueConstraints": {
"auth_email_unique": { "auth_email_unique": {
"name": "auth_email_unique", "name": "auth_email_unique",
"nullsNotDistinct": false,
"columns": [ "columns": [
"email" "email"
] ],
"nullsNotDistinct": false
} }
} }
}, },
"project": { "public.project": {
"name": "project", "name": "project",
"schema": "", "schema": "",
"columns": { "columns": {
@@ -742,21 +740,21 @@
"project_adminId_admin_adminId_fk": { "project_adminId_admin_adminId_fk": {
"name": "project_adminId_admin_adminId_fk", "name": "project_adminId_admin_adminId_fk",
"tableFrom": "project", "tableFrom": "project",
"tableTo": "admin",
"columnsFrom": [ "columnsFrom": [
"adminId" "adminId"
], ],
"tableTo": "admin",
"columnsTo": [ "columnsTo": [
"adminId" "adminId"
], ],
"onDelete": "cascade", "onUpdate": "no action",
"onUpdate": "no action" "onDelete": "cascade"
} }
}, },
"compositePrimaryKeys": {}, "compositePrimaryKeys": {},
"uniqueConstraints": {} "uniqueConstraints": {}
}, },
"domain": { "public.domain": {
"name": "domain", "name": "domain",
"schema": "", "schema": "",
"columns": { "columns": {
@@ -824,21 +822,21 @@
"domain_applicationId_application_applicationId_fk": { "domain_applicationId_application_applicationId_fk": {
"name": "domain_applicationId_application_applicationId_fk", "name": "domain_applicationId_application_applicationId_fk",
"tableFrom": "domain", "tableFrom": "domain",
"tableTo": "application",
"columnsFrom": [ "columnsFrom": [
"applicationId" "applicationId"
], ],
"tableTo": "application",
"columnsTo": [ "columnsTo": [
"applicationId" "applicationId"
], ],
"onDelete": "cascade", "onUpdate": "no action",
"onUpdate": "no action" "onDelete": "cascade"
} }
}, },
"compositePrimaryKeys": {}, "compositePrimaryKeys": {},
"uniqueConstraints": {} "uniqueConstraints": {}
}, },
"mariadb": { "public.mariadb": {
"name": "mariadb", "name": "mariadb",
"schema": "", "schema": "",
"columns": { "columns": {
@@ -963,29 +961,29 @@
"mariadb_projectId_project_projectId_fk": { "mariadb_projectId_project_projectId_fk": {
"name": "mariadb_projectId_project_projectId_fk", "name": "mariadb_projectId_project_projectId_fk",
"tableFrom": "mariadb", "tableFrom": "mariadb",
"tableTo": "project",
"columnsFrom": [ "columnsFrom": [
"projectId" "projectId"
], ],
"tableTo": "project",
"columnsTo": [ "columnsTo": [
"projectId" "projectId"
], ],
"onDelete": "cascade", "onUpdate": "no action",
"onUpdate": "no action" "onDelete": "cascade"
} }
}, },
"compositePrimaryKeys": {}, "compositePrimaryKeys": {},
"uniqueConstraints": { "uniqueConstraints": {
"mariadb_appName_unique": { "mariadb_appName_unique": {
"name": "mariadb_appName_unique", "name": "mariadb_appName_unique",
"nullsNotDistinct": false,
"columns": [ "columns": [
"appName" "appName"
] ],
"nullsNotDistinct": false
} }
} }
}, },
"mongo": { "public.mongo": {
"name": "mongo", "name": "mongo",
"schema": "", "schema": "",
"columns": { "columns": {
@@ -1098,29 +1096,29 @@
"mongo_projectId_project_projectId_fk": { "mongo_projectId_project_projectId_fk": {
"name": "mongo_projectId_project_projectId_fk", "name": "mongo_projectId_project_projectId_fk",
"tableFrom": "mongo", "tableFrom": "mongo",
"tableTo": "project",
"columnsFrom": [ "columnsFrom": [
"projectId" "projectId"
], ],
"tableTo": "project",
"columnsTo": [ "columnsTo": [
"projectId" "projectId"
], ],
"onDelete": "cascade", "onUpdate": "no action",
"onUpdate": "no action" "onDelete": "cascade"
} }
}, },
"compositePrimaryKeys": {}, "compositePrimaryKeys": {},
"uniqueConstraints": { "uniqueConstraints": {
"mongo_appName_unique": { "mongo_appName_unique": {
"name": "mongo_appName_unique", "name": "mongo_appName_unique",
"nullsNotDistinct": false,
"columns": [ "columns": [
"appName" "appName"
] ],
"nullsNotDistinct": false
} }
} }
}, },
"mysql": { "public.mysql": {
"name": "mysql", "name": "mysql",
"schema": "", "schema": "",
"columns": { "columns": {
@@ -1245,29 +1243,29 @@
"mysql_projectId_project_projectId_fk": { "mysql_projectId_project_projectId_fk": {
"name": "mysql_projectId_project_projectId_fk", "name": "mysql_projectId_project_projectId_fk",
"tableFrom": "mysql", "tableFrom": "mysql",
"tableTo": "project",
"columnsFrom": [ "columnsFrom": [
"projectId" "projectId"
], ],
"tableTo": "project",
"columnsTo": [ "columnsTo": [
"projectId" "projectId"
], ],
"onDelete": "cascade", "onUpdate": "no action",
"onUpdate": "no action" "onDelete": "cascade"
} }
}, },
"compositePrimaryKeys": {}, "compositePrimaryKeys": {},
"uniqueConstraints": { "uniqueConstraints": {
"mysql_appName_unique": { "mysql_appName_unique": {
"name": "mysql_appName_unique", "name": "mysql_appName_unique",
"nullsNotDistinct": false,
"columns": [ "columns": [
"appName" "appName"
] ],
"nullsNotDistinct": false
} }
} }
}, },
"backup": { "public.backup": {
"name": "backup", "name": "backup",
"schema": "", "schema": "",
"columns": { "columns": {
@@ -1343,73 +1341,73 @@
"backup_destinationId_destination_destinationId_fk": { "backup_destinationId_destination_destinationId_fk": {
"name": "backup_destinationId_destination_destinationId_fk", "name": "backup_destinationId_destination_destinationId_fk",
"tableFrom": "backup", "tableFrom": "backup",
"tableTo": "destination",
"columnsFrom": [ "columnsFrom": [
"destinationId" "destinationId"
], ],
"tableTo": "destination",
"columnsTo": [ "columnsTo": [
"destinationId" "destinationId"
], ],
"onDelete": "cascade", "onUpdate": "no action",
"onUpdate": "no action" "onDelete": "cascade"
}, },
"backup_postgresId_postgres_postgresId_fk": { "backup_postgresId_postgres_postgresId_fk": {
"name": "backup_postgresId_postgres_postgresId_fk", "name": "backup_postgresId_postgres_postgresId_fk",
"tableFrom": "backup", "tableFrom": "backup",
"tableTo": "postgres",
"columnsFrom": [ "columnsFrom": [
"postgresId" "postgresId"
], ],
"tableTo": "postgres",
"columnsTo": [ "columnsTo": [
"postgresId" "postgresId"
], ],
"onDelete": "cascade", "onUpdate": "no action",
"onUpdate": "no action" "onDelete": "cascade"
}, },
"backup_mariadbId_mariadb_mariadbId_fk": { "backup_mariadbId_mariadb_mariadbId_fk": {
"name": "backup_mariadbId_mariadb_mariadbId_fk", "name": "backup_mariadbId_mariadb_mariadbId_fk",
"tableFrom": "backup", "tableFrom": "backup",
"tableTo": "mariadb",
"columnsFrom": [ "columnsFrom": [
"mariadbId" "mariadbId"
], ],
"tableTo": "mariadb",
"columnsTo": [ "columnsTo": [
"mariadbId" "mariadbId"
], ],
"onDelete": "cascade", "onUpdate": "no action",
"onUpdate": "no action" "onDelete": "cascade"
}, },
"backup_mysqlId_mysql_mysqlId_fk": { "backup_mysqlId_mysql_mysqlId_fk": {
"name": "backup_mysqlId_mysql_mysqlId_fk", "name": "backup_mysqlId_mysql_mysqlId_fk",
"tableFrom": "backup", "tableFrom": "backup",
"tableTo": "mysql",
"columnsFrom": [ "columnsFrom": [
"mysqlId" "mysqlId"
], ],
"tableTo": "mysql",
"columnsTo": [ "columnsTo": [
"mysqlId" "mysqlId"
], ],
"onDelete": "cascade", "onUpdate": "no action",
"onUpdate": "no action" "onDelete": "cascade"
}, },
"backup_mongoId_mongo_mongoId_fk": { "backup_mongoId_mongo_mongoId_fk": {
"name": "backup_mongoId_mongo_mongoId_fk", "name": "backup_mongoId_mongo_mongoId_fk",
"tableFrom": "backup", "tableFrom": "backup",
"tableTo": "mongo",
"columnsFrom": [ "columnsFrom": [
"mongoId" "mongoId"
], ],
"tableTo": "mongo",
"columnsTo": [ "columnsTo": [
"mongoId" "mongoId"
], ],
"onDelete": "cascade", "onUpdate": "no action",
"onUpdate": "no action" "onDelete": "cascade"
} }
}, },
"compositePrimaryKeys": {}, "compositePrimaryKeys": {},
"uniqueConstraints": {} "uniqueConstraints": {}
}, },
"destination": { "public.destination": {
"name": "destination", "name": "destination",
"schema": "", "schema": "",
"columns": { "columns": {
@@ -1467,21 +1465,21 @@
"destination_adminId_admin_adminId_fk": { "destination_adminId_admin_adminId_fk": {
"name": "destination_adminId_admin_adminId_fk", "name": "destination_adminId_admin_adminId_fk",
"tableFrom": "destination", "tableFrom": "destination",
"tableTo": "admin",
"columnsFrom": [ "columnsFrom": [
"adminId" "adminId"
], ],
"tableTo": "admin",
"columnsTo": [ "columnsTo": [
"adminId" "adminId"
], ],
"onDelete": "cascade", "onUpdate": "no action",
"onUpdate": "no action" "onDelete": "cascade"
} }
}, },
"compositePrimaryKeys": {}, "compositePrimaryKeys": {},
"uniqueConstraints": {} "uniqueConstraints": {}
}, },
"deployment": { "public.deployment": {
"name": "deployment", "name": "deployment",
"schema": "", "schema": "",
"columns": { "columns": {
@@ -1528,21 +1526,21 @@
"deployment_applicationId_application_applicationId_fk": { "deployment_applicationId_application_applicationId_fk": {
"name": "deployment_applicationId_application_applicationId_fk", "name": "deployment_applicationId_application_applicationId_fk",
"tableFrom": "deployment", "tableFrom": "deployment",
"tableTo": "application",
"columnsFrom": [ "columnsFrom": [
"applicationId" "applicationId"
], ],
"tableTo": "application",
"columnsTo": [ "columnsTo": [
"applicationId" "applicationId"
], ],
"onDelete": "cascade", "onUpdate": "no action",
"onUpdate": "no action" "onDelete": "cascade"
} }
}, },
"compositePrimaryKeys": {}, "compositePrimaryKeys": {},
"uniqueConstraints": {} "uniqueConstraints": {}
}, },
"mount": { "public.mount": {
"name": "mount", "name": "mount",
"schema": "", "schema": "",
"columns": { "columns": {
@@ -1631,86 +1629,86 @@
"mount_applicationId_application_applicationId_fk": { "mount_applicationId_application_applicationId_fk": {
"name": "mount_applicationId_application_applicationId_fk", "name": "mount_applicationId_application_applicationId_fk",
"tableFrom": "mount", "tableFrom": "mount",
"tableTo": "application",
"columnsFrom": [ "columnsFrom": [
"applicationId" "applicationId"
], ],
"tableTo": "application",
"columnsTo": [ "columnsTo": [
"applicationId" "applicationId"
], ],
"onDelete": "cascade", "onUpdate": "no action",
"onUpdate": "no action" "onDelete": "cascade"
}, },
"mount_postgresId_postgres_postgresId_fk": { "mount_postgresId_postgres_postgresId_fk": {
"name": "mount_postgresId_postgres_postgresId_fk", "name": "mount_postgresId_postgres_postgresId_fk",
"tableFrom": "mount", "tableFrom": "mount",
"tableTo": "postgres",
"columnsFrom": [ "columnsFrom": [
"postgresId" "postgresId"
], ],
"tableTo": "postgres",
"columnsTo": [ "columnsTo": [
"postgresId" "postgresId"
], ],
"onDelete": "cascade", "onUpdate": "no action",
"onUpdate": "no action" "onDelete": "cascade"
}, },
"mount_mariadbId_mariadb_mariadbId_fk": { "mount_mariadbId_mariadb_mariadbId_fk": {
"name": "mount_mariadbId_mariadb_mariadbId_fk", "name": "mount_mariadbId_mariadb_mariadbId_fk",
"tableFrom": "mount", "tableFrom": "mount",
"tableTo": "mariadb",
"columnsFrom": [ "columnsFrom": [
"mariadbId" "mariadbId"
], ],
"tableTo": "mariadb",
"columnsTo": [ "columnsTo": [
"mariadbId" "mariadbId"
], ],
"onDelete": "cascade", "onUpdate": "no action",
"onUpdate": "no action" "onDelete": "cascade"
}, },
"mount_mongoId_mongo_mongoId_fk": { "mount_mongoId_mongo_mongoId_fk": {
"name": "mount_mongoId_mongo_mongoId_fk", "name": "mount_mongoId_mongo_mongoId_fk",
"tableFrom": "mount", "tableFrom": "mount",
"tableTo": "mongo",
"columnsFrom": [ "columnsFrom": [
"mongoId" "mongoId"
], ],
"tableTo": "mongo",
"columnsTo": [ "columnsTo": [
"mongoId" "mongoId"
], ],
"onDelete": "cascade", "onUpdate": "no action",
"onUpdate": "no action" "onDelete": "cascade"
}, },
"mount_mysqlId_mysql_mysqlId_fk": { "mount_mysqlId_mysql_mysqlId_fk": {
"name": "mount_mysqlId_mysql_mysqlId_fk", "name": "mount_mysqlId_mysql_mysqlId_fk",
"tableFrom": "mount", "tableFrom": "mount",
"tableTo": "mysql",
"columnsFrom": [ "columnsFrom": [
"mysqlId" "mysqlId"
], ],
"tableTo": "mysql",
"columnsTo": [ "columnsTo": [
"mysqlId" "mysqlId"
], ],
"onDelete": "cascade", "onUpdate": "no action",
"onUpdate": "no action" "onDelete": "cascade"
}, },
"mount_redisId_redis_redisId_fk": { "mount_redisId_redis_redisId_fk": {
"name": "mount_redisId_redis_redisId_fk", "name": "mount_redisId_redis_redisId_fk",
"tableFrom": "mount", "tableFrom": "mount",
"tableTo": "redis",
"columnsFrom": [ "columnsFrom": [
"redisId" "redisId"
], ],
"tableTo": "redis",
"columnsTo": [ "columnsTo": [
"redisId" "redisId"
], ],
"onDelete": "cascade", "onUpdate": "no action",
"onUpdate": "no action" "onDelete": "cascade"
} }
}, },
"compositePrimaryKeys": {}, "compositePrimaryKeys": {},
"uniqueConstraints": {} "uniqueConstraints": {}
}, },
"certificate": { "public.certificate": {
"name": "certificate", "name": "certificate",
"schema": "", "schema": "",
"columns": { "columns": {
@@ -1757,14 +1755,14 @@
"uniqueConstraints": { "uniqueConstraints": {
"certificate_certificatePath_unique": { "certificate_certificatePath_unique": {
"name": "certificate_certificatePath_unique", "name": "certificate_certificatePath_unique",
"nullsNotDistinct": false,
"columns": [ "columns": [
"certificatePath" "certificatePath"
] ],
"nullsNotDistinct": false
} }
} }
}, },
"session": { "public.session": {
"name": "session", "name": "session",
"schema": "", "schema": "",
"columns": { "columns": {
@@ -1792,21 +1790,21 @@
"session_user_id_auth_id_fk": { "session_user_id_auth_id_fk": {
"name": "session_user_id_auth_id_fk", "name": "session_user_id_auth_id_fk",
"tableFrom": "session", "tableFrom": "session",
"tableTo": "auth",
"columnsFrom": [ "columnsFrom": [
"user_id" "user_id"
], ],
"tableTo": "auth",
"columnsTo": [ "columnsTo": [
"id" "id"
], ],
"onDelete": "cascade", "onUpdate": "no action",
"onUpdate": "no action" "onDelete": "cascade"
} }
}, },
"compositePrimaryKeys": {}, "compositePrimaryKeys": {},
"uniqueConstraints": {} "uniqueConstraints": {}
}, },
"redirect": { "public.redirect": {
"name": "redirect", "name": "redirect",
"schema": "", "schema": "",
"columns": { "columns": {
@@ -1859,21 +1857,21 @@
"redirect_applicationId_application_applicationId_fk": { "redirect_applicationId_application_applicationId_fk": {
"name": "redirect_applicationId_application_applicationId_fk", "name": "redirect_applicationId_application_applicationId_fk",
"tableFrom": "redirect", "tableFrom": "redirect",
"tableTo": "application",
"columnsFrom": [ "columnsFrom": [
"applicationId" "applicationId"
], ],
"tableTo": "application",
"columnsTo": [ "columnsTo": [
"applicationId" "applicationId"
], ],
"onDelete": "cascade", "onUpdate": "no action",
"onUpdate": "no action" "onDelete": "cascade"
} }
}, },
"compositePrimaryKeys": {}, "compositePrimaryKeys": {},
"uniqueConstraints": {} "uniqueConstraints": {}
}, },
"security": { "public.security": {
"name": "security", "name": "security",
"schema": "", "schema": "",
"columns": { "columns": {
@@ -1913,30 +1911,30 @@
"security_applicationId_application_applicationId_fk": { "security_applicationId_application_applicationId_fk": {
"name": "security_applicationId_application_applicationId_fk", "name": "security_applicationId_application_applicationId_fk",
"tableFrom": "security", "tableFrom": "security",
"tableTo": "application",
"columnsFrom": [ "columnsFrom": [
"applicationId" "applicationId"
], ],
"tableTo": "application",
"columnsTo": [ "columnsTo": [
"applicationId" "applicationId"
], ],
"onDelete": "cascade", "onUpdate": "no action",
"onUpdate": "no action" "onDelete": "cascade"
} }
}, },
"compositePrimaryKeys": {}, "compositePrimaryKeys": {},
"uniqueConstraints": { "uniqueConstraints": {
"security_username_applicationId_unique": { "security_username_applicationId_unique": {
"name": "security_username_applicationId_unique", "name": "security_username_applicationId_unique",
"nullsNotDistinct": false,
"columns": [ "columns": [
"username", "username",
"applicationId" "applicationId"
] ],
"nullsNotDistinct": false
} }
} }
}, },
"port": { "public.port": {
"name": "port", "name": "port",
"schema": "", "schema": "",
"columns": { "columns": {
@@ -1976,21 +1974,21 @@
"port_applicationId_application_applicationId_fk": { "port_applicationId_application_applicationId_fk": {
"name": "port_applicationId_application_applicationId_fk", "name": "port_applicationId_application_applicationId_fk",
"tableFrom": "port", "tableFrom": "port",
"tableTo": "application",
"columnsFrom": [ "columnsFrom": [
"applicationId" "applicationId"
], ],
"tableTo": "application",
"columnsTo": [ "columnsTo": [
"applicationId" "applicationId"
], ],
"onDelete": "cascade", "onUpdate": "no action",
"onUpdate": "no action" "onDelete": "cascade"
} }
}, },
"compositePrimaryKeys": {}, "compositePrimaryKeys": {},
"uniqueConstraints": {} "uniqueConstraints": {}
}, },
"redis": { "public.redis": {
"name": "redis", "name": "redis",
"schema": "", "schema": "",
"columns": { "columns": {
@@ -2097,118 +2095,130 @@
"redis_projectId_project_projectId_fk": { "redis_projectId_project_projectId_fk": {
"name": "redis_projectId_project_projectId_fk", "name": "redis_projectId_project_projectId_fk",
"tableFrom": "redis", "tableFrom": "redis",
"tableTo": "project",
"columnsFrom": [ "columnsFrom": [
"projectId" "projectId"
], ],
"tableTo": "project",
"columnsTo": [ "columnsTo": [
"projectId" "projectId"
], ],
"onDelete": "cascade", "onUpdate": "no action",
"onUpdate": "no action" "onDelete": "cascade"
} }
}, },
"compositePrimaryKeys": {}, "compositePrimaryKeys": {},
"uniqueConstraints": { "uniqueConstraints": {
"redis_appName_unique": { "redis_appName_unique": {
"name": "redis_appName_unique", "name": "redis_appName_unique",
"nullsNotDistinct": false,
"columns": [ "columns": [
"appName" "appName"
] ],
"nullsNotDistinct": false
} }
} }
} }
}, },
"enums": { "enums": {
"buildType": { "public.buildType": {
"name": "buildType", "name": "buildType",
"values": { "schema": "public",
"dockerfile": "dockerfile", "values": [
"heroku_buildpacks": "heroku_buildpacks", "dockerfile",
"paketo_buildpacks": "paketo_buildpacks", "heroku_buildpacks",
"nixpacks": "nixpacks" "paketo_buildpacks",
} "nixpacks"
]
}, },
"sourceType": { "public.sourceType": {
"name": "sourceType", "name": "sourceType",
"values": { "schema": "public",
"docker": "docker", "values": [
"git": "git", "docker",
"github": "github" "git",
} "github"
]
}, },
"Roles": { "public.Roles": {
"name": "Roles", "name": "Roles",
"values": { "schema": "public",
"admin": "admin", "values": [
"user": "user" "admin",
} "user"
]
}, },
"databaseType": { "public.databaseType": {
"name": "databaseType", "name": "databaseType",
"values": { "schema": "public",
"postgres": "postgres", "values": [
"mariadb": "mariadb", "postgres",
"mysql": "mysql", "mariadb",
"mongo": "mongo" "mysql",
} "mongo"
]
}, },
"deploymentStatus": { "public.deploymentStatus": {
"name": "deploymentStatus", "name": "deploymentStatus",
"values": { "schema": "public",
"running": "running", "values": [
"done": "done", "running",
"error": "error" "done",
} "error"
]
}, },
"mountType": { "public.mountType": {
"name": "mountType", "name": "mountType",
"values": { "schema": "public",
"bind": "bind", "values": [
"volume": "volume", "bind",
"file": "file" "volume",
} "file"
]
}, },
"serviceType": { "public.serviceType": {
"name": "serviceType", "name": "serviceType",
"values": { "schema": "public",
"application": "application", "values": [
"postgres": "postgres", "application",
"mysql": "mysql", "postgres",
"mariadb": "mariadb", "mysql",
"mongo": "mongo", "mariadb",
"redis": "redis" "mongo",
} "redis"
]
}, },
"protocolType": { "public.protocolType": {
"name": "protocolType", "name": "protocolType",
"values": { "schema": "public",
"tcp": "tcp", "values": [
"udp": "udp" "tcp",
} "udp"
]
}, },
"applicationStatus": { "public.applicationStatus": {
"name": "applicationStatus", "name": "applicationStatus",
"values": { "schema": "public",
"idle": "idle", "values": [
"running": "running", "idle",
"done": "done", "running",
"error": "error" "done",
} "error"
]
}, },
"certificateType": { "public.certificateType": {
"name": "certificateType", "name": "certificateType",
"values": { "schema": "public",
"letsencrypt": "letsencrypt", "values": [
"none": "none" "letsencrypt",
} "none"
]
} }
}, },
"schemas": {}, "schemas": {},
"_meta": { "_meta": {
"columns": {},
"schemas": {}, "schemas": {},
"tables": {} "tables": {},
} "columns": {}
},
"id": "3a4dfad7-ae33-4ae3-b60e-4f40f44f5652",
"prevId": "c6215051-7cd1-412d-b8df-b50d58acacff"
} }

View File

@@ -1,10 +1,8 @@
{ {
"id": "665483bd-5123-4c2b-beef-bfa9b91b9356", "version": "6",
"prevId": "3a4dfad7-ae33-4ae3-b60e-4f40f44f5652", "dialect": "postgresql",
"version": "5",
"dialect": "pg",
"tables": { "tables": {
"application": { "public.application": {
"name": "application", "name": "application",
"schema": "", "schema": "",
"columns": { "columns": {
@@ -234,29 +232,29 @@
"application_projectId_project_projectId_fk": { "application_projectId_project_projectId_fk": {
"name": "application_projectId_project_projectId_fk", "name": "application_projectId_project_projectId_fk",
"tableFrom": "application", "tableFrom": "application",
"tableTo": "project",
"columnsFrom": [ "columnsFrom": [
"projectId" "projectId"
], ],
"tableTo": "project",
"columnsTo": [ "columnsTo": [
"projectId" "projectId"
], ],
"onDelete": "cascade", "onUpdate": "no action",
"onUpdate": "no action" "onDelete": "cascade"
} }
}, },
"compositePrimaryKeys": {}, "compositePrimaryKeys": {},
"uniqueConstraints": { "uniqueConstraints": {
"application_appName_unique": { "application_appName_unique": {
"name": "application_appName_unique", "name": "application_appName_unique",
"nullsNotDistinct": false,
"columns": [ "columns": [
"appName" "appName"
] ],
"nullsNotDistinct": false
} }
} }
}, },
"postgres": { "public.postgres": {
"name": "postgres", "name": "postgres",
"schema": "", "schema": "",
"columns": { "columns": {
@@ -375,29 +373,29 @@
"postgres_projectId_project_projectId_fk": { "postgres_projectId_project_projectId_fk": {
"name": "postgres_projectId_project_projectId_fk", "name": "postgres_projectId_project_projectId_fk",
"tableFrom": "postgres", "tableFrom": "postgres",
"tableTo": "project",
"columnsFrom": [ "columnsFrom": [
"projectId" "projectId"
], ],
"tableTo": "project",
"columnsTo": [ "columnsTo": [
"projectId" "projectId"
], ],
"onDelete": "cascade", "onUpdate": "no action",
"onUpdate": "no action" "onDelete": "cascade"
} }
}, },
"compositePrimaryKeys": {}, "compositePrimaryKeys": {},
"uniqueConstraints": { "uniqueConstraints": {
"postgres_appName_unique": { "postgres_appName_unique": {
"name": "postgres_appName_unique", "name": "postgres_appName_unique",
"nullsNotDistinct": false,
"columns": [ "columns": [
"appName" "appName"
] ],
"nullsNotDistinct": false
} }
} }
}, },
"user": { "public.user": {
"name": "user", "name": "user",
"schema": "", "schema": "",
"columns": { "columns": {
@@ -499,34 +497,34 @@
"user_adminId_admin_adminId_fk": { "user_adminId_admin_adminId_fk": {
"name": "user_adminId_admin_adminId_fk", "name": "user_adminId_admin_adminId_fk",
"tableFrom": "user", "tableFrom": "user",
"tableTo": "admin",
"columnsFrom": [ "columnsFrom": [
"adminId" "adminId"
], ],
"tableTo": "admin",
"columnsTo": [ "columnsTo": [
"adminId" "adminId"
], ],
"onDelete": "cascade", "onUpdate": "no action",
"onUpdate": "no action" "onDelete": "cascade"
}, },
"user_authId_auth_id_fk": { "user_authId_auth_id_fk": {
"name": "user_authId_auth_id_fk", "name": "user_authId_auth_id_fk",
"tableFrom": "user", "tableFrom": "user",
"tableTo": "auth",
"columnsFrom": [ "columnsFrom": [
"authId" "authId"
], ],
"tableTo": "auth",
"columnsTo": [ "columnsTo": [
"id" "id"
], ],
"onDelete": "cascade", "onUpdate": "no action",
"onUpdate": "no action" "onDelete": "cascade"
} }
}, },
"compositePrimaryKeys": {}, "compositePrimaryKeys": {},
"uniqueConstraints": {} "uniqueConstraints": {}
}, },
"admin": { "public.admin": {
"name": "admin", "name": "admin",
"schema": "", "schema": "",
"columns": { "columns": {
@@ -628,21 +626,21 @@
"admin_authId_auth_id_fk": { "admin_authId_auth_id_fk": {
"name": "admin_authId_auth_id_fk", "name": "admin_authId_auth_id_fk",
"tableFrom": "admin", "tableFrom": "admin",
"tableTo": "auth",
"columnsFrom": [ "columnsFrom": [
"authId" "authId"
], ],
"tableTo": "auth",
"columnsTo": [ "columnsTo": [
"id" "id"
], ],
"onDelete": "cascade", "onUpdate": "no action",
"onUpdate": "no action" "onDelete": "cascade"
} }
}, },
"compositePrimaryKeys": {}, "compositePrimaryKeys": {},
"uniqueConstraints": {} "uniqueConstraints": {}
}, },
"auth": { "public.auth": {
"name": "auth", "name": "auth",
"schema": "", "schema": "",
"columns": { "columns": {
@@ -702,14 +700,14 @@
"uniqueConstraints": { "uniqueConstraints": {
"auth_email_unique": { "auth_email_unique": {
"name": "auth_email_unique", "name": "auth_email_unique",
"nullsNotDistinct": false,
"columns": [ "columns": [
"email" "email"
] ],
"nullsNotDistinct": false
} }
} }
}, },
"project": { "public.project": {
"name": "project", "name": "project",
"schema": "", "schema": "",
"columns": { "columns": {
@@ -749,21 +747,21 @@
"project_adminId_admin_adminId_fk": { "project_adminId_admin_adminId_fk": {
"name": "project_adminId_admin_adminId_fk", "name": "project_adminId_admin_adminId_fk",
"tableFrom": "project", "tableFrom": "project",
"tableTo": "admin",
"columnsFrom": [ "columnsFrom": [
"adminId" "adminId"
], ],
"tableTo": "admin",
"columnsTo": [ "columnsTo": [
"adminId" "adminId"
], ],
"onDelete": "cascade", "onUpdate": "no action",
"onUpdate": "no action" "onDelete": "cascade"
} }
}, },
"compositePrimaryKeys": {}, "compositePrimaryKeys": {},
"uniqueConstraints": {} "uniqueConstraints": {}
}, },
"domain": { "public.domain": {
"name": "domain", "name": "domain",
"schema": "", "schema": "",
"columns": { "columns": {
@@ -831,21 +829,21 @@
"domain_applicationId_application_applicationId_fk": { "domain_applicationId_application_applicationId_fk": {
"name": "domain_applicationId_application_applicationId_fk", "name": "domain_applicationId_application_applicationId_fk",
"tableFrom": "domain", "tableFrom": "domain",
"tableTo": "application",
"columnsFrom": [ "columnsFrom": [
"applicationId" "applicationId"
], ],
"tableTo": "application",
"columnsTo": [ "columnsTo": [
"applicationId" "applicationId"
], ],
"onDelete": "cascade", "onUpdate": "no action",
"onUpdate": "no action" "onDelete": "cascade"
} }
}, },
"compositePrimaryKeys": {}, "compositePrimaryKeys": {},
"uniqueConstraints": {} "uniqueConstraints": {}
}, },
"mariadb": { "public.mariadb": {
"name": "mariadb", "name": "mariadb",
"schema": "", "schema": "",
"columns": { "columns": {
@@ -970,29 +968,29 @@
"mariadb_projectId_project_projectId_fk": { "mariadb_projectId_project_projectId_fk": {
"name": "mariadb_projectId_project_projectId_fk", "name": "mariadb_projectId_project_projectId_fk",
"tableFrom": "mariadb", "tableFrom": "mariadb",
"tableTo": "project",
"columnsFrom": [ "columnsFrom": [
"projectId" "projectId"
], ],
"tableTo": "project",
"columnsTo": [ "columnsTo": [
"projectId" "projectId"
], ],
"onDelete": "cascade", "onUpdate": "no action",
"onUpdate": "no action" "onDelete": "cascade"
} }
}, },
"compositePrimaryKeys": {}, "compositePrimaryKeys": {},
"uniqueConstraints": { "uniqueConstraints": {
"mariadb_appName_unique": { "mariadb_appName_unique": {
"name": "mariadb_appName_unique", "name": "mariadb_appName_unique",
"nullsNotDistinct": false,
"columns": [ "columns": [
"appName" "appName"
] ],
"nullsNotDistinct": false
} }
} }
}, },
"mongo": { "public.mongo": {
"name": "mongo", "name": "mongo",
"schema": "", "schema": "",
"columns": { "columns": {
@@ -1105,29 +1103,29 @@
"mongo_projectId_project_projectId_fk": { "mongo_projectId_project_projectId_fk": {
"name": "mongo_projectId_project_projectId_fk", "name": "mongo_projectId_project_projectId_fk",
"tableFrom": "mongo", "tableFrom": "mongo",
"tableTo": "project",
"columnsFrom": [ "columnsFrom": [
"projectId" "projectId"
], ],
"tableTo": "project",
"columnsTo": [ "columnsTo": [
"projectId" "projectId"
], ],
"onDelete": "cascade", "onUpdate": "no action",
"onUpdate": "no action" "onDelete": "cascade"
} }
}, },
"compositePrimaryKeys": {}, "compositePrimaryKeys": {},
"uniqueConstraints": { "uniqueConstraints": {
"mongo_appName_unique": { "mongo_appName_unique": {
"name": "mongo_appName_unique", "name": "mongo_appName_unique",
"nullsNotDistinct": false,
"columns": [ "columns": [
"appName" "appName"
] ],
"nullsNotDistinct": false
} }
} }
}, },
"mysql": { "public.mysql": {
"name": "mysql", "name": "mysql",
"schema": "", "schema": "",
"columns": { "columns": {
@@ -1252,29 +1250,29 @@
"mysql_projectId_project_projectId_fk": { "mysql_projectId_project_projectId_fk": {
"name": "mysql_projectId_project_projectId_fk", "name": "mysql_projectId_project_projectId_fk",
"tableFrom": "mysql", "tableFrom": "mysql",
"tableTo": "project",
"columnsFrom": [ "columnsFrom": [
"projectId" "projectId"
], ],
"tableTo": "project",
"columnsTo": [ "columnsTo": [
"projectId" "projectId"
], ],
"onDelete": "cascade", "onUpdate": "no action",
"onUpdate": "no action" "onDelete": "cascade"
} }
}, },
"compositePrimaryKeys": {}, "compositePrimaryKeys": {},
"uniqueConstraints": { "uniqueConstraints": {
"mysql_appName_unique": { "mysql_appName_unique": {
"name": "mysql_appName_unique", "name": "mysql_appName_unique",
"nullsNotDistinct": false,
"columns": [ "columns": [
"appName" "appName"
] ],
"nullsNotDistinct": false
} }
} }
}, },
"backup": { "public.backup": {
"name": "backup", "name": "backup",
"schema": "", "schema": "",
"columns": { "columns": {
@@ -1350,73 +1348,73 @@
"backup_destinationId_destination_destinationId_fk": { "backup_destinationId_destination_destinationId_fk": {
"name": "backup_destinationId_destination_destinationId_fk", "name": "backup_destinationId_destination_destinationId_fk",
"tableFrom": "backup", "tableFrom": "backup",
"tableTo": "destination",
"columnsFrom": [ "columnsFrom": [
"destinationId" "destinationId"
], ],
"tableTo": "destination",
"columnsTo": [ "columnsTo": [
"destinationId" "destinationId"
], ],
"onDelete": "cascade", "onUpdate": "no action",
"onUpdate": "no action" "onDelete": "cascade"
}, },
"backup_postgresId_postgres_postgresId_fk": { "backup_postgresId_postgres_postgresId_fk": {
"name": "backup_postgresId_postgres_postgresId_fk", "name": "backup_postgresId_postgres_postgresId_fk",
"tableFrom": "backup", "tableFrom": "backup",
"tableTo": "postgres",
"columnsFrom": [ "columnsFrom": [
"postgresId" "postgresId"
], ],
"tableTo": "postgres",
"columnsTo": [ "columnsTo": [
"postgresId" "postgresId"
], ],
"onDelete": "cascade", "onUpdate": "no action",
"onUpdate": "no action" "onDelete": "cascade"
}, },
"backup_mariadbId_mariadb_mariadbId_fk": { "backup_mariadbId_mariadb_mariadbId_fk": {
"name": "backup_mariadbId_mariadb_mariadbId_fk", "name": "backup_mariadbId_mariadb_mariadbId_fk",
"tableFrom": "backup", "tableFrom": "backup",
"tableTo": "mariadb",
"columnsFrom": [ "columnsFrom": [
"mariadbId" "mariadbId"
], ],
"tableTo": "mariadb",
"columnsTo": [ "columnsTo": [
"mariadbId" "mariadbId"
], ],
"onDelete": "cascade", "onUpdate": "no action",
"onUpdate": "no action" "onDelete": "cascade"
}, },
"backup_mysqlId_mysql_mysqlId_fk": { "backup_mysqlId_mysql_mysqlId_fk": {
"name": "backup_mysqlId_mysql_mysqlId_fk", "name": "backup_mysqlId_mysql_mysqlId_fk",
"tableFrom": "backup", "tableFrom": "backup",
"tableTo": "mysql",
"columnsFrom": [ "columnsFrom": [
"mysqlId" "mysqlId"
], ],
"tableTo": "mysql",
"columnsTo": [ "columnsTo": [
"mysqlId" "mysqlId"
], ],
"onDelete": "cascade", "onUpdate": "no action",
"onUpdate": "no action" "onDelete": "cascade"
}, },
"backup_mongoId_mongo_mongoId_fk": { "backup_mongoId_mongo_mongoId_fk": {
"name": "backup_mongoId_mongo_mongoId_fk", "name": "backup_mongoId_mongo_mongoId_fk",
"tableFrom": "backup", "tableFrom": "backup",
"tableTo": "mongo",
"columnsFrom": [ "columnsFrom": [
"mongoId" "mongoId"
], ],
"tableTo": "mongo",
"columnsTo": [ "columnsTo": [
"mongoId" "mongoId"
], ],
"onDelete": "cascade", "onUpdate": "no action",
"onUpdate": "no action" "onDelete": "cascade"
} }
}, },
"compositePrimaryKeys": {}, "compositePrimaryKeys": {},
"uniqueConstraints": {} "uniqueConstraints": {}
}, },
"destination": { "public.destination": {
"name": "destination", "name": "destination",
"schema": "", "schema": "",
"columns": { "columns": {
@@ -1474,21 +1472,21 @@
"destination_adminId_admin_adminId_fk": { "destination_adminId_admin_adminId_fk": {
"name": "destination_adminId_admin_adminId_fk", "name": "destination_adminId_admin_adminId_fk",
"tableFrom": "destination", "tableFrom": "destination",
"tableTo": "admin",
"columnsFrom": [ "columnsFrom": [
"adminId" "adminId"
], ],
"tableTo": "admin",
"columnsTo": [ "columnsTo": [
"adminId" "adminId"
], ],
"onDelete": "cascade", "onUpdate": "no action",
"onUpdate": "no action" "onDelete": "cascade"
} }
}, },
"compositePrimaryKeys": {}, "compositePrimaryKeys": {},
"uniqueConstraints": {} "uniqueConstraints": {}
}, },
"deployment": { "public.deployment": {
"name": "deployment", "name": "deployment",
"schema": "", "schema": "",
"columns": { "columns": {
@@ -1535,21 +1533,21 @@
"deployment_applicationId_application_applicationId_fk": { "deployment_applicationId_application_applicationId_fk": {
"name": "deployment_applicationId_application_applicationId_fk", "name": "deployment_applicationId_application_applicationId_fk",
"tableFrom": "deployment", "tableFrom": "deployment",
"tableTo": "application",
"columnsFrom": [ "columnsFrom": [
"applicationId" "applicationId"
], ],
"tableTo": "application",
"columnsTo": [ "columnsTo": [
"applicationId" "applicationId"
], ],
"onDelete": "cascade", "onUpdate": "no action",
"onUpdate": "no action" "onDelete": "cascade"
} }
}, },
"compositePrimaryKeys": {}, "compositePrimaryKeys": {},
"uniqueConstraints": {} "uniqueConstraints": {}
}, },
"mount": { "public.mount": {
"name": "mount", "name": "mount",
"schema": "", "schema": "",
"columns": { "columns": {
@@ -1638,86 +1636,86 @@
"mount_applicationId_application_applicationId_fk": { "mount_applicationId_application_applicationId_fk": {
"name": "mount_applicationId_application_applicationId_fk", "name": "mount_applicationId_application_applicationId_fk",
"tableFrom": "mount", "tableFrom": "mount",
"tableTo": "application",
"columnsFrom": [ "columnsFrom": [
"applicationId" "applicationId"
], ],
"tableTo": "application",
"columnsTo": [ "columnsTo": [
"applicationId" "applicationId"
], ],
"onDelete": "cascade", "onUpdate": "no action",
"onUpdate": "no action" "onDelete": "cascade"
}, },
"mount_postgresId_postgres_postgresId_fk": { "mount_postgresId_postgres_postgresId_fk": {
"name": "mount_postgresId_postgres_postgresId_fk", "name": "mount_postgresId_postgres_postgresId_fk",
"tableFrom": "mount", "tableFrom": "mount",
"tableTo": "postgres",
"columnsFrom": [ "columnsFrom": [
"postgresId" "postgresId"
], ],
"tableTo": "postgres",
"columnsTo": [ "columnsTo": [
"postgresId" "postgresId"
], ],
"onDelete": "cascade", "onUpdate": "no action",
"onUpdate": "no action" "onDelete": "cascade"
}, },
"mount_mariadbId_mariadb_mariadbId_fk": { "mount_mariadbId_mariadb_mariadbId_fk": {
"name": "mount_mariadbId_mariadb_mariadbId_fk", "name": "mount_mariadbId_mariadb_mariadbId_fk",
"tableFrom": "mount", "tableFrom": "mount",
"tableTo": "mariadb",
"columnsFrom": [ "columnsFrom": [
"mariadbId" "mariadbId"
], ],
"tableTo": "mariadb",
"columnsTo": [ "columnsTo": [
"mariadbId" "mariadbId"
], ],
"onDelete": "cascade", "onUpdate": "no action",
"onUpdate": "no action" "onDelete": "cascade"
}, },
"mount_mongoId_mongo_mongoId_fk": { "mount_mongoId_mongo_mongoId_fk": {
"name": "mount_mongoId_mongo_mongoId_fk", "name": "mount_mongoId_mongo_mongoId_fk",
"tableFrom": "mount", "tableFrom": "mount",
"tableTo": "mongo",
"columnsFrom": [ "columnsFrom": [
"mongoId" "mongoId"
], ],
"tableTo": "mongo",
"columnsTo": [ "columnsTo": [
"mongoId" "mongoId"
], ],
"onDelete": "cascade", "onUpdate": "no action",
"onUpdate": "no action" "onDelete": "cascade"
}, },
"mount_mysqlId_mysql_mysqlId_fk": { "mount_mysqlId_mysql_mysqlId_fk": {
"name": "mount_mysqlId_mysql_mysqlId_fk", "name": "mount_mysqlId_mysql_mysqlId_fk",
"tableFrom": "mount", "tableFrom": "mount",
"tableTo": "mysql",
"columnsFrom": [ "columnsFrom": [
"mysqlId" "mysqlId"
], ],
"tableTo": "mysql",
"columnsTo": [ "columnsTo": [
"mysqlId" "mysqlId"
], ],
"onDelete": "cascade", "onUpdate": "no action",
"onUpdate": "no action" "onDelete": "cascade"
}, },
"mount_redisId_redis_redisId_fk": { "mount_redisId_redis_redisId_fk": {
"name": "mount_redisId_redis_redisId_fk", "name": "mount_redisId_redis_redisId_fk",
"tableFrom": "mount", "tableFrom": "mount",
"tableTo": "redis",
"columnsFrom": [ "columnsFrom": [
"redisId" "redisId"
], ],
"tableTo": "redis",
"columnsTo": [ "columnsTo": [
"redisId" "redisId"
], ],
"onDelete": "cascade", "onUpdate": "no action",
"onUpdate": "no action" "onDelete": "cascade"
} }
}, },
"compositePrimaryKeys": {}, "compositePrimaryKeys": {},
"uniqueConstraints": {} "uniqueConstraints": {}
}, },
"certificate": { "public.certificate": {
"name": "certificate", "name": "certificate",
"schema": "", "schema": "",
"columns": { "columns": {
@@ -1764,14 +1762,14 @@
"uniqueConstraints": { "uniqueConstraints": {
"certificate_certificatePath_unique": { "certificate_certificatePath_unique": {
"name": "certificate_certificatePath_unique", "name": "certificate_certificatePath_unique",
"nullsNotDistinct": false,
"columns": [ "columns": [
"certificatePath" "certificatePath"
] ],
"nullsNotDistinct": false
} }
} }
}, },
"session": { "public.session": {
"name": "session", "name": "session",
"schema": "", "schema": "",
"columns": { "columns": {
@@ -1799,21 +1797,21 @@
"session_user_id_auth_id_fk": { "session_user_id_auth_id_fk": {
"name": "session_user_id_auth_id_fk", "name": "session_user_id_auth_id_fk",
"tableFrom": "session", "tableFrom": "session",
"tableTo": "auth",
"columnsFrom": [ "columnsFrom": [
"user_id" "user_id"
], ],
"tableTo": "auth",
"columnsTo": [ "columnsTo": [
"id" "id"
], ],
"onDelete": "cascade", "onUpdate": "no action",
"onUpdate": "no action" "onDelete": "cascade"
} }
}, },
"compositePrimaryKeys": {}, "compositePrimaryKeys": {},
"uniqueConstraints": {} "uniqueConstraints": {}
}, },
"redirect": { "public.redirect": {
"name": "redirect", "name": "redirect",
"schema": "", "schema": "",
"columns": { "columns": {
@@ -1866,21 +1864,21 @@
"redirect_applicationId_application_applicationId_fk": { "redirect_applicationId_application_applicationId_fk": {
"name": "redirect_applicationId_application_applicationId_fk", "name": "redirect_applicationId_application_applicationId_fk",
"tableFrom": "redirect", "tableFrom": "redirect",
"tableTo": "application",
"columnsFrom": [ "columnsFrom": [
"applicationId" "applicationId"
], ],
"tableTo": "application",
"columnsTo": [ "columnsTo": [
"applicationId" "applicationId"
], ],
"onDelete": "cascade", "onUpdate": "no action",
"onUpdate": "no action" "onDelete": "cascade"
} }
}, },
"compositePrimaryKeys": {}, "compositePrimaryKeys": {},
"uniqueConstraints": {} "uniqueConstraints": {}
}, },
"security": { "public.security": {
"name": "security", "name": "security",
"schema": "", "schema": "",
"columns": { "columns": {
@@ -1920,30 +1918,30 @@
"security_applicationId_application_applicationId_fk": { "security_applicationId_application_applicationId_fk": {
"name": "security_applicationId_application_applicationId_fk", "name": "security_applicationId_application_applicationId_fk",
"tableFrom": "security", "tableFrom": "security",
"tableTo": "application",
"columnsFrom": [ "columnsFrom": [
"applicationId" "applicationId"
], ],
"tableTo": "application",
"columnsTo": [ "columnsTo": [
"applicationId" "applicationId"
], ],
"onDelete": "cascade", "onUpdate": "no action",
"onUpdate": "no action" "onDelete": "cascade"
} }
}, },
"compositePrimaryKeys": {}, "compositePrimaryKeys": {},
"uniqueConstraints": { "uniqueConstraints": {
"security_username_applicationId_unique": { "security_username_applicationId_unique": {
"name": "security_username_applicationId_unique", "name": "security_username_applicationId_unique",
"nullsNotDistinct": false,
"columns": [ "columns": [
"username", "username",
"applicationId" "applicationId"
] ],
"nullsNotDistinct": false
} }
} }
}, },
"port": { "public.port": {
"name": "port", "name": "port",
"schema": "", "schema": "",
"columns": { "columns": {
@@ -1983,21 +1981,21 @@
"port_applicationId_application_applicationId_fk": { "port_applicationId_application_applicationId_fk": {
"name": "port_applicationId_application_applicationId_fk", "name": "port_applicationId_application_applicationId_fk",
"tableFrom": "port", "tableFrom": "port",
"tableTo": "application",
"columnsFrom": [ "columnsFrom": [
"applicationId" "applicationId"
], ],
"tableTo": "application",
"columnsTo": [ "columnsTo": [
"applicationId" "applicationId"
], ],
"onDelete": "cascade", "onUpdate": "no action",
"onUpdate": "no action" "onDelete": "cascade"
} }
}, },
"compositePrimaryKeys": {}, "compositePrimaryKeys": {},
"uniqueConstraints": {} "uniqueConstraints": {}
}, },
"redis": { "public.redis": {
"name": "redis", "name": "redis",
"schema": "", "schema": "",
"columns": { "columns": {
@@ -2104,118 +2102,130 @@
"redis_projectId_project_projectId_fk": { "redis_projectId_project_projectId_fk": {
"name": "redis_projectId_project_projectId_fk", "name": "redis_projectId_project_projectId_fk",
"tableFrom": "redis", "tableFrom": "redis",
"tableTo": "project",
"columnsFrom": [ "columnsFrom": [
"projectId" "projectId"
], ],
"tableTo": "project",
"columnsTo": [ "columnsTo": [
"projectId" "projectId"
], ],
"onDelete": "cascade", "onUpdate": "no action",
"onUpdate": "no action" "onDelete": "cascade"
} }
}, },
"compositePrimaryKeys": {}, "compositePrimaryKeys": {},
"uniqueConstraints": { "uniqueConstraints": {
"redis_appName_unique": { "redis_appName_unique": {
"name": "redis_appName_unique", "name": "redis_appName_unique",
"nullsNotDistinct": false,
"columns": [ "columns": [
"appName" "appName"
] ],
"nullsNotDistinct": false
} }
} }
} }
}, },
"enums": { "enums": {
"buildType": { "public.buildType": {
"name": "buildType", "name": "buildType",
"values": { "schema": "public",
"dockerfile": "dockerfile", "values": [
"heroku_buildpacks": "heroku_buildpacks", "dockerfile",
"paketo_buildpacks": "paketo_buildpacks", "heroku_buildpacks",
"nixpacks": "nixpacks" "paketo_buildpacks",
} "nixpacks"
]
}, },
"sourceType": { "public.sourceType": {
"name": "sourceType", "name": "sourceType",
"values": { "schema": "public",
"docker": "docker", "values": [
"git": "git", "docker",
"github": "github" "git",
} "github"
]
}, },
"Roles": { "public.Roles": {
"name": "Roles", "name": "Roles",
"values": { "schema": "public",
"admin": "admin", "values": [
"user": "user" "admin",
} "user"
]
}, },
"databaseType": { "public.databaseType": {
"name": "databaseType", "name": "databaseType",
"values": { "schema": "public",
"postgres": "postgres", "values": [
"mariadb": "mariadb", "postgres",
"mysql": "mysql", "mariadb",
"mongo": "mongo" "mysql",
} "mongo"
]
}, },
"deploymentStatus": { "public.deploymentStatus": {
"name": "deploymentStatus", "name": "deploymentStatus",
"values": { "schema": "public",
"running": "running", "values": [
"done": "done", "running",
"error": "error" "done",
} "error"
]
}, },
"mountType": { "public.mountType": {
"name": "mountType", "name": "mountType",
"values": { "schema": "public",
"bind": "bind", "values": [
"volume": "volume", "bind",
"file": "file" "volume",
} "file"
]
}, },
"serviceType": { "public.serviceType": {
"name": "serviceType", "name": "serviceType",
"values": { "schema": "public",
"application": "application", "values": [
"postgres": "postgres", "application",
"mysql": "mysql", "postgres",
"mariadb": "mariadb", "mysql",
"mongo": "mongo", "mariadb",
"redis": "redis" "mongo",
} "redis"
]
}, },
"protocolType": { "public.protocolType": {
"name": "protocolType", "name": "protocolType",
"values": { "schema": "public",
"tcp": "tcp", "values": [
"udp": "udp" "tcp",
} "udp"
]
}, },
"applicationStatus": { "public.applicationStatus": {
"name": "applicationStatus", "name": "applicationStatus",
"values": { "schema": "public",
"idle": "idle", "values": [
"running": "running", "idle",
"done": "done", "running",
"error": "error" "done",
} "error"
]
}, },
"certificateType": { "public.certificateType": {
"name": "certificateType", "name": "certificateType",
"values": { "schema": "public",
"letsencrypt": "letsencrypt", "values": [
"none": "none" "letsencrypt",
} "none"
]
} }
}, },
"schemas": {}, "schemas": {},
"_meta": { "_meta": {
"columns": {},
"schemas": {}, "schemas": {},
"tables": {} "tables": {},
} "columns": {}
},
"id": "665483bd-5123-4c2b-beef-bfa9b91b9356",
"prevId": "3a4dfad7-ae33-4ae3-b60e-4f40f44f5652"
} }

View File

@@ -1,10 +1,8 @@
{ {
"id": "5a1d3f2b-9c31-4125-9645-015170550b51", "version": "6",
"prevId": "665483bd-5123-4c2b-beef-bfa9b91b9356", "dialect": "postgresql",
"version": "5",
"dialect": "pg",
"tables": { "tables": {
"application": { "public.application": {
"name": "application", "name": "application",
"schema": "", "schema": "",
"columns": { "columns": {
@@ -234,29 +232,29 @@
"application_projectId_project_projectId_fk": { "application_projectId_project_projectId_fk": {
"name": "application_projectId_project_projectId_fk", "name": "application_projectId_project_projectId_fk",
"tableFrom": "application", "tableFrom": "application",
"tableTo": "project",
"columnsFrom": [ "columnsFrom": [
"projectId" "projectId"
], ],
"tableTo": "project",
"columnsTo": [ "columnsTo": [
"projectId" "projectId"
], ],
"onDelete": "cascade", "onUpdate": "no action",
"onUpdate": "no action" "onDelete": "cascade"
} }
}, },
"compositePrimaryKeys": {}, "compositePrimaryKeys": {},
"uniqueConstraints": { "uniqueConstraints": {
"application_appName_unique": { "application_appName_unique": {
"name": "application_appName_unique", "name": "application_appName_unique",
"nullsNotDistinct": false,
"columns": [ "columns": [
"appName" "appName"
] ],
"nullsNotDistinct": false
} }
} }
}, },
"postgres": { "public.postgres": {
"name": "postgres", "name": "postgres",
"schema": "", "schema": "",
"columns": { "columns": {
@@ -375,29 +373,29 @@
"postgres_projectId_project_projectId_fk": { "postgres_projectId_project_projectId_fk": {
"name": "postgres_projectId_project_projectId_fk", "name": "postgres_projectId_project_projectId_fk",
"tableFrom": "postgres", "tableFrom": "postgres",
"tableTo": "project",
"columnsFrom": [ "columnsFrom": [
"projectId" "projectId"
], ],
"tableTo": "project",
"columnsTo": [ "columnsTo": [
"projectId" "projectId"
], ],
"onDelete": "cascade", "onUpdate": "no action",
"onUpdate": "no action" "onDelete": "cascade"
} }
}, },
"compositePrimaryKeys": {}, "compositePrimaryKeys": {},
"uniqueConstraints": { "uniqueConstraints": {
"postgres_appName_unique": { "postgres_appName_unique": {
"name": "postgres_appName_unique", "name": "postgres_appName_unique",
"nullsNotDistinct": false,
"columns": [ "columns": [
"appName" "appName"
] ],
"nullsNotDistinct": false
} }
} }
}, },
"user": { "public.user": {
"name": "user", "name": "user",
"schema": "", "schema": "",
"columns": { "columns": {
@@ -506,34 +504,34 @@
"user_adminId_admin_adminId_fk": { "user_adminId_admin_adminId_fk": {
"name": "user_adminId_admin_adminId_fk", "name": "user_adminId_admin_adminId_fk",
"tableFrom": "user", "tableFrom": "user",
"tableTo": "admin",
"columnsFrom": [ "columnsFrom": [
"adminId" "adminId"
], ],
"tableTo": "admin",
"columnsTo": [ "columnsTo": [
"adminId" "adminId"
], ],
"onDelete": "cascade", "onUpdate": "no action",
"onUpdate": "no action" "onDelete": "cascade"
}, },
"user_authId_auth_id_fk": { "user_authId_auth_id_fk": {
"name": "user_authId_auth_id_fk", "name": "user_authId_auth_id_fk",
"tableFrom": "user", "tableFrom": "user",
"tableTo": "auth",
"columnsFrom": [ "columnsFrom": [
"authId" "authId"
], ],
"tableTo": "auth",
"columnsTo": [ "columnsTo": [
"id" "id"
], ],
"onDelete": "cascade", "onUpdate": "no action",
"onUpdate": "no action" "onDelete": "cascade"
} }
}, },
"compositePrimaryKeys": {}, "compositePrimaryKeys": {},
"uniqueConstraints": {} "uniqueConstraints": {}
}, },
"admin": { "public.admin": {
"name": "admin", "name": "admin",
"schema": "", "schema": "",
"columns": { "columns": {
@@ -635,21 +633,21 @@
"admin_authId_auth_id_fk": { "admin_authId_auth_id_fk": {
"name": "admin_authId_auth_id_fk", "name": "admin_authId_auth_id_fk",
"tableFrom": "admin", "tableFrom": "admin",
"tableTo": "auth",
"columnsFrom": [ "columnsFrom": [
"authId" "authId"
], ],
"tableTo": "auth",
"columnsTo": [ "columnsTo": [
"id" "id"
], ],
"onDelete": "cascade", "onUpdate": "no action",
"onUpdate": "no action" "onDelete": "cascade"
} }
}, },
"compositePrimaryKeys": {}, "compositePrimaryKeys": {},
"uniqueConstraints": {} "uniqueConstraints": {}
}, },
"auth": { "public.auth": {
"name": "auth", "name": "auth",
"schema": "", "schema": "",
"columns": { "columns": {
@@ -709,14 +707,14 @@
"uniqueConstraints": { "uniqueConstraints": {
"auth_email_unique": { "auth_email_unique": {
"name": "auth_email_unique", "name": "auth_email_unique",
"nullsNotDistinct": false,
"columns": [ "columns": [
"email" "email"
] ],
"nullsNotDistinct": false
} }
} }
}, },
"project": { "public.project": {
"name": "project", "name": "project",
"schema": "", "schema": "",
"columns": { "columns": {
@@ -756,21 +754,21 @@
"project_adminId_admin_adminId_fk": { "project_adminId_admin_adminId_fk": {
"name": "project_adminId_admin_adminId_fk", "name": "project_adminId_admin_adminId_fk",
"tableFrom": "project", "tableFrom": "project",
"tableTo": "admin",
"columnsFrom": [ "columnsFrom": [
"adminId" "adminId"
], ],
"tableTo": "admin",
"columnsTo": [ "columnsTo": [
"adminId" "adminId"
], ],
"onDelete": "cascade", "onUpdate": "no action",
"onUpdate": "no action" "onDelete": "cascade"
} }
}, },
"compositePrimaryKeys": {}, "compositePrimaryKeys": {},
"uniqueConstraints": {} "uniqueConstraints": {}
}, },
"domain": { "public.domain": {
"name": "domain", "name": "domain",
"schema": "", "schema": "",
"columns": { "columns": {
@@ -838,21 +836,21 @@
"domain_applicationId_application_applicationId_fk": { "domain_applicationId_application_applicationId_fk": {
"name": "domain_applicationId_application_applicationId_fk", "name": "domain_applicationId_application_applicationId_fk",
"tableFrom": "domain", "tableFrom": "domain",
"tableTo": "application",
"columnsFrom": [ "columnsFrom": [
"applicationId" "applicationId"
], ],
"tableTo": "application",
"columnsTo": [ "columnsTo": [
"applicationId" "applicationId"
], ],
"onDelete": "cascade", "onUpdate": "no action",
"onUpdate": "no action" "onDelete": "cascade"
} }
}, },
"compositePrimaryKeys": {}, "compositePrimaryKeys": {},
"uniqueConstraints": {} "uniqueConstraints": {}
}, },
"mariadb": { "public.mariadb": {
"name": "mariadb", "name": "mariadb",
"schema": "", "schema": "",
"columns": { "columns": {
@@ -977,29 +975,29 @@
"mariadb_projectId_project_projectId_fk": { "mariadb_projectId_project_projectId_fk": {
"name": "mariadb_projectId_project_projectId_fk", "name": "mariadb_projectId_project_projectId_fk",
"tableFrom": "mariadb", "tableFrom": "mariadb",
"tableTo": "project",
"columnsFrom": [ "columnsFrom": [
"projectId" "projectId"
], ],
"tableTo": "project",
"columnsTo": [ "columnsTo": [
"projectId" "projectId"
], ],
"onDelete": "cascade", "onUpdate": "no action",
"onUpdate": "no action" "onDelete": "cascade"
} }
}, },
"compositePrimaryKeys": {}, "compositePrimaryKeys": {},
"uniqueConstraints": { "uniqueConstraints": {
"mariadb_appName_unique": { "mariadb_appName_unique": {
"name": "mariadb_appName_unique", "name": "mariadb_appName_unique",
"nullsNotDistinct": false,
"columns": [ "columns": [
"appName" "appName"
] ],
"nullsNotDistinct": false
} }
} }
}, },
"mongo": { "public.mongo": {
"name": "mongo", "name": "mongo",
"schema": "", "schema": "",
"columns": { "columns": {
@@ -1112,29 +1110,29 @@
"mongo_projectId_project_projectId_fk": { "mongo_projectId_project_projectId_fk": {
"name": "mongo_projectId_project_projectId_fk", "name": "mongo_projectId_project_projectId_fk",
"tableFrom": "mongo", "tableFrom": "mongo",
"tableTo": "project",
"columnsFrom": [ "columnsFrom": [
"projectId" "projectId"
], ],
"tableTo": "project",
"columnsTo": [ "columnsTo": [
"projectId" "projectId"
], ],
"onDelete": "cascade", "onUpdate": "no action",
"onUpdate": "no action" "onDelete": "cascade"
} }
}, },
"compositePrimaryKeys": {}, "compositePrimaryKeys": {},
"uniqueConstraints": { "uniqueConstraints": {
"mongo_appName_unique": { "mongo_appName_unique": {
"name": "mongo_appName_unique", "name": "mongo_appName_unique",
"nullsNotDistinct": false,
"columns": [ "columns": [
"appName" "appName"
] ],
"nullsNotDistinct": false
} }
} }
}, },
"mysql": { "public.mysql": {
"name": "mysql", "name": "mysql",
"schema": "", "schema": "",
"columns": { "columns": {
@@ -1259,29 +1257,29 @@
"mysql_projectId_project_projectId_fk": { "mysql_projectId_project_projectId_fk": {
"name": "mysql_projectId_project_projectId_fk", "name": "mysql_projectId_project_projectId_fk",
"tableFrom": "mysql", "tableFrom": "mysql",
"tableTo": "project",
"columnsFrom": [ "columnsFrom": [
"projectId" "projectId"
], ],
"tableTo": "project",
"columnsTo": [ "columnsTo": [
"projectId" "projectId"
], ],
"onDelete": "cascade", "onUpdate": "no action",
"onUpdate": "no action" "onDelete": "cascade"
} }
}, },
"compositePrimaryKeys": {}, "compositePrimaryKeys": {},
"uniqueConstraints": { "uniqueConstraints": {
"mysql_appName_unique": { "mysql_appName_unique": {
"name": "mysql_appName_unique", "name": "mysql_appName_unique",
"nullsNotDistinct": false,
"columns": [ "columns": [
"appName" "appName"
] ],
"nullsNotDistinct": false
} }
} }
}, },
"backup": { "public.backup": {
"name": "backup", "name": "backup",
"schema": "", "schema": "",
"columns": { "columns": {
@@ -1357,73 +1355,73 @@
"backup_destinationId_destination_destinationId_fk": { "backup_destinationId_destination_destinationId_fk": {
"name": "backup_destinationId_destination_destinationId_fk", "name": "backup_destinationId_destination_destinationId_fk",
"tableFrom": "backup", "tableFrom": "backup",
"tableTo": "destination",
"columnsFrom": [ "columnsFrom": [
"destinationId" "destinationId"
], ],
"tableTo": "destination",
"columnsTo": [ "columnsTo": [
"destinationId" "destinationId"
], ],
"onDelete": "cascade", "onUpdate": "no action",
"onUpdate": "no action" "onDelete": "cascade"
}, },
"backup_postgresId_postgres_postgresId_fk": { "backup_postgresId_postgres_postgresId_fk": {
"name": "backup_postgresId_postgres_postgresId_fk", "name": "backup_postgresId_postgres_postgresId_fk",
"tableFrom": "backup", "tableFrom": "backup",
"tableTo": "postgres",
"columnsFrom": [ "columnsFrom": [
"postgresId" "postgresId"
], ],
"tableTo": "postgres",
"columnsTo": [ "columnsTo": [
"postgresId" "postgresId"
], ],
"onDelete": "cascade", "onUpdate": "no action",
"onUpdate": "no action" "onDelete": "cascade"
}, },
"backup_mariadbId_mariadb_mariadbId_fk": { "backup_mariadbId_mariadb_mariadbId_fk": {
"name": "backup_mariadbId_mariadb_mariadbId_fk", "name": "backup_mariadbId_mariadb_mariadbId_fk",
"tableFrom": "backup", "tableFrom": "backup",
"tableTo": "mariadb",
"columnsFrom": [ "columnsFrom": [
"mariadbId" "mariadbId"
], ],
"tableTo": "mariadb",
"columnsTo": [ "columnsTo": [
"mariadbId" "mariadbId"
], ],
"onDelete": "cascade", "onUpdate": "no action",
"onUpdate": "no action" "onDelete": "cascade"
}, },
"backup_mysqlId_mysql_mysqlId_fk": { "backup_mysqlId_mysql_mysqlId_fk": {
"name": "backup_mysqlId_mysql_mysqlId_fk", "name": "backup_mysqlId_mysql_mysqlId_fk",
"tableFrom": "backup", "tableFrom": "backup",
"tableTo": "mysql",
"columnsFrom": [ "columnsFrom": [
"mysqlId" "mysqlId"
], ],
"tableTo": "mysql",
"columnsTo": [ "columnsTo": [
"mysqlId" "mysqlId"
], ],
"onDelete": "cascade", "onUpdate": "no action",
"onUpdate": "no action" "onDelete": "cascade"
}, },
"backup_mongoId_mongo_mongoId_fk": { "backup_mongoId_mongo_mongoId_fk": {
"name": "backup_mongoId_mongo_mongoId_fk", "name": "backup_mongoId_mongo_mongoId_fk",
"tableFrom": "backup", "tableFrom": "backup",
"tableTo": "mongo",
"columnsFrom": [ "columnsFrom": [
"mongoId" "mongoId"
], ],
"tableTo": "mongo",
"columnsTo": [ "columnsTo": [
"mongoId" "mongoId"
], ],
"onDelete": "cascade", "onUpdate": "no action",
"onUpdate": "no action" "onDelete": "cascade"
} }
}, },
"compositePrimaryKeys": {}, "compositePrimaryKeys": {},
"uniqueConstraints": {} "uniqueConstraints": {}
}, },
"destination": { "public.destination": {
"name": "destination", "name": "destination",
"schema": "", "schema": "",
"columns": { "columns": {
@@ -1481,21 +1479,21 @@
"destination_adminId_admin_adminId_fk": { "destination_adminId_admin_adminId_fk": {
"name": "destination_adminId_admin_adminId_fk", "name": "destination_adminId_admin_adminId_fk",
"tableFrom": "destination", "tableFrom": "destination",
"tableTo": "admin",
"columnsFrom": [ "columnsFrom": [
"adminId" "adminId"
], ],
"tableTo": "admin",
"columnsTo": [ "columnsTo": [
"adminId" "adminId"
], ],
"onDelete": "cascade", "onUpdate": "no action",
"onUpdate": "no action" "onDelete": "cascade"
} }
}, },
"compositePrimaryKeys": {}, "compositePrimaryKeys": {},
"uniqueConstraints": {} "uniqueConstraints": {}
}, },
"deployment": { "public.deployment": {
"name": "deployment", "name": "deployment",
"schema": "", "schema": "",
"columns": { "columns": {
@@ -1542,21 +1540,21 @@
"deployment_applicationId_application_applicationId_fk": { "deployment_applicationId_application_applicationId_fk": {
"name": "deployment_applicationId_application_applicationId_fk", "name": "deployment_applicationId_application_applicationId_fk",
"tableFrom": "deployment", "tableFrom": "deployment",
"tableTo": "application",
"columnsFrom": [ "columnsFrom": [
"applicationId" "applicationId"
], ],
"tableTo": "application",
"columnsTo": [ "columnsTo": [
"applicationId" "applicationId"
], ],
"onDelete": "cascade", "onUpdate": "no action",
"onUpdate": "no action" "onDelete": "cascade"
} }
}, },
"compositePrimaryKeys": {}, "compositePrimaryKeys": {},
"uniqueConstraints": {} "uniqueConstraints": {}
}, },
"mount": { "public.mount": {
"name": "mount", "name": "mount",
"schema": "", "schema": "",
"columns": { "columns": {
@@ -1645,86 +1643,86 @@
"mount_applicationId_application_applicationId_fk": { "mount_applicationId_application_applicationId_fk": {
"name": "mount_applicationId_application_applicationId_fk", "name": "mount_applicationId_application_applicationId_fk",
"tableFrom": "mount", "tableFrom": "mount",
"tableTo": "application",
"columnsFrom": [ "columnsFrom": [
"applicationId" "applicationId"
], ],
"tableTo": "application",
"columnsTo": [ "columnsTo": [
"applicationId" "applicationId"
], ],
"onDelete": "cascade", "onUpdate": "no action",
"onUpdate": "no action" "onDelete": "cascade"
}, },
"mount_postgresId_postgres_postgresId_fk": { "mount_postgresId_postgres_postgresId_fk": {
"name": "mount_postgresId_postgres_postgresId_fk", "name": "mount_postgresId_postgres_postgresId_fk",
"tableFrom": "mount", "tableFrom": "mount",
"tableTo": "postgres",
"columnsFrom": [ "columnsFrom": [
"postgresId" "postgresId"
], ],
"tableTo": "postgres",
"columnsTo": [ "columnsTo": [
"postgresId" "postgresId"
], ],
"onDelete": "cascade", "onUpdate": "no action",
"onUpdate": "no action" "onDelete": "cascade"
}, },
"mount_mariadbId_mariadb_mariadbId_fk": { "mount_mariadbId_mariadb_mariadbId_fk": {
"name": "mount_mariadbId_mariadb_mariadbId_fk", "name": "mount_mariadbId_mariadb_mariadbId_fk",
"tableFrom": "mount", "tableFrom": "mount",
"tableTo": "mariadb",
"columnsFrom": [ "columnsFrom": [
"mariadbId" "mariadbId"
], ],
"tableTo": "mariadb",
"columnsTo": [ "columnsTo": [
"mariadbId" "mariadbId"
], ],
"onDelete": "cascade", "onUpdate": "no action",
"onUpdate": "no action" "onDelete": "cascade"
}, },
"mount_mongoId_mongo_mongoId_fk": { "mount_mongoId_mongo_mongoId_fk": {
"name": "mount_mongoId_mongo_mongoId_fk", "name": "mount_mongoId_mongo_mongoId_fk",
"tableFrom": "mount", "tableFrom": "mount",
"tableTo": "mongo",
"columnsFrom": [ "columnsFrom": [
"mongoId" "mongoId"
], ],
"tableTo": "mongo",
"columnsTo": [ "columnsTo": [
"mongoId" "mongoId"
], ],
"onDelete": "cascade", "onUpdate": "no action",
"onUpdate": "no action" "onDelete": "cascade"
}, },
"mount_mysqlId_mysql_mysqlId_fk": { "mount_mysqlId_mysql_mysqlId_fk": {
"name": "mount_mysqlId_mysql_mysqlId_fk", "name": "mount_mysqlId_mysql_mysqlId_fk",
"tableFrom": "mount", "tableFrom": "mount",
"tableTo": "mysql",
"columnsFrom": [ "columnsFrom": [
"mysqlId" "mysqlId"
], ],
"tableTo": "mysql",
"columnsTo": [ "columnsTo": [
"mysqlId" "mysqlId"
], ],
"onDelete": "cascade", "onUpdate": "no action",
"onUpdate": "no action" "onDelete": "cascade"
}, },
"mount_redisId_redis_redisId_fk": { "mount_redisId_redis_redisId_fk": {
"name": "mount_redisId_redis_redisId_fk", "name": "mount_redisId_redis_redisId_fk",
"tableFrom": "mount", "tableFrom": "mount",
"tableTo": "redis",
"columnsFrom": [ "columnsFrom": [
"redisId" "redisId"
], ],
"tableTo": "redis",
"columnsTo": [ "columnsTo": [
"redisId" "redisId"
], ],
"onDelete": "cascade", "onUpdate": "no action",
"onUpdate": "no action" "onDelete": "cascade"
} }
}, },
"compositePrimaryKeys": {}, "compositePrimaryKeys": {},
"uniqueConstraints": {} "uniqueConstraints": {}
}, },
"certificate": { "public.certificate": {
"name": "certificate", "name": "certificate",
"schema": "", "schema": "",
"columns": { "columns": {
@@ -1771,14 +1769,14 @@
"uniqueConstraints": { "uniqueConstraints": {
"certificate_certificatePath_unique": { "certificate_certificatePath_unique": {
"name": "certificate_certificatePath_unique", "name": "certificate_certificatePath_unique",
"nullsNotDistinct": false,
"columns": [ "columns": [
"certificatePath" "certificatePath"
] ],
"nullsNotDistinct": false
} }
} }
}, },
"session": { "public.session": {
"name": "session", "name": "session",
"schema": "", "schema": "",
"columns": { "columns": {
@@ -1806,21 +1804,21 @@
"session_user_id_auth_id_fk": { "session_user_id_auth_id_fk": {
"name": "session_user_id_auth_id_fk", "name": "session_user_id_auth_id_fk",
"tableFrom": "session", "tableFrom": "session",
"tableTo": "auth",
"columnsFrom": [ "columnsFrom": [
"user_id" "user_id"
], ],
"tableTo": "auth",
"columnsTo": [ "columnsTo": [
"id" "id"
], ],
"onDelete": "cascade", "onUpdate": "no action",
"onUpdate": "no action" "onDelete": "cascade"
} }
}, },
"compositePrimaryKeys": {}, "compositePrimaryKeys": {},
"uniqueConstraints": {} "uniqueConstraints": {}
}, },
"redirect": { "public.redirect": {
"name": "redirect", "name": "redirect",
"schema": "", "schema": "",
"columns": { "columns": {
@@ -1873,21 +1871,21 @@
"redirect_applicationId_application_applicationId_fk": { "redirect_applicationId_application_applicationId_fk": {
"name": "redirect_applicationId_application_applicationId_fk", "name": "redirect_applicationId_application_applicationId_fk",
"tableFrom": "redirect", "tableFrom": "redirect",
"tableTo": "application",
"columnsFrom": [ "columnsFrom": [
"applicationId" "applicationId"
], ],
"tableTo": "application",
"columnsTo": [ "columnsTo": [
"applicationId" "applicationId"
], ],
"onDelete": "cascade", "onUpdate": "no action",
"onUpdate": "no action" "onDelete": "cascade"
} }
}, },
"compositePrimaryKeys": {}, "compositePrimaryKeys": {},
"uniqueConstraints": {} "uniqueConstraints": {}
}, },
"security": { "public.security": {
"name": "security", "name": "security",
"schema": "", "schema": "",
"columns": { "columns": {
@@ -1927,30 +1925,30 @@
"security_applicationId_application_applicationId_fk": { "security_applicationId_application_applicationId_fk": {
"name": "security_applicationId_application_applicationId_fk", "name": "security_applicationId_application_applicationId_fk",
"tableFrom": "security", "tableFrom": "security",
"tableTo": "application",
"columnsFrom": [ "columnsFrom": [
"applicationId" "applicationId"
], ],
"tableTo": "application",
"columnsTo": [ "columnsTo": [
"applicationId" "applicationId"
], ],
"onDelete": "cascade", "onUpdate": "no action",
"onUpdate": "no action" "onDelete": "cascade"
} }
}, },
"compositePrimaryKeys": {}, "compositePrimaryKeys": {},
"uniqueConstraints": { "uniqueConstraints": {
"security_username_applicationId_unique": { "security_username_applicationId_unique": {
"name": "security_username_applicationId_unique", "name": "security_username_applicationId_unique",
"nullsNotDistinct": false,
"columns": [ "columns": [
"username", "username",
"applicationId" "applicationId"
] ],
"nullsNotDistinct": false
} }
} }
}, },
"port": { "public.port": {
"name": "port", "name": "port",
"schema": "", "schema": "",
"columns": { "columns": {
@@ -1990,21 +1988,21 @@
"port_applicationId_application_applicationId_fk": { "port_applicationId_application_applicationId_fk": {
"name": "port_applicationId_application_applicationId_fk", "name": "port_applicationId_application_applicationId_fk",
"tableFrom": "port", "tableFrom": "port",
"tableTo": "application",
"columnsFrom": [ "columnsFrom": [
"applicationId" "applicationId"
], ],
"tableTo": "application",
"columnsTo": [ "columnsTo": [
"applicationId" "applicationId"
], ],
"onDelete": "cascade", "onUpdate": "no action",
"onUpdate": "no action" "onDelete": "cascade"
} }
}, },
"compositePrimaryKeys": {}, "compositePrimaryKeys": {},
"uniqueConstraints": {} "uniqueConstraints": {}
}, },
"redis": { "public.redis": {
"name": "redis", "name": "redis",
"schema": "", "schema": "",
"columns": { "columns": {
@@ -2111,118 +2109,130 @@
"redis_projectId_project_projectId_fk": { "redis_projectId_project_projectId_fk": {
"name": "redis_projectId_project_projectId_fk", "name": "redis_projectId_project_projectId_fk",
"tableFrom": "redis", "tableFrom": "redis",
"tableTo": "project",
"columnsFrom": [ "columnsFrom": [
"projectId" "projectId"
], ],
"tableTo": "project",
"columnsTo": [ "columnsTo": [
"projectId" "projectId"
], ],
"onDelete": "cascade", "onUpdate": "no action",
"onUpdate": "no action" "onDelete": "cascade"
} }
}, },
"compositePrimaryKeys": {}, "compositePrimaryKeys": {},
"uniqueConstraints": { "uniqueConstraints": {
"redis_appName_unique": { "redis_appName_unique": {
"name": "redis_appName_unique", "name": "redis_appName_unique",
"nullsNotDistinct": false,
"columns": [ "columns": [
"appName" "appName"
] ],
"nullsNotDistinct": false
} }
} }
} }
}, },
"enums": { "enums": {
"buildType": { "public.buildType": {
"name": "buildType", "name": "buildType",
"values": { "schema": "public",
"dockerfile": "dockerfile", "values": [
"heroku_buildpacks": "heroku_buildpacks", "dockerfile",
"paketo_buildpacks": "paketo_buildpacks", "heroku_buildpacks",
"nixpacks": "nixpacks" "paketo_buildpacks",
} "nixpacks"
]
}, },
"sourceType": { "public.sourceType": {
"name": "sourceType", "name": "sourceType",
"values": { "schema": "public",
"docker": "docker", "values": [
"git": "git", "docker",
"github": "github" "git",
} "github"
]
}, },
"Roles": { "public.Roles": {
"name": "Roles", "name": "Roles",
"values": { "schema": "public",
"admin": "admin", "values": [
"user": "user" "admin",
} "user"
]
}, },
"databaseType": { "public.databaseType": {
"name": "databaseType", "name": "databaseType",
"values": { "schema": "public",
"postgres": "postgres", "values": [
"mariadb": "mariadb", "postgres",
"mysql": "mysql", "mariadb",
"mongo": "mongo" "mysql",
} "mongo"
]
}, },
"deploymentStatus": { "public.deploymentStatus": {
"name": "deploymentStatus", "name": "deploymentStatus",
"values": { "schema": "public",
"running": "running", "values": [
"done": "done", "running",
"error": "error" "done",
} "error"
]
}, },
"mountType": { "public.mountType": {
"name": "mountType", "name": "mountType",
"values": { "schema": "public",
"bind": "bind", "values": [
"volume": "volume", "bind",
"file": "file" "volume",
} "file"
]
}, },
"serviceType": { "public.serviceType": {
"name": "serviceType", "name": "serviceType",
"values": { "schema": "public",
"application": "application", "values": [
"postgres": "postgres", "application",
"mysql": "mysql", "postgres",
"mariadb": "mariadb", "mysql",
"mongo": "mongo", "mariadb",
"redis": "redis" "mongo",
} "redis"
]
}, },
"protocolType": { "public.protocolType": {
"name": "protocolType", "name": "protocolType",
"values": { "schema": "public",
"tcp": "tcp", "values": [
"udp": "udp" "tcp",
} "udp"
]
}, },
"applicationStatus": { "public.applicationStatus": {
"name": "applicationStatus", "name": "applicationStatus",
"values": { "schema": "public",
"idle": "idle", "values": [
"running": "running", "idle",
"done": "done", "running",
"error": "error" "done",
} "error"
]
}, },
"certificateType": { "public.certificateType": {
"name": "certificateType", "name": "certificateType",
"values": { "schema": "public",
"letsencrypt": "letsencrypt", "values": [
"none": "none" "letsencrypt",
} "none"
]
} }
}, },
"schemas": {}, "schemas": {},
"_meta": { "_meta": {
"columns": {},
"schemas": {}, "schemas": {},
"tables": {} "tables": {},
} "columns": {}
},
"id": "5a1d3f2b-9c31-4125-9645-015170550b51",
"prevId": "665483bd-5123-4c2b-beef-bfa9b91b9356"
} }

View File

@@ -1,10 +1,8 @@
{ {
"id": "7bb4bbcf-791c-4888-919e-f74bc0528b5f", "version": "6",
"prevId": "5a1d3f2b-9c31-4125-9645-015170550b51", "dialect": "postgresql",
"version": "5",
"dialect": "pg",
"tables": { "tables": {
"application": { "public.application": {
"name": "application", "name": "application",
"schema": "", "schema": "",
"columns": { "columns": {
@@ -210,29 +208,29 @@
"application_projectId_project_projectId_fk": { "application_projectId_project_projectId_fk": {
"name": "application_projectId_project_projectId_fk", "name": "application_projectId_project_projectId_fk",
"tableFrom": "application", "tableFrom": "application",
"tableTo": "project",
"columnsFrom": [ "columnsFrom": [
"projectId" "projectId"
], ],
"tableTo": "project",
"columnsTo": [ "columnsTo": [
"projectId" "projectId"
], ],
"onDelete": "cascade", "onUpdate": "no action",
"onUpdate": "no action" "onDelete": "cascade"
} }
}, },
"compositePrimaryKeys": {}, "compositePrimaryKeys": {},
"uniqueConstraints": { "uniqueConstraints": {
"application_appName_unique": { "application_appName_unique": {
"name": "application_appName_unique", "name": "application_appName_unique",
"nullsNotDistinct": false,
"columns": [ "columns": [
"appName" "appName"
] ],
"nullsNotDistinct": false
} }
} }
}, },
"postgres": { "public.postgres": {
"name": "postgres", "name": "postgres",
"schema": "", "schema": "",
"columns": { "columns": {
@@ -351,29 +349,29 @@
"postgres_projectId_project_projectId_fk": { "postgres_projectId_project_projectId_fk": {
"name": "postgres_projectId_project_projectId_fk", "name": "postgres_projectId_project_projectId_fk",
"tableFrom": "postgres", "tableFrom": "postgres",
"tableTo": "project",
"columnsFrom": [ "columnsFrom": [
"projectId" "projectId"
], ],
"tableTo": "project",
"columnsTo": [ "columnsTo": [
"projectId" "projectId"
], ],
"onDelete": "cascade", "onUpdate": "no action",
"onUpdate": "no action" "onDelete": "cascade"
} }
}, },
"compositePrimaryKeys": {}, "compositePrimaryKeys": {},
"uniqueConstraints": { "uniqueConstraints": {
"postgres_appName_unique": { "postgres_appName_unique": {
"name": "postgres_appName_unique", "name": "postgres_appName_unique",
"nullsNotDistinct": false,
"columns": [ "columns": [
"appName" "appName"
] ],
"nullsNotDistinct": false
} }
} }
}, },
"user": { "public.user": {
"name": "user", "name": "user",
"schema": "", "schema": "",
"columns": { "columns": {
@@ -482,34 +480,34 @@
"user_adminId_admin_adminId_fk": { "user_adminId_admin_adminId_fk": {
"name": "user_adminId_admin_adminId_fk", "name": "user_adminId_admin_adminId_fk",
"tableFrom": "user", "tableFrom": "user",
"tableTo": "admin",
"columnsFrom": [ "columnsFrom": [
"adminId" "adminId"
], ],
"tableTo": "admin",
"columnsTo": [ "columnsTo": [
"adminId" "adminId"
], ],
"onDelete": "cascade", "onUpdate": "no action",
"onUpdate": "no action" "onDelete": "cascade"
}, },
"user_authId_auth_id_fk": { "user_authId_auth_id_fk": {
"name": "user_authId_auth_id_fk", "name": "user_authId_auth_id_fk",
"tableFrom": "user", "tableFrom": "user",
"tableTo": "auth",
"columnsFrom": [ "columnsFrom": [
"authId" "authId"
], ],
"tableTo": "auth",
"columnsTo": [ "columnsTo": [
"id" "id"
], ],
"onDelete": "cascade", "onUpdate": "no action",
"onUpdate": "no action" "onDelete": "cascade"
} }
}, },
"compositePrimaryKeys": {}, "compositePrimaryKeys": {},
"uniqueConstraints": {} "uniqueConstraints": {}
}, },
"admin": { "public.admin": {
"name": "admin", "name": "admin",
"schema": "", "schema": "",
"columns": { "columns": {
@@ -611,21 +609,21 @@
"admin_authId_auth_id_fk": { "admin_authId_auth_id_fk": {
"name": "admin_authId_auth_id_fk", "name": "admin_authId_auth_id_fk",
"tableFrom": "admin", "tableFrom": "admin",
"tableTo": "auth",
"columnsFrom": [ "columnsFrom": [
"authId" "authId"
], ],
"tableTo": "auth",
"columnsTo": [ "columnsTo": [
"id" "id"
], ],
"onDelete": "cascade", "onUpdate": "no action",
"onUpdate": "no action" "onDelete": "cascade"
} }
}, },
"compositePrimaryKeys": {}, "compositePrimaryKeys": {},
"uniqueConstraints": {} "uniqueConstraints": {}
}, },
"auth": { "public.auth": {
"name": "auth", "name": "auth",
"schema": "", "schema": "",
"columns": { "columns": {
@@ -685,14 +683,14 @@
"uniqueConstraints": { "uniqueConstraints": {
"auth_email_unique": { "auth_email_unique": {
"name": "auth_email_unique", "name": "auth_email_unique",
"nullsNotDistinct": false,
"columns": [ "columns": [
"email" "email"
] ],
"nullsNotDistinct": false
} }
} }
}, },
"project": { "public.project": {
"name": "project", "name": "project",
"schema": "", "schema": "",
"columns": { "columns": {
@@ -732,21 +730,21 @@
"project_adminId_admin_adminId_fk": { "project_adminId_admin_adminId_fk": {
"name": "project_adminId_admin_adminId_fk", "name": "project_adminId_admin_adminId_fk",
"tableFrom": "project", "tableFrom": "project",
"tableTo": "admin",
"columnsFrom": [ "columnsFrom": [
"adminId" "adminId"
], ],
"tableTo": "admin",
"columnsTo": [ "columnsTo": [
"adminId" "adminId"
], ],
"onDelete": "cascade", "onUpdate": "no action",
"onUpdate": "no action" "onDelete": "cascade"
} }
}, },
"compositePrimaryKeys": {}, "compositePrimaryKeys": {},
"uniqueConstraints": {} "uniqueConstraints": {}
}, },
"domain": { "public.domain": {
"name": "domain", "name": "domain",
"schema": "", "schema": "",
"columns": { "columns": {
@@ -814,21 +812,21 @@
"domain_applicationId_application_applicationId_fk": { "domain_applicationId_application_applicationId_fk": {
"name": "domain_applicationId_application_applicationId_fk", "name": "domain_applicationId_application_applicationId_fk",
"tableFrom": "domain", "tableFrom": "domain",
"tableTo": "application",
"columnsFrom": [ "columnsFrom": [
"applicationId" "applicationId"
], ],
"tableTo": "application",
"columnsTo": [ "columnsTo": [
"applicationId" "applicationId"
], ],
"onDelete": "cascade", "onUpdate": "no action",
"onUpdate": "no action" "onDelete": "cascade"
} }
}, },
"compositePrimaryKeys": {}, "compositePrimaryKeys": {},
"uniqueConstraints": {} "uniqueConstraints": {}
}, },
"mariadb": { "public.mariadb": {
"name": "mariadb", "name": "mariadb",
"schema": "", "schema": "",
"columns": { "columns": {
@@ -953,29 +951,29 @@
"mariadb_projectId_project_projectId_fk": { "mariadb_projectId_project_projectId_fk": {
"name": "mariadb_projectId_project_projectId_fk", "name": "mariadb_projectId_project_projectId_fk",
"tableFrom": "mariadb", "tableFrom": "mariadb",
"tableTo": "project",
"columnsFrom": [ "columnsFrom": [
"projectId" "projectId"
], ],
"tableTo": "project",
"columnsTo": [ "columnsTo": [
"projectId" "projectId"
], ],
"onDelete": "cascade", "onUpdate": "no action",
"onUpdate": "no action" "onDelete": "cascade"
} }
}, },
"compositePrimaryKeys": {}, "compositePrimaryKeys": {},
"uniqueConstraints": { "uniqueConstraints": {
"mariadb_appName_unique": { "mariadb_appName_unique": {
"name": "mariadb_appName_unique", "name": "mariadb_appName_unique",
"nullsNotDistinct": false,
"columns": [ "columns": [
"appName" "appName"
] ],
"nullsNotDistinct": false
} }
} }
}, },
"mongo": { "public.mongo": {
"name": "mongo", "name": "mongo",
"schema": "", "schema": "",
"columns": { "columns": {
@@ -1088,29 +1086,29 @@
"mongo_projectId_project_projectId_fk": { "mongo_projectId_project_projectId_fk": {
"name": "mongo_projectId_project_projectId_fk", "name": "mongo_projectId_project_projectId_fk",
"tableFrom": "mongo", "tableFrom": "mongo",
"tableTo": "project",
"columnsFrom": [ "columnsFrom": [
"projectId" "projectId"
], ],
"tableTo": "project",
"columnsTo": [ "columnsTo": [
"projectId" "projectId"
], ],
"onDelete": "cascade", "onUpdate": "no action",
"onUpdate": "no action" "onDelete": "cascade"
} }
}, },
"compositePrimaryKeys": {}, "compositePrimaryKeys": {},
"uniqueConstraints": { "uniqueConstraints": {
"mongo_appName_unique": { "mongo_appName_unique": {
"name": "mongo_appName_unique", "name": "mongo_appName_unique",
"nullsNotDistinct": false,
"columns": [ "columns": [
"appName" "appName"
] ],
"nullsNotDistinct": false
} }
} }
}, },
"mysql": { "public.mysql": {
"name": "mysql", "name": "mysql",
"schema": "", "schema": "",
"columns": { "columns": {
@@ -1235,29 +1233,29 @@
"mysql_projectId_project_projectId_fk": { "mysql_projectId_project_projectId_fk": {
"name": "mysql_projectId_project_projectId_fk", "name": "mysql_projectId_project_projectId_fk",
"tableFrom": "mysql", "tableFrom": "mysql",
"tableTo": "project",
"columnsFrom": [ "columnsFrom": [
"projectId" "projectId"
], ],
"tableTo": "project",
"columnsTo": [ "columnsTo": [
"projectId" "projectId"
], ],
"onDelete": "cascade", "onUpdate": "no action",
"onUpdate": "no action" "onDelete": "cascade"
} }
}, },
"compositePrimaryKeys": {}, "compositePrimaryKeys": {},
"uniqueConstraints": { "uniqueConstraints": {
"mysql_appName_unique": { "mysql_appName_unique": {
"name": "mysql_appName_unique", "name": "mysql_appName_unique",
"nullsNotDistinct": false,
"columns": [ "columns": [
"appName" "appName"
] ],
"nullsNotDistinct": false
} }
} }
}, },
"backup": { "public.backup": {
"name": "backup", "name": "backup",
"schema": "", "schema": "",
"columns": { "columns": {
@@ -1333,73 +1331,73 @@
"backup_destinationId_destination_destinationId_fk": { "backup_destinationId_destination_destinationId_fk": {
"name": "backup_destinationId_destination_destinationId_fk", "name": "backup_destinationId_destination_destinationId_fk",
"tableFrom": "backup", "tableFrom": "backup",
"tableTo": "destination",
"columnsFrom": [ "columnsFrom": [
"destinationId" "destinationId"
], ],
"tableTo": "destination",
"columnsTo": [ "columnsTo": [
"destinationId" "destinationId"
], ],
"onDelete": "cascade", "onUpdate": "no action",
"onUpdate": "no action" "onDelete": "cascade"
}, },
"backup_postgresId_postgres_postgresId_fk": { "backup_postgresId_postgres_postgresId_fk": {
"name": "backup_postgresId_postgres_postgresId_fk", "name": "backup_postgresId_postgres_postgresId_fk",
"tableFrom": "backup", "tableFrom": "backup",
"tableTo": "postgres",
"columnsFrom": [ "columnsFrom": [
"postgresId" "postgresId"
], ],
"tableTo": "postgres",
"columnsTo": [ "columnsTo": [
"postgresId" "postgresId"
], ],
"onDelete": "cascade", "onUpdate": "no action",
"onUpdate": "no action" "onDelete": "cascade"
}, },
"backup_mariadbId_mariadb_mariadbId_fk": { "backup_mariadbId_mariadb_mariadbId_fk": {
"name": "backup_mariadbId_mariadb_mariadbId_fk", "name": "backup_mariadbId_mariadb_mariadbId_fk",
"tableFrom": "backup", "tableFrom": "backup",
"tableTo": "mariadb",
"columnsFrom": [ "columnsFrom": [
"mariadbId" "mariadbId"
], ],
"tableTo": "mariadb",
"columnsTo": [ "columnsTo": [
"mariadbId" "mariadbId"
], ],
"onDelete": "cascade", "onUpdate": "no action",
"onUpdate": "no action" "onDelete": "cascade"
}, },
"backup_mysqlId_mysql_mysqlId_fk": { "backup_mysqlId_mysql_mysqlId_fk": {
"name": "backup_mysqlId_mysql_mysqlId_fk", "name": "backup_mysqlId_mysql_mysqlId_fk",
"tableFrom": "backup", "tableFrom": "backup",
"tableTo": "mysql",
"columnsFrom": [ "columnsFrom": [
"mysqlId" "mysqlId"
], ],
"tableTo": "mysql",
"columnsTo": [ "columnsTo": [
"mysqlId" "mysqlId"
], ],
"onDelete": "cascade", "onUpdate": "no action",
"onUpdate": "no action" "onDelete": "cascade"
}, },
"backup_mongoId_mongo_mongoId_fk": { "backup_mongoId_mongo_mongoId_fk": {
"name": "backup_mongoId_mongo_mongoId_fk", "name": "backup_mongoId_mongo_mongoId_fk",
"tableFrom": "backup", "tableFrom": "backup",
"tableTo": "mongo",
"columnsFrom": [ "columnsFrom": [
"mongoId" "mongoId"
], ],
"tableTo": "mongo",
"columnsTo": [ "columnsTo": [
"mongoId" "mongoId"
], ],
"onDelete": "cascade", "onUpdate": "no action",
"onUpdate": "no action" "onDelete": "cascade"
} }
}, },
"compositePrimaryKeys": {}, "compositePrimaryKeys": {},
"uniqueConstraints": {} "uniqueConstraints": {}
}, },
"destination": { "public.destination": {
"name": "destination", "name": "destination",
"schema": "", "schema": "",
"columns": { "columns": {
@@ -1457,21 +1455,21 @@
"destination_adminId_admin_adminId_fk": { "destination_adminId_admin_adminId_fk": {
"name": "destination_adminId_admin_adminId_fk", "name": "destination_adminId_admin_adminId_fk",
"tableFrom": "destination", "tableFrom": "destination",
"tableTo": "admin",
"columnsFrom": [ "columnsFrom": [
"adminId" "adminId"
], ],
"tableTo": "admin",
"columnsTo": [ "columnsTo": [
"adminId" "adminId"
], ],
"onDelete": "cascade", "onUpdate": "no action",
"onUpdate": "no action" "onDelete": "cascade"
} }
}, },
"compositePrimaryKeys": {}, "compositePrimaryKeys": {},
"uniqueConstraints": {} "uniqueConstraints": {}
}, },
"deployment": { "public.deployment": {
"name": "deployment", "name": "deployment",
"schema": "", "schema": "",
"columns": { "columns": {
@@ -1518,21 +1516,21 @@
"deployment_applicationId_application_applicationId_fk": { "deployment_applicationId_application_applicationId_fk": {
"name": "deployment_applicationId_application_applicationId_fk", "name": "deployment_applicationId_application_applicationId_fk",
"tableFrom": "deployment", "tableFrom": "deployment",
"tableTo": "application",
"columnsFrom": [ "columnsFrom": [
"applicationId" "applicationId"
], ],
"tableTo": "application",
"columnsTo": [ "columnsTo": [
"applicationId" "applicationId"
], ],
"onDelete": "cascade", "onUpdate": "no action",
"onUpdate": "no action" "onDelete": "cascade"
} }
}, },
"compositePrimaryKeys": {}, "compositePrimaryKeys": {},
"uniqueConstraints": {} "uniqueConstraints": {}
}, },
"mount": { "public.mount": {
"name": "mount", "name": "mount",
"schema": "", "schema": "",
"columns": { "columns": {
@@ -1621,86 +1619,86 @@
"mount_applicationId_application_applicationId_fk": { "mount_applicationId_application_applicationId_fk": {
"name": "mount_applicationId_application_applicationId_fk", "name": "mount_applicationId_application_applicationId_fk",
"tableFrom": "mount", "tableFrom": "mount",
"tableTo": "application",
"columnsFrom": [ "columnsFrom": [
"applicationId" "applicationId"
], ],
"tableTo": "application",
"columnsTo": [ "columnsTo": [
"applicationId" "applicationId"
], ],
"onDelete": "cascade", "onUpdate": "no action",
"onUpdate": "no action" "onDelete": "cascade"
}, },
"mount_postgresId_postgres_postgresId_fk": { "mount_postgresId_postgres_postgresId_fk": {
"name": "mount_postgresId_postgres_postgresId_fk", "name": "mount_postgresId_postgres_postgresId_fk",
"tableFrom": "mount", "tableFrom": "mount",
"tableTo": "postgres",
"columnsFrom": [ "columnsFrom": [
"postgresId" "postgresId"
], ],
"tableTo": "postgres",
"columnsTo": [ "columnsTo": [
"postgresId" "postgresId"
], ],
"onDelete": "cascade", "onUpdate": "no action",
"onUpdate": "no action" "onDelete": "cascade"
}, },
"mount_mariadbId_mariadb_mariadbId_fk": { "mount_mariadbId_mariadb_mariadbId_fk": {
"name": "mount_mariadbId_mariadb_mariadbId_fk", "name": "mount_mariadbId_mariadb_mariadbId_fk",
"tableFrom": "mount", "tableFrom": "mount",
"tableTo": "mariadb",
"columnsFrom": [ "columnsFrom": [
"mariadbId" "mariadbId"
], ],
"tableTo": "mariadb",
"columnsTo": [ "columnsTo": [
"mariadbId" "mariadbId"
], ],
"onDelete": "cascade", "onUpdate": "no action",
"onUpdate": "no action" "onDelete": "cascade"
}, },
"mount_mongoId_mongo_mongoId_fk": { "mount_mongoId_mongo_mongoId_fk": {
"name": "mount_mongoId_mongo_mongoId_fk", "name": "mount_mongoId_mongo_mongoId_fk",
"tableFrom": "mount", "tableFrom": "mount",
"tableTo": "mongo",
"columnsFrom": [ "columnsFrom": [
"mongoId" "mongoId"
], ],
"tableTo": "mongo",
"columnsTo": [ "columnsTo": [
"mongoId" "mongoId"
], ],
"onDelete": "cascade", "onUpdate": "no action",
"onUpdate": "no action" "onDelete": "cascade"
}, },
"mount_mysqlId_mysql_mysqlId_fk": { "mount_mysqlId_mysql_mysqlId_fk": {
"name": "mount_mysqlId_mysql_mysqlId_fk", "name": "mount_mysqlId_mysql_mysqlId_fk",
"tableFrom": "mount", "tableFrom": "mount",
"tableTo": "mysql",
"columnsFrom": [ "columnsFrom": [
"mysqlId" "mysqlId"
], ],
"tableTo": "mysql",
"columnsTo": [ "columnsTo": [
"mysqlId" "mysqlId"
], ],
"onDelete": "cascade", "onUpdate": "no action",
"onUpdate": "no action" "onDelete": "cascade"
}, },
"mount_redisId_redis_redisId_fk": { "mount_redisId_redis_redisId_fk": {
"name": "mount_redisId_redis_redisId_fk", "name": "mount_redisId_redis_redisId_fk",
"tableFrom": "mount", "tableFrom": "mount",
"tableTo": "redis",
"columnsFrom": [ "columnsFrom": [
"redisId" "redisId"
], ],
"tableTo": "redis",
"columnsTo": [ "columnsTo": [
"redisId" "redisId"
], ],
"onDelete": "cascade", "onUpdate": "no action",
"onUpdate": "no action" "onDelete": "cascade"
} }
}, },
"compositePrimaryKeys": {}, "compositePrimaryKeys": {},
"uniqueConstraints": {} "uniqueConstraints": {}
}, },
"certificate": { "public.certificate": {
"name": "certificate", "name": "certificate",
"schema": "", "schema": "",
"columns": { "columns": {
@@ -1747,14 +1745,14 @@
"uniqueConstraints": { "uniqueConstraints": {
"certificate_certificatePath_unique": { "certificate_certificatePath_unique": {
"name": "certificate_certificatePath_unique", "name": "certificate_certificatePath_unique",
"nullsNotDistinct": false,
"columns": [ "columns": [
"certificatePath" "certificatePath"
] ],
"nullsNotDistinct": false
} }
} }
}, },
"session": { "public.session": {
"name": "session", "name": "session",
"schema": "", "schema": "",
"columns": { "columns": {
@@ -1782,21 +1780,21 @@
"session_user_id_auth_id_fk": { "session_user_id_auth_id_fk": {
"name": "session_user_id_auth_id_fk", "name": "session_user_id_auth_id_fk",
"tableFrom": "session", "tableFrom": "session",
"tableTo": "auth",
"columnsFrom": [ "columnsFrom": [
"user_id" "user_id"
], ],
"tableTo": "auth",
"columnsTo": [ "columnsTo": [
"id" "id"
], ],
"onDelete": "cascade", "onUpdate": "no action",
"onUpdate": "no action" "onDelete": "cascade"
} }
}, },
"compositePrimaryKeys": {}, "compositePrimaryKeys": {},
"uniqueConstraints": {} "uniqueConstraints": {}
}, },
"redirect": { "public.redirect": {
"name": "redirect", "name": "redirect",
"schema": "", "schema": "",
"columns": { "columns": {
@@ -1849,21 +1847,21 @@
"redirect_applicationId_application_applicationId_fk": { "redirect_applicationId_application_applicationId_fk": {
"name": "redirect_applicationId_application_applicationId_fk", "name": "redirect_applicationId_application_applicationId_fk",
"tableFrom": "redirect", "tableFrom": "redirect",
"tableTo": "application",
"columnsFrom": [ "columnsFrom": [
"applicationId" "applicationId"
], ],
"tableTo": "application",
"columnsTo": [ "columnsTo": [
"applicationId" "applicationId"
], ],
"onDelete": "cascade", "onUpdate": "no action",
"onUpdate": "no action" "onDelete": "cascade"
} }
}, },
"compositePrimaryKeys": {}, "compositePrimaryKeys": {},
"uniqueConstraints": {} "uniqueConstraints": {}
}, },
"security": { "public.security": {
"name": "security", "name": "security",
"schema": "", "schema": "",
"columns": { "columns": {
@@ -1903,30 +1901,30 @@
"security_applicationId_application_applicationId_fk": { "security_applicationId_application_applicationId_fk": {
"name": "security_applicationId_application_applicationId_fk", "name": "security_applicationId_application_applicationId_fk",
"tableFrom": "security", "tableFrom": "security",
"tableTo": "application",
"columnsFrom": [ "columnsFrom": [
"applicationId" "applicationId"
], ],
"tableTo": "application",
"columnsTo": [ "columnsTo": [
"applicationId" "applicationId"
], ],
"onDelete": "cascade", "onUpdate": "no action",
"onUpdate": "no action" "onDelete": "cascade"
} }
}, },
"compositePrimaryKeys": {}, "compositePrimaryKeys": {},
"uniqueConstraints": { "uniqueConstraints": {
"security_username_applicationId_unique": { "security_username_applicationId_unique": {
"name": "security_username_applicationId_unique", "name": "security_username_applicationId_unique",
"nullsNotDistinct": false,
"columns": [ "columns": [
"username", "username",
"applicationId" "applicationId"
] ],
"nullsNotDistinct": false
} }
} }
}, },
"port": { "public.port": {
"name": "port", "name": "port",
"schema": "", "schema": "",
"columns": { "columns": {
@@ -1966,21 +1964,21 @@
"port_applicationId_application_applicationId_fk": { "port_applicationId_application_applicationId_fk": {
"name": "port_applicationId_application_applicationId_fk", "name": "port_applicationId_application_applicationId_fk",
"tableFrom": "port", "tableFrom": "port",
"tableTo": "application",
"columnsFrom": [ "columnsFrom": [
"applicationId" "applicationId"
], ],
"tableTo": "application",
"columnsTo": [ "columnsTo": [
"applicationId" "applicationId"
], ],
"onDelete": "cascade", "onUpdate": "no action",
"onUpdate": "no action" "onDelete": "cascade"
} }
}, },
"compositePrimaryKeys": {}, "compositePrimaryKeys": {},
"uniqueConstraints": {} "uniqueConstraints": {}
}, },
"redis": { "public.redis": {
"name": "redis", "name": "redis",
"schema": "", "schema": "",
"columns": { "columns": {
@@ -2087,118 +2085,130 @@
"redis_projectId_project_projectId_fk": { "redis_projectId_project_projectId_fk": {
"name": "redis_projectId_project_projectId_fk", "name": "redis_projectId_project_projectId_fk",
"tableFrom": "redis", "tableFrom": "redis",
"tableTo": "project",
"columnsFrom": [ "columnsFrom": [
"projectId" "projectId"
], ],
"tableTo": "project",
"columnsTo": [ "columnsTo": [
"projectId" "projectId"
], ],
"onDelete": "cascade", "onUpdate": "no action",
"onUpdate": "no action" "onDelete": "cascade"
} }
}, },
"compositePrimaryKeys": {}, "compositePrimaryKeys": {},
"uniqueConstraints": { "uniqueConstraints": {
"redis_appName_unique": { "redis_appName_unique": {
"name": "redis_appName_unique", "name": "redis_appName_unique",
"nullsNotDistinct": false,
"columns": [ "columns": [
"appName" "appName"
] ],
"nullsNotDistinct": false
} }
} }
} }
}, },
"enums": { "enums": {
"buildType": { "public.buildType": {
"name": "buildType", "name": "buildType",
"values": { "schema": "public",
"dockerfile": "dockerfile", "values": [
"heroku_buildpacks": "heroku_buildpacks", "dockerfile",
"paketo_buildpacks": "paketo_buildpacks", "heroku_buildpacks",
"nixpacks": "nixpacks" "paketo_buildpacks",
} "nixpacks"
]
}, },
"sourceType": { "public.sourceType": {
"name": "sourceType", "name": "sourceType",
"values": { "schema": "public",
"docker": "docker", "values": [
"git": "git", "docker",
"github": "github" "git",
} "github"
]
}, },
"Roles": { "public.Roles": {
"name": "Roles", "name": "Roles",
"values": { "schema": "public",
"admin": "admin", "values": [
"user": "user" "admin",
} "user"
]
}, },
"databaseType": { "public.databaseType": {
"name": "databaseType", "name": "databaseType",
"values": { "schema": "public",
"postgres": "postgres", "values": [
"mariadb": "mariadb", "postgres",
"mysql": "mysql", "mariadb",
"mongo": "mongo" "mysql",
} "mongo"
]
}, },
"deploymentStatus": { "public.deploymentStatus": {
"name": "deploymentStatus", "name": "deploymentStatus",
"values": { "schema": "public",
"running": "running", "values": [
"done": "done", "running",
"error": "error" "done",
} "error"
]
}, },
"mountType": { "public.mountType": {
"name": "mountType", "name": "mountType",
"values": { "schema": "public",
"bind": "bind", "values": [
"volume": "volume", "bind",
"file": "file" "volume",
} "file"
]
}, },
"serviceType": { "public.serviceType": {
"name": "serviceType", "name": "serviceType",
"values": { "schema": "public",
"application": "application", "values": [
"postgres": "postgres", "application",
"mysql": "mysql", "postgres",
"mariadb": "mariadb", "mysql",
"mongo": "mongo", "mariadb",
"redis": "redis" "mongo",
} "redis"
]
}, },
"protocolType": { "public.protocolType": {
"name": "protocolType", "name": "protocolType",
"values": { "schema": "public",
"tcp": "tcp", "values": [
"udp": "udp" "tcp",
} "udp"
]
}, },
"applicationStatus": { "public.applicationStatus": {
"name": "applicationStatus", "name": "applicationStatus",
"values": { "schema": "public",
"idle": "idle", "values": [
"running": "running", "idle",
"done": "done", "running",
"error": "error" "done",
} "error"
]
}, },
"certificateType": { "public.certificateType": {
"name": "certificateType", "name": "certificateType",
"values": { "schema": "public",
"letsencrypt": "letsencrypt", "values": [
"none": "none" "letsencrypt",
} "none"
]
} }
}, },
"schemas": {}, "schemas": {},
"_meta": { "_meta": {
"columns": {},
"schemas": {}, "schemas": {},
"tables": {} "tables": {},
} "columns": {}
},
"id": "7bb4bbcf-791c-4888-919e-f74bc0528b5f",
"prevId": "5a1d3f2b-9c31-4125-9645-015170550b51"
} }

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -36,6 +36,69 @@
"when": 1714004732716, "when": 1714004732716,
"tag": "0004_nice_tenebrous", "tag": "0004_nice_tenebrous",
"breakpoints": true "breakpoints": true
},
{
"idx": 5,
"version": "5",
"when": 1715551130605,
"tag": "0005_cute_terror",
"breakpoints": true
},
{
"idx": 6,
"version": "6",
"when": 1715563165991,
"tag": "0006_oval_jimmy_woo",
"breakpoints": true
},
{
"idx": 7,
"version": "6",
"when": 1715563497100,
"tag": "0007_cute_guardsmen",
"breakpoints": true
},
{
"idx": 8,
"version": "6",
"when": 1715564143641,
"tag": "0008_lazy_sage",
"breakpoints": true
},
{
"idx": 9,
"version": "6",
"when": 1715564774423,
"tag": "0009_majestic_spencer_smythe",
"breakpoints": true
},
{
"idx": 10,
"version": "6",
"when": 1715574037832,
"tag": "0010_lean_black_widow",
"breakpoints": true
},
{
"idx": 11,
"version": "6",
"when": 1715574230599,
"tag": "0011_petite_calypso",
"breakpoints": true
},
{
"idx": 12,
"version": "6",
"when": 1716015716708,
"tag": "0012_chubby_umar",
"breakpoints": true
},
{
"idx": 13,
"version": "6",
"when": 1716076179443,
"tag": "0013_blushing_starjammers",
"breakpoints": true
} }
] ]
} }

View File

@@ -12,15 +12,17 @@
"setup": "tsx -r dotenv/config setup.ts && sleep 5 && pnpm run migration:run", "setup": "tsx -r dotenv/config setup.ts && sleep 5 && pnpm run migration:run",
"reset-password": "node dist/reset-password.mjs", "reset-password": "node dist/reset-password.mjs",
"dev": "tsx watch -r dotenv/config ./server/server.ts --project tsconfig.server.json ", "dev": "tsx watch -r dotenv/config ./server/server.ts --project tsconfig.server.json ",
"migration:generate": "drizzle-kit generate:pg --config ./server/db/drizzle.config.ts", "studio":"drizzle-kit studio --config ./server/db/drizzle.config.ts",
"migration:generate": "drizzle-kit generate --config ./server/db/drizzle.config.ts",
"migration:run": "tsx -r dotenv/config migration.ts", "migration:run": "tsx -r dotenv/config migration.ts",
"migration:up":"drizzle-kit up --config ./server/db/drizzle.config.ts",
"migration:drop": "drizzle-kit drop --config ./server/db/drizzle.config.ts", "migration:drop": "drizzle-kit drop --config ./server/db/drizzle.config.ts",
"db:push": "drizzle-kit push:pg --config ./server/db/drizzle.config.ts", "db:push": "drizzle-kit --config ./server/db/drizzle.config.ts",
"db:truncate": "tsx -r dotenv/config ./server/db/reset.ts", "db:truncate": "tsx -r dotenv/config ./server/db/reset.ts",
"db:studio": "drizzle-kit studio", "db:studio": "drizzle-kit studio",
"lint": "biome lint", "lint": "biome lint",
"db:seed": "dotenv tsx ./server/db/seed.ts", "db:seed": "tsx -r dotenv/config ./server/db/seed.ts",
"db:clean": "dotenv tsx ./server/db/reset.ts", "db:clean": "tsx -r dotenv/config ./server/db/reset.ts",
"docker:build": "./docker/build.sh", "docker:build": "./docker/build.sh",
"docker:push": "./docker/push.sh", "docker:push": "./docker/push.sh",
"docker:build:canary": "./docker/build.sh canary", "docker:build:canary": "./docker/build.sh canary",
@@ -116,7 +118,7 @@
"@types/tar-fs": "2.0.4", "@types/tar-fs": "2.0.4",
"@types/ws": "8.5.10", "@types/ws": "8.5.10",
"autoprefixer": "^10.4.14", "autoprefixer": "^10.4.14",
"drizzle-kit": "^0.20.14", "drizzle-kit": "^0.21.1",
"esbuild": "0.20.2", "esbuild": "0.20.2",
"localtunnel": "2.0.2", "localtunnel": "2.0.2",
"postcss": "^8.4.31", "postcss": "^8.4.31",

View File

@@ -12,8 +12,8 @@ export default function Custom404({ statusCode }: Props) {
<div className="container mx-auto h-screen items-center justify-center flex"> <div className="container mx-auto h-screen items-center justify-center flex">
<div className="-mx-4 flex"> <div className="-mx-4 flex">
<div className="w-full px-4"> <div className="w-full px-4">
<div className="mx-auto max-w-[400px] text-center"> <div className="mx-auto max-w-[700px] text-center">
<h2 className="mb-2 text-[50px] font-bold leading-none text-white sm:text-[80px] md:text-[100px]"> <h2 className="mb-2 text-[50px] font-bold leading-none text-white sm:text-[80px]">
{statusCode {statusCode
? `An error ${statusCode} occurred on server` ? `An error ${statusCode} occurred on server`
: "An error occurred on client"} : "An error occurred on client"}

View File

@@ -1,3 +1,4 @@
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";
import { ShowRedirects } from "@/components/dashboard/application/advanced/redirects/show-redirects"; import { ShowRedirects } from "@/components/dashboard/application/advanced/redirects/show-redirects";
@@ -134,7 +135,7 @@ const Service = (
<TabsTrigger value="domains">Domains</TabsTrigger> <TabsTrigger value="domains">Domains</TabsTrigger>
<TabsTrigger value="advanced">Advanced</TabsTrigger> <TabsTrigger value="advanced">Advanced</TabsTrigger>
</TabsList> </TabsList>
<div className="flex flex-row gap-4"> <div className="flex flex-row gap-2">
<UpdateApplication applicationId={applicationId} /> <UpdateApplication applicationId={applicationId} />
{(auth?.rol === "admin" || user?.canDeleteServices) && ( {(auth?.rol === "admin" || user?.canDeleteServices) && (
<DeleteApplication applicationId={applicationId} /> <DeleteApplication applicationId={applicationId} />
@@ -175,6 +176,7 @@ 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} />
<ShowApplicationResources applicationId={applicationId} /> <ShowApplicationResources applicationId={applicationId} />
<ShowVolumes applicationId={applicationId} /> <ShowVolumes applicationId={applicationId} />
<ShowRedirects applicationId={applicationId} /> <ShowRedirects applicationId={applicationId} />

View File

@@ -116,7 +116,7 @@ const Mariadb = (
<TabsTrigger value="logs">Logs</TabsTrigger> <TabsTrigger value="logs">Logs</TabsTrigger>
<TabsTrigger value="advanced">Advanced</TabsTrigger> <TabsTrigger value="advanced">Advanced</TabsTrigger>
</TabsList> </TabsList>
<div className="flex flex-row gap-4"> <div className="flex flex-row gap-2">
<UpdateMariadb mariadbId={mariadbId} /> <UpdateMariadb mariadbId={mariadbId} />
{(auth?.rol === "admin" || user?.canDeleteServices) && ( {(auth?.rol === "admin" || user?.canDeleteServices) && (
<DeleteMariadb mariadbId={mariadbId} /> <DeleteMariadb mariadbId={mariadbId} />

View File

@@ -118,7 +118,7 @@ const Mongo = (
<TabsTrigger value="advanced">Advanced</TabsTrigger> <TabsTrigger value="advanced">Advanced</TabsTrigger>
</TabsList> </TabsList>
<div className="flex flex-row gap-4"> <div className="flex flex-row gap-2">
<UpdateMongo mongoId={mongoId} /> <UpdateMongo mongoId={mongoId} />
{(auth?.rol === "admin" || user?.canDeleteServices) && ( {(auth?.rol === "admin" || user?.canDeleteServices) && (
<DeleteMongo mongoId={mongoId} /> <DeleteMongo mongoId={mongoId} />

View File

@@ -117,7 +117,7 @@ const MySql = (
<TabsTrigger value="advanced">Advanced</TabsTrigger> <TabsTrigger value="advanced">Advanced</TabsTrigger>
</TabsList> </TabsList>
<div className="flex flex-row gap-4"> <div className="flex flex-row gap-2">
<UpdateMysql mysqlId={mysqlId} /> <UpdateMysql mysqlId={mysqlId} />
{(auth?.rol === "admin" || user?.canDeleteServices) && ( {(auth?.rol === "admin" || user?.canDeleteServices) && (
<DeleteMysql mysqlId={mysqlId} /> <DeleteMysql mysqlId={mysqlId} />

View File

@@ -118,7 +118,7 @@ const Postgresql = (
<TabsTrigger value="advanced">Advanced</TabsTrigger> <TabsTrigger value="advanced">Advanced</TabsTrigger>
</TabsList> </TabsList>
<div className="flex flex-row gap-4"> <div className="flex flex-row gap-2">
<UpdatePostgres postgresId={postgresId} /> <UpdatePostgres postgresId={postgresId} />
{(auth?.rol === "admin" || user?.canDeleteServices) && ( {(auth?.rol === "admin" || user?.canDeleteServices) && (
<DeletePostgres postgresId={postgresId} /> <DeletePostgres postgresId={postgresId} />

View File

@@ -116,7 +116,7 @@ const Redis = (
<TabsTrigger value="advanced">Advanced</TabsTrigger> <TabsTrigger value="advanced">Advanced</TabsTrigger>
</TabsList> </TabsList>
<div className="flex flex-row gap-4"> <div className="flex flex-row gap-2">
<UpdateRedis redisId={redisId} /> <UpdateRedis redisId={redisId} />
{(auth?.rol === "admin" || user?.canDeleteServices) && ( {(auth?.rol === "admin" || user?.canDeleteServices) && (
<DeleteRedis redisId={redisId} /> <DeleteRedis redisId={redisId} />

View File

@@ -0,0 +1,43 @@
import { ShowRegistry } from "@/components/dashboard/settings/cluster/registry/show-registry";
import { ShowNodes } from "@/components/dashboard/settings/cluster/nodes/show-nodes";
import { DashboardLayout } from "@/components/layouts/dashboard-layout";
import { SettingsLayout } from "@/components/layouts/settings-layout";
import { validateRequest } from "@/server/auth/auth";
import type { GetServerSidePropsContext } from "next";
import React, { type ReactElement } from "react";
const Page = () => {
return (
<div className="flex flex-col gap-4 w-full">
<ShowRegistry />
<ShowNodes />
</div>
);
};
export default Page;
Page.getLayout = (page: ReactElement) => {
return (
<DashboardLayout tab={"settings"}>
<SettingsLayout>{page}</SettingsLayout>
</DashboardLayout>
);
};
export async function getServerSideProps(
ctx: GetServerSidePropsContext<{ serviceId: string }>,
) {
const { user, session } = await validateRequest(ctx.req, ctx.res);
if (!user || user.rol === "user") {
return {
redirect: {
permanent: true,
destination: "/",
},
};
}
return {
props: {},
};
}

View File

@@ -193,7 +193,7 @@ export default function Home({ hasAdmin }: Props) {
<div className="mt-4 text-sm flex flex-row justify-center gap-2"> <div className="mt-4 text-sm flex flex-row justify-center gap-2">
<Link <Link
className="hover:underline text-muted-foreground" className="hover:underline text-muted-foreground"
href="https://docs.dokploy.com/reset-password" href="https://docs.dokploy.com/get-started/reset-password"
target="_blank" target="_blank"
> >
Lost your password? Lost your password?

42
pnpm-lock.yaml generated
View File

@@ -266,8 +266,8 @@ devDependencies:
specifier: ^10.4.14 specifier: ^10.4.14
version: 10.4.18(postcss@8.4.35) version: 10.4.18(postcss@8.4.35)
drizzle-kit: drizzle-kit:
specifier: ^0.20.14 specifier: ^0.21.1
version: 0.20.14 version: 0.21.1
esbuild: esbuild:
specifier: 0.20.2 specifier: 0.20.2
version: 0.20.2 version: 0.20.2
@@ -1032,12 +1032,6 @@ packages:
dev: true dev: true
optional: true optional: true
/@drizzle-team/studio@0.0.39:
resolution: {integrity: sha512-c5Hkm7MmQC2n5qAsKShjQrHoqlfGslB8+qWzsGGZ+2dHMRTNG60UuzalF0h0rvBax5uzPXuGkYLGaQ+TUX3yMw==}
dependencies:
superjson: 2.2.1
dev: true
/@emnapi/core@0.45.0: /@emnapi/core@0.45.0:
resolution: {integrity: sha512-DPWjcUDQkCeEM4VnljEOEcXdAD7pp8zSZsgOujk/LGIwCXWbXJngin+MO4zbH429lzeC3WbYLGjE2MaUOwzpyw==} resolution: {integrity: sha512-DPWjcUDQkCeEM4VnljEOEcXdAD7pp8zSZsgOujk/LGIwCXWbXJngin+MO4zbH429lzeC3WbYLGjE2MaUOwzpyw==}
requiresBuild: true requiresBuild: true
@@ -4728,19 +4722,9 @@ packages:
engines: {node: '>=6'} engines: {node: '>=6'}
dev: false dev: false
/camelcase@7.0.1:
resolution: {integrity: sha512-xlx1yCK2Oc1APsPXDL2LdlNP6+uu8OCDdhOBSVT279M/S+y75O30C2VuD8T2ogdePBBl7PfPF4504tnLgX3zfw==}
engines: {node: '>=14.16'}
dev: true
/caniuse-lite@1.0.30001598: /caniuse-lite@1.0.30001598:
resolution: {integrity: sha512-j8mQRDziG94uoBfeFuqsJUNECW37DXpnvhcMJMdlH2u3MRkq1sAI0LJcXP1i/Py0KbSIC4UDj8YHPrTn5YsL+Q==} resolution: {integrity: sha512-j8mQRDziG94uoBfeFuqsJUNECW37DXpnvhcMJMdlH2u3MRkq1sAI0LJcXP1i/Py0KbSIC4UDj8YHPrTn5YsL+Q==}
/chalk@5.3.0:
resolution: {integrity: sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==}
engines: {node: ^12.17.0 || ^14.13 || >=16.0.0}
dev: true
/chokidar@3.6.0: /chokidar@3.6.0:
resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==} resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==}
engines: {node: '>= 8.10.0'} engines: {node: '>= 8.10.0'}
@@ -4877,6 +4861,7 @@ packages:
engines: {node: '>=12.13'} engines: {node: '>=12.13'}
dependencies: dependencies:
is-what: 4.1.16 is-what: 4.1.16
dev: false
/copy-to-clipboard@3.3.3: /copy-to-clipboard@3.3.3:
resolution: {integrity: sha512-2KV8NhB5JqC3ky0r9PMCAZKbUHSwtEo4CwCs0KXgruG43gX5PMqDEBbVU4OUzw2MuAWUfsuFmWvEKG5QRfSnJA==} resolution: {integrity: sha512-2KV8NhB5JqC3ky0r9PMCAZKbUHSwtEo4CwCs0KXgruG43gX5PMqDEBbVU4OUzw2MuAWUfsuFmWvEKG5QRfSnJA==}
@@ -5143,14 +5128,11 @@ packages:
wordwrap: 1.0.0 wordwrap: 1.0.0
dev: true dev: true
/drizzle-kit@0.20.14: /drizzle-kit@0.21.1:
resolution: {integrity: sha512-0fHv3YIEaUcSVPSGyaaBfOi9bmpajjhbJNdPsRMIUvYdLVxBu9eGjH8mRc3Qk7HVmEidFc/lhG1YyJhoXrn5yA==} resolution: {integrity: sha512-Sp7OnCdROiE2ebMuHsAfrnRoHVGYCvErQxUh7/0l6R1caHssZu9oZu/hW9rLU19xnTK4/y3iSe3sL0Cc530wCg==}
hasBin: true hasBin: true
dependencies: dependencies:
'@drizzle-team/studio': 0.0.39
'@esbuild-kit/esm-loader': 2.6.5 '@esbuild-kit/esm-loader': 2.6.5
camelcase: 7.0.1
chalk: 5.3.0
commander: 9.5.0 commander: 9.5.0
env-paths: 3.0.0 env-paths: 3.0.0
esbuild: 0.19.12 esbuild: 0.19.12
@@ -5158,8 +5140,6 @@ packages:
glob: 8.1.0 glob: 8.1.0
hanji: 0.0.5 hanji: 0.0.5
json-diff: 0.9.0 json-diff: 0.9.0
minimatch: 7.4.6
semver: 7.6.0
zod: 3.23.4 zod: 3.23.4
transitivePeerDependencies: transitivePeerDependencies:
- supports-color - supports-color
@@ -5834,6 +5814,7 @@ packages:
/is-what@4.1.16: /is-what@4.1.16:
resolution: {integrity: sha512-ZhMwEosbFJkA0YhFnNDgTM4ZxDRsS6HqTo7qsZM08fehyRYIYa0yHu5R6mgo1n/8MgaPBXiPimPD77baVFYg+A==} resolution: {integrity: sha512-ZhMwEosbFJkA0YhFnNDgTM4ZxDRsS6HqTo7qsZM08fehyRYIYa0yHu5R6mgo1n/8MgaPBXiPimPD77baVFYg+A==}
engines: {node: '>=12.13'} engines: {node: '>=12.13'}
dev: false
/isexe@2.0.0: /isexe@2.0.0:
resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==}
@@ -6015,6 +5996,7 @@ packages:
engines: {node: '>=10'} engines: {node: '>=10'}
dependencies: dependencies:
yallist: 4.0.0 yallist: 4.0.0
dev: false
/lru-queue@0.1.0: /lru-queue@0.1.0:
resolution: {integrity: sha512-BpdYkt9EvGl8OfWHDQPISVpcl5xZthb+XPsbELj5AQXxIC8IriDZIQYjBJPEm5rS420sjZ0TLEzRcq5KdBhYrQ==} resolution: {integrity: sha512-BpdYkt9EvGl8OfWHDQPISVpcl5xZthb+XPsbELj5AQXxIC8IriDZIQYjBJPEm5rS420sjZ0TLEzRcq5KdBhYrQ==}
@@ -6095,13 +6077,6 @@ packages:
brace-expansion: 2.0.1 brace-expansion: 2.0.1
dev: true dev: true
/minimatch@7.4.6:
resolution: {integrity: sha512-sBz8G/YjVniEz6lKPNpKxXwazJe4c19fEfV2GDMX6AjFz+MX9uDWIZW8XreVhkFW3fkIdTv/gxWr/Kks5FFAVw==}
engines: {node: '>=10'}
dependencies:
brace-expansion: 2.0.1
dev: true
/minimatch@9.0.3: /minimatch@9.0.3:
resolution: {integrity: sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==} resolution: {integrity: sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==}
engines: {node: '>=16 || 14 >=14.17'} engines: {node: '>=16 || 14 >=14.17'}
@@ -6909,6 +6884,7 @@ packages:
hasBin: true hasBin: true
dependencies: dependencies:
lru-cache: 6.0.0 lru-cache: 6.0.0
dev: false
/set-blocking@2.0.0: /set-blocking@2.0.0:
resolution: {integrity: sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==} resolution: {integrity: sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==}
@@ -7073,6 +7049,7 @@ packages:
engines: {node: '>=16'} engines: {node: '>=16'}
dependencies: dependencies:
copy-anything: 3.0.5 copy-anything: 3.0.5
dev: false
/supports-preserve-symlinks-flag@1.0.0: /supports-preserve-symlinks-flag@1.0.0:
resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==}
@@ -7456,6 +7433,7 @@ packages:
/yallist@4.0.0: /yallist@4.0.0:
resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==} resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==}
dev: false
/yaml@2.4.1: /yaml@2.4.1:
resolution: {integrity: sha512-pIXzoImaqmfOrL7teGUBt/T7ZDnyeGBWyXQBvOVhLkWLN37GXv8NMLK406UY6dS51JfcQHsmcW5cJ441bHg6Lg==} resolution: {integrity: sha512-pIXzoImaqmfOrL7teGUBt/T7ZDnyeGBWyXQBvOVhLkWLN37GXv8NMLK406UY6dS51JfcQHsmcW5cJ441bHg6Lg==}

View File

@@ -20,6 +20,8 @@ import { securityRouter } from "./routers/security";
import { portRouter } from "./routers/port"; import { portRouter } from "./routers/port";
import { adminRouter } from "./routers/admin"; import { adminRouter } from "./routers/admin";
import { dockerRouter } from "./routers/docker"; import { dockerRouter } from "./routers/docker";
import { registryRouter } from "./routers/registry";
import { clusterRouter } from "./routers/cluster";
/** /**
* This is the primary router for your server. * This is the primary router for your server.
* *
@@ -47,6 +49,8 @@ export const appRouter = createTRPCRouter({
security: securityRouter, security: securityRouter,
redirects: redirectsRouter, redirects: redirectsRouter,
port: portRouter, port: portRouter,
registry: registryRouter,
cluster: clusterRouter,
}); });
// export type definition of API // export type definition of API

View File

@@ -0,0 +1,48 @@
import { docker } from "@/server/constants";
import { createTRPCRouter, protectedProcedure } from "../trpc";
import { getPublicIpWithFallback } from "@/server/wss/terminal";
import type { DockerNode } from "../services/cluster";
import { z } from "zod";
import { TRPCError } from "@trpc/server";
import { execAsync } from "@/server/utils/process/execAsync";
export const clusterRouter = createTRPCRouter({
getNodes: protectedProcedure.query(async () => {
const workers: DockerNode[] = await docker.listNodes();
return workers;
}),
removeWorker: protectedProcedure
.input(
z.object({
nodeId: z.string(),
}),
)
.mutation(async ({ input }) => {
try {
await execAsync(
`docker node update --availability drain ${input.nodeId}`,
);
await execAsync(`docker node rm ${input.nodeId} --force`);
return true;
} catch (error) {
throw new TRPCError({
code: "INTERNAL_SERVER_ERROR",
message: "Error to remove the node",
cause: error,
});
}
}),
addWorker: protectedProcedure.query(async ({ input }) => {
const result = await docker.swarmInspect();
return `docker swarm join --token ${
result.JoinTokens.Worker
} ${await getPublicIpWithFallback()}:2377`;
}),
addManager: protectedProcedure.query(async ({ input }) => {
const result = await docker.swarmInspect();
return `docker swarm join --token ${
result.JoinTokens.Manager
} ${await getPublicIpWithFallback()}:2377`;
}),
});

View File

@@ -0,0 +1,92 @@
import {
apiCreateRegistry,
apiEnableSelfHostedRegistry,
apiFindOneRegistry,
apiRemoveRegistry,
apiTestRegistry,
apiUpdateRegistry,
} from "@/server/db/schema";
import {
createRegistry,
findAllRegistry,
findRegistryById,
removeRegistry,
updateRegistry,
} from "../services/registry";
import { adminProcedure, createTRPCRouter, protectedProcedure } from "../trpc";
import { TRPCError } from "@trpc/server";
import { manageRegistry } from "@/server/utils/traefik/registry";
import { initializeRegistry } from "@/server/setup/registry-setup";
import { docker } from "@/server/constants";
export const registryRouter = createTRPCRouter({
create: adminProcedure
.input(apiCreateRegistry)
.mutation(async ({ ctx, input }) => {
return await createRegistry(input);
}),
remove: adminProcedure
.input(apiRemoveRegistry)
.mutation(async ({ ctx, input }) => {
return await removeRegistry(input.registryId);
}),
update: protectedProcedure
.input(apiUpdateRegistry)
.mutation(async ({ input }) => {
const { registryId, ...rest } = input;
const application = await updateRegistry(registryId, {
...rest,
});
if (!application) {
throw new TRPCError({
code: "BAD_REQUEST",
message: "Update: Error to update registry",
});
}
return true;
}),
all: protectedProcedure.query(async () => {
return await findAllRegistry();
}),
one: adminProcedure.input(apiFindOneRegistry).query(async ({ input }) => {
return await findRegistryById(input.registryId);
}),
testRegistry: protectedProcedure
.input(apiTestRegistry)
.mutation(async ({ input }) => {
try {
const result = await docker.checkAuth({
username: input.username,
password: input.password,
serveraddress: input.registryUrl,
});
return true;
} catch (error) {
console.log(error);
return false;
}
}),
enableSelfHostedRegistry: adminProcedure
.input(apiEnableSelfHostedRegistry)
.mutation(async ({ input }) => {
const selfHostedRegistry = await createRegistry({
...input,
registryName: "Self Hosted Registry",
registryType: "selfHosted",
registryUrl:
process.env.NODE_ENV === "production"
? input.registryUrl
: "dokploy-registry.docker.localhost",
imagePrefix: null,
});
await manageRegistry(selfHostedRegistry);
await initializeRegistry(input.username, input.password);
return selfHostedRegistry;
}),
});

View File

@@ -181,7 +181,7 @@ export const settingsRouter = createTRPCRouter({
return true; return true;
}), }),
checkAndUpdateImage: adminProcedure.query(async () => { checkAndUpdateImage: adminProcedure.mutation(async () => {
return await pullLatestRelease(); return await pullLatestRelease();
}), }),
updateServer: adminProcedure.mutation(async () => { updateServer: adminProcedure.mutation(async () => {
@@ -238,3 +238,4 @@ export const settingsRouter = createTRPCRouter({
return readConfigInPath(input.path); return readConfigInPath(input.path);
}), }),
}); });
// apt-get install apache2-utils

View File

@@ -61,6 +61,7 @@ export const findApplicationById = async (applicationId: string) => {
redirects: true, redirects: true,
security: true, security: true,
ports: true, ports: true,
registry: true,
}, },
}); });
if (!application) { if (!application) {

View File

@@ -0,0 +1,41 @@
export interface DockerNode {
ID: string;
Version: {
Index: number;
};
CreatedAt: string;
UpdatedAt: string;
Spec: {
Name: string;
Labels: Record<string, string>;
Role: "worker" | "manager";
Availability: "active" | "pause" | "drain";
};
Description: {
Hostname: string;
Platform: {
Architecture: string;
OS: string;
};
Resources: {
NanoCPUs: number;
MemoryBytes: number;
};
Engine: {
EngineVersion: string;
Plugins: Array<{
Type: string;
Name: string;
}>;
};
};
Status: {
State: "unknown" | "down" | "ready" | "disconnected";
Message: string;
Addr: string;
};
ManagerStatus?: {
Leader: boolean;
Addr: string;
};
}

View File

@@ -0,0 +1,113 @@
import { type apiCreateRegistry, registry } from "@/server/db/schema";
import { TRPCError } from "@trpc/server";
import { db } from "@/server/db";
import { eq } from "drizzle-orm";
import { findAdmin } from "./admin";
import {
manageRegistry,
removeSelfHostedRegistry,
} from "@/server/utils/traefik/registry";
import { removeService } from "@/server/utils/docker/utils";
import { initializeRegistry } from "@/server/setup/registry-setup";
export type Registry = typeof registry.$inferSelect;
export const createRegistry = async (input: typeof apiCreateRegistry._type) => {
const admin = await findAdmin();
const newRegistry = await db
.insert(registry)
.values({
...input,
adminId: admin.adminId,
})
.returning()
.then((value) => value[0]);
if (!newRegistry) {
throw new TRPCError({
code: "BAD_REQUEST",
message: "Error input: Inserting registry",
});
}
return newRegistry;
};
export const removeRegistry = async (registryId: string) => {
try {
const response = await db
.delete(registry)
.where(eq(registry.registryId, registryId))
.returning()
.then((res) => res[0]);
if (!response) {
throw new TRPCError({
code: "NOT_FOUND",
message: "Registry not found",
});
}
if (response.registryType === "selfHosted") {
await removeSelfHostedRegistry();
await removeService("dokploy-registry");
}
return response;
} catch (error) {
throw new TRPCError({
code: "BAD_REQUEST",
message: "Error to remove this registry",
cause: error,
});
}
};
export const updateRegistry = async (
registryId: string,
registryData: Partial<Registry>,
) => {
try {
const response = await db
.update(registry)
.set({
...registryData,
})
.where(eq(registry.registryId, registryId))
.returning()
.then((res) => res[0]);
if (response?.registryType === "selfHosted") {
await manageRegistry(response);
await initializeRegistry(response.username, response.password);
}
return response;
} catch (error) {
throw new TRPCError({
code: "BAD_REQUEST",
message: "Error to update this registry",
});
}
};
export const findRegistryById = async (registryId: string) => {
const registryResponse = await db.query.registry.findFirst({
where: eq(registry.registryId, registryId),
columns: {
password: false,
},
});
if (!registryResponse) {
throw new TRPCError({
code: "NOT_FOUND",
message: "Registry not found",
});
}
return registryResponse;
};
export const findAllRegistry = async () => {
const registryResponse = await db.query.registry.findMany();
return registryResponse;
};

View File

@@ -11,5 +11,6 @@ export const LOGS_PATH = `${BASE_PATH}/logs`;
export const APPLICATIONS_PATH = `${BASE_PATH}/applications`; export const APPLICATIONS_PATH = `${BASE_PATH}/applications`;
export const SSH_PATH = `${BASE_PATH}/ssh`; export const SSH_PATH = `${BASE_PATH}/ssh`;
export const CERTIFICATES_PATH = `${DYNAMIC_TRAEFIK_PATH}/certificates`; export const CERTIFICATES_PATH = `${DYNAMIC_TRAEFIK_PATH}/certificates`;
export const REGISTRY_PATH = `${DYNAMIC_TRAEFIK_PATH}/registry`;
export const MONITORING_PATH = `${BASE_PATH}/monitoring`; export const MONITORING_PATH = `${BASE_PATH}/monitoring`;
export const docker = new Docker(); export const docker = new Docker();

View File

@@ -1,13 +1,14 @@
import type { Config } from "drizzle-kit"; import { defineConfig } from "drizzle-kit";
console.log("> Generating PG Schema:", process.env.DATABASE_URL); export default defineConfig({
export default {
schema: "./server/db/schema/index.ts", schema: "./server/db/schema/index.ts",
driver: "pg", dialect: "postgresql",
dbCredentials: { dbCredentials: {
connectionString: process.env.DATABASE_URL || "", url: process.env.DATABASE_URL || "",
}, },
verbose: true,
strict: true,
out: "drizzle", out: "drizzle",
} satisfies Config; migrations: {
table: "migrations",
schema: "public",
},
});

View File

@@ -6,6 +6,7 @@ import { users } from "./user";
import { createInsertSchema } from "drizzle-zod"; import { createInsertSchema } from "drizzle-zod";
import { z } from "zod"; import { z } from "zod";
import { certificateType } from "./shared"; import { certificateType } from "./shared";
import { registry } from "./registry";
export const admins = pgTable("admin", { export const admins = pgTable("admin", {
adminId: text("adminId") adminId: text("adminId")
@@ -39,6 +40,7 @@ export const adminsRelations = relations(admins, ({ one, many }) => ({
references: [auth.id], references: [auth.id],
}), }),
users: many(users), users: many(users),
registry: many(registry),
})); }));
const createSchema = createInsertSchema(admins, { const createSchema = createInsertSchema(admins, {

View File

@@ -10,8 +10,16 @@ import { projects } from "./project";
import { security } from "./security"; import { security } from "./security";
import { applicationStatus } from "./shared"; import { applicationStatus } from "./shared";
import { ports } from "./port"; import { ports } from "./port";
import { boolean, integer, pgEnum, pgTable, text } from "drizzle-orm/pg-core"; import {
boolean,
integer,
json,
pgEnum,
pgTable,
text,
} from "drizzle-orm/pg-core";
import { generateAppName } from "./utils"; import { generateAppName } from "./utils";
import { registry } from "./registry";
export const sourceType = pgEnum("sourceType", ["docker", "git", "github"]); export const sourceType = pgEnum("sourceType", ["docker", "git", "github"]);
@@ -22,6 +30,65 @@ export const buildType = pgEnum("buildType", [
"nixpacks", "nixpacks",
]); ]);
// TODO: refactor this types
interface HealthCheckSwarm {
Test?: string[] | undefined;
Interval?: number | undefined;
Timeout?: number | undefined;
StartPeriod?: number | undefined;
Retries?: number | undefined;
}
interface RestartPolicySwarm {
Condition?: string | undefined;
Delay?: number | undefined;
MaxAttempts?: number | undefined;
Window?: number | undefined;
}
interface PlacementSwarm {
Constraints?: string[] | undefined;
Preferences?: Array<{ Spread: { SpreadDescriptor: string } }> | undefined;
MaxReplicas?: number | undefined;
Platforms?:
| Array<{
Architecture: string;
OS: string;
}>
| undefined;
}
interface UpdateConfigSwarm {
Parallelism: number;
Delay?: number | undefined;
FailureAction?: string | undefined;
Monitor?: number | undefined;
MaxFailureRatio?: number | undefined;
Order: string;
}
interface ServiceModeSwarm {
Replicated?: { Replicas?: number | undefined } | undefined;
Global?: {} | undefined;
ReplicatedJob?:
| {
MaxConcurrent?: number | undefined;
TotalCompletions?: number | undefined;
}
| undefined;
GlobalJob?: {} | undefined;
}
interface NetworkSwarm {
Target?: string | undefined;
Aliases?: string[] | undefined;
DriverOpts?: { [key: string]: string } | undefined;
}
interface LabelsSwarm {
[name: string]: string;
}
export const applications = pgTable("application", { export const applications = pgTable("application", {
applicationId: text("applicationId") applicationId: text("applicationId")
.notNull() .notNull()
@@ -60,6 +127,17 @@ export const applications = pgTable("application", {
customGitBuildPath: text("customGitBuildPath"), customGitBuildPath: text("customGitBuildPath"),
customGitSSHKey: text("customGitSSHKey"), customGitSSHKey: text("customGitSSHKey"),
dockerfile: text("dockerfile"), dockerfile: text("dockerfile"),
// Docker swarm json
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(),
applicationStatus: applicationStatus("applicationStatus") applicationStatus: applicationStatus("applicationStatus")
.notNull() .notNull()
.default("idle"), .default("idle"),
@@ -67,6 +145,9 @@ export const applications = pgTable("application", {
createdAt: text("createdAt") createdAt: text("createdAt")
.notNull() .notNull()
.$defaultFn(() => new Date().toISOString()), .$defaultFn(() => new Date().toISOString()),
registryId: text("registryId").references(() => registry.registryId, {
onDelete: "set null",
}),
projectId: text("projectId") projectId: text("projectId")
.notNull() .notNull()
.references(() => projects.projectId, { onDelete: "cascade" }), .references(() => projects.projectId, { onDelete: "cascade" }),
@@ -85,9 +166,101 @@ export const applicationsRelations = relations(
redirects: many(redirects), redirects: many(redirects),
security: many(security), security: many(security),
ports: many(ports), ports: many(ports),
registry: one(registry, {
fields: [applications.registryId],
references: [registry.registryId],
}),
}), }),
); );
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(),
@@ -124,6 +297,14 @@ const createSchema = createInsertSchema(applications, {
"nixpacks", "nixpacks",
]), ]),
owner: z.string(), owner: z.string(),
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 apiCreateApplication = createSchema.pick({ export const apiCreateApplication = createSchema.pick({

View File

@@ -1,6 +1,5 @@
export * from "./application"; export * from "./application";
export * from "./postgres"; export * from "./postgres";
export * from "./user"; export * from "./user";
export * from "./admin"; export * from "./admin";
export * from "./auth"; export * from "./auth";
@@ -20,3 +19,4 @@ export * from "./security";
export * from "./port"; export * from "./port";
export * from "./redis"; export * from "./redis";
export * from "./shared"; export * from "./shared";
export * from "./registry";

View File

@@ -0,0 +1,98 @@
import { createInsertSchema } from "drizzle-zod";
import { nanoid } from "nanoid";
import { relations, sql } from "drizzle-orm";
import { boolean, pgEnum, pgTable, text, timestamp } from "drizzle-orm/pg-core";
import { auth } from "./auth";
import { admins } from "./admin";
import { z } from "zod";
import { applications } from "./application";
/**
* This is an example of how to use the multi-project schema feature of Drizzle ORM. Use the same
* database instance for multiple projects.
*
* @see https://orm.drizzle.team/docs/goodies#multi-project-schema
*/
export const registryType = pgEnum("RegistryType", ["selfHosted", "cloud"]);
export const registry = pgTable("registry", {
registryId: text("registryId")
.notNull()
.primaryKey()
.$defaultFn(() => nanoid()),
registryName: text("registryName").notNull(),
imagePrefix: text("imagePrefix"),
username: text("username").notNull(),
password: text("password").notNull(),
registryUrl: text("registryUrl").notNull(),
createdAt: text("createdAt")
.notNull()
.$defaultFn(() => new Date().toISOString()),
registryType: registryType("selfHosted").notNull().default("cloud"),
adminId: text("adminId")
.notNull()
.references(() => admins.adminId, { onDelete: "cascade" }),
});
export const registryRelations = relations(registry, ({ one, many }) => ({
admin: one(admins, {
fields: [registry.adminId],
references: [admins.adminId],
}),
applications: many(applications),
}));
const createSchema = createInsertSchema(registry, {
registryName: z.string().min(1),
username: z.string().min(1),
password: z.string().min(1),
registryUrl: z.string().min(1),
adminId: z.string().min(1),
registryId: z.string().min(1),
registryType: z.enum(["selfHosted", "cloud"]),
imagePrefix: z.string().nullable().optional(),
});
export const apiCreateRegistry = createSchema
.pick({})
.extend({
registryName: z.string().min(1),
username: z.string().min(1),
password: z.string().min(1),
registryUrl: z.string(),
registryType: z.enum(["selfHosted", "cloud"]),
imagePrefix: z.string().nullable().optional(),
})
.required();
export const apiTestRegistry = createSchema.pick({}).extend({
registryName: z.string().min(1),
username: z.string().min(1),
password: z.string().min(1),
registryUrl: z.string(),
registryType: z.enum(["selfHosted", "cloud"]),
imagePrefix: z.string().nullable().optional(),
});
export const apiRemoveRegistry = createSchema
.pick({
registryId: true,
})
.required();
export const apiFindOneRegistry = createSchema
.pick({
registryId: true,
})
.required();
export const apiUpdateRegistry = createSchema.partial().extend({
registryId: z.string().min(1),
});
export const apiEnableSelfHostedRegistry = createSchema
.pick({
registryUrl: true,
username: true,
password: true,
})
.required();

View File

@@ -10,7 +10,7 @@ import {
import { setupDeploymentLogsWebSocketServer } from "./wss/listen-deployment"; import { setupDeploymentLogsWebSocketServer } from "./wss/listen-deployment";
import { setupDockerStatsMonitoringSocketServer } from "./wss/docker-stats"; import { setupDockerStatsMonitoringSocketServer } from "./wss/docker-stats";
import { setupDirectories } from "./setup/config-paths"; import { setupDirectories } from "./setup/config-paths";
import { initializeNetwork, initializeSwarm } from "./setup/setup"; import { initializeNetwork } from "./setup/setup";
import { import {
createDefaultMiddlewares, createDefaultMiddlewares,
createDefaultServerTraefikConfig, createDefaultServerTraefikConfig,
@@ -44,7 +44,6 @@ void app.prepare().then(async () => {
if (process.env.NODE_ENV === "production") { if (process.env.NODE_ENV === "production") {
setupDirectories(); setupDirectories();
createDefaultMiddlewares(); createDefaultMiddlewares();
await initializeSwarm();
await initializeNetwork(); await initializeNetwork();
createDefaultTraefikConfig(); createDefaultTraefikConfig();
createDefaultServerTraefikConfig(); createDefaultServerTraefikConfig();

View File

@@ -0,0 +1,89 @@
import type { CreateServiceOptions } from "dockerode";
import { docker, REGISTRY_PATH } from "../constants";
import { pullImage } from "../utils/docker/utils";
import { execAsync } from "../utils/process/execAsync";
import { generateRandomPassword } from "../auth/random-password";
export const initializeRegistry = async (
username: string,
password: string,
) => {
const imageName = "registry:2.8.3";
const containerName = "dokploy-registry";
await generateRegistryPassword(username, password);
const randomPass = await generateRandomPassword();
const settings: CreateServiceOptions = {
Name: containerName,
TaskTemplate: {
ContainerSpec: {
Image: imageName,
Env: [
"REGISTRY_STORAGE_DELETE_ENABLED=true",
"REGISTRY_AUTH=htpasswd",
"REGISTRY_AUTH_HTPASSWD_REALM=Registry Realm",
"REGISTRY_AUTH_HTPASSWD_PATH=/auth/htpasswd",
`REGISTRY_HTTP_SECRET=${randomPass.hashedPassword}`,
],
Mounts: [
{
Type: "bind",
Source: `${REGISTRY_PATH}/htpasswd`,
Target: "/auth/htpasswd",
ReadOnly: true,
},
{
Type: "volume",
Source: "registry-data",
Target: "/var/lib/registry",
ReadOnly: false,
},
],
},
Networks: [{ Target: "dokploy-network" }],
RestartPolicy: {
Condition: "on-failure",
},
},
Mode: {
Replicated: {
Replicas: 1,
},
},
EndpointSpec: {
Ports: [
{
TargetPort: 5000,
PublishedPort: 5000,
Protocol: "tcp",
PublishMode: "host",
},
],
},
};
try {
await pullImage(imageName);
const service = docker.getService(containerName);
const inspect = await service.inspect();
await service.update({
version: Number.parseInt(inspect.Version.Index),
...settings,
});
console.log("Registry Started ✅");
} catch (error) {
await docker.createService(settings);
console.log("Registry Not Found: Starting ✅");
}
};
const generateRegistryPassword = async (username: string, password: string) => {
try {
const command = `htpasswd -nbB ${username} "${password}" > ${REGISTRY_PATH}/htpasswd`;
const result = await execAsync(command);
console.log("Password generated ✅");
return result.stdout.trim();
} catch (error) {
console.error("Error generating password:", error);
return null;
}
};

View File

@@ -5,6 +5,7 @@ import type { CreateServiceOptions } from "dockerode";
import { import {
calculateResources, calculateResources,
generateBindMounts, generateBindMounts,
generateConfigContainer,
generateFileMounts, generateFileMounts,
generateVolumeMounts, generateVolumeMounts,
prepareEnvironmentVariables, prepareEnvironmentVariables,
@@ -13,6 +14,7 @@ import { buildCustomDocker } from "./docker-file";
import { buildHeroku } from "./heroku"; import { buildHeroku } from "./heroku";
import { buildNixpacks } from "./nixpacks"; import { buildNixpacks } from "./nixpacks";
import { buildPaketo } from "./paketo"; import { buildPaketo } from "./paketo";
import { uploadImage } from "../cluster/upload";
// NIXPACKS codeDirectory = where is the path of the code directory // NIXPACKS codeDirectory = where is the path of the code directory
// HEROKU codeDirectory = where is the path of the code directory // HEROKU codeDirectory = where is the path of the code directory
@@ -20,7 +22,7 @@ import { buildPaketo } from "./paketo";
// DOCKERFILE codeDirectory = where is the exact path of the (Dockerfile) // DOCKERFILE codeDirectory = where is the exact path of the (Dockerfile)
export type ApplicationNested = InferResultType< export type ApplicationNested = InferResultType<
"applications", "applications",
{ mounts: true; security: true; redirects: true; ports: true } { mounts: true; security: true; redirects: true; ports: true; registry: true }
>; >;
export const buildApplication = async ( export const buildApplication = async (
application: ApplicationNested, application: ApplicationNested,
@@ -42,6 +44,10 @@ export const buildApplication = async (
} else if (buildType === "dockerfile") { } else if (buildType === "dockerfile") {
await buildCustomDocker(application, writeStream); await buildCustomDocker(application, writeStream);
} }
if (application.registryId) {
await uploadImage(application, writeStream);
}
await mechanizeDockerContainer(application); await mechanizeDockerContainer(application);
writeStream.write("Docker Deployed: ✅"); writeStream.write("Docker Deployed: ✅");
} catch (error) { } catch (error) {
@@ -75,16 +81,50 @@ export const mechanizeDockerContainer = async (
cpuLimit, cpuLimit,
cpuReservation, cpuReservation,
}); });
const volumesMount = generateVolumeMounts(mounts); const volumesMount = generateVolumeMounts(mounts);
const {
HealthCheck,
RestartPolicy,
Placement,
Labels,
Mode,
RollbackConfig,
UpdateConfig,
Networks,
} = generateConfigContainer(application);
const bindsMount = generateBindMounts(mounts); const bindsMount = generateBindMounts(mounts);
const filesMount = generateFileMounts(appName, mounts); const filesMount = generateFileMounts(appName, mounts);
const envVariables = prepareEnvironmentVariables(env); const envVariables = prepareEnvironmentVariables(env);
const registry = application.registry;
let image =
sourceType === "docker"
? dockerImage || "ERROR-NO-IMAGE-PROVIDED"
: `${appName}:latest`;
if (registry) {
image = `${registry.registryUrl}/${appName}`;
if (registry.imagePrefix) {
image = `${registry.registryUrl}/${registry.imagePrefix}/${appName}`;
}
}
const settings: CreateServiceOptions = { const settings: CreateServiceOptions = {
authconfig: {
password: registry?.password || "",
username: registry?.username || "",
serveraddress: registry?.registryUrl || "",
},
Name: appName, Name: appName,
TaskTemplate: { TaskTemplate: {
ContainerSpec: { ContainerSpec: {
Image: sourceType === "docker" ? dockerImage! : `${appName}:latest`, HealthCheck,
Image: image,
Env: envVariables, Env: envVariables,
Mounts: [...volumesMount, ...bindsMount, ...filesMount], Mounts: [...volumesMount, ...bindsMount, ...filesMount],
...(command ...(command
@@ -93,20 +133,17 @@ export const mechanizeDockerContainer = async (
Args: ["-c", command], Args: ["-c", command],
} }
: {}), : {}),
Labels,
}, },
Networks: [{ Target: "dokploy-network" }], Networks,
RestartPolicy: { RestartPolicy,
Condition: "on-failure", Placement,
},
Resources: { Resources: {
...resources, ...resources,
}, },
}, },
Mode: { Mode,
Replicated: { RollbackConfig,
Replicas: 1,
},
},
EndpointSpec: { EndpointSpec: {
Ports: ports.map((port) => ({ Ports: ports.map((port) => ({
Protocol: port.protocol, Protocol: port.protocol,
@@ -114,10 +151,7 @@ export const mechanizeDockerContainer = async (
PublishedPort: port.publishedPort, PublishedPort: port.publishedPort,
})), })),
}, },
UpdateConfig: { UpdateConfig,
Parallelism: 1,
Order: "start-first",
},
}; };
try { try {
@@ -132,7 +166,7 @@ export const mechanizeDockerContainer = async (
}, },
}); });
} catch (error) { } catch (error) {
console.log(error);
await docker.createService(settings); await docker.createService(settings);
} }
// await cleanUpUnusedImages();
}; };

View File

@@ -0,0 +1,65 @@
import type { ApplicationNested } from "../builders";
import { spawnAsync } from "../process/spawnAsync";
import type { WriteStream } from "node:fs";
export const uploadImage = async (
application: ApplicationNested,
writeStream: WriteStream,
) => {
const registry = application.registry;
if (!registry) {
throw new Error("Registry not found");
}
const { registryUrl, imagePrefix, registryType } = registry;
const { appName } = application;
const imageName = `${appName}:latest`;
const finalURL =
registryType === "selfHosted"
? process.env.NODE_ENV === "development"
? "localhost:5000"
: registryUrl
: registryUrl;
const registryTag = imagePrefix
? `${finalURL}/${imagePrefix}/${imageName}`
: `${finalURL}/${imageName}`;
try {
console.log(finalURL, registryTag);
writeStream.write(
`📦 [Enabled Registry] Uploading image to ${registry.registryType} | ${registryTag} | ${finalURL}\n`,
);
await spawnAsync(
"docker",
["login", finalURL, "-u", registry.username, "-p", registry.password],
(data) => {
if (writeStream.writable) {
writeStream.write(data);
}
},
);
await spawnAsync("docker", ["tag", imageName, registryTag], (data) => {
if (writeStream.writable) {
writeStream.write(data);
}
});
await spawnAsync("docker", ["push", registryTag], (data) => {
if (writeStream.writable) {
writeStream.write(data);
}
});
} catch (error) {
console.log(error);
throw error;
}
};
// docker:
// endpoint: "unix:///var/run/docker.sock"
// exposedByDefault: false
// swarmMode: true

View File

@@ -122,10 +122,10 @@ export const cleanUpInactiveContainers = async () => {
for (const container of inactiveContainers) { for (const container of inactiveContainers) {
await docker.getContainer(container.Id).remove({ force: true }); await docker.getContainer(container.Id).remove({ force: true });
console.log(`Contenedor eliminado: ${container.Id}`); console.log(`Cleaning up inactive container: ${container.Id}`);
} }
} catch (error) { } catch (error) {
console.error("Error al limpiar contenedores inactivos:", error); console.error("Error cleaning up inactive containers:", error);
throw error; throw error;
} }
}; };
@@ -199,6 +199,83 @@ export const calculateResources = ({
}; };
}; };
export const generateConfigContainer = (application: ApplicationNested) => {
const {
healthCheckSwarm,
restartPolicySwarm,
placementSwarm,
updateConfigSwarm,
rollbackConfigSwarm,
modeSwarm,
labelsSwarm,
replicas,
mounts,
networkSwarm,
} = application;
const haveMounts = mounts.length > 0;
return {
...(healthCheckSwarm && {
HealthCheck: healthCheckSwarm,
}),
...(restartPolicySwarm
? {
RestartPolicy: restartPolicySwarm,
}
: {
// if no restartPolicySwarm provided use default
RestartPolicy: {
Condition: "on-failure",
},
}),
...(placementSwarm
? {
Placement: placementSwarm,
}
: {
// if app have mounts keep manager as constraint
Placement: {
Constraints: haveMounts ? ["node.role==manager"] : [],
},
}),
...(labelsSwarm && {
Labels: labelsSwarm,
}),
...(modeSwarm
? {
Mode: modeSwarm,
}
: {
// use replicas value if no modeSwarm provided
Mode: {
Replicated: {
Replicas: replicas,
},
},
}),
...(rollbackConfigSwarm && {
RollbackConfig: rollbackConfigSwarm,
}),
...(updateConfigSwarm
? { UpdateConfig: updateConfigSwarm }
: {
// default config if no updateConfigSwarm provided
UpdateConfig: {
Parallelism: 1,
Order: "start-first",
},
}),
...(networkSwarm
? {
Networks: networkSwarm,
}
: {
Networks: [{ Target: "dokploy-network" }],
}),
};
};
export const generateBindMounts = (mounts: ApplicationNested["mounts"]) => { export const generateBindMounts = (mounts: ApplicationNested["mounts"]) => {
if (!mounts || mounts.length === 0) { if (!mounts || mounts.length === 0) {
return []; return [];

Some files were not shown because too many files have changed in this diff Show More