diff --git a/modules/pi_control/bootstrap.php b/modules/pi_control/bootstrap.php index 08c08e1..a84a1f4 100644 --- a/modules/pi_control/bootstrap.php +++ b/modules/pi_control/bootstrap.php @@ -32,6 +32,17 @@ $mm->registerFunction($moduleName, 'pdo', function () use ($moduleName): \PDO { return $base; }); +$mm->registerFunction($moduleName, 'redis', function () use ($moduleName) { + $settings = modules()->settings($moduleName); + $redis = (array)($settings['redis'] ?? []); + $host = (string)($redis['host'] ?? 'redis'); + $port = (int)($redis['port'] ?? 6379); + $password = (string)($redis['password'] ?? ''); + $db = (int)($redis['db'] ?? 0); + + return new \App\RedisClient($host, $port, $password !== '' ? $password : null, $db); +}); + $mm->registerFunction($moduleName, 'ensure_schema', function () use ($moduleName): void { $pdo = module_fn($moduleName, 'pdo'); $table = fn(string $name) => module_fn($moduleName, 'table', $name); @@ -60,6 +71,7 @@ $mm->registerFunction($moduleName, 'ensure_schema', function () use ($moduleName label VARCHAR(160) NOT NULL, command TEXT NOT NULL, admin_only BOOLEAN NOT NULL DEFAULT false, + timeout_sec INTEGER NULL, created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP )"); $pdo->exec("CREATE TABLE IF NOT EXISTS {$runTable} ( @@ -69,7 +81,12 @@ $mm->registerFunction($moduleName, 'ensure_schema', function () use ($moduleName command_text TEXT NOT NULL, status VARCHAR(20) NOT NULL DEFAULT 'pending', output TEXT NULL, + error TEXT NULL, + exit_code INTEGER NULL, + timeout_sec INTEGER NULL, created_by VARCHAR(120) NULL, + started_at TIMESTAMP NULL, + finished_at TIMESTAMP NULL, created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP )"); $pdo->exec("CREATE TABLE IF NOT EXISTS {$sessionTable} ( @@ -99,6 +116,7 @@ $mm->registerFunction($moduleName, 'ensure_schema', function () use ($moduleName label VARCHAR(160) NOT NULL, command TEXT NOT NULL, admin_only INTEGER NOT NULL DEFAULT 0, + timeout_sec INTEGER NULL, created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP )"); $pdo->exec("CREATE TABLE IF NOT EXISTS {$runTable} ( @@ -108,7 +126,12 @@ $mm->registerFunction($moduleName, 'ensure_schema', function () use ($moduleName command_text TEXT NOT NULL, status VARCHAR(20) NOT NULL DEFAULT 'pending', output TEXT NULL, + error TEXT NULL, + exit_code INTEGER NULL, + timeout_sec INTEGER NULL, created_by VARCHAR(120) NULL, + started_at DATETIME NULL, + finished_at DATETIME NULL, created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP )"); $pdo->exec("CREATE TABLE IF NOT EXISTS {$sessionTable} ( @@ -123,6 +146,46 @@ $mm->registerFunction($moduleName, 'ensure_schema', function () use ($moduleName )"); } + // Schema migrations for existing tables + if ($driver === 'pgsql') { + $pdo->exec("ALTER TABLE {$cmdTable} ADD COLUMN IF NOT EXISTS timeout_sec INTEGER NULL"); + $pdo->exec("ALTER TABLE {$runTable} ADD COLUMN IF NOT EXISTS error TEXT NULL"); + $pdo->exec("ALTER TABLE {$runTable} ADD COLUMN IF NOT EXISTS exit_code INTEGER NULL"); + $pdo->exec("ALTER TABLE {$runTable} ADD COLUMN IF NOT EXISTS timeout_sec INTEGER NULL"); + $pdo->exec("ALTER TABLE {$runTable} ADD COLUMN IF NOT EXISTS started_at TIMESTAMP NULL"); + $pdo->exec("ALTER TABLE {$runTable} ADD COLUMN IF NOT EXISTS finished_at TIMESTAMP NULL"); + } else { + $columns = []; + $stmt = $pdo->query('PRAGMA table_info(' . $cmdTable . ')'); + foreach ($stmt->fetchAll(PDO::FETCH_ASSOC) as $col) { + $columns[$col['name']] = true; + } + if (empty($columns['timeout_sec'])) { + $pdo->exec("ALTER TABLE {$cmdTable} ADD COLUMN timeout_sec INTEGER NULL"); + } + + $columns = []; + $stmt = $pdo->query('PRAGMA table_info(' . $runTable . ')'); + foreach ($stmt->fetchAll(PDO::FETCH_ASSOC) as $col) { + $columns[$col['name']] = true; + } + if (empty($columns['error'])) { + $pdo->exec("ALTER TABLE {$runTable} ADD COLUMN error TEXT NULL"); + } + if (empty($columns['exit_code'])) { + $pdo->exec("ALTER TABLE {$runTable} ADD COLUMN exit_code INTEGER NULL"); + } + if (empty($columns['timeout_sec'])) { + $pdo->exec("ALTER TABLE {$runTable} ADD COLUMN timeout_sec INTEGER NULL"); + } + if (empty($columns['started_at'])) { + $pdo->exec("ALTER TABLE {$runTable} ADD COLUMN started_at DATETIME NULL"); + } + if (empty($columns['finished_at'])) { + $pdo->exec("ALTER TABLE {$runTable} ADD COLUMN finished_at DATETIME NULL"); + } + } + // Seed default commands (only when empty) $count = (int)$pdo->query('SELECT COUNT(*) FROM ' . $cmdTable)->fetchColumn(); if ($count === 0) { diff --git a/modules/pi_control/module.json b/modules/pi_control/module.json index a17abac..e6292f7 100644 --- a/modules/pi_control/module.json +++ b/modules/pi_control/module.json @@ -33,7 +33,13 @@ { "name": "db.password", "label": "DB Passwort", "type": "password", "required": false }, { "name": "ttyd_url", "label": "ttyd URL", "type": "text", "required": false, "help": "z.B. https://staging.nexus.int.kusche.berlin/ttyd" }, { "name": "terminal_token_ttl", "label": "Token TTL (Minuten)", "type": "number", "required": false, "help": "Gültigkeit der Konsole-Token, z.B. 10" }, - { "name": "terminal_shared_secret", "label": "Terminal Shared Secret", "type": "password", "required": false, "help": "Zusätzliche Absicherung für terminal_info (Header X-Terminal-Secret)" } + { "name": "terminal_shared_secret", "label": "Terminal Shared Secret", "type": "password", "required": false, "help": "Zusätzliche Absicherung für terminal_info (Header X-Terminal-Secret)" }, + { "name": "exec_default_timeout", "label": "Command-Timeout (Sek.)", "type": "number", "required": false, "help": "Default-Timeout für Befehle, z.B. 300" }, + { "name": "redis.host", "label": "Redis Host", "type": "text", "required": false, "help": "Service-Name, z.B. redis" }, + { "name": "redis.port", "label": "Redis Port", "type": "number", "required": false, "help": "Standard 6379" }, + { "name": "redis.password", "label": "Redis Passwort", "type": "password", "required": false }, + { "name": "redis.db", "label": "Redis DB", "type": "number", "required": false, "help": "Standard 0" }, + { "name": "redis.queue", "label": "Redis Queue", "type": "text", "required": false, "help": "z.B. pi_control:queue" } ] }, "db_defaults": { diff --git a/modules/pi_control/pages/commands.php b/modules/pi_control/pages/commands.php index 7a488d1..bc78928 100644 --- a/modules/pi_control/pages/commands.php +++ b/modules/pi_control/pages/commands.php @@ -11,17 +11,19 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') { $label = trim((string)($_POST['label'] ?? '')); $command = trim((string)($_POST['command'] ?? '')); $adminOnly = !empty($_POST['admin_only']) ? 1 : 0; + $timeoutSec = (int)($_POST['timeout_sec'] ?? 0); if ($label === '' || $command === '') { $error = 'Bitte Label und Command angeben.'; } else { $stmt = $pdo->prepare( - 'INSERT INTO ' . $table('commands') . ' (label, command, admin_only) VALUES (:label, :command, :admin_only)' + 'INSERT INTO ' . $table('commands') . ' (label, command, admin_only, timeout_sec) VALUES (:label, :command, :admin_only, :timeout_sec)' ); $stmt->execute([ 'label' => $label, 'command' => $command, 'admin_only' => $adminOnly, + 'timeout_sec' => $timeoutSec > 0 ? $timeoutSec : null, ]); $notice = 'Befehl gespeichert.'; } @@ -47,9 +49,10 @@ $commands = $pdo->query('SELECT * FROM ' . $table('commands') . ' ORDER BY id DE