From 1e4ca69c89171965b5590edf583fc5a218a6c6ef Mon Sep 17 00:00:00 2001 From: Owen Date: Sat, 4 Oct 2025 21:29:59 -0700 Subject: [PATCH] priority add for traefik config setup --- server/lib/blueprints/proxyResources.ts | 8 +++ server/lib/blueprints/types.ts | 3 +- server/lib/traefik/privateGetTraefikConfig.ts | 56 ++++++++++++++----- server/routers/target/createTarget.ts | 4 +- server/routers/target/updateTarget.ts | 4 +- .../resources/[niceId]/proxy/page.tsx | 11 ++-- .../settings/resources/create/page.tsx | 10 +++- 7 files changed, 67 insertions(+), 29 deletions(-) diff --git a/server/lib/blueprints/proxyResources.ts b/server/lib/blueprints/proxyResources.ts index e6525191..c142cdc0 100644 --- a/server/lib/blueprints/proxyResources.ts +++ b/server/lib/blueprints/proxyResources.ts @@ -113,8 +113,12 @@ export async function updateProxyResources( internalPort: internalPortToCreate, path: targetData.path, pathMatchType: targetData["path-match"], +<<<<<<< HEAD rewritePath: targetData.rewritePath, rewritePathType: targetData["rewrite-match"] +======= + priority: targetData.priority +>>>>>>> b8d96345 (priority add for traefik config setup) }) .returning(); @@ -362,8 +366,12 @@ export async function updateProxyResources( enabled: targetData.enabled, path: targetData.path, pathMatchType: targetData["path-match"], +<<<<<<< HEAD rewritePath: targetData.rewritePath, rewritePathType: targetData["rewrite-match"] +======= + priority: targetData.priority +>>>>>>> b8d96345 (priority add for traefik config setup) }) .where(eq(targets.targetId, existingTarget.targetId)) .returning(); diff --git a/server/lib/blueprints/types.ts b/server/lib/blueprints/types.ts index 54105dde..bc152d57 100644 --- a/server/lib/blueprints/types.ts +++ b/server/lib/blueprints/types.ts @@ -33,7 +33,8 @@ export const TargetSchema = z.object({ "path-match": z.enum(["exact", "prefix", "regex"]).optional().nullable(), healthcheck: TargetHealthCheckSchema.optional(), rewritePath: z.string().optional(), - "rewrite-match": z.enum(["exact", "prefix", "regex", "stripPrefix"]).optional().nullable() + "rewrite-match": z.enum(["exact", "prefix", "regex", "stripPrefix"]).optional().nullable(), + priority: z.number().int().min(1).max(1000).optional().default(100) }); export type TargetData = z.infer; diff --git a/server/lib/traefik/privateGetTraefikConfig.ts b/server/lib/traefik/privateGetTraefikConfig.ts index 7f1ff614..f8e7b8b5 100644 --- a/server/lib/traefik/privateGetTraefikConfig.ts +++ b/server/lib/traefik/privateGetTraefikConfig.ts @@ -20,7 +20,7 @@ import { loginPage, targetHealthCheck } from "@server/db"; -import { and, eq, inArray, or, isNull, ne, isNotNull } from "drizzle-orm"; +import { and, eq, inArray, or, isNull, ne, isNotNull, desc } from "drizzle-orm"; import logger from "@server/logger"; import HttpCode from "@server/types/HttpCode"; import config from "@server/lib/config"; @@ -77,7 +77,8 @@ export async function getTraefikConfig( hcHealth: targetHealthCheck.hcHealth, path: targets.path, pathMatchType: targets.pathMatchType, - + priority: targets.priority, + // Site fields siteId: sites.siteId, siteType: sites.type, @@ -118,7 +119,8 @@ export async function getTraefikConfig( ? isNotNull(resources.http) // ignore the http check if allow_raw_resources is true : eq(resources.http, true) ) - ); + ) + .orderBy(desc(targets.priority), targets.targetId); // stable ordering // Group by resource and include targets with their unique site data const resourcesMap = new Map(); @@ -127,6 +129,7 @@ export async function getTraefikConfig( const resourceId = row.resourceId; const targetPath = sanitizePath(row.path) || ""; // Handle null/undefined paths const pathMatchType = row.pathMatchType || ""; + const priority = row.priority ?? 100; if (filterOutNamespaceDomains && row.domainNamespaceId) { return; @@ -155,7 +158,8 @@ export async function getTraefikConfig( targets: [], headers: row.headers, path: row.path, // the targets will all have the same path - pathMatchType: row.pathMatchType // the targets will all have the same pathMatchType + pathMatchType: row.pathMatchType, // the targets will all have the same pathMatchType + priority: priority // may be null, we fallback later }); } @@ -168,6 +172,7 @@ export async function getTraefikConfig( port: row.port, internalPort: row.internalPort, enabled: row.targetEnabled, + priority: row.priority, site: { siteId: row.siteId, type: row.siteType, @@ -331,9 +336,30 @@ export async function getTraefikConfig( } let rule = `Host(\`${fullDomain}\`)`; - let priority = 100; + + // priority logic + let priority: number; + if (resource.priority && resource.priority != 100) { + priority = resource.priority; + } else { + priority = 100; + if (resource.path && resource.pathMatchType) { + priority += 10; + if (resource.pathMatchType === "exact") { + priority += 5; + } else if (resource.pathMatchType === "prefix") { + priority += 3; + } else if (resource.pathMatchType === "regex") { + priority += 2; + } + if (resource.path === "/") { + priority = 1; // lowest for catch-all + } + } + } + if (resource.path && resource.pathMatchType) { - priority += 1; + //priority += 1; // add path to rule based on match type let path = resource.path; // if the path doesn't start with a /, add it @@ -389,7 +415,7 @@ export async function getTraefikConfig( return ( (targets as TargetWithSite[]) - .filter((target: TargetWithSite) => { + .filter((target: TargetWithSite) => { if (!target.enabled) { return false; } @@ -410,7 +436,7 @@ export async function getTraefikConfig( ) { return false; } - } else if (target.site.type === "newt") { + } else if (target.site.type === "newt") { if ( !target.internalPort || !target.method || @@ -418,10 +444,10 @@ export async function getTraefikConfig( ) { return false; } - } - return true; - }) - .map((target: TargetWithSite) => { + } + return true; + }) + .map((target: TargetWithSite) => { if ( target.site.type === "local" || target.site.type === "wireguard" @@ -429,14 +455,14 @@ export async function getTraefikConfig( return { url: `${target.method}://${target.ip}:${target.port}` }; - } else if (target.site.type === "newt") { + } else if (target.site.type === "newt") { const ip = target.site.subnet!.split("/")[0]; return { url: `${target.method}://${ip}:${target.internalPort}` }; - } - }) + } + }) // filter out duplicates .filter( (v, i, a) => diff --git a/server/routers/target/createTarget.ts b/server/routers/target/createTarget.ts index d29d5f7d..46dd3916 100644 --- a/server/routers/target/createTarget.ts +++ b/server/routers/target/createTarget.ts @@ -52,9 +52,7 @@ const createTargetSchema = z hcStatus: z.number().int().optional().nullable(), path: z.string().optional().nullable(), pathMatchType: z.enum(["exact", "prefix", "regex"]).optional().nullable(), - rewritePath: z.string().optional().nullable(), - rewritePathType: z.enum(["exact", "prefix", "regex", "stripPrefix"]).optional().nullable(), - priority: z.number().int().min(1).max(1000).default(100) + priority: z.number().int().min(1).max(1000) }) .strict(); diff --git a/server/routers/target/updateTarget.ts b/server/routers/target/updateTarget.ts index e7794b32..5e111f0d 100644 --- a/server/routers/target/updateTarget.ts +++ b/server/routers/target/updateTarget.ts @@ -49,9 +49,7 @@ const updateTargetBodySchema = z hcStatus: z.number().int().optional().nullable(), path: z.string().optional().nullable(), pathMatchType: z.enum(["exact", "prefix", "regex"]).optional().nullable(), - rewritePath: z.string().optional().nullable(), - rewritePathType: z.enum(["exact", "prefix", "regex", "stripPrefix"]).optional().nullable(), - priority: z.number().int().min(1).max(1000).default(100) + priority: z.number().int().min(1).max(1000).optional(), }) .strict() .refine((data) => Object.keys(data).length > 0, { diff --git a/src/app/[orgId]/settings/resources/[niceId]/proxy/page.tsx b/src/app/[orgId]/settings/resources/[niceId]/proxy/page.tsx index c4068741..7df76cb5 100644 --- a/src/app/[orgId]/settings/resources/[niceId]/proxy/page.tsx +++ b/src/app/[orgId]/settings/resources/[niceId]/proxy/page.tsx @@ -305,7 +305,8 @@ export default function ReverseProxyTargets(props: { path: null, pathMatchType: null, rewritePath: null, - rewritePathType: null + rewritePathType: null, + priority: 100 } as z.infer }); @@ -514,7 +515,8 @@ export default function ReverseProxyTargets(props: { path: null, pathMatchType: null, rewritePath: null, - rewritePathType: null + rewritePathType: null, + priority: 100, }); } @@ -592,7 +594,8 @@ export default function ReverseProxyTargets(props: { path: target.path, pathMatchType: target.pathMatchType, rewritePath: target.rewritePath, - rewritePathType: target.rewritePathType + rewritePathType: target.rewritePathType, + priority: target.priority }; if (target.new) { @@ -676,7 +679,7 @@ export default function ReverseProxyTargets(props: { -

Higher priority routes are evaluated first. Use this to ensure specific paths like /api/v1 are checked before catch-all routes like /

+

Higher priority routes are evaluated first. Priority = 100 means automatic ordering (system decides). Use another number to enforce manual priority.

diff --git a/src/app/[orgId]/settings/resources/create/page.tsx b/src/app/[orgId]/settings/resources/create/page.tsx index 80eb5da1..55a7a7be 100644 --- a/src/app/[orgId]/settings/resources/create/page.tsx +++ b/src/app/[orgId]/settings/resources/create/page.tsx @@ -120,7 +120,8 @@ const addTargetSchema = z.object({ path: z.string().optional().nullable(), pathMatchType: z.enum(["exact", "prefix", "regex"]).optional().nullable(), rewritePath: z.string().optional().nullable(), - rewritePathType: z.enum(["exact", "prefix", "regex", "stripPrefix"]).optional().nullable() + rewritePathType: z.enum(["exact", "prefix", "regex", "stripPrefix"]).optional().nullable(), + priority: z.number().int().min(1).max(1000) }).refine( (data) => { // If path is provided, pathMatchType must be provided @@ -263,6 +264,7 @@ export default function Page() { pathMatchType: null, rewritePath: null, rewritePathType: null, + priority: 100, } as z.infer }); @@ -368,6 +370,7 @@ export default function Page() { pathMatchType: null, rewritePath: null, rewritePathType: null, + priority: 100, }); } @@ -477,7 +480,8 @@ export default function Page() { path: target.path, pathMatchType: target.pathMatchType, rewritePath: target.rewritePath, - rewritePathType: target.rewritePathType + rewritePathType: target.rewritePathType, + priority: target.priority }; await api.put(`/resource/${id}/target`, data); @@ -611,7 +615,7 @@ export default function Page() { -

Higher priority routes are evaluated first. Use this to ensure specific paths like /api/v1 are checked before catch-all routes like /

+

Higher priority routes are evaluated first. Priority = 100 means automatic ordering (system decides). Use another number to enforce manual priority.