import { AppSidebar } from "@/components/app-sidebar"; import { Breadcrumb, BreadcrumbItem, BreadcrumbLink, BreadcrumbList, BreadcrumbPage, BreadcrumbSeparator, } from "@/components/ui/breadcrumb"; import { Separator } from "@/components/ui/separator"; import { SidebarInset, SidebarProvider, SidebarTrigger, } from "@/components/ui/sidebar"; import axios from "axios"; import { Card, CardHeader } from "@/components/ui/card"; import * as Tooltip from "@radix-ui/react-tooltip"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; import { Pagination, PaginationContent, PaginationItem, PaginationPrevious, PaginationNext, PaginationLink, } from "@/components/ui/pagination"; import { useState, useEffect, useRef } from "react"; import Cookies from "js-cookie"; import { Label } from "@/components/ui/label"; import { Input } from "@/components/ui/input"; import { toast } from "sonner"; import { Toaster } from "@/components/ui/sonner"; const timeFormats = { 1: (timestamp: string) => new Date(timestamp).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit', hour12: false }), 2: (timestamp: string) => new Date(timestamp).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit', hour12: false }), 3: (timestamp: string) => new Date(timestamp).toLocaleDateString([], { day: '2-digit', month: 'short' }), 4: (timestamp: string) => new Date(timestamp).toLocaleDateString([], { day: '2-digit', month: 'short' }) }; const minBoxWidths = { 1: 20, 2: 20, 3: 24, 4: 24 }; interface UptimeData { appName: string; appId: number; uptimeSummary: { timestamp: string; missing: boolean; online: boolean | null; }[]; } interface PaginationData { currentPage: number; totalPages: number; totalItems: number; } export default function Uptime() { const [data, setData] = useState([]); const [timespan, setTimespan] = useState<1 | 2 | 3 | 4>(1); const [pagination, setPagination] = useState({ currentPage: 1, totalPages: 1, totalItems: 0 }); const [isLoading, setIsLoading] = useState(false); const savedItemsPerPage = Cookies.get("itemsPerPage-uptime"); const defaultItemsPerPage = 5; const initialItemsPerPage = savedItemsPerPage ? parseInt(savedItemsPerPage) : defaultItemsPerPage; const [itemsPerPage, setItemsPerPage] = useState(initialItemsPerPage); const customInputRef = useRef(null); const debounceTimerRef = useRef(null); const getData = async (selectedTimespan: number, page: number, itemsPerPage: number) => { setIsLoading(true); try { const response = await axios.post<{ data: UptimeData[]; pagination: PaginationData; }>("/api/applications/uptime", { timespan: selectedTimespan, page, itemsPerPage }); setData(response.data.data); setPagination(response.data.pagination); } catch (error) { console.error("Error:", error); setData([]); setPagination({ currentPage: 1, totalPages: 1, totalItems: 0 }); } finally { setIsLoading(false); } }; const handlePrevious = () => { const newPage = Math.max(1, pagination.currentPage - 1); setPagination(prev => ({...prev, currentPage: newPage})); getData(timespan, newPage, itemsPerPage); }; const handleNext = () => { const newPage = Math.min(pagination.totalPages, pagination.currentPage + 1); setPagination(prev => ({...prev, currentPage: newPage})); getData(timespan, newPage, itemsPerPage); }; const handleItemsPerPageChange = (value: string) => { // Clear any existing timer if (debounceTimerRef.current) { clearTimeout(debounceTimerRef.current); } // Set a new timer debounceTimerRef.current = setTimeout(() => { const newItemsPerPage = parseInt(value); // Ensure the value is within the valid range if (isNaN(newItemsPerPage) || newItemsPerPage < 1) { toast.error("Please enter a number between 1 and 100"); return; } const validatedValue = Math.min(Math.max(newItemsPerPage, 1), 100); setItemsPerPage(validatedValue); setPagination(prev => ({...prev, currentPage: 1})); // Reset to first page Cookies.set("itemsPerPage-uptime", String(validatedValue), { expires: 365, path: "/", sameSite: "strict", }); // Fetch data with new pagination getData(timespan, 1, validatedValue); }, 300); // 300ms delay }; useEffect(() => { getData(timespan, 1, itemsPerPage); }, [timespan]); return (
/ My Infrastructure Uptime
Uptime
{ // Don't immediately apply the change while typing // Just validate the input for visual feedback const value = parseInt(e.target.value); if (isNaN(value) || value < 1 || value > 100) { e.target.classList.add("border-red-500"); } else { e.target.classList.remove("border-red-500"); } }} onBlur={(e) => { // Apply the change when the input loses focus const value = parseInt(e.target.value); if (value >= 1 && value <= 100) { handleItemsPerPageChange(e.target.value); } }} onKeyDown={(e) => { if (e.key === 'Enter') { // Clear any existing debounce timer to apply immediately if (debounceTimerRef.current) { clearTimeout(debounceTimerRef.current); debounceTimerRef.current = null; } const value = parseInt((e.target as HTMLInputElement).value); if (value >= 1 && value <= 100) { // Apply change immediately on Enter const validatedValue = Math.min(Math.max(value, 1), 100); setItemsPerPage(validatedValue); setPagination(prev => ({...prev, currentPage: 1})); Cookies.set("itemsPerPage-uptime", String(validatedValue), { expires: 365, path: "/", sameSite: "strict", }); getData(timespan, 1, validatedValue); // Close the dropdown document.body.click(); } } }} onClick={(e) => e.stopPropagation()} /> items
{isLoading ? (
Loading...
) : ( data.map((app) => { const reversedSummary = [...app.uptimeSummary].reverse(); const startTime = reversedSummary[0]?.timestamp; const endTime = reversedSummary[reversedSummary.length - 1]?.timestamp; return (
{app.appName}
{startTime ? timeFormats[timespan](startTime) : ""} {endTime ? timeFormats[timespan](endTime) : ""}
{reversedSummary.map((entry) => (

{new Date(entry.timestamp).toLocaleString([], { year: 'numeric', month: 'short', day: timespan > 2 ? 'numeric' : undefined, hour: '2-digit', minute: timespan === 1 ? '2-digit' : undefined, hour12: false })}

{entry.missing ? "No data" : entry.online ? "Online" : "Offline"}

))}
); }) )}
{pagination.totalItems > 0 && !isLoading && (
{pagination.totalItems > 0 ? `Showing ${((pagination.currentPage - 1) * itemsPerPage) + 1}-${Math.min(pagination.currentPage * itemsPerPage, pagination.totalItems)} of ${pagination.totalItems} items` : "No items found"}
{pagination.currentPage}
)}
); }