diff --git a/modules/pi_control/bootstrap.php b/modules/pi_control/bootstrap.php new file mode 100644 index 0000000..432939a --- /dev/null +++ b/modules/pi_control/bootstrap.php @@ -0,0 +1,103 @@ +registerFunction($moduleName, 'table', function (string $name): string { + $prefix = 'picontrol_'; + $sanitized = preg_replace('/[^a-zA-Z0-9_]/', '', $name); + return $prefix . $sanitized; +}); + +modules()->registerFunction($moduleName, 'pdo', function () use ($moduleName): \PDO { + $settings = modules()->settings($moduleName); + $useSeparate = !empty($settings['use_separate_db']); + + if ($useSeparate) { + // Uses module-specific DB config + $module = modules()->get($moduleName); + $fallback = $module['db_defaults'] ?? []; + return modules()->modulePdo($moduleName, $fallback); + } + + $base = app()->basePdo(); + if (!$base) { + throw new ModuleConfigException( + $moduleName, + 'Base-DB ist deaktiviert. Bitte Base-DB aktivieren oder eigene Modul-DB konfigurieren.' + ); + } + + return $base; +}); + +modules()->registerFunction($moduleName, 'ensure_schema', function () use ($moduleName): void { + $pdo = module_fn($moduleName, 'pdo'); + $table = fn(string $name) => module_fn($moduleName, 'table', $name); + + $driver = (string)$pdo->getAttribute(PDO::ATTR_DRIVER_NAME); + + $hostTable = $table('hosts'); + $cmdTable = $table('commands'); + $runTable = $table('runs'); + + if ($driver === 'pgsql') { + $pdo->exec("CREATE TABLE IF NOT EXISTS {$hostTable} ( + id SERIAL PRIMARY KEY, + name VARCHAR(120) NOT NULL, + host VARCHAR(255) NOT NULL, + port INTEGER NOT NULL DEFAULT 22, + username VARCHAR(120) NOT NULL, + auth_type VARCHAR(20) NOT NULL DEFAULT 'key', + key_path TEXT NULL, + password TEXT NULL, + created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP + )"); + $pdo->exec("CREATE TABLE IF NOT EXISTS {$cmdTable} ( + id SERIAL PRIMARY KEY, + label VARCHAR(160) NOT NULL, + command TEXT NOT NULL, + admin_only BOOLEAN NOT NULL DEFAULT false, + created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP + )"); + $pdo->exec("CREATE TABLE IF NOT EXISTS {$runTable} ( + id SERIAL PRIMARY KEY, + host_id INTEGER NULL, + command_id INTEGER NULL, + command_text TEXT NOT NULL, + status VARCHAR(20) NOT NULL DEFAULT 'pending', + output TEXT NULL, + created_by VARCHAR(120) NULL, + created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP + )"); + } else { + $pdo->exec("CREATE TABLE IF NOT EXISTS {$hostTable} ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + name VARCHAR(120) NOT NULL, + host VARCHAR(255) NOT NULL, + port INTEGER NOT NULL DEFAULT 22, + username VARCHAR(120) NOT NULL, + auth_type VARCHAR(20) NOT NULL DEFAULT 'key', + key_path TEXT NULL, + password TEXT NULL, + created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP + )"); + $pdo->exec("CREATE TABLE IF NOT EXISTS {$cmdTable} ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + label VARCHAR(160) NOT NULL, + command TEXT NOT NULL, + admin_only INTEGER NOT NULL DEFAULT 0, + created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP + )"); + $pdo->exec("CREATE TABLE IF NOT EXISTS {$runTable} ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + host_id INTEGER NULL, + command_id INTEGER NULL, + command_text TEXT NOT NULL, + status VARCHAR(20) NOT NULL DEFAULT 'pending', + output TEXT NULL, + created_by VARCHAR(120) NULL, + created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP + )"); + } +}); diff --git a/modules/pi_control/module.json b/modules/pi_control/module.json new file mode 100644 index 0000000..2de09be --- /dev/null +++ b/modules/pi_control/module.json @@ -0,0 +1,45 @@ +{ + "title": "Pi Control", + "version": "0.1.0", + "description": "Verwaltung und Steuerung von Raspberry Pis (SSH/Commands/Presets).", + "menu": [ + { "label": "Übersicht", "href": "/module/pi_control" }, + { "label": "Hosts", "href": "/module/pi_control/hosts" }, + { "label": "Befehle", "href": "/module/pi_control/commands" }, + { "label": "Konsole", "href": "/module/pi_control/console" }, + { "label": "Setup", "href": "/modules/setup/pi_control" } + ], + "sidebar": { + "enabled": true, + "collapsible": true, + "default": "collapsed", + "items": [ + { "label": "Übersicht", "href": "/module/pi_control" }, + { "label": "Hosts", "href": "/module/pi_control/hosts" }, + { "label": "Befehle", "href": "/module/pi_control/commands" }, + { "label": "Konsole", "href": "/module/pi_control/console" }, + { "label": "Setup", "href": "/modules/setup/pi_control" } + ] + }, + "setup": { + "fields": [ + { "name": "use_separate_db", "label": "Eigene Modul-DB nutzen", "type": "checkbox", "required": false, "help": "Wenn aktiv, werden die DB-Daten unten verwendet. Sonst wird die Base-DB genutzt." }, + { "name": "db.driver", "label": "DB Driver", "type": "text", "required": false, "help": "z.B. pgsql, mysql, sqlite" }, + { "name": "db.host", "label": "DB Host", "type": "text", "required": false }, + { "name": "db.port", "label": "DB Port", "type": "number", "required": false }, + { "name": "db.dbname", "label": "DB Name", "type": "text", "required": false }, + { "name": "db.schema", "label": "DB Schema", "type": "text", "required": false }, + { "name": "db.user", "label": "DB User", "type": "text", "required": false }, + { "name": "db.password", "label": "DB Passwort", "type": "password", "required": false } + ] + }, + "db_defaults": { + "driver": "pgsql", + "host": "localhost", + "port": 5432, + "dbname": "", + "schema": "public", + "user": "", + "password": "" + } +} diff --git a/modules/pi_control/pages/commands.php b/modules/pi_control/pages/commands.php new file mode 100644 index 0000000..7a488d1 --- /dev/null +++ b/modules/pi_control/pages/commands.php @@ -0,0 +1,90 @@ + module_fn('pi_control', 'table', $name); + +$notice = null; +$error = null; + +if ($_SERVER['REQUEST_METHOD'] === 'POST') { + require_admin(); + $label = trim((string)($_POST['label'] ?? '')); + $command = trim((string)($_POST['command'] ?? '')); + $adminOnly = !empty($_POST['admin_only']) ? 1 : 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)' + ); + $stmt->execute([ + 'label' => $label, + 'command' => $command, + 'admin_only' => $adminOnly, + ]); + $notice = 'Befehl gespeichert.'; + } +} + +$commands = $pdo->query('SELECT * FROM ' . $table('commands') . ' ORDER BY id DESC')->fetchAll(PDO::FETCH_ASSOC); +?> +
+
Pi Control
+

Befehle

+

Verwalte vordefinierte SSH-Befehle.

+ + +
+ +
+ +
+ +
+ + +
+
+ Neuer Befehl +
+ + + + +
+
+ +
+ Vorhandene Befehle +
+ + + + + + + + + + + + + + + + + + + + +
LabelCommandAdmin
Keine Befehle vorhanden.
+ +
+
+
+
+
diff --git a/modules/pi_control/pages/console.php b/modules/pi_control/pages/console.php new file mode 100644 index 0000000..80fbc6f --- /dev/null +++ b/modules/pi_control/pages/console.php @@ -0,0 +1,125 @@ + module_fn('pi_control', 'table', $name); + +$notice = null; +$error = null; + +$hosts = $pdo->query('SELECT * FROM ' . $table('hosts') . ' ORDER BY name ASC')->fetchAll(PDO::FETCH_ASSOC); +$commands = $pdo->query('SELECT * FROM ' . $table('commands') . ' ORDER BY label ASC')->fetchAll(PDO::FETCH_ASSOC); + +if ($_SERVER['REQUEST_METHOD'] === 'POST') { + $hostId = (int)($_POST['host_id'] ?? 0); + $commandId = (int)($_POST['command_id'] ?? 0); + $rawCommand = trim((string)($_POST['command_text'] ?? '')); + + 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( + 'INSERT INTO ' . $table('runs') . ' (host_id, command_id, command_text, status, created_by) VALUES (:host_id, :command_id, :command_text, :status, :created_by)' + ); + $stmt->execute([ + 'host_id' => $hostId, + 'command_id' => $commandId > 0 ? $commandId : null, + 'command_text' => $commandText, + 'status' => 'pending', + 'created_by' => auth_display_name() ?: null, + ]); + + $notice = 'Befehl wurde erfasst. (Execution-Backend folgt)'; + } + } +} + +$runs = $pdo->query('SELECT * FROM ' . $table('runs') . ' ORDER BY id DESC LIMIT 20')->fetchAll(PDO::FETCH_ASSOC); +?> +
+
Pi Control
+

Konsole

+

Wähle einen Host und führe einen Befehl aus.

+ + +
+ +
+ +
+ +
+ + +
+
+ Ausführen +
+ + + + + + +
+

Hinweis: Execution-Backend wird im nächsten Schritt ergänzt.

+
+ +
+ Letzte Runs +
+ + + + + + + + + + + + + + + + + + + + + + +
IDStatusCommandVon
Noch keine Runs.
+
+
+
+
diff --git a/modules/pi_control/pages/hosts.php b/modules/pi_control/pages/hosts.php new file mode 100644 index 0000000..2f7673f --- /dev/null +++ b/modules/pi_control/pages/hosts.php @@ -0,0 +1,101 @@ + module_fn('pi_control', 'table', $name); + +$notice = null; +$error = null; + +if ($_SERVER['REQUEST_METHOD'] === 'POST') { + require_admin(); + $name = trim((string)($_POST['name'] ?? '')); + $host = trim((string)($_POST['host'] ?? '')); + $port = (int)($_POST['port'] ?? 22); + $username = trim((string)($_POST['username'] ?? '')); + $authType = trim((string)($_POST['auth_type'] ?? 'key')); + $keyPath = trim((string)($_POST['key_path'] ?? '')); + $password = trim((string)($_POST['password'] ?? '')); + + if ($name === '' || $host === '' || $username === '') { + $error = 'Bitte Name, Host und Benutzer angeben.'; + } else { + $stmt = $pdo->prepare( + 'INSERT INTO ' . $table('hosts') . ' (name, host, port, username, auth_type, key_path, password) VALUES (:name, :host, :port, :username, :auth_type, :key_path, :password)' + ); + $stmt->execute([ + 'name' => $name, + 'host' => $host, + 'port' => $port > 0 ? $port : 22, + 'username' => $username, + 'auth_type' => $authType !== '' ? $authType : 'key', + 'key_path' => $keyPath !== '' ? $keyPath : null, + 'password' => $password !== '' ? $password : null, + ]); + $notice = 'Host gespeichert.'; + } +} + +$hosts = $pdo->query('SELECT * FROM ' . $table('hosts') . ' ORDER BY id DESC')->fetchAll(PDO::FETCH_ASSOC); +?> +
+
Pi Control
+

Hosts

+

Verwalte die Raspberry Pis, die du steuern möchtest.

+ + +
+ +
+ +
+ +
+ + +
+
+ Neuer Host +
+ + + + + + + + +
+
+ +
+ Registrierte Hosts +
+ + + + + + + + + + + + + + + + + + + + + + + + +
NameHostUserPortAuth
Keine Hosts vorhanden.
+
+
+
+
diff --git a/modules/pi_control/pages/index.php b/modules/pi_control/pages/index.php new file mode 100644 index 0000000..867c896 --- /dev/null +++ b/modules/pi_control/pages/index.php @@ -0,0 +1,32 @@ + module_fn('pi_control', 'table', $name); + +$hostCount = (int)$pdo->query('SELECT COUNT(*) FROM ' . $table('hosts'))->fetchColumn(); +$cmdCount = (int)$pdo->query('SELECT COUNT(*) FROM ' . $table('commands'))->fetchColumn(); +$runCount = (int)$pdo->query('SELECT COUNT(*) FROM ' . $table('runs'))->fetchColumn(); +?> +
+
Pi Control
+

Raspberry Pi Steuerung

+

SSH Hosts verwalten, Befehle definieren und Aktionen ausführen.

+ +
+
+ Hosts +
Registriert:
+
Hosts verwalten
+
+
+ Befehle +
Presets:
+
Befehle verwalten
+
+
+ Konsole +
Runs:
+
Konsole öffnen
+
+
+