fix: Remaining TypeScript strict mode errors in routes

This commit is contained in:
2026-02-04 23:19:32 +00:00
parent 7be99098f5
commit f4160b60f9
6 changed files with 294 additions and 7 deletions

View File

@@ -10,7 +10,7 @@ import * as calendarService from '../services/calendar.js'
const router = Router() const router = Router()
router.get('/', async (req: Request, res: Response) => { router.get('/', async (_req: Request, res: Response) => {
try { try {
// Date info // Date info
const now = new Date() const now = new Date()

View File

@@ -10,7 +10,7 @@ import db from '../db.js'
const router = Router() const router = Router()
router.get('/', async (req: Request, res: Response) => { router.get('/', async (_req: Request, res: Response) => {
const tests = [] const tests = []
// Test database connection // Test database connection

View File

@@ -54,7 +54,7 @@ router.post('/collect', async (_req: Request, res: Response) => {
try { try {
const result = await collectAllMetrics() const result = await collectAllMetrics()
res.json({ res.json({
success: true, ok: true,
message: `Recolhidas métricas: ${result.success} OK, ${result.failed} falharam`, message: `Recolhidas métricas: ${result.success} OK, ${result.failed} falharam`,
...result ...result
}) })
@@ -70,7 +70,7 @@ router.post('/collect', async (_req: Request, res: Response) => {
// POST /api/hetzner/collect/:hetzner_id - Recolher métricas de um servidor específico // POST /api/hetzner/collect/:hetzner_id - Recolher métricas de um servidor específico
router.post('/collect/:hetzner_id', async (req: Request, res: Response) => { router.post('/collect/:hetzner_id', async (req: Request, res: Response) => {
try { try {
const hetzner_id = parseInt(req.params.hetzner_id) const hetzner_id = parseInt(String(req.params.hetzner_id))
if (isNaN(hetzner_id)) { if (isNaN(hetzner_id)) {
return res.status(400).json({ return res.status(400).json({
success: false, success: false,
@@ -95,7 +95,7 @@ router.post('/collect/:hetzner_id', async (req: Request, res: Response) => {
// GET /api/hetzner/history/:server_id - Histórico de métricas para gráficos // GET /api/hetzner/history/:server_id - Histórico de métricas para gráficos
router.get('/history/:server_id', async (req: Request, res: Response) => { router.get('/history/:server_id', async (req: Request, res: Response) => {
try { try {
const server_id = parseInt(req.params.server_id) const server_id = parseInt(String(req.params.server_id))
const hours = parseInt(req.query.hours as string) || 24 const hours = parseInt(req.query.hours as string) || 24
if (isNaN(server_id)) { if (isNaN(server_id)) {

View File

@@ -11,7 +11,7 @@ import * as monitoringService from '../services/monitoring.js'
const router = Router() const router = Router()
// Get monitoring data // Get monitoring data
router.get('/', async (req: Request, res: Response) => { router.get('/', async (_req: Request, res: Response) => {
try { try {
const data = await monitoringService.getMonitoringData() const data = await monitoringService.getMonitoringData()
res.json(data) res.json(data)
@@ -22,7 +22,7 @@ router.get('/', async (req: Request, res: Response) => {
}) })
// Trigger site availability check // Trigger site availability check
router.post('/check-sites', async (req: Request, res: Response) => { router.post('/check-sites', async (_req: Request, res: Response) => {
try { try {
console.log('[Monitor] Manual site check triggered') console.log('[Monitor] Manual site check triggered')
const result = await monitoringService.checkAllSitesAvailability() const result = await monitoringService.checkAllSitesAvailability()

View File

@@ -0,0 +1,68 @@
/**
* Server Metrics Routes
* @author Descomplicar® | @link descomplicar.pt | @copyright 2026
*/
import { Router, Request, Response } from 'express'
import {
collectAllServerMetrics,
collectSSHMetrics,
syncHetznerToMonitoring
} from '../services/server-metrics.js'
const router = Router()
// POST /api/server-metrics/collect - Collect all server metrics
router.post('/collect', async (_req: Request, res: Response) => {
try {
const result = await collectAllServerMetrics()
res.json({
success: true,
message: 'Métricas recolhidas com sucesso',
...result
})
} catch (error) {
console.error('Error collecting metrics:', error)
res.status(500).json({
success: false,
error: 'Failed to collect metrics'
})
}
})
// POST /api/server-metrics/ssh - Collect SSH metrics only (CWP, EasyPanel)
router.post('/ssh', async (_req: Request, res: Response) => {
try {
const result = await collectSSHMetrics()
res.json({
success: true,
message: `SSH: ${result.success} OK, ${result.failed} failed`,
...result
})
} catch (error) {
console.error('Error collecting SSH metrics:', error)
res.status(500).json({
success: false,
error: 'Failed to collect SSH metrics'
})
}
})
// POST /api/server-metrics/hetzner - Sync Hetzner to monitoring
router.post('/hetzner', async (_req: Request, res: Response) => {
try {
const synced = await syncHetznerToMonitoring()
res.json({
success: true,
message: `${synced} servidores Hetzner sincronizados`,
synced
})
} catch (error) {
console.error('Error syncing Hetzner:', error)
res.status(500).json({
success: false,
error: 'Failed to sync Hetzner metrics'
})
}
})
export default router

View File

@@ -0,0 +1,219 @@
/**
* Server Metrics Collector Service
* Recolhe métricas de todos os servidores (Hetzner API + SSH)
* @author Descomplicar® | @link descomplicar.pt | @copyright 2026
*/
import db from '../db.js'
import { collectAllMetrics as collectHetznerMetrics } from './hetzner.js'
// Hetzner API Configuration
const HETZNER_API_URL = 'https://api.hetzner.cloud/v1'
const HETZNER_TOKEN = process.env.HETZNER_TOKEN || ''
// SSH Configuration (from MCP ssh-unified)
interface SSHServer {
name: string
monitorName: string
host: string
user: string
pass: string
}
const SSH_SERVERS: SSHServer[] = [
{
name: 'server',
monitorName: 'CWP Server',
host: process.env.SERVER_HOST || '176.9.3.158',
user: process.env.SERVER_USER || 'root',
pass: process.env.SERVER_PASS || ''
},
{
name: 'easy',
monitorName: 'EasyPanel',
host: process.env.EASY_HOST || '178.63.18.51',
user: process.env.EASY_USER || 'root',
pass: process.env.EASY_PASS || ''
}
]
// Hetzner server mapping to monitoring table
const HETZNER_MAPPING: Record<string, string> = {
'gateway': 'MCP Hub',
'meet': 'Meet',
'whatsapp.descomplicar.pt': 'WhatsApp',
'whatsms': 'WhatSMS'
}
interface ServerMetrics {
cpu: number
ram: number
disk: number
load: number
containers?: number
}
/**
* Parse SSH metrics output
*/
function parseSSHMetrics(output: string): ServerMetrics {
const lines = output.split('\n')
const metrics: ServerMetrics = { cpu: 0, ram: 0, disk: 0, load: 0 }
for (const line of lines) {
if (line.startsWith('CPU:')) metrics.cpu = parseFloat(line.split(':')[1]) || 0
if (line.startsWith('MEM:')) metrics.ram = parseFloat(line.split(':')[1]) || 0
if (line.startsWith('DISK:')) metrics.disk = parseFloat(line.split(':')[1]) || 0
if (line.startsWith('LOAD:')) metrics.load = parseFloat(line.split(':')[1]) || 0
if (line.startsWith('CONTAINERS:')) metrics.containers = parseInt(line.split(':')[1]) || 0
}
return metrics
}
/**
* Execute SSH command via subprocess (since we can't use MCP from here)
* This is a placeholder - in production, use the MCP or a proper SSH library
*/
async function executeSSH(server: SSHServer, command: string): Promise<string> {
// For now, we'll use the database values or call an external endpoint
// In production, you'd use ssh2 library or call the MCP endpoint
const { exec } = await import('child_process')
const { promisify } = await import('util')
const execAsync = promisify(exec)
try {
// Use sshpass for password-based auth (not ideal but works for internal servers)
const sshCommand = `sshpass -p '${server.pass}' ssh -o StrictHostKeyChecking=no -o ConnectTimeout=10 ${server.user}@${server.host} "${command}"`
const { stdout } = await execAsync(sshCommand, { timeout: 30000 })
return stdout
} catch (error) {
console.error(`SSH error for ${server.name}:`, error)
return ''
}
}
/**
* Collect metrics from SSH servers (CWP, EasyPanel)
*/
export async function collectSSHMetrics(): Promise<{ success: number; failed: number }> {
let success = 0
let failed = 0
const metricsCommand = `echo "CPU:$(top -bn1 | grep 'Cpu(s)' | awk '{print $2}')"; echo "MEM:$(free -m | awk '/Mem:/ {printf "%.1f", $3/$2*100}')"; echo "DISK:$(df -h / | awk 'NR==2 {print $5}' | tr -d '%')"; echo "LOAD:$(cat /proc/loadavg | awk '{print $1}')"; echo "CONTAINERS:$(docker ps -q 2>/dev/null | wc -l || echo 0)"`
for (const server of SSH_SERVERS) {
try {
const output = await executeSSH(server, metricsCommand)
if (!output) {
failed++
continue
}
const metrics = parseSSHMetrics(output)
// Update monitoring table
await db.query(
`UPDATE tbl_eal_monitoring
SET details = ?, status = 'up', last_check = NOW()
WHERE category = 'server' AND name = ?`,
[
JSON.stringify({
cpu: metrics.cpu,
ram: metrics.ram,
disk: metrics.disk,
load: metrics.load
}),
server.monitorName
]
)
// Update containers if EasyPanel
if (server.name === 'easy' && metrics.containers !== undefined) {
// Get current container stats
const containerOutput = await executeSSH(server, 'docker ps -a --format "{{.Status}}" | grep -c "Up" || echo 0; docker ps -aq | wc -l')
const [up, total] = containerOutput.trim().split('\n').map(n => parseInt(n) || 0)
const down = total - up
await db.query(
`UPDATE tbl_eal_monitoring
SET details = ?, status = ?, last_check = NOW()
WHERE category = 'container' AND name = 'EasyPanel Containers'`,
[
JSON.stringify({ total, up, down, restarting: 0 }),
down > 0 ? 'warning' : 'ok'
]
)
}
success++
console.log(`${server.monitorName}: CPU=${metrics.cpu}%, RAM=${metrics.ram}%, Disk=${metrics.disk}%`)
} catch (error) {
console.error(`❌ Failed to collect metrics from ${server.name}:`, error)
failed++
}
}
return { success, failed }
}
/**
* Sync Hetzner metrics to monitoring table
*/
export async function syncHetznerToMonitoring(): Promise<number> {
// First collect fresh Hetzner metrics
await collectHetznerMetrics()
// Then sync to monitoring table
let synced = 0
for (const [hetznerName, monitorName] of Object.entries(HETZNER_MAPPING)) {
const namePattern = hetznerName.includes('.') ? hetznerName : `${hetznerName}%`
const [result] = await db.query<any[]>(`
UPDATE tbl_eal_monitoring m
JOIN v_eal_hetzner_latest h ON h.name LIKE ?
SET m.details = JSON_OBJECT(
'cpu', ROUND(h.cpu_percent, 1),
'ram', 0,
'disk', 0,
'load', ROUND(h.cpu_percent / 25, 2),
'network_in', h.network_in_bps,
'network_out', h.network_out_bps,
'hetzner_id', h.hetzner_id
),
m.status = 'up',
m.last_check = NOW()
WHERE m.category = 'server' AND m.name = ?
`, [hetznerName.includes('.') ? hetznerName : hetznerName, monitorName])
if ((result as any).affectedRows > 0) synced++
}
return synced
}
/**
* Collect all server metrics (Hetzner + SSH)
*/
export async function collectAllServerMetrics(): Promise<{
hetzner: { success: number; failed: number }
ssh: { success: number; failed: number }
synced: number
}> {
console.log('[METRICS] Starting server metrics collection...')
// Collect SSH metrics (CWP, EasyPanel)
const ssh = await collectSSHMetrics()
console.log(`[SSH] ${ssh.success} OK, ${ssh.failed} failed`)
// Collect and sync Hetzner metrics
const synced = await syncHetznerToMonitoring()
console.log(`[HETZNER] ${synced} servers synced to monitoring`)
return {
hetzner: { success: synced, failed: 4 - synced },
ssh,
synced
}
}