diff --git a/public/assets/app.svg b/public/assets/app.svg new file mode 100644 index 0000000..63a7966 --- /dev/null +++ b/public/assets/app.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/public/assets/docker.svg b/public/assets/docker.svg new file mode 100644 index 0000000..fa74b76 --- /dev/null +++ b/public/assets/docker.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/public/assets/icons/app.svg b/public/assets/icons/app.svg new file mode 100644 index 0000000..63a7966 --- /dev/null +++ b/public/assets/icons/app.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/public/assets/icons/docker.svg b/public/assets/icons/docker.svg new file mode 100644 index 0000000..fa74b76 --- /dev/null +++ b/public/assets/icons/docker.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/public/assets/icons/linux.svg b/public/assets/icons/linux.svg new file mode 100644 index 0000000..d0a2444 --- /dev/null +++ b/public/assets/icons/linux.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/public/assets/icons/mysql.svg b/public/assets/icons/mysql.svg new file mode 100644 index 0000000..bf998a4 --- /dev/null +++ b/public/assets/icons/mysql.svg @@ -0,0 +1,6 @@ + + + + + MySQL + diff --git a/public/assets/icons/nginx.svg b/public/assets/icons/nginx.svg new file mode 100644 index 0000000..9b0d133 --- /dev/null +++ b/public/assets/icons/nginx.svg @@ -0,0 +1,4 @@ + + + + diff --git a/public/assets/icons/node.svg b/public/assets/icons/node.svg new file mode 100644 index 0000000..9d7dba7 --- /dev/null +++ b/public/assets/icons/node.svg @@ -0,0 +1,4 @@ + + + node + diff --git a/public/assets/icons/php.svg b/public/assets/icons/php.svg new file mode 100644 index 0000000..38eba68 --- /dev/null +++ b/public/assets/icons/php.svg @@ -0,0 +1,4 @@ + + + php + diff --git a/public/assets/icons/proxmox.svg b/public/assets/icons/proxmox.svg new file mode 100644 index 0000000..3253b16 --- /dev/null +++ b/public/assets/icons/proxmox.svg @@ -0,0 +1,4 @@ + + + + diff --git a/public/assets/icons/python.svg b/public/assets/icons/python.svg new file mode 100644 index 0000000..44fae47 --- /dev/null +++ b/public/assets/icons/python.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/public/assets/icons/redis.svg b/public/assets/icons/redis.svg new file mode 100644 index 0000000..d49bac6 --- /dev/null +++ b/public/assets/icons/redis.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/public/assets/icons/server.svg b/public/assets/icons/server.svg new file mode 100644 index 0000000..fcda703 --- /dev/null +++ b/public/assets/icons/server.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/public/assets/icons/windows.svg b/public/assets/icons/windows.svg new file mode 100644 index 0000000..d5144ee --- /dev/null +++ b/public/assets/icons/windows.svg @@ -0,0 +1,3 @@ + + + diff --git a/public/assets/linux.svg b/public/assets/linux.svg new file mode 100644 index 0000000..d0a2444 --- /dev/null +++ b/public/assets/linux.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/public/assets/mysql.svg b/public/assets/mysql.svg new file mode 100644 index 0000000..bf998a4 --- /dev/null +++ b/public/assets/mysql.svg @@ -0,0 +1,6 @@ + + + + + MySQL + diff --git a/public/assets/nginx.svg b/public/assets/nginx.svg new file mode 100644 index 0000000..9b0d133 --- /dev/null +++ b/public/assets/nginx.svg @@ -0,0 +1,4 @@ + + + + diff --git a/public/assets/node.svg b/public/assets/node.svg new file mode 100644 index 0000000..9d7dba7 --- /dev/null +++ b/public/assets/node.svg @@ -0,0 +1,4 @@ + + + node + diff --git a/public/assets/php.svg b/public/assets/php.svg new file mode 100644 index 0000000..38eba68 --- /dev/null +++ b/public/assets/php.svg @@ -0,0 +1,4 @@ + + + php + diff --git a/public/assets/proxmox.svg b/public/assets/proxmox.svg new file mode 100644 index 0000000..3253b16 --- /dev/null +++ b/public/assets/proxmox.svg @@ -0,0 +1,4 @@ + + + + diff --git a/public/assets/python.svg b/public/assets/python.svg new file mode 100644 index 0000000..44fae47 --- /dev/null +++ b/public/assets/python.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/public/assets/redis.svg b/public/assets/redis.svg new file mode 100644 index 0000000..d49bac6 --- /dev/null +++ b/public/assets/redis.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/public/assets/server.svg b/public/assets/server.svg new file mode 100644 index 0000000..fcda703 --- /dev/null +++ b/public/assets/server.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/public/assets/windows.svg b/public/assets/windows.svg new file mode 100644 index 0000000..d5144ee --- /dev/null +++ b/public/assets/windows.svg @@ -0,0 +1,3 @@ + + + diff --git a/public/index.php b/public/index.php index 3f4f743..127e8cf 100644 --- a/public/index.php +++ b/public/index.php @@ -285,20 +285,37 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') { } } - // Edit Watchdog Monitor (Full Update) + // Edit Watchdog Monitor (Rich Edit with Icon Upload & Mute Switch matching screenshot) if ($action === 'edit_watchdog_monitor') { - $oldSource = trim($_POST['old_source'] ?? ''); - $newSource = trim($_POST['new_source'] ?? $oldSource); + $oldSource = trim($_POST['old_source'] ?? $_POST['source'] ?? ''); + $newSource = trim($_POST['new_source'] ?? $_POST['source'] ?? ''); $type = trim($_POST['type'] ?? 'host'); - $group = trim($_POST['group_key'] ?? 'Default'); + $group = trim($_POST['group_key'] ?? $_POST['group'] ?? 'Default'); $parent = trim($_POST['parent_source'] ?? ''); $os = trim($_POST['os'] ?? ''); - $interval = (int)($_POST['interval'] ?? 60); $notes = trim($_POST['notes'] ?? ''); + $url = trim($_POST['url'] ?? ''); + $icon = trim($_POST['icon'] ?? ''); + $interval = (int)($_POST['expected_interval_sec'] ?? $_POST['interval'] ?? 60); + $isMuted = isset($_POST['is_muted']) ? true : false; + + // Custom icon file upload + if (!empty($_FILES['custom_icon_file']['name'])) { + $file = $_FILES['custom_icon_file']; + $ext = strtolower(pathinfo($file['name'], PATHINFO_EXTENSION)); + if (in_array($ext, ['svg', 'png', 'jpg', 'jpeg', 'webp'], true)) { + $customDir = __DIR__ . '/assets/icons/custom'; + if (!is_dir($customDir)) mkdir($customDir, 0777, true); + $filename = 'icon_' . time() . '_' . preg_replace('/[^a-z0-9]/', '', strtolower(pathinfo($file['name'], PATHINFO_FILENAME))) . '.' . $ext; + if (move_uploaded_file($file['tmp_name'], $customDir . '/' . $filename)) { + $icon = 'custom/' . $filename; + } + } + } if ($oldSource && $newSource) { $monRepo = new MonitorRepo($pdo); - $monRepo->updateMonitor($oldSource, $newSource, 'default', $type, $group, $parent, $os, $notes, null, $interval); + $monRepo->updateMonitor($oldSource, $newSource, 'default', $type, $group, $parent, $os, $notes, $url, $interval, $icon, $isMuted); $msg = "Monitor '{$newSource}' wurde erfolgreich aktualisiert."; } } @@ -449,11 +466,43 @@ function buildMonitorTree(array $monitors): array { $hierarchicalMonitors = buildMonitorTree($monitors); +// Calculate Monitor Groups Stats (for Dashboard Group Panel) +$groupStats = []; $monitorsUp = 0; $monitorsWarning = 0; $monitorsDown = 0; foreach ($monitors as $m) { if ($m['state'] === 'up') $monitorsUp++; elseif ($m['state'] === 'warning') $monitorsWarning++; else $monitorsDown++; + + $gKey = !empty($m['group_key']) ? $m['group_key'] : 'Default'; + if (!isset($groupStats[$gKey])) { + $groupStats[$gKey] = ['total' => 0, 'ok' => 0]; + } + $groupStats[$gKey]['total']++; + if ($m['state'] === 'up') { + $groupStats[$gKey]['ok']++; + } +} + +// Icon Resolver Helper +function getMonitorIconUrl(?string $icon, ?string $source, ?string $os, ?string $type): string { + if (!empty($icon) && $icon !== 'auto') { + if (str_starts_with($icon, 'custom/')) return 'assets/icons/' . $icon; + if (str_starts_with($icon, 'assets/')) return $icon; + return 'assets/icons/' . $icon; + } + $s = strtolower(($source ?? '') . ' ' . ($os ?? '') . ' ' . ($type ?? '')); + if (str_contains($s, 'proxmox') || str_contains($s, 'pve')) return 'assets/icons/proxmox.svg'; + if (str_contains($s, 'win')) return 'assets/icons/windows.svg'; + if (str_contains($s, 'linux') || str_contains($s, 'ubuntu') || str_contains($s, 'debian')) return 'assets/icons/linux.svg'; + if (str_contains($s, 'mysql') || str_contains($s, 'mariadb') || str_contains($s, 'db')) return 'assets/icons/mysql.svg'; + if (str_contains($s, 'docker')) return 'assets/icons/docker.svg'; + if (str_contains($s, 'nginx')) return 'assets/icons/nginx.svg'; + if (str_contains($s, 'redis')) return 'assets/icons/redis.svg'; + if (str_contains($s, 'python')) return 'assets/icons/python.svg'; + if (str_contains($s, 'php')) return 'assets/icons/php.svg'; + if (str_contains($s, 'node')) return 'assets/icons/node.svg'; + return 'assets/icons/server.svg'; } $eventLog = new EventLog($pdo); @@ -554,6 +603,17 @@ $baseUrl = $protocol . '://' . $host; .stat-header { display: flex; align-items: center; justify-content: space-between; color: var(--text-muted); font-size: 0.75rem; font-weight: 600; } .stat-value { font-size: 2rem; font-weight: 800; margin-top: 0.5rem; color: #fff; font-family: 'Roboto Mono', monospace; letter-spacing: -0.02em; } + /* Group Overview Cards Panel (Screenshot 2) */ + .group-cards-grid { display: flex; flex-wrap: wrap; gap: 1rem; margin-top: 0.75rem; } + .group-card { background: rgba(255,255,255,0.03); border: 1px solid var(--border-glass); border-radius: 12px; padding: 0.85rem 1.1rem; display: flex; align-items: center; justify-content: space-between; min-width: 180px; gap: 1rem; } + .group-card-name { font-size: 0.875rem; font-weight: 700; color: #fff; } + + /* Icon Grid Selector in Edit Modal (Screenshot 1) */ + .icon-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 0.5rem; max-height: 180px; overflow-y: auto; padding-right: 4px; } + .icon-option-btn { background: rgba(255,255,255,0.03); border: 1px solid var(--border-glass); border-radius: 10px; padding: 0.55rem 0.75rem; color: var(--text-main); font-size: 0.8rem; font-weight: 600; cursor: pointer; display: flex; align-items: center; gap: 0.5rem; transition: all 0.2s; } + .icon-option-btn:hover { background: rgba(255,255,255,0.08); } + .icon-option-btn.selected { border-color: #FF7A45; background: rgba(242,98,46,0.15); color: #fff; box-shadow: 0 0 10px rgba(242,98,46,0.3); } + /* Cards & Tables */ .card { background: var(--bg-card); border: 1px solid var(--border-glass); border-radius: 18px; padding: 1.5rem; margin-bottom: 1.75rem; backdrop-filter: blur(24px); box-shadow: 0 8px 30px rgba(0,0,0,0.25); } .card-header { display: flex; align-items: center; justify-content: space-between; margin-bottom: 1.25rem; } @@ -586,7 +646,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 dark text/bg across all operating systems & browsers) */ + /* Dropdown Option List Styling Fix */ select.form-input option { background-color: #0e121c; color: #EDEFF5; @@ -601,7 +661,7 @@ $baseUrl = $protocol . '://' . $host; /* 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 var(--border-glass); border-radius: 18px; padding: 2rem; max-width: 550px; width: 90%; box-shadow: 0 20px 50px rgba(0,0,0,0.8); } + .modal-card { background: #14171d; border: 1px solid rgba(255,255,255,0.12); border-radius: 18px; padding: 1.75rem; max-width: 600px; width: 95%; max-height: 90vh; overflow-y: auto; 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; } @@ -754,13 +814,15 @@ $baseUrl = $protocol . '://' . $host; $depth = (int)$m['depth']; $indentPx = $depth * 24; $isChild = ($depth > 0); + $iconUrl = getMonitorIconUrl($m['icon']??null, $m['source']??null, $m['os']??null, $m['type']??null); ?> -
+
+ icon ()
@@ -872,30 +934,57 @@ $baseUrl = $protocol . '://' . $host;
- + + + +
+

