/** * Monitoring Queries Service * @author Descomplicar® | @link descomplicar.pt | @copyright 2026 */ import db from '../db.js' import type { RowDataPacket } from 'mysql2' interface MonitoringItem { id: number name: string category: string type: string status: string details: any last_check: string created_at: string updated_at: string } interface CategorySummary { category: string total: number ok: number warning: number critical: number } /** * Check if a URL is accessible (HTTP HEAD request) */ export async function checkSiteAvailability(url: string, timeout = 10000): Promise<{ available: boolean statusCode?: number responseTime?: number error?: string }> { const startTime = Date.now() const controller = new AbortController() const timeoutId = setTimeout(() => controller.abort(), timeout) try { const response = await fetch(url, { method: 'HEAD', signal: controller.signal, headers: { 'User-Agent': 'Descomplicar-Monitor/1.0' } }) clearTimeout(timeoutId) const responseTime = Date.now() - startTime return { available: response.ok || response.status < 500, statusCode: response.status, responseTime } } catch (error) { clearTimeout(timeoutId) return { available: false, error: (error as Error).message, responseTime: Date.now() - startTime } } } /** * Check all sites and update their availability status */ export async function checkAllSitesAvailability(): Promise<{ checked: number up: number down: number results: any[] }> { // Get all sites from monitoring table const [sites] = await db.query(` SELECT id, name, details FROM tbl_eal_monitoring WHERE category = 'site' `) const results: any[] = [] let up = 0 let down = 0 for (const site of sites) { const details = typeof site.details === 'string' ? JSON.parse(site.details) : site.details const siteUrl = details?.site_url || `https://${site.name}` const check = await checkSiteAvailability(siteUrl) // Update status if site is down if (!check.available) { await db.query( 'UPDATE tbl_eal_monitoring SET status = ?, last_check = NOW() WHERE id = ?', ['down', site.id] ) down++ } else { // If was down and now is up, set to 'up' (will be replaced by plugin data later) const currentStatus = details?.health?.status || 'ok' if (currentStatus === 'down') { await db.query( 'UPDATE tbl_eal_monitoring SET status = ?, last_check = NOW() WHERE id = ?', ['up', site.id] ) } up++ } results.push({ name: site.name, url: siteUrl, ...check }) } return { checked: sites.length, up, down, results } } export async function getMonitoringData() { // Get all items const [items] = await db.query(` SELECT * FROM tbl_eal_monitoring ORDER BY category, name `) // Get summary by category const [summary] = await db.query(` SELECT category, COUNT(*) as total, SUM(CASE WHEN status IN ('ok','up') THEN 1 ELSE 0 END) as ok, SUM(CASE WHEN status = 'warning' THEN 1 ELSE 0 END) as warning, SUM(CASE WHEN status IN ('failed','down') THEN 1 ELSE 0 END) as critical FROM tbl_eal_monitoring GROUP BY category `) // Parse details JSON and cast to MonitoringItem const itemsParsed: MonitoringItem[] = items.map(item => ({ ...item, details: typeof item.details === 'string' ? JSON.parse(item.details) : item.details } as MonitoringItem)) // Organize by category const data: Record = {} for (const item of itemsParsed) { if (!data[item.category]) { data[item.category] = [] } data[item.category].push(item) } // Calculate overall status let overall: 'ok' | 'warning' | 'critical' = 'ok' let total_critical = 0 let total_warning = 0 for (const s of summary as CategorySummary[]) { total_critical += s.critical total_warning += s.warning } if (total_critical > 0) overall = 'critical' else if (total_warning > 0) overall = 'warning' return { items: data, summary, overall, stats: { total_critical, total_warning, total_ok: items.length - total_critical - total_warning } } }