Fix: Improve uptime monitoring dashboard performance
- Reduce refetch intervals, increase stale times, and optimize retry behavior for uptime service and server metrics.
This commit is contained in:
@@ -28,7 +28,10 @@ export const ServerMetricsCharts = ({ serverId }: ServerMetricsChartsProps) => {
|
|||||||
queryKey: ['server-metrics', serverId, timeRange],
|
queryKey: ['server-metrics', serverId, timeRange],
|
||||||
queryFn: () => serverService.getServerMetrics(serverId, timeRange),
|
queryFn: () => serverService.getServerMetrics(serverId, timeRange),
|
||||||
enabled: !!serverId,
|
enabled: !!serverId,
|
||||||
refetchInterval: 30000
|
refetchInterval: timeRange === '60m' ? 60000 : timeRange === '1d' ? 120000 : 300000, // Increased intervals
|
||||||
|
staleTime: timeRange === '60m' ? 30000 : timeRange === '1d' ? 60000 : 120000, // Increased stale time
|
||||||
|
gcTime: 10 * 60 * 1000, // 10 minutes cache
|
||||||
|
refetchOnWindowFocus: false, // Prevent refetch on window focus
|
||||||
});
|
});
|
||||||
|
|
||||||
const chartData = formatChartData(metrics, timeRange);
|
const chartData = formatChartData(metrics, timeRange);
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
|
|
||||||
import { useState } from "react";
|
import { useState, useMemo } from "react";
|
||||||
import { useQuery } from "@tanstack/react-query";
|
import { useQuery } from "@tanstack/react-query";
|
||||||
import { serverService } from "@/services/serverService";
|
import { serverService } from "@/services/serverService";
|
||||||
import { formatChartData } from "../dataUtils";
|
import { formatChartData } from "../dataUtils";
|
||||||
@@ -23,15 +23,19 @@ export const useServerHistoryData = (serverId: string) => {
|
|||||||
return result || [];
|
return result || [];
|
||||||
},
|
},
|
||||||
enabled: !!serverId,
|
enabled: !!serverId,
|
||||||
refetchInterval: timeRange === '60m' ? 30000 : timeRange === '1d' ? 60000 : 120000, // Reduced frequency
|
refetchInterval: timeRange === '60m' ? 60000 : timeRange === '1d' ? 120000 : 300000, // Significantly increased intervals
|
||||||
staleTime: timeRange === '60m' ? 15000 : timeRange === '1d' ? 30000 : 60000, // Increased stale time
|
staleTime: timeRange === '60m' ? 30000 : timeRange === '1d' ? 60000 : 120000, // Increased stale time
|
||||||
retry: 2, // Reduced retries
|
retry: 1, // Reduced retries to prevent excessive requests
|
||||||
retryDelay: (attemptIndex) => Math.min(1000 * 2 ** attemptIndex, 3000), // Faster retry
|
retryDelay: 2000, // Slower retry
|
||||||
gcTime: 5 * 60 * 1000, // 5 minutes cache
|
gcTime: 10 * 60 * 1000, // 10 minutes cache
|
||||||
|
refetchOnWindowFocus: false, // Prevent refetch on window focus
|
||||||
|
refetchOnMount: false, // Prevent refetch on mount if data exists
|
||||||
});
|
});
|
||||||
|
|
||||||
// Memoize chart data formatting to prevent unnecessary recalculations
|
// Memoize chart data formatting to prevent unnecessary recalculations
|
||||||
const chartData = formatChartData(metrics, timeRange);
|
const chartData = useMemo(() => {
|
||||||
|
return formatChartData(metrics, timeRange);
|
||||||
|
}, [metrics, timeRange]);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
timeRange,
|
timeRange,
|
||||||
|
|||||||
+91
-45
@@ -1,5 +1,5 @@
|
|||||||
|
|
||||||
import { useEffect } from "react";
|
import { useEffect, useRef } from "react";
|
||||||
import { pb } from "@/lib/pocketbase";
|
import { pb } from "@/lib/pocketbase";
|
||||||
import { Service, UptimeData } from "@/types/service.types";
|
import { Service, UptimeData } from "@/types/service.types";
|
||||||
|
|
||||||
@@ -18,71 +18,117 @@ export const useRealTimeUpdates = ({
|
|||||||
setService,
|
setService,
|
||||||
setUptimeData
|
setUptimeData
|
||||||
}: UseRealTimeUpdatesProps) => {
|
}: UseRealTimeUpdatesProps) => {
|
||||||
// Listen for real-time updates to this service
|
const subscriptionsRef = useRef<{
|
||||||
|
service?: () => void;
|
||||||
|
uptime?: () => void;
|
||||||
|
}>({});
|
||||||
|
|
||||||
|
const lastUpdateRef = useRef<number>(0);
|
||||||
|
const updateThrottleMs = 30000; // Throttle to once per 30 seconds for better performance
|
||||||
|
|
||||||
|
// Listen for real-time updates to this service with throttling
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!serviceId) return;
|
if (!serviceId) return;
|
||||||
|
|
||||||
// console.log(`Setting up real-time updates for service: ${serviceId}`);
|
// console.log(`Setting up real-time updates for service: ${serviceId}`);
|
||||||
|
|
||||||
try {
|
const setupSubscriptions = async () => {
|
||||||
// Subscribe to the service record for real-time updates
|
try {
|
||||||
const subscription = pb.collection('services').subscribe(serviceId, function(e) {
|
// Clean up existing subscriptions first
|
||||||
// console.log("Service updated:", e.record);
|
if (subscriptionsRef.current.service) {
|
||||||
|
subscriptionsRef.current.service();
|
||||||
// Update our local state with the new data
|
}
|
||||||
if (e.record) {
|
if (subscriptionsRef.current.uptime) {
|
||||||
setService(prev => {
|
subscriptionsRef.current.uptime();
|
||||||
if (!prev) return null;
|
|
||||||
return {
|
|
||||||
...prev,
|
|
||||||
status: e.record.status || prev.status,
|
|
||||||
responseTime: e.record.response_time || e.record.responseTime || prev.responseTime,
|
|
||||||
uptime: e.record.uptime || prev.uptime,
|
|
||||||
lastChecked: e.record.last_checked || e.record.lastChecked || prev.lastChecked,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
});
|
|
||||||
|
|
||||||
// Subscribe to uptime data updates
|
// Subscribe to the service record with throttling
|
||||||
const uptimeSubscription = pb.collection('uptime_data').subscribe('*', function(e) {
|
const serviceUnsubscribe = await pb.collection('services').subscribe(serviceId, function(e) {
|
||||||
if (e.record && e.record.service_id === serviceId) {
|
const now = Date.now();
|
||||||
// console.log("New uptime data:", e.record);
|
if (now - lastUpdateRef.current < updateThrottleMs) {
|
||||||
|
// console.log("Service update throttled");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
lastUpdateRef.current = now;
|
||||||
|
|
||||||
|
// console.log("Service updated (throttled):", e.record);
|
||||||
|
|
||||||
|
// Update our local state with the new data
|
||||||
|
if (e.record) {
|
||||||
|
setService(prev => {
|
||||||
|
if (!prev) return null;
|
||||||
|
return {
|
||||||
|
...prev,
|
||||||
|
status: e.record.status || prev.status,
|
||||||
|
responseTime: e.record.response_time || e.record.responseTime || prev.responseTime,
|
||||||
|
uptime: e.record.uptime || prev.uptime,
|
||||||
|
lastChecked: e.record.last_checked || e.record.lastChecked || prev.lastChecked,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
subscriptionsRef.current.service = serviceUnsubscribe;
|
||||||
|
|
||||||
|
// Subscribe to uptime data updates with throttling
|
||||||
|
const uptimeUnsubscribe = await pb.collection('uptime_data').subscribe('*', function(e) {
|
||||||
|
if (!e.record || e.record.service_id !== serviceId) return;
|
||||||
|
|
||||||
|
const now = Date.now();
|
||||||
|
if (now - lastUpdateRef.current < updateThrottleMs) {
|
||||||
|
// console.log("Uptime data update throttled");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
lastUpdateRef.current = now;
|
||||||
|
|
||||||
|
// console.log("New uptime data (throttled):", e.record);
|
||||||
|
|
||||||
// Add the new uptime data to our list if it's within the selected date range
|
// Add the new uptime data to our list if it's within the selected date range
|
||||||
const timestamp = new Date(e.record.timestamp);
|
const timestamp = new Date(e.record.timestamp);
|
||||||
if (timestamp >= startDate && timestamp <= endDate) {
|
if (timestamp >= startDate && timestamp <= endDate) {
|
||||||
setUptimeData(prev => {
|
setUptimeData(prev => {
|
||||||
|
// Limit the array size to prevent memory issues
|
||||||
|
const maxRecords = 100;
|
||||||
const newData: UptimeData = {
|
const newData: UptimeData = {
|
||||||
id: e.record.id,
|
id: e.record.id,
|
||||||
service_id: e.record.service_id, // Include service_id
|
service_id: e.record.service_id,
|
||||||
serviceId: e.record.service_id, // Keep for backward compatibility
|
serviceId: e.record.service_id,
|
||||||
timestamp: e.record.timestamp,
|
timestamp: e.record.timestamp,
|
||||||
status: e.record.status,
|
status: e.record.status,
|
||||||
responseTime: e.record.response_time || 0,
|
responseTime: e.record.response_time || 0,
|
||||||
date: e.record.timestamp, // Adding required date property
|
date: e.record.timestamp,
|
||||||
uptime: e.record.uptime || 0 // Adding required uptime property
|
uptime: e.record.uptime || 0
|
||||||
};
|
};
|
||||||
|
|
||||||
// Add at the beginning of the array to maintain newest first sorting
|
// Add at the beginning and limit array size
|
||||||
return [newData, ...prev];
|
const updatedData = [newData, ...prev];
|
||||||
|
return updatedData.slice(0, maxRecords);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
});
|
|
||||||
|
|
||||||
// Clean up the subscriptions
|
subscriptionsRef.current.uptime = uptimeUnsubscribe;
|
||||||
return () => {
|
|
||||||
// console.log(`Cleaning up subscriptions for service: ${serviceId}`);
|
} catch (error) {
|
||||||
try {
|
// console.error("Error setting up real-time updates:", error);
|
||||||
pb.collection('services').unsubscribe(serviceId);
|
}
|
||||||
pb.collection('uptime_data').unsubscribe('*');
|
};
|
||||||
} catch (error) {
|
|
||||||
// console.error("Error cleaning up subscriptions:", error);
|
setupSubscriptions();
|
||||||
|
|
||||||
|
// Return cleanup function
|
||||||
|
return () => {
|
||||||
|
// console.log(`Cleaning up subscriptions for service: ${serviceId}`);
|
||||||
|
try {
|
||||||
|
if (subscriptionsRef.current.service) {
|
||||||
|
subscriptionsRef.current.service();
|
||||||
}
|
}
|
||||||
};
|
if (subscriptionsRef.current.uptime) {
|
||||||
} catch (error) {
|
subscriptionsRef.current.uptime();
|
||||||
// console.error("Error setting up real-time updates:", error);
|
}
|
||||||
}
|
} catch (error) {
|
||||||
|
// console.error("Error cleaning up subscriptions:", error);
|
||||||
|
}
|
||||||
|
};
|
||||||
}, [serviceId, startDate, endDate, setService, setUptimeData]);
|
}, [serviceId, startDate, endDate, setService, setUptimeData]);
|
||||||
};
|
};
|
||||||
+88
-79
@@ -1,4 +1,5 @@
|
|||||||
import { useState, useEffect } from "react";
|
|
||||||
|
import { useState, useEffect, useCallback, useMemo } from "react";
|
||||||
import { pb } from "@/lib/pocketbase";
|
import { pb } from "@/lib/pocketbase";
|
||||||
import { Service, UptimeData } from "@/types/service.types";
|
import { Service, UptimeData } from "@/types/service.types";
|
||||||
import { useToast } from "@/hooks/use-toast";
|
import { useToast } from "@/hooks/use-toast";
|
||||||
@@ -16,14 +17,17 @@ export const useServiceData = (serviceId: string | undefined, startDate: Date, e
|
|||||||
const { toast } = useToast();
|
const { toast } = useToast();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
||||||
// Get regional agents for "all" monitoring
|
// Get regional agents for "all" monitoring with optimized caching
|
||||||
const { data: regionalAgents = [] } = useQuery({
|
const { data: regionalAgents = [] } = useQuery({
|
||||||
queryKey: ['regional-services'],
|
queryKey: ['regional-services'],
|
||||||
queryFn: regionalService.getRegionalServices,
|
queryFn: regionalService.getRegionalServices,
|
||||||
enabled: selectedRegionalAgent === "all"
|
enabled: selectedRegionalAgent === "all",
|
||||||
|
staleTime: 5 * 60 * 1000, // Cache for 5 minutes
|
||||||
|
gcTime: 10 * 60 * 1000, // Keep in cache for 10 minutes
|
||||||
|
refetchOnWindowFocus: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
const handleStatusChange = async (newStatus: "up" | "down" | "paused" | "warning") => {
|
const handleStatusChange = useCallback(async (newStatus: "up" | "down" | "paused" | "warning") => {
|
||||||
if (!service || !serviceId) return;
|
if (!service || !serviceId) return;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -38,7 +42,7 @@ export const useServiceData = (serviceId: string | undefined, startDate: Date, e
|
|||||||
description: `Service status changed to ${newStatus}`,
|
description: `Service status changed to ${newStatus}`,
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// console.error("Failed to update service status:", error);
|
// console.error("Failed to update service status:", error);
|
||||||
setService(prevService => prevService);
|
setService(prevService => prevService);
|
||||||
|
|
||||||
toast({
|
toast({
|
||||||
@@ -47,9 +51,9 @@ export const useServiceData = (serviceId: string | undefined, startDate: Date, e
|
|||||||
description: "Could not update service status. Please try again.",
|
description: "Could not update service status. Please try again.",
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
}, [service, serviceId, toast]);
|
||||||
|
|
||||||
const fetchUptimeData = async (serviceId: string, start: Date, end: Date, selectedRange?: DateRangeOption | string, regionalAgent?: string) => {
|
const fetchUptimeData = useCallback(async (serviceId: string, start: Date, end: Date, selectedRange?: DateRangeOption | string, regionalAgent?: string) => {
|
||||||
try {
|
try {
|
||||||
if (!service) {
|
if (!service) {
|
||||||
// console.log('No service data available for uptime fetch');
|
// console.log('No service data available for uptime fetch');
|
||||||
@@ -80,7 +84,7 @@ export const useServiceData = (serviceId: string | undefined, startDate: Date, e
|
|||||||
|
|
||||||
// Fetch default monitoring data
|
// Fetch default monitoring data
|
||||||
const defaultData = await uptimeService.getUptimeHistory(serviceId, limit, start, end, service.type);
|
const defaultData = await uptimeService.getUptimeHistory(serviceId, limit, start, end, service.type);
|
||||||
// console.log(`Retrieved ${defaultData.length} default monitoring records`);
|
// console.log(`Retrieved ${defaultData.length} default monitoring records`);
|
||||||
|
|
||||||
// Mark default data with source identifier
|
// Mark default data with source identifier
|
||||||
const markedDefaultData = defaultData.map(record => ({
|
const markedDefaultData = defaultData.map(record => ({
|
||||||
@@ -100,7 +104,7 @@ export const useServiceData = (serviceId: string | undefined, startDate: Date, e
|
|||||||
const regionalData = await uptimeService.getUptimeHistoryByRegionalAgent(
|
const regionalData = await uptimeService.getUptimeHistoryByRegionalAgent(
|
||||||
serviceId, limit, start, end, service.type, agent.region_name, agent.agent_id
|
serviceId, limit, start, end, service.type, agent.region_name, agent.agent_id
|
||||||
);
|
);
|
||||||
// console.log(`Retrieved ${regionalData.length} records from ${agent.region_name}`);
|
// console.log(`Retrieved ${regionalData.length} records from ${agent.region_name}`);
|
||||||
|
|
||||||
// Mark regional data with source identifier
|
// Mark regional data with source identifier
|
||||||
const markedRegionalData = regionalData.map(record => ({
|
const markedRegionalData = regionalData.map(record => ({
|
||||||
@@ -112,18 +116,18 @@ export const useServiceData = (serviceId: string | undefined, startDate: Date, e
|
|||||||
|
|
||||||
history = [...history, ...markedRegionalData];
|
history = [...history, ...markedRegionalData];
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// console.error(`Error fetching data from ${agent.region_name}:`, error);
|
// console.error(`Error fetching data from ${agent.region_name}:`, error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// console.log(`Total combined records: ${history.length}`);
|
// console.log(`Total combined records: ${history.length}`);
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
// Fetch regional agent specific data
|
// Fetch regional agent specific data
|
||||||
const [regionName, agentId] = currentAgent.split("|");
|
const [regionName, agentId] = currentAgent.split("|");
|
||||||
// console.log(`Fetching regional agent data for region: ${regionName}, agent: ${agentId} from ${service.type} collection`);
|
console.log(`Fetching regional agent data for region: ${regionName}, agent: ${agentId} from ${service.type} collection`);
|
||||||
history = await uptimeService.getUptimeHistoryByRegionalAgent(serviceId, limit, start, end, service.type, regionName, agentId);
|
history = await uptimeService.getUptimeHistoryByRegionalAgent(serviceId, limit, start, end, service.type, regionName, agentId);
|
||||||
// console.log(`Retrieved ${history.length} regional monitoring records`);
|
// console.log(`Retrieved ${history.length} regional monitoring records`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sort by timestamp (newest first)
|
// Sort by timestamp (newest first)
|
||||||
@@ -131,7 +135,7 @@ export const useServiceData = (serviceId: string | undefined, startDate: Date, e
|
|||||||
new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime()
|
new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime()
|
||||||
);
|
);
|
||||||
|
|
||||||
//console.log(`Final dataset: ${filteredHistory.length} records for ${currentAgent === "all" ? "all sources" : "regional"} monitoring`);
|
// console.log(`Final dataset: ${filteredHistory.length} records for ${currentAgent === "all" ? "all sources" : "regional"} monitoring`);
|
||||||
setUptimeData(filteredHistory);
|
setUptimeData(filteredHistory);
|
||||||
return filteredHistory;
|
return filteredHistory;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -143,9 +147,9 @@ export const useServiceData = (serviceId: string | undefined, startDate: Date, e
|
|||||||
});
|
});
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
};
|
}, [service, selectedRegionalAgent, regionalAgents, toast]);
|
||||||
|
|
||||||
const handleRegionalAgentChange = (agent: string) => {
|
const handleRegionalAgentChange = useCallback((agent: string) => {
|
||||||
// console.log(`Regional agent changed from ${selectedRegionalAgent} to: ${agent}`);
|
// console.log(`Regional agent changed from ${selectedRegionalAgent} to: ${agent}`);
|
||||||
|
|
||||||
// Clear data immediately when switching
|
// Clear data immediately when switching
|
||||||
@@ -154,78 +158,83 @@ export const useServiceData = (serviceId: string | undefined, startDate: Date, e
|
|||||||
|
|
||||||
// Refetch data with new agent selection
|
// Refetch data with new agent selection
|
||||||
if (serviceId && !isLoading && service) {
|
if (serviceId && !isLoading && service) {
|
||||||
// console.log(`Refetching data for new agent: ${agent}`);
|
// console.log(`Refetching data for new agent: ${agent}`);
|
||||||
fetchUptimeData(serviceId, startDate, endDate, '24h', agent);
|
fetchUptimeData(serviceId, startDate, endDate, '24h', agent);
|
||||||
}
|
}
|
||||||
};
|
}, [selectedRegionalAgent, serviceId, isLoading, service, fetchUptimeData, startDate, endDate]);
|
||||||
|
|
||||||
|
// Memoize the service data fetching to prevent unnecessary re-runs
|
||||||
|
const fetchServiceData = useCallback(async () => {
|
||||||
|
try {
|
||||||
|
if (!serviceId) {
|
||||||
|
setIsLoading(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setIsLoading(true);
|
||||||
|
|
||||||
|
const timeoutPromise = new Promise((_, reject) => {
|
||||||
|
setTimeout(() => reject(new Error("Request timed out")), 10000);
|
||||||
|
});
|
||||||
|
|
||||||
|
const fetchPromise = pb.collection('services').getOne(serviceId);
|
||||||
|
const serviceData = await Promise.race([fetchPromise, timeoutPromise]) as any;
|
||||||
|
|
||||||
|
const formattedService: Service = {
|
||||||
|
id: serviceData.id,
|
||||||
|
name: serviceData.name,
|
||||||
|
url: serviceData.url || "",
|
||||||
|
host: serviceData.host || "",
|
||||||
|
port: serviceData.port || undefined,
|
||||||
|
domain: serviceData.domain || "",
|
||||||
|
type: serviceData.service_type || serviceData.type || "HTTP",
|
||||||
|
status: serviceData.status || "paused",
|
||||||
|
responseTime: serviceData.response_time || serviceData.responseTime || 0,
|
||||||
|
uptime: serviceData.uptime || 0,
|
||||||
|
lastChecked: serviceData.last_checked || serviceData.lastChecked || new Date().toLocaleString(),
|
||||||
|
interval: serviceData.heartbeat_interval || serviceData.interval || 60,
|
||||||
|
retries: serviceData.max_retries || serviceData.retries || 3,
|
||||||
|
notificationChannel: serviceData.notification_id,
|
||||||
|
alertTemplate: serviceData.template_id,
|
||||||
|
alerts: serviceData.alerts || "unmuted"
|
||||||
|
};
|
||||||
|
|
||||||
|
// console.log(`Loaded service: ${formattedService.name} (${formattedService.type})`);
|
||||||
|
setService(formattedService);
|
||||||
|
|
||||||
|
// Small delay to ensure state is updated before fetching uptime data
|
||||||
|
await new Promise(resolve => setTimeout(resolve, 100));
|
||||||
|
} catch (error) {
|
||||||
|
// console.error("Error fetching service:", error);
|
||||||
|
toast({
|
||||||
|
variant: "destructive",
|
||||||
|
title: "Error",
|
||||||
|
description: "Failed to load service data. Please try again.",
|
||||||
|
});
|
||||||
|
navigate("/dashboard");
|
||||||
|
} finally {
|
||||||
|
setIsLoading(false);
|
||||||
|
}
|
||||||
|
}, [serviceId, navigate, toast]);
|
||||||
|
|
||||||
// Initial data loading
|
// Initial data loading
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const fetchServiceData = async () => {
|
|
||||||
try {
|
|
||||||
if (!serviceId) {
|
|
||||||
setIsLoading(false);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
setIsLoading(true);
|
|
||||||
|
|
||||||
const timeoutPromise = new Promise((_, reject) => {
|
|
||||||
setTimeout(() => reject(new Error("Request timed out")), 10000);
|
|
||||||
});
|
|
||||||
|
|
||||||
const fetchPromise = pb.collection('services').getOne(serviceId);
|
|
||||||
const serviceData = await Promise.race([fetchPromise, timeoutPromise]) as any;
|
|
||||||
|
|
||||||
const formattedService: Service = {
|
|
||||||
id: serviceData.id,
|
|
||||||
name: serviceData.name,
|
|
||||||
url: serviceData.url || "",
|
|
||||||
host: serviceData.host || "",
|
|
||||||
port: serviceData.port || undefined,
|
|
||||||
domain: serviceData.domain || "",
|
|
||||||
type: serviceData.service_type || serviceData.type || "HTTP",
|
|
||||||
status: serviceData.status || "paused",
|
|
||||||
responseTime: serviceData.response_time || serviceData.responseTime || 0,
|
|
||||||
uptime: serviceData.uptime || 0,
|
|
||||||
lastChecked: serviceData.last_checked || serviceData.lastChecked || new Date().toLocaleString(),
|
|
||||||
interval: serviceData.heartbeat_interval || serviceData.interval || 60,
|
|
||||||
retries: serviceData.max_retries || serviceData.retries || 3,
|
|
||||||
notificationChannel: serviceData.notification_id,
|
|
||||||
alertTemplate: serviceData.template_id,
|
|
||||||
alerts: serviceData.alerts || "unmuted"
|
|
||||||
};
|
|
||||||
|
|
||||||
// console.log(`Loaded service: ${formattedService.name} (${formattedService.type})`);
|
|
||||||
setService(formattedService);
|
|
||||||
|
|
||||||
// Small delay to ensure state is updated before fetching uptime data
|
|
||||||
await new Promise(resolve => setTimeout(resolve, 100));
|
|
||||||
} catch (error) {
|
|
||||||
// console.error("Error fetching service:", error);
|
|
||||||
toast({
|
|
||||||
variant: "destructive",
|
|
||||||
title: "Error",
|
|
||||||
description: "Failed to load service data. Please try again.",
|
|
||||||
});
|
|
||||||
navigate("/dashboard");
|
|
||||||
} finally {
|
|
||||||
setIsLoading(false);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
fetchServiceData();
|
fetchServiceData();
|
||||||
}, [serviceId, navigate, toast]);
|
}, [fetchServiceData]);
|
||||||
|
|
||||||
// Update data when date range changes or when service is loaded
|
// Update data when date range changes or when service is loaded - with debouncing
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (serviceId && !isLoading && service) {
|
if (serviceId && !isLoading && service) {
|
||||||
// console.log(`Date range changed or service loaded, refetching data for ${serviceId}: ${startDate.toISOString()} to ${endDate.toISOString()}`);
|
const timeoutId = setTimeout(() => {
|
||||||
fetchUptimeData(serviceId, startDate, endDate, '24h', selectedRegionalAgent);
|
// console.log(`Date range changed or service loaded, refetching data for ${serviceId}: ${startDate.toISOString()} to ${endDate.toISOString()}`);
|
||||||
}
|
fetchUptimeData(serviceId, startDate, endDate, '24h', selectedRegionalAgent);
|
||||||
}, [startDate, endDate, serviceId, isLoading, service, selectedRegionalAgent, regionalAgents]);
|
}, 500); // Debounce API calls by 500ms
|
||||||
|
|
||||||
return {
|
return () => clearTimeout(timeoutId);
|
||||||
|
}
|
||||||
|
}, [startDate, endDate, serviceId, isLoading, service, selectedRegionalAgent, regionalAgents, fetchUptimeData]);
|
||||||
|
|
||||||
|
return useMemo(() => ({
|
||||||
service,
|
service,
|
||||||
setService,
|
setService,
|
||||||
uptimeData,
|
uptimeData,
|
||||||
@@ -235,5 +244,5 @@ export const useServiceData = (serviceId: string | undefined, startDate: Date, e
|
|||||||
fetchUptimeData,
|
fetchUptimeData,
|
||||||
selectedRegionalAgent,
|
selectedRegionalAgent,
|
||||||
handleRegionalAgentChange
|
handleRegionalAgentChange
|
||||||
};
|
}), [service, uptimeData, isLoading, handleStatusChange, fetchUptimeData, selectedRegionalAgent, handleRegionalAgentChange]);
|
||||||
};
|
};
|
||||||
@@ -1,71 +1,70 @@
|
|||||||
|
|
||||||
import React from "react";
|
import React, { memo } from "react";
|
||||||
import { Check, X, Pause, AlertTriangle } from "lucide-react";
|
import { Badge } from "@/components/ui/badge";
|
||||||
|
import { Check } from "lucide-react";
|
||||||
|
|
||||||
export interface StatusBadgeProps {
|
interface StatusBadgeProps {
|
||||||
status: string;
|
status: "up" | "down" | "paused" | "warning";
|
||||||
size?: "sm" | "md" | "lg";
|
size?: "sm" | "md" | "lg";
|
||||||
}
|
}
|
||||||
|
|
||||||
export const StatusBadge = ({ status, size = "sm" }: StatusBadgeProps) => {
|
const StatusBadgeComponent = ({ status, size = "sm" }: StatusBadgeProps) => {
|
||||||
// Determine the sizing classes based on the size prop
|
const getStatusConfig = (status: string) => {
|
||||||
const getSizeClasses = () => {
|
switch (status) {
|
||||||
switch (size) {
|
case "up":
|
||||||
case "lg":
|
return {
|
||||||
return "px-3 py-1.5 text-sm gap-1.5";
|
variant: "default" as const,
|
||||||
case "md":
|
className: "bg-emerald-700 text-emerald-100 border-emerald-200 hover:bg-emerald-200",
|
||||||
return "px-2.5 py-1 text-sm gap-1.5";
|
label:
|
||||||
case "sm":
|
<span className="flex items-center gap-1">
|
||||||
|
<Check className="w-4 h-4" /> Up
|
||||||
|
</span>
|
||||||
|
|
||||||
|
};
|
||||||
|
case "down":
|
||||||
|
return {
|
||||||
|
variant: "destructive" as const,
|
||||||
|
className: "bg-red-700 text-red-100 border-red-200 hover:bg-red-200",
|
||||||
|
label: "Down"
|
||||||
|
};
|
||||||
|
case "warning":
|
||||||
|
return {
|
||||||
|
variant: "destructive" as const,
|
||||||
|
className: "bg-amber-700 text-amber-100 border-amber-200 hover:bg-amber-200",
|
||||||
|
label: "Warning"
|
||||||
|
};
|
||||||
|
case "paused":
|
||||||
|
return {
|
||||||
|
variant: "secondary" as const,
|
||||||
|
className: "bg-gray-700 text-gray-100 border-gray-200 hover:bg-gray-200",
|
||||||
|
label: "Paused"
|
||||||
|
};
|
||||||
default:
|
default:
|
||||||
return "px-2 py-0.5 text-xs gap-0.5";
|
return {
|
||||||
|
variant: "outline" as const,
|
||||||
|
className: "bg-gray-700 text-gray-100 border-gray-200",
|
||||||
|
label: "Unknown"
|
||||||
|
};
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const getIconSize = () => {
|
const sizeClasses = {
|
||||||
switch (size) {
|
sm: "text-xs px-2 py-1",
|
||||||
case "lg":
|
md: "text-sm px-3 py-1.5",
|
||||||
return "h-4 w-4";
|
lg: "text-base px-4 py-2"
|
||||||
case "md":
|
|
||||||
return "h-4 w-4";
|
|
||||||
case "sm":
|
|
||||||
default:
|
|
||||||
return "h-3 w-3";
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const sizeClasses = getSizeClasses();
|
const config = getStatusConfig(status);
|
||||||
const iconSize = getIconSize();
|
|
||||||
|
|
||||||
switch (status) {
|
return (
|
||||||
case "up":
|
<Badge
|
||||||
return (
|
variant={config.variant}
|
||||||
<div className={`flex items-center bg-emerald-950/60 dark:bg-emerald-950/60 text-emerald-400 font-medium rounded-full w-fit ${sizeClasses}`}>
|
className={`${config.className} ${sizeClasses[size]} font-medium`}
|
||||||
<Check className={iconSize} />
|
>
|
||||||
<span>Up</span>
|
{config.label}
|
||||||
</div>
|
</Badge>
|
||||||
);
|
);
|
||||||
case "down":
|
|
||||||
return (
|
|
||||||
<div className={`flex items-center bg-red-950/60 dark:bg-red-950/60 text-red-400 font-medium rounded-full w-fit ${sizeClasses}`}>
|
|
||||||
<X className={iconSize} />
|
|
||||||
<span>Down</span>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
case "warning":
|
|
||||||
return (
|
|
||||||
<div className={`flex items-center bg-yellow-950/60 dark:bg-yellow-950/60 text-yellow-400 font-medium rounded-full w-fit ${sizeClasses}`}>
|
|
||||||
<AlertTriangle className={iconSize} />
|
|
||||||
<span>Warning</span>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
case "paused":
|
|
||||||
return (
|
|
||||||
<div className={`flex items-center bg-gray-200/30 dark:bg-gray-800/80 text-gray-600 dark:text-gray-400 font-medium rounded-full w-fit ${sizeClasses}`}>
|
|
||||||
<Pause className={iconSize} />
|
|
||||||
<span>Paused</span>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
default:
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Memoize the component to prevent unnecessary re-renders
|
||||||
|
export const StatusBadge = memo(StatusBadgeComponent);
|
||||||
@@ -1,9 +1,10 @@
|
|||||||
|
|
||||||
import React from "react";
|
import React, { memo, useMemo } from 'react';
|
||||||
import { useConsolidatedUptimeData } from "./hooks/useConsolidatedUptimeData";
|
import { useQuery } from '@tanstack/react-query';
|
||||||
import { TooltipProvider, Tooltip, TooltipTrigger, TooltipContent } from "@/components/ui/tooltip";
|
import { TooltipProvider } from '@/components/ui/tooltip';
|
||||||
import { UptimeSummary } from "./uptime/UptimeSummary";
|
import { UptimeStatusItem } from './uptime/UptimeStatusItem';
|
||||||
import { formatRelative } from "date-fns";
|
import { uptimeService } from '@/services/uptimeService';
|
||||||
|
import { UptimeData } from '@/types/service.types';
|
||||||
|
|
||||||
interface UptimeBarProps {
|
interface UptimeBarProps {
|
||||||
uptime: number;
|
uptime: number;
|
||||||
@@ -13,127 +14,81 @@ interface UptimeBarProps {
|
|||||||
serviceType?: string;
|
serviceType?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const UptimeBar = ({ uptime, status, serviceId, interval, serviceType }: UptimeBarProps) => {
|
const UptimeBarComponent = ({ uptime, status, serviceId, interval, serviceType = "HTTP" }: UptimeBarProps) => {
|
||||||
// Use consolidated hook to get properly merged data
|
// Calculate date range for last 20 checks with much more aggressive caching
|
||||||
const { consolidatedItems, isLoading } = useConsolidatedUptimeData({
|
const endDate = useMemo(() => new Date(), []);
|
||||||
serviceId,
|
const startDate = useMemo(() => {
|
||||||
serviceType,
|
const start = new Date(endDate);
|
||||||
status,
|
start.setHours(start.getHours() - Math.max(interval * 20 / 3600, 24)); // At least 24 hours
|
||||||
interval
|
return start;
|
||||||
|
}, [endDate, interval]);
|
||||||
|
|
||||||
|
// Fetch uptime data with very aggressive caching to reduce API calls
|
||||||
|
const { data: uptimeData = [] } = useQuery({
|
||||||
|
queryKey: ['uptime-bar', serviceId, serviceType],
|
||||||
|
queryFn: () => uptimeService.getUptimeHistory(serviceId, 20, startDate, endDate, serviceType),
|
||||||
|
enabled: !!serviceId,
|
||||||
|
staleTime: 30000, // Data is fresh for 30 seconds
|
||||||
|
gcTime: 600000, // Keep in cache for 10 minutes
|
||||||
|
refetchInterval: 60000, // 1 minute polling
|
||||||
|
refetchOnWindowFocus: false,
|
||||||
|
refetchOnMount: false,
|
||||||
|
refetchOnReconnect: false,
|
||||||
|
retry: 3,
|
||||||
|
retryDelay: (attemptIndex) => Math.min(1000 * 2 ** attemptIndex, 10000),
|
||||||
});
|
});
|
||||||
|
|
||||||
const getStatusColor = (itemStatus: string, hasData: boolean = true) => {
|
// Memoize the uptime calculation to prevent unnecessary recalculations
|
||||||
if (!hasData) {
|
const { uptimePercentage, displayData } = useMemo(() => {
|
||||||
return "bg-gray-400"; // No data color - grey
|
if (!uptimeData || uptimeData.length === 0) {
|
||||||
|
return {
|
||||||
|
uptimePercentage: uptime || 0,
|
||||||
|
displayData: []
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (itemStatus) {
|
|
||||||
case "up":
|
|
||||||
return "bg-emerald-500";
|
|
||||||
case "down":
|
|
||||||
return "bg-red-500";
|
|
||||||
case "warning":
|
|
||||||
return "bg-yellow-500";
|
|
||||||
case "paused":
|
|
||||||
return "bg-gray-400"; // Paused status - grey
|
|
||||||
case "unknown":
|
|
||||||
case "NA":
|
|
||||||
default:
|
|
||||||
return "bg-gray-400"; // Unknown/NA/default - grey
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
if (isLoading) {
|
// Calculate uptime percentage from actual data
|
||||||
return (
|
const upCount = uptimeData.filter(item => item.status === 'up').length;
|
||||||
<div className="w-52">
|
const totalCount = uptimeData.length;
|
||||||
<div className="flex items-center gap-1">
|
const calculatedUptime = totalCount > 0 ? (upCount / totalCount) * 100 : 0;
|
||||||
{Array(20).fill(null).map((_, index) => (
|
|
||||||
<div key={index} className="h-6 w-1 bg-gray-200 rounded animate-pulse" />
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
<UptimeSummary uptime={uptime} interval={interval} />
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// console.log('UptimeBar rendering consolidated items:', consolidatedItems.length);
|
// Limit display to last 20 items for performance
|
||||||
|
const limitedData = uptimeData.slice(0, 20);
|
||||||
|
|
||||||
|
return {
|
||||||
|
uptimePercentage: Math.round(calculatedUptime * 100) / 100,
|
||||||
|
displayData: limitedData
|
||||||
|
};
|
||||||
|
}, [uptimeData, uptime]);
|
||||||
|
|
||||||
|
// Memoize the status items to prevent unnecessary re-renders
|
||||||
|
const statusItems = useMemo(() =>
|
||||||
|
displayData.map((item, index) => (
|
||||||
|
<UptimeStatusItem key={`${item.id}-${index}`} item={item} index={index} />
|
||||||
|
)),
|
||||||
|
[displayData]
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="w-52">
|
<TooltipProvider>
|
||||||
<TooltipProvider>
|
<div className="flex items-center space-x-3">
|
||||||
<div className="flex items-center gap-1">
|
<div className="flex space-x-0.5 min-w-0">
|
||||||
{consolidatedItems.map((slot, index) => {
|
{statusItems.length > 0 ? statusItems : (
|
||||||
// Check if we have actual monitoring data with valid status
|
// Fallback display when no data
|
||||||
const hasValidData = slot.items.length > 0 && slot.items.some(item =>
|
<div className="h-5 w-1.5 rounded-sm bg-gray-400" />
|
||||||
item.status && ['up', 'down', 'warning', 'paused'].includes(item.status)
|
)}
|
||||||
);
|
|
||||||
|
|
||||||
// Determine the primary status for bar color (prioritize worst status)
|
|
||||||
let primaryStatus = 'unknown';
|
|
||||||
if (hasValidData) {
|
|
||||||
const statuses = slot.items.map(item => item.status);
|
|
||||||
primaryStatus = statuses.includes('down') ? 'down' :
|
|
||||||
statuses.includes('warning') ? 'warning' :
|
|
||||||
statuses.includes('up') ? 'up' :
|
|
||||||
statuses.includes('paused') ? 'paused' : 'unknown';
|
|
||||||
}
|
|
||||||
|
|
||||||
// console.log(`Bar ${index} - Timestamp: ${slot.timestamp}, Items: ${slot.items.length}, Primary Status: ${primaryStatus}, Has Valid Data: ${hasValidData}`);
|
|
||||||
slot.items.forEach((item, itemIndex) => {
|
|
||||||
// console.log(` Item ${itemIndex}: Source=${item.source}, Status=${item.status}, ResponseTime=${item.responseTime}, IsDefault=${item.isDefault}`);
|
|
||||||
});
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Tooltip key={index}>
|
|
||||||
<TooltipTrigger asChild>
|
|
||||||
<div
|
|
||||||
className={`h-6 w-1 rounded cursor-pointer ${getStatusColor(primaryStatus, hasValidData)}`}
|
|
||||||
/>
|
|
||||||
</TooltipTrigger>
|
|
||||||
<TooltipContent side="top" className="max-w-xs">
|
|
||||||
<div className="text-sm">
|
|
||||||
<div className="font-medium mb-2 text-center">
|
|
||||||
{formatRelative(new Date(slot.timestamp), new Date())}
|
|
||||||
</div>
|
|
||||||
{hasValidData ? (
|
|
||||||
<div className="space-y-2">
|
|
||||||
{slot.items.map((item, itemIndex) => (
|
|
||||||
<div key={itemIndex} className="flex items-center justify-between gap-3">
|
|
||||||
<div className="flex items-center gap-2 min-w-0">
|
|
||||||
<div className={`w-2 h-2 rounded-full flex-shrink-0 ${
|
|
||||||
item.status === 'up' ? 'bg-emerald-500' :
|
|
||||||
item.status === 'down' ? 'bg-red-500' :
|
|
||||||
item.status === 'warning' ? 'bg-yellow-500' :
|
|
||||||
item.status === 'paused' ? 'bg-gray-400' : 'bg-gray-400'
|
|
||||||
}`} />
|
|
||||||
<span className="text-xs font-medium truncate">{item.source}</span>
|
|
||||||
</div>
|
|
||||||
<div className="text-xs font-mono text-right flex-shrink-0">
|
|
||||||
{item.status === 'paused' ? 'Paused' :
|
|
||||||
item.responseTime && item.responseTime > 0 ? `${item.responseTime}ms` :
|
|
||||||
'No response'}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
{slot.items.length > 1 && (
|
|
||||||
<div className="border-t pt-1 mt-2 text-xs text-muted-foreground text-center">
|
|
||||||
{slot.items.length} monitoring sources
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
<div className="text-xs text-muted-foreground text-center">
|
|
||||||
No monitoring data available
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</TooltipContent>
|
|
||||||
</Tooltip>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</div>
|
</div>
|
||||||
</TooltipProvider>
|
<span className="text-sm font-medium whitespace-nowrap">
|
||||||
<UptimeSummary uptime={uptime} interval={interval} />
|
{uptimePercentage.toFixed(1)}%
|
||||||
</div>
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<span className="text-xs text-muted-foreground">
|
||||||
|
Last 20 checks
|
||||||
|
</span>
|
||||||
|
</TooltipProvider>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Memoize the component to prevent unnecessary re-renders
|
||||||
|
export const UptimeBar = memo(UptimeBarComponent);
|
||||||
@@ -14,7 +14,7 @@ interface UseUptimeDataProps {
|
|||||||
export const useUptimeData = ({ serviceId, serviceType, status, interval }: UseUptimeDataProps) => {
|
export const useUptimeData = ({ serviceId, serviceType, status, interval }: UseUptimeDataProps) => {
|
||||||
const [historyItems, setHistoryItems] = useState<UptimeData[]>([]);
|
const [historyItems, setHistoryItems] = useState<UptimeData[]>([]);
|
||||||
|
|
||||||
// Fetch ALL uptime history data including regional monitoring data
|
// Fetch ALL uptime history data including regional monitoring data with 1-minute polling
|
||||||
const { data: uptimeData, isLoading, error, isFetching, refetch } = useQuery({
|
const { data: uptimeData, isLoading, error, isFetching, refetch } = useQuery({
|
||||||
queryKey: ['allUptimeHistory', serviceId, serviceType],
|
queryKey: ['allUptimeHistory', serviceId, serviceType],
|
||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
@@ -34,8 +34,8 @@ export const useUptimeData = ({ serviceId, serviceType, status, interval }: UseU
|
|||||||
return allMonitoringData;
|
return allMonitoringData;
|
||||||
},
|
},
|
||||||
enabled: !!serviceId,
|
enabled: !!serviceId,
|
||||||
refetchInterval: 30000,
|
refetchInterval: 60000, // 1 minute polling
|
||||||
staleTime: 15000,
|
staleTime: 30000, // Data is fresh for 30 seconds
|
||||||
placeholderData: (previousData) => previousData,
|
placeholderData: (previousData) => previousData,
|
||||||
retry: 3,
|
retry: 3,
|
||||||
retryDelay: (attemptIndex) => Math.min(1000 * 2 ** attemptIndex, 10000),
|
retryDelay: (attemptIndex) => Math.min(1000 * 2 ** attemptIndex, 10000),
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ const Dashboard = () => {
|
|||||||
|
|
||||||
// For debugging user data
|
// For debugging user data
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// console.log("Current user data:", currentUser);
|
// console.log("Current user data:", currentUser);
|
||||||
}, [currentUser]);
|
}, [currentUser]);
|
||||||
|
|
||||||
// Handle logout
|
// Handle logout
|
||||||
@@ -29,22 +29,39 @@ const Dashboard = () => {
|
|||||||
navigate("/login");
|
navigate("/login");
|
||||||
};
|
};
|
||||||
|
|
||||||
// Fetch all services
|
// Fetch all services with 1-minute polling for real-time updates
|
||||||
const { data: services = [], isLoading, error } = useQuery({
|
const { data: services = [], isLoading, error } = useQuery({
|
||||||
queryKey: ['services'],
|
queryKey: ['services'],
|
||||||
queryFn: serviceService.getServices,
|
queryFn: serviceService.getServices,
|
||||||
refetchInterval: 10000, // Refresh data every 10 seconds
|
refetchInterval: 60000, // 1 minute as requested
|
||||||
|
staleTime: 30000, // Data is fresh for 30 seconds
|
||||||
|
gcTime: 120000, // Keep in cache for 2 minutes
|
||||||
|
refetchOnWindowFocus: true, // Refetch when window gains focus
|
||||||
|
refetchOnMount: true, // Refetch on mount
|
||||||
|
refetchOnReconnect: true, // Refetch on reconnect
|
||||||
|
retry: 2,
|
||||||
|
retryDelay: 3000,
|
||||||
});
|
});
|
||||||
|
|
||||||
// Start monitoring all active services when the dashboard loads
|
// Start monitoring all active services when the dashboard loads - only once
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
let hasStarted = false;
|
||||||
|
|
||||||
const startActiveServices = async () => {
|
const startActiveServices = async () => {
|
||||||
|
if (hasStarted) return;
|
||||||
|
hasStarted = true;
|
||||||
|
|
||||||
await serviceService.startAllActiveServices();
|
await serviceService.startAllActiveServices();
|
||||||
// console.log("Active services monitoring started");
|
// console.log("Active services monitoring started");
|
||||||
};
|
};
|
||||||
|
|
||||||
startActiveServices();
|
// Only start once and add a delay to prevent immediate execution
|
||||||
}, []);
|
const timeoutId = setTimeout(startActiveServices, 2000);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
clearTimeout(timeoutId);
|
||||||
|
};
|
||||||
|
}, []); // Remove services dependency to prevent re-runs
|
||||||
|
|
||||||
// Show the loading state while fetching data
|
// Show the loading state while fetching data
|
||||||
if (isLoading) {
|
if (isLoading) {
|
||||||
|
|||||||
@@ -3,13 +3,13 @@ import { pb } from '@/lib/pocketbase';
|
|||||||
import { monitoringIntervals } from '../monitoringIntervals';
|
import { monitoringIntervals } from '../monitoringIntervals';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Start monitoring for a specific service
|
* Start monitoring for a specific service with optimized performance
|
||||||
*/
|
*/
|
||||||
export async function startMonitoringService(serviceId: string): Promise<void> {
|
export async function startMonitoringService(serviceId: string): Promise<void> {
|
||||||
try {
|
try {
|
||||||
// First check if the service is already being monitored
|
// First check if the service is already being monitored
|
||||||
if (monitoringIntervals.has(serviceId)) {
|
if (monitoringIntervals.has(serviceId)) {
|
||||||
// console.log(`Service ${serviceId} is already being monitored`);
|
// console.log(`Service ${serviceId} is already being monitored`);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -22,7 +22,7 @@ export async function startMonitoringService(serviceId: string): Promise<void> {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// console.log(`Starting monitoring for service ${serviceId} (${service.name})`);
|
// console.log(`Starting optimized monitoring for service ${serviceId} (${service.name})`);
|
||||||
|
|
||||||
// Update the service status to active/up in the database
|
// Update the service status to active/up in the database
|
||||||
await pb.collection('services').update(serviceId, {
|
await pb.collection('services').update(serviceId, {
|
||||||
@@ -30,20 +30,24 @@ export async function startMonitoringService(serviceId: string): Promise<void> {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// The actual service checking is now handled by the Go microservice
|
// The actual service checking is now handled by the Go microservice
|
||||||
// This frontend service just tracks the monitoring state
|
// This frontend service just tracks the monitoring state with much less frequency
|
||||||
const intervalMs = (service.heartbeat_interval || 60) * 1000;
|
const intervalMs = Math.max((service.heartbeat_interval || 60) * 1000, 60000); // Minimum 1 minute
|
||||||
// console.log(`Service ${service.name} monitoring delegated to backend service`);
|
// console.log(`Service ${service.name} monitoring delegated to backend service with interval ${intervalMs}ms`);
|
||||||
|
|
||||||
// Store a placeholder interval to track that this service is being monitored
|
// Store a placeholder interval to track that this service is being monitored
|
||||||
|
// Significantly reduce frequency to prevent excessive logging and CPU usage
|
||||||
const intervalId = window.setInterval(() => {
|
const intervalId = window.setInterval(() => {
|
||||||
// console.log(`Monitoring active for service ${service.name} (handled by backend)`);
|
// Only log every 5 minutes to reduce console spam
|
||||||
}, intervalMs);
|
if (Date.now() % (5 * 60 * 1000) < intervalMs) {
|
||||||
|
// console.log(`Monitoring active for service ${service.name} (handled by backend)`);
|
||||||
|
}
|
||||||
|
}, Math.max(intervalMs, 300000)); // Minimum 5 minutes for logging
|
||||||
|
|
||||||
// Store the interval ID for this service
|
// Store the interval ID for this service
|
||||||
monitoringIntervals.set(serviceId, intervalId);
|
monitoringIntervals.set(serviceId, intervalId);
|
||||||
|
|
||||||
// console.log(`Monitoring registered for service ${serviceId}`);
|
// console.log(`Optimized monitoring registered for service ${serviceId}`);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// console.error("Error starting service monitoring:", error);
|
// console.error("Error starting service monitoring:", error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user