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;
20 private $dashboardMessageKey =
'';
21 private $dashboardMessageError =
false;
22 private $updateStatusCache =
null;
27 private function default_admin_password_warning():
string {
29 $db =
dbx()->get_system_obj(
'dbxDB');
30 if (!is_object(
$db)) {
35 array(
'uname' =>
'admin'),
39 }
catch (\Throwable $exception) {
43 return $this->default_admin_password_warning_html(
45 (
string)(
$admin[
'pass'] ??
'')
49 private function default_admin_password_warning_html(
53 if ($adminId <= 0 || $hash ===
'' || !password_verify(
'123456', $hash)) {
57 $url =
'?dbx_modul=dbxAdmin&dbx_run1=user'
58 .
'&dbx_run2=edit_user&rid=' . $adminId
59 .
'&dbx_page=admin';
60 return '<div class="alert alert-warning dbx-admin-dashboard-password-warning" role="alert">'
61 .
'<div class="dbx-admin-dashboard-password-warning-icon">'
62 .
'<i class="bi bi-shield-exclamation" aria-hidden="true"></i></div>'
63 .
'<div class="dbx-admin-dashboard-password-warning-copy">'
64 .
'<strong>Unsicheres Installationspasswort aktiv</strong>'
65 .
'<span>Der Administrator verwendet noch den öffentlichen Standardzugang '
66 .
'<code>admin / 123456</code>. Ändern Sie das Passwort jetzt.</span></div>'
67 .
'<a class="btn btn-warning fw-semibold" href="' .
$url .
'">'
68 .
'<i class="bi bi-key me-1" aria-hidden="true"></i>Passwort ändern</a>'
72 private function fmt(
$value) {
74 return number_format(
$value, 0,
',',
'.');
77 private function percent(
$value, $max) {
85 return max(0, min(100, (
int) round((
$value / $max) * 100)));
88 private function health_reason_label(
int $inventoryCount,
int $existingCount,
int $sysmsgRisk,
int $missing):
string {
92 $reasons[] =
'Missing';
95 if ($sysmsgRisk > 0) {
96 $reasons[] =
'SysMsg';
99 if ($existingCount < $inventoryCount) {
103 return count($reasons) ? implode(
'/', $reasons) :
'OK';
112 private function process_error_log_action():
void {
113 $work =
dbx()->get_modul_var(
'dbx_do',
'',
'parameter');
114 if (
$work !==
'delete_error_log') {
118 $sysMsg =
dbx()->get_include_obj(
'dbxSysMsg');
122 $this->dashboardMessageKey =
'error_log_deleted';
123 } elseif (
$result ===
'empty') {
124 $this->dashboardMessageKey =
'error_log_empty';
126 $this->dashboardMessageKey =
'error_log_delete_error';
127 $this->dashboardMessageError =
true;
130 $this->metricCache = array();
140 $sysMsg =
dbx()->get_include_obj(
'dbxSysMsg');
141 if (!
$sysMsg->error_log_exists()) {
151 clearstatcache(
true,
$file);
152 $size = @filesize(
$file);
153 $size = $size ===
false ? strlen(
$content) : (int)$size;
155 $action =
dbx()->action_url(
156 '?dbx_modul=dbxAdmin&dbx_run1=dashboard&dbx_run2=delete_error_log'
157 .
'&dbx_do=delete_error_log&rid=error_log'
160 return dbx()->get_system_obj(
'dbxTPL')->get_tpl(
161 'dbxAdmin|admin-dashboard-error-log',
163 'error_log_title' =>
dbx()->
esc(
$texts->get_fd_message(
'error_log_title')),
164 'error_log_subtitle' =>
dbx()->
esc(
$texts->get_fd_message(
'error_log_subtitle')),
165 'file_label' =>
dbx()->
esc(
$texts->get_fd_message(
'error_log_file_label')),
166 'file' =>
'files/dbxError.log',
167 'size' => $this->fmt($size),
168 'bytes_label' =>
dbx()->
esc(
$texts->get_fd_message(
'error_log_bytes')),
169 'content' => htmlspecialchars(
171 ENT_QUOTES | ENT_SUBSTITUTE,
174 'delete_action' =>
dbx()->
esc($action),
175 'delete_label' =>
dbx()->
esc(
$texts->get_fd_message(
'error_log_delete_label')),
176 'delete_title' =>
dbx()->
esc(
$texts->get_fd_message(
'error_log_delete_title')),
177 'delete_confirm' =>
dbx()->
esc(
$texts->get_fd_message(
'error_log_delete_confirm')),
178 'delete_hint' =>
dbx()->
esc(
$texts->get_fd_message(
'error_log_delete_hint')),
193 private function update_status(): array {
194 if (is_array($this->updateStatusCache)) {
195 return $this->updateStatusCache;
199 dbx()->get_include_obj(
'dbxUpdateService',
'dbxAdmin');
201 $this->updateStatusCache[
'status_available'] =
true;
202 }
catch (\Throwable $exception) {
203 $this->updateStatusCache = array(
204 'current_version' =>
'',
205 'available_version' =>
'',
206 'update_available' =>
false,
208 'staged_version' =>
'',
209 'stop_available' =>
false,
210 'status_available' =>
false,
214 return $this->updateStatusCache;
223 private function update_state(array $status): array {
224 $current = (string)($status[
'current_version'] ??
'');
225 $staged = (string)($status[
'staged_version'] ??
'');
226 $ready = $staged !==
''
227 && ($current ===
'' || version_compare($staged, $current,
'>'));
232 'icon' =>
'bi-shield-check',
233 'status_message' =>
'update_status_ready',
234 'action_message' =>
'update_action_ready',
235 'nav_message' =>
'update_nav_ready',
236 'action_class' =>
'btn-primary',
240 if (!empty($status[
'update_available'])) {
242 'class' =>
'available',
243 'icon' =>
'bi-cloud-arrow-down-fill',
244 'status_message' =>
'update_status_available',
245 'action_message' =>
'update_action_available',
246 'nav_message' =>
'update_nav_available',
247 'action_class' =>
'btn-warning',
251 if (!empty($status[
'status_available'])
252 && (
string)($status[
'checked_at'] ??
'') !==
'') {
254 'class' =>
'current',
255 'icon' =>
'bi-check-circle-fill',
256 'status_message' =>
'update_status_current',
257 'action_message' =>
'update_action_current',
258 'nav_message' =>
'update_nav_current',
259 'action_class' =>
'btn-outline-primary',
264 'class' =>
'unknown',
265 'icon' =>
'bi-question-circle-fill',
266 'status_message' =>
'update_status_unknown',
267 'action_message' =>
'update_action_unknown',
268 'nav_message' =>
'update_nav_unknown',
269 'action_class' =>
'btn-outline-primary',
276 private function update_status_panel(\
dbxForm $texts):
string {
277 $status = $this->update_status();
278 $state = $this->update_state($status);
279 $current = trim((
string)($status[
'current_version'] ??
''));
280 $available = trim((
string)($status[
'available_version'] ??
''));
281 $staged = trim((
string)($status[
'staged_version'] ??
''));
283 if (
$state[
'class'] ===
'ready') {
284 $statusText =
$texts->format_fd_message(
286 array(
'version' => $staged)
288 } elseif (
$state[
'class'] ===
'available') {
289 $statusText =
$texts->format_fd_message(
291 array(
'version' => $available)
294 $statusText =
$texts->get_fd_message(
$state[
'status_message']);
297 $versionText = $available !==
''
298 ?
$texts->format_fd_message(
300 array(
'current' => $current,
'available' => $available)
302 :
$texts->format_fd_message(
303 'update_version_current',
304 array(
'current' => $current !==
'' ? $current :
'–')
306 $checkedAt = strtotime((
string)($status[
'checked_at'] ??
''));
307 $checkedText = $checkedAt
308 ?
$texts->format_fd_message(
310 array(
'date' => date(
'd.m.Y H:i', $checkedAt))
312 :
$texts->get_fd_message(
'update_not_checked');
314 return dbx()->get_system_obj(
'dbxTPL')->get_tpl(
315 'dbxAdmin|admin-dashboard-update-status',
317 'update_title' =>
dbx()->
esc(
$texts->get_fd_message(
'update_title')),
320 'status_text' =>
dbx()->
esc($statusText),
321 'version_text' =>
dbx()->
esc($versionText),
322 'checked_text' =>
dbx()->
esc($checkedText),
324 'action_label' =>
dbx()->
esc(
331 private function request_runtime_ms() {
332 $runtime =
dbx()->get_system_obj(
'dbxRuntime');
333 return max(0, (
int)round($runtime->current_php_runtime() * 1000));
336 private function memory_peak_kb() {
337 $bytes = (int)
dbx()->get_system_obj(
'dbxRuntime')->current_memory_bytes();
340 global $dbx_run_timer;
341 $startMemory = isset($dbx_run_timer[
'system'][
'start_memory']) && is_numeric($dbx_run_timer[
'system'][
'start_memory'])
342 ? (int) $dbx_run_timer[
'system'][
'start_memory']
345 if ($startMemory > 0) {
346 $bytes = max(0, (
int) memory_get_peak_usage() - $startMemory);
351 $bytes = (int) memory_get_usage();
354 return max(1, (
int) ceil($bytes / 1024));
357 private function card_action($href, $label) {
358 $tpl =
dbx()->get_system_obj(
'dbxTPL');
360 return $tpl->get_tpl(
'dbxAdmin|admin-dashboard-card-action', array(
366 private function collapse_action(
$target, $label =
'Aufklappen', $expanded =
false) {
367 $tpl =
dbx()->get_system_obj(
'dbxTPL');
369 return $tpl->get_tpl(
'dbxAdmin|admin-dashboard-collapse-action', array(
372 'expanded' => $expanded ?
'true' :
'false',
376 private function help_action(
string $topic):
string {
378 $help =
dbx()->get_include_obj(
'dbxAdminHelp',
'dbxAdmin');
379 return (
string)
$help->button($topic);
380 }
catch (\Throwable $e) {
385 private function card_bar_data($title, $icon, $subtitle =
'', $action =
'') {
387 'bar_class' =>
'dbx-module-bar',
388 'bar_title_class' =>
'dbx-module-bar-titleblock',
389 'bar_actions_class' =>
'dbx-module-bar-actions',
390 'bar_title' => $title,
392 'bar_subtitle' => $subtitle,
393 'bar_title_pre' =>
'',
394 'bar_title_heading_attrs' =>
'',
395 'bar_actions' => $action,
399 'subtitle' => $subtitle,
404 private function card_bar($title, $icon, $subtitle =
'', $action =
'') {
405 $tpl =
dbx()->get_system_obj(
'dbxTPL');
407 return $tpl->get_tpl(
'dbx|component-bar', $this->card_bar_data($title, $icon, $subtitle, $action));
410 private function safe_count(
$dd, $where =
'') {
414 $db =
dbx()->get_system_obj(
'dbxDB');
415 $res =
$db->count(
$dd, $where);
416 if (is_numeric($res) && (
int) $res > 0) {
419 }
catch (\Throwable $e) {
426 private function safe_select(
$dd, $where =
'', $columns =
'*', $orderby =
'', $asc_desc =
'ASC', $groupby =
'', $max = 0, $offset = 0) {
430 $db =
dbx()->get_system_obj(
'dbxDB');
431 $res =
$db->select(
$dd, $where, $columns, $orderby, $asc_desc, $groupby, $max, $offset, 0);
432 if (is_array($res)) {
435 }
catch (\Throwable $e) {
442 private function ensure_performance_tables() {
444 $dd =
dbx()->get_system_obj(
'dbxDD');
445 return $dd->create_db_tab(self::PERF_REQUEST_DD) &&
$dd->create_db_tab(self::PERF_TIMER_DD);
446 }
catch (\Throwable $e) {
451 private function ensure_history_table() {
452 if ($this->historyReady !==
null) {
453 return $this->historyReady;
456 $this->historyReady =
false;
459 $dd =
dbx()->get_system_obj(
'dbxDD');
460 $this->historyReady =
$dd->create_db_tab(self::HISTORY_DD) ? $this->ensure_history_columns() :
false;
461 }
catch (\Throwable $e) {
462 $this->historyReady =
false;
465 return $this->historyReady;
468 private function ensure_history_columns() {
469 $server =
'dbxAdmin|dbxAdmin.db3';
470 $table =
'dbx_admin_dashboard_metric';
473 $db =
dbx()->get_system_obj(
'dbxDB');
476 if (!is_array(
$rows)) {
481 foreach (
$rows as $row) {
482 $name = (string) ($row[
'name'] ??
'');
484 $existing[$name] = 1;
489 'request_runtime_ms' => array(
'name' =>
'request_runtime_ms',
'type' =>
'int',
'length' =>
'11',
'default' =>
'0',
'index' =>
''),
490 'memory_peak_mb' => array(
'name' =>
'memory_peak_mb',
'type' =>
'int',
'length' =>
'11',
'default' =>
'0',
'index' =>
''),
491 'memory_peak_kb' => array(
'name' =>
'memory_peak_kb',
'type' =>
'int',
'length' =>
'11',
'default' =>
'0',
'index' =>
''),
494 $dd =
dbx()->get_system_obj(
'dbxDD');
502 }
catch (\Throwable $e) {
507 private function history_bucket_time() {
508 $bucketSeconds = self::HISTORY_BUCKET_MINUTES * 60;
509 return (
int) (floor(time() / $bucketSeconds) * $bucketSeconds);
512 private function history_snapshot_record($metrics, $bucketTime) {
513 $now = date(
'Y-m-d H:i:s');
514 $uid = (int)
dbx()->user();
517 'bucket_key' => date(
'YmdHi', $bucketTime),
518 'snapshot_date' => date(
'Y-m-d H:i:s', $bucketTime),
519 'create_date' =>
$now,
520 'create_uid' => $uid,
521 'update_date' =>
$now,
522 'update_uid' => $uid,
524 'users' => (
int) ($metrics[
'users'] ?? 0),
525 'online' => (
int) ($metrics[
'online'] ?? 0),
526 'modules' => (
int) ($metrics[
'modules'] ?? 0),
527 'records' => (
int) ($metrics[
'records'] ?? 0),
528 'databases' => (
int) ($metrics[
'databases'] ?? 0),
529 'health_percent' => (
int) ($metrics[
'health_percent'] ?? 0),
530 'active_users' => (
int) ($metrics[
'active_users'] ?? 0),
531 'sessions' => (
int) ($metrics[
'sessions'] ?? 0),
532 'tables' => (
int) ($metrics[
'tables'] ?? 0),
533 'sysmsg_risk' => (
int) ($metrics[
'sysmsg_risk'] ?? 0),
534 'missing' => (
int) ($metrics[
'missing'] ?? 0),
535 'request_runtime_ms' => (
int) ($metrics[
'request_runtime_ms'] ?? 0),
536 'memory_peak_mb' => (
int) ceil(((
int) ($metrics[
'memory_peak_kb'] ?? 0)) / 1024),
537 'memory_peak_kb' => (
int) ($metrics[
'memory_peak_kb'] ?? 0),
541 private function store_history_snapshot($metrics) {
542 if (!$this->ensure_history_table()) {
547 $db =
dbx()->get_system_obj(
'dbxDB');
548 $bucketTime = $this->history_bucket_time();
549 $record = $this->history_snapshot_record($metrics, $bucketTime);
550 $rows =
$db->select(self::HISTORY_DD, array(
'bucket_key' => $record[
'bucket_key']), array(
'id'),
'id',
'DESC',
'', 1, 0, 0);
551 $id = is_array(
$rows) ? (int) (
$rows[0][
'id'] ?? 0) : 0;
554 unset($record[
'create_date'], $record[
'create_uid'], $record[
'owner']);
555 return $db->update(self::HISTORY_DD, $record, array(
'id' => $id), 0, 1, 1, 0) > 0;
558 return $db->insert(self::HISTORY_DD, $record, 0, 1, 1, 0) > 0;
559 }
catch (\Throwable $e) {
564 private function metric_history_rows($metrics, $max = 48) {
565 if (!$this->ensure_history_table()) {
566 return array($this->history_snapshot_record($metrics, $this->history_bucket_time()));
569 $rows = $this->safe_select(
572 array(
'snapshot_date',
'users',
'online',
'modules',
'records',
'databases',
'health_percent',
'request_runtime_ms',
'memory_peak_kb'),
581 return array($this->history_snapshot_record($metrics, $this->history_bucket_time()));
584 return array_reverse(
$rows);
587 private function metric_series_definitions() {
589 'users' => array(
'label' =>
'Benutzer',
'tone' =>
'teal'),
590 'online' => array(
'label' =>
'Online',
'tone' =>
'green'),
591 'modules' => array(
'label' =>
'Module',
'tone' =>
'navy'),
592 'records' => array(
'label' =>
'Datensaetze',
'tone' =>
'amber'),
593 'databases' => array(
'label' =>
'Datenbanken',
'tone' =>
'cyan'),
594 'health_percent' => array(
'label' =>
'Systemzustand',
'tone' =>
'red'),
595 'request_runtime_ms' => array(
'label' =>
'Speed',
'tone' =>
'purple'),
596 'memory_peak_kb' => array(
'label' =>
'DBX Memory',
'tone' =>
'slate'),
600 private function metric_history($metrics) {
601 $rows = $this->metric_history_rows($metrics);
605 foreach ($this->metric_series_definitions() as $key => $def) {
606 $series[$key] = array(
608 'label' => $def[
'label'],
609 'tone' => $def[
'tone'],
614 foreach (
$rows as $row) {
615 $time = strtotime((
string) ($row[
'snapshot_date'] ??
''));
616 $labels[] = $time ? date(
'd.m. H:i', $time) :
'';
618 foreach ($series as $key => $def) {
619 $series[$key][
'values'][] = (int) ($row[$key] ?? 0);
626 'series' => array_values($series),
630 private function history_values($history, $key, $current = 0) {
633 foreach (($history[
'rows'] ?? array()) as $row) {
634 $values[] = (int) ($row[$key] ?? 0);
638 $values[] = (int) $current;
641 if (count($values) === 1) {
642 $values[] = $values[0];
648 private function spark_values($history, $key, $current = 0) {
649 return implode(
',', $this->history_values($history, $key, $current));
652 private function trend_text($history, $key, $suffix =
'') {
653 $values = $this->history_values($history, $key, 0);
655 if (count($values) < 2) {
656 return '1 Messpunkt';
659 $first = (int) reset($values);
660 $last = (int) end($values);
664 return 'unveraendert';
667 return (
$delta > 0 ?
'+' :
'') . $this->fmt(
$delta) . $suffix .
' im Verlauf';
670 private function fmt_ms(
$value) {
672 return number_format(
$value / 1000, 3,
',',
'.') .
' Sec';
675 private function fmt_ms_precision(
$value, $precision = 3, $minSeconds = 0) {
677 $precision = max(0, min(6, (
int) $precision));
680 if ((
float) $minSeconds > 0 && $seconds < (
float) $minSeconds) {
681 $seconds = (float) $minSeconds;
684 return number_format($seconds, $precision,
',',
'.') .
' Sec';
687 private function fmt_memory_kb(
$value) {
691 return number_format(
$value / 1024, 1,
',',
'.') .
' MB';
694 return $this->fmt(
$value) .
' KB';
697 private function fmt_memory_delta_kb(
$value) {
699 return '+' . $this->fmt_memory_kb(
$value);
702 private function performance_dd_info(
$dd) {
704 $db =
dbx()->get_system_obj(
'dbxDB');
708 if (!preg_match(
'/^[A-Za-z0-9_]+$/',
$table)) {
709 return array(
'',
'');
713 }
catch (\Throwable $e) {
714 return array(
'',
'');
718 private function performance_table_count(
string $dd):
int {
720 $db =
dbx()->get_system_obj(
'dbxDB');
723 }
catch (\Throwable $e) {
728 private function performance_now_record_base(): array {
729 $uid = (int)
dbx()->user();
730 $now = date(
'Y-m-d H:i:s');
733 'create_date' =>
$now,
734 'create_uid' => $uid,
735 'update_date' =>
$now,
736 'update_uid' => $uid,
741 private function optimize_performance_db(): array {
742 $result = array(
'ok' =>
true,
'messages' => array());
743 $db =
dbx()->get_system_obj(
'dbxDB');
746 foreach (array(self::PERF_REQUEST_DD, self::PERF_TIMER_DD) as
$dd) {
755 $tables = array_values(array_unique($tables));
756 $type = strtolower((
string)
$db->get_db_type(
$server));
758 if ($type ===
'mysql') {
760 foreach ($tables as
$table) {
761 $quoted[] =
'`' . str_replace(
'`',
'``',
$table) .
'`';
763 $ok =
$db->exec(
$server,
'OPTIMIZE TABLE ' . implode(
', ', $quoted));
765 $result[
'messages'][] =
$ok ?
'MySQL OPTIMIZE TABLE ausgefuehrt.' :
'MySQL OPTIMIZE TABLE fehlgeschlagen.';
769 if ($type ===
'sqlite') {
773 $result[
'messages'][] = $vacuum ?
'SQLite VACUUM ausgefuehrt.' :
'SQLite VACUUM fehlgeschlagen.';
775 $result[
'messages'][] =
'SQLite ANALYZE ausgefuehrt.';
780 $result[
'messages'][] =
'Keine Optimierung fuer DB-Typ ' . $type .
' definiert.';
786 private function compress_performance_db(): array {
788 'request_before' => $this->performance_table_count(self::PERF_REQUEST_DD),
789 'timer_before' => $this->performance_table_count(self::PERF_TIMER_DD),
790 'request_after' => 0,
794 'messages' => array(),
797 if (!$this->ensure_performance_tables()) {
798 $stats[
'messages'][] =
'Performance-Tabellen konnten nicht vorbereitet werden.';
802 list($requestServer, $requestTable) = $this->performance_dd_info(self::PERF_REQUEST_DD);
803 list($timerServer, $timerTable) = $this->performance_dd_info(self::PERF_TIMER_DD);
804 if ($requestServer ===
'' || $requestTable ===
'' || $timerServer ===
'' || $timerTable ===
'') {
805 $stats[
'messages'][] =
'Performance-Tabellen konnten nicht ermittelt werden.';
809 $db =
dbx()->get_system_obj(
'dbxDB');
810 $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");
811 $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");
813 $db->empty(self::PERF_TIMER_DD);
814 $db->empty(self::PERF_REQUEST_DD);
816 $base = $this->performance_now_record_base();
818 $requestDate = date(
'Y-m-d H:i:s');
819 $requestCount = is_array($requestRows) ? (int) ($requestRows[0][
'row_count'] ?? 0) : 0;
821 if ($requestCount > 0) {
822 $request = array_merge(
$base, array(
823 'request_date' => $requestDate,
825 'session_id' =>
'compressed',
826 'modul' =>
'compressed',
827 'run1' =>
'performance',
828 'run2' =>
'compress',
831 'method' =>
'COMPRESS',
832 'uri' =>
'Performance DB komprimiert aus ' . $requestCount .
' Request-Datensaetzen',
833 'total_time_ms' => (
int) round((
float) ($requestRows[0][
'total_time_ms'] ?? 0)),
834 'total_memory_kb' => (
int) round((
float) ($requestRows[0][
'total_memory_kb'] ?? 0)),
835 'peak_memory_mb' => (
int) round((
float) ($requestRows[0][
'peak_memory_mb'] ?? 0)),
836 'timer_count' => (
int) round((
float) ($requestRows[0][
'timer_count'] ?? 0)),
837 'sample_rate' => max(1, $requestCount),
839 if (
$db->insert(self::PERF_REQUEST_DD, $request, 0, 1, 1, 0) === 1) {
840 $requestId =
$db->get_insert_id();
841 $stats[
'inserted']++;
845 if (is_array($timerRows)) {
847 foreach ($timerRows as $row) {
848 $section = trim((
string) ($row[
'section'] ??
''));
849 if ($section ===
'') {
853 $count = max(1, (
int) ($row[
'row_count'] ?? 0));
854 $timer = array_merge(
$base, array(
855 'request_id' => $requestId,
856 'request_date' => $requestDate,
857 'sort_order' => $sort,
858 'section' => substr($section, 0, 80),
859 'info' => substr(trim((
string) ($row[
'info'] ??
'')) .
' | komprimiert aus ' .
$count .
' Messungen', 0, 160),
860 'time_ms' => (
int) round((
float) ($row[
'time_ms'] ?? 0)),
861 'memory_kb' => (
int) round((
float) ($row[
'memory_kb'] ?? 0)),
862 'start_memory_kb' => (
int) round((
float) ($row[
'start_memory_kb'] ?? 0)),
863 'end_memory_kb' => (
int) round((
float) ($row[
'end_memory_kb'] ?? 0)),
865 if (
$db->insert(self::PERF_TIMER_DD, $timer, 0, 1, 1, 0) === 1) {
866 $stats[
'inserted']++;
872 $optimize = $this->optimize_performance_db();
873 $stats[
'messages'] = array_merge($stats[
'messages'], $optimize[
'messages']);
874 $stats[
'request_after'] = $this->performance_table_count(self::PERF_REQUEST_DD);
875 $stats[
'timer_after'] = $this->performance_table_count(self::PERF_TIMER_DD);
876 $stats[
'ok'] = (bool) ($optimize[
'ok'] ??
false);
881 private function clear_performance_db(): array {
883 'request_before' => $this->performance_table_count(self::PERF_REQUEST_DD),
884 'timer_before' => $this->performance_table_count(self::PERF_TIMER_DD),
885 'request_after' => 0,
889 'messages' => array(),
892 if (!$this->ensure_performance_tables()) {
893 $stats[
'messages'][] =
'Performance-Tabellen konnten nicht vorbereitet werden.';
897 $db =
dbx()->get_system_obj(
'dbxDB');
898 $db->empty(self::PERF_TIMER_DD);
899 $db->empty(self::PERF_REQUEST_DD);
901 $optimize = $this->optimize_performance_db();
902 $stats[
'messages'] = array_merge($stats[
'messages'], $optimize[
'messages']);
903 $stats[
'request_after'] = $this->performance_table_count(self::PERF_REQUEST_DD);
904 $stats[
'timer_after'] = $this->performance_table_count(self::PERF_TIMER_DD);
905 $stats[
'ok'] = (bool) ($optimize[
'ok'] ??
false);
910 private function render_performance_process(
string $title, array $stats):
string {
911 $tpl =
dbx()->get_system_obj(
'dbxTPL');
912 $ok = (bool) ($stats[
'ok'] ??
false);
914 foreach (($stats[
'messages'] ?? array()) as $message) {
915 if (trim((
string) $message) !==
'') {
919 $message = (
$ok ?
'Prozess abgeschlossen.' :
'Prozess mit Fehler beendet.')
920 .
' Request: ' . $this->fmt($stats[
'request_before'] ?? 0) .
' -> ' . $this->fmt($stats[
'request_after'] ?? 0)
921 .
', Timer: ' . $this->fmt($stats[
'timer_before'] ?? 0) .
' -> ' . $this->fmt($stats[
'timer_after'] ?? 0)
922 .
', neu geschrieben: ' . $this->fmt($stats[
'inserted'] ?? 0)
925 return $tpl->get_tpl(
'dbxAdmin|schema-process', array(
926 'target_id' =>
'dbx_process_performance_' . substr(md5($title), 0, 10),
927 'title' =>
dbx()->
esc($title),
928 'status_key' =>
$ok ?
'finished' :
'error',
929 'status_label' =>
$ok ?
'Fertig' :
'Fehler',
930 'status_class' =>
$ok ?
'bg-success' :
'bg-danger',
931 'status_icon' =>
$ok ?
'bi bi-check-circle' :
'bi bi-exclamation-triangle',
932 'message' => $message,
933 'percent' =>
$ok ? 100 : 0,
934 'step_percent' =>
$ok ? 100 : 0,
935 'bar_class' =>
$ok ?
'bg-success' :
'bg-danger',
936 'task_label' =>
'Performance DB',
937 'step_label' =>
'Optimierung',
938 'process_label' =>
'Performance Wartung',
939 'updated_at' =>
dbx()->
esc(date(
'd.m.Y H:i:s')),
943 'continue_url' =>
'',
948 'pause_visible' =>
'_none',
949 'resume_visible' =>
'_none',
950 'continue_visible' =>
'_none',
951 'restart_visible' =>
'_none',
952 'cancel_visible' =>
'_none',
953 'back_url' =>
'?dbx_modul=dbxAdmin',
957 private function run_performance_maintenance_process(
string $mode):
string {
958 dbx()->set_system_var(
'dbx_performance_timer_skip', 1);
960 if (
$mode ===
'clear') {
961 return $this->render_performance_process(
'Performance DB leeren und optimieren', $this->clear_performance_db());
964 return $this->render_performance_process(
'Performance DB komprimieren und optimieren', $this->compress_performance_db());
967 private function performance_openwin_action(
string $href,
string $label,
string $icon,
string $class =
'btn-outline-secondary'):
string {
968 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">'
969 .
'<i class="bi ' .
dbx()->esc($icon) .
'"></i> ' .
dbx()->esc($label)
973 private function performance_maintenance_actions():
string {
974 return $this->performance_openwin_action(
'?dbx_modul=dbxAdmin&dbx_run1=run&dbx_run2=performance_compress',
'Komprimieren',
'bi-file-zip')
975 . $this->performance_openwin_action(
'?dbx_modul=dbxAdmin&dbx_run1=run&dbx_run2=performance_clear',
'Performance DB leeren',
'bi-trash3',
'btn-outline-danger');
978 private function performance_request_average() {
979 if ($this->performanceRequestAverage !==
null) {
980 return $this->performanceRequestAverage;
983 $this->performanceRequestAverage = array();
985 if (!$this->ensure_performance_tables()) {
986 return $this->performanceRequestAverage;
989 list(
$server,
$table) = $this->performance_dd_info(self::PERF_REQUEST_DD);
991 return $this->performanceRequestAverage;
995 $db =
dbx()->get_system_obj(
'dbxDB');
996 $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");
997 $row = is_array(
$rows) ? (
$rows[0] ?? array()) : array();
999 if ((
int) ($row[
'row_count'] ?? 0) <= 0) {
1000 return $this->performanceRequestAverage;
1003 $this->performanceRequestAverage = array(
1004 'count' => (
int) ($row[
'row_count'] ?? 0),
1005 'avg_time_ms' => (
int) round((
float) ($row[
'avg_time_ms'] ?? 0)),
1006 'avg_memory_kb' => (
int) round((
float) ($row[
'avg_memory_kb'] ?? 0)),
1007 'avg_timer_count' => (
int) round((
float) ($row[
'avg_timer_count'] ?? 0)),
1009 }
catch (\Throwable $e) {
1010 $this->performanceRequestAverage = array();
1013 return $this->performanceRequestAverage;
1016 private function performance_timer_averages() {
1017 if ($this->performanceTimerAverages !==
null) {
1018 return $this->performanceTimerAverages;
1021 $this->performanceTimerAverages = array();
1023 if (!$this->ensure_performance_tables()) {
1024 return $this->performanceTimerAverages;
1027 list(
$server,
$table) = $this->performance_dd_info(self::PERF_TIMER_DD);
1029 return $this->performanceTimerAverages;
1033 $db =
dbx()->get_system_obj(
'dbxDB');
1034 $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
1036 WHERE section <> 'system'
1038 ORDER BY MIN(sort_order) ASC, section ASC";
1040 $this->performanceTimerAverages = is_array(
$rows) ?
$rows : array();
1041 }
catch (\Throwable $e) {
1042 $this->performanceTimerAverages = array();
1045 return $this->performanceTimerAverages;
1048 private function performance_timer_average($section) {
1049 $section = (string) $section;
1051 foreach ($this->performance_timer_averages() as $row) {
1052 if ((
string) ($row[
'section'] ??
'') === $section) {
1054 'count' => (
int) ($row[
'row_count'] ?? 0),
1055 'avg_time_ms' => (
int) round((
float) ($row[
'avg_time_ms'] ?? 0)),
1056 'avg_memory_kb' => (
int) round((
float) ($row[
'avg_memory_kb'] ?? 0)),
1064 private function performance_module_averages() {
1065 if ($this->performanceModuleAverages !==
null) {
1066 return $this->performanceModuleAverages;
1069 $this->performanceModuleAverages = array();
1071 if (!$this->ensure_performance_tables()) {
1072 return $this->performanceModuleAverages;
1075 list($requestServer, $requestTable) = $this->performance_dd_info(self::PERF_REQUEST_DD);
1076 list($timerServer, $timerTable) = $this->performance_dd_info(self::PERF_TIMER_DD);
1077 if ($requestServer ===
'' || $requestTable ===
'' || $timerServer !== $requestServer || $timerTable ===
'') {
1078 return $this->performanceModuleAverages;
1082 $db =
dbx()->get_system_obj(
'dbxDB');
1083 $sql =
"SELECT r.modul AS modul,
1084 COUNT(*) AS row_count,
1085 AVG(r.total_time_ms) AS avg_total_ms,
1086 AVG(COALESCE(d.db_time_ms, 0)) AS avg_db_ms
1087 FROM $requestTable r
1089 SELECT request_id, SUM(time_ms) AS db_time_ms
1091 WHERE section = 'db-total'
1093 ) d ON d.request_id = r.id
1094 WHERE TRIM(COALESCE(r.modul, '')) <> ''
1096 ORDER BY AVG(r.total_time_ms) DESC, r.modul ASC
1098 $rows =
$db->select_query($requestServer, $sql);
1099 $this->performanceModuleAverages = is_array(
$rows) ?
$rows : array();
1100 }
catch (\Throwable $e) {
1101 $this->performanceModuleAverages = array();
1104 return $this->performanceModuleAverages;
1107 private function hero_performance($metrics) {
1108 $request = $this->performance_request_average();
1109 $dbTotal = $this->performance_timer_average(
'db-total');
1111 $requestMs = $request ? (int) ($request[
'avg_time_ms'] ?? 0) : (int) ($metrics[
'request_runtime_ms'] ?? 0);
1112 $dbMs = $dbTotal ? (int) ($dbTotal[
'avg_time_ms'] ?? 0) : 0;
1113 $phpMs = max(0, $requestMs - $dbMs);
1114 $dbShare = $requestMs > 0 ? min(999, (
int) round(($dbMs / $requestMs) * 100)) : 0;
1115 $phpShare = $requestMs > 0 ? min(999, (
int) round(($phpMs / $requestMs) * 100)) : 0;
1118 'request_avg' => $this->fmt_ms($requestMs),
1119 'request_raw' => $requestMs,
1120 'request_count' => $this->fmt($request[
'count'] ?? 0),
1121 'php_avg' => $this->fmt_ms($phpMs),
1122 'php_raw' => $phpMs,
1123 'php_share' => $this->fmt($phpShare),
1124 'db_avg' => $this->fmt_ms($dbMs),
1126 'db_count' => $this->fmt($dbTotal[
'count'] ?? 0),
1127 'db_share' => $this->fmt($dbShare),
1131 private function hero_performance_gauge($label, $icon, $tone, $raw,
$value, $subtitle) {
1132 $tpl =
dbx()->get_system_obj(
'dbxTPL');
1134 return $tpl->get_tpl(
'dbxAdmin|admin-dashboard-hero-gauge', array(
1138 'raw' => max(0, (
int) $raw),
1141 'subtitle' => $subtitle,
1145 private function hero_performance_gauges($heroPerformance) {
1147 $items .= $this->hero_performance_gauge(
1151 $heroPerformance[
'request_raw'] ?? 0,
1152 $heroPerformance[
'request_avg'] ??
'0,000 Sec',
1153 'Durchschnitt aus ' . ($heroPerformance[
'request_count'] ??
'0') .
' Requests'
1155 $items .= $this->hero_performance_gauge(
1159 $heroPerformance[
'php_raw'] ?? 0,
1160 $heroPerformance[
'php_avg'] ??
'0,000 Sec',
1161 'Durchschnitt ohne DB-Anteil'
1163 $items .= $this->hero_performance_gauge(
1165 'bi-database-check',
1167 $heroPerformance[
'db_raw'] ?? 0,
1168 $heroPerformance[
'db_avg'] ??
'0,000 Sec',
1169 'Durchschnitt aus DB-Timern'
1175 private function hero_summary_rows(array
$items):
string {
1178 foreach (
$items as $item) {
1179 $max = max($max, (
int) ($item[
'count'] ?? 0));
1182 foreach (
$items as $item) {
1183 $count = (int) ($item[
'count'] ?? 0);
1184 $pct = max(3, min(100, (
int) round((
$count / $max) * 100)));
1185 $tone =
dbx()->esc((
string) ($item[
'tone'] ??
'blue'));
1186 $html .=
'<div class="dbx-admin-dashboard-hero-summary-row dbx-admin-dashboard-hero-summary-row-' . $tone .
'">'
1187 .
'<span>' .
dbx()->esc((
string) ($item[
'label'] ??
'')) .
'</span>'
1188 .
'<div><em style="width:' . $pct .
'%"></em></div>'
1189 .
'<strong>' .
dbx()->esc($this->fmt(
$count)) .
'</strong>'
1196 private function hero_summary_card(
string $title,
string $subtitle,
string $icon,
string $tone,
int $total, array
$rows):
string {
1197 $tpl =
dbx()->get_system_obj(
'dbxTPL');
1199 return $tpl->get_tpl(
'dbxAdmin|admin-dashboard-hero-summary', array(
1201 'subtitle' => $subtitle,
1204 'total' => $this->fmt($total),
1205 'rows' => $this->hero_summary_rows(
$rows),
1209 private function hero_status_summaries():
string {
1210 $contactDd =
'dbxContact|contactRequest';
1211 $contactTotal = $this->safe_count($contactDd);
1212 $contactRows = array(
1213 array(
'label' =>
'Offen',
'count' => $this->safe_count($contactDd, array(
'status' =>
'open')),
'tone' =>
'blue'),
1214 array(
'label' =>
'In Arbeit',
'count' => $this->safe_count($contactDd, array(
'status' =>
'in_progress')),
'tone' =>
'cyan'),
1215 array(
'label' =>
'Rueckfrage',
'count' => $this->safe_count($contactDd, array(
'status' =>
'waiting_customer')),
'tone' =>
'amber'),
1216 array(
'label' =>
'Beantwortet',
'count' => $this->safe_count($contactDd, array(
'status' =>
'answered')),
'tone' =>
'green'),
1217 array(
'label' =>
'Geschlossen',
'count' => $this->safe_count($contactDd, array(
'status' =>
'closed')),
'tone' =>
'slate'),
1220 $sysmsgDd =
'dbxSysMsg';
1221 $sysmsgTotal = $this->safe_count($sysmsgDd);
1222 $sysmsgRows = array(
1223 array(
'label' =>
'Info',
'count' => $this->safe_count($sysmsgDd,
"LOWER(status) = 'info'"),
'tone' =>
'blue'),
1224 array(
'label' =>
'Warning',
'count' => $this->safe_count($sysmsgDd,
"LOWER(status) = 'warning'"),
'tone' =>
'amber'),
1225 array(
'label' =>
'Error',
'count' => $this->safe_count($sysmsgDd,
"LOWER(status) = 'error'"),
'tone' =>
'red'),
1226 array(
'label' =>
'Security',
'count' => $this->safe_count($sysmsgDd,
"LOWER(status) = 'security'"),
'tone' =>
'purple'),
1229 return $this->hero_summary_card(
'Kontakte',
'Alle Anfragen nach Status',
'bi-life-preserver',
'contact', $contactTotal, $contactRows)
1230 . $this->hero_summary_card(
'SysMsg',
'Meldungen nach Status',
'bi-bell',
'sysmsg', $sysmsgTotal, $sysmsgRows);
1233 private function performance_label($section, $info =
'') {
1234 $section = trim((
string) $section);
1235 $info = trim((
string) $info);
1237 if ($section ===
'db-total') {
1241 if ($section ===
'js-total') {
1245 if ($section ===
'system') {
1246 return 'PHP/System Gesamt';
1249 if ($section ===
'db-select') {
1250 return 'DB Select gesamt';
1253 if ($section ===
'db-save') {
1254 return 'DB Save gesamt';
1257 if (substr($section, 0, 10) ===
'db-select-') {
1261 if (substr($section, 0, 8) ===
'db-save-') {
1266 'system-load' =>
'System Load',
1267 'session-load' =>
'Session Load',
1268 'system-check' =>
'System Check',
1269 'modul-run' =>
'Modul Run',
1270 'page-load' =>
'Page Load',
1271 'interpreter' =>
'Interpreter',
1272 'db-select' =>
'DB Select',
1273 'db-save' =>
'DB Save',
1276 if (isset($known[$section])) {
1277 return $known[$section];
1280 if (substr($section, 0, 4) ===
'run-') {
1281 $modul = substr($section, 4);
1282 return $modul !==
'' ? $modul :
'Modul';
1285 $label = ucwords(str_replace(array(
'-',
'_'),
' ', $section));
1286 return $label !==
'' ? $label : ($info !==
'' ? $info :
'Timer');
1289 private function performance_is_db_section($section) {
1290 $section = (string) $section;
1291 return $section ===
'db-total'
1292 || $section ===
'db-select'
1293 || $section ===
'db-save'
1294 || substr($section, 0, 10) ===
'db-select-'
1295 || substr($section, 0, 8) ===
'db-save-';
1298 private function performance_dd_detail($section, $info =
'') {
1299 $section = trim((
string) $section);
1300 $info = trim((
string) $info);
1302 if (substr($section, 0, 10) ===
'db-select-') {
1303 $dd = substr($info, 0, 7) ===
'select ' ? trim(substr($info, 7)) : trim(substr($section, 10));
1304 return $dd !==
'' ?
$dd :
'DD';
1307 if (substr($section, 0, 8) ===
'db-save-') {
1308 $dd = substr($info, 0, 5) ===
'save ' ? trim(substr($info, 5)) : trim(substr($section, 8));
1309 return $dd !==
'' ?
$dd :
'DD';
1315 private function performance_db_sort($row) {
1316 $section = (string) ($row[
'section'] ??
'');
1318 if ($section ===
'db-total') {
1322 if ($section ===
'db-select') {
1326 if ($section ===
'db-save') {
1330 if (substr($section, 0, 10) ===
'db-select-') {
1334 if (substr($section, 0, 8) ===
'db-save-') {
1341 private function performance_tone(
$index) {
1342 $tones = array(
'teal',
'green',
'navy',
'amber',
'cyan',
'red',
'purple',
'slate');
1343 return $tones[
$index % count($tones)];
1346 private function performance_rows($metrics,
$mode =
'request') {
1349 if (
$mode ===
'module') {
1351 foreach ($this->performance_module_averages() as
$module) {
1352 $modul = trim((
string) (
$module[
'modul'] ??
''));
1354 if ($modul ===
'' ||
$count <= 0) {
1358 $totalMs = (int) round((
float) (
$module[
'avg_total_ms'] ?? 0));
1359 $dbMs = (int) round((
float) (
$module[
'avg_db_ms'] ?? 0));
1360 $phpMs = max(0, $totalMs - $dbMs);
1361 $subtitle =
'Modul ' . $modul .
' · ' . $this->fmt(
$count) .
' Requests';
1364 'section' =>
'module-' . $modul .
'-php',
1365 'label' => $modul .
' · PHP gesamt',
1367 'value_ms' => $phpMs,
1368 'detail' =>
'PHP gesamt',
1370 'min_display_sec' => 0,
1371 'subtitle' => $subtitle,
1372 'tone' => $this->performance_tone(
$index),
1376 'section' =>
'module-' . $modul .
'-db',
1377 'label' => $modul .
' · DB gesamt',
1378 'icon' =>
'bi-database-check',
1379 'value_ms' => $dbMs,
1380 'detail' =>
'DB gesamt',
1382 'min_display_sec' => 0.0001,
1383 'subtitle' => $subtitle,
1384 'tone' => $this->performance_tone(
$index + 1),
1389 return array(
'rows' =>
$rows);
1392 $segments = $this->performance_timer_averages();
1394 foreach ($segments as $segment) {
1395 $section = (string) ($segment[
'section'] ??
'');
1396 $count = (int) ($segment[
'row_count'] ?? 0);
1397 if ($section ===
'' ||
$count <= 0) {
1401 if (
$mode ===
'db' && $section ===
'db-total') {
1405 $isDb = $this->performance_is_db_section($section);
1406 if ((
$mode ===
'db') !== $isDb) {
1411 'section' => $section,
1412 'label' => $this->performance_label($section, $segment[
'info'] ??
''),
1413 'icon' =>
'bi-stopwatch',
1414 'value_ms' => (
int) round((
float) ($segment[
'avg_time_ms'] ?? 0)),
1415 'detail' => $this->performance_dd_detail($section, $segment[
'info'] ??
''),
1416 'precision' => $isDb ? 4 : 3,
1417 'min_display_sec' => $isDb ? 0.0001 : 0,
1418 'subtitle' =>
'Durchschnitt aus ' . $this->fmt(
$count) .
' Messungen, Memory ' . $this->fmt_memory_delta_kb($segment[
'avg_memory_kb'] ?? 0),
1419 'tone' => $this->performance_tone(
$index),
1424 if (
$mode ===
'db') {
1425 usort(
$rows,
function ($a, $b) {
1426 $prio = $this->performance_db_sort($a) <=> $this->performance_db_sort($b);
1431 return strcmp((
string) ($a[
'detail'] ?? $a[
'label'] ??
''), (
string) ($b[
'detail'] ?? $b[
'label'] ??
''));
1435 return array(
'rows' =>
$rows);
1438 private function performance_module_image_url(
string $modul):
string {
1439 $modul = trim($modul);
1440 $safeModul = preg_match(
'/^[A-Za-z0-9_\\-]+$/', $modul) ? $modul :
'dbx';
1441 $baseDir = rtrim((
string)
dbx()->
get_base_dir(),
'/\\') . DIRECTORY_SEPARATOR;
1442 $rel =
'dbx/modules/' . $safeModul .
'/tpl/img/' . $safeModul .
'.png';
1443 $path = $baseDir . str_replace(
'/', DIRECTORY_SEPARATOR, $rel);
1445 if (!is_file($path)) {
1446 $rel =
'dbx/modules/dbx/tpl/img/dbx.png';
1452 private function performance_metric($row,
int $max):
string {
1453 $tpl =
dbx()->get_system_obj(
'dbxTPL');
1454 $value = (int) ($row[
'value_ms'] ?? 0);
1456 return $tpl->get_tpl(
'dbxAdmin|admin-dashboard-performance-metric', array(
1457 'tone' => (
string) ($row[
'tone'] ??
'teal'),
1458 'icon' => (
string) ($row[
'icon'] ??
'bi-speedometer2'),
1459 'label' => (
string) ($row[
'label'] ??
'Messung'),
1461 'max' => max(1, $max),
1462 'value' => $this->fmt_ms_precision(
$value, $row[
'precision'] ?? 3, $row[
'min_display_sec'] ?? 0),
1463 'detail' => (
string) ($row[
'detail'] ??
''),
1467 private function performance_module_list():
string {
1468 $tpl =
dbx()->get_system_obj(
'dbxTPL');
1473 foreach ($this->performance_module_averages() as
$module) {
1474 $modul = trim((
string) (
$module[
'modul'] ??
''));
1476 if ($modul ===
'' ||
$count <= 0) {
1480 $totalMs = (int) round((
float) (
$module[
'avg_total_ms'] ?? 0));
1481 $dbMs = (int) round((
float) (
$module[
'avg_db_ms'] ?? 0));
1482 $phpMs = max(0, $totalMs - $dbMs);
1485 $metrics .= $this->performance_metric(array(
1486 'label' =>
'PHP gesamt',
1488 'value_ms' => $phpMs,
1489 'detail' =>
'PHP gesamt',
1491 'min_display_sec' => 0,
1492 'tone' => $this->performance_tone(
$index),
1494 $metrics .= $this->performance_metric(array(
1495 'label' =>
'DB gesamt',
1496 'icon' =>
'bi-database-check',
1497 'value_ms' => $dbMs,
1498 'detail' =>
'DB gesamt',
1500 'min_display_sec' => 0.0001,
1501 'tone' => $this->performance_tone(
$index + 1),
1504 $html .=
$tpl->get_tpl(
'dbxAdmin|admin-dashboard-performance-module', array(
1505 'module_img' => $this->performance_module_image_url($modul),
1506 'module_name' => $modul,
1507 'request_count' => $this->fmt(
$count),
1508 'metrics' => $metrics,
1517 private function speedometer($row, $max) {
1518 $tpl =
dbx()->get_system_obj(
'dbxTPL');
1519 $value = (int) ($row[
'value_ms'] ?? 0);
1520 $label = (string) ($row[
'label'] ??
'Timer');
1521 $icon = (string) ($row[
'icon'] ??
'bi-speedometer2');
1522 $tone = (string) ($row[
'tone'] ??
'teal');
1524 return $tpl->get_tpl(
'dbxAdmin|admin-dashboard-speedometer', array(
1525 'bar' => $this->card_bar($label, $icon),
1528 'max' => max(1, (
int) $max),
1529 'value' => $this->fmt_ms_precision(
$value, $row[
'precision'] ?? 3, $row[
'min_display_sec'] ?? 0),
1530 'detail' => $row[
'detail'] ??
'',
1531 'subtitle' => $row[
'subtitle'] ??
'',
1535 private function speedometer_panel($title, $icon, $subtitle,
$rows, $panelClass =
'', $panelTarget =
'', $controls =
'', $barActions =
'') {
1536 $tpl =
dbx()->get_system_obj(
'dbxTPL');
1539 $panelTarget = trim((
string) $panelTarget);
1540 $actions = $barActions . ($panelTarget !==
'' ? $this->collapse_action($panelTarget,
'Zuklappen',
true) :
'');
1542 foreach (
$rows as $row) {
1543 $items .= $this->speedometer($row, $max);
1546 return $tpl->get_tpl(
'dbxAdmin|admin-dashboard-performance', array(
1547 'panel_class' => $panelClass,
1548 'panel_target' => $panelTarget,
1549 'performance_bar' => $this->card_bar($title, $icon, $subtitle,
$actions),
1550 'performance_controls' => $controls,
1551 'performance_items' =>
$items,
1555 private function performance_panel($metrics) {
1556 $tpl =
dbx()->get_system_obj(
'dbxTPL');
1557 $panelTarget =
'request-performance';
1558 $actions = $this->performance_maintenance_actions()
1559 . $this->collapse_action($panelTarget,
'Zuklappen',
true)
1560 . $this->help_action(
'dashboard');
1562 return $tpl->get_tpl(
'dbxAdmin|admin-dashboard-performance', array(
1563 'panel_class' =>
'',
1564 'panel_target' => $panelTarget,
1565 'performance_bar' => $this->card_bar(
'Performance pro Modul',
'bi-speedometer2',
'Durchschnittswerte je Modul: PHP gesamt und DB gesamt',
$actions),
1566 'performance_controls' => $this->performance_level_control(),
1567 'performance_items' => $this->performance_module_list(),
1571 private function normalize_sys_msg_level($level):
string {
1572 $level = strtolower(trim((
string) $level));
1573 if ($level ===
'warn') {
1577 return in_array($level, array(
'error',
'warning',
'all'),
true) ? $level :
'all';
1580 private function sys_msg_level_config():
string {
1581 return $this->normalize_sys_msg_level(
dbx()->get_config(
'dbx',
'sys_msg_level',
'all'));
1584 private function sys_msg_level_options(
string $current):
string {
1586 'error' =>
'Nur Error',
1587 'warning' =>
'Error + Warning',
1593 $selected =
$value === $current ?
' selected' :
'';
1594 $html .=
'<option value="' .
dbx()->esc(
$value) .
'"' . $selected .
'>' .
dbx()->esc($label) .
'</option>';
1600 private function set_sys_msg_level_config(
string $level):
bool {
1606 $config[
'sys_msg_level'] = $this->normalize_sys_msg_level($level);
1607 return (
int)
dbx()->set_config(
'dbx',
$config) > 0;
1610 private function process_sys_msg_level_action():
bool {
1611 $run2 =
dbx()->get_modul_var(
'dbx_run2',
'',
'parameter');
1612 if ($run2 !==
'sysmsg_level_save') {
1616 $level =
dbx()->get_request_var(
'sys_msg_level',
null,
'parameter');
1617 if ($level ===
null || $level ===
'') {
1618 $level =
$_POST[
'sys_msg_level'] ??
'all';
1621 return $this->set_sys_msg_level_config((
string) $level);
1624 private function sys_msg_level_control_data(): array {
1625 $level = $this->sys_msg_level_config();
1629 'icon' =>
'bi-bell-fill',
1630 'label' =>
'Systemmeldungen: Alles',
1631 'hint' =>
'Alle Systemmeldungen werden gespeichert.',
1635 'icon' =>
'bi-exclamation-triangle-fill',
1636 'label' =>
'Error + Warning',
1637 'hint' =>
'Nur Error und Warning werden gespeichert.',
1641 'icon' =>
'bi-exclamation-octagon-fill',
1642 'label' =>
'Nur Error',
1643 'hint' =>
'Nur Fehlermeldungen werden gespeichert.',
1646 $state = $states[$level] ?? $states[
'all'];
1649 'sys_msg_level_save_base' =>
dbx()->
esc(
'?dbx_modul=dbxAdmin&dbx_run1=run&dbx_run2=sysmsg_level_save'),
1650 'sys_msg_level_options' => $this->sys_msg_level_options($level),
1658 private function sys_msg_level_control():
string {
1659 $oForm = new \dbxForm();
1660 $oForm->init(
'admin-dashboard-sysmsg-control',
'admin-dashboard-sysmsg-control');
1661 $oForm->set_form_help_enabled(
false);
1662 $oForm->_action =
'?dbx_modul=dbxAdmin&dbx_run1=run&dbx_run2=sysmsg_level_save';
1663 $oForm->_msg_info =
'';
1665 foreach ($this->sys_msg_level_control_data() as $key =>
$value) {
1666 $oForm->add_rep($key,
$value);
1669 return $oForm->add_norep($oForm->run());
1672 private function sysmsg_panel_body_html():
string {
1673 $oForm = new \dbxForm();
1674 $oForm->init(
'admin-dashboard-sysmsg-body',
'admin-dashboard-sysmsg-body');
1675 $oForm->set_form_help_enabled(
false);
1676 $oForm->_action =
'?dbx_modul=dbxAdmin&dbx_run1=run';
1677 $oForm->_msg_info =
'';
1678 $oForm->add_rep(
'sysmsg_control', $this->sys_msg_level_control());
1680 return $oForm->add_norep($oForm->run());
1683 private function sysmsg_panel() {
1684 $oForm = new \dbxForm();
1685 $oForm->init(
'admin-dashboard-sysmsg-panel',
'admin-dashboard-sysmsg-panel');
1686 $oForm->set_form_help_enabled(
false);
1687 $oForm->_action =
'?dbx_modul=dbxAdmin&dbx_run1=run';
1688 $oForm->_msg_info =
'';
1689 $oForm->add_rep(
'sysmsg_body', $this->sysmsg_panel_body_html());
1691 return $oForm->add_norep($oForm->run());
1694 private function session_db_enabled_config():
bool {
1695 return $this->dbx_config_bool(
'session_db', 1);
1698 private function set_session_db_config(
bool $enabled):
bool {
1704 $config[
'session_db'] = $enabled ? 1 : 0;
1705 return (
int)
dbx()->set_config(
'dbx',
$config) > 0;
1708 private function process_session_db_action():
bool {
1709 $run2 =
dbx()->get_modul_var(
'dbx_run2',
'',
'parameter');
1710 if ($run2 !==
'session_db_save') {
1714 return $this->set_session_db_config(isset(
$_POST[
'session_db']));
1717 private function session_panel_body_data(): array {
1718 $enabled = $this->session_db_enabled_config();
1721 'session_save_url' =>
dbx()->
esc(
'?dbx_modul=dbxAdmin&dbx_run1=run&dbx_run2=session_db_save'),
1722 'session_enabled_checked' => $enabled ?
'checked' :
'',
1723 'session_status_tone' => $enabled ?
'on' :
'off',
1724 'session_status_icon' => $enabled ?
'bi-check-circle-fill' :
'bi-pause-circle-fill',
1725 'session_status_label' =>
dbx()->
esc($enabled ?
'Session-DB aktiv' :
'Session-DB inaktiv'),
1726 'session_status_hint' =>
dbx()->
esc(
1728 ?
'Normale HTTP-Requests und HTML-AJAX-Requests schreiben ihre Session am Request-Ende in die DB.'
1729 :
'Sessions laufen nur ueber PHP-Session; die Session-Liste wird nicht fortgeschrieben.'
1734 private function session_panel_control_html():
string {
1735 $oForm = new \dbxForm();
1736 $oForm->init(
'admin-dashboard-session-control',
'admin-dashboard-session-control');
1737 $oForm->set_form_help_enabled(
false);
1738 $oForm->_action =
'?dbx_modul=dbxAdmin&dbx_run1=run';
1739 $oForm->_msg_info =
'';
1741 foreach ($this->session_panel_body_data() as $key =>
$value) {
1742 $oForm->add_rep($key,
$value);
1745 return $oForm->add_norep($oForm->run());
1748 private function session_panel_body_html():
string {
1749 $oForm = new \dbxForm();
1750 $oForm->init(
'admin-dashboard-session-body',
'admin-dashboard-session-body');
1751 $oForm->set_form_help_enabled(
false);
1752 $oForm->_action =
'?dbx_modul=dbxAdmin&dbx_run1=run';
1753 $oForm->_msg_info =
'';
1754 $oForm->add_rep(
'session_control', $this->session_panel_control_html());
1756 return $oForm->add_norep($oForm->run());
1759 private function session_panel() {
1760 $oForm = new \dbxForm();
1761 $oForm->init(
'admin-dashboard-session-panel',
'admin-dashboard-session-panel');
1762 $oForm->set_form_help_enabled(
false);
1763 $oForm->_action =
'?dbx_modul=dbxAdmin&dbx_run1=run';
1764 $oForm->_msg_info =
'';
1765 $oForm->add_rep(
'session_body', $this->session_panel_body_html());
1767 return $oForm->add_norep($oForm->run());
1770 private function module_count() {
1774 if (!is_dir($path)) {
1784 if (is_dir($path .
$file) &&
$file !==
'tpl' && substr(
$file, 0, 1) !==
'.') {
1792 private function dd_inventory() {
1796 if (!is_dir(
$base)) {
1802 if ($modul ===
'.' || $modul ===
'..' || substr($modul, 0, 1) ===
'.') {
1806 $ddPath =
dbx()->os_path(
$base . $modul .
'/dd/');
1807 if (!is_dir($ddPath)) {
1811 $files = scandir($ddPath);
1813 if (!str_ends_with(
$file,
'.dd.php')) {
1817 $dd = str_replace(
'.dd.php',
'',
$file);
1818 if (
$dd ===
'' ||
$dd ===
'new') {
1822 $key = $modul .
'|' .
$dd;
1825 $db =
dbx()->get_system_obj(
'dbxDB');
1826 $tableDef =
$db->get_dd_table($key, 1);
1828 if (!is_array($tableDef)) {
1832 $server = (string) ($tableDef[
'server'] ??
'');
1833 $table = (string) ($tableDef[
'table'] ??
'');
1840 $count = $exist ? $this->safe_count($key) : 0;
1850 'sync' => (
int) ($tableDef[
'autosync'] ?? 0),
1851 'trace' => (
int) ($tableDef[
'trace'] ?? 0),
1853 }
catch (\Throwable $e) {
1862 private function metrics() {
1863 if ($this->metricCache) {
1864 return $this->metricCache;
1867 $this->ensure_history_table();
1869 $inventory = $this->dd_inventory();
1870 $uniqueTables = array();
1871 $uniqueServers = array();
1877 foreach ($inventory as $row) {
1878 $server = (string) ($row[
'server'] ??
'');
1879 $table = (string) ($row[
'table'] ??
'');
1886 if ($key !==
'|' && !isset($uniqueTables[$key])) {
1887 $uniqueTables[$key] = 1;
1888 $records += (int) ($row[
'count'] ?? 0);
1891 if (!empty($row[
'exist'])) {
1895 if (!empty($row[
'sync'])) {
1899 if (!empty($row[
'trace'])) {
1904 $onlineCutoff = date(
'Y-m-d H:i:s', time() - 900);
1905 $users = $this->safe_count(
'dbxUser');
1906 $activeUsers = $this->safe_count(
'dbxUser',
"status = 'active'");
1907 $sessions = $this->safe_count(
'dbxSession');
1908 $online = $this->safe_count(
'dbxSession',
"update_date >= '" . $onlineCutoff .
"'");
1909 $sysmsg = $this->safe_count(
'dbxSysMsg');
1910 $sysmsgRisk = $this->safe_count(
'dbxSysMsg',
"LOWER(status) IN ('warning','error')");
1911 $missing = $this->safe_count(
'dbxMissing');
1912 $traceCount = $this->safe_count(
'dbxTrace');
1913 $errorLogExists =
dbx()->get_include_obj(
'dbxSysMsg')->error_log_exists();
1915 $inventoryCount = count($inventory);
1916 $healthPercent = $this->percent($existing, max(1, $inventoryCount));
1917 if ($sysmsgRisk > 0) {
1918 $healthPercent = max(0, $healthPercent - min(30, $sysmsgRisk * 3));
1921 $healthPercent = max(0, $healthPercent - min(20,
$missing * 2));
1923 $healthReason = $this->health_reason_label($inventoryCount, $existing, $sysmsgRisk,
$missing);
1924 if ($errorLogExists) {
1926 $healthReason =
'dbxError.log';
1929 $this->metricCache = array(
1930 'inventory' => $inventory,
1932 'active_users' => $activeUsers,
1933 'sessions' => $sessions,
1934 'online' => $online,
1935 'modules' => $this->module_count(),
1936 'dd_count' => $inventoryCount,
1937 'records' => $records,
1938 'databases' => count($uniqueServers),
1939 'tables' => count($uniqueTables),
1940 'sysmsg' => $sysmsg,
1941 'sysmsg_risk' => $sysmsgRisk,
1943 'trace_count' => $traceCount,
1944 'existing_dd' => $existing,
1945 'autosync' => $autosync,
1946 'trace_enabled' => $trace,
1947 'health_percent' => $healthPercent,
1948 'health_reason' => $healthReason,
1949 'health_error_log' => $errorLogExists ? 1 : 0,
1950 'request_runtime_ms' => $this->request_runtime_ms(),
1951 'memory_peak_kb' => $this->memory_peak_kb(),
1954 return $this->metricCache;
1957 private function widget($data) {
1958 $tpl =
dbx()->get_system_obj(
'dbxTPL');
1960 $data[
'bar'] = $this->card_bar($data[
'label'] ??
'', $data[
'icon'] ??
'bi-circle');
1961 $data[
'label'] = $data[
'label'] ??
'';
1962 $data[
'value'] = $data[
'value'] ??
'';
1963 $data[
'raw'] = (int) ($data[
'raw'] ?? 0);
1964 $data[
'note'] = $data[
'note'] ??
'';
1965 $data[
'icon'] = $data[
'icon'] ??
'bi-circle';
1966 $data[
'tone'] = $data[
'tone'] ??
'teal';
1967 $data[
'spark'] = $data[
'spark'] ??
'';
1968 $data[
'trend'] = $data[
'trend'] ??
'';
1970 return $tpl->get_tpl(
'dbxAdmin|admin-dashboard-widget', $data);
1973 private function widgets($metrics, $history) {
1976 'label' =>
'Benutzer',
1977 'value' => $this->fmt($metrics[
'users']),
1978 'raw' => $metrics[
'users'],
1979 'note' => $this->fmt($metrics[
'active_users']) .
' aktiv',
1980 'icon' =>
'bi-people',
1982 'spark' => $this->spark_values($history,
'users', $metrics[
'users']),
1983 'trend' => $this->trend_text($history,
'users'),
1986 'label' =>
'Online',
1987 'value' => $this->fmt($metrics[
'online']),
1988 'raw' => $metrics[
'online'],
1989 'note' => $this->fmt($metrics[
'sessions']) .
' Sessions',
1990 'icon' =>
'bi-broadcast-pin',
1992 'spark' => $this->spark_values($history,
'online', $metrics[
'online']),
1993 'trend' => $this->trend_text($history,
'online'),
1996 'label' =>
'Module',
1997 'value' => $this->fmt($metrics[
'modules']),
1998 'raw' => $metrics[
'modules'],
1999 'note' => $this->fmt($metrics[
'dd_count']) .
' DDs',
2000 'icon' =>
'bi-grid-1x2',
2002 'spark' => $this->spark_values($history,
'modules', $metrics[
'modules']),
2003 'trend' => $this->trend_text($history,
'modules'),
2006 'label' =>
'Datensaetze',
2007 'value' => $this->fmt($metrics[
'records']),
2008 'raw' => $metrics[
'records'],
2009 'note' => $this->fmt($metrics[
'tables']) .
' Tabellen',
2010 'icon' =>
'bi-database-check',
2012 'spark' => $this->spark_values($history,
'records', $metrics[
'records']),
2013 'trend' => $this->trend_text($history,
'records'),
2016 'label' =>
'Datenbanken',
2017 'value' => $this->fmt($metrics[
'databases']),
2018 'raw' => $metrics[
'databases'],
2019 'note' => $this->fmt($metrics[
'existing_dd']) .
' Quellen ok',
2020 'icon' =>
'bi-hdd-stack',
2022 'spark' => $this->spark_values($history,
'databases', $metrics[
'databases']),
2023 'trend' => $this->trend_text($history,
'databases'),
2026 'label' =>
'Systemzustand',
2027 'value' => (
int) $metrics[
'health_percent'] .
'%',
2028 'raw' => $metrics[
'health_percent'],
2029 'note' => $this->fmt($metrics[
'sysmsg_risk']) .
' Warnungen/Fehler',
2030 'icon' =>
'bi-shield-check',
2032 'spark' => $this->spark_values($history,
'health_percent', $metrics[
'health_percent']),
2033 'trend' => $this->trend_text($history,
'health_percent',
'%'),
2037 'value' => $this->fmt($metrics[
'request_runtime_ms']) .
' ms',
2038 'raw' => $metrics[
'request_runtime_ms'],
2039 'note' =>
'PHP Request',
2040 'icon' =>
'bi-speedometer2',
2042 'spark' => $this->spark_values($history,
'request_runtime_ms', $metrics[
'request_runtime_ms']),
2043 'trend' => $this->trend_text($history,
'request_runtime_ms',
' ms'),
2046 'label' =>
'DBX Memory',
2047 'value' => $this->fmt($metrics[
'memory_peak_kb']) .
' KB',
2048 'raw' => $metrics[
'memory_peak_kb'],
2049 'note' =>
'dbx Verbrauch',
2050 'icon' =>
'bi-memory',
2052 'spark' => $this->spark_values($history,
'memory_peak_kb', $metrics[
'memory_peak_kb']),
2053 'trend' => $this->trend_text($history,
'memory_peak_kb',
' KB'),
2058 foreach ($widgets as $widget) {
2059 $content .= $this->widget($widget);
2065 private function database_rows($metrics) {
2067 $inventory = $metrics[
'inventory'] ?? array();
2069 usort($inventory,
function ($a, $b) {
2070 return ((
int) ($b[
'count'] ?? 0)) <=> ((int) ($a[
'count'] ?? 0));
2074 foreach ($inventory as $row) {
2075 $server = (string) ($row[
'server'] ??
'');
2076 $table = (string) ($row[
'table'] ??
'');
2079 if ($key ===
'|' || isset($seen[$key])) {
2084 $count = (int) ($row[
'count'] ?? 0);
2088 'count' => $this->fmt(
$count),
2089 'percent' => $this->percent(
$count, max(1, (
int) ($metrics[
'records'] ?? 0))),
2090 'status' => !empty($row[
'exist']) ?
'ok' :
'fehlt',
2091 'tone' => !empty($row[
'exist']) ?
'ok' :
'warn',
2094 if (count(
$rows) >= 7) {
2101 'title' =>
'Keine Tabellen',
2102 'meta' =>
'Noch keine DD-Tabellen gefunden',
2113 private function database_report($metrics) {
2114 $oReport = new \dbxReport();
2115 $panelTarget =
'database-report';
2116 $oReport->init(
'admin-dashboard-db',
'admin-dashboard-db-report');
2117 $oReport->add_obj(
'database_bar',
'dbx|component-bar', $this->card_bar_data(
2118 'Datenbanken und Tabellen',
2121 $this->card_action(
'?dbx_modul=dbxAdmin&dbx_run1=db&dbx_run2=list_db',
'DB Sync')
2122 . $this->collapse_action($panelTarget,
'Zuklappen',
true)
2124 $oReport->add_rep(
'panel_target',
dbx()->
esc($panelTarget));
2125 $oReport->_mode =
'tpl';
2126 $oReport->_pages = 0;
2127 $oReport->_rdata = $this->database_rows($metrics);
2128 $oReport->_rcount = count($oReport->_rdata);
2129 $oReport->_rrows = 20;
2130 $oReport->_msg_info =
'';
2132 return $oReport->run();
2135 private function activity_rows($metrics) {
2137 $traceRows = $this->safe_select(
'dbxTrace',
'', array(
'id',
'create_date',
'action',
'dd',
'record_id'),
'create_date',
'DESC',
'', 5);
2139 foreach ($traceRows as $row) {
2140 $title = trim((
string) ($row[
'action'] ??
'Trace'));
2141 $dd = trim((
string) ($row[
'dd'] ??
''));
2142 $record = trim((
string) ($row[
'record_id'] ??
''));
2145 'icon' =>
'bi-clock-history',
2146 'title' =>
dbx()->
esc($title !==
'' ? ucfirst($title) :
'Trace'),
2147 'meta' =>
dbx()->
esc((
$dd !==
'' ?
$dd :
'Datensatz') . ($record !==
'' ?
' #' . $record :
'')),
2148 'time' =>
dbx()->
esc($row[
'create_date'] ??
''),
2153 if (count(
$rows) < 5) {
2154 $msgRows = $this->safe_select(
'dbxSysMsg',
'', array(
'id',
'create_date',
'status',
'modul',
'message'),
'create_date',
'DESC',
'', 5 - count(
$rows));
2155 foreach ($msgRows as $row) {
2156 $message = trim(strip_tags((
string) ($row[
'message'] ??
'Systemmeldung')));
2157 if (strlen($message) > 92) {
2158 $message = substr($message, 0, 89) .
'...';
2162 'icon' =>
'bi-info-circle',
2163 'title' =>
dbx()->
esc($row[
'status'] ??
'Info'),
2164 'meta' =>
dbx()->
esc($message),
2165 'time' =>
dbx()->
esc($row[
'create_date'] ??
''),
2173 'icon' =>
'bi-check2-circle',
2174 'title' =>
'Keine Aktivitaet',
2175 'meta' =>
'Trace und Systemmeldungen sind aktuell leer.',
2184 private function activity_report($metrics) {
2185 $oReport = new \dbxReport();
2186 $oReport->init(
'admin-dashboard-activity',
'admin-dashboard-activity-report');
2187 $oReport->add_obj(
'activity_bar',
'dbx|component-bar', $this->card_bar_data(
'Aktivitaet',
'bi-clock-history'));
2188 $oReport->_mode =
'tpl';
2189 $oReport->_pages = 0;
2190 $oReport->_rdata = $this->activity_rows($metrics);
2191 $oReport->_rcount = count($oReport->_rdata);
2192 $oReport->_rrows = 10;
2193 $oReport->_msg_info =
'';
2195 return $oReport->run();
2198 private function quick_actions() {
2199 $oForm = new \dbxForm();
2200 $oForm->init(
'admin-dashboard-actions',
'admin-dashboard-actions');
2201 $oForm->_fd =
'dbxAdmin|admin-dashboard-status';
2202 $oForm->load_fd_messages();
2203 $oForm->_action =
'?dbx_modul=dbxAdmin&dbx_run1=run';
2204 $oForm->_msg_info =
'';
2205 $oForm->add_obj(
'actions_bar',
'dbx|component-bar', $this->card_bar_data(
'Quick Actions',
'bi-lightning-charge'));
2208 'users' => array(
'href' =>
'?dbx_modul=dbxAdmin&dbx_run1=user&dbx_run2=list_user',
'icon' =>
'bi-people',
'label' =>
'Benutzer',
'ajax_class' =>
'dbxAjax'),
2209 'sessions' => array(
'href' =>
'?dbx_modul=dbxAdmin&dbx_run1=session&dbx_run2=list_session',
'icon' =>
'bi-broadcast',
'label' =>
'Sessions',
'ajax_class' =>
'dbxAjax'),
2210 'modules' => array(
'href' =>
'?dbx_modul=dbxAdmin&dbx_run1=modules&dbx_run2=modul_list',
'icon' =>
'bi-grid',
'label' =>
'Module',
'ajax_class' =>
'dbxAjax'),
2211 'dd' => array(
'href' =>
'?dbx_modul=dbxAdmin&dbx_run1=dd&dbx_run2=list_dd',
'icon' =>
'bi-diagram-3',
'label' =>
'DD Sync',
'ajax_class' =>
'dbxAjax'),
2212 'db' => array(
'href' =>
'?dbx_modul=dbxAdmin&dbx_run1=db&dbx_run2=list_db',
'icon' =>
'bi-hdd-stack',
'label' =>
'DB Sync',
'ajax_class' =>
'dbxAjax'),
2213 'sysmsg' => array(
'href' =>
'?dbx_modul=dbxAdmin&dbx_run1=sysmsg&dbx_run2=list_sysmsg',
'icon' =>
'bi-bell',
'label' =>
'SysMsg',
'ajax_class' =>
'dbxAjax'),
2214 'update' => array(
'href' =>
'?dbx_modul=dbxAdmin&dbx_run1=update',
'icon' =>
'bi-arrow-repeat',
'label' => $oForm->get_fd_message(
'update_title'),
'ajax_class' =>
''),
2217 foreach (
$actions as $key => $data) {
2218 $oForm->add_obj(
'action_' . $key,
'dbxAdmin|admin-dashboard-action-link', $data);
2221 return $oForm->run();
2225 dbx()->load_content_cache_classes();
2228 private function content_cache_language_catalog(): array {
2230 'de' => array(
'label' =>
'Deutsch',
'flag' =>
'🇩🇪',
'tone' =>
'teal'),
2231 'en' => array(
'label' =>
'English',
'flag' =>
'🇬🇧',
'tone' =>
'navy'),
2232 'es' => array(
'label' =>
'Español',
'flag' =>
'🇪🇸',
'tone' =>
'amber'),
2236 private function content_cache_enabled_config():
bool {
2237 return \dbx\dbxContent\dbxContentPageCache::isConfigEnabled();
2240 private function dbx_config_bool(
string $key, $default = 0):
bool {
2241 $value =
dbx()->get_config(
'dbx', $key);
2246 return (
int)
$value === 1;
2249 private function performance_request_enabled_config():
bool {
2250 return $this->dbx_config_bool(
'performance_timer_request',
dbx()->get_config(
'dbx',
'performance_timer'));
2253 private function performance_db_enabled_config():
bool {
2254 return $this->dbx_config_bool(
'performance_timer_db',
dbx()->get_config(
'dbx',
'performance_timer_detail'));
2257 private function normalize_performance_level($level):
string {
2258 $level = strtolower(trim((
string) $level));
2259 if ($level ===
'details') {
2263 return in_array($level, array(
'off',
'main',
'detail'),
true) ? $level :
'off';
2266 private function performance_level_config():
string {
2267 $level =
dbx()->get_config(
'dbx',
'performance_timer_level');
2268 if ($level !==
'undef' && $level !==
'' && $level !==
null) {
2269 return $this->normalize_performance_level($level);
2272 return ($this->performance_request_enabled_config() || $this->performance_db_enabled_config()) ?
'detail' :
'off';
2275 private function set_performance_level_config(
string $level):
bool {
2276 $level = $this->normalize_performance_level($level);
2282 $enabled = $level !==
'off';
2283 $detail = $level ===
'detail';
2284 $config[
'performance_timer_level'] = $level;
2285 $config[
'performance_timer'] = $enabled ? 1 : 0;
2286 $config[
'performance_timer_request'] = $enabled ? 1 : 0;
2287 $config[
'performance_timer_db'] = $enabled ? 1 : 0;
2288 $config[
'performance_timer_detail'] = $detail ? 1 : 0;
2290 return (
int)
dbx()->set_config(
'dbx',
$config) > 0;
2293 private function set_performance_config(
string $target,
bool $enabled):
bool {
2295 return $this->set_performance_level_config(
'off');
2298 return $this->set_performance_level_config(
'detail');
2301 private function process_performance_config_action():
bool {
2302 $run2 =
dbx()->get_modul_var(
'dbx_run2',
'',
'parameter');
2303 if ($run2 !==
'performance_save') {
2307 if (isset(
$_POST[
'performance_level'])) {
2308 return $this->set_performance_level_config((
string)
$_POST[
'performance_level']);
2311 $target = strtolower(trim((
string) (
$_POST[
'performance_target'] ??
'request')));
2313 $enabled = isset(
$_POST[
'performance_enabled']);
2314 return $this->set_performance_config(
$target, $enabled);
2317 private function is_ajax_request():
bool {
2318 return (
int)
dbx()->get_system_var(
'dbx_ajax', 0,
'int') === 1;
2321 private function respond_dashboard_ajax_html(
string $html):
void {
2322 if (!headers_sent()) {
2323 header(
'Content-Type: text/html; charset=utf-8');
2328 $oSession =
dbx()->get_system_obj(
'dbxSession');
2329 if (is_object($oSession) && method_exists($oSession,
'save_session')) {
2330 $oSession->save_session();
2336 private function performance_level_options(
string $current):
string {
2339 'main' =>
'Nur Hauptkennzahlen',
2340 'detail' =>
'Hauptkennzahlen und Details',
2345 $selected =
$value === $current ?
' selected' :
'';
2346 $html .=
'<option value="' .
dbx()->esc(
$value) .
'"' . $selected .
'>' .
dbx()->esc($label) .
'</option>';
2352 private function performance_level_control():
string {
2353 $level = $this->performance_level_config();
2356 'label' =>
'Performance aus',
2357 'hint' =>
'dbxapp schreibt aktuell keine neuen Performance-Daten.',
2358 'icon' =>
'bi-pause-circle-fill',
2362 'label' =>
'Performance Hauptkennzahlen',
2363 'hint' =>
'dbxapp schreibt nur PHP/System, JS und DB Gesamtwerte.',
2364 'icon' =>
'bi-check-circle-fill',
2368 'label' =>
'Performance Details',
2369 'hint' =>
'dbxapp schreibt Hauptkennzahlen und Detail-Timer.',
2370 'icon' =>
'bi-check-circle-fill',
2374 $state = $meta[$level] ?? $meta[
'off'];
2376 $oForm = new \dbxForm();
2377 $oForm->init(
'admin-dashboard-performance-config',
'admin-dashboard-performance-config');
2378 $oForm->set_form_help_enabled(
false);
2379 $oForm->_action =
'?dbx_modul=dbxAdmin&dbx_run1=run&dbx_run2=performance_save';
2380 $oForm->_msg_info =
'';
2381 $oForm->add_rep(
'action',
dbx()->
esc(
'?dbx_modul=dbxAdmin&dbx_run1=run&dbx_run2=performance_save'));
2382 $oForm->add_rep(
'status_tone',
dbx()->
esc(
$state[
'tone']));
2383 $oForm->add_rep(
'status_icon',
dbx()->
esc(
$state[
'icon']));
2384 $oForm->add_rep(
'status_label',
dbx()->
esc(
$state[
'label']));
2385 $oForm->add_rep(
'status_hint',
dbx()->
esc(
$state[
'hint']));
2386 $oForm->add_rep(
'performance_level_options', $this->performance_level_options($level));
2388 return $oForm->add_norep($oForm->run());
2391 private function performance_toggle_control(
string $target):
string {
2392 return $this->performance_level_control();
2395 private function content_cache_files_for_lng(
string $lng):
int {
2397 $lng = strtolower(trim(
$lng));
2403 return count(glob(
$base .
'full-page/*_' .
$lng .
'_*.htm') ?: array());
2406 private function content_cache_language_rows():
string {
2408 $tpl =
dbx()->get_system_obj(
'dbxTPL');
2409 $catalog = $this->content_cache_language_catalog();
2411 $tones = array(
'teal',
'navy',
'amber',
'cyan',
'purple',
'green');
2415 foreach ($lngs as
$lng) {
2416 $lng = strtolower(trim((
string)
$lng));
2422 'label' => strtoupper(
$lng),
2424 'tone' => $tones[$toneIndex % count($tones)],
2428 $pagesTotal = $this->safe_count(\
dbx\
dbxContent\dbxContentLng::ddContent(
$lng));
2429 $pagesActive = $this->safe_count(\
dbx\
dbxContent\dbxContentLng::ddContent(
$lng),
'activ = 1');
2432 $rows .=
$tpl->get_tpl(
'dbxAdmin|admin-dashboard-content-cache-lng', array(
2433 'flag' =>
dbx()->
esc($meta[
'flag'] ??
'🌐'),
2434 'label' =>
dbx()->
esc($meta[
'label'] ?? strtoupper(
$lng)),
2436 'tone' =>
dbx()->
esc($meta[
'tone'] ??
'teal'),
2437 'pages_total' => $this->fmt($pagesTotal),
2438 'pages_active' => $this->fmt($pagesActive),
2439 'folders' => $this->fmt($folders),
2440 'cached' => $this->fmt($this->content_cache_files_for_lng(
$lng)),
2445 $rows =
'<article class="dbx-admin-dashboard-cache-lng-card dbx-admin-dashboard-cache-lng-empty">'
2446 .
'<div class="dbx-admin-dashboard-cache-lng-title"><strong>Keine Sprachen konfiguriert</strong></div>'
2453 private function process_content_cache_action():
void {
2454 $run2 =
dbx()->get_modul_var(
'dbx_run2',
'',
'parameter');
2461 if ($run2 ===
'cache_flush') {
2466 if ($run2 ===
'sitemap_rebuild') {
2471 if ($run2 ===
'cache_save') {
2472 $enabled = isset(
$_POST[
'cache_content']);
2477 private function content_cache_panel_body_data(): array {
2481 $enabled = $this->content_cache_enabled_config();
2484 'cache_save_url' =>
dbx()->
esc(
'?dbx_modul=dbxAdmin&dbx_run1=run&dbx_run2=cache_save'),
2485 'cache_flush_url' =>
dbx()->
esc(
'?dbx_modul=dbxAdmin&dbx_run1=run&dbx_run2=cache_flush'),
2486 'sitemap_rebuild_url' =>
dbx()->
esc(
'?dbx_modul=dbxAdmin&dbx_run1=run&dbx_run2=sitemap_rebuild'),
2488 'cache_admin_url' =>
dbx()->
esc(
'?dbx_modul=dbxAdmin&dbx_run1=cache'),
2489 'cache_enabled_checked' => $enabled ?
'checked' :
'',
2490 'cache_status_tone' => $enabled ?
'on' :
'off',
2491 'cache_status_icon' => $enabled ?
'bi-check-circle-fill' :
'bi-pause-circle-fill',
2492 'cache_status_label' =>
dbx()->
esc($enabled ?
'Cache-Schreiben aktiv' :
'Cache-Schreiben pausiert'),
2493 'cache_status_hint' => $enabled
2494 ?
dbx()->
esc(
'Vorhandene Gastseiten werden gelesen; Cache-Misses werden als vollstaendige Endausgabe gespeichert.')
2496 .
dbx()->
esc(
'Head-Metadaten, Design, Menues und Module sind fertig aufgeloest; ein HIT braucht nur den Session-Zugriff, aber keine Content-Datenbank.')
2497 :
dbx()->
esc(
'Vorhandene Gastseiten werden weiterhin aus dem Cache ausgegeben. Cache-Misses werden live gerendert, aber nicht neu gespeichert.'),
2498 'cache_content_count' => $this->fmt((
int) ($stats[
'content'] ?? 0)),
2499 'sitemap_count' => $this->fmt((
int) ($sitemapStats[
'urls'] ?? 0)),
2500 'sitemap_generated' =>
dbx()->
esc((
string) ($sitemapStats[
'generated_at'] ??
'')),
2501 'sitemap_state' =>
dbx()->
esc(!empty($sitemapStats[
'exists']) ?
'vorhanden' :
'nicht erstellt'),
2502 'cache_dir' =>
dbx()->
esc((
string) ($stats[
'base_dir'] ??
'')),
2503 'lng_rows' => $this->content_cache_language_rows(),
2507 private function content_cache_panel_body_html():
string {
2508 $oForm = new \dbxForm();
2509 $oForm->init(
'admin-dashboard-content-cache-body',
'admin-dashboard-content-cache-body');
2510 $oForm->_action =
'?dbx_modul=dbxAdmin&dbx_run1=run';
2511 $oForm->_msg_info =
'';
2513 foreach ($this->content_cache_panel_body_data() as $key =>
$value) {
2514 $oForm->add_rep($key,
$value);
2517 return $oForm->add_norep($oForm->run());
2520 private function content_cache_bar_actions_html():
string {
2522 $enabled = $this->content_cache_enabled_config();
2523 $oForm = new \dbxForm();
2524 $oForm->init(
'admin-dashboard-content-cache-actions',
'admin-dashboard-content-cache-actions');
2525 $oForm->_action =
'?dbx_modul=dbxAdmin&dbx_run1=run&dbx_run2=cache_save';
2526 $oForm->_msg_info =
'';
2527 $oForm->add_rep(
'cache_save_url',
dbx()->
esc(
'?dbx_modul=dbxAdmin&dbx_run1=run&dbx_run2=cache_save'));
2528 $oForm->add_rep(
'cache_flush_url',
dbx()->
esc(
'?dbx_modul=dbxAdmin&dbx_run1=run&dbx_run2=cache_flush'));
2529 $oForm->add_rep(
'sitemap_rebuild_url',
dbx()->
esc(
'?dbx_modul=dbxAdmin&dbx_run1=run&dbx_run2=sitemap_rebuild'));
2530 $oForm->add_rep(
'cache_enabled_checked', $enabled ?
'checked' :
'');
2531 $oForm->add_rep(
'bar_extra', $this->help_action(
'cache'));
2533 return $oForm->add_norep($oForm->run());
2536 private function content_cache_panel() {
2537 $oForm = new \dbxForm();
2538 $panelTarget =
'content-cache';
2539 $oForm->init(
'admin-dashboard-content-cache',
'admin-dashboard-content-cache');
2540 $oForm->_action =
'?dbx_modul=dbxAdmin&dbx_run1=run';
2541 $oForm->_msg_info =
'';
2542 $oForm->add_obj(
'cache_bar',
'dbx|component-bar', $this->card_bar_data(
2543 'Gast-Full-Page-Cache',
2544 'bi-lightning-charge-fill',
2545 'Komplette Endausgabe gueltiger Permalinks, ausschliesslich fuer nicht angemeldete Gaeste',
2546 $this->content_cache_bar_actions_html()
2547 . $this->card_action(
'?dbx_modul=dbxContent_admin&dbx_run1=content&dbx_run2=edit',
'CMS')
2548 . $this->collapse_action($panelTarget,
'Zuklappen',
true)
2550 $oForm->add_rep(
'panel_target',
dbx()->
esc($panelTarget));
2551 $oForm->add_rep(
'cache_body', $this->content_cache_panel_body_html());
2553 return $oForm->run();
2556 private function hero_panel($metrics) {
2557 $heroPerformance = $this->hero_performance($metrics);
2558 $oForm = new \dbxForm();
2559 $oForm->init(
'admin-dashboard-hero',
'admin-dashboard-hero');
2560 $oForm->_fd =
'dbxAdmin|admin-dashboard-status';
2561 $oForm->load_fd_messages();
2562 $oForm->_msg_info =
'';
2563 $oForm->add_rep(
'bar_class',
'dbx-admin-dashboard-hero-bar');
2564 $oForm->add_rep(
'bar_title', $oForm->get_fd_message(
'bar_title'));
2565 $oForm->add_rep(
'bar_subtitle', $oForm->get_fd_message(
'bar_subtitle'));
2569 '<small class="dbx-bar-meta">'
2570 .
dbx()->
esc($oForm->get_fd_message(
'status_timestamp'))
2572 .
dbx()->
esc(date(
'd.m.Y H:i'))
2575 $oForm->add_rep(
'health_percent', (
int) $metrics[
'health_percent']);
2576 $hasErrorLog = !empty($metrics[
'health_error_log']);
2577 $healthReason = $hasErrorLog
2578 ? $oForm->get_fd_message(
'health_error')
2579 : (string)($metrics[
'health_reason'] ?? $oForm->get_fd_message(
'health_ok'));
2580 if ($healthReason ===
'OK') {
2581 $healthReason = $oForm->get_fd_message(
'health_ok');
2583 $oForm->add_rep(
'health_reason',
dbx()->
esc($healthReason));
2584 $oForm->add_rep(
'health_state_class', $hasErrorLog ?
'is-error' :
'is-ok');
2585 $oForm->add_rep(
'health_icon', $hasErrorLog ?
'bi-exclamation-octagon-fill' :
'bi-shield-check');
2586 $oForm->add_rep(
'performance_aria', $oForm->get_fd_message(
'performance_aria'));
2587 $oForm->add_rep(
'system_status_label', $oForm->get_fd_message(
'system_status_label'));
2588 $oForm->add_obj(
'hero_performance_gauges',
'obj-value', $this->hero_performance_gauges($heroPerformance));
2589 $oForm->add_obj(
'hero_status_summaries',
'obj-value', $this->hero_status_summaries());
2590 $oForm->add_obj(
'update_status',
'obj-value', $this->update_status_panel($oForm));
2591 $oForm->add_obj(
'error_log',
'obj-value', $hasErrorLog ? $this->error_log_panel($oForm) :
'');
2593 return $oForm->run();
2596 private function widgets_panel($metrics) {
2597 $oForm = new \dbxForm();
2598 $oForm->init(
'admin-dashboard-widgets',
'admin-dashboard-widgets');
2599 $oForm->_msg_info =
'';
2600 $oForm->add_obj(
'widgets',
'obj-value', $this->widgets($metrics, $this->metric_history($metrics)));
2602 return $oForm->run();
2605 private function chart_json($metrics) {
2607 array(
'label' =>
'Benutzer',
'value' => (
int) $metrics[
'users'],
'tone' =>
'teal'),
2608 array(
'label' =>
'Online',
'value' => (
int) $metrics[
'online'],
'tone' =>
'green'),
2609 array(
'label' =>
'Module',
'value' => (
int) $metrics[
'modules'],
'tone' =>
'navy'),
2610 array(
'label' =>
'DDs',
'value' => (
int) $metrics[
'dd_count'],
'tone' =>
'cyan'),
2611 array(
'label' =>
'DBs',
'value' => (
int) $metrics[
'databases'],
'tone' =>
'amber'),
2614 return dbx()->esc(json_encode($data, JSON_UNESCAPED_UNICODE | JSON_HEX_APOS | JSON_HEX_QUOT));
2617 private function chart_panel($metrics) {
2618 $oForm = new \dbxForm();
2619 $oForm->init(
'admin-dashboard-chart-panel',
'admin-dashboard-chart-panel');
2620 $oForm->_msg_info =
'';
2621 $oForm->add_rep(
'chart_json', $this->chart_json($metrics));
2622 $oForm->add_rep(
'trace_count', $this->fmt($metrics[
'trace_count']));
2623 $oForm->add_rep(
'missing_count', $this->fmt($metrics[
'missing']));
2624 $oForm->add_rep(
'autosync_count', $this->fmt($metrics[
'autosync']));
2625 $oForm->add_rep(
'trace_enabled_count', $this->fmt($metrics[
'trace_enabled']));
2626 $oForm->add_rep(
'request_runtime_ms', $this->fmt($metrics[
'request_runtime_ms']));
2627 $oForm->add_rep(
'memory_peak_kb', $this->fmt($metrics[
'memory_peak_kb']));
2628 $oForm->add_obj(
'chart_bar',
'dbx|component-bar', $this->card_bar_data(
'Grafische Auswertung',
'bi-bar-chart-line',
'Relative Verteilung'));
2630 return $oForm->run();
2633 private function dashboard_area($area) {
2634 $metrics = $this->metrics();
2636 switch ((
string) $area) {
2638 return $this->hero_panel($metrics);
2640 return $this->widgets_panel($metrics);
2641 case 'quick_actions':
2642 return $this->quick_actions();
2643 case 'performance_panel':
2644 return $this->performance_panel($metrics);
2645 case 'sysmsg_panel':
2646 return $this->sysmsg_panel();
2647 case 'session_panel':
2648 return $this->session_panel();
2649 case 'content_cache_panel':
2650 return $this->content_cache_panel();
2652 return $this->chart_panel($metrics);
2653 case 'activity_report':
2654 return $this->activity_report($metrics);
2655 case 'database_report':
2656 return $this->database_report($metrics);
2663 $run2 =
dbx()->get_modul_var(
'dbx_run2',
'',
'parameter');
2664 if ($run2 ===
'delete_error_log') {
2665 $this->process_error_log_action();
2668 if ($run2 ===
'performance_compress') {
2669 return $this->run_performance_maintenance_process(
'compress');
2671 if ($run2 ===
'performance_clear') {
2672 return $this->run_performance_maintenance_process(
'clear');
2675 if ($this->is_ajax_request()) {
2676 if ($run2 ===
'cache_flush' || $run2 ===
'cache_save' || $run2 ===
'sitemap_rebuild') {
2677 $this->process_content_cache_action();
2678 $this->respond_dashboard_ajax_html($this->content_cache_panel_body_html());
2681 if ($run2 ===
'performance_save') {
2682 $this->process_performance_config_action();
2683 $target = strtolower(trim((
string) (
$_POST[
'performance_target'] ??
'request')));
2685 $this->respond_dashboard_ajax_html($this->performance_toggle_control(
$target));
2688 if ($run2 ===
'sysmsg_level_save') {
2689 $this->process_sys_msg_level_action();
2690 $this->respond_dashboard_ajax_html($this->sys_msg_level_control());
2693 if ($run2 ===
'session_db_save') {
2694 $this->process_session_db_action();
2695 $this->respond_dashboard_ajax_html($this->session_panel_control_html());
2699 if ($run2 ===
'performance_save') {
2700 $this->process_performance_config_action();
2702 if ($run2 ===
'sysmsg_level_save') {
2703 $this->process_sys_msg_level_action();
2705 if ($run2 ===
'session_db_save') {
2706 $this->process_session_db_action();
2708 if ($run2 ===
'cache_flush' || $run2 ===
'cache_save' || $run2 ===
'sitemap_rebuild') {
2709 $this->process_content_cache_action();
2713 $areaContent = $this->dashboard_area($run2);
2714 if ($areaContent !==
null) {
2715 return $areaContent;
2719 $oForm = new \dbxForm();
2721 $oForm->init(
'admin-dashboard',
'admin-dashboard');
2722 $oForm->_fd =
'dbxAdmin|admin-dashboard-status';
2723 $oForm->load_fd_messages();
2724 $oForm->_action =
'?dbx_modul=dbxAdmin&dbx_run1=run';
2725 $oForm->_msg_info =
'';
2727 if ($this->dashboardMessageKey !==
'') {
2728 $message = $oForm->get_fd_message($this->dashboardMessageKey);
2729 if ($this->dashboardMessageError) {
2730 $oForm->_msg_error = $message;
2732 $oForm->_msg_success = $message;
2736 $messageHtml = $this->default_admin_password_warning();
2737 if ($this->dashboardMessageKey !==
'') {
2738 $message =
dbx()->esc($oForm->get_fd_message($this->dashboardMessageKey));
2739 $tone = $this->dashboardMessageError ?
'danger' :
'success';
2740 $icon = $this->dashboardMessageError
2741 ?
'bi-exclamation-triangle-fill'
2742 :
'bi-check-circle-fill';
2743 $messageHtml .=
'<div class="alert alert-' . $tone
2744 .
' d-flex align-items-center gap-2 mb-3" role="alert">'
2745 .
'<i class="bi ' . $icon .
'" aria-hidden="true"></i>'
2746 .
'<span>' . $message .
'</span></div>';
2748 $oForm->add_obj(
'dashboard_message',
'obj-value', $messageHtml);
2750 $updateState = $this->update_state($this->update_status());
2751 $oForm->add_rep(
'update_nav_class',
dbx()->
esc($updateState[
'class']));
2754 dbx()->
esc($oForm->get_fd_message($updateState[
'nav_message']))
2756 $oForm->add_rep(
'update_nav_label',
dbx()->
esc($oForm->get_fd_message(
'update_nav_title')));
2758 $metrics = $this->metrics();
2759 $this->store_history_snapshot($metrics);