📁 Gruppen-Übersicht

+
+ $gData): ?> + +
+ + / OK +
+ +
+
- +
-

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

+

🌳 Watchdog System-Hierarchie

- + - - + + @@ -1240,55 +1366,24 @@ $baseUrl = $protocol . '://' . $host; $depth = (int)$m['depth']; $indentPx = $depth * 24; $isChild = ($depth > 0); - $hasToken = isset($tokensBySource[$m['source']]); - $tokenData = $hasToken ? $tokensBySource[$m['source']] : null; + $iconUrl = getMonitorIconUrl($m['icon']??null, $m['source']??null, $m['os']??null, $m['type']??null); ?> - - + + @@ -1350,62 +1445,6 @@ $baseUrl = $protocol . '://' . $host; - -
-
-

⏱️ Uptime Monitor & Vollständige Verwaltung

-
Hierarchie / Entity SourceName TypParent EntityAgent Token StatusZuletzt gesehenKurz-Info / Message Aktionen
-
+
+ icon
-
- - - -
-
- - ... - -
- - - - -
- -
- -
- - - -
+
- - - - - - - - - - - - - - - - - - - - - - - -
Monitor SourceTypParentAgent TokenStatusAktionen
- - ... - -
- - - - -
- -
- - .ps1 - .sh - -
- - - -
-
-
-
-
@@ -1659,9 +1698,8 @@ $baseUrl = $protocol . '://' . $host; ], 'watchdog': [ { id: 'sub-watchdog-dashboard', label: '📊 Dashboard', active: true }, - { id: 'sub-watchdog-hierarchy', label: '🌳 System-Hierarchie (3 Ebenen)' }, + { id: 'sub-watchdog-hierarchy', label: '🌳 System-Hierarchie' }, { id: 'sub-watchdog-add', label: '➕ Monitor Hinzufügen' }, - { id: 'sub-watchdog-uptime', label: '⏱️ Uptime & CRUD' }, { id: 'sub-watchdog-eventlog', label: '📜 Event-Log' }, { id: 'sub-watchdog-tokens', label: '🔑 Agent-Tokens' } ], @@ -1801,15 +1839,35 @@ $baseUrl = $protocol . '://' . $host; document.getElementById('deleteProjectModal').classList.remove('active'); } - // Monitor Editing Functions + // Monitor Icon Selector (Matching Screenshot 1) + function selectIcon(btnEl, iconVal) { + document.querySelectorAll('.icon-option-btn').forEach(b => b.classList.remove('selected')); + btnEl.classList.add('selected'); + document.getElementById('monEditIconSelected').value = iconVal; + } + + // Rich Monitor Editing Modal Functions (Screenshot 1) 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('monEditGroup').value = m.group_key || 'Applications'; document.getElementById('monEditInterval').value = m.expected_interval_sec || 60; + document.getElementById('monEditNotes').value = m.notes || ''; + document.getElementById('monEditUrl').value = m.url || ''; + document.getElementById('monEditMuted').checked = parseInt(m.is_muted) === 1; + + // Highlight selected icon preset or auto + const iconVal = m.icon || ''; + document.getElementById('monEditIconSelected').value = iconVal; + document.querySelectorAll('.icon-option-btn').forEach(b => { + b.classList.remove('selected'); + if (b.getAttribute('data-icon') === iconVal) { + b.classList.add('selected'); + } + }); + document.getElementById('editMonitorModal').classList.add('active'); } @@ -1817,6 +1875,14 @@ $baseUrl = $protocol . '://' . $host; document.getElementById('editMonitorModal').classList.remove('active'); } + function triggerDeleteMonitorFromModal() { + const source = document.getElementById('monEditOldSource').value; + if (confirm(`Möchtest du den Monitor '${source}' wirklich unwiderruflich löschen?`)) { + document.getElementById('hiddenDeleteSource').value = source; + document.getElementById('hiddenDeleteMonitorForm').submit(); + } + } + // License Editing Functions function openEditLicense(l) { switchMainTab('license', document.querySelector('.nav-link[onclick*="license"]'));