Upload via GUI (340 Dateien)

This commit is contained in:
2026-10-26 21:19:00 +00:00
parent 9acec37e18
commit ce27492b24
2 changed files with 249 additions and 0 deletions
+249
View File
@@ -1542,6 +1542,255 @@ function run_migrations(PDO $pdo, bool $verbose = false): array
}
}
// Migration 0055: Status-Komponenten lassen sich für Anzeige und Prüfungen aktivieren/deaktivieren.
if (!isset($done['php_0055_status_component_enabled'])) {
try {
$drv = $pdo->getAttribute(PDO::ATTR_DRIVER_NAME);
$cols = $pdo->query($drv === 'sqlite' ? 'PRAGMA table_info(status_components)' : 'SHOW COLUMNS FROM status_components')->fetchAll();
$hasEnabled = false;
foreach ($cols as $col) if (($col['name'] ?? $col['Field'] ?? '') === 'is_enabled') $hasEnabled = true;
if (!$hasEnabled) $pdo->exec('ALTER TABLE status_components ADD COLUMN is_enabled TINYINT NOT NULL DEFAULT 1');
$categoryStmt = $pdo->prepare('SELECT id FROM status_categories WHERE LOWER(name) = LOWER(?) LIMIT 1');
$categoryStmt->execute(['Apps']);
$gameCategoryId = $categoryStmt->fetchColumn();
if (!$gameCategoryId) {
$categoryOrder = (int) $pdo->query('SELECT COALESCE(MAX(sort_order), 0) FROM status_categories')->fetchColumn() + 10;
$pdo->prepare('INSERT INTO status_categories (name, sort_order) VALUES (?,?)')->execute(['Apps', $categoryOrder]);
$gameCategoryId = (int) $pdo->lastInsertId();
}
$minecraftStmt = $pdo->query("SELECT id, category_id, category FROM status_components WHERE LOWER(name) LIKE '%minecraft%' ORDER BY id LIMIT 1");
$minecraft = $minecraftStmt->fetch(PDO::FETCH_ASSOC);
if (!$minecraft) {
$sortOrder = (int) $pdo->query('SELECT COALESCE(MAX(sort_order), 0) FROM status_components')->fetchColumn() + 10;
$insert = $pdo->prepare('INSERT INTO status_components (name, description, status, sort_order, created_at, category_id, monitor_type, is_enabled) VALUES (?,?,?,?,?,?,?,0)');
$insert->execute(['Minecraft-Server', 'Minecraft Java – IP-Adresse oder Domain und Port eintragen.', 'operational', $sortOrder, db_now(), (int) $gameCategoryId, 'tcp']);
} elseif (empty($minecraft['category_id']) && trim((string) ($minecraft['category'] ?? '')) === '') {
$pdo->prepare('UPDATE status_components SET category_id = ? WHERE id = ?')->execute([(int) $gameCategoryId, (int) $minecraft['id']]);
}
$pdo->prepare('INSERT INTO schema_migrations (migration) VALUES (?)')->execute(['php_0055_status_component_enabled']);
$result['applied']++;
if ($verbose) $result['log'][] = ' [OK] php_0055_status_component_enabled';
} catch (Throwable $e) {
$result['errors'][] = 'php_0055_status_component_enabled: ' . $e->getMessage();
if ($verbose) $result['log'][] = ' [FAIL] php_0055_status_component_enabled - ' . $e->getMessage();
}
}
// Migration 0056: Minecraft-Statusdienst der Kategorie „Apps“ zuordnen.
if (!isset($done['php_0056_minecraft_status_category'])) {
try {
$categoryStmt = $pdo->prepare('SELECT id FROM status_categories WHERE LOWER(name) = LOWER(?) LIMIT 1');
$categoryStmt->execute(['Apps']);
$gameCategoryId = $categoryStmt->fetchColumn();
if (!$gameCategoryId) {
$categoryOrder = (int) $pdo->query('SELECT COALESCE(MAX(sort_order), 0) FROM status_categories')->fetchColumn() + 10;
$pdo->prepare('INSERT INTO status_categories (name, sort_order) VALUES (?,?)')->execute(['Apps', $categoryOrder]);
$gameCategoryId = (int) $pdo->lastInsertId();
}
$pdo->prepare("UPDATE status_components SET category_id = ? WHERE LOWER(name) LIKE '%minecraft%' AND category_id IS NULL AND (category IS NULL OR TRIM(category) = '')")
->execute([(int) $gameCategoryId]);
$pdo->prepare('INSERT INTO schema_migrations (migration) VALUES (?)')->execute(['php_0056_minecraft_status_category']);
$result['applied']++;
if ($verbose) $result['log'][] = ' [OK] php_0056_minecraft_status_category';
} catch (Throwable $e) {
$result['errors'][] = 'php_0056_minecraft_status_category: ' . $e->getMessage();
if ($verbose) $result['log'][] = ' [FAIL] php_0056_minecraft_status_category - ' . $e->getMessage();
}
}
// Migration 0057: Minecraft-Dienst unter „Apps“ einsortieren.
if (!isset($done['php_0057_minecraft_status_apps_category'])) {
try {
$categoryStmt = $pdo->prepare('SELECT id FROM status_categories WHERE LOWER(name) = LOWER(?) LIMIT 1');
$categoryStmt->execute(['Apps']);
$appCategoryId = $categoryStmt->fetchColumn();
if (!$appCategoryId) {
$categoryOrder = (int) $pdo->query('SELECT COALESCE(MAX(sort_order), 0) FROM status_categories')->fetchColumn() + 10;
$pdo->prepare('INSERT INTO status_categories (name, sort_order) VALUES (?,?)')->execute(['Apps', $categoryOrder]);
$appCategoryId = (int) $pdo->lastInsertId();
}
$pdo->prepare("UPDATE status_components SET category_id = ?, category = NULL WHERE LOWER(name) LIKE '%minecraft%'")
->execute([(int) $appCategoryId]);
$pdo->prepare('INSERT INTO schema_migrations (migration) VALUES (?)')->execute(['php_0057_minecraft_status_apps_category']);
$result['applied']++;
if ($verbose) $result['log'][] = ' [OK] php_0057_minecraft_status_apps_category';
} catch (Throwable $e) {
$result['errors'][] = 'php_0057_minecraft_status_apps_category: ' . $e->getMessage();
if ($verbose) $result['log'][] = ' [FAIL] php_0057_minecraft_status_apps_category - ' . $e->getMessage();
}
}
// Migration 0059: Optionalen Backup-Server für die Statusseite anlegen.
if (!isset($done['php_0059_backup_server_status_component'])) {
try {
$categoryStmt = $pdo->prepare('SELECT id FROM status_categories WHERE LOWER(name) = LOWER(?) LIMIT 1');
$categoryStmt->execute(['Server & Datenbank']);
$categoryId = $categoryStmt->fetchColumn();
if (!$categoryId) {
$categoryOrder = (int) $pdo->query('SELECT COALESCE(MAX(sort_order), 0) FROM status_categories')->fetchColumn() + 10;
$pdo->prepare('INSERT INTO status_categories (name, sort_order) VALUES (?,?)')->execute(['Server & Datenbank', $categoryOrder]);
$categoryId = (int) $pdo->lastInsertId();
}
$existing = $pdo->query("SELECT id FROM status_components WHERE LOWER(name) IN ('backup-server', 'backup server', 'ftp-backup-server') LIMIT 1")->fetchColumn();
if (!$existing) {
$sortOrder = (int) $pdo->query('SELECT COALESCE(MAX(sort_order), 0) FROM status_components')->fetchColumn() + 10;
$insert = $pdo->prepare('INSERT INTO status_components (name, description, status, sort_order, created_at, category_id, monitor_type, is_enabled) VALUES (?,?,?,?,?,?,?,0)');
$insert->execute(['Backup-Server', 'Optionaler externer Backup-Server; Prüfung mit Host und Port.', 'operational', $sortOrder, db_now(), (int) $categoryId, 'tcp']);
}
$pdo->prepare('INSERT INTO schema_migrations (migration) VALUES (?)')->execute(['php_0059_backup_server_status_component']);
$result['applied']++;
if ($verbose) $result['log'][] = ' [OK] php_0059_backup_server_status_component';
} catch (Throwable $e) {
$result['errors'][] = 'php_0059_backup_server_status_component: ' . $e->getMessage();
if ($verbose) $result['log'][] = ' [FAIL] php_0059_backup_server_status_component - ' . $e->getMessage();
}
}
// Migration 0060: Geplante Wartungsfenster der Statusseite.
if (!isset($done['php_0060_status_maintenance_windows'])) {
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_maintenance_windows (
id $ai,
title VARCHAR(200) NOT NULL,
body TEXT NULL,
component_id INT NULL,
starts_at DATETIME NOT NULL,
ends_at DATETIME NOT NULL,
created_by INT NULL,
created_at DATETIME NOT NULL,
cancelled_at DATETIME NULL
)$eng");
$pdo->exec('CREATE INDEX idx_status_maintenance_window_time ON status_maintenance_windows (starts_at, ends_at)');
$pdo->prepare('INSERT INTO schema_migrations (migration) VALUES (?)')->execute(['php_0060_status_maintenance_windows']);
$result['applied']++;
if ($verbose) $result['log'][] = ' [OK] php_0060_status_maintenance_windows';
} catch (Throwable $e) {
$result['errors'][] = 'php_0060_status_maintenance_windows: ' . $e->getMessage();
if ($verbose) $result['log'][] = ' [FAIL] php_0060_status_maintenance_windows - ' . $e->getMessage();
}
}
// Migration 0061: Prüfintervall je Statusdienst konfigurierbar machen.
if (!isset($done['php_0061_status_check_intervals'])) {
try {
$drv = $pdo->getAttribute(PDO::ATTR_DRIVER_NAME);
$cols = $pdo->query($drv === 'sqlite' ? 'PRAGMA table_info(status_components)' : 'SHOW COLUMNS FROM status_components')->fetchAll();
$hasInterval = false;
foreach ($cols as $col) if (($col['name'] ?? $col['Field'] ?? '') === 'check_interval_minutes') $hasInterval = true;
if (!$hasInterval) $pdo->exec('ALTER TABLE status_components ADD COLUMN check_interval_minutes INT NOT NULL DEFAULT 1');
$pdo->prepare('INSERT INTO schema_migrations (migration) VALUES (?)')->execute(['php_0061_status_check_intervals']);
$result['applied']++;
if ($verbose) $result['log'][] = ' [OK] php_0061_status_check_intervals';
} catch (Throwable $e) {
$result['errors'][] = 'php_0061_status_check_intervals: ' . $e->getMessage();
if ($verbose) $result['log'][] = ' [FAIL] php_0061_status_check_intervals - ' . $e->getMessage();
}
}
// Migration 0063: SLA-Wartepausen und Ereignisverlauf.
if (!isset($done['php_0063_ticket_sla_pause_history'])) {
try {
$drv = $pdo->getAttribute(PDO::ATTR_DRIVER_NAME);
$cols = $pdo->query($drv === 'sqlite' ? 'PRAGMA table_info(tickets)' : 'SHOW COLUMNS FROM tickets')->fetchAll();
$hasPauseColumn = false;
foreach ($cols as $col) if ((string) ($col['name'] ?? $col['Field'] ?? '') === 'sla_paused_at') $hasPauseColumn = true;
if (!$hasPauseColumn) $pdo->exec('ALTER TABLE tickets ADD COLUMN sla_paused_at DATETIME NULL');
$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 ticket_sla_history (
id $ai, ticket_id INT NOT NULL, event_type VARCHAR(24) NOT NULL,
deadline_type VARCHAR(24) NULL, details TEXT NULL, staff_id INT NULL, created_at DATETIME NOT NULL
)$eng");
$indexes = $pdo->query($drv === 'sqlite' ? 'PRAGMA index_list(ticket_sla_history)' : 'SHOW INDEX FROM ticket_sla_history')->fetchAll();
$hasIndex = false;
foreach ($indexes as $index) if ((string) ($index['name'] ?? $index['Key_name'] ?? '') === 'idx_ticket_sla_history_ticket_time') $hasIndex = true;
if (!$hasIndex) $pdo->exec('CREATE INDEX idx_ticket_sla_history_ticket_time ON ticket_sla_history (ticket_id, created_at)');
foreach ([['first_response_due_at', 'first_response'], ['resolution_due_at', 'resolution']] as [$dueColumn, $type]) {
$sql = "INSERT INTO ticket_sla_history (ticket_id, event_type, deadline_type, details, created_at)
SELECT t.id, 'started', ?, ?, t.created_at FROM tickets t
WHERE t.$dueColumn IS NOT NULL AND NOT EXISTS (
SELECT 1 FROM ticket_sla_history h WHERE h.ticket_id = t.id AND h.event_type = 'started' AND h.deadline_type = ?
)";
$pdo->prepare($sql)->execute([$type, 'SLA gestartet; Fristfeld: ' . $dueColumn, $type]);
}
$now = db_now();
$pdo->prepare("UPDATE tickets SET sla_paused_at = ? WHERE sla_paused_at IS NULL AND status IN ('pending','on_hold')")->execute([$now]);
$pdo->prepare("INSERT INTO ticket_sla_history (ticket_id, event_type, details, created_at)
SELECT id, 'paused', 'SLA-Pause bei Aktivierung uebernommen', ? FROM tickets
WHERE status IN ('pending','on_hold') AND sla_paused_at = ?")->execute([$now, $now]);
$pdo->exec("INSERT INTO ticket_sla_history (ticket_id,event_type,deadline_type,details,created_at)
SELECT t.id,'overdue','resolution',NULL,t.resolution_due_at FROM tickets t
WHERE t.is_overdue = 1 AND t.resolution_due_at IS NOT NULL AND NOT EXISTS (
SELECT 1 FROM ticket_sla_history h WHERE h.ticket_id=t.id AND h.event_type='overdue' AND h.deadline_type='resolution'
)");
$backfillResponse = $pdo->prepare("INSERT INTO ticket_sla_history (ticket_id,event_type,deadline_type,details,created_at)
SELECT t.id,'overdue','first_response',NULL,t.first_response_due_at FROM tickets t
WHERE t.first_responded_at IS NULL AND t.first_response_due_at IS NOT NULL AND t.first_response_due_at < ?
AND t.status NOT IN ('pending','on_hold','resolved','closed') AND NOT EXISTS (
SELECT 1 FROM ticket_sla_history h WHERE h.ticket_id=t.id AND h.event_type='overdue' AND h.deadline_type='first_response'
)");
$backfillResponse->execute([$now]);
$pdo->prepare('INSERT INTO schema_migrations (migration) VALUES (?)')->execute(['php_0063_ticket_sla_pause_history']);
$result['applied']++;
if ($verbose) $result['log'][] = ' [OK] php_0063_ticket_sla_pause_history';
} catch (Throwable $e) {
$result['errors'][] = 'php_0063_ticket_sla_pause_history: ' . $e->getMessage();
if ($verbose) $result['log'][] = ' [FAIL] php_0063_ticket_sla_pause_history - ' . $e->getMessage();
}
}
// Migration 0062: Problemmanagement und dokumentierte Ticket-Abschlussgründe.
if (!isset($done['php_0062_problems_and_resolution_codes'])) {
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';
$ticketCols = $pdo->query($drv === 'sqlite' ? 'PRAGMA table_info(tickets)' : 'SHOW COLUMNS FROM tickets')->fetchAll();
$has = [];
foreach ($ticketCols as $col) $has[] = (string) ($col['name'] ?? $col['Field'] ?? '');
if (!in_array('resolution_code', $has, true)) $pdo->exec('ALTER TABLE tickets ADD COLUMN resolution_code VARCHAR(40) NULL');
if (!in_array('resolution_note', $has, true)) $pdo->exec('ALTER TABLE tickets ADD COLUMN resolution_note TEXT NULL');
$pdo->exec("CREATE TABLE IF NOT EXISTS problems (
id $ai,
title VARCHAR(200) NOT NULL,
description TEXT NULL,
root_cause TEXT NULL,
workaround TEXT NULL,
resolution TEXT NULL,
status VARCHAR(24) NOT NULL DEFAULT 'investigating',
created_by INT NULL,
updated_by INT NULL,
created_at DATETIME NOT NULL,
updated_at DATETIME NOT NULL,
resolved_at DATETIME NULL
)$eng");
$pdo->exec("CREATE TABLE IF NOT EXISTS problem_tickets (
problem_id INT NOT NULL,
ticket_id INT NOT NULL,
linked_by INT NULL,
linked_at DATETIME NOT NULL,
PRIMARY KEY (problem_id, ticket_id)
)$eng");
$indexes = $pdo->query($drv === 'sqlite' ? 'PRAGMA index_list(problem_tickets)' : 'SHOW INDEX FROM problem_tickets')->fetchAll();
$ticketIndexExists = false;
foreach ($indexes as $index) if ((string) ($index['name'] ?? $index['Key_name'] ?? '') === 'idx_problem_tickets_ticket') $ticketIndexExists = true;
if (!$ticketIndexExists) $pdo->exec('CREATE INDEX idx_problem_tickets_ticket ON problem_tickets (ticket_id)');
$indexes = $pdo->query($drv === 'sqlite' ? 'PRAGMA index_list(problems)' : 'SHOW INDEX FROM problems')->fetchAll();
$problemIndexExists = false;
foreach ($indexes as $index) if ((string) ($index['name'] ?? $index['Key_name'] ?? '') === 'idx_problems_status_updated') $problemIndexExists = true;
if (!$problemIndexExists) $pdo->exec('CREATE INDEX idx_problems_status_updated ON problems (status, updated_at)');
$pdo->prepare('INSERT INTO schema_migrations (migration) VALUES (?)')->execute(['php_0062_problems_and_resolution_codes']);
$result['applied']++;
if ($verbose) $result['log'][] = ' [OK] php_0062_problems_and_resolution_codes';
} catch (Throwable $e) {
$result['errors'][] = 'php_0062_problems_and_resolution_codes: ' . $e->getMessage();
if ($verbose) $result['log'][] = ' [FAIL] php_0062_problems_and_resolution_codes - ' . $e->getMessage();
}
}
// Migration 0049: gesammelte, noch nicht übersetzte Texte (Sammelmodus der Mehrsprachigkeit).
if (!isset($done['php_0049_i18n_missing'])) {
try {
Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 MiB

After

Width:  |  Height:  |  Size: 1.9 MiB