mirror of
https://github.com/Dokploy/dokploy
synced 2025-06-26 18:27:59 +00:00
Merge dde12e132a into 2a89be6efc
This commit is contained in:
@@ -19,6 +19,8 @@ describe("createDomainLabels", () => {
|
|||||||
path: "/",
|
path: "/",
|
||||||
createdAt: "",
|
createdAt: "",
|
||||||
previewDeploymentId: "",
|
previewDeploymentId: "",
|
||||||
|
internalPath: "/",
|
||||||
|
stripPath: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
it("should create basic labels for web entrypoint", async () => {
|
it("should create basic labels for web entrypoint", async () => {
|
||||||
|
|||||||
@@ -119,6 +119,8 @@ const baseDomain: Domain = {
|
|||||||
domainType: "application",
|
domainType: "application",
|
||||||
uniqueConfigKey: 1,
|
uniqueConfigKey: 1,
|
||||||
previewDeploymentId: "",
|
previewDeploymentId: "",
|
||||||
|
internalPath: "/",
|
||||||
|
stripPath: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
const baseRedirect: Redirect = {
|
const baseRedirect: Redirect = {
|
||||||
|
|||||||
@@ -49,6 +49,8 @@ export const domain = z
|
|||||||
.object({
|
.object({
|
||||||
host: z.string().min(1, { message: "Add a hostname" }),
|
host: z.string().min(1, { message: "Add a hostname" }),
|
||||||
path: z.string().min(1).optional(),
|
path: z.string().min(1).optional(),
|
||||||
|
internalPath: z.string().optional(),
|
||||||
|
stripPath: z.boolean().optional(),
|
||||||
port: z
|
port: z
|
||||||
.number()
|
.number()
|
||||||
.min(1, { message: "Port must be at least 1" })
|
.min(1, { message: "Port must be at least 1" })
|
||||||
@@ -84,6 +86,24 @@ export const domain = z
|
|||||||
message: "Required",
|
message: "Required",
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Validate stripPath requires a valid path
|
||||||
|
if (input.stripPath && (!input.path || input.path === "/")) {
|
||||||
|
ctx.addIssue({
|
||||||
|
code: z.ZodIssueCode.custom,
|
||||||
|
path: ["stripPath"],
|
||||||
|
message: "Strip path can only be enabled when a path other than '/' is specified",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate internalPath starts with /
|
||||||
|
if (input.internalPath && input.internalPath !== "/" && !input.internalPath.startsWith("/")) {
|
||||||
|
ctx.addIssue({
|
||||||
|
code: z.ZodIssueCode.custom,
|
||||||
|
path: ["internalPath"],
|
||||||
|
message: "Internal path must start with '/'",
|
||||||
|
});
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
type Domain = z.infer<typeof domain>;
|
type Domain = z.infer<typeof domain>;
|
||||||
@@ -162,6 +182,8 @@ export const AddDomain = ({ id, type, domainId = "", children }: Props) => {
|
|||||||
defaultValues: {
|
defaultValues: {
|
||||||
host: "",
|
host: "",
|
||||||
path: undefined,
|
path: undefined,
|
||||||
|
internalPath: undefined,
|
||||||
|
stripPath: false,
|
||||||
port: undefined,
|
port: undefined,
|
||||||
https: false,
|
https: false,
|
||||||
certificateType: undefined,
|
certificateType: undefined,
|
||||||
@@ -182,6 +204,8 @@ export const AddDomain = ({ id, type, domainId = "", children }: Props) => {
|
|||||||
...data,
|
...data,
|
||||||
/* Convert null to undefined */
|
/* Convert null to undefined */
|
||||||
path: data?.path || undefined,
|
path: data?.path || undefined,
|
||||||
|
internalPath: data?.internalPath || undefined,
|
||||||
|
stripPath: data?.stripPath || false,
|
||||||
port: data?.port || undefined,
|
port: data?.port || undefined,
|
||||||
certificateType: data?.certificateType || undefined,
|
certificateType: data?.certificateType || undefined,
|
||||||
customCertResolver: data?.customCertResolver || undefined,
|
customCertResolver: data?.customCertResolver || undefined,
|
||||||
@@ -194,6 +218,8 @@ export const AddDomain = ({ id, type, domainId = "", children }: Props) => {
|
|||||||
form.reset({
|
form.reset({
|
||||||
host: "",
|
host: "",
|
||||||
path: undefined,
|
path: undefined,
|
||||||
|
internalPath: undefined,
|
||||||
|
stripPath: false,
|
||||||
port: undefined,
|
port: undefined,
|
||||||
https: false,
|
https: false,
|
||||||
certificateType: undefined,
|
certificateType: undefined,
|
||||||
@@ -469,6 +495,49 @@ export const AddDomain = ({ id, type, domainId = "", children }: Props) => {
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="internalPath"
|
||||||
|
render={({ field }) => {
|
||||||
|
return (
|
||||||
|
<FormItem>
|
||||||
|
<FormLabel>Internal Path</FormLabel>
|
||||||
|
<FormDescription>
|
||||||
|
The path where your application expects to receive
|
||||||
|
requests internally (defaults to "/")
|
||||||
|
</FormDescription>
|
||||||
|
<FormControl>
|
||||||
|
<Input placeholder={"/"} {...field} />
|
||||||
|
</FormControl>
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="stripPath"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem className="flex flex-row items-center justify-between p-3 border rounded-lg shadow-sm">
|
||||||
|
<div className="space-y-0.5">
|
||||||
|
<FormLabel>Strip Path</FormLabel>
|
||||||
|
<FormDescription>
|
||||||
|
Remove the external path from the request before
|
||||||
|
forwarding to the application
|
||||||
|
</FormDescription>
|
||||||
|
<FormMessage />
|
||||||
|
</div>
|
||||||
|
<FormControl>
|
||||||
|
<Switch
|
||||||
|
checked={field.value}
|
||||||
|
onCheckedChange={field.onChange}
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
|
||||||
<FormField
|
<FormField
|
||||||
control={form.control}
|
control={form.control}
|
||||||
name="port"
|
name="port"
|
||||||
|
|||||||
2
apps/dokploy/drizzle/0098_warm_tiger_shark.sql
Normal file
2
apps/dokploy/drizzle/0098_warm_tiger_shark.sql
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
ALTER TABLE "domain" ADD COLUMN "internalPath" text DEFAULT '/';--> statement-breakpoint
|
||||||
|
ALTER TABLE "domain" ADD COLUMN "stripPath" boolean DEFAULT false NOT NULL;
|
||||||
5840
apps/dokploy/drizzle/meta/0098_snapshot.json
Normal file
5840
apps/dokploy/drizzle/meta/0098_snapshot.json
Normal file
File diff suppressed because it is too large
Load Diff
@@ -687,6 +687,13 @@
|
|||||||
"when": 1750567641441,
|
"when": 1750567641441,
|
||||||
"tag": "0097_hard_lizard",
|
"tag": "0097_hard_lizard",
|
||||||
"breakpoints": true
|
"breakpoints": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idx": 98,
|
||||||
|
"version": "7",
|
||||||
|
"when": 1750570751159,
|
||||||
|
"tag": "0098_warm_tiger_shark",
|
||||||
|
"breakpoints": true
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@@ -51,6 +51,8 @@ export const domains = pgTable("domain", {
|
|||||||
{ onDelete: "cascade" },
|
{ onDelete: "cascade" },
|
||||||
),
|
),
|
||||||
certificateType: certificateType("certificateType").notNull().default("none"),
|
certificateType: certificateType("certificateType").notNull().default("none"),
|
||||||
|
internalPath: text("internalPath").default("/"),
|
||||||
|
stripPath: boolean("stripPath").notNull().default(false),
|
||||||
});
|
});
|
||||||
|
|
||||||
export const domainsRelations = relations(domains, ({ one }) => ({
|
export const domainsRelations = relations(domains, ({ one }) => ({
|
||||||
@@ -82,6 +84,8 @@ export const apiCreateDomain = createSchema.pick({
|
|||||||
serviceName: true,
|
serviceName: true,
|
||||||
domainType: true,
|
domainType: true,
|
||||||
previewDeploymentId: true,
|
previewDeploymentId: true,
|
||||||
|
internalPath: true,
|
||||||
|
stripPath: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
export const apiFindDomain = createSchema
|
export const apiFindDomain = createSchema
|
||||||
@@ -112,5 +116,7 @@ export const apiUpdateDomain = createSchema
|
|||||||
customCertResolver: true,
|
customCertResolver: true,
|
||||||
serviceName: true,
|
serviceName: true,
|
||||||
domainType: true,
|
domainType: true,
|
||||||
|
internalPath: true,
|
||||||
|
stripPath: true,
|
||||||
})
|
})
|
||||||
.merge(createSchema.pick({ domainId: true }).required());
|
.merge(createSchema.pick({ domainId: true }).required());
|
||||||
|
|||||||
@@ -4,6 +4,8 @@ export const domain = z
|
|||||||
.object({
|
.object({
|
||||||
host: z.string().min(1, { message: "Add a hostname" }),
|
host: z.string().min(1, { message: "Add a hostname" }),
|
||||||
path: z.string().min(1).optional(),
|
path: z.string().min(1).optional(),
|
||||||
|
internalPath: z.string().optional(),
|
||||||
|
stripPath: z.boolean().optional(),
|
||||||
port: z
|
port: z
|
||||||
.number()
|
.number()
|
||||||
.min(1, { message: "Port must be at least 1" })
|
.min(1, { message: "Port must be at least 1" })
|
||||||
@@ -29,12 +31,32 @@ export const domain = z
|
|||||||
message: "Required when certificate type is custom",
|
message: "Required when certificate type is custom",
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Validate stripPath requires a valid path
|
||||||
|
if (input.stripPath && (!input.path || input.path === "/")) {
|
||||||
|
ctx.addIssue({
|
||||||
|
code: z.ZodIssueCode.custom,
|
||||||
|
path: ["stripPath"],
|
||||||
|
message: "Strip path can only be enabled when a path other than '/' is specified",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate internalPath starts with /
|
||||||
|
if (input.internalPath && input.internalPath !== "/" && !input.internalPath.startsWith("/")) {
|
||||||
|
ctx.addIssue({
|
||||||
|
code: z.ZodIssueCode.custom,
|
||||||
|
path: ["internalPath"],
|
||||||
|
message: "Internal path must start with '/'",
|
||||||
|
});
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
export const domainCompose = z
|
export const domainCompose = z
|
||||||
.object({
|
.object({
|
||||||
host: z.string().min(1, { message: "Host is required" }),
|
host: z.string().min(1, { message: "Host is required" }),
|
||||||
path: z.string().min(1).optional(),
|
path: z.string().min(1).optional(),
|
||||||
|
internalPath: z.string().optional(),
|
||||||
|
stripPath: z.boolean().optional(),
|
||||||
port: z
|
port: z
|
||||||
.number()
|
.number()
|
||||||
.min(1, { message: "Port must be at least 1" })
|
.min(1, { message: "Port must be at least 1" })
|
||||||
@@ -61,4 +83,22 @@ export const domainCompose = z
|
|||||||
message: "Required when certificate type is custom",
|
message: "Required when certificate type is custom",
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Validate stripPath requires a valid path
|
||||||
|
if (input.stripPath && (!input.path || input.path === "/")) {
|
||||||
|
ctx.addIssue({
|
||||||
|
code: z.ZodIssueCode.custom,
|
||||||
|
path: ["stripPath"],
|
||||||
|
message: "Strip path can only be enabled when a path other than '/' is specified",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate internalPath starts with /
|
||||||
|
if (input.internalPath && input.internalPath !== "/" && !input.internalPath.startsWith("/")) {
|
||||||
|
ctx.addIssue({
|
||||||
|
code: z.ZodIssueCode.custom,
|
||||||
|
path: ["internalPath"],
|
||||||
|
message: "Internal path must start with '/'",
|
||||||
|
});
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -301,6 +301,8 @@ export const createDomainLabels = (
|
|||||||
certificateType,
|
certificateType,
|
||||||
path,
|
path,
|
||||||
customCertResolver,
|
customCertResolver,
|
||||||
|
stripPath,
|
||||||
|
internalPath
|
||||||
} = domain;
|
} = domain;
|
||||||
const routerName = `${appName}-${uniqueConfigKey}-${entrypoint}`;
|
const routerName = `${appName}-${uniqueConfigKey}-${entrypoint}`;
|
||||||
const labels = [
|
const labels = [
|
||||||
@@ -310,6 +312,26 @@ export const createDomainLabels = (
|
|||||||
`traefik.http.routers.${routerName}.service=${routerName}`,
|
`traefik.http.routers.${routerName}.service=${routerName}`,
|
||||||
];
|
];
|
||||||
|
|
||||||
|
// Validate stripPath - it should only be used when path is defined and not "/"
|
||||||
|
if (stripPath) {
|
||||||
|
if (!path || path === "/") {
|
||||||
|
console.warn(`stripPath is enabled but path is not defined or is "/" for domain ${host}`);
|
||||||
|
} else {
|
||||||
|
const middlewareName = `stripprefix-${appName}-${uniqueConfigKey}`;
|
||||||
|
labels.push(`traefik.http.middlewares.${middlewareName}.stripprefix.prefixes=${path}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate internalPath - ensure it's a valid path format
|
||||||
|
if (internalPath && internalPath !== "/") {
|
||||||
|
if (!internalPath.startsWith("/")) {
|
||||||
|
console.warn(`internalPath "${internalPath}" should start with "/" and not be empty for domain ${host}`);
|
||||||
|
} else {
|
||||||
|
const middlewareName = `addprefix-${appName}-${uniqueConfigKey}`;
|
||||||
|
labels.push(`traefik.http.middlewares.${middlewareName}.addprefix.prefix=${internalPath}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (entrypoint === "web" && https) {
|
if (entrypoint === "web" && https) {
|
||||||
labels.push(
|
labels.push(
|
||||||
`traefik.http.routers.${routerName}.middlewares=redirect-to-https@file`,
|
`traefik.http.routers.${routerName}.middlewares=redirect-to-https@file`,
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import {
|
|||||||
writeTraefikConfigRemote,
|
writeTraefikConfigRemote,
|
||||||
} from "./application";
|
} from "./application";
|
||||||
import type { FileConfig, HttpRouter } from "./file-types";
|
import type { FileConfig, HttpRouter } from "./file-types";
|
||||||
|
import { createPathMiddlewares, removePathMiddlewares } from "./middleware";
|
||||||
|
|
||||||
export const manageDomain = async (app: ApplicationNested, domain: Domain) => {
|
export const manageDomain = async (app: ApplicationNested, domain: Domain) => {
|
||||||
const { appName } = app;
|
const { appName } = app;
|
||||||
@@ -46,6 +47,8 @@ export const manageDomain = async (app: ApplicationNested, domain: Domain) => {
|
|||||||
|
|
||||||
config.http.services[serviceName] = createServiceConfig(appName, domain);
|
config.http.services[serviceName] = createServiceConfig(appName, domain);
|
||||||
|
|
||||||
|
await createPathMiddlewares(app, domain);
|
||||||
|
|
||||||
if (app.serverId) {
|
if (app.serverId) {
|
||||||
await writeTraefikConfigRemote(config, appName, app.serverId);
|
await writeTraefikConfigRemote(config, appName, app.serverId);
|
||||||
} else {
|
} else {
|
||||||
@@ -80,6 +83,8 @@ export const removeDomain = async (
|
|||||||
delete config.http.services[serviceKey];
|
delete config.http.services[serviceKey];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
await removePathMiddlewares(application, uniqueKey);
|
||||||
|
|
||||||
// verify if is the last router if so we delete the router
|
// verify if is the last router if so we delete the router
|
||||||
if (
|
if (
|
||||||
config?.http?.routers &&
|
config?.http?.routers &&
|
||||||
@@ -107,7 +112,8 @@ export const createRouterConfig = async (
|
|||||||
const { appName, redirects, security } = app;
|
const { appName, redirects, security } = app;
|
||||||
const { certificateType } = domain;
|
const { certificateType } = domain;
|
||||||
|
|
||||||
const { host, path, https, uniqueConfigKey } = domain;
|
const { host, path, https, uniqueConfigKey, internalPath, stripPath } =
|
||||||
|
domain;
|
||||||
const routerConfig: HttpRouter = {
|
const routerConfig: HttpRouter = {
|
||||||
rule: `Host(\`${host}\`)${path !== null && path !== "/" ? ` && PathPrefix(\`${path}\`)` : ""}`,
|
rule: `Host(\`${host}\`)${path !== null && path !== "/" ? ` && PathPrefix(\`${path}\`)` : ""}`,
|
||||||
service: `${appName}-service-${uniqueConfigKey}`,
|
service: `${appName}-service-${uniqueConfigKey}`,
|
||||||
@@ -115,6 +121,17 @@ export const createRouterConfig = async (
|
|||||||
entryPoints: [entryPoint],
|
entryPoints: [entryPoint],
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Add path rewriting middleware if needed
|
||||||
|
if (internalPath && internalPath !== "/" && internalPath !== path) {
|
||||||
|
const pathMiddleware = `addprefix-${appName}-${uniqueConfigKey}`;
|
||||||
|
routerConfig.middlewares?.push(pathMiddleware);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (stripPath && path && path !== "/") {
|
||||||
|
const stripMiddleware = `stripprefix-${appName}-${uniqueConfigKey}`;
|
||||||
|
routerConfig.middlewares?.push(stripMiddleware);
|
||||||
|
}
|
||||||
|
|
||||||
if (entryPoint === "web" && https) {
|
if (entryPoint === "web" && https) {
|
||||||
routerConfig.middlewares = ["redirect-to-https"];
|
routerConfig.middlewares = ["redirect-to-https"];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import type { ApplicationNested } from "../builders";
|
|||||||
import { execAsyncRemote } from "../process/execAsync";
|
import { execAsyncRemote } from "../process/execAsync";
|
||||||
import { writeTraefikConfigRemote } from "./application";
|
import { writeTraefikConfigRemote } from "./application";
|
||||||
import type { FileConfig } from "./file-types";
|
import type { FileConfig } from "./file-types";
|
||||||
|
import type { Domain } from "@dokploy/server/services/domain";
|
||||||
|
|
||||||
export const addMiddleware = (config: FileConfig, middlewareName: string) => {
|
export const addMiddleware = (config: FileConfig, middlewareName: string) => {
|
||||||
if (config.http?.routers) {
|
if (config.http?.routers) {
|
||||||
@@ -105,3 +106,97 @@ export const writeMiddleware = <T>(config: T) => {
|
|||||||
const newYamlContent = dump(config);
|
const newYamlContent = dump(config);
|
||||||
writeFileSync(configPath, newYamlContent, "utf8");
|
writeFileSync(configPath, newYamlContent, "utf8");
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const createPathMiddlewares = async (
|
||||||
|
app: ApplicationNested,
|
||||||
|
domain: Domain,
|
||||||
|
) => {
|
||||||
|
let config: FileConfig;
|
||||||
|
|
||||||
|
if (app.serverId) {
|
||||||
|
try {
|
||||||
|
config = await loadRemoteMiddlewares(app.serverId);
|
||||||
|
} catch {
|
||||||
|
config = { http: { middlewares: {} } };
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
config = loadMiddlewares<FileConfig>();
|
||||||
|
} catch {
|
||||||
|
config = { http: { middlewares: {} } };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const { appName } = app;
|
||||||
|
const { uniqueConfigKey, internalPath, stripPath, path } = domain;
|
||||||
|
|
||||||
|
if (!config.http) {
|
||||||
|
config.http = { middlewares: {} };
|
||||||
|
}
|
||||||
|
if (!config.http.middlewares) {
|
||||||
|
config.http.middlewares = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add internal path prefix middleware
|
||||||
|
if (internalPath && internalPath !== "/" && internalPath !== path) {
|
||||||
|
const middlewareName = `addprefix-${appName}-${uniqueConfigKey}`;
|
||||||
|
config.http.middlewares[middlewareName] = {
|
||||||
|
addPrefix: {
|
||||||
|
prefix: internalPath,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Strip external path middleware if needed
|
||||||
|
if (stripPath && path && path !== "/") {
|
||||||
|
const middlewareName = `stripprefix-${appName}-${uniqueConfigKey}`;
|
||||||
|
config.http.middlewares[middlewareName] = {
|
||||||
|
stripPrefix: {
|
||||||
|
prefixes: [path],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (app.serverId) {
|
||||||
|
await writeTraefikConfigRemote(config, "middlewares", app.serverId);
|
||||||
|
} else {
|
||||||
|
writeMiddleware(config);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const removePathMiddlewares = async (
|
||||||
|
app: ApplicationNested,
|
||||||
|
uniqueConfigKey: number,
|
||||||
|
) => {
|
||||||
|
let config: FileConfig;
|
||||||
|
|
||||||
|
if (app.serverId) {
|
||||||
|
try {
|
||||||
|
config = await loadRemoteMiddlewares(app.serverId);
|
||||||
|
} catch {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
config = loadMiddlewares<FileConfig>();
|
||||||
|
} catch {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const { appName } = app;
|
||||||
|
|
||||||
|
if (config.http?.middlewares) {
|
||||||
|
const addPrefixMiddleware = `addprefix-${appName}-${uniqueConfigKey}`;
|
||||||
|
const stripPrefixMiddleware = `stripprefix-${appName}-${uniqueConfigKey}`;
|
||||||
|
|
||||||
|
delete config.http.middlewares[addPrefixMiddleware];
|
||||||
|
delete config.http.middlewares[stripPrefixMiddleware];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (app.serverId) {
|
||||||
|
await writeTraefikConfigRemote(config, "middlewares", app.serverId);
|
||||||
|
} else {
|
||||||
|
writeMiddleware(config);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user