mirror of
https://github.com/Dokploy/dokploy.git
synced 2026-10-15 13:21:11 +00:00
Merge pull request #96 from Dokploy/feat/multi-node-support
Feat/multi node support
This commit is contained in:
+1
-2
@@ -26,7 +26,7 @@ FROM node:18-slim AS production
|
||||
# Install dependencies only for production
|
||||
ENV PNPM_HOME="/pnpm"
|
||||
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
|
||||
|
||||
@@ -47,7 +47,6 @@ RUN --mount=type=cache,id=pnpm,target=/pnpm/store pnpm install --prod --frozen-l
|
||||
# Install docker
|
||||
RUN curl -fsSL https://get.docker.com -o get-docker.sh && sh get-docker.sh && rm get-docker.sh
|
||||
|
||||
|
||||
# Install Nixpacks and tsx
|
||||
# | VERBOSE=1 VERSION=1.21.0 bash
|
||||
RUN curl -sSL https://nixpacks.com/install.sh -o install.sh \
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
};
|
||||
@@ -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>
|
||||
);
|
||||
};
|
||||
@@ -20,7 +20,7 @@ import { Input } from "@/components/ui/input";
|
||||
import { api } from "@/utils/api";
|
||||
import { AlertBlock } from "@/components/shared/alert-block";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { Pencil } from "lucide-react";
|
||||
import { PenBoxIcon, Pencil } from "lucide-react";
|
||||
import { useEffect } from "react";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { toast } from "sonner";
|
||||
@@ -99,7 +99,7 @@ export const UpdatePort = ({ portId }: Props) => {
|
||||
<Dialog>
|
||||
<DialogTrigger asChild>
|
||||
<Button variant="ghost" isLoading={isLoading}>
|
||||
<Pencil className="size-4 text-muted-foreground" />
|
||||
<PenBoxIcon className="size-4 text-muted-foreground" />
|
||||
</Button>
|
||||
</DialogTrigger>
|
||||
<DialogContent className="max-h-screen overflow-y-auto sm:max-w-lg">
|
||||
|
||||
@@ -21,7 +21,7 @@ import { Input } from "@/components/ui/input";
|
||||
import { api } from "@/utils/api";
|
||||
import { AlertBlock } from "@/components/shared/alert-block";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { Pencil } from "lucide-react";
|
||||
import { PenBoxIcon, Pencil } from "lucide-react";
|
||||
import { useEffect } from "react";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { toast } from "sonner";
|
||||
@@ -94,7 +94,7 @@ export const UpdateRedirect = ({ redirectId }: Props) => {
|
||||
<Dialog>
|
||||
<DialogTrigger asChild>
|
||||
<Button variant="ghost" isLoading={isLoading}>
|
||||
<Pencil className="size-4 text-muted-foreground" />
|
||||
<PenBoxIcon className="size-4 text-muted-foreground" />
|
||||
</Button>
|
||||
</DialogTrigger>
|
||||
<DialogContent className="max-h-screen overflow-y-auto sm:max-w-lg">
|
||||
|
||||
@@ -20,7 +20,7 @@ import { Input } from "@/components/ui/input";
|
||||
import { api } from "@/utils/api";
|
||||
import { AlertBlock } from "@/components/shared/alert-block";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { Pencil } from "lucide-react";
|
||||
import { PenBoxIcon, Pencil } from "lucide-react";
|
||||
import { useEffect } from "react";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { toast } from "sonner";
|
||||
@@ -89,7 +89,7 @@ export const UpdateSecurity = ({ securityId }: Props) => {
|
||||
<Dialog>
|
||||
<DialogTrigger asChild>
|
||||
<Button variant="ghost" isLoading={isLoading}>
|
||||
<Pencil className="size-4 text-muted-foreground" />
|
||||
<PenBoxIcon className="size-4 text-muted-foreground" />
|
||||
</Button>
|
||||
</DialogTrigger>
|
||||
<DialogContent className="max-h-screen overflow-y-auto sm:max-w-lg">
|
||||
|
||||
@@ -25,8 +25,8 @@ export const DeleteApplication = ({ applicationId }: Props) => {
|
||||
return (
|
||||
<AlertDialog>
|
||||
<AlertDialogTrigger asChild>
|
||||
<Button variant="destructive" isLoading={isLoading}>
|
||||
<TrashIcon className="size-4 " />
|
||||
<Button variant="ghost" isLoading={isLoading}>
|
||||
<TrashIcon className="size-4 text-muted-foreground" />
|
||||
</Button>
|
||||
</AlertDialogTrigger>
|
||||
<AlertDialogContent>
|
||||
|
||||
@@ -24,8 +24,8 @@ export const DeleteDomain = ({ domainId }: Props) => {
|
||||
return (
|
||||
<AlertDialog>
|
||||
<AlertDialogTrigger asChild>
|
||||
<Button variant="destructive" isLoading={isLoading}>
|
||||
<TrashIcon className="size-4 " />
|
||||
<Button variant="ghost" isLoading={isLoading}>
|
||||
<TrashIcon className="size-4 text-muted-foreground " />
|
||||
</Button>
|
||||
</AlertDialogTrigger>
|
||||
<AlertDialogContent>
|
||||
|
||||
@@ -40,7 +40,9 @@ export const ShowDomains = ({ applicationId }: Props) => {
|
||||
</div>
|
||||
|
||||
{data && data?.length > 0 && (
|
||||
<AddDomain applicationId={applicationId} />
|
||||
<AddDomain applicationId={applicationId}>
|
||||
<GlobeIcon className="size-4" /> Add Domain
|
||||
</AddDomain>
|
||||
)}
|
||||
</CardHeader>
|
||||
<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
|
||||
domain
|
||||
</span>
|
||||
<AddDomain applicationId={applicationId}>Add Domain</AddDomain>
|
||||
<AddDomain applicationId={applicationId}>
|
||||
<GlobeIcon className="size-4" /> Add Domain
|
||||
</AddDomain>
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex w-full flex-col gap-4">
|
||||
@@ -75,8 +79,10 @@ export const ShowDomains = ({ applicationId }: Props) => {
|
||||
<Button variant="outline" disabled>
|
||||
{item.https ? "HTTPS" : "HTTP"}
|
||||
</Button>
|
||||
<UpdateDomain domainId={item.domainId} />
|
||||
<DeleteDomain domainId={item.domainId} />
|
||||
<div className="flex flex-row gap-1">
|
||||
<UpdateDomain domainId={item.domainId} />
|
||||
<DeleteDomain domainId={item.domainId} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
|
||||
@@ -115,8 +115,8 @@ export const UpdateDomain = ({ domainId }: Props) => {
|
||||
return (
|
||||
<Dialog>
|
||||
<DialogTrigger className="" asChild>
|
||||
<Button>
|
||||
<PenBoxIcon className="size-4" />
|
||||
<Button variant="ghost">
|
||||
<PenBoxIcon className="size-4 text-muted-foreground" />
|
||||
</Button>
|
||||
</DialogTrigger>
|
||||
<DialogContent className="max-h-screen overflow-y-auto sm:max-w-2xl">
|
||||
|
||||
@@ -90,7 +90,7 @@ export const UpdateApplication = ({ applicationId }: Props) => {
|
||||
<Dialog>
|
||||
<DialogTrigger asChild>
|
||||
<Button variant="ghost">
|
||||
<SquarePen className="size-4" />
|
||||
<SquarePen className="size-4 text-muted-foreground" />
|
||||
</Button>
|
||||
</DialogTrigger>
|
||||
<DialogContent className="max-h-screen overflow-y-auto sm:max-w-lg">
|
||||
|
||||
@@ -20,7 +20,7 @@ import {
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { api } from "@/utils/api";
|
||||
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 { useForm } from "react-hook-form";
|
||||
import { toast } from "sonner";
|
||||
@@ -115,7 +115,7 @@ export const UpdateBackup = ({ backupId, refetch }: Props) => {
|
||||
<Dialog>
|
||||
<DialogTrigger asChild>
|
||||
<Button variant="ghost">
|
||||
<Pencil className="size-4 text-muted-foreground" />
|
||||
<PenBoxIcon className="size-4 text-muted-foreground" />
|
||||
</Button>
|
||||
</DialogTrigger>
|
||||
<DialogContent className="max-h-screen overflow-y-auto sm:max-w-lg">
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
import {
|
||||
AlertDialog,
|
||||
AlertDialogAction,
|
||||
AlertDialogCancel,
|
||||
AlertDialogContent,
|
||||
AlertDialogDescription,
|
||||
AlertDialogFooter,
|
||||
AlertDialogHeader,
|
||||
AlertDialogTitle,
|
||||
AlertDialogTrigger,
|
||||
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";
|
||||
@@ -16,47 +16,47 @@ import { useRouter } from "next/router";
|
||||
import { toast } from "sonner";
|
||||
|
||||
interface Props {
|
||||
mariadbId: string;
|
||||
mariadbId: string;
|
||||
}
|
||||
|
||||
export const DeleteMariadb = ({ mariadbId }: Props) => {
|
||||
const { mutateAsync, isLoading } = api.mariadb.remove.useMutation();
|
||||
const { push } = useRouter();
|
||||
return (
|
||||
<AlertDialog>
|
||||
<AlertDialogTrigger asChild>
|
||||
<Button variant="destructive" isLoading={isLoading}>
|
||||
<TrashIcon className="size-4 " />
|
||||
</Button>
|
||||
</AlertDialogTrigger>
|
||||
<AlertDialogContent>
|
||||
<AlertDialogHeader>
|
||||
<AlertDialogTitle>Are you absolutely sure?</AlertDialogTitle>
|
||||
<AlertDialogDescription>
|
||||
This action cannot be undone. This will permanently delete the
|
||||
database
|
||||
</AlertDialogDescription>
|
||||
</AlertDialogHeader>
|
||||
<AlertDialogFooter>
|
||||
<AlertDialogCancel>Cancel</AlertDialogCancel>
|
||||
<AlertDialogAction
|
||||
onClick={async () => {
|
||||
await mutateAsync({
|
||||
mariadbId,
|
||||
})
|
||||
.then((data) => {
|
||||
push(`/dashboard/project/${data?.projectId}`);
|
||||
toast.success("Database delete succesfully");
|
||||
})
|
||||
.catch(() => {
|
||||
toast.error("Error to delete the database");
|
||||
});
|
||||
}}
|
||||
>
|
||||
Confirm
|
||||
</AlertDialogAction>
|
||||
</AlertDialogFooter>
|
||||
</AlertDialogContent>
|
||||
</AlertDialog>
|
||||
);
|
||||
const { mutateAsync, isLoading } = api.mariadb.remove.useMutation();
|
||||
const { push } = useRouter();
|
||||
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
|
||||
database
|
||||
</AlertDialogDescription>
|
||||
</AlertDialogHeader>
|
||||
<AlertDialogFooter>
|
||||
<AlertDialogCancel>Cancel</AlertDialogCancel>
|
||||
<AlertDialogAction
|
||||
onClick={async () => {
|
||||
await mutateAsync({
|
||||
mariadbId,
|
||||
})
|
||||
.then((data) => {
|
||||
push(`/dashboard/project/${data?.projectId}`);
|
||||
toast.success("Database delete succesfully");
|
||||
})
|
||||
.catch(() => {
|
||||
toast.error("Error to delete the database");
|
||||
});
|
||||
}}
|
||||
>
|
||||
Confirm
|
||||
</AlertDialogAction>
|
||||
</AlertDialogFooter>
|
||||
</AlertDialogContent>
|
||||
</AlertDialog>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -90,7 +90,7 @@ export const UpdateMariadb = ({ mariadbId }: Props) => {
|
||||
<Dialog>
|
||||
<DialogTrigger asChild>
|
||||
<Button variant="ghost">
|
||||
<SquarePen className="size-4" />
|
||||
<SquarePen className="size-4 text-muted-foreground" />
|
||||
</Button>
|
||||
</DialogTrigger>
|
||||
<DialogContent className="max-h-screen overflow-y-auto sm:max-w-lg">
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
import {
|
||||
AlertDialog,
|
||||
AlertDialogAction,
|
||||
AlertDialogCancel,
|
||||
AlertDialogContent,
|
||||
AlertDialogDescription,
|
||||
AlertDialogFooter,
|
||||
AlertDialogHeader,
|
||||
AlertDialogTitle,
|
||||
AlertDialogTrigger,
|
||||
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";
|
||||
@@ -16,47 +16,47 @@ import { useRouter } from "next/router";
|
||||
import { toast } from "sonner";
|
||||
|
||||
interface Props {
|
||||
mongoId: string;
|
||||
mongoId: string;
|
||||
}
|
||||
|
||||
export const DeleteMongo = ({ mongoId }: Props) => {
|
||||
const { mutateAsync, isLoading } = api.mongo.remove.useMutation();
|
||||
const { push } = useRouter();
|
||||
return (
|
||||
<AlertDialog>
|
||||
<AlertDialogTrigger asChild>
|
||||
<Button variant="destructive" isLoading={isLoading}>
|
||||
<TrashIcon className="size-4 " />
|
||||
</Button>
|
||||
</AlertDialogTrigger>
|
||||
<AlertDialogContent>
|
||||
<AlertDialogHeader>
|
||||
<AlertDialogTitle>Are you absolutely sure?</AlertDialogTitle>
|
||||
<AlertDialogDescription>
|
||||
This action cannot be undone. This will permanently delete the
|
||||
database
|
||||
</AlertDialogDescription>
|
||||
</AlertDialogHeader>
|
||||
<AlertDialogFooter>
|
||||
<AlertDialogCancel>Cancel</AlertDialogCancel>
|
||||
<AlertDialogAction
|
||||
onClick={async () => {
|
||||
await mutateAsync({
|
||||
mongoId,
|
||||
})
|
||||
.then((data) => {
|
||||
push(`/dashboard/project/${data?.projectId}`);
|
||||
toast.success("Database delete succesfully");
|
||||
})
|
||||
.catch(() => {
|
||||
toast.error("Error to delete the database");
|
||||
});
|
||||
}}
|
||||
>
|
||||
Confirm
|
||||
</AlertDialogAction>
|
||||
</AlertDialogFooter>
|
||||
</AlertDialogContent>
|
||||
</AlertDialog>
|
||||
);
|
||||
const { mutateAsync, isLoading } = api.mongo.remove.useMutation();
|
||||
const { push } = useRouter();
|
||||
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
|
||||
database
|
||||
</AlertDialogDescription>
|
||||
</AlertDialogHeader>
|
||||
<AlertDialogFooter>
|
||||
<AlertDialogCancel>Cancel</AlertDialogCancel>
|
||||
<AlertDialogAction
|
||||
onClick={async () => {
|
||||
await mutateAsync({
|
||||
mongoId,
|
||||
})
|
||||
.then((data) => {
|
||||
push(`/dashboard/project/${data?.projectId}`);
|
||||
toast.success("Database delete succesfully");
|
||||
})
|
||||
.catch(() => {
|
||||
toast.error("Error to delete the database");
|
||||
});
|
||||
}}
|
||||
>
|
||||
Confirm
|
||||
</AlertDialogAction>
|
||||
</AlertDialogFooter>
|
||||
</AlertDialogContent>
|
||||
</AlertDialog>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -90,7 +90,7 @@ export const UpdateMongo = ({ mongoId }: Props) => {
|
||||
<Dialog>
|
||||
<DialogTrigger asChild>
|
||||
<Button variant="ghost">
|
||||
<SquarePen className="size-4" />
|
||||
<SquarePen className="size-4 text-muted-foreground" />
|
||||
</Button>
|
||||
</DialogTrigger>
|
||||
<DialogContent className="max-h-screen overflow-y-auto sm:max-w-lg">
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
import {
|
||||
AlertDialog,
|
||||
AlertDialogAction,
|
||||
AlertDialogCancel,
|
||||
AlertDialogContent,
|
||||
AlertDialogDescription,
|
||||
AlertDialogFooter,
|
||||
AlertDialogHeader,
|
||||
AlertDialogTitle,
|
||||
AlertDialogTrigger,
|
||||
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";
|
||||
@@ -16,47 +16,47 @@ import { useRouter } from "next/router";
|
||||
import { toast } from "sonner";
|
||||
|
||||
interface Props {
|
||||
mysqlId: string;
|
||||
mysqlId: string;
|
||||
}
|
||||
|
||||
export const DeleteMysql = ({ mysqlId }: Props) => {
|
||||
const { mutateAsync, isLoading } = api.mysql.remove.useMutation();
|
||||
const { push } = useRouter();
|
||||
return (
|
||||
<AlertDialog>
|
||||
<AlertDialogTrigger asChild>
|
||||
<Button variant="destructive" isLoading={isLoading}>
|
||||
<TrashIcon className="size-4 " />
|
||||
</Button>
|
||||
</AlertDialogTrigger>
|
||||
<AlertDialogContent>
|
||||
<AlertDialogHeader>
|
||||
<AlertDialogTitle>Are you absolutely sure?</AlertDialogTitle>
|
||||
<AlertDialogDescription>
|
||||
This action cannot be undone. This will permanently delete the
|
||||
database
|
||||
</AlertDialogDescription>
|
||||
</AlertDialogHeader>
|
||||
<AlertDialogFooter>
|
||||
<AlertDialogCancel>Cancel</AlertDialogCancel>
|
||||
<AlertDialogAction
|
||||
onClick={async () => {
|
||||
await mutateAsync({
|
||||
mysqlId,
|
||||
})
|
||||
.then((data) => {
|
||||
push(`/dashboard/project/${data?.projectId}`);
|
||||
toast.success("Database delete succesfully");
|
||||
})
|
||||
.catch(() => {
|
||||
toast.error("Error to delete the database");
|
||||
});
|
||||
}}
|
||||
>
|
||||
Confirm
|
||||
</AlertDialogAction>
|
||||
</AlertDialogFooter>
|
||||
</AlertDialogContent>
|
||||
</AlertDialog>
|
||||
);
|
||||
const { mutateAsync, isLoading } = api.mysql.remove.useMutation();
|
||||
const { push } = useRouter();
|
||||
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
|
||||
database
|
||||
</AlertDialogDescription>
|
||||
</AlertDialogHeader>
|
||||
<AlertDialogFooter>
|
||||
<AlertDialogCancel>Cancel</AlertDialogCancel>
|
||||
<AlertDialogAction
|
||||
onClick={async () => {
|
||||
await mutateAsync({
|
||||
mysqlId,
|
||||
})
|
||||
.then((data) => {
|
||||
push(`/dashboard/project/${data?.projectId}`);
|
||||
toast.success("Database delete succesfully");
|
||||
})
|
||||
.catch(() => {
|
||||
toast.error("Error to delete the database");
|
||||
});
|
||||
}}
|
||||
>
|
||||
Confirm
|
||||
</AlertDialogAction>
|
||||
</AlertDialogFooter>
|
||||
</AlertDialogContent>
|
||||
</AlertDialog>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -90,7 +90,7 @@ export const UpdateMysql = ({ mysqlId }: Props) => {
|
||||
<Dialog>
|
||||
<DialogTrigger asChild>
|
||||
<Button variant="ghost">
|
||||
<SquarePen className="size-4" />
|
||||
<SquarePen className="size-4 text-muted-foreground" />
|
||||
</Button>
|
||||
</DialogTrigger>
|
||||
<DialogContent className="max-h-screen overflow-y-auto sm:max-w-lg">
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
import {
|
||||
AlertDialog,
|
||||
AlertDialogAction,
|
||||
AlertDialogCancel,
|
||||
AlertDialogContent,
|
||||
AlertDialogDescription,
|
||||
AlertDialogFooter,
|
||||
AlertDialogHeader,
|
||||
AlertDialogTitle,
|
||||
AlertDialogTrigger,
|
||||
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";
|
||||
@@ -16,47 +16,47 @@ import { useRouter } from "next/router";
|
||||
import { toast } from "sonner";
|
||||
|
||||
interface Props {
|
||||
postgresId: string;
|
||||
postgresId: string;
|
||||
}
|
||||
|
||||
export const DeletePostgres = ({ postgresId }: Props) => {
|
||||
const { mutateAsync, isLoading } = api.postgres.remove.useMutation();
|
||||
const { push } = useRouter();
|
||||
return (
|
||||
<AlertDialog>
|
||||
<AlertDialogTrigger asChild>
|
||||
<Button variant="destructive" isLoading={isLoading}>
|
||||
<TrashIcon className="size-4 " />
|
||||
</Button>
|
||||
</AlertDialogTrigger>
|
||||
<AlertDialogContent>
|
||||
<AlertDialogHeader>
|
||||
<AlertDialogTitle>Are you absolutely sure?</AlertDialogTitle>
|
||||
<AlertDialogDescription>
|
||||
This action cannot be undone. This will permanently delete the
|
||||
database
|
||||
</AlertDialogDescription>
|
||||
</AlertDialogHeader>
|
||||
<AlertDialogFooter>
|
||||
<AlertDialogCancel>Cancel</AlertDialogCancel>
|
||||
<AlertDialogAction
|
||||
onClick={async () => {
|
||||
await mutateAsync({
|
||||
postgresId,
|
||||
})
|
||||
.then((data) => {
|
||||
push(`/dashboard/project/${data?.projectId}`);
|
||||
toast.success("Database delete succesfully");
|
||||
})
|
||||
.catch(() => {
|
||||
toast.error("Error to delete the database");
|
||||
});
|
||||
}}
|
||||
>
|
||||
Confirm
|
||||
</AlertDialogAction>
|
||||
</AlertDialogFooter>
|
||||
</AlertDialogContent>
|
||||
</AlertDialog>
|
||||
);
|
||||
const { mutateAsync, isLoading } = api.postgres.remove.useMutation();
|
||||
const { push } = useRouter();
|
||||
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
|
||||
database
|
||||
</AlertDialogDescription>
|
||||
</AlertDialogHeader>
|
||||
<AlertDialogFooter>
|
||||
<AlertDialogCancel>Cancel</AlertDialogCancel>
|
||||
<AlertDialogAction
|
||||
onClick={async () => {
|
||||
await mutateAsync({
|
||||
postgresId,
|
||||
})
|
||||
.then((data) => {
|
||||
push(`/dashboard/project/${data?.projectId}`);
|
||||
toast.success("Database delete succesfully");
|
||||
})
|
||||
.catch(() => {
|
||||
toast.error("Error to delete the database");
|
||||
});
|
||||
}}
|
||||
>
|
||||
Confirm
|
||||
</AlertDialogAction>
|
||||
</AlertDialogFooter>
|
||||
</AlertDialogContent>
|
||||
</AlertDialog>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -90,7 +90,7 @@ export const UpdatePostgres = ({ postgresId }: Props) => {
|
||||
<Dialog>
|
||||
<DialogTrigger asChild>
|
||||
<Button variant="ghost">
|
||||
<SquarePen className="size-4" />
|
||||
<SquarePen className="size-4 text-muted-foreground" />
|
||||
</Button>
|
||||
</DialogTrigger>
|
||||
<DialogContent className="max-h-screen overflow-y-auto sm:max-w-lg">
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
import {
|
||||
AlertDialog,
|
||||
AlertDialogAction,
|
||||
AlertDialogCancel,
|
||||
AlertDialogContent,
|
||||
AlertDialogDescription,
|
||||
AlertDialogFooter,
|
||||
AlertDialogHeader,
|
||||
AlertDialogTitle,
|
||||
AlertDialogTrigger,
|
||||
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";
|
||||
@@ -16,47 +16,47 @@ import { useRouter } from "next/router";
|
||||
import { toast } from "sonner";
|
||||
|
||||
interface Props {
|
||||
redisId: string;
|
||||
redisId: string;
|
||||
}
|
||||
|
||||
export const DeleteRedis = ({ redisId }: Props) => {
|
||||
const { mutateAsync, isLoading } = api.redis.remove.useMutation();
|
||||
const { push } = useRouter();
|
||||
return (
|
||||
<AlertDialog>
|
||||
<AlertDialogTrigger asChild>
|
||||
<Button variant="destructive" isLoading={isLoading}>
|
||||
<TrashIcon className="size-4 " />
|
||||
</Button>
|
||||
</AlertDialogTrigger>
|
||||
<AlertDialogContent>
|
||||
<AlertDialogHeader>
|
||||
<AlertDialogTitle>Are you absolutely sure?</AlertDialogTitle>
|
||||
<AlertDialogDescription>
|
||||
This action cannot be undone. This will permanently delete the
|
||||
database
|
||||
</AlertDialogDescription>
|
||||
</AlertDialogHeader>
|
||||
<AlertDialogFooter>
|
||||
<AlertDialogCancel>Cancel</AlertDialogCancel>
|
||||
<AlertDialogAction
|
||||
onClick={async () => {
|
||||
await mutateAsync({
|
||||
redisId,
|
||||
})
|
||||
.then((data) => {
|
||||
push(`/dashboard/project/${data?.projectId}`);
|
||||
toast.success("Database delete succesfully");
|
||||
})
|
||||
.catch(() => {
|
||||
toast.error("Error to delete the database");
|
||||
});
|
||||
}}
|
||||
>
|
||||
Confirm
|
||||
</AlertDialogAction>
|
||||
</AlertDialogFooter>
|
||||
</AlertDialogContent>
|
||||
</AlertDialog>
|
||||
);
|
||||
const { mutateAsync, isLoading } = api.redis.remove.useMutation();
|
||||
const { push } = useRouter();
|
||||
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
|
||||
database
|
||||
</AlertDialogDescription>
|
||||
</AlertDialogHeader>
|
||||
<AlertDialogFooter>
|
||||
<AlertDialogCancel>Cancel</AlertDialogCancel>
|
||||
<AlertDialogAction
|
||||
onClick={async () => {
|
||||
await mutateAsync({
|
||||
redisId,
|
||||
})
|
||||
.then((data) => {
|
||||
push(`/dashboard/project/${data?.projectId}`);
|
||||
toast.success("Database delete succesfully");
|
||||
})
|
||||
.catch(() => {
|
||||
toast.error("Error to delete the database");
|
||||
});
|
||||
}}
|
||||
>
|
||||
Confirm
|
||||
</AlertDialogAction>
|
||||
</AlertDialogFooter>
|
||||
</AlertDialogContent>
|
||||
</AlertDialog>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -90,7 +90,7 @@ export const UpdateRedis = ({ redisId }: Props) => {
|
||||
<Dialog>
|
||||
<DialogTrigger asChild>
|
||||
<Button variant="ghost">
|
||||
<SquarePen className="size-4" />
|
||||
<SquarePen className="size-4 text-muted-foreground" />
|
||||
</Button>
|
||||
</DialogTrigger>
|
||||
<DialogContent className="max-h-screen overflow-y-auto sm:max-w-lg">
|
||||
|
||||
@@ -33,21 +33,23 @@ export const ShowCertificates = () => {
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex flex-col gap-6">
|
||||
{data?.map((destination, index) => (
|
||||
<div
|
||||
key={destination.certificateId}
|
||||
className="flex items-center justify-between"
|
||||
>
|
||||
<span className="text-sm text-muted-foreground">
|
||||
{index + 1}. {destination.name}
|
||||
</span>
|
||||
<div className="flex flex-row gap-3">
|
||||
<DeleteCertificate
|
||||
certificateId={destination.certificateId}
|
||||
/>
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 gap-6">
|
||||
{data?.map((destination, index) => (
|
||||
<div
|
||||
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>
|
||||
<div className="flex flex-row gap-3">
|
||||
<DeleteCertificate
|
||||
certificateId={destination.certificateId}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
))}
|
||||
</div>
|
||||
<div>
|
||||
<AddCertificate />
|
||||
</div>
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
};
|
||||
@@ -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>
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -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>
|
||||
);
|
||||
};
|
||||
@@ -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>
|
||||
);
|
||||
};
|
||||
@@ -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>
|
||||
);
|
||||
};
|
||||
@@ -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>
|
||||
);
|
||||
};
|
||||
@@ -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>
|
||||
);
|
||||
};
|
||||
@@ -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>
|
||||
);
|
||||
};
|
||||
@@ -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>
|
||||
);
|
||||
};
|
||||
@@ -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>
|
||||
);
|
||||
};
|
||||
@@ -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>
|
||||
);
|
||||
};
|
||||
@@ -34,16 +34,16 @@ export const ShowDestinations = () => {
|
||||
<AddDestination />
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex flex-col gap-6">
|
||||
<div className="flex flex-col gap-4">
|
||||
{data?.map((destination, index) => (
|
||||
<div
|
||||
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">
|
||||
{index + 1}. {destination.name}
|
||||
</span>
|
||||
<div className="flex flex-row gap-3">
|
||||
<div className="flex flex-row gap-1">
|
||||
<UpdateDestination
|
||||
destinationId={destination.destinationId}
|
||||
/>
|
||||
|
||||
@@ -87,10 +87,10 @@ export const GithubSetup = () => {
|
||||
{haveGithubConfigured ? (
|
||||
<div className="flex flex-col gap-2">
|
||||
<div className="flex items-center gap-4">
|
||||
<span className="text-muted-foreground">
|
||||
<span className="text-muted-foreground text-sm">
|
||||
Github account configured succesfully.
|
||||
</span>
|
||||
<BadgeCheck className="size-5 text-green-700" />
|
||||
<BadgeCheck className="size-4 text-green-700" />
|
||||
</div>
|
||||
<div className="flex items-end">
|
||||
<RemoveGithubApp />
|
||||
@@ -100,10 +100,10 @@ export const GithubSetup = () => {
|
||||
<>
|
||||
{data?.githubAppName ? (
|
||||
<div className="flex w-fit flex-col gap-4">
|
||||
<span className="text-muted-foreground text-sm">
|
||||
Youve successfully created a GitHub app named
|
||||
{data.githubAppName}! The next step is to install this app in
|
||||
your GitHub account.
|
||||
<span className="text-muted-foreground">
|
||||
You've successfully created a github app named{" "}
|
||||
<strong>{data.githubAppName}</strong>! The next step is to
|
||||
install this app in your GitHub account.
|
||||
</span>
|
||||
|
||||
<div className="flex flex-row gap-4">
|
||||
@@ -121,12 +121,12 @@ export const GithubSetup = () => {
|
||||
) : (
|
||||
<div>
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="text-muted-foreground text-sm">
|
||||
To integrate your GitHub account with our services, youll
|
||||
<p className="text-muted-foreground text-sm">
|
||||
To integrate your GitHub account with our services, you'll
|
||||
need to create and install a GitHub app. This process is
|
||||
straightforward and only takes a few minutes. Click the
|
||||
button below to get started.
|
||||
</span>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="mt-4 flex flex-col gap-4">
|
||||
|
||||
@@ -23,11 +23,11 @@ import { extractServices } from "@/pages/dashboard/project/[projectId]";
|
||||
import { api } from "@/utils/api";
|
||||
import { AlertBlock } from "@/components/shared/alert-block";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { ListTodo } from "lucide-react";
|
||||
import { useEffect } from "react";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { toast } from "sonner";
|
||||
import { z } from "zod";
|
||||
import { DropdownMenuItem } from "@/components/ui/dropdown-menu";
|
||||
|
||||
const addPermissions = z.object({
|
||||
accesedProjects: z.array(z.string()).optional(),
|
||||
@@ -107,9 +107,12 @@ export const AddUserPermissions = ({ userId }: Props) => {
|
||||
return (
|
||||
<Dialog>
|
||||
<DialogTrigger className="" asChild>
|
||||
<Button variant="ghost">
|
||||
<ListTodo className="size-4 text-muted-foreground " />
|
||||
</Button>
|
||||
<DropdownMenuItem
|
||||
className="w-full cursor-pointer"
|
||||
onSelect={(e) => e.preventDefault()}
|
||||
>
|
||||
Add Permissions
|
||||
</DropdownMenuItem>
|
||||
</DialogTrigger>
|
||||
<DialogContent className="max-h-[85vh] overflow-y-auto sm:max-w-4xl">
|
||||
<DialogHeader>
|
||||
|
||||
@@ -25,6 +25,7 @@ import { useEffect } from "react";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { toast } from "sonner";
|
||||
import { z } from "zod";
|
||||
import { PlusIcon } from "lucide-react";
|
||||
|
||||
const addUser = z.object({
|
||||
email: z
|
||||
@@ -66,7 +67,9 @@ export const AddUser = () => {
|
||||
return (
|
||||
<Dialog>
|
||||
<DialogTrigger className="" asChild>
|
||||
<Button>Add User</Button>
|
||||
<Button>
|
||||
<PlusIcon className="h-4 w-4" /> Add User
|
||||
</Button>
|
||||
</DialogTrigger>
|
||||
<DialogContent className="max-h-screen overflow-y-auto sm:max-w-2xl">
|
||||
<DialogHeader>
|
||||
|
||||
@@ -14,6 +14,7 @@ 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 {
|
||||
authId: string;
|
||||
@@ -24,9 +25,12 @@ export const DeleteUser = ({ authId }: Props) => {
|
||||
return (
|
||||
<AlertDialog>
|
||||
<AlertDialogTrigger asChild>
|
||||
<Button variant="ghost" isLoading={isLoading}>
|
||||
<TrashIcon className="size-4 text-muted-foreground " />
|
||||
</Button>
|
||||
<DropdownMenuItem
|
||||
className="w-full cursor-pointer text-red-500 hover:!text-red-600"
|
||||
onSelect={(e) => e.preventDefault()}
|
||||
>
|
||||
Delete User
|
||||
</DropdownMenuItem>
|
||||
</AlertDialogTrigger>
|
||||
<AlertDialogContent>
|
||||
<AlertDialogHeader>
|
||||
|
||||
@@ -6,7 +6,7 @@ import {
|
||||
CardTitle,
|
||||
} from "@/components/ui/card";
|
||||
import { api } from "@/utils/api";
|
||||
import { CopyIcon, Users } from "lucide-react";
|
||||
import { MoreHorizontal, Users } from "lucide-react";
|
||||
import { AddUser } from "./add-user";
|
||||
import { DeleteUser } from "./delete-user";
|
||||
import { format } from "date-fns";
|
||||
@@ -14,7 +14,24 @@ import { useEffect, useState } from "react";
|
||||
import { AddUserPermissions } from "./add-permissions";
|
||||
import copy from "copy-to-clipboard";
|
||||
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 = () => {
|
||||
const { data } = api.user.all.useQuery();
|
||||
@@ -25,82 +42,109 @@ export const ShowUsers = () => {
|
||||
|
||||
return (
|
||||
<div className="h-full col-span-2">
|
||||
<Card className="bg-transparent h-full border-none">
|
||||
<CardHeader>
|
||||
<CardTitle className="text-xl">Users</CardTitle>
|
||||
<CardDescription>Add, manage and delete users.</CardDescription>
|
||||
<Card className="bg-transparent h-full ">
|
||||
<CardHeader className="flex flex-row gap-2 justify-between w-full flex-wrap">
|
||||
<div className="flex flex-col gap-2">
|
||||
<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>
|
||||
<CardContent className="space-y-2 h-full">
|
||||
{data?.length === 0 ? (
|
||||
<div className="flex flex-col items-center gap-3">
|
||||
<Users className="size-8 self-center 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>
|
||||
<AddUser />
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex flex-col gap-6">
|
||||
{data?.map((user) => {
|
||||
return (
|
||||
<div
|
||||
key={user.userId}
|
||||
className="flex gap-2 flex-col justify-start border p-4 rounded-lg"
|
||||
>
|
||||
<span className="text-sm text-foreground">
|
||||
{user.auth.email}
|
||||
</span>
|
||||
{!user.isRegistered && (
|
||||
<span className="text-sm text-muted-foreground">
|
||||
Expire In{" "}
|
||||
{format(new Date(user.expirationDate), "PPpp")}
|
||||
</span>
|
||||
)}
|
||||
|
||||
<span className="text-sm text-muted-foreground">
|
||||
{user.isRegistered ? "Registered" : "Not Registered"}
|
||||
</span>
|
||||
{user.auth.is2FAEnabled && (
|
||||
<span className="text-sm text-muted-foreground">
|
||||
{user.auth.is2FAEnabled
|
||||
? "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");
|
||||
}}
|
||||
<Table>
|
||||
<TableCaption>See all users</TableCaption>
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHead className="w-[100px]">Email</TableHead>
|
||||
<TableHead className="text-center">Status</TableHead>
|
||||
<TableHead className="text-center">2FA</TableHead>
|
||||
<TableHead className="text-center">Expiration</TableHead>
|
||||
<TableHead className="text-right">Actions</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{data?.map((user) => {
|
||||
return (
|
||||
<TableRow key={user.userId}>
|
||||
<TableCell className="w-[100px]">
|
||||
{user.auth.email}
|
||||
</TableCell>
|
||||
<TableCell className="text-center">
|
||||
<Badge
|
||||
variant={
|
||||
user.isRegistered ? "default" : "secondary"
|
||||
}
|
||||
>
|
||||
<CopyIcon className="size-4 text-muted-foreground" />
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
{user.isRegistered
|
||||
? "Registered"
|
||||
: "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 && (
|
||||
<AddUserPermissions userId={user.userId} />
|
||||
)}
|
||||
{user.isRegistered && <UpdateUser authId={user.authId} />}
|
||||
<DeleteUser authId={user.authId} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
<div className="flex flex-col justify-end gap-3 w-full items-end">
|
||||
<AddUser />
|
||||
</div>
|
||||
<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>
|
||||
{!user.isRegistered && (
|
||||
<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>
|
||||
)}
|
||||
</CardContent>
|
||||
|
||||
@@ -27,10 +27,9 @@ import { DockerTerminalModal } from "./web-server/docker-terminal-modal";
|
||||
import { ShowMainTraefikConfig } from "./web-server/show-main-traefik-config";
|
||||
import { ShowServerTraefikConfig } from "./web-server/show-server-traefik-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 = () => {
|
||||
const [fetchAfterFirstRender, setFetchAfterFirstRender] = useState(false);
|
||||
const { data, refetch } = api.admin.one.useQuery();
|
||||
const { mutateAsync: reloadServer, isLoading } =
|
||||
api.settings.reloadServer.useMutation();
|
||||
@@ -61,13 +60,6 @@ export const WebServer = () => {
|
||||
|
||||
const { mutateAsync: updateDockerCleanup } =
|
||||
api.settings.updateDockerCleanup.useMutation();
|
||||
const { data: query } = api.settings.checkAndUpdateImage.useQuery(void 0, {
|
||||
enabled: fetchAfterFirstRender,
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
setFetchAfterFirstRender(true);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<Card className="rounded-lg w-full bg-transparent">
|
||||
@@ -279,7 +271,7 @@ export const WebServer = () => {
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
|
||||
{query ? <UpdateWebServer /> : null}
|
||||
<UpdateServer />
|
||||
</div>
|
||||
|
||||
<div className="flex items-center flex-wrap justify-between gap-4">
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
};
|
||||
@@ -19,7 +19,11 @@ export const UpdateWebServer = () => {
|
||||
return (
|
||||
<AlertDialog>
|
||||
<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="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" />
|
||||
|
||||
@@ -59,6 +59,12 @@ export const SettingsLayout = ({ children }: Props) => {
|
||||
icon: Users,
|
||||
href: "/dashboard/settings/users",
|
||||
},
|
||||
{
|
||||
title: "Cluster",
|
||||
label: "",
|
||||
icon: Server,
|
||||
href: "/dashboard/settings/cluster",
|
||||
},
|
||||
]
|
||||
: []),
|
||||
]}
|
||||
@@ -75,6 +81,7 @@ import {
|
||||
Activity,
|
||||
Database,
|
||||
Route,
|
||||
Server,
|
||||
ShieldCheck,
|
||||
User2,
|
||||
Users,
|
||||
|
||||
@@ -4,19 +4,26 @@ import {
|
||||
TooltipProvider,
|
||||
TooltipTrigger,
|
||||
} from "@/components/ui/tooltip";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { format, formatDistanceToNow } from "date-fns";
|
||||
|
||||
interface Props {
|
||||
date: string;
|
||||
children?: React.ReactNode;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export const DateTooltip = ({ date, children }: Props) => {
|
||||
export const DateTooltip = ({ date, children, className }: Props) => {
|
||||
return (
|
||||
<TooltipProvider delayDuration={0}>
|
||||
<Tooltip>
|
||||
<TooltipTrigger>
|
||||
<span className="flex items-center text-muted-foreground text-left">
|
||||
<span
|
||||
className={cn(
|
||||
"flex items-center text-muted-foreground text-left",
|
||||
className,
|
||||
)}
|
||||
>
|
||||
{children}{" "}
|
||||
{formatDistanceToNow(new Date(date), {
|
||||
addSuffix: true,
|
||||
|
||||
@@ -147,7 +147,6 @@ const FormMessage = React.forwardRef<
|
||||
>(({ className, children, ...props }, ref) => {
|
||||
const { error, formMessageId } = useFormField();
|
||||
const body = error ? String(error?.message) : children;
|
||||
|
||||
if (!body) {
|
||||
return null;
|
||||
}
|
||||
|
||||
+1
-1
@@ -46,7 +46,7 @@ else
|
||||
fi
|
||||
|
||||
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"
|
||||
|
||||
|
||||
@@ -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 $$;
|
||||
@@ -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 $$;
|
||||
@@ -0,0 +1 @@
|
||||
ALTER TABLE "application" ADD COLUMN "replicas" integer DEFAULT 1;
|
||||
@@ -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 $$;
|
||||
@@ -0,0 +1 @@
|
||||
ALTER TABLE "application" ALTER COLUMN "replicas" SET NOT NULL;
|
||||
@@ -0,0 +1 @@
|
||||
ALTER TABLE "registry" ADD COLUMN "imagePrefix" text NOT NULL;
|
||||
@@ -0,0 +1 @@
|
||||
ALTER TABLE "registry" ALTER COLUMN "imagePrefix" DROP NOT NULL;
|
||||
@@ -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;
|
||||
@@ -0,0 +1 @@
|
||||
ALTER TABLE "application" ADD COLUMN "networkSwarm" json;
|
||||
+202
-192
@@ -1,10 +1,8 @@
|
||||
{
|
||||
"id": "c6215051-7cd1-412d-b8df-b50d58acacff",
|
||||
"prevId": "00000000-0000-0000-0000-000000000000",
|
||||
"version": "5",
|
||||
"dialect": "pg",
|
||||
"version": "6",
|
||||
"dialect": "postgresql",
|
||||
"tables": {
|
||||
"application": {
|
||||
"public.application": {
|
||||
"name": "application",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
@@ -234,29 +232,29 @@
|
||||
"application_projectId_project_projectId_fk": {
|
||||
"name": "application_projectId_project_projectId_fk",
|
||||
"tableFrom": "application",
|
||||
"tableTo": "project",
|
||||
"columnsFrom": [
|
||||
"projectId"
|
||||
],
|
||||
"tableTo": "project",
|
||||
"columnsTo": [
|
||||
"projectId"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
"onUpdate": "no action",
|
||||
"onDelete": "cascade"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {
|
||||
"application_appName_unique": {
|
||||
"name": "application_appName_unique",
|
||||
"nullsNotDistinct": false,
|
||||
"columns": [
|
||||
"appName"
|
||||
]
|
||||
],
|
||||
"nullsNotDistinct": false
|
||||
}
|
||||
}
|
||||
},
|
||||
"postgres": {
|
||||
"public.postgres": {
|
||||
"name": "postgres",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
@@ -375,29 +373,29 @@
|
||||
"postgres_projectId_project_projectId_fk": {
|
||||
"name": "postgres_projectId_project_projectId_fk",
|
||||
"tableFrom": "postgres",
|
||||
"tableTo": "project",
|
||||
"columnsFrom": [
|
||||
"projectId"
|
||||
],
|
||||
"tableTo": "project",
|
||||
"columnsTo": [
|
||||
"projectId"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
"onUpdate": "no action",
|
||||
"onDelete": "cascade"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {
|
||||
"postgres_appName_unique": {
|
||||
"name": "postgres_appName_unique",
|
||||
"nullsNotDistinct": false,
|
||||
"columns": [
|
||||
"appName"
|
||||
]
|
||||
],
|
||||
"nullsNotDistinct": false
|
||||
}
|
||||
}
|
||||
},
|
||||
"user": {
|
||||
"public.user": {
|
||||
"name": "user",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
@@ -499,34 +497,34 @@
|
||||
"user_adminId_admin_adminId_fk": {
|
||||
"name": "user_adminId_admin_adminId_fk",
|
||||
"tableFrom": "user",
|
||||
"tableTo": "admin",
|
||||
"columnsFrom": [
|
||||
"adminId"
|
||||
],
|
||||
"tableTo": "admin",
|
||||
"columnsTo": [
|
||||
"adminId"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
"onUpdate": "no action",
|
||||
"onDelete": "cascade"
|
||||
},
|
||||
"user_authId_auth_id_fk": {
|
||||
"name": "user_authId_auth_id_fk",
|
||||
"tableFrom": "user",
|
||||
"tableTo": "auth",
|
||||
"columnsFrom": [
|
||||
"authId"
|
||||
],
|
||||
"tableTo": "auth",
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
"onUpdate": "no action",
|
||||
"onDelete": "cascade"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {}
|
||||
},
|
||||
"admin": {
|
||||
"public.admin": {
|
||||
"name": "admin",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
@@ -628,21 +626,21 @@
|
||||
"admin_authId_auth_id_fk": {
|
||||
"name": "admin_authId_auth_id_fk",
|
||||
"tableFrom": "admin",
|
||||
"tableTo": "auth",
|
||||
"columnsFrom": [
|
||||
"authId"
|
||||
],
|
||||
"tableTo": "auth",
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
"onUpdate": "no action",
|
||||
"onDelete": "cascade"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {}
|
||||
},
|
||||
"auth": {
|
||||
"public.auth": {
|
||||
"name": "auth",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
@@ -689,14 +687,14 @@
|
||||
"uniqueConstraints": {
|
||||
"auth_email_unique": {
|
||||
"name": "auth_email_unique",
|
||||
"nullsNotDistinct": false,
|
||||
"columns": [
|
||||
"email"
|
||||
]
|
||||
],
|
||||
"nullsNotDistinct": false
|
||||
}
|
||||
}
|
||||
},
|
||||
"project": {
|
||||
"public.project": {
|
||||
"name": "project",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
@@ -736,21 +734,21 @@
|
||||
"project_adminId_admin_adminId_fk": {
|
||||
"name": "project_adminId_admin_adminId_fk",
|
||||
"tableFrom": "project",
|
||||
"tableTo": "admin",
|
||||
"columnsFrom": [
|
||||
"adminId"
|
||||
],
|
||||
"tableTo": "admin",
|
||||
"columnsTo": [
|
||||
"adminId"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
"onUpdate": "no action",
|
||||
"onDelete": "cascade"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {}
|
||||
},
|
||||
"domain": {
|
||||
"public.domain": {
|
||||
"name": "domain",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
@@ -818,21 +816,21 @@
|
||||
"domain_applicationId_application_applicationId_fk": {
|
||||
"name": "domain_applicationId_application_applicationId_fk",
|
||||
"tableFrom": "domain",
|
||||
"tableTo": "application",
|
||||
"columnsFrom": [
|
||||
"applicationId"
|
||||
],
|
||||
"tableTo": "application",
|
||||
"columnsTo": [
|
||||
"applicationId"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
"onUpdate": "no action",
|
||||
"onDelete": "cascade"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {}
|
||||
},
|
||||
"mariadb": {
|
||||
"public.mariadb": {
|
||||
"name": "mariadb",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
@@ -957,29 +955,29 @@
|
||||
"mariadb_projectId_project_projectId_fk": {
|
||||
"name": "mariadb_projectId_project_projectId_fk",
|
||||
"tableFrom": "mariadb",
|
||||
"tableTo": "project",
|
||||
"columnsFrom": [
|
||||
"projectId"
|
||||
],
|
||||
"tableTo": "project",
|
||||
"columnsTo": [
|
||||
"projectId"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
"onUpdate": "no action",
|
||||
"onDelete": "cascade"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {
|
||||
"mariadb_appName_unique": {
|
||||
"name": "mariadb_appName_unique",
|
||||
"nullsNotDistinct": false,
|
||||
"columns": [
|
||||
"appName"
|
||||
]
|
||||
],
|
||||
"nullsNotDistinct": false
|
||||
}
|
||||
}
|
||||
},
|
||||
"mongo": {
|
||||
"public.mongo": {
|
||||
"name": "mongo",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
@@ -1092,29 +1090,29 @@
|
||||
"mongo_projectId_project_projectId_fk": {
|
||||
"name": "mongo_projectId_project_projectId_fk",
|
||||
"tableFrom": "mongo",
|
||||
"tableTo": "project",
|
||||
"columnsFrom": [
|
||||
"projectId"
|
||||
],
|
||||
"tableTo": "project",
|
||||
"columnsTo": [
|
||||
"projectId"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
"onUpdate": "no action",
|
||||
"onDelete": "cascade"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {
|
||||
"mongo_appName_unique": {
|
||||
"name": "mongo_appName_unique",
|
||||
"nullsNotDistinct": false,
|
||||
"columns": [
|
||||
"appName"
|
||||
]
|
||||
],
|
||||
"nullsNotDistinct": false
|
||||
}
|
||||
}
|
||||
},
|
||||
"mysql": {
|
||||
"public.mysql": {
|
||||
"name": "mysql",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
@@ -1239,29 +1237,29 @@
|
||||
"mysql_projectId_project_projectId_fk": {
|
||||
"name": "mysql_projectId_project_projectId_fk",
|
||||
"tableFrom": "mysql",
|
||||
"tableTo": "project",
|
||||
"columnsFrom": [
|
||||
"projectId"
|
||||
],
|
||||
"tableTo": "project",
|
||||
"columnsTo": [
|
||||
"projectId"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
"onUpdate": "no action",
|
||||
"onDelete": "cascade"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {
|
||||
"mysql_appName_unique": {
|
||||
"name": "mysql_appName_unique",
|
||||
"nullsNotDistinct": false,
|
||||
"columns": [
|
||||
"appName"
|
||||
]
|
||||
],
|
||||
"nullsNotDistinct": false
|
||||
}
|
||||
}
|
||||
},
|
||||
"backup": {
|
||||
"public.backup": {
|
||||
"name": "backup",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
@@ -1337,73 +1335,73 @@
|
||||
"backup_destinationId_destination_destinationId_fk": {
|
||||
"name": "backup_destinationId_destination_destinationId_fk",
|
||||
"tableFrom": "backup",
|
||||
"tableTo": "destination",
|
||||
"columnsFrom": [
|
||||
"destinationId"
|
||||
],
|
||||
"tableTo": "destination",
|
||||
"columnsTo": [
|
||||
"destinationId"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
"onUpdate": "no action",
|
||||
"onDelete": "cascade"
|
||||
},
|
||||
"backup_postgresId_postgres_postgresId_fk": {
|
||||
"name": "backup_postgresId_postgres_postgresId_fk",
|
||||
"tableFrom": "backup",
|
||||
"tableTo": "postgres",
|
||||
"columnsFrom": [
|
||||
"postgresId"
|
||||
],
|
||||
"tableTo": "postgres",
|
||||
"columnsTo": [
|
||||
"postgresId"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
"onUpdate": "no action",
|
||||
"onDelete": "cascade"
|
||||
},
|
||||
"backup_mariadbId_mariadb_mariadbId_fk": {
|
||||
"name": "backup_mariadbId_mariadb_mariadbId_fk",
|
||||
"tableFrom": "backup",
|
||||
"tableTo": "mariadb",
|
||||
"columnsFrom": [
|
||||
"mariadbId"
|
||||
],
|
||||
"tableTo": "mariadb",
|
||||
"columnsTo": [
|
||||
"mariadbId"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
"onUpdate": "no action",
|
||||
"onDelete": "cascade"
|
||||
},
|
||||
"backup_mysqlId_mysql_mysqlId_fk": {
|
||||
"name": "backup_mysqlId_mysql_mysqlId_fk",
|
||||
"tableFrom": "backup",
|
||||
"tableTo": "mysql",
|
||||
"columnsFrom": [
|
||||
"mysqlId"
|
||||
],
|
||||
"tableTo": "mysql",
|
||||
"columnsTo": [
|
||||
"mysqlId"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
"onUpdate": "no action",
|
||||
"onDelete": "cascade"
|
||||
},
|
||||
"backup_mongoId_mongo_mongoId_fk": {
|
||||
"name": "backup_mongoId_mongo_mongoId_fk",
|
||||
"tableFrom": "backup",
|
||||
"tableTo": "mongo",
|
||||
"columnsFrom": [
|
||||
"mongoId"
|
||||
],
|
||||
"tableTo": "mongo",
|
||||
"columnsTo": [
|
||||
"mongoId"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
"onUpdate": "no action",
|
||||
"onDelete": "cascade"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {}
|
||||
},
|
||||
"destination": {
|
||||
"public.destination": {
|
||||
"name": "destination",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
@@ -1461,21 +1459,21 @@
|
||||
"destination_adminId_admin_adminId_fk": {
|
||||
"name": "destination_adminId_admin_adminId_fk",
|
||||
"tableFrom": "destination",
|
||||
"tableTo": "admin",
|
||||
"columnsFrom": [
|
||||
"adminId"
|
||||
],
|
||||
"tableTo": "admin",
|
||||
"columnsTo": [
|
||||
"adminId"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
"onUpdate": "no action",
|
||||
"onDelete": "cascade"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {}
|
||||
},
|
||||
"deployment": {
|
||||
"public.deployment": {
|
||||
"name": "deployment",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
@@ -1522,21 +1520,21 @@
|
||||
"deployment_applicationId_application_applicationId_fk": {
|
||||
"name": "deployment_applicationId_application_applicationId_fk",
|
||||
"tableFrom": "deployment",
|
||||
"tableTo": "application",
|
||||
"columnsFrom": [
|
||||
"applicationId"
|
||||
],
|
||||
"tableTo": "application",
|
||||
"columnsTo": [
|
||||
"applicationId"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
"onUpdate": "no action",
|
||||
"onDelete": "cascade"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {}
|
||||
},
|
||||
"mount": {
|
||||
"public.mount": {
|
||||
"name": "mount",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
@@ -1625,86 +1623,86 @@
|
||||
"mount_applicationId_application_applicationId_fk": {
|
||||
"name": "mount_applicationId_application_applicationId_fk",
|
||||
"tableFrom": "mount",
|
||||
"tableTo": "application",
|
||||
"columnsFrom": [
|
||||
"applicationId"
|
||||
],
|
||||
"tableTo": "application",
|
||||
"columnsTo": [
|
||||
"applicationId"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
"onUpdate": "no action",
|
||||
"onDelete": "cascade"
|
||||
},
|
||||
"mount_postgresId_postgres_postgresId_fk": {
|
||||
"name": "mount_postgresId_postgres_postgresId_fk",
|
||||
"tableFrom": "mount",
|
||||
"tableTo": "postgres",
|
||||
"columnsFrom": [
|
||||
"postgresId"
|
||||
],
|
||||
"tableTo": "postgres",
|
||||
"columnsTo": [
|
||||
"postgresId"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
"onUpdate": "no action",
|
||||
"onDelete": "cascade"
|
||||
},
|
||||
"mount_mariadbId_mariadb_mariadbId_fk": {
|
||||
"name": "mount_mariadbId_mariadb_mariadbId_fk",
|
||||
"tableFrom": "mount",
|
||||
"tableTo": "mariadb",
|
||||
"columnsFrom": [
|
||||
"mariadbId"
|
||||
],
|
||||
"tableTo": "mariadb",
|
||||
"columnsTo": [
|
||||
"mariadbId"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
"onUpdate": "no action",
|
||||
"onDelete": "cascade"
|
||||
},
|
||||
"mount_mongoId_mongo_mongoId_fk": {
|
||||
"name": "mount_mongoId_mongo_mongoId_fk",
|
||||
"tableFrom": "mount",
|
||||
"tableTo": "mongo",
|
||||
"columnsFrom": [
|
||||
"mongoId"
|
||||
],
|
||||
"tableTo": "mongo",
|
||||
"columnsTo": [
|
||||
"mongoId"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
"onUpdate": "no action",
|
||||
"onDelete": "cascade"
|
||||
},
|
||||
"mount_mysqlId_mysql_mysqlId_fk": {
|
||||
"name": "mount_mysqlId_mysql_mysqlId_fk",
|
||||
"tableFrom": "mount",
|
||||
"tableTo": "mysql",
|
||||
"columnsFrom": [
|
||||
"mysqlId"
|
||||
],
|
||||
"tableTo": "mysql",
|
||||
"columnsTo": [
|
||||
"mysqlId"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
"onUpdate": "no action",
|
||||
"onDelete": "cascade"
|
||||
},
|
||||
"mount_redisId_redis_redisId_fk": {
|
||||
"name": "mount_redisId_redis_redisId_fk",
|
||||
"tableFrom": "mount",
|
||||
"tableTo": "redis",
|
||||
"columnsFrom": [
|
||||
"redisId"
|
||||
],
|
||||
"tableTo": "redis",
|
||||
"columnsTo": [
|
||||
"redisId"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
"onUpdate": "no action",
|
||||
"onDelete": "cascade"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {}
|
||||
},
|
||||
"certificate": {
|
||||
"public.certificate": {
|
||||
"name": "certificate",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
@@ -1751,14 +1749,14 @@
|
||||
"uniqueConstraints": {
|
||||
"certificate_certificatePath_unique": {
|
||||
"name": "certificate_certificatePath_unique",
|
||||
"nullsNotDistinct": false,
|
||||
"columns": [
|
||||
"certificatePath"
|
||||
]
|
||||
],
|
||||
"nullsNotDistinct": false
|
||||
}
|
||||
}
|
||||
},
|
||||
"session": {
|
||||
"public.session": {
|
||||
"name": "session",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
@@ -1786,21 +1784,21 @@
|
||||
"session_user_id_auth_id_fk": {
|
||||
"name": "session_user_id_auth_id_fk",
|
||||
"tableFrom": "session",
|
||||
"tableTo": "auth",
|
||||
"columnsFrom": [
|
||||
"user_id"
|
||||
],
|
||||
"tableTo": "auth",
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
"onUpdate": "no action",
|
||||
"onDelete": "cascade"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {}
|
||||
},
|
||||
"redirect": {
|
||||
"public.redirect": {
|
||||
"name": "redirect",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
@@ -1853,21 +1851,21 @@
|
||||
"redirect_applicationId_application_applicationId_fk": {
|
||||
"name": "redirect_applicationId_application_applicationId_fk",
|
||||
"tableFrom": "redirect",
|
||||
"tableTo": "application",
|
||||
"columnsFrom": [
|
||||
"applicationId"
|
||||
],
|
||||
"tableTo": "application",
|
||||
"columnsTo": [
|
||||
"applicationId"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
"onUpdate": "no action",
|
||||
"onDelete": "cascade"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {}
|
||||
},
|
||||
"security": {
|
||||
"public.security": {
|
||||
"name": "security",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
@@ -1907,30 +1905,30 @@
|
||||
"security_applicationId_application_applicationId_fk": {
|
||||
"name": "security_applicationId_application_applicationId_fk",
|
||||
"tableFrom": "security",
|
||||
"tableTo": "application",
|
||||
"columnsFrom": [
|
||||
"applicationId"
|
||||
],
|
||||
"tableTo": "application",
|
||||
"columnsTo": [
|
||||
"applicationId"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
"onUpdate": "no action",
|
||||
"onDelete": "cascade"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {
|
||||
"security_username_applicationId_unique": {
|
||||
"name": "security_username_applicationId_unique",
|
||||
"nullsNotDistinct": false,
|
||||
"columns": [
|
||||
"username",
|
||||
"applicationId"
|
||||
]
|
||||
],
|
||||
"nullsNotDistinct": false
|
||||
}
|
||||
}
|
||||
},
|
||||
"port": {
|
||||
"public.port": {
|
||||
"name": "port",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
@@ -1970,21 +1968,21 @@
|
||||
"port_applicationId_application_applicationId_fk": {
|
||||
"name": "port_applicationId_application_applicationId_fk",
|
||||
"tableFrom": "port",
|
||||
"tableTo": "application",
|
||||
"columnsFrom": [
|
||||
"applicationId"
|
||||
],
|
||||
"tableTo": "application",
|
||||
"columnsTo": [
|
||||
"applicationId"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
"onUpdate": "no action",
|
||||
"onDelete": "cascade"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {}
|
||||
},
|
||||
"redis": {
|
||||
"public.redis": {
|
||||
"name": "redis",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
@@ -2091,118 +2089,130 @@
|
||||
"redis_projectId_project_projectId_fk": {
|
||||
"name": "redis_projectId_project_projectId_fk",
|
||||
"tableFrom": "redis",
|
||||
"tableTo": "project",
|
||||
"columnsFrom": [
|
||||
"projectId"
|
||||
],
|
||||
"tableTo": "project",
|
||||
"columnsTo": [
|
||||
"projectId"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
"onUpdate": "no action",
|
||||
"onDelete": "cascade"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {
|
||||
"redis_appName_unique": {
|
||||
"name": "redis_appName_unique",
|
||||
"nullsNotDistinct": false,
|
||||
"columns": [
|
||||
"appName"
|
||||
]
|
||||
],
|
||||
"nullsNotDistinct": false
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"enums": {
|
||||
"buildType": {
|
||||
"public.buildType": {
|
||||
"name": "buildType",
|
||||
"values": {
|
||||
"dockerfile": "dockerfile",
|
||||
"heroku_buildpacks": "heroku_buildpacks",
|
||||
"paketo_buildpacks": "paketo_buildpacks",
|
||||
"nixpacks": "nixpacks"
|
||||
}
|
||||
"schema": "public",
|
||||
"values": [
|
||||
"dockerfile",
|
||||
"heroku_buildpacks",
|
||||
"paketo_buildpacks",
|
||||
"nixpacks"
|
||||
]
|
||||
},
|
||||
"sourceType": {
|
||||
"public.sourceType": {
|
||||
"name": "sourceType",
|
||||
"values": {
|
||||
"docker": "docker",
|
||||
"git": "git",
|
||||
"github": "github"
|
||||
}
|
||||
"schema": "public",
|
||||
"values": [
|
||||
"docker",
|
||||
"git",
|
||||
"github"
|
||||
]
|
||||
},
|
||||
"Roles": {
|
||||
"public.Roles": {
|
||||
"name": "Roles",
|
||||
"values": {
|
||||
"admin": "admin",
|
||||
"user": "user"
|
||||
}
|
||||
"schema": "public",
|
||||
"values": [
|
||||
"admin",
|
||||
"user"
|
||||
]
|
||||
},
|
||||
"databaseType": {
|
||||
"public.databaseType": {
|
||||
"name": "databaseType",
|
||||
"values": {
|
||||
"postgres": "postgres",
|
||||
"mariadb": "mariadb",
|
||||
"mysql": "mysql",
|
||||
"mongo": "mongo"
|
||||
}
|
||||
"schema": "public",
|
||||
"values": [
|
||||
"postgres",
|
||||
"mariadb",
|
||||
"mysql",
|
||||
"mongo"
|
||||
]
|
||||
},
|
||||
"deploymentStatus": {
|
||||
"public.deploymentStatus": {
|
||||
"name": "deploymentStatus",
|
||||
"values": {
|
||||
"running": "running",
|
||||
"done": "done",
|
||||
"error": "error"
|
||||
}
|
||||
"schema": "public",
|
||||
"values": [
|
||||
"running",
|
||||
"done",
|
||||
"error"
|
||||
]
|
||||
},
|
||||
"mountType": {
|
||||
"public.mountType": {
|
||||
"name": "mountType",
|
||||
"values": {
|
||||
"bind": "bind",
|
||||
"volume": "volume",
|
||||
"file": "file"
|
||||
}
|
||||
"schema": "public",
|
||||
"values": [
|
||||
"bind",
|
||||
"volume",
|
||||
"file"
|
||||
]
|
||||
},
|
||||
"serviceType": {
|
||||
"public.serviceType": {
|
||||
"name": "serviceType",
|
||||
"values": {
|
||||
"application": "application",
|
||||
"postgres": "postgres",
|
||||
"mysql": "mysql",
|
||||
"mariadb": "mariadb",
|
||||
"mongo": "mongo",
|
||||
"redis": "redis"
|
||||
}
|
||||
"schema": "public",
|
||||
"values": [
|
||||
"application",
|
||||
"postgres",
|
||||
"mysql",
|
||||
"mariadb",
|
||||
"mongo",
|
||||
"redis"
|
||||
]
|
||||
},
|
||||
"protocolType": {
|
||||
"public.protocolType": {
|
||||
"name": "protocolType",
|
||||
"values": {
|
||||
"tcp": "tcp",
|
||||
"udp": "udp"
|
||||
}
|
||||
"schema": "public",
|
||||
"values": [
|
||||
"tcp",
|
||||
"udp"
|
||||
]
|
||||
},
|
||||
"applicationStatus": {
|
||||
"public.applicationStatus": {
|
||||
"name": "applicationStatus",
|
||||
"values": {
|
||||
"idle": "idle",
|
||||
"running": "running",
|
||||
"done": "done",
|
||||
"error": "error"
|
||||
}
|
||||
"schema": "public",
|
||||
"values": [
|
||||
"idle",
|
||||
"running",
|
||||
"done",
|
||||
"error"
|
||||
]
|
||||
},
|
||||
"certificateType": {
|
||||
"public.certificateType": {
|
||||
"name": "certificateType",
|
||||
"values": {
|
||||
"letsencrypt": "letsencrypt",
|
||||
"none": "none"
|
||||
}
|
||||
"schema": "public",
|
||||
"values": [
|
||||
"letsencrypt",
|
||||
"none"
|
||||
]
|
||||
}
|
||||
},
|
||||
"schemas": {},
|
||||
"_meta": {
|
||||
"columns": {},
|
||||
"schemas": {},
|
||||
"tables": {}
|
||||
}
|
||||
"tables": {},
|
||||
"columns": {}
|
||||
},
|
||||
"id": "c6215051-7cd1-412d-b8df-b50d58acacff",
|
||||
"prevId": "00000000-0000-0000-0000-000000000000"
|
||||
}
|
||||
+202
-192
@@ -1,10 +1,8 @@
|
||||
{
|
||||
"id": "3a4dfad7-ae33-4ae3-b60e-4f40f44f5652",
|
||||
"prevId": "c6215051-7cd1-412d-b8df-b50d58acacff",
|
||||
"version": "5",
|
||||
"dialect": "pg",
|
||||
"version": "6",
|
||||
"dialect": "postgresql",
|
||||
"tables": {
|
||||
"application": {
|
||||
"public.application": {
|
||||
"name": "application",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
@@ -234,29 +232,29 @@
|
||||
"application_projectId_project_projectId_fk": {
|
||||
"name": "application_projectId_project_projectId_fk",
|
||||
"tableFrom": "application",
|
||||
"tableTo": "project",
|
||||
"columnsFrom": [
|
||||
"projectId"
|
||||
],
|
||||
"tableTo": "project",
|
||||
"columnsTo": [
|
||||
"projectId"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
"onUpdate": "no action",
|
||||
"onDelete": "cascade"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {
|
||||
"application_appName_unique": {
|
||||
"name": "application_appName_unique",
|
||||
"nullsNotDistinct": false,
|
||||
"columns": [
|
||||
"appName"
|
||||
]
|
||||
],
|
||||
"nullsNotDistinct": false
|
||||
}
|
||||
}
|
||||
},
|
||||
"postgres": {
|
||||
"public.postgres": {
|
||||
"name": "postgres",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
@@ -375,29 +373,29 @@
|
||||
"postgres_projectId_project_projectId_fk": {
|
||||
"name": "postgres_projectId_project_projectId_fk",
|
||||
"tableFrom": "postgres",
|
||||
"tableTo": "project",
|
||||
"columnsFrom": [
|
||||
"projectId"
|
||||
],
|
||||
"tableTo": "project",
|
||||
"columnsTo": [
|
||||
"projectId"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
"onUpdate": "no action",
|
||||
"onDelete": "cascade"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {
|
||||
"postgres_appName_unique": {
|
||||
"name": "postgres_appName_unique",
|
||||
"nullsNotDistinct": false,
|
||||
"columns": [
|
||||
"appName"
|
||||
]
|
||||
],
|
||||
"nullsNotDistinct": false
|
||||
}
|
||||
}
|
||||
},
|
||||
"user": {
|
||||
"public.user": {
|
||||
"name": "user",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
@@ -499,34 +497,34 @@
|
||||
"user_adminId_admin_adminId_fk": {
|
||||
"name": "user_adminId_admin_adminId_fk",
|
||||
"tableFrom": "user",
|
||||
"tableTo": "admin",
|
||||
"columnsFrom": [
|
||||
"adminId"
|
||||
],
|
||||
"tableTo": "admin",
|
||||
"columnsTo": [
|
||||
"adminId"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
"onUpdate": "no action",
|
||||
"onDelete": "cascade"
|
||||
},
|
||||
"user_authId_auth_id_fk": {
|
||||
"name": "user_authId_auth_id_fk",
|
||||
"tableFrom": "user",
|
||||
"tableTo": "auth",
|
||||
"columnsFrom": [
|
||||
"authId"
|
||||
],
|
||||
"tableTo": "auth",
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
"onUpdate": "no action",
|
||||
"onDelete": "cascade"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {}
|
||||
},
|
||||
"admin": {
|
||||
"public.admin": {
|
||||
"name": "admin",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
@@ -628,21 +626,21 @@
|
||||
"admin_authId_auth_id_fk": {
|
||||
"name": "admin_authId_auth_id_fk",
|
||||
"tableFrom": "admin",
|
||||
"tableTo": "auth",
|
||||
"columnsFrom": [
|
||||
"authId"
|
||||
],
|
||||
"tableTo": "auth",
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
"onUpdate": "no action",
|
||||
"onDelete": "cascade"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {}
|
||||
},
|
||||
"auth": {
|
||||
"public.auth": {
|
||||
"name": "auth",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
@@ -695,14 +693,14 @@
|
||||
"uniqueConstraints": {
|
||||
"auth_email_unique": {
|
||||
"name": "auth_email_unique",
|
||||
"nullsNotDistinct": false,
|
||||
"columns": [
|
||||
"email"
|
||||
]
|
||||
],
|
||||
"nullsNotDistinct": false
|
||||
}
|
||||
}
|
||||
},
|
||||
"project": {
|
||||
"public.project": {
|
||||
"name": "project",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
@@ -742,21 +740,21 @@
|
||||
"project_adminId_admin_adminId_fk": {
|
||||
"name": "project_adminId_admin_adminId_fk",
|
||||
"tableFrom": "project",
|
||||
"tableTo": "admin",
|
||||
"columnsFrom": [
|
||||
"adminId"
|
||||
],
|
||||
"tableTo": "admin",
|
||||
"columnsTo": [
|
||||
"adminId"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
"onUpdate": "no action",
|
||||
"onDelete": "cascade"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {}
|
||||
},
|
||||
"domain": {
|
||||
"public.domain": {
|
||||
"name": "domain",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
@@ -824,21 +822,21 @@
|
||||
"domain_applicationId_application_applicationId_fk": {
|
||||
"name": "domain_applicationId_application_applicationId_fk",
|
||||
"tableFrom": "domain",
|
||||
"tableTo": "application",
|
||||
"columnsFrom": [
|
||||
"applicationId"
|
||||
],
|
||||
"tableTo": "application",
|
||||
"columnsTo": [
|
||||
"applicationId"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
"onUpdate": "no action",
|
||||
"onDelete": "cascade"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {}
|
||||
},
|
||||
"mariadb": {
|
||||
"public.mariadb": {
|
||||
"name": "mariadb",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
@@ -963,29 +961,29 @@
|
||||
"mariadb_projectId_project_projectId_fk": {
|
||||
"name": "mariadb_projectId_project_projectId_fk",
|
||||
"tableFrom": "mariadb",
|
||||
"tableTo": "project",
|
||||
"columnsFrom": [
|
||||
"projectId"
|
||||
],
|
||||
"tableTo": "project",
|
||||
"columnsTo": [
|
||||
"projectId"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
"onUpdate": "no action",
|
||||
"onDelete": "cascade"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {
|
||||
"mariadb_appName_unique": {
|
||||
"name": "mariadb_appName_unique",
|
||||
"nullsNotDistinct": false,
|
||||
"columns": [
|
||||
"appName"
|
||||
]
|
||||
],
|
||||
"nullsNotDistinct": false
|
||||
}
|
||||
}
|
||||
},
|
||||
"mongo": {
|
||||
"public.mongo": {
|
||||
"name": "mongo",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
@@ -1098,29 +1096,29 @@
|
||||
"mongo_projectId_project_projectId_fk": {
|
||||
"name": "mongo_projectId_project_projectId_fk",
|
||||
"tableFrom": "mongo",
|
||||
"tableTo": "project",
|
||||
"columnsFrom": [
|
||||
"projectId"
|
||||
],
|
||||
"tableTo": "project",
|
||||
"columnsTo": [
|
||||
"projectId"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
"onUpdate": "no action",
|
||||
"onDelete": "cascade"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {
|
||||
"mongo_appName_unique": {
|
||||
"name": "mongo_appName_unique",
|
||||
"nullsNotDistinct": false,
|
||||
"columns": [
|
||||
"appName"
|
||||
]
|
||||
],
|
||||
"nullsNotDistinct": false
|
||||
}
|
||||
}
|
||||
},
|
||||
"mysql": {
|
||||
"public.mysql": {
|
||||
"name": "mysql",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
@@ -1245,29 +1243,29 @@
|
||||
"mysql_projectId_project_projectId_fk": {
|
||||
"name": "mysql_projectId_project_projectId_fk",
|
||||
"tableFrom": "mysql",
|
||||
"tableTo": "project",
|
||||
"columnsFrom": [
|
||||
"projectId"
|
||||
],
|
||||
"tableTo": "project",
|
||||
"columnsTo": [
|
||||
"projectId"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
"onUpdate": "no action",
|
||||
"onDelete": "cascade"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {
|
||||
"mysql_appName_unique": {
|
||||
"name": "mysql_appName_unique",
|
||||
"nullsNotDistinct": false,
|
||||
"columns": [
|
||||
"appName"
|
||||
]
|
||||
],
|
||||
"nullsNotDistinct": false
|
||||
}
|
||||
}
|
||||
},
|
||||
"backup": {
|
||||
"public.backup": {
|
||||
"name": "backup",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
@@ -1343,73 +1341,73 @@
|
||||
"backup_destinationId_destination_destinationId_fk": {
|
||||
"name": "backup_destinationId_destination_destinationId_fk",
|
||||
"tableFrom": "backup",
|
||||
"tableTo": "destination",
|
||||
"columnsFrom": [
|
||||
"destinationId"
|
||||
],
|
||||
"tableTo": "destination",
|
||||
"columnsTo": [
|
||||
"destinationId"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
"onUpdate": "no action",
|
||||
"onDelete": "cascade"
|
||||
},
|
||||
"backup_postgresId_postgres_postgresId_fk": {
|
||||
"name": "backup_postgresId_postgres_postgresId_fk",
|
||||
"tableFrom": "backup",
|
||||
"tableTo": "postgres",
|
||||
"columnsFrom": [
|
||||
"postgresId"
|
||||
],
|
||||
"tableTo": "postgres",
|
||||
"columnsTo": [
|
||||
"postgresId"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
"onUpdate": "no action",
|
||||
"onDelete": "cascade"
|
||||
},
|
||||
"backup_mariadbId_mariadb_mariadbId_fk": {
|
||||
"name": "backup_mariadbId_mariadb_mariadbId_fk",
|
||||
"tableFrom": "backup",
|
||||
"tableTo": "mariadb",
|
||||
"columnsFrom": [
|
||||
"mariadbId"
|
||||
],
|
||||
"tableTo": "mariadb",
|
||||
"columnsTo": [
|
||||
"mariadbId"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
"onUpdate": "no action",
|
||||
"onDelete": "cascade"
|
||||
},
|
||||
"backup_mysqlId_mysql_mysqlId_fk": {
|
||||
"name": "backup_mysqlId_mysql_mysqlId_fk",
|
||||
"tableFrom": "backup",
|
||||
"tableTo": "mysql",
|
||||
"columnsFrom": [
|
||||
"mysqlId"
|
||||
],
|
||||
"tableTo": "mysql",
|
||||
"columnsTo": [
|
||||
"mysqlId"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
"onUpdate": "no action",
|
||||
"onDelete": "cascade"
|
||||
},
|
||||
"backup_mongoId_mongo_mongoId_fk": {
|
||||
"name": "backup_mongoId_mongo_mongoId_fk",
|
||||
"tableFrom": "backup",
|
||||
"tableTo": "mongo",
|
||||
"columnsFrom": [
|
||||
"mongoId"
|
||||
],
|
||||
"tableTo": "mongo",
|
||||
"columnsTo": [
|
||||
"mongoId"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
"onUpdate": "no action",
|
||||
"onDelete": "cascade"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {}
|
||||
},
|
||||
"destination": {
|
||||
"public.destination": {
|
||||
"name": "destination",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
@@ -1467,21 +1465,21 @@
|
||||
"destination_adminId_admin_adminId_fk": {
|
||||
"name": "destination_adminId_admin_adminId_fk",
|
||||
"tableFrom": "destination",
|
||||
"tableTo": "admin",
|
||||
"columnsFrom": [
|
||||
"adminId"
|
||||
],
|
||||
"tableTo": "admin",
|
||||
"columnsTo": [
|
||||
"adminId"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
"onUpdate": "no action",
|
||||
"onDelete": "cascade"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {}
|
||||
},
|
||||
"deployment": {
|
||||
"public.deployment": {
|
||||
"name": "deployment",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
@@ -1528,21 +1526,21 @@
|
||||
"deployment_applicationId_application_applicationId_fk": {
|
||||
"name": "deployment_applicationId_application_applicationId_fk",
|
||||
"tableFrom": "deployment",
|
||||
"tableTo": "application",
|
||||
"columnsFrom": [
|
||||
"applicationId"
|
||||
],
|
||||
"tableTo": "application",
|
||||
"columnsTo": [
|
||||
"applicationId"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
"onUpdate": "no action",
|
||||
"onDelete": "cascade"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {}
|
||||
},
|
||||
"mount": {
|
||||
"public.mount": {
|
||||
"name": "mount",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
@@ -1631,86 +1629,86 @@
|
||||
"mount_applicationId_application_applicationId_fk": {
|
||||
"name": "mount_applicationId_application_applicationId_fk",
|
||||
"tableFrom": "mount",
|
||||
"tableTo": "application",
|
||||
"columnsFrom": [
|
||||
"applicationId"
|
||||
],
|
||||
"tableTo": "application",
|
||||
"columnsTo": [
|
||||
"applicationId"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
"onUpdate": "no action",
|
||||
"onDelete": "cascade"
|
||||
},
|
||||
"mount_postgresId_postgres_postgresId_fk": {
|
||||
"name": "mount_postgresId_postgres_postgresId_fk",
|
||||
"tableFrom": "mount",
|
||||
"tableTo": "postgres",
|
||||
"columnsFrom": [
|
||||
"postgresId"
|
||||
],
|
||||
"tableTo": "postgres",
|
||||
"columnsTo": [
|
||||
"postgresId"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
"onUpdate": "no action",
|
||||
"onDelete": "cascade"
|
||||
},
|
||||
"mount_mariadbId_mariadb_mariadbId_fk": {
|
||||
"name": "mount_mariadbId_mariadb_mariadbId_fk",
|
||||
"tableFrom": "mount",
|
||||
"tableTo": "mariadb",
|
||||
"columnsFrom": [
|
||||
"mariadbId"
|
||||
],
|
||||
"tableTo": "mariadb",
|
||||
"columnsTo": [
|
||||
"mariadbId"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
"onUpdate": "no action",
|
||||
"onDelete": "cascade"
|
||||
},
|
||||
"mount_mongoId_mongo_mongoId_fk": {
|
||||
"name": "mount_mongoId_mongo_mongoId_fk",
|
||||
"tableFrom": "mount",
|
||||
"tableTo": "mongo",
|
||||
"columnsFrom": [
|
||||
"mongoId"
|
||||
],
|
||||
"tableTo": "mongo",
|
||||
"columnsTo": [
|
||||
"mongoId"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
"onUpdate": "no action",
|
||||
"onDelete": "cascade"
|
||||
},
|
||||
"mount_mysqlId_mysql_mysqlId_fk": {
|
||||
"name": "mount_mysqlId_mysql_mysqlId_fk",
|
||||
"tableFrom": "mount",
|
||||
"tableTo": "mysql",
|
||||
"columnsFrom": [
|
||||
"mysqlId"
|
||||
],
|
||||
"tableTo": "mysql",
|
||||
"columnsTo": [
|
||||
"mysqlId"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
"onUpdate": "no action",
|
||||
"onDelete": "cascade"
|
||||
},
|
||||
"mount_redisId_redis_redisId_fk": {
|
||||
"name": "mount_redisId_redis_redisId_fk",
|
||||
"tableFrom": "mount",
|
||||
"tableTo": "redis",
|
||||
"columnsFrom": [
|
||||
"redisId"
|
||||
],
|
||||
"tableTo": "redis",
|
||||
"columnsTo": [
|
||||
"redisId"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
"onUpdate": "no action",
|
||||
"onDelete": "cascade"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {}
|
||||
},
|
||||
"certificate": {
|
||||
"public.certificate": {
|
||||
"name": "certificate",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
@@ -1757,14 +1755,14 @@
|
||||
"uniqueConstraints": {
|
||||
"certificate_certificatePath_unique": {
|
||||
"name": "certificate_certificatePath_unique",
|
||||
"nullsNotDistinct": false,
|
||||
"columns": [
|
||||
"certificatePath"
|
||||
]
|
||||
],
|
||||
"nullsNotDistinct": false
|
||||
}
|
||||
}
|
||||
},
|
||||
"session": {
|
||||
"public.session": {
|
||||
"name": "session",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
@@ -1792,21 +1790,21 @@
|
||||
"session_user_id_auth_id_fk": {
|
||||
"name": "session_user_id_auth_id_fk",
|
||||
"tableFrom": "session",
|
||||
"tableTo": "auth",
|
||||
"columnsFrom": [
|
||||
"user_id"
|
||||
],
|
||||
"tableTo": "auth",
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
"onUpdate": "no action",
|
||||
"onDelete": "cascade"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {}
|
||||
},
|
||||
"redirect": {
|
||||
"public.redirect": {
|
||||
"name": "redirect",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
@@ -1859,21 +1857,21 @@
|
||||
"redirect_applicationId_application_applicationId_fk": {
|
||||
"name": "redirect_applicationId_application_applicationId_fk",
|
||||
"tableFrom": "redirect",
|
||||
"tableTo": "application",
|
||||
"columnsFrom": [
|
||||
"applicationId"
|
||||
],
|
||||
"tableTo": "application",
|
||||
"columnsTo": [
|
||||
"applicationId"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
"onUpdate": "no action",
|
||||
"onDelete": "cascade"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {}
|
||||
},
|
||||
"security": {
|
||||
"public.security": {
|
||||
"name": "security",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
@@ -1913,30 +1911,30 @@
|
||||
"security_applicationId_application_applicationId_fk": {
|
||||
"name": "security_applicationId_application_applicationId_fk",
|
||||
"tableFrom": "security",
|
||||
"tableTo": "application",
|
||||
"columnsFrom": [
|
||||
"applicationId"
|
||||
],
|
||||
"tableTo": "application",
|
||||
"columnsTo": [
|
||||
"applicationId"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
"onUpdate": "no action",
|
||||
"onDelete": "cascade"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {
|
||||
"security_username_applicationId_unique": {
|
||||
"name": "security_username_applicationId_unique",
|
||||
"nullsNotDistinct": false,
|
||||
"columns": [
|
||||
"username",
|
||||
"applicationId"
|
||||
]
|
||||
],
|
||||
"nullsNotDistinct": false
|
||||
}
|
||||
}
|
||||
},
|
||||
"port": {
|
||||
"public.port": {
|
||||
"name": "port",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
@@ -1976,21 +1974,21 @@
|
||||
"port_applicationId_application_applicationId_fk": {
|
||||
"name": "port_applicationId_application_applicationId_fk",
|
||||
"tableFrom": "port",
|
||||
"tableTo": "application",
|
||||
"columnsFrom": [
|
||||
"applicationId"
|
||||
],
|
||||
"tableTo": "application",
|
||||
"columnsTo": [
|
||||
"applicationId"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
"onUpdate": "no action",
|
||||
"onDelete": "cascade"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {}
|
||||
},
|
||||
"redis": {
|
||||
"public.redis": {
|
||||
"name": "redis",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
@@ -2097,118 +2095,130 @@
|
||||
"redis_projectId_project_projectId_fk": {
|
||||
"name": "redis_projectId_project_projectId_fk",
|
||||
"tableFrom": "redis",
|
||||
"tableTo": "project",
|
||||
"columnsFrom": [
|
||||
"projectId"
|
||||
],
|
||||
"tableTo": "project",
|
||||
"columnsTo": [
|
||||
"projectId"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
"onUpdate": "no action",
|
||||
"onDelete": "cascade"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {
|
||||
"redis_appName_unique": {
|
||||
"name": "redis_appName_unique",
|
||||
"nullsNotDistinct": false,
|
||||
"columns": [
|
||||
"appName"
|
||||
]
|
||||
],
|
||||
"nullsNotDistinct": false
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"enums": {
|
||||
"buildType": {
|
||||
"public.buildType": {
|
||||
"name": "buildType",
|
||||
"values": {
|
||||
"dockerfile": "dockerfile",
|
||||
"heroku_buildpacks": "heroku_buildpacks",
|
||||
"paketo_buildpacks": "paketo_buildpacks",
|
||||
"nixpacks": "nixpacks"
|
||||
}
|
||||
"schema": "public",
|
||||
"values": [
|
||||
"dockerfile",
|
||||
"heroku_buildpacks",
|
||||
"paketo_buildpacks",
|
||||
"nixpacks"
|
||||
]
|
||||
},
|
||||
"sourceType": {
|
||||
"public.sourceType": {
|
||||
"name": "sourceType",
|
||||
"values": {
|
||||
"docker": "docker",
|
||||
"git": "git",
|
||||
"github": "github"
|
||||
}
|
||||
"schema": "public",
|
||||
"values": [
|
||||
"docker",
|
||||
"git",
|
||||
"github"
|
||||
]
|
||||
},
|
||||
"Roles": {
|
||||
"public.Roles": {
|
||||
"name": "Roles",
|
||||
"values": {
|
||||
"admin": "admin",
|
||||
"user": "user"
|
||||
}
|
||||
"schema": "public",
|
||||
"values": [
|
||||
"admin",
|
||||
"user"
|
||||
]
|
||||
},
|
||||
"databaseType": {
|
||||
"public.databaseType": {
|
||||
"name": "databaseType",
|
||||
"values": {
|
||||
"postgres": "postgres",
|
||||
"mariadb": "mariadb",
|
||||
"mysql": "mysql",
|
||||
"mongo": "mongo"
|
||||
}
|
||||
"schema": "public",
|
||||
"values": [
|
||||
"postgres",
|
||||
"mariadb",
|
||||
"mysql",
|
||||
"mongo"
|
||||
]
|
||||
},
|
||||
"deploymentStatus": {
|
||||
"public.deploymentStatus": {
|
||||
"name": "deploymentStatus",
|
||||
"values": {
|
||||
"running": "running",
|
||||
"done": "done",
|
||||
"error": "error"
|
||||
}
|
||||
"schema": "public",
|
||||
"values": [
|
||||
"running",
|
||||
"done",
|
||||
"error"
|
||||
]
|
||||
},
|
||||
"mountType": {
|
||||
"public.mountType": {
|
||||
"name": "mountType",
|
||||
"values": {
|
||||
"bind": "bind",
|
||||
"volume": "volume",
|
||||
"file": "file"
|
||||
}
|
||||
"schema": "public",
|
||||
"values": [
|
||||
"bind",
|
||||
"volume",
|
||||
"file"
|
||||
]
|
||||
},
|
||||
"serviceType": {
|
||||
"public.serviceType": {
|
||||
"name": "serviceType",
|
||||
"values": {
|
||||
"application": "application",
|
||||
"postgres": "postgres",
|
||||
"mysql": "mysql",
|
||||
"mariadb": "mariadb",
|
||||
"mongo": "mongo",
|
||||
"redis": "redis"
|
||||
}
|
||||
"schema": "public",
|
||||
"values": [
|
||||
"application",
|
||||
"postgres",
|
||||
"mysql",
|
||||
"mariadb",
|
||||
"mongo",
|
||||
"redis"
|
||||
]
|
||||
},
|
||||
"protocolType": {
|
||||
"public.protocolType": {
|
||||
"name": "protocolType",
|
||||
"values": {
|
||||
"tcp": "tcp",
|
||||
"udp": "udp"
|
||||
}
|
||||
"schema": "public",
|
||||
"values": [
|
||||
"tcp",
|
||||
"udp"
|
||||
]
|
||||
},
|
||||
"applicationStatus": {
|
||||
"public.applicationStatus": {
|
||||
"name": "applicationStatus",
|
||||
"values": {
|
||||
"idle": "idle",
|
||||
"running": "running",
|
||||
"done": "done",
|
||||
"error": "error"
|
||||
}
|
||||
"schema": "public",
|
||||
"values": [
|
||||
"idle",
|
||||
"running",
|
||||
"done",
|
||||
"error"
|
||||
]
|
||||
},
|
||||
"certificateType": {
|
||||
"public.certificateType": {
|
||||
"name": "certificateType",
|
||||
"values": {
|
||||
"letsencrypt": "letsencrypt",
|
||||
"none": "none"
|
||||
}
|
||||
"schema": "public",
|
||||
"values": [
|
||||
"letsencrypt",
|
||||
"none"
|
||||
]
|
||||
}
|
||||
},
|
||||
"schemas": {},
|
||||
"_meta": {
|
||||
"columns": {},
|
||||
"schemas": {},
|
||||
"tables": {}
|
||||
}
|
||||
"tables": {},
|
||||
"columns": {}
|
||||
},
|
||||
"id": "3a4dfad7-ae33-4ae3-b60e-4f40f44f5652",
|
||||
"prevId": "c6215051-7cd1-412d-b8df-b50d58acacff"
|
||||
}
|
||||
+202
-192
@@ -1,10 +1,8 @@
|
||||
{
|
||||
"id": "665483bd-5123-4c2b-beef-bfa9b91b9356",
|
||||
"prevId": "3a4dfad7-ae33-4ae3-b60e-4f40f44f5652",
|
||||
"version": "5",
|
||||
"dialect": "pg",
|
||||
"version": "6",
|
||||
"dialect": "postgresql",
|
||||
"tables": {
|
||||
"application": {
|
||||
"public.application": {
|
||||
"name": "application",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
@@ -234,29 +232,29 @@
|
||||
"application_projectId_project_projectId_fk": {
|
||||
"name": "application_projectId_project_projectId_fk",
|
||||
"tableFrom": "application",
|
||||
"tableTo": "project",
|
||||
"columnsFrom": [
|
||||
"projectId"
|
||||
],
|
||||
"tableTo": "project",
|
||||
"columnsTo": [
|
||||
"projectId"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
"onUpdate": "no action",
|
||||
"onDelete": "cascade"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {
|
||||
"application_appName_unique": {
|
||||
"name": "application_appName_unique",
|
||||
"nullsNotDistinct": false,
|
||||
"columns": [
|
||||
"appName"
|
||||
]
|
||||
],
|
||||
"nullsNotDistinct": false
|
||||
}
|
||||
}
|
||||
},
|
||||
"postgres": {
|
||||
"public.postgres": {
|
||||
"name": "postgres",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
@@ -375,29 +373,29 @@
|
||||
"postgres_projectId_project_projectId_fk": {
|
||||
"name": "postgres_projectId_project_projectId_fk",
|
||||
"tableFrom": "postgres",
|
||||
"tableTo": "project",
|
||||
"columnsFrom": [
|
||||
"projectId"
|
||||
],
|
||||
"tableTo": "project",
|
||||
"columnsTo": [
|
||||
"projectId"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
"onUpdate": "no action",
|
||||
"onDelete": "cascade"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {
|
||||
"postgres_appName_unique": {
|
||||
"name": "postgres_appName_unique",
|
||||
"nullsNotDistinct": false,
|
||||
"columns": [
|
||||
"appName"
|
||||
]
|
||||
],
|
||||
"nullsNotDistinct": false
|
||||
}
|
||||
}
|
||||
},
|
||||
"user": {
|
||||
"public.user": {
|
||||
"name": "user",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
@@ -499,34 +497,34 @@
|
||||
"user_adminId_admin_adminId_fk": {
|
||||
"name": "user_adminId_admin_adminId_fk",
|
||||
"tableFrom": "user",
|
||||
"tableTo": "admin",
|
||||
"columnsFrom": [
|
||||
"adminId"
|
||||
],
|
||||
"tableTo": "admin",
|
||||
"columnsTo": [
|
||||
"adminId"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
"onUpdate": "no action",
|
||||
"onDelete": "cascade"
|
||||
},
|
||||
"user_authId_auth_id_fk": {
|
||||
"name": "user_authId_auth_id_fk",
|
||||
"tableFrom": "user",
|
||||
"tableTo": "auth",
|
||||
"columnsFrom": [
|
||||
"authId"
|
||||
],
|
||||
"tableTo": "auth",
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
"onUpdate": "no action",
|
||||
"onDelete": "cascade"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {}
|
||||
},
|
||||
"admin": {
|
||||
"public.admin": {
|
||||
"name": "admin",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
@@ -628,21 +626,21 @@
|
||||
"admin_authId_auth_id_fk": {
|
||||
"name": "admin_authId_auth_id_fk",
|
||||
"tableFrom": "admin",
|
||||
"tableTo": "auth",
|
||||
"columnsFrom": [
|
||||
"authId"
|
||||
],
|
||||
"tableTo": "auth",
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
"onUpdate": "no action",
|
||||
"onDelete": "cascade"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {}
|
||||
},
|
||||
"auth": {
|
||||
"public.auth": {
|
||||
"name": "auth",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
@@ -702,14 +700,14 @@
|
||||
"uniqueConstraints": {
|
||||
"auth_email_unique": {
|
||||
"name": "auth_email_unique",
|
||||
"nullsNotDistinct": false,
|
||||
"columns": [
|
||||
"email"
|
||||
]
|
||||
],
|
||||
"nullsNotDistinct": false
|
||||
}
|
||||
}
|
||||
},
|
||||
"project": {
|
||||
"public.project": {
|
||||
"name": "project",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
@@ -749,21 +747,21 @@
|
||||
"project_adminId_admin_adminId_fk": {
|
||||
"name": "project_adminId_admin_adminId_fk",
|
||||
"tableFrom": "project",
|
||||
"tableTo": "admin",
|
||||
"columnsFrom": [
|
||||
"adminId"
|
||||
],
|
||||
"tableTo": "admin",
|
||||
"columnsTo": [
|
||||
"adminId"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
"onUpdate": "no action",
|
||||
"onDelete": "cascade"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {}
|
||||
},
|
||||
"domain": {
|
||||
"public.domain": {
|
||||
"name": "domain",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
@@ -831,21 +829,21 @@
|
||||
"domain_applicationId_application_applicationId_fk": {
|
||||
"name": "domain_applicationId_application_applicationId_fk",
|
||||
"tableFrom": "domain",
|
||||
"tableTo": "application",
|
||||
"columnsFrom": [
|
||||
"applicationId"
|
||||
],
|
||||
"tableTo": "application",
|
||||
"columnsTo": [
|
||||
"applicationId"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
"onUpdate": "no action",
|
||||
"onDelete": "cascade"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {}
|
||||
},
|
||||
"mariadb": {
|
||||
"public.mariadb": {
|
||||
"name": "mariadb",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
@@ -970,29 +968,29 @@
|
||||
"mariadb_projectId_project_projectId_fk": {
|
||||
"name": "mariadb_projectId_project_projectId_fk",
|
||||
"tableFrom": "mariadb",
|
||||
"tableTo": "project",
|
||||
"columnsFrom": [
|
||||
"projectId"
|
||||
],
|
||||
"tableTo": "project",
|
||||
"columnsTo": [
|
||||
"projectId"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
"onUpdate": "no action",
|
||||
"onDelete": "cascade"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {
|
||||
"mariadb_appName_unique": {
|
||||
"name": "mariadb_appName_unique",
|
||||
"nullsNotDistinct": false,
|
||||
"columns": [
|
||||
"appName"
|
||||
]
|
||||
],
|
||||
"nullsNotDistinct": false
|
||||
}
|
||||
}
|
||||
},
|
||||
"mongo": {
|
||||
"public.mongo": {
|
||||
"name": "mongo",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
@@ -1105,29 +1103,29 @@
|
||||
"mongo_projectId_project_projectId_fk": {
|
||||
"name": "mongo_projectId_project_projectId_fk",
|
||||
"tableFrom": "mongo",
|
||||
"tableTo": "project",
|
||||
"columnsFrom": [
|
||||
"projectId"
|
||||
],
|
||||
"tableTo": "project",
|
||||
"columnsTo": [
|
||||
"projectId"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
"onUpdate": "no action",
|
||||
"onDelete": "cascade"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {
|
||||
"mongo_appName_unique": {
|
||||
"name": "mongo_appName_unique",
|
||||
"nullsNotDistinct": false,
|
||||
"columns": [
|
||||
"appName"
|
||||
]
|
||||
],
|
||||
"nullsNotDistinct": false
|
||||
}
|
||||
}
|
||||
},
|
||||
"mysql": {
|
||||
"public.mysql": {
|
||||
"name": "mysql",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
@@ -1252,29 +1250,29 @@
|
||||
"mysql_projectId_project_projectId_fk": {
|
||||
"name": "mysql_projectId_project_projectId_fk",
|
||||
"tableFrom": "mysql",
|
||||
"tableTo": "project",
|
||||
"columnsFrom": [
|
||||
"projectId"
|
||||
],
|
||||
"tableTo": "project",
|
||||
"columnsTo": [
|
||||
"projectId"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
"onUpdate": "no action",
|
||||
"onDelete": "cascade"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {
|
||||
"mysql_appName_unique": {
|
||||
"name": "mysql_appName_unique",
|
||||
"nullsNotDistinct": false,
|
||||
"columns": [
|
||||
"appName"
|
||||
]
|
||||
],
|
||||
"nullsNotDistinct": false
|
||||
}
|
||||
}
|
||||
},
|
||||
"backup": {
|
||||
"public.backup": {
|
||||
"name": "backup",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
@@ -1350,73 +1348,73 @@
|
||||
"backup_destinationId_destination_destinationId_fk": {
|
||||
"name": "backup_destinationId_destination_destinationId_fk",
|
||||
"tableFrom": "backup",
|
||||
"tableTo": "destination",
|
||||
"columnsFrom": [
|
||||
"destinationId"
|
||||
],
|
||||
"tableTo": "destination",
|
||||
"columnsTo": [
|
||||
"destinationId"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
"onUpdate": "no action",
|
||||
"onDelete": "cascade"
|
||||
},
|
||||
"backup_postgresId_postgres_postgresId_fk": {
|
||||
"name": "backup_postgresId_postgres_postgresId_fk",
|
||||
"tableFrom": "backup",
|
||||
"tableTo": "postgres",
|
||||
"columnsFrom": [
|
||||
"postgresId"
|
||||
],
|
||||
"tableTo": "postgres",
|
||||
"columnsTo": [
|
||||
"postgresId"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
"onUpdate": "no action",
|
||||
"onDelete": "cascade"
|
||||
},
|
||||
"backup_mariadbId_mariadb_mariadbId_fk": {
|
||||
"name": "backup_mariadbId_mariadb_mariadbId_fk",
|
||||
"tableFrom": "backup",
|
||||
"tableTo": "mariadb",
|
||||
"columnsFrom": [
|
||||
"mariadbId"
|
||||
],
|
||||
"tableTo": "mariadb",
|
||||
"columnsTo": [
|
||||
"mariadbId"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
"onUpdate": "no action",
|
||||
"onDelete": "cascade"
|
||||
},
|
||||
"backup_mysqlId_mysql_mysqlId_fk": {
|
||||
"name": "backup_mysqlId_mysql_mysqlId_fk",
|
||||
"tableFrom": "backup",
|
||||
"tableTo": "mysql",
|
||||
"columnsFrom": [
|
||||
"mysqlId"
|
||||
],
|
||||
"tableTo": "mysql",
|
||||
"columnsTo": [
|
||||
"mysqlId"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
"onUpdate": "no action",
|
||||
"onDelete": "cascade"
|
||||
},
|
||||
"backup_mongoId_mongo_mongoId_fk": {
|
||||
"name": "backup_mongoId_mongo_mongoId_fk",
|
||||
"tableFrom": "backup",
|
||||
"tableTo": "mongo",
|
||||
"columnsFrom": [
|
||||
"mongoId"
|
||||
],
|
||||
"tableTo": "mongo",
|
||||
"columnsTo": [
|
||||
"mongoId"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
"onUpdate": "no action",
|
||||
"onDelete": "cascade"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {}
|
||||
},
|
||||
"destination": {
|
||||
"public.destination": {
|
||||
"name": "destination",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
@@ -1474,21 +1472,21 @@
|
||||
"destination_adminId_admin_adminId_fk": {
|
||||
"name": "destination_adminId_admin_adminId_fk",
|
||||
"tableFrom": "destination",
|
||||
"tableTo": "admin",
|
||||
"columnsFrom": [
|
||||
"adminId"
|
||||
],
|
||||
"tableTo": "admin",
|
||||
"columnsTo": [
|
||||
"adminId"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
"onUpdate": "no action",
|
||||
"onDelete": "cascade"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {}
|
||||
},
|
||||
"deployment": {
|
||||
"public.deployment": {
|
||||
"name": "deployment",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
@@ -1535,21 +1533,21 @@
|
||||
"deployment_applicationId_application_applicationId_fk": {
|
||||
"name": "deployment_applicationId_application_applicationId_fk",
|
||||
"tableFrom": "deployment",
|
||||
"tableTo": "application",
|
||||
"columnsFrom": [
|
||||
"applicationId"
|
||||
],
|
||||
"tableTo": "application",
|
||||
"columnsTo": [
|
||||
"applicationId"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
"onUpdate": "no action",
|
||||
"onDelete": "cascade"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {}
|
||||
},
|
||||
"mount": {
|
||||
"public.mount": {
|
||||
"name": "mount",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
@@ -1638,86 +1636,86 @@
|
||||
"mount_applicationId_application_applicationId_fk": {
|
||||
"name": "mount_applicationId_application_applicationId_fk",
|
||||
"tableFrom": "mount",
|
||||
"tableTo": "application",
|
||||
"columnsFrom": [
|
||||
"applicationId"
|
||||
],
|
||||
"tableTo": "application",
|
||||
"columnsTo": [
|
||||
"applicationId"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
"onUpdate": "no action",
|
||||
"onDelete": "cascade"
|
||||
},
|
||||
"mount_postgresId_postgres_postgresId_fk": {
|
||||
"name": "mount_postgresId_postgres_postgresId_fk",
|
||||
"tableFrom": "mount",
|
||||
"tableTo": "postgres",
|
||||
"columnsFrom": [
|
||||
"postgresId"
|
||||
],
|
||||
"tableTo": "postgres",
|
||||
"columnsTo": [
|
||||
"postgresId"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
"onUpdate": "no action",
|
||||
"onDelete": "cascade"
|
||||
},
|
||||
"mount_mariadbId_mariadb_mariadbId_fk": {
|
||||
"name": "mount_mariadbId_mariadb_mariadbId_fk",
|
||||
"tableFrom": "mount",
|
||||
"tableTo": "mariadb",
|
||||
"columnsFrom": [
|
||||
"mariadbId"
|
||||
],
|
||||
"tableTo": "mariadb",
|
||||
"columnsTo": [
|
||||
"mariadbId"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
"onUpdate": "no action",
|
||||
"onDelete": "cascade"
|
||||
},
|
||||
"mount_mongoId_mongo_mongoId_fk": {
|
||||
"name": "mount_mongoId_mongo_mongoId_fk",
|
||||
"tableFrom": "mount",
|
||||
"tableTo": "mongo",
|
||||
"columnsFrom": [
|
||||
"mongoId"
|
||||
],
|
||||
"tableTo": "mongo",
|
||||
"columnsTo": [
|
||||
"mongoId"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
"onUpdate": "no action",
|
||||
"onDelete": "cascade"
|
||||
},
|
||||
"mount_mysqlId_mysql_mysqlId_fk": {
|
||||
"name": "mount_mysqlId_mysql_mysqlId_fk",
|
||||
"tableFrom": "mount",
|
||||
"tableTo": "mysql",
|
||||
"columnsFrom": [
|
||||
"mysqlId"
|
||||
],
|
||||
"tableTo": "mysql",
|
||||
"columnsTo": [
|
||||
"mysqlId"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
"onUpdate": "no action",
|
||||
"onDelete": "cascade"
|
||||
},
|
||||
"mount_redisId_redis_redisId_fk": {
|
||||
"name": "mount_redisId_redis_redisId_fk",
|
||||
"tableFrom": "mount",
|
||||
"tableTo": "redis",
|
||||
"columnsFrom": [
|
||||
"redisId"
|
||||
],
|
||||
"tableTo": "redis",
|
||||
"columnsTo": [
|
||||
"redisId"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
"onUpdate": "no action",
|
||||
"onDelete": "cascade"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {}
|
||||
},
|
||||
"certificate": {
|
||||
"public.certificate": {
|
||||
"name": "certificate",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
@@ -1764,14 +1762,14 @@
|
||||
"uniqueConstraints": {
|
||||
"certificate_certificatePath_unique": {
|
||||
"name": "certificate_certificatePath_unique",
|
||||
"nullsNotDistinct": false,
|
||||
"columns": [
|
||||
"certificatePath"
|
||||
]
|
||||
],
|
||||
"nullsNotDistinct": false
|
||||
}
|
||||
}
|
||||
},
|
||||
"session": {
|
||||
"public.session": {
|
||||
"name": "session",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
@@ -1799,21 +1797,21 @@
|
||||
"session_user_id_auth_id_fk": {
|
||||
"name": "session_user_id_auth_id_fk",
|
||||
"tableFrom": "session",
|
||||
"tableTo": "auth",
|
||||
"columnsFrom": [
|
||||
"user_id"
|
||||
],
|
||||
"tableTo": "auth",
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
"onUpdate": "no action",
|
||||
"onDelete": "cascade"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {}
|
||||
},
|
||||
"redirect": {
|
||||
"public.redirect": {
|
||||
"name": "redirect",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
@@ -1866,21 +1864,21 @@
|
||||
"redirect_applicationId_application_applicationId_fk": {
|
||||
"name": "redirect_applicationId_application_applicationId_fk",
|
||||
"tableFrom": "redirect",
|
||||
"tableTo": "application",
|
||||
"columnsFrom": [
|
||||
"applicationId"
|
||||
],
|
||||
"tableTo": "application",
|
||||
"columnsTo": [
|
||||
"applicationId"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
"onUpdate": "no action",
|
||||
"onDelete": "cascade"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {}
|
||||
},
|
||||
"security": {
|
||||
"public.security": {
|
||||
"name": "security",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
@@ -1920,30 +1918,30 @@
|
||||
"security_applicationId_application_applicationId_fk": {
|
||||
"name": "security_applicationId_application_applicationId_fk",
|
||||
"tableFrom": "security",
|
||||
"tableTo": "application",
|
||||
"columnsFrom": [
|
||||
"applicationId"
|
||||
],
|
||||
"tableTo": "application",
|
||||
"columnsTo": [
|
||||
"applicationId"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
"onUpdate": "no action",
|
||||
"onDelete": "cascade"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {
|
||||
"security_username_applicationId_unique": {
|
||||
"name": "security_username_applicationId_unique",
|
||||
"nullsNotDistinct": false,
|
||||
"columns": [
|
||||
"username",
|
||||
"applicationId"
|
||||
]
|
||||
],
|
||||
"nullsNotDistinct": false
|
||||
}
|
||||
}
|
||||
},
|
||||
"port": {
|
||||
"public.port": {
|
||||
"name": "port",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
@@ -1983,21 +1981,21 @@
|
||||
"port_applicationId_application_applicationId_fk": {
|
||||
"name": "port_applicationId_application_applicationId_fk",
|
||||
"tableFrom": "port",
|
||||
"tableTo": "application",
|
||||
"columnsFrom": [
|
||||
"applicationId"
|
||||
],
|
||||
"tableTo": "application",
|
||||
"columnsTo": [
|
||||
"applicationId"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
"onUpdate": "no action",
|
||||
"onDelete": "cascade"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {}
|
||||
},
|
||||
"redis": {
|
||||
"public.redis": {
|
||||
"name": "redis",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
@@ -2104,118 +2102,130 @@
|
||||
"redis_projectId_project_projectId_fk": {
|
||||
"name": "redis_projectId_project_projectId_fk",
|
||||
"tableFrom": "redis",
|
||||
"tableTo": "project",
|
||||
"columnsFrom": [
|
||||
"projectId"
|
||||
],
|
||||
"tableTo": "project",
|
||||
"columnsTo": [
|
||||
"projectId"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
"onUpdate": "no action",
|
||||
"onDelete": "cascade"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {
|
||||
"redis_appName_unique": {
|
||||
"name": "redis_appName_unique",
|
||||
"nullsNotDistinct": false,
|
||||
"columns": [
|
||||
"appName"
|
||||
]
|
||||
],
|
||||
"nullsNotDistinct": false
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"enums": {
|
||||
"buildType": {
|
||||
"public.buildType": {
|
||||
"name": "buildType",
|
||||
"values": {
|
||||
"dockerfile": "dockerfile",
|
||||
"heroku_buildpacks": "heroku_buildpacks",
|
||||
"paketo_buildpacks": "paketo_buildpacks",
|
||||
"nixpacks": "nixpacks"
|
||||
}
|
||||
"schema": "public",
|
||||
"values": [
|
||||
"dockerfile",
|
||||
"heroku_buildpacks",
|
||||
"paketo_buildpacks",
|
||||
"nixpacks"
|
||||
]
|
||||
},
|
||||
"sourceType": {
|
||||
"public.sourceType": {
|
||||
"name": "sourceType",
|
||||
"values": {
|
||||
"docker": "docker",
|
||||
"git": "git",
|
||||
"github": "github"
|
||||
}
|
||||
"schema": "public",
|
||||
"values": [
|
||||
"docker",
|
||||
"git",
|
||||
"github"
|
||||
]
|
||||
},
|
||||
"Roles": {
|
||||
"public.Roles": {
|
||||
"name": "Roles",
|
||||
"values": {
|
||||
"admin": "admin",
|
||||
"user": "user"
|
||||
}
|
||||
"schema": "public",
|
||||
"values": [
|
||||
"admin",
|
||||
"user"
|
||||
]
|
||||
},
|
||||
"databaseType": {
|
||||
"public.databaseType": {
|
||||
"name": "databaseType",
|
||||
"values": {
|
||||
"postgres": "postgres",
|
||||
"mariadb": "mariadb",
|
||||
"mysql": "mysql",
|
||||
"mongo": "mongo"
|
||||
}
|
||||
"schema": "public",
|
||||
"values": [
|
||||
"postgres",
|
||||
"mariadb",
|
||||
"mysql",
|
||||
"mongo"
|
||||
]
|
||||
},
|
||||
"deploymentStatus": {
|
||||
"public.deploymentStatus": {
|
||||
"name": "deploymentStatus",
|
||||
"values": {
|
||||
"running": "running",
|
||||
"done": "done",
|
||||
"error": "error"
|
||||
}
|
||||
"schema": "public",
|
||||
"values": [
|
||||
"running",
|
||||
"done",
|
||||
"error"
|
||||
]
|
||||
},
|
||||
"mountType": {
|
||||
"public.mountType": {
|
||||
"name": "mountType",
|
||||
"values": {
|
||||
"bind": "bind",
|
||||
"volume": "volume",
|
||||
"file": "file"
|
||||
}
|
||||
"schema": "public",
|
||||
"values": [
|
||||
"bind",
|
||||
"volume",
|
||||
"file"
|
||||
]
|
||||
},
|
||||
"serviceType": {
|
||||
"public.serviceType": {
|
||||
"name": "serviceType",
|
||||
"values": {
|
||||
"application": "application",
|
||||
"postgres": "postgres",
|
||||
"mysql": "mysql",
|
||||
"mariadb": "mariadb",
|
||||
"mongo": "mongo",
|
||||
"redis": "redis"
|
||||
}
|
||||
"schema": "public",
|
||||
"values": [
|
||||
"application",
|
||||
"postgres",
|
||||
"mysql",
|
||||
"mariadb",
|
||||
"mongo",
|
||||
"redis"
|
||||
]
|
||||
},
|
||||
"protocolType": {
|
||||
"public.protocolType": {
|
||||
"name": "protocolType",
|
||||
"values": {
|
||||
"tcp": "tcp",
|
||||
"udp": "udp"
|
||||
}
|
||||
"schema": "public",
|
||||
"values": [
|
||||
"tcp",
|
||||
"udp"
|
||||
]
|
||||
},
|
||||
"applicationStatus": {
|
||||
"public.applicationStatus": {
|
||||
"name": "applicationStatus",
|
||||
"values": {
|
||||
"idle": "idle",
|
||||
"running": "running",
|
||||
"done": "done",
|
||||
"error": "error"
|
||||
}
|
||||
"schema": "public",
|
||||
"values": [
|
||||
"idle",
|
||||
"running",
|
||||
"done",
|
||||
"error"
|
||||
]
|
||||
},
|
||||
"certificateType": {
|
||||
"public.certificateType": {
|
||||
"name": "certificateType",
|
||||
"values": {
|
||||
"letsencrypt": "letsencrypt",
|
||||
"none": "none"
|
||||
}
|
||||
"schema": "public",
|
||||
"values": [
|
||||
"letsencrypt",
|
||||
"none"
|
||||
]
|
||||
}
|
||||
},
|
||||
"schemas": {},
|
||||
"_meta": {
|
||||
"columns": {},
|
||||
"schemas": {},
|
||||
"tables": {}
|
||||
}
|
||||
"tables": {},
|
||||
"columns": {}
|
||||
},
|
||||
"id": "665483bd-5123-4c2b-beef-bfa9b91b9356",
|
||||
"prevId": "3a4dfad7-ae33-4ae3-b60e-4f40f44f5652"
|
||||
}
|
||||
+202
-192
@@ -1,10 +1,8 @@
|
||||
{
|
||||
"id": "5a1d3f2b-9c31-4125-9645-015170550b51",
|
||||
"prevId": "665483bd-5123-4c2b-beef-bfa9b91b9356",
|
||||
"version": "5",
|
||||
"dialect": "pg",
|
||||
"version": "6",
|
||||
"dialect": "postgresql",
|
||||
"tables": {
|
||||
"application": {
|
||||
"public.application": {
|
||||
"name": "application",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
@@ -234,29 +232,29 @@
|
||||
"application_projectId_project_projectId_fk": {
|
||||
"name": "application_projectId_project_projectId_fk",
|
||||
"tableFrom": "application",
|
||||
"tableTo": "project",
|
||||
"columnsFrom": [
|
||||
"projectId"
|
||||
],
|
||||
"tableTo": "project",
|
||||
"columnsTo": [
|
||||
"projectId"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
"onUpdate": "no action",
|
||||
"onDelete": "cascade"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {
|
||||
"application_appName_unique": {
|
||||
"name": "application_appName_unique",
|
||||
"nullsNotDistinct": false,
|
||||
"columns": [
|
||||
"appName"
|
||||
]
|
||||
],
|
||||
"nullsNotDistinct": false
|
||||
}
|
||||
}
|
||||
},
|
||||
"postgres": {
|
||||
"public.postgres": {
|
||||
"name": "postgres",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
@@ -375,29 +373,29 @@
|
||||
"postgres_projectId_project_projectId_fk": {
|
||||
"name": "postgres_projectId_project_projectId_fk",
|
||||
"tableFrom": "postgres",
|
||||
"tableTo": "project",
|
||||
"columnsFrom": [
|
||||
"projectId"
|
||||
],
|
||||
"tableTo": "project",
|
||||
"columnsTo": [
|
||||
"projectId"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
"onUpdate": "no action",
|
||||
"onDelete": "cascade"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {
|
||||
"postgres_appName_unique": {
|
||||
"name": "postgres_appName_unique",
|
||||
"nullsNotDistinct": false,
|
||||
"columns": [
|
||||
"appName"
|
||||
]
|
||||
],
|
||||
"nullsNotDistinct": false
|
||||
}
|
||||
}
|
||||
},
|
||||
"user": {
|
||||
"public.user": {
|
||||
"name": "user",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
@@ -506,34 +504,34 @@
|
||||
"user_adminId_admin_adminId_fk": {
|
||||
"name": "user_adminId_admin_adminId_fk",
|
||||
"tableFrom": "user",
|
||||
"tableTo": "admin",
|
||||
"columnsFrom": [
|
||||
"adminId"
|
||||
],
|
||||
"tableTo": "admin",
|
||||
"columnsTo": [
|
||||
"adminId"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
"onUpdate": "no action",
|
||||
"onDelete": "cascade"
|
||||
},
|
||||
"user_authId_auth_id_fk": {
|
||||
"name": "user_authId_auth_id_fk",
|
||||
"tableFrom": "user",
|
||||
"tableTo": "auth",
|
||||
"columnsFrom": [
|
||||
"authId"
|
||||
],
|
||||
"tableTo": "auth",
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
"onUpdate": "no action",
|
||||
"onDelete": "cascade"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {}
|
||||
},
|
||||
"admin": {
|
||||
"public.admin": {
|
||||
"name": "admin",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
@@ -635,21 +633,21 @@
|
||||
"admin_authId_auth_id_fk": {
|
||||
"name": "admin_authId_auth_id_fk",
|
||||
"tableFrom": "admin",
|
||||
"tableTo": "auth",
|
||||
"columnsFrom": [
|
||||
"authId"
|
||||
],
|
||||
"tableTo": "auth",
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
"onUpdate": "no action",
|
||||
"onDelete": "cascade"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {}
|
||||
},
|
||||
"auth": {
|
||||
"public.auth": {
|
||||
"name": "auth",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
@@ -709,14 +707,14 @@
|
||||
"uniqueConstraints": {
|
||||
"auth_email_unique": {
|
||||
"name": "auth_email_unique",
|
||||
"nullsNotDistinct": false,
|
||||
"columns": [
|
||||
"email"
|
||||
]
|
||||
],
|
||||
"nullsNotDistinct": false
|
||||
}
|
||||
}
|
||||
},
|
||||
"project": {
|
||||
"public.project": {
|
||||
"name": "project",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
@@ -756,21 +754,21 @@
|
||||
"project_adminId_admin_adminId_fk": {
|
||||
"name": "project_adminId_admin_adminId_fk",
|
||||
"tableFrom": "project",
|
||||
"tableTo": "admin",
|
||||
"columnsFrom": [
|
||||
"adminId"
|
||||
],
|
||||
"tableTo": "admin",
|
||||
"columnsTo": [
|
||||
"adminId"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
"onUpdate": "no action",
|
||||
"onDelete": "cascade"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {}
|
||||
},
|
||||
"domain": {
|
||||
"public.domain": {
|
||||
"name": "domain",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
@@ -838,21 +836,21 @@
|
||||
"domain_applicationId_application_applicationId_fk": {
|
||||
"name": "domain_applicationId_application_applicationId_fk",
|
||||
"tableFrom": "domain",
|
||||
"tableTo": "application",
|
||||
"columnsFrom": [
|
||||
"applicationId"
|
||||
],
|
||||
"tableTo": "application",
|
||||
"columnsTo": [
|
||||
"applicationId"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
"onUpdate": "no action",
|
||||
"onDelete": "cascade"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {}
|
||||
},
|
||||
"mariadb": {
|
||||
"public.mariadb": {
|
||||
"name": "mariadb",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
@@ -977,29 +975,29 @@
|
||||
"mariadb_projectId_project_projectId_fk": {
|
||||
"name": "mariadb_projectId_project_projectId_fk",
|
||||
"tableFrom": "mariadb",
|
||||
"tableTo": "project",
|
||||
"columnsFrom": [
|
||||
"projectId"
|
||||
],
|
||||
"tableTo": "project",
|
||||
"columnsTo": [
|
||||
"projectId"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
"onUpdate": "no action",
|
||||
"onDelete": "cascade"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {
|
||||
"mariadb_appName_unique": {
|
||||
"name": "mariadb_appName_unique",
|
||||
"nullsNotDistinct": false,
|
||||
"columns": [
|
||||
"appName"
|
||||
]
|
||||
],
|
||||
"nullsNotDistinct": false
|
||||
}
|
||||
}
|
||||
},
|
||||
"mongo": {
|
||||
"public.mongo": {
|
||||
"name": "mongo",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
@@ -1112,29 +1110,29 @@
|
||||
"mongo_projectId_project_projectId_fk": {
|
||||
"name": "mongo_projectId_project_projectId_fk",
|
||||
"tableFrom": "mongo",
|
||||
"tableTo": "project",
|
||||
"columnsFrom": [
|
||||
"projectId"
|
||||
],
|
||||
"tableTo": "project",
|
||||
"columnsTo": [
|
||||
"projectId"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
"onUpdate": "no action",
|
||||
"onDelete": "cascade"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {
|
||||
"mongo_appName_unique": {
|
||||
"name": "mongo_appName_unique",
|
||||
"nullsNotDistinct": false,
|
||||
"columns": [
|
||||
"appName"
|
||||
]
|
||||
],
|
||||
"nullsNotDistinct": false
|
||||
}
|
||||
}
|
||||
},
|
||||
"mysql": {
|
||||
"public.mysql": {
|
||||
"name": "mysql",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
@@ -1259,29 +1257,29 @@
|
||||
"mysql_projectId_project_projectId_fk": {
|
||||
"name": "mysql_projectId_project_projectId_fk",
|
||||
"tableFrom": "mysql",
|
||||
"tableTo": "project",
|
||||
"columnsFrom": [
|
||||
"projectId"
|
||||
],
|
||||
"tableTo": "project",
|
||||
"columnsTo": [
|
||||
"projectId"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
"onUpdate": "no action",
|
||||
"onDelete": "cascade"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {
|
||||
"mysql_appName_unique": {
|
||||
"name": "mysql_appName_unique",
|
||||
"nullsNotDistinct": false,
|
||||
"columns": [
|
||||
"appName"
|
||||
]
|
||||
],
|
||||
"nullsNotDistinct": false
|
||||
}
|
||||
}
|
||||
},
|
||||
"backup": {
|
||||
"public.backup": {
|
||||
"name": "backup",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
@@ -1357,73 +1355,73 @@
|
||||
"backup_destinationId_destination_destinationId_fk": {
|
||||
"name": "backup_destinationId_destination_destinationId_fk",
|
||||
"tableFrom": "backup",
|
||||
"tableTo": "destination",
|
||||
"columnsFrom": [
|
||||
"destinationId"
|
||||
],
|
||||
"tableTo": "destination",
|
||||
"columnsTo": [
|
||||
"destinationId"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
"onUpdate": "no action",
|
||||
"onDelete": "cascade"
|
||||
},
|
||||
"backup_postgresId_postgres_postgresId_fk": {
|
||||
"name": "backup_postgresId_postgres_postgresId_fk",
|
||||
"tableFrom": "backup",
|
||||
"tableTo": "postgres",
|
||||
"columnsFrom": [
|
||||
"postgresId"
|
||||
],
|
||||
"tableTo": "postgres",
|
||||
"columnsTo": [
|
||||
"postgresId"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
"onUpdate": "no action",
|
||||
"onDelete": "cascade"
|
||||
},
|
||||
"backup_mariadbId_mariadb_mariadbId_fk": {
|
||||
"name": "backup_mariadbId_mariadb_mariadbId_fk",
|
||||
"tableFrom": "backup",
|
||||
"tableTo": "mariadb",
|
||||
"columnsFrom": [
|
||||
"mariadbId"
|
||||
],
|
||||
"tableTo": "mariadb",
|
||||
"columnsTo": [
|
||||
"mariadbId"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
"onUpdate": "no action",
|
||||
"onDelete": "cascade"
|
||||
},
|
||||
"backup_mysqlId_mysql_mysqlId_fk": {
|
||||
"name": "backup_mysqlId_mysql_mysqlId_fk",
|
||||
"tableFrom": "backup",
|
||||
"tableTo": "mysql",
|
||||
"columnsFrom": [
|
||||
"mysqlId"
|
||||
],
|
||||
"tableTo": "mysql",
|
||||
"columnsTo": [
|
||||
"mysqlId"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
"onUpdate": "no action",
|
||||
"onDelete": "cascade"
|
||||
},
|
||||
"backup_mongoId_mongo_mongoId_fk": {
|
||||
"name": "backup_mongoId_mongo_mongoId_fk",
|
||||
"tableFrom": "backup",
|
||||
"tableTo": "mongo",
|
||||
"columnsFrom": [
|
||||
"mongoId"
|
||||
],
|
||||
"tableTo": "mongo",
|
||||
"columnsTo": [
|
||||
"mongoId"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
"onUpdate": "no action",
|
||||
"onDelete": "cascade"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {}
|
||||
},
|
||||
"destination": {
|
||||
"public.destination": {
|
||||
"name": "destination",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
@@ -1481,21 +1479,21 @@
|
||||
"destination_adminId_admin_adminId_fk": {
|
||||
"name": "destination_adminId_admin_adminId_fk",
|
||||
"tableFrom": "destination",
|
||||
"tableTo": "admin",
|
||||
"columnsFrom": [
|
||||
"adminId"
|
||||
],
|
||||
"tableTo": "admin",
|
||||
"columnsTo": [
|
||||
"adminId"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
"onUpdate": "no action",
|
||||
"onDelete": "cascade"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {}
|
||||
},
|
||||
"deployment": {
|
||||
"public.deployment": {
|
||||
"name": "deployment",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
@@ -1542,21 +1540,21 @@
|
||||
"deployment_applicationId_application_applicationId_fk": {
|
||||
"name": "deployment_applicationId_application_applicationId_fk",
|
||||
"tableFrom": "deployment",
|
||||
"tableTo": "application",
|
||||
"columnsFrom": [
|
||||
"applicationId"
|
||||
],
|
||||
"tableTo": "application",
|
||||
"columnsTo": [
|
||||
"applicationId"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
"onUpdate": "no action",
|
||||
"onDelete": "cascade"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {}
|
||||
},
|
||||
"mount": {
|
||||
"public.mount": {
|
||||
"name": "mount",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
@@ -1645,86 +1643,86 @@
|
||||
"mount_applicationId_application_applicationId_fk": {
|
||||
"name": "mount_applicationId_application_applicationId_fk",
|
||||
"tableFrom": "mount",
|
||||
"tableTo": "application",
|
||||
"columnsFrom": [
|
||||
"applicationId"
|
||||
],
|
||||
"tableTo": "application",
|
||||
"columnsTo": [
|
||||
"applicationId"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
"onUpdate": "no action",
|
||||
"onDelete": "cascade"
|
||||
},
|
||||
"mount_postgresId_postgres_postgresId_fk": {
|
||||
"name": "mount_postgresId_postgres_postgresId_fk",
|
||||
"tableFrom": "mount",
|
||||
"tableTo": "postgres",
|
||||
"columnsFrom": [
|
||||
"postgresId"
|
||||
],
|
||||
"tableTo": "postgres",
|
||||
"columnsTo": [
|
||||
"postgresId"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
"onUpdate": "no action",
|
||||
"onDelete": "cascade"
|
||||
},
|
||||
"mount_mariadbId_mariadb_mariadbId_fk": {
|
||||
"name": "mount_mariadbId_mariadb_mariadbId_fk",
|
||||
"tableFrom": "mount",
|
||||
"tableTo": "mariadb",
|
||||
"columnsFrom": [
|
||||
"mariadbId"
|
||||
],
|
||||
"tableTo": "mariadb",
|
||||
"columnsTo": [
|
||||
"mariadbId"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
"onUpdate": "no action",
|
||||
"onDelete": "cascade"
|
||||
},
|
||||
"mount_mongoId_mongo_mongoId_fk": {
|
||||
"name": "mount_mongoId_mongo_mongoId_fk",
|
||||
"tableFrom": "mount",
|
||||
"tableTo": "mongo",
|
||||
"columnsFrom": [
|
||||
"mongoId"
|
||||
],
|
||||
"tableTo": "mongo",
|
||||
"columnsTo": [
|
||||
"mongoId"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
"onUpdate": "no action",
|
||||
"onDelete": "cascade"
|
||||
},
|
||||
"mount_mysqlId_mysql_mysqlId_fk": {
|
||||
"name": "mount_mysqlId_mysql_mysqlId_fk",
|
||||
"tableFrom": "mount",
|
||||
"tableTo": "mysql",
|
||||
"columnsFrom": [
|
||||
"mysqlId"
|
||||
],
|
||||
"tableTo": "mysql",
|
||||
"columnsTo": [
|
||||
"mysqlId"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
"onUpdate": "no action",
|
||||
"onDelete": "cascade"
|
||||
},
|
||||
"mount_redisId_redis_redisId_fk": {
|
||||
"name": "mount_redisId_redis_redisId_fk",
|
||||
"tableFrom": "mount",
|
||||
"tableTo": "redis",
|
||||
"columnsFrom": [
|
||||
"redisId"
|
||||
],
|
||||
"tableTo": "redis",
|
||||
"columnsTo": [
|
||||
"redisId"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
"onUpdate": "no action",
|
||||
"onDelete": "cascade"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {}
|
||||
},
|
||||
"certificate": {
|
||||
"public.certificate": {
|
||||
"name": "certificate",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
@@ -1771,14 +1769,14 @@
|
||||
"uniqueConstraints": {
|
||||
"certificate_certificatePath_unique": {
|
||||
"name": "certificate_certificatePath_unique",
|
||||
"nullsNotDistinct": false,
|
||||
"columns": [
|
||||
"certificatePath"
|
||||
]
|
||||
],
|
||||
"nullsNotDistinct": false
|
||||
}
|
||||
}
|
||||
},
|
||||
"session": {
|
||||
"public.session": {
|
||||
"name": "session",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
@@ -1806,21 +1804,21 @@
|
||||
"session_user_id_auth_id_fk": {
|
||||
"name": "session_user_id_auth_id_fk",
|
||||
"tableFrom": "session",
|
||||
"tableTo": "auth",
|
||||
"columnsFrom": [
|
||||
"user_id"
|
||||
],
|
||||
"tableTo": "auth",
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
"onUpdate": "no action",
|
||||
"onDelete": "cascade"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {}
|
||||
},
|
||||
"redirect": {
|
||||
"public.redirect": {
|
||||
"name": "redirect",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
@@ -1873,21 +1871,21 @@
|
||||
"redirect_applicationId_application_applicationId_fk": {
|
||||
"name": "redirect_applicationId_application_applicationId_fk",
|
||||
"tableFrom": "redirect",
|
||||
"tableTo": "application",
|
||||
"columnsFrom": [
|
||||
"applicationId"
|
||||
],
|
||||
"tableTo": "application",
|
||||
"columnsTo": [
|
||||
"applicationId"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
"onUpdate": "no action",
|
||||
"onDelete": "cascade"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {}
|
||||
},
|
||||
"security": {
|
||||
"public.security": {
|
||||
"name": "security",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
@@ -1927,30 +1925,30 @@
|
||||
"security_applicationId_application_applicationId_fk": {
|
||||
"name": "security_applicationId_application_applicationId_fk",
|
||||
"tableFrom": "security",
|
||||
"tableTo": "application",
|
||||
"columnsFrom": [
|
||||
"applicationId"
|
||||
],
|
||||
"tableTo": "application",
|
||||
"columnsTo": [
|
||||
"applicationId"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
"onUpdate": "no action",
|
||||
"onDelete": "cascade"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {
|
||||
"security_username_applicationId_unique": {
|
||||
"name": "security_username_applicationId_unique",
|
||||
"nullsNotDistinct": false,
|
||||
"columns": [
|
||||
"username",
|
||||
"applicationId"
|
||||
]
|
||||
],
|
||||
"nullsNotDistinct": false
|
||||
}
|
||||
}
|
||||
},
|
||||
"port": {
|
||||
"public.port": {
|
||||
"name": "port",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
@@ -1990,21 +1988,21 @@
|
||||
"port_applicationId_application_applicationId_fk": {
|
||||
"name": "port_applicationId_application_applicationId_fk",
|
||||
"tableFrom": "port",
|
||||
"tableTo": "application",
|
||||
"columnsFrom": [
|
||||
"applicationId"
|
||||
],
|
||||
"tableTo": "application",
|
||||
"columnsTo": [
|
||||
"applicationId"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
"onUpdate": "no action",
|
||||
"onDelete": "cascade"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {}
|
||||
},
|
||||
"redis": {
|
||||
"public.redis": {
|
||||
"name": "redis",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
@@ -2111,118 +2109,130 @@
|
||||
"redis_projectId_project_projectId_fk": {
|
||||
"name": "redis_projectId_project_projectId_fk",
|
||||
"tableFrom": "redis",
|
||||
"tableTo": "project",
|
||||
"columnsFrom": [
|
||||
"projectId"
|
||||
],
|
||||
"tableTo": "project",
|
||||
"columnsTo": [
|
||||
"projectId"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
"onUpdate": "no action",
|
||||
"onDelete": "cascade"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {
|
||||
"redis_appName_unique": {
|
||||
"name": "redis_appName_unique",
|
||||
"nullsNotDistinct": false,
|
||||
"columns": [
|
||||
"appName"
|
||||
]
|
||||
],
|
||||
"nullsNotDistinct": false
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"enums": {
|
||||
"buildType": {
|
||||
"public.buildType": {
|
||||
"name": "buildType",
|
||||
"values": {
|
||||
"dockerfile": "dockerfile",
|
||||
"heroku_buildpacks": "heroku_buildpacks",
|
||||
"paketo_buildpacks": "paketo_buildpacks",
|
||||
"nixpacks": "nixpacks"
|
||||
}
|
||||
"schema": "public",
|
||||
"values": [
|
||||
"dockerfile",
|
||||
"heroku_buildpacks",
|
||||
"paketo_buildpacks",
|
||||
"nixpacks"
|
||||
]
|
||||
},
|
||||
"sourceType": {
|
||||
"public.sourceType": {
|
||||
"name": "sourceType",
|
||||
"values": {
|
||||
"docker": "docker",
|
||||
"git": "git",
|
||||
"github": "github"
|
||||
}
|
||||
"schema": "public",
|
||||
"values": [
|
||||
"docker",
|
||||
"git",
|
||||
"github"
|
||||
]
|
||||
},
|
||||
"Roles": {
|
||||
"public.Roles": {
|
||||
"name": "Roles",
|
||||
"values": {
|
||||
"admin": "admin",
|
||||
"user": "user"
|
||||
}
|
||||
"schema": "public",
|
||||
"values": [
|
||||
"admin",
|
||||
"user"
|
||||
]
|
||||
},
|
||||
"databaseType": {
|
||||
"public.databaseType": {
|
||||
"name": "databaseType",
|
||||
"values": {
|
||||
"postgres": "postgres",
|
||||
"mariadb": "mariadb",
|
||||
"mysql": "mysql",
|
||||
"mongo": "mongo"
|
||||
}
|
||||
"schema": "public",
|
||||
"values": [
|
||||
"postgres",
|
||||
"mariadb",
|
||||
"mysql",
|
||||
"mongo"
|
||||
]
|
||||
},
|
||||
"deploymentStatus": {
|
||||
"public.deploymentStatus": {
|
||||
"name": "deploymentStatus",
|
||||
"values": {
|
||||
"running": "running",
|
||||
"done": "done",
|
||||
"error": "error"
|
||||
}
|
||||
"schema": "public",
|
||||
"values": [
|
||||
"running",
|
||||
"done",
|
||||
"error"
|
||||
]
|
||||
},
|
||||
"mountType": {
|
||||
"public.mountType": {
|
||||
"name": "mountType",
|
||||
"values": {
|
||||
"bind": "bind",
|
||||
"volume": "volume",
|
||||
"file": "file"
|
||||
}
|
||||
"schema": "public",
|
||||
"values": [
|
||||
"bind",
|
||||
"volume",
|
||||
"file"
|
||||
]
|
||||
},
|
||||
"serviceType": {
|
||||
"public.serviceType": {
|
||||
"name": "serviceType",
|
||||
"values": {
|
||||
"application": "application",
|
||||
"postgres": "postgres",
|
||||
"mysql": "mysql",
|
||||
"mariadb": "mariadb",
|
||||
"mongo": "mongo",
|
||||
"redis": "redis"
|
||||
}
|
||||
"schema": "public",
|
||||
"values": [
|
||||
"application",
|
||||
"postgres",
|
||||
"mysql",
|
||||
"mariadb",
|
||||
"mongo",
|
||||
"redis"
|
||||
]
|
||||
},
|
||||
"protocolType": {
|
||||
"public.protocolType": {
|
||||
"name": "protocolType",
|
||||
"values": {
|
||||
"tcp": "tcp",
|
||||
"udp": "udp"
|
||||
}
|
||||
"schema": "public",
|
||||
"values": [
|
||||
"tcp",
|
||||
"udp"
|
||||
]
|
||||
},
|
||||
"applicationStatus": {
|
||||
"public.applicationStatus": {
|
||||
"name": "applicationStatus",
|
||||
"values": {
|
||||
"idle": "idle",
|
||||
"running": "running",
|
||||
"done": "done",
|
||||
"error": "error"
|
||||
}
|
||||
"schema": "public",
|
||||
"values": [
|
||||
"idle",
|
||||
"running",
|
||||
"done",
|
||||
"error"
|
||||
]
|
||||
},
|
||||
"certificateType": {
|
||||
"public.certificateType": {
|
||||
"name": "certificateType",
|
||||
"values": {
|
||||
"letsencrypt": "letsencrypt",
|
||||
"none": "none"
|
||||
}
|
||||
"schema": "public",
|
||||
"values": [
|
||||
"letsencrypt",
|
||||
"none"
|
||||
]
|
||||
}
|
||||
},
|
||||
"schemas": {},
|
||||
"_meta": {
|
||||
"columns": {},
|
||||
"schemas": {},
|
||||
"tables": {}
|
||||
}
|
||||
"tables": {},
|
||||
"columns": {}
|
||||
},
|
||||
"id": "5a1d3f2b-9c31-4125-9645-015170550b51",
|
||||
"prevId": "665483bd-5123-4c2b-beef-bfa9b91b9356"
|
||||
}
|
||||
+202
-192
@@ -1,10 +1,8 @@
|
||||
{
|
||||
"id": "7bb4bbcf-791c-4888-919e-f74bc0528b5f",
|
||||
"prevId": "5a1d3f2b-9c31-4125-9645-015170550b51",
|
||||
"version": "5",
|
||||
"dialect": "pg",
|
||||
"version": "6",
|
||||
"dialect": "postgresql",
|
||||
"tables": {
|
||||
"application": {
|
||||
"public.application": {
|
||||
"name": "application",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
@@ -210,29 +208,29 @@
|
||||
"application_projectId_project_projectId_fk": {
|
||||
"name": "application_projectId_project_projectId_fk",
|
||||
"tableFrom": "application",
|
||||
"tableTo": "project",
|
||||
"columnsFrom": [
|
||||
"projectId"
|
||||
],
|
||||
"tableTo": "project",
|
||||
"columnsTo": [
|
||||
"projectId"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
"onUpdate": "no action",
|
||||
"onDelete": "cascade"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {
|
||||
"application_appName_unique": {
|
||||
"name": "application_appName_unique",
|
||||
"nullsNotDistinct": false,
|
||||
"columns": [
|
||||
"appName"
|
||||
]
|
||||
],
|
||||
"nullsNotDistinct": false
|
||||
}
|
||||
}
|
||||
},
|
||||
"postgres": {
|
||||
"public.postgres": {
|
||||
"name": "postgres",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
@@ -351,29 +349,29 @@
|
||||
"postgres_projectId_project_projectId_fk": {
|
||||
"name": "postgres_projectId_project_projectId_fk",
|
||||
"tableFrom": "postgres",
|
||||
"tableTo": "project",
|
||||
"columnsFrom": [
|
||||
"projectId"
|
||||
],
|
||||
"tableTo": "project",
|
||||
"columnsTo": [
|
||||
"projectId"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
"onUpdate": "no action",
|
||||
"onDelete": "cascade"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {
|
||||
"postgres_appName_unique": {
|
||||
"name": "postgres_appName_unique",
|
||||
"nullsNotDistinct": false,
|
||||
"columns": [
|
||||
"appName"
|
||||
]
|
||||
],
|
||||
"nullsNotDistinct": false
|
||||
}
|
||||
}
|
||||
},
|
||||
"user": {
|
||||
"public.user": {
|
||||
"name": "user",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
@@ -482,34 +480,34 @@
|
||||
"user_adminId_admin_adminId_fk": {
|
||||
"name": "user_adminId_admin_adminId_fk",
|
||||
"tableFrom": "user",
|
||||
"tableTo": "admin",
|
||||
"columnsFrom": [
|
||||
"adminId"
|
||||
],
|
||||
"tableTo": "admin",
|
||||
"columnsTo": [
|
||||
"adminId"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
"onUpdate": "no action",
|
||||
"onDelete": "cascade"
|
||||
},
|
||||
"user_authId_auth_id_fk": {
|
||||
"name": "user_authId_auth_id_fk",
|
||||
"tableFrom": "user",
|
||||
"tableTo": "auth",
|
||||
"columnsFrom": [
|
||||
"authId"
|
||||
],
|
||||
"tableTo": "auth",
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
"onUpdate": "no action",
|
||||
"onDelete": "cascade"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {}
|
||||
},
|
||||
"admin": {
|
||||
"public.admin": {
|
||||
"name": "admin",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
@@ -611,21 +609,21 @@
|
||||
"admin_authId_auth_id_fk": {
|
||||
"name": "admin_authId_auth_id_fk",
|
||||
"tableFrom": "admin",
|
||||
"tableTo": "auth",
|
||||
"columnsFrom": [
|
||||
"authId"
|
||||
],
|
||||
"tableTo": "auth",
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
"onUpdate": "no action",
|
||||
"onDelete": "cascade"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {}
|
||||
},
|
||||
"auth": {
|
||||
"public.auth": {
|
||||
"name": "auth",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
@@ -685,14 +683,14 @@
|
||||
"uniqueConstraints": {
|
||||
"auth_email_unique": {
|
||||
"name": "auth_email_unique",
|
||||
"nullsNotDistinct": false,
|
||||
"columns": [
|
||||
"email"
|
||||
]
|
||||
],
|
||||
"nullsNotDistinct": false
|
||||
}
|
||||
}
|
||||
},
|
||||
"project": {
|
||||
"public.project": {
|
||||
"name": "project",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
@@ -732,21 +730,21 @@
|
||||
"project_adminId_admin_adminId_fk": {
|
||||
"name": "project_adminId_admin_adminId_fk",
|
||||
"tableFrom": "project",
|
||||
"tableTo": "admin",
|
||||
"columnsFrom": [
|
||||
"adminId"
|
||||
],
|
||||
"tableTo": "admin",
|
||||
"columnsTo": [
|
||||
"adminId"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
"onUpdate": "no action",
|
||||
"onDelete": "cascade"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {}
|
||||
},
|
||||
"domain": {
|
||||
"public.domain": {
|
||||
"name": "domain",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
@@ -814,21 +812,21 @@
|
||||
"domain_applicationId_application_applicationId_fk": {
|
||||
"name": "domain_applicationId_application_applicationId_fk",
|
||||
"tableFrom": "domain",
|
||||
"tableTo": "application",
|
||||
"columnsFrom": [
|
||||
"applicationId"
|
||||
],
|
||||
"tableTo": "application",
|
||||
"columnsTo": [
|
||||
"applicationId"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
"onUpdate": "no action",
|
||||
"onDelete": "cascade"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {}
|
||||
},
|
||||
"mariadb": {
|
||||
"public.mariadb": {
|
||||
"name": "mariadb",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
@@ -953,29 +951,29 @@
|
||||
"mariadb_projectId_project_projectId_fk": {
|
||||
"name": "mariadb_projectId_project_projectId_fk",
|
||||
"tableFrom": "mariadb",
|
||||
"tableTo": "project",
|
||||
"columnsFrom": [
|
||||
"projectId"
|
||||
],
|
||||
"tableTo": "project",
|
||||
"columnsTo": [
|
||||
"projectId"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
"onUpdate": "no action",
|
||||
"onDelete": "cascade"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {
|
||||
"mariadb_appName_unique": {
|
||||
"name": "mariadb_appName_unique",
|
||||
"nullsNotDistinct": false,
|
||||
"columns": [
|
||||
"appName"
|
||||
]
|
||||
],
|
||||
"nullsNotDistinct": false
|
||||
}
|
||||
}
|
||||
},
|
||||
"mongo": {
|
||||
"public.mongo": {
|
||||
"name": "mongo",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
@@ -1088,29 +1086,29 @@
|
||||
"mongo_projectId_project_projectId_fk": {
|
||||
"name": "mongo_projectId_project_projectId_fk",
|
||||
"tableFrom": "mongo",
|
||||
"tableTo": "project",
|
||||
"columnsFrom": [
|
||||
"projectId"
|
||||
],
|
||||
"tableTo": "project",
|
||||
"columnsTo": [
|
||||
"projectId"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
"onUpdate": "no action",
|
||||
"onDelete": "cascade"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {
|
||||
"mongo_appName_unique": {
|
||||
"name": "mongo_appName_unique",
|
||||
"nullsNotDistinct": false,
|
||||
"columns": [
|
||||
"appName"
|
||||
]
|
||||
],
|
||||
"nullsNotDistinct": false
|
||||
}
|
||||
}
|
||||
},
|
||||
"mysql": {
|
||||
"public.mysql": {
|
||||
"name": "mysql",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
@@ -1235,29 +1233,29 @@
|
||||
"mysql_projectId_project_projectId_fk": {
|
||||
"name": "mysql_projectId_project_projectId_fk",
|
||||
"tableFrom": "mysql",
|
||||
"tableTo": "project",
|
||||
"columnsFrom": [
|
||||
"projectId"
|
||||
],
|
||||
"tableTo": "project",
|
||||
"columnsTo": [
|
||||
"projectId"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
"onUpdate": "no action",
|
||||
"onDelete": "cascade"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {
|
||||
"mysql_appName_unique": {
|
||||
"name": "mysql_appName_unique",
|
||||
"nullsNotDistinct": false,
|
||||
"columns": [
|
||||
"appName"
|
||||
]
|
||||
],
|
||||
"nullsNotDistinct": false
|
||||
}
|
||||
}
|
||||
},
|
||||
"backup": {
|
||||
"public.backup": {
|
||||
"name": "backup",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
@@ -1333,73 +1331,73 @@
|
||||
"backup_destinationId_destination_destinationId_fk": {
|
||||
"name": "backup_destinationId_destination_destinationId_fk",
|
||||
"tableFrom": "backup",
|
||||
"tableTo": "destination",
|
||||
"columnsFrom": [
|
||||
"destinationId"
|
||||
],
|
||||
"tableTo": "destination",
|
||||
"columnsTo": [
|
||||
"destinationId"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
"onUpdate": "no action",
|
||||
"onDelete": "cascade"
|
||||
},
|
||||
"backup_postgresId_postgres_postgresId_fk": {
|
||||
"name": "backup_postgresId_postgres_postgresId_fk",
|
||||
"tableFrom": "backup",
|
||||
"tableTo": "postgres",
|
||||
"columnsFrom": [
|
||||
"postgresId"
|
||||
],
|
||||
"tableTo": "postgres",
|
||||
"columnsTo": [
|
||||
"postgresId"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
"onUpdate": "no action",
|
||||
"onDelete": "cascade"
|
||||
},
|
||||
"backup_mariadbId_mariadb_mariadbId_fk": {
|
||||
"name": "backup_mariadbId_mariadb_mariadbId_fk",
|
||||
"tableFrom": "backup",
|
||||
"tableTo": "mariadb",
|
||||
"columnsFrom": [
|
||||
"mariadbId"
|
||||
],
|
||||
"tableTo": "mariadb",
|
||||
"columnsTo": [
|
||||
"mariadbId"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
"onUpdate": "no action",
|
||||
"onDelete": "cascade"
|
||||
},
|
||||
"backup_mysqlId_mysql_mysqlId_fk": {
|
||||
"name": "backup_mysqlId_mysql_mysqlId_fk",
|
||||
"tableFrom": "backup",
|
||||
"tableTo": "mysql",
|
||||
"columnsFrom": [
|
||||
"mysqlId"
|
||||
],
|
||||
"tableTo": "mysql",
|
||||
"columnsTo": [
|
||||
"mysqlId"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
"onUpdate": "no action",
|
||||
"onDelete": "cascade"
|
||||
},
|
||||
"backup_mongoId_mongo_mongoId_fk": {
|
||||
"name": "backup_mongoId_mongo_mongoId_fk",
|
||||
"tableFrom": "backup",
|
||||
"tableTo": "mongo",
|
||||
"columnsFrom": [
|
||||
"mongoId"
|
||||
],
|
||||
"tableTo": "mongo",
|
||||
"columnsTo": [
|
||||
"mongoId"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
"onUpdate": "no action",
|
||||
"onDelete": "cascade"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {}
|
||||
},
|
||||
"destination": {
|
||||
"public.destination": {
|
||||
"name": "destination",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
@@ -1457,21 +1455,21 @@
|
||||
"destination_adminId_admin_adminId_fk": {
|
||||
"name": "destination_adminId_admin_adminId_fk",
|
||||
"tableFrom": "destination",
|
||||
"tableTo": "admin",
|
||||
"columnsFrom": [
|
||||
"adminId"
|
||||
],
|
||||
"tableTo": "admin",
|
||||
"columnsTo": [
|
||||
"adminId"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
"onUpdate": "no action",
|
||||
"onDelete": "cascade"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {}
|
||||
},
|
||||
"deployment": {
|
||||
"public.deployment": {
|
||||
"name": "deployment",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
@@ -1518,21 +1516,21 @@
|
||||
"deployment_applicationId_application_applicationId_fk": {
|
||||
"name": "deployment_applicationId_application_applicationId_fk",
|
||||
"tableFrom": "deployment",
|
||||
"tableTo": "application",
|
||||
"columnsFrom": [
|
||||
"applicationId"
|
||||
],
|
||||
"tableTo": "application",
|
||||
"columnsTo": [
|
||||
"applicationId"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
"onUpdate": "no action",
|
||||
"onDelete": "cascade"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {}
|
||||
},
|
||||
"mount": {
|
||||
"public.mount": {
|
||||
"name": "mount",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
@@ -1621,86 +1619,86 @@
|
||||
"mount_applicationId_application_applicationId_fk": {
|
||||
"name": "mount_applicationId_application_applicationId_fk",
|
||||
"tableFrom": "mount",
|
||||
"tableTo": "application",
|
||||
"columnsFrom": [
|
||||
"applicationId"
|
||||
],
|
||||
"tableTo": "application",
|
||||
"columnsTo": [
|
||||
"applicationId"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
"onUpdate": "no action",
|
||||
"onDelete": "cascade"
|
||||
},
|
||||
"mount_postgresId_postgres_postgresId_fk": {
|
||||
"name": "mount_postgresId_postgres_postgresId_fk",
|
||||
"tableFrom": "mount",
|
||||
"tableTo": "postgres",
|
||||
"columnsFrom": [
|
||||
"postgresId"
|
||||
],
|
||||
"tableTo": "postgres",
|
||||
"columnsTo": [
|
||||
"postgresId"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
"onUpdate": "no action",
|
||||
"onDelete": "cascade"
|
||||
},
|
||||
"mount_mariadbId_mariadb_mariadbId_fk": {
|
||||
"name": "mount_mariadbId_mariadb_mariadbId_fk",
|
||||
"tableFrom": "mount",
|
||||
"tableTo": "mariadb",
|
||||
"columnsFrom": [
|
||||
"mariadbId"
|
||||
],
|
||||
"tableTo": "mariadb",
|
||||
"columnsTo": [
|
||||
"mariadbId"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
"onUpdate": "no action",
|
||||
"onDelete": "cascade"
|
||||
},
|
||||
"mount_mongoId_mongo_mongoId_fk": {
|
||||
"name": "mount_mongoId_mongo_mongoId_fk",
|
||||
"tableFrom": "mount",
|
||||
"tableTo": "mongo",
|
||||
"columnsFrom": [
|
||||
"mongoId"
|
||||
],
|
||||
"tableTo": "mongo",
|
||||
"columnsTo": [
|
||||
"mongoId"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
"onUpdate": "no action",
|
||||
"onDelete": "cascade"
|
||||
},
|
||||
"mount_mysqlId_mysql_mysqlId_fk": {
|
||||
"name": "mount_mysqlId_mysql_mysqlId_fk",
|
||||
"tableFrom": "mount",
|
||||
"tableTo": "mysql",
|
||||
"columnsFrom": [
|
||||
"mysqlId"
|
||||
],
|
||||
"tableTo": "mysql",
|
||||
"columnsTo": [
|
||||
"mysqlId"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
"onUpdate": "no action",
|
||||
"onDelete": "cascade"
|
||||
},
|
||||
"mount_redisId_redis_redisId_fk": {
|
||||
"name": "mount_redisId_redis_redisId_fk",
|
||||
"tableFrom": "mount",
|
||||
"tableTo": "redis",
|
||||
"columnsFrom": [
|
||||
"redisId"
|
||||
],
|
||||
"tableTo": "redis",
|
||||
"columnsTo": [
|
||||
"redisId"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
"onUpdate": "no action",
|
||||
"onDelete": "cascade"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {}
|
||||
},
|
||||
"certificate": {
|
||||
"public.certificate": {
|
||||
"name": "certificate",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
@@ -1747,14 +1745,14 @@
|
||||
"uniqueConstraints": {
|
||||
"certificate_certificatePath_unique": {
|
||||
"name": "certificate_certificatePath_unique",
|
||||
"nullsNotDistinct": false,
|
||||
"columns": [
|
||||
"certificatePath"
|
||||
]
|
||||
],
|
||||
"nullsNotDistinct": false
|
||||
}
|
||||
}
|
||||
},
|
||||
"session": {
|
||||
"public.session": {
|
||||
"name": "session",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
@@ -1782,21 +1780,21 @@
|
||||
"session_user_id_auth_id_fk": {
|
||||
"name": "session_user_id_auth_id_fk",
|
||||
"tableFrom": "session",
|
||||
"tableTo": "auth",
|
||||
"columnsFrom": [
|
||||
"user_id"
|
||||
],
|
||||
"tableTo": "auth",
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
"onUpdate": "no action",
|
||||
"onDelete": "cascade"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {}
|
||||
},
|
||||
"redirect": {
|
||||
"public.redirect": {
|
||||
"name": "redirect",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
@@ -1849,21 +1847,21 @@
|
||||
"redirect_applicationId_application_applicationId_fk": {
|
||||
"name": "redirect_applicationId_application_applicationId_fk",
|
||||
"tableFrom": "redirect",
|
||||
"tableTo": "application",
|
||||
"columnsFrom": [
|
||||
"applicationId"
|
||||
],
|
||||
"tableTo": "application",
|
||||
"columnsTo": [
|
||||
"applicationId"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
"onUpdate": "no action",
|
||||
"onDelete": "cascade"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {}
|
||||
},
|
||||
"security": {
|
||||
"public.security": {
|
||||
"name": "security",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
@@ -1903,30 +1901,30 @@
|
||||
"security_applicationId_application_applicationId_fk": {
|
||||
"name": "security_applicationId_application_applicationId_fk",
|
||||
"tableFrom": "security",
|
||||
"tableTo": "application",
|
||||
"columnsFrom": [
|
||||
"applicationId"
|
||||
],
|
||||
"tableTo": "application",
|
||||
"columnsTo": [
|
||||
"applicationId"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
"onUpdate": "no action",
|
||||
"onDelete": "cascade"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {
|
||||
"security_username_applicationId_unique": {
|
||||
"name": "security_username_applicationId_unique",
|
||||
"nullsNotDistinct": false,
|
||||
"columns": [
|
||||
"username",
|
||||
"applicationId"
|
||||
]
|
||||
],
|
||||
"nullsNotDistinct": false
|
||||
}
|
||||
}
|
||||
},
|
||||
"port": {
|
||||
"public.port": {
|
||||
"name": "port",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
@@ -1966,21 +1964,21 @@
|
||||
"port_applicationId_application_applicationId_fk": {
|
||||
"name": "port_applicationId_application_applicationId_fk",
|
||||
"tableFrom": "port",
|
||||
"tableTo": "application",
|
||||
"columnsFrom": [
|
||||
"applicationId"
|
||||
],
|
||||
"tableTo": "application",
|
||||
"columnsTo": [
|
||||
"applicationId"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
"onUpdate": "no action",
|
||||
"onDelete": "cascade"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {}
|
||||
},
|
||||
"redis": {
|
||||
"public.redis": {
|
||||
"name": "redis",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
@@ -2087,118 +2085,130 @@
|
||||
"redis_projectId_project_projectId_fk": {
|
||||
"name": "redis_projectId_project_projectId_fk",
|
||||
"tableFrom": "redis",
|
||||
"tableTo": "project",
|
||||
"columnsFrom": [
|
||||
"projectId"
|
||||
],
|
||||
"tableTo": "project",
|
||||
"columnsTo": [
|
||||
"projectId"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
"onUpdate": "no action",
|
||||
"onDelete": "cascade"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {
|
||||
"redis_appName_unique": {
|
||||
"name": "redis_appName_unique",
|
||||
"nullsNotDistinct": false,
|
||||
"columns": [
|
||||
"appName"
|
||||
]
|
||||
],
|
||||
"nullsNotDistinct": false
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"enums": {
|
||||
"buildType": {
|
||||
"public.buildType": {
|
||||
"name": "buildType",
|
||||
"values": {
|
||||
"dockerfile": "dockerfile",
|
||||
"heroku_buildpacks": "heroku_buildpacks",
|
||||
"paketo_buildpacks": "paketo_buildpacks",
|
||||
"nixpacks": "nixpacks"
|
||||
}
|
||||
"schema": "public",
|
||||
"values": [
|
||||
"dockerfile",
|
||||
"heroku_buildpacks",
|
||||
"paketo_buildpacks",
|
||||
"nixpacks"
|
||||
]
|
||||
},
|
||||
"sourceType": {
|
||||
"public.sourceType": {
|
||||
"name": "sourceType",
|
||||
"values": {
|
||||
"docker": "docker",
|
||||
"git": "git",
|
||||
"github": "github"
|
||||
}
|
||||
"schema": "public",
|
||||
"values": [
|
||||
"docker",
|
||||
"git",
|
||||
"github"
|
||||
]
|
||||
},
|
||||
"Roles": {
|
||||
"public.Roles": {
|
||||
"name": "Roles",
|
||||
"values": {
|
||||
"admin": "admin",
|
||||
"user": "user"
|
||||
}
|
||||
"schema": "public",
|
||||
"values": [
|
||||
"admin",
|
||||
"user"
|
||||
]
|
||||
},
|
||||
"databaseType": {
|
||||
"public.databaseType": {
|
||||
"name": "databaseType",
|
||||
"values": {
|
||||
"postgres": "postgres",
|
||||
"mariadb": "mariadb",
|
||||
"mysql": "mysql",
|
||||
"mongo": "mongo"
|
||||
}
|
||||
"schema": "public",
|
||||
"values": [
|
||||
"postgres",
|
||||
"mariadb",
|
||||
"mysql",
|
||||
"mongo"
|
||||
]
|
||||
},
|
||||
"deploymentStatus": {
|
||||
"public.deploymentStatus": {
|
||||
"name": "deploymentStatus",
|
||||
"values": {
|
||||
"running": "running",
|
||||
"done": "done",
|
||||
"error": "error"
|
||||
}
|
||||
"schema": "public",
|
||||
"values": [
|
||||
"running",
|
||||
"done",
|
||||
"error"
|
||||
]
|
||||
},
|
||||
"mountType": {
|
||||
"public.mountType": {
|
||||
"name": "mountType",
|
||||
"values": {
|
||||
"bind": "bind",
|
||||
"volume": "volume",
|
||||
"file": "file"
|
||||
}
|
||||
"schema": "public",
|
||||
"values": [
|
||||
"bind",
|
||||
"volume",
|
||||
"file"
|
||||
]
|
||||
},
|
||||
"serviceType": {
|
||||
"public.serviceType": {
|
||||
"name": "serviceType",
|
||||
"values": {
|
||||
"application": "application",
|
||||
"postgres": "postgres",
|
||||
"mysql": "mysql",
|
||||
"mariadb": "mariadb",
|
||||
"mongo": "mongo",
|
||||
"redis": "redis"
|
||||
}
|
||||
"schema": "public",
|
||||
"values": [
|
||||
"application",
|
||||
"postgres",
|
||||
"mysql",
|
||||
"mariadb",
|
||||
"mongo",
|
||||
"redis"
|
||||
]
|
||||
},
|
||||
"protocolType": {
|
||||
"public.protocolType": {
|
||||
"name": "protocolType",
|
||||
"values": {
|
||||
"tcp": "tcp",
|
||||
"udp": "udp"
|
||||
}
|
||||
"schema": "public",
|
||||
"values": [
|
||||
"tcp",
|
||||
"udp"
|
||||
]
|
||||
},
|
||||
"applicationStatus": {
|
||||
"public.applicationStatus": {
|
||||
"name": "applicationStatus",
|
||||
"values": {
|
||||
"idle": "idle",
|
||||
"running": "running",
|
||||
"done": "done",
|
||||
"error": "error"
|
||||
}
|
||||
"schema": "public",
|
||||
"values": [
|
||||
"idle",
|
||||
"running",
|
||||
"done",
|
||||
"error"
|
||||
]
|
||||
},
|
||||
"certificateType": {
|
||||
"public.certificateType": {
|
||||
"name": "certificateType",
|
||||
"values": {
|
||||
"letsencrypt": "letsencrypt",
|
||||
"none": "none"
|
||||
}
|
||||
"schema": "public",
|
||||
"values": [
|
||||
"letsencrypt",
|
||||
"none"
|
||||
]
|
||||
}
|
||||
},
|
||||
"schemas": {},
|
||||
"_meta": {
|
||||
"columns": {},
|
||||
"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
@@ -36,6 +36,69 @@
|
||||
"when": 1714004732716,
|
||||
"tag": "0004_nice_tenebrous",
|
||||
"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
|
||||
}
|
||||
]
|
||||
}
|
||||
+7
-5
@@ -12,15 +12,17 @@
|
||||
"setup": "tsx -r dotenv/config setup.ts && sleep 5 && pnpm run migration:run",
|
||||
"reset-password": "node dist/reset-password.mjs",
|
||||
"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:up":"drizzle-kit up --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:studio": "drizzle-kit studio",
|
||||
"lint": "biome lint",
|
||||
"db:seed": "dotenv tsx ./server/db/seed.ts",
|
||||
"db:clean": "dotenv tsx ./server/db/reset.ts",
|
||||
"db:seed": "tsx -r dotenv/config ./server/db/seed.ts",
|
||||
"db:clean": "tsx -r dotenv/config ./server/db/reset.ts",
|
||||
"docker:build": "./docker/build.sh",
|
||||
"docker:push": "./docker/push.sh",
|
||||
"docker:build:canary": "./docker/build.sh canary",
|
||||
@@ -116,7 +118,7 @@
|
||||
"@types/tar-fs": "2.0.4",
|
||||
"@types/ws": "8.5.10",
|
||||
"autoprefixer": "^10.4.14",
|
||||
"drizzle-kit": "^0.20.14",
|
||||
"drizzle-kit": "^0.21.1",
|
||||
"esbuild": "0.20.2",
|
||||
"localtunnel": "2.0.2",
|
||||
"postcss": "^8.4.31",
|
||||
|
||||
+2
-2
@@ -12,8 +12,8 @@ export default function Custom404({ statusCode }: Props) {
|
||||
<div className="container mx-auto h-screen items-center justify-center flex">
|
||||
<div className="-mx-4 flex">
|
||||
<div className="w-full px-4">
|
||||
<div className="mx-auto max-w-[400px] text-center">
|
||||
<h2 className="mb-2 text-[50px] font-bold leading-none text-white sm:text-[80px] md:text-[100px]">
|
||||
<div className="mx-auto max-w-[700px] text-center">
|
||||
<h2 className="mb-2 text-[50px] font-bold leading-none text-white sm:text-[80px]">
|
||||
{statusCode
|
||||
? `An error ${statusCode} occurred on server`
|
||||
: "An error occurred on client"}
|
||||
|
||||
@@ -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 { ShowPorts } from "@/components/dashboard/application/advanced/ports/show-port";
|
||||
import { ShowRedirects } from "@/components/dashboard/application/advanced/redirects/show-redirects";
|
||||
@@ -134,7 +135,7 @@ const Service = (
|
||||
<TabsTrigger value="domains">Domains</TabsTrigger>
|
||||
<TabsTrigger value="advanced">Advanced</TabsTrigger>
|
||||
</TabsList>
|
||||
<div className="flex flex-row gap-4">
|
||||
<div className="flex flex-row gap-2">
|
||||
<UpdateApplication applicationId={applicationId} />
|
||||
{(auth?.rol === "admin" || user?.canDeleteServices) && (
|
||||
<DeleteApplication applicationId={applicationId} />
|
||||
@@ -175,6 +176,7 @@ const Service = (
|
||||
<TabsContent value="advanced">
|
||||
<div className="flex flex-col gap-4 pt-2.5">
|
||||
<AddCommand applicationId={applicationId} />
|
||||
<ShowClusterSettings applicationId={applicationId} />
|
||||
<ShowApplicationResources applicationId={applicationId} />
|
||||
<ShowVolumes applicationId={applicationId} />
|
||||
<ShowRedirects applicationId={applicationId} />
|
||||
|
||||
@@ -116,7 +116,7 @@ const Mariadb = (
|
||||
<TabsTrigger value="logs">Logs</TabsTrigger>
|
||||
<TabsTrigger value="advanced">Advanced</TabsTrigger>
|
||||
</TabsList>
|
||||
<div className="flex flex-row gap-4">
|
||||
<div className="flex flex-row gap-2">
|
||||
<UpdateMariadb mariadbId={mariadbId} />
|
||||
{(auth?.rol === "admin" || user?.canDeleteServices) && (
|
||||
<DeleteMariadb mariadbId={mariadbId} />
|
||||
|
||||
@@ -118,7 +118,7 @@ const Mongo = (
|
||||
<TabsTrigger value="advanced">Advanced</TabsTrigger>
|
||||
</TabsList>
|
||||
|
||||
<div className="flex flex-row gap-4">
|
||||
<div className="flex flex-row gap-2">
|
||||
<UpdateMongo mongoId={mongoId} />
|
||||
{(auth?.rol === "admin" || user?.canDeleteServices) && (
|
||||
<DeleteMongo mongoId={mongoId} />
|
||||
|
||||
@@ -117,7 +117,7 @@ const MySql = (
|
||||
<TabsTrigger value="advanced">Advanced</TabsTrigger>
|
||||
</TabsList>
|
||||
|
||||
<div className="flex flex-row gap-4">
|
||||
<div className="flex flex-row gap-2">
|
||||
<UpdateMysql mysqlId={mysqlId} />
|
||||
{(auth?.rol === "admin" || user?.canDeleteServices) && (
|
||||
<DeleteMysql mysqlId={mysqlId} />
|
||||
|
||||
@@ -118,7 +118,7 @@ const Postgresql = (
|
||||
<TabsTrigger value="advanced">Advanced</TabsTrigger>
|
||||
</TabsList>
|
||||
|
||||
<div className="flex flex-row gap-4">
|
||||
<div className="flex flex-row gap-2">
|
||||
<UpdatePostgres postgresId={postgresId} />
|
||||
{(auth?.rol === "admin" || user?.canDeleteServices) && (
|
||||
<DeletePostgres postgresId={postgresId} />
|
||||
|
||||
@@ -116,7 +116,7 @@ const Redis = (
|
||||
<TabsTrigger value="advanced">Advanced</TabsTrigger>
|
||||
</TabsList>
|
||||
|
||||
<div className="flex flex-row gap-4">
|
||||
<div className="flex flex-row gap-2">
|
||||
<UpdateRedis redisId={redisId} />
|
||||
{(auth?.rol === "admin" || user?.canDeleteServices) && (
|
||||
<DeleteRedis redisId={redisId} />
|
||||
|
||||
@@ -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: {},
|
||||
};
|
||||
}
|
||||
+1
-1
@@ -193,7 +193,7 @@ export default function Home({ hasAdmin }: Props) {
|
||||
<div className="mt-4 text-sm flex flex-row justify-center gap-2">
|
||||
<Link
|
||||
className="hover:underline text-muted-foreground"
|
||||
href="https://docs.dokploy.com/reset-password"
|
||||
href="https://docs.dokploy.com/get-started/reset-password"
|
||||
target="_blank"
|
||||
>
|
||||
Lost your password?
|
||||
|
||||
Generated
+10
-32
@@ -266,8 +266,8 @@ devDependencies:
|
||||
specifier: ^10.4.14
|
||||
version: 10.4.18(postcss@8.4.35)
|
||||
drizzle-kit:
|
||||
specifier: ^0.20.14
|
||||
version: 0.20.14
|
||||
specifier: ^0.21.1
|
||||
version: 0.21.1
|
||||
esbuild:
|
||||
specifier: 0.20.2
|
||||
version: 0.20.2
|
||||
@@ -1032,12 +1032,6 @@ packages:
|
||||
dev: 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:
|
||||
resolution: {integrity: sha512-DPWjcUDQkCeEM4VnljEOEcXdAD7pp8zSZsgOujk/LGIwCXWbXJngin+MO4zbH429lzeC3WbYLGjE2MaUOwzpyw==}
|
||||
requiresBuild: true
|
||||
@@ -4728,19 +4722,9 @@ packages:
|
||||
engines: {node: '>=6'}
|
||||
dev: false
|
||||
|
||||
/camelcase@7.0.1:
|
||||
resolution: {integrity: sha512-xlx1yCK2Oc1APsPXDL2LdlNP6+uu8OCDdhOBSVT279M/S+y75O30C2VuD8T2ogdePBBl7PfPF4504tnLgX3zfw==}
|
||||
engines: {node: '>=14.16'}
|
||||
dev: true
|
||||
|
||||
/caniuse-lite@1.0.30001598:
|
||||
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:
|
||||
resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==}
|
||||
engines: {node: '>= 8.10.0'}
|
||||
@@ -4877,6 +4861,7 @@ packages:
|
||||
engines: {node: '>=12.13'}
|
||||
dependencies:
|
||||
is-what: 4.1.16
|
||||
dev: false
|
||||
|
||||
/copy-to-clipboard@3.3.3:
|
||||
resolution: {integrity: sha512-2KV8NhB5JqC3ky0r9PMCAZKbUHSwtEo4CwCs0KXgruG43gX5PMqDEBbVU4OUzw2MuAWUfsuFmWvEKG5QRfSnJA==}
|
||||
@@ -5143,14 +5128,11 @@ packages:
|
||||
wordwrap: 1.0.0
|
||||
dev: true
|
||||
|
||||
/drizzle-kit@0.20.14:
|
||||
resolution: {integrity: sha512-0fHv3YIEaUcSVPSGyaaBfOi9bmpajjhbJNdPsRMIUvYdLVxBu9eGjH8mRc3Qk7HVmEidFc/lhG1YyJhoXrn5yA==}
|
||||
/drizzle-kit@0.21.1:
|
||||
resolution: {integrity: sha512-Sp7OnCdROiE2ebMuHsAfrnRoHVGYCvErQxUh7/0l6R1caHssZu9oZu/hW9rLU19xnTK4/y3iSe3sL0Cc530wCg==}
|
||||
hasBin: true
|
||||
dependencies:
|
||||
'@drizzle-team/studio': 0.0.39
|
||||
'@esbuild-kit/esm-loader': 2.6.5
|
||||
camelcase: 7.0.1
|
||||
chalk: 5.3.0
|
||||
commander: 9.5.0
|
||||
env-paths: 3.0.0
|
||||
esbuild: 0.19.12
|
||||
@@ -5158,8 +5140,6 @@ packages:
|
||||
glob: 8.1.0
|
||||
hanji: 0.0.5
|
||||
json-diff: 0.9.0
|
||||
minimatch: 7.4.6
|
||||
semver: 7.6.0
|
||||
zod: 3.23.4
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
@@ -5834,6 +5814,7 @@ packages:
|
||||
/is-what@4.1.16:
|
||||
resolution: {integrity: sha512-ZhMwEosbFJkA0YhFnNDgTM4ZxDRsS6HqTo7qsZM08fehyRYIYa0yHu5R6mgo1n/8MgaPBXiPimPD77baVFYg+A==}
|
||||
engines: {node: '>=12.13'}
|
||||
dev: false
|
||||
|
||||
/isexe@2.0.0:
|
||||
resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==}
|
||||
@@ -6015,6 +5996,7 @@ packages:
|
||||
engines: {node: '>=10'}
|
||||
dependencies:
|
||||
yallist: 4.0.0
|
||||
dev: false
|
||||
|
||||
/lru-queue@0.1.0:
|
||||
resolution: {integrity: sha512-BpdYkt9EvGl8OfWHDQPISVpcl5xZthb+XPsbELj5AQXxIC8IriDZIQYjBJPEm5rS420sjZ0TLEzRcq5KdBhYrQ==}
|
||||
@@ -6095,13 +6077,6 @@ packages:
|
||||
brace-expansion: 2.0.1
|
||||
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:
|
||||
resolution: {integrity: sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==}
|
||||
engines: {node: '>=16 || 14 >=14.17'}
|
||||
@@ -6909,6 +6884,7 @@ packages:
|
||||
hasBin: true
|
||||
dependencies:
|
||||
lru-cache: 6.0.0
|
||||
dev: false
|
||||
|
||||
/set-blocking@2.0.0:
|
||||
resolution: {integrity: sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==}
|
||||
@@ -7073,6 +7049,7 @@ packages:
|
||||
engines: {node: '>=16'}
|
||||
dependencies:
|
||||
copy-anything: 3.0.5
|
||||
dev: false
|
||||
|
||||
/supports-preserve-symlinks-flag@1.0.0:
|
||||
resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==}
|
||||
@@ -7456,6 +7433,7 @@ packages:
|
||||
|
||||
/yallist@4.0.0:
|
||||
resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==}
|
||||
dev: false
|
||||
|
||||
/yaml@2.4.1:
|
||||
resolution: {integrity: sha512-pIXzoImaqmfOrL7teGUBt/T7ZDnyeGBWyXQBvOVhLkWLN37GXv8NMLK406UY6dS51JfcQHsmcW5cJ441bHg6Lg==}
|
||||
|
||||
@@ -20,6 +20,8 @@ import { securityRouter } from "./routers/security";
|
||||
import { portRouter } from "./routers/port";
|
||||
import { adminRouter } from "./routers/admin";
|
||||
import { dockerRouter } from "./routers/docker";
|
||||
import { registryRouter } from "./routers/registry";
|
||||
import { clusterRouter } from "./routers/cluster";
|
||||
/**
|
||||
* This is the primary router for your server.
|
||||
*
|
||||
@@ -47,6 +49,8 @@ export const appRouter = createTRPCRouter({
|
||||
security: securityRouter,
|
||||
redirects: redirectsRouter,
|
||||
port: portRouter,
|
||||
registry: registryRouter,
|
||||
cluster: clusterRouter,
|
||||
});
|
||||
|
||||
// export type definition of API
|
||||
|
||||
@@ -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`;
|
||||
}),
|
||||
});
|
||||
@@ -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;
|
||||
}),
|
||||
});
|
||||
@@ -181,7 +181,7 @@ export const settingsRouter = createTRPCRouter({
|
||||
return true;
|
||||
}),
|
||||
|
||||
checkAndUpdateImage: adminProcedure.query(async () => {
|
||||
checkAndUpdateImage: adminProcedure.mutation(async () => {
|
||||
return await pullLatestRelease();
|
||||
}),
|
||||
updateServer: adminProcedure.mutation(async () => {
|
||||
@@ -238,3 +238,4 @@ export const settingsRouter = createTRPCRouter({
|
||||
return readConfigInPath(input.path);
|
||||
}),
|
||||
});
|
||||
// apt-get install apache2-utils
|
||||
|
||||
@@ -61,6 +61,7 @@ export const findApplicationById = async (applicationId: string) => {
|
||||
redirects: true,
|
||||
security: true,
|
||||
ports: true,
|
||||
registry: true,
|
||||
},
|
||||
});
|
||||
if (!application) {
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
}
|
||||
@@ -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;
|
||||
};
|
||||
@@ -11,5 +11,6 @@ export const LOGS_PATH = `${BASE_PATH}/logs`;
|
||||
export const APPLICATIONS_PATH = `${BASE_PATH}/applications`;
|
||||
export const SSH_PATH = `${BASE_PATH}/ssh`;
|
||||
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 docker = new Docker();
|
||||
|
||||
@@ -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 {
|
||||
export default defineConfig({
|
||||
schema: "./server/db/schema/index.ts",
|
||||
driver: "pg",
|
||||
dialect: "postgresql",
|
||||
dbCredentials: {
|
||||
connectionString: process.env.DATABASE_URL || "",
|
||||
url: process.env.DATABASE_URL || "",
|
||||
},
|
||||
verbose: true,
|
||||
strict: true,
|
||||
out: "drizzle",
|
||||
} satisfies Config;
|
||||
migrations: {
|
||||
table: "migrations",
|
||||
schema: "public",
|
||||
},
|
||||
});
|
||||
|
||||
@@ -6,6 +6,7 @@ import { users } from "./user";
|
||||
import { createInsertSchema } from "drizzle-zod";
|
||||
import { z } from "zod";
|
||||
import { certificateType } from "./shared";
|
||||
import { registry } from "./registry";
|
||||
|
||||
export const admins = pgTable("admin", {
|
||||
adminId: text("adminId")
|
||||
@@ -39,6 +40,7 @@ export const adminsRelations = relations(admins, ({ one, many }) => ({
|
||||
references: [auth.id],
|
||||
}),
|
||||
users: many(users),
|
||||
registry: many(registry),
|
||||
}));
|
||||
|
||||
const createSchema = createInsertSchema(admins, {
|
||||
|
||||
@@ -10,8 +10,16 @@ import { projects } from "./project";
|
||||
import { security } from "./security";
|
||||
import { applicationStatus } from "./shared";
|
||||
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 { registry } from "./registry";
|
||||
|
||||
export const sourceType = pgEnum("sourceType", ["docker", "git", "github"]);
|
||||
|
||||
@@ -22,6 +30,65 @@ export const buildType = pgEnum("buildType", [
|
||||
"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", {
|
||||
applicationId: text("applicationId")
|
||||
.notNull()
|
||||
@@ -60,6 +127,17 @@ export const applications = pgTable("application", {
|
||||
customGitBuildPath: text("customGitBuildPath"),
|
||||
customGitSSHKey: text("customGitSSHKey"),
|
||||
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")
|
||||
.notNull()
|
||||
.default("idle"),
|
||||
@@ -67,6 +145,9 @@ export const applications = pgTable("application", {
|
||||
createdAt: text("createdAt")
|
||||
.notNull()
|
||||
.$defaultFn(() => new Date().toISOString()),
|
||||
registryId: text("registryId").references(() => registry.registryId, {
|
||||
onDelete: "set null",
|
||||
}),
|
||||
projectId: text("projectId")
|
||||
.notNull()
|
||||
.references(() => projects.projectId, { onDelete: "cascade" }),
|
||||
@@ -85,9 +166,101 @@ export const applicationsRelations = relations(
|
||||
redirects: many(redirects),
|
||||
security: many(security),
|
||||
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, {
|
||||
appName: z.string(),
|
||||
createdAt: z.string(),
|
||||
@@ -124,6 +297,14 @@ const createSchema = createInsertSchema(applications, {
|
||||
"nixpacks",
|
||||
]),
|
||||
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({
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
export * from "./application";
|
||||
export * from "./postgres";
|
||||
|
||||
export * from "./user";
|
||||
export * from "./admin";
|
||||
export * from "./auth";
|
||||
@@ -20,3 +19,4 @@ export * from "./security";
|
||||
export * from "./port";
|
||||
export * from "./redis";
|
||||
export * from "./shared";
|
||||
export * from "./registry";
|
||||
|
||||
@@ -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();
|
||||
+1
-2
@@ -10,7 +10,7 @@ import {
|
||||
import { setupDeploymentLogsWebSocketServer } from "./wss/listen-deployment";
|
||||
import { setupDockerStatsMonitoringSocketServer } from "./wss/docker-stats";
|
||||
import { setupDirectories } from "./setup/config-paths";
|
||||
import { initializeNetwork, initializeSwarm } from "./setup/setup";
|
||||
import { initializeNetwork } from "./setup/setup";
|
||||
import {
|
||||
createDefaultMiddlewares,
|
||||
createDefaultServerTraefikConfig,
|
||||
@@ -44,7 +44,6 @@ void app.prepare().then(async () => {
|
||||
if (process.env.NODE_ENV === "production") {
|
||||
setupDirectories();
|
||||
createDefaultMiddlewares();
|
||||
await initializeSwarm();
|
||||
await initializeNetwork();
|
||||
createDefaultTraefikConfig();
|
||||
createDefaultServerTraefikConfig();
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
};
|
||||
@@ -5,6 +5,7 @@ import type { CreateServiceOptions } from "dockerode";
|
||||
import {
|
||||
calculateResources,
|
||||
generateBindMounts,
|
||||
generateConfigContainer,
|
||||
generateFileMounts,
|
||||
generateVolumeMounts,
|
||||
prepareEnvironmentVariables,
|
||||
@@ -13,6 +14,7 @@ import { buildCustomDocker } from "./docker-file";
|
||||
import { buildHeroku } from "./heroku";
|
||||
import { buildNixpacks } from "./nixpacks";
|
||||
import { buildPaketo } from "./paketo";
|
||||
import { uploadImage } from "../cluster/upload";
|
||||
|
||||
// NIXPACKS 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)
|
||||
export type ApplicationNested = InferResultType<
|
||||
"applications",
|
||||
{ mounts: true; security: true; redirects: true; ports: true }
|
||||
{ mounts: true; security: true; redirects: true; ports: true; registry: true }
|
||||
>;
|
||||
export const buildApplication = async (
|
||||
application: ApplicationNested,
|
||||
@@ -42,6 +44,10 @@ export const buildApplication = async (
|
||||
} else if (buildType === "dockerfile") {
|
||||
await buildCustomDocker(application, writeStream);
|
||||
}
|
||||
|
||||
if (application.registryId) {
|
||||
await uploadImage(application, writeStream);
|
||||
}
|
||||
await mechanizeDockerContainer(application);
|
||||
writeStream.write("Docker Deployed: ✅");
|
||||
} catch (error) {
|
||||
@@ -75,16 +81,50 @@ export const mechanizeDockerContainer = async (
|
||||
cpuLimit,
|
||||
cpuReservation,
|
||||
});
|
||||
|
||||
const volumesMount = generateVolumeMounts(mounts);
|
||||
|
||||
const {
|
||||
HealthCheck,
|
||||
RestartPolicy,
|
||||
Placement,
|
||||
Labels,
|
||||
Mode,
|
||||
RollbackConfig,
|
||||
UpdateConfig,
|
||||
Networks,
|
||||
} = generateConfigContainer(application);
|
||||
|
||||
const bindsMount = generateBindMounts(mounts);
|
||||
const filesMount = generateFileMounts(appName, mounts);
|
||||
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 = {
|
||||
authconfig: {
|
||||
password: registry?.password || "",
|
||||
username: registry?.username || "",
|
||||
serveraddress: registry?.registryUrl || "",
|
||||
},
|
||||
Name: appName,
|
||||
TaskTemplate: {
|
||||
ContainerSpec: {
|
||||
Image: sourceType === "docker" ? dockerImage! : `${appName}:latest`,
|
||||
HealthCheck,
|
||||
Image: image,
|
||||
Env: envVariables,
|
||||
Mounts: [...volumesMount, ...bindsMount, ...filesMount],
|
||||
...(command
|
||||
@@ -93,20 +133,17 @@ export const mechanizeDockerContainer = async (
|
||||
Args: ["-c", command],
|
||||
}
|
||||
: {}),
|
||||
Labels,
|
||||
},
|
||||
Networks: [{ Target: "dokploy-network" }],
|
||||
RestartPolicy: {
|
||||
Condition: "on-failure",
|
||||
},
|
||||
Networks,
|
||||
RestartPolicy,
|
||||
Placement,
|
||||
Resources: {
|
||||
...resources,
|
||||
},
|
||||
},
|
||||
Mode: {
|
||||
Replicated: {
|
||||
Replicas: 1,
|
||||
},
|
||||
},
|
||||
Mode,
|
||||
RollbackConfig,
|
||||
EndpointSpec: {
|
||||
Ports: ports.map((port) => ({
|
||||
Protocol: port.protocol,
|
||||
@@ -114,10 +151,7 @@ export const mechanizeDockerContainer = async (
|
||||
PublishedPort: port.publishedPort,
|
||||
})),
|
||||
},
|
||||
UpdateConfig: {
|
||||
Parallelism: 1,
|
||||
Order: "start-first",
|
||||
},
|
||||
UpdateConfig,
|
||||
};
|
||||
|
||||
try {
|
||||
@@ -132,7 +166,7 @@ export const mechanizeDockerContainer = async (
|
||||
},
|
||||
});
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
await docker.createService(settings);
|
||||
}
|
||||
// await cleanUpUnusedImages();
|
||||
};
|
||||
|
||||
@@ -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
|
||||
@@ -122,10 +122,10 @@ export const cleanUpInactiveContainers = async () => {
|
||||
|
||||
for (const container of inactiveContainers) {
|
||||
await docker.getContainer(container.Id).remove({ force: true });
|
||||
console.log(`Contenedor eliminado: ${container.Id}`);
|
||||
console.log(`Cleaning up inactive container: ${container.Id}`);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error al limpiar contenedores inactivos:", error);
|
||||
console.error("Error cleaning up inactive containers:", 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"]) => {
|
||||
if (!mounts || mounts.length === 0) {
|
||||
return [];
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user