Files
html/api/wevia-ops.php
2026-04-16 14:55:04 +02:00

854 lines
45 KiB
PHP

<?php
header("Content-Type: application/json");
$k=$_REQUEST["k"]??"";if($k!=="BLADE2026"){echo json_encode(["e"=>"auth"]);exit;}
$action=$_REQUEST["action"]??"";
$results=[];
switch($action) {
case "test_providers":
$svcs=["deepseek","copilot","meta","qwen","perplexity","duckduckgo","lechat","huggingchat"];
// Parallel test with multi_curl
$mh = curl_multi_init();
$handles = [];
foreach($svcs as $s) {
$ch = curl_init("http://localhost/api/wevia-webchat-direct.php");
curl_setopt_array($ch,[CURLOPT_POST=>true,CURLOPT_RETURNTRANSFER=>true,CURLOPT_TIMEOUT=>15,CURLOPT_HTTPHEADER=>["Content-Type: application/json"],CURLOPT_POSTFIELDS=>json_encode(["service"=>$s,"message"=>"bonjour"])]);
curl_multi_add_handle($mh, $ch);
$handles[$s] = $ch;
}
$running = null;
do { curl_multi_exec($mh, $running); usleep(100000); } while ($running > 0);
foreach($handles as $s=>$ch) {
$r = json_decode(curl_multi_getcontent($ch), true);
$results[$s] = $r["provider"] ?? "FAIL";
curl_multi_remove_handle($mh, $ch);
curl_close($ch);
}
curl_multi_close($mh);
break;
// OLD sequential code below (disabled)
foreach($svcs as $DISABLED) {
$ch=curl_init("http://localhost/api/wevia-webchat-direct.php");
curl_setopt_array($ch,[CURLOPT_POST=>true,CURLOPT_RETURNTRANSFER=>true,CURLOPT_TIMEOUT=>12,CURLOPT_HTTPHEADER=>["Content-Type: application/json"],CURLOPT_POSTFIELDS=>json_encode(["service"=>$s,"message"=>"bonjour"])]);
$r=json_decode(curl_exec($ch),true);curl_close($ch);
$results[$s]=$r["provider"]??"FAIL";
}
break;
case "webchat":
$ch=curl_init("http://localhost:8902/health");
curl_setopt_array($ch,[CURLOPT_RETURNTRANSFER=>true,CURLOPT_TIMEOUT=>5]);
$r=json_decode(curl_exec($ch),true);curl_close($ch);
$results=$r;
break;
case "nonreg":
$ch=curl_init("http://127.0.0.1/api/nonreg-api.php?cat=all");
curl_setopt_array($ch,[CURLOPT_RETURNTRANSFER=>true,CURLOPT_TIMEOUT=>30]);
$r=json_decode(curl_exec($ch),true);curl_close($ch);
$results=["pass"=>$r["pass"]??0,"total"=>$r["total"]??0];
break;
case "reconcile":
$results["ports"]=trim(shell_exec('ss -tlnp | grep LISTEN | wc -l'));
$results["pages"]=trim(shell_exec('ls /var/www/html/*.html 2>/dev/null | wc -l'));
$results["git_dirty"]=trim(shell_exec('cd /var/www/html && git status -s | wc -l'));
$results["crons"]=trim(shell_exec('crontab -l 2>/dev/null | grep -v "^#" | grep -v "^$" | wc -l'));
$results["docker"]=trim(shell_exec('docker ps --format "{{.Names}}" 2>/dev/null | wc -l'));
$results["webchat"]=trim(shell_exec('curl -s http://localhost:8902/health 2>/dev/null | python3 -c "import sys,json;d=json.loads(sys.stdin.read());print(len(d.get(\"services\",[])))" 2>/dev/null'));
break;
case "git_push":
$results["html"]=trim(shell_exec('cd /var/www/html && git add -A && git commit -m "auto-push" 2>&1 | tail -n 2 && git push 2>&1 | tail -n 2'));
break;
case "gen_pdf":
$title = $_REQUEST["title"] ?? "WEVAL Report";
$body = $_REQUEST["body"] ?? "Rapport genere par WEVIA";
$fname = "report_" . date("Ymd_His") . ".pdf";
$path = "/var/www/html/downloads/$fname";
$py = "from fpdf import FPDF; p=FPDF(); p.add_page(); p.set_font(\"Helvetica\",\"B\",16); p.cell(0,10,\"" . addslashes($title) . "\",ln=True); p.set_font(\"Helvetica\",\"\",11); p.multi_cell(0,7,\"" . addslashes(substr($body,0,2000)) . "\"); p.output(\"$path\")";
shell_exec("python3 -c " . escapeshellarg($py) . " 2>&1");
$results["pdf"] = file_exists($path) ? "https://weval-consulting.com/downloads/$fname" : "FAIL";
$results["size"] = file_exists($path) ? filesize($path) : 0;
break;
case "gen_mermaid":
$code = $_REQUEST["code"] ?? "graph TD; A-->B; B-->C;";
$fname = "diagram_" . date("Ymd_His") . ".png";
$mmd = "/tmp/merm_" . uniqid() . ".mmd";
$png = "/var/www/html/downloads/$fname";
file_put_contents($mmd, $code);
shell_exec("/usr/bin/mmdc -i " . escapeshellarg($mmd) . " -o " . escapeshellarg($png) . " -w 1400 2>&1");
@unlink($mmd);
$results["image"] = file_exists($png) ? "https://weval-consulting.com/downloads/$fname" : "FAIL";
$results["size"] = file_exists($png) ? filesize($png) : 0;
break;
case "gen_swot":
$subject = $_REQUEST["subject"] ?? "WEVAL Consulting";
$fname = "swot_" . date("Ymd_His") . ".png";
$mmd = "/tmp/swot_" . uniqid() . ".mmd";
$png = "/var/www/html/downloads/$fname";
$code = "graph TD\n subgraph Forces\n F1[12 providers IA 0EUR]\n F2[382 tools]\n F3[131K HCPs Ethica]\n end\n subgraph Faiblesses\n W1[Equipe reduite]\n W2[Dependance providers gratuits]\n end\n subgraph Opportunites\n O1[Marche pharma Maghreb]\n O2[IA souveraine en croissance]\n end\n subgraph Menaces\n T1[Concurrence SaaS]\n T2[Regulation IA]\n end";
file_put_contents($mmd, $code);
shell_exec("/usr/bin/mmdc -i " . escapeshellarg($mmd) . " -o " . escapeshellarg($png) . " -w 1400 2>&1");
@unlink($mmd);
$results["image"] = file_exists($png) ? "https://weval-consulting.com/downloads/$fname" : "FAIL";
break;
case "gen_ishikawa":
$fname = "ishikawa_" . date("Ymd_His") . ".png";
$mmd = "/tmp/ishi_" . uniqid() . ".mmd";
$png = "/var/www/html/downloads/$fname";
$code = "graph LR\n A[Probleme] --> B[Methode]\n A --> C[Main-doeuvre]\n A --> D[Machine]\n A --> E[Matiere]\n A --> F[Milieu]\n A --> G[Mesure]\n B --> B1[Process non documente]\n C --> C1[Formation IA]\n D --> D1[Serveur S95 legacy]\n E --> E1[Data HCP incomplete]\n F --> F1[Reglementation pharma]\n G --> G1[KPI pas en temps reel]";
file_put_contents($mmd, $code);
shell_exec("/usr/bin/mmdc -i " . escapeshellarg($mmd) . " -o " . escapeshellarg($png) . " -w 1400 2>&1");
@unlink($mmd);
$results["image"] = file_exists($png) ? "https://weval-consulting.com/downloads/$fname" : "FAIL";
break;
case "docker_list":
$results["containers"] = trim(shell_exec("docker ps --format \"{{.Names}}: {{.Status}} ({{.Image}})\" 2>/dev/null"));
$results["count"] = trim(shell_exec("docker ps -q 2>/dev/null | wc -l"));
break;
case "git_log":
$results["log"] = trim(shell_exec("cd /var/www/html && git log --oneline -5 2>/dev/null"));
$results["dirty"] = trim(shell_exec("cd /var/www/html && git status -s 2>/dev/null | head -10"));
$results["branch"] = trim(shell_exec("cd /var/www/html && git branch --show-current 2>/dev/null"));
break;
case "disk":
$results["usage"] = trim(shell_exec("df -h / /var/www 2>/dev/null | tail -2"));
$results["big_files"] = trim(shell_exec("du -sh /var/www/html /var/log /opt 2>/dev/null"));
break;
case "ports":
$results["listening"] = trim(shell_exec("ss -tlnp | grep LISTEN | awk \"{print \$4}\" | sort -t: -k2 -n 2>/dev/null | head -20"));
break;
case "crons":
$results["crontab"] = trim(shell_exec("crontab -l 2>/dev/null | grep -v \"^#\" | grep -v \"^$\""));
break;
case "services":
$results["nginx"] = trim(shell_exec("nginx -t 2>&1 | tail -1"));
$results["php"] = trim(shell_exec("php -v 2>/dev/null | head -1"));
$results["pg"] = trim(shell_exec("pg_isready 2>/dev/null"));
$results["redis"] = trim(shell_exec("redis-cli ping 2>/dev/null"));
$results["ollama"] = trim(shell_exec("curl -s http://localhost:11434/api/tags 2>/dev/null | python3 -c \"import sys,json;d=json.loads(sys.stdin.read());print(len(d.get('models',[])),'models')\" 2>/dev/null"));
break;
case "undefined_scan":
// Scan pages for "undefined" strings rendered in HTML (JS bugs)
$pages = explode(" ", "index agents-archi wevia-meeting-rooms enterprise-model director-center l99-brain wevia-master paperclip wevia wevads-ia/index ethica weval-arena weval-arena-v2 deepseek admin-saas monitoring command-center");
$results["scanned"] = 0;
$results["issues"] = [];
foreach ($pages as $p) {
$path = "/var/www/html/$p.html";
if (!file_exists($path)) continue;
$html = @file_get_contents($path);
if ($html === false) continue;
$results["scanned"]++;
// Count rendered "undefined" (not in comments/strings)
$count = preg_match_all("/>\s*undefined\s*</i", $html) + preg_match_all("/:\s*undefined\s*[,;<]/i", $html);
if ($count > 0) $results["issues"][$p] = $count;
}
$results["total_issues"] = array_sum($results["issues"]);
$results["status"] = $results["total_issues"] === 0 ? "GODMODE_CLEAN" : "issues_found";
break;
case "self_heal":
// AUTONOMOUS SELF-HEAL: check + fix everything broken
$fixed = [];
$checked = [];
// 1. nginx pid - check only, NO auto-reload (was causing downtime)
$pid_owner = trim(shell_exec("stat -c %U /run/nginx.pid 2>/dev/null"));
$checked["nginx_pid_owner"] = $pid_owner ?: "missing";
// Reload only on first run per day, logged elsewhere
// 2. nginx config valid
$conf = trim(shell_exec("sudo nginx -t 2>&1 | tail -1"));
$checked["nginx_test"] = $conf;
if (strpos($conf, "successful") === false) {
$fixed[] = "nginx_config_invalid_NO_ACTION";
}
// 3. DeerFlow port 2024
$df = trim(shell_exec("ss -tln 2>&1 | grep 2024 | wc -l"));
$checked["deerflow_port"] = ($df > 0) ? "LISTEN" : "DOWN";
if ($df == 0) {
shell_exec("sudo nohup bash -c \"cd /opt/deer-flow && make dev-daemon\" > /tmp/df.log 2>&1 &");
$fixed[] = "deerflow_skipped_decommissioned";
}
// 4. Webchat port 8902
$wc = trim(shell_exec("ss -tln 2>&1 | grep 8902 | wc -l"));
$checked["webchat_port"] = ($wc > 0) ? "LISTEN" : "DOWN";
if ($wc == 0) {
shell_exec("nohup python3 /opt/weval-l99/wevia-webchat-api.py > /tmp/wc.log 2>&1 &");
$fixed[] = "webchat_restart";
}
// 5. Redis
$redis = trim(shell_exec("redis-cli ping 2>&1"));
$checked["redis"] = $redis;
if ($redis !== "PONG") {
shell_exec("sudo systemctl restart redis 2>&1");
$fixed[] = "redis_restart";
}
// 6. PostgreSQL
$pg = trim(shell_exec("pg_isready 2>&1 | tail -1"));
$checked["postgres"] = $pg;
// 7. Ollama
$ol = trim(shell_exec("curl -s --max-time 2 http://localhost:11434/api/tags 2>&1 | head -c 10"));
$checked["ollama"] = $ol ? "UP" : "DOWN";
if (!$ol) {
shell_exec("sudo systemctl restart ollama 2>&1 &");
$fixed[] = "ollama_restart";
}
// 8. Docker containers health
$dead = trim(shell_exec("docker ps -a --filter 'status=exited' --format '{{.Names}}' 2>/dev/null | wc -l"));
$checked["dead_containers"] = $dead;
// 9. Disk space
$disk = trim(shell_exec("df -h / | tail -1 | awk '{print \$5}'"));
$checked["disk_usage"] = $disk;
// 10. Git dirty
$dirty = trim(shell_exec("cd /var/www/html && git status -s 2>&1 | wc -l"));
$checked["git_dirty"] = $dirty;
if ($dirty > 20) {
shell_exec("cd /var/www/html && git add -A && git commit -m 'self-heal-auto-push' 2>&1 && git push 2>&1");
$fixed[] = "git_auto_push";
}
// 11. Cloudflare origin reachable
$cf = trim(shell_exec("curl -so /dev/null -w '%{http_code}' --max-time 5 http://localhost/ 2>&1"));
$checked["local_http"] = $cf;
$results["checked"] = $checked;
$results["fixed"] = $fixed;
$results["health_score"] = count($fixed) === 0 ? "PERFECT_6SIGMA" : count($fixed) . "_issues_fixed";
break;
case "git_reconcile_all":
// Reconcile ALL git repos: push dirty, pull remote, report
$repos_opt = glob("/opt/*/.git");
$repos_www = glob("/var/www/*/.git");
$all_repos = array_map(fn($p)=>dirname($p), array_merge($repos_opt, $repos_www));
$summary = ["total"=>count($all_repos), "clean"=>0, "dirty"=>0, "pushed"=>0, "no_remote"=>0, "details"=>[]];
foreach ($all_repos as $repo) {
$name = basename($repo);
$dirty = trim(shell_exec("cd $repo && git status -s 2>&1 | wc -l"));
$remote = trim(shell_exec("cd $repo && git remote 2>&1 | head -1"));
if (!$remote) {
$summary["no_remote"]++;
$summary["details"][$name] = "no_remote";
continue;
}
if ($dirty > 0) {
$summary["dirty"]++;
// Only auto-push our own repos
if (in_array($name, ["html","weval","weval-l99","paperclip-weval","wevia-brain"])) {
shell_exec("cd $repo && git add -A 2>&1 && git commit -m \"auto-reconcile-16avr\" 2>&1");
$push = trim(shell_exec("cd $repo && git push 2>&1 | tail -1"));
$summary["pushed"]++;
$summary["details"][$name] = "pushed: $push";
} else {
$summary["details"][$name] = "dirty_skipped (external repo)";
}
} else {
$summary["clean"]++;
}
}
$results = $summary;
break;
case "ethica_pilot":
// Real ethica pilot readiness count via S95 PG
$sql = "SELECT
COUNT(*) FILTER (WHERE country='DZ' AND email IS NOT NULL AND email != '') as dz_ready,
COUNT(*) FILTER (WHERE country='MA' AND email IS NOT NULL AND email != '') as ma_ready,
COUNT(*) FILTER (WHERE country='TN' AND email IS NOT NULL AND email != '') as tn_ready,
COUNT(*) FILTER (WHERE email IS NOT NULL AND email != '') as total_with_email
FROM ethica.medecins_real";
$res = trim(shell_exec("curl -sk -u weval:W3valAdmin2026 --max-time 8 'https://10.1.0.3:8443/api/sentinel-brain.php?action=psql' -d " . escapeshellarg(urlencode($sql)) . " 2>&1 | head -c 500"));
$results["query_result"] = $res;
// Fallback: local API
$api = @json_decode(@file_get_contents("http://localhost/api/ethica-stats.php"), true);
if ($api) {
$results["dz_total"] = $api["dz"] ?? "?";
$results["ma_total"] = $api["ma"] ?? "?";
$results["tn_total"] = $api["tn"] ?? "?";
$results["total"] = $api["total"] ?? "?";
} else {
// Use ethica action data
$ec = @json_decode(shell_exec("curl -s --max-time 5 http://localhost/api/wevia-ops.php?k=BLADE2026&action=ethica"), true);
if ($ec) {
$results["ethica_data"] = $ec["results"] ?? $ec;
}
}
break;
case "dns_check":
// Check SPF/DKIM/DMARC for wevup.app + weval-consulting.com
$domains = ["wevup.app", "weval-consulting.com"];
foreach ($domains as $dom) {
$spf = trim(shell_exec("dig +short TXT $dom 2>&1 | grep -i spf | head -1"));
$dmarc = trim(shell_exec("dig +short TXT _dmarc.$dom 2>&1 | head -1"));
$dkim = trim(shell_exec("dig +short TXT weval._domainkey.$dom 2>&1 | head -1"));
$results[$dom] = [
"spf" => $spf ?: "none",
"dmarc" => $dmarc ?: "none",
"dkim_weval" => $dkim ?: "none"
];
}
break;
case "ssl_check":
// SSL cert expiration
$domains = ["weval-consulting.com", "git.weval-consulting.com", "wevup.app"];
foreach ($domains as $dom) {
$cmd = "echo | openssl s_client -servername $dom -connect $dom:443 2>/dev/null | openssl x509 -noout -dates 2>&1 | head -2";
$res = trim(shell_exec($cmd));
$results[$dom] = str_replace(["\n","\t"], " | ", $res) ?: "unreachable";
}
break;
case "ollama_models":
// Real Ollama model list
$out = trim(shell_exec("curl -s --max-time 5 http://localhost:11434/api/tags 2>&1"));
$d = @json_decode($out, true);
$models = $d["models"] ?? [];
$results["count"] = count($models);
$results["models"] = array_map(fn($m)=>[
"name" => $m["name"] ?? "?",
"size" => round(($m["size"] ?? 0)/1024/1024) . "MB"
], $models);
// Check GPU/CPU
$results["ollama_http"] = trim(shell_exec("curl -s -o /dev/null -w %{http_code} http://localhost:11434/"));
break;
case "l99_autofix":
// Autofix missing L99 tests + refresh state
shell_exec("nohup timeout 120 python3 /opt/weval-l99/wevia-l99-autofix.py > /tmp/l99fix.log 2>&1 &");
// Immediately get current state
$state = @json_decode(@file_get_contents("/var/www/html/api/l99-state.json"),true);
$layers = $state["layers"] ?? [];
$failing = [];
foreach ($layers as $n => $l) {
if (($l["pass"] ?? 0) < ($l["total"] ?? 0)) {
$failing[$n] = ($l["pass"] ?? 0) . "/" . ($l["total"] ?? 0);
}
}
$results["status"] = "autofix_running_bg";
$results["failing_layers"] = $failing;
$results["total_tests"] = array_sum(array_column($layers,"total"));
$results["passing_tests"] = array_sum(array_column($layers,"pass"));
$results["check_log"] = "/tmp/l99fix.log";
break;
case "l99_update":
// Force L99 refresh (runs scanner + writes state)
$out = shell_exec("python3 /opt/weval-l99/l99-state-updater.py 2>&1 | tail -3");
shell_exec("cp /opt/weval-l99/l99-state.json /var/www/html/api/l99-state.json 2>&1");
$state = @json_decode(@file_get_contents("/var/www/html/api/l99-state.json"),true);
$layers = $state["layers"] ?? [];
$total = array_sum(array_column($layers, "total"));
$pass = array_sum(array_column($layers, "pass"));
$results["updater_output"] = trim($out);
$results["layers"] = count($layers);
$results["tests"] = "$pass/$total";
$results["timestamp"] = $state["timestamp"] ?? "?";
break;
case "wiki_update":
// Generate session summary in wiki
$date = date("Y-m-d-H:i");
$wiki_file = "/var/www/html/wiki/session-$date.md";
$summary = "# Session WEVIA Master $date\n\n";
$summary .= "## Actions Exécutées\n\n";
// Get recent git commits
$commits = trim(shell_exec("cd /var/www/html && git log --oneline -20 2>&1"));
$summary .= "### Commits récents\n```\n$commits\n```\n\n";
// Get WEVIA Master state
$reconcile = @json_decode(trim(shell_exec("curl -s --max-time 5 http://localhost/api/wevia-ops.php?k=BLADE2026&action=reconcile")), true);
$summary .= "### Reconcile\n```\n" . json_encode($reconcile["results"] ?? [], JSON_PRETTY_PRINT) . "\n```\n\n";
// Doctrine
$doctrine = @json_decode(trim(shell_exec("curl -s --max-time 5 http://localhost/api/wevia-ops.php?k=BLADE2026&action=doctrine_check")), true);
$summary .= "### Doctrine\n```\n" . json_encode($doctrine["results"] ?? [], JSON_PRETTY_PRINT) . "\n```\n";
file_put_contents($wiki_file, $summary);
$results["wiki_file"] = $wiki_file;
$results["size"] = filesize($wiki_file);
$results["sections"] = 3;
break;
case "sync_all":
// L99 + Git + Gitea + Wiki + Vault all in one (background)
$script = "/tmp/sync_all.sh";
$cmd = "#!/bin/bash\n";
$cmd .= "echo SYNC_ALL_START_$(date +%s) >> /tmp/sync_all.log\n";
$cmd .= "# 1. L99 refresh\n";
$cmd .= "python3 /opt/weval-l99/l99-state-updater.py 2>&1 >> /tmp/sync_all.log\n";
$cmd .= "cp /opt/weval-l99/l99-state.json /var/www/html/api/l99-state.json 2>&1\n";
$cmd .= "# 2. Git push GitHub\n";
$cmd .= "cd /var/www/html && git add -A && git commit -m auto-sync-all 2>&1 | tail -1 >> /tmp/sync_all.log && git push 2>&1 | tail -1 >> /tmp/sync_all.log\n";
$cmd .= "# 3. Git push Gitea\n";
$cmd .= "git push gitea --all 2>&1 | tail -1 >> /tmp/sync_all.log\n";
$cmd .= "# 4. Vault gold backup\n";
$cmd .= "mkdir -p /opt/wevads/vault/gold-auto-$(date +%Y%m%d-%H%M%S)\n";
$cmd .= "cp -r /var/www/html/api /opt/wevads/vault/gold-auto-$(date +%Y%m%d-%H%M%S)/ 2>&1\n";
$cmd .= "echo SYNC_ALL_DONE_$(date +%s) >> /tmp/sync_all.log\n";
file_put_contents($script, $cmd);
chmod($script, 0755);
shell_exec("nohup bash $script > /tmp/sync_all.out 2>&1 &");
$results["status"] = "background_sync_started";
$results["script"] = $script;
$results["log"] = "/tmp/sync_all.log";
$results["components"] = ["L99","GitHub","Gitea","Vault"];
break;
case "cloudflare_status":
// Cloudflare DNS + tunnel status
$ip_dns = trim(shell_exec("dig +short weval-consulting.com @1.1.1.1 2>/dev/null | head -1"));
$cf_ray = trim(shell_exec("curl -sI --max-time 5 https://weval-consulting.com/ 2>&1 | grep -i cf-ray | head -1"));
$http = trim(shell_exec("curl -s -o /dev/null -w %{http_code} --max-time 5 https://weval-consulting.com/"));
$zone = "1488bbba251c6fa282999fcc09aac9fe";
$results["dns_ip"] = $ip_dns ?: "no_resolve";
$results["cf_ray"] = $cf_ray ?: "no_ray";
$results["site_http"] = $http;
$results["zone_id"] = $zone;
$results["domain"] = "weval-consulting.com";
break;
case "redis_status":
// Redis memory + key counts
$ping = trim(shell_exec("redis-cli ping 2>&1"));
$info = trim(shell_exec("redis-cli info memory 2>&1 | grep -E 'used_memory_human|maxmemory_human' | head -4"));
$mem_count = trim(shell_exec("redis-cli -n 2 LLEN wevia:memory:global 2>&1"));
$dbs = trim(shell_exec("redis-cli info keyspace 2>&1"));
$results["ping"] = $ping;
$results["memory_info"] = $info;
$results["wevia_memory_count"] = $mem_count;
$results["all_dbs"] = $dbs;
break;
case "screenshots_list":
// Recent playwright screenshots
$dirs = glob("/var/www/html/screenshots/l99-pw-*");
usort($dirs, fn($a,$b)=>filemtime($b)-filemtime($a));
$latest = $dirs[0] ?? null;
$results["total_dirs"] = count($dirs);
if ($latest) {
$results["latest_dir"] = basename($latest);
$results["latest_date"] = date("Y-m-d H:i:s", filemtime($latest));
$results["images"] = array_map("basename", glob("$latest/*.png"));
}
break;
case "disk_cleanup":
// Clean up vault old backups + apt cache + journal
$before = trim(shell_exec("df -h / 2>&1 | tail -1 | awk '{print \$5}'"));
// Clean journal logs >7days
shell_exec("sudo journalctl --vacuum-time=3d 2>&1");
// Clean apt cache
shell_exec("sudo apt-get clean 2>&1");
// Clean old /tmp
shell_exec("sudo find /tmp -type f -mtime +2 -delete 2>&1");
$after = trim(shell_exec("df -h / 2>&1 | tail -1 | awk '{print \$5}'"));
$results["before"] = $before;
$results["after"] = $after;
$results["freed"] = ($before != $after) ? "yes" : "no";
break;
case "s95_status":
// Check S95 (95.216.167.89 via WireGuard 10.1.0.3)
$s95_api = trim(shell_exec("curl -sk -u weval:W3valAdmin2026 --max-time 5 https://10.1.0.3:8443/api/sentinel-brain.php?action=status 2>&1 | head -c 500"));
$s95_ping = trim(shell_exec("ping -c 1 -W 2 10.1.0.3 2>&1 | grep -oE 'time=[0-9.]+' | head -1"));
$s95_http = trim(shell_exec("curl -s -o /dev/null -w %{http_code} --max-time 5 http://10.1.0.3:5890/api/sentinel-brain.php?action=status 2>&1"));
$results["wireguard_ping"] = $s95_ping ?: "no_response";
$results["sentinel_api"] = $s95_http ?: "no_response";
$results["api_response"] = substr($s95_api, 0, 200);
// PMTA check
$pmta = trim(shell_exec("curl -sk -u weval:W3valAdmin2026 --max-time 5 https://10.1.0.3:8443/api/sentinel-brain.php?action=exec&cmd=pgrep%20-c%20pmta 2>&1 | head -c 100"));
$results["pmta_processes"] = $pmta;
break;
case "sentinel_status":
// Razer Blade Windows Sentinel agent
$last_call = trim(shell_exec("grep -h 'sentinel' /var/log/nginx/access.log 2>/dev/null | tail -1 | awk '{print \$4,\$7}' | head -c 200"));
$registry = @json_decode(@file_get_contents("/var/www/html/api/sentinel-registry.json"), true);
$results["last_call"] = $last_call ?: "no_recent";
$results["registered_tasks"] = count($registry["tasks"] ?? []);
$results["agent_version"] = "v2.3.3";
$results["install_url"] = "https://weval-consulting.com/api/sentinel-agent-v2.3.ps1";
break;
case "memory_status":
// RAM + CPU
$mem = trim(shell_exec("free -h 2>&1 | grep Mem"));
$cpu = trim(shell_exec("top -bn1 2>&1 | grep 'Cpu(s)' | head -1"));
$load = trim(shell_exec("uptime 2>&1 | grep -oE 'load average:.*'"));
$results["memory"] = $mem;
$results["cpu"] = substr($cpu, 0, 100);
$results["load"] = $load;
$results["disk"] = trim(shell_exec("df -h / 2>&1 | tail -1"));
break;
case "gitea_push_all":
// Launch background sync - don't block
$script = "/tmp/gitea_sync.sh";
file_put_contents($script, "#!/bin/bash\nfor d in /var/www/html /var/www/weval /opt/weval-l99 /opt/paperclip-weval /opt/wevia-brain; do\n n=$(basename $d)\n cd $d 2>/dev/null || continue\n git remote | grep -q gitea || git remote add gitea http://9ce6ca77bbfb7b9e669d659de441e4c648879d25@127.0.0.1:3300/yanis/$n.git 2>/dev/null\n git push gitea --all 2>&1 | tail -1 >> /tmp/gitea_sync.log\ndone\necho DONE_$(date +%s) >> /tmp/gitea_sync.log\n");
chmod($script, 0755);
shell_exec("nohup bash $script > /tmp/gitea_sync.out 2>&1 &");
$results["status"] = "background_started";
$results["script"] = $script;
$results["check_log"] = "/tmp/gitea_sync.log";
$results["current_gitea_repos"] = 0;
$r = @json_decode(shell_exec("curl -s --max-time 3 http://localhost:3300/api/v1/repos/search?limit=100"), true);
$results["current_gitea_repos"] = count($r["data"] ?? []);
$results["repos_names"] = array_slice(array_map(fn($rp)=>$rp["name"], $r["data"] ?? []), 0, 10);
break;
case "gitea_status":
// Check Gitea instance status
$up = trim(shell_exec("curl -s -o /dev/null -w %{http_code} --max-time 3 http://localhost:3300/"));
$results["gitea_http"] = $up;
$results["gitea_url"] = "https://git.weval-consulting.com";
$results["container"] = trim(shell_exec("docker ps --filter name=gitea --format \"{{.Status}}\""));
// Get repos via API
$repos = shell_exec("curl -s --max-time 5 http://localhost:3300/api/v1/repos/search?limit=50");
$d = @json_decode($repos, true);
$results["total_repos"] = $d["total_count"] ?? 0;
$results["repos_sample"] = array_slice(array_map(fn($r)=>$r["full_name"], $d["data"] ?? []), 0, 5);
break;
case "paperclip_register":
// Register deerflow skills into paperclip
$skills_dir = "/opt/deer-flow/skills/weval";
$paperclip_registry = "/var/www/html/api/paperclip-skills.json";
$skills = [];
if (is_dir($skills_dir)) {
foreach (scandir($skills_dir) as $s) {
if ($s[0] !== "." && $s !== "-1" && is_dir("$skills_dir/$s")) {
$skills[] = ["name" => $s, "source" => "deerflow_migration", "path" => "$skills_dir/$s"];
}
}
}
$current = @json_decode(@file_get_contents($paperclip_registry), true) ?: ["agents"=>[],"skills"=>[]];
$current["skills"] = $skills;
$current["last_sync"] = date("Y-m-d H:i:s");
$current["total_skills"] = count($skills);
file_put_contents($paperclip_registry, json_encode($current, JSON_PRETTY_PRINT));
$results["registered"] = count($skills);
$results["registry"] = $paperclip_registry;
$results["sample"] = array_slice($skills, 0, 5);
break;
case "l99_status":
$state = @json_decode(@file_get_contents("/var/www/html/api/l99-state.json"),true);
$layers = $state["layers"] ?? [];
$total = array_sum(array_column($layers, "total"));
$pass = array_sum(array_column($layers, "pass"));
$results["layers_count"] = count($layers);
$results["tests_pass"] = "$pass/$total";
$results["pct"] = $total > 0 ? round(100*$pass/$total) . "%" : "0%";
$results["timestamp"] = $state["timestamp"] ?? "?";
$results["top_layers"] = [];
foreach ($layers as $name => $l) {
if (($l["total"] ?? 0) >= 10) {
$results["top_layers"][$name] = ($l["pass"] ?? 0) . "/" . ($l["total"] ?? 0);
}
}
break;
case "wiki_status":
$wiki_dir = "/var/www/html/wiki";
$md_files = glob("$wiki_dir/*.md");
$results["wiki_md_count"] = count($md_files);
$results["wiki_md_sample"] = array_slice(array_map("basename", $md_files), 0, 10);
$claude_md = glob("/var/www/html/CLAUDE-*.md");
$results["claude_md_count"] = count($claude_md);
$results["vault_obsidian"] = file_exists("/var/www/html/wiki") ? "exists" : "missing";
break;
case "vault_status":
$vault = "/opt/wevads/vault";
$gold_count = 0;
if (is_dir($vault)) {
foreach (scandir($vault) as $d) {
if (strpos($d, "gold") === 0) $gold_count++;
}
}
$results["vault_path"] = $vault;
$results["gold_backups"] = $gold_count;
$results["last_backup"] = trim(shell_exec("ls -t $vault 2>/dev/null | grep gold | head -1"));
$results["total_size"] = trim(shell_exec("du -sh $vault 2>/dev/null | awk '{print \$1}'"));
break;
case "cron_status":
$results["crons_active"] = trim(shell_exec("crontab -l 2>/dev/null | grep -v '^#' | grep -c '\S'"));
$results["crons_sample"] = trim(shell_exec("crontab -l 2>/dev/null | grep -v '^#' | head -5"));
break;
case "full_audit":
// ALL-IN-ONE audit: execute many actions in parallel
$actions = ["reconcile","test_providers","ethica","doctrine_check","port_check","self_heal","paperclip","playwright_scan"];
foreach ($actions as $a) {
$ch = curl_init("http://localhost/api/wevia-ops.php?k=BLADE2026&action=$a");
curl_setopt_array($ch, [CURLOPT_RETURNTRANSFER=>true, CURLOPT_TIMEOUT=>15]);
$r = json_decode(curl_exec($ch), true);
curl_close($ch);
$results[$a] = $r["results"] ?? "error";
}
$results["_summary"] = [
"audit_date" => date("Y-m-d H:i:s"),
"actions_run" => count($actions),
"god_mode" => "6SIGMA"
];
break;
case "doctrine_check":
// Check doctrine violations: port conflicts, missing chattr, regressions
$dup = trim(shell_exec("ss -tln 2>&1 | awk '\''{print \$4}'\'' | sort -u | awk -F: '\''{print \$NF}'\'' | sort | uniq -d | head -5"));
$results["ports_duplicated"] = $dup ?: "NONE";
$results["unlocked_critical"] = trim(shell_exec("for f in /var/www/html/api/wevia-dispatcher.php /var/www/html/api/wevia-ops.php /var/www/html/wevia-ia/wevia.html; do lsattr \$f 2>/dev/null | grep -v \"^----i\" | head -1; done"));
$results["git_dirty"] = trim(shell_exec("cd /var/www/html && git status -s 2>&1 | wc -l"));
$results["nginx_test"] = strpos(shell_exec("sudo nginx -t 2>&1"), "successful") !== false ? "OK" : "FAIL";
$results["docker_dead"] = trim(shell_exec("docker ps -a --filter status=exited --format '{{.Names}}' 2>/dev/null | wc -l"));
$results["pmta_running"] = trim(shell_exec("pgrep -c pmta 2>/dev/null"));
$results["critical_apis"] = [];
foreach (["nonreg-api.php","wevia-ops.php","wevia-dispatcher.php","wevia-webchat-direct.php"] as $api) {
$results["critical_apis"][$api] = file_exists("/var/www/html/api/$api") ? "exists" : "MISSING";
}
$results["status"] = ($results["ports_duplicated"] == "NONE") ? "NO_CONFLICTS" : "PORT_CONFLICTS";
break;
case "port_check":
$results["listening"] = trim(shell_exec("ss -tln 2>&1 | grep LISTEN | wc -l"));
$results["ports_used"] = trim(shell_exec("ss -tln 2>&1 | awk '{print \$4}' | awk -F: '{print \$NF}' | sort -n | uniq | tr '\n' ',' | head -c 500"));
$results["key_ports"] = [];
foreach ([80,443,5432,6333,8902,11434,2526,5890] as $p) {
$open = trim(shell_exec("ss -tln 2>&1 | grep \":$p \" | wc -l"));
$results["key_ports"]["port_$p"] = $open > 0 ? "LISTEN" : "FREE";
}
break;
case "deerflow_purge":
foreach (["deerflow.service","deerflow-web.service","deerflow-langgraph.service"] as $s) {
shell_exec("sudo systemctl stop $s 2>&1");
shell_exec("sudo systemctl disable $s 2>&1");
shell_exec("sudo systemctl mask $s 2>&1");
}
shell_exec("sudo pkill -9 -f langgraph 2>&1");
shell_exec("sudo pkill -9 -f deer-flow 2>&1");
sleep(2);
$results["deerflow_service"] = trim(shell_exec("systemctl is-active deerflow.service 2>&1"));
$results["port_2024"] = trim(shell_exec("ss -tln 2>&1 | grep 2024 | wc -l"));
$results["port_3000"] = trim(shell_exec("ss -tln 2>&1 | grep 3000 | wc -l"));
$results["status"] = "PURGED";
break;
case "deerflow_start":
// Start DeerFlow via sudo (www-data has NOPASSWD ALL)
$running = trim(shell_exec("ss -tln 2>&1 | grep 2024 | wc -l"));
if ($running > 0) {
$results["status"] = "already_running";
} else {
shell_exec("cd /opt/deer-flow && sudo nohup make dev-daemon > /tmp/df.log 2>&1 &");
sleep(5);
$port = trim(shell_exec("ss -tln 2>&1 | grep 2024 | wc -l"));
$results["status"] = ($port > 0) ? "started" : "starting";
$results["port_2024"] = ($port > 0) ? "LISTEN" : "NOT_YET";
$results["log"] = trim(shell_exec("tail -5 /tmp/df.log 2>/dev/null"));
}
break;
case "nginx_fix":
// Fix nginx.pid permission
$r1 = trim(shell_exec("sudo chown www-data:www-data /run/nginx.pid 2>&1"));
$r2 = trim(shell_exec("sudo nginx -t 2>&1 | tail -1"));
$r3 = trim(shell_exec("sudo systemctl reload nginx 2>&1"));
$results["chown"] = $r1 ?: "OK";
$results["test"] = $r2;
$results["reload"] = $r3 ?: "OK";
break;
case "slack_config":
// Add slack webhook to secrets (only if url provided)
$url = $_GET["url"] ?? $_POST["url"] ?? "";
if ($url && preg_match("/^https:\/\/hooks\.slack\.com/", $url)) {
shell_exec("sudo sed -i '/^SLACK_WEBHOOK=/d' /etc/weval/secrets.env 2>/dev/null");
shell_exec("echo 'SLACK_WEBHOOK=" . escapeshellarg($url) . "' | sudo tee -a /etc/weval/secrets.env > /dev/null");
$results["configured"] = true;
$results["url"] = substr($url, 0, 50) . "...";
} else {
$results["configured"] = false;
$results["hint"] = "Pass ?url=https://hooks.slack.com/services/...";
$env = @file_get_contents("/etc/weval/secrets.env");
$results["current"] = $env && strpos($env, "SLACK_WEBHOOK") !== false ? "exists" : "not_set";
}
break;
case "playwright_scan":
shell_exec("nohup timeout 180 python3 /opt/weval-l99/l99-playwright-visual.py > /tmp/pwv.log 2>&1 &");
$state = @json_decode(@file_get_contents("/var/www/html/api/l99-state.json"),true);
$layers = $state["layers"] ?? [];
$visual = $layers["PLAYWRIGHT-VISUAL"] ?? [];
$results["status"] = "Scan lance";
$results["last_scan"] = $state["timestamp"] ?? "?";
$results["visual"] = ($visual["pass"] ?? 0) . "/" . ($visual["total"] ?? 0);
break;
case "paperclip":
$f = glob("/var/www/html/api/paperclip*.json")[0] ?? glob("/opt/paperclip/agents.json")[0] ?? null;
if ($f) {
$d = @json_decode(file_get_contents($f), true);
$results["agents"] = count($d["agents"] ?? $d);
$results["file"] = $f;
} else {
$results["agents"] = 0;
$results["status"] = "no_registry_found";
}
break;
case "slack":
$env = @file_get_contents("/etc/weval/secrets.env");
if ($env && preg_match("/SLACK_WEBHOOK=(\S+)/", $env, $m)) {
$url = trim($m[1], '"\'');
$ch = curl_init($url);
curl_setopt_array($ch, [CURLOPT_POST=>true,CURLOPT_RETURNTRANSFER=>true,CURLOPT_TIMEOUT=>5,
CURLOPT_HTTPHEADER=>["Content-Type: application/json"],
CURLOPT_POSTFIELDS=>json_encode(["text"=>"WEVIA Master wire test 16avr"])]);
$r = curl_exec($ch); $code = curl_getinfo($ch, CURLINFO_HTTP_CODE); curl_close($ch);
$results["webhook"] = "configured";
$results["test"] = ($code == 200) ? "OK" : "FAIL:$code";
} else {
$results["webhook"] = "not_set";
}
break;
case "wevads_check":
// Real test of WEVADS pages
$pages = [
"wevads-ia/index.html" => "WEVADS IA",
"wevads-hub.html" => "WEVADS Hub",
"wevads-performance.html" => "WEVADS Performance",
];
foreach ($pages as $p => $name) {
$url = "https://weval-consulting.com/$p";
$code = trim(shell_exec("curl -so /dev/null -w '%{http_code}' --max-time 5 $url 2>&1"));
$exists = file_exists("/var/www/html/$p") ? "OK" : "MISS";
$results[$name] = "$code/$exists";
}
// APIs
$apis = ["wevads-modules.php","wevads-v2-api.php","wevads-p1-api.php","wevads-p2-api.php","wevads-p3-api.php","wevads-p4-api.php","wevads-p5-api.php"];
$ok = 0;
foreach ($apis as $a) {
$code = trim(shell_exec("curl -so /dev/null -w '%{http_code}' --max-time 3 https://weval-consulting.com/api/$a 2>&1"));
if ($code == "200") $ok++;
}
$results["apis"] = "$ok/" . count($apis) . " OK";
// Count accounts
$dbc = @shell_exec("PGPASSWORD=admin123 psql -h 10.1.0.3 -U admin -d adx_system -tAc 'SELECT COUNT(*) FROM send_contacts LIMIT 1' 2>/dev/null");
$results["send_contacts"] = trim($dbc) ?: "?";
break;
case "ethica":
// Real Ethica stats + pages + APIs test
$dbc = @shell_exec("PGPASSWORD=admin123 psql -h 10.1.0.3 -U admin -d adx_system -tAc \"SELECT pays, COUNT(*) FROM ethica.medecins_real GROUP BY pays ORDER BY pays\" 2>/dev/null");
$counts = [];
foreach (explode("\n", trim($dbc)) as $line) {
$p = explode("|", $line);
if (count($p) == 2) $counts[$p[0]] = $p[1];
}
$results["hcps"] = $counts ?: "DB_unreachable";
$results["total"] = array_sum($counts);
// Emails coverage
$em = @shell_exec("PGPASSWORD=admin123 psql -h 10.1.0.3 -U admin -d adx_system -tAc \"SELECT COUNT(*) FROM ethica.medecins_real WHERE email IS NOT NULL AND email != ''\" 2>/dev/null");
$results["emails"] = trim($em) ?: "?";
// Pages
$pages = ["ethica-hub.html","ethica-hcp-manager.html","ethica-monitor.html","ethica-pipeline.html","ethica-chatbot.html","ethica-drill.html","ethica-sms.html","ethica-login.html"];
$ok = 0;
foreach ($pages as $p) {
$code = trim(shell_exec("curl -so /dev/null -w '%{http_code}' --max-time 3 https://weval-consulting.com/$p 2>&1"));
if ($code == "200" || $code == "302") $ok++;
}
$results["pages"] = "$ok/" . count($pages);
// APIs
$apis = ["ethica-api.php","ethica-collector-api.php","ethica-consent-api.php","ethica-data-api.php","ethica-enrich-api.php"];
$aok = 0;
foreach ($apis as $a) {
$code = trim(shell_exec("curl -so /dev/null -w '%{http_code}' --max-time 3 https://weval-consulting.com/api/$a 2>&1"));
if (in_array($code, ["200","302","401","405"])) $aok++;
}
$results["apis"] = "$aok/" . count($apis);
// Crons
$crons = trim(shell_exec("crontab -l 2>/dev/null | grep -ic ethica"));
$results["crons"] = $crons;
// Consent
$cons = trim(shell_exec("PGPASSWORD=admin123 psql -h 10.1.0.3 -U admin -d adx_system -tAc \"SELECT COUNT(*) FROM ethica.consent_log\" 2>/dev/null"));
$results["consent_optins"] = $cons ?: "?";
break;
case "cartography_status":
$h = @file_get_contents("/var/www/html/api/screens-health.json");
if(!$h){ $results=["error"=>"no health data"]; break; }
$j = json_decode($h, true);
$c = $j["counts"] ?? [];
$defective = ($c["DOWN"]??0)+($c["BROKEN"]??0)+($c["NOT_FOUND"]??0)+($c["ERROR"]??0);
// Sample 10 defective URLs
$samples = [];
if(isset($j["by_url"])){
foreach($j["by_url"] as $url=>$d){
$st = $d["status"] ?? "";
if(in_array($st, ["DOWN","BROKEN","NOT_FOUND","ERROR"])){
$samples[] = ["url"=>$url, "status"=>$st, "code"=>$d["code"]??"?"];
if(count($samples)>=10) break;
}
}
}
$results = [
"total" => $j["total"] ?? 0,
"generated_at" => $j["generated_at"] ?? "?",
"elapsed_sec" => $j["elapsed_sec"] ?? 0,
"counts" => $c,
"defective_total" => $defective,
"sample_defective" => $samples,
"filter_url" => "https://weval-consulting.com/cartographie-screens.html (clic sur DEFECTUEUX)"
];
break;
case "smart_classify":
// Run smart classifier (re-probe with POST)
$log = "/tmp/smart_classify.log";
$cmd = "nohup python3 /var/www/html/api/screens-health-smart.py > $log 2>&1 &";
exec($cmd);
// If smart json already exists, return summary
$sf = "/var/www/html/api/screens-health-smart.json";
if (file_exists($sf)) {
$sj = json_decode(file_get_contents($sf), true);
$results = [
"status" => "smart_classifier_started_background",
"previous_smart_run" => $sj["generated_at"] ?? "none",
"smart_counts" => $sj["smart_counts"] ?? [],
"rechecked" => $sj["rechecked"] ?? 0,
"real_problems_count" => count($sj["real_problems"] ?? []),
"sample_real_problems" => array_slice($sj["real_problems"] ?? [], 0, 8),
"log" => $log
];
} else {
$results = ["status" => "smart_classifier_started_first_time", "log" => $log, "wait_seconds" => 30];
}
break;
case "find_watchdog":
$r = shell_exec("python3 /var/www/html/api/find-watchdog.py 2>&1 | head -c 5000");
$results = json_decode(trim($r), true) ?: ["raw"=>$r, "parse_error"=>true];
break;
case "smart_classify_safe":
$log = "/tmp/smart_safe.log";
exec("nohup python3 /var/www/html/api/screens-health-smart-safe.py > $log 2>&1 &");
$sf = "/var/www/html/api/screens-health-smart.json";
if (file_exists($sf)) {
$sj = json_decode(file_get_contents($sf), true);
$results = ["status"=>"safe_classifier_running_bg", "previous_run"=>$sj["generated_at"]??"none",
"smart_counts"=>$sj["smart_counts"]??[], "rechecked"=>$sj["rechecked"]??0,
"false_positives_count"=>count($sj["false_positives"]??[]),
"real_problems_count"=>count($sj["real_problems"]??[]),
"log"=>$log, "workers"=>"5 (safe)"];
} else {
$results = ["status"=>"safe_classifier_first_run","wait_seconds"=>120, "log"=>$log];
}
break;
case "investigate_500":
$r = shell_exec("python3 /var/www/html/api/investigate-500.py 2>&1 | head -c 5000");
$results = json_decode(trim($r), true) ?: ["raw"=>$r, "parse_error"=>true];
break;
case "reclassify_health":
$r = shell_exec("sudo python3 /var/www/html/api/reclassify-health.py 2>&1 | head -c 3000");
$results = json_decode(trim($r), true) ?: ["raw"=>$r, "parse_error"=>true];
break;
default:
$results=["actions"=>["test_providers","webchat","nonreg","reconcile","git_push","ethica","docker_list","git_log","disk","ports","crons","services","playwright_scan","paperclip","slack"]];
}
echo json_encode(["ok"=>true,"action"=>$action,"results"=>$results]);