diff --git a/client/src/components/domain/domain-details.bk.tsx b/client/src/components/domain/domain-details.bk.tsx new file mode 100644 index 0000000..bbec394 --- /dev/null +++ b/client/src/components/domain/domain-details.bk.tsx @@ -0,0 +1,976 @@ +import { useState } from "react"; +import { useQuery, useMutation } from "@tanstack/react-query"; +import { DnsRecord, InsertDnsRecord, Domain, Provider, recordTypes } from "@shared/schema"; +import { apiRequest, queryClient } from "@/lib/queryClient"; +import { useToast } from "@/hooks/use-toast"; +import { useForm } from "react-hook-form"; +import { zodResolver } from "@hookform/resolvers/zod"; +import { z } from "zod"; +import { + Table, + TableBody, + TableCell, + TableHead, + TableHeader, + TableRow, +} from "@/components/ui/table"; +import { + Dialog, + DialogContent, + DialogDescription, + DialogFooter, + DialogHeader, + DialogTitle, +} from "@/components/ui/dialog"; +import { + Form, + FormControl, + FormField, + FormItem, + FormLabel, + FormMessage, + FormDescription, +} from "@/components/ui/form"; +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "@/components/ui/select"; +import { + AlertDialog, + AlertDialogAction, + AlertDialogCancel, + AlertDialogContent, + AlertDialogDescription, + AlertDialogFooter, + AlertDialogHeader, + AlertDialogTitle, +} from "@/components/ui/alert-dialog"; +import { Button } from "@/components/ui/button"; +import { Input } from "@/components/ui/input"; +import { Switch } from "@/components/ui/switch"; +import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; +import { Badge } from "@/components/ui/badge"; +import { + Tooltip, + TooltipContent, + TooltipProvider, + TooltipTrigger +} from "@/components/ui/tooltip"; +import { RecentActivity } from "@/components/activity/recent-activity"; +import { Pagination } from "@/components/shared/pagination"; +import { Loader2, FileText, Pencil, Trash2, Plus } from "lucide-react"; +import { formatDistanceToNow } from "date-fns"; + +// DNS Record form schema +const dnsRecordSchema = z.object({ + name: z.string().min(1, "Record name is required"), + type: z.enum(recordTypes), + content: z.string().optional().or(z.literal('')), + ttl: z.number().int().min(1).default(3600), + proxied: z.boolean().default(false), + isActive: z.boolean().default(true), + isAutoIP: z.boolean().default(false), + notes: z.string().optional(), +}).refine(data => { + // Different validation based on record type + if ((data.type === 'A' || data.type === 'AAAA') && data.isAutoIP) { + // Auto IP enabled, content not required + return true; + } else if (data.type === 'A') { + // A record - validate IPv4 + return !!data.content && /^(\d{1,3}\.){3}\d{1,3}$/.test(data.content.trim()); + } else if (data.type === 'AAAA') { + // AAAA record - accept any non-empty content for IPv6 (simplified validation) + return !!data.content && data.content.trim().length > 0; + } else if (data.type === 'CNAME' || data.type === 'MX' || data.type === 'NS') { + // Domain-based records - ensure content is present and looks like domain + return !!data.content && data.content.trim().length > 0; + } else { + // For all other types, just ensure content is non-empty + return !!data.content && data.content.trim().length > 0; + } +}, { + message: "Content is required and must be valid for the selected record type", + path: ["content"] +}); + +interface DomainDetailsProps { + domain: Domain; + onBack?: () => void; +} + +export function DomainDetails({ domain, onBack }: DomainDetailsProps) { + const { toast } = useToast(); + + const [isAddRecordDialogOpen, setIsAddRecordDialogOpen] = useState(false); + const [isEditRecordDialogOpen, setIsEditRecordDialogOpen] = useState(false); + const [isDeleteDialogOpen, setIsDeleteDialogOpen] = useState(false); + const [selectedRecord, setSelectedRecord] = useState(null); + const [currentPage, setCurrentPage] = useState(1); + const pageSize = 5; + + // Fetch providers for the form + const { data: providers = [] } = useQuery({ + queryKey: ["/api/providers"], + }); + + // Fetch DNS Records + const { + data: records = [], + isLoading + } = useQuery({ + queryKey: ["/api/dns-records", domain.id], + queryFn: async () => { + const res = await apiRequest("GET", `/api/dns-records?domainId=${domain.id}`); + return await res.json(); + }, + enabled: !!domain?.id, + }); + + // Form for adding/editing a record + const form = useForm>({ + resolver: zodResolver(dnsRecordSchema), + defaultValues: { + name: "", + type: "A", + content: "", + ttl: 3600, + proxied: false, + isActive: true, + isAutoIP: false, + notes: "", + }, + }); + + // Add record mutation + const addRecordMutation = useMutation({ + mutationFn: async (data: z.infer) => { + // Prepare record data + const recordData: InsertDnsRecord = { + ...data, + domainId: domain.id, + // Inherit providerId from domain + providerId: domain.providerId + }; + + const res = await apiRequest("POST", "/api/dns-records", recordData); + return await res.json(); + }, + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: ["/api/dns-records", domain.id] }); + setIsAddRecordDialogOpen(false); + form.reset(); + toast({ + title: "Record added", + description: "The DNS record has been successfully added.", + }); + }, + onError: (error) => { + toast({ + title: "Failed to add record", + description: error.message, + variant: "destructive", + }); + }, + }); + + // Update record mutation + const updateRecordMutation = useMutation({ + mutationFn: async (data: z.infer & { id: string }) => { + const { id, ...updateData } = data; + + // Always use domain's provider for the record + const updatedData = { + ...updateData, + providerId: domain.providerId + }; + + const res = await apiRequest("PUT", `/api/dns-records/${id}`, updatedData); + return await res.json(); + }, + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: ["/api/dns-records", domain.id] }); + setIsEditRecordDialogOpen(false); + setSelectedRecord(null); + toast({ + title: "Record updated", + description: "The DNS record has been successfully updated.", + }); + }, + onError: (error) => { + toast({ + title: "Failed to update record", + description: error.message, + variant: "destructive", + }); + }, + }); + + // Delete record mutation + const deleteRecordMutation = useMutation({ + mutationFn: async (recordId: string) => { + await apiRequest("DELETE", `/api/dns-records/${recordId}`); + }, + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: ["/api/dns-records", domain.id] }); + setIsDeleteDialogOpen(false); + setSelectedRecord(null); + toast({ + title: "Record deleted", + description: "The DNS record has been successfully deleted.", + }); + }, + onError: (error) => { + toast({ + title: "Failed to delete record", + description: error.message, + variant: "destructive", + }); + }, + }); + + // Form submission handlers + const onAddSubmit = (data: z.infer) => { + addRecordMutation.mutate(data); + }; + + const onEditSubmit = (data: z.infer) => { + if (selectedRecord) { + updateRecordMutation.mutate({ ...data, id: selectedRecord.id }); + } + }; + + // Handle record editing + const handleEditRecord = (record: DnsRecord) => { + setSelectedRecord(record); + + // Set form values for editing + form.reset({ + name: record.name, + type: record.type as any, // Type cast to handle compatibility + content: record.content, + ttl: record.ttl as number, // Type cast to handle nullable + proxied: record.proxied as boolean, // Type cast to handle nullable + isActive: record.isActive, + isAutoIP: record.isAutoIP, + notes: record.notes || "", + }); + + setIsEditRecordDialogOpen(true); + }; + + // Handle record deletion + const handleDeleteRecord = (record: DnsRecord) => { + setSelectedRecord(record); + setIsDeleteDialogOpen(true); + }; + + // Confirm deletion + const confirmDeleteRecord = () => { + if (selectedRecord) { + deleteRecordMutation.mutate(selectedRecord.id); + } + }; + + // Get a human-readable provider name from the provider ID + const getProviderName = (providerId: string | null) => { + if (!providerId) return "None"; + const provider = providers.find(p => p.id === providerId); + return provider ? provider.name : "Unknown Provider"; + }; + + // Calculate pagination + const startIndex = (currentPage - 1) * pageSize; + const endIndex = startIndex + pageSize; + const paginatedRecords = records.slice(startIndex, endIndex); + const totalPages = Math.ceil(records.length / pageSize); + + // Handle adding a new record + const handleAddRecord = () => { + // Reset form with defaults + form.reset({ + name: "", + type: "A", + content: "", + ttl: 3600, + proxied: false, + isActive: true, + isAutoIP: false, + notes: "", + }); + setIsAddRecordDialogOpen(true); + }; + + if (isLoading) { + return ( +
+ +
+ ); + } + + return ( + <> +
+

DNS Records for {domain.name}

+ +
+ + {records.length === 0 ? ( +
+
+ +
+

No DNS records found

+

+ Get started by adding your first DNS record. +

+
+ +
+
+ ) : ( +
+
+ + + + Name + Type + Content + TTL + Provider + Last Update + Status + Actions + + + + {paginatedRecords.map(record => ( + + + {record.name === "@" ? ( + + + + {record.name} + + +

Root domain: {domain.name}

+
+
+
+ ) : ( + record.name + )} +
+ + {record.type} + + + + + + + {record.isAutoIP ? "Auto IP" : record.content} + + + +

{record.isAutoIP ? "Auto IP" : record.content}

+ {record.notes && ( + <> +
+

{record.notes}

+ + )} +
+
+
+
+ {record.ttl}s + {getProviderName(record.providerId)} + + {record.lastUpdated + ? formatDistanceToNow(new Date(record.lastUpdated), { addSuffix: true }) + : "Never"} + + + {record.isActive ? ( + Active + ) : ( + Inactive + )} + + +
+ + +
+
+
+ ))} +
+
+
+ + {records.length > pageSize && ( +
+ +
+ )} +
+ )} + +
+ + + Recent Activity + + + + + +
+ + {/* Add Record Dialog */} + + + + Add DNS Record + + Add a new DNS record to {domain.name}. + + + +
+ + ( + + Name + + + + + Use @ for root domain or enter subdomain name + + + + )} + /> + + ( + + DNS Provider + + + Select the DNS provider to use for this record + + + + )} + /> + + ( + + Record Type + + + + )} + /> + + {(form.watch("type") === "A" || form.watch("type") === "AAAA") && ( + ( + +
+ Auto IP Address + + Automatically determine IP address using STUN + +
+ + + +
+ )} + /> + )} + + {(!form.watch("isAutoIP") || (form.watch("type") !== "A" && form.watch("type") !== "AAAA")) && ( + ( + + Content + + + + + {form.watch("type") === "A" && "IP address (e.g. 192.168.1.1)"} + {form.watch("type") === "AAAA" && "IPv6 address"} + {form.watch("type") === "CNAME" && "Domain name (e.g. example.com)"} + {form.watch("type") === "MX" && "Mail server (e.g. mail.example.com)"} + {form.watch("type") === "TXT" && "Text content"} + + + + )} + /> + )} + + ( + + TTL (seconds) + + field.onChange(parseInt(e.target.value || "3600"))} + /> + + + Time-to-live in seconds. 3600 = 1 hour + + + + )} + /> + + ( + +
+ Proxied + + Enable proxying through CDN (Cloudflare only) + +
+ + + +
+ )} + /> + + ( + +
+ Active + + Enable this record to be used + +
+ + + +
+ )} + /> + + ( + + Notes + + + + + Add any additional information about this record + + + + )} + /> + + + + + + +
+
+ + {/* Edit Record Dialog */} + + + + Edit DNS Record + + Update the DNS record for {domain.name}. + + + +
+ + ( + + Name + + + + + Use @ for root domain or enter subdomain name + + + + )} + /> + + ( + + DNS Provider + + + Select the DNS provider to use for this record + + + + )} + /> + + ( + + Record Type + + + + )} + /> + + {(form.watch("type") === "A" || form.watch("type") === "AAAA") && ( + ( + +
+ Auto IP Address + + Automatically determine IP address using STUN + +
+ + + +
+ )} + /> + )} + + {(!form.watch("isAutoIP") || (form.watch("type") !== "A" && form.watch("type") !== "AAAA")) && ( + ( + + Content + + + + + {form.watch("type") === "A" && "IP address (e.g. 192.168.1.1)"} + {form.watch("type") === "AAAA" && "IPv6 address"} + {form.watch("type") === "CNAME" && "Domain name (e.g. example.com)"} + {form.watch("type") === "MX" && "Mail server (e.g. mail.example.com)"} + {form.watch("type") === "TXT" && "Text content"} + + + + )} + /> + )} + + ( + + TTL (seconds) + + field.onChange(parseInt(e.target.value || "3600"))} + /> + + + Time-to-live in seconds. 3600 = 1 hour + + + + )} + /> + + ( + +
+ Proxied + + Enable proxying through CDN (Cloudflare only) + +
+ + + +
+ )} + /> + + ( + +
+ Active + + Enable this record to be used + +
+ + + +
+ )} + /> + + ( + + Notes + + + + + Add any additional information about this record + + + + )} + /> + + + + + + +
+
+ + {/* Delete Confirmation Dialog */} + + + + Are you sure? + + This will permanently delete the DNS record {selectedRecord?.name} of type {selectedRecord?.type}. + This action cannot be undone. + + + + Cancel + + {deleteRecordMutation.isPending ? ( + <> + + Deleting... + + ) : ( + "Delete" + )} + + + + + + ); +} \ No newline at end of file diff --git a/client/src/components/domain/domain-details.optimized.tsx b/client/src/components/domain/domain-details.optimized.tsx new file mode 100644 index 0000000..f774f31 --- /dev/null +++ b/client/src/components/domain/domain-details.optimized.tsx @@ -0,0 +1,913 @@ +import { useState } from "react"; +import { useQuery, useMutation } from "@tanstack/react-query"; +import { DnsRecord, InsertDnsRecord, Domain, Provider, recordTypes } from "@shared/schema"; +import { apiRequest, queryClient } from "@/lib/queryClient"; +import { useToast } from "@/hooks/use-toast"; +import { useForm } from "react-hook-form"; +import { zodResolver } from "@hookform/resolvers/zod"; +import { z } from "zod"; +import { + Table, + TableBody, + TableCell, + TableHead, + TableHeader, + TableRow, +} from "@/components/ui/table"; +import { + Dialog, + DialogContent, + DialogDescription, + DialogFooter, + DialogHeader, + DialogTitle, +} from "@/components/ui/dialog"; +import { + Form, + FormControl, + FormField, + FormItem, + FormLabel, + FormMessage, + FormDescription, +} from "@/components/ui/form"; +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "@/components/ui/select"; +import { + AlertDialog, + AlertDialogAction, + AlertDialogCancel, + AlertDialogContent, + AlertDialogDescription, + AlertDialogFooter, + AlertDialogHeader, + AlertDialogTitle, +} from "@/components/ui/alert-dialog"; +import { Button } from "@/components/ui/button"; +import { Input } from "@/components/ui/input"; +import { Switch } from "@/components/ui/switch"; +import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; +import { Badge } from "@/components/ui/badge"; +import { + Tooltip, + TooltipContent, + TooltipProvider, + TooltipTrigger +} from "@/components/ui/tooltip"; +import { RecentActivity } from "@/components/activity/recent-activity"; +import { Pagination } from "@/components/shared/pagination"; +import { Loader2, FileText, Pencil, Trash2, Plus } from "lucide-react"; +import { formatDistanceToNow } from "date-fns"; + +// DNS Record form schema +const dnsRecordSchema = z.object({ + name: z.string().min(1, "Record name is required"), + type: z.enum(recordTypes), + content: z.string().optional().or(z.literal('')), + ttl: z.number().int().min(1).default(3600), + proxied: z.boolean().default(false), + isActive: z.boolean().default(true), + isAutoIP: z.boolean().default(false), + notes: z.string().optional(), +}).refine(data => { + // Different validation based on record type + if ((data.type === 'A' || data.type === 'AAAA') && data.isAutoIP) { + // Auto IP enabled, content not required + return true; + } else if (data.type === 'A') { + // A record - validate IPv4 + return !!data.content && /^(\d{1,3}\.){3}\d{1,3}$/.test(data.content.trim()); + } else if (data.type === 'AAAA') { + // AAAA record - accept any non-empty content for IPv6 (simplified validation) + return !!data.content && data.content.trim().length > 0; + } else if (data.type === 'CNAME' || data.type === 'MX' || data.type === 'NS') { + // Domain-based records - ensure content is present and looks like domain + return !!data.content && data.content.trim().length > 0; + } else { + // For all other types, just ensure content is non-empty + return !!data.content && data.content.trim().length > 0; + } +}, { + message: "Content is required and must be valid for the selected record type", + path: ["content"] +}); + +interface DomainDetailsProps { + domain: Domain; + onBack?: () => void; +} + +export function DomainDetails({ domain, onBack }: DomainDetailsProps) { + const { toast } = useToast(); + + const [isAddRecordDialogOpen, setIsAddRecordDialogOpen] = useState(false); + const [isEditRecordDialogOpen, setIsEditRecordDialogOpen] = useState(false); + const [isDeleteDialogOpen, setIsDeleteDialogOpen] = useState(false); + const [selectedRecord, setSelectedRecord] = useState(null); + const [currentPage, setCurrentPage] = useState(1); + const pageSize = 5; + + // Fetch providers for the form + const { data: providers = [] } = useQuery({ + queryKey: ["/api/providers"], + }); + + // Fetch DNS Records + const { + data: records = [], + isLoading + } = useQuery({ + queryKey: ["/api/dns-records", domain.id], + queryFn: async () => { + const res = await apiRequest("GET", `/api/dns-records?domainId=${domain.id}`); + return await res.json(); + }, + enabled: !!domain?.id, + }); + + // Form for adding/editing a record + const form = useForm>({ + resolver: zodResolver(dnsRecordSchema), + defaultValues: { + name: "", + type: "A", + content: "", + ttl: 3600, + proxied: false, + isActive: true, + isAutoIP: false, + notes: "", + }, + }); + + // Add record mutation + const addRecordMutation = useMutation({ + mutationFn: async (data: z.infer) => { + // Prepare record data + const recordData: InsertDnsRecord = { + ...data, + domainId: domain.id, + // Inherit providerId from domain + providerId: domain.providerId + }; + + const res = await apiRequest("POST", "/api/dns-records", recordData); + return await res.json(); + }, + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: ["/api/dns-records", domain.id] }); + setIsAddRecordDialogOpen(false); + form.reset(); + toast({ + title: "Record added", + description: "The DNS record has been successfully added.", + }); + }, + onError: (error) => { + toast({ + title: "Failed to add record", + description: error.message, + variant: "destructive", + }); + }, + }); + + // Update record mutation + const updateRecordMutation = useMutation({ + mutationFn: async (data: z.infer & { id: string }) => { + const { id, ...updateData } = data; + + // Always use domain's provider for the record + const updatedData = { + ...updateData, + providerId: domain.providerId + }; + + const res = await apiRequest("PUT", `/api/dns-records/${id}`, updatedData); + return await res.json(); + }, + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: ["/api/dns-records", domain.id] }); + setIsEditRecordDialogOpen(false); + setSelectedRecord(null); + toast({ + title: "Record updated", + description: "The DNS record has been successfully updated.", + }); + }, + onError: (error) => { + toast({ + title: "Failed to update record", + description: error.message, + variant: "destructive", + }); + }, + }); + + // Delete record mutation + const deleteRecordMutation = useMutation({ + mutationFn: async (recordId: string) => { + await apiRequest("DELETE", `/api/dns-records/${recordId}`); + }, + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: ["/api/dns-records", domain.id] }); + setIsDeleteDialogOpen(false); + setSelectedRecord(null); + toast({ + title: "Record deleted", + description: "The DNS record has been successfully deleted.", + }); + }, + onError: (error) => { + toast({ + title: "Failed to delete record", + description: error.message, + variant: "destructive", + }); + }, + }); + + // Form submission handlers + const onAddSubmit = (data: z.infer) => { + addRecordMutation.mutate(data); + }; + + const onEditSubmit = (data: z.infer) => { + if (selectedRecord) { + updateRecordMutation.mutate({ ...data, id: selectedRecord.id }); + } + }; + + // Handle record editing + const handleEditRecord = (record: DnsRecord) => { + setSelectedRecord(record); + + // Set form values for editing + form.reset({ + name: record.name, + type: record.type as any, // Type cast to handle compatibility + content: record.content, + ttl: record.ttl as number, // Type cast to handle nullable + proxied: record.proxied as boolean, // Type cast to handle nullable + isActive: record.isActive, + isAutoIP: record.isAutoIP, + notes: record.notes || "", + }); + + setIsEditRecordDialogOpen(true); + }; + + // Handle record deletion + const handleDeleteRecord = (record: DnsRecord) => { + setSelectedRecord(record); + setIsDeleteDialogOpen(true); + }; + + // Confirm deletion + const confirmDeleteRecord = () => { + if (selectedRecord) { + deleteRecordMutation.mutate(selectedRecord.id); + } + }; + + // Get a human-readable provider name from the provider ID + const getProviderName = (providerId: string | null) => { + if (!providerId) return "None"; + const provider = providers.find(p => p.id === providerId); + return provider ? provider.name : "Unknown Provider"; + }; + + // Calculate pagination + const startIndex = (currentPage - 1) * pageSize; + const endIndex = startIndex + pageSize; + const paginatedRecords = records.slice(startIndex, endIndex); + const totalPages = Math.ceil(records.length / pageSize); + + // Handle adding a new record + const handleAddRecord = () => { + // Reset form with defaults + form.reset({ + name: "", + type: "A", + content: "", + ttl: 3600, + proxied: false, + isActive: true, + isAutoIP: false, + notes: "", + }); + setIsAddRecordDialogOpen(true); + }; + + if (isLoading) { + return ( +
+ +
+ ); + } + + return ( + <> +
+

DNS Records for {domain.name}

+ +
+ + {records.length === 0 ? ( +
+
+ +
+

No DNS records found

+

+ Get started by adding your first DNS record. +

+
+ +
+
+ ) : ( +
+
+ + + + Name + Type + Content + TTL + Provider + Last Update + Status + Actions + + + + {paginatedRecords.map(record => ( + + + {record.name === "@" ? ( + + + + {record.name} + + +

Root domain: {domain.name}

+
+
+
+ ) : ( + record.name + )} +
+ + {record.type} + + + + + + + {record.isAutoIP ? "Auto IP" : record.content} + + + +

{record.isAutoIP ? "Auto IP" : record.content}

+ {record.notes && ( + <> +
+

{record.notes}

+ + )} +
+
+
+
+ {record.ttl}s + {getProviderName(record.providerId)} + + {record.lastUpdated + ? formatDistanceToNow(new Date(record.lastUpdated), { addSuffix: true }) + : "Never"} + + + {record.isActive ? ( + Active + ) : ( + Inactive + )} + + +
+ + +
+
+
+ ))} +
+
+
+ + {records.length > pageSize && ( +
+ +
+ )} +
+ )} + +
+ + + Recent Activity + + + + + +
+ + {/* Add Record Dialog */} + + + + Add DNS Record + + Add a new DNS record to {domain.name}. + + + +
+ + ( + + Name + + + + + Use @ for root domain or enter subdomain name + + + + )} + /> + + ( + + Record Type + + + + )} + /> + + {/* Moved proxied toggle above isAutoIP */} + ( + +
+ Proxied + + Enable proxying through CDN (Cloudflare only) + +
+ + + +
+ )} + /> + + {(form.watch("type") === "A" || form.watch("type") === "AAAA") && ( + ( + +
+ Auto IP Address + + Automatically determine IP address using STUN + +
+ + + +
+ )} + /> + )} + + {(!form.watch("isAutoIP") || (form.watch("type") !== "A" && form.watch("type") !== "AAAA")) && ( + ( + + Content + + + + + The content of the DNS record (e.g. IP address, domain name) + + + + )} + /> + )} + + ( + + TTL (seconds) + + field.onChange(parseInt(e.target.value))} + /> + + + Time to live in seconds, how long DNS servers should cache this record + + + + )} + /> + + ( + +
+ Active + + Enable or disable this DNS record + +
+ + + +
+ )} + /> + + ( + + Notes + + + + + Add optional notes about this record (for internal reference only) + + + + )} + /> + + + + + + + +
+
+ + {/* Edit Record Dialog */} + + + + Edit DNS Record + + Update the DNS record for {domain.name}. + + + +
+ + ( + + Name + + + + + Use @ for root domain or enter subdomain name + + + + )} + /> + + ( + + Record Type + + + + )} + /> + + {/* Moved proxied toggle above isAutoIP */} + ( + +
+ Proxied + + Enable proxying through CDN (Cloudflare only) + +
+ + + +
+ )} + /> + + {(form.watch("type") === "A" || form.watch("type") === "AAAA") && ( + ( + +
+ Auto IP Address + + Automatically determine IP address using STUN + +
+ + + +
+ )} + /> + )} + + {(!form.watch("isAutoIP") || (form.watch("type") !== "A" && form.watch("type") !== "AAAA")) && ( + ( + + Content + + + + + The content of the DNS record (e.g. IP address, domain name) + + + + )} + /> + )} + + ( + + TTL (seconds) + + field.onChange(parseInt(e.target.value))} + /> + + + Time to live in seconds, how long DNS servers should cache this record + + + + )} + /> + + ( + +
+ Active + + Enable or disable this DNS record + +
+ + + +
+ )} + /> + + ( + + Notes + + + + + Add optional notes about this record (for internal reference only) + + + + )} + /> + + + + + + + +
+
+ + {/* Delete Confirmation Dialog */} + + + + Are you sure? + + This will permanently delete the DNS record "{selectedRecord?.name}" + of type {selectedRecord?.type}. This action cannot be undone. + + + + Cancel + + {deleteRecordMutation.isPending && ( + + )} + Delete + + + + + + {/* Back Button */} + {onBack && ( +
+ +
+ )} + + ); +} \ No newline at end of file diff --git a/client/src/components/domain/domain-details.tsx b/client/src/components/domain/domain-details.tsx index bbec394..f774f31 100644 --- a/client/src/components/domain/domain-details.tsx +++ b/client/src/components/domain/domain-details.tsx @@ -487,41 +487,6 @@ export function DomainDetails({ domain, onBack }: DomainDetailsProps) { )} /> - ( - - DNS Provider - - - Select the DNS provider to use for this record - - - - )} - /> - + {/* Moved proxied toggle above isAutoIP */} + ( + +
+ Proxied + + Enable proxying through CDN (Cloudflare only) + +
+ + + +
+ )} + /> + {(form.watch("type") === "A" || form.watch("type") === "AAAA") && ( Content - + - {form.watch("type") === "A" && "IP address (e.g. 192.168.1.1)"} - {form.watch("type") === "AAAA" && "IPv6 address"} - {form.watch("type") === "CNAME" && "Domain name (e.g. example.com)"} - {form.watch("type") === "MX" && "Mail server (e.g. mail.example.com)"} - {form.watch("type") === "TXT" && "Text content"} + The content of the DNS record (e.g. IP address, domain name) @@ -608,40 +591,19 @@ export function DomainDetails({ domain, onBack }: DomainDetailsProps) { field.onChange(parseInt(e.target.value || "3600"))} + onChange={e => field.onChange(parseInt(e.target.value))} /> - Time-to-live in seconds. 3600 = 1 hour + Time to live in seconds, how long DNS servers should cache this record )} /> - ( - -
- Proxied - - Enable proxying through CDN (Cloudflare only) - -
- - - -
- )} - /> - Active - Enable this record to be used + Enable or disable this DNS record @@ -673,7 +635,7 @@ export function DomainDetails({ domain, onBack }: DomainDetailsProps) { - Add any additional information about this record + Add optional notes about this record (for internal reference only) @@ -682,24 +644,27 @@ export function DomainDetails({ domain, onBack }: DomainDetailsProps) { + - + {/* Edit Record Dialog */} @@ -729,41 +694,6 @@ export function DomainDetails({ domain, onBack }: DomainDetailsProps) { )} /> - ( - - DNS Provider - - - Select the DNS provider to use for this record - - - - )} - /> - + {/* Moved proxied toggle above isAutoIP */} + ( + +
+ Proxied + + Enable proxying through CDN (Cloudflare only) + +
+ + + +
+ )} + /> + {(form.watch("type") === "A" || form.watch("type") === "AAAA") && ( Content - + - {form.watch("type") === "A" && "IP address (e.g. 192.168.1.1)"} - {form.watch("type") === "AAAA" && "IPv6 address"} - {form.watch("type") === "CNAME" && "Domain name (e.g. example.com)"} - {form.watch("type") === "MX" && "Mail server (e.g. mail.example.com)"} - {form.watch("type") === "TXT" && "Text content"} + The content of the DNS record (e.g. IP address, domain name) @@ -850,40 +798,19 @@ export function DomainDetails({ domain, onBack }: DomainDetailsProps) { field.onChange(parseInt(e.target.value || "3600"))} + onChange={e => field.onChange(parseInt(e.target.value))} /> - Time-to-live in seconds. 3600 = 1 hour + Time to live in seconds, how long DNS servers should cache this record )} /> - ( - -
- Proxied - - Enable proxying through CDN (Cloudflare only) - -
- - - -
- )} - /> - Active - Enable this record to be used + Enable or disable this DNS record @@ -915,7 +842,7 @@ export function DomainDetails({ domain, onBack }: DomainDetailsProps) { - Add any additional information about this record + Add optional notes about this record (for internal reference only) @@ -924,53 +851,63 @@ export function DomainDetails({ domain, onBack }: DomainDetailsProps) { +
- + {/* Delete Confirmation Dialog */} - + Are you sure? - This will permanently delete the DNS record {selectedRecord?.name} of type {selectedRecord?.type}. - This action cannot be undone. + This will permanently delete the DNS record "{selectedRecord?.name}" + of type {selectedRecord?.type}. This action cannot be undone. Cancel - - {deleteRecordMutation.isPending ? ( - <> - - Deleting... - - ) : ( - "Delete" + {deleteRecordMutation.isPending && ( + )} + Delete + + {/* Back Button */} + {onBack && ( +
+ +
+ )} ); } \ No newline at end of file