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 @@ + 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 @@ + 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 @@ + 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 @@ + 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 @@ + 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 @@ + 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); ?>
| Hierarchie / Entity Source | +Name | Typ | -Parent Entity | -Agent Token | Status | +Zuletzt gesehen | +Kurz-Info / Message | Aktionen |
|---|---|---|---|---|---|---|---|---|
|
-
+
= $m['is_last'] ? '└─' : '├─' ?>
+
|
= htmlspecialchars($m['type']) ?> |
- - - | -
-
- = htmlspecialchars(substr($tokenData['raw_token'], 0, 10)) ?>...
-
-
-
- |
= strtoupper($m['state']) ?> | += htmlspecialchars($m['last_seen_utc'] ?? 'Nie') ?> | += htmlspecialchars($m['last_message'] ?? '-') ?> | - - + |
| Monitor Source | -Typ | -Parent | -Agent Token | -Status | -Aktionen | -
|---|---|---|---|---|---|
| = htmlspecialchars($m['source']) ?> | -= htmlspecialchars($m['type']) ?> |
- = htmlspecialchars($m['parent_source'] ?? '-') ?> | -
-
- = htmlspecialchars(substr($tokenData['raw_token'], 0, 10)) ?>...
-
-
-
- |
- = strtoupper($m['state']) ?> | -- - .ps1 - .sh - - - | -