mirror of
https://github.com/fosrl/pangolin.git
synced 2026-09-15 15:19:51 +02:00
💄 create and prefetch certificates for redirects
This commit is contained in:
@@ -28,6 +28,7 @@ export type ListRedirectsResponse = PaginatedResponse<{
|
|||||||
resourceName: string | null;
|
resourceName: string | null;
|
||||||
resourceNiceId: string | null;
|
resourceNiceId: string | null;
|
||||||
resourceFullDomain: string | null;
|
resourceFullDomain: string | null;
|
||||||
|
resourceDomainId: string | null;
|
||||||
domainId: string | null;
|
domainId: string | null;
|
||||||
baseDomain: string | null;
|
baseDomain: string | null;
|
||||||
}>;
|
}>;
|
||||||
@@ -150,6 +151,7 @@ export async function listRedirects(
|
|||||||
resourceName: resources.name,
|
resourceName: resources.name,
|
||||||
resourceNiceId: resources.niceId,
|
resourceNiceId: resources.niceId,
|
||||||
resourceFullDomain: resources.fullDomain,
|
resourceFullDomain: resources.fullDomain,
|
||||||
|
resourceDomainId: resources.domainId,
|
||||||
domainId: redirects.domainId,
|
domainId: redirects.domainId,
|
||||||
baseDomain: domains.baseDomain
|
baseDomain: domains.baseDomain
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -2,6 +2,8 @@ import RedirectsTable from "@app/components/RedirectsTable";
|
|||||||
import SettingsSectionTitle from "@app/components/SettingsSectionTitle";
|
import SettingsSectionTitle from "@app/components/SettingsSectionTitle";
|
||||||
import { internal } from "@app/lib/api";
|
import { internal } from "@app/lib/api";
|
||||||
import { authCookieHeader } from "@app/lib/api/cookies";
|
import { authCookieHeader } from "@app/lib/api/cookies";
|
||||||
|
import { build } from "@server/build";
|
||||||
|
import type { GetBatchedCertificateResponse } from "@server/routers/certificates/types";
|
||||||
import type { ListRedirectsResponse } from "@server/routers/redirect";
|
import type { ListRedirectsResponse } from "@server/routers/redirect";
|
||||||
import type { AxiosResponse } from "axios";
|
import type { AxiosResponse } from "axios";
|
||||||
import type { Metadata } from "next";
|
import type { Metadata } from "next";
|
||||||
@@ -42,6 +44,60 @@ export default async function RedirectIndexPage(props: RedirectIndexPageProps) {
|
|||||||
// empty list on error
|
// empty list on error
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const redirectRows = redirects.map((redirect) => ({
|
||||||
|
redirectId: redirect.redirectId,
|
||||||
|
niceId: redirect.niceId,
|
||||||
|
name: redirect.name,
|
||||||
|
subdomain: redirect.subdomain,
|
||||||
|
destinationDomain: redirect.destinationDomain,
|
||||||
|
pathMatchType: redirect.pathMatchType,
|
||||||
|
matchPath: redirect.matchPath,
|
||||||
|
rewritePath: redirect.rewritePath,
|
||||||
|
rewritePathType: redirect.rewritePathType,
|
||||||
|
permanent: redirect.permanent,
|
||||||
|
enabled: redirect.enabled,
|
||||||
|
resourceId: redirect.resourceId,
|
||||||
|
resourceName: redirect.resourceName,
|
||||||
|
resourceNiceId: redirect.resourceNiceId,
|
||||||
|
resourceFullDomain: redirect.resourceFullDomain,
|
||||||
|
resourceDomainId: redirect.resourceDomainId,
|
||||||
|
domainId: redirect.domainId,
|
||||||
|
baseDomain: redirect.baseDomain
|
||||||
|
}));
|
||||||
|
|
||||||
|
// Prefetched in one batched call so the table doesn't fire a separate
|
||||||
|
// certificate request per visible row once it mounts on the client.
|
||||||
|
const certDomains = Array.from(
|
||||||
|
new Set(
|
||||||
|
redirectRows
|
||||||
|
.map((r) => {
|
||||||
|
const domainHost = r.baseDomain
|
||||||
|
? [r.subdomain, r.baseDomain].filter(Boolean).join(".")
|
||||||
|
: null;
|
||||||
|
return r.resourceFullDomain ?? domainHost;
|
||||||
|
})
|
||||||
|
.filter((host): host is string => Boolean(host))
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
let initialCertificates: GetBatchedCertificateResponse | undefined;
|
||||||
|
if (build !== "oss" && certDomains.length > 0) {
|
||||||
|
try {
|
||||||
|
const certSearchParams = new URLSearchParams(
|
||||||
|
certDomains.map((domain) => ["domains", domain])
|
||||||
|
);
|
||||||
|
const certRes = await internal.get<
|
||||||
|
AxiosResponse<GetBatchedCertificateResponse>
|
||||||
|
>(
|
||||||
|
`/org/${orgId}/batched-certificates?${certSearchParams.toString()}`,
|
||||||
|
await authCookieHeader()
|
||||||
|
);
|
||||||
|
initialCertificates = certRes.data.data;
|
||||||
|
} catch {
|
||||||
|
// leave undefined so each row falls back to fetching its own
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<SettingsSectionTitle
|
<SettingsSectionTitle
|
||||||
@@ -51,30 +107,13 @@ export default async function RedirectIndexPage(props: RedirectIndexPageProps) {
|
|||||||
|
|
||||||
<RedirectsTable
|
<RedirectsTable
|
||||||
orgId={orgId}
|
orgId={orgId}
|
||||||
redirects={redirects.map((redirect) => ({
|
redirects={redirectRows}
|
||||||
redirectId: redirect.redirectId,
|
|
||||||
niceId: redirect.niceId,
|
|
||||||
name: redirect.name,
|
|
||||||
subdomain: redirect.subdomain,
|
|
||||||
destinationDomain: redirect.destinationDomain,
|
|
||||||
pathMatchType: redirect.pathMatchType,
|
|
||||||
matchPath: redirect.matchPath,
|
|
||||||
rewritePath: redirect.rewritePath,
|
|
||||||
rewritePathType: redirect.rewritePathType,
|
|
||||||
permanent: redirect.permanent,
|
|
||||||
enabled: redirect.enabled,
|
|
||||||
resourceId: redirect.resourceId,
|
|
||||||
resourceName: redirect.resourceName,
|
|
||||||
resourceNiceId: redirect.resourceNiceId,
|
|
||||||
resourceFullDomain: redirect.resourceFullDomain,
|
|
||||||
domainId: redirect.domainId,
|
|
||||||
baseDomain: redirect.baseDomain
|
|
||||||
}))}
|
|
||||||
rowCount={pagination.total}
|
rowCount={pagination.total}
|
||||||
pagination={{
|
pagination={{
|
||||||
pageIndex: pagination.page - 1,
|
pageIndex: pagination.page - 1,
|
||||||
pageSize: pagination.pageSize
|
pageSize: pagination.pageSize
|
||||||
}}
|
}}
|
||||||
|
initialCertificates={initialCertificates}
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -14,10 +14,12 @@ import {
|
|||||||
ControlledDataTable,
|
ControlledDataTable,
|
||||||
type ExtendedColumnDef
|
type ExtendedColumnDef
|
||||||
} from "@app/components/ui/controlled-data-table";
|
} from "@app/components/ui/controlled-data-table";
|
||||||
|
import { ResourceAccessCertIndicator } from "@app/components/ResourceAccessCertIndicator";
|
||||||
import { useEnvContext } from "@app/hooks/useEnvContext";
|
import { useEnvContext } from "@app/hooks/useEnvContext";
|
||||||
import { useNavigationContext } from "@app/hooks/useNavigationContext";
|
import { useNavigationContext } from "@app/hooks/useNavigationContext";
|
||||||
import { toast } from "@app/hooks/useToast";
|
import { toast } from "@app/hooks/useToast";
|
||||||
import { createApiClient, formatAxiosError } from "@app/lib/api";
|
import { createApiClient, formatAxiosError } from "@app/lib/api";
|
||||||
|
import type { GetBatchedCertificateResponse } from "@server/routers/certificates/types";
|
||||||
import type { PaginationState } from "@tanstack/react-table";
|
import type { PaginationState } from "@tanstack/react-table";
|
||||||
import {
|
import {
|
||||||
ArrowRight,
|
ArrowRight,
|
||||||
@@ -48,6 +50,7 @@ export type RedirectRow = {
|
|||||||
resourceName: string | null;
|
resourceName: string | null;
|
||||||
resourceNiceId: string | null;
|
resourceNiceId: string | null;
|
||||||
resourceFullDomain: string | null;
|
resourceFullDomain: string | null;
|
||||||
|
resourceDomainId: string | null;
|
||||||
domainId: string | null;
|
domainId: string | null;
|
||||||
baseDomain: string | null;
|
baseDomain: string | null;
|
||||||
};
|
};
|
||||||
@@ -57,13 +60,15 @@ type RedirectsTableProps = {
|
|||||||
orgId: string;
|
orgId: string;
|
||||||
pagination: PaginationState;
|
pagination: PaginationState;
|
||||||
rowCount: number;
|
rowCount: number;
|
||||||
|
initialCertificates?: GetBatchedCertificateResponse;
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function RedirectsTable({
|
export default function RedirectsTable({
|
||||||
redirects,
|
redirects,
|
||||||
orgId,
|
orgId,
|
||||||
pagination,
|
pagination,
|
||||||
rowCount
|
rowCount,
|
||||||
|
initialCertificates
|
||||||
}: RedirectsTableProps) {
|
}: RedirectsTableProps) {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const t = useTranslations();
|
const t = useTranslations();
|
||||||
@@ -263,16 +268,33 @@ export default function RedirectsTable({
|
|||||||
.join(".")
|
.join(".")
|
||||||
: null;
|
: null;
|
||||||
const host = redirect.resourceFullDomain ?? domainHost;
|
const host = redirect.resourceFullDomain ?? domainHost;
|
||||||
|
// The cert lives on whichever domain actually terminates
|
||||||
|
// TLS: the resource's domain when attached to a resource,
|
||||||
|
// otherwise the redirect's own domain.
|
||||||
|
const certDomainId =
|
||||||
|
redirect.resourceDomainId ?? redirect.domainId;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<code className="text-sm truncate">
|
<div className="flex items-center gap-2 min-w-0">
|
||||||
{host ?? ""}
|
{certDomainId && host ? (
|
||||||
<span className="text-muted-foreground">
|
<ResourceAccessCertIndicator
|
||||||
{redirect.pathMatchType === "prefix"
|
orgId={orgId}
|
||||||
? withPrefixGlob(redirect.matchPath)
|
domainId={certDomainId}
|
||||||
: redirect.matchPath}
|
fullDomain={host}
|
||||||
</span>
|
initialCertValue={
|
||||||
</code>
|
initialCertificates?.[host]
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
) : null}
|
||||||
|
<code className="text-sm truncate">
|
||||||
|
{host ?? ""}
|
||||||
|
<span className="text-muted-foreground">
|
||||||
|
{redirect.pathMatchType === "prefix"
|
||||||
|
? withPrefixGlob(redirect.matchPath)
|
||||||
|
: redirect.matchPath}
|
||||||
|
</span>
|
||||||
|
</code>
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -371,7 +393,7 @@ export default function RedirectsTable({
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
[orgId, t]
|
[orgId, t, initialCertificates]
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
Reference in New Issue
Block a user