250 lines
9.3 KiB
TypeScript
Raw Normal View History

2025-04-12 12:33:37 +02:00
import { NextResponse, NextRequest } from "next/server";
2025-04-13 21:10:17 +02:00
import { prisma } from "@/lib/prisma";
2025-04-23 18:55:45 +02:00
import { Prisma } from "@prisma/client";
2025-04-12 12:33:37 +02:00
interface GetRequest {
2025-04-14 21:26:12 +02:00
page?: number;
ITEMS_PER_PAGE?: number;
2025-04-23 20:24:24 +02:00
timeRange?: '1h' | '1d' | '7d' | '30d';
2025-04-23 18:55:45 +02:00
serverId?: number;
2025-04-12 12:33:37 +02:00
}
2025-04-23 20:24:24 +02:00
const getTimeRange = (timeRange: '1h' | '1d' | '7d' | '30d' = '1h') => {
2025-04-23 18:55:45 +02:00
const now = new Date();
switch (timeRange) {
2025-04-23 20:24:24 +02:00
case '1d':
return {
start: new Date(now.getTime() - 24 * 60 * 60 * 1000),
end: now,
intervalMinutes: 15 // 15 minute intervals
};
2025-04-23 18:55:45 +02:00
case '7d':
return {
start: new Date(now.getTime() - 7 * 24 * 60 * 60 * 1000),
end: now,
intervalMinutes: 60 // 1 hour intervals
};
case '30d':
return {
start: new Date(now.getTime() - 30 * 24 * 60 * 60 * 1000),
end: now,
intervalMinutes: 240 // 4 hour intervals
};
case '1h':
default:
return {
start: new Date(now.getTime() - 60 * 60 * 1000),
end: now,
intervalMinutes: 1 // 1 minute intervals
};
}
};
2025-04-23 20:24:24 +02:00
const getIntervals = (timeRange: '1h' | '1d' | '7d' | '30d' = '1h') => {
2025-04-23 18:55:45 +02:00
const { start, end, intervalMinutes } = getTimeRange(timeRange);
let intervalCount: number;
switch (timeRange) {
2025-04-23 20:24:24 +02:00
case '1d':
intervalCount = 96; // 24 hours * 4 (15-minute intervals)
break;
2025-04-23 18:55:45 +02:00
case '7d':
intervalCount = 168; // 7 days * 24 hours
break;
case '30d':
intervalCount = 180; // 30 days * 6 (4-hour intervals)
break;
case '1h':
default:
intervalCount = 60;
break;
}
// Calculate the total time span in minutes
const totalMinutes = Math.floor((end.getTime() - start.getTime()) / (1000 * 60));
// Create equally spaced intervals
return Array.from({ length: intervalCount }, (_, i) => {
const minutesFromEnd = Math.floor(i * (totalMinutes / (intervalCount - 1)));
const d = new Date(end.getTime() - minutesFromEnd * 60 * 1000);
return d;
}).reverse(); // Return in chronological order
};
const parseUsageValue = (value: string | null): number => {
if (!value) return 0;
2025-04-23 20:14:06 +02:00
return Math.round(parseFloat(value.replace('%', '')) * 100) / 100;
2025-04-23 18:55:45 +02:00
};
2025-04-12 12:33:37 +02:00
export async function POST(request: NextRequest) {
try {
const body: GetRequest = await request.json();
const page = Math.max(1, body.page || 1);
2025-04-14 21:26:12 +02:00
const ITEMS_PER_PAGE = body.ITEMS_PER_PAGE || 4;
2025-04-23 18:55:45 +02:00
const timeRange = body.timeRange || '1h';
const serverId = body.serverId;
2025-04-18 14:35:36 +02:00
2025-04-23 18:55:45 +02:00
// If serverId is provided, only fetch that specific server
const hostsQuery = serverId
? { id: serverId }
: { hostServer: 0 };
let hosts;
if (!serverId) {
hosts = await prisma.server.findMany({
where: hostsQuery,
orderBy: { name: 'asc' as Prisma.SortOrder },
skip: (page - 1) * ITEMS_PER_PAGE,
take: ITEMS_PER_PAGE,
});
} else {
hosts = await prisma.server.findMany({
where: hostsQuery,
orderBy: { name: 'asc' as Prisma.SortOrder },
});
}
const { start } = getTimeRange(timeRange);
const intervals = getIntervals(timeRange);
2025-04-12 12:33:37 +02:00
2025-04-18 14:35:36 +02:00
const hostsWithVms = await Promise.all(
hosts.map(async (host) => {
const vms = await prisma.server.findMany({
2025-04-18 14:35:36 +02:00
where: { hostServer: host.id },
orderBy: { name: 'asc' }
});
2025-04-23 18:55:45 +02:00
// Get server history for the host
const serverHistory = await prisma.server_history.findMany({
where: {
serverId: host.id,
createdAt: {
gte: start
}
},
orderBy: {
createdAt: 'asc'
}
});
// Process history data into intervals
const historyMap = new Map<string, {
cpu: number[],
ram: number[],
disk: number[],
online: boolean[]
}>();
// Initialize intervals
intervals.forEach(date => {
const key = date.toISOString();
historyMap.set(key, {
cpu: [],
ram: [],
disk: [],
online: []
});
});
// Group data by interval
serverHistory.forEach(record => {
const recordDate = new Date(record.createdAt);
let nearestInterval: Date = intervals[0];
let minDiff = Infinity;
// Find the nearest interval for this record
intervals.forEach(intervalDate => {
const diff = Math.abs(recordDate.getTime() - intervalDate.getTime());
if (diff < minDiff) {
minDiff = diff;
nearestInterval = intervalDate;
}
});
const key = nearestInterval.toISOString();
const interval = historyMap.get(key);
if (interval) {
interval.cpu.push(parseUsageValue(record.cpuUsage));
interval.ram.push(parseUsageValue(record.ramUsage));
interval.disk.push(parseUsageValue(record.diskUsage));
interval.online.push(record.online);
}
});
// Calculate averages for each interval
const historyData = intervals.map(date => {
const key = date.toISOString();
const data = historyMap.get(key) || {
cpu: [],
ram: [],
disk: [],
online: []
};
const average = (arr: number[]) =>
2025-04-23 20:14:06 +02:00
arr.length ? Math.round((arr.reduce((a, b) => a + b, 0) / arr.length) * 100) / 100 : null;
2025-04-23 18:55:45 +02:00
return {
timestamp: key,
cpu: average(data.cpu),
ram: average(data.ram),
disk: average(data.disk),
online: data.online.length ?
data.online.filter(Boolean).length / data.online.length >= 0.5
: null
};
});
// Add isVM flag to VMs
const vmsWithFlag = vms.map(vm => ({
...vm,
isVM: true,
hostedVMs: [] // Initialize empty hostedVMs array for VMs
}));
return {
...host,
2025-04-23 18:55:45 +02:00
isVM: false,
hostedVMs: vmsWithFlag,
history: {
labels: intervals.map(d => d.toISOString()),
datasets: {
cpu: intervals.map(d => {
const data = historyMap.get(d.toISOString())?.cpu || [];
2025-04-23 20:14:06 +02:00
return data.length ? Math.round((data.reduce((a, b) => a + b) / data.length) * 100) / 100 : null;
2025-04-23 18:55:45 +02:00
}),
ram: intervals.map(d => {
const data = historyMap.get(d.toISOString())?.ram || [];
2025-04-23 20:14:06 +02:00
return data.length ? Math.round((data.reduce((a, b) => a + b) / data.length) * 100) / 100 : null;
2025-04-23 18:55:45 +02:00
}),
disk: intervals.map(d => {
const data = historyMap.get(d.toISOString())?.disk || [];
2025-04-23 20:14:06 +02:00
return data.length ? Math.round((data.reduce((a, b) => a + b) / data.length) * 100) / 100 : null;
2025-04-23 18:55:45 +02:00
}),
online: intervals.map(d => {
const data = historyMap.get(d.toISOString())?.online || [];
return data.length ? data.filter(Boolean).length / data.length >= 0.5 : null;
})
}
}
};
})
2025-04-18 14:35:36 +02:00
);
2025-04-23 18:55:45 +02:00
// Only calculate maxPage when not requesting a specific server
let maxPage = 1;
if (!serverId) {
const totalHosts = await prisma.server.count({
where: { OR: [{ hostServer: 0 }, { hostServer: null }] }
});
maxPage = Math.ceil(totalHosts / ITEMS_PER_PAGE);
}
2025-04-12 12:33:37 +02:00
return NextResponse.json({
2025-04-18 14:35:36 +02:00
servers: hostsWithVms,
2025-04-12 12:33:37 +02:00
maxPage
});
} catch (error: any) {
return NextResponse.json({ error: error.message }, { status: 500 });
}
}