From b47fc9f901952c624b5cae90bb82b3d2689fea5e Mon Sep 17 00:00:00 2001 From: Owen Date: Sat, 4 Oct 2025 21:21:42 -0700 Subject: [PATCH 1/7] frontend for ordered priority --- .../resources/[niceId]/proxy/page.tsx | 47 ++++++++++++++++++- 1 file changed, 46 insertions(+), 1 deletion(-) diff --git a/src/app/[orgId]/settings/resources/[niceId]/proxy/page.tsx b/src/app/[orgId]/settings/resources/[niceId]/proxy/page.tsx index a4277f6b..f27022e0 100644 --- a/src/app/[orgId]/settings/resources/[niceId]/proxy/page.tsx +++ b/src/app/[orgId]/settings/resources/[niceId]/proxy/page.tsx @@ -74,7 +74,10 @@ import { CircleX, ArrowRight, Plus, - MoveRight + MoveRight, + ArrowUp, + Info, + ArrowDown } from "lucide-react"; import { ContainersSelector } from "@app/components/ContainersSelector"; import { useTranslations } from "next-intl"; @@ -106,6 +109,7 @@ import { PathRewriteModal } from "@app/components/PathMatchRenameModal"; import { Badge } from "@app/components/ui/badge"; +import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@app/components/ui/tooltip"; const addTargetSchema = z .object({ @@ -660,6 +664,47 @@ export default function ReverseProxyTargets(props: { } const columns: ColumnDef[] = [ + { + id: "priority", + header: () => ( +
+ Priority + + + + + + +

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

+
+
+
+
+ ), + cell: ({ row }) => { + const targetIndex = targets.findIndex(t => t.targetId === row.original.targetId); + return ( +
+ { + const value = parseInt(e.target.value, 10); + if (value >= 1 && value <= 1000) { + updateTarget(row.original.targetId, { + ...row.original, + //priority: value + }); + } + }} + /> +
+ ); + } + }, { accessorKey: "path", header: t("matchPath"), From ff2bcfb0e7b4754f04610be4a4710e2296a5e063 Mon Sep 17 00:00:00 2001 From: Owen Date: Sat, 4 Oct 2025 21:25:31 -0700 Subject: [PATCH 2/7] backend setup --- server/db/pg/schema.ts | 3 +- server/db/sqlite/schema.ts | 3 +- server/routers/target/createTarget.ts | 3 +- server/routers/target/listTargets.ts | 3 +- server/routers/target/updateTarget.ts | 3 +- .../resources/[niceId]/proxy/page.tsx | 6 +-- .../settings/resources/create/page.tsx | 44 ++++++++++++++++++- 7 files changed, 56 insertions(+), 9 deletions(-) diff --git a/server/db/pg/schema.ts b/server/db/pg/schema.ts index 29c14560..764e343d 100644 --- a/server/db/pg/schema.ts +++ b/server/db/pg/schema.ts @@ -125,7 +125,8 @@ export const targets = pgTable("targets", { path: text("path"), pathMatchType: text("pathMatchType"), // exact, prefix, regex rewritePath: text("rewritePath"), // if set, rewrites the path to this value before sending to the target - rewritePathType: text("rewritePathType") // exact, prefix, regex, stripPrefix + rewritePathType: text("rewritePathType"), // exact, prefix, regex, stripPrefix + priority: integer("priority").notNull().default(100) }); export const targetHealthCheck = pgTable("targetHealthCheck", { diff --git a/server/db/sqlite/schema.ts b/server/db/sqlite/schema.ts index 62fca8b4..21e44a92 100644 --- a/server/db/sqlite/schema.ts +++ b/server/db/sqlite/schema.ts @@ -137,7 +137,8 @@ export const targets = sqliteTable("targets", { path: text("path"), pathMatchType: text("pathMatchType"), // exact, prefix, regex rewritePath: text("rewritePath"), // if set, rewrites the path to this value before sending to the target - rewritePathType: text("rewritePathType") // exact, prefix, regex, stripPrefix + rewritePathType: text("rewritePathType"), // exact, prefix, regex, stripPrefix + priority: integer("priority").notNull().default(100) }); export const targetHealthCheck = sqliteTable("targetHealthCheck", { diff --git a/server/routers/target/createTarget.ts b/server/routers/target/createTarget.ts index 0b473563..d29d5f7d 100644 --- a/server/routers/target/createTarget.ts +++ b/server/routers/target/createTarget.ts @@ -53,7 +53,8 @@ const createTargetSchema = z 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).default(100) }) .strict(); diff --git a/server/routers/target/listTargets.ts b/server/routers/target/listTargets.ts index 178ec967..04966f6e 100644 --- a/server/routers/target/listTargets.ts +++ b/server/routers/target/listTargets.ts @@ -62,7 +62,8 @@ function queryTargets(resourceId: number) { path: targets.path, pathMatchType: targets.pathMatchType, rewritePath: targets.rewritePath, - rewritePathType: targets.rewritePathType + rewritePathType: targets.rewritePathType, + priority: targets.priority, }) .from(targets) .leftJoin(sites, eq(sites.siteId, targets.siteId)) diff --git a/server/routers/target/updateTarget.ts b/server/routers/target/updateTarget.ts index af629729..e7794b32 100644 --- a/server/routers/target/updateTarget.ts +++ b/server/routers/target/updateTarget.ts @@ -50,7 +50,8 @@ const updateTargetBodySchema = z 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).default(100) }) .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 f27022e0..c4068741 100644 --- a/src/app/[orgId]/settings/resources/[niceId]/proxy/page.tsx +++ b/src/app/[orgId]/settings/resources/[niceId]/proxy/page.tsx @@ -489,6 +489,7 @@ export default function ReverseProxyTargets(props: { targetId: new Date().getTime(), new: true, resourceId: resource.resourceId, + priority: 100, hcEnabled: false, hcPath: null, hcMethod: null, @@ -682,21 +683,20 @@ export default function ReverseProxyTargets(props: { ), cell: ({ row }) => { - const targetIndex = targets.findIndex(t => t.targetId === row.original.targetId); return (
{ const value = parseInt(e.target.value, 10); if (value >= 1 && value <= 1000) { updateTarget(row.original.targetId, { ...row.original, - //priority: value + priority: value }); } }} diff --git a/src/app/[orgId]/settings/resources/create/page.tsx b/src/app/[orgId]/settings/resources/create/page.tsx index 1810f09e..80eb5da1 100644 --- a/src/app/[orgId]/settings/resources/create/page.tsx +++ b/src/app/[orgId]/settings/resources/create/page.tsx @@ -58,7 +58,7 @@ import { } from "@app/components/ui/popover"; import { CaretSortIcon, CheckIcon } from "@radix-ui/react-icons"; import { cn } from "@app/lib/cn"; -import { ArrowRight, MoveRight, Plus, SquareArrowOutUpRight } from "lucide-react"; +import { ArrowRight, Info, MoveRight, Plus, SquareArrowOutUpRight } from "lucide-react"; import CopyTextBox from "@app/components/CopyTextBox"; import Link from "next/link"; import { useTranslations } from "next-intl"; @@ -92,6 +92,7 @@ import { parseHostTarget } from "@app/lib/parseHostTarget"; import { toASCII, toUnicode } from 'punycode'; import { DomainRow } from "../../../../../components/DomainsTable"; import { finalizeSubdomainSanitize } from "@app/lib/subdomain-utils"; +import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@app/components/ui/tooltip"; import { PathMatchDisplay, PathMatchModal, PathRewriteDisplay, PathRewriteModal } from "@app/components/PathMatchRenameModal"; @@ -341,6 +342,7 @@ export default function Page() { targetId: new Date().getTime(), new: true, resourceId: 0, // Will be set when resource is created + priority: 100, // Default priority hcEnabled: false, hcPath: null, hcMethod: null, @@ -598,6 +600,46 @@ export default function Page() { }, []); const columns: ColumnDef[] = [ + { + id: "priority", + header: () => ( +
+ Priority + + + + + + +

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

+
+
+
+
+ ), + cell: ({ row }) => { + return ( +
+ { + const value = parseInt(e.target.value, 10); + if (value >= 1 && value <= 1000) { + updateTarget(row.original.targetId, { + ...row.original, + priority: value + }); + } + }} + /> +
+ ); + } + }, { accessorKey: "path", header: t("matchPath"), From 1e4ca69c89171965b5590edf583fc5a218a6c6ef Mon Sep 17 00:00:00 2001 From: Owen Date: Sat, 4 Oct 2025 21:29:59 -0700 Subject: [PATCH 3/7] 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.

From 043834274d89663f11e97c545ff9ec74ff975a32 Mon Sep 17 00:00:00 2001 From: Pallavi Kumari Date: Mon, 6 Oct 2025 01:22:23 +0530 Subject: [PATCH 4/7] fix priority inside blueprints --- server/lib/blueprints/proxyResources.ts | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/server/lib/blueprints/proxyResources.ts b/server/lib/blueprints/proxyResources.ts index c142cdc0..bbfc260f 100644 --- a/server/lib/blueprints/proxyResources.ts +++ b/server/lib/blueprints/proxyResources.ts @@ -113,12 +113,9 @@ export async function updateProxyResources( internalPort: internalPortToCreate, path: targetData.path, pathMatchType: targetData["path-match"], -<<<<<<< HEAD rewritePath: targetData.rewritePath, - rewritePathType: targetData["rewrite-match"] -======= + rewritePathType: targetData["rewrite-match"], priority: targetData.priority ->>>>>>> b8d96345 (priority add for traefik config setup) }) .returning(); @@ -366,12 +363,9 @@ export async function updateProxyResources( enabled: targetData.enabled, path: targetData.path, pathMatchType: targetData["path-match"], -<<<<<<< HEAD rewritePath: targetData.rewritePath, - rewritePathType: targetData["rewrite-match"] -======= + rewritePathType: targetData["rewrite-match"], priority: targetData.priority ->>>>>>> b8d96345 (priority add for traefik config setup) }) .where(eq(targets.targetId, existingTarget.targetId)) .returning(); From b6c76a21641a887ee3169266d06297724e05edde Mon Sep 17 00:00:00 2001 From: Pallavi Kumari Date: Mon, 6 Oct 2025 01:37:33 +0530 Subject: [PATCH 5/7] add priority type --- src/app/[orgId]/settings/resources/[niceId]/proxy/page.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/app/[orgId]/settings/resources/[niceId]/proxy/page.tsx b/src/app/[orgId]/settings/resources/[niceId]/proxy/page.tsx index 7df76cb5..302d16d2 100644 --- a/src/app/[orgId]/settings/resources/[niceId]/proxy/page.tsx +++ b/src/app/[orgId]/settings/resources/[niceId]/proxy/page.tsx @@ -126,7 +126,8 @@ const addTargetSchema = z rewritePathType: z .enum(["exact", "prefix", "regex", "stripPrefix"]) .optional() - .nullable() + .nullable(), + priority: z.number().int().min(1).max(1000) }) .refine( (data) => { From 22477b7e8161930da120d6ee98c8720eb41b101c Mon Sep 17 00:00:00 2001 From: Pallavi Kumari Date: Mon, 6 Oct 2025 02:16:06 +0530 Subject: [PATCH 6/7] add removed rewrite schema --- server/routers/target/createTarget.ts | 2 ++ server/routers/target/updateTarget.ts | 2 ++ 2 files changed, 4 insertions(+) diff --git a/server/routers/target/createTarget.ts b/server/routers/target/createTarget.ts index 46dd3916..d5be025b 100644 --- a/server/routers/target/createTarget.ts +++ b/server/routers/target/createTarget.ts @@ -52,6 +52,8 @@ 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) }) .strict(); diff --git a/server/routers/target/updateTarget.ts b/server/routers/target/updateTarget.ts index 5e111f0d..d66c7cd0 100644 --- a/server/routers/target/updateTarget.ts +++ b/server/routers/target/updateTarget.ts @@ -49,6 +49,8 @@ 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).optional(), }) .strict() From e4c0a157e3d5c1b83732fec2deeb032b9c0f3630 Mon Sep 17 00:00:00 2001 From: Owen Date: Sun, 5 Oct 2025 15:46:46 -0700 Subject: [PATCH 7/7] Add to oss traefik config and fix create/update --- server/lib/traefik/getTraefikConfig.ts | 36 ++++++++++++++++++++++---- server/routers/target/createTarget.ts | 5 +++- server/routers/target/updateTarget.ts | 5 +++- 3 files changed, 39 insertions(+), 7 deletions(-) diff --git a/server/lib/traefik/getTraefikConfig.ts b/server/lib/traefik/getTraefikConfig.ts index 598ce984..97a6826d 100644 --- a/server/lib/traefik/getTraefikConfig.ts +++ b/server/lib/traefik/getTraefikConfig.ts @@ -1,5 +1,5 @@ import { db, exitNodes, 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 config from "@server/lib/config"; import { orgs, resources, sites, Target, targets } from "@server/db"; @@ -124,6 +124,8 @@ export async function getTraefikConfig( pathMatchType: targets.pathMatchType, rewritePath: targets.rewritePath, rewritePathType: targets.rewritePathType, + priority: targets.priority, + // Site fields siteId: sites.siteId, siteType: sites.type, @@ -152,7 +154,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(); @@ -163,6 +166,7 @@ export async function getTraefikConfig( const pathMatchType = row.pathMatchType || ""; const rewritePath = row.rewritePath || ""; const rewritePathType = row.rewritePathType || ""; + const priority = row.priority ?? 100; // Create a unique key combining resourceId, path config, and rewrite config const pathKey = [targetPath, pathMatchType, rewritePath, rewritePathType] @@ -202,7 +206,8 @@ export async function getTraefikConfig( path: row.path, // the targets will all have the same path pathMatchType: row.pathMatchType, // the targets will all have the same pathMatchType rewritePath: row.rewritePath, - rewritePathType: row.rewritePathType + rewritePathType: row.rewritePathType, + priority: priority // may be null, we fallback later }); } @@ -217,6 +222,7 @@ export async function getTraefikConfig( enabled: row.targetEnabled, rewritePath: row.rewritePath, rewritePathType: row.rewritePathType, + priority: row.priority, site: { siteId: row.siteId, type: row.siteType, @@ -402,10 +408,30 @@ export async function getTraefikConfig( // Build routing rules 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 diff --git a/server/routers/target/createTarget.ts b/server/routers/target/createTarget.ts index d5be025b..73e21521 100644 --- a/server/routers/target/createTarget.ts +++ b/server/routers/target/createTarget.ts @@ -211,7 +211,10 @@ export async function createTarget( internalPort, enabled: targetData.enabled, path: targetData.path, - pathMatchType: targetData.pathMatchType + pathMatchType: targetData.pathMatchType, + rewritePath: targetData.rewritePath, + rewritePathType: targetData.rewritePathType, + priority: targetData.priority }) .returning(); diff --git a/server/routers/target/updateTarget.ts b/server/routers/target/updateTarget.ts index d66c7cd0..d332609d 100644 --- a/server/routers/target/updateTarget.ts +++ b/server/routers/target/updateTarget.ts @@ -199,7 +199,10 @@ export async function updateTarget( internalPort, enabled: parsedBody.data.enabled, path: parsedBody.data.path, - pathMatchType: parsedBody.data.pathMatchType + pathMatchType: parsedBody.data.pathMatchType, + priority: parsedBody.data.priority, + rewritePath: parsedBody.data.rewritePath, + rewritePathType: parsedBody.data.rewritePathType }) .where(eq(targets.targetId, targetId)) .returning();