diff --git a/modules/pi_control/assets/console.js b/modules/pi_control/assets/console.js index cc68cae..3230444 100644 --- a/modules/pi_control/assets/console.js +++ b/modules/pi_control/assets/console.js @@ -353,11 +353,34 @@ } return; } - if (consoleNotice) { - consoleNotice.textContent = 'Bestehende Konsole kann nicht direkt gesteuert werden – Befehl wird in neuer Konsole ausgeführt.'; - consoleNotice.style.display = 'block'; - } - submitOpen(); + const formData = new FormData(consoleForm); + if (presetSelect) formData.set('terminal_preset_id', ''); + const url = new URL(window.location.href); + url.searchParams.set('send_active_json', '1'); + fetch(url.toString(), { method: 'POST', body: formData, cache: 'no-store' }) + .then((res) => res.json()) + .then((data) => { + if (data.ok) { + if (consoleNotice) { + consoleNotice.textContent = data.notice || 'Befehl wurde in der bestehenden Konsole ausgeführt.'; + consoleNotice.style.display = 'block'; + } + if (consoleError) consoleError.style.display = 'none'; + return; + } + if (consoleNotice) { + consoleNotice.textContent = data.error || 'Bestehende Konsole nicht verfügbar – Befehl wird in neuer Konsole ausgeführt.'; + consoleNotice.style.display = 'block'; + } + submitOpen(); + }) + .catch(() => { + if (consoleNotice) { + consoleNotice.textContent = 'Bestehende Konsole nicht verfügbar – Befehl wird in neuer Konsole ausgeführt.'; + consoleNotice.style.display = 'block'; + } + submitOpen(); + }); }); } } diff --git a/modules/pi_control/pages/console.php b/modules/pi_control/pages/console.php index ac04cb2..9225f9a 100644 --- a/modules/pi_control/pages/console.php +++ b/modules/pi_control/pages/console.php @@ -295,6 +295,59 @@ if (isset($_GET['run_command_json'])) { exit; } +if (isset($_GET['send_active_json'])) { + $hostId = (int)($_POST['terminal_host_id'] ?? 0); + $commandId = (int)($_POST['terminal_preset_id'] ?? 0); + $rawCommand = trim((string)($_POST['terminal_command_text'] ?? '')); + $error = null; + $notice = null; + + if ($hostId <= 0) { + $error = 'Bitte einen Host wählen.'; + } elseif ($commandId <= 0 && $rawCommand === '') { + $error = 'Bitte einen Befehl wählen oder einen Befehl eingeben.'; + } else { + $selectedCommand = ''; + if ($commandId > 0) { + foreach ($commands as $c) { + if ((int)$c['id'] === $commandId) { + if (!auth_is_admin() && !empty($c['admin_only'])) { + $error = 'Dieser Befehl ist nur für Admins.'; + } else { + $selectedCommand = (string)$c['command']; + } + break; + } + } + } + if (!$error) { + $commandText = $selectedCommand !== '' ? $selectedCommand : $rawCommand; + $stmt = $pdo->prepare('SELECT * FROM ' . $table('hosts') . ' WHERE id = :id LIMIT 1'); + $stmt->execute(['id' => $hostId]); + $host = $stmt->fetch(PDO::FETCH_ASSOC); + if (!$host) { + $error = 'Host nicht gefunden.'; + } else { + $strictHostKey = getenv('PI_CONTROL_STRICT_HOSTKEY') === '1'; + [$ok, $sendError] = sendToActiveConsole($host, $commandText, $strictHostKey); + if ($ok) { + $notice = 'Befehl wurde in der bestehenden Konsole ausgeführt.'; + } else { + $error = $sendError ?: 'Bestehende Konsole nicht verfügbar.'; + } + } + } + } + + header('Content-Type: application/json; charset=utf-8'); + echo json_encode([ + 'ok' => $error === null, + 'error' => $error, + 'notice' => $notice, + ]); + exit; +} + // Form submits are handled via AJAX to avoid reloads. $runs = $pdo->query( @@ -303,6 +356,66 @@ $runs = $pdo->query( LEFT JOIN ' . $table('hosts') . ' h ON h.id = r.host_id ORDER BY r.id DESC LIMIT 20' )->fetchAll(PDO::FETCH_ASSOC); + +function sendToActiveConsole(array $host, string $command, bool $strictHostKey): array +{ + $hostAddr = (string)($host['host'] ?? ''); + $user = (string)($host['username'] ?? ''); + $port = (int)($host['port'] ?? 22); + $authType = (string)($host['auth_type'] ?? 'key'); + $keyPath = (string)($host['key_path'] ?? ''); + $password = (string)($host['password'] ?? ''); + + if ($hostAddr === '' || $user === '') { + return [false, 'Hostdaten unvollständig.']; + } + + $opts = $strictHostKey + ? '-o StrictHostKeyChecking=accept-new -o UserKnownHostsFile=/root/.ssh/known_hosts' + : '-o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null'; + + $target = escapeshellarg($user . '@' . $hostAddr); + $cmdB64 = base64_encode($command); + $remote = 'CMD_B64="$0"; CMD="$(printf "%s" "$CMD_B64" | base64 -d)"; ' . + 'command -v tmux >/dev/null 2>&1 || exit 2; ' . + 'tmux has-session -t nexus 2>/dev/null || exit 3; ' . + 'tmux send-keys -t nexus "$CMD" C-m'; + + $cmd = 'ssh ' . $opts . ' -p ' . (int)$port . ' '; + if ($authType === 'key' && $keyPath !== '') { + $cmd .= '-i ' . escapeshellarg($keyPath) . ' '; + } + $cmd .= $target . ' -- /bin/bash -lc ' . escapeshellarg($remote) . ' ' . escapeshellarg($cmdB64); + if ($authType === 'pass' && $password !== '') { + $cmd = 'sshpass -p ' . escapeshellarg($password) . ' ' . $cmd; + } + + $descriptors = [ + 1 => ['pipe', 'w'], + 2 => ['pipe', 'w'], + ]; + $process = proc_open($cmd, $descriptors, $pipes); + if (!is_resource($process)) { + return [false, 'proc_open failed']; + } + $out = stream_get_contents($pipes[1]); + $err = stream_get_contents($pipes[2]); + $status = proc_get_status($process); + $exitCode = (int)($status['exitcode'] ?? 1); + proc_close($process); + + if ($exitCode === 0) { + return [true, null]; + } + if ($exitCode === 2) { + return [false, 'tmux ist auf dem Host nicht installiert.']; + } + if ($exitCode === 3) { + return [false, 'Keine aktive Konsole gefunden.']; + } + $msg = trim($err !== '' ? $err : $out); + return [false, $msg !== '' ? $msg : 'Befehl konnte nicht gesendet werden.']; +} ?>
Pi Control