mirror of
https://github.com/Dokploy/dokploy
synced 2025-06-26 18:27:59 +00:00
refactor: agroupate utilities
This commit is contained in:
@@ -15,7 +15,8 @@ import { toast } from "sonner";
|
||||
import { z } from "zod";
|
||||
import { validateAndFormatYAML } from "../../application/advanced/traefik/update-traefik-config";
|
||||
import { RandomizeCompose } from "./randomize-compose";
|
||||
import { RandomizeDeployable } from "./randomize-deployable";
|
||||
import { RandomizeDeployable } from "./isolated-deployment";
|
||||
import { ShowUtilities } from "./show-utilities";
|
||||
|
||||
interface Props {
|
||||
composeId: string;
|
||||
@@ -126,8 +127,7 @@ services:
|
||||
</Form>
|
||||
<div className="flex justify-between flex-col lg:flex-row gap-2">
|
||||
<div className="w-full flex flex-col lg:flex-row gap-4 items-end">
|
||||
<RandomizeCompose composeId={composeId} />
|
||||
<RandomizeDeployable composeId={composeId} />
|
||||
<ShowUtilities composeId={composeId} />
|
||||
</div>
|
||||
<Button
|
||||
type="submit"
|
||||
|
||||
@@ -0,0 +1,191 @@
|
||||
import { AlertBlock } from "@/components/shared/alert-block";
|
||||
import { CodeEditor } from "@/components/shared/code-editor";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import {
|
||||
DialogDescription,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
} from "@/components/ui/dialog";
|
||||
import {
|
||||
Form,
|
||||
FormControl,
|
||||
FormDescription,
|
||||
FormField,
|
||||
FormItem,
|
||||
FormLabel,
|
||||
} from "@/components/ui/form";
|
||||
import { Label } from "@/components/ui/label";
|
||||
import { Switch } from "@/components/ui/switch";
|
||||
import { api } from "@/utils/api";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { AlertTriangle } from "lucide-react";
|
||||
import { useEffect, useState } from "react";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { toast } from "sonner";
|
||||
import { z } from "zod";
|
||||
|
||||
interface Props {
|
||||
composeId: string;
|
||||
}
|
||||
|
||||
const schema = z.object({
|
||||
isolatedDeployment: z.boolean().optional(),
|
||||
});
|
||||
|
||||
type Schema = z.infer<typeof schema>;
|
||||
|
||||
export const IsolatedDeployment = ({ composeId }: Props) => {
|
||||
const utils = api.useUtils();
|
||||
const [compose, setCompose] = useState<string>("");
|
||||
const { mutateAsync, error, isError } =
|
||||
api.compose.isolatedDeployment.useMutation();
|
||||
|
||||
const { mutateAsync: updateCompose } = api.compose.update.useMutation();
|
||||
|
||||
const { data, refetch } = api.compose.one.useQuery(
|
||||
{ composeId },
|
||||
{ enabled: !!composeId },
|
||||
);
|
||||
|
||||
console.log(data);
|
||||
|
||||
const form = useForm<Schema>({
|
||||
defaultValues: {
|
||||
isolatedDeployment: false,
|
||||
},
|
||||
resolver: zodResolver(schema),
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
randomizeCompose();
|
||||
if (data) {
|
||||
form.reset({
|
||||
isolatedDeployment: data?.isolatedDeployment || false,
|
||||
});
|
||||
}
|
||||
}, [form, form.reset, form.formState.isSubmitSuccessful, data]);
|
||||
|
||||
const onSubmit = async (formData: Schema) => {
|
||||
await updateCompose({
|
||||
composeId,
|
||||
isolatedDeployment: formData?.isolatedDeployment || false,
|
||||
})
|
||||
.then(async (data) => {
|
||||
randomizeCompose();
|
||||
refetch();
|
||||
toast.success("Compose updated");
|
||||
})
|
||||
.catch(() => {
|
||||
toast.error("Error updating the compose");
|
||||
});
|
||||
};
|
||||
|
||||
const randomizeCompose = async () => {
|
||||
await mutateAsync({
|
||||
composeId,
|
||||
suffix: data?.appName || "",
|
||||
})
|
||||
.then(async (data) => {
|
||||
await utils.project.all.invalidate();
|
||||
setCompose(data);
|
||||
toast.success("Compose Isolated");
|
||||
})
|
||||
.catch(() => {
|
||||
toast.error("Error isolating the compose");
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<DialogHeader>
|
||||
<DialogTitle>Isolate Deployment</DialogTitle>
|
||||
<DialogDescription>
|
||||
Use this option to isolate the deployment of this compose file.
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
<div className="text-sm text-muted-foreground flex flex-col gap-2">
|
||||
<span>
|
||||
This feature creates an isolated environment for your deployment by
|
||||
adding unique prefixes to all resources. It establishes a dedicated
|
||||
network based on your compose file's name, ensuring your services run
|
||||
in isolation. This prevents conflicts when running multiple instances
|
||||
of the same template or services with identical names.
|
||||
</span>
|
||||
<div className="space-y-4">
|
||||
<div>
|
||||
<h4 className="font-medium mb-2">
|
||||
Resources that will be isolated:
|
||||
</h4>
|
||||
<ul className="list-disc list-inside">
|
||||
<li>Docker volumes</li>
|
||||
<li>Docker networks</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{isError && <AlertBlock type="error">{error?.message}</AlertBlock>}
|
||||
<Form {...form}>
|
||||
<form
|
||||
onSubmit={form.handleSubmit(onSubmit)}
|
||||
id="hook-form-add-project"
|
||||
className="grid w-full gap-4"
|
||||
>
|
||||
{isError && (
|
||||
<div className="flex flex-row gap-4 rounded-lg items-center 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>
|
||||
)}
|
||||
|
||||
<div className="flex flex-col lg:flex-col gap-4 w-full ">
|
||||
<div>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="isolatedDeployment"
|
||||
render={({ field }) => (
|
||||
<FormItem className="mt-4 flex flex-row items-center justify-between rounded-lg border p-3 shadow-sm">
|
||||
<div className="space-y-0.5">
|
||||
<FormLabel>Enable Randomize ({data?.appName})</FormLabel>
|
||||
<FormDescription>
|
||||
Enable randomize to the compose file.
|
||||
</FormDescription>
|
||||
</div>
|
||||
<FormControl>
|
||||
<Switch
|
||||
checked={field.value}
|
||||
onCheckedChange={field.onChange}
|
||||
/>
|
||||
</FormControl>
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col lg:flex-row gap-4 w-full items-end justify-end">
|
||||
<Button
|
||||
form="hook-form-add-project"
|
||||
type="submit"
|
||||
className="lg:w-fit"
|
||||
>
|
||||
Save
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-col gap-4">
|
||||
<Label>Preview</Label>
|
||||
<pre>
|
||||
<CodeEditor
|
||||
value={compose || ""}
|
||||
language="yaml"
|
||||
readOnly
|
||||
height="50rem"
|
||||
/>
|
||||
</pre>
|
||||
</div>
|
||||
</form>
|
||||
</Form>
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -1,14 +1,10 @@
|
||||
import { AlertBlock } from "@/components/shared/alert-block";
|
||||
import { CodeEditor } from "@/components/shared/code-editor";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { CardTitle } from "@/components/ui/card";
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogDescription,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
DialogTrigger,
|
||||
} from "@/components/ui/dialog";
|
||||
import {
|
||||
Form,
|
||||
@@ -20,11 +16,6 @@ import {
|
||||
FormMessage,
|
||||
} from "@/components/ui/form";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import {
|
||||
InputOTP,
|
||||
InputOTPGroup,
|
||||
InputOTPSlot,
|
||||
} from "@/components/ui/input-otp";
|
||||
import { Switch } from "@/components/ui/switch";
|
||||
import { api } from "@/utils/api";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
@@ -70,6 +61,7 @@ export const RandomizeCompose = ({ composeId }: Props) => {
|
||||
const suffix = form.watch("suffix");
|
||||
|
||||
useEffect(() => {
|
||||
randomizeCompose();
|
||||
if (data) {
|
||||
form.reset({
|
||||
suffix: data?.suffix || "",
|
||||
@@ -110,126 +102,117 @@ export const RandomizeCompose = ({ composeId }: Props) => {
|
||||
};
|
||||
|
||||
return (
|
||||
<Dialog open={isOpen} onOpenChange={setIsOpen}>
|
||||
<DialogTrigger asChild onClick={() => randomizeCompose()}>
|
||||
<Button className="max-lg:w-full" variant="outline">
|
||||
<Dices className="h-4 w-4" />
|
||||
Randomize Compose
|
||||
</Button>
|
||||
</DialogTrigger>
|
||||
<DialogContent className="sm:max-w-6xl max-h-[50rem] overflow-y-auto">
|
||||
<DialogHeader>
|
||||
<DialogTitle>Randomize Compose (Experimental)</DialogTitle>
|
||||
<DialogDescription>
|
||||
Use this in case you want to deploy the same compose file and you
|
||||
have conflicts with some property like volumes, networks, etc.
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
<div className="text-sm text-muted-foreground flex flex-col gap-2">
|
||||
<span>
|
||||
This will randomize the compose file and will add a suffix to the
|
||||
property to avoid conflicts
|
||||
</span>
|
||||
<ul className="list-disc list-inside">
|
||||
<li>volumes</li>
|
||||
<li>networks</li>
|
||||
<li>services</li>
|
||||
<li>configs</li>
|
||||
<li>secrets</li>
|
||||
</ul>
|
||||
<AlertBlock type="info">
|
||||
When you activate this option, we will include a env
|
||||
`COMPOSE_PREFIX` variable to the compose file so you can use it in
|
||||
your compose file.
|
||||
</AlertBlock>
|
||||
</div>
|
||||
{isError && <AlertBlock type="error">{error?.message}</AlertBlock>}
|
||||
<Form {...form}>
|
||||
<form
|
||||
onSubmit={form.handleSubmit(onSubmit)}
|
||||
id="hook-form-add-project"
|
||||
className="grid w-full gap-4"
|
||||
>
|
||||
{isError && (
|
||||
<div className="flex flex-row gap-4 rounded-lg items-center 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>
|
||||
)}
|
||||
|
||||
<div className="flex flex-col lg:flex-col gap-4 w-full ">
|
||||
<div>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="suffix"
|
||||
render={({ field }) => (
|
||||
<FormItem className="flex flex-col justify-center max-sm:items-center w-full">
|
||||
<FormLabel>Suffix</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
placeholder="Enter a suffix (Optional, example: prod)"
|
||||
{...field}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="randomize"
|
||||
render={({ field }) => (
|
||||
<FormItem className="mt-4 flex flex-row items-center justify-between rounded-lg border p-3 shadow-sm">
|
||||
<div className="space-y-0.5">
|
||||
<FormLabel>Apply Randomize</FormLabel>
|
||||
<FormDescription>
|
||||
Apply randomize to the compose file.
|
||||
</FormDescription>
|
||||
</div>
|
||||
<FormControl>
|
||||
<Switch
|
||||
checked={field.value}
|
||||
onCheckedChange={field.onChange}
|
||||
/>
|
||||
</FormControl>
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col lg:flex-row gap-4 w-full items-end justify-end">
|
||||
<Button
|
||||
form="hook-form-add-project"
|
||||
type="submit"
|
||||
className="lg:w-fit"
|
||||
>
|
||||
Save
|
||||
</Button>
|
||||
<Button
|
||||
type="button"
|
||||
variant="secondary"
|
||||
onClick={async () => {
|
||||
await randomizeCompose();
|
||||
}}
|
||||
className="lg:w-fit"
|
||||
>
|
||||
Random
|
||||
</Button>
|
||||
</div>
|
||||
<div className="w-full">
|
||||
<DialogHeader>
|
||||
<DialogTitle>Randomize Compose (Experimental)</DialogTitle>
|
||||
<DialogDescription>
|
||||
Use this in case you want to deploy the same compose file and you have
|
||||
conflicts with some property like volumes, networks, etc.
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
<div className="text-sm text-muted-foreground flex flex-col gap-2">
|
||||
<span>
|
||||
This will randomize the compose file and will add a suffix to the
|
||||
property to avoid conflicts
|
||||
</span>
|
||||
<ul className="list-disc list-inside">
|
||||
<li>volumes</li>
|
||||
<li>networks</li>
|
||||
<li>services</li>
|
||||
<li>configs</li>
|
||||
<li>secrets</li>
|
||||
</ul>
|
||||
<AlertBlock type="info">
|
||||
When you activate this option, we will include a env `COMPOSE_PREFIX`
|
||||
variable to the compose file so you can use it in your compose file.
|
||||
</AlertBlock>
|
||||
</div>
|
||||
{isError && <AlertBlock type="error">{error?.message}</AlertBlock>}
|
||||
<Form {...form}>
|
||||
<form
|
||||
onSubmit={form.handleSubmit(onSubmit)}
|
||||
id="hook-form-add-project"
|
||||
className="grid w-full gap-4"
|
||||
>
|
||||
{isError && (
|
||||
<div className="flex flex-row gap-4 rounded-lg items-center 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>
|
||||
<pre>
|
||||
<CodeEditor
|
||||
value={compose || ""}
|
||||
language="yaml"
|
||||
readOnly
|
||||
height="50rem"
|
||||
)}
|
||||
|
||||
<div className="flex flex-col lg:flex-col gap-4 w-full ">
|
||||
<div>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="suffix"
|
||||
render={({ field }) => (
|
||||
<FormItem className="flex flex-col justify-center max-sm:items-center w-full mt-4">
|
||||
<FormLabel>Suffix</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
placeholder="Enter a suffix (Optional, example: prod)"
|
||||
{...field}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
</pre>
|
||||
</form>
|
||||
</Form>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="randomize"
|
||||
render={({ field }) => (
|
||||
<FormItem className="mt-4 flex flex-row items-center justify-between rounded-lg border p-3 shadow-sm">
|
||||
<div className="space-y-0.5">
|
||||
<FormLabel>Apply Randomize</FormLabel>
|
||||
<FormDescription>
|
||||
Apply randomize to the compose file.
|
||||
</FormDescription>
|
||||
</div>
|
||||
<FormControl>
|
||||
<Switch
|
||||
checked={field.value}
|
||||
onCheckedChange={field.onChange}
|
||||
/>
|
||||
</FormControl>
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col lg:flex-row gap-4 w-full items-end justify-end">
|
||||
<Button
|
||||
form="hook-form-add-project"
|
||||
type="submit"
|
||||
className="lg:w-fit"
|
||||
>
|
||||
Save
|
||||
</Button>
|
||||
<Button
|
||||
type="button"
|
||||
variant="secondary"
|
||||
onClick={async () => {
|
||||
await randomizeCompose();
|
||||
}}
|
||||
className="lg:w-fit"
|
||||
>
|
||||
Random
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
<pre>
|
||||
<CodeEditor
|
||||
value={compose || ""}
|
||||
language="yaml"
|
||||
readOnly
|
||||
height="50rem"
|
||||
/>
|
||||
</pre>
|
||||
</form>
|
||||
</Form>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,195 +0,0 @@
|
||||
import { AlertBlock } from "@/components/shared/alert-block";
|
||||
import { CodeEditor } from "@/components/shared/code-editor";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { CardTitle } from "@/components/ui/card";
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogDescription,
|
||||
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 { Switch } from "@/components/ui/switch";
|
||||
import { api } from "@/utils/api";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { AlertTriangle, Dices } from "lucide-react";
|
||||
import { useEffect, useState } from "react";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { toast } from "sonner";
|
||||
import { z } from "zod";
|
||||
|
||||
interface Props {
|
||||
composeId: string;
|
||||
}
|
||||
|
||||
const schema = z.object({
|
||||
deployable: z.boolean().optional(),
|
||||
});
|
||||
|
||||
type Schema = z.infer<typeof schema>;
|
||||
|
||||
export const RandomizeDeployable = ({ composeId }: Props) => {
|
||||
const utils = api.useUtils();
|
||||
const [compose, setCompose] = useState<string>("");
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const { mutateAsync, error, isError } =
|
||||
api.compose.randomizeDeployableCompose.useMutation();
|
||||
|
||||
const { mutateAsync: updateCompose } = api.compose.update.useMutation();
|
||||
|
||||
const { data, refetch } = api.compose.one.useQuery(
|
||||
{ composeId },
|
||||
{ enabled: !!composeId },
|
||||
);
|
||||
|
||||
const form = useForm<Schema>({
|
||||
defaultValues: {
|
||||
deployable: false,
|
||||
},
|
||||
resolver: zodResolver(schema),
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (data) {
|
||||
form.reset({
|
||||
deployable: data?.deployable || false,
|
||||
});
|
||||
}
|
||||
}, [form, form.reset, form.formState.isSubmitSuccessful, data]);
|
||||
|
||||
const onSubmit = async (formData: Schema) => {
|
||||
await updateCompose({
|
||||
composeId,
|
||||
deployable: formData?.deployable || false,
|
||||
})
|
||||
.then(async (data) => {
|
||||
randomizeCompose();
|
||||
refetch();
|
||||
toast.success("Compose updated");
|
||||
})
|
||||
.catch(() => {
|
||||
toast.error("Error randomizing the compose");
|
||||
});
|
||||
};
|
||||
|
||||
const randomizeCompose = async () => {
|
||||
await mutateAsync({
|
||||
composeId,
|
||||
suffix: data?.appName || "",
|
||||
})
|
||||
.then(async (data) => {
|
||||
await utils.project.all.invalidate();
|
||||
setCompose(data);
|
||||
toast.success("Compose randomized");
|
||||
})
|
||||
.catch(() => {
|
||||
toast.error("Error randomizing the compose");
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<Dialog open={isOpen} onOpenChange={setIsOpen}>
|
||||
<DialogTrigger asChild onClick={() => randomizeCompose()}>
|
||||
<Button className="max-lg:w-full" variant="outline">
|
||||
<Dices className="h-4 w-4" />
|
||||
Randomize Deployable
|
||||
</Button>
|
||||
</DialogTrigger>
|
||||
<DialogContent className="sm:max-w-6xl max-h-[50rem] overflow-y-auto">
|
||||
<DialogHeader>
|
||||
<DialogTitle>Randomize Deployable (Experimental)</DialogTitle>
|
||||
<DialogDescription>
|
||||
Use this in case you want to deploy the same compose file twice.
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
<div className="text-sm text-muted-foreground flex flex-col gap-2">
|
||||
<span>
|
||||
This will randomize the compose file and will add a suffix to the
|
||||
property to avoid conflicts
|
||||
</span>
|
||||
<ul className="list-disc list-inside">
|
||||
<li>volumes</li>
|
||||
<li>networks</li>
|
||||
</ul>
|
||||
<AlertBlock type="info">
|
||||
When you activate this option, we will include a env
|
||||
`DOKPLOY_SUFFIX` variable to the compose file so you can use it in
|
||||
your compose file, also we don't include the{" "}
|
||||
<code>dokploy-network</code> to any of the services by default.
|
||||
</AlertBlock>
|
||||
</div>
|
||||
{isError && <AlertBlock type="error">{error?.message}</AlertBlock>}
|
||||
<Form {...form}>
|
||||
<form
|
||||
onSubmit={form.handleSubmit(onSubmit)}
|
||||
id="hook-form-add-project"
|
||||
className="grid w-full gap-4"
|
||||
>
|
||||
{isError && (
|
||||
<div className="flex flex-row gap-4 rounded-lg items-center 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>
|
||||
)}
|
||||
|
||||
<div className="flex flex-col lg:flex-col gap-4 w-full ">
|
||||
<div>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="deployable"
|
||||
render={({ field }) => (
|
||||
<FormItem className="mt-4 flex flex-row items-center justify-between rounded-lg border p-3 shadow-sm">
|
||||
<div className="space-y-0.5">
|
||||
<FormLabel>Apply Randomize ({data?.appName})</FormLabel>
|
||||
<FormDescription>
|
||||
Apply randomize to the compose file.
|
||||
</FormDescription>
|
||||
</div>
|
||||
<FormControl>
|
||||
<Switch
|
||||
checked={field.value}
|
||||
onCheckedChange={field.onChange}
|
||||
/>
|
||||
</FormControl>
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col lg:flex-row gap-4 w-full items-end justify-end">
|
||||
<Button
|
||||
form="hook-form-add-project"
|
||||
type="submit"
|
||||
className="lg:w-fit"
|
||||
>
|
||||
Save
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
<pre>
|
||||
<CodeEditor
|
||||
value={compose || ""}
|
||||
language="yaml"
|
||||
readOnly
|
||||
height="50rem"
|
||||
/>
|
||||
</pre>
|
||||
</form>
|
||||
</Form>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,46 @@
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
||||
import { IsolatedDeployment } from "./isolated-deployment";
|
||||
import { RandomizeCompose } from "./randomize-compose";
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogDescription,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
DialogTrigger,
|
||||
} from "@/components/ui/dialog";
|
||||
import { useState } from "react";
|
||||
import { Button } from "@/components/ui/button";
|
||||
|
||||
interface Props {
|
||||
composeId: string;
|
||||
}
|
||||
|
||||
export const ShowUtilities = ({ composeId }: Props) => {
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
return (
|
||||
<Dialog open={isOpen} onOpenChange={setIsOpen}>
|
||||
<DialogTrigger asChild>
|
||||
<Button variant="ghost">Show Utilities</Button>
|
||||
</DialogTrigger>
|
||||
<DialogContent className="max-h-screen overflow-y-auto sm:max-w-5xl">
|
||||
<DialogHeader>
|
||||
<DialogTitle>Utilities </DialogTitle>
|
||||
<DialogDescription>Modify the application data</DialogDescription>
|
||||
</DialogHeader>
|
||||
<Tabs defaultValue="isolated">
|
||||
<TabsList className="grid w-full grid-cols-2">
|
||||
<TabsTrigger value="isolated">Isolated Deployment</TabsTrigger>
|
||||
<TabsTrigger value="randomize">Randomize Compose</TabsTrigger>
|
||||
</TabsList>
|
||||
<TabsContent value="randomize" className="pt-5">
|
||||
<RandomizeCompose composeId={composeId} />
|
||||
</TabsContent>
|
||||
<TabsContent value="isolated" className="pt-5">
|
||||
<IsolatedDeployment composeId={composeId} />
|
||||
</TabsContent>
|
||||
</Tabs>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
};
|
||||
1
apps/dokploy/drizzle/0065_daily_zaladane.sql
Normal file
1
apps/dokploy/drizzle/0065_daily_zaladane.sql
Normal file
@@ -0,0 +1 @@
|
||||
ALTER TABLE "compose" RENAME COLUMN "deployable" TO "isolatedDeployment";
|
||||
4485
apps/dokploy/drizzle/meta/0065_snapshot.json
Normal file
4485
apps/dokploy/drizzle/meta/0065_snapshot.json
Normal file
File diff suppressed because it is too large
Load Diff
@@ -456,6 +456,13 @@
|
||||
"when": 1738564387043,
|
||||
"tag": "0064_previous_agent_brand",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 65,
|
||||
"version": "7",
|
||||
"when": 1739087857244,
|
||||
"tag": "0065_daily_zaladane",
|
||||
"breakpoints": true
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,22 +1,22 @@
|
||||
import { slugify } from "@/lib/slug";
|
||||
import { db } from "@/server/db";
|
||||
import {
|
||||
apiCreateCompose,
|
||||
apiCreateComposeByTemplate,
|
||||
apiDeleteCompose,
|
||||
apiFetchServices,
|
||||
apiFindCompose,
|
||||
apiRandomizeCompose,
|
||||
apiUpdateCompose,
|
||||
compose,
|
||||
apiCreateCompose,
|
||||
apiCreateComposeByTemplate,
|
||||
apiDeleteCompose,
|
||||
apiFetchServices,
|
||||
apiFindCompose,
|
||||
apiRandomizeCompose,
|
||||
apiUpdateCompose,
|
||||
compose,
|
||||
} from "@/server/db/schema";
|
||||
import { cleanQueuesByCompose, myQueue } from "@/server/queues/queueSetup";
|
||||
import { templates } from "@/templates/templates";
|
||||
import type { TemplatesKeys } from "@/templates/types/templates-data.type";
|
||||
import {
|
||||
generatePassword,
|
||||
loadTemplateModule,
|
||||
readTemplateComposeFile,
|
||||
generatePassword,
|
||||
loadTemplateModule,
|
||||
readTemplateComposeFile,
|
||||
} from "@/templates/utils";
|
||||
import { TRPCError } from "@trpc/server";
|
||||
import { eq } from "drizzle-orm";
|
||||
@@ -28,443 +28,443 @@ import { createTRPCRouter, protectedProcedure } from "../trpc";
|
||||
import type { DeploymentJob } from "@/server/queues/queue-types";
|
||||
import { deploy } from "@/server/utils/deploy";
|
||||
import {
|
||||
IS_CLOUD,
|
||||
addDomainToCompose,
|
||||
addNewService,
|
||||
checkServiceAccess,
|
||||
cloneCompose,
|
||||
cloneComposeRemote,
|
||||
createCommand,
|
||||
createCompose,
|
||||
createComposeByTemplate,
|
||||
createDomain,
|
||||
createMount,
|
||||
findAdminById,
|
||||
findComposeById,
|
||||
findDomainsByComposeId,
|
||||
findProjectById,
|
||||
findServerById,
|
||||
loadServices,
|
||||
randomizeComposeFile,
|
||||
randomizeDeployableComposeFile,
|
||||
removeCompose,
|
||||
removeComposeDirectory,
|
||||
removeDeploymentsByComposeId,
|
||||
startCompose,
|
||||
stopCompose,
|
||||
updateCompose,
|
||||
IS_CLOUD,
|
||||
addDomainToCompose,
|
||||
addNewService,
|
||||
checkServiceAccess,
|
||||
cloneCompose,
|
||||
cloneComposeRemote,
|
||||
createCommand,
|
||||
createCompose,
|
||||
createComposeByTemplate,
|
||||
createDomain,
|
||||
createMount,
|
||||
findAdminById,
|
||||
findComposeById,
|
||||
findDomainsByComposeId,
|
||||
findProjectById,
|
||||
findServerById,
|
||||
loadServices,
|
||||
randomizeComposeFile,
|
||||
randomizeIsolatedDeploymentComposeFile,
|
||||
removeCompose,
|
||||
removeComposeDirectory,
|
||||
removeDeploymentsByComposeId,
|
||||
startCompose,
|
||||
stopCompose,
|
||||
updateCompose,
|
||||
} from "@dokploy/server";
|
||||
|
||||
export const composeRouter = createTRPCRouter({
|
||||
create: protectedProcedure
|
||||
.input(apiCreateCompose)
|
||||
.mutation(async ({ ctx, input }) => {
|
||||
try {
|
||||
if (ctx.user.rol === "user") {
|
||||
await checkServiceAccess(ctx.user.authId, input.projectId, "create");
|
||||
}
|
||||
create: protectedProcedure
|
||||
.input(apiCreateCompose)
|
||||
.mutation(async ({ ctx, input }) => {
|
||||
try {
|
||||
if (ctx.user.rol === "user") {
|
||||
await checkServiceAccess(ctx.user.authId, input.projectId, "create");
|
||||
}
|
||||
|
||||
if (IS_CLOUD && !input.serverId) {
|
||||
throw new TRPCError({
|
||||
code: "UNAUTHORIZED",
|
||||
message: "You need to use a server to create a compose",
|
||||
});
|
||||
}
|
||||
const project = await findProjectById(input.projectId);
|
||||
if (project.adminId !== ctx.user.adminId) {
|
||||
throw new TRPCError({
|
||||
code: "UNAUTHORIZED",
|
||||
message: "You are not authorized to access this project",
|
||||
});
|
||||
}
|
||||
const newService = await createCompose(input);
|
||||
if (IS_CLOUD && !input.serverId) {
|
||||
throw new TRPCError({
|
||||
code: "UNAUTHORIZED",
|
||||
message: "You need to use a server to create a compose",
|
||||
});
|
||||
}
|
||||
const project = await findProjectById(input.projectId);
|
||||
if (project.adminId !== ctx.user.adminId) {
|
||||
throw new TRPCError({
|
||||
code: "UNAUTHORIZED",
|
||||
message: "You are not authorized to access this project",
|
||||
});
|
||||
}
|
||||
const newService = await createCompose(input);
|
||||
|
||||
if (ctx.user.rol === "user") {
|
||||
await addNewService(ctx.user.authId, newService.composeId);
|
||||
}
|
||||
if (ctx.user.rol === "user") {
|
||||
await addNewService(ctx.user.authId, newService.composeId);
|
||||
}
|
||||
|
||||
return newService;
|
||||
} catch (error) {
|
||||
throw error;
|
||||
}
|
||||
}),
|
||||
return newService;
|
||||
} catch (error) {
|
||||
throw error;
|
||||
}
|
||||
}),
|
||||
|
||||
one: protectedProcedure
|
||||
.input(apiFindCompose)
|
||||
.query(async ({ input, ctx }) => {
|
||||
if (ctx.user.rol === "user") {
|
||||
await checkServiceAccess(ctx.user.authId, input.composeId, "access");
|
||||
}
|
||||
one: protectedProcedure
|
||||
.input(apiFindCompose)
|
||||
.query(async ({ input, ctx }) => {
|
||||
if (ctx.user.rol === "user") {
|
||||
await checkServiceAccess(ctx.user.authId, input.composeId, "access");
|
||||
}
|
||||
|
||||
const compose = await findComposeById(input.composeId);
|
||||
if (compose.project.adminId !== ctx.user.adminId) {
|
||||
throw new TRPCError({
|
||||
code: "UNAUTHORIZED",
|
||||
message: "You are not authorized to access this compose",
|
||||
});
|
||||
}
|
||||
return compose;
|
||||
}),
|
||||
const compose = await findComposeById(input.composeId);
|
||||
if (compose.project.adminId !== ctx.user.adminId) {
|
||||
throw new TRPCError({
|
||||
code: "UNAUTHORIZED",
|
||||
message: "You are not authorized to access this compose",
|
||||
});
|
||||
}
|
||||
return compose;
|
||||
}),
|
||||
|
||||
update: protectedProcedure
|
||||
.input(apiUpdateCompose)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
const compose = await findComposeById(input.composeId);
|
||||
if (compose.project.adminId !== ctx.user.adminId) {
|
||||
throw new TRPCError({
|
||||
code: "UNAUTHORIZED",
|
||||
message: "You are not authorized to update this compose",
|
||||
});
|
||||
}
|
||||
return updateCompose(input.composeId, input);
|
||||
}),
|
||||
delete: protectedProcedure
|
||||
.input(apiDeleteCompose)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
if (ctx.user.rol === "user") {
|
||||
await checkServiceAccess(ctx.user.authId, input.composeId, "delete");
|
||||
}
|
||||
const composeResult = await findComposeById(input.composeId);
|
||||
update: protectedProcedure
|
||||
.input(apiUpdateCompose)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
const compose = await findComposeById(input.composeId);
|
||||
if (compose.project.adminId !== ctx.user.adminId) {
|
||||
throw new TRPCError({
|
||||
code: "UNAUTHORIZED",
|
||||
message: "You are not authorized to update this compose",
|
||||
});
|
||||
}
|
||||
return updateCompose(input.composeId, input);
|
||||
}),
|
||||
delete: protectedProcedure
|
||||
.input(apiDeleteCompose)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
if (ctx.user.rol === "user") {
|
||||
await checkServiceAccess(ctx.user.authId, input.composeId, "delete");
|
||||
}
|
||||
const composeResult = await findComposeById(input.composeId);
|
||||
|
||||
if (composeResult.project.adminId !== ctx.user.adminId) {
|
||||
throw new TRPCError({
|
||||
code: "UNAUTHORIZED",
|
||||
message: "You are not authorized to delete this compose",
|
||||
});
|
||||
}
|
||||
4;
|
||||
if (composeResult.project.adminId !== ctx.user.adminId) {
|
||||
throw new TRPCError({
|
||||
code: "UNAUTHORIZED",
|
||||
message: "You are not authorized to delete this compose",
|
||||
});
|
||||
}
|
||||
4;
|
||||
|
||||
const result = await db
|
||||
.delete(compose)
|
||||
.where(eq(compose.composeId, input.composeId))
|
||||
.returning();
|
||||
const result = await db
|
||||
.delete(compose)
|
||||
.where(eq(compose.composeId, input.composeId))
|
||||
.returning();
|
||||
|
||||
const cleanupOperations = [
|
||||
async () => await removeCompose(composeResult, input.deleteVolumes),
|
||||
async () => await removeDeploymentsByComposeId(composeResult),
|
||||
async () => await removeComposeDirectory(composeResult.appName),
|
||||
];
|
||||
const cleanupOperations = [
|
||||
async () => await removeCompose(composeResult, input.deleteVolumes),
|
||||
async () => await removeDeploymentsByComposeId(composeResult),
|
||||
async () => await removeComposeDirectory(composeResult.appName),
|
||||
];
|
||||
|
||||
for (const operation of cleanupOperations) {
|
||||
try {
|
||||
await operation();
|
||||
} catch (error) {}
|
||||
}
|
||||
for (const operation of cleanupOperations) {
|
||||
try {
|
||||
await operation();
|
||||
} catch (error) {}
|
||||
}
|
||||
|
||||
return result[0];
|
||||
}),
|
||||
cleanQueues: protectedProcedure
|
||||
.input(apiFindCompose)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
const compose = await findComposeById(input.composeId);
|
||||
if (compose.project.adminId !== ctx.user.adminId) {
|
||||
throw new TRPCError({
|
||||
code: "UNAUTHORIZED",
|
||||
message: "You are not authorized to clean this compose",
|
||||
});
|
||||
}
|
||||
await cleanQueuesByCompose(input.composeId);
|
||||
}),
|
||||
return result[0];
|
||||
}),
|
||||
cleanQueues: protectedProcedure
|
||||
.input(apiFindCompose)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
const compose = await findComposeById(input.composeId);
|
||||
if (compose.project.adminId !== ctx.user.adminId) {
|
||||
throw new TRPCError({
|
||||
code: "UNAUTHORIZED",
|
||||
message: "You are not authorized to clean this compose",
|
||||
});
|
||||
}
|
||||
await cleanQueuesByCompose(input.composeId);
|
||||
}),
|
||||
|
||||
loadServices: protectedProcedure
|
||||
.input(apiFetchServices)
|
||||
.query(async ({ input, ctx }) => {
|
||||
const compose = await findComposeById(input.composeId);
|
||||
if (compose.project.adminId !== ctx.user.adminId) {
|
||||
throw new TRPCError({
|
||||
code: "UNAUTHORIZED",
|
||||
message: "You are not authorized to load this compose",
|
||||
});
|
||||
}
|
||||
return await loadServices(input.composeId, input.type);
|
||||
}),
|
||||
fetchSourceType: protectedProcedure
|
||||
.input(apiFindCompose)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
try {
|
||||
const compose = await findComposeById(input.composeId);
|
||||
loadServices: protectedProcedure
|
||||
.input(apiFetchServices)
|
||||
.query(async ({ input, ctx }) => {
|
||||
const compose = await findComposeById(input.composeId);
|
||||
if (compose.project.adminId !== ctx.user.adminId) {
|
||||
throw new TRPCError({
|
||||
code: "UNAUTHORIZED",
|
||||
message: "You are not authorized to load this compose",
|
||||
});
|
||||
}
|
||||
return await loadServices(input.composeId, input.type);
|
||||
}),
|
||||
fetchSourceType: protectedProcedure
|
||||
.input(apiFindCompose)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
try {
|
||||
const compose = await findComposeById(input.composeId);
|
||||
|
||||
if (compose.project.adminId !== ctx.user.adminId) {
|
||||
throw new TRPCError({
|
||||
code: "UNAUTHORIZED",
|
||||
message: "You are not authorized to fetch this compose",
|
||||
});
|
||||
}
|
||||
if (compose.serverId) {
|
||||
await cloneComposeRemote(compose);
|
||||
} else {
|
||||
await cloneCompose(compose);
|
||||
}
|
||||
return compose.sourceType;
|
||||
} catch (err) {
|
||||
throw new TRPCError({
|
||||
code: "BAD_REQUEST",
|
||||
message: "Error fetching source type",
|
||||
cause: err,
|
||||
});
|
||||
}
|
||||
}),
|
||||
if (compose.project.adminId !== ctx.user.adminId) {
|
||||
throw new TRPCError({
|
||||
code: "UNAUTHORIZED",
|
||||
message: "You are not authorized to fetch this compose",
|
||||
});
|
||||
}
|
||||
if (compose.serverId) {
|
||||
await cloneComposeRemote(compose);
|
||||
} else {
|
||||
await cloneCompose(compose);
|
||||
}
|
||||
return compose.sourceType;
|
||||
} catch (err) {
|
||||
throw new TRPCError({
|
||||
code: "BAD_REQUEST",
|
||||
message: "Error fetching source type",
|
||||
cause: err,
|
||||
});
|
||||
}
|
||||
}),
|
||||
|
||||
randomizeCompose: protectedProcedure
|
||||
.input(apiRandomizeCompose)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
const compose = await findComposeById(input.composeId);
|
||||
if (compose.project.adminId !== ctx.user.adminId) {
|
||||
throw new TRPCError({
|
||||
code: "UNAUTHORIZED",
|
||||
message: "You are not authorized to randomize this compose",
|
||||
});
|
||||
}
|
||||
return await randomizeComposeFile(input.composeId, input.suffix);
|
||||
}),
|
||||
randomizeDeployableCompose: protectedProcedure
|
||||
.input(apiRandomizeCompose)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
const compose = await findComposeById(input.composeId);
|
||||
if (compose.project.adminId !== ctx.user.adminId) {
|
||||
throw new TRPCError({
|
||||
code: "UNAUTHORIZED",
|
||||
message: "You are not authorized to randomize this compose",
|
||||
});
|
||||
}
|
||||
return await randomizeDeployableComposeFile(
|
||||
input.composeId,
|
||||
input.suffix,
|
||||
);
|
||||
}),
|
||||
getConvertedCompose: protectedProcedure
|
||||
.input(apiFindCompose)
|
||||
.query(async ({ input, ctx }) => {
|
||||
const compose = await findComposeById(input.composeId);
|
||||
if (compose.project.adminId !== ctx.user.adminId) {
|
||||
throw new TRPCError({
|
||||
code: "UNAUTHORIZED",
|
||||
message: "You are not authorized to get this compose",
|
||||
});
|
||||
}
|
||||
const domains = await findDomainsByComposeId(input.composeId);
|
||||
const composeFile = await addDomainToCompose(compose, domains);
|
||||
return dump(composeFile, {
|
||||
lineWidth: 1000,
|
||||
});
|
||||
}),
|
||||
randomizeCompose: protectedProcedure
|
||||
.input(apiRandomizeCompose)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
const compose = await findComposeById(input.composeId);
|
||||
if (compose.project.adminId !== ctx.user.adminId) {
|
||||
throw new TRPCError({
|
||||
code: "UNAUTHORIZED",
|
||||
message: "You are not authorized to randomize this compose",
|
||||
});
|
||||
}
|
||||
return await randomizeComposeFile(input.composeId, input.suffix);
|
||||
}),
|
||||
isolatedDeployment: protectedProcedure
|
||||
.input(apiRandomizeCompose)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
const compose = await findComposeById(input.composeId);
|
||||
if (compose.project.adminId !== ctx.user.adminId) {
|
||||
throw new TRPCError({
|
||||
code: "UNAUTHORIZED",
|
||||
message: "You are not authorized to randomize this compose",
|
||||
});
|
||||
}
|
||||
return await randomizeIsolatedDeploymentComposeFile(
|
||||
input.composeId,
|
||||
input.suffix
|
||||
);
|
||||
}),
|
||||
getConvertedCompose: protectedProcedure
|
||||
.input(apiFindCompose)
|
||||
.query(async ({ input, ctx }) => {
|
||||
const compose = await findComposeById(input.composeId);
|
||||
if (compose.project.adminId !== ctx.user.adminId) {
|
||||
throw new TRPCError({
|
||||
code: "UNAUTHORIZED",
|
||||
message: "You are not authorized to get this compose",
|
||||
});
|
||||
}
|
||||
const domains = await findDomainsByComposeId(input.composeId);
|
||||
const composeFile = await addDomainToCompose(compose, domains);
|
||||
return dump(composeFile, {
|
||||
lineWidth: 1000,
|
||||
});
|
||||
}),
|
||||
|
||||
deploy: protectedProcedure
|
||||
.input(apiFindCompose)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
const compose = await findComposeById(input.composeId);
|
||||
deploy: protectedProcedure
|
||||
.input(apiFindCompose)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
const compose = await findComposeById(input.composeId);
|
||||
|
||||
if (compose.project.adminId !== ctx.user.adminId) {
|
||||
throw new TRPCError({
|
||||
code: "UNAUTHORIZED",
|
||||
message: "You are not authorized to deploy this compose",
|
||||
});
|
||||
}
|
||||
const jobData: DeploymentJob = {
|
||||
composeId: input.composeId,
|
||||
titleLog: "Manual deployment",
|
||||
type: "deploy",
|
||||
applicationType: "compose",
|
||||
descriptionLog: "",
|
||||
server: !!compose.serverId,
|
||||
};
|
||||
if (compose.project.adminId !== ctx.user.adminId) {
|
||||
throw new TRPCError({
|
||||
code: "UNAUTHORIZED",
|
||||
message: "You are not authorized to deploy this compose",
|
||||
});
|
||||
}
|
||||
const jobData: DeploymentJob = {
|
||||
composeId: input.composeId,
|
||||
titleLog: "Manual deployment",
|
||||
type: "deploy",
|
||||
applicationType: "compose",
|
||||
descriptionLog: "",
|
||||
server: !!compose.serverId,
|
||||
};
|
||||
|
||||
if (IS_CLOUD && compose.serverId) {
|
||||
jobData.serverId = compose.serverId;
|
||||
await deploy(jobData);
|
||||
return true;
|
||||
}
|
||||
await myQueue.add(
|
||||
"deployments",
|
||||
{ ...jobData },
|
||||
{
|
||||
removeOnComplete: true,
|
||||
removeOnFail: true,
|
||||
},
|
||||
);
|
||||
}),
|
||||
redeploy: protectedProcedure
|
||||
.input(apiFindCompose)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
const compose = await findComposeById(input.composeId);
|
||||
if (compose.project.adminId !== ctx.user.adminId) {
|
||||
throw new TRPCError({
|
||||
code: "UNAUTHORIZED",
|
||||
message: "You are not authorized to redeploy this compose",
|
||||
});
|
||||
}
|
||||
const jobData: DeploymentJob = {
|
||||
composeId: input.composeId,
|
||||
titleLog: "Rebuild deployment",
|
||||
type: "redeploy",
|
||||
applicationType: "compose",
|
||||
descriptionLog: "",
|
||||
server: !!compose.serverId,
|
||||
};
|
||||
if (IS_CLOUD && compose.serverId) {
|
||||
jobData.serverId = compose.serverId;
|
||||
await deploy(jobData);
|
||||
return true;
|
||||
}
|
||||
await myQueue.add(
|
||||
"deployments",
|
||||
{ ...jobData },
|
||||
{
|
||||
removeOnComplete: true,
|
||||
removeOnFail: true,
|
||||
},
|
||||
);
|
||||
}),
|
||||
stop: protectedProcedure
|
||||
.input(apiFindCompose)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
const compose = await findComposeById(input.composeId);
|
||||
if (compose.project.adminId !== ctx.user.adminId) {
|
||||
throw new TRPCError({
|
||||
code: "UNAUTHORIZED",
|
||||
message: "You are not authorized to stop this compose",
|
||||
});
|
||||
}
|
||||
await stopCompose(input.composeId);
|
||||
if (IS_CLOUD && compose.serverId) {
|
||||
jobData.serverId = compose.serverId;
|
||||
await deploy(jobData);
|
||||
return true;
|
||||
}
|
||||
await myQueue.add(
|
||||
"deployments",
|
||||
{ ...jobData },
|
||||
{
|
||||
removeOnComplete: true,
|
||||
removeOnFail: true,
|
||||
}
|
||||
);
|
||||
}),
|
||||
redeploy: protectedProcedure
|
||||
.input(apiFindCompose)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
const compose = await findComposeById(input.composeId);
|
||||
if (compose.project.adminId !== ctx.user.adminId) {
|
||||
throw new TRPCError({
|
||||
code: "UNAUTHORIZED",
|
||||
message: "You are not authorized to redeploy this compose",
|
||||
});
|
||||
}
|
||||
const jobData: DeploymentJob = {
|
||||
composeId: input.composeId,
|
||||
titleLog: "Rebuild deployment",
|
||||
type: "redeploy",
|
||||
applicationType: "compose",
|
||||
descriptionLog: "",
|
||||
server: !!compose.serverId,
|
||||
};
|
||||
if (IS_CLOUD && compose.serverId) {
|
||||
jobData.serverId = compose.serverId;
|
||||
await deploy(jobData);
|
||||
return true;
|
||||
}
|
||||
await myQueue.add(
|
||||
"deployments",
|
||||
{ ...jobData },
|
||||
{
|
||||
removeOnComplete: true,
|
||||
removeOnFail: true,
|
||||
}
|
||||
);
|
||||
}),
|
||||
stop: protectedProcedure
|
||||
.input(apiFindCompose)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
const compose = await findComposeById(input.composeId);
|
||||
if (compose.project.adminId !== ctx.user.adminId) {
|
||||
throw new TRPCError({
|
||||
code: "UNAUTHORIZED",
|
||||
message: "You are not authorized to stop this compose",
|
||||
});
|
||||
}
|
||||
await stopCompose(input.composeId);
|
||||
|
||||
return true;
|
||||
}),
|
||||
start: protectedProcedure
|
||||
.input(apiFindCompose)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
const compose = await findComposeById(input.composeId);
|
||||
if (compose.project.adminId !== ctx.user.adminId) {
|
||||
throw new TRPCError({
|
||||
code: "UNAUTHORIZED",
|
||||
message: "You are not authorized to stop this compose",
|
||||
});
|
||||
}
|
||||
await startCompose(input.composeId);
|
||||
return true;
|
||||
}),
|
||||
start: protectedProcedure
|
||||
.input(apiFindCompose)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
const compose = await findComposeById(input.composeId);
|
||||
if (compose.project.adminId !== ctx.user.adminId) {
|
||||
throw new TRPCError({
|
||||
code: "UNAUTHORIZED",
|
||||
message: "You are not authorized to stop this compose",
|
||||
});
|
||||
}
|
||||
await startCompose(input.composeId);
|
||||
|
||||
return true;
|
||||
}),
|
||||
getDefaultCommand: protectedProcedure
|
||||
.input(apiFindCompose)
|
||||
.query(async ({ input, ctx }) => {
|
||||
const compose = await findComposeById(input.composeId);
|
||||
return true;
|
||||
}),
|
||||
getDefaultCommand: protectedProcedure
|
||||
.input(apiFindCompose)
|
||||
.query(async ({ input, ctx }) => {
|
||||
const compose = await findComposeById(input.composeId);
|
||||
|
||||
if (compose.project.adminId !== ctx.user.adminId) {
|
||||
throw new TRPCError({
|
||||
code: "UNAUTHORIZED",
|
||||
message: "You are not authorized to get this compose",
|
||||
});
|
||||
}
|
||||
const command = createCommand(compose);
|
||||
return `docker ${command}`;
|
||||
}),
|
||||
refreshToken: protectedProcedure
|
||||
.input(apiFindCompose)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
const compose = await findComposeById(input.composeId);
|
||||
if (compose.project.adminId !== ctx.user.adminId) {
|
||||
throw new TRPCError({
|
||||
code: "UNAUTHORIZED",
|
||||
message: "You are not authorized to refresh this compose",
|
||||
});
|
||||
}
|
||||
await updateCompose(input.composeId, {
|
||||
refreshToken: nanoid(),
|
||||
});
|
||||
return true;
|
||||
}),
|
||||
deployTemplate: protectedProcedure
|
||||
.input(apiCreateComposeByTemplate)
|
||||
.mutation(async ({ ctx, input }) => {
|
||||
if (ctx.user.rol === "user") {
|
||||
await checkServiceAccess(ctx.user.authId, input.projectId, "create");
|
||||
}
|
||||
if (compose.project.adminId !== ctx.user.adminId) {
|
||||
throw new TRPCError({
|
||||
code: "UNAUTHORIZED",
|
||||
message: "You are not authorized to get this compose",
|
||||
});
|
||||
}
|
||||
const command = createCommand(compose);
|
||||
return `docker ${command}`;
|
||||
}),
|
||||
refreshToken: protectedProcedure
|
||||
.input(apiFindCompose)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
const compose = await findComposeById(input.composeId);
|
||||
if (compose.project.adminId !== ctx.user.adminId) {
|
||||
throw new TRPCError({
|
||||
code: "UNAUTHORIZED",
|
||||
message: "You are not authorized to refresh this compose",
|
||||
});
|
||||
}
|
||||
await updateCompose(input.composeId, {
|
||||
refreshToken: nanoid(),
|
||||
});
|
||||
return true;
|
||||
}),
|
||||
deployTemplate: protectedProcedure
|
||||
.input(apiCreateComposeByTemplate)
|
||||
.mutation(async ({ ctx, input }) => {
|
||||
if (ctx.user.rol === "user") {
|
||||
await checkServiceAccess(ctx.user.authId, input.projectId, "create");
|
||||
}
|
||||
|
||||
if (IS_CLOUD && !input.serverId) {
|
||||
throw new TRPCError({
|
||||
code: "UNAUTHORIZED",
|
||||
message: "You need to use a server to create a compose",
|
||||
});
|
||||
}
|
||||
if (IS_CLOUD && !input.serverId) {
|
||||
throw new TRPCError({
|
||||
code: "UNAUTHORIZED",
|
||||
message: "You need to use a server to create a compose",
|
||||
});
|
||||
}
|
||||
|
||||
const composeFile = await readTemplateComposeFile(input.id);
|
||||
const composeFile = await readTemplateComposeFile(input.id);
|
||||
|
||||
const generate = await loadTemplateModule(input.id as TemplatesKeys);
|
||||
const generate = await loadTemplateModule(input.id as TemplatesKeys);
|
||||
|
||||
const admin = await findAdminById(ctx.user.adminId);
|
||||
let serverIp = admin.serverIp || "127.0.0.1";
|
||||
const admin = await findAdminById(ctx.user.adminId);
|
||||
let serverIp = admin.serverIp || "127.0.0.1";
|
||||
|
||||
const project = await findProjectById(input.projectId);
|
||||
const project = await findProjectById(input.projectId);
|
||||
|
||||
if (input.serverId) {
|
||||
const server = await findServerById(input.serverId);
|
||||
serverIp = server.ipAddress;
|
||||
} else if (process.env.NODE_ENV === "development") {
|
||||
serverIp = "127.0.0.1";
|
||||
}
|
||||
const projectName = slugify(`${project.name} ${input.id}`);
|
||||
const { envs, mounts, domains } = generate({
|
||||
serverIp: serverIp || "",
|
||||
projectName: projectName,
|
||||
});
|
||||
if (input.serverId) {
|
||||
const server = await findServerById(input.serverId);
|
||||
serverIp = server.ipAddress;
|
||||
} else if (process.env.NODE_ENV === "development") {
|
||||
serverIp = "127.0.0.1";
|
||||
}
|
||||
const projectName = slugify(`${project.name} ${input.id}`);
|
||||
const { envs, mounts, domains } = generate({
|
||||
serverIp: serverIp || "",
|
||||
projectName: projectName,
|
||||
});
|
||||
|
||||
const compose = await createComposeByTemplate({
|
||||
...input,
|
||||
composeFile: composeFile,
|
||||
env: envs?.join("\n"),
|
||||
serverId: input.serverId,
|
||||
name: input.id,
|
||||
sourceType: "raw",
|
||||
appName: `${projectName}-${generatePassword(6)}`,
|
||||
});
|
||||
const compose = await createComposeByTemplate({
|
||||
...input,
|
||||
composeFile: composeFile,
|
||||
env: envs?.join("\n"),
|
||||
serverId: input.serverId,
|
||||
name: input.id,
|
||||
sourceType: "raw",
|
||||
appName: `${projectName}-${generatePassword(6)}`,
|
||||
});
|
||||
|
||||
if (ctx.user.rol === "user") {
|
||||
await addNewService(ctx.user.authId, compose.composeId);
|
||||
}
|
||||
if (ctx.user.rol === "user") {
|
||||
await addNewService(ctx.user.authId, compose.composeId);
|
||||
}
|
||||
|
||||
if (mounts && mounts?.length > 0) {
|
||||
for (const mount of mounts) {
|
||||
await createMount({
|
||||
filePath: mount.filePath,
|
||||
mountPath: "",
|
||||
content: mount.content,
|
||||
serviceId: compose.composeId,
|
||||
serviceType: "compose",
|
||||
type: "file",
|
||||
});
|
||||
}
|
||||
}
|
||||
if (mounts && mounts?.length > 0) {
|
||||
for (const mount of mounts) {
|
||||
await createMount({
|
||||
filePath: mount.filePath,
|
||||
mountPath: "",
|
||||
content: mount.content,
|
||||
serviceId: compose.composeId,
|
||||
serviceType: "compose",
|
||||
type: "file",
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (domains && domains?.length > 0) {
|
||||
for (const domain of domains) {
|
||||
await createDomain({
|
||||
...domain,
|
||||
domainType: "compose",
|
||||
certificateType: "none",
|
||||
composeId: compose.composeId,
|
||||
});
|
||||
}
|
||||
}
|
||||
if (domains && domains?.length > 0) {
|
||||
for (const domain of domains) {
|
||||
await createDomain({
|
||||
...domain,
|
||||
domainType: "compose",
|
||||
certificateType: "none",
|
||||
composeId: compose.composeId,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}),
|
||||
return null;
|
||||
}),
|
||||
|
||||
templates: protectedProcedure.query(async () => {
|
||||
const templatesData = templates.map((t) => ({
|
||||
name: t.name,
|
||||
description: t.description,
|
||||
id: t.id,
|
||||
links: t.links,
|
||||
tags: t.tags,
|
||||
logo: t.logo,
|
||||
version: t.version,
|
||||
}));
|
||||
templates: protectedProcedure.query(async () => {
|
||||
const templatesData = templates.map((t) => ({
|
||||
name: t.name,
|
||||
description: t.description,
|
||||
id: t.id,
|
||||
links: t.links,
|
||||
tags: t.tags,
|
||||
logo: t.logo,
|
||||
version: t.version,
|
||||
}));
|
||||
|
||||
return templatesData;
|
||||
}),
|
||||
return templatesData;
|
||||
}),
|
||||
|
||||
getTags: protectedProcedure.query(async ({ input }) => {
|
||||
const allTags = templates.flatMap((template) => template.tags);
|
||||
const uniqueTags = _.uniq(allTags);
|
||||
return uniqueTags;
|
||||
}),
|
||||
getTags: protectedProcedure.query(async ({ input }) => {
|
||||
const allTags = templates.flatMap((template) => template.tags);
|
||||
const uniqueTags = _.uniq(allTags);
|
||||
return uniqueTags;
|
||||
}),
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user