From bcebcfdfdf4c4ffc0699eee2b6aeb74a339b62af Mon Sep 17 00:00:00 2001 From: Mauricio Siu <47042324+Siumauricio@users.noreply.github.com> Date: Wed, 30 Apr 2025 01:13:30 -0600 Subject: [PATCH] Refactor ShowDomains component to enhance domain validation functionality with real-time feedback and improved UI. Integrate tooltips for domain details and validation status, and update API queries for better data handling. --- .../application/domains/show-domains.tsx | 326 ++++++++++++++---- .../compose/domains/show-domains.tsx | 4 +- 2 files changed, 266 insertions(+), 64 deletions(-) diff --git a/apps/dokploy/components/dashboard/application/domains/show-domains.tsx b/apps/dokploy/components/dashboard/application/domains/show-domains.tsx index 17dbc91f..c62f1c74 100644 --- a/apps/dokploy/components/dashboard/application/domains/show-domains.tsx +++ b/apps/dokploy/components/dashboard/application/domains/show-domains.tsx @@ -8,16 +8,49 @@ import { CardTitle, } from "@/components/ui/card"; import { api } from "@/utils/api"; -import { ExternalLink, GlobeIcon, PenBoxIcon, Trash2 } from "lucide-react"; +import { + CheckCircle2, + ExternalLink, + GlobeIcon, + InfoIcon, + Loader2, + PenBoxIcon, + RefreshCw, + Trash2, + XCircle, +} from "lucide-react"; import Link from "next/link"; import { toast } from "sonner"; import { AddDomain } from "./add-domain"; +import { useState } from "react"; +import { + Tooltip, + TooltipContent, + TooltipProvider, + TooltipTrigger, +} from "@/components/ui/tooltip"; +import type { ValidationStates } from "../../compose/domains/show-domains"; +import { DnsHelperModal } from "../../compose/domains/dns-helper-modal"; +import { Badge } from "@/components/ui/badge"; interface Props { applicationId: string; } export const ShowDomains = ({ applicationId }: Props) => { + const { data: application } = api.application.one.useQuery( + { + applicationId, + }, + { + enabled: !!applicationId, + }, + ); + const [validationStates, setValidationStates] = useState( + {}, + ); + const { data: ip } = api.settings.getIp.useQuery(); + const { data, refetch } = api.domain.byApplicationId.useQuery( { applicationId, @@ -26,10 +59,46 @@ export const ShowDomains = ({ applicationId }: Props) => { enabled: !!applicationId, }, ); - + const { mutateAsync: validateDomain } = + api.domain.validateDomain.useMutation(); const { mutateAsync: deleteDomain, isLoading: isRemoving } = api.domain.delete.useMutation(); + const handleValidateDomain = async (host: string) => { + setValidationStates((prev) => ({ + ...prev, + [host]: { isLoading: true }, + })); + + try { + const result = await validateDomain({ + domain: host, + serverIp: + application?.server?.ipAddress?.toString() || ip?.toString() || "", + }); + + setValidationStates((prev) => ({ + ...prev, + [host]: { + isLoading: false, + isValid: result.isValid, + error: result.error, + resolvedIp: result.resolvedIp, + }, + })); + } catch (err) { + const error = err as Error; + setValidationStates((prev) => ({ + ...prev, + [host]: { + isLoading: false, + isValid: false, + error: error.message || "Failed to validate domain", + }, + })); + } + }; + return (
@@ -68,73 +137,206 @@ export const ShowDomains = ({ applicationId }: Props) => {
) : ( -
+
{data?.map((item) => { + const validationState = validationStates[item.host]; return ( -
- - - {item.host} - - - + +
+ {/* Service & Domain Info */} +
+
+ + {item.host} + + +
+
+ {!item.host.includes("traefik.me") && ( + + )} + + + + { + await deleteDomain({ + domainId: item.domainId, + }) + .then((_data) => { + refetch(); + toast.success( + "Domain deleted successfully", + ); + }) + .catch(() => { + toast.error("Error deleting domain"); + }); + }} + > + + +
+
-
-
- {item.path} - {item.port} - {item.https ? "HTTPS" : "HTTP"} -
+ {/* Domain Details */} +
+ + + + + + Path: {item.path || "/"} + + + +

URL path for this service

+
+
+
-
- - - - { - await deleteDomain({ - domainId: item.domainId, - }) - .then(() => { - refetch(); - toast.success("Domain deleted successfully"); - }) - .catch(() => { - toast.error("Error deleting domain"); - }); - }} - > - - + + + + + + Port: {item.port} + + + +

Container port exposed

+
+
+
+ + + + + + {item.https ? "HTTPS" : "HTTP"} + + + +

+ {item.https + ? "Secure HTTPS connection" + : "Standard HTTP connection"} +

+
+
+
+ + {item.certificateType && ( + + + + + Cert: {item.certificateType} + + + +

SSL Certificate Provider

+
+
+
+ )} + + + + + + handleValidateDomain(item.host) + } + > + {validationState?.isLoading ? ( + <> + + Checking DNS... + + ) : validationState?.isValid ? ( + <> + + {"DNS Valid"} + + ) : validationState?.error ? ( + <> + + {validationState.error} + + ) : ( + <> + + Validate DNS + + )} + + + + {validationState?.error ? ( +
+

+ Error: +

+

{validationState.error}

+
+ ) : ( + "Click to validate DNS configuration" + )} +
+
+
+
-
-
+
+ ); })}
diff --git a/apps/dokploy/components/dashboard/compose/domains/show-domains.tsx b/apps/dokploy/components/dashboard/compose/domains/show-domains.tsx index d6c0c332..85ae3d9a 100644 --- a/apps/dokploy/components/dashboard/compose/domains/show-domains.tsx +++ b/apps/dokploy/components/dashboard/compose/domains/show-domains.tsx @@ -37,14 +37,14 @@ interface Props { composeId: string; } -type ValidationState = { +export type ValidationState = { isLoading: boolean; isValid?: boolean; error?: string; resolvedIp?: string; }; -type ValidationStates = { +export type ValidationStates = { [key: string]: ValidationState; };