diff --git a/statuspulse.php b/statuspulse.php index bd9df12..54ed7ac 100644 --- a/statuspulse.php +++ b/statuspulse.php @@ -14,8 +14,10 @@ if (!class_exists('StatusAPI_Backend_Helper')) { class StatusAPI_Backend_Helper { const OPTION_KEY = 'statusapi_backend_helper_options'; const STATE_OPTION_KEY = 'statusapi_backend_helper_state'; + const HISTORY_OPTION_KEY = 'statusapi_backend_helper_history'; const MENU_SLUG = 'statuspulse'; const MESSAGES_SLUG = 'statuspulse-meldungen'; + const LIST_SLUG = 'statuspulse-liste'; public static function boot() { add_action('admin_menu', array(__CLASS__, 'register_menu')); @@ -45,62 +47,179 @@ if (!class_exists('StatusAPI_Backend_Helper')) { $messages = isset($state['messages']) && is_array($state['messages']) ? $state['messages'] : array(); $last_message = !empty($messages) ? $messages[0] : null; - $status_color = '#f59e0b'; + $status_color = '#dc3545'; + $status_text = 'OFFLINE'; if ($live_status['status'] === 'ok') { - $status_color = '#16a34a'; - } elseif ($live_status['status'] === 'error') { - $status_color = '#dc2626'; + $status_color = '#28a745'; + $status_text = 'ONLINE'; + } elseif ($live_status['status'] === 'warn') { + $status_color = '#ffc107'; + $status_text = 'WARNUNG'; } - echo '
'; - echo '
'; - echo '
'; - echo '
Verbindungsstatus
'; - echo '
' . esc_html($live_status['label']) . '
'; - echo '
' . esc_html($live_status['meta']) . '
'; + echo ''; + + echo '
'; + + echo '
'; + echo '

StatusPulse Monitor

'; + echo '' . esc_html($status_text) . ''; echo '
'; - echo '
'; - echo '
Letzte Meldung
'; + echo '
'; + + echo '
'; + + echo '
'; + echo 'Status'; + echo '
' . esc_html($live_status['label']) . '
'; + echo '
' . esc_html($live_status['meta']) . '
'; + echo '
'; + + echo '
'; + echo 'Letzte Meldung'; if ($last_message !== null) { $msg_title = isset($last_message['title']) ? (string) $last_message['title'] : 'Meldung'; $msg_text = isset($last_message['message']) ? (string) $last_message['message'] : ''; - $msg_ts = isset($last_message['ts']) ? (int) $last_message['ts'] : 0; - echo '
' . esc_html($msg_title) . '
'; - echo '
' . esc_html($msg_text) . '
'; - echo '
' . esc_html(self::format_timestamp($msg_ts)) . '
'; + echo '
' . esc_html($msg_title) . '
'; + echo '
' . esc_html(substr($msg_text, 0, 50)) . '
'; } else { - echo '
Noch keine Meldungen vorhanden.
'; + echo '
Keine Meldungen
'; } echo '
'; + echo '
'; - echo '
'; - echo '
'; - echo '
HTTP
'; - echo '
' . esc_html($live_status['http_code'] > 0 ? (string) $live_status['http_code'] : 'n/a') . '
'; + echo '
'; + echo '
HTTP
' . esc_html($live_status['http_code'] > 0 ? (string) $live_status['http_code'] : '—') . '
'; + echo '
Spieler
' . esc_html($live_status['metrics']['online_players']) . '
'; + echo '
RAM
' . esc_html($live_status['metrics']['memory']) . '
'; + echo '
Check
' . esc_html(self::format_timestamp($state['last_check_at'])) . '
'; echo '
'; - echo '
'; - echo '
Spieler online
'; - echo '
' . esc_html($live_status['metrics']['online_players']) . '
'; + echo ''; - echo '
'; - echo '
RAM
'; - echo '
' . esc_html($live_status['metrics']['memory']) . '
'; echo '
'; - - echo '
'; - echo '
Zuletzt geprüft
'; - echo '
' . esc_html(self::format_timestamp($state['last_check_at'])) . '
'; - echo '
'; - echo '
'; - - echo '

'; - echo 'StatusPulse öffnen '; - echo 'Meldungen öffnen'; - echo '

'; echo '
'; } @@ -123,6 +242,15 @@ if (!class_exists('StatusAPI_Backend_Helper')) { self::MESSAGES_SLUG, array(__CLASS__, 'render_messages_page') ); + + add_submenu_page( + self::MENU_SLUG, + 'Angriffsversuche', + 'Angriffsversuche', + 'manage_options', + self::LIST_SLUG, + array(__CLASS__, 'render_list_page') + ); } public static function register_settings() { @@ -200,6 +328,33 @@ if (!class_exists('StatusAPI_Backend_Helper')) { if (count($state['messages']) > 60) { $state['messages'] = array_slice($state['messages'], 0, 60); } + + self::push_history_entry($level, $title, $message); + } + + private static function get_history() { + $stored = get_option(self::HISTORY_OPTION_KEY, array()); + return is_array($stored) ? $stored : array(); + } + + private static function save_history($history) { + update_option(self::HISTORY_OPTION_KEY, is_array($history) ? $history : array(), false); + } + + private static function push_history_entry($level, $title, $message) { + $history = self::get_history(); + array_unshift($history, array( + 'ts' => time(), + 'level' => (string) $level, + 'title' => (string) $title, + 'message' => (string) $message, + )); + + if (count($history) > 1000) { + $history = array_slice($history, 0, 1000); + } + + self::save_history($history); } private static function build_messages_markup($state) { @@ -375,6 +530,64 @@ if (!class_exists('StatusAPI_Backend_Helper')) { echo '
'; } + public static function render_list_page() { + if (!current_user_can('manage_options')) { + return; + } + + $options = self::get_options(); + $notice = self::build_notice(); + $attacker_data = self::fetch_attacker_entries($options['statusapi_base_url']); + $rows = isset($attacker_data['entries']) && is_array($attacker_data['entries']) ? $attacker_data['entries'] : array(); + $list_error = isset($attacker_data['error']) ? (string) $attacker_data['error'] : ''; + + echo '
'; + self::render_styles(); + + echo '

StatusPulse Angriffsversuche

'; + echo '

Zeigt nur Spielername und UUID von erkannten Angriffsversuchen (inkl. Datum/Uhrzeit).

'; + + if ($notice !== null) { + printf( + '

%2$s

', + esc_attr($notice['type']), + esc_html($notice['message']) + ); + } + + echo '
'; + echo '

Angriffs-Ereignisse

Neueste Einträge oben.

'; + + if ($list_error !== '') { + echo '

' . esc_html($list_error) . '

'; + } + + if (empty($rows)) { + echo '

Keine Angriffs-Einträge mit Spielername/UUID vorhanden.

'; + } else { + echo ''; + echo ''; + echo ''; + foreach ($rows as $row) { + $date = isset($row['datetime']) ? (string) $row['datetime'] : ''; + $player = isset($row['player']) ? (string) $row['player'] : '-'; + $ip = isset($row['ip']) ? (string) $row['ip'] : '-'; + $uuid = isset($row['uuid']) ? (string) $row['uuid'] : '-'; + + echo ''; + echo ''; + echo ''; + echo ''; + echo ''; + echo ''; + } + echo '
Datum / UhrzeitSpielernameIPUUID
' . esc_html($date) . '' . esc_html($player) . '' . esc_html($ip) . '' . esc_html($uuid) . '
'; + } + + echo '
'; + echo '
'; + } + private static function render_messages_center($messages_markup, $redirect_page) { echo '
'; echo '

Meldungs-Center

Hier siehst du laufend erkannte Status-, Attack- und Warnmeldungen aus deinem Proxy.

'; @@ -765,12 +978,19 @@ if (!class_exists('StatusAPI_Backend_Helper')) { check_admin_referer('statusapi_backend_helper_clear_messages'); $state = self::get_state(); - $state['messages'] = array(); + $redirect_page = isset($_POST['redirect_page']) ? sanitize_text_field(wp_unslash($_POST['redirect_page'])) : self::MENU_SLUG; + + if ($redirect_page === self::LIST_SLUG) { + self::save_history(array()); + } else { + $state['messages'] = array(); + } self::save_state($state); - $redirect_page = isset($_POST['redirect_page']) ? sanitize_text_field(wp_unslash($_POST['redirect_page'])) : self::MENU_SLUG; if ($redirect_page !== self::MESSAGES_SLUG) { - $redirect_page = self::MENU_SLUG; + if ($redirect_page !== self::LIST_SLUG) { + $redirect_page = self::MENU_SLUG; + } } self::redirect_with_notice('messages_cleared', $redirect_page); @@ -843,8 +1063,53 @@ if (!class_exists('StatusAPI_Backend_Helper')) { return $code >= 200 && $code < 300; } + private static function fetch_attacker_entries($base_url) { + if ($base_url === '') { + return array( + 'entries' => array(), + 'error' => 'StatusAPI Basis-URL ist nicht konfiguriert.', + ); + } + + $endpoint = untrailingslashit($base_url) . '/antibot/security-log'; + $response = wp_remote_get($endpoint, array('timeout' => 8)); + if (is_wp_error($response)) { + return array( + 'entries' => array(), + 'error' => 'Konnte Angriffsdaten nicht laden: ' . $response->get_error_message(), + ); + } + + $code = (int) wp_remote_retrieve_response_code($response); + if ($code < 200 || $code >= 300) { + return array( + 'entries' => array(), + 'error' => 'StatusAPI lieferte HTTP ' . $code . ' für /antibot/security-log.', + ); + } + + $body = wp_remote_retrieve_body($response); + $decoded = json_decode($body, true); + if (!is_array($decoded) || !isset($decoded['events']) || !is_array($decoded['events'])) { + return array( + 'entries' => array(), + 'error' => 'Antwort von /antibot/security-log ist ungültig.', + ); + } + + return array( + 'entries' => $decoded['events'], + 'error' => '', + ); + } + private static function redirect_with_notice($code, $page = self::MENU_SLUG) { - $target_page = ($page === self::MESSAGES_SLUG) ? self::MESSAGES_SLUG : self::MENU_SLUG; + $target_page = self::MENU_SLUG; + if ($page === self::MESSAGES_SLUG) { + $target_page = self::MESSAGES_SLUG; + } elseif ($page === self::LIST_SLUG) { + $target_page = self::LIST_SLUG; + } $url = add_query_arg( array( 'page' => $target_page, @@ -1217,6 +1482,30 @@ if (!class_exists('StatusAPI_Backend_Helper')) { .statusapi-message-empty { border-left-color: #2563eb; } + .statusapi-level-pill { + display: inline-block; + padding: 4px 8px; + border-radius: 999px; + font-size: 11px; + font-weight: 700; + letter-spacing: .04em; + } + .statusapi-level-pill.statusapi-level-success { + background: #dcfce7; + color: #166534; + } + .statusapi-level-pill.statusapi-level-warning { + background: #fef3c7; + color: #92400e; + } + .statusapi-level-pill.statusapi-level-error { + background: #fee2e2; + color: #991b1b; + } + .statusapi-level-pill.statusapi-level-info { + background: #dbeafe; + color: #1e40af; + } @media (max-width: 1100px) { .statusapi-metric-grid, .statusapi-status-grid {