Files
wevads-platform/public/weval-ssh-api.php
2026-04-07 03:04:16 +02:00

242 lines
12 KiB
PHP

<?php
/**
* WEVAL SSH API - Exécute des commandes SSH sur les serveurs
* GET = Interface de test | POST = API
*/
$servers = [
'weval-app' => ['name' => 'WEVAL Marketing', 'host' => '95.216.167.89', 'user' => 'root', 'local' => true],
'tracking' => ['name' => 'WEVAL Tracking', 'host' => '151.80.235.110', 'user' => 'ubuntu', 'pass' => getenv('SSH_PASS') ?: '***', 'local' => false],
'consulting' => ['name' => 'WEVAL Consulting', 'host' => '204.168.152.13', 'user' => 'root', 'pass' => getenv('SSH_PASS') ?: '***', 'local' => false]
];
$blocked = ['rm -rf /','rm -rf /*','mkfs',':(){ :|:& };:','dd if=/dev/zero','chmod -R 777 /','> /dev/sda'];
// POST = API JSON
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
header('Content-Type: application/json');
$input = json_decode(file_get_contents('php://input'), true);
$command = trim($input['command'] ?? '');
$serverKey = $input['server'] ?? 'weval-app';
if (empty($command)) { echo json_encode(['error' => 'Command required']); exit; }
foreach ($blocked as $b) { if (stripos($command, $b) !== false) { echo json_encode(['error' => 'Blocked command']); exit; } }
$server = $servers[$serverKey] ?? $servers['weval-app'];
$start = microtime(true);
if ($server['local']) {
exec($command . ' 2>&1', $output, $code);
} else {
putenv("HOME=/var/www");
$ssh = "/usr/bin/sshpass -p " . $server['pass'] . " /usr/bin/ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o ConnectTimeout=5 " . $server['user'] . "@" . $server['host'] . " " . escapeshellarg($command) . " 2>&1";
$raw = shell_exec($ssh);
$lines = explode(chr(10), $raw); $clean = []; foreach($lines as $l){ $l=str_replace(chr(13),"",$l); if(strpos($l,"Warning:")===false && trim($l)!=="") $clean[]=$l; } $raw = implode(chr(10),$clean);
$output = $raw ? explode("
", trim($raw)) : [];
$code = 0;
if (stripos($raw, 'Permission denied') !== false || stripos($raw, 'Connection refused') !== false) $code = 1;
}
echo json_encode([
'success' => $code === 0,
'output' => implode("\n", $output),
'exit_code' => $code,
'duration' => round((microtime(true) - $start) * 1000) . 'ms',
'server' => $server['name'],
'host' => $server['host']
]);
exit;
}
// GET = Interface de test
?>
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>WEVAL SSH API - Test Interface</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body { font-family: -apple-system, sans-serif; background: #0a0a0f; color: #f1f5f9; min-height: 100vh; padding: 30px; }
.container { max-width: 1200px; margin: 0 auto; }
h1 { font-size: 24px; margin-bottom: 8px; display: flex; align-items: center; gap: 12px; }
.subtitle { color: #64748b; font-size: 14px; margin-bottom: 30px; }
.grid { display: grid; grid-template-columns: 1fr 1fr; gap: 24px; }
@media (max-width: 900px) { .grid { grid-template-columns: 1fr; } }
.card { background: #12121a; border: 1px solid #2a2a3e; border-radius: 16px; overflow: hidden; }
.card-header { padding: 16px 20px; border-bottom: 1px solid #2a2a3e; font-weight: 600; display: flex; align-items: center; gap: 10px; }
.card-body { padding: 20px; }
.form-group { margin-bottom: 16px; }
.form-group label { display: block; font-size: 12px; color: #94a3b8; margin-bottom: 6px; }
.form-group select, .form-group input { width: 100%; padding: 12px; background: #1a1a2e; border: 1px solid #2a2a3e; border-radius: 8px; color: #f1f5f9; font-size: 14px; }
.form-group select:focus, .form-group input:focus { outline: none; border-color: #22d3ee; }
.btn { padding: 12px 24px; border-radius: 8px; font-size: 14px; font-weight: 600; cursor: pointer; border: none; transition: all 0.2s; }
.btn-primary { background: linear-gradient(135deg, #22d3ee, #3b82f6); color: white; }
.btn-primary:hover { transform: translateY(-2px); box-shadow: 0 10px 30px rgba(34,211,238,0.3); }
.output { background: #0d1117; border: 1px solid #2a2a3e; border-radius: 8px; padding: 16px; font-family: 'Fira Code', monospace; font-size: 12px; min-height: 300px; max-height: 500px; overflow-y: auto; white-space: pre-wrap; color: #8b949e; }
.output .success { color: #22c55e; }
.output .error { color: #ef4444; }
.output .info { color: #22d3ee; }
.servers-list { display: flex; flex-direction: column; gap: 12px; }
.server-item { background: #1a1a2e; border: 1px solid #2a2a3e; border-radius: 10px; padding: 14px; cursor: pointer; transition: all 0.2s; display: flex; justify-content: space-between; align-items: center; }
.server-item:hover { border-color: #22d3ee; }
.server-item.active { border-color: #22c55e; background: rgba(34,197,94,0.1); }
.server-item .name { font-weight: 600; }
.server-item .host { font-size: 12px; color: #64748b; font-family: monospace; }
.server-item .status { width: 10px; height: 10px; border-radius: 50%; background: #22c55e; }
.quick-cmds { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 16px; }
.quick-cmd { background: #1a1a2e; border: 1px solid #2a2a3e; padding: 8px 12px; border-radius: 6px; font-size: 11px; cursor: pointer; transition: all 0.2s; font-family: monospace; }
.quick-cmd:hover { border-color: #22d3ee; background: #2a2a3e; }
.stats { display: grid; grid-template-columns: repeat(3, 1fr); gap: 12px; margin-bottom: 20px; }
.stat { background: #1a1a2e; border-radius: 10px; padding: 14px; text-align: center; cursor: pointer; transition: all 0.2s; }
.stat:hover { background: #2a2a3e; }
.stat .value { font-size: 24px; font-weight: 700; color: #22d3ee; }
.stat .label { font-size: 11px; color: #64748b; margin-top: 4px; }
</style>
</head>
<body>
<div class="container">
<h1>🔌 WEVAL SSH API</h1>
<p class="subtitle">Interface de test pour l'API SSH - Exécutez des commandes sur vos serveurs</p>
<div class="stats">
<div class="stat" onclick="runQuickCmd('uptime')">
<div class="value" id="statUptime">--</div>
<div class="label">Uptime</div>
</div>
<div class="stat" onclick="runQuickCmd('free -h | grep Mem')">
<div class="value" id="statMem">--</div>
<div class="label">Mémoire</div>
</div>
<div class="stat" onclick="runQuickCmd('df -h / | tail -1')">
<div class="value" id="statDisk">--</div>
<div class="label">Disque</div>
</div>
</div>
<div class="grid">
<div class="card">
<div class="card-header">🖥️ Serveurs</div>
<div class="card-body">
<div class="servers-list">
<?php foreach ($servers as $key => $srv): ?>
<div class="server-item <?= $key === 'weval-app' ? 'active' : '' ?>" onclick="selectServer('<?= $key ?>')">
<div>
<div class="name"><?= $srv['name'] ?></div>
<div class="host"><?= $srv['host'] ?> <?= $srv['local'] ? '(local)' : '(remote)' ?></div>
</div>
<div class="status"></div>
</div>
<?php endforeach; ?>
</div>
<div class="quick-cmds">
<div class="quick-cmd" onclick="runQuickCmd('hostname')">hostname</div>
<div class="quick-cmd" onclick="runQuickCmd('uptime')">uptime</div>
<div class="quick-cmd" onclick="runQuickCmd('df -h')">df -h</div>
<div class="quick-cmd" onclick="runQuickCmd('free -h')">free -h</div>
<div class="quick-cmd" onclick="runQuickCmd('top -bn1 | head -15')">top</div>
<div class="quick-cmd" onclick="runQuickCmd('ps aux | head -20')">ps aux</div>
<div class="quick-cmd" onclick="runQuickCmd('netstat -tlnp')">netstat</div>
<div class="quick-cmd" onclick="runQuickCmd('systemctl status apache2')">apache</div>
<div class="quick-cmd" onclick="runQuickCmd('pmta status')">pmta</div>
</div>
</div>
</div>
<div class="card">
<div class="card-header">⌨️ Commande</div>
<div class="card-body">
<div class="form-group">
<label>Commande à exécuter</label>
<input type="text" id="cmdInput" placeholder="Ex: ls -la /opt/wevads" onkeypress="if(event.key==='Enter')runCmd()">
</div>
<button class="btn btn-primary" onclick="runCmd()">▶️ Exécuter</button>
</div>
</div>
</div>
<div class="card" style="margin-top:24px;">
<div class="card-header">📟 Output</div>
<div class="card-body">
<div class="output" id="output">Sélectionnez un serveur et entrez une commande...</div>
</div>
</div>
</div>
<script>
let currentServer = 'weval-app';
function selectServer(key) {
currentServer = key;
document.querySelectorAll('.server-item').forEach(el => el.classList.remove('active'));
event.currentTarget.classList.add('active');
appendOutput('<span class="info">→ Serveur sélectionné: ' + key + '</span>\n');
}
function runQuickCmd(cmd) {
document.getElementById('cmdInput').value = cmd;
runCmd();
}
async function runCmd() {
const cmd = document.getElementById('cmdInput').value.trim();
if (!cmd) return;
appendOutput('<span class="info">$ ' + cmd + '</span>\n');
try {
const r = await fetch(location.href, {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({command: cmd, server: currentServer})
});
const d = await r.json();
if (d.success) {
appendOutput(d.output + '\n');
appendOutput('<span class="success">[✓ ' + d.server + ' | ' + d.duration + ']</span>\n\n');
} else {
appendOutput('<span class="error">' + (d.output || d.error) + '</span>\n');
appendOutput('<span class="error">[✗ Exit: ' + d.exit_code + ']</span>\n\n');
}
} catch (e) {
appendOutput('<span class="error">Erreur: ' + e.message + '</span>\n\n');
}
}
function appendOutput(text) {
const el = document.getElementById('output');
el.innerHTML += text;
el.scrollTop = el.scrollHeight;
}
// Load stats on page load
async function loadStats() {
try {
// Uptime
let r = await fetch(location.href, {method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({command:'uptime -p',server:'weval-app'})});
let d = await r.json();
document.getElementById('statUptime').textContent = d.output?.replace('up ','') || '--';
// Memory
r = await fetch(location.href, {method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({command:"free -h | awk '/Mem:/{print $3\"/\"$2}'",server:'weval-app'})});
d = await r.json();
document.getElementById('statMem').textContent = d.output || '--';
// Disk
r = await fetch(location.href, {method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({command:"df -h / | awk 'NR==2{print $5}'",server:'weval-app'})});
d = await r.json();
document.getElementById('statDisk').textContent = d.output || '--';
} catch(e) {}
}
loadStats();
</script>
</body>
</html>