Add full monitor edit modal, direct token creation per monitor row, and cascade deletion/renaming in Watchdog
This commit is contained in:
+150
-32
@@ -142,19 +142,15 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
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]);
|
||||
|
||||
@@ -289,19 +285,21 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
}
|
||||
}
|
||||
|
||||
// Edit Watchdog Monitor
|
||||
// Edit Watchdog Monitor (Full Update)
|
||||
if ($action === 'edit_watchdog_monitor') {
|
||||
$oldSource = trim($_POST['old_source'] ?? '');
|
||||
$newSource = trim($_POST['new_source'] ?? $oldSource);
|
||||
$type = trim($_POST['type'] ?? 'host');
|
||||
$group = trim($_POST['group_key'] ?? 'Default');
|
||||
$parent = trim($_POST['parent_source'] ?? '');
|
||||
$os = trim($_POST['os'] ?? '');
|
||||
$interval = (int)($_POST['interval'] ?? 60);
|
||||
$notes = trim($_POST['notes'] ?? '');
|
||||
|
||||
if ($oldSource && $newSource) {
|
||||
$monRepo = new MonitorRepo($pdo);
|
||||
$monRepo->updateMonitor($oldSource, $newSource, 'default', $group, $parent, $notes, null, $interval);
|
||||
$msg = "Monitor '{$newSource}' wurde aktualisiert.";
|
||||
$monRepo->updateMonitor($oldSource, $newSource, 'default', $type, $group, $parent, $os, $notes, null, $interval);
|
||||
$msg = "Monitor '{$newSource}' wurde erfolgreich aktualisiert.";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -330,11 +328,12 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
if ($action === 'create_agent_token') {
|
||||
$source = trim($_POST['token_source'] ?? '');
|
||||
$name = trim($_POST['token_name'] ?? '');
|
||||
if ($name) {
|
||||
$tokMgr = new TokenManager($pdo);
|
||||
$res = $tokMgr->createToken($source, $name);
|
||||
$msg = "Agent-Token für '{$name}' generiert: <strong style='font-family:monospace;'>{$res['raw_token']}</strong>";
|
||||
if (empty($name)) {
|
||||
$name = "Token for " . ($source ?: 'General');
|
||||
}
|
||||
$tokMgr = new TokenManager($pdo);
|
||||
$res = $tokMgr->createToken($source, $name);
|
||||
$msg = "Agent-Token generiert: <strong style='font-family:monospace;'>{$res['raw_token']}</strong>";
|
||||
}
|
||||
|
||||
// Revoke Agent Token
|
||||
@@ -392,6 +391,17 @@ $auditLogs = $pdo->query('SELECT * FROM license_audit_log ORDER BY created_at DE
|
||||
$monitorRepo = new MonitorRepo($pdo);
|
||||
$monitors = $monitorRepo->getAllMonitors();
|
||||
|
||||
$tokenManager = new TokenManager($pdo);
|
||||
$agentTokens = $tokenManager->getAllTokens();
|
||||
|
||||
// Map Active Tokens per Monitor Source
|
||||
$tokensBySource = [];
|
||||
foreach ($agentTokens as $tok) {
|
||||
if (!$tok['revoked'] && !empty($tok['monitor_source'])) {
|
||||
$tokensBySource[$tok['monitor_source']] = $tok;
|
||||
}
|
||||
}
|
||||
|
||||
// Flatten Tree for Hierarchy Rendering (Up to 3 levels: Hypervisor -> Host -> App)
|
||||
function buildMonitorTree(array $monitors): array {
|
||||
$childrenOf = [];
|
||||
@@ -449,9 +459,6 @@ foreach ($monitors as $m) {
|
||||
$eventLog = new EventLog($pdo);
|
||||
$recentEvents = $eventLog->getRecentEvents(100);
|
||||
|
||||
$tokenManager = new TokenManager($pdo);
|
||||
$agentTokens = $tokenManager->getAllTokens();
|
||||
|
||||
$updateMgr = new UpdateManager($pdo);
|
||||
$releases = $updateMgr->getReleases();
|
||||
|
||||
@@ -579,7 +586,7 @@ $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) */
|
||||
/* Dropdown Option List Styling Fix (Readable dark text/bg across all operating systems & browsers) */
|
||||
select.form-input option {
|
||||
background-color: #0e121c;
|
||||
color: #EDEFF5;
|
||||
@@ -591,10 +598,10 @@ $baseUrl = $protocol . '://' . $host;
|
||||
.alert-success { background: rgba(18,212,138,0.15); border: 1px solid rgba(18,212,138,0.3); color: #a7f3d0; }
|
||||
.alert-danger { background: rgba(246,70,93,0.15); border: 1px solid rgba(246,70,93,0.3); color: #fca5a5; }
|
||||
|
||||
/* Safety Modal */
|
||||
/* Modals */
|
||||
.modal-backdrop { position: fixed; inset: 0; background: rgba(0,0,0,0.75); backdrop-filter: blur(10px); display: none; align-items: center; justify-content: center; z-index: 200; }
|
||||
.modal-backdrop.active { display: flex; }
|
||||
.modal-card { background: #0e121c; border: 1px solid rgba(246,70,93,0.3); border-radius: 18px; padding: 2rem; max-width: 500px; width: 90%; box-shadow: 0 20px 50px rgba(0,0,0,0.8); }
|
||||
.modal-card { background: #0e121c; border: 1px solid var(--border-glass); border-radius: 18px; padding: 2rem; max-width: 550px; width: 90%; box-shadow: 0 20px 50px rgba(0,0,0,0.8); }
|
||||
|
||||
.tab-content, .subtab-content { display: none; }
|
||||
.tab-content.active, .subtab-content.active { display: block; }
|
||||
@@ -865,6 +872,59 @@ $baseUrl = $protocol . '://' . $host;
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Edit Watchdog Monitor Modal -->
|
||||
<div class="modal-backdrop" id="editMonitorModal">
|
||||
<div class="modal-card">
|
||||
<h2 style="color:var(--primary); margin-bottom:0.75rem;">✏️ Monitor Bearbeiten</h2>
|
||||
<form method="POST" action="index.php#sub-watchdog-uptime">
|
||||
<input type="hidden" name="action" value="edit_watchdog_monitor">
|
||||
<input type="hidden" name="old_source" id="monEditOldSource">
|
||||
<div class="form-group" style="margin-bottom:1rem;">
|
||||
<label class="form-label">Monitor Name (source)</label>
|
||||
<input type="text" name="new_source" id="monEditNewSource" class="form-input" required>
|
||||
</div>
|
||||
<div class="form-group" style="margin-bottom:1rem;">
|
||||
<label class="form-label">Typ</label>
|
||||
<select name="type" id="monEditType" class="form-input">
|
||||
<option value="host">Host / Server (Windows / Linux)</option>
|
||||
<option value="heartbeat">Dienst / Worker (Heartbeat)</option>
|
||||
<option value="hypervisor_node">Hypervisor Node (Proxmox)</option>
|
||||
<option value="guest">Guest VM / Container</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group" style="margin-bottom:1rem;">
|
||||
<label class="form-label">Übergeordnete Entität (Parent Source)</label>
|
||||
<select name="parent_source" id="monEditParent" class="form-input">
|
||||
<option value="">-- Keine (Top Level) --</option>
|
||||
<?php foreach ($monitors as $pm): ?>
|
||||
<?php if ($pm['type'] !== 'heartbeat'): ?>
|
||||
<option value="<?= htmlspecialchars($pm['source']) ?>"><?= htmlspecialchars($pm['source']) ?> (<?= htmlspecialchars($pm['type']) ?>)</option>
|
||||
<?php endif; ?>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-grid">
|
||||
<div class="form-group">
|
||||
<label class="form-label">Gruppe</label>
|
||||
<input type="text" name="group_key" id="monEditGroup" class="form-input">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label">Betriebssystem / OS</label>
|
||||
<input type="text" name="os" id="monEditOs" class="form-input">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label">Intervall (Sekunden)</label>
|
||||
<input type="number" name="interval" id="monEditInterval" class="form-input" value="60">
|
||||
</div>
|
||||
</div>
|
||||
<div style="display:flex; justify-content:flex-end; gap:0.75rem; margin-top:1.25rem;">
|
||||
<button type="button" class="btn btn-secondary" onclick="closeEditMonitorModal()">Abbrechen</button>
|
||||
<button type="submit" class="btn">Änderungen Speichern</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ================= MODULE 2: LIZENZEN ================= -->
|
||||
<div id="tab-license" class="tab-content">
|
||||
|
||||
@@ -1159,7 +1219,7 @@ $baseUrl = $protocol . '://' . $host;
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Subtab: Watchdog Hierarchie & Tree View (3 Ebenen Support) -->
|
||||
<!-- Subtab: Watchdog Hierarchie & Tree View (3 Ebenen Support & Direct Editing) -->
|
||||
<div id="sub-watchdog-hierarchy" class="subtab-content">
|
||||
<div class="card">
|
||||
<div class="card-header"><h2 class="card-title">🌳 Proxmox, Host & Worker System-Hierarchie (3 Ebenen)</h2></div>
|
||||
@@ -1169,8 +1229,9 @@ $baseUrl = $protocol . '://' . $host;
|
||||
<th>Hierarchie / Entity Source</th>
|
||||
<th>Typ</th>
|
||||
<th>Parent Entity</th>
|
||||
<th>Agent Token</th>
|
||||
<th>Status</th>
|
||||
<th>Parent Verknüpfen</th>
|
||||
<th>Aktionen</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@@ -1179,6 +1240,8 @@ $baseUrl = $protocol . '://' . $host;
|
||||
$depth = (int)$m['depth'];
|
||||
$indentPx = $depth * 24;
|
||||
$isChild = ($depth > 0);
|
||||
$hasToken = isset($tokensBySource[$m['source']]);
|
||||
$tokenData = $hasToken ? $tokensBySource[$m['source']] : null;
|
||||
?>
|
||||
<tr>
|
||||
<td>
|
||||
@@ -1190,8 +1253,6 @@ $baseUrl = $protocol . '://' . $host;
|
||||
</div>
|
||||
</td>
|
||||
<td><code><?= htmlspecialchars($m['type']) ?></code></td>
|
||||
<td><?= htmlspecialchars($m['parent_source'] ?? 'Keine (Top Level)') ?></td>
|
||||
<td><span class="badge badge-<?= $m['state'] === 'up' ? 'up' : 'down' ?>"><?= strtoupper($m['state']) ?></span></td>
|
||||
<td>
|
||||
<form method="POST" action="index.php#sub-watchdog-hierarchy" style="display:flex; gap:0.35rem;">
|
||||
<input type="hidden" name="action" value="link_entity">
|
||||
@@ -1199,7 +1260,6 @@ $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): ?>
|
||||
<!-- 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['type']) ?>)
|
||||
@@ -1209,6 +1269,27 @@ $baseUrl = $protocol . '://' . $host;
|
||||
</select>
|
||||
</form>
|
||||
</td>
|
||||
<td>
|
||||
<?php if ($hasToken): ?>
|
||||
<code style="font-size:0.75rem; color:var(--success);"><?= htmlspecialchars(substr($tokenData['raw_token'], 0, 10)) ?>...</code>
|
||||
<?php else: ?>
|
||||
<form method="POST" action="index.php#sub-watchdog-hierarchy" style="display:inline">
|
||||
<input type="hidden" name="action" value="create_agent_token">
|
||||
<input type="hidden" name="token_source" value="<?= htmlspecialchars($m['source']) ?>">
|
||||
<input type="hidden" name="token_name" value="Token for <?= htmlspecialchars($m['source']) ?>">
|
||||
<button type="submit" class="btn btn-sm btn-secondary">🔑 Token Erstellen</button>
|
||||
</form>
|
||||
<?php endif; ?>
|
||||
</td>
|
||||
<td><span class="badge badge-<?= $m['state'] === 'up' ? 'up' : 'down' ?>"><?= strtoupper($m['state']) ?></span></td>
|
||||
<td style="display:flex; gap:0.35rem;">
|
||||
<button class="btn btn-sm btn-secondary" onclick="openEditMonitorModal(<?= htmlspecialchars(json_encode($m)) ?>)">✏️ Edit</button>
|
||||
<form method="POST" action="index.php#sub-watchdog-hierarchy" onsubmit="return confirm('Monitor \'<?= htmlspecialchars($m['source']) ?>\' wirklich löschen?');" style="display:inline">
|
||||
<input type="hidden" name="action" value="delete_watchdog_monitor">
|
||||
<input type="hidden" name="source" value="<?= htmlspecialchars($m['source']) ?>">
|
||||
<button type="submit" class="btn btn-sm btn-danger">🗑️</button>
|
||||
</form>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
</tbody>
|
||||
@@ -1216,10 +1297,10 @@ $baseUrl = $protocol . '://' . $host;
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Subtab: Add / Edit Monitor Wizard -->
|
||||
<!-- Subtab: Add Monitor Wizard -->
|
||||
<div id="sub-watchdog-add" class="subtab-content">
|
||||
<div class="card">
|
||||
<div class="card-header"><h2 class="card-title">🖥️ Monitor Anlegen / Bearbeiten</h2></div>
|
||||
<div class="card-header"><h2 class="card-title">🖥️ Monitor Anlegen</h2></div>
|
||||
<form method="POST" action="index.php#sub-watchdog-add">
|
||||
<input type="hidden" name="action" value="add_watchdog_monitor">
|
||||
<div class="form-grid">
|
||||
@@ -1272,29 +1353,43 @@ $baseUrl = $protocol . '://' . $host;
|
||||
<!-- Subtab: Watchdog Uptime & Full CRUD -->
|
||||
<div id="sub-watchdog-uptime" class="subtab-content">
|
||||
<div class="card">
|
||||
<div class="card-header"><h2 class="card-title">⏱️ Uptime Monitor & CRUD Verwaltung</h2></div>
|
||||
<div class="card-header"><h2 class="card-title">⏱️ Uptime Monitor & Vollständige Verwaltung</h2></div>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Monitor Source</th>
|
||||
<th>Typ</th>
|
||||
<th>Parent</th>
|
||||
<th>Gruppe</th>
|
||||
<th>Intervall</th>
|
||||
<th>Agent Token</th>
|
||||
<th>Status</th>
|
||||
<th>Zuletzt Gesehen</th>
|
||||
<th>Aktionen</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php foreach ($monitors as $m): ?>
|
||||
<?php
|
||||
$hasToken = isset($tokensBySource[$m['source']]);
|
||||
$tokenData = $hasToken ? $tokensBySource[$m['source']] : null;
|
||||
?>
|
||||
<tr>
|
||||
<td><strong><?= htmlspecialchars($m['source']) ?></strong></td>
|
||||
<td><code><?= htmlspecialchars($m['type']) ?></code></td>
|
||||
<td><?= htmlspecialchars($m['parent_source'] ?? '-') ?></td>
|
||||
<td><?= htmlspecialchars($m['group_key'] ?? 'Default') ?></td>
|
||||
<td><?= $m['expected_interval_sec'] ?>s</td>
|
||||
<td>
|
||||
<?php if ($hasToken): ?>
|
||||
<code style="font-size:0.75rem; color:var(--success);"><?= htmlspecialchars(substr($tokenData['raw_token'], 0, 10)) ?>...</code>
|
||||
<?php else: ?>
|
||||
<form method="POST" action="index.php#sub-watchdog-uptime" style="display:inline">
|
||||
<input type="hidden" name="action" value="create_agent_token">
|
||||
<input type="hidden" name="token_source" value="<?= htmlspecialchars($m['source']) ?>">
|
||||
<input type="hidden" name="token_name" value="Token for <?= htmlspecialchars($m['source']) ?>">
|
||||
<button type="submit" class="btn btn-sm btn-secondary">🔑 Token Erstellen</button>
|
||||
</form>
|
||||
<?php endif; ?>
|
||||
</td>
|
||||
<td><span class="badge badge-<?= $m['state'] === 'up' ? 'up' : 'down' ?>"><?= strtoupper($m['state']) ?></span></td>
|
||||
<td><?= htmlspecialchars($m['last_seen_utc'] ?? 'Nie') ?></td>
|
||||
<td style="display:flex; gap:0.35rem;">
|
||||
<button class="btn btn-sm btn-secondary" onclick="openEditMonitorModal(<?= htmlspecialchars(json_encode($m)) ?>)">✏️ Bearbeiten</button>
|
||||
<a href="index.php?action=download_agent&source=<?= urlencode($m['source']) ?>&os=windows" class="btn btn-sm btn-secondary">.ps1</a>
|
||||
<a href="index.php?action=download_agent&source=<?= urlencode($m['source']) ?>&os=linux" class="btn btn-sm btn-secondary">.sh</a>
|
||||
|
||||
@@ -1353,7 +1448,12 @@ $baseUrl = $protocol . '://' . $host;
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label">Gebunden an Source (optional)</label>
|
||||
<input type="text" name="token_source" class="form-input" placeholder="srv-db-01">
|
||||
<select name="token_source" class="form-input">
|
||||
<option value="">-- Alle Sources (Universal) --</option>
|
||||
<?php foreach ($monitors as $m): ?>
|
||||
<option value="<?= htmlspecialchars($m['source']) ?>"><?= htmlspecialchars($m['source']) ?></option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<button type="submit" class="btn">Token Generieren</button>
|
||||
@@ -1367,6 +1467,7 @@ $baseUrl = $protocol . '://' . $host;
|
||||
<tr>
|
||||
<th>Token ID</th>
|
||||
<th>Bezeichnung</th>
|
||||
<th>Gebundene Source</th>
|
||||
<th>Token Value</th>
|
||||
<th>Status</th>
|
||||
<th>Aktion</th>
|
||||
@@ -1381,6 +1482,7 @@ $baseUrl = $protocol . '://' . $host;
|
||||
<tr>
|
||||
<td><code><?= htmlspecialchars($tok['token_id']) ?></code></td>
|
||||
<td><strong><?= htmlspecialchars($tok['name']) ?></strong></td>
|
||||
<td><?= htmlspecialchars($tok['monitor_source'] ?? 'Alle Sources') ?></td>
|
||||
<td>
|
||||
<code id="tok-text-<?= $tok['token_id'] ?>" data-full="<?= htmlspecialchars($rawVal) ?>" data-masked="<?= htmlspecialchars($masked) ?>">
|
||||
<?= htmlspecialchars($masked) ?>
|
||||
@@ -1699,6 +1801,22 @@ $baseUrl = $protocol . '://' . $host;
|
||||
document.getElementById('deleteProjectModal').classList.remove('active');
|
||||
}
|
||||
|
||||
// Monitor Editing Functions
|
||||
function openEditMonitorModal(m) {
|
||||
document.getElementById('monEditOldSource').value = m.source;
|
||||
document.getElementById('monEditNewSource').value = m.source;
|
||||
document.getElementById('monEditType').value = m.type || 'host';
|
||||
document.getElementById('monEditParent').value = m.parent_source || '';
|
||||
document.getElementById('monEditGroup').value = m.group_key || 'Default';
|
||||
document.getElementById('monEditOs').value = m.os || '';
|
||||
document.getElementById('monEditInterval').value = m.expected_interval_sec || 60;
|
||||
document.getElementById('editMonitorModal').classList.add('active');
|
||||
}
|
||||
|
||||
function closeEditMonitorModal() {
|
||||
document.getElementById('editMonitorModal').classList.remove('active');
|
||||
}
|
||||
|
||||
// License Editing Functions
|
||||
function openEditLicense(l) {
|
||||
switchMainTab('license', document.querySelector('.nav-link[onclick*="license"]'));
|
||||
|
||||
Reference in New Issue
Block a user