- Add POST /api/wp-monitor endpoint for WP plugin data - Add GET /api/wp-monitor for listing monitored sites - Add checkSiteAvailability() function for HTTP health checks - Add checkAllSitesAvailability() for batch checking - Add /api/scripts/check-sites.ts for cron execution - Add POST /api/monitor/check-sites for manual trigger DeskCRM Task: #1556 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
185 lines
4.3 KiB
TypeScript
185 lines
4.3 KiB
TypeScript
/**
|
|
* 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<RowDataPacket[]>(`
|
|
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<RowDataPacket[]>(`
|
|
SELECT * FROM tbl_eal_monitoring
|
|
ORDER BY category, name
|
|
`)
|
|
|
|
// Get summary by category
|
|
const [summary] = await db.query<RowDataPacket[]>(`
|
|
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
|
|
const itemsParsed = items.map(item => ({
|
|
...item,
|
|
details: typeof item.details === 'string' ? JSON.parse(item.details) : item.details
|
|
}))
|
|
|
|
// Organize by category
|
|
const data: Record<string, MonitoringItem[]> = {}
|
|
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
|
|
}
|
|
}
|
|
}
|