Merge pull request #2502 from Harikrishnan1367709/Issue-1852-Harikrishnan

feat: Add default "Dokploy" option to server selection dropdown (#1852)
This commit is contained in:
Mauricio Siu
2025-09-06 13:47:42 -06:00
committed by GitHub
6 changed files with 214 additions and 77 deletions
@@ -76,6 +76,10 @@ export const AddApplication = ({ environmentId, projectName }: Props) => {
const { data: servers } = api.server.withSSHKey.useQuery(); const { data: servers } = api.server.withSSHKey.useQuery();
const hasServers = servers && servers.length > 0; const hasServers = servers && servers.length > 0;
// Show dropdown logic based on cloud environment
// Cloud: show only if there are remote servers (no Dokploy option)
// Self-hosted: show only if there are remote servers (Dokploy is default, hide if no remote servers)
const shouldShowServerDropdown = hasServers;
const { mutateAsync, isLoading, error, isError } = const { mutateAsync, isLoading, error, isError } =
api.application.create.useMutation(); api.application.create.useMutation();
@@ -94,8 +98,8 @@ export const AddApplication = ({ environmentId, projectName }: Props) => {
name: data.name, name: data.name,
appName: data.appName, appName: data.appName,
description: data.description, description: data.description,
serverId: data.serverId === "dokploy" ? undefined : data.serverId,
environmentId, environmentId,
serverId: data.serverId,
}) })
.then(async () => { .then(async () => {
toast.success("Service Created"); toast.success("Service Created");
@@ -157,7 +161,7 @@ export const AddApplication = ({ environmentId, projectName }: Props) => {
</FormItem> </FormItem>
)} )}
/> />
{hasServers && ( {shouldShowServerDropdown && (
<FormField <FormField
control={form.control} control={form.control}
name="serverId" name="serverId"
@@ -186,13 +190,27 @@ export const AddApplication = ({ environmentId, projectName }: Props) => {
<Select <Select
onValueChange={field.onChange} onValueChange={field.onChange}
defaultValue={field.value} defaultValue={
field.value || (!isCloud ? "dokploy" : undefined)
}
> >
<SelectTrigger> <SelectTrigger>
<SelectValue placeholder="Select a Server" /> <SelectValue
placeholder={!isCloud ? "Dokploy" : "Select a Server"}
/>
</SelectTrigger> </SelectTrigger>
<SelectContent> <SelectContent>
<SelectGroup> <SelectGroup>
{!isCloud && (
<SelectItem value="dokploy">
<span className="flex items-center gap-2 justify-between w-full">
<span>Dokploy</span>
<span className="text-muted-foreground text-xs self-center">
Default
</span>
</span>
</SelectItem>
)}
{servers?.map((server) => ( {servers?.map((server) => (
<SelectItem <SelectItem
key={server.serverId} key={server.serverId}
@@ -206,7 +224,9 @@ export const AddApplication = ({ environmentId, projectName }: Props) => {
</span> </span>
</SelectItem> </SelectItem>
))} ))}
<SelectLabel>Servers ({servers?.length})</SelectLabel> <SelectLabel>
Servers ({servers?.length + (!isCloud ? 1 : 0)})
</SelectLabel>
</SelectGroup> </SelectGroup>
</SelectContent> </SelectContent>
</Select> </Select>
@@ -82,6 +82,10 @@ export const AddCompose = ({ environmentId, projectName }: Props) => {
const { data: environment } = api.environment.one.useQuery({ environmentId }); const { data: environment } = api.environment.one.useQuery({ environmentId });
const hasServers = servers && servers.length > 0; const hasServers = servers && servers.length > 0;
// Show dropdown logic based on cloud environment
// Cloud: show only if there are remote servers (no Dokploy option)
// Self-hosted: show only if there are remote servers (Dokploy is default, hide if no remote servers)
const shouldShowServerDropdown = hasServers;
const form = useForm<AddCompose>({ const form = useForm<AddCompose>({
defaultValues: { defaultValues: {
@@ -104,7 +108,7 @@ export const AddCompose = ({ environmentId, projectName }: Props) => {
environmentId, environmentId,
composeType: data.composeType, composeType: data.composeType,
appName: data.appName, appName: data.appName,
serverId: data.serverId, serverId: data.serverId === "dokploy" ? undefined : data.serverId,
}) })
.then(async () => { .then(async () => {
toast.success("Compose Created"); toast.success("Compose Created");
@@ -169,7 +173,7 @@ export const AddCompose = ({ environmentId, projectName }: Props) => {
)} )}
/> />
</div> </div>
{hasServers && ( {shouldShowServerDropdown && (
<FormField <FormField
control={form.control} control={form.control}
name="serverId" name="serverId"
@@ -198,13 +202,27 @@ export const AddCompose = ({ environmentId, projectName }: Props) => {
<Select <Select
onValueChange={field.onChange} onValueChange={field.onChange}
defaultValue={field.value} defaultValue={
field.value || (!isCloud ? "dokploy" : undefined)
}
> >
<SelectTrigger> <SelectTrigger>
<SelectValue placeholder="Select a Server" /> <SelectValue
placeholder={!isCloud ? "Dokploy" : "Select a Server"}
/>
</SelectTrigger> </SelectTrigger>
<SelectContent> <SelectContent>
<SelectGroup> <SelectGroup>
{!isCloud && (
<SelectItem value="dokploy">
<span className="flex items-center gap-2 justify-between w-full">
<span>Dokploy</span>
<span className="text-muted-foreground text-xs self-center">
Default
</span>
</span>
</SelectItem>
)}
{servers?.map((server) => ( {servers?.map((server) => (
<SelectItem <SelectItem
key={server.serverId} key={server.serverId}
@@ -218,7 +236,9 @@ export const AddCompose = ({ environmentId, projectName }: Props) => {
</span> </span>
</SelectItem> </SelectItem>
))} ))}
<SelectLabel>Servers ({servers?.length})</SelectLabel> <SelectLabel>
Servers ({servers?.length + (!isCloud ? 1 : 0)})
</SelectLabel>
</SelectGroup> </SelectGroup>
</SelectContent> </SelectContent>
</Select> </Select>
@@ -179,6 +179,7 @@ export const AddDatabase = ({ environmentId, projectName }: Props) => {
const utils = api.useUtils(); const utils = api.useUtils();
const [visible, setVisible] = useState(false); const [visible, setVisible] = useState(false);
const slug = slugify(projectName); const slug = slugify(projectName);
const { data: isCloud } = api.settings.isCloud.useQuery();
const { data: servers } = api.server.withSSHKey.useQuery(); const { data: servers } = api.server.withSSHKey.useQuery();
const postgresMutation = api.postgres.create.useMutation(); const postgresMutation = api.postgres.create.useMutation();
const mongoMutation = api.mongo.create.useMutation(); const mongoMutation = api.mongo.create.useMutation();
@@ -190,6 +191,10 @@ export const AddDatabase = ({ environmentId, projectName }: Props) => {
const { data: environment } = api.environment.one.useQuery({ environmentId }); const { data: environment } = api.environment.one.useQuery({ environmentId });
const hasServers = servers && servers.length > 0; const hasServers = servers && servers.length > 0;
// Show dropdown logic based on cloud environment
// Cloud: show only if there are remote servers (no Dokploy option)
// Self-hosted: show only if there are remote servers (Dokploy is default, hide if no remote servers)
const shouldShowServerDropdown = hasServers;
const form = useForm<AddDatabase>({ const form = useForm<AddDatabase>({
defaultValues: { defaultValues: {
@@ -223,9 +228,8 @@ export const AddDatabase = ({ environmentId, projectName }: Props) => {
name: data.name, name: data.name,
appName: data.appName, appName: data.appName,
dockerImage: defaultDockerImage, dockerImage: defaultDockerImage,
projectId: environment?.projectId || "", serverId: data.serverId === "dokploy" ? undefined : data.serverId,
environmentId, environmentId,
serverId: data.serverId,
description: data.description, description: data.description,
}; };
@@ -237,7 +241,7 @@ export const AddDatabase = ({ environmentId, projectName }: Props) => {
databaseUser: databaseUser:
data.databaseUser || databasesUserDefaultPlaceholder[data.type], data.databaseUser || databasesUserDefaultPlaceholder[data.type],
serverId: data.serverId, serverId: data.serverId === "dokploy" ? null : data.serverId,
}); });
} else if (data.type === "mongo") { } else if (data.type === "mongo") {
promise = mongoMutation.mutateAsync({ promise = mongoMutation.mutateAsync({
@@ -245,14 +249,14 @@ export const AddDatabase = ({ environmentId, projectName }: Props) => {
databasePassword: data.databasePassword, databasePassword: data.databasePassword,
databaseUser: databaseUser:
data.databaseUser || databasesUserDefaultPlaceholder[data.type], data.databaseUser || databasesUserDefaultPlaceholder[data.type],
serverId: data.serverId, serverId: data.serverId === "dokploy" ? null : data.serverId,
replicaSets: data.replicaSets, replicaSets: data.replicaSets,
}); });
} else if (data.type === "redis") { } else if (data.type === "redis") {
promise = redisMutation.mutateAsync({ promise = redisMutation.mutateAsync({
...commonParams, ...commonParams,
databasePassword: data.databasePassword, databasePassword: data.databasePassword,
serverId: data.serverId, serverId: data.serverId === "dokploy" ? null : data.serverId,
}); });
} else if (data.type === "mariadb") { } else if (data.type === "mariadb") {
promise = mariadbMutation.mutateAsync({ promise = mariadbMutation.mutateAsync({
@@ -262,7 +266,7 @@ export const AddDatabase = ({ environmentId, projectName }: Props) => {
databaseName: data.databaseName || "mariadb", databaseName: data.databaseName || "mariadb",
databaseUser: databaseUser:
data.databaseUser || databasesUserDefaultPlaceholder[data.type], data.databaseUser || databasesUserDefaultPlaceholder[data.type],
serverId: data.serverId, serverId: data.serverId === "dokploy" ? null : data.serverId,
}); });
} else if (data.type === "mysql") { } else if (data.type === "mysql") {
promise = mysqlMutation.mutateAsync({ promise = mysqlMutation.mutateAsync({
@@ -271,8 +275,8 @@ export const AddDatabase = ({ environmentId, projectName }: Props) => {
databaseName: data.databaseName || "mysql", databaseName: data.databaseName || "mysql",
databaseUser: databaseUser:
data.databaseUser || databasesUserDefaultPlaceholder[data.type], data.databaseUser || databasesUserDefaultPlaceholder[data.type],
serverId: data.serverId === "dokploy" ? null : data.serverId,
databaseRootPassword: data.databaseRootPassword || "", databaseRootPassword: data.databaseRootPassword || "",
serverId: data.serverId,
}); });
} }
@@ -403,7 +407,7 @@ export const AddDatabase = ({ environmentId, projectName }: Props) => {
</FormItem> </FormItem>
)} )}
/> />
{hasServers && ( {shouldShowServerDropdown && (
<FormField <FormField
control={form.control} control={form.control}
name="serverId" name="serverId"
@@ -412,13 +416,29 @@ export const AddDatabase = ({ environmentId, projectName }: Props) => {
<FormLabel>Select a Server</FormLabel> <FormLabel>Select a Server</FormLabel>
<Select <Select
onValueChange={field.onChange} onValueChange={field.onChange}
defaultValue={field.value || ""} defaultValue={
field.value || (!isCloud ? "dokploy" : undefined)
}
> >
<SelectTrigger> <SelectTrigger>
<SelectValue placeholder="Select a Server" /> <SelectValue
placeholder={
!isCloud ? "Dokploy" : "Select a Server"
}
/>
</SelectTrigger> </SelectTrigger>
<SelectContent> <SelectContent>
<SelectGroup> <SelectGroup>
{!isCloud && (
<SelectItem value="dokploy">
<span className="flex items-center gap-2 justify-between w-full">
<span>Dokploy</span>
<span className="text-muted-foreground text-xs self-center">
Default
</span>
</span>
</SelectItem>
)}
{servers?.map((server) => ( {servers?.map((server) => (
<SelectItem <SelectItem
key={server.serverId} key={server.serverId}
@@ -428,7 +448,7 @@ export const AddDatabase = ({ environmentId, projectName }: Props) => {
</SelectItem> </SelectItem>
))} ))}
<SelectLabel> <SelectLabel>
Servers ({servers?.length}) Servers ({servers?.length + (!isCloud ? 1 : 0)})
</SelectLabel> </SelectLabel>
</SelectGroup> </SelectGroup>
</SelectContent> </SelectContent>
@@ -141,6 +141,10 @@ export const AddTemplate = ({ environmentId, baseUrl }: Props) => {
}) || []; }) || [];
const hasServers = servers && servers.length > 0; const hasServers = servers && servers.length > 0;
// Show dropdown logic based on cloud environment
// Cloud: show only if there are remote servers (no Dokploy option)
// Self-hosted: show only if there are remote servers (Dokploy is default, hide if no remote servers)
const shouldShowServerDropdown = hasServers;
return ( return (
<Dialog open={open} onOpenChange={setOpen}> <Dialog open={open} onOpenChange={setOpen}>
@@ -430,7 +434,7 @@ export const AddTemplate = ({ environmentId, baseUrl }: Props) => {
project. project.
</AlertDialogDescription> </AlertDialogDescription>
{hasServers && ( {shouldShowServerDropdown && (
<div> <div>
<TooltipProvider delayDuration={0}> <TooltipProvider delayDuration={0}>
<Tooltip> <Tooltip>
@@ -459,12 +463,29 @@ export const AddTemplate = ({ environmentId, baseUrl }: Props) => {
onValueChange={(e) => { onValueChange={(e) => {
setServerId(e); setServerId(e);
}} }}
defaultValue={
!isCloud ? "dokploy" : undefined
}
> >
<SelectTrigger> <SelectTrigger>
<SelectValue placeholder="Select a Server" /> <SelectValue
placeholder={
!isCloud ? "Dokploy" : "Select a Server"
}
/>
</SelectTrigger> </SelectTrigger>
<SelectContent> <SelectContent>
<SelectGroup> <SelectGroup>
{!isCloud && (
<SelectItem value="dokploy">
<span className="flex items-center gap-2 justify-between w-full">
<span>Dokploy</span>
<span className="text-muted-foreground text-xs self-center">
Default
</span>
</span>
</SelectItem>
)}
{servers?.map((server) => ( {servers?.map((server) => (
<SelectItem <SelectItem
key={server.serverId} key={server.serverId}
@@ -479,7 +500,8 @@ export const AddTemplate = ({ environmentId, baseUrl }: Props) => {
</SelectItem> </SelectItem>
))} ))}
<SelectLabel> <SelectLabel>
Servers ({servers?.length}) Servers (
{servers?.length + (!isCloud ? 1 : 0)})
</SelectLabel> </SelectLabel>
</SelectGroup> </SelectGroup>
</SelectContent> </SelectContent>
@@ -493,8 +515,11 @@ export const AddTemplate = ({ environmentId, baseUrl }: Props) => {
disabled={isLoading} disabled={isLoading}
onClick={async () => { onClick={async () => {
const promise = mutateAsync({ const promise = mutateAsync({
serverId:
serverId === "dokploy"
? undefined
: serverId,
environmentId, environmentId,
serverId: serverId || undefined,
id: template.id, id: template.id,
baseUrl: customBaseUrl, baseUrl: customBaseUrl,
}); });
@@ -25,7 +25,12 @@ const examples = [
export const StepOne = ({ setTemplateInfo, templateInfo }: any) => { export const StepOne = ({ setTemplateInfo, templateInfo }: any) => {
// Get servers from the API // Get servers from the API
const { data: servers } = api.server.withSSHKey.useQuery(); const { data: servers } = api.server.withSSHKey.useQuery();
const { data: isCloud } = api.settings.isCloud.useQuery();
const hasServers = servers && servers.length > 0; const hasServers = servers && servers.length > 0;
// Show dropdown logic based on cloud environment
// Cloud: show only if there are remote servers (no Dokploy option)
// Self-hosted: show only if there are remote servers (Dokploy is default, hide if no remote servers)
const shouldShowServerDropdown = hasServers;
const handleExampleClick = (example: string) => { const handleExampleClick = (example: string) => {
setTemplateInfo({ ...templateInfo, userInput: example }); setTemplateInfo({ ...templateInfo, userInput: example });
@@ -48,34 +53,58 @@ export const StepOne = ({ setTemplateInfo, templateInfo }: any) => {
/> />
</div> </div>
{hasServers && ( {shouldShowServerDropdown && (
<div className="space-y-2"> <div className="space-y-2">
<Label htmlFor="server-deploy"> <Label htmlFor="server-deploy">
Select the server where you want to deploy (optional) Select the server where you want to deploy (optional)
</Label> </Label>
<Select <Select
value={templateInfo.server?.serverId} value={
templateInfo.server?.serverId ||
(!isCloud ? "dokploy" : undefined)
}
onValueChange={(value) => { onValueChange={(value) => {
const server = servers?.find((s) => s.serverId === value); if (value === "dokploy") {
if (server) {
setTemplateInfo({ setTemplateInfo({
...templateInfo, ...templateInfo,
server: server, server: undefined,
}); });
} else {
const server = servers?.find((s) => s.serverId === value);
if (server) {
setTemplateInfo({
...templateInfo,
server: server,
});
}
} }
}} }}
> >
<SelectTrigger className="w-full"> <SelectTrigger className="w-full">
<SelectValue placeholder="Select a server" /> <SelectValue
placeholder={!isCloud ? "Dokploy" : "Select a Server"}
/>
</SelectTrigger> </SelectTrigger>
<SelectContent> <SelectContent>
<SelectGroup> <SelectGroup>
{!isCloud && (
<SelectItem value="dokploy">
<span className="flex items-center gap-2 justify-between w-full">
<span>Dokploy</span>
<span className="text-muted-foreground text-xs self-center">
Default
</span>
</span>
</SelectItem>
)}
{servers?.map((server) => ( {servers?.map((server) => (
<SelectItem key={server.serverId} value={server.serverId}> <SelectItem key={server.serverId} value={server.serverId}>
{server.name} {server.name}
</SelectItem> </SelectItem>
))} ))}
<SelectLabel>Servers ({servers?.length})</SelectLabel> <SelectLabel>
Servers ({servers?.length + (!isCloud ? 1 : 0)})
</SelectLabel>
</SelectGroup> </SelectGroup>
</SelectContent> </SelectContent>
</Select> </Select>
@@ -65,6 +65,11 @@ export const AddCertificate = () => {
const { mutateAsync, isError, error, isLoading } = const { mutateAsync, isError, error, isLoading } =
api.certificates.create.useMutation(); api.certificates.create.useMutation();
const { data: servers } = api.server.withSSHKey.useQuery(); const { data: servers } = api.server.withSSHKey.useQuery();
const hasServers = servers && servers.length > 0;
// Show dropdown logic based on cloud environment
// Cloud: show only if there are remote servers (no Dokploy option)
// Self-hosted: show only if there are remote servers (Dokploy is default, hide if no remote servers)
const shouldShowServerDropdown = hasServers;
const form = useForm<AddCertificate>({ const form = useForm<AddCertificate>({
defaultValues: { defaultValues: {
@@ -85,7 +90,7 @@ export const AddCertificate = () => {
certificateData: data.certificateData, certificateData: data.certificateData,
privateKey: data.privateKey, privateKey: data.privateKey,
autoRenew: data.autoRenew, autoRenew: data.autoRenew,
serverId: data.serverId, serverId: data.serverId === "dokploy" ? undefined : data.serverId,
organizationId: "", organizationId: "",
}) })
.then(async () => { .then(async () => {
@@ -174,52 +179,70 @@ export const AddCertificate = () => {
</FormItem> </FormItem>
)} )}
/> />
<FormField {shouldShowServerDropdown && (
control={form.control} <FormField
name="serverId" control={form.control}
render={({ field }) => ( name="serverId"
<FormItem> render={({ field }) => (
<TooltipProvider delayDuration={0}> <FormItem>
<Tooltip> <TooltipProvider delayDuration={0}>
<TooltipTrigger asChild> <Tooltip>
<FormLabel className="break-all w-fit flex flex-row gap-1 items-center"> <TooltipTrigger asChild>
Select a Server {!isCloud && "(Optional)"} <FormLabel className="break-all w-fit flex flex-row gap-1 items-center">
<HelpCircle className="size-4 text-muted-foreground" /> Select a Server {!isCloud && "(Optional)"}
</FormLabel> <HelpCircle className="size-4 text-muted-foreground" />
</TooltipTrigger> </FormLabel>
</Tooltip> </TooltipTrigger>
</TooltipProvider> </Tooltip>
</TooltipProvider>
<Select <Select
onValueChange={field.onChange} onValueChange={field.onChange}
defaultValue={field.value} defaultValue={
> field.value || (!isCloud ? "dokploy" : undefined)
<SelectTrigger> }
<SelectValue placeholder="Select a Server" /> >
</SelectTrigger> <SelectTrigger>
<SelectContent> <SelectValue
<SelectGroup> placeholder={!isCloud ? "Dokploy" : "Select a Server"}
{servers?.map((server) => ( />
<SelectItem </SelectTrigger>
key={server.serverId} <SelectContent>
value={server.serverId} <SelectGroup>
> {!isCloud && (
<span className="flex items-center gap-2 justify-between w-full"> <SelectItem value="dokploy">
<span>{server.name}</span> <span className="flex items-center gap-2 justify-between w-full">
<span className="text-muted-foreground text-xs self-center"> <span>Dokploy</span>
{server.ipAddress} <span className="text-muted-foreground text-xs self-center">
Default
</span>
</span> </span>
</span> </SelectItem>
</SelectItem> )}
))} {servers?.map((server) => (
<SelectLabel>Servers ({servers?.length})</SelectLabel> <SelectItem
</SelectGroup> key={server.serverId}
</SelectContent> value={server.serverId}
</Select> >
<FormMessage /> <span className="flex items-center gap-2 justify-between w-full">
</FormItem> <span>{server.name}</span>
)} <span className="text-muted-foreground text-xs self-center">
/> {server.ipAddress}
</span>
</span>
</SelectItem>
))}
<SelectLabel>
Servers ({servers?.length + (!isCloud ? 1 : 0)})
</SelectLabel>
</SelectGroup>
</SelectContent>
</Select>
<FormMessage />
</FormItem>
)}
/>
)}
</form> </form>
<DialogFooter className="flex w-full flex-row !justify-end"> <DialogFooter className="flex w-full flex-row !justify-end">