From 1643257f19fba7e8a9fc665c4a98087c8510a1d4 Mon Sep 17 00:00:00 2001 From: alphaeusmote <41258468-alphaeusmote@users.noreply.replit.com> Date: Thu, 10 Apr 2025 02:25:07 +0000 Subject: [PATCH] Update DNS metrics charts and API to include dynamic date ranges and improved data fetching. Replit-Commit-Author: Agent Replit-Commit-Session-Id: 9111ef36-26c8-4085-84ca-a35dc1fec1b5 Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/7083d608-d6d3-4a6a-9a27-6286c5109627/288dfb12-98b8-4936-9b81-c64cee571edd.jpg --- .../components/charts/dns-update-chart.tsx | 143 +++++++++++++++--- client/src/pages/metrics.tsx | 47 +++++- server/routes.ts | 77 ++++++++++ 3 files changed, 243 insertions(+), 24 deletions(-) diff --git a/client/src/components/charts/dns-update-chart.tsx b/client/src/components/charts/dns-update-chart.tsx index 36ff426..edb2b23 100644 --- a/client/src/components/charts/dns-update-chart.tsx +++ b/client/src/components/charts/dns-update-chart.tsx @@ -14,68 +14,175 @@ import { ResponsiveContainer, Legend, } from "recharts"; +import { DnsMetric } from "@shared/schema"; interface DnsUpdateChartProps { timeframe?: string; - domainId?: number; + domainId?: string; +} + +interface ChartDataPoint { + time: string; + updates: number; } export function DnsUpdateChart({ timeframe = "day", domainId }: DnsUpdateChartProps) { - // This would be a real API query in a production app - // For this MVP, we'll generate sample data + // Calculate the date range based on timeframe + const getDateRange = () => { + const endDate = new Date(); + let startDate = new Date(); + + if (timeframe === "day") { + startDate.setDate(startDate.getDate() - 1); + } else if (timeframe === "week") { + startDate.setDate(startDate.getDate() - 7); + } else if (timeframe === "month") { + startDate.setMonth(startDate.getMonth() - 1); + } + + return { + startDate: startDate.toISOString(), + endDate: endDate.toISOString() + }; + }; + + const { startDate, endDate } = getDateRange(); // Create a query key that includes the timeframe and domainId - const queryKey = ["/api/metrics/dns-updates", timeframe, domainId]; + const queryKey = domainId ? + ["/api/dns-metrics/domain", domainId, timeframe, startDate, endDate] : + ["/api/dns-metrics", timeframe, startDate, endDate]; - // In a real app, this would fetch data from the API - const { data: chartData, isLoading } = useQuery({ + // Use the actual DNS metrics API endpoint + const { data: metricsData, isLoading } = useQuery({ queryKey, - queryFn: async () => generateSampleData(timeframe), + queryFn: async () => { + const baseUrl = domainId ? + `/api/dns-metrics/domain/${domainId}` : + "/api/dns-metrics"; + + const url = new URL(baseUrl, window.location.origin); + url.searchParams.append("type", "update"); + url.searchParams.append("startDate", startDate); + url.searchParams.append("endDate", endDate); + + const response = await fetch(url.toString()); + + if (!response.ok) { + throw new Error("Failed to fetch DNS metrics"); + } + + return response.json(); + }, // Keep data fresh for 5 minutes staleTime: 5 * 60 * 1000, }); - // Generate sample data based on timeframe - // In a real application, this would come from the API - const generateSampleData = (timeframe: string) => { - const data = []; + // Transform the metrics data into chart format + const transformMetricsToChartData = (metrics: DnsMetric[] | undefined): ChartDataPoint[] => { + if (!metrics || metrics.length === 0) { + return generateFallbackData(timeframe); + } + + // Group metrics by time period (hour, day, or week) + const groupedData = new Map(); + + metrics.forEach((metric) => { + const date = new Date(metric.timestamp); + let timeKey: string; + + if (timeframe === "day") { + // Group by hour + timeKey = `${date.getHours().toString().padStart(2, "0")}:00`; + } else if (timeframe === "week") { + // Group by day + const days = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"]; + timeKey = days[date.getDay()]; + } else { + // Group by week for month view + const weekOfMonth = Math.ceil((date.getDate() + (new Date(date.getFullYear(), date.getMonth(), 1).getDay())) / 7); + timeKey = `Week ${weekOfMonth}`; + } + + // Increment the count for this time period + groupedData.set(timeKey, (groupedData.get(timeKey) || 0) + 1); + }); + + // Convert the map to an array of data points + const result: ChartDataPoint[] = []; + + if (timeframe === "day") { + // Ensure all 24 hours are represented + for (let i = 0; i < 24; i++) { + const hour = `${i.toString().padStart(2, "0")}:00`; + result.push({ + time: hour, + updates: groupedData.get(hour) || 0 + }); + } + } else if (timeframe === "week") { + // Ensure all 7 days are represented + const days = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"]; + days.forEach(day => { + result.push({ + time: day, + updates: groupedData.get(day) || 0 + }); + }); + } else { + // For month, ensure all weeks are represented + for (let i = 1; i <= 4; i++) { + const week = `Week ${i}`; + result.push({ + time: week, + updates: groupedData.get(week) || 0 + }); + } + } + + return result; + }; + + // Generate fallback data when no metrics are available + const generateFallbackData = (timeframe: string): ChartDataPoint[] => { + const data: ChartDataPoint[] = []; if (timeframe === "day") { // Generate hourly data for a day for (let i = 0; i < 24; i++) { const hour = i.toString().padStart(2, "0") + ":00"; - const updateCount = Math.floor(Math.random() * 10) + 1; data.push({ time: hour, - updates: updateCount, + updates: 0, }); } } else if (timeframe === "week") { // Generate daily data for a week - const days = ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"]; + const days = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"]; for (let i = 0; i < 7; i++) { - const updateCount = Math.floor(Math.random() * 50) + 10; data.push({ time: days[i], - updates: updateCount, + updates: 0, }); } } else if (timeframe === "month") { // Generate weekly data for a month for (let i = 1; i <= 4; i++) { - const updateCount = Math.floor(Math.random() * 200) + 50; data.push({ time: `Week ${i}`, - updates: updateCount, + updates: 0, }); } } return data; }; + + // Process the metrics data for the chart + const chartData = transformMetricsToChartData(metricsData); if (isLoading) { return ( diff --git a/client/src/pages/metrics.tsx b/client/src/pages/metrics.tsx index a1ee81f..18c6e8c 100644 --- a/client/src/pages/metrics.tsx +++ b/client/src/pages/metrics.tsx @@ -4,7 +4,7 @@ import { MainLayout } from "@/components/layouts/main-layout"; import { DnsUpdateChart } from "@/components/charts/dns-update-chart"; import { ProviderDistributionChart } from "@/components/charts/provider-distribution-chart"; import { RecordTypeChart } from "@/components/charts/record-type-chart"; -import { Domain, DnsRecord } from "@shared/schema"; +import { Domain, DnsMetric } from "@shared/schema"; import { useOrganization } from "@/context/organization-context"; import { Card, @@ -39,10 +39,45 @@ export default function MetricsPage() { enabled: !!currentOrganization?.id, }); - // Fetch DNS records stats (would be from actual API) - const { data: recordsStats } = useQuery({ - queryKey: ["/api/metrics/records", selectedTimeframe, selectedDomain], - enabled: false, // Would be enabled in a real implementation + // Calculate date range for metrics queries + const getDateRange = () => { + const endDate = new Date(); + let startDate = new Date(); + + if (selectedTimeframe === "day") { + startDate.setDate(startDate.getDate() - 1); + } else if (selectedTimeframe === "week") { + startDate.setDate(startDate.getDate() - 7); + } else if (selectedTimeframe === "month") { + startDate.setMonth(startDate.getMonth() - 1); + } + + return { + startDate: startDate.toISOString(), + endDate: endDate.toISOString() + }; + }; + + const { startDate, endDate } = getDateRange(); + + // Fetch DNS metrics for the performance cards + const { data: performanceMetrics = [] } = useQuery({ + queryKey: ["/api/dns-metrics", "performance", startDate, endDate], + queryFn: async () => { + const url = new URL("/api/dns-metrics", window.location.origin); + url.searchParams.append("type", "performance"); + url.searchParams.append("startDate", startDate); + url.searchParams.append("endDate", endDate); + + const response = await fetch(url.toString()); + + if (!response.ok) { + throw new Error("Failed to fetch performance metrics"); + } + + return response.json(); + }, + staleTime: 5 * 60 * 1000, // 5 minutes }); return ( @@ -108,7 +143,7 @@ export default function MetricsPage() {
- +
diff --git a/server/routes.ts b/server/routes.ts index a121e65..09631b1 100644 --- a/server/routes.ts +++ b/server/routes.ts @@ -743,6 +743,83 @@ export async function registerRoutes(app: Express): Promise { } }); + // DNS Metrics + app.get("/api/dns-metrics", requireRole(["admin", "manager", "user", "readonly"]), async (req, res) => { + try { + const metricType = req.query.type ? req.query.type as string : undefined; + const startDate = req.query.startDate ? new Date(req.query.startDate as string) : undefined; + const endDate = req.query.endDate ? new Date(req.query.endDate as string) : undefined; + + const metrics = await storage.getDnsMetricsByType( + metricType || "all", + startDate, + endDate + ); + + res.json(metrics); + } catch (error) { + console.error("Error fetching DNS metrics:", error); + res.status(500).json({ message: "Internal server error" }); + } + }); + + app.get("/api/dns-metrics/domain/:domainId", requireRole(["admin", "manager", "user", "readonly"]), async (req, res) => { + try { + const domainId = req.params.domainId; + const metricType = req.query.type ? req.query.type as string : undefined; + const startDate = req.query.startDate ? new Date(req.query.startDate as string) : undefined; + const endDate = req.query.endDate ? new Date(req.query.endDate as string) : undefined; + + const metrics = await storage.getDnsMetricsByDomain( + domainId, + metricType, + startDate, + endDate + ); + + res.json(metrics); + } catch (error) { + console.error("Error fetching domain DNS metrics:", error); + res.status(500).json({ message: "Internal server error" }); + } + }); + + app.get("/api/dns-metrics/record/:recordId", requireRole(["admin", "manager", "user", "readonly"]), async (req, res) => { + try { + const recordId = req.params.recordId; + const metricType = req.query.type ? req.query.type as string : undefined; + const startDate = req.query.startDate ? new Date(req.query.startDate as string) : undefined; + const endDate = req.query.endDate ? new Date(req.query.endDate as string) : undefined; + + const metrics = await storage.getDnsMetricsByRecord( + recordId, + metricType, + startDate, + endDate + ); + + res.json(metrics); + } catch (error) { + console.error("Error fetching record DNS metrics:", error); + res.status(500).json({ message: "Internal server error" }); + } + }); + + app.post("/api/dns-metrics", requireRole(["admin", "manager"]), async (req, res) => { + try { + const validatedData = insertDnsMetricSchema.parse(req.body); + const metric = await storage.addDnsMetric(validatedData); + res.status(201).json(metric); + } catch (error) { + if (error instanceof z.ZodError) { + res.status(400).json({ message: "Validation error", errors: error.errors }); + } else { + console.error("Error creating DNS metric:", error); + res.status(500).json({ message: "Internal server error" }); + } + } + }); + // Webhooks app.get("/api/webhooks", requireRole(["admin", "manager"]), async (req, res) => { try {