Add alert about domain error

This commit is contained in:
Owen
2026-03-11 17:59:28 -07:00
parent 74f4751bcc
commit f021b73458
7 changed files with 68 additions and 6 deletions

View File

@@ -2681,5 +2681,6 @@
"approvalsEmptyStateStep2Title": "Enable Device Approvals", "approvalsEmptyStateStep2Title": "Enable Device Approvals",
"approvalsEmptyStateStep2Description": "Edit a role and enable the 'Require Device Approvals' option. Users with this role will need admin approval for new devices.", "approvalsEmptyStateStep2Description": "Edit a role and enable the 'Require Device Approvals' option. Users with this role will need admin approval for new devices.",
"approvalsEmptyStatePreviewDescription": "Preview: When enabled, pending device requests will appear here for review", "approvalsEmptyStatePreviewDescription": "Preview: When enabled, pending device requests will appear here for review",
"approvalsEmptyStateButtonText": "Manage Roles" "approvalsEmptyStateButtonText": "Manage Roles",
"domainErrorTitle": "We are having trouble verifying your domain"
} }

View File

@@ -22,7 +22,8 @@ export const domains = pgTable("domains", {
tries: integer("tries").notNull().default(0), tries: integer("tries").notNull().default(0),
certResolver: varchar("certResolver"), certResolver: varchar("certResolver"),
customCertResolver: varchar("customCertResolver"), customCertResolver: varchar("customCertResolver"),
preferWildcardCert: boolean("preferWildcardCert") preferWildcardCert: boolean("preferWildcardCert"),
errorMessage: text("errorMessage")
}); });
export const dnsRecords = pgTable("dnsRecords", { export const dnsRecords = pgTable("dnsRecords", {

View File

@@ -13,7 +13,8 @@ export const domains = sqliteTable("domains", {
failed: integer("failed", { mode: "boolean" }).notNull().default(false), failed: integer("failed", { mode: "boolean" }).notNull().default(false),
tries: integer("tries").notNull().default(0), tries: integer("tries").notNull().default(0),
certResolver: text("certResolver"), certResolver: text("certResolver"),
preferWildcardCert: integer("preferWildcardCert", { mode: "boolean" }) preferWildcardCert: integer("preferWildcardCert", { mode: "boolean" }),
errorMessage: text("errorMessage")
}); });
export const dnsRecords = sqliteTable("dnsRecords", { export const dnsRecords = sqliteTable("dnsRecords", {

View File

@@ -40,7 +40,8 @@ async function queryDomains(orgId: string, limit: number, offset: number) {
tries: domains.tries, tries: domains.tries,
configManaged: domains.configManaged, configManaged: domains.configManaged,
certResolver: domains.certResolver, certResolver: domains.certResolver,
preferWildcardCert: domains.preferWildcardCert preferWildcardCert: domains.preferWildcardCert,
errorMessage: domains.errorMessage
}) })
.from(orgDomains) .from(orgDomains)
.where(eq(orgDomains.orgId, orgId)) .where(eq(orgDomains.orgId, orgId))

View File

@@ -69,6 +69,7 @@ export default async function DomainSettingsPage({
failed={domain.failed} failed={domain.failed}
verified={domain.verified} verified={domain.verified}
type={domain.type} type={domain.type}
errorMessage={domain.errorMessage}
/> />
<DNSRecordsTable records={dnsRecords} type={domain.type} /> <DNSRecordsTable records={dnsRecords} type={domain.type} />

View File

@@ -10,17 +10,20 @@ import {
import { useTranslations } from "next-intl"; import { useTranslations } from "next-intl";
import { Badge } from "./ui/badge"; import { Badge } from "./ui/badge";
import { useEnvContext } from "@app/hooks/useEnvContext"; import { useEnvContext } from "@app/hooks/useEnvContext";
import { AlertTriangle } from "lucide-react";
type DomainInfoCardProps = { type DomainInfoCardProps = {
failed: boolean; failed: boolean;
verified: boolean; verified: boolean;
type: string | null; type: string | null;
errorMessage?: string | null;
}; };
export default function DomainInfoCard({ export default function DomainInfoCard({
failed, failed,
verified, verified,
type type,
errorMessage
}: DomainInfoCardProps) { }: DomainInfoCardProps) {
const t = useTranslations(); const t = useTranslations();
const env = useEnvContext(); const env = useEnvContext();
@@ -39,6 +42,7 @@ export default function DomainInfoCard({
}; };
return ( return (
<div className="space-y-3">
<Alert> <Alert>
<AlertDescription> <AlertDescription>
<InfoSections cols={3}> <InfoSections cols={3}>
@@ -79,5 +83,19 @@ export default function DomainInfoCard({
</InfoSections> </InfoSections>
</AlertDescription> </AlertDescription>
</Alert> </Alert>
{errorMessage && (failed || !verified) && (
<Alert variant={failed ? "destructive" : "warning"}>
<AlertTriangle className="h-4 w-4" />
<AlertTitle>
{failed
? t("domainErrorTitle", { fallback: "Domain Error" })
: t("domainPendingErrorTitle", { fallback: "Verification Issue" })}
</AlertTitle>
<AlertDescription className="font-mono text-xs break-all">
{errorMessage}
</AlertDescription>
</Alert>
)}
</div>
); );
} }

View File

@@ -27,6 +27,12 @@ import {
DropdownMenuItem, DropdownMenuItem,
DropdownMenuTrigger DropdownMenuTrigger
} from "./ui/dropdown-menu"; } from "./ui/dropdown-menu";
import {
Tooltip,
TooltipContent,
TooltipProvider,
TooltipTrigger
} from "./ui/tooltip";
import Link from "next/link"; import Link from "next/link";
export type DomainRow = { export type DomainRow = {
@@ -39,6 +45,7 @@ export type DomainRow = {
configManaged: boolean; configManaged: boolean;
certResolver: string; certResolver: string;
preferWildcardCert: boolean; preferWildcardCert: boolean;
errorMessage?: string | null;
}; };
type Props = { type Props = {
@@ -175,7 +182,7 @@ export default function DomainsTable({ domains, orgId }: Props) {
); );
}, },
cell: ({ row }) => { cell: ({ row }) => {
const { verified, failed, type } = row.original; const { verified, failed, type, errorMessage } = row.original;
if (verified) { if (verified) {
return type == "wildcard" ? ( return type == "wildcard" ? (
<Badge variant="outlinePrimary">{t("manual")}</Badge> <Badge variant="outlinePrimary">{t("manual")}</Badge>
@@ -183,12 +190,44 @@ export default function DomainsTable({ domains, orgId }: Props) {
<Badge variant="green">{t("verified")}</Badge> <Badge variant="green">{t("verified")}</Badge>
); );
} else if (failed) { } else if (failed) {
if (errorMessage) {
return (
<TooltipProvider>
<Tooltip>
<TooltipTrigger asChild>
<Badge variant="red" className="cursor-help">
{t("failed", { fallback: "Failed" })}
</Badge>
</TooltipTrigger>
<TooltipContent className="max-w-xs">
<p className="break-words">{errorMessage}</p>
</TooltipContent>
</Tooltip>
</TooltipProvider>
);
}
return ( return (
<Badge variant="red"> <Badge variant="red">
{t("failed", { fallback: "Failed" })} {t("failed", { fallback: "Failed" })}
</Badge> </Badge>
); );
} else { } else {
if (errorMessage) {
return (
<TooltipProvider>
<Tooltip>
<TooltipTrigger asChild>
<Badge variant="yellow" className="cursor-help">
{t("pending")}
</Badge>
</TooltipTrigger>
<TooltipContent className="max-w-xs">
<p className="break-words">{errorMessage}</p>
</TooltipContent>
</Tooltip>
</TooltipProvider>
);
}
return <Badge variant="yellow">{t("pending")}</Badge>; return <Badge variant="yellow">{t("pending")}</Badge>;
} }
} }