diff --git a/app/api/applications/uptime/route.ts b/app/api/applications/uptime/route.ts index 4f7510e..af72649 100644 --- a/app/api/applications/uptime/route.ts +++ b/app/api/applications/uptime/route.ts @@ -2,8 +2,10 @@ import { NextResponse, NextRequest } from "next/server"; import { prisma } from "@/lib/prisma"; interface RequestBody { - timespan?: number; -} + timespan?: number; + page?: number; + } + const getTimeRange = (timespan: number) => { const now = new Date(); @@ -84,62 +86,77 @@ const getIntervalKey = (date: Date, timespan: number) => { }; export async function POST(request: NextRequest) { - try { - const { timespan = 1 }: RequestBody = await request.json(); - const { start } = getTimeRange(timespan); - - const applications = await prisma.application.findMany(); - const uptimeHistory = await prisma.uptime_history.findMany({ - where: { - applicationId: { in: applications.map(app => app.id) }, - createdAt: { gte: start } - }, - orderBy: { createdAt: "desc" } - }); - - const intervals = generateIntervals(timespan); - - const result = applications.map(app => { - const appChecks = uptimeHistory.filter(check => check.applicationId === app.id); - const checksMap = new Map(); - - for (const check of appChecks) { - const intervalKey = getIntervalKey(check.createdAt, timespan); - const current = checksMap.get(intervalKey) || { failed: 0, total: 0 }; - current.total++; - if (!check.online) current.failed++; - checksMap.set(intervalKey, current); - } - - const uptimeSummary = intervals.map(interval => { - const intervalKey = getIntervalKey(interval, timespan); - const stats = checksMap.get(intervalKey); - - if (!stats) { - return { - timestamp: interval.toISOString(), - missing: true, - online: null - }; + try { + const { timespan = 1, page = 1 }: RequestBody = await request.json(); + const itemsPerPage = 5; + const skip = (page - 1) * itemsPerPage; + + // Get paginated and sorted applications + const [applications, totalCount] = await Promise.all([ + prisma.application.findMany({ + skip, + take: itemsPerPage, + orderBy: { name: 'asc' } + }), + prisma.application.count() + ]); + + const applicationIds = applications.map(app => app.id); + + // Get time range and intervals + const { start } = getTimeRange(timespan); + const intervals = generateIntervals(timespan); + + // Get uptime history for the filtered applications + const uptimeHistory = await prisma.uptime_history.findMany({ + where: { + applicationId: { in: applicationIds }, + createdAt: { gte: start } + }, + orderBy: { createdAt: "desc" } + }); + + // Process data for each application + const result = applications.map(app => { + const appChecks = uptimeHistory.filter(check => check.applicationId === app.id); + const checksMap = new Map(); + + for (const check of appChecks) { + const intervalKey = getIntervalKey(check.createdAt, timespan); + const current = checksMap.get(intervalKey) || { failed: 0, total: 0 }; + current.total++; + if (!check.online) current.failed++; + checksMap.set(intervalKey, current); } - + + const uptimeSummary = intervals.map(interval => { + const intervalKey = getIntervalKey(interval, timespan); + const stats = checksMap.get(intervalKey); + + return { + timestamp: intervalKey, + missing: !stats, + online: stats ? stats.failed < 3 : null + }; + }); + return { - timestamp: intervalKey, - missing: false, - online: stats.failed < 3 + appName: app.name, + appId: app.id, + uptimeSummary }; }); - - return { - appName: app.name, - appId: app.id, - uptimeSummary - }; - }); - - return NextResponse.json(result); - } catch (error: unknown) { - const message = error instanceof Error ? error.message : "Unknown error"; - return NextResponse.json({ error: message }, { status: 500 }); - } -} \ No newline at end of file + + return NextResponse.json({ + data: result, + pagination: { + currentPage: page, + totalPages: Math.ceil(totalCount / itemsPerPage), + totalItems: totalCount + } + }); + } catch (error: unknown) { + const message = error instanceof Error ? error.message : "Unknown error"; + return NextResponse.json({ error: message }, { status: 500 }); + } + } \ No newline at end of file diff --git a/app/dashboard/uptime/Uptime.tsx b/app/dashboard/uptime/Uptime.tsx index b00a325..01b5842 100644 --- a/app/dashboard/uptime/Uptime.tsx +++ b/app/dashboard/uptime/Uptime.tsx @@ -63,41 +63,62 @@ interface UptimeData { }[]; } +interface PaginationData { + currentPage: number; + totalPages: number; + totalItems: number; +} + export default function Uptime() { const [data, setData] = useState([]); const [timespan, setTimespan] = useState<1 | 2 | 3>(1); - const [currentPage, setCurrentPage] = useState(1); - const itemsPerPage = 5; + const [pagination, setPagination] = useState({ + currentPage: 1, + totalPages: 1, + totalItems: 0 + }); + const [isLoading, setIsLoading] = useState(false); - const maxPage = Math.ceil(data.length / itemsPerPage); - const paginatedData = data.slice( - (currentPage - 1) * itemsPerPage, - currentPage * itemsPerPage - ); - - const getData = async (selectedTimespan: number) => { + const getData = async (selectedTimespan: number, page: number) => { + setIsLoading(true); try { - const response = await axios.post("/api/applications/uptime", { - timespan: selectedTimespan + const response = await axios.post<{ + data: UptimeData[]; + pagination: PaginationData; + }>("/api/applications/uptime", { + timespan: selectedTimespan, + page }); - setData(response.data); - setCurrentPage(1); + + 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 = () => { - setCurrentPage((prev) => Math.max(1, prev - 1)); + const newPage = Math.max(1, pagination.currentPage - 1); + setPagination(prev => ({...prev, currentPage: newPage})); + getData(timespan, newPage); }; const handleNext = () => { - setCurrentPage((prev) => Math.min(maxPage, prev + 1)); + const newPage = Math.min(pagination.totalPages, pagination.currentPage + 1); + setPagination(prev => ({...prev, currentPage: newPage})); + getData(timespan, newPage); }; useEffect(() => { - getData(timespan); + getData(timespan, 1); }, [timespan]); return ( @@ -130,7 +151,11 @@ export default function Uptime() { Uptime -
- {paginatedData.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) : ""} +
+ {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}
- -
- {reversedSummary.map((entry) => ( - - -
- - - -
-

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

-

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

-
- -
-
- - ))} +
+
+ {startTime ? timeFormats[timespan](startTime) : ""} + {endTime ? timeFormats[timespan](endTime) : ""}
- + + +
+ {reversedSummary.map((entry) => ( + + +
+ + + +
+

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

+

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

+
+ +
+
+ + ))} +
+ +
-
- - - ); - })} + + + ); + }) + )}
- - {data.length > 0 && ( + + {pagination.totalItems > 0 && !isLoading && (
- Page {currentPage} of {maxPage} + Page {pagination.currentPage} of {pagination.totalPages} +
+ Showing {data.length} of {pagination.totalItems} applications +
)}