Merge pull request #2297 from Dokploy/feat/add-custom-service-field-domains-compose

feat(dashboard): add manual input for service name in domains for docker compose
This commit is contained in:
Mauricio Siu
2025-08-02 13:21:01 -06:00
committed by GitHub
2 changed files with 119 additions and 69 deletions

View File

@@ -1,3 +1,10 @@
import { zodResolver } from "@hookform/resolvers/zod";
import { DatabaseZap, Dices, RefreshCw } from "lucide-react";
import Link from "next/link";
import { useEffect, useState } from "react";
import { useForm } from "react-hook-form";
import { toast } from "sonner";
import z from "zod";
import { AlertBlock } from "@/components/shared/alert-block"; import { AlertBlock } from "@/components/shared/alert-block";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { import {
@@ -34,14 +41,6 @@ import {
TooltipTrigger, TooltipTrigger,
} from "@/components/ui/tooltip"; } from "@/components/ui/tooltip";
import { api } from "@/utils/api"; import { api } from "@/utils/api";
import { useEffect, useState } from "react";
import { useForm } from "react-hook-form";
import { toast } from "sonner";
import { zodResolver } from "@hookform/resolvers/zod";
import { DatabaseZap, Dices, RefreshCw } from "lucide-react";
import Link from "next/link";
import z from "zod";
export type CacheType = "fetch" | "cache"; export type CacheType = "fetch" | "cache";
@@ -123,6 +122,7 @@ interface Props {
export const AddDomain = ({ id, type, domainId = "", children }: Props) => { export const AddDomain = ({ id, type, domainId = "", children }: Props) => {
const [isOpen, setIsOpen] = useState(false); const [isOpen, setIsOpen] = useState(false);
const [cacheType, setCacheType] = useState<CacheType>("cache"); const [cacheType, setCacheType] = useState<CacheType>("cache");
const [isManualInput, setIsManualInput] = useState(false);
const utils = api.useUtils(); const utils = api.useUtils();
const { data, refetch } = api.domain.one.useQuery( const { data, refetch } = api.domain.one.useQuery(
@@ -325,46 +325,126 @@ export const AddDomain = ({ id, type, domainId = "", children }: Props) => {
<FormItem className="w-full"> <FormItem className="w-full">
<FormLabel>Service Name</FormLabel> <FormLabel>Service Name</FormLabel>
<div className="flex gap-2"> <div className="flex gap-2">
<Select {isManualInput ? (
onValueChange={field.onChange}
defaultValue={field.value || ""}
>
<FormControl> <FormControl>
<SelectTrigger> <Input
<SelectValue placeholder="Select a service name" /> placeholder="Enter service name manually"
</SelectTrigger> {...field}
className="w-full"
/>
</FormControl> </FormControl>
) : (
<Select
onValueChange={field.onChange}
defaultValue={field.value || ""}
>
<FormControl>
<SelectTrigger>
<SelectValue placeholder="Select a service name" />
</SelectTrigger>
</FormControl>
<SelectContent> <SelectContent>
{services?.map((service, index) => ( {services?.map((service, index) => (
<SelectItem <SelectItem
value={service} value={service}
key={`${service}-${index}`} key={`${service}-${index}`}
> >
{service} {service}
</SelectItem>
))}
<SelectItem value="none" disabled>
Empty
</SelectItem> </SelectItem>
))} </SelectContent>
<SelectItem value="none" disabled> </Select>
Empty )}
</SelectItem> {!isManualInput && (
</SelectContent> <>
</Select> <TooltipProvider delayDuration={0}>
<Tooltip>
<TooltipTrigger asChild>
<Button
variant="secondary"
type="button"
isLoading={isLoadingServices}
onClick={() => {
if (cacheType === "fetch") {
refetchServices();
} else {
setCacheType("fetch");
}
}}
>
<RefreshCw className="size-4 text-muted-foreground" />
</Button>
</TooltipTrigger>
<TooltipContent
side="left"
sideOffset={5}
className="max-w-[10rem]"
>
<p>
Fetch: Will clone the repository and
load the services
</p>
</TooltipContent>
</Tooltip>
</TooltipProvider>
<TooltipProvider delayDuration={0}>
<Tooltip>
<TooltipTrigger asChild>
<Button
variant="secondary"
type="button"
isLoading={isLoadingServices}
onClick={() => {
if (cacheType === "cache") {
refetchServices();
} else {
setCacheType("cache");
}
}}
>
<DatabaseZap className="size-4 text-muted-foreground" />
</Button>
</TooltipTrigger>
<TooltipContent
side="left"
sideOffset={5}
className="max-w-[10rem]"
>
<p>
Cache: If you previously deployed this
compose, it will read the services
from the last deployment/fetch from
the repository
</p>
</TooltipContent>
</Tooltip>
</TooltipProvider>
</>
)}
<TooltipProvider delayDuration={0}> <TooltipProvider delayDuration={0}>
<Tooltip> <Tooltip>
<TooltipTrigger asChild> <TooltipTrigger asChild>
<Button <Button
variant="secondary" variant="secondary"
type="button" type="button"
isLoading={isLoadingServices}
onClick={() => { onClick={() => {
if (cacheType === "fetch") { setIsManualInput(!isManualInput);
refetchServices(); if (!isManualInput) {
} else { field.onChange("");
setCacheType("fetch");
} }
}} }}
> >
<RefreshCw className="size-4 text-muted-foreground" /> {isManualInput ? (
<RefreshCw className="size-4 text-muted-foreground" />
) : (
<span className="text-xs text-muted-foreground">
Manual
</span>
)}
</Button> </Button>
</TooltipTrigger> </TooltipTrigger>
<TooltipContent <TooltipContent
@@ -373,40 +453,9 @@ export const AddDomain = ({ id, type, domainId = "", children }: Props) => {
className="max-w-[10rem]" className="max-w-[10rem]"
> >
<p> <p>
Fetch: Will clone the repository and load {isManualInput
the services ? "Switch to service selection"
</p> : "Enter service name manually"}
</TooltipContent>
</Tooltip>
</TooltipProvider>
<TooltipProvider delayDuration={0}>
<Tooltip>
<TooltipTrigger asChild>
<Button
variant="secondary"
type="button"
isLoading={isLoadingServices}
onClick={() => {
if (cacheType === "cache") {
refetchServices();
} else {
setCacheType("cache");
}
}}
>
<DatabaseZap className="size-4 text-muted-foreground" />
</Button>
</TooltipTrigger>
<TooltipContent
side="left"
sideOffset={5}
className="max-w-[10rem]"
>
<p>
Cache: If you previously deployed this
compose, it will read the services from
the last deployment/fetch from the
repository
</p> </p>
</TooltipContent> </TooltipContent>
</Tooltip> </Tooltip>

View File

@@ -27,7 +27,8 @@
"noUnsafeOptionalChaining": "off", "noUnsafeOptionalChaining": "off",
"noUnusedImports": "error", "noUnusedImports": "error",
"noUnusedFunctionParameters": "error", "noUnusedFunctionParameters": "error",
"noUnusedVariables": "error" "noUnusedVariables": "error",
"useHookAtTopLevel": "off"
}, },
"style": { "style": {
"noNonNullAssertion": "off", "noNonNullAssertion": "off",