Files
wevia-brain/s89-ai-apis/sentinel-brain.php
2026-04-12 23:01:36 +02:00

306 lines
12 KiB
PHP
Executable File
Raw Permalink Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
// === SECURITY: IP Whitelist (added 2026-03-02) ===
$allowed = array("127.0.0.1","127.0.0.1","46.62.220.135","88.198.4.195","151.80.235.110","89.167.1.139","105.159.122.54","41.250.206.28","41.251.40.10","41.250.81.114","41.251.82.55","::1");
$client_ip = $_SERVER["REMOTE_ADDR"] ?? "";
if (!in_array($client_ip, $allowed)) { http_response_code(403); die(json_encode(["error"=>"Forbidden"])); }
// === END SECURITY ===
/**
* SENTINEL V2 — Monitoring & Alerting Only
* ==========================================
* REPLACES the bloated 720-line sentinel-brain.php
*
* Philosophy: READ-ONLY monitoring. No file modifications, no exec for external AI,
* no cron manipulation. Only 'exec' action preserved for Sentinel remote management.
*
* Actions:
* exec — Run shell command (preserved for remote management via Claude)
* exec_remote — SSH to OVH tracking server
* health — Full system health dashboard
* alerts — Active alerts requiring attention
* pipeline — Pipeline status snapshot
* db_check — Database integrity checks
* services — Service status
* disk — Disk/memory usage
* logs — Recent error logs
* tracking — OVH tracking stats
*/
error_reporting(E_ERROR);
header('Content-Type: application/json');
// CORS locked by WAF shield
header('Access-Control-Allow-Methods: GET, POST, OPTIONS');
header('Access-Control-Allow-Headers: Content-Type');
if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') { http_response_code(200); exit; }
// ======================== DB ========================
function db() {
static $pdo;
if (!$pdo) {
$pdo = new PDO('pgsql:host=localhost;dbname=adx_system', 'admin', 'admin123');
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
}
return $pdo;
}
function qval($sql) {
try { return db()->query($sql)->fetchColumn() ?: 0; } catch(Exception $e) { return 0; }
}
function qall($sql) {
try { return db()->query($sql)->fetchAll(PDO::FETCH_ASSOC) ?: []; } catch(Exception $e) { return []; }
}
// ======================== CORE EXEC (preserved) ========================
function doExec($cmd) {
if (empty($cmd)) return ['error' => 'no command'];
$output = [];
$code = 0;
exec($cmd . ' 2>&1', $output, $code);
return ['output' => implode("\n", $output), 'exit_code' => $code];
}
function doRemoteExec($cmd, $host = '151.80.235.110', $user = 'ubuntu', $pass = 'MX8D3zSAty7k3243242', $port = 22) {
$escaped = escapeshellarg($cmd);
$sshCmd = "sshpass -p '$pass' ssh -o StrictHostKeyChecking=no -o ConnectTimeout=10 -p $port $user@$host $escaped 2>&1";
$output = [];
exec($sshCmd, $output);
return ['output' => implode("\n", $output), 'host' => $host];
}
// ======================== MONITORING FUNCTIONS ========================
function getServiceStatus() {
$services = ['apache2', 'postgresql', 'pmta', 'ollama', 'cron'];
$result = [];
foreach ($services as $s) {
$st = trim(shell_exec("systemctl is-active $s 2>/dev/null") ?: 'unknown');
$result[$s] = $st;
}
return $result;
}
function getDiskMemory() {
$disk_raw = trim(shell_exec("df -h / 2>/dev/null | tail -1") ?: '');
$parts = preg_split('/\s+/', $disk_raw);
$mem_raw = trim(shell_exec("free -m 2>/dev/null | grep Mem") ?: '');
$mparts = preg_split('/\s+/', $mem_raw);
$load = trim(shell_exec("cat /proc/loadavg 2>/dev/null") ?: '');
$uptime = trim(shell_exec("uptime -p 2>/dev/null") ?: '');
return [
'disk_total' => $parts[1] ?? 'N/A',
'disk_used' => $parts[2] ?? 'N/A',
'disk_pct' => $parts[4] ?? 'N/A',
'ram_total_mb' => (int)($mparts[1] ?? 0),
'ram_used_mb' => (int)($mparts[2] ?? 0),
'ram_pct' => ($mparts[1] ?? 0) > 0 ? round(($mparts[2] ?? 0) / $mparts[1] * 100) . '%' : 'N/A',
'load' => $load,
'uptime' => $uptime,
];
}
function getPipelineStatus() {
return [
'offers' => (int)qval("SELECT COUNT(*) FROM affiliate.offers WHERE status='Activated'"),
'creatives' => (int)qval("SELECT COUNT(*) FROM affiliate.creatives WHERE status='Activated'"),
'from_names' => (int)qval("SELECT COUNT(*) FROM affiliate.from_names WHERE status='Activated'"),
'subjects' => (int)qval("SELECT COUNT(*) FROM affiliate.subjects WHERE status='Activated'"),
'links' => (int)qval("SELECT COUNT(*) FROM affiliate.links WHERE status='Activated'"),
'brain_configs' => (int)qval("SELECT COUNT(*) FROM admin.brain_configs WHERE is_active=true"),
'brain_winners' => (int)qval("SELECT COUNT(*) FROM admin.brain_configs WHERE is_winner=true"),
'send_methods' => (int)qval("SELECT COUNT(*) FROM admin.send_methods WHERE is_active=true"),
'o365_senders' => (int)qval("SELECT COUNT(*) FROM admin.office_accounts WHERE status='graph_send'"),
'o365_active' => (int)qval("SELECT COUNT(*) FROM admin.office_accounts WHERE status IN ('Active','active','graph_ok','graph_send')"),
'contacts_active' => (int)qval("SELECT COUNT(*) FROM admin.contacts WHERE status='active'"),
'sent_today' => (int)qval("SELECT COUNT(*) FROM admin.unified_send_log WHERE created_at::date = CURRENT_DATE"),
'sent_total' => (int)qval("SELECT COUNT(*) FROM admin.unified_send_log"),
'domains' => (int)qval("SELECT COUNT(*) FROM admin.freedns_domains WHERE status='verified'"),
'isps' => (int)qval("SELECT COUNT(*) FROM admin.isp_configs"),
];
}
function getAlerts() {
$alerts = [];
$p = getPipelineStatus();
// Critical alerts (🔴)
if ($p['offers'] == 0) $alerts[] = ['level'=>'critical', 'msg'=>'Aucune offre importée', 'fix'=>'offer-importer-api.php?action=import_all'];
if ($p['o365_senders'] == 0) $alerts[] = ['level'=>'critical', 'msg'=>'Aucun compte O365 en mode envoi', 'fix'=>'Configurer graph_send sur des comptes actifs'];
// Warnings (⚠️)
if ($p['creatives'] == 0 && $p['offers'] > 0) $alerts[] = ['level'=>'warning', 'msg'=>'Offres sans créatives', 'fix'=>'Importer les créatives'];
if ($p['brain_winners'] == 0) $alerts[] = ['level'=>'warning', 'msg'=>'Aucun brain winner', 'fix'=>'Les tests brain sont en cours'];
if ($p['o365_senders'] < 3 && $p['o365_senders'] > 0) $alerts[] = ['level'=>'warning', 'msg'=>"Seulement {$p['o365_senders']} sender(s) O365", 'fix'=>'Ajouter plus de comptes en graph_send'];
// Service checks
$svcs = getServiceStatus();
foreach ($svcs as $name => $status) {
if ($status !== 'active' && $name !== 'ollama') {
$alerts[] = ['level'=>'critical', 'msg'=>"Service $name: $status", 'fix'=>"systemctl restart $name"];
}
}
// Disk check
$dm = getDiskMemory();
$diskPct = (int)str_replace('%', '', $dm['disk_pct']);
if ($diskPct > 90) $alerts[] = ['level'=>'critical', 'msg'=>"Disque à {$dm['disk_pct']}", 'fix'=>'Nettoyer les logs/tmp'];
elseif ($diskPct > 80) $alerts[] = ['level'=>'warning', 'msg'=>"Disque à {$dm['disk_pct']}", 'fix'=>'Surveiller l\'espace'];
// Info ()
if ($p['sent_today'] > 0) $alerts[] = ['level'=>'info', 'msg'=>"{$p['sent_today']} emails envoyés aujourd'hui"];
if ($p['brain_winners'] > 0) $alerts[] = ['level'=>'info', 'msg'=>"{$p['brain_winners']} brain winners actifs"];
return $alerts;
}
function getDbIntegrity() {
$checks = [];
// Table existence
$required = [
'affiliate.offers', 'affiliate.creatives', 'affiliate.links',
'affiliate.from_names', 'affiliate.subjects',
'admin.brain_configs', 'admin.send_methods', 'admin.office_accounts',
'admin.contacts', 'admin.unified_send_log', 'admin.offer_creatives'
];
foreach ($required as $t) {
try {
$c = qval("SELECT COUNT(*) FROM $t");
$checks[$t] = ['exists'=>true, 'rows'=>(int)$c];
} catch(Exception $e) {
$checks[$t] = ['exists'=>false, 'error'=>$e->getMessage()];
}
}
// Sequence health
$seqs = qall("SELECT sequencename, last_value FROM pg_sequences WHERE schemaname IN ('affiliate','admin','public') AND last_value IS NOT NULL ORDER BY sequencename");
$checks['sequences'] = count($seqs);
// DB size
$checks['db_size'] = qval("SELECT pg_size_pretty(pg_database_size('adx_system'))");
return $checks;
}
function getRecentLogs($lines = 30) {
$logs = [];
// PHP errors
$phpErr = trim(shell_exec("tail -$lines /var/log/wevads/php-error.log 2>/dev/null | grep -v '^$' | tail -10") ?: '');
if ($phpErr) $logs['php_errors'] = explode("\n", $phpErr);
// Apache errors (last relevant ones)
$apacheErr = trim(shell_exec("tail -$lines /var/log/apache2/error.log 2>/dev/null | grep -v 'AH00558\\|ssl:warn\\|mpm_prefork' | tail -10") ?: '');
if ($apacheErr) $logs['apache_errors'] = explode("\n", $apacheErr);
// Framework errors
$fwErr = trim(shell_exec("tail -10 /opt/wevads/storage/logs/frontend_errors.log 2>/dev/null") ?: '');
if ($fwErr) $logs['framework_errors'] = explode("\n", $fwErr);
return $logs;
}
function getTrackingStats() {
$r = doRemoteExec("wc -l /var/www/html/logs/opens.log /var/www/html/logs/clicks.log 2>/dev/null && echo '---' && tail -5 /var/www/html/logs/clicks.log 2>/dev/null");
return ['raw' => $r['output'], 'host' => '151.80.235.110'];
}
// ======================== ROUTER ========================
$action = $_GET['action'] ?? $_POST['action'] ?? 'health';
try {
switch ($action) {
case 'exec':
$cmd = $_POST['cmd'] ?? $_GET['cmd'] ?? '';
echo json_encode(doExec($cmd));
break;
case 'exec_remote':
$cmd = $_POST['cmd'] ?? $_GET['cmd'] ?? '';
$host = $_POST['host'] ?? '151.80.235.110';
echo json_encode(doRemoteExec($cmd, $host));
break;
case 'health':
echo json_encode([
'timestamp' => date('c'),
'services' => getServiceStatus(),
'system' => getDiskMemory(),
'pipeline' => getPipelineStatus(),
'alerts' => getAlerts(),
]);
break;
case 'alerts':
echo json_encode(['alerts' => getAlerts(), 'timestamp' => date('c')]);
break;
case 'pipeline':
echo json_encode(getPipelineStatus());
break;
case 'db_check':
echo json_encode(getDbIntegrity());
break;
case 'services':
echo json_encode(getServiceStatus());
break;
case 'disk':
echo json_encode(getDiskMemory());
break;
case 'logs':
echo json_encode(getRecentLogs());
break;
case 'tracking':
echo json_encode(getTrackingStats());
break;
// Legacy compatibility
case 'status':
echo json_encode([
'sentinel' => 'v2',
'mode' => 'monitoring',
'services' => getServiceStatus(),
'pipeline' => getPipelineStatus(),
]);
break;
case 'scan':
case 'architecture':
case 'arch':
// Return health instead of old broken scan
echo json_encode([
'timestamp' => date('c'),
'services' => getServiceStatus(),
'system' => getDiskMemory(),
'pipeline' => getPipelineStatus(),
'alerts' => getAlerts(),
'db' => getDbIntegrity(),
]);
break;
// Disabled dangerous actions
case 'chat':
case 'history':
case 'fixes':
case 'patterns':
echo json_encode(['info' => 'Action disabled in Sentinel V2 (monitoring-only mode)', 'use' => 'hamid-chef.php for AI chat']);
break;
default:
echo json_encode([
'error' => 'Unknown action',
'available' => ['exec','exec_remote','health','alerts','pipeline','db_check','services','disk','logs','tracking','status','scan'],
'disabled' => ['chat','history','fixes','patterns (use hamid-chef.php)'],
]);
}
} catch (Exception $e) {
http_response_code(500);
echo json_encode(['error' => $e->getMessage()]);
}