Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions application/src/components/dashboard/Sidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -117,10 +117,10 @@ export const Sidebar = ({
<BookOpen className="h-4 w-4 mr-2" />
<span className="text-sm">{t("alertsTemplates")}</span>
</Link>
<div className={getMenuItemClasses(false)}>
<Link to={`/settings?panel=data-retention`} className={getMenuItemClasses(activeSettingsItem === 'data-retention')} onClick={() => handleSettingsItemClick('data-retention')}>
<Database className="h-4 w-4 mr-2" />
<span className="text-sm">{t("dataRetention")}</span>
</div>
</Link>
<Link to={`/settings?panel=about`} className={getMenuItemClasses(activeSettingsItem === 'about')} onClick={() => handleSettingsItemClick('about')}>
<Info className="h-4 w-4 mr-2" />
<span className="text-sm">{t("aboutSystem")}</span>
Expand Down
38 changes: 17 additions & 21 deletions application/src/components/profile/UpdateProfileForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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({
Expand All @@ -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<string | null>(null);
const [updateSuccess, setUpdateSuccess] = useState<string | null>(null);
const { toast } = useToast();
const navigate = useNavigate();

// Initialize the form with current user data
const form = useForm<ProfileFormValues>({
Expand All @@ -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);
Expand All @@ -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",
Expand Down Expand Up @@ -132,16 +138,6 @@ export function UpdateProfileForm({ user }: UpdateProfileFormProps) {
</AlertDescription>
</Alert>
)}

{emailChangeRequested && (
<Alert className="bg-yellow-50 border-yellow-200 text-yellow-800">
<AlertCircle className="h-4 w-4 text-yellow-600" />
<AlertDescription>
A verification email has been sent to your new email address.
Please check your inbox and follow the instructions to complete the change.
</AlertDescription>
</Alert>
)}

<FormField
control={form.control}
Expand Down Expand Up @@ -183,7 +179,7 @@ export function UpdateProfileForm({ user }: UpdateProfileFormProps) {
<FormMessage />
{field.value !== user.email && (
<p className="text-xs text-muted-foreground mt-1">
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.
</p>
)}
</FormItem>
Expand Down
53 changes: 21 additions & 32 deletions application/src/components/services/DateRangeFilter.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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 (
Expand All @@ -105,7 +95,6 @@ export function DateRangeFilter({ onRangeChange, selectedOption = '24h' }: DateR
<SelectValue placeholder="Select time range" />
</SelectTrigger>
<SelectContent>
<SelectItem value="60min">Last 60 minutes</SelectItem>
<SelectItem value="24h">Last 24 hours</SelectItem>
<SelectItem value="7d">Last 7 days</SelectItem>
<SelectItem value="30d">Last 30 days</SelectItem>
Expand Down Expand Up @@ -153,4 +142,4 @@ export function DateRangeFilter({ onRangeChange, selectedOption = '24h' }: DateR
)}
</div>
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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<Service | null>(null);
Expand All @@ -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
});
Expand All @@ -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({
Expand All @@ -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({
Expand All @@ -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);
});
Expand All @@ -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);
Expand All @@ -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,
Expand All @@ -170,4 +146,4 @@ export const useServiceData = (serviceId: string | undefined, startDate: Date, e
handleStatusChange,
fetchUptimeData
};
};
};
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@

import { useState, useEffect, useCallback } from "react";
import { useParams, useNavigate } from "react-router-dom";
import { DateRangeOption } from "../DateRangeFilter";
Expand All @@ -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<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;
});
const [endDate, setEndDate] = useState<Date>(new Date());
const [selectedRange, setSelectedRange] = useState<DateRangeOption>('24h');

// State for sidebar collapse functionality (shared with Dashboard)
Expand Down Expand Up @@ -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]);
Expand All @@ -123,4 +128,4 @@ export const ServiceDetailContainer = () => {
)}
</ServiceDetailWrapper>
);
};
};
Loading