78 lines
2.9 KiB
TypeScript
78 lines
2.9 KiB
TypeScript
import { NextResponse } from 'next/server';
|
|
import { db } from "@/drizzle/db";
|
|
import { networkTelemetry, quantumLogs, liveKillSwitches } from "@/drizzle/schema";
|
|
import { cookies } from 'next/headers';
|
|
import jwt from 'jsonwebtoken';
|
|
import { desc, count, sql, eq } from 'drizzle-orm';
|
|
|
|
export const dynamic = 'force-dynamic';
|
|
|
|
interface DecodedToken {
|
|
userId: string;
|
|
email: string;
|
|
role: string;
|
|
tenantId: string;
|
|
}
|
|
|
|
// PANOPTICON: Tenant Admin Telemetry API (Sandboxed View)
|
|
// Returns ONLY data scoped to the caller's tenant_id.
|
|
export async function GET() {
|
|
try {
|
|
const cookieStore = await cookies();
|
|
const token = cookieStore.get('jumpa_token')?.value;
|
|
if (!token) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
|
|
|
|
const decoded = jwt.verify(token, process.env.JWT_SECRET as string) as DecodedToken;
|
|
if (decoded.role !== 'admin' && decoded.role !== 'superadmin') {
|
|
return NextResponse.json({ error: 'Admin Access Required' }, { status: 403 });
|
|
}
|
|
|
|
const tenantId = decoded.tenantId;
|
|
|
|
// 1. Telemetry scoped to tenant (last 50)
|
|
const recentTelemetry = await db.select().from(networkTelemetry)
|
|
.where(eq(networkTelemetry.tenantId, tenantId))
|
|
.orderBy(desc(networkTelemetry.timestamp))
|
|
.limit(50);
|
|
|
|
// 2. Quantum logs scoped to tenant (last 100)
|
|
const recentLogs = await db.select().from(quantumLogs)
|
|
.where(eq(quantumLogs.tenantId, tenantId))
|
|
.orderBy(desc(quantumLogs.nanoTimestamp))
|
|
.limit(100);
|
|
|
|
// 3. Kill switches scoped to tenant
|
|
const activeKills = await db.select().from(liveKillSwitches)
|
|
.where(eq(liveKillSwitches.tenantId, tenantId));
|
|
|
|
// 4. Aggregated stats (tenant-scoped)
|
|
const [telemetryCount] = await db.select({ total: count() }).from(networkTelemetry)
|
|
.where(eq(networkTelemetry.tenantId, tenantId));
|
|
const [logsCount] = await db.select({ total: count() }).from(quantumLogs)
|
|
.where(eq(quantumLogs.tenantId, tenantId));
|
|
|
|
// 5. Bandwidth aggregate (tenant-scoped)
|
|
const bandwidthAgg = await db.select({
|
|
totalBytes: sql<string>`COALESCE(SUM(CAST(traffic_bytes AS BIGINT)), 0)`,
|
|
avgResponseMs: sql<string>`COALESCE(AVG(CAST(response_time_ms AS NUMERIC)), 0)`,
|
|
}).from(networkTelemetry)
|
|
.where(eq(networkTelemetry.tenantId, tenantId));
|
|
|
|
return NextResponse.json({
|
|
telemetry: recentTelemetry,
|
|
logs: recentLogs,
|
|
kills: activeKills,
|
|
stats: {
|
|
totalTelemetryRecords: telemetryCount?.total || 0,
|
|
totalLogRecords: logsCount?.total || 0,
|
|
activeKillSwitches: activeKills.length,
|
|
totalBandwidthBytes: bandwidthAgg[0]?.totalBytes || '0',
|
|
avgResponseTimeMs: parseFloat(String(bandwidthAgg[0]?.avgResponseMs || '0')).toFixed(2),
|
|
},
|
|
});
|
|
} catch (error: unknown) {
|
|
console.error('[TENANT TELEMETRY ERROR]', error);
|
|
return NextResponse.json({ error: 'Telemetry Sync Failed' }, { status: 500 });
|
|
}
|
|
}
|