+
handleSettingsItemClick('about')}>
{t("aboutSystem")}
diff --git a/application/src/components/profile/UpdateProfileForm.tsx b/application/src/components/profile/UpdateProfileForm.tsx
index ac601ec..05ef7ff 100644
--- a/application/src/components/profile/UpdateProfileForm.tsx
+++ b/application/src/components/profile/UpdateProfileForm.tsx
@@ -11,6 +11,7 @@ import { useToast } from "@/hooks/use-toast";
import { authService } from "@/services/authService";
import { AlertCircle, CheckCircle } from "lucide-react";
import { Alert, AlertDescription } from "@/components/ui/alert";
+import { useNavigate } from "react-router-dom";
// Profile update form schema
const profileFormSchema = z.object({
@@ -33,10 +34,10 @@ interface UpdateProfileFormProps {
export function UpdateProfileForm({ user }: UpdateProfileFormProps) {
const [isSubmitting, setIsSubmitting] = useState(false);
- const [emailChangeRequested, setEmailChangeRequested] = useState(false);
const [updateError, setUpdateError] = useState(null);
const [updateSuccess, setUpdateSuccess] = useState(null);
const { toast } = useToast();
+ const navigate = useNavigate();
// Initialize the form with current user data
const form = useForm({
@@ -52,7 +53,6 @@ export function UpdateProfileForm({ user }: UpdateProfileFormProps) {
setIsSubmitting(true);
setUpdateError(null);
setUpdateSuccess(null);
- setEmailChangeRequested(false);
try {
console.log("Submitting profile update with data:", data);
@@ -75,19 +75,25 @@ export function UpdateProfileForm({ user }: UpdateProfileFormProps) {
// Update user data using the userService
await userService.updateUser(user.id, updateData);
- // Refresh user data in auth context
- await authService.refreshUserData();
-
- // If email was changed, show a specific message
+ // If email was changed, show success message and auto-logout
if (isEmailChanged) {
- setEmailChangeRequested(true);
- setUpdateSuccess("A verification email has been sent to your new email address. Please check your inbox to complete the change.");
+ setUpdateSuccess("Email changed successfully! You will be logged out for security reasons. Please log in again with your new email.");
+
toast({
- title: "Email verification sent",
- description: "A verification email has been sent to your new email address. Please check your inbox and follow the instructions to complete the change.",
+ title: "Email changed successfully",
+ description: "You will be logged out for security reasons. Please log in again with your new email.",
variant: "default",
});
+
+ // Auto-logout after 3 seconds
+ setTimeout(() => {
+ authService.logout();
+ navigate("/login");
+ }, 3000);
} else {
+ // Refresh user data in auth context for other field changes
+ await authService.refreshUserData();
+
setUpdateSuccess("Your profile information has been updated successfully.");
toast({
title: "Profile updated",
@@ -132,16 +138,6 @@ export function UpdateProfileForm({ user }: UpdateProfileFormProps) {
)}
-
- {emailChangeRequested && (
-
-
-
- A verification email has been sent to your new email address.
- Please check your inbox and follow the instructions to complete the change.
-
-
- )}
{field.value !== user.email && (
- Changing your email requires verification. A verification email will be sent.
+ Changing your email will log you out for security reasons. You will need to log in again with your new email.
)}
diff --git a/application/src/components/services/DateRangeFilter.tsx b/application/src/components/services/DateRangeFilter.tsx
index 51ddcf9..1e1a736 100644
--- a/application/src/components/services/DateRangeFilter.tsx
+++ b/application/src/components/services/DateRangeFilter.tsx
@@ -14,18 +14,17 @@ import {
SelectTrigger,
SelectValue,
} from "@/components/ui/select";
-import { format, subDays, subHours, subMonths, subWeeks, subYears } from "date-fns";
+import { format } from "date-fns";
import { Calendar as CalendarIcon } from "lucide-react";
import { cn } from "@/lib/utils";
-export type DateRangeOption = '60min' | '24h' | '7d' | '30d' | '1y' | 'custom';
+export type DateRangeOption = '24h' | '7d' | '30d' | '1y' | 'custom';
interface DateRangeFilterProps {
onRangeChange: (startDate: Date, endDate: Date, option: DateRangeOption) => void;
selectedOption?: DateRangeOption;
}
-// Define a proper type for the date range
interface DateRange {
from: Date | undefined;
to: Date | undefined;
@@ -45,57 +44,48 @@ export function DateRangeFilter({ onRangeChange, selectedOption = '24h' }: DateR
const now = new Date();
let startDate: Date;
+ let endDate: Date = new Date(now.getTime() + (5 * 60 * 1000)); // Add 5 minutes buffer to future
switch (option) {
- case '60min':
- // Ensure we're getting exactly 60 minutes ago
- startDate = new Date(now.getTime() - 60 * 60 * 1000);
- console.log(`60min option selected: ${startDate.toISOString()} to ${now.toISOString()}`);
- break;
case '24h':
- startDate = new Date(now.getTime() - 24 * 60 * 60 * 1000);
+ startDate = new Date(now.getTime() - (24 * 60 * 60 * 1000));
break;
case '7d':
- startDate = subDays(now, 7);
+ startDate = new Date(now.getTime() - (7 * 24 * 60 * 60 * 1000));
break;
case '30d':
- startDate = subDays(now, 30);
+ startDate = new Date(now.getTime() - (30 * 24 * 60 * 60 * 1000));
break;
case '1y':
- startDate = subYears(now, 1);
+ startDate = new Date(now.getTime() - (365 * 24 * 60 * 60 * 1000));
break;
case 'custom':
- // Don't trigger onRangeChange for custom until both dates are selected
setIsCalendarOpen(true);
return;
default:
- startDate = new Date(now.getTime() - 24 * 60 * 60 * 1000);
+ startDate = new Date(now.getTime() - (24 * 60 * 60 * 1000)); // Default to 24 hours
}
- console.log(`DateRangeFilter: Option changed to ${option}, date range: ${startDate.toISOString()} to ${now.toISOString()}`);
- onRangeChange(startDate, now, option);
+ console.log(`DateRangeFilter: ${option} selected, range: ${startDate.toISOString()} to ${endDate.toISOString()}`);
+ onRangeChange(startDate, endDate, option);
};
- // Handle custom date range selection
const handleCustomRangeSelect = (range: DateRange | undefined) => {
- if (!range) {
+ if (!range || !range.from || !range.to) {
return;
}
setCustomDateRange(range);
- if (range.from && range.to) {
- // Ensure that we have both from and to dates before triggering the change
- const startOfDay = new Date(range.from);
- startOfDay.setHours(0, 0, 0, 0);
-
- const endOfDay = new Date(range.to);
- endOfDay.setHours(23, 59, 59, 999);
-
- console.log(`DateRangeFilter: Custom range selected: ${startOfDay.toISOString()} to ${endOfDay.toISOString()}`);
- onRangeChange(startOfDay, endOfDay, 'custom');
- setIsCalendarOpen(false);
- }
+ const startOfDay = new Date(range.from);
+ startOfDay.setHours(0, 0, 0, 0);
+
+ const endOfDay = new Date(range.to);
+ endOfDay.setHours(23, 59, 59, 999);
+
+ console.log(`Custom range: ${startOfDay.toISOString()} to ${endOfDay.toISOString()}`);
+ onRangeChange(startOfDay, endOfDay, 'custom');
+ setIsCalendarOpen(false);
};
return (
@@ -105,7 +95,6 @@ export function DateRangeFilter({ onRangeChange, selectedOption = '24h' }: DateR
- Last 60 minutesLast 24 hoursLast 7 daysLast 30 days
@@ -153,4 +142,4 @@ export function DateRangeFilter({ onRangeChange, selectedOption = '24h' }: DateR
)}
);
-}
+}
\ No newline at end of file
diff --git a/application/src/components/services/ServiceDetailContainer/hooks/useServiceData.tsx b/application/src/components/services/ServiceDetailContainer/hooks/useServiceData.tsx
index b344764..32db1f8 100644
--- a/application/src/components/services/ServiceDetailContainer/hooks/useServiceData.tsx
+++ b/application/src/components/services/ServiceDetailContainer/hooks/useServiceData.tsx
@@ -5,6 +5,7 @@ import { Service, UptimeData } from "@/types/service.types";
import { useToast } from "@/hooks/use-toast";
import { useNavigate } from "react-router-dom";
import { uptimeService } from "@/services/uptimeService";
+import { DateRangeOption } from "../../DateRangeFilter";
export const useServiceData = (serviceId: string | undefined, startDate: Date, endDate: Date) => {
const [service, setService] = useState(null);
@@ -13,15 +14,12 @@ export const useServiceData = (serviceId: string | undefined, startDate: Date, e
const { toast } = useToast();
const navigate = useNavigate();
- // Handler for service status changes
const handleStatusChange = async (newStatus: "up" | "down" | "paused" | "warning") => {
if (!service || !serviceId) return;
try {
- // Optimistic UI update
setService({ ...service, status: newStatus as Service["status"] });
- // Update the service status in PocketBase
await pb.collection('services').update(serviceId, {
status: newStatus
});
@@ -32,7 +30,6 @@ export const useServiceData = (serviceId: string | undefined, startDate: Date, e
});
} catch (error) {
console.error("Failed to update service status:", error);
- // Revert the optimistic update
setService(prevService => prevService);
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: string) => {
+ const fetchUptimeData = async (serviceId: string, start: Date, end: Date, selectedRange?: DateRangeOption | string) => {
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 = 200; // default
+ let limit = 500; // Default limit
- // Adjust limits based on selected range
- if (selectedRange === '60min') {
- limit = 300; // More points for shorter time ranges
- } else if (selectedRange === '24h') {
- limit = 200;
+ if (selectedRange === '24h') {
+ limit = 300;
} else if (selectedRange === '7d') {
- limit = 250;
- } else if (selectedRange === '30d' || selectedRange === '1y') {
- limit = 300; // More points for longer time ranges
+ limit = 400;
}
console.log(`Using limit ${limit} for range ${selectedRange}`);
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) {
- console.log("No data returned from API, checking if we need to fetch with a higher limit");
- // 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) =>
+ // Sort by timestamp (newest first)
+ const filteredHistory = [...history].sort((a, b) =>
new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime()
);
- setUptimeData(sortedHistory);
- return sortedHistory;
+ setUptimeData(filteredHistory);
+ return filteredHistory;
} catch (error) {
console.error("Error fetching uptime data:", error);
toast({
@@ -110,7 +86,6 @@ export const useServiceData = (serviceId: string | undefined, startDate: Date, e
setIsLoading(true);
- // Add a timeout to prevent hanging
const timeoutPromise = new Promise((_, reject) => {
setTimeout(() => reject(new Error("Request timed out")), 10000);
});
@@ -136,7 +111,7 @@ export const useServiceData = (serviceId: string | undefined, startDate: Date, e
setService(formattedService);
- // Fetch uptime history with date range
+ // Fetch initial uptime history with 24h default
await fetchUptimeData(serviceId, startDate, endDate, '24h');
} catch (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
useEffect(() => {
- if (serviceId && !isLoading) {
- fetchUptimeData(serviceId, startDate, endDate, '24h');
+ if (serviceId && !isLoading && service) {
+ 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 {
service,
@@ -170,4 +146,4 @@ export const useServiceData = (serviceId: string | undefined, startDate: Date, e
handleStatusChange,
fetchUptimeData
};
-};
+};
\ No newline at end of file
diff --git a/application/src/components/services/ServiceDetailContainer/index.tsx b/application/src/components/services/ServiceDetailContainer/index.tsx
index 2bd7d2c..e23e054 100644
--- a/application/src/components/services/ServiceDetailContainer/index.tsx
+++ b/application/src/components/services/ServiceDetailContainer/index.tsx
@@ -1,4 +1,3 @@
-
import { useState, useEffect, useCallback } from "react";
import { useParams, useNavigate } from "react-router-dom";
import { DateRangeOption } from "../DateRangeFilter";
@@ -12,13 +11,17 @@ export const ServiceDetailContainer = () => {
const { id } = useParams<{ id: string }>();
const navigate = useNavigate();
- // Ensure we use exact timestamp for startDate
+ // Set default to 24h
const [startDate, setStartDate] = useState(() => {
const date = new Date();
- date.setHours(date.getHours() - 24);
+ date.setHours(date.getHours() - 24); // Go back 24 hours
+ return date;
+ });
+ const [endDate, setEndDate] = useState(() => {
+ const date = new Date();
+ date.setMinutes(date.getMinutes() + 5); // Add 5 minutes buffer to future
return date;
});
- const [endDate, setEndDate] = useState(new Date());
const [selectedRange, setSelectedRange] = useState('24h');
// State for sidebar collapse functionality (shared with Dashboard)
@@ -91,14 +94,16 @@ export const ServiceDetailContainer = () => {
// Handle date range filter changes
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);
setEndDate(end);
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) {
+ console.log(`ServiceDetailContainer: Explicitly fetching data for service ${id} with new range`);
fetchUptimeData(id, start, end, option);
}
}, [id, fetchUptimeData]);
@@ -123,4 +128,4 @@ export const ServiceDetailContainer = () => {
)}
);
-};
+};
\ No newline at end of file
diff --git a/application/src/components/services/ServiceRow.tsx b/application/src/components/services/ServiceRow.tsx
index 313f79e..fd9324c 100644
--- a/application/src/components/services/ServiceRow.tsx
+++ b/application/src/components/services/ServiceRow.tsx
@@ -56,7 +56,12 @@ export const ServiceRow = ({
-
+ {
+export const UptimeBar = ({ uptime, status, serviceId, interval = 60 }: UptimeBarProps) => {
const { theme } = useTheme();
const [historyItems, setHistoryItems] = useState([]);
// Fetch real uptime history data if serviceId is provided with improved caching and error handling
const { data: uptimeData, isLoading, error, isFetching, refetch } = useQuery({
queryKey: ['uptimeHistory', serviceId],
- queryFn: () => serviceId ? uptimeService.getUptimeHistory(serviceId, 20) : Promise.resolve([]),
+ queryFn: () => serviceId ? uptimeService.getUptimeHistory(serviceId, 50) : Promise.resolve([]),
enabled: !!serviceId,
refetchInterval: 30000, // Refresh every 30 seconds
staleTime: 15000, // Consider data fresh for 15 seconds
@@ -36,10 +36,45 @@ export const UptimeBar = ({ uptime, status, serviceId }: UptimeBarProps) => {
retryDelay: (attemptIndex) => Math.min(1000 * 2 ** attemptIndex, 10000), // Exponential backoff with max 10s
});
+ // Filter uptime data to respect the service interval
+ const filterUptimeDataByInterval = (data: UptimeData[], intervalSeconds: number): UptimeData[] => {
+ if (!data || data.length === 0) return [];
+
+ // Sort data by timestamp (newest first)
+ const sortedData = [...data].sort((a, b) =>
+ new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime()
+ );
+
+ const filtered: UptimeData[] = [];
+ let lastIncludedTime: number | null = null;
+ const intervalMs = intervalSeconds * 1000; // Convert to milliseconds
+
+ // Include the most recent record first
+ if (sortedData.length > 0) {
+ filtered.push(sortedData[0]);
+ lastIncludedTime = new Date(sortedData[0].timestamp).getTime();
+ }
+
+ // Filter subsequent records to maintain proper interval spacing
+ for (let i = 1; i < sortedData.length && filtered.length < 20; i++) {
+ const currentTime = new Date(sortedData[i].timestamp).getTime();
+
+ // Only include if enough time has passed since the last included record
+ if (lastIncludedTime && (lastIncludedTime - currentTime) >= intervalMs) {
+ filtered.push(sortedData[i]);
+ lastIncludedTime = currentTime;
+ }
+ }
+
+ return filtered;
+ };
+
// Update history items when data changes
useEffect(() => {
if (uptimeData && uptimeData.length > 0) {
- setHistoryItems(uptimeData);
+ // Filter data based on the service interval
+ const filteredData = filterUptimeDataByInterval(uptimeData, interval);
+ setHistoryItems(filteredData);
} else if (status === "paused" || (uptimeData && uptimeData.length === 0)) {
// For paused services with no history, or empty history data, show all as paused
const statusValue = (status === "up" || status === "down" || status === "warning" || status === "paused")
@@ -49,13 +84,13 @@ export const UptimeBar = ({ uptime, status, serviceId }: UptimeBarProps) => {
const placeholderHistory: UptimeData[] = Array(20).fill(null).map((_, index) => ({
id: `placeholder-${index}`,
serviceId: serviceId || "",
- timestamp: new Date().toISOString(),
+ timestamp: new Date(Date.now() - (index * interval * 1000)).toISOString(),
status: statusValue as "up" | "down" | "warning" | "paused",
responseTime: 0
}));
setHistoryItems(placeholderHistory);
}
- }, [uptimeData, serviceId, status]);
+ }, [uptimeData, serviceId, status, interval]);
// Get appropriate color classes for each status type
const getStatusColor = (itemStatus: string) => {
@@ -153,13 +188,19 @@ export const UptimeBar = ({ uptime, status, serviceId }: UptimeBarProps) => {
(status === "up" || status === "down" || status === "warning" || status === "paused") ?
status as "up" | "down" | "warning" | "paused" : "paused";
- const paddingItems: UptimeData[] = Array(20 - displayItems.length).fill(null).map((_, index) => ({
- id: `padding-${index}`,
- serviceId: serviceId || "",
- timestamp: new Date().toISOString(),
- status: lastStatus,
- responseTime: 0
- }));
+ // Generate padding items with proper time spacing
+ const paddingItems: UptimeData[] = Array(20 - displayItems.length).fill(null).map((_, index) => {
+ const baseTime = lastItem ? new Date(lastItem.timestamp).getTime() : Date.now();
+ const timeOffset = (index + 1) * interval * 1000; // Respect the interval
+
+ return {
+ id: `padding-${index}`,
+ serviceId: serviceId || "",
+ timestamp: new Date(baseTime - timeOffset).toISOString(),
+ status: lastStatus,
+ responseTime: 0
+ };
+ });
displayItems.push(...paddingItems);
}
@@ -201,10 +242,10 @@ export const UptimeBar = ({ uptime, status, serviceId }: UptimeBarProps) => {
{Math.round(uptime)}% uptime
- Last 20 checks
+ Last 20 checks
);
-}
+}
\ No newline at end of file
diff --git a/application/src/components/settings/about-system/AboutSystem.tsx b/application/src/components/settings/about-system/AboutSystem.tsx
index 9509d2e..ae2ce8d 100644
--- a/application/src/components/settings/about-system/AboutSystem.tsx
+++ b/application/src/components/settings/about-system/AboutSystem.tsx
@@ -1,30 +1,51 @@
-
-import React from 'react';
-import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
+import React, { useEffect, useState } from 'react';
+import { format } from 'date-fns';
+import {
+ Card, CardContent, CardDescription, CardHeader, CardTitle,
+} from "@/components/ui/card";
import { Separator } from "@/components/ui/separator";
-import { Github, FileText, Twitter, MessageCircle, Code2, ServerIcon } from "lucide-react";
+import {
+ Github, FileText, Twitter, MessageCircle, Code2, ServerIcon,
+} from "lucide-react";
import { Button } from "@/components/ui/button";
import { useLanguage } from "@/contexts/LanguageContext";
import { useTheme } from "@/contexts/ThemeContext";
import { useSystemSettings } from "@/hooks/useSystemSettings";
+
export const AboutSystem: React.FC = () => {
- const {
- t
- } = useLanguage();
- const {
- theme
- } = useTheme();
- const {
- systemName
- } = useSystemSettings();
- return