Update Uptime Pagination

This commit is contained in:
headlessdev 2025-04-15 14:09:36 +02:00
parent e34407539a
commit a320c04b92
2 changed files with 216 additions and 158 deletions

View File

@ -3,7 +3,9 @@ import { prisma } from "@/lib/prisma";
interface RequestBody {
timespan?: number;
}
page?: number;
}
const getTimeRange = (timespan: number) => {
const now = new Date();
@ -85,20 +87,36 @@ 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 { timespan = 1, page = 1 }: RequestBody = await request.json();
const itemsPerPage = 5;
const skip = (page - 1) * itemsPerPage;
const applications = await prisma.application.findMany();
// 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: applications.map(app => app.id) },
applicationId: { in: applicationIds },
createdAt: { gte: start }
},
orderBy: { createdAt: "desc" }
});
const intervals = generateIntervals(timespan);
// Process data for each application
const result = applications.map(app => {
const appChecks = uptimeHistory.filter(check => check.applicationId === app.id);
const checksMap = new Map<string, { failed: number; total: number }>();
@ -115,18 +133,10 @@ export async function POST(request: NextRequest) {
const intervalKey = getIntervalKey(interval, timespan);
const stats = checksMap.get(intervalKey);
if (!stats) {
return {
timestamp: interval.toISOString(),
missing: true,
online: null
};
}
return {
timestamp: intervalKey,
missing: false,
online: stats.failed < 3
missing: !stats,
online: stats ? stats.failed < 3 : null
};
});
@ -137,9 +147,16 @@ export async function POST(request: NextRequest) {
};
});
return NextResponse.json(result);
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 });
}
}
}

View File

@ -63,41 +63,62 @@ interface UptimeData {
}[];
}
interface PaginationData {
currentPage: number;
totalPages: number;
totalItems: number;
}
export default function Uptime() {
const [data, setData] = useState<UptimeData[]>([]);
const [timespan, setTimespan] = useState<1 | 2 | 3>(1);
const [currentPage, setCurrentPage] = useState(1);
const itemsPerPage = 5;
const maxPage = Math.ceil(data.length / itemsPerPage);
const paginatedData = data.slice(
(currentPage - 1) * itemsPerPage,
currentPage * itemsPerPage
);
const getData = async (selectedTimespan: number) => {
try {
const response = await axios.post<UptimeData[]>("/api/applications/uptime", {
timespan: selectedTimespan
const [pagination, setPagination] = useState<PaginationData>({
currentPage: 1,
totalPages: 1,
totalItems: 0
});
setData(response.data);
setCurrentPage(1);
const [isLoading, setIsLoading] = useState(false);
const getData = async (selectedTimespan: number, page: number) => {
setIsLoading(true);
try {
const response = await axios.post<{
data: UptimeData[];
pagination: PaginationData;
}>("/api/applications/uptime", {
timespan: selectedTimespan,
page
});
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() {
<span className="text-2xl font-semibold">Uptime</span>
<Select
value={String(timespan)}
onValueChange={(v) => setTimespan(Number(v) as 1 | 2 | 3)}
onValueChange={(v) => {
setTimespan(Number(v) as 1 | 2 | 3);
setPagination(prev => ({...prev, currentPage: 1}));
}}
disabled={isLoading}
>
<SelectTrigger className="w-[180px]">
<SelectValue placeholder="Select timespan" />
@ -142,8 +167,12 @@ export default function Uptime() {
</SelectContent>
</Select>
</div>
<div className="pt-4 space-y-4">
{paginatedData.map((app) => {
{isLoading ? (
<div className="text-center py-8">Loading...</div>
) : (
data.map((app) => {
const reversedSummary = [...app.uptimeSummary].reverse();
const startTime = reversedSummary[0]?.timestamp;
const endTime = reversedSummary[reversedSummary.length - 1]?.timestamp;
@ -223,34 +252,46 @@ export default function Uptime() {
</CardHeader>
</Card>
);
})}
})
)}
</div>
{data.length > 0 && (
{pagination.totalItems > 0 && !isLoading && (
<div className="pt-4 pb-4">
<Pagination>
<PaginationContent>
<PaginationItem>
<PaginationPrevious
onClick={handlePrevious}
aria-disabled={currentPage === 1}
className={currentPage === 1 ? "opacity-50 cursor-not-allowed" : ""}
aria-disabled={pagination.currentPage === 1 || isLoading}
className={
pagination.currentPage === 1 || isLoading
? "opacity-50 cursor-not-allowed"
: "hover:cursor-pointer"
}
/>
</PaginationItem>
<PaginationItem>
<span className="px-4">
Page {currentPage} of {maxPage}
Page {pagination.currentPage} of {pagination.totalPages}
</span>
</PaginationItem>
<PaginationItem>
<PaginationNext
onClick={handleNext}
aria-disabled={currentPage === maxPage}
className={currentPage === maxPage ? "opacity-50 cursor-not-allowed" : ""}
aria-disabled={pagination.currentPage === pagination.totalPages || isLoading}
className={
pagination.currentPage === pagination.totalPages || isLoading
? "opacity-50 cursor-not-allowed"
: "hover:cursor-pointer"
}
/>
</PaginationItem>
</PaginationContent>
</Pagination>
<div className="text-center text-sm text-muted-foreground mt-2">
Showing {data.length} of {pagination.totalItems} applications
</div>
</div>
)}
</div>