Files
J-Board-Lite/src/app/(admin)/admin/traffic/traffic-data.ts
2026-04-29 05:16:29 +10:00

112 lines
3.3 KiB
TypeScript

import type { Prisma } from "@prisma/client";
import { prisma } from "@/lib/prisma";
import { parsePage } from "@/lib/utils";
const trafficClientInclude = {
user: true,
inbound: { include: { server: true } },
subscription: { include: { plan: true } },
} satisfies Prisma.NodeClientInclude;
export type TrafficClientRow = Prisma.NodeClientGetPayload<{
include: typeof trafficClientInclude;
}>;
export interface TrafficTrendPoint {
date: string;
valueGb: number;
}
export interface TrafficOverview {
totalClients: number;
enabledClients: number;
disabledClients: number;
activeClients24h: number;
}
export async function getTrafficClients(
searchParams: Record<string, string | string[] | undefined>,
) {
const { page, skip, pageSize } = parsePage(searchParams);
const q = typeof searchParams.q === "string" ? searchParams.q.trim() : "";
const status = typeof searchParams.status === "string" ? searchParams.status : "";
const protocol = typeof searchParams.protocol === "string" ? searchParams.protocol : "";
const where = {
...(status ? { isEnabled: status === "enabled" } : {}),
...(protocol
? {
inbound: {
protocol: protocol as "VMESS" | "VLESS" | "TROJAN" | "SHADOWSOCKS" | "HYSTERIA2",
},
}
: {}),
...(q
? {
OR: [
{ user: { email: { contains: q, mode: "insensitive" as const } } },
{ inbound: { server: { name: { contains: q, mode: "insensitive" as const } } } },
{ email: { contains: q, mode: "insensitive" as const } },
],
}
: {}),
} satisfies Prisma.NodeClientWhereInput;
const [clients, total] = await Promise.all([
prisma.nodeClient.findMany({
where,
include: trafficClientInclude,
orderBy: { updatedAt: "desc" },
skip,
take: pageSize,
}),
prisma.nodeClient.count({ where }),
]);
return { clients, total, page, pageSize, filters: { q, status, protocol } };
}
export async function getSiteTrafficTrend(days = 14): Promise<TrafficTrendPoint[]> {
const trafficWindowStart = new Date();
trafficWindowStart.setDate(trafficWindowStart.getDate() - days);
const logs = await prisma.trafficLog.findMany({
where: { timestamp: { gte: trafficWindowStart } },
select: { timestamp: true, upload: true, download: true },
orderBy: { timestamp: "asc" },
});
const trendMap = new Map<string, number>();
for (const log of logs) {
const day = new Date(log.timestamp).toISOString().slice(5, 10);
const valueGb = Number(log.upload + log.download) / 1024 ** 3;
trendMap.set(day, (trendMap.get(day) ?? 0) + valueGb);
}
return Array.from(trendMap.entries()).map(([date, valueGb]) => ({
date,
valueGb: Number(valueGb.toFixed(2)),
}));
}
export async function getTrafficOverview(): Promise<TrafficOverview> {
const since = new Date(Date.now() - 24 * 60 * 60 * 1000);
const [totalClients, enabledClients, activeClientIds] = await Promise.all([
prisma.nodeClient.count(),
prisma.nodeClient.count({ where: { isEnabled: true } }),
prisma.trafficLog.findMany({
where: { timestamp: { gte: since } },
select: { clientId: true },
distinct: ["clientId"],
}),
]);
return {
totalClients,
enabledClients,
disabledClients: totalClients - enabledClients,
activeClients24h: activeClientIds.length,
};
}