refactor: use stepper

This commit is contained in:
Mauricio Siu
2025-01-18 23:49:47 -06:00
parent e68465f9e6
commit e7329a727f
5 changed files with 454 additions and 358 deletions

View File

@@ -24,23 +24,12 @@ const examples = [
];
export const StepOne = ({ nextStep, setTemplateInfo, templateInfo }: any) => {
const [userInput, setUserInput] = useState(templateInfo.userInput);
// Get servers from the API
const { data: servers } = api.server.withSSHKey.useQuery();
const handleNext = () => {
setTemplateInfo({
...templateInfo,
userInput,
});
nextStep();
};
const handleExampleClick = (example: string) => {
setUserInput(example);
setTemplateInfo({ ...templateInfo, userInput: example });
};
return (
<div className="flex flex-col h-full gap-4">
<div className="">
@@ -51,8 +40,10 @@ export const StepOne = ({ nextStep, setTemplateInfo, templateInfo }: any) => {
<Textarea
id="user-needs"
placeholder="Describe the type of template you need, its purpose, and any specific features you'd like to include."
value={userInput}
onChange={(e) => setUserInput(e.target.value)}
value={templateInfo?.userInput}
onChange={(e) =>
setTemplateInfo({ ...templateInfo, userInput: e.target.value })
}
className="min-h-[100px]"
/>
</div>
@@ -106,13 +97,6 @@ export const StepOne = ({ nextStep, setTemplateInfo, templateInfo }: any) => {
</div>
</div>
</div>
<div className="">
<div className="flex justify-end">
<Button onClick={handleNext} disabled={!userInput.trim()}>
Next
</Button>
</div>
</div>
</div>
);
};

View File

@@ -1,13 +1,8 @@
import { CodeEditor } from "@/components/shared/code-editor";
import { Button } from "@/components/ui/button";
import ReactMarkdown from "react-markdown";
import type { StepProps } from "./step-two";
export const StepThree = ({
prevStep,
templateInfo,
onSubmit,
}: StepProps & { onSubmit: () => void }) => {
export const StepThree = ({ templateInfo }: StepProps) => {
return (
<div className="flex flex-col h-full">
<div className="flex-grow">
@@ -93,14 +88,6 @@ export const StepThree = ({
</div>
</div>
</div>
<div className="pt-6">
<div className="flex justify-between">
<Button onClick={prevStep} variant="outline">
Back
</Button>
<Button onClick={onSubmit}>Create</Button>
</div>
</div>
</div>
);
};

View File

@@ -1,3 +1,4 @@
import { AlertBlock } from "@/components/shared/alert-block";
import { CodeEditor } from "@/components/shared/code-editor";
import {
Accordion,
@@ -16,54 +17,45 @@ import { useEffect, useState } from "react";
import ReactMarkdown from "react-markdown";
import { toast } from "sonner";
import type { TemplateInfo } from "./template-generator";
import { AlertBlock } from "@/components/shared/alert-block";
export interface StepProps {
nextStep: () => void;
prevStep: () => void;
stepper?: any;
templateInfo: TemplateInfo;
setTemplateInfo: React.Dispatch<React.SetStateAction<TemplateInfo>>;
}
export const StepTwo = ({
nextStep,
prevStep,
stepper,
templateInfo,
setTemplateInfo,
}: StepProps) => {
const [suggestions, setSuggestions] = useState<TemplateInfo["details"][]>();
const [selectedVariant, setSelectedVariant] =
useState<TemplateInfo["details"]>();
const suggestions = templateInfo.suggestions || [];
const selectedVariant = templateInfo.details;
const [showValues, setShowValues] = useState<Record<string, boolean>>({});
const { mutateAsync, isLoading, error, isError } =
api.ai.suggest.useMutation();
useEffect(() => {
if (suggestions?.length > 0) {
return;
}
mutateAsync({
aiId: templateInfo.aiId,
serverId: templateInfo.server?.serverId || "",
input: templateInfo.userInput,
})
.then((data) => {
console.log(data);
setSuggestions(data);
setTemplateInfo({
...templateInfo,
suggestions: data,
});
})
.catch((error) => {
toast.error("Error generating suggestions");
});
}, [templateInfo.userInput]);
const handleNext = () => {
if (selectedVariant) {
setTemplateInfo({
...templateInfo,
details: selectedVariant,
});
}
nextStep();
};
const toggleShowValue = (name: string) => {
setShowValues((prev) => ({ ...prev, [name]: !prev[name] }));
};
@@ -82,9 +74,14 @@ export const StepTwo = ({
[field]: value,
};
setSelectedVariant({
...selectedVariant,
envVariables: updatedEnvVariables,
setTemplateInfo({
...templateInfo,
...(templateInfo.details && {
details: {
...templateInfo.details,
envVariables: updatedEnvVariables,
},
}),
});
};
@@ -95,9 +92,14 @@ export const StepTwo = ({
(_, i) => i !== index,
);
setSelectedVariant({
...selectedVariant,
envVariables: updatedEnvVariables,
setTemplateInfo({
...templateInfo,
...(templateInfo.details && {
details: {
...templateInfo.details,
envVariables: updatedEnvVariables,
},
}),
});
};
@@ -114,9 +116,14 @@ export const StepTwo = ({
[field]: value,
};
setSelectedVariant({
...selectedVariant,
domains: updatedDomains,
setTemplateInfo({
...templateInfo,
...(templateInfo.details && {
details: {
...templateInfo.details,
domains: updatedDomains,
},
}),
});
};
@@ -127,30 +134,49 @@ export const StepTwo = ({
(_, i) => i !== index,
);
setSelectedVariant({
...selectedVariant,
domains: updatedDomains,
setTemplateInfo({
...templateInfo,
...(templateInfo.details && {
details: {
...templateInfo.details,
domains: updatedDomains,
},
}),
});
};
const addEnvVariable = () => {
if (!selectedVariant) return;
setSelectedVariant({
...selectedVariant,
envVariables: [...selectedVariant.envVariables, { name: "", value: "" }],
setTemplateInfo({
...templateInfo,
...(templateInfo.details && {
details: {
...templateInfo.details,
envVariables: [
...selectedVariant.envVariables,
{ name: "", value: "" },
],
},
}),
});
};
const addDomain = () => {
if (!selectedVariant) return;
setSelectedVariant({
...selectedVariant,
domains: [
...selectedVariant.domains,
{ host: "", port: 80, serviceName: "" },
],
setTemplateInfo({
...templateInfo,
...(templateInfo.details && {
details: {
...templateInfo.details,
domains: [
...selectedVariant.domains,
{ host: "", port: 80, serviceName: "" },
],
},
}),
});
};
if (isError) {
@@ -161,15 +187,6 @@ export const StepTwo = ({
<AlertBlock type="error">
{error?.message || "Error generating suggestions"}
</AlertBlock>
<Button
onClick={() =>
selectedVariant ? setSelectedVariant(undefined) : prevStep()
}
variant="outline"
>
{selectedVariant ? "Change Variant" : "Back"}
</Button>
</div>
);
}
@@ -197,10 +214,13 @@ export const StepTwo = ({
<div className="space-y-4">
<div>Based on your input, we suggest the following variants:</div>
<RadioGroup
value={selectedVariant}
// value={selectedVariant?.}
onValueChange={(value) => {
const element = suggestions?.find((s) => s?.id === value);
setSelectedVariant(element);
setTemplateInfo({
...templateInfo,
details: element,
});
}}
className="space-y-4"
>
@@ -254,9 +274,14 @@ export const StepTwo = ({
value={selectedVariant?.dockerCompose}
className="font-mono"
onChange={(value) => {
setSelectedVariant({
...selectedVariant,
dockerCompose: value,
setTemplateInfo({
...templateInfo,
...(templateInfo?.details && {
details: {
...templateInfo.details,
dockerCompose: value,
},
}),
});
}}
/>
@@ -416,17 +441,17 @@ export const StepTwo = ({
</div>
<div className="">
<div className="flex justify-between">
<Button
onClick={() =>
selectedVariant ? setSelectedVariant(undefined) : prevStep()
}
variant="outline"
>
{selectedVariant ? "Change Variant" : "Back"}
</Button>
<Button onClick={handleNext} disabled={!selectedVariant}>
Next
</Button>
{selectedVariant && (
<Button
onClick={() => {
const { details, ...rest } = templateInfo;
setTemplateInfo(rest);
}}
variant="outline"
>
Change Variant
</Button>
)}
</div>
</div>
</div>

View File

@@ -3,6 +3,7 @@ import {
Dialog,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
DialogTrigger,
@@ -18,11 +19,14 @@ import {
import { api } from "@/utils/api";
import { Bot } from "lucide-react";
import Link from "next/link";
import { useState } from "react";
import React, { useState } from "react";
import { toast } from "sonner";
import { StepOne } from "./step-one";
import { StepThree } from "./step-three";
import { StepTwo } from "./step-two";
import { defineStepper } from "@stepperize/react";
import { Separator } from "@/components/ui/separator";
import { Button } from "@/components/ui/button";
interface EnvVariable {
name: string;
@@ -34,17 +38,20 @@ interface Domain {
port: number;
serviceName: string;
}
interface Details {
name: string;
id: string;
description: string;
dockerCompose: string;
envVariables: EnvVariable[];
shortDescription: string;
domains: Domain[];
}
export interface TemplateInfo {
userInput: string;
details?: {
name: string;
id: string;
description: string;
dockerCompose: string;
envVariables: EnvVariable[];
shortDescription: string;
domains: Domain[];
};
details?: Details | null;
suggestions?: Details[];
server?: {
serverId: string;
name: string;
@@ -56,17 +63,25 @@ const defaultTemplateInfo: TemplateInfo = {
aiId: "",
userInput: "",
server: undefined,
details: {
id: "",
name: "",
description: "",
dockerCompose: "",
envVariables: [],
shortDescription: "",
domains: [],
},
details: null,
suggestions: [],
};
export const { useStepper, steps, Scoped } = defineStepper(
{
id: "needs",
title: "Describe your needs",
},
{
id: "variant",
title: "Choose a Variant",
},
{
id: "review",
title: "Review and Finalize",
},
);
interface Props {
projectId: string;
projectName?: string;
@@ -74,32 +89,58 @@ interface Props {
export const TemplateGenerator = ({ projectId }: Props) => {
const [open, setOpen] = useState(false);
const [step, setStep] = useState(1);
const stepper = useStepper();
const { data: aiSettings } = api.ai.getAll.useQuery();
const { mutateAsync } = api.ai.deploy.useMutation();
const [templateInfo, setTemplateInfo] =
useState<TemplateInfo>(defaultTemplateInfo);
const utils = api.useUtils();
const totalSteps = 3;
const nextStep = () => setStep((prev) => Math.min(prev + 1, totalSteps));
const prevStep = () => setStep((prev) => Math.max(prev - 1, 1));
const handleOpenChange = (newOpen: boolean) => {
setOpen(newOpen);
if (!newOpen) {
setStep(1);
setTemplateInfo(defaultTemplateInfo);
}
};
const haveAtleasOneProviderEnabled = aiSettings?.some(
(ai) => ai.isEnabled === true,
);
const isDisabled = () => {
if (stepper.current.id === "needs") {
return !templateInfo.aiId || !templateInfo.userInput;
}
if (stepper.current.id === "variant") {
return !templateInfo?.details?.id;
}
return false;
};
const onSubmit = async () => {
await mutateAsync({
projectId,
id: templateInfo.details?.id || "",
name: templateInfo?.details?.name || "",
description: templateInfo?.details?.shortDescription || "",
dockerCompose: templateInfo?.details?.dockerCompose || "",
envVariables: (templateInfo?.details?.envVariables || [])
.map((env: any) => `${env.name}=${env.value}`)
.join("\n"),
domains: templateInfo?.details?.domains || [],
...(templateInfo.server?.serverId && {
serverId: templateInfo.server?.serverId || "",
}),
})
.then(async () => {
toast.success("Compose Created");
setOpen(false);
await utils.project.one.invalidate({
projectId,
});
})
.catch(() => {
toast.error("Error creating the compose");
});
};
return (
<Dialog open={open} onOpenChange={handleOpenChange}>
<Dialog open={open} onOpenChange={setOpen}>
<DialogTrigger asChild className="w-full">
<DropdownMenuItem
className="w-full cursor-pointer space-x-3"
@@ -116,114 +157,173 @@ export const TemplateGenerator = ({ projectId }: Props) => {
Create a custom template based on your needs
</DialogDescription>
</DialogHeader>
<div className="mt-4 ">
{step === 1 && (
<>
{!haveAtleasOneProviderEnabled && (
<AlertBlock type="warning">
<div className="flex flex-col w-full">
<span>AI features are not enabled</span>
<span>
To use AI-powered template generation, please{" "}
<Link
href="/dashboard/settings/ai"
className="font-medium underline underline-offset-4"
>
enable AI in your settings
</Link>
.
</span>
</div>
</AlertBlock>
)}
{haveAtleasOneProviderEnabled &&
aiSettings &&
aiSettings?.length > 0 && (
<div className="space-y-4">
<div className="flex flex-col gap-2">
<label
htmlFor="user-needs"
className="text-sm font-medium"
>
Select AI Provider
</label>
<Select
value={templateInfo.aiId}
onValueChange={(value) =>
setTemplateInfo((prev) => ({
...prev,
aiId: value,
}))
<div className="grid gap-4">
<div className="flex justify-between">
<h2 className="text-lg font-semibold">Steps</h2>
<div className="flex items-center gap-2">
<span className="text-sm text-muted-foreground">
Step {stepper.current.index + 1} of {steps.length}
</span>
<div />
</div>
</div>
<Scoped>
<nav aria-label="Checkout Steps" className="group my-4">
<ol
className="flex items-center justify-between gap-2"
aria-orientation="horizontal"
>
{stepper.all.map((step, index, array) => (
<React.Fragment key={step.id}>
<li className="flex items-center gap-4 flex-shrink-0">
<Button
type="button"
role="tab"
variant={
index <= stepper.current.index ? "secondary" : "ghost"
}
aria-current={
stepper.current.id === step.id ? "step" : undefined
}
aria-posinset={index + 1}
aria-setsize={steps.length}
aria-selected={stepper.current.id === step.id}
className="flex size-10 items-center justify-center rounded-full border-2 border-border"
>
<SelectTrigger>
<SelectValue placeholder="Select an AI provider" />
</SelectTrigger>
<SelectContent>
{aiSettings.map((ai) => (
<SelectItem key={ai.aiId} value={ai.aiId}>
{ai.name} ({ai.model})
</SelectItem>
))}
</SelectContent>
</Select>
</div>
{templateInfo.aiId && (
<StepOne
nextStep={nextStep}
setTemplateInfo={setTemplateInfo}
templateInfo={templateInfo}
{index + 1}
</Button>
<span className="text-sm font-medium">{step.title}</span>
</li>
{index < array.length - 1 && (
<Separator
className={`flex-1 ${
index < stepper.current.index
? "bg-primary"
: "bg-muted"
}`}
/>
)}
</div>
)}
</>
)}
{step === 2 && (
<StepTwo
nextStep={nextStep}
prevStep={prevStep}
templateInfo={templateInfo}
setTemplateInfo={setTemplateInfo}
/>
)}
{step === 3 && (
<StepThree
prevStep={prevStep}
templateInfo={templateInfo}
nextStep={nextStep}
setTemplateInfo={setTemplateInfo}
onSubmit={async () => {
console.log("Submitting template:", templateInfo);
await mutateAsync({
projectId,
id: templateInfo.details?.id || "",
name: templateInfo?.details?.name || "",
description: templateInfo?.details?.shortDescription || "",
dockerCompose: templateInfo?.details?.dockerCompose || "",
envVariables: (templateInfo?.details?.envVariables || [])
.map((env: any) => `${env.name}=${env.value}`)
.join("\n"),
domains: templateInfo?.details?.domains || [],
...(templateInfo.server?.serverId && {
serverId: templateInfo.server?.serverId || "",
}),
})
.then(async () => {
toast.success("Compose Created");
setOpen(false);
await utils.project.one.invalidate({
projectId,
});
})
.catch(() => {
toast.error("Error creating the compose");
});
}}
/>
)}
</React.Fragment>
))}
</ol>
</nav>
{stepper.switch({
needs: () => (
<>
{!haveAtleasOneProviderEnabled && (
<AlertBlock type="warning">
<div className="flex flex-col w-full">
<span>AI features are not enabled</span>
<span>
To use AI-powered template generation, please{" "}
<Link
href="/dashboard/settings/ai"
className="font-medium underline underline-offset-4"
>
enable AI in your settings
</Link>
.
</span>
</div>
</AlertBlock>
)}
{haveAtleasOneProviderEnabled &&
aiSettings &&
aiSettings?.length > 0 && (
<div className="space-y-4">
<div className="flex flex-col gap-2">
<label
htmlFor="user-needs"
className="text-sm font-medium"
>
Select AI Provider
</label>
<Select
value={templateInfo.aiId}
onValueChange={(value) =>
setTemplateInfo((prev) => ({
...prev,
aiId: value,
}))
}
>
<SelectTrigger>
<SelectValue placeholder="Select an AI provider" />
</SelectTrigger>
<SelectContent>
{aiSettings.map((ai) => (
<SelectItem key={ai.aiId} value={ai.aiId}>
{ai.name} ({ai.model})
</SelectItem>
))}
</SelectContent>
</Select>
</div>
{templateInfo.aiId && (
<StepOne
setTemplateInfo={setTemplateInfo}
templateInfo={templateInfo}
/>
)}
</div>
)}
</>
),
variant: () => (
<StepTwo
templateInfo={templateInfo}
setTemplateInfo={setTemplateInfo}
/>
),
review: () => (
<StepThree
templateInfo={templateInfo}
setTemplateInfo={setTemplateInfo}
/>
),
})}
</Scoped>
</div>
<DialogFooter>
<div className="flex items-center justify-between w-full">
<div className="flex items-center gap-2 w-full justify-end">
<Button
onClick={stepper.prev}
disabled={stepper.isFirst}
variant="secondary"
>
Back
</Button>
<Button
disabled={isDisabled()}
onClick={async () => {
if (stepper.current.id === "needs") {
setTemplateInfo((prev) => ({
...prev,
suggestions: [],
details: null,
}));
}
if (stepper.isLast) {
await onSubmit();
return;
}
stepper.next();
// if (stepper.isLast) {
// // setIsOpen(false);
// // push("/dashboard/projects");
// } else {
// stepper.next();
// }
}}
>
{stepper.isLast ? "Create" : "Next"}
</Button>
</div>
</div>
</DialogFooter>
</DialogContent>
</Dialog>
);

View File

@@ -1,152 +1,152 @@
import { slugify } from "@/lib/slug";
import {
adminProcedure,
createTRPCRouter,
protectedProcedure,
adminProcedure,
createTRPCRouter,
protectedProcedure,
} from "@/server/api/trpc";
import { generatePassword } from "@/templates/utils";
import { IS_CLOUD } from "@dokploy/server/constants";
import {
apiCreateAi,
apiUpdateAi,
deploySuggestionSchema,
apiCreateAi,
apiUpdateAi,
deploySuggestionSchema,
} from "@dokploy/server/db/schema/ai";
import { createDomain } from "@dokploy/server/index";
import {
deleteAiSettings,
getAiSettingById,
getAiSettingsByAdminId,
saveAiSettings,
suggestVariants,
deleteAiSettings,
getAiSettingById,
getAiSettingsByAdminId,
saveAiSettings,
suggestVariants,
} from "@dokploy/server/services/ai";
import { createComposeByTemplate } from "@dokploy/server/services/compose";
import { findProjectById } from "@dokploy/server/services/project";
import {
addNewService,
checkServiceAccess,
addNewService,
checkServiceAccess,
} from "@dokploy/server/services/user";
import { TRPCError } from "@trpc/server";
import { z } from "zod";
export const aiRouter = createTRPCRouter({
one: protectedProcedure
.input(z.object({ aiId: z.string() }))
.query(async ({ ctx, input }) => {
const aiSetting = await getAiSettingById(input.aiId);
if (aiSetting.adminId !== ctx.user.adminId) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You don't have access to this AI configuration",
});
}
return aiSetting;
}),
create: adminProcedure.input(apiCreateAi).mutation(async ({ ctx, input }) => {
return await saveAiSettings(ctx.user.adminId, input);
}),
one: protectedProcedure
.input(z.object({ aiId: z.string() }))
.query(async ({ ctx, input }) => {
const aiSetting = await getAiSettingById(input.aiId);
if (aiSetting.adminId !== ctx.user.adminId) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You don't have access to this AI configuration",
});
}
return aiSetting;
}),
create: adminProcedure.input(apiCreateAi).mutation(async ({ ctx, input }) => {
return await saveAiSettings(ctx.user.adminId, input);
}),
update: protectedProcedure
.input(apiUpdateAi)
.mutation(async ({ ctx, input }) => {
return await saveAiSettings(ctx.user.adminId, input);
}),
update: protectedProcedure
.input(apiUpdateAi)
.mutation(async ({ ctx, input }) => {
return await saveAiSettings(ctx.user.adminId, input);
}),
getAll: adminProcedure.query(async ({ ctx }) => {
return await getAiSettingsByAdminId(ctx.user.adminId);
}),
getAll: adminProcedure.query(async ({ ctx }) => {
return await getAiSettingsByAdminId(ctx.user.adminId);
}),
get: protectedProcedure
.input(z.object({ aiId: z.string() }))
.query(async ({ ctx, input }) => {
const aiSetting = await getAiSettingById(input.aiId);
if (aiSetting.adminId !== ctx.user.authId) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You don't have access to this AI configuration",
});
}
return aiSetting;
}),
get: protectedProcedure
.input(z.object({ aiId: z.string() }))
.query(async ({ ctx, input }) => {
const aiSetting = await getAiSettingById(input.aiId);
if (aiSetting.adminId !== ctx.user.authId) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You don't have access to this AI configuration",
});
}
return aiSetting;
}),
delete: protectedProcedure
.input(z.object({ aiId: z.string() }))
.mutation(async ({ ctx, input }) => {
const aiSetting = await getAiSettingById(input.aiId);
if (aiSetting.adminId !== ctx.user.adminId) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You don't have access to this AI configuration",
});
}
return await deleteAiSettings(input.aiId);
}),
delete: protectedProcedure
.input(z.object({ aiId: z.string() }))
.mutation(async ({ ctx, input }) => {
const aiSetting = await getAiSettingById(input.aiId);
if (aiSetting.adminId !== ctx.user.adminId) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You don't have access to this AI configuration",
});
}
return await deleteAiSettings(input.aiId);
}),
suggest: protectedProcedure
.input(
z.object({
aiId: z.string(),
input: z.string(),
serverId: z.string().optional(),
})
)
.mutation(async ({ ctx, input }) => {
try {
return await suggestVariants({
...input,
adminId: ctx.user.adminId,
});
} catch (error) {
throw new TRPCError({
code: "BAD_REQUEST",
message: error instanceof Error ? error?.message : `Error: ${error}`,
});
}
}),
deploy: protectedProcedure
.input(deploySuggestionSchema)
.mutation(async ({ ctx, input }) => {
if (ctx.user.rol === "user") {
await checkServiceAccess(ctx.user.adminId, input.projectId, "create");
}
suggest: protectedProcedure
.input(
z.object({
aiId: z.string(),
input: z.string(),
serverId: z.string().optional(),
}),
)
.mutation(async ({ ctx, input }) => {
try {
return await suggestVariants({
...input,
adminId: ctx.user.adminId,
});
} catch (error) {
throw new TRPCError({
code: "BAD_REQUEST",
message: error instanceof Error ? error?.message : `Error: ${error}`,
});
}
}),
deploy: protectedProcedure
.input(deploySuggestionSchema)
.mutation(async ({ ctx, input }) => {
if (ctx.user.rol === "user") {
await checkServiceAccess(ctx.user.adminId, 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 project = await findProjectById(input.projectId);
const project = await findProjectById(input.projectId);
const projectName = slugify(`${project.name} ${input.id}`);
const projectName = slugify(`${project.name} ${input.id}`);
console.log(input);
console.log(input);
const compose = await createComposeByTemplate({
...input,
composeFile: input.dockerCompose,
env: input.envVariables,
serverId: input.serverId,
name: input.name,
sourceType: "raw",
appName: `${projectName}-${generatePassword(6)}`,
});
const compose = await createComposeByTemplate({
...input,
composeFile: input.dockerCompose,
env: input.envVariables,
serverId: input.serverId,
name: input.name,
sourceType: "raw",
appName: `${projectName}-${generatePassword(6)}`,
});
if (input.domains && input.domains?.length > 0) {
for (const domain of input.domains) {
await createDomain({
...domain,
domainType: "compose",
certificateType: "none",
composeId: compose.composeId,
});
}
}
if (input.domains && input.domains?.length > 0) {
for (const domain of input.domains) {
await createDomain({
...domain,
domainType: "compose",
certificateType: "none",
composeId: compose.composeId,
});
}
}
if (ctx.user.rol === "user") {
await addNewService(ctx.user.authId, compose.composeId);
}
if (ctx.user.rol === "user") {
await addNewService(ctx.user.authId, compose.composeId);
}
return null;
}),
return null;
}),
});