Zuletzt aktiv 1 month ago

Erreur32's Avatar Erreur32 hat die Gist bearbeitet 7 months ago. Zu Änderung gehen

1 file changed, 1347 insertions

beszel_dashboard_simple.php(Datei erstellt)

@@ -0,0 +1,1347 @@
1 + /**
2 + * Dashboard Beszel Simple - Version sans graphiques avec rafraîchissement automatique
3 + * Fonctionnalités :
4 + * - Données en temps réel sans graphiques
5 + * - Rafraîchissement automatique configurable
6 + * - Options persistantes
7 + * - Interface simple et rapide
8 + */
9 +
10 + // === CONFIGURATION ===
11 + $baseUrl = 'URL';
12 + $username = 'mail';
13 + $password = 'rpass';
14 +
15 + // === CONFIGURATION DES COLLECTIONS ===
16 + $collections = [
17 + 'metrics' => 'system_stats',
18 + 'alerts' => 'alerts',
19 + 'systems' => 'systems'
20 + ];
21 +
22 + // === RÉCUPÉRATION DES PRÉFÉRENCES ===
23 + $refreshInterval = $_GET['refresh'] ?? $_COOKIE['beszel_refresh'] ?? '30';
24 + $autoRefresh = $_GET['auto'] ?? $_COOKIE['beszel_auto'] ?? 'true';
25 + $theme = $_GET['theme'] ?? $_COOKIE['beszel_theme'] ?? 'light';
26 + $viewMode = $_GET['view'] ?? $_COOKIE['beszel_view'] ?? 'cards';
27 +
28 + // Forcer le rafraîchissement automatique si pas défini
29 + if (!isset($_COOKIE['beszel_auto'])) {
30 + $autoRefresh = 'true';
31 + }
32 +
33 + // Sauvegarder les préférences
34 + if (isset($_GET['refresh'])) setcookie('beszel_refresh', $refreshInterval, time() + (365 * 24 * 60 * 60));
35 + if (isset($_GET['auto'])) setcookie('beszel_auto', $autoRefresh, time() + (365 * 24 * 60 * 60));
36 + if (isset($_GET['theme'])) setcookie('beszel_theme', $theme, time() + (365 * 24 * 60 * 60));
37 + if (isset($_GET['view'])) setcookie('beszel_view', $viewMode, time() + (365 * 24 * 60 * 60));
38 +
39 + // === FONCTIONS UTILITAIRES ===
40 + function getToken() {
41 + global $baseUrl, $username, $password;
42 + $ch = curl_init($baseUrl . '/api/collections/users/auth-with-password');
43 + curl_setopt_array($ch, [
44 + CURLOPT_RETURNTRANSFER => true,
45 + CURLOPT_POST => true,
46 + CURLOPT_HTTPHEADER => ['Content-Type: application/json'],
47 + CURLOPT_POSTFIELDS => json_encode([
48 + 'identity' => $username,
49 + 'password' => $password
50 + ])
51 + ]);
52 + $resp = curl_exec($ch);
53 + $code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
54 + curl_close($ch);
55 + if ($code !== 200) die("Erreur auth ($code): $resp");
56 + $data = json_decode($resp, true);
57 + return $data['token'] ?? null;
58 + }
59 +
60 + function apiGet($endpoint, $token) {
61 + global $baseUrl;
62 + $ch = curl_init($baseUrl . $endpoint);
63 + curl_setopt_array($ch, [
64 + CURLOPT_RETURNTRANSFER => true,
65 + CURLOPT_HTTPHEADER => [
66 + "Authorization: Bearer $token",
67 + "Content-Type: application/json"
68 + ]
69 + ]);
70 + $resp = curl_exec($ch);
71 + $code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
72 + curl_close($ch);
73 + if ($code !== 200) {
74 + error_log("Erreur API ($endpoint) : $code\n$resp");
75 + return ['items' => []];
76 + }
77 + return json_decode($resp, true);
78 + }
79 +
80 + function formatBytes($bytes, $precision = 2) {
81 + $units = array('B', 'KB', 'MB', 'GB', 'TB');
82 + for ($i = 0; $bytes > 1024 && $i < count($units) - 1; $i++) {
83 + $bytes /= 1024;
84 + }
85 + return round($bytes, $precision) . ' ' . $units[$i];
86 + }
87 +
88 + function getStatusColor($status) {
89 + switch ($status) {
90 + case 'up': return '#10b981';
91 + case 'down': return '#ef4444';
92 + case 'paused': return '#f59e0b';
93 + default: return '#6b7280';
94 + }
95 + }
96 +
97 + function getStatusIcon($status) {
98 + switch ($status) {
99 + case 'up': return '🟢';
100 + case 'down': return '🔴';
101 + case 'paused': return '⏸️';
102 + default: return '⚪';
103 + }
104 + }
105 +
106 + function getCpuColorClass($cpu) {
107 + if ($cpu < 30) return 'cpu-low';
108 + if ($cpu < 70) return 'cpu-medium';
109 + return 'cpu-high';
110 + }
111 +
112 + function getLoadColorClass($load) {
113 + if ($load < 1) return 'load-low';
114 + if ($load < 3) return 'load-medium';
115 + return 'load-high';
116 + }
117 +
118 + function getTempColorClass($temp) {
119 + if ($temp < 40) return 'temp-low';
120 + if ($temp < 70) return 'temp-medium';
121 + return 'temp-high';
122 + }
123 +
124 + function getDataFreshnessClass($lastUpdate) {
125 + $now = time();
126 + $dataTime = strtotime($lastUpdate);
127 + $age = $now - $dataTime;
128 +
129 + if ($age < 60) return 'data-fresh'; // < 1 minute
130 + if ($age < 300) return 'data-recent'; // < 5 minutes
131 + return 'data-old'; // > 5 minutes
132 + }
133 +
134 + function getProgressColorClass($value, $type) {
135 + switch ($type) {
136 + case 'cpu':
137 + if ($value < 30) return 'cpu-low';
138 + if ($value < 70) return 'cpu-medium';
139 + return 'cpu-high';
140 + case 'memory':
141 + if ($value < 30) return 'memory-low';
142 + if ($value < 70) return 'memory-medium';
143 + return 'memory-high';
144 + case 'load':
145 + if ($value < 1) return 'load-low';
146 + if ($value < 3) return 'load-medium';
147 + return 'load-high';
148 + case 'temp':
149 + if ($value < 40) return 'temp-low';
150 + if ($value < 70) return 'temp-medium';
151 + return 'temp-high';
152 + case 'disk':
153 + if ($value < 30) return 'disk-low';
154 + if ($value < 70) return 'disk-medium';
155 + return 'disk-high';
156 + case 'gpu':
157 + if ($value < 30) return 'gpu-low';
158 + if ($value < 70) return 'gpu-medium';
159 + return 'gpu-high';
160 + default:
161 + return 'cpu-low';
162 + }
163 + }
164 +
165 + function getMetricColorClass($value, $type) {
166 + switch ($type) {
167 + case 'cpu':
168 + if ($value < 30) return 'cpu-low';
169 + if ($value < 70) return 'cpu-medium';
170 + return 'cpu-high';
171 + case 'memory':
172 + if ($value < 30) return 'memory-low';
173 + if ($value < 70) return 'memory-medium';
174 + return 'memory-high';
175 + case 'load':
176 + if ($value < 1) return 'load-low';
177 + if ($value < 3) return 'load-medium';
178 + return 'load-high';
179 + case 'temp':
180 + if ($value < 40) return 'temp-low';
181 + if ($value < 70) return 'temp-medium';
182 + return 'temp-high';
183 + case 'disk':
184 + if ($value < 30) return 'disk-low';
185 + if ($value < 70) return 'disk-medium';
186 + return 'disk-high';
187 + case 'gpu':
188 + if ($value < 30) return 'gpu-low';
189 + if ($value < 70) return 'gpu-medium';
190 + return 'gpu-high';
191 + default:
192 + return 'cpu-low';
193 + }
194 + }
195 +
196 + // === RÉCUPÉRATION DES DONNÉES ===
197 + $token = getToken();
198 + if (!$token) {
199 + die("❌ Impossible d'obtenir le token d'authentification.");
200 + }
201 +
202 + $data = apiGet('/api/collections/systems/records?perPage=50&expand=latest_metrics', $token);
203 + $systems = $data['items'] ?? [];
204 +
205 + // Récupération des alertes
206 + $alertsData = apiGet('/api/collections/alerts/records?sort=-created&perPage=20', $token);
207 + $alerts = $alertsData['items'] ?? [];
208 +
209 + // === ENVOI MQTT VERS HOME ASSISTANT ===
210 + $mqttHost = "192.168.1.200"; // IP de ton Home Assistant
211 + $mqttUser = "mqtt32mqtt32"; // identifiants MQTT
212 + $mqttPass = "mqtt32";
213 +
214 + // Boucle sur chaque système
215 + foreach ($systems as $sys) {
216 + $name = preg_replace('/[^a-zA-Z0-9_-]/', '_', strtolower($sys['name'] ?? 'unknown'));
217 + $info = $sys['info'] ?? [];
218 + $payload = [
219 + 'cpu' => $info['cpu'] ?? 0,
220 + 'ram' => $info['mp'] ?? 0,
221 + 'load' => $info['la'][0] ?? 0,
222 + 'temp' => $info['t'] ?? 0,
223 + 'disk' => $info['dp'] ?? 0,
224 + 'gpu' => $info['g'] ?? 0,
225 + 'status' => $sys['status'] ?? 'unknown',
226 + 'time' => date('c')
227 + ];
228 +
229 + $json = json_encode($payload);
230 + // Envoi sur un topic dédié au système
231 + exec("mosquitto_pub -h $mqttHost -u $mqttUser -P $mqttPass -t beszel/$name -m '$json'");
232 + }
233 +
234 +
235 + ?>
236 + <!DOCTYPE html>
237 + <html lang="fr">
238 + <head>
239 + <meta charset="utf-8">
240 + <meta name="viewport" content="width=device-width, initial-scale=1.0">
241 + <title>Dashboard32 Beszel Simple</title>
242 + <style>
243 + :root {
244 + --bg: #f8fafc;
245 + --card-bg: #ffffff;
246 + --text: #1e293b;
247 + --sub: #64748b;
248 + --border: #e2e8f0;
249 + --primary: #3b82f6;
250 + --success: #10b981;
251 + --warning: #f59e0b;
252 + --danger: #ef4444;
253 + --info: #06b6d4;
254 + --shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px 0 rgba(0, 0, 0, 0.06);
255 + --shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05);
256 + }
257 +
258 + body.dark {
259 + --bg: #0f172a;
260 + --card-bg: #1e293b;
261 + --text: #f1f5f9;
262 + --sub: #94a3b8;
263 + --border: #334155;
264 + --primary: #60a5fa;
265 + --success: #34d399;
266 + --warning: #fbbf24;
267 + --danger: #f87171;
268 + --info: #22d3ee;
269 + }
270 +
271 + * {
272 + margin: 0;
273 + padding: 0;
274 + box-sizing: border-box;
275 + }
276 +
277 + body {
278 + background: var(--bg);
279 + color: var(--text);
280 + font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
281 + line-height: 1.6;
282 + transition: all 0.3s ease;
283 + }
284 +
285 + .header {
286 + background: var(--card-bg);
287 + border-bottom: 1px solid var(--border);
288 + padding: 1rem 2rem;
289 + display: flex;
290 + justify-content: space-between;
291 + align-items: center;
292 + box-shadow: var(--shadow);
293 + position: sticky;
294 + top: 0;
295 + z-index: 100;
296 + }
297 +
298 + .header h1 {
299 + font-size: 1.5rem;
300 + font-weight: 700;
301 + color: var(--text);
302 + }
303 +
304 + .controls {
305 + display: flex;
306 + gap: 1rem;
307 + align-items: center;
308 + }
309 +
310 + .btn {
311 + background: var(--primary);
312 + color: white;
313 + border: none;
314 + border-radius: 8px;
315 + padding: 0.5rem 1rem;
316 + font-size: 0.875rem;
317 + font-weight: 600;
318 + cursor: pointer;
319 + transition: all 0.2s;
320 + text-decoration: none;
321 + display: inline-flex;
322 + align-items: center;
323 + gap: 0.5rem;
324 + }
325 +
326 + .btn:hover {
327 + background: #2563eb;
328 + transform: translateY(-1px);
329 + }
330 +
331 + .btn-secondary {
332 + background: var(--border);
333 + color: var(--text);
334 + }
335 +
336 + .btn-secondary:hover {
337 + background: var(--sub);
338 + }
339 +
340 + .btn-success {
341 + background: var(--success);
342 + }
343 +
344 + .btn-warning {
345 + background: var(--warning);
346 + }
347 +
348 + .btn-danger {
349 + background: var(--danger);
350 + }
351 +
352 + .settings-panel {
353 + position: fixed;
354 + top: 0;
355 + right: -400px;
356 + width: 400px;
357 + height: 100vh;
358 + background: var(--card-bg);
359 + border-left: 1px solid var(--border);
360 + padding: 2rem;
361 + transition: right 0.3s ease;
362 + z-index: 1000;
363 + overflow-y: auto;
364 + }
365 +
366 + .settings-panel.open {
367 + right: 0;
368 + }
369 +
370 + .settings-overlay {
371 + position: fixed;
372 + top: 0;
373 + left: 0;
374 + width: 100%;
375 + height: 100%;
376 + background: rgba(0, 0, 0, 0.5);
377 + z-index: 999;
378 + opacity: 0;
379 + visibility: hidden;
380 + transition: all 0.3s ease;
381 + }
382 +
383 + .settings-overlay.open {
384 + opacity: 1;
385 + visibility: visible;
386 + }
387 +
388 + .container {
389 + max-width: 100%;
390 + margin: 0;
391 + padding: 1rem;
392 + }
393 +
394 + .stats-grid {
395 + display: grid;
396 + grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
397 + gap: 0.75rem;
398 + margin-bottom: 1.5rem;
399 + }
400 +
401 + .stat-card {
402 + background: var(--card-bg);
403 + border-radius: 8px;
404 + padding: 1rem;
405 + box-shadow: var(--shadow);
406 + border: 1px solid var(--border);
407 + text-align: center;
408 + }
409 +
410 + .stat-value {
411 + font-size: 2rem;
412 + font-weight: 700;
413 + color: var(--text);
414 + margin-bottom: 0.5rem;
415 + }
416 +
417 + .stat-label {
418 + font-size: 0.875rem;
419 + color: var(--sub);
420 + text-transform: uppercase;
421 + letter-spacing: 0.05em;
422 + }
423 +
424 + .systems-grid {
425 + display: grid;
426 + grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
427 + gap: 1rem;
428 + width: 100%;
429 + align-items: stretch;
430 + }
431 +
432 + .system-card {
433 + background: var(--card-bg);
434 + border-radius: 8px;
435 + box-shadow: var(--shadow);
436 + border: 1px solid var(--border);
437 + overflow: hidden;
438 + transition: all 0.3s ease;
439 + display: flex;
440 + flex-direction: column;
441 + height: 100%;
442 + }
443 +
444 + .system-card:hover {
445 + transform: translateY(-2px);
446 + box-shadow: var(--shadow-lg);
447 + }
448 +
449 + .card-header {
450 + padding: 1rem;
451 + border-bottom: 1px solid var(--border);
452 + display: flex;
453 + justify-content: space-between;
454 + align-items: center;
455 + min-height: 60px;
456 + flex-shrink: 0;
457 + }
458 +
459 + .card-title {
460 + font-size: 1.25rem;
461 + font-weight: 600;
462 + color: var(--text);
463 + }
464 +
465 + .status {
466 + display: inline-flex;
467 + align-items: center;
468 + gap: 0.5rem;
469 + padding: 0.25rem 0.75rem;
470 + border-radius: 20px;
471 + font-size: 0.75rem;
472 + font-weight: 600;
473 + text-transform: uppercase;
474 + }
475 +
476 + .status.up {
477 + background: rgba(16, 185, 129, 0.1);
478 + color: var(--success);
479 + }
480 +
481 + .status.down {
482 + background: rgba(239, 68, 68, 0.1);
483 + color: var(--danger);
484 + }
485 +
486 + .status.paused {
487 + background: rgba(245, 158, 11, 0.1);
488 + color: var(--warning);
489 + }
490 +
491 + .status.unknown {
492 + background: rgba(107, 114, 128, 0.1);
493 + color: var(--sub);
494 + }
495 +
496 + .card-content {
497 + padding: 1rem;
498 + }
499 +
500 + .metrics-grid {
501 + display: grid;
502 + grid-template-columns: repeat(auto-fit, minmax(100px, 1fr));
503 + gap: 0.5rem;
504 + margin-bottom: 1rem;
505 + }
506 +
507 + .metric {
508 + text-align: center;
509 + padding: 0.75rem;
510 + background: var(--bg);
511 + border-radius: 6px;
512 + border: 1px solid var(--border);
513 + transition: all 0.2s;
514 + }
515 +
516 + /* Couleurs pour les mini-cadres des métriques */
517 + .metric.cpu-low { background: rgba(16, 185, 129, 0.1); border-color: #10b981; }
518 + .metric.cpu-medium { background: rgba(245, 158, 11, 0.1); border-color: #f59e0b; }
519 + .metric.cpu-high { background: rgba(239, 68, 68, 0.1); border-color: #ef4444; }
520 +
521 + .metric.memory-low { background: rgba(16, 185, 129, 0.1); border-color: #10b981; }
522 + .metric.memory-medium { background: rgba(245, 158, 11, 0.1); border-color: #f59e0b; }
523 + .metric.memory-high { background: rgba(239, 68, 68, 0.1); border-color: #ef4444; }
524 +
525 + .metric.load-low { background: rgba(16, 185, 129, 0.1); border-color: #10b981; }
526 + .metric.load-medium { background: rgba(245, 158, 11, 0.1); border-color: #f59e0b; }
527 + .metric.load-high { background: rgba(239, 68, 68, 0.1); border-color: #ef4444; }
528 +
529 + .metric.temp-low { background: rgba(6, 182, 212, 0.1); border-color: #06b6d4; }
530 + .metric.temp-medium { background: rgba(245, 158, 11, 0.1); border-color: #f59e0b; }
531 + .metric.temp-high { background: rgba(239, 68, 68, 0.1); border-color: #ef4444; }
532 +
533 + .metric.disk-low { background: rgba(16, 185, 129, 0.1); border-color: #10b981; }
534 + .metric.disk-medium { background: rgba(245, 158, 11, 0.1); border-color: #f59e0b; }
535 + .metric.disk-high { background: rgba(239, 68, 68, 0.1); border-color: #ef4444; }
536 +
537 + .metric.gpu-low { background: rgba(16, 185, 129, 0.1); border-color: #10b981; }
538 + .metric.gpu-medium { background: rgba(245, 158, 11, 0.1); border-color: #f59e0b; }
539 + .metric.gpu-high { background: rgba(239, 68, 68, 0.1); border-color: #ef4444; }
540 +
541 + .metric:hover {
542 + background: var(--border);
543 + }
544 +
545 + .metric-value {
546 + font-size: 1rem;
547 + font-weight: 700;
548 + color: var(--text);
549 + margin-bottom: 0.25rem;
550 + }
551 +
552 + /* Couleurs selon les valeurs */
553 + .metric.cpu-low { background: rgba(16, 185, 129, 0.1); border-color: #10b981; }
554 + .metric.cpu-medium { background: rgba(245, 158, 11, 0.1); border-color: #f59e0b; }
555 + .metric.cpu-high { background: rgba(239, 68, 68, 0.1); border-color: #ef4444; }
556 +
557 + .metric.load-low { background: rgba(16, 185, 129, 0.1); border-color: #10b981; }
558 + .metric.load-medium { background: rgba(245, 158, 11, 0.1); border-color: #f59e0b; }
559 + .metric.load-high { background: rgba(239, 68, 68, 0.1); border-color: #ef4444; }
560 +
561 + .metric.temp-low { background: rgba(6, 182, 212, 0.1); border-color: #06b6d4; }
562 + .metric.temp-medium { background: rgba(245, 158, 11, 0.1); border-color: #f59e0b; }
563 + .metric.temp-high { background: rgba(239, 68, 68, 0.1); border-color: #ef4444; }
564 +
565 + /* Couleurs pour la fraîcheur des données */
566 + .data-fresh { color: #10b981; }
567 + .data-recent { color: #f59e0b; }
568 + .data-old { color: #ef4444; }
569 +
570 + /* Badges pour IP et Port */
571 + .badge {
572 + display: inline-flex;
573 + align-items: center;
574 + gap: 0.2rem;
575 + padding: 0.15rem 0.3rem;
576 + border-radius: 5px;
577 + font-size: 0.6rem;
578 + font-weight: 600;
579 + text-transform: uppercase;
580 + letter-spacing: 0.05em;
581 + margin: 0.05rem;
582 + transition: all 0.2s;
583 + white-space: nowrap;
584 + }
585 +
586 + .badge:hover {
587 + transform: translateY(-1px);
588 + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
589 + }
590 +
591 + .badge-ip {
592 + background: rgba(59, 130, 246, 0.1);
593 + color: #3b82f6;
594 + border: 1px solid #3b82f6;
595 + }
596 +
597 + .badge-port {
598 + background: rgba(16, 185, 129, 0.1);
599 + color: #10b981;
600 + border: 1px solid #10b981;
601 + }
602 +
603 + .badge-id {
604 + background: rgba(107, 114, 128, 0.1);
605 + color: #6b7280;
606 + border: 1px solid #6b7280;
607 + }
608 +
609 + .badge-time {
610 + background: rgba(6, 182, 212, 0.1);
611 + color: #06b6d4;
612 + border: 1px solid #06b6d4;
613 + }
614 +
615 + /* Styles pour l'affichage en liste */
616 + .list-view {
617 + display: none;
618 + }
619 +
620 + .list-view.active {
621 + display: block;
622 + }
623 +
624 + .cards-view {
625 + /* display: block;*/
626 + }
627 +
628 + .cards-view.hidden {
629 + display: none;
630 + }
631 +
632 + .list-item {
633 + background: var(--card-bg);
634 + border-radius: 8px;
635 + box-shadow: var(--shadow);
636 + border: 1px solid var(--border);
637 + margin-bottom: 0.5rem;
638 + padding: 0.75rem;
639 + /*display: flex;*/
640 + align-items: center;
641 + justify-content: space-between;
642 + transition: all 0.3s ease;
643 + min-height: 80px;
644 + }
645 +
646 + .list-item:hover {
647 + transform: translateY(-1px);
648 + box-shadow: var(--shadow-lg);
649 + }
650 +
651 + .list-item-left {
652 + display: flex;
653 + align-items: center;
654 + gap: 1rem;
655 + flex: 0 0 250px;
656 + min-width: 250px;
657 + }
658 +
659 + .list-item-right {
660 + display: flex;
661 + align-items: center;
662 + gap: 0.5rem;
663 + /* flex: 1;*/
664 +
665 + justify-content: space-between;
666 + }
667 + /*
668 + .list-system-name {
669 + font-size: 1rem;
670 + font-weight: 600;
671 + color: var(--text);
672 + white-space: nowrap;
673 + overflow: hidden;
674 + text-overflow: ellipsis;
675 + flex: 1;
676 + min-width: 200px;
677 + }*/
678 + .list-system-name {
679 + font-size: 1rem;
680 + font-weight: 600;
681 + color: #3bb2e1;
682 + white-space: nowrap;
683 + overflow: hidden;
684 + text-overflow: ellipsis;
685 + flex: 1;
686 + min-width: 200px;
687 + }
688 +
689 + .list-metrics {
690 + display: grid;
691 + grid-template-columns: repeat(6, 1fr);
692 + gap: 0.25rem;
693 + align-items: center;
694 + flex: 1;
695 + margin-right: 0.5rem;
696 + min-width: 500px;
697 + }
698 +
699 + .list-metric {
700 + text-align: center;
701 + padding: 0.15rem;
702 + display: flex;
703 + flex-direction: column;
704 + align-items: center;
705 + min-width: 85px;
706 + max-width: 85px;
707 + }
708 +
709 + .list-metric-value {
710 + font-size: 0.8rem;
711 + font-weight: 700;
712 + color: var(--text);
713 + margin-bottom: 0.1rem;
714 + min-height: 1em;
715 + }
716 +
717 + .list-metric-label {
718 + font-size: 0.65rem;
719 + color: var(--sub);
720 + text-transform: uppercase;
721 + letter-spacing: 0.05em;
722 + margin-bottom: 0.08rem;
723 + min-height: 0.8em;
724 + }
725 +
726 + /* Barres de progression pour la vue liste */
727 + .progress-bar {
728 + width: 100%;
729 + height: 4px;
730 + background: var(--border);
731 + border-radius: 2px;
732 + overflow: hidden;
733 + margin-top: 0.05rem;
734 + min-width: 50px;
735 + }
736 +
737 + .progress-fill {
738 + height: 100%;
739 + border-radius: 2px;
740 + transition: width 0.3s ease;
741 + }
742 +
743 + .progress-fill.cpu-low { background: #10b981; }
744 + .progress-fill.cpu-medium { background: #f59e0b; }
745 + .progress-fill.cpu-high { background: #ef4444; }
746 +
747 + .progress-fill.memory-low { background: #10b981; }
748 + .progress-fill.memory-medium { background: #f59e0b; }
749 + .progress-fill.memory-high { background: #ef4444; }
750 +
751 + .progress-fill.load-low { background: #10b981; }
752 + .progress-fill.load-medium { background: #f59e0b; }
753 + .progress-fill.load-high { background: #ef4444; }
754 +
755 + .progress-fill.temp-low { background: #06b6d4; }
756 + .progress-fill.temp-medium { background: #f59e0b; }
757 + .progress-fill.temp-high { background: #ef4444; }
758 +
759 + .progress-fill.disk-low { background: #10b981; }
760 + .progress-fill.disk-medium { background: #f59e0b; }
761 + .progress-fill.disk-high { background: #ef4444; }
762 +
763 + /* Badges dans la vue liste */
764 + .list-badges {
765 + display: flex;
766 + flex-wrap: nowrap;
767 + gap: 0.2rem;
768 + align-items: center;
769 + flex-shrink: 0;
770 + min-width: 320px;
771 + }
772 +
773 + .metric-label {
774 + font-size: 0.75rem;
775 + color: var(--sub);
776 + text-transform: uppercase;
777 + letter-spacing: 0.05em;
778 + }
779 +
780 + .system-info {
781 + margin-top: auto;
782 + padding-top: 0.75rem;
783 + border-top: 1px solid var(--border);
784 + font-size: 0.75rem;
785 + color: var(--sub);
786 + flex-shrink: 0;
787 + }
788 +
789 + .refresh-indicator {
790 + position: fixed;
791 + bottom: 2rem;
792 + right: 2rem;
793 + background: var(--card-bg);
794 + border: 1px solid var(--border);
795 + border-radius: 8px;
796 + padding: 0.75rem 1rem;
797 + box-shadow: var(--shadow-lg);
798 + font-size: 0.875rem;
799 + color: var(--sub);
800 + }
801 +
802 + @keyframes spin {
803 + from { transform: rotate(0deg); }
804 + to { transform: rotate(360deg); }
805 + }
806 +
807 + .form-group {
808 + margin-bottom: 1rem;
809 + }
810 +
811 + .form-label {
812 + display: block;
813 + margin-bottom: 0.5rem;
814 + font-weight: 600;
815 + color: var(--text);
816 + }
817 +
818 + .form-select, .form-input {
819 + width: 100%;
820 + padding: 0.75rem;
821 + border: 1px solid var(--border);
822 + border-radius: 8px;
823 + background: var(--card-bg);
824 + color: var(--text);
825 + font-size: 0.875rem;
826 + }
827 +
828 + .form-select:focus, .form-input:focus {
829 + outline: none;
830 + border-color: var(--primary);
831 + box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);
832 + }
833 +
834 + .checkbox-group {
835 + display: flex;
836 + align-items: center;
837 + gap: 0.5rem;
838 + }
839 +
840 + .checkbox-group input[type="checkbox"] {
841 + width: 16px;
842 + height: 16px;
843 + }
844 +
845 + .checkbox-group label {
846 + font-size: 0.875rem;
847 + color: var(--text);
848 + cursor: pointer;
849 + }
850 +
851 + .alerts-section {
852 + margin-top: 2rem;
853 + }
854 +
855 + .alert-item {
856 + display: flex;
857 + align-items: center;
858 + gap: 0.75rem;
859 + padding: 0.75rem;
860 + background: var(--bg);
861 + border-radius: 8px;
862 + margin-bottom: 0.5rem;
863 + font-size: 0.875rem;
864 + border-left: 4px solid var(--warning);
865 + }
866 +
867 + .alert-critical {
868 + border-left-color: var(--danger);
869 + }
870 +
871 + .alert-warning {
872 + border-left-color: var(--warning);
873 + }
874 +
875 + .alert-info {
876 + border-left-color: var(--info);
877 + }
878 +
879 + @media (max-width: 768px) {
880 + .header {
881 + padding: 1rem;
882 + flex-direction: column;
883 + gap: 1rem;
884 + }
885 +
886 + .container {
887 + padding: 0.5rem;
888 + }
889 +
890 + .systems-grid {
891 + grid-template-columns: 1fr;
892 + gap: 0.5rem;
893 + }
894 +
895 + .metrics-grid {
896 + grid-template-columns: repeat(auto-fit, minmax(70px, 1fr));
897 + }
898 +
899 + .badge {
900 + font-size: 0.65rem;
901 + padding: 0.2rem 0.4rem;
902 + }
903 +
904 + .list-item {
905 + flex-direction: column;
906 + align-items: flex-start;
907 + gap: 1rem;
908 + }
909 +
910 + .list-item-left {
911 + width: 100%;
912 + justify-content: space-between;
913 + }
914 +
915 + .list-item-right {
916 + width: 100%;
917 + flex-direction: column;
918 + gap: 0.5rem;
919 + }
920 +
921 + .list-metrics {
922 + grid-template-columns: repeat(3, 1fr);
923 + gap: 0.15rem;
924 + min-width: 280px;
925 + }
926 +
927 + .list-badges {
928 + min-width: 200px;
929 + flex-wrap: wrap;
930 + }
931 +
932 + .list-item {
933 + flex-direction: column;
934 + align-items: flex-start;
935 + gap: 0.5rem;
936 + min-height: auto;
937 + }
938 +
939 + .list-item-left {
940 + flex: 1;
941 + min-width: auto;
942 + width: 100%;
943 + }
944 +
945 + .list-item-right {
946 + width: 100%;
947 + flex-direction: column;
948 + gap: 0.5rem;
949 + }
950 +
951 + .list-system-name {
952 + min-width: auto;
953 + }
954 +
955 + .settings-panel {
956 + width: 100%;
957 + right: -100%;
958 + }
959 + }
960 + </style>
961 + </head>
962 + <body class="<?= $theme === 'dark' ? 'dark' : '' ?>">
963 + <div class="header">
964 + <h1>🚀 Dashboard32 Beszel Simple</h1>
965 + <div class="controls">
966 + <div style="font-size: 0.75rem; color: var(--sub); margin-right: 1rem;">
967 + 🕒 Données: <?= date('H:i:s') ?> (temps réel)
968 + </div>
969 + <button class="btn btn-secondary" onclick="toggleView()"><?= $viewMode === 'cards' ? '📋 Liste' : '🃏 Cartes' ?></button>
970 + <button class="btn btn-secondary" onclick="toggleSettings()">⚙️ Paramètres</button>
971 + <button class="btn btn-secondary" onclick="toggleLegend()">🎨 Légende</button>
972 + <button class="btn btn-secondary" onclick="toggleTheme()">🌗 Thème</button>
973 + <button class="btn btn-secondary" onclick="location.reload()">🔄 Actualiser</button>
974 + </div>
975 + </div>
976 +
977 + <div class="settings-overlay" id="settingsOverlay" onclick="closeSettings()"></div>
978 + <div class="settings-panel" id="settingsPanel">
979 + <h2>Paramètres du Dashboard</h2>
980 +
981 + <form method="GET" action="">
982 + <div class="form-group">
983 + <label class="form-label">Rafraîchissement automatique (secondes)</label>
984 + <select class="form-select" name="refresh">
985 + <option value="10" <?= $refreshInterval === '10' ? 'selected' : '' ?>>10 secondes</option>
986 + <option value="30" <?= $refreshInterval === '30' ? 'selected' : '' ?>>30 secondes</option>
987 + <option value="60" <?= $refreshInterval === '60' ? 'selected' : '' ?>>1 minute</option>
988 + <option value="120" <?= $refreshInterval === '120' ? 'selected' : '' ?>>2 minutes</option>
989 + <option value="300" <?= $refreshInterval === '300' ? 'selected' : '' ?>>5 minutes</option>
990 + <option value="0" <?= $refreshInterval === '0' ? 'selected' : '' ?>>Désactivé</option>
991 + </select>
992 + </div>
993 +
994 + <div class="form-group">
995 + <label class="form-label">Thème</label>
996 + <select class="form-select" name="theme">
997 + <option value="light" <?= $theme === 'light' ? 'selected' : '' ?>>Clair</option>
998 + <option value="dark" <?= $theme === 'dark' ? 'selected' : '' ?>>Sombre</option>
999 + </select>
1000 + </div>
1001 +
1002 + <div class="form-group">
1003 + <div class="checkbox-group">
1004 + <input type="checkbox" id="autoRefresh" name="auto" value="true" <?= $autoRefresh === 'true' ? 'checked' : '' ?>>
1005 + <label for="autoRefresh">Rafraîchissement automatique</label>
1006 + </div>
1007 + </div>
1008 +
1009 + <button type="submit" class="btn">Appliquer</button>
1010 + <button type="button" class="btn btn-secondary" onclick="closeSettings()">Fermer</button>
1011 + </form>
1012 + </div>
1013 +
1014 + <!-- Fenêtre modale pour la légende -->
1015 + <div class="settings-overlay" id="legendOverlay" onclick="closeLegend()"></div>
1016 + <div class="settings-panel" id="legendPanel">
1017 + <h2>🎨 Légende des couleurs</h2>
1018 +
1019 + <div style="margin-bottom: 1.5rem;">
1020 + <h3 style="margin-bottom: 1rem; color: var(--text);">Seuils de couleur :</h3>
1021 + <div style="display: flex; gap: 1rem; margin-bottom: 1rem;">
1022 + <span style="color: #10b981; font-weight: 600;">🟢 Faible</span>
1023 + <span style="color: #f59e0b; font-weight: 600;">🟡 Moyen</span>
1024 + <span style="color: #ef4444; font-weight: 600;">🔴 Élevé</span>
1025 + </div>
1026 + </div>
1027 +
1028 + <div style="margin-bottom: 1.5rem;">
1029 + <h4 style="margin-bottom: 0.5rem; color: var(--text);">CPU (Processeur) :</h4>
1030 + <div style="font-size: 0.875rem; color: var(--sub);">
1031 + <span style="color: #10b981;">&lt; 30%</span> |
1032 + <span style="color: #f59e0b;">30-70%</span> |
1033 + <span style="color: #ef4444;">&gt; 70%</span>
1034 + </div>
1035 + </div>
1036 +
1037 + <div style="margin-bottom: 1.5rem;">
1038 + <h4 style="margin-bottom: 0.5rem; color: var(--text);">Load (Charge système) :</h4>
1039 + <div style="font-size: 0.875rem; color: var(--sub);">
1040 + <span style="color: #10b981;">&lt; 1</span> |
1041 + <span style="color: #f59e0b;">1-3</span> |
1042 + <span style="color: #ef4444;">&gt; 3</span>
1043 + </div>
1044 + </div>
1045 +
1046 + <div style="margin-bottom: 1.5rem;">
1047 + <h4 style="margin-bottom: 0.5rem; color: var(--text);">Température :</h4>
1048 + <div style="font-size: 0.875rem; color: var(--sub);">
1049 + <span style="color: #06b6d4;">&lt; 40°C</span> |
1050 + <span style="color: #f59e0b;">40-70°C</span> |
1051 + <span style="color: #ef4444;">&gt; 70°C</span>
1052 + </div>
1053 + </div>
1054 +
1055 + <button type="button" class="btn btn-secondary" onclick="closeLegend()">Fermer</button>
1056 + </div>
1057 +
1058 + <div class="container">
1059 +
1060 + <!-- Statistiques globales -->
1061 + <div class="stats-grid">
1062 + <div class="stat-card">
1063 + <div class="stat-value"><?= count($systems) ?></div>
1064 + <div class="stat-label">Systèmes</div>
1065 + </div>
1066 + <div class="stat-card">
1067 + <div class="stat-value"><?= count(array_filter($systems, fn($s) => $s['status'] === 'up')) ?></div>
1068 + <div class="stat-label">En ligne</div>
1069 + </div>
1070 + <div class="stat-card">
1071 + <div class="stat-value"><?= count(array_filter($systems, fn($s) => $s['status'] === 'down')) ?></div>
1072 + <div class="stat-label">Hors ligne</div>
1073 + </div>
1074 + <div class="stat-card">
1075 + <div class="stat-value"><?= count(array_filter($systems, fn($s) => $s['status'] === 'paused')) ?></div>
1076 + <div class="stat-label">En pause</div>
1077 + </div>
1078 + <div class="stat-card">
1079 + <div class="stat-value"><?= count($alerts) ?></div>
1080 + <div class="stat-label">Alertes</div>
1081 + </div>
1082 + </div>
1083 +
1084 + <!-- Systèmes - Vue Cartes -->
1085 + <div class="systems-grid cards-view <?= $viewMode === 'cards' ? '' : 'hidden' ?>">
1086 + <?php foreach ($systems as $sys):
1087 + $name = htmlspecialchars($sys['name'] ?? 'Système inconnu');
1088 + $status = htmlspecialchars($sys['status'] ?? 'unknown');
1089 + // Utiliser l'heure actuelle comme dernière mise à jour des données
1090 + $lastUpdate = date('Y-m-d H:i:s');
1091 + $systemId = $sys['id'] ?? '';
1092 +
1093 + // Récupération des métriques actuelles
1094 + $info = $sys['info'] ?? [];
1095 + $cpu = $info['cpu'] ?? 0;
1096 + $memory = $info['mp'] ?? 0;
1097 + $load = $info['la'][0] ?? 0;
1098 + // Utiliser la température si disponible, sinon utiliser le CPU comme approximation
1099 + $temperature = $info['t'] ?? ($cpu > 0 ? $cpu : 0);
1100 + $disk = $info['dp'] ?? 0;
1101 + $networkIn = $info['bb'] ?? 0;
1102 + $gpu = $info['g'] ?? 0;
1103 + ?>
1104 + <div class="system-card">
1105 + <div class="card-header">
1106 + <div class="card-title"><?= $name ?></div>
1107 + <div class="status <?= $status ?>">
1108 + <span><?= getStatusIcon($status) ?></span>
1109 + <?= strtoupper($status) ?>
1110 + </div>
1111 + </div>
1112 +
1113 + <div class="card-content">
1114 + <div class="metrics-grid">
1115 + <div class="metric <?= getMetricColorClass($cpu, 'cpu') ?>">
1116 + <div class="metric-value"><?= round($cpu, 1) ?>%</div>
1117 + <div class="metric-label">CPU</div>
1118 + </div>
1119 + <div class="metric <?= getMetricColorClass($memory, 'memory') ?>">
1120 + <div class="metric-value"><?= round($memory, 1) ?>%</div>
1121 + <div class="metric-label">RAM</div>
1122 + </div>
1123 + <div class="metric <?= getMetricColorClass($load, 'load') ?>">
1124 + <div class="metric-value"><?= round($load, 2) ?></div>
1125 + <div class="metric-label">Load</div>
1126 + </div>
1127 + <div class="metric <?= getMetricColorClass($temperature, 'temp') ?>">
1128 + <div class="metric-value"><?= round($temperature, 1) ?>°C</div>
1129 + <div class="metric-label">Temp</div>
1130 + </div>
1131 + <div class="metric <?= getMetricColorClass($disk, 'disk') ?>">
1132 + <div class="metric-value"><?= round($disk, 1) ?>%</div>
1133 + <div class="metric-label">Disk</div>
1134 + </div>
1135 + <?php if ($gpu > 0): ?>
1136 + <div class="metric <?= getMetricColorClass($gpu, 'gpu') ?>">
1137 + <div class="metric-value"><?= round($gpu, 1) ?>%</div>
1138 + <div class="metric-label">GPU</div>
1139 + </div>
1140 + <?php endif; ?>
1141 + </div>
1142 +
1143 + <div class="system-info">
1144 + <div style="display: flex; flex-wrap: wrap; gap: 0.25rem; margin-bottom: 0.5rem;">
1145 + <span class="badge badge-time">🕒 <?= $lastUpdate ?></span>
1146 + <span class="badge badge-id">🆔 <?= substr($systemId, 0, 8) ?>...</span>
1147 + <span class="badge badge-ip">🌐 <?= htmlspecialchars($sys['host'] ?? 'N/A') ?></span>
1148 + <span class="badge badge-port">🔌 <?= htmlspecialchars($sys['port'] ?? 'N/A') ?></span>
1149 + </div>
1150 + </div>
1151 + </div>
1152 + </div>
1153 + <?php endforeach; ?>
1154 + </div>
1155 +
1156 + <!-- Systèmes - Vue Liste -->
1157 + <div class="list-view <?= $viewMode === 'list' ? 'active' : '' ?>">
1158 + <?php foreach ($systems as $sys):
1159 + $name = htmlspecialchars($sys['name'] ?? 'Système inconnu');
1160 + $status = htmlspecialchars($sys['status'] ?? 'unknown');
1161 + $lastUpdate = date('Y-m-d H:i:s');
1162 + $systemId = $sys['id'] ?? '';
1163 +
1164 + // Récupération des métriques actuelles
1165 + $info = $sys['info'] ?? [];
1166 + $cpu = $info['cpu'] ?? 0;
1167 + $memory = $info['mp'] ?? 0;
1168 + $load = $info['la'][0] ?? 0;
1169 + $temperature = $info['t'] ?? ($cpu > 0 ? $cpu : 0);
1170 + $disk = $info['dp'] ?? 0;
1171 + $gpu = $info['g'] ?? 0;
1172 + ?>
1173 + <div class="list-item">
1174 + <div class="list-item-left">
1175 + <div class="list-system-name"><?= $name ?></div>
1176 + <div class="status <?= $status ?>">
1177 + <span><?= getStatusIcon($status) ?></span>
1178 + <?= strtoupper($status) ?>
1179 + </div>
1180 + </div>
1181 + <div class="list-item-right">
1182 + <div class="list-metrics">
1183 + <div class="list-metric">
1184 + <div class="list-metric-value"><?= round($cpu, 1) ?>%</div>
1185 + <div class="list-metric-label">CPU</div>
1186 + <div class="progress-bar">
1187 + <div class="progress-fill <?= getProgressColorClass($cpu, 'cpu') ?>" style="width: <?= min($cpu, 100) ?>%"></div>
1188 + </div>
1189 + </div>
1190 + <div class="list-metric">
1191 + <div class="list-metric-value"><?= round($memory, 1) ?>%</div>
1192 + <div class="list-metric-label">RAM</div>
1193 + <div class="progress-bar">
1194 + <div class="progress-fill <?= getProgressColorClass($memory, 'memory') ?>" style="width: <?= min($memory, 100) ?>%"></div>
1195 + </div>
1196 + </div>
1197 + <div class="list-metric">
1198 + <div class="list-metric-value"><?= round($load, 2) ?></div>
1199 + <div class="list-metric-label">Load</div>
1200 + <div class="progress-bar">
1201 + <div class="progress-fill <?= getProgressColorClass($load, 'load') ?>" style="width: <?= min($load * 20, 100) ?>%"></div>
1202 + </div>
1203 + </div>
1204 + <div class="list-metric">
1205 + <div class="list-metric-value"><?= round($temperature, 1) ?>°C</div>
1206 + <div class="list-metric-label">Temp</div>
1207 + <div class="progress-bar">
1208 + <div class="progress-fill <?= getProgressColorClass($temperature, 'temp') ?>" style="width: <?= min($temperature * 1.2, 100) ?>%"></div>
1209 + </div>
1210 + </div>
1211 + <div class="list-metric">
1212 + <div class="list-metric-value"><?= round($disk, 1) ?>%</div>
1213 + <div class="list-metric-label">Disk</div>
1214 + <div class="progress-bar">
1215 + <div class="progress-fill <?= getProgressColorClass($disk, 'disk') ?>" style="width: <?= min($disk, 100) ?>%"></div>
1216 + </div>
1217 + </div>
1218 + <?php if ($gpu > 0): ?>
1219 + <div class="list-metric">
1220 + <div class="list-metric-value"><?= round($gpu, 1) ?>%</div>
1221 + <div class="list-metric-label">GPU</div>
1222 + <div class="progress-bar">
1223 + <div class="progress-fill <?= getProgressColorClass($gpu, 'cpu') ?>" style="width: <?= min($gpu, 100) ?>%"></div>
1224 + </div>
1225 + </div>
1226 + <?php endif; ?>
1227 + </div>
1228 + <div class="list-badges">
1229 + <span class="badge badge-time">🕒 <?= $lastUpdate ?></span>
1230 + <span class="badge badge-id">🆔 <?= substr($systemId, 0, 8) ?>...</span>
1231 + <span class="badge badge-ip">🌐 <?= htmlspecialchars($sys['host'] ?? 'N/A') ?></span>
1232 + <span class="badge badge-port">🔌 <?= htmlspecialchars($sys['port'] ?? 'N/A') ?></span>
1233 + </div>
1234 + </div>
1235 + </div>
1236 + <?php endforeach; ?>
1237 + </div>
1238 +
1239 + <!-- Alertes -->
1240 + <?php if (!empty($alerts)): ?>
1241 + <div class="alerts-section">
1242 + <h2>🚨 Alertes récentes</h2>
1243 + <?php foreach (array_slice($alerts, 0, 10) as $alert): ?>
1244 + <div class="alert-item alert-<?= $alert['severity'] ?? 'info' ?>">
1245 + <div>
1246 + <strong><?= htmlspecialchars($alert['message'] ?? 'Alerte') ?></strong><br>
1247 + <small><?= date('d/m/Y H:i', strtotime($alert['created'])) ?></small>
1248 + </div>
1249 + </div>
1250 + <?php endforeach; ?>
1251 + </div>
1252 + <?php endif; ?>
1253 + </div>
1254 +
1255 + <?php if ($autoRefresh === 'true' && $refreshInterval > 0): ?>
1256 + <div class="refresh-indicator" id="refreshIndicator">
1257 + <span id="refreshStatus">🔄</span> Prochaine mise à jour dans <span id="countdown"><?= $refreshInterval ?></span>s
1258 + </div>
1259 + <?php endif; ?>
1260 +
1261 + <script>
1262 + // Configuration des thèmes
1263 + function toggleTheme() {
1264 + const currentTheme = document.body.classList.contains('dark') ? 'dark' : 'light';
1265 + const newTheme = currentTheme === 'dark' ? 'light' : 'dark';
1266 + window.location.href = `?theme=${newTheme}&refresh=<?= $refreshInterval ?>&auto=<?= $autoRefresh ?>`;
1267 + }
1268 +
1269 + // Gestion des paramètres
1270 + function toggleSettings() {
1271 + document.getElementById('settingsPanel').classList.toggle('open');
1272 + document.getElementById('settingsOverlay').classList.toggle('open');
1273 + }
1274 +
1275 + function closeSettings() {
1276 + document.getElementById('settingsPanel').classList.remove('open');
1277 + document.getElementById('settingsOverlay').classList.remove('open');
1278 + }
1279 +
1280 + // Gestion de la légende
1281 + function toggleLegend() {
1282 + document.getElementById('legendPanel').classList.toggle('open');
1283 + document.getElementById('legendOverlay').classList.toggle('open');
1284 + }
1285 +
1286 + function closeLegend() {
1287 + document.getElementById('legendPanel').classList.remove('open');
1288 + document.getElementById('legendOverlay').classList.remove('open');
1289 + }
1290 +
1291 + // Gestion du mode d'affichage
1292 + function toggleView() {
1293 + const currentView = '<?= $viewMode ?>';
1294 + const newView = currentView === 'cards' ? 'list' : 'cards';
1295 + window.location.href = `?view=${newView}&refresh=<?= $refreshInterval ?>&auto=<?= $autoRefresh ?>&theme=<?= $theme ?>`;
1296 + }
1297 +
1298 + // Compteur de rafraîchissement
1299 + <?php if ($autoRefresh === 'true' && $refreshInterval > 0): ?>
1300 + let countdown = <?= $refreshInterval ?>;
1301 + const countdownElement = document.getElementById('countdown');
1302 + const refreshStatusElement = document.getElementById('refreshStatus');
1303 +
1304 + const countdownInterval = setInterval(function() {
1305 + countdown--;
1306 + if (countdownElement) {
1307 + countdownElement.textContent = countdown;
1308 + }
1309 +
1310 + // Animation de l'icône de rafraîchissement
1311 + if (refreshStatusElement) {
1312 + refreshStatusElement.style.animation = 'spin 1s linear infinite';
1313 + }
1314 +
1315 + if (countdown <= 0) {
1316 + clearInterval(countdownInterval);
1317 + if (refreshStatusElement) {
1318 + refreshStatusElement.textContent = '⏳';
1319 + refreshStatusElement.style.animation = 'none';
1320 + }
1321 + location.reload();
1322 + }
1323 + }, 1000);
1324 + <?php endif; ?>
1325 +
1326 + // Mise à jour de l'heure
1327 + function updateTime() {
1328 + const now = new Date();
1329 + const timeString = now.toLocaleTimeString('fr-FR');
1330 + document.title = `Dashboard32 Beszel - ${timeString}`;
1331 + }
1332 +
1333 + setInterval(updateTime, 1000);
1334 + updateTime();
1335 +
1336 + // Forcer le rafraîchissement automatique si pas configuré
1337 + <?php if ($autoRefresh === 'true' && $refreshInterval > 0): ?>
1338 + // Le rafraîchissement automatique est déjà configuré
1339 + <?php else: ?>
1340 + // Forcer le rafraîchissement toutes les 30 secondes si pas configuré
1341 + setTimeout(function() {
1342 + location.reload();
1343 + }, 30000);
1344 + <?php endif; ?>
1345 + </script>
1346 + </body>
1347 + </html>
Neuer Älter