Adjusted date range calculation to ensure data is displayed
This commit is contained in:
+18
-42
@@ -5,6 +5,7 @@ import { Service, UptimeData } from "@/types/service.types";
|
|||||||
import { useToast } from "@/hooks/use-toast";
|
import { useToast } from "@/hooks/use-toast";
|
||||||
import { useNavigate } from "react-router-dom";
|
import { useNavigate } from "react-router-dom";
|
||||||
import { uptimeService } from "@/services/uptimeService";
|
import { uptimeService } from "@/services/uptimeService";
|
||||||
|
import { DateRangeOption } from "../../DateRangeFilter";
|
||||||
|
|
||||||
export const useServiceData = (serviceId: string | undefined, startDate: Date, endDate: Date) => {
|
export const useServiceData = (serviceId: string | undefined, startDate: Date, endDate: Date) => {
|
||||||
const [service, setService] = useState<Service | null>(null);
|
const [service, setService] = useState<Service | null>(null);
|
||||||
@@ -13,15 +14,12 @@ export const useServiceData = (serviceId: string | undefined, startDate: Date, e
|
|||||||
const { toast } = useToast();
|
const { toast } = useToast();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
||||||
// Handler for service status changes
|
|
||||||
const handleStatusChange = async (newStatus: "up" | "down" | "paused" | "warning") => {
|
const handleStatusChange = async (newStatus: "up" | "down" | "paused" | "warning") => {
|
||||||
if (!service || !serviceId) return;
|
if (!service || !serviceId) return;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Optimistic UI update
|
|
||||||
setService({ ...service, status: newStatus as Service["status"] });
|
setService({ ...service, status: newStatus as Service["status"] });
|
||||||
|
|
||||||
// Update the service status in PocketBase
|
|
||||||
await pb.collection('services').update(serviceId, {
|
await pb.collection('services').update(serviceId, {
|
||||||
status: newStatus
|
status: newStatus
|
||||||
});
|
});
|
||||||
@@ -32,7 +30,6 @@ export const useServiceData = (serviceId: string | undefined, startDate: Date, e
|
|||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Failed to update service status:", error);
|
console.error("Failed to update service status:", error);
|
||||||
// Revert the optimistic update
|
|
||||||
setService(prevService => prevService);
|
setService(prevService => prevService);
|
||||||
|
|
||||||
toast({
|
toast({
|
||||||
@@ -43,51 +40,30 @@ export const useServiceData = (serviceId: string | undefined, startDate: Date, e
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Function to fetch uptime data with date filters
|
const fetchUptimeData = async (serviceId: string, start: Date, end: Date, selectedRange?: DateRangeOption | string) => {
|
||||||
const fetchUptimeData = async (serviceId: string, start: Date, end: Date, selectedRange: string) => {
|
|
||||||
try {
|
try {
|
||||||
console.log(`Fetching uptime data from ${start.toISOString()} to ${end.toISOString()}`);
|
console.log(`Fetching uptime data: ${start.toISOString()} to ${end.toISOString()} for range: ${selectedRange}`);
|
||||||
|
|
||||||
// Set appropriate limits based on time range to ensure enough granularity
|
let limit = 500; // Default limit
|
||||||
let limit = 200; // default
|
|
||||||
|
|
||||||
// Adjust limits based on selected range
|
if (selectedRange === '24h') {
|
||||||
if (selectedRange === '60min') {
|
limit = 300;
|
||||||
limit = 300; // More points for shorter time ranges
|
|
||||||
} else if (selectedRange === '24h') {
|
|
||||||
limit = 200;
|
|
||||||
} else if (selectedRange === '7d') {
|
} else if (selectedRange === '7d') {
|
||||||
limit = 250;
|
limit = 400;
|
||||||
} else if (selectedRange === '30d' || selectedRange === '1y') {
|
|
||||||
limit = 300; // More points for longer time ranges
|
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log(`Using limit ${limit} for range ${selectedRange}`);
|
console.log(`Using limit ${limit} for range ${selectedRange}`);
|
||||||
|
|
||||||
const history = await uptimeService.getUptimeHistory(serviceId, limit, start, end);
|
const history = await uptimeService.getUptimeHistory(serviceId, limit, start, end);
|
||||||
console.log(`Fetched ${history.length} uptime records for time range ${selectedRange}`);
|
console.log(`Retrieved ${history.length} uptime records`);
|
||||||
|
|
||||||
if (history.length === 0) {
|
// Sort by timestamp (newest first)
|
||||||
console.log("No data returned from API, checking if we need to fetch with a higher limit");
|
const filteredHistory = [...history].sort((a, b) =>
|
||||||
// If no data, try with a higher limit as fallback
|
|
||||||
if (limit < 500) {
|
|
||||||
const extendedHistory = await uptimeService.getUptimeHistory(serviceId, 500, start, end);
|
|
||||||
console.log(`Fallback: Fetched ${extendedHistory.length} uptime records with higher limit`);
|
|
||||||
|
|
||||||
if (extendedHistory.length > 0) {
|
|
||||||
setUptimeData(extendedHistory);
|
|
||||||
return extendedHistory;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sort data by timestamp (newest first)
|
|
||||||
const sortedHistory = [...history].sort((a, b) =>
|
|
||||||
new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime()
|
new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime()
|
||||||
);
|
);
|
||||||
|
|
||||||
setUptimeData(sortedHistory);
|
setUptimeData(filteredHistory);
|
||||||
return sortedHistory;
|
return filteredHistory;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error fetching uptime data:", error);
|
console.error("Error fetching uptime data:", error);
|
||||||
toast({
|
toast({
|
||||||
@@ -110,7 +86,6 @@ export const useServiceData = (serviceId: string | undefined, startDate: Date, e
|
|||||||
|
|
||||||
setIsLoading(true);
|
setIsLoading(true);
|
||||||
|
|
||||||
// Add a timeout to prevent hanging
|
|
||||||
const timeoutPromise = new Promise((_, reject) => {
|
const timeoutPromise = new Promise((_, reject) => {
|
||||||
setTimeout(() => reject(new Error("Request timed out")), 10000);
|
setTimeout(() => reject(new Error("Request timed out")), 10000);
|
||||||
});
|
});
|
||||||
@@ -136,7 +111,7 @@ export const useServiceData = (serviceId: string | undefined, startDate: Date, e
|
|||||||
|
|
||||||
setService(formattedService);
|
setService(formattedService);
|
||||||
|
|
||||||
// Fetch uptime history with date range
|
// Fetch initial uptime history with 24h default
|
||||||
await fetchUptimeData(serviceId, startDate, endDate, '24h');
|
await fetchUptimeData(serviceId, startDate, endDate, '24h');
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error fetching service:", error);
|
console.error("Error fetching service:", error);
|
||||||
@@ -156,10 +131,11 @@ export const useServiceData = (serviceId: string | undefined, startDate: Date, e
|
|||||||
|
|
||||||
// Update data when date range changes
|
// Update data when date range changes
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (serviceId && !isLoading) {
|
if (serviceId && !isLoading && service) {
|
||||||
fetchUptimeData(serviceId, startDate, endDate, '24h');
|
console.log(`Date range changed, refetching data for ${serviceId}: ${startDate.toISOString()} to ${endDate.toISOString()}`);
|
||||||
|
fetchUptimeData(serviceId, startDate, endDate);
|
||||||
}
|
}
|
||||||
}, [startDate, endDate]);
|
}, [startDate, endDate, serviceId, isLoading, service]);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
service,
|
service,
|
||||||
@@ -170,4 +146,4 @@ export const useServiceData = (serviceId: string | undefined, startDate: Date, e
|
|||||||
handleStatusChange,
|
handleStatusChange,
|
||||||
fetchUptimeData
|
fetchUptimeData
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
@@ -1,4 +1,3 @@
|
|||||||
|
|
||||||
import { useState, useEffect, useCallback } from "react";
|
import { useState, useEffect, useCallback } from "react";
|
||||||
import { useParams, useNavigate } from "react-router-dom";
|
import { useParams, useNavigate } from "react-router-dom";
|
||||||
import { DateRangeOption } from "../DateRangeFilter";
|
import { DateRangeOption } from "../DateRangeFilter";
|
||||||
@@ -12,13 +11,17 @@ export const ServiceDetailContainer = () => {
|
|||||||
const { id } = useParams<{ id: string }>();
|
const { id } = useParams<{ id: string }>();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
||||||
// Ensure we use exact timestamp for startDate
|
// Set default to 24h
|
||||||
const [startDate, setStartDate] = useState<Date>(() => {
|
const [startDate, setStartDate] = useState<Date>(() => {
|
||||||
const date = new Date();
|
const date = new Date();
|
||||||
date.setHours(date.getHours() - 24);
|
date.setHours(date.getHours() - 24); // Go back 24 hours
|
||||||
|
return date;
|
||||||
|
});
|
||||||
|
const [endDate, setEndDate] = useState<Date>(() => {
|
||||||
|
const date = new Date();
|
||||||
|
date.setMinutes(date.getMinutes() + 5); // Add 5 minutes buffer to future
|
||||||
return date;
|
return date;
|
||||||
});
|
});
|
||||||
const [endDate, setEndDate] = useState<Date>(new Date());
|
|
||||||
const [selectedRange, setSelectedRange] = useState<DateRangeOption>('24h');
|
const [selectedRange, setSelectedRange] = useState<DateRangeOption>('24h');
|
||||||
|
|
||||||
// State for sidebar collapse functionality (shared with Dashboard)
|
// State for sidebar collapse functionality (shared with Dashboard)
|
||||||
@@ -91,14 +94,16 @@ export const ServiceDetailContainer = () => {
|
|||||||
|
|
||||||
// Handle date range filter changes
|
// Handle date range filter changes
|
||||||
const handleDateRangeChange = useCallback((start: Date, end: Date, option: DateRangeOption) => {
|
const handleDateRangeChange = useCallback((start: Date, end: Date, option: DateRangeOption) => {
|
||||||
console.log(`Date range changed: ${start.toISOString()} to ${end.toISOString()}, option: ${option}`);
|
console.log(`ServiceDetailContainer: Date range changed: ${start.toISOString()} to ${end.toISOString()}, option: ${option}`);
|
||||||
|
|
||||||
|
// Update state which will trigger the useEffect in useServiceData
|
||||||
setStartDate(start);
|
setStartDate(start);
|
||||||
setEndDate(end);
|
setEndDate(end);
|
||||||
setSelectedRange(option);
|
setSelectedRange(option);
|
||||||
|
|
||||||
// Refetch uptime data with new date range, passing the selected range option
|
// Also explicitly fetch data with the new range to ensure immediate update
|
||||||
if (id) {
|
if (id) {
|
||||||
|
console.log(`ServiceDetailContainer: Explicitly fetching data for service ${id} with new range`);
|
||||||
fetchUptimeData(id, start, end, option);
|
fetchUptimeData(id, start, end, option);
|
||||||
}
|
}
|
||||||
}, [id, fetchUptimeData]);
|
}, [id, fetchUptimeData]);
|
||||||
@@ -123,4 +128,4 @@ export const ServiceDetailContainer = () => {
|
|||||||
)}
|
)}
|
||||||
</ServiceDetailWrapper>
|
</ServiceDetailWrapper>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@@ -2,29 +2,24 @@
|
|||||||
import { pb } from '@/lib/pocketbase';
|
import { pb } from '@/lib/pocketbase';
|
||||||
import { UptimeData } from '@/types/service.types';
|
import { UptimeData } from '@/types/service.types';
|
||||||
|
|
||||||
// Simple in-memory cache to avoid excessive requests
|
|
||||||
const uptimeCache = new Map<string, {
|
const uptimeCache = new Map<string, {
|
||||||
data: UptimeData[],
|
data: UptimeData[],
|
||||||
timestamp: number,
|
timestamp: number,
|
||||||
expiresIn: number
|
expiresIn: number
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
// Cache time-to-live in milliseconds
|
const CACHE_TTL = 3000; // 3 seconds for faster updates
|
||||||
const CACHE_TTL = 15000; // 15 seconds
|
|
||||||
|
|
||||||
export const uptimeService = {
|
export const uptimeService = {
|
||||||
async recordUptimeData(data: UptimeData): Promise<void> {
|
async recordUptimeData(data: UptimeData): Promise<void> {
|
||||||
try {
|
try {
|
||||||
console.log(`Recording uptime data for service ${data.serviceId}: Status ${data.status}, Response time: ${data.responseTime}ms`);
|
console.log(`Recording uptime data for service ${data.serviceId}: Status ${data.status}, Response time: ${data.responseTime}ms`);
|
||||||
|
|
||||||
// Create a custom request options object to disable auto-cancellation
|
|
||||||
const options = {
|
const options = {
|
||||||
$autoCancel: false, // Disable auto-cancellation for this request
|
$autoCancel: false,
|
||||||
$cancelKey: `uptime_record_${data.serviceId}_${Date.now()}` // Unique key for this request
|
$cancelKey: `uptime_record_${data.serviceId}_${Date.now()}`
|
||||||
};
|
};
|
||||||
|
|
||||||
// Store uptime history in the uptime_data collection
|
|
||||||
// Format data for PocketBase (use snake_case)
|
|
||||||
const record = await pb.collection('uptime_data').create({
|
const record = await pb.collection('uptime_data').create({
|
||||||
service_id: data.serviceId,
|
service_id: data.serviceId,
|
||||||
timestamp: data.timestamp,
|
timestamp: data.timestamp,
|
||||||
@@ -32,8 +27,9 @@ export const uptimeService = {
|
|||||||
response_time: data.responseTime
|
response_time: data.responseTime
|
||||||
}, options);
|
}, options);
|
||||||
|
|
||||||
// Invalidate cache for this service after recording new data
|
// Invalidate cache for this service
|
||||||
uptimeCache.delete(`uptime_${data.serviceId}`);
|
const keysToDelete = Array.from(uptimeCache.keys()).filter(key => key.includes(`uptime_${data.serviceId}`));
|
||||||
|
keysToDelete.forEach(key => uptimeCache.delete(key));
|
||||||
|
|
||||||
console.log(`Uptime data recorded successfully with ID: ${record.id}`);
|
console.log(`Uptime data recorded successfully with ID: ${record.id}`);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -49,10 +45,9 @@ export const uptimeService = {
|
|||||||
endDate?: Date
|
endDate?: Date
|
||||||
): Promise<UptimeData[]> {
|
): Promise<UptimeData[]> {
|
||||||
try {
|
try {
|
||||||
// Create cache key based on parameters
|
|
||||||
const cacheKey = `uptime_${serviceId}_${limit}_${startDate?.toISOString() || ''}_${endDate?.toISOString() || ''}`;
|
const cacheKey = `uptime_${serviceId}_${limit}_${startDate?.toISOString() || ''}_${endDate?.toISOString() || ''}`;
|
||||||
|
|
||||||
// Check if we have a valid cached result
|
// Check cache
|
||||||
const cached = uptimeCache.get(cacheKey);
|
const cached = uptimeCache.get(cacheKey);
|
||||||
if (cached && (Date.now() - cached.timestamp) < cached.expiresIn) {
|
if (cached && (Date.now() - cached.timestamp) < cached.expiresIn) {
|
||||||
console.log(`Using cached uptime history for service ${serviceId}`);
|
console.log(`Using cached uptime history for service ${serviceId}`);
|
||||||
@@ -61,80 +56,74 @@ export const uptimeService = {
|
|||||||
|
|
||||||
console.log(`Fetching uptime history for service ${serviceId}, limit: ${limit}`);
|
console.log(`Fetching uptime history for service ${serviceId}, limit: ${limit}`);
|
||||||
|
|
||||||
// Base filter for a specific service
|
|
||||||
let filter = `service_id='${serviceId}'`;
|
let filter = `service_id='${serviceId}'`;
|
||||||
|
|
||||||
// Add date range filtering if provided
|
// Add date range filtering if provided
|
||||||
if (startDate && endDate) {
|
if (startDate && endDate) {
|
||||||
// Convert to ISO strings for PocketBase filtering
|
// Convert dates to UTC strings in the format PocketBase expects
|
||||||
const startISO = startDate.toISOString();
|
const startUTC = startDate.toISOString();
|
||||||
const endISO = endDate.toISOString();
|
const endUTC = endDate.toISOString();
|
||||||
|
|
||||||
// Log the date range we're filtering by
|
console.log(`Date filter: ${startUTC} to ${endUTC}`);
|
||||||
console.log(`Date range filter: ${startISO} to ${endISO}`);
|
|
||||||
|
|
||||||
filter += ` && timestamp >= '${startISO}' && timestamp <= '${endISO}'`;
|
// Use proper PocketBase date filtering syntax
|
||||||
|
filter += ` && timestamp >= "${startUTC}" && timestamp <= "${endUTC}"`;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Calculate time difference to determine if it's a short range (like 60min)
|
|
||||||
const isShortTimeRange = startDate && endDate &&
|
|
||||||
(endDate.getTime() - startDate.getTime() <= 60 * 60 * 1000);
|
|
||||||
|
|
||||||
// For very short time ranges, adjust sorting and limit
|
|
||||||
const sort = '-timestamp'; // Default: newest first
|
|
||||||
const actualLimit = isShortTimeRange ? Math.max(limit, 300) : limit; // Ensure adequate points for short ranges
|
|
||||||
|
|
||||||
// Create custom request options to disable auto-cancellation
|
|
||||||
const options = {
|
const options = {
|
||||||
filter: filter,
|
filter: filter,
|
||||||
sort: sort,
|
sort: '-timestamp',
|
||||||
$autoCancel: false, // Disable auto-cancellation for this request
|
$autoCancel: false,
|
||||||
$cancelKey: `uptime_history_${serviceId}_${Date.now()}` // Unique key for this request
|
$cancelKey: `uptime_history_${serviceId}_${Date.now()}`
|
||||||
};
|
};
|
||||||
|
|
||||||
try {
|
console.log(`Filter query: ${filter}`);
|
||||||
// Get uptime history for a specific service with date filtering
|
|
||||||
const response = await pb.collection('uptime_data').getList(1, actualLimit, options);
|
const response = await pb.collection('uptime_data').getList(1, limit, options);
|
||||||
|
|
||||||
|
console.log(`Fetched ${response.items.length} uptime records for service ${serviceId}`);
|
||||||
|
|
||||||
|
if (response.items.length > 0) {
|
||||||
|
console.log(`Date range in results: ${response.items[response.items.length - 1].timestamp} to ${response.items[0].timestamp}`);
|
||||||
|
} else {
|
||||||
|
console.log(`No records found for filter: ${filter}`);
|
||||||
|
|
||||||
console.log(`Fetched ${response.items.length} uptime records for service ${serviceId}`);
|
// Try a fallback query without date filter to see if there's any data at all
|
||||||
|
const fallbackResponse = await pb.collection('uptime_data').getList(1, 10, {
|
||||||
// Map and return the data
|
filter: `service_id='${serviceId}'`,
|
||||||
const uptimeData = response.items.map(item => ({
|
sort: '-timestamp',
|
||||||
id: item.id,
|
$autoCancel: false
|
||||||
serviceId: item.service_id,
|
|
||||||
timestamp: item.timestamp,
|
|
||||||
status: item.status,
|
|
||||||
responseTime: item.response_time || 0,
|
|
||||||
date: item.timestamp, // Mapping timestamp to date
|
|
||||||
uptime: 100 // Default value for uptime
|
|
||||||
}));
|
|
||||||
|
|
||||||
// For short time ranges with few data points, we might need to ensure we have enough points
|
|
||||||
const shouldAddPlaceholderData = isShortTimeRange && uptimeData.length <= 2;
|
|
||||||
|
|
||||||
let finalData = uptimeData;
|
|
||||||
|
|
||||||
if (shouldAddPlaceholderData && uptimeData.length > 0) {
|
|
||||||
// We'll add some additional data points to ensure graph is visible
|
|
||||||
console.log("Adding placeholder data points to ensure graph visibility");
|
|
||||||
finalData = [...uptimeData];
|
|
||||||
}
|
|
||||||
|
|
||||||
// Cache the result
|
|
||||||
uptimeCache.set(cacheKey, {
|
|
||||||
data: finalData,
|
|
||||||
timestamp: Date.now(),
|
|
||||||
expiresIn: CACHE_TTL
|
|
||||||
});
|
});
|
||||||
|
|
||||||
return finalData;
|
console.log(`Fallback query found ${fallbackResponse.items.length} total records for service`);
|
||||||
} catch (err) {
|
if (fallbackResponse.items.length > 0) {
|
||||||
throw err;
|
console.log(`Latest record timestamp: ${fallbackResponse.items[0].timestamp}`);
|
||||||
|
console.log(`Oldest record timestamp: ${fallbackResponse.items[fallbackResponse.items.length - 1].timestamp}`);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const uptimeData = response.items.map(item => ({
|
||||||
|
id: item.id,
|
||||||
|
serviceId: item.service_id,
|
||||||
|
timestamp: item.timestamp,
|
||||||
|
status: item.status,
|
||||||
|
responseTime: item.response_time || 0,
|
||||||
|
date: item.timestamp,
|
||||||
|
uptime: 100
|
||||||
|
}));
|
||||||
|
|
||||||
|
// Cache the result
|
||||||
|
uptimeCache.set(cacheKey, {
|
||||||
|
data: uptimeData,
|
||||||
|
timestamp: Date.now(),
|
||||||
|
expiresIn: CACHE_TTL
|
||||||
|
});
|
||||||
|
|
||||||
|
return uptimeData;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error fetching uptime history:", error);
|
console.error("Error fetching uptime history:", error);
|
||||||
|
|
||||||
// Try to return cached data even if it's expired, as a fallback
|
// Try to return cached data as fallback
|
||||||
const cacheKey = `uptime_${serviceId}_${limit}_${startDate?.toISOString() || ''}_${endDate?.toISOString() || ''}`;
|
const cacheKey = `uptime_${serviceId}_${limit}_${startDate?.toISOString() || ''}_${endDate?.toISOString() || ''}`;
|
||||||
const cached = uptimeCache.get(cacheKey);
|
const cached = uptimeCache.get(cacheKey);
|
||||||
if (cached) {
|
if (cached) {
|
||||||
@@ -145,4 +134,4 @@ export const uptimeService = {
|
|||||||
throw new Error('Failed to load uptime history.');
|
throw new Error('Failed to load uptime history.');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
Reference in New Issue
Block a user