♻️ Batch certificate queries

This commit is contained in:
Fred KISSIE
2026-07-28 20:59:42 +01:00
parent 49854f31a5
commit 10773432bb
9 changed files with 151 additions and 186 deletions
-1
View File
@@ -412,7 +412,6 @@ function AuthPageSettings({
fullDomain={
loginPage.fullDomain
}
autoFetch={true}
showLabel={true}
polling={true}
/>
-3
View File
@@ -187,7 +187,6 @@ type CertificateStatusProps = {
orgId: string;
domainId: string;
fullDomain: string;
autoFetch?: boolean;
showLabel?: boolean;
className?: string;
onRefresh?: () => void;
@@ -199,7 +198,6 @@ export default function CertificateStatus({
orgId,
domainId,
fullDomain,
autoFetch = true,
showLabel = true,
className = "",
onRefresh,
@@ -210,7 +208,6 @@ export default function CertificateStatus({
orgId,
domainId,
fullDomain,
autoFetch,
polling,
pollingInterval
});
+14 -37
View File
@@ -7,8 +7,7 @@ import {
ResourceSitesStatusCell,
type ResourceSiteRow
} from "@app/components/ResourceSitesStatusCell";
import { Selectedsite, SitesSelector } from "@app/components/site-selector";
import { Badge } from "@app/components/ui/badge";
import { Selectedsite } from "@app/components/site-selector";
import { Button } from "@app/components/ui/button";
import { ExtendedColumnDef } from "@app/components/ui/data-table";
import {
@@ -18,25 +17,18 @@ import {
DropdownMenuTrigger
} from "@app/components/ui/dropdown-menu";
import { InfoPopup } from "@app/components/ui/info-popup";
import {
Popover,
PopoverContent,
PopoverTrigger
} from "@app/components/ui/popover";
import { Switch } from "@app/components/ui/switch";
import { useEnvContext } from "@app/hooks/useEnvContext";
import { useNavigationContext } from "@app/hooks/useNavigationContext";
import { useOptimisticLabels } from "@app/hooks/useOptimisticLabels";
import { usePaidStatus } from "@app/hooks/usePaidStatus";
import { toast } from "@app/hooks/useToast";
import { createApiClient, formatAxiosError } from "@app/lib/api";
import { cn } from "@app/lib/cn";
import { dataTableFilterPopoverContentClassName } from "@app/lib/dataTableFilterPopover";
import { durationToMs } from "@app/lib/durationToMs";
import { orgQueries } from "@app/lib/queries";
import { getNextSortOrder, getSortDirection } from "@app/lib/sortColumn";
import { build } from "@server/build";
import { tierMatrix } from "@server/lib/billing/tierMatrix";
import { UpdateResourceResponse } from "@server/routers/resource";
import type { GetBatchedCertificateResponse } from "@server/routers/certificates/types";
import { useQuery } from "@tanstack/react-query";
import type { PaginationState } from "@tanstack/react-table";
import { AxiosResponse } from "axios";
@@ -48,9 +40,7 @@ import {
ChevronDown,
ChevronsUpDownIcon,
Clock,
Funnel,
MoreHorizontal,
PlusIcon,
ShieldCheck,
ShieldOff,
XCircle
@@ -60,7 +50,6 @@ import Link from "next/link";
import { useRouter } from "next/navigation";
import {
startTransition,
useEffect,
useMemo,
useOptimistic,
useRef,
@@ -71,15 +60,11 @@ import {
import { useDebouncedCallback } from "use-debounce";
import z from "zod";
import { ColumnFilterButton } from "./ColumnFilterButton";
import { LabelColumnFilterButton } from "./LabelColumnFilterButton";
import { LabelsTableCell } from "./LabelsTableCell";
import { SitesColumnFilterButton } from "./SitesColumnFilterButton";
import { ControlledDataTable } from "./ui/controlled-data-table";
import { UptimeMiniBar } from "./UptimeMiniBar";
import { type SelectedLabel } from "./labels-selector";
import { LabelColumnFilterButton } from "./LabelColumnFilterButton";
import { useLocalLabels } from "@app/hooks/useLocalLabels";
import { LabelsTableCell } from "./LabelsTableCell";
import { useOptimisticLabels } from "@app/hooks/useOptimisticLabels";
import { refresh } from "next/cache";
import { SitesColumnFilterButton } from "./SitesColumnFilterButton";
export type TargetHealth = {
targetId: number;
@@ -123,6 +108,8 @@ type ProxyResourcesTableProps = {
pagination: PaginationState;
rowCount: number;
initialFilterSite?: Selectedsite | null;
/** Certificates prefetched on the server, keyed by full domain. */
initialCertificates?: GetBatchedCertificateResponse;
};
const booleanSearchFilterSchema = z
@@ -137,7 +124,7 @@ export default function PublicResourcesTable({
orgId,
pagination,
rowCount,
initialFilterSite = null
initialCertificates
}: ProxyResourcesTableProps) {
const router = useRouter();
const {
@@ -166,12 +153,6 @@ export default function PublicResourcesTable({
[resources]
);
// Domain list
const resourceDomains = useMemo(
() => resources.map((r) => r.fullDomain).filter(Boolean) as string[],
[resources]
);
const statusHistoryQuery = useQuery({
...orgQueries.batchedResourceStatusHistory({
orgId,
@@ -181,14 +162,6 @@ export default function PublicResourcesTable({
enabled: statusHistoryResourceIds.length > 0
});
const domainCertificatesQuery = useQuery({
...orgQueries.batchedDomainCertificates({
orgId,
domains: resourceDomains
}),
enabled: resourceDomains.length > 0
});
const refreshData = () => {
startTransition(() => {
try {
@@ -499,6 +472,9 @@ export default function PublicResourcesTable({
orgId={resourceRow.orgId}
domainId={domainId}
fullDomain={certHostname}
initialCertValue={
initialCertificates?.[certHostname]
}
/>
) : null}
<div className="">
@@ -668,7 +644,8 @@ export default function PublicResourcesTable({
t,
searchParams,
statusHistoryQuery.data,
statusHistoryQuery.isLoading
statusHistoryQuery.isLoading,
initialCertificates
]);
function handleFilterChange(
@@ -7,6 +7,7 @@ import {
PopoverContent
} from "@app/components/ui/popover";
import { useCertificate } from "@app/hooks/useCertificate";
import type { GetCertificateResponse } from "@server/routers/certificates/types";
import { cn } from "@app/lib/cn";
import { FileBadge } from "lucide-react";
import { useTranslations } from "next-intl";
@@ -22,7 +23,7 @@ type ResourceAccessCertIndicatorProps = {
orgId: string;
domainId: string;
fullDomain: string;
initialCertValue?: any;
initialCertValue?: GetCertificateResponse | null;
};
function getStatusColor(status: string) {
@@ -44,7 +45,8 @@ function getStatusColor(status: string) {
export function ResourceAccessCertIndicator({
orgId,
domainId,
fullDomain
fullDomain,
initialCertValue
}: ResourceAccessCertIndicatorProps) {
const t = useTranslations();
const [open, setOpen] = useState(false);
@@ -54,16 +56,19 @@ export function ResourceAccessCertIndicator({
orgId,
domainId,
fullDomain,
autoFetch: true,
initialCertValue,
polling: open,
pollingInterval: 5000
});
const { cert, certLoading, certError, refreshing, fetchCert } = certificate;
// `polling` only schedules on predefined intervals (1 second),
// so the first request would be a full interval away if open = true (which set polling = true).
// So we fetch immediately so the popover opens with fresh data.
useEffect(() => {
if (!open) return;
void fetchCert(false);
void fetchCert();
}, [open, fetchCert]);
const clearCloseTimer = useCallback(() => {
-1
View File
@@ -180,7 +180,6 @@ export default function ResourceInfoBox({}: ResourceInfoBoxType) {
orgId={resource.orgId}
domainId={resource.domainId!}
fullDomain={resource.fullDomain!}
autoFetch={true}
showLabel={false}
polling={true}
/>
-1
View File
@@ -182,7 +182,6 @@ export function SiteResourceInfoSections({
orgId={siteResource.orgId}
domainId={siteResource.domainId!}
fullDomain={siteResource.fullDomain!}
autoFetch={true}
showLabel={false}
polling={true}
/>