Fix project deletion 500 error, select dropdown option styling, 3-level watchdog hierarchy traversal, and filter out heartbeat workers as parent candidates

This commit is contained in:
Deploymentcenter Bot
2026-08-05 22:42:32 +02:00
parent 731965bde5
commit 9808d143f7
+70 -32
View File
@@ -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,24 +128,49 @@ 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) {
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)) {
$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]);
$msg = "Projekt '{$proj['name']}' ({$proj['slug']}) wurde unwiderruflich gelöscht.";
$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 {
$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;
</div>
</div>
<!-- Subtab: Watchdog Hierarchie & Tree View -->
<!-- Subtab: Watchdog Hierarchie & Tree View (3 Ebenen Support) -->
<div id="sub-watchdog-hierarchy" class="subtab-content">
<div class="card">
<div class="card-header"><h2 class="card-title">🌳 Proxmox & Host System-Hierarchie</h2></div>
<div class="card-header"><h2 class="card-title">🌳 Proxmox, Host & Worker System-Hierarchie (3 Ebenen)</h2></div>
<table>
<thead>
<tr>
@@ -1135,7 +1170,7 @@ $baseUrl = $protocol . '://' . $host;
<th>Typ</th>
<th>Parent Entity</th>
<th>Status</th>
<th>Verknüpfen (Parent)</th>
<th>Parent Verknüpfen</th>
</tr>
</thead>
<tbody>
@@ -1164,9 +1199,10 @@ $baseUrl = $protocol . '://' . $host;
<select name="parent_source" class="form-input" style="padding:0.25rem 0.5rem; font-size:0.75rem;" onchange="this.form.submit()">
<option value="">-- Keine (Top Level) --</option>
<?php foreach ($monitors as $pCandidate): ?>
<?php if ($pCandidate['source'] !== $m['source']): ?>
<!-- Dienst/Worker (heartbeat) kann NIEMALS Parent sein! -->
<?php if ($pCandidate['source'] !== $m['source'] && $pCandidate['type'] !== 'heartbeat'): ?>
<option value="<?= htmlspecialchars($pCandidate['source']) ?>" <?= $m['parent_source'] === $pCandidate['source'] ? 'selected' : '' ?>>
<?= htmlspecialchars($pCandidate['source']) ?>
<?= htmlspecialchars($pCandidate['source']) ?> (<?= htmlspecialchars($pCandidate['type']) ?>)
</option>
<?php endif; ?>
<?php endforeach; ?>
@@ -1205,7 +1241,9 @@ $baseUrl = $protocol . '://' . $host;
<select name="parent_source" class="form-input">
<option value="">-- Keine (Top Level) --</option>
<?php foreach ($monitors as $pm): ?>
<option value="<?= htmlspecialchars($pm['source']) ?>"><?= htmlspecialchars($pm['source']) ?></option>
<?php if ($pm['type'] !== 'heartbeat'): ?>
<option value="<?= htmlspecialchars($pm['source']) ?>"><?= htmlspecialchars($pm['source']) ?> (<?= htmlspecialchars($pm['type']) ?>)</option>
<?php endif; ?>
<?php endforeach; ?>
</select>
</div>
@@ -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' },