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;
15 private $metricCache = array();
16 private $historyReady =
null;
17 private $performanceRequestAverage =
null;
18 private $performanceTimerAverages =
null;
19 private $performanceModuleAverages =
null;
21 private function fmt($value) {
22 $value = (int) $value;
23 return number_format($value, 0,
',',
'.');
26 private function percent($value, $max) {
27 $value = (float) $value;
34 return max(0, min(100, (
int) round(($value / $max) * 100)));
37 private function health_reason_label(
int $inventoryCount,
int $existingCount,
int $sysmsgRisk,
int $missing):
string {
41 $reasons[] =
'Missing';
44 if ($sysmsgRisk > 0) {
45 $reasons[] =
'SysMsg';
48 if ($existingCount < $inventoryCount) {
52 return count($reasons) ? implode(
'/', $reasons) :
'OK';
55 private function request_runtime_ms() {
56 return max(0, (
int) round(
dbx()->current_php_runtime() * 1000));
59 private function memory_peak_kb() {
60 $bytes = (int)
dbx()->current_memory_bytes();
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']
68 if ($startMemory > 0) {
69 $bytes = max(0, (
int) memory_get_peak_usage() - $startMemory);
74 $bytes = (int) memory_get_usage();
77 return max(1, (
int) ceil($bytes / 1024));
80 private function card_action($href, $label) {
81 $tpl =
dbx()->get_system_obj(
'dbxTPL');
83 return $tpl->get_tpl(
'dbxAdmin|admin-dashboard-card-action', array(
89 private function collapse_action($target, $label =
'Aufklappen', $expanded =
false) {
90 $tpl =
dbx()->get_system_obj(
'dbxTPL');
92 return $tpl->get_tpl(
'dbxAdmin|admin-dashboard-collapse-action', array(
95 'expanded' => $expanded ?
'true' :
'false',
99 private function card_bar_data($title, $icon, $subtitle =
'', $action =
'') {
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,
106 'bar_subtitle' => $subtitle,
107 'bar_title_pre' =>
'',
108 'bar_title_heading_attrs' =>
'',
109 'bar_actions' => $action,
113 'subtitle' => $subtitle,
118 private function card_bar($title, $icon, $subtitle =
'', $action =
'') {
119 $tpl =
dbx()->get_system_obj(
'dbxTPL');
121 return $tpl->get_tpl(
'dbx|component-bar', $this->card_bar_data($title, $icon, $subtitle, $action));
124 private function safe_count($dd, $where =
'') {
128 $db =
dbx()->get_system_obj(
'dbxDB');
129 $res =
$db->count($dd, $where);
130 if (is_numeric($res) && (
int) $res > 0) {
133 }
catch (\Throwable $e) {
140 private function safe_select($dd, $where =
'', $columns =
'*', $orderby =
'', $asc_desc =
'ASC', $groupby =
'', $max = 0, $offset = 0) {
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)) {
149 }
catch (\Throwable $e) {
156 private function ensure_performance_tables() {
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) {
165 private function ensure_history_table() {
166 if ($this->historyReady !==
null) {
167 return $this->historyReady;
170 $this->historyReady =
false;
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;
179 return $this->historyReady;
182 private function ensure_history_columns() {
183 $server =
'dbxAdmin|dbxAdmin.db3';
184 $table =
'dbx_admin_dashboard_metric';
187 $db =
dbx()->get_system_obj(
'dbxDB');
190 if (!is_array($rows)) {
195 foreach ($rows as $row) {
196 $name = (string) ($row[
'name'] ??
'');
198 $existing[$name] = 1;
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' =>
''),
208 $dd =
dbx()->get_system_obj(
'dbxDD');
216 }
catch (\Throwable $e) {
221 private function history_bucket_time() {
222 $bucketSeconds = self::HISTORY_BUCKET_MINUTES * 60;
223 return (
int) (floor(time() / $bucketSeconds) * $bucketSeconds);
226 private function history_snapshot_record($metrics, $bucketTime) {
227 $now = date(
'Y-m-d H:i:s');
228 $uid = (int)
dbx()->user();
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,
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),
255 private function store_history_snapshot($metrics) {
256 if (!$this->ensure_history_table()) {
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;
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;
272 return $db->insert(self::HISTORY_DD, $record, 0, 1, 1, 0) > 0;
273 }
catch (\Throwable $e) {
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()));
283 $rows = $this->safe_select(
286 array(
'snapshot_date',
'users',
'online',
'modules',
'records',
'databases',
'health_percent',
'request_runtime_ms',
'memory_peak_kb'),
295 return array($this->history_snapshot_record($metrics, $this->history_bucket_time()));
298 return array_reverse($rows);
301 private function metric_series_definitions() {
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'),
314 private function metric_history($metrics) {
315 $rows = $this->metric_history_rows($metrics);
319 foreach ($this->metric_series_definitions() as $key => $def) {
320 $series[$key] = array(
322 'label' => $def[
'label'],
323 'tone' => $def[
'tone'],
328 foreach ($rows as $row) {
329 $time = strtotime((
string) ($row[
'snapshot_date'] ??
''));
330 $labels[] = $time ? date(
'd.m. H:i', $time) :
'';
332 foreach ($series as $key => $def) {
333 $series[$key][
'values'][] = (int) ($row[$key] ?? 0);
340 'series' => array_values($series),
344 private function history_values($history, $key, $current = 0) {
347 foreach (($history[
'rows'] ?? array()) as $row) {
348 $values[] = (int) ($row[$key] ?? 0);
352 $values[] = (int) $current;
355 if (count($values) === 1) {
356 $values[] = $values[0];
362 private function spark_values($history, $key, $current = 0) {
363 return implode(
',', $this->history_values($history, $key, $current));
366 private function trend_text($history, $key, $suffix =
'') {
367 $values = $this->history_values($history, $key, 0);
369 if (count($values) < 2) {
370 return '1 Messpunkt';
373 $first = (int) reset($values);
374 $last = (int) end($values);
375 $delta = $last - $first;
378 return 'unveraendert';
381 return ($delta > 0 ?
'+' :
'') . $this->fmt($delta) . $suffix .
' im Verlauf';
384 private function fmt_ms($value) {
385 $value = max(0, (
int) $value);
386 return number_format($value / 1000, 3,
',',
'.') .
' Sec';
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;
394 if ((
float) $minSeconds > 0 && $seconds < (
float) $minSeconds) {
395 $seconds = (float) $minSeconds;
398 return number_format($seconds, $precision,
',',
'.') .
' Sec';
401 private function fmt_memory_kb($value) {
402 $value = max(0, (
int) $value);
404 if ($value >= 1024) {
405 return number_format($value / 1024, 1,
',',
'.') .
' MB';
408 return $this->fmt($value) .
' KB';
411 private function fmt_memory_delta_kb($value) {
412 $value = max(0, (
int) $value);
413 return '+' . $this->fmt_memory_kb($value);
416 private function performance_dd_info($dd) {
418 $db =
dbx()->get_system_obj(
'dbxDB');
419 $table = (string)
$db->get_dd_table($dd);
422 if (!preg_match(
'/^[A-Za-z0-9_]+$/',
$table)) {
423 return array(
'',
'');
427 }
catch (\Throwable $e) {
428 return array(
'',
'');
432 private function performance_table_count(
string $dd):
int {
434 $db =
dbx()->get_system_obj(
'dbxDB');
437 }
catch (\Throwable $e) {
442 private function performance_now_record_base(): array {
443 $uid = (int)
dbx()->user();
444 $now = date(
'Y-m-d H:i:s');
447 'create_date' => $now,
448 'create_uid' => $uid,
449 'update_date' => $now,
450 'update_uid' => $uid,
455 private function optimize_performance_db(): array {
456 $result = array(
'ok' =>
true,
'messages' => array());
457 $db =
dbx()->get_system_obj(
'dbxDB');
460 foreach (array(self::PERF_REQUEST_DD, self::PERF_TIMER_DD) as $dd) {
468 foreach ($groups as
$server => $tables) {
469 $tables = array_values(array_unique($tables));
470 $type = strtolower((
string)
$db->get_db_type(
$server));
472 if ($type ===
'mysql') {
474 foreach ($tables as
$table) {
475 $quoted[] =
'`' . str_replace(
'`',
'``',
$table) .
'`';
477 $ok =
$db->exec(
$server,
'OPTIMIZE TABLE ' . implode(
', ', $quoted));
479 $result[
'messages'][] = $ok ?
'MySQL OPTIMIZE TABLE ausgefuehrt.' :
'MySQL OPTIMIZE TABLE fehlgeschlagen.';
483 if ($type ===
'sqlite') {
487 $result[
'messages'][] = $vacuum ?
'SQLite VACUUM ausgefuehrt.' :
'SQLite VACUUM fehlgeschlagen.';
489 $result[
'messages'][] =
'SQLite ANALYZE ausgefuehrt.';
494 $result[
'messages'][] =
'Keine Optimierung fuer DB-Typ ' . $type .
' definiert.';
500 private function compress_performance_db(): 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,
508 'messages' => array(),
511 if (!$this->ensure_performance_tables()) {
512 $stats[
'messages'][] =
'Performance-Tabellen konnten nicht vorbereitet werden.';
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.';
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");
527 $db->empty(self::PERF_TIMER_DD);
528 $db->empty(self::PERF_REQUEST_DD);
530 $base = $this->performance_now_record_base();
532 $requestDate = date(
'Y-m-d H:i:s');
533 $requestCount = is_array($requestRows) ? (int) ($requestRows[0][
'row_count'] ?? 0) : 0;
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',
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),
553 if (
$db->insert(self::PERF_REQUEST_DD, $request, 0, 1, 1, 0) === 1) {
554 $requestId =
$db->get_insert_id();
555 $stats[
'inserted']++;
559 if (is_array($timerRows)) {
561 foreach ($timerRows as $row) {
562 $section = trim((
string) ($row[
'section'] ??
''));
563 if ($section ===
'') {
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)),
579 if (
$db->insert(self::PERF_TIMER_DD, $timer, 0, 1, 1, 0) === 1) {
580 $stats[
'inserted']++;
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);
595 private function clear_performance_db(): 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,
603 'messages' => array(),
606 if (!$this->ensure_performance_tables()) {
607 $stats[
'messages'][] =
'Performance-Tabellen konnten nicht vorbereitet werden.';
611 $db =
dbx()->get_system_obj(
'dbxDB');
612 $db->empty(self::PERF_TIMER_DD);
613 $db->empty(self::PERF_REQUEST_DD);
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);
624 private function render_performance_process(
string $title, array $stats):
string {
625 $tpl =
dbx()->get_system_obj(
'dbxTPL');
626 $ok = (bool) ($stats[
'ok'] ??
false);
628 foreach (($stats[
'messages'] ?? array()) as $message) {
629 if (trim((
string) $message) !==
'') {
630 $messages[] =
dbx()->esc($message);
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) :
'');
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')),
657 'continue_url' =>
'',
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',
671 private function run_performance_maintenance_process(
string $mode):
string {
672 dbx()->set_system_var(
'dbx_performance_timer_skip', 1);
674 if ($mode ===
'clear') {
675 return $this->render_performance_process(
'Performance DB leeren und optimieren', $this->clear_performance_db());
678 return $this->render_performance_process(
'Performance DB komprimieren und optimieren', $this->compress_performance_db());
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)
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');
692 private function performance_request_average() {
693 if ($this->performanceRequestAverage !==
null) {
694 return $this->performanceRequestAverage;
697 $this->performanceRequestAverage = array();
699 if (!$this->ensure_performance_tables()) {
700 return $this->performanceRequestAverage;
703 list(
$server,
$table) = $this->performance_dd_info(self::PERF_REQUEST_DD);
705 return $this->performanceRequestAverage;
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();
713 if ((
int) ($row[
'row_count'] ?? 0) <= 0) {
714 return $this->performanceRequestAverage;
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)),
723 }
catch (\Throwable $e) {
724 $this->performanceRequestAverage = array();
727 return $this->performanceRequestAverage;
730 private function performance_timer_averages() {
731 if ($this->performanceTimerAverages !==
null) {
732 return $this->performanceTimerAverages;
735 $this->performanceTimerAverages = array();
737 if (!$this->ensure_performance_tables()) {
738 return $this->performanceTimerAverages;
741 list(
$server,
$table) = $this->performance_dd_info(self::PERF_TIMER_DD);
743 return $this->performanceTimerAverages;
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
750 WHERE section <> 'system'
752 ORDER BY MIN(sort_order) ASC, section ASC";
754 $this->performanceTimerAverages = is_array($rows) ? $rows : array();
755 }
catch (\Throwable $e) {
756 $this->performanceTimerAverages = array();
759 return $this->performanceTimerAverages;
762 private function performance_timer_average($section) {
763 $section = (string) $section;
765 foreach ($this->performance_timer_averages() as $row) {
766 if ((
string) ($row[
'section'] ??
'') === $section) {
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)),
778 private function performance_module_averages() {
779 if ($this->performanceModuleAverages !==
null) {
780 return $this->performanceModuleAverages;
783 $this->performanceModuleAverages = array();
785 if (!$this->ensure_performance_tables()) {
786 return $this->performanceModuleAverages;
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;
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
803 SELECT request_id, SUM(time_ms) AS db_time_ms
805 WHERE section = 'db-total'
807 ) d ON d.request_id = r.id
808 WHERE TRIM(COALESCE(r.modul, '')) <> ''
810 ORDER BY AVG(r.total_time_ms) DESC, r.modul ASC
812 $rows =
$db->select_query($requestServer, $sql);
813 $this->performanceModuleAverages = is_array($rows) ? $rows : array();
814 }
catch (\Throwable $e) {
815 $this->performanceModuleAverages = array();
818 return $this->performanceModuleAverages;
821 private function hero_performance($metrics) {
822 $request = $this->performance_request_average();
823 $dbTotal = $this->performance_timer_average(
'db-total');
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;
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),
837 'php_share' => $this->fmt($phpShare),
838 'db_avg' => $this->fmt_ms($dbMs),
840 'db_count' => $this->fmt($dbTotal[
'count'] ?? 0),
841 'db_share' => $this->fmt($dbShare),
845 private function hero_performance_gauge($label, $icon, $tone, $raw, $value, $subtitle) {
846 $tpl =
dbx()->get_system_obj(
'dbxTPL');
848 return $tpl->get_tpl(
'dbxAdmin|admin-dashboard-hero-gauge', array(
852 'raw' => max(0, (
int) $raw),
855 'subtitle' => $subtitle,
859 private function hero_performance_gauges($heroPerformance) {
861 $items .= $this->hero_performance_gauge(
865 $heroPerformance[
'request_raw'] ?? 0,
866 $heroPerformance[
'request_avg'] ??
'0,000 Sec',
867 'Durchschnitt aus ' . ($heroPerformance[
'request_count'] ??
'0') .
' Requests'
869 $items .= $this->hero_performance_gauge(
873 $heroPerformance[
'php_raw'] ?? 0,
874 $heroPerformance[
'php_avg'] ??
'0,000 Sec',
875 'Durchschnitt ohne DB-Anteil'
877 $items .= $this->hero_performance_gauge(
881 $heroPerformance[
'db_raw'] ?? 0,
882 $heroPerformance[
'db_avg'] ??
'0,000 Sec',
883 'Durchschnitt aus DB-Timern'
889 private function hero_summary_rows(array $items):
string {
892 foreach ($items as $item) {
893 $max = max($max, (
int) ($item[
'count'] ?? 0));
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>'
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');
913 return $tpl->get_tpl(
'dbxAdmin|admin-dashboard-hero-summary', array(
915 'subtitle' => $subtitle,
918 'total' => $this->fmt($total),
919 'rows' => $this->hero_summary_rows($rows),
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'),
934 $sysmsgDd =
'dbxSysMsg';
935 $sysmsgTotal = $this->safe_count($sysmsgDd);
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'),
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);
947 private function performance_label($section, $info =
'') {
948 $section = trim((
string) $section);
949 $info = trim((
string) $info);
951 if ($section ===
'db-total') {
955 if ($section ===
'js-total') {
959 if ($section ===
'system') {
960 return 'PHP/System Gesamt';
963 if ($section ===
'db-select') {
964 return 'DB Select gesamt';
967 if ($section ===
'db-save') {
968 return 'DB Save gesamt';
971 if (substr($section, 0, 10) ===
'db-select-') {
975 if (substr($section, 0, 8) ===
'db-save-') {
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',
990 if (isset($known[$section])) {
991 return $known[$section];
994 if (substr($section, 0, 4) ===
'run-') {
995 $modul = substr($section, 4);
996 return $modul !==
'' ? $modul :
'Modul';
999 $label = ucwords(str_replace(array(
'-',
'_'),
' ', $section));
1000 return $label !==
'' ? $label : ($info !==
'' ? $info :
'Timer');
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-';
1012 private function performance_dd_detail($section, $info =
'') {
1013 $section = trim((
string) $section);
1014 $info = trim((
string) $info);
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';
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';
1029 private function performance_db_sort($row) {
1030 $section = (string) ($row[
'section'] ??
'');
1032 if ($section ===
'db-total') {
1036 if ($section ===
'db-select') {
1040 if ($section ===
'db-save') {
1044 if (substr($section, 0, 10) ===
'db-select-') {
1048 if (substr($section, 0, 8) ===
'db-save-') {
1055 private function performance_tone(
$index) {
1056 $tones = array(
'teal',
'green',
'navy',
'amber',
'cyan',
'red',
'purple',
'slate');
1057 return $tones[
$index % count($tones)];
1060 private function performance_rows($metrics, $mode =
'request') {
1063 if ($mode ===
'module') {
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) {
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';
1078 'section' =>
'module-' . $modul .
'-php',
1079 'label' => $modul .
' · PHP gesamt',
1081 'value_ms' => $phpMs,
1082 'detail' =>
'PHP gesamt',
1084 'min_display_sec' => 0,
1085 'subtitle' => $subtitle,
1086 'tone' => $this->performance_tone(
$index),
1090 'section' =>
'module-' . $modul .
'-db',
1091 'label' => $modul .
' · DB gesamt',
1092 'icon' =>
'bi-database-check',
1093 'value_ms' => $dbMs,
1094 'detail' =>
'DB gesamt',
1096 'min_display_sec' => 0.0001,
1097 'subtitle' => $subtitle,
1098 'tone' => $this->performance_tone(
$index + 1),
1103 return array(
'rows' => $rows);
1106 $segments = $this->performance_timer_averages();
1108 foreach ($segments as $segment) {
1109 $section = (string) ($segment[
'section'] ??
'');
1110 $count = (int) ($segment[
'row_count'] ?? 0);
1111 if ($section ===
'' ||
$count <= 0) {
1115 if ($mode ===
'db' && $section ===
'db-total') {
1119 $isDb = $this->performance_is_db_section($section);
1120 if (($mode ===
'db') !== $isDb) {
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),
1138 if ($mode ===
'db') {
1139 usort($rows,
function ($a, $b) {
1140 $prio = $this->performance_db_sort($a) <=> $this->performance_db_sort($b);
1145 return strcmp((
string) ($a[
'detail'] ?? $a[
'label'] ??
''), (
string) ($b[
'detail'] ?? $b[
'label'] ??
''));
1149 return array(
'rows' => $rows);
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);
1159 if (!is_file($path)) {
1160 $rel =
'dbx/modules/dbx/tpl/img/dbx.png';
1163 return rtrim((
string)
dbx()->get_base_url(),
'/\\') .
'/' . $rel;
1166 private function performance_metric($row,
int $max):
string {
1167 $tpl =
dbx()->get_system_obj(
'dbxTPL');
1168 $value = (int) ($row[
'value_ms'] ?? 0);
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'),
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'] ??
''),
1181 private function performance_module_list():
string {
1182 $tpl =
dbx()->get_system_obj(
'dbxTPL');
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) {
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);
1199 $metrics .= $this->performance_metric(array(
1200 'label' =>
'PHP gesamt',
1202 'value_ms' => $phpMs,
1203 'detail' =>
'PHP gesamt',
1205 'min_display_sec' => 0,
1206 'tone' => $this->performance_tone(
$index),
1208 $metrics .= $this->performance_metric(array(
1209 'label' =>
'DB gesamt',
1210 'icon' =>
'bi-database-check',
1211 'value_ms' => $dbMs,
1212 'detail' =>
'DB gesamt',
1214 'min_display_sec' => 0.0001,
1215 'tone' => $this->performance_tone(
$index + 1),
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,
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');
1238 return $tpl->get_tpl(
'dbxAdmin|admin-dashboard-speedometer', array(
1239 'bar' => $this->card_bar($label, $icon),
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'] ??
'',
1249 private function speedometer_panel($title, $icon, $subtitle, $rows, $panelClass =
'', $panelTarget =
'', $controls =
'', $barActions =
'') {
1250 $tpl =
dbx()->get_system_obj(
'dbxTPL');
1253 $panelTarget = trim((
string) $panelTarget);
1254 $actions = $barActions . ($panelTarget !==
'' ? $this->collapse_action($panelTarget,
'Zuklappen',
true) :
'');
1256 foreach ($rows as $row) {
1257 $items .= $this->speedometer($row, $max);
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,
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);
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(),
1283 private function normalize_sys_msg_level($level):
string {
1284 $level = strtolower(trim((
string) $level));
1285 if ($level ===
'warn') {
1289 return in_array($level, array(
'error',
'warning',
'all'),
true) ? $level :
'all';
1292 private function sys_msg_level_config():
string {
1293 return $this->normalize_sys_msg_level(
dbx()->get_config(
'dbx',
'sys_msg_level',
'all'));
1296 private function sys_msg_level_options(
string $current):
string {
1298 'error' =>
'Nur Error',
1299 'warning' =>
'Error + Warning',
1304 foreach ($options as $value => $label) {
1305 $selected = $value === $current ?
' selected' :
'';
1306 $html .=
'<option value="' .
dbx()->esc($value) .
'"' . $selected .
'>' .
dbx()->esc($label) .
'</option>';
1312 private function set_sys_msg_level_config(
string $level):
bool {
1318 $config[
'sys_msg_level'] = $this->normalize_sys_msg_level($level);
1319 return (
int)
dbx()->set_config(
'dbx',
$config) > 0;
1322 private function process_sys_msg_level_action():
bool {
1323 $run2 =
dbx()->get_modul_var(
'dbx_run2',
'',
'parameter');
1324 if ($run2 !==
'sysmsg_level_save') {
1328 $level =
dbx()->get_request_var(
'sys_msg_level',
null,
'parameter');
1329 if ($level ===
null || $level ===
'') {
1330 $level = $_POST[
'sys_msg_level'] ??
'all';
1333 return $this->set_sys_msg_level_config((
string) $level);
1336 private function sys_msg_level_control_data(): array {
1337 $level = $this->sys_msg_level_config();
1341 'icon' =>
'bi-bell-fill',
1342 'label' =>
'Systemmeldungen: Alles',
1343 'hint' =>
'Alle Systemmeldungen werden gespeichert.',
1347 'icon' =>
'bi-exclamation-triangle-fill',
1348 'label' =>
'Error + Warning',
1349 'hint' =>
'Nur Error und Warning werden gespeichert.',
1353 'icon' =>
'bi-exclamation-octagon-fill',
1354 'label' =>
'Nur Error',
1355 'hint' =>
'Nur Fehlermeldungen werden gespeichert.',
1358 $state = $states[$level] ?? $states[
'all'];
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']),
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 =
'';
1376 foreach ($this->sys_msg_level_control_data() as $key => $value) {
1377 $oForm->add_rep($key, $value);
1380 return $oForm->add_norep($oForm->run());
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());
1390 return $oForm->add_norep($oForm->run());
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());
1400 return $oForm->add_norep($oForm->run());
1403 private function session_db_enabled_config():
bool {
1404 return $this->dbx_config_bool(
'session_db', 1);
1407 private function set_session_db_config(
bool $enabled):
bool {
1413 $config[
'session_db'] = $enabled ? 1 : 0;
1414 return (
int)
dbx()->set_config(
'dbx',
$config) > 0;
1417 private function process_session_db_action():
bool {
1418 $run2 =
dbx()->get_modul_var(
'dbx_run2',
'',
'parameter');
1419 if ($run2 !==
'session_db_save') {
1423 return $this->set_session_db_config(isset($_POST[
'session_db']));
1426 private function session_panel_body_data(): array {
1427 $enabled = $this->session_db_enabled_config();
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(
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.'
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 =
'';
1449 foreach ($this->session_panel_body_data() as $key => $value) {
1450 $oForm->add_rep($key, $value);
1453 return $oForm->add_norep($oForm->run());
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());
1463 return $oForm->add_norep($oForm->run());
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());
1473 return $oForm->add_norep($oForm->run());
1476 private function module_count() {
1480 if (!is_dir($path)) {
1484 $files = scandir($path);
1485 foreach ($files as $file) {
1486 if ($file ===
'.' || $file ===
'..') {
1490 if (is_dir($path . $file) && $file !==
'tpl' && substr($file, 0, 1) !==
'.') {
1498 private function dd_inventory() {
1502 if (!is_dir(
$base)) {
1506 $modules = scandir(
$base);
1507 foreach ($modules as $modul) {
1508 if ($modul ===
'.' || $modul ===
'..' || substr($modul, 0, 1) ===
'.') {
1513 if (!is_dir($ddPath)) {
1517 $files = scandir($ddPath);
1518 foreach ($files as $file) {
1519 if (!str_ends_with($file,
'.dd.php')) {
1523 $dd = str_replace(
'.dd.php',
'', $file);
1524 if ($dd ===
'' || $dd ===
'new') {
1528 $key = $modul .
'|' . $dd;
1531 $db =
dbx()->get_system_obj(
'dbxDB');
1532 $tableDef =
$db->get_dd_table($key, 1);
1534 if (!is_array($tableDef)) {
1538 $server = (string) ($tableDef[
'server'] ??
'');
1539 $table = (string) ($tableDef[
'table'] ??
'');
1546 $count = $exist ? $this->safe_count($key) : 0;
1556 'sync' => (
int) ($tableDef[
'autosync'] ?? 0),
1557 'trace' => (
int) ($tableDef[
'trace'] ?? 0),
1559 }
catch (\Throwable $e) {
1568 private function metrics() {
1569 if ($this->metricCache) {
1570 return $this->metricCache;
1573 $this->ensure_history_table();
1575 $inventory = $this->dd_inventory();
1576 $uniqueTables = array();
1577 $uniqueServers = array();
1583 foreach ($inventory as $row) {
1584 $server = (string) ($row[
'server'] ??
'');
1585 $table = (string) ($row[
'table'] ??
'');
1592 if ($key !==
'|' && !isset($uniqueTables[$key])) {
1593 $uniqueTables[$key] = 1;
1594 $records += (int) ($row[
'count'] ?? 0);
1597 if (!empty($row[
'exist'])) {
1601 if (!empty($row[
'sync'])) {
1605 if (!empty($row[
'trace'])) {
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');
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));
1626 $healthPercent = max(0, $healthPercent - min(20, $missing * 2));
1628 $healthReason = $this->health_reason_label($inventoryCount, $existing, $sysmsgRisk, $missing);
1630 $this->metricCache = array(
1631 'inventory' => $inventory,
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(),
1654 return $this->metricCache;
1657 private function widget($data) {
1658 $tpl =
dbx()->get_system_obj(
'dbxTPL');
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'] ??
'';
1670 return $tpl->get_tpl(
'dbxAdmin|admin-dashboard-widget', $data);
1673 private function widgets($metrics, $history) {
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',
1682 'spark' => $this->spark_values($history,
'users', $metrics[
'users']),
1683 'trend' => $this->trend_text($history,
'users'),
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',
1692 'spark' => $this->spark_values($history,
'online', $metrics[
'online']),
1693 'trend' => $this->trend_text($history,
'online'),
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',
1702 'spark' => $this->spark_values($history,
'modules', $metrics[
'modules']),
1703 'trend' => $this->trend_text($history,
'modules'),
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',
1712 'spark' => $this->spark_values($history,
'records', $metrics[
'records']),
1713 'trend' => $this->trend_text($history,
'records'),
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',
1722 'spark' => $this->spark_values($history,
'databases', $metrics[
'databases']),
1723 'trend' => $this->trend_text($history,
'databases'),
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',
1732 'spark' => $this->spark_values($history,
'health_percent', $metrics[
'health_percent']),
1733 'trend' => $this->trend_text($history,
'health_percent',
'%'),
1737 'value' => $this->fmt($metrics[
'request_runtime_ms']) .
' ms',
1738 'raw' => $metrics[
'request_runtime_ms'],
1739 'note' =>
'PHP Request',
1740 'icon' =>
'bi-speedometer2',
1742 'spark' => $this->spark_values($history,
'request_runtime_ms', $metrics[
'request_runtime_ms']),
1743 'trend' => $this->trend_text($history,
'request_runtime_ms',
' ms'),
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',
1752 'spark' => $this->spark_values($history,
'memory_peak_kb', $metrics[
'memory_peak_kb']),
1753 'trend' => $this->trend_text($history,
'memory_peak_kb',
' KB'),
1758 foreach ($widgets as $widget) {
1759 $content .= $this->widget($widget);
1765 private function database_rows($metrics) {
1767 $inventory = $metrics[
'inventory'] ?? array();
1769 usort($inventory,
function ($a, $b) {
1770 return ((
int) ($b[
'count'] ?? 0)) <=> ((int) ($a[
'count'] ?? 0));
1774 foreach ($inventory as $row) {
1775 $server = (string) ($row[
'server'] ??
'');
1776 $table = (string) ($row[
'table'] ??
'');
1779 if ($key ===
'|' || isset($seen[$key])) {
1784 $count = (int) ($row[
'count'] ?? 0);
1786 'title' =>
dbx()->esc($row[
'dd'] ??
$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',
1794 if (count($rows) >= 7) {
1801 'title' =>
'Keine Tabellen',
1802 'meta' =>
'Noch keine DD-Tabellen gefunden',
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',
1821 $this->card_action(
'?dbx_modul=dbxAdmin&dbx_run1=db&dbx_run2=list_db',
'DB Sync')
1822 . $this->collapse_action($panelTarget,
'Zuklappen',
true)
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 =
'';
1832 return $oReport->run();
1835 private function activity_rows($metrics) {
1837 $traceRows = $this->safe_select(
'dbxTrace',
'', array(
'id',
'create_date',
'action',
'dd',
'record_id'),
'create_date',
'DESC',
'', 5);
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'] ??
''));
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'] ??
''),
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) .
'...';
1862 'icon' =>
'bi-info-circle',
1863 'title' =>
dbx()->esc($row[
'status'] ??
'Info'),
1864 'meta' =>
dbx()->esc($message),
1865 'time' =>
dbx()->esc($row[
'create_date'] ??
''),
1873 'icon' =>
'bi-check2-circle',
1874 'title' =>
'Keine Aktivitaet',
1875 'meta' =>
'Trace und Systemmeldungen sind aktuell leer.',
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 =
'';
1895 return $oReport->run();
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'));
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'),
1914 foreach ($actions as $key => $data) {
1915 $oForm->add_obj(
'action_' . $key,
'dbxAdmin|admin-dashboard-action-link', $data);
1918 return $oForm->run();
1921 private function load_content_cache_classes():
void {
1922 dbx()->load_content_cache_classes();
1925 private function content_cache_language_catalog(): 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'),
1933 private function content_cache_enabled_config():
bool {
1934 return \dbx\dbxContent\dbxContentPageCache::isConfigEnabled();
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) {
1943 return (
int) $value === 1;
1946 private function performance_request_enabled_config():
bool {
1947 return $this->dbx_config_bool(
'performance_timer_request',
dbx()->get_config(
'dbx',
'performance_timer'));
1950 private function performance_db_enabled_config():
bool {
1951 return $this->dbx_config_bool(
'performance_timer_db',
dbx()->get_config(
'dbx',
'performance_timer_detail'));
1954 private function normalize_performance_level($level):
string {
1955 $level = strtolower(trim((
string) $level));
1956 if ($level ===
'details') {
1960 return in_array($level, array(
'off',
'main',
'detail'),
true) ? $level :
'off';
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);
1969 return ($this->performance_request_enabled_config() || $this->performance_db_enabled_config()) ?
'detail' :
'off';
1972 private function set_performance_level_config(
string $level):
bool {
1973 $level = $this->normalize_performance_level($level);
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;
1987 return (
int)
dbx()->set_config(
'dbx',
$config) > 0;
1990 private function set_performance_config(
string $target,
bool $enabled):
bool {
1992 return $this->set_performance_level_config(
'off');
1995 return $this->set_performance_level_config(
'detail');
1998 private function process_performance_config_action():
bool {
1999 $run2 =
dbx()->get_modul_var(
'dbx_run2',
'',
'parameter');
2000 if ($run2 !==
'performance_save') {
2004 if (isset($_POST[
'performance_level'])) {
2005 return $this->set_performance_level_config((
string) $_POST[
'performance_level']);
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);
2014 private function is_ajax_request():
bool {
2015 return (
int)
dbx()->get_system_var(
'dbx_ajax', 0,
'int') === 1;
2018 private function respond_dashboard_ajax_html(
string $html):
void {
2019 if (!headers_sent()) {
2020 header(
'Content-Type: text/html; charset=utf-8');
2025 $oSession =
dbx()->get_system_obj(
'dbxSession');
2026 if (is_object($oSession) && method_exists($oSession,
'save_session')) {
2027 $oSession->save_session();
2033 private function performance_level_options(
string $current):
string {
2036 'main' =>
'Nur Hauptkennzahlen',
2037 'detail' =>
'Hauptkennzahlen und Details',
2041 foreach ($options as $value => $label) {
2042 $selected = $value === $current ?
' selected' :
'';
2043 $html .=
'<option value="' .
dbx()->esc($value) .
'"' . $selected .
'>' .
dbx()->esc($label) .
'</option>';
2049 private function performance_level_control():
string {
2050 $level = $this->performance_level_config();
2053 'label' =>
'Performance aus',
2054 'hint' =>
'dbxapp schreibt aktuell keine neuen Performance-Daten.',
2055 'icon' =>
'bi-pause-circle-fill',
2059 'label' =>
'Performance Hauptkennzahlen',
2060 'hint' =>
'dbxapp schreibt nur PHP/System, JS und DB Gesamtwerte.',
2061 'icon' =>
'bi-check-circle-fill',
2065 'label' =>
'Performance Details',
2066 'hint' =>
'dbxapp schreibt Hauptkennzahlen und Detail-Timer.',
2067 'icon' =>
'bi-check-circle-fill',
2071 $state = $meta[$level] ?? $meta[
'off'];
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));
2084 return $oForm->add_norep($oForm->run());
2087 private function performance_toggle_control(
string $target):
string {
2088 return $this->performance_level_control();
2091 private function content_cache_files_for_lng(
string $lng):
int {
2092 $this->load_content_cache_classes();
2093 $lng = strtolower(trim($lng));
2099 return count(glob(
$base .
'cid-*.' . $lng .
'.htm') ?: array());
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');
2111 foreach ($lngs as $lng) {
2112 $lng = strtolower(trim((
string) $lng));
2117 $meta = $catalog[$lng] ?? array(
2118 'label' => strtoupper($lng),
2120 'tone' => $tones[$toneIndex % count($tones)],
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));
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)),
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>'
2149 private function process_content_cache_action():
void {
2150 $run2 =
dbx()->get_modul_var(
'dbx_run2',
'',
'parameter');
2155 $this->load_content_cache_classes();
2157 if ($run2 ===
'cache_flush') {
2162 if ($run2 ===
'sitemap_rebuild') {
2167 if ($run2 ===
'cache_save') {
2168 $enabled = isset($_POST[
'cache_content']);
2173 private function content_cache_panel_body_data(): array {
2174 $this->load_content_cache_classes();
2177 $enabled = $this->content_cache_enabled_config();
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.')
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(),
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 =
'';
2209 foreach ($this->content_cache_panel_body_data() as $key => $value) {
2210 $oForm->add_rep($key, $value);
2213 return $oForm->add_norep($oForm->run());
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' :
'');
2228 return $oForm->add_norep($oForm->run());
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)
2245 $oForm->add_rep(
'panel_target',
dbx()->esc($panelTarget));
2246 $oForm->add_rep(
'cache_body', $this->content_cache_panel_body_html());
2248 return $oForm->run();
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());
2263 return $oForm->run();
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)));
2272 return $oForm->run();
2275 private function chart_json($metrics) {
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'),
2284 return dbx()->esc(json_encode($data, JSON_UNESCAPED_UNICODE | JSON_HEX_APOS | JSON_HEX_QUOT));
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'));
2300 return $oForm->run();
2303 private function dashboard_area($area) {
2304 $metrics = $this->metrics();
2306 switch ((
string) $area) {
2308 return $this->hero_panel($metrics);
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();
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);
2333 $run2 =
dbx()->get_modul_var(
'dbx_run2',
'',
'parameter');
2334 if ($run2 ===
'performance_compress') {
2335 return $this->run_performance_maintenance_process(
'compress');
2337 if ($run2 ===
'performance_clear') {
2338 return $this->run_performance_maintenance_process(
'clear');
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());
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));
2354 if ($run2 ===
'sysmsg_level_save') {
2355 $this->process_sys_msg_level_action();
2356 $this->respond_dashboard_ajax_html($this->sys_msg_level_control());
2359 if ($run2 ===
'session_db_save') {
2360 $this->process_session_db_action();
2361 $this->respond_dashboard_ajax_html($this->session_panel_control_html());
2365 if ($run2 ===
'performance_save') {
2366 $this->process_performance_config_action();
2368 if ($run2 ===
'sysmsg_level_save') {
2369 $this->process_sys_msg_level_action();
2371 if ($run2 ===
'session_db_save') {
2372 $this->process_session_db_action();
2374 if ($run2 ===
'cache_flush' || $run2 ===
'cache_save' || $run2 ===
'sitemap_rebuild') {
2375 $this->process_content_cache_action();
2379 $areaContent = $this->dashboard_area($run2);
2380 if ($areaContent !==
null) {
2381 return $areaContent;
2385 $oForm = new \dbxForm();
2387 $oForm->init(
'admin-dashboard',
'admin-dashboard');
2388 $oForm->_action =
'?dbx_modul=dbxAdmin&dbx_run1=run';
2389 $oForm->_msg_info =
'';
2391 $metrics = $this->metrics();
2392 $this->store_history_snapshot($metrics);
2394 $content = $oForm->run();