dbXapp 2.0
RAD, CMS, Module und Runtime-IDE fuer dbXapp
Loading...
Searching...
No Matches
dbxDashboard.class.php
Go to the documentation of this file.
1<?php
2namespace dbx\dbxAdmin;
3
4dbx()->use_system_class('dbxReport');
5dbx()->use_system_class('dbxForm');
6dbx()->use_system_class('dbxDD');
7
8class dbxDashboard extends \dbxObj {
9
10 private const HISTORY_DD = 'dbxAdmin|dbxAdminDashboardMetric';
11 private const PERF_REQUEST_DD = 'dbx|dbxPerformanceRequest';
12 private const PERF_TIMER_DD = 'dbx|dbxPerformanceTimer';
13 private const HISTORY_BUCKET_MINUTES = 15;
14
15 private $metricCache = array();
16 private $historyReady = null;
17 private $performanceRequestAverage = null;
18 private $performanceTimerAverages = null;
19 private $performanceModuleAverages = null;
20
21 private function fmt($value) {
22 $value = (int) $value;
23 return number_format($value, 0, ',', '.');
24 }
25
26 private function percent($value, $max) {
27 $value = (float) $value;
28 $max = (float) $max;
29
30 if ($max <= 0) {
31 return 0;
32 }
33
34 return max(0, min(100, (int) round(($value / $max) * 100)));
35 }
36
37 private function health_reason_label(int $inventoryCount, int $existingCount, int $sysmsgRisk, int $missing): string {
38 $reasons = array();
39
40 if ($missing > 0) {
41 $reasons[] = 'Missing';
42 }
43
44 if ($sysmsgRisk > 0) {
45 $reasons[] = 'SysMsg';
46 }
47
48 if ($existingCount < $inventoryCount) {
49 $reasons[] = 'DB';
50 }
51
52 return count($reasons) ? implode('/', $reasons) : 'OK';
53 }
54
55 private function request_runtime_ms() {
56 return max(0, (int) round(dbx()->current_php_runtime() * 1000));
57 }
58
59 private function memory_peak_kb() {
60 $bytes = (int) dbx()->current_memory_bytes();
61
62 if ($bytes <= 0) {
63 global $dbx_run_timer;
64 $startMemory = isset($dbx_run_timer['system']['start_memory']) && is_numeric($dbx_run_timer['system']['start_memory'])
65 ? (int) $dbx_run_timer['system']['start_memory']
66 : 0;
67
68 if ($startMemory > 0) {
69 $bytes = max(0, (int) memory_get_peak_usage() - $startMemory);
70 }
71 }
72
73 if ($bytes <= 0) {
74 $bytes = (int) memory_get_usage();
75 }
76
77 return max(1, (int) ceil($bytes / 1024));
78 }
79
80 private function card_action($href, $label) {
81 $tpl = dbx()->get_system_obj('dbxTPL');
82
83 return $tpl->get_tpl('dbxAdmin|admin-dashboard-card-action', array(
84 'href' => $href,
85 'label' => $label,
86 ));
87 }
88
89 private function collapse_action($target, $label = 'Aufklappen', $expanded = false) {
90 $tpl = dbx()->get_system_obj('dbxTPL');
91
92 return $tpl->get_tpl('dbxAdmin|admin-dashboard-collapse-action', array(
93 'target' => $target,
94 'label' => $label,
95 'expanded' => $expanded ? 'true' : 'false',
96 ));
97 }
98
99 private function card_bar_data($title, $icon, $subtitle = '', $action = '') {
100 return array(
101 'bar_class' => 'dbx-module-bar',
102 'bar_title_class' => 'dbx-module-bar-titleblock',
103 'bar_actions_class' => 'dbx-module-bar-actions',
104 'bar_title' => $title,
105 'bar_icon' => $icon,
106 'bar_subtitle' => $subtitle,
107 'bar_title_pre' => '',
108 'bar_title_heading_attrs' => '',
109 'bar_actions' => $action,
110 'bar_extra' => '',
111 'title' => $title,
112 'icon' => $icon,
113 'subtitle' => $subtitle,
114 'action' => $action,
115 );
116 }
117
118 private function card_bar($title, $icon, $subtitle = '', $action = '') {
119 $tpl = dbx()->get_system_obj('dbxTPL');
120
121 return $tpl->get_tpl('dbx|component-bar', $this->card_bar_data($title, $icon, $subtitle, $action));
122 }
123
124 private function safe_count($dd, $where = '') {
125 $count = 0;
126
127 try {
128 $db = dbx()->get_system_obj('dbxDB');
129 $res = $db->count($dd, $where);
130 if (is_numeric($res) && (int) $res > 0) {
131 $count = (int) $res;
132 }
133 } catch (\Throwable $e) {
134 $count = 0;
135 }
136
137 return $count;
138 }
139
140 private function safe_select($dd, $where = '', $columns = '*', $orderby = '', $asc_desc = 'ASC', $groupby = '', $max = 0, $offset = 0) {
141 $rows = array();
142
143 try {
144 $db = dbx()->get_system_obj('dbxDB');
145 $res = $db->select($dd, $where, $columns, $orderby, $asc_desc, $groupby, $max, $offset, 0);
146 if (is_array($res)) {
147 $rows = $res;
148 }
149 } catch (\Throwable $e) {
150 $rows = array();
151 }
152
153 return $rows;
154 }
155
156 private function ensure_performance_tables() {
157 try {
158 $dd = dbx()->get_system_obj('dbxDD');
159 return $dd->create_db_tab(self::PERF_REQUEST_DD) && $dd->create_db_tab(self::PERF_TIMER_DD);
160 } catch (\Throwable $e) {
161 return false;
162 }
163 }
164
165 private function ensure_history_table() {
166 if ($this->historyReady !== null) {
167 return $this->historyReady;
168 }
169
170 $this->historyReady = false;
171
172 try {
173 $dd = dbx()->get_system_obj('dbxDD');
174 $this->historyReady = $dd->create_db_tab(self::HISTORY_DD) ? $this->ensure_history_columns() : false;
175 } catch (\Throwable $e) {
176 $this->historyReady = false;
177 }
178
179 return $this->historyReady;
180 }
181
182 private function ensure_history_columns() {
183 $server = 'dbxAdmin|dbxAdmin.db3';
184 $table = 'dbx_admin_dashboard_metric';
185
186 try {
187 $db = dbx()->get_system_obj('dbxDB');
188 $rows = $db->select_query($server, 'PRAGMA table_info(' . $table . ')');
189
190 if (!is_array($rows)) {
191 return false;
192 }
193
194 $existing = array();
195 foreach ($rows as $row) {
196 $name = (string) ($row['name'] ?? '');
197 if ($name !== '') {
198 $existing[$name] = 1;
199 }
200 }
201
202 $fields = array(
203 'request_runtime_ms' => array('name' => 'request_runtime_ms', 'type' => 'int', 'length' => '11', 'default' => '0', 'index' => ''),
204 'memory_peak_mb' => array('name' => 'memory_peak_mb', 'type' => 'int', 'length' => '11', 'default' => '0', 'index' => ''),
205 'memory_peak_kb' => array('name' => 'memory_peak_kb', 'type' => 'int', 'length' => '11', 'default' => '0', 'index' => ''),
206 );
207
208 $dd = dbx()->get_system_obj('dbxDD');
209 foreach ($fields as $name => $field) {
210 if (!isset($existing[$name]) && !$dd->add_db_field_from_dd($server, $table, $field)) {
211 return false;
212 }
213 }
214
215 return true;
216 } catch (\Throwable $e) {
217 return false;
218 }
219 }
220
221 private function history_bucket_time() {
222 $bucketSeconds = self::HISTORY_BUCKET_MINUTES * 60;
223 return (int) (floor(time() / $bucketSeconds) * $bucketSeconds);
224 }
225
226 private function history_snapshot_record($metrics, $bucketTime) {
227 $now = date('Y-m-d H:i:s');
228 $uid = (int) dbx()->user();
229
230 return array(
231 'bucket_key' => date('YmdHi', $bucketTime),
232 'snapshot_date' => date('Y-m-d H:i:s', $bucketTime),
233 'create_date' => $now,
234 'create_uid' => $uid,
235 'update_date' => $now,
236 'update_uid' => $uid,
237 'owner' => $uid,
238 'users' => (int) ($metrics['users'] ?? 0),
239 'online' => (int) ($metrics['online'] ?? 0),
240 'modules' => (int) ($metrics['modules'] ?? 0),
241 'records' => (int) ($metrics['records'] ?? 0),
242 'databases' => (int) ($metrics['databases'] ?? 0),
243 'health_percent' => (int) ($metrics['health_percent'] ?? 0),
244 'active_users' => (int) ($metrics['active_users'] ?? 0),
245 'sessions' => (int) ($metrics['sessions'] ?? 0),
246 'tables' => (int) ($metrics['tables'] ?? 0),
247 'sysmsg_risk' => (int) ($metrics['sysmsg_risk'] ?? 0),
248 'missing' => (int) ($metrics['missing'] ?? 0),
249 'request_runtime_ms' => (int) ($metrics['request_runtime_ms'] ?? 0),
250 'memory_peak_mb' => (int) ceil(((int) ($metrics['memory_peak_kb'] ?? 0)) / 1024),
251 'memory_peak_kb' => (int) ($metrics['memory_peak_kb'] ?? 0),
252 );
253 }
254
255 private function store_history_snapshot($metrics) {
256 if (!$this->ensure_history_table()) {
257 return false;
258 }
259
260 try {
261 $db = dbx()->get_system_obj('dbxDB');
262 $bucketTime = $this->history_bucket_time();
263 $record = $this->history_snapshot_record($metrics, $bucketTime);
264 $rows = $db->select(self::HISTORY_DD, array('bucket_key' => $record['bucket_key']), array('id'), 'id', 'DESC', '', 1, 0, 0);
265 $id = is_array($rows) ? (int) ($rows[0]['id'] ?? 0) : 0;
266
267 if ($id > 0) {
268 unset($record['create_date'], $record['create_uid'], $record['owner']);
269 return $db->update(self::HISTORY_DD, $record, array('id' => $id), 0, 1, 1, 0) > 0;
270 }
271
272 return $db->insert(self::HISTORY_DD, $record, 0, 1, 1, 0) > 0;
273 } catch (\Throwable $e) {
274 return false;
275 }
276 }
277
278 private function metric_history_rows($metrics, $max = 48) {
279 if (!$this->ensure_history_table()) {
280 return array($this->history_snapshot_record($metrics, $this->history_bucket_time()));
281 }
282
283 $rows = $this->safe_select(
284 self::HISTORY_DD,
285 '',
286 array('snapshot_date', 'users', 'online', 'modules', 'records', 'databases', 'health_percent', 'request_runtime_ms', 'memory_peak_kb'),
287 'snapshot_date',
288 'DESC',
289 '',
290 $max,
291 0
292 );
293
294 if (!$rows) {
295 return array($this->history_snapshot_record($metrics, $this->history_bucket_time()));
296 }
297
298 return array_reverse($rows);
299 }
300
301 private function metric_series_definitions() {
302 return array(
303 'users' => array('label' => 'Benutzer', 'tone' => 'teal'),
304 'online' => array('label' => 'Online', 'tone' => 'green'),
305 'modules' => array('label' => 'Module', 'tone' => 'navy'),
306 'records' => array('label' => 'Datensaetze', 'tone' => 'amber'),
307 'databases' => array('label' => 'Datenbanken', 'tone' => 'cyan'),
308 'health_percent' => array('label' => 'Systemzustand', 'tone' => 'red'),
309 'request_runtime_ms' => array('label' => 'Speed', 'tone' => 'purple'),
310 'memory_peak_kb' => array('label' => 'DBX Memory', 'tone' => 'slate'),
311 );
312 }
313
314 private function metric_history($metrics) {
315 $rows = $this->metric_history_rows($metrics);
316 $labels = array();
317 $series = array();
318
319 foreach ($this->metric_series_definitions() as $key => $def) {
320 $series[$key] = array(
321 'key' => $key,
322 'label' => $def['label'],
323 'tone' => $def['tone'],
324 'values' => array(),
325 );
326 }
327
328 foreach ($rows as $row) {
329 $time = strtotime((string) ($row['snapshot_date'] ?? ''));
330 $labels[] = $time ? date('d.m. H:i', $time) : '';
331
332 foreach ($series as $key => $def) {
333 $series[$key]['values'][] = (int) ($row[$key] ?? 0);
334 }
335 }
336
337 return array(
338 'labels' => $labels,
339 'rows' => $rows,
340 'series' => array_values($series),
341 );
342 }
343
344 private function history_values($history, $key, $current = 0) {
345 $values = array();
346
347 foreach (($history['rows'] ?? array()) as $row) {
348 $values[] = (int) ($row[$key] ?? 0);
349 }
350
351 if (!$values) {
352 $values[] = (int) $current;
353 }
354
355 if (count($values) === 1) {
356 $values[] = $values[0];
357 }
358
359 return $values;
360 }
361
362 private function spark_values($history, $key, $current = 0) {
363 return implode(',', $this->history_values($history, $key, $current));
364 }
365
366 private function trend_text($history, $key, $suffix = '') {
367 $values = $this->history_values($history, $key, 0);
368
369 if (count($values) < 2) {
370 return '1 Messpunkt';
371 }
372
373 $first = (int) reset($values);
374 $last = (int) end($values);
375 $delta = $last - $first;
376
377 if ($delta === 0) {
378 return 'unveraendert';
379 }
380
381 return ($delta > 0 ? '+' : '') . $this->fmt($delta) . $suffix . ' im Verlauf';
382 }
383
384 private function fmt_ms($value) {
385 $value = max(0, (int) $value);
386 return number_format($value / 1000, 3, ',', '.') . ' Sec';
387 }
388
389 private function fmt_ms_precision($value, $precision = 3, $minSeconds = 0) {
390 $value = max(0, (float) $value);
391 $precision = max(0, min(6, (int) $precision));
392 $seconds = $value / 1000;
393
394 if ((float) $minSeconds > 0 && $seconds < (float) $minSeconds) {
395 $seconds = (float) $minSeconds;
396 }
397
398 return number_format($seconds, $precision, ',', '.') . ' Sec';
399 }
400
401 private function fmt_memory_kb($value) {
402 $value = max(0, (int) $value);
403
404 if ($value >= 1024) {
405 return number_format($value / 1024, 1, ',', '.') . ' MB';
406 }
407
408 return $this->fmt($value) . ' KB';
409 }
410
411 private function fmt_memory_delta_kb($value) {
412 $value = max(0, (int) $value);
413 return '+' . $this->fmt_memory_kb($value);
414 }
415
416 private function performance_dd_info($dd) {
417 try {
418 $db = dbx()->get_system_obj('dbxDB');
419 $table = (string) $db->get_dd_table($dd);
420 $server = (string) $db->get_dd_server($dd);
421
422 if (!preg_match('/^[A-Za-z0-9_]+$/', $table)) {
423 return array('', '');
424 }
425
426 return array($server, $table);
427 } catch (\Throwable $e) {
428 return array('', '');
429 }
430 }
431
432 private function performance_table_count(string $dd): int {
433 try {
434 $db = dbx()->get_system_obj('dbxDB');
435 $count = $db->count($dd, '');
436 return is_numeric($count) ? max(0, (int) $count) : 0;
437 } catch (\Throwable $e) {
438 return 0;
439 }
440 }
441
442 private function performance_now_record_base(): array {
443 $uid = (int) dbx()->user();
444 $now = date('Y-m-d H:i:s');
445
446 return array(
447 'create_date' => $now,
448 'create_uid' => $uid,
449 'update_date' => $now,
450 'update_uid' => $uid,
451 'owner' => $uid,
452 );
453 }
454
455 private function optimize_performance_db(): array {
456 $result = array('ok' => true, 'messages' => array());
457 $db = dbx()->get_system_obj('dbxDB');
458 $groups = array();
459
460 foreach (array(self::PERF_REQUEST_DD, self::PERF_TIMER_DD) as $dd) {
461 list($server, $table) = $this->performance_dd_info($dd);
462 if ($server === '' || $table === '') {
463 continue;
464 }
465 $groups[$server][] = $table;
466 }
467
468 foreach ($groups as $server => $tables) {
469 $tables = array_values(array_unique($tables));
470 $type = strtolower((string) $db->get_db_type($server));
471
472 if ($type === 'mysql') {
473 $quoted = array();
474 foreach ($tables as $table) {
475 $quoted[] = '`' . str_replace('`', '``', $table) . '`';
476 }
477 $ok = $db->exec($server, 'OPTIMIZE TABLE ' . implode(', ', $quoted));
478 $result['ok'] = $result['ok'] && (bool) $ok;
479 $result['messages'][] = $ok ? 'MySQL OPTIMIZE TABLE ausgefuehrt.' : 'MySQL OPTIMIZE TABLE fehlgeschlagen.';
480 continue;
481 }
482
483 if ($type === 'sqlite') {
484 $vacuum = $db->exec($server, 'VACUUM');
485 $analyze = $db->exec($server, 'ANALYZE');
486 $result['ok'] = $result['ok'] && (bool) $vacuum;
487 $result['messages'][] = $vacuum ? 'SQLite VACUUM ausgefuehrt.' : 'SQLite VACUUM fehlgeschlagen.';
488 if ($analyze) {
489 $result['messages'][] = 'SQLite ANALYZE ausgefuehrt.';
490 }
491 continue;
492 }
493
494 $result['messages'][] = 'Keine Optimierung fuer DB-Typ ' . $type . ' definiert.';
495 }
496
497 return $result;
498 }
499
500 private function compress_performance_db(): array {
501 $stats = array(
502 'request_before' => $this->performance_table_count(self::PERF_REQUEST_DD),
503 'timer_before' => $this->performance_table_count(self::PERF_TIMER_DD),
504 'request_after' => 0,
505 'timer_after' => 0,
506 'inserted' => 0,
507 'ok' => false,
508 'messages' => array(),
509 );
510
511 if (!$this->ensure_performance_tables()) {
512 $stats['messages'][] = 'Performance-Tabellen konnten nicht vorbereitet werden.';
513 return $stats;
514 }
515
516 list($requestServer, $requestTable) = $this->performance_dd_info(self::PERF_REQUEST_DD);
517 list($timerServer, $timerTable) = $this->performance_dd_info(self::PERF_TIMER_DD);
518 if ($requestServer === '' || $requestTable === '' || $timerServer === '' || $timerTable === '') {
519 $stats['messages'][] = 'Performance-Tabellen konnten nicht ermittelt werden.';
520 return $stats;
521 }
522
523 $db = dbx()->get_system_obj('dbxDB');
524 $requestRows = $db->select_query($requestServer, "SELECT COUNT(*) AS row_count, AVG(total_time_ms) AS total_time_ms, AVG(total_memory_kb) AS total_memory_kb, AVG(peak_memory_mb) AS peak_memory_mb, AVG(timer_count) AS timer_count FROM $requestTable");
525 $timerRows = $db->select_query($timerServer, "SELECT section, MAX(info) AS info, MIN(sort_order) AS sort_order, COUNT(*) AS row_count, AVG(time_ms) AS time_ms, AVG(memory_kb) AS memory_kb, AVG(start_memory_kb) AS start_memory_kb, AVG(end_memory_kb) AS end_memory_kb FROM $timerTable WHERE section <> '' AND section <> 'system' GROUP BY section ORDER BY MIN(sort_order) ASC, section ASC");
526
527 $db->empty(self::PERF_TIMER_DD);
528 $db->empty(self::PERF_REQUEST_DD);
529
530 $base = $this->performance_now_record_base();
531 $requestId = 0;
532 $requestDate = date('Y-m-d H:i:s');
533 $requestCount = is_array($requestRows) ? (int) ($requestRows[0]['row_count'] ?? 0) : 0;
534
535 if ($requestCount > 0) {
536 $request = array_merge($base, array(
537 'request_date' => $requestDate,
538 'uid' => (int) dbx()->user(),
539 'session_id' => 'compressed',
540 'modul' => 'compressed',
541 'run1' => 'performance',
542 'run2' => 'compress',
543 'ajax' => 0,
544 'sync' => 0,
545 'method' => 'COMPRESS',
546 'uri' => 'Performance DB komprimiert aus ' . $requestCount . ' Request-Datensaetzen',
547 'total_time_ms' => (int) round((float) ($requestRows[0]['total_time_ms'] ?? 0)),
548 'total_memory_kb' => (int) round((float) ($requestRows[0]['total_memory_kb'] ?? 0)),
549 'peak_memory_mb' => (int) round((float) ($requestRows[0]['peak_memory_mb'] ?? 0)),
550 'timer_count' => (int) round((float) ($requestRows[0]['timer_count'] ?? 0)),
551 'sample_rate' => max(1, $requestCount),
552 ));
553 if ($db->insert(self::PERF_REQUEST_DD, $request, 0, 1, 1, 0) === 1) {
554 $requestId = $db->get_insert_id();
555 $stats['inserted']++;
556 }
557 }
558
559 if (is_array($timerRows)) {
560 $sort = 0;
561 foreach ($timerRows as $row) {
562 $section = trim((string) ($row['section'] ?? ''));
563 if ($section === '') {
564 continue;
565 }
566
567 $count = max(1, (int) ($row['row_count'] ?? 0));
568 $timer = array_merge($base, array(
569 'request_id' => $requestId,
570 'request_date' => $requestDate,
571 'sort_order' => $sort,
572 'section' => substr($section, 0, 80),
573 'info' => substr(trim((string) ($row['info'] ?? '')) . ' | komprimiert aus ' . $count . ' Messungen', 0, 160),
574 'time_ms' => (int) round((float) ($row['time_ms'] ?? 0)),
575 'memory_kb' => (int) round((float) ($row['memory_kb'] ?? 0)),
576 'start_memory_kb' => (int) round((float) ($row['start_memory_kb'] ?? 0)),
577 'end_memory_kb' => (int) round((float) ($row['end_memory_kb'] ?? 0)),
578 ));
579 if ($db->insert(self::PERF_TIMER_DD, $timer, 0, 1, 1, 0) === 1) {
580 $stats['inserted']++;
581 $sort++;
582 }
583 }
584 }
585
586 $optimize = $this->optimize_performance_db();
587 $stats['messages'] = array_merge($stats['messages'], $optimize['messages']);
588 $stats['request_after'] = $this->performance_table_count(self::PERF_REQUEST_DD);
589 $stats['timer_after'] = $this->performance_table_count(self::PERF_TIMER_DD);
590 $stats['ok'] = (bool) ($optimize['ok'] ?? false);
591
592 return $stats;
593 }
594
595 private function clear_performance_db(): array {
596 $stats = array(
597 'request_before' => $this->performance_table_count(self::PERF_REQUEST_DD),
598 'timer_before' => $this->performance_table_count(self::PERF_TIMER_DD),
599 'request_after' => 0,
600 'timer_after' => 0,
601 'inserted' => 0,
602 'ok' => false,
603 'messages' => array(),
604 );
605
606 if (!$this->ensure_performance_tables()) {
607 $stats['messages'][] = 'Performance-Tabellen konnten nicht vorbereitet werden.';
608 return $stats;
609 }
610
611 $db = dbx()->get_system_obj('dbxDB');
612 $db->empty(self::PERF_TIMER_DD);
613 $db->empty(self::PERF_REQUEST_DD);
614
615 $optimize = $this->optimize_performance_db();
616 $stats['messages'] = array_merge($stats['messages'], $optimize['messages']);
617 $stats['request_after'] = $this->performance_table_count(self::PERF_REQUEST_DD);
618 $stats['timer_after'] = $this->performance_table_count(self::PERF_TIMER_DD);
619 $stats['ok'] = (bool) ($optimize['ok'] ?? false);
620
621 return $stats;
622 }
623
624 private function render_performance_process(string $title, array $stats): string {
625 $tpl = dbx()->get_system_obj('dbxTPL');
626 $ok = (bool) ($stats['ok'] ?? false);
627 $messages = array();
628 foreach (($stats['messages'] ?? array()) as $message) {
629 if (trim((string) $message) !== '') {
630 $messages[] = dbx()->esc($message);
631 }
632 }
633 $message = ($ok ? 'Prozess abgeschlossen.' : 'Prozess mit Fehler beendet.')
634 . ' Request: ' . $this->fmt($stats['request_before'] ?? 0) . ' -> ' . $this->fmt($stats['request_after'] ?? 0)
635 . ', Timer: ' . $this->fmt($stats['timer_before'] ?? 0) . ' -> ' . $this->fmt($stats['timer_after'] ?? 0)
636 . ', neu geschrieben: ' . $this->fmt($stats['inserted'] ?? 0)
637 . (count($messages) ? ' | ' . implode(' | ', $messages) : '');
638
639 return $tpl->get_tpl('dbxAdmin|schema-process', array(
640 'target_id' => 'dbx_process_performance_' . substr(md5($title), 0, 10),
641 'title' => dbx()->esc($title),
642 'status_key' => $ok ? 'finished' : 'error',
643 'status_label' => $ok ? 'Fertig' : 'Fehler',
644 'status_class' => $ok ? 'bg-success' : 'bg-danger',
645 'status_icon' => $ok ? 'bi bi-check-circle' : 'bi bi-exclamation-triangle',
646 'message' => $message,
647 'percent' => $ok ? 100 : 0,
648 'step_percent' => $ok ? 100 : 0,
649 'bar_class' => $ok ? 'bg-success' : 'bg-danger',
650 'task_label' => 'Performance DB',
651 'step_label' => 'Optimierung',
652 'process_label' => 'Performance Wartung',
653 'updated_at' => dbx()->esc(date('d.m.Y H:i:s')),
654 'next_url' => '',
655 'pause_url' => '',
656 'resume_url' => '',
657 'continue_url' => '',
658 'cancel_url' => '',
659 'restart_url' => '',
660 'autostart' => 0,
661 'interval' => 800,
662 'pause_visible' => '_none',
663 'resume_visible' => '_none',
664 'continue_visible' => '_none',
665 'restart_visible' => '_none',
666 'cancel_visible' => '_none',
667 'back_url' => '?dbx_modul=dbxAdmin',
668 ));
669 }
670
671 private function run_performance_maintenance_process(string $mode): string {
672 dbx()->set_system_var('dbx_performance_timer_skip', 1);
673
674 if ($mode === 'clear') {
675 return $this->render_performance_process('Performance DB leeren und optimieren', $this->clear_performance_db());
676 }
677
678 return $this->render_performance_process('Performance DB komprimieren und optimieren', $this->compress_performance_db());
679 }
680
681 private function performance_openwin_action(string $href, string $label, string $icon, string $class = 'btn-outline-secondary'): string {
682 return '<a class="btn btn-sm ' . dbx()->esc($class) . ' dbx-win" href="' . dbx()->esc($href) . '" data-url="' . dbx()->esc($href) . '" data-title="' . dbx()->esc($label) . '" data-width="760" data-height="520" role="button">'
683 . '<i class="bi ' . dbx()->esc($icon) . '"></i> ' . dbx()->esc($label)
684 . '</a>';
685 }
686
687 private function performance_maintenance_actions(): string {
688 return $this->performance_openwin_action('?dbx_modul=dbxAdmin&dbx_run1=run&dbx_run2=performance_compress', 'Komprimieren', 'bi-file-zip')
689 . $this->performance_openwin_action('?dbx_modul=dbxAdmin&dbx_run1=run&dbx_run2=performance_clear', 'Performance DB leeren', 'bi-trash3', 'btn-outline-danger');
690 }
691
692 private function performance_request_average() {
693 if ($this->performanceRequestAverage !== null) {
694 return $this->performanceRequestAverage;
695 }
696
697 $this->performanceRequestAverage = array();
698
699 if (!$this->ensure_performance_tables()) {
700 return $this->performanceRequestAverage;
701 }
702
703 list($server, $table) = $this->performance_dd_info(self::PERF_REQUEST_DD);
704 if ($server === '' || $table === '') {
705 return $this->performanceRequestAverage;
706 }
707
708 try {
709 $db = dbx()->get_system_obj('dbxDB');
710 $rows = $db->select_query($server, "SELECT COUNT(*) AS row_count, AVG(total_time_ms) AS avg_time_ms, AVG(total_memory_kb) AS avg_memory_kb, AVG(timer_count) AS avg_timer_count FROM $table");
711 $row = is_array($rows) ? ($rows[0] ?? array()) : array();
712
713 if ((int) ($row['row_count'] ?? 0) <= 0) {
714 return $this->performanceRequestAverage;
715 }
716
717 $this->performanceRequestAverage = array(
718 'count' => (int) ($row['row_count'] ?? 0),
719 'avg_time_ms' => (int) round((float) ($row['avg_time_ms'] ?? 0)),
720 'avg_memory_kb' => (int) round((float) ($row['avg_memory_kb'] ?? 0)),
721 'avg_timer_count' => (int) round((float) ($row['avg_timer_count'] ?? 0)),
722 );
723 } catch (\Throwable $e) {
724 $this->performanceRequestAverage = array();
725 }
726
727 return $this->performanceRequestAverage;
728 }
729
730 private function performance_timer_averages() {
731 if ($this->performanceTimerAverages !== null) {
732 return $this->performanceTimerAverages;
733 }
734
735 $this->performanceTimerAverages = array();
736
737 if (!$this->ensure_performance_tables()) {
738 return $this->performanceTimerAverages;
739 }
740
741 list($server, $table) = $this->performance_dd_info(self::PERF_TIMER_DD);
742 if ($server === '' || $table === '') {
743 return $this->performanceTimerAverages;
744 }
745
746 try {
747 $db = dbx()->get_system_obj('dbxDB');
748 $sql = "SELECT section, MAX(info) AS info, MIN(sort_order) AS sort_order, COUNT(*) AS row_count, AVG(time_ms) AS avg_time_ms, AVG(memory_kb) AS avg_memory_kb
749 FROM $table
750 WHERE section <> 'system'
751 GROUP BY section
752 ORDER BY MIN(sort_order) ASC, section ASC";
753 $rows = $db->select_query($server, $sql);
754 $this->performanceTimerAverages = is_array($rows) ? $rows : array();
755 } catch (\Throwable $e) {
756 $this->performanceTimerAverages = array();
757 }
758
759 return $this->performanceTimerAverages;
760 }
761
762 private function performance_timer_average($section) {
763 $section = (string) $section;
764
765 foreach ($this->performance_timer_averages() as $row) {
766 if ((string) ($row['section'] ?? '') === $section) {
767 return array(
768 'count' => (int) ($row['row_count'] ?? 0),
769 'avg_time_ms' => (int) round((float) ($row['avg_time_ms'] ?? 0)),
770 'avg_memory_kb' => (int) round((float) ($row['avg_memory_kb'] ?? 0)),
771 );
772 }
773 }
774
775 return array();
776 }
777
778 private function performance_module_averages() {
779 if ($this->performanceModuleAverages !== null) {
780 return $this->performanceModuleAverages;
781 }
782
783 $this->performanceModuleAverages = array();
784
785 if (!$this->ensure_performance_tables()) {
786 return $this->performanceModuleAverages;
787 }
788
789 list($requestServer, $requestTable) = $this->performance_dd_info(self::PERF_REQUEST_DD);
790 list($timerServer, $timerTable) = $this->performance_dd_info(self::PERF_TIMER_DD);
791 if ($requestServer === '' || $requestTable === '' || $timerServer !== $requestServer || $timerTable === '') {
792 return $this->performanceModuleAverages;
793 }
794
795 try {
796 $db = dbx()->get_system_obj('dbxDB');
797 $sql = "SELECT r.modul AS modul,
798 COUNT(*) AS row_count,
799 AVG(r.total_time_ms) AS avg_total_ms,
800 AVG(COALESCE(d.db_time_ms, 0)) AS avg_db_ms
801 FROM $requestTable r
802 LEFT JOIN (
803 SELECT request_id, SUM(time_ms) AS db_time_ms
804 FROM $timerTable
805 WHERE section = 'db-total'
806 GROUP BY request_id
807 ) d ON d.request_id = r.id
808 WHERE TRIM(COALESCE(r.modul, '')) <> ''
809 GROUP BY r.modul
810 ORDER BY AVG(r.total_time_ms) DESC, r.modul ASC
811 LIMIT 16";
812 $rows = $db->select_query($requestServer, $sql);
813 $this->performanceModuleAverages = is_array($rows) ? $rows : array();
814 } catch (\Throwable $e) {
815 $this->performanceModuleAverages = array();
816 }
817
818 return $this->performanceModuleAverages;
819 }
820
821 private function hero_performance($metrics) {
822 $request = $this->performance_request_average();
823 $dbTotal = $this->performance_timer_average('db-total');
824
825 $requestMs = $request ? (int) ($request['avg_time_ms'] ?? 0) : (int) ($metrics['request_runtime_ms'] ?? 0);
826 $dbMs = $dbTotal ? (int) ($dbTotal['avg_time_ms'] ?? 0) : 0;
827 $phpMs = max(0, $requestMs - $dbMs);
828 $dbShare = $requestMs > 0 ? min(999, (int) round(($dbMs / $requestMs) * 100)) : 0;
829 $phpShare = $requestMs > 0 ? min(999, (int) round(($phpMs / $requestMs) * 100)) : 0;
830
831 return array(
832 'request_avg' => $this->fmt_ms($requestMs),
833 'request_raw' => $requestMs,
834 'request_count' => $this->fmt($request['count'] ?? 0),
835 'php_avg' => $this->fmt_ms($phpMs),
836 'php_raw' => $phpMs,
837 'php_share' => $this->fmt($phpShare),
838 'db_avg' => $this->fmt_ms($dbMs),
839 'db_raw' => $dbMs,
840 'db_count' => $this->fmt($dbTotal['count'] ?? 0),
841 'db_share' => $this->fmt($dbShare),
842 );
843 }
844
845 private function hero_performance_gauge($label, $icon, $tone, $raw, $value, $subtitle) {
846 $tpl = dbx()->get_system_obj('dbxTPL');
847
848 return $tpl->get_tpl('dbxAdmin|admin-dashboard-hero-gauge', array(
849 'label' => $label,
850 'icon' => $icon,
851 'tone' => $tone,
852 'raw' => max(0, (int) $raw),
853 'max' => 6000,
854 'value' => $value,
855 'subtitle' => $subtitle,
856 ));
857 }
858
859 private function hero_performance_gauges($heroPerformance) {
860 $items = '';
861 $items .= $this->hero_performance_gauge(
862 'Request gesamt',
863 'bi-stopwatch',
864 'request',
865 $heroPerformance['request_raw'] ?? 0,
866 $heroPerformance['request_avg'] ?? '0,000 Sec',
867 'Durchschnitt aus ' . ($heroPerformance['request_count'] ?? '0') . ' Requests'
868 );
869 $items .= $this->hero_performance_gauge(
870 'PHP gesamt',
871 'bi-cpu',
872 'php',
873 $heroPerformance['php_raw'] ?? 0,
874 $heroPerformance['php_avg'] ?? '0,000 Sec',
875 'Durchschnitt ohne DB-Anteil'
876 );
877 $items .= $this->hero_performance_gauge(
878 'DB gesamt',
879 'bi-database-check',
880 'db',
881 $heroPerformance['db_raw'] ?? 0,
882 $heroPerformance['db_avg'] ?? '0,000 Sec',
883 'Durchschnitt aus DB-Timern'
884 );
885
886 return $items;
887 }
888
889 private function hero_summary_rows(array $items): string {
890 $html = '';
891 $max = 1;
892 foreach ($items as $item) {
893 $max = max($max, (int) ($item['count'] ?? 0));
894 }
895
896 foreach ($items as $item) {
897 $count = (int) ($item['count'] ?? 0);
898 $pct = max(3, min(100, (int) round(($count / $max) * 100)));
899 $tone = dbx()->esc((string) ($item['tone'] ?? 'blue'));
900 $html .= '<div class="dbx-admin-dashboard-hero-summary-row dbx-admin-dashboard-hero-summary-row-' . $tone . '">'
901 . '<span>' . dbx()->esc((string) ($item['label'] ?? '')) . '</span>'
902 . '<div><em style="width:' . $pct . '%"></em></div>'
903 . '<strong>' . dbx()->esc($this->fmt($count)) . '</strong>'
904 . '</div>';
905 }
906
907 return $html;
908 }
909
910 private function hero_summary_card(string $title, string $subtitle, string $icon, string $tone, int $total, array $rows): string {
911 $tpl = dbx()->get_system_obj('dbxTPL');
912
913 return $tpl->get_tpl('dbxAdmin|admin-dashboard-hero-summary', array(
914 'title' => $title,
915 'subtitle' => $subtitle,
916 'icon' => $icon,
917 'tone' => $tone,
918 'total' => $this->fmt($total),
919 'rows' => $this->hero_summary_rows($rows),
920 ));
921 }
922
923 private function hero_status_summaries(): string {
924 $contactDd = 'dbxContact|contactRequest';
925 $contactTotal = $this->safe_count($contactDd);
926 $contactRows = array(
927 array('label' => 'Offen', 'count' => $this->safe_count($contactDd, array('status' => 'open')), 'tone' => 'blue'),
928 array('label' => 'In Arbeit', 'count' => $this->safe_count($contactDd, array('status' => 'in_progress')), 'tone' => 'cyan'),
929 array('label' => 'Rueckfrage', 'count' => $this->safe_count($contactDd, array('status' => 'waiting_customer')), 'tone' => 'amber'),
930 array('label' => 'Beantwortet', 'count' => $this->safe_count($contactDd, array('status' => 'answered')), 'tone' => 'green'),
931 array('label' => 'Geschlossen', 'count' => $this->safe_count($contactDd, array('status' => 'closed')), 'tone' => 'slate'),
932 );
933
934 $sysmsgDd = 'dbxSysMsg';
935 $sysmsgTotal = $this->safe_count($sysmsgDd);
936 $sysmsgRows = array(
937 array('label' => 'Info', 'count' => $this->safe_count($sysmsgDd, "LOWER(status) = 'info'"), 'tone' => 'blue'),
938 array('label' => 'Warning', 'count' => $this->safe_count($sysmsgDd, "LOWER(status) = 'warning'"), 'tone' => 'amber'),
939 array('label' => 'Error', 'count' => $this->safe_count($sysmsgDd, "LOWER(status) = 'error'"), 'tone' => 'red'),
940 array('label' => 'Security', 'count' => $this->safe_count($sysmsgDd, "LOWER(status) = 'security'"), 'tone' => 'purple'),
941 );
942
943 return $this->hero_summary_card('Kontakte', 'Alle Anfragen nach Status', 'bi-life-preserver', 'contact', $contactTotal, $contactRows)
944 . $this->hero_summary_card('SysMsg', 'Meldungen nach Status', 'bi-bell', 'sysmsg', $sysmsgTotal, $sysmsgRows);
945 }
946
947 private function performance_label($section, $info = '') {
948 $section = trim((string) $section);
949 $info = trim((string) $info);
950
951 if ($section === 'db-total') {
952 return 'DB Gesamt';
953 }
954
955 if ($section === 'js-total') {
956 return 'JS Gesamt';
957 }
958
959 if ($section === 'system') {
960 return 'PHP/System Gesamt';
961 }
962
963 if ($section === 'db-select') {
964 return 'DB Select gesamt';
965 }
966
967 if ($section === 'db-save') {
968 return 'DB Save gesamt';
969 }
970
971 if (substr($section, 0, 10) === 'db-select-') {
972 return 'DB Select';
973 }
974
975 if (substr($section, 0, 8) === 'db-save-') {
976 return 'DB Save';
977 }
978
979 $known = array(
980 'system-load' => 'System Load',
981 'session-load' => 'Session Load',
982 'system-check' => 'System Check',
983 'modul-run' => 'Modul Run',
984 'page-load' => 'Page Load',
985 'interpreter' => 'Interpreter',
986 'db-select' => 'DB Select',
987 'db-save' => 'DB Save',
988 );
989
990 if (isset($known[$section])) {
991 return $known[$section];
992 }
993
994 if (substr($section, 0, 4) === 'run-') {
995 $modul = substr($section, 4);
996 return $modul !== '' ? $modul : 'Modul';
997 }
998
999 $label = ucwords(str_replace(array('-', '_'), ' ', $section));
1000 return $label !== '' ? $label : ($info !== '' ? $info : 'Timer');
1001 }
1002
1003 private function performance_is_db_section($section) {
1004 $section = (string) $section;
1005 return $section === 'db-total'
1006 || $section === 'db-select'
1007 || $section === 'db-save'
1008 || substr($section, 0, 10) === 'db-select-'
1009 || substr($section, 0, 8) === 'db-save-';
1010 }
1011
1012 private function performance_dd_detail($section, $info = '') {
1013 $section = trim((string) $section);
1014 $info = trim((string) $info);
1015
1016 if (substr($section, 0, 10) === 'db-select-') {
1017 $dd = substr($info, 0, 7) === 'select ' ? trim(substr($info, 7)) : trim(substr($section, 10));
1018 return $dd !== '' ? $dd : 'DD';
1019 }
1020
1021 if (substr($section, 0, 8) === 'db-save-') {
1022 $dd = substr($info, 0, 5) === 'save ' ? trim(substr($info, 5)) : trim(substr($section, 8));
1023 return $dd !== '' ? $dd : 'DD';
1024 }
1025
1026 return null;
1027 }
1028
1029 private function performance_db_sort($row) {
1030 $section = (string) ($row['section'] ?? '');
1031
1032 if ($section === 'db-total') {
1033 return 0;
1034 }
1035
1036 if ($section === 'db-select') {
1037 return 10;
1038 }
1039
1040 if ($section === 'db-save') {
1041 return 20;
1042 }
1043
1044 if (substr($section, 0, 10) === 'db-select-') {
1045 return 100;
1046 }
1047
1048 if (substr($section, 0, 8) === 'db-save-') {
1049 return 200;
1050 }
1051
1052 return 900;
1053 }
1054
1055 private function performance_tone($index) {
1056 $tones = array('teal', 'green', 'navy', 'amber', 'cyan', 'red', 'purple', 'slate');
1057 return $tones[$index % count($tones)];
1058 }
1059
1060 private function performance_rows($metrics, $mode = 'request') {
1061 $rows = array();
1062
1063 if ($mode === 'module') {
1064 $index = 1;
1065 foreach ($this->performance_module_averages() as $module) {
1066 $modul = trim((string) ($module['modul'] ?? ''));
1067 $count = (int) ($module['row_count'] ?? 0);
1068 if ($modul === '' || $count <= 0) {
1069 continue;
1070 }
1071
1072 $totalMs = (int) round((float) ($module['avg_total_ms'] ?? 0));
1073 $dbMs = (int) round((float) ($module['avg_db_ms'] ?? 0));
1074 $phpMs = max(0, $totalMs - $dbMs);
1075 $subtitle = 'Modul ' . $modul . ' · ' . $this->fmt($count) . ' Requests';
1076
1077 $rows[] = array(
1078 'section' => 'module-' . $modul . '-php',
1079 'label' => $modul . ' · PHP gesamt',
1080 'icon' => 'bi-cpu',
1081 'value_ms' => $phpMs,
1082 'detail' => 'PHP gesamt',
1083 'precision' => 3,
1084 'min_display_sec' => 0,
1085 'subtitle' => $subtitle,
1086 'tone' => $this->performance_tone($index),
1087 );
1088
1089 $rows[] = array(
1090 'section' => 'module-' . $modul . '-db',
1091 'label' => $modul . ' · DB gesamt',
1092 'icon' => 'bi-database-check',
1093 'value_ms' => $dbMs,
1094 'detail' => 'DB gesamt',
1095 'precision' => 4,
1096 'min_display_sec' => 0.0001,
1097 'subtitle' => $subtitle,
1098 'tone' => $this->performance_tone($index + 1),
1099 );
1100 $index += 2;
1101 }
1102
1103 return array('rows' => $rows);
1104 }
1105
1106 $segments = $this->performance_timer_averages();
1107 $index = 1;
1108 foreach ($segments as $segment) {
1109 $section = (string) ($segment['section'] ?? '');
1110 $count = (int) ($segment['row_count'] ?? 0);
1111 if ($section === '' || $count <= 0) {
1112 continue;
1113 }
1114
1115 if ($mode === 'db' && $section === 'db-total') {
1116 continue;
1117 }
1118
1119 $isDb = $this->performance_is_db_section($section);
1120 if (($mode === 'db') !== $isDb) {
1121 continue;
1122 }
1123
1124 $rows[] = array(
1125 'section' => $section,
1126 'label' => $this->performance_label($section, $segment['info'] ?? ''),
1127 'icon' => 'bi-stopwatch',
1128 'value_ms' => (int) round((float) ($segment['avg_time_ms'] ?? 0)),
1129 'detail' => $this->performance_dd_detail($section, $segment['info'] ?? ''),
1130 'precision' => $isDb ? 4 : 3,
1131 'min_display_sec' => $isDb ? 0.0001 : 0,
1132 'subtitle' => 'Durchschnitt aus ' . $this->fmt($count) . ' Messungen, Memory ' . $this->fmt_memory_delta_kb($segment['avg_memory_kb'] ?? 0),
1133 'tone' => $this->performance_tone($index),
1134 );
1135 $index++;
1136 }
1137
1138 if ($mode === 'db') {
1139 usort($rows, function ($a, $b) {
1140 $prio = $this->performance_db_sort($a) <=> $this->performance_db_sort($b);
1141 if ($prio !== 0) {
1142 return $prio;
1143 }
1144
1145 return strcmp((string) ($a['detail'] ?? $a['label'] ?? ''), (string) ($b['detail'] ?? $b['label'] ?? ''));
1146 });
1147 }
1148
1149 return array('rows' => $rows);
1150 }
1151
1152 private function performance_module_image_url(string $modul): string {
1153 $modul = trim($modul);
1154 $safeModul = preg_match('/^[A-Za-z0-9_\\-]+$/', $modul) ? $modul : 'dbx';
1155 $baseDir = rtrim((string) dbx()->get_base_dir(), '/\\') . DIRECTORY_SEPARATOR;
1156 $rel = 'dbx/modules/' . $safeModul . '/tpl/img/' . $safeModul . '.png';
1157 $path = $baseDir . str_replace('/', DIRECTORY_SEPARATOR, $rel);
1158
1159 if (!is_file($path)) {
1160 $rel = 'dbx/modules/dbx/tpl/img/dbx.png';
1161 }
1162
1163 return rtrim((string) dbx()->get_base_url(), '/\\') . '/' . $rel;
1164 }
1165
1166 private function performance_metric($row, int $max): string {
1167 $tpl = dbx()->get_system_obj('dbxTPL');
1168 $value = (int) ($row['value_ms'] ?? 0);
1169
1170 return $tpl->get_tpl('dbxAdmin|admin-dashboard-performance-metric', array(
1171 'tone' => (string) ($row['tone'] ?? 'teal'),
1172 'icon' => (string) ($row['icon'] ?? 'bi-speedometer2'),
1173 'label' => (string) ($row['label'] ?? 'Messung'),
1174 'raw' => $value,
1175 'max' => max(1, $max),
1176 'value' => $this->fmt_ms_precision($value, $row['precision'] ?? 3, $row['min_display_sec'] ?? 0),
1177 'detail' => (string) ($row['detail'] ?? ''),
1178 ));
1179 }
1180
1181 private function performance_module_list(): string {
1182 $tpl = dbx()->get_system_obj('dbxTPL');
1183 $max = 6000;
1184 $html = '';
1185 $index = 1;
1186
1187 foreach ($this->performance_module_averages() as $module) {
1188 $modul = trim((string) ($module['modul'] ?? ''));
1189 $count = (int) ($module['row_count'] ?? 0);
1190 if ($modul === '' || $count <= 0) {
1191 continue;
1192 }
1193
1194 $totalMs = (int) round((float) ($module['avg_total_ms'] ?? 0));
1195 $dbMs = (int) round((float) ($module['avg_db_ms'] ?? 0));
1196 $phpMs = max(0, $totalMs - $dbMs);
1197
1198 $metrics = '';
1199 $metrics .= $this->performance_metric(array(
1200 'label' => 'PHP gesamt',
1201 'icon' => 'bi-cpu',
1202 'value_ms' => $phpMs,
1203 'detail' => 'PHP gesamt',
1204 'precision' => 3,
1205 'min_display_sec' => 0,
1206 'tone' => $this->performance_tone($index),
1207 ), $max);
1208 $metrics .= $this->performance_metric(array(
1209 'label' => 'DB gesamt',
1210 'icon' => 'bi-database-check',
1211 'value_ms' => $dbMs,
1212 'detail' => 'DB gesamt',
1213 'precision' => 4,
1214 'min_display_sec' => 0.0001,
1215 'tone' => $this->performance_tone($index + 1),
1216 ), $max);
1217
1218 $html .= $tpl->get_tpl('dbxAdmin|admin-dashboard-performance-module', array(
1219 'module_img' => $this->performance_module_image_url($modul),
1220 'module_name' => $modul,
1221 'request_count' => $this->fmt($count),
1222 'metrics' => $metrics,
1223 ));
1224
1225 $index += 2;
1226 }
1227
1228 return $html;
1229 }
1230
1231 private function speedometer($row, $max) {
1232 $tpl = dbx()->get_system_obj('dbxTPL');
1233 $value = (int) ($row['value_ms'] ?? 0);
1234 $label = (string) ($row['label'] ?? 'Timer');
1235 $icon = (string) ($row['icon'] ?? 'bi-speedometer2');
1236 $tone = (string) ($row['tone'] ?? 'teal');
1237
1238 return $tpl->get_tpl('dbxAdmin|admin-dashboard-speedometer', array(
1239 'bar' => $this->card_bar($label, $icon),
1240 'tone' => $tone,
1241 'raw' => $value,
1242 'max' => max(1, (int) $max),
1243 'value' => $this->fmt_ms_precision($value, $row['precision'] ?? 3, $row['min_display_sec'] ?? 0),
1244 'detail' => $row['detail'] ?? '',
1245 'subtitle' => $row['subtitle'] ?? '',
1246 ));
1247 }
1248
1249 private function speedometer_panel($title, $icon, $subtitle, $rows, $panelClass = '', $panelTarget = '', $controls = '', $barActions = '') {
1250 $tpl = dbx()->get_system_obj('dbxTPL');
1251 $max = 6000;
1252 $items = '';
1253 $panelTarget = trim((string) $panelTarget);
1254 $actions = $barActions . ($panelTarget !== '' ? $this->collapse_action($panelTarget, 'Zuklappen', true) : '');
1255
1256 foreach ($rows as $row) {
1257 $items .= $this->speedometer($row, $max);
1258 }
1259
1260 return $tpl->get_tpl('dbxAdmin|admin-dashboard-performance', array(
1261 'panel_class' => $panelClass,
1262 'panel_target' => $panelTarget,
1263 'performance_bar' => $this->card_bar($title, $icon, $subtitle, $actions),
1264 'performance_controls' => $controls,
1265 'performance_items' => $items,
1266 ));
1267 }
1268
1269 private function performance_panel($metrics) {
1270 $tpl = dbx()->get_system_obj('dbxTPL');
1271 $panelTarget = 'request-performance';
1272 $actions = $this->performance_maintenance_actions() . $this->collapse_action($panelTarget, 'Zuklappen', true);
1273
1274 return $tpl->get_tpl('dbxAdmin|admin-dashboard-performance', array(
1275 'panel_class' => '',
1276 'panel_target' => $panelTarget,
1277 'performance_bar' => $this->card_bar('Performance pro Modul', 'bi-speedometer2', 'Durchschnittswerte je Modul: PHP gesamt und DB gesamt', $actions),
1278 'performance_controls' => $this->performance_level_control(),
1279 'performance_items' => $this->performance_module_list(),
1280 ));
1281 }
1282
1283 private function normalize_sys_msg_level($level): string {
1284 $level = strtolower(trim((string) $level));
1285 if ($level === 'warn') {
1286 $level = 'warning';
1287 }
1288
1289 return in_array($level, array('error', 'warning', 'all'), true) ? $level : 'all';
1290 }
1291
1292 private function sys_msg_level_config(): string {
1293 return $this->normalize_sys_msg_level(dbx()->get_config('dbx', 'sys_msg_level', 'all'));
1294 }
1295
1296 private function sys_msg_level_options(string $current): string {
1297 $options = array(
1298 'error' => 'Nur Error',
1299 'warning' => 'Error + Warning',
1300 'all' => 'Alles',
1301 );
1302 $html = '';
1303
1304 foreach ($options as $value => $label) {
1305 $selected = $value === $current ? ' selected' : '';
1306 $html .= '<option value="' . dbx()->esc($value) . '"' . $selected . '>' . dbx()->esc($label) . '</option>';
1307 }
1308
1309 return $html;
1310 }
1311
1312 private function set_sys_msg_level_config(string $level): bool {
1313 $config = dbx()->get_config('dbx');
1314 if (!is_array($config)) {
1315 $config = array();
1316 }
1317
1318 $config['sys_msg_level'] = $this->normalize_sys_msg_level($level);
1319 return (int) dbx()->set_config('dbx', $config) > 0;
1320 }
1321
1322 private function process_sys_msg_level_action(): bool {
1323 $run2 = dbx()->get_modul_var('dbx_run2', '', 'parameter');
1324 if ($run2 !== 'sysmsg_level_save') {
1325 return false;
1326 }
1327
1328 $level = dbx()->get_request_var('sys_msg_level', null, 'parameter');
1329 if ($level === null || $level === '') {
1330 $level = $_POST['sys_msg_level'] ?? 'all';
1331 }
1332
1333 return $this->set_sys_msg_level_config((string) $level);
1334 }
1335
1336 private function sys_msg_level_control_data(): array {
1337 $level = $this->sys_msg_level_config();
1338 $states = array(
1339 'all' => array(
1340 'tone' => 'on',
1341 'icon' => 'bi-bell-fill',
1342 'label' => 'Systemmeldungen: Alles',
1343 'hint' => 'Alle Systemmeldungen werden gespeichert.',
1344 ),
1345 'warning' => array(
1346 'tone' => 'on',
1347 'icon' => 'bi-exclamation-triangle-fill',
1348 'label' => 'Error + Warning',
1349 'hint' => 'Nur Error und Warning werden gespeichert.',
1350 ),
1351 'error' => array(
1352 'tone' => 'off',
1353 'icon' => 'bi-exclamation-octagon-fill',
1354 'label' => 'Nur Error',
1355 'hint' => 'Nur Fehlermeldungen werden gespeichert.',
1356 ),
1357 );
1358 $state = $states[$level] ?? $states['all'];
1359
1360 return array(
1361 'sys_msg_level_save_base' => dbx()->esc('?dbx_modul=dbxAdmin&dbx_run1=run&dbx_run2=sysmsg_level_save'),
1362 'sys_msg_level_options' => $this->sys_msg_level_options($level),
1363 'sysmsg_status_tone' => dbx()->esc($state['tone']),
1364 'sysmsg_status_icon' => dbx()->esc($state['icon']),
1365 'sysmsg_status_label' => dbx()->esc($state['label']),
1366 'sysmsg_status_hint' => dbx()->esc($state['hint']),
1367 );
1368 }
1369
1370 private function sys_msg_level_control(): string {
1371 $oForm = new \dbxForm();
1372 $oForm->init('admin-dashboard-sysmsg-control', 'admin-dashboard-sysmsg-control');
1373 $oForm->_action = '?dbx_modul=dbxAdmin&dbx_run1=run&dbx_run2=sysmsg_level_save';
1374 $oForm->_msg_info = '';
1375
1376 foreach ($this->sys_msg_level_control_data() as $key => $value) {
1377 $oForm->add_rep($key, $value);
1378 }
1379
1380 return $oForm->add_norep($oForm->run());
1381 }
1382
1383 private function sysmsg_panel_body_html(): string {
1384 $oForm = new \dbxForm();
1385 $oForm->init('admin-dashboard-sysmsg-body', 'admin-dashboard-sysmsg-body');
1386 $oForm->_action = '?dbx_modul=dbxAdmin&dbx_run1=run';
1387 $oForm->_msg_info = '';
1388 $oForm->add_rep('sysmsg_control', $this->sys_msg_level_control());
1389
1390 return $oForm->add_norep($oForm->run());
1391 }
1392
1393 private function sysmsg_panel() {
1394 $oForm = new \dbxForm();
1395 $oForm->init('admin-dashboard-sysmsg-panel', 'admin-dashboard-sysmsg-panel');
1396 $oForm->_action = '?dbx_modul=dbxAdmin&dbx_run1=run';
1397 $oForm->_msg_info = '';
1398 $oForm->add_rep('sysmsg_body', $this->sysmsg_panel_body_html());
1399
1400 return $oForm->add_norep($oForm->run());
1401 }
1402
1403 private function session_db_enabled_config(): bool {
1404 return $this->dbx_config_bool('session_db', 1);
1405 }
1406
1407 private function set_session_db_config(bool $enabled): bool {
1408 $config = dbx()->get_config('dbx');
1409 if (!is_array($config)) {
1410 $config = array();
1411 }
1412
1413 $config['session_db'] = $enabled ? 1 : 0;
1414 return (int) dbx()->set_config('dbx', $config) > 0;
1415 }
1416
1417 private function process_session_db_action(): bool {
1418 $run2 = dbx()->get_modul_var('dbx_run2', '', 'parameter');
1419 if ($run2 !== 'session_db_save') {
1420 return false;
1421 }
1422
1423 return $this->set_session_db_config(isset($_POST['session_db']));
1424 }
1425
1426 private function session_panel_body_data(): array {
1427 $enabled = $this->session_db_enabled_config();
1428
1429 return array(
1430 'session_save_url' => dbx()->esc('?dbx_modul=dbxAdmin&dbx_run1=run&dbx_run2=session_db_save'),
1431 'session_enabled_checked' => $enabled ? 'checked' : '',
1432 'session_status_tone' => $enabled ? 'on' : 'off',
1433 'session_status_icon' => $enabled ? 'bi-check-circle-fill' : 'bi-pause-circle-fill',
1434 'session_status_label' => dbx()->esc($enabled ? 'Session-DB aktiv' : 'Session-DB inaktiv'),
1435 'session_status_hint' => dbx()->esc(
1436 $enabled
1437 ? 'Normale HTTP-Requests und HTML-AJAX-Requests schreiben ihre Session am Request-Ende in die DB.'
1438 : 'Sessions laufen nur ueber PHP-Session; die Session-Liste wird nicht fortgeschrieben.'
1439 ),
1440 );
1441 }
1442
1443 private function session_panel_control_html(): string {
1444 $oForm = new \dbxForm();
1445 $oForm->init('admin-dashboard-session-control', 'admin-dashboard-session-control');
1446 $oForm->_action = '?dbx_modul=dbxAdmin&dbx_run1=run';
1447 $oForm->_msg_info = '';
1448
1449 foreach ($this->session_panel_body_data() as $key => $value) {
1450 $oForm->add_rep($key, $value);
1451 }
1452
1453 return $oForm->add_norep($oForm->run());
1454 }
1455
1456 private function session_panel_body_html(): string {
1457 $oForm = new \dbxForm();
1458 $oForm->init('admin-dashboard-session-body', 'admin-dashboard-session-body');
1459 $oForm->_action = '?dbx_modul=dbxAdmin&dbx_run1=run';
1460 $oForm->_msg_info = '';
1461 $oForm->add_rep('session_control', $this->session_panel_control_html());
1462
1463 return $oForm->add_norep($oForm->run());
1464 }
1465
1466 private function session_panel() {
1467 $oForm = new \dbxForm();
1468 $oForm->init('admin-dashboard-session-panel', 'admin-dashboard-session-panel');
1469 $oForm->_action = '?dbx_modul=dbxAdmin&dbx_run1=run';
1470 $oForm->_msg_info = '';
1471 $oForm->add_rep('session_body', $this->session_panel_body_html());
1472
1473 return $oForm->add_norep($oForm->run());
1474 }
1475
1476 private function module_count() {
1477 $count = 0;
1478 $path = dbx_os_path_file(dbx_get_base_dir() . 'dbx/modules/');
1479
1480 if (!is_dir($path)) {
1481 return 0;
1482 }
1483
1484 $files = scandir($path);
1485 foreach ($files as $file) {
1486 if ($file === '.' || $file === '..') {
1487 continue;
1488 }
1489
1490 if (is_dir($path . $file) && $file !== 'tpl' && substr($file, 0, 1) !== '.') {
1491 $count++;
1492 }
1493 }
1494
1495 return $count;
1496 }
1497
1498 private function dd_inventory() {
1499 $records = array();
1500 $base = dbx_os_path_file(dbx_get_base_dir() . 'dbx/modules/');
1501
1502 if (!is_dir($base)) {
1503 return $records;
1504 }
1505
1506 $modules = scandir($base);
1507 foreach ($modules as $modul) {
1508 if ($modul === '.' || $modul === '..' || substr($modul, 0, 1) === '.') {
1509 continue;
1510 }
1511
1512 $ddPath = dbx_os_path_file($base . $modul . '/dd/');
1513 if (!is_dir($ddPath)) {
1514 continue;
1515 }
1516
1517 $files = scandir($ddPath);
1518 foreach ($files as $file) {
1519 if (!str_ends_with($file, '.dd.php')) {
1520 continue;
1521 }
1522
1523 $dd = str_replace('.dd.php', '', $file);
1524 if ($dd === '' || $dd === 'new') {
1525 continue;
1526 }
1527
1528 $key = $modul . '|' . $dd;
1529
1530 try {
1531 $db = dbx()->get_system_obj('dbxDB');
1532 $tableDef = $db->get_dd_table($key, 1);
1533
1534 if (!is_array($tableDef)) {
1535 continue;
1536 }
1537
1538 $server = (string) ($tableDef['server'] ?? '');
1539 $table = (string) ($tableDef['table'] ?? '');
1540
1541 if ($server === '' || $table === '') {
1542 continue;
1543 }
1544
1545 $exist = $db->get_table_exist($server, $table) ? 1 : 0;
1546 $count = $exist ? $this->safe_count($key) : 0;
1547
1548 $records[] = array(
1549 'dd' => $dd,
1550 'key' => $key,
1551 'modul' => $modul,
1552 'server' => $server,
1553 'table' => $table,
1554 'exist' => $exist,
1555 'count' => $count,
1556 'sync' => (int) ($tableDef['autosync'] ?? 0),
1557 'trace' => (int) ($tableDef['trace'] ?? 0),
1558 );
1559 } catch (\Throwable $e) {
1560 continue;
1561 }
1562 }
1563 }
1564
1565 return $records;
1566 }
1567
1568 private function metrics() {
1569 if ($this->metricCache) {
1570 return $this->metricCache;
1571 }
1572
1573 $this->ensure_history_table();
1574
1575 $inventory = $this->dd_inventory();
1576 $uniqueTables = array();
1577 $uniqueServers = array();
1578 $records = 0;
1579 $existing = 0;
1580 $autosync = 0;
1581 $trace = 0;
1582
1583 foreach ($inventory as $row) {
1584 $server = (string) ($row['server'] ?? '');
1585 $table = (string) ($row['table'] ?? '');
1586 $key = $server . '|' . $table;
1587
1588 if ($server !== '') {
1589 $uniqueServers[$server] = 1;
1590 }
1591
1592 if ($key !== '|' && !isset($uniqueTables[$key])) {
1593 $uniqueTables[$key] = 1;
1594 $records += (int) ($row['count'] ?? 0);
1595 }
1596
1597 if (!empty($row['exist'])) {
1598 $existing++;
1599 }
1600
1601 if (!empty($row['sync'])) {
1602 $autosync++;
1603 }
1604
1605 if (!empty($row['trace'])) {
1606 $trace++;
1607 }
1608 }
1609
1610 $onlineCutoff = date('Y-m-d H:i:s', time() - 900);
1611 $users = $this->safe_count('dbxUser');
1612 $activeUsers = $this->safe_count('dbxUser', "status = 'active'");
1613 $sessions = $this->safe_count('dbxSession');
1614 $online = $this->safe_count('dbxSession', "update_date >= '" . $onlineCutoff . "'");
1615 $sysmsg = $this->safe_count('dbxSysMsg');
1616 $sysmsgRisk = $this->safe_count('dbxSysMsg', "LOWER(status) IN ('warning','error')");
1617 $missing = $this->safe_count('dbxMissing');
1618 $traceCount = $this->safe_count('dbxTrace');
1619
1620 $inventoryCount = count($inventory);
1621 $healthPercent = $this->percent($existing, max(1, $inventoryCount));
1622 if ($sysmsgRisk > 0) {
1623 $healthPercent = max(0, $healthPercent - min(30, $sysmsgRisk * 3));
1624 }
1625 if ($missing > 0) {
1626 $healthPercent = max(0, $healthPercent - min(20, $missing * 2));
1627 }
1628 $healthReason = $this->health_reason_label($inventoryCount, $existing, $sysmsgRisk, $missing);
1629
1630 $this->metricCache = array(
1631 'inventory' => $inventory,
1632 'users' => $users,
1633 'active_users' => $activeUsers,
1634 'sessions' => $sessions,
1635 'online' => $online,
1636 'modules' => $this->module_count(),
1637 'dd_count' => $inventoryCount,
1638 'records' => $records,
1639 'databases' => count($uniqueServers),
1640 'tables' => count($uniqueTables),
1641 'sysmsg' => $sysmsg,
1642 'sysmsg_risk' => $sysmsgRisk,
1643 'missing' => $missing,
1644 'trace_count' => $traceCount,
1645 'existing_dd' => $existing,
1646 'autosync' => $autosync,
1647 'trace_enabled' => $trace,
1648 'health_percent' => $healthPercent,
1649 'health_reason' => $healthReason,
1650 'request_runtime_ms' => $this->request_runtime_ms(),
1651 'memory_peak_kb' => $this->memory_peak_kb(),
1652 );
1653
1654 return $this->metricCache;
1655 }
1656
1657 private function widget($data) {
1658 $tpl = dbx()->get_system_obj('dbxTPL');
1659
1660 $data['bar'] = $this->card_bar($data['label'] ?? '', $data['icon'] ?? 'bi-circle');
1661 $data['label'] = $data['label'] ?? '';
1662 $data['value'] = $data['value'] ?? '';
1663 $data['raw'] = (int) ($data['raw'] ?? 0);
1664 $data['note'] = $data['note'] ?? '';
1665 $data['icon'] = $data['icon'] ?? 'bi-circle';
1666 $data['tone'] = $data['tone'] ?? 'teal';
1667 $data['spark'] = $data['spark'] ?? '';
1668 $data['trend'] = $data['trend'] ?? '';
1669
1670 return $tpl->get_tpl('dbxAdmin|admin-dashboard-widget', $data);
1671 }
1672
1673 private function widgets($metrics, $history) {
1674 $widgets = array(
1675 array(
1676 'label' => 'Benutzer',
1677 'value' => $this->fmt($metrics['users']),
1678 'raw' => $metrics['users'],
1679 'note' => $this->fmt($metrics['active_users']) . ' aktiv',
1680 'icon' => 'bi-people',
1681 'tone' => 'teal',
1682 'spark' => $this->spark_values($history, 'users', $metrics['users']),
1683 'trend' => $this->trend_text($history, 'users'),
1684 ),
1685 array(
1686 'label' => 'Online',
1687 'value' => $this->fmt($metrics['online']),
1688 'raw' => $metrics['online'],
1689 'note' => $this->fmt($metrics['sessions']) . ' Sessions',
1690 'icon' => 'bi-broadcast-pin',
1691 'tone' => 'green',
1692 'spark' => $this->spark_values($history, 'online', $metrics['online']),
1693 'trend' => $this->trend_text($history, 'online'),
1694 ),
1695 array(
1696 'label' => 'Module',
1697 'value' => $this->fmt($metrics['modules']),
1698 'raw' => $metrics['modules'],
1699 'note' => $this->fmt($metrics['dd_count']) . ' DDs',
1700 'icon' => 'bi-grid-1x2',
1701 'tone' => 'navy',
1702 'spark' => $this->spark_values($history, 'modules', $metrics['modules']),
1703 'trend' => $this->trend_text($history, 'modules'),
1704 ),
1705 array(
1706 'label' => 'Datensaetze',
1707 'value' => $this->fmt($metrics['records']),
1708 'raw' => $metrics['records'],
1709 'note' => $this->fmt($metrics['tables']) . ' Tabellen',
1710 'icon' => 'bi-database-check',
1711 'tone' => 'amber',
1712 'spark' => $this->spark_values($history, 'records', $metrics['records']),
1713 'trend' => $this->trend_text($history, 'records'),
1714 ),
1715 array(
1716 'label' => 'Datenbanken',
1717 'value' => $this->fmt($metrics['databases']),
1718 'raw' => $metrics['databases'],
1719 'note' => $this->fmt($metrics['existing_dd']) . ' Quellen ok',
1720 'icon' => 'bi-hdd-stack',
1721 'tone' => 'cyan',
1722 'spark' => $this->spark_values($history, 'databases', $metrics['databases']),
1723 'trend' => $this->trend_text($history, 'databases'),
1724 ),
1725 array(
1726 'label' => 'Systemzustand',
1727 'value' => (int) $metrics['health_percent'] . '%',
1728 'raw' => $metrics['health_percent'],
1729 'note' => $this->fmt($metrics['sysmsg_risk']) . ' Warnungen/Fehler',
1730 'icon' => 'bi-shield-check',
1731 'tone' => 'red',
1732 'spark' => $this->spark_values($history, 'health_percent', $metrics['health_percent']),
1733 'trend' => $this->trend_text($history, 'health_percent', '%'),
1734 ),
1735 array(
1736 'label' => 'Speed',
1737 'value' => $this->fmt($metrics['request_runtime_ms']) . ' ms',
1738 'raw' => $metrics['request_runtime_ms'],
1739 'note' => 'PHP Request',
1740 'icon' => 'bi-speedometer2',
1741 'tone' => 'purple',
1742 'spark' => $this->spark_values($history, 'request_runtime_ms', $metrics['request_runtime_ms']),
1743 'trend' => $this->trend_text($history, 'request_runtime_ms', ' ms'),
1744 ),
1745 array(
1746 'label' => 'DBX Memory',
1747 'value' => $this->fmt($metrics['memory_peak_kb']) . ' KB',
1748 'raw' => $metrics['memory_peak_kb'],
1749 'note' => 'dbx Verbrauch',
1750 'icon' => 'bi-memory',
1751 'tone' => 'slate',
1752 'spark' => $this->spark_values($history, 'memory_peak_kb', $metrics['memory_peak_kb']),
1753 'trend' => $this->trend_text($history, 'memory_peak_kb', ' KB'),
1754 ),
1755 );
1756
1757 $content = '';
1758 foreach ($widgets as $widget) {
1759 $content .= $this->widget($widget);
1760 }
1761
1762 return $content;
1763 }
1764
1765 private function database_rows($metrics) {
1766 $rows = array();
1767 $inventory = $metrics['inventory'] ?? array();
1768
1769 usort($inventory, function ($a, $b) {
1770 return ((int) ($b['count'] ?? 0)) <=> ((int) ($a['count'] ?? 0));
1771 });
1772
1773 $seen = array();
1774 foreach ($inventory as $row) {
1775 $server = (string) ($row['server'] ?? '');
1776 $table = (string) ($row['table'] ?? '');
1777 $key = $server . '|' . $table;
1778
1779 if ($key === '|' || isset($seen[$key])) {
1780 continue;
1781 }
1782
1783 $seen[$key] = 1;
1784 $count = (int) ($row['count'] ?? 0);
1785 $rows[] = array(
1786 'title' => dbx()->esc($row['dd'] ?? $table),
1787 'meta' => dbx()->esc($server . ' / ' . $table),
1788 'count' => $this->fmt($count),
1789 'percent' => $this->percent($count, max(1, (int) ($metrics['records'] ?? 0))),
1790 'status' => !empty($row['exist']) ? 'ok' : 'fehlt',
1791 'tone' => !empty($row['exist']) ? 'ok' : 'warn',
1792 );
1793
1794 if (count($rows) >= 7) {
1795 break;
1796 }
1797 }
1798
1799 if (!$rows) {
1800 $rows[] = array(
1801 'title' => 'Keine Tabellen',
1802 'meta' => 'Noch keine DD-Tabellen gefunden',
1803 'count' => '0',
1804 'percent' => 0,
1805 'status' => 'leer',
1806 'tone' => 'warn',
1807 );
1808 }
1809
1810 return $rows;
1811 }
1812
1813 private function database_report($metrics) {
1814 $oReport = new \dbxReport();
1815 $panelTarget = 'database-report';
1816 $oReport->init('admin-dashboard-db', 'admin-dashboard-db-report');
1817 $oReport->add_obj('database_bar', 'dbx|component-bar', $this->card_bar_data(
1818 'Datenbanken und Tabellen',
1819 'bi-hdd-stack',
1820 '',
1821 $this->card_action('?dbx_modul=dbxAdmin&dbx_run1=db&dbx_run2=list_db', 'DB Sync')
1822 . $this->collapse_action($panelTarget, 'Zuklappen', true)
1823 ));
1824 $oReport->add_rep('panel_target', dbx()->esc($panelTarget));
1825 $oReport->_mode = 'tpl';
1826 $oReport->_pages = 0;
1827 $oReport->_rdata = $this->database_rows($metrics);
1828 $oReport->_rcount = count($oReport->_rdata);
1829 $oReport->_rrows = 20;
1830 $oReport->_msg_info = '';
1831
1832 return $oReport->run();
1833 }
1834
1835 private function activity_rows($metrics) {
1836 $rows = array();
1837 $traceRows = $this->safe_select('dbxTrace', '', array('id', 'create_date', 'action', 'dd', 'record_id'), 'create_date', 'DESC', '', 5);
1838
1839 foreach ($traceRows as $row) {
1840 $title = trim((string) ($row['action'] ?? 'Trace'));
1841 $dd = trim((string) ($row['dd'] ?? ''));
1842 $record = trim((string) ($row['record_id'] ?? ''));
1843
1844 $rows[] = array(
1845 'icon' => 'bi-clock-history',
1846 'title' => dbx()->esc($title !== '' ? ucfirst($title) : 'Trace'),
1847 'meta' => dbx()->esc(($dd !== '' ? $dd : 'Datensatz') . ($record !== '' ? ' #' . $record : '')),
1848 'time' => dbx()->esc($row['create_date'] ?? ''),
1849 'tone' => 'trace',
1850 );
1851 }
1852
1853 if (count($rows) < 5) {
1854 $msgRows = $this->safe_select('dbxSysMsg', '', array('id', 'create_date', 'status', 'modul', 'message'), 'create_date', 'DESC', '', 5 - count($rows));
1855 foreach ($msgRows as $row) {
1856 $message = trim(strip_tags((string) ($row['message'] ?? 'Systemmeldung')));
1857 if (strlen($message) > 92) {
1858 $message = substr($message, 0, 89) . '...';
1859 }
1860
1861 $rows[] = array(
1862 'icon' => 'bi-info-circle',
1863 'title' => dbx()->esc($row['status'] ?? 'Info'),
1864 'meta' => dbx()->esc($message),
1865 'time' => dbx()->esc($row['create_date'] ?? ''),
1866 'tone' => 'msg',
1867 );
1868 }
1869 }
1870
1871 if (!$rows) {
1872 $rows[] = array(
1873 'icon' => 'bi-check2-circle',
1874 'title' => 'Keine Aktivitaet',
1875 'meta' => 'Trace und Systemmeldungen sind aktuell leer.',
1876 'time' => '',
1877 'tone' => 'empty',
1878 );
1879 }
1880
1881 return $rows;
1882 }
1883
1884 private function activity_report($metrics) {
1885 $oReport = new \dbxReport();
1886 $oReport->init('admin-dashboard-activity', 'admin-dashboard-activity-report');
1887 $oReport->add_obj('activity_bar', 'dbx|component-bar', $this->card_bar_data('Aktivitaet', 'bi-clock-history'));
1888 $oReport->_mode = 'tpl';
1889 $oReport->_pages = 0;
1890 $oReport->_rdata = $this->activity_rows($metrics);
1891 $oReport->_rcount = count($oReport->_rdata);
1892 $oReport->_rrows = 10;
1893 $oReport->_msg_info = '';
1894
1895 return $oReport->run();
1896 }
1897
1898 private function quick_actions() {
1899 $oForm = new \dbxForm();
1900 $oForm->init('admin-dashboard-actions', 'admin-dashboard-actions');
1901 $oForm->_action = '?dbx_modul=dbxAdmin&dbx_run1=run';
1902 $oForm->_msg_info = '';
1903 $oForm->add_obj('actions_bar', 'dbx|component-bar', $this->card_bar_data('Quick Actions', 'bi-lightning-charge'));
1904
1905 $actions = array(
1906 'users' => array('href' => '?dbx_modul=dbxAdmin&dbx_run1=user&dbx_run2=list_user', 'icon' => 'bi-people', 'label' => 'Benutzer'),
1907 'sessions' => array('href' => '?dbx_modul=dbxAdmin&dbx_run1=session&dbx_run2=list_session', 'icon' => 'bi-broadcast', 'label' => 'Sessions'),
1908 'modules' => array('href' => '?dbx_modul=dbxAdmin&dbx_run1=modules&dbx_run2=modul_list', 'icon' => 'bi-grid', 'label' => 'Module'),
1909 'dd' => array('href' => '?dbx_modul=dbxAdmin&dbx_run1=dd&dbx_run2=list_dd', 'icon' => 'bi-diagram-3', 'label' => 'DD Sync'),
1910 'db' => array('href' => '?dbx_modul=dbxAdmin&dbx_run1=db&dbx_run2=list_db', 'icon' => 'bi-hdd-stack', 'label' => 'DB Sync'),
1911 'sysmsg' => array('href' => '?dbx_modul=dbxAdmin&dbx_run1=sysmsg&dbx_run2=list_sysmsg', 'icon' => 'bi-bell', 'label' => 'SysMsg'),
1912 );
1913
1914 foreach ($actions as $key => $data) {
1915 $oForm->add_obj('action_' . $key, 'dbxAdmin|admin-dashboard-action-link', $data);
1916 }
1917
1918 return $oForm->run();
1919 }
1920
1921 private function load_content_cache_classes(): void {
1922 dbx()->load_content_cache_classes();
1923 }
1924
1925 private function content_cache_language_catalog(): array {
1926 return array(
1927 'de' => array('label' => 'Deutsch', 'flag' => '🇩🇪', 'tone' => 'teal'),
1928 'en' => array('label' => 'English', 'flag' => '🇬🇧', 'tone' => 'navy'),
1929 'es' => array('label' => 'Español', 'flag' => '🇪🇸', 'tone' => 'amber'),
1930 );
1931 }
1932
1933 private function content_cache_enabled_config(): bool {
1934 return \dbx\dbxContent\dbxContentPageCache::isConfigEnabled();
1935 }
1936
1937 private function dbx_config_bool(string $key, $default = 0): bool {
1938 $value = dbx()->get_config('dbx', $key);
1939 if ($value === 'undef' || $value === '' || $value === null) {
1940 $value = $default;
1941 }
1942
1943 return (int) $value === 1;
1944 }
1945
1946 private function performance_request_enabled_config(): bool {
1947 return $this->dbx_config_bool('performance_timer_request', dbx()->get_config('dbx', 'performance_timer'));
1948 }
1949
1950 private function performance_db_enabled_config(): bool {
1951 return $this->dbx_config_bool('performance_timer_db', dbx()->get_config('dbx', 'performance_timer_detail'));
1952 }
1953
1954 private function normalize_performance_level($level): string {
1955 $level = strtolower(trim((string) $level));
1956 if ($level === 'details') {
1957 $level = 'detail';
1958 }
1959
1960 return in_array($level, array('off', 'main', 'detail'), true) ? $level : 'off';
1961 }
1962
1963 private function performance_level_config(): string {
1964 $level = dbx()->get_config('dbx', 'performance_timer_level');
1965 if ($level !== 'undef' && $level !== '' && $level !== null) {
1966 return $this->normalize_performance_level($level);
1967 }
1968
1969 return ($this->performance_request_enabled_config() || $this->performance_db_enabled_config()) ? 'detail' : 'off';
1970 }
1971
1972 private function set_performance_level_config(string $level): bool {
1973 $level = $this->normalize_performance_level($level);
1974 $config = dbx()->get_config('dbx');
1975 if (!is_array($config)) {
1976 $config = array();
1977 }
1978
1979 $enabled = $level !== 'off';
1980 $detail = $level === 'detail';
1981 $config['performance_timer_level'] = $level;
1982 $config['performance_timer'] = $enabled ? 1 : 0;
1983 $config['performance_timer_request'] = $enabled ? 1 : 0;
1984 $config['performance_timer_db'] = $enabled ? 1 : 0;
1985 $config['performance_timer_detail'] = $detail ? 1 : 0;
1986
1987 return (int) dbx()->set_config('dbx', $config) > 0;
1988 }
1989
1990 private function set_performance_config(string $target, bool $enabled): bool {
1991 if (!$enabled) {
1992 return $this->set_performance_level_config('off');
1993 }
1994
1995 return $this->set_performance_level_config('detail');
1996 }
1997
1998 private function process_performance_config_action(): bool {
1999 $run2 = dbx()->get_modul_var('dbx_run2', '', 'parameter');
2000 if ($run2 !== 'performance_save') {
2001 return false;
2002 }
2003
2004 if (isset($_POST['performance_level'])) {
2005 return $this->set_performance_level_config((string) $_POST['performance_level']);
2006 }
2007
2008 $target = strtolower(trim((string) ($_POST['performance_target'] ?? 'request')));
2009 $target = $target === 'db' ? 'db' : 'request';
2010 $enabled = isset($_POST['performance_enabled']);
2011 return $this->set_performance_config($target, $enabled);
2012 }
2013
2014 private function is_ajax_request(): bool {
2015 return (int) dbx()->get_system_var('dbx_ajax', 0, 'int') === 1;
2016 }
2017
2018 private function respond_dashboard_ajax_html(string $html): void {
2019 if (!headers_sent()) {
2020 header('Content-Type: text/html; charset=utf-8');
2021 }
2022
2023 echo $html;
2024
2025 $oSession = dbx()->get_system_obj('dbxSession');
2026 if (is_object($oSession) && method_exists($oSession, 'save_session')) {
2027 $oSession->save_session();
2028 }
2029
2030 exit;
2031 }
2032
2033 private function performance_level_options(string $current): string {
2034 $options = array(
2035 'off' => 'Aus',
2036 'main' => 'Nur Hauptkennzahlen',
2037 'detail' => 'Hauptkennzahlen und Details',
2038 );
2039 $html = '';
2040
2041 foreach ($options as $value => $label) {
2042 $selected = $value === $current ? ' selected' : '';
2043 $html .= '<option value="' . dbx()->esc($value) . '"' . $selected . '>' . dbx()->esc($label) . '</option>';
2044 }
2045
2046 return $html;
2047 }
2048
2049 private function performance_level_control(): string {
2050 $level = $this->performance_level_config();
2051 $meta = array(
2052 'off' => array(
2053 'label' => 'Performance aus',
2054 'hint' => 'dbxapp schreibt aktuell keine neuen Performance-Daten.',
2055 'icon' => 'bi-pause-circle-fill',
2056 'tone' => 'off',
2057 ),
2058 'main' => array(
2059 'label' => 'Performance Hauptkennzahlen',
2060 'hint' => 'dbxapp schreibt nur PHP/System, JS und DB Gesamtwerte.',
2061 'icon' => 'bi-check-circle-fill',
2062 'tone' => 'on',
2063 ),
2064 'detail' => array(
2065 'label' => 'Performance Details',
2066 'hint' => 'dbxapp schreibt Hauptkennzahlen und Detail-Timer.',
2067 'icon' => 'bi-check-circle-fill',
2068 'tone' => 'on',
2069 ),
2070 );
2071 $state = $meta[$level] ?? $meta['off'];
2072
2073 $oForm = new \dbxForm();
2074 $oForm->init('admin-dashboard-performance-config', 'admin-dashboard-performance-config');
2075 $oForm->_action = '?dbx_modul=dbxAdmin&dbx_run1=run&dbx_run2=performance_save';
2076 $oForm->_msg_info = '';
2077 $oForm->add_rep('action', dbx()->esc('?dbx_modul=dbxAdmin&dbx_run1=run&dbx_run2=performance_save'));
2078 $oForm->add_rep('status_tone', dbx()->esc($state['tone']));
2079 $oForm->add_rep('status_icon', dbx()->esc($state['icon']));
2080 $oForm->add_rep('status_label', dbx()->esc($state['label']));
2081 $oForm->add_rep('status_hint', dbx()->esc($state['hint']));
2082 $oForm->add_rep('performance_level_options', $this->performance_level_options($level));
2083
2084 return $oForm->add_norep($oForm->run());
2085 }
2086
2087 private function performance_toggle_control(string $target): string {
2088 return $this->performance_level_control();
2089 }
2090
2091 private function content_cache_files_for_lng(string $lng): int {
2092 $this->load_content_cache_classes();
2093 $lng = strtolower(trim($lng));
2094 if ($lng === '') {
2095 return 0;
2096 }
2097
2099 return count(glob($base . 'cid-*.' . $lng . '.htm') ?: array());
2100 }
2101
2102 private function content_cache_language_rows(): string {
2103 $this->load_content_cache_classes();
2104 $tpl = dbx()->get_system_obj('dbxTPL');
2105 $catalog = $this->content_cache_language_catalog();
2106 $lngs = function_exists('dbx_accessible_lngs') ? dbx_accessible_lngs() : array('de');
2107 $tones = array('teal', 'navy', 'amber', 'cyan', 'purple', 'green');
2108 $rows = '';
2109 $toneIndex = 0;
2110
2111 foreach ($lngs as $lng) {
2112 $lng = strtolower(trim((string) $lng));
2113 if ($lng === '') {
2114 continue;
2115 }
2116
2117 $meta = $catalog[$lng] ?? array(
2118 'label' => strtoupper($lng),
2119 'flag' => '🌐',
2120 'tone' => $tones[$toneIndex % count($tones)],
2121 );
2122 $toneIndex++;
2123
2124 $pagesTotal = $this->safe_count(\dbx\dbxContent\dbxContentLng::ddContent($lng));
2125 $pagesActive = $this->safe_count(\dbx\dbxContent\dbxContentLng::ddContent($lng), 'activ = 1');
2126 $folders = $this->safe_count(\dbx\dbxContent\dbxContentLng::ddFolder($lng));
2127
2128 $rows .= $tpl->get_tpl('dbxAdmin|admin-dashboard-content-cache-lng', array(
2129 'flag' => dbx()->esc($meta['flag'] ?? '🌐'),
2130 'label' => dbx()->esc($meta['label'] ?? strtoupper($lng)),
2131 'code' => dbx()->esc(strtoupper($lng)),
2132 'tone' => dbx()->esc($meta['tone'] ?? 'teal'),
2133 'pages_total' => $this->fmt($pagesTotal),
2134 'pages_active' => $this->fmt($pagesActive),
2135 'folders' => $this->fmt($folders),
2136 'cached' => $this->fmt($this->content_cache_files_for_lng($lng)),
2137 ));
2138 }
2139
2140 if ($rows === '') {
2141 $rows = '<article class="dbx-admin-dashboard-cache-lng-card dbx-admin-dashboard-cache-lng-empty">'
2142 . '<div class="dbx-admin-dashboard-cache-lng-title"><strong>Keine Sprachen konfiguriert</strong></div>'
2143 . '</article>';
2144 }
2145
2146 return $rows;
2147 }
2148
2149 private function process_content_cache_action(): void {
2150 $run2 = dbx()->get_modul_var('dbx_run2', '', 'parameter');
2151 if ($run2 === '') {
2152 return;
2153 }
2154
2155 $this->load_content_cache_classes();
2156
2157 if ($run2 === 'cache_flush') {
2159 return;
2160 }
2161
2162 if ($run2 === 'sitemap_rebuild') {
2164 return;
2165 }
2166
2167 if ($run2 === 'cache_save') {
2168 $enabled = isset($_POST['cache_content']);
2170 }
2171 }
2172
2173 private function content_cache_panel_body_data(): array {
2174 $this->load_content_cache_classes();
2177 $enabled = $this->content_cache_enabled_config();
2178
2179 return array(
2180 'cache_save_url' => dbx()->esc('?dbx_modul=dbxAdmin&dbx_run1=run&dbx_run2=cache_save'),
2181 'cache_flush_url' => dbx()->esc('?dbx_modul=dbxAdmin&dbx_run1=run&dbx_run2=cache_flush'),
2182 'sitemap_rebuild_url' => dbx()->esc('?dbx_modul=dbxAdmin&dbx_run1=run&dbx_run2=sitemap_rebuild'),
2183 'sitemap_url' => dbx()->esc(rtrim((string) dbx()->get_base_url(), '/') . '/sitemap.xml'),
2184 'cache_admin_url' => dbx()->esc('?dbx_modul=dbxAdmin&dbx_run1=cache'),
2185 'cache_enabled_checked' => $enabled ? 'checked' : '',
2186 'cache_status_tone' => $enabled ? 'on' : 'off',
2187 'cache_status_icon' => $enabled ? 'bi-check-circle-fill' : 'bi-pause-circle-fill',
2188 'cache_status_label' => dbx()->esc($enabled ? 'Cache aktiv' : 'Cache inaktiv'),
2189 'cache_status_hint' => $enabled
2190 ? dbx()->esc('Nur die Rueckgabe von dbxContent wird bei gueltigen Permalink-Aufrufen je Seite und Sprache gecacht.')
2191 . '<br>'
2192 . dbx()->esc('Enthaltene Module werden danach aufgeloest.')
2193 : dbx()->esc('dbxContent wird bei jedem Aufruf neu gerendert.'),
2194 'cache_content_count' => $this->fmt((int) ($stats['content'] ?? 0)),
2195 'sitemap_count' => $this->fmt((int) ($sitemapStats['urls'] ?? 0)),
2196 'sitemap_generated' => dbx()->esc((string) ($sitemapStats['generated_at'] ?? '')),
2197 'sitemap_state' => dbx()->esc(!empty($sitemapStats['exists']) ? 'vorhanden' : 'nicht erstellt'),
2198 'cache_dir' => dbx()->esc((string) ($stats['base_dir'] ?? '')),
2199 'lng_rows' => $this->content_cache_language_rows(),
2200 );
2201 }
2202
2203 private function content_cache_panel_body_html(): string {
2204 $oForm = new \dbxForm();
2205 $oForm->init('admin-dashboard-content-cache-body', 'admin-dashboard-content-cache-body');
2206 $oForm->_action = '?dbx_modul=dbxAdmin&dbx_run1=run';
2207 $oForm->_msg_info = '';
2208
2209 foreach ($this->content_cache_panel_body_data() as $key => $value) {
2210 $oForm->add_rep($key, $value);
2211 }
2212
2213 return $oForm->add_norep($oForm->run());
2214 }
2215
2216 private function content_cache_bar_actions_html(): string {
2217 $this->load_content_cache_classes();
2218 $enabled = $this->content_cache_enabled_config();
2219 $oForm = new \dbxForm();
2220 $oForm->init('admin-dashboard-content-cache-actions', 'admin-dashboard-content-cache-actions');
2221 $oForm->_action = '?dbx_modul=dbxAdmin&dbx_run1=run&dbx_run2=cache_save';
2222 $oForm->_msg_info = '';
2223 $oForm->add_rep('cache_save_url', dbx()->esc('?dbx_modul=dbxAdmin&dbx_run1=run&dbx_run2=cache_save'));
2224 $oForm->add_rep('cache_flush_url', dbx()->esc('?dbx_modul=dbxAdmin&dbx_run1=run&dbx_run2=cache_flush'));
2225 $oForm->add_rep('sitemap_rebuild_url', dbx()->esc('?dbx_modul=dbxAdmin&dbx_run1=run&dbx_run2=sitemap_rebuild'));
2226 $oForm->add_rep('cache_enabled_checked', $enabled ? 'checked' : '');
2227
2228 return $oForm->add_norep($oForm->run());
2229 }
2230
2231 private function content_cache_panel() {
2232 $oForm = new \dbxForm();
2233 $panelTarget = 'content-cache';
2234 $oForm->init('admin-dashboard-content-cache', 'admin-dashboard-content-cache');
2235 $oForm->_action = '?dbx_modul=dbxAdmin&dbx_run1=run';
2236 $oForm->_msg_info = '';
2237 $oForm->add_obj('cache_bar', 'dbx|component-bar', $this->card_bar_data(
2238 'dbxContent-Ausgabe-Cache',
2239 'bi-lightning-charge-fill',
2240 'Ein Cache nur fuer die Modulausgabe von dbxContent bei gueltigen Permalinks',
2241 $this->content_cache_bar_actions_html()
2242 . $this->card_action('?dbx_modul=dbxContent_admin&dbx_run1=content&dbx_run2=edit', 'CMS')
2243 . $this->collapse_action($panelTarget, 'Zuklappen', true)
2244 ));
2245 $oForm->add_rep('panel_target', dbx()->esc($panelTarget));
2246 $oForm->add_rep('cache_body', $this->content_cache_panel_body_html());
2247
2248 return $oForm->run();
2249 }
2250
2251 private function hero_panel($metrics) {
2252 $heroPerformance = $this->hero_performance($metrics);
2253 $oForm = new \dbxForm();
2254 $oForm->init('admin-dashboard-hero', 'admin-dashboard-hero');
2255 $oForm->_msg_info = '';
2256 $oForm->add_rep('bar_class', 'dbx-admin-dashboard-hero-bar');
2257 $oForm->add_obj('bar_actions', 'obj-value', '<small class="dbx-bar-meta">Stand ' . dbx()->esc(date('d.m.Y H:i')) . '</small>');
2258 $oForm->add_rep('health_percent', (int) $metrics['health_percent']);
2259 $oForm->add_rep('health_reason', dbx()->esc($metrics['health_reason'] ?? 'OK'));
2260 $oForm->add_obj('hero_performance_gauges', 'obj-value', $this->hero_performance_gauges($heroPerformance));
2261 $oForm->add_obj('hero_status_summaries', 'obj-value', $this->hero_status_summaries());
2262
2263 return $oForm->run();
2264 }
2265
2266 private function widgets_panel($metrics) {
2267 $oForm = new \dbxForm();
2268 $oForm->init('admin-dashboard-widgets', 'admin-dashboard-widgets');
2269 $oForm->_msg_info = '';
2270 $oForm->add_obj('widgets', 'obj-value', $this->widgets($metrics, $this->metric_history($metrics)));
2271
2272 return $oForm->run();
2273 }
2274
2275 private function chart_json($metrics) {
2276 $data = array(
2277 array('label' => 'Benutzer', 'value' => (int) $metrics['users'], 'tone' => 'teal'),
2278 array('label' => 'Online', 'value' => (int) $metrics['online'], 'tone' => 'green'),
2279 array('label' => 'Module', 'value' => (int) $metrics['modules'], 'tone' => 'navy'),
2280 array('label' => 'DDs', 'value' => (int) $metrics['dd_count'], 'tone' => 'cyan'),
2281 array('label' => 'DBs', 'value' => (int) $metrics['databases'], 'tone' => 'amber'),
2282 );
2283
2284 return dbx()->esc(json_encode($data, JSON_UNESCAPED_UNICODE | JSON_HEX_APOS | JSON_HEX_QUOT));
2285 }
2286
2287 private function chart_panel($metrics) {
2288 $oForm = new \dbxForm();
2289 $oForm->init('admin-dashboard-chart-panel', 'admin-dashboard-chart-panel');
2290 $oForm->_msg_info = '';
2291 $oForm->add_rep('chart_json', $this->chart_json($metrics));
2292 $oForm->add_rep('trace_count', $this->fmt($metrics['trace_count']));
2293 $oForm->add_rep('missing_count', $this->fmt($metrics['missing']));
2294 $oForm->add_rep('autosync_count', $this->fmt($metrics['autosync']));
2295 $oForm->add_rep('trace_enabled_count', $this->fmt($metrics['trace_enabled']));
2296 $oForm->add_rep('request_runtime_ms', $this->fmt($metrics['request_runtime_ms']));
2297 $oForm->add_rep('memory_peak_kb', $this->fmt($metrics['memory_peak_kb']));
2298 $oForm->add_obj('chart_bar', 'dbx|component-bar', $this->card_bar_data('Grafische Auswertung', 'bi-bar-chart-line', 'Relative Verteilung'));
2299
2300 return $oForm->run();
2301 }
2302
2303 private function dashboard_area($area) {
2304 $metrics = $this->metrics();
2305
2306 switch ((string) $area) {
2307 case 'hero':
2308 return $this->hero_panel($metrics);
2309 case 'widgets':
2310 return $this->widgets_panel($metrics);
2311 case 'quick_actions':
2312 return $this->quick_actions();
2313 case 'performance_panel':
2314 return $this->performance_panel($metrics);
2315 case 'sysmsg_panel':
2316 return $this->sysmsg_panel();
2317 case 'session_panel':
2318 return $this->session_panel();
2319 case 'content_cache_panel':
2320 return $this->content_cache_panel();
2321 case 'chart_panel':
2322 return $this->chart_panel($metrics);
2323 case 'activity_report':
2324 return $this->activity_report($metrics);
2325 case 'database_report':
2326 return $this->database_report($metrics);
2327 }
2328
2329 return null;
2330 }
2331
2332 public function run() {
2333 $run2 = dbx()->get_modul_var('dbx_run2', '', 'parameter');
2334 if ($run2 === 'performance_compress') {
2335 return $this->run_performance_maintenance_process('compress');
2336 }
2337 if ($run2 === 'performance_clear') {
2338 return $this->run_performance_maintenance_process('clear');
2339 }
2340
2341 if ($this->is_ajax_request()) {
2342 if ($run2 === 'cache_flush' || $run2 === 'cache_save' || $run2 === 'sitemap_rebuild') {
2343 $this->process_content_cache_action();
2344 $this->respond_dashboard_ajax_html($this->content_cache_panel_body_html());
2345 }
2346
2347 if ($run2 === 'performance_save') {
2348 $this->process_performance_config_action();
2349 $target = strtolower(trim((string) ($_POST['performance_target'] ?? 'request')));
2350 $target = $target === 'db' ? 'db' : 'request';
2351 $this->respond_dashboard_ajax_html($this->performance_toggle_control($target));
2352 }
2353
2354 if ($run2 === 'sysmsg_level_save') {
2355 $this->process_sys_msg_level_action();
2356 $this->respond_dashboard_ajax_html($this->sys_msg_level_control());
2357 }
2358
2359 if ($run2 === 'session_db_save') {
2360 $this->process_session_db_action();
2361 $this->respond_dashboard_ajax_html($this->session_panel_control_html());
2362 }
2363 }
2364
2365 if ($run2 === 'performance_save') {
2366 $this->process_performance_config_action();
2367 }
2368 if ($run2 === 'sysmsg_level_save') {
2369 $this->process_sys_msg_level_action();
2370 }
2371 if ($run2 === 'session_db_save') {
2372 $this->process_session_db_action();
2373 }
2374 if ($run2 === 'cache_flush' || $run2 === 'cache_save' || $run2 === 'sitemap_rebuild') {
2375 $this->process_content_cache_action();
2376 }
2377
2378 if ($run2 !== '') {
2379 $areaContent = $this->dashboard_area($run2);
2380 if ($areaContent !== null) {
2381 return $areaContent;
2382 }
2383 }
2384
2385 $oForm = new \dbxForm();
2386
2387 $oForm->init('admin-dashboard', 'admin-dashboard');
2388 $oForm->_action = '?dbx_modul=dbxAdmin&dbx_run1=run';
2389 $oForm->_msg_info = '';
2390
2391 $metrics = $this->metrics();
2392 $this->store_history_snapshot($metrics);
2393
2394 $content = $oForm->run();
2395
2396 return $content;
2397 }
2398}
$table['server']
Definition .dd.php:6
$index['name']
Definition .dd.php:162
Gemeinsame Basisklasse fuer dbXapp-System-, Modul- und Include-Objekte.
Definition dbxKernel.php:63
$fields[]
Definition config.dd.php:16
$field
Definition config.dd.php:4
$config['version']
Definition config.php:2
dbx_os_path_file($path_file)
Definition index.php:164
exit
Definition index.php:532
dbx_get_base_dir($cut_Data=0)
Definition index.php:157
DBX schema administration.
if(! $db->connect_db_server($server)) $result