refactor(manage-traefik-ports): remove publishMode from port management and update related logic

This commit is contained in:
Mauricio Siu 2025-03-15 23:55:29 -06:00
parent 4c5bc541d6
commit 160742c2cf
3 changed files with 73 additions and 116 deletions

View File

@ -19,13 +19,6 @@ import {
} from "@/components/ui/form"; } from "@/components/ui/form";
import { Input } from "@/components/ui/input"; import { Input } from "@/components/ui/input";
import { ScrollArea } from "@/components/ui/scroll-area"; import { ScrollArea } from "@/components/ui/scroll-area";
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/components/ui/select";
import { api } from "@/utils/api"; import { api } from "@/utils/api";
import { zodResolver } from "@hookform/resolvers/zod"; import { zodResolver } from "@hookform/resolvers/zod";
import { ArrowRightLeft, Plus, Trash2 } from "lucide-react"; import { ArrowRightLeft, Plus, Trash2 } from "lucide-react";
@ -44,7 +37,6 @@ interface Props {
const PortSchema = z.object({ const PortSchema = z.object({
targetPort: z.number().min(1, "Target port is required"), targetPort: z.number().min(1, "Target port is required"),
publishedPort: z.number().min(1, "Published port is required"), publishedPort: z.number().min(1, "Published port is required"),
publishMode: z.enum(["ingress", "host"]),
}); });
const TraefikPortsSchema = z.object({ const TraefikPortsSchema = z.object({
@ -88,7 +80,7 @@ export const ManageTraefikPorts = ({ children, serverId }: Props) => {
}, [currentPorts, form]); }, [currentPorts, form]);
const handleAddPort = () => { const handleAddPort = () => {
append({ targetPort: 0, publishedPort: 0, publishMode: "host" }); append({ targetPort: 0, publishedPort: 0 });
}; };
const onSubmit = async (data: TraefikPortsForm) => { const onSubmit = async (data: TraefikPortsForm) => {
@ -154,7 +146,7 @@ export const ManageTraefikPorts = ({ children, serverId }: Props) => {
<div className="grid gap-4"> <div className="grid gap-4">
{fields.map((field, index) => ( {fields.map((field, index) => (
<Card key={field.id}> <Card key={field.id}>
<CardContent className="grid grid-cols-[1fr_1fr_1.5fr_auto] gap-4 p-4 transparent"> <CardContent className="grid grid-cols-[1fr_1fr_auto] gap-4 p-4 transparent">
<FormField <FormField
control={form.control} control={form.control}
name={`ports.${index}.targetPort`} name={`ports.${index}.targetPort`}
@ -207,39 +199,6 @@ export const ManageTraefikPorts = ({ children, serverId }: Props) => {
)} )}
/> />
<FormField
control={form.control}
name={`ports.${index}.publishMode`}
render={({ field }) => (
<FormItem>
<FormLabel className="text-sm font-medium text-muted-foreground">
{t(
"settings.server.webServer.traefik.publishMode",
)}
</FormLabel>
<Select
onValueChange={field.onChange}
value={field.value}
>
<FormControl>
<SelectTrigger className="dark:bg-black">
<SelectValue />
</SelectTrigger>
</FormControl>
<SelectContent>
<SelectItem value="host">
Host Mode
</SelectItem>
<SelectItem value="ingress">
Ingress Mode
</SelectItem>
</SelectContent>
</Select>
<FormMessage />
</FormItem>
)}
/>
<div className="flex items-end"> <div className="flex items-end">
<Button <Button
onClick={() => remove(index)} onClick={() => remove(index)}
@ -263,30 +222,23 @@ export const ManageTraefikPorts = ({ children, serverId }: Props) => {
<span className="text-sm"> <span className="text-sm">
<strong> <strong>
Each port mapping defines how external traffic reaches Each port mapping defines how external traffic reaches
your containers. your containers through Traefik.
</strong> </strong>
<ul className="pt-2"> <ul className="pt-2">
<li> <li>
<strong>Host Mode:</strong> Directly binds the port <strong>Target Port:</strong> The port inside your
to the host machine. container that the service is listening on.
<ul className="p-2 list-inside list-disc">
<li>
Best for single-node deployments or when you
need guaranteed port availability.
</li>
</ul>
</li> </li>
<li> <li>
<strong>Ingress Mode:</strong> Routes through Docker <strong>Published Port:</strong> The port on your
Swarm's load balancer. host machine that will be mapped to the target port.
<ul className="p-2 list-inside list-disc">
<li>
Recommended for multi-node deployments and
better scalability.
</li>
</ul>
</li> </li>
</ul> </ul>
<p className="mt-2">
All ports are bound directly to the host machine,
allowing Traefik to handle incoming traffic and route
it appropriately to your services.
</p>
</span> </span>
</div> </div>
</AlertBlock> </AlertBlock>

View File

@ -97,14 +97,20 @@ export const settingsRouter = createTRPCRouter({
toggleDashboard: adminProcedure toggleDashboard: adminProcedure
.input(apiEnableDashboard) .input(apiEnableDashboard)
.mutation(async ({ input }) => { .mutation(async ({ input }) => {
const ports = (await getTraefikPorts(input.serverId)).filter(
(port) =>
port.targetPort !== 80 &&
port.targetPort !== 443 &&
port.targetPort !== 8080,
);
await initializeTraefik({ await initializeTraefik({
additionalPorts: ports,
enableDashboard: input.enableDashboard, enableDashboard: input.enableDashboard,
serverId: input.serverId, serverId: input.serverId,
force: true, force: true,
}); });
return true; return true;
}), }),
cleanUnusedImages: adminProcedure cleanUnusedImages: adminProcedure
.input(apiServerSchema) .input(apiServerSchema)
.mutation(async ({ input }) => { .mutation(async ({ input }) => {
@ -749,7 +755,6 @@ export const settingsRouter = createTRPCRouter({
z.object({ z.object({
targetPort: z.number(), targetPort: z.number(),
publishedPort: z.number(), publishedPort: z.number(),
publishMode: z.enum(["ingress", "host"]).default("host"),
}), }),
), ),
}), }),
@ -782,59 +787,7 @@ export const settingsRouter = createTRPCRouter({
getTraefikPorts: adminProcedure getTraefikPorts: adminProcedure
.input(apiServerSchema) .input(apiServerSchema)
.query(async ({ input }) => { .query(async ({ input }) => {
const command = `docker container inspect --format='{{json .NetworkSettings.Ports}}' dokploy-traefik`; return await getTraefikPorts(input?.serverId);
try {
let stdout = "";
if (input?.serverId) {
const result = await execAsyncRemote(input.serverId, command);
stdout = result.stdout;
} else if (!IS_CLOUD) {
const result = await execAsync(command);
stdout = result.stdout;
}
const portsMap = JSON.parse(stdout.trim());
const additionalPorts: Array<{
targetPort: number;
publishedPort: number;
publishMode: "host" | "ingress";
}> = [];
// Convert the Docker container port format to our expected format
for (const [containerPort, bindings] of Object.entries(portsMap)) {
if (!bindings) continue;
const [port = ""] = containerPort.split("/");
if (!port) continue;
const targetPortNum = Number.parseInt(port, 10);
if (Number.isNaN(targetPortNum)) continue;
// Skip default ports
if ([80, 443, 8080].includes(targetPortNum)) continue;
for (const binding of bindings as Array<{ HostPort: string }>) {
if (!binding.HostPort) continue;
const publishedPort = Number.parseInt(binding.HostPort, 10);
if (Number.isNaN(publishedPort)) continue;
additionalPorts.push({
targetPort: targetPortNum,
publishedPort,
publishMode: "host", // Docker standalone uses host mode by default
});
}
}
return additionalPorts;
} catch (error) {
throw new TRPCError({
code: "INTERNAL_SERVER_ERROR",
message: "Failed to get Traefik ports",
cause: error,
});
}
}), }),
updateLogCleanup: adminProcedure updateLogCleanup: adminProcedure
.input( .input(
@ -853,3 +806,56 @@ export const settingsRouter = createTRPCRouter({
return getLogCleanupStatus(); return getLogCleanupStatus();
}), }),
}); });
export const getTraefikPorts = async (serverId?: string) => {
const command = `docker container inspect --format='{{json .NetworkSettings.Ports}}' dokploy-traefik`;
try {
let stdout = "";
if (serverId) {
const result = await execAsyncRemote(serverId, command);
stdout = result.stdout;
} else if (!IS_CLOUD) {
const result = await execAsync(command);
stdout = result.stdout;
}
const portsMap = JSON.parse(stdout.trim());
const additionalPorts: Array<{
targetPort: number;
publishedPort: number;
}> = [];
// Convert the Docker container port format to our expected format
for (const [containerPort, bindings] of Object.entries(portsMap)) {
if (!bindings) continue;
const [port = ""] = containerPort.split("/");
if (!port) continue;
const targetPortNum = Number.parseInt(port, 10);
if (Number.isNaN(targetPortNum)) continue;
// Skip default ports
if ([80, 443].includes(targetPortNum)) continue;
for (const binding of bindings as Array<{ HostPort: string }>) {
if (!binding.HostPort) continue;
const publishedPort = Number.parseInt(binding.HostPort, 10);
if (Number.isNaN(publishedPort)) continue;
additionalPorts.push({
targetPort: targetPortNum,
publishedPort,
});
}
}
return additionalPorts;
} catch (error) {
throw new TRPCError({
code: "INTERNAL_SERVER_ERROR",
message: "Failed to get Traefik ports",
cause: error,
});
}
};

View File

@ -22,7 +22,6 @@ interface TraefikOptions {
additionalPorts?: { additionalPorts?: {
targetPort: number; targetPort: number;
publishedPort: number; publishedPort: number;
publishMode?: "ingress" | "host";
}[]; }[];
force?: boolean; force?: boolean;
} }