111 lines
4.2 KiB
TypeScript
111 lines
4.2 KiB
TypeScript
import { NextRequest, NextResponse } from 'next/server'
|
|
import { statsCollectionService } from '@/lib/services/stats-collection-service'
|
|
import { logScanningService } from '@/lib/services/log-scanning-service'
|
|
import { containerHealthService } from '@/lib/services/container-health-service'
|
|
|
|
/**
|
|
* GET /api/cron/collect-stats
|
|
* Collect stats, scan logs for errors, and check container health for all enterprise servers
|
|
*
|
|
* This endpoint is designed to be called by a cron job or scheduled task.
|
|
* It should be protected by a secret token in production.
|
|
*
|
|
* Example cron schedule: Every 5 minutes
|
|
* Vercel cron config in vercel.json:
|
|
* {
|
|
* "crons": [
|
|
* { "path": "/api/cron/collect-stats", "schedule": "*\/5 * * * *" }
|
|
* ]
|
|
* }
|
|
*
|
|
* What this cron does:
|
|
* 1. Collects performance stats from Netcup + Portainer for all active servers
|
|
* 2. Scans container logs for errors matching client-defined rules
|
|
* 3. Checks container health and detects crashes/OOM kills
|
|
*/
|
|
export async function GET(request: NextRequest) {
|
|
// Verify cron secret (for security in production)
|
|
const cronSecret = process.env.CRON_SECRET
|
|
const authHeader = request.headers.get('authorization')
|
|
|
|
if (cronSecret && authHeader !== `Bearer ${cronSecret}`) {
|
|
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
|
|
}
|
|
|
|
const startTime = Date.now()
|
|
const results = {
|
|
stats: { collected: 0, failed: 0 },
|
|
logScan: { totalServers: 0, scannedServers: 0, failedServers: 0, totalErrorsFound: 0, duration: 0 },
|
|
healthCheck: { totalServers: 0, checkedServers: 0, failedServers: 0, eventsDetected: 0, crashes: 0, oomKills: 0, duration: 0 },
|
|
errors: [] as string[],
|
|
}
|
|
|
|
// 1. Collect performance stats (existing functionality)
|
|
try {
|
|
results.stats = await statsCollectionService.collectAllStats()
|
|
console.log(`[Cron] Stats collection: ${results.stats.collected} servers, ${results.stats.failed} failed`)
|
|
} catch (error) {
|
|
console.error('[Cron] Stats collection failed:', error)
|
|
results.errors.push(`Stats collection: ${error instanceof Error ? error.message : 'Unknown error'}`)
|
|
}
|
|
|
|
// 2. Scan container logs for errors
|
|
try {
|
|
results.logScan = await logScanningService.scanAllServers()
|
|
console.log(`[Cron] Log scan: ${results.logScan.scannedServers}/${results.logScan.totalServers} servers, ${results.logScan.totalErrorsFound} errors found`)
|
|
} catch (error) {
|
|
console.error('[Cron] Log scanning failed:', error)
|
|
results.errors.push(`Log scanning: ${error instanceof Error ? error.message : 'Unknown error'}`)
|
|
}
|
|
|
|
// 3. Check container health (crash detection)
|
|
try {
|
|
results.healthCheck = await containerHealthService.checkAllServers()
|
|
console.log(`[Cron] Health check: ${results.healthCheck.checkedServers}/${results.healthCheck.totalServers} servers, ${results.healthCheck.eventsDetected} events detected`)
|
|
} catch (error) {
|
|
console.error('[Cron] Health check failed:', error)
|
|
results.errors.push(`Health check: ${error instanceof Error ? error.message : 'Unknown error'}`)
|
|
}
|
|
|
|
const totalDuration = Date.now() - startTime
|
|
const hasErrors = results.errors.length > 0
|
|
|
|
return NextResponse.json({
|
|
success: !hasErrors,
|
|
timestamp: new Date().toISOString(),
|
|
duration: `${totalDuration}ms`,
|
|
|
|
// Stats collection results
|
|
stats: {
|
|
serversCollected: results.stats.collected,
|
|
serversFailed: results.stats.failed,
|
|
},
|
|
|
|
// Log scanning results
|
|
logScan: {
|
|
serversScanned: results.logScan.scannedServers,
|
|
serversFailed: results.logScan.failedServers,
|
|
errorsFound: results.logScan.totalErrorsFound,
|
|
duration: `${results.logScan.duration}ms`,
|
|
},
|
|
|
|
// Health check results
|
|
healthCheck: {
|
|
serversChecked: results.healthCheck.checkedServers,
|
|
serversFailed: results.healthCheck.failedServers,
|
|
eventsDetected: results.healthCheck.eventsDetected,
|
|
crashes: results.healthCheck.crashes,
|
|
oomKills: results.healthCheck.oomKills,
|
|
duration: `${results.healthCheck.duration}ms`,
|
|
},
|
|
|
|
// Any errors that occurred
|
|
...(hasErrors && { errors: results.errors }),
|
|
}, { status: hasErrors ? 207 : 200 }) // 207 Multi-Status if partial failure
|
|
}
|
|
|
|
// Also support POST for flexibility
|
|
export async function POST(request: NextRequest) {
|
|
return GET(request)
|
|
}
|