mirror of
https://github.com/fosrl/pangolin.git
synced 2026-09-19 09:09:51 +02:00
♻️ handle priority field
This commit is contained in:
@@ -4393,6 +4393,7 @@
|
|||||||
"redirectRewritePathDescription": "Optionally change the path before redirecting. Leave unset to keep the original path.",
|
"redirectRewritePathDescription": "Optionally change the path before redirecting. Leave unset to keep the original path.",
|
||||||
"redirectRewritePathRequired": "Enter a rewrite path, or choose Strip Prefix",
|
"redirectRewritePathRequired": "Enter a rewrite path, or choose Strip Prefix",
|
||||||
"redirectMatchPathInvalidRegex": "Match path must be a valid regular expression",
|
"redirectMatchPathInvalidRegex": "Match path must be a valid regular expression",
|
||||||
|
"redirectPriorityInvalid": "Enter a whole number between 1 and 1000",
|
||||||
"redirectCreate": "Create Redirect",
|
"redirectCreate": "Create Redirect",
|
||||||
"redirectCreateDescription": "Forward requests matching a path to another URL",
|
"redirectCreateDescription": "Forward requests matching a path to another URL",
|
||||||
"redirectEditDescription": "Update how this redirect forwards incoming requests",
|
"redirectEditDescription": "Update how this redirect forwards incoming requests",
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import {
|
|||||||
redirectPathMatchTypeSchema,
|
redirectPathMatchTypeSchema,
|
||||||
redirectRewritePathSchema,
|
redirectRewritePathSchema,
|
||||||
isValidMatchPath,
|
isValidMatchPath,
|
||||||
|
redirectPrioritySchema,
|
||||||
redirectRewritePathTypeSchema
|
redirectRewritePathTypeSchema
|
||||||
} from "@server/routers/redirect/validation";
|
} from "@server/routers/redirect/validation";
|
||||||
import { getUniqueRedirectName } from "@server/db/names";
|
import { getUniqueRedirectName } from "@server/db/names";
|
||||||
@@ -39,6 +40,7 @@ const bodySchema = z
|
|||||||
matchPath: redirectMatchPathSchema.optional().nullable(),
|
matchPath: redirectMatchPathSchema.optional().nullable(),
|
||||||
rewritePath: redirectRewritePathSchema.optional().nullable(),
|
rewritePath: redirectRewritePathSchema.optional().nullable(),
|
||||||
rewritePathType: redirectRewritePathTypeSchema.optional().nullable(),
|
rewritePathType: redirectRewritePathTypeSchema.optional().nullable(),
|
||||||
|
priority: redirectPrioritySchema.optional().nullable(),
|
||||||
permanent: z.boolean().optional(),
|
permanent: z.boolean().optional(),
|
||||||
enabled: z.boolean().optional()
|
enabled: z.boolean().optional()
|
||||||
})
|
})
|
||||||
@@ -123,6 +125,7 @@ export async function createRedirect(
|
|||||||
matchPath,
|
matchPath,
|
||||||
rewritePath,
|
rewritePath,
|
||||||
rewritePathType,
|
rewritePathType,
|
||||||
|
priority,
|
||||||
permanent,
|
permanent,
|
||||||
enabled
|
enabled
|
||||||
} = parsedBody.data;
|
} = parsedBody.data;
|
||||||
@@ -195,6 +198,7 @@ export async function createRedirect(
|
|||||||
matchPath: matchPath ?? null,
|
matchPath: matchPath ?? null,
|
||||||
rewritePath: rewritePath ?? null,
|
rewritePath: rewritePath ?? null,
|
||||||
rewritePathType: rewritePathType ?? null,
|
rewritePathType: rewritePathType ?? null,
|
||||||
|
priority: priority ?? 100,
|
||||||
permanent: permanent ?? false,
|
permanent: permanent ?? false,
|
||||||
enabled: enabled ?? true
|
enabled: enabled ?? true
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ export type GetRedirectResponse = {
|
|||||||
matchPath: string | null;
|
matchPath: string | null;
|
||||||
rewritePath: string | null;
|
rewritePath: string | null;
|
||||||
rewritePathType: "exact" | "prefix" | "regex" | "stripPrefix" | null;
|
rewritePathType: "exact" | "prefix" | "regex" | "stripPrefix" | null;
|
||||||
|
priority: number | null;
|
||||||
permanent: boolean;
|
permanent: boolean;
|
||||||
enabled: boolean;
|
enabled: boolean;
|
||||||
resourceId: number | null;
|
resourceId: number | null;
|
||||||
@@ -46,6 +47,7 @@ const redirectColumns = {
|
|||||||
matchPath: redirects.matchPath,
|
matchPath: redirects.matchPath,
|
||||||
rewritePath: redirects.rewritePath,
|
rewritePath: redirects.rewritePath,
|
||||||
rewritePathType: redirects.rewritePathType,
|
rewritePathType: redirects.rewritePathType,
|
||||||
|
priority: redirects.priority,
|
||||||
permanent: redirects.permanent,
|
permanent: redirects.permanent,
|
||||||
enabled: redirects.enabled,
|
enabled: redirects.enabled,
|
||||||
resourceId: redirects.resourceId,
|
resourceId: redirects.resourceId,
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ export type ListRedirectsResponse = PaginatedResponse<{
|
|||||||
matchPath: string | null;
|
matchPath: string | null;
|
||||||
rewritePath: string | null;
|
rewritePath: string | null;
|
||||||
rewritePathType: "exact" | "prefix" | "regex" | "stripPrefix" | null;
|
rewritePathType: "exact" | "prefix" | "regex" | "stripPrefix" | null;
|
||||||
|
priority: number | null;
|
||||||
permanent: boolean;
|
permanent: boolean;
|
||||||
enabled: boolean;
|
enabled: boolean;
|
||||||
resourceId: number | null;
|
resourceId: number | null;
|
||||||
@@ -145,6 +146,7 @@ export async function listRedirects(
|
|||||||
matchPath: redirects.matchPath,
|
matchPath: redirects.matchPath,
|
||||||
rewritePath: redirects.rewritePath,
|
rewritePath: redirects.rewritePath,
|
||||||
rewritePathType: redirects.rewritePathType,
|
rewritePathType: redirects.rewritePathType,
|
||||||
|
priority: redirects.priority,
|
||||||
permanent: redirects.permanent,
|
permanent: redirects.permanent,
|
||||||
enabled: redirects.enabled,
|
enabled: redirects.enabled,
|
||||||
resourceId: redirects.resourceId,
|
resourceId: redirects.resourceId,
|
||||||
@@ -173,7 +175,7 @@ export async function listRedirects(
|
|||||||
baseQuery
|
baseQuery
|
||||||
.limit(pageSize)
|
.limit(pageSize)
|
||||||
.offset(pageSize * (page - 1))
|
.offset(pageSize * (page - 1))
|
||||||
.orderBy(desc(redirects.redirectId))
|
.orderBy(desc(redirects.priority), desc(redirects.redirectId))
|
||||||
]);
|
]);
|
||||||
|
|
||||||
return response<ListRedirectsResponse>(res, {
|
return response<ListRedirectsResponse>(res, {
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ import {
|
|||||||
redirectPathMatchTypeSchema,
|
redirectPathMatchTypeSchema,
|
||||||
redirectRewritePathSchema,
|
redirectRewritePathSchema,
|
||||||
redirectRewritePathTypeSchema,
|
redirectRewritePathTypeSchema,
|
||||||
|
redirectPrioritySchema,
|
||||||
isValidMatchPath
|
isValidMatchPath
|
||||||
} from "@server/routers/redirect/validation";
|
} from "@server/routers/redirect/validation";
|
||||||
import { createCertificate } from "../certificates";
|
import { createCertificate } from "../certificates";
|
||||||
@@ -40,6 +41,7 @@ const bodySchema = z.strictObject({
|
|||||||
matchPath: redirectMatchPathSchema.optional().nullable(),
|
matchPath: redirectMatchPathSchema.optional().nullable(),
|
||||||
rewritePath: redirectRewritePathSchema.optional().nullable(),
|
rewritePath: redirectRewritePathSchema.optional().nullable(),
|
||||||
rewritePathType: redirectRewritePathTypeSchema.optional().nullable(),
|
rewritePathType: redirectRewritePathTypeSchema.optional().nullable(),
|
||||||
|
priority: redirectPrioritySchema.optional(),
|
||||||
permanent: z.boolean().optional(),
|
permanent: z.boolean().optional(),
|
||||||
enabled: z.boolean().optional()
|
enabled: z.boolean().optional()
|
||||||
});
|
});
|
||||||
@@ -255,6 +257,9 @@ export async function updateRedirect(
|
|||||||
if (body.rewritePathType !== undefined) {
|
if (body.rewritePathType !== undefined) {
|
||||||
updateData.rewritePathType = body.rewritePathType;
|
updateData.rewritePathType = body.rewritePathType;
|
||||||
}
|
}
|
||||||
|
if (body.priority !== undefined) {
|
||||||
|
updateData.priority = body.priority;
|
||||||
|
}
|
||||||
if (body.permanent !== undefined) {
|
if (body.permanent !== undefined) {
|
||||||
updateData.permanent = body.permanent;
|
updateData.permanent = body.permanent;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -45,6 +45,9 @@ export function isValidMatchPath(
|
|||||||
|
|
||||||
export const redirectRewritePathSchema = z.string().nonempty();
|
export const redirectRewritePathSchema = z.string().nonempty();
|
||||||
|
|
||||||
|
// Same range as target priorities; 100 means "let the system order it".
|
||||||
|
export const redirectPrioritySchema = z.int().min(1).max(1000);
|
||||||
|
|
||||||
export const redirectDestinationDomainSchema = z
|
export const redirectDestinationDomainSchema = z
|
||||||
.string()
|
.string()
|
||||||
.nonempty()
|
.nonempty()
|
||||||
|
|||||||
@@ -54,6 +54,7 @@ export default async function RedirectIndexPage(props: RedirectIndexPageProps) {
|
|||||||
matchPath: redirect.matchPath,
|
matchPath: redirect.matchPath,
|
||||||
rewritePath: redirect.rewritePath,
|
rewritePath: redirect.rewritePath,
|
||||||
rewritePathType: redirect.rewritePathType,
|
rewritePathType: redirect.rewritePathType,
|
||||||
|
priority: redirect.priority,
|
||||||
permanent: redirect.permanent,
|
permanent: redirect.permanent,
|
||||||
enabled: redirect.enabled,
|
enabled: redirect.enabled,
|
||||||
resourceId: redirect.resourceId,
|
resourceId: redirect.resourceId,
|
||||||
|
|||||||
@@ -67,6 +67,7 @@ import DomainPicker from "@app/components/DomainPicker";
|
|||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
|
|
||||||
const DEFAULT_PATH_MATCH_TYPE = "regex" as const;
|
const DEFAULT_PATH_MATCH_TYPE = "regex" as const;
|
||||||
|
const DEFAULT_PRIORITY = 100;
|
||||||
|
|
||||||
export type ExistingRedirect = GetRedirectResponse["redirect"];
|
export type ExistingRedirect = GetRedirectResponse["redirect"];
|
||||||
|
|
||||||
@@ -132,6 +133,11 @@ export default function RedirectForm({
|
|||||||
rewritePathType: z
|
rewritePathType: z
|
||||||
.enum(["exact", "prefix", "regex", "stripPrefix"])
|
.enum(["exact", "prefix", "regex", "stripPrefix"])
|
||||||
.nullable(),
|
.nullable(),
|
||||||
|
priority: z
|
||||||
|
.number()
|
||||||
|
.int()
|
||||||
|
.min(1, { message: t("redirectPriorityInvalid") })
|
||||||
|
.max(1000, { message: t("redirectPriorityInvalid") }),
|
||||||
permanent: z.boolean(),
|
permanent: z.boolean(),
|
||||||
enabled: z.boolean()
|
enabled: z.boolean()
|
||||||
})
|
})
|
||||||
@@ -193,6 +199,7 @@ export default function RedirectForm({
|
|||||||
matchPath: redirect?.matchPath ?? null,
|
matchPath: redirect?.matchPath ?? null,
|
||||||
rewritePath: redirect?.rewritePath ?? null,
|
rewritePath: redirect?.rewritePath ?? null,
|
||||||
rewritePathType: redirect?.rewritePathType ?? null,
|
rewritePathType: redirect?.rewritePathType ?? null,
|
||||||
|
priority: redirect?.priority ?? DEFAULT_PRIORITY,
|
||||||
permanent: redirect?.permanent ?? false,
|
permanent: redirect?.permanent ?? false,
|
||||||
enabled: redirect?.enabled ?? true
|
enabled: redirect?.enabled ?? true
|
||||||
}
|
}
|
||||||
@@ -248,6 +255,7 @@ export default function RedirectForm({
|
|||||||
matchPath: values.matchPath?.trim() || null,
|
matchPath: values.matchPath?.trim() || null,
|
||||||
rewritePath: values.rewritePath?.trim() || null,
|
rewritePath: values.rewritePath?.trim() || null,
|
||||||
rewritePathType: values.rewritePathType,
|
rewritePathType: values.rewritePathType,
|
||||||
|
priority: values.priority,
|
||||||
permanent: values.permanent,
|
permanent: values.permanent,
|
||||||
enabled: values.enabled
|
enabled: values.enabled
|
||||||
};
|
};
|
||||||
@@ -792,6 +800,40 @@ export default function RedirectForm({
|
|||||||
/>
|
/>
|
||||||
</SettingsFormCell>
|
</SettingsFormCell>
|
||||||
|
|
||||||
|
<SettingsFormCell span="half">
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="priority"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem>
|
||||||
|
<FormLabel>
|
||||||
|
{t("priority")}
|
||||||
|
</FormLabel>
|
||||||
|
<FormControl>
|
||||||
|
<Input
|
||||||
|
type="number"
|
||||||
|
min={1}
|
||||||
|
max={1000}
|
||||||
|
{...field}
|
||||||
|
onChange={(e) =>
|
||||||
|
field.onChange(
|
||||||
|
e.target
|
||||||
|
.valueAsNumber
|
||||||
|
)
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
<FormDescription>
|
||||||
|
{t(
|
||||||
|
"priorityDescription"
|
||||||
|
)}
|
||||||
|
</FormDescription>
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</SettingsFormCell>
|
||||||
|
|
||||||
<SettingsFormCell span="full">
|
<SettingsFormCell span="full">
|
||||||
<FormField
|
<FormField
|
||||||
control={form.control}
|
control={form.control}
|
||||||
|
|||||||
@@ -22,9 +22,12 @@ import { createApiClient, formatAxiosError } from "@app/lib/api";
|
|||||||
import type { GetBatchedCertificateResponse } from "@server/routers/certificates/types";
|
import type { GetBatchedCertificateResponse } from "@server/routers/certificates/types";
|
||||||
import type { PaginationState } from "@tanstack/react-table";
|
import type { PaginationState } from "@tanstack/react-table";
|
||||||
import {
|
import {
|
||||||
|
ArrowDown,
|
||||||
ArrowRight,
|
ArrowRight,
|
||||||
|
ArrowUp,
|
||||||
ArrowUpRight,
|
ArrowUpRight,
|
||||||
GlobeIcon,
|
GlobeIcon,
|
||||||
|
MinusIcon,
|
||||||
MoreHorizontal,
|
MoreHorizontal,
|
||||||
WaypointsIcon
|
WaypointsIcon
|
||||||
} from "lucide-react";
|
} from "lucide-react";
|
||||||
@@ -52,6 +55,7 @@ export type RedirectRow = {
|
|||||||
matchPath: string | null;
|
matchPath: string | null;
|
||||||
rewritePath: string | null;
|
rewritePath: string | null;
|
||||||
rewritePathType: "exact" | "prefix" | "regex" | "stripPrefix" | null;
|
rewritePathType: "exact" | "prefix" | "regex" | "stripPrefix" | null;
|
||||||
|
priority: number | null;
|
||||||
permanent: boolean;
|
permanent: boolean;
|
||||||
enabled: boolean;
|
enabled: boolean;
|
||||||
resourceId: number | null;
|
resourceId: number | null;
|
||||||
@@ -286,9 +290,7 @@ export default function RedirectsTable({
|
|||||||
{redirect.matchPath && (
|
{redirect.matchPath && (
|
||||||
<span className="text-muted-foreground">
|
<span className="text-muted-foreground">
|
||||||
{redirect.pathMatchType === "prefix"
|
{redirect.pathMatchType === "prefix"
|
||||||
? withPrefixGlob(
|
? withPrefixGlob(redirect.matchPath)
|
||||||
redirect.matchPath
|
|
||||||
)
|
|
||||||
: redirect.matchPath}
|
: redirect.matchPath}
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
@@ -320,6 +322,28 @@ export default function RedirectsTable({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
accessorKey: "priority",
|
||||||
|
friendlyName: t("priority"),
|
||||||
|
header: () => <span className="p-3">{t("priority")}</span>,
|
||||||
|
cell: ({ row }) => {
|
||||||
|
// 100 is the automatic default; anything else was set
|
||||||
|
// deliberately, so flag which way it deviates.
|
||||||
|
const priority = row.original.priority ?? 100;
|
||||||
|
return (
|
||||||
|
<span className="inline-flex items-center gap-1">
|
||||||
|
{priority}
|
||||||
|
{priority > 100 ? (
|
||||||
|
<ArrowUp className="size-3 text-green-500" />
|
||||||
|
) : priority < 100 ? (
|
||||||
|
<ArrowDown className="size-3 text-red-500" />
|
||||||
|
) : (
|
||||||
|
<MinusIcon className="size-3 text-muted-foreground" />
|
||||||
|
)}
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
accessorKey: "permanent",
|
accessorKey: "permanent",
|
||||||
friendlyName: t("redirectType"),
|
friendlyName: t("redirectType"),
|
||||||
@@ -439,7 +463,8 @@ export default function RedirectsTable({
|
|||||||
columnVisibility={{
|
columnVisibility={{
|
||||||
attachedTo: false,
|
attachedTo: false,
|
||||||
niceId: false,
|
niceId: false,
|
||||||
permanent: false
|
permanent: false,
|
||||||
|
priority: false
|
||||||
}}
|
}}
|
||||||
enableColumnVisibility
|
enableColumnVisibility
|
||||||
stickyLeftColumn="name"
|
stickyLeftColumn="name"
|
||||||
|
|||||||
Reference in New Issue
Block a user