diff --git a/apps/dokploy/components/dashboard/project/add-application.tsx b/apps/dokploy/components/dashboard/project/add-application.tsx index 16c56917d6..740b9beeb6 100644 --- a/apps/dokploy/components/dashboard/project/add-application.tsx +++ b/apps/dokploy/components/dashboard/project/add-application.tsx @@ -9,6 +9,7 @@ import { DialogTitle, DialogTrigger, } from "@/components/ui/dialog"; + import { DropdownMenuItem } from "@/components/ui/dropdown-menu"; import { Form, @@ -60,7 +61,6 @@ const AddTemplateSchema = z.object({ description: z.string().optional(), serverId: z.string().optional(), }); - type AddTemplate = z.infer; interface Props { @@ -72,6 +72,7 @@ export const AddApplication = ({ projectId, projectName }: Props) => { const utils = api.useUtils(); const { data: isCloud } = api.settings.isCloud.useQuery(); const [visible, setVisible] = useState(false); + const [appNameLocked, setAppNameLocked] = useState(true); const slug = slugify(projectName); const { data: servers } = api.server.withSSHKey.useQuery(); @@ -87,6 +88,22 @@ export const AddApplication = ({ projectId, projectName }: Props) => { resolver: zodResolver(AddTemplateSchema), }); + const generateAppName = (value: string): string => { + if (!value) return `${slug}-`; + + const processedValue = value + .trim() + .toLowerCase() + // replace dots, spaces, and special characters with dashes + .replace(/[^\w]+/g, "-") + // remove any consecutive dashes + .replace(/-+/g, "-") + // remove leading/trailing dashes + .replace(/^-+|-+$/g, ""); + + return `${slug}-${processedValue}`; + }; + const onSubmit = async (data: AddTemplate) => { await mutateAsync({ name: data.name, @@ -143,13 +160,16 @@ export const AddApplication = ({ projectId, projectName }: Props) => { { - const val = e.target.value?.trim() || ""; - form.setValue( - "appName", - `${slug}-${val.toLowerCase().replaceAll(" ", "-")}`, - ); + const val = e.target.value; field.onChange(val); + + if (appNameLocked && val) { + form.setValue("appName", generateAppName(val)); + } }} /> @@ -157,6 +177,56 @@ export const AddApplication = ({ projectId, projectName }: Props) => { )} /> + ( + + App Name + + + +
+ + { + if (appNameLocked) { + setAppNameLocked(false); + } + }} + /> + + {appNameLocked && ( +
+ +
+ )} +
+
+ + {appNameLocked + ? "App name is auto-generated. Click to edit manually." + : "App name can be edited manually."} + +
+
+ +
+ )} + /> { className="z-[999] w-[300px]" align="start" side="top" + avoidCollisions > If no server is selected, the application will be @@ -199,13 +270,9 @@ export const AddApplication = ({ projectId, projectName }: Props) => { > {server.name} - - {server.ipAddress} - ))} - Servers ({servers?.length}) @@ -213,19 +280,6 @@ export const AddApplication = ({ projectId, projectName }: Props) => { )} /> - ( - - App Name - - - - - - )} - /> { {...field} /> - )} /> - - - - + + + ); diff --git a/apps/dokploy/components/dashboard/project/add-compose.tsx b/apps/dokploy/components/dashboard/project/add-compose.tsx index ea8690a833..65118349c4 100644 --- a/apps/dokploy/components/dashboard/project/add-compose.tsx +++ b/apps/dokploy/components/dashboard/project/add-compose.tsx @@ -72,6 +72,7 @@ interface Props { export const AddCompose = ({ projectId, projectName }: Props) => { const utils = api.useUtils(); const [visible, setVisible] = useState(false); + const [appNameLocked, setAppNameLocked] = useState(true); const slug = slugify(projectName); const { data: isCloud } = api.settings.isCloud.useQuery(); const { data: servers } = api.server.withSSHKey.useQuery(); @@ -88,6 +89,22 @@ export const AddCompose = ({ projectId, projectName }: Props) => { resolver: zodResolver(AddComposeSchema), }); + const generateAppName = (value: string): string => { + if (!value) return `${slug}-`; + + const processedValue = value + .trim() + .toLowerCase() + // replace dots, spaces, and special characters with dashes + .replace(/[^\w]+/g, "-") + // remove any consecutive dashes + .replace(/-+/g, "-") + // remove leading/trailing dashes + .replace(/^-+|-+$/g, ""); + + return `${slug}-${processedValue}`; + }; + useEffect(() => { form.reset(); }, [form, form.reset, form.formState.isSubmitSuccessful]); @@ -150,13 +167,17 @@ export const AddCompose = ({ projectId, projectName }: Props) => { { - const val = e.target.value?.trim() || ""; - form.setValue( - "appName", - `${slug}-${val.toLowerCase()}`, - ); + const val = e.target.value; field.onChange(val); + + // Auto-generate App Name if locked + if (appNameLocked && val) { + form.setValue("appName", generateAppName(val)); + } }} /> @@ -165,6 +186,56 @@ export const AddCompose = ({ projectId, projectName }: Props) => { )} /> + ( + + App Name + + + +
+ + { + if (appNameLocked) { + setAppNameLocked(false); + } + }} + /> + + {appNameLocked && ( +
+ +
+ )} +
+
+ + {appNameLocked + ? "App name is auto-generated. Click to edit manually." + : "App name can be edited manually."} + +
+
+ +
+ )} + /> { className="z-[999] w-[300px]" align="start" side="top" + avoidCollisions > If no server is selected, the application will be @@ -221,19 +293,6 @@ export const AddCompose = ({ projectId, projectName }: Props) => { )} /> - ( - - App Name - - - - - - )} - /> { const utils = api.useUtils(); const [visible, setVisible] = useState(false); const slug = slugify(projectName); + const [appNameLocked, setAppNameLocked] = useState(true); const { data: servers } = api.server.withSSHKey.useQuery(); const postgresMutation = api.postgres.create.useMutation(); const mongoMutation = api.mongo.create.useMutation(); @@ -186,6 +193,22 @@ export const AddDatabase = ({ projectId, projectName }: Props) => { mysql: mysqlMutation, }; + const generateAppName = (value: string): string => { + if (!value) return `${slug}-`; + + const processedValue = value + .trim() + .toLowerCase() + // replace dots, spaces, and special characters with dashes + .replace(/[^\w]+/g, "-") + // remove any consecutive dashes + .replace(/-+/g, "-") + // remove leading/trailing dashes + .replace(/^-+|-+$/g, ""); + + return `${slug}-${processedValue}`; + }; + const onSubmit = async (data: AddDatabase) => { const defaultDockerImage = data.dockerImage || dockerImageDefaultPlaceholder[data.type]; @@ -361,13 +384,16 @@ export const AddDatabase = ({ projectId, projectName }: Props) => { { const val = e.target.value?.trim() || ""; - form.setValue( - "appName", - `${slug}-${val.toLowerCase()}`, - ); field.onChange(val); + + if (appNameLocked && val) { + form.setValue("appName", generateAppName(val)); + } }} /> @@ -376,6 +402,56 @@ export const AddDatabase = ({ projectId, projectName }: Props) => { )} /> + ( + + App Name + + + +
+ + { + if (appNameLocked) { + setAppNameLocked(false); + } + }} + /> + + {appNameLocked && ( +
+ +
+ )} +
+
+ + {appNameLocked + ? "App name is auto-generated. Click to edit manually." + : "App name can be edited manually."} + +
+
+ +
+ )} + /> { )} /> - ( - - App Name - - - - - - )} - /> - { Database Name - + @@ -514,6 +580,7 @@ export const AddDatabase = ({ projectId, projectName }: Props) => {