From 4688e56d9f12cd29ce837b1c29368cd5d252f736 Mon Sep 17 00:00:00 2001 From: miloschwartz Date: Tue, 20 Oct 2026 16:19:43 -0400 Subject: [PATCH] add cert status to redirects --- src/app/[orgId]/settings/redirects/page.tsx | 46 +++++++++++++++++++ .../settings/resources/private/page.tsx | 3 +- .../settings/resources/public/page.tsx | 3 +- src/components/RedirectsTable.tsx | 41 +++++++++++++++-- 4 files changed, 86 insertions(+), 7 deletions(-) diff --git a/src/app/[orgId]/settings/redirects/page.tsx b/src/app/[orgId]/settings/redirects/page.tsx index b4bb18f25..160134ca3 100644 --- a/src/app/[orgId]/settings/redirects/page.tsx +++ b/src/app/[orgId]/settings/redirects/page.tsx @@ -5,6 +5,7 @@ import type { SelectedResource } from "@app/components/resource-selector"; import SettingsSectionTitle from "@app/components/SettingsSectionTitle"; import { internal } from "@app/lib/api"; import { authCookieHeader } from "@app/lib/api/cookies"; +import type { GetBatchedCertificateResponse } from "@server/routers/certificates/types"; import { GetDomainResponse } from "@server/routers/domain/getDomain"; import type { ListRedirectsResponse } from "@server/routers/redirect"; import { GetResourceResponse } from "@server/routers/resource/getResource"; @@ -127,6 +128,50 @@ export default async function RedirectIndexPage(props: RedirectIndexPageProps) { 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((redirect) => { + if (redirect.resourceId) { + if ( + redirect.resourceSsl && + redirect.resourceFullDomain + ) { + return redirect.resourceFullDomain; + } + return null; + } + if (!redirect.ssl || !redirect.baseDomain) { + return null; + } + return [redirect.subdomain, redirect.baseDomain] + .filter(Boolean) + .join("."); + }) + .filter((domain): domain is string => Boolean(domain)) + ) + ); + + let initialCertificates: GetBatchedCertificateResponse | undefined; + if (certDomains.length > 0) { + try { + const certSearchParams = new URLSearchParams( + certDomains.map((domain) => ["domains", domain]) + ); + const certRes = await internal.get< + AxiosResponse + >( + `/org/${orgId}/batched-certificates?${certSearchParams.toString()}`, + header + ); + initialCertificates = certRes.data.data; + } catch { + // leave undefined so each row falls back to fetching its own + } + } + return ( <> ); diff --git a/src/app/[orgId]/settings/resources/private/page.tsx b/src/app/[orgId]/settings/resources/private/page.tsx index 6aa1a4726..fa41beca4 100644 --- a/src/app/[orgId]/settings/resources/private/page.tsx +++ b/src/app/[orgId]/settings/resources/private/page.tsx @@ -6,7 +6,6 @@ import { internal } from "@app/lib/api"; import { authCookieHeader } from "@app/lib/api/cookies"; import { getCachedOrg } from "@app/lib/api/getCachedOrg"; import OrgProvider from "@app/providers/OrgProvider"; -import { build } from "@server/build"; import type { GetBatchedCertificateResponse } from "@server/routers/certificates/types"; import type { ListAllSiteResourcesByOrgResponse } from "@server/routers/siteResource"; import type { AxiosResponse } from "axios"; @@ -120,7 +119,7 @@ export default async function ClientResourcesPage( ); let initialCertificates: GetBatchedCertificateResponse | undefined; - if (build !== "oss" && certDomains.length > 0) { + if (certDomains.length > 0) { try { const certSearchParams = new URLSearchParams( certDomains.map((domain) => ["domains", domain]) diff --git a/src/app/[orgId]/settings/resources/public/page.tsx b/src/app/[orgId]/settings/resources/public/page.tsx index 27ca4e110..ba78f6071 100644 --- a/src/app/[orgId]/settings/resources/public/page.tsx +++ b/src/app/[orgId]/settings/resources/public/page.tsx @@ -5,7 +5,6 @@ import PublicResourcesBanner from "@app/components/PublicResourcesBanner"; import { internal } from "@app/lib/api"; import { authCookieHeader } from "@app/lib/api/cookies"; import OrgProvider from "@app/providers/OrgProvider"; -import { build } from "@server/build"; import type { GetBatchedCertificateResponse } from "@server/routers/certificates/types"; import type { GetOrgResponse } from "@server/routers/org"; import type { ListResourcesResponse } from "@server/routers/resource"; @@ -132,7 +131,7 @@ export default async function ProxyResourcesPage( ); let initialCertificates: GetBatchedCertificateResponse | undefined; - if (build !== "oss" && certDomains.length > 0) { + if (certDomains.length > 0) { try { const certSearchParams = new URLSearchParams( certDomains.map((domain) => ["domains", domain]) diff --git a/src/components/RedirectsTable.tsx b/src/components/RedirectsTable.tsx index 9519cd150..a954b6b13 100644 --- a/src/components/RedirectsTable.tsx +++ b/src/components/RedirectsTable.tsx @@ -2,6 +2,7 @@ import ConfirmDeleteDialog from "@app/components/ConfirmDeleteDialog"; import CopyToClipboard from "@app/components/CopyToClipboard"; +import { ResourceAccessCertIndicator } from "@app/components/ResourceAccessCertIndicator"; import { ResourceSelector, type SelectedResource @@ -40,6 +41,7 @@ import { cn } from "@app/lib/cn"; import { dataTableFilterPopoverContentClassName } from "@app/lib/dataTableFilterPopover"; import { orgQueries } from "@app/lib/queries"; import { getNextSortOrder, getSortDirection } from "@app/lib/sortColumn"; +import type { GetBatchedCertificateResponse } from "@server/routers/certificates/types"; import { useQuery } from "@tanstack/react-query"; import type { PaginationState } from "@tanstack/react-table"; import { @@ -113,6 +115,8 @@ type RedirectsTableProps = { rowCount: number; initialFilterResource?: SelectedResource | null; initialFilterDomain?: RedirectFilterDomain | null; + /** Certificates prefetched on the server, keyed by full domain. */ + initialCertificates?: GetBatchedCertificateResponse; }; export default function RedirectsTable({ @@ -121,7 +125,8 @@ export default function RedirectsTable({ pagination, rowCount, initialFilterResource = null, - initialFilterDomain = null + initialFilterDomain = null, + initialCertificates }: RedirectsTableProps) { const router = useRouter(); const t = useTranslations(); @@ -460,7 +465,36 @@ export default function RedirectsTable({ return -; } - return ; + const certDomainId = redirect.resourceId + ? redirect.resourceDomainId + : redirect.domainId; + const certHostname = redirect.resourceId + ? redirect.resourceFullDomain + : domainHost; + const showHttpsCertIndicator = + sourceSsl && + certHostname != null && + certHostname !== "" && + certDomainId != null && + certDomainId !== ""; + + return ( +
+ {showHttpsCertIndicator ? ( + + ) : null} +
+ +
+
+ ); } }, { @@ -647,7 +681,8 @@ export default function RedirectsTable({ domainFilterOpen, resourceFilterOpen, selectedDomain, - selectedResource + selectedResource, + initialCertificates ] );