diff --git a/public/index.php b/public/index.php index 8e0f198..97e2ad3 100644 --- a/public/index.php +++ b/public/index.php @@ -111,12 +111,10 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') { if ($name) { try { if ($id > 0) { - // Update: Slug is immutable! $stmt = $pdo->prepare('UPDATE dc_projects SET name = :n, default_cache_ttl_hours = :t, notes = :notes WHERE id = :id'); $stmt->execute([':n' => $name, ':t' => $ttl, ':notes' => $notes, ':id' => $id]); $msg = "Projekt '{$name}' wurde aktualisiert."; } else { - // New Project if ($slug) { $stmt = $pdo->prepare('INSERT INTO dc_projects (slug, name, default_cache_ttl_hours, notes) VALUES (:s, :n, :t, :notes)'); $stmt->execute([':s' => $slug, ':n' => $name, ':t' => $ttl, ':notes' => $notes]); @@ -130,22 +128,47 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') { } } - // Delete Project (with Safety Confirmation) + // Delete Project (Clean transaction cascade to avoid 500 errors) if ($action === 'delete_project') { $id = (int)($_POST['project_id'] ?? 0); $confirmSlug = trim($_POST['confirm_slug'] ?? ''); if ($id > 0) { - $stmt = $pdo->prepare('SELECT slug, name FROM dc_projects WHERE id = :id'); - $stmt->execute([':id' => $id]); - $proj = $stmt->fetch(); + try { + $stmt = $pdo->prepare('SELECT slug, name FROM dc_projects WHERE id = :id'); + $stmt->execute([':id' => $id]); + $proj = $stmt->fetch(); - if ($proj && hash_equals($proj['slug'], $confirmSlug)) { - $delStmt = $pdo->prepare('DELETE FROM dc_projects WHERE id = :id'); - $delStmt->execute([':id' => $id]); - $msg = "Projekt '{$proj['name']}' ({$proj['slug']}) wurde unwiderruflich gelöscht."; - } else { - $msg = "Sicherheitsbestätigung fehlgeschlagen! Der eingegebene Slug stimmte nicht überein."; + if ($proj && hash_equals($proj['slug'], $confirmSlug)) { + $pdo->beginTransaction(); + + // 1. Delete associated activations + $pdo->prepare('DELETE a FROM license_activations a JOIN license_licenses l ON a.license_id = l.id WHERE l.product_id = :id') + ->execute([':id' => $id]); + + // 2. Delete associated licenses + $pdo->prepare('DELETE FROM license_licenses WHERE product_id = :id') + ->execute([':id' => $id]); + + // 3. Delete associated releases + $pdo->prepare('DELETE FROM updateservice_releases WHERE product_slug = :slug') + ->execute([':slug' => $proj['slug']]); + + // 4. Delete project + $delStmt = $pdo->prepare('DELETE FROM dc_projects WHERE id = :id'); + $delStmt->execute([':id' => $id]); + + $pdo->commit(); + $msg = "Projekt '{$proj['name']}' ({$proj['slug']}) und zugehörige Daten wurden gelöscht."; + } else { + $msg = "Sicherheitsbestätigung fehlgeschlagen! Der eingegebene Slug stimmte nicht überein."; + $msgType = 'danger'; + } + } catch (Throwable $e) { + if ($pdo->inTransaction()) { + $pdo->rollBack(); + } + $msg = "Fehler beim Löschen des Projekts: " . $e->getMessage(); $msgType = 'danger'; } } @@ -369,41 +392,47 @@ $auditLogs = $pdo->query('SELECT * FROM license_audit_log ORDER BY created_at DE $monitorRepo = new MonitorRepo($pdo); $monitors = $monitorRepo->getAllMonitors(); -// Flatten Tree for Hierarchy Rendering +// Flatten Tree for Hierarchy Rendering (Up to 3 levels: Hypervisor -> Host -> App) function buildMonitorTree(array $monitors): array { - $bySource = []; + $childrenOf = []; + $bySource = []; + foreach ($monitors as $m) { $bySource[$m['source']] = $m; - $bySource[$m['source']]['children'] = []; + $parent = !empty($m['parent_source']) ? $m['parent_source'] : '__ROOT__'; + $childrenOf[$parent][] = $m['source']; } - $tree = []; - foreach ($bySource as $source => &$m) { - $parent = $m['parent_source'] ?? null; - if (!empty($parent) && isset($bySource[$parent])) { - $bySource[$parent]['children'][] = &$m; - } else { - $tree[] = &$m; - } - } - unset($m); - $flat = []; - $walk = function(array $nodes, int $depth = 0) use (&$walk, &$flat) { + $walk = function(string $parentSource, int $depth = 0) use (&$walk, &$flat, $childrenOf, $bySource) { + if ($depth > 3) return; + if (empty($childrenOf[$parentSource])) return; + + $nodes = $childrenOf[$parentSource]; $count = count($nodes); for ($i = 0; $i < $count; $i++) { - $node = $nodes[$i]; + $src = $nodes[$i]; + if (!isset($bySource[$src])) continue; + $node = $bySource[$src]; $node['depth'] = $depth; $node['is_last'] = ($i === $count - 1); - $children = $node['children'] ?? []; - unset($node['children']); $flat[] = $node; - if (!empty($children)) { - $walk($children, $depth + 1); - } + + $walk($src, $depth + 1); } }; - $walk($tree, 0); + + $walk('__ROOT__', 0); + + // Append any unvisited monitors as root level + $visited = array_column($flat, 'source'); + foreach ($monitors as $m) { + if (!in_array($m['source'], $visited, true)) { + $m['depth'] = 0; + $m['is_last'] = true; + $flat[] = $m; + } + } return $flat; } @@ -550,6 +579,12 @@ $baseUrl = $protocol . '://' . $host; .form-input { background: rgba(255,255,255,0.04); border: 1px solid var(--border-glass); border-radius: 10px; padding: 0.65rem 0.85rem; color: #fff; font-size: 0.875rem; outline: none; font-family: 'Manrope', system-ui; transition: border-color 0.2s; } .form-input:focus { border-color: var(--primary); } + /* Dropdown Option List Styling Fix (Readable text across all browsers) */ + select.form-input option { + background-color: #0e121c; + color: #EDEFF5; + } + .prompt-box { background: rgba(0,0,0,0.5); border: 1px solid var(--border-glass); border-radius: 10px; padding: 1.1rem; font-family: 'Roboto Mono', monospace; font-size: 0.825rem; color: #cbd5e1; white-space: pre-wrap; word-break: break-all; margin-bottom: 0.75rem; position: relative; max-height: 380px; overflow-y: auto; } .alert { padding: 1rem 1.25rem; border-radius: 12px; margin-bottom: 1.5rem; font-size: 0.875rem; font-weight: 600; } @@ -1124,10 +1159,10 @@ $baseUrl = $protocol . '://' . $host; - +
-

🌳 Proxmox & Host System-Hierarchie

+

🌳 Proxmox, Host & Worker System-Hierarchie (3 Ebenen)

@@ -1135,7 +1170,7 @@ $baseUrl = $protocol . '://' . $host; - + @@ -1164,9 +1199,10 @@ $baseUrl = $protocol . '://' . $host; - + + + @@ -1519,7 +1557,7 @@ $baseUrl = $protocol . '://' . $host; ], 'watchdog': [ { id: 'sub-watchdog-dashboard', label: '📊 Dashboard', active: true }, - { id: 'sub-watchdog-hierarchy', label: '🌳 System-Hierarchie' }, + { id: 'sub-watchdog-hierarchy', label: '🌳 System-Hierarchie (3 Ebenen)' }, { id: 'sub-watchdog-add', label: '➕ Monitor Hinzufügen' }, { id: 'sub-watchdog-uptime', label: '⏱️ Uptime & CRUD' }, { id: 'sub-watchdog-eventlog', label: '📜 Event-Log' },
Typ Parent Entity StatusVerknüpfen (Parent)Parent Verknüpfen