diff --git a/DeskHub-Webseite/database/migrate.php b/DeskHub-Webseite/database/migrate.php index 562739b3..c3c8a81b 100644 --- a/DeskHub-Webseite/database/migrate.php +++ b/DeskHub-Webseite/database/migrate.php @@ -1409,6 +1409,139 @@ function run_migrations(PDO $pdo, bool $verbose = false): array } } + // Migration 0050: Kategorien für Dienste auf der Statusseite. + if (!isset($done['php_0050_status_categories'])) { + try { + $drv = $pdo->getAttribute(PDO::ATTR_DRIVER_NAME); + $cols = $pdo->query($drv === 'sqlite' ? 'PRAGMA table_info(status_components)' : 'SHOW COLUMNS FROM status_components')->fetchAll(); + $hasCategory = false; + foreach ($cols as $col) if (($col['name'] ?? $col['Field'] ?? '') === 'category') $hasCategory = true; + if (!$hasCategory) $pdo->exec('ALTER TABLE status_components ADD COLUMN category VARCHAR(80) NULL'); + $pdo->prepare('INSERT INTO schema_migrations (migration) VALUES (?)')->execute(['php_0050_status_categories']); + $result['applied']++; + if ($verbose) $result['log'][] = ' [OK] php_0050_status_categories'; + } catch (Throwable $e) { + $result['errors'][] = 'php_0050_status_categories: ' . $e->getMessage(); + if ($verbose) $result['log'][] = ' [FAIL] php_0050_status_categories - ' . $e->getMessage(); + } + } + + // Migration 0051: Kategorien als eigene Datensätze mit Dropdown-Zuordnung. + if (!isset($done['php_0051_status_category_records'])) { + try { + $drv = $pdo->getAttribute(PDO::ATTR_DRIVER_NAME); + $ai = $drv === 'sqlite' ? 'INTEGER PRIMARY KEY AUTOINCREMENT' : 'INT AUTO_INCREMENT PRIMARY KEY'; + $eng = $drv === 'sqlite' ? '' : ' ENGINE=InnoDB DEFAULT CHARSET=utf8mb4'; + $pdo->exec("CREATE TABLE IF NOT EXISTS status_categories ( + id $ai, + name VARCHAR(80) NOT NULL, + sort_order INT NOT NULL DEFAULT 0 + )$eng"); + $cols = $pdo->query($drv === 'sqlite' ? 'PRAGMA table_info(status_components)' : 'SHOW COLUMNS FROM status_components')->fetchAll(); + $hasCategoryId = false; + foreach ($cols as $col) if (($col['name'] ?? $col['Field'] ?? '') === 'category_id') $hasCategoryId = true; + if (!$hasCategoryId) $pdo->exec('ALTER TABLE status_components ADD COLUMN category_id INT NULL'); + $legacy = $pdo->query("SELECT DISTINCT category FROM status_components WHERE category IS NOT NULL AND TRIM(category) <> ''")->fetchAll(PDO::FETCH_COLUMN); + $find = $pdo->prepare('SELECT id FROM status_categories WHERE LOWER(name) = LOWER(?) LIMIT 1'); + $add = $pdo->prepare('INSERT INTO status_categories (name, sort_order) VALUES (?,?)'); + $assign = $pdo->prepare('UPDATE status_components SET category_id = ? WHERE category = ? AND category_id IS NULL'); + foreach ($legacy as $name) { + $find->execute([$name]); + $categoryId = $find->fetchColumn(); + if (!$categoryId) { + $add->execute([$name, 0]); + $categoryId = (int) $pdo->lastInsertId(); + } + $assign->execute([$categoryId, $name]); + } + $pdo->prepare('INSERT INTO schema_migrations (migration) VALUES (?)')->execute(['php_0051_status_category_records']); + $result['applied']++; + if ($verbose) $result['log'][] = ' [OK] php_0051_status_category_records'; + } catch (Throwable $e) { + $result['errors'][] = 'php_0051_status_category_records: ' . $e->getMessage(); + if ($verbose) $result['log'][] = ' [FAIL] php_0051_status_category_records - ' . $e->getMessage(); + } + } + + // Migration 0052: Statuswechsel der Dienste für eine ehrliche Verlaufsanzeige speichern. + if (!isset($done['php_0052_status_component_history'])) { + try { + $drv = $pdo->getAttribute(PDO::ATTR_DRIVER_NAME); + $ai = $drv === 'sqlite' ? 'INTEGER PRIMARY KEY AUTOINCREMENT' : 'INT AUTO_INCREMENT PRIMARY KEY'; + $eng = $drv === 'sqlite' ? '' : ' ENGINE=InnoDB DEFAULT CHARSET=utf8mb4'; + $pdo->exec("CREATE TABLE IF NOT EXISTS status_component_events ( + id $ai, + component_id INT NOT NULL, + status VARCHAR(20) NOT NULL, + created_at DATETIME NOT NULL + )$eng"); + // Der erste Messpunkt ist ab jetzt; frühere Zustände werden nicht geraten. + $existing = $pdo->prepare('SELECT COUNT(*) FROM status_component_events WHERE component_id = ?'); + $seed = $pdo->prepare('INSERT INTO status_component_events (component_id, status, created_at) VALUES (?,?,?)'); + foreach ($pdo->query('SELECT id, status FROM status_components')->fetchAll() as $component) { + $existing->execute([(int) $component['id']]); + if ((int) $existing->fetchColumn() === 0) $seed->execute([(int) $component['id'], (string) $component['status'], date('Y-m-d H:i:s')]); + } + $pdo->prepare('INSERT INTO schema_migrations (migration) VALUES (?)')->execute(['php_0052_status_component_history']); + $result['applied']++; + if ($verbose) $result['log'][] = ' [OK] php_0052_status_component_history'; + } catch (Throwable $e) { + $result['errors'][] = 'php_0052_status_component_history: ' . $e->getMessage(); + if ($verbose) $result['log'][] = ' [FAIL] php_0052_status_component_history - ' . $e->getMessage(); + } + } + + // Migration 0053: automatische Statusprüfungen für Dienste konfigurieren. + if (!isset($done['php_0053_status_auto_checks'])) { + try { + $drv = $pdo->getAttribute(PDO::ATTR_DRIVER_NAME); + $cols = $pdo->query($drv === 'sqlite' ? 'PRAGMA table_info(status_components)' : 'SHOW COLUMNS FROM status_components')->fetchAll(); + $existing = []; + foreach ($cols as $col) $existing[] = (string) ($col['name'] ?? $col['Field'] ?? ''); + $add = [ + 'monitor_type' => "VARCHAR(20) NOT NULL DEFAULT 'auto'", + 'monitor_target' => 'VARCHAR(500) NULL', + 'last_checked_at' => 'DATETIME NULL', + 'last_check_ms' => 'INT NULL', + 'last_check_error' => 'VARCHAR(255) NULL', + 'consecutive_failures' => 'INT NOT NULL DEFAULT 0', + ]; + foreach ($add as $column => $definition) { + if (!in_array($column, $existing, true)) $pdo->exec("ALTER TABLE status_components ADD COLUMN $column $definition"); + } + $pdo->prepare('INSERT INTO schema_migrations (migration) VALUES (?)')->execute(['php_0053_status_auto_checks']); + $result['applied']++; + if ($verbose) $result['log'][] = ' [OK] php_0053_status_auto_checks'; + } catch (Throwable $e) { + $result['errors'][] = 'php_0053_status_auto_checks: ' . $e->getMessage(); + if ($verbose) $result['log'][] = ' [FAIL] php_0053_status_auto_checks - ' . $e->getMessage(); + } + } + + // Migration 0054: einzelne Prüfergebnisse für die Heartbeat-Leiste speichern. + if (!isset($done['php_0054_status_check_samples'])) { + try { + $drv = $pdo->getAttribute(PDO::ATTR_DRIVER_NAME); + $ai = $drv === 'sqlite' ? 'INTEGER PRIMARY KEY AUTOINCREMENT' : 'INT AUTO_INCREMENT PRIMARY KEY'; + $eng = $drv === 'sqlite' ? '' : ' ENGINE=InnoDB DEFAULT CHARSET=utf8mb4'; + $pdo->exec("CREATE TABLE IF NOT EXISTS status_component_checks ( + id $ai, + component_id INT NOT NULL, + status VARCHAR(20) NOT NULL, + checked_at DATETIME NOT NULL, + response_ms INT NULL + )$eng"); + $pdo->exec('CREATE INDEX idx_status_component_checks_lookup ON status_component_checks (component_id, checked_at, id)'); + $pdo->exec('CREATE INDEX idx_status_component_checks_checked_at ON status_component_checks (checked_at)'); + $pdo->prepare('INSERT INTO schema_migrations (migration) VALUES (?)')->execute(['php_0054_status_check_samples']); + $result['applied']++; + if ($verbose) $result['log'][] = ' [OK] php_0054_status_check_samples'; + } catch (Throwable $e) { + $result['errors'][] = 'php_0054_status_check_samples: ' . $e->getMessage(); + if ($verbose) $result['log'][] = ' [FAIL] php_0054_status_check_samples - ' . $e->getMessage(); + } + } + // Migration 0049: gesammelte, noch nicht übersetzte Texte (Sammelmodus der Mehrsprachigkeit). if (!isset($done['php_0049_i18n_missing'])) { try {