diff --git a/apps/dokploy/components/dashboard/settings/web-server/edit-traefik-env.tsx b/apps/dokploy/components/dashboard/settings/web-server/edit-traefik-env.tsx index 645eda90..838ee849 100644 --- a/apps/dokploy/components/dashboard/settings/web-server/edit-traefik-env.tsx +++ b/apps/dokploy/components/dashboard/settings/web-server/edit-traefik-env.tsx @@ -80,8 +80,10 @@ export const EditTraefikEnv = ({ children, serverId }: Props) => { {children} - Update Traefik Env - Update the traefik env + Update Traefik Environment + + Update the traefik environment variables + {isError && {error?.message}} diff --git a/apps/dokploy/components/dashboard/settings/web-server/manage-traefik-ports.tsx b/apps/dokploy/components/dashboard/settings/web-server/manage-traefik-ports.tsx index 180b2fcb..66525a0b 100644 --- a/apps/dokploy/components/dashboard/settings/web-server/manage-traefik-ports.tsx +++ b/apps/dokploy/components/dashboard/settings/web-server/manage-traefik-ports.tsx @@ -1,11 +1,15 @@ +import { AlertBlock } from "@/components/shared/alert-block"; import { Button } from "@/components/ui/button"; +import { Card, CardContent } from "@/components/ui/card"; import { Dialog, DialogContent, DialogDescription, + DialogFooter, DialogHeader, DialogTitle, } from "@/components/ui/dialog"; +import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; import { Select, @@ -15,6 +19,7 @@ import { SelectValue, } from "@/components/ui/select"; import { api } from "@/utils/api"; +import { ArrowRightLeft, Plus, Trash2 } from "lucide-react"; import { useTranslation } from "next-i18next"; import type React from "react"; import { useEffect, useState } from "react"; @@ -67,6 +72,7 @@ export const ManageTraefikPorts = ({ children, serverId }: Props) => { const { t } = useTranslation("settings"); const [open, setOpen] = useState(false); const [additionalPorts, setAdditionalPorts] = useState([]); + const [isDirty, setIsDirty] = useState(false); const { data: currentPorts, refetch: refetchPorts } = api.settings.getTraefikPorts.useQuery({ @@ -91,6 +97,7 @@ export const ManageTraefikPorts = ({ children, serverId }: Props) => { ...additionalPorts, { targetPort: 0, publishedPort: 0, publishMode: "host" }, ]); + setIsDirty(true); }; const handleUpdatePorts = async () => { @@ -101,130 +108,208 @@ export const ManageTraefikPorts = ({ children, serverId }: Props) => { }); toast.success(t("settings.server.webServer.traefik.portsUpdated")); setOpen(false); + setIsDirty(false); } catch (error) { toast.error(t("settings.server.webServer.traefik.portsUpdateError")); } }; + const handleRemovePort = (index: number) => { + const newPorts = additionalPorts.filter((_, i) => i !== index); + setAdditionalPorts(newPorts); + setIsDirty(true); + }; + return ( <>
setOpen(true)}>{children}
- + - + {t("settings.server.webServer.traefik.managePorts")} - - {t("settings.server.webServer.traefik.managePortsDescription")} + +
+ {t("settings.server.webServer.traefik.managePortsDescription")} + +
-
- {additionalPorts.map((port, index) => ( -
-
- - { - const newPorts = [...additionalPorts]; - if (newPorts[index]) { - newPorts[index].targetPort = Number.parseInt( - e.target.value, - ); - } - - setAdditionalPorts(newPorts); - }} - className="w-full rounded border p-2" - /> -
-
- - { - const newPorts = [...additionalPorts]; - if (newPorts[index]) { - newPorts[index].publishedPort = Number.parseInt( - e.target.value, - ); - } - setAdditionalPorts(newPorts); - }} - className="w-full rounded border p-2" - /> -
-
- - -
-
- -
+
+ {additionalPorts.length === 0 ? ( +
+ + + No port mappings configured + +

+ Add one to get started +

- ))} -
- + ) : ( +
+ {additionalPorts.map((port, index) => ( + + +
+ + { + const newPorts = [...additionalPorts]; + if (newPorts[index]) { + newPorts[index].targetPort = Number.parseInt( + e.target.value, + ); + } + setAdditionalPorts(newPorts); + }} + className="w-full dark:bg-black" + placeholder="e.g. 8080" + /> +
+ +
+ + { + const newPorts = [...additionalPorts]; + if (newPorts[index]) { + newPorts[index].publishedPort = Number.parseInt( + e.target.value, + ); + } + setAdditionalPorts(newPorts); + }} + className="w-full dark:bg-black" + placeholder="e.g. 80" + /> +
+ +
+ + +
+ +
+ +
+
+
+ ))} +
+ )} + + {additionalPorts.length > 0 && ( + +
+ + + Each port mapping defines how external traffic reaches + your containers. + +
    +
  • + Host Mode: Directly binds the port to + the host machine. +
      +
    • + Best for single-node deployments or when you need + guaranteed port availability. +
    • +
    +
  • +
  • + Ingress Mode: Routes through Docker + Swarm's load balancer. +
      +
    • + Recommended for multi-node deployments and better + scalability. +
    • +
    +
  • +
+
+
+
+ )} +
+ + + {(additionalPorts.length > 0 || isDirty) && ( -
-
+ )} +
); }; + +export default ManageTraefikPorts; diff --git a/apps/dokploy/public/locales/en/settings.json b/apps/dokploy/public/locales/en/settings.json index 1ce54692..39af4178 100644 --- a/apps/dokploy/public/locales/en/settings.json +++ b/apps/dokploy/public/locales/en/settings.json @@ -17,8 +17,8 @@ "settings.server.webServer.updateServerIp": "Update Server IP", "settings.server.webServer.server.label": "Server", "settings.server.webServer.traefik.label": "Traefik", - "settings.server.webServer.traefik.modifyEnv": "Modify Env", - "settings.server.webServer.traefik.managePorts": "Additional Ports", + "settings.server.webServer.traefik.modifyEnv": "Modify Environment", + "settings.server.webServer.traefik.managePorts": "Additional Port Mappings", "settings.server.webServer.traefik.managePortsDescription": "Add or remove additional ports for Traefik", "settings.server.webServer.traefik.targetPort": "Target Port", "settings.server.webServer.traefik.publishedPort": "Published Port",