.spw-widget { background: #fff; border: 1px solid #ccc; border-radius: 4px; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; } .spw-header { background: #f5f5f5; border-bottom: 1px solid #ddd; padding: 8px 12px; display: flex; justify-content: space-between; align-items: center; } .spw-title { font-weight: 600; font-size: 13px; color: #333; margin: 0; } .spw-status { display: inline-block; padding: 2px 8px; border-radius: 3px; font-size: 11px; font-weight: 600; background: ' . esc_attr($status_color) . '; color: #fff; } .spw-body { padding: 12px; } .spw-row { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; margin-bottom: 12px; } .spw-item { background: #f9f9f9; border: 1px solid #eee; border-radius: 4px; padding: 8px; } .spw-item-label { font-size: 11px; color: #666; font-weight: 600; text-transform: uppercase; margin-bottom: 3px; display: block; } .spw-item-value { font-size: 20px; font-weight: 600; color: #000; } .spw-item-meta { font-size: 11px; color: #999; margin-top: 2px; } .spw-metrics { display: grid; grid-template-columns: repeat(4, 1fr); gap: 8px; margin-bottom: 12px; } .spw-metric { background: #f9f9f9; border: 1px solid #eee; border-radius: 4px; padding: 6px; text-align: center; } .spw-metric-label { font-size: 10px; color: #666; font-weight: 600; text-transform: uppercase; margin-bottom: 2px; } .spw-metric-value { font-size: 16px; font-weight: 600; color: #000; } .spw-footer { display: flex; gap: 8px; border-top: 1px solid #eee; padding-top: 8px; } .spw-link { flex: 1; text-align: center; padding: 6px 8px; background: #f5f5f5; border: 1px solid #ddd; border-radius: 3px; font-size: 11px; color: #0073aa; text-decoration: none; font-weight: 600; transition: background 0.2s; } .spw-link:hover { background: #e5e5e5; color: #005a87; text-decoration: none; } @media (max-width: 900px) { .spw-row { grid-template-columns: 1fr; } .spw-metrics { grid-template-columns: repeat(2, 1fr); } } '; echo '
'; echo '
'; echo '

StatusPulse Monitor

'; echo '' . esc_html($status_text) . ''; echo '
'; 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'] : ''; echo '
' . esc_html($msg_title) . '
'; echo '
' . esc_html(substr($msg_text, 0, 50)) . '
'; } else { echo '
Keine Meldungen
'; } echo '
'; echo '
'; 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 '
'; echo '
'; } public static function register_menu() { add_menu_page( 'StatusPulse', 'StatusPulse', 'manage_options', self::MENU_SLUG, array(__CLASS__, 'render_page'), 'dashicons-shield-alt', 81 ); add_submenu_page( self::MENU_SLUG, 'Meldungen', 'Meldungen', 'manage_options', 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() { register_setting( 'statusapi_backend_helper_group', self::OPTION_KEY, array(__CLASS__, 'sanitize_options') ); } public static function sanitize_options($input) { $input = is_array($input) ? $input : array(); return array( 'statusapi_base_url' => self::sanitize_url(isset($input['statusapi_base_url']) ? $input['statusapi_base_url'] : ''), 'attack_api_key' => sanitize_text_field(isset($input['attack_api_key']) ? $input['attack_api_key'] : ''), 'attack_source' => sanitize_text_field(isset($input['attack_source']) ? $input['attack_source'] : 'WordPress'), ); } private static function sanitize_url($url) { $url = trim((string) $url); if ($url === '') { return ''; } return untrailingslashit(esc_url_raw($url)); } private static function get_options() { $stored = get_option(self::OPTION_KEY, array()); $stored = is_array($stored) ? $stored : array(); return wp_parse_args($stored, array( 'statusapi_base_url' => '', 'attack_api_key' => '', 'attack_source' => 'WordPress', )); } private static function get_state() { $stored = get_option(self::STATE_OPTION_KEY, array()); $stored = is_array($stored) ? $stored : array(); return wp_parse_args($stored, array( 'last_check_at' => 0, 'last_http_code' => 0, 'last_success_at' => 0, 'last_success_code' => 0, 'last_status' => 'unknown', 'last_error' => '', 'last_attack_mode' => null, 'last_memory_high' => false, 'last_player_high' => false, 'messages' => array(), )); } private static function save_state($state) { update_option(self::STATE_OPTION_KEY, $state, false); } private static function push_message(&$state, $level, $title, $message) { if (!isset($state['messages']) || !is_array($state['messages'])) { $state['messages'] = array(); } array_unshift($state['messages'], array( 'ts' => time(), 'level' => $level, 'title' => $title, 'message' => $message, )); 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) { $messages = isset($state['messages']) && is_array($state['messages']) ? $state['messages'] : array(); if (empty($messages)) { return '
  • Noch keine Meldungen

    Sobald Statuswechsel oder Attack-Events erkannt werden, erscheinen sie hier.

  • '; } $html = ''; foreach (array_slice($messages, 0, 20) as $msg) { $level = isset($msg['level']) ? (string) $msg['level'] : 'info'; $title = isset($msg['title']) ? (string) $msg['title'] : 'Meldung'; $text = isset($msg['message']) ? (string) $msg['message'] : ''; $ts = isset($msg['ts']) ? (int) $msg['ts'] : 0; $html .= '
  • '; $html .= '
    ' . esc_html($title) . '' . esc_html(self::format_timestamp($ts)) . '
    '; $html .= '

    ' . esc_html($text) . '

    '; $html .= '
  • '; } return $html; } public static function render_page() { if (!current_user_can('manage_options')) { return; } $options = self::get_options(); $statusapi_base_url = $options['statusapi_base_url']; $attack_api_key = $options['attack_api_key']; $attack_source = $options['attack_source']; $live_status = self::probe_statusapi($statusapi_base_url); $state = self::get_state(); $notice = self::build_notice(); $status_endpoint = $statusapi_base_url !== '' ? $statusapi_base_url : 'http://dein-proxy:9191'; $attack_endpoint = $status_endpoint . '/network/attack'; $status_json_snippet = "Status JSON: {$status_endpoint}"; $network_guard_snippet = "networkinfo.attack.api_key={$attack_api_key}\nnetworkinfo.attack.source={$attack_source}"; $curl_examples = self::build_curl_examples($status_endpoint, $attack_endpoint, $attack_api_key, $attack_source); $refresh_nonce = wp_create_nonce('statusapi_backend_helper_refresh'); echo '
    '; self::render_styles(); self::render_scripts($refresh_nonce); echo '
    '; echo '
    '; echo 'Proxy Control Surface'; echo '

    StatusPulse

    '; echo '

    Reduzierte Admin-Seite für Proxy-URL, Attack-Key und direkte Tests gegen dein StatusAPI-Plugin.

    '; echo '
    '; echo '
    '; echo 'Live Refresh'; echo 'alle 15s'; echo 'wartet auf erstes Update'; echo '
    '; echo '
    '; if ($notice !== null) { printf( '

    %2$s

    ', esc_attr($notice['type']), esc_html($notice['message']) ); } echo '
    '; self::render_status_card_named( 'statusapi-card-proxy', 'Proxy Status', $live_status['label'], $live_status['meta'], $live_status['status'] ); self::render_status_card_named( 'statusapi-card-http', 'Letzter HTTP-Code', $live_status['http_code'] > 0 ? (string) $live_status['http_code'] : 'n/a', $live_status['checked_at_human'], $live_status['status'] ); self::render_status_card_named( 'statusapi-card-success', 'Letzte erfolgreiche Prüfung', self::format_timestamp($state['last_success_at']), $state['last_success_code'] > 0 ? 'HTTP ' . $state['last_success_code'] : 'Noch kein erfolgreicher Check gespeichert', $state['last_success_at'] > 0 ? 'ok' : 'unknown' ); self::render_status_card_named( 'statusapi-card-lastcheck', 'Zuletzt geprüft', self::format_timestamp($state['last_check_at']), $state['last_error'] !== '' ? $state['last_error'] : 'Keine Fehler gespeichert', $state['last_status'] ); echo '
    '; echo '
    '; self::render_metric_card('statusapi-metric-online', 'Spieler online', $live_status['metrics']['online_players'], $live_status['metrics']['online_meta']); self::render_metric_card('statusapi-metric-max', 'Max. Spieler', $live_status['metrics']['max_players'], $live_status['metrics']['max_meta']); self::render_metric_card('statusapi-metric-uptime', 'Uptime', $live_status['metrics']['uptime'], $live_status['metrics']['uptime_meta']); self::render_metric_card('statusapi-metric-memory', 'RAM Nutzung', $live_status['metrics']['memory'], $live_status['metrics']['memory_meta']); echo '
    '; echo '
    '; echo '
    '; echo '

    Konfiguration

    Die Werte werden lokal in WordPress gespeichert und als Snippets für deine Proxy-Dateien angezeigt.

    '; echo '
    '; settings_fields('statusapi_backend_helper_group'); echo '
    '; self::render_input_field('StatusAPI Basis-URL', self::OPTION_KEY . '[statusapi_base_url]', $statusapi_base_url, 'z. B. http://127.0.0.1:9191'); self::render_input_field('Attack API-Key', self::OPTION_KEY . '[attack_api_key]', $attack_api_key, 'Entspricht networkinfo.attack.api_key in network-guard.properties.'); self::render_input_field('Attack Source', self::OPTION_KEY . '[attack_source]', $attack_source, 'Wird in Testmeldungen als Quelle angezeigt.'); echo '
    '; echo '
    '; submit_button('Einstellungen speichern', 'primary', 'submit', false); echo '
    '; echo '
    '; echo '
    '; echo '
    '; echo '

    Snippets

    Direkt für deine Proxy-Konfiguration und externe Tests vorbereitet.

    '; self::render_snippet_card('Für network-guard.properties', $network_guard_snippet, 4); self::render_snippet_card('Wichtiger Endpunkt', $status_json_snippet . "\nAttack POST: {$attack_endpoint}", 4); self::render_snippet_card('Test-Requests', $curl_examples, 8); echo '
    '; echo '
    '; echo '

    Aktionen

    Teste direkt aus dem Backend, ob dein Proxy erreichbar ist und Attack-Meldungen akzeptiert.

    '; echo '
    '; wp_nonce_field('statusapi_backend_helper_test'); echo ''; echo '
    '; submit_button('StatusAPI Verbindung prüfen', 'secondary', 'test_connection', false); submit_button('Test Attack-Meldung senden', 'secondary', 'test_attack', false); echo '
    '; echo '
    '; echo '
    '; echo '
    Gespeicherte Basis-URL' . esc_html($statusapi_base_url !== '' ? $statusapi_base_url : 'nicht gesetzt') . '
    '; echo '
    Attack-Key' . esc_html($attack_api_key !== '' ? 'gesetzt' : 'leer') . '
    '; echo '
    Quelle' . esc_html($attack_source !== '' ? $attack_source : 'WordPress') . '
    '; echo '
    '; echo '
    '; echo '
    '; } public static function render_messages_page() { if (!current_user_can('manage_options')) { return; } $state = self::get_state(); $notice = self::build_notice(); $messages_markup = self::build_messages_markup($state); $refresh_nonce = wp_create_nonce('statusapi_backend_helper_refresh'); echo '
    '; self::render_styles(); self::render_scripts($refresh_nonce); echo '

    StatusPulse Meldungen

    '; echo '

    Dedizierte Seite für alle laufenden Status-, Attack- und Warnmeldungen.

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

    %2$s

    ', esc_attr($notice['type']), esc_html($notice['message']) ); } self::render_messages_center($messages_markup, self::MESSAGES_SLUG); 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.

    '; echo '
    '; echo ''; echo ''; echo ''; echo ''; echo ''; echo '
    '; wp_nonce_field('statusapi_backend_helper_clear_messages'); echo ''; echo ''; echo ''; echo '
    '; echo '
    '; echo ''; echo '
    '; } private static function render_input_field($label, $name, $value, $description) { echo ''; } private static function render_snippet_card($title, $content, $rows) { echo '
    '; echo '
    '; echo '

    ' . esc_html($title) . '

    '; echo ''; echo '
    '; printf( '', (int) $rows, esc_attr('statusapi-copy-' . md5($title . $content)), esc_textarea($content) ); echo '
    '; } private static function render_status_card($title, $value, $meta, $status) { self::render_status_card_named('', $title, $value, $meta, $status); } private static function render_status_card_named($id, $title, $value, $meta, $status) { $status_class = 'statusapi-state-unknown'; if ($status === 'ok') { $status_class = 'statusapi-state-ok'; } elseif ($status === 'error') { $status_class = 'statusapi-state-error'; } $id_attr = $id !== '' ? ' id="' . esc_attr($id) . '"' : ''; echo ''; echo '' . esc_html($title) . ''; echo '' . esc_html($value) . ''; echo '' . esc_html($meta) . ''; echo '
    '; } private static function render_metric_card($id, $title, $value, $meta) { echo '
    '; echo '' . esc_html($title) . ''; echo '' . esc_html($value) . ''; echo '' . esc_html($meta) . ''; echo '
    '; } private static function build_curl_examples($status_endpoint, $attack_endpoint, $attack_api_key, $attack_source) { $payload = wp_json_encode(array( 'event' => 'detected', 'source' => $attack_source, 'connectionsPerSecond' => 250, 'ipAddressesBlocked' => 12, 'connectionsBlocked' => 1800, )); return "GET {$status_endpoint}\n\nPOST {$attack_endpoint}\nHeader: X-API-Key: {$attack_api_key}\nBody: {$payload}"; } private static function probe_statusapi($base_url) { $state = self::get_state(); $previous_status = isset($state['last_status']) ? (string) $state['last_status'] : 'unknown'; $previous_attack_mode = array_key_exists('last_attack_mode', $state) ? $state['last_attack_mode'] : null; $previous_memory_high = !empty($state['last_memory_high']); $previous_player_high = !empty($state['last_player_high']); if ($base_url === '') { return array( 'status' => 'unknown', 'label' => 'Nicht konfiguriert', 'meta' => 'Bitte zuerst eine StatusAPI Basis-URL eintragen.', 'http_code' => 0, 'checked_at_human' => self::format_timestamp($state['last_check_at']), 'metrics' => self::empty_metrics(), ); } $response = wp_remote_get($base_url, array('timeout' => 5)); $now = time(); if (is_wp_error($response)) { $state['last_check_at'] = $now; $state['last_http_code'] = 0; $state['last_status'] = 'error'; $state['last_error'] = $response->get_error_message(); if ($previous_status !== 'error') { self::push_message($state, 'error', 'Proxy offline', 'StatusAPI ist nicht erreichbar: ' . $response->get_error_message()); } self::save_state($state); return array( 'status' => 'error', 'label' => 'Offline', 'meta' => $response->get_error_message(), 'http_code' => 0, 'checked_at_human' => self::format_timestamp($now), 'metrics' => self::empty_metrics(), ); } $code = (int) wp_remote_retrieve_response_code($response); $ok = $code >= 200 && $code < 300; $body = wp_remote_retrieve_body($response); $decoded = json_decode($body, true); $state['last_check_at'] = $now; $state['last_http_code'] = $code; $state['last_status'] = $ok ? 'ok' : 'error'; $state['last_error'] = $ok ? '' : 'HTTP ' . $code; if ($ok && $previous_status === 'error') { self::push_message($state, 'success', 'Proxy wieder online', 'StatusAPI antwortet wieder erfolgreich.'); } if (!$ok && $previous_status !== 'error') { self::push_message($state, 'error', 'Proxy-Fehler', 'StatusAPI antwortet mit HTTP ' . $code . '.'); } if ($ok && is_array($decoded)) { $network = isset($decoded['network']) && is_array($decoded['network']) ? $decoded['network'] : array(); $antibot = isset($decoded['antibot']) && is_array($decoded['antibot']) ? $decoded['antibot'] : array(); if (array_key_exists('attack_mode', $antibot)) { $attack_mode = (bool) $antibot['attack_mode']; if ($previous_attack_mode !== null && $attack_mode !== (bool) $previous_attack_mode) { if ($attack_mode) { $cps = isset($antibot['last_cps']) ? $antibot['last_cps'] : 'n/a'; self::push_message($state, 'warning', 'Attack erkannt', 'AntiBot hat den Attack-Mode aktiviert (CPS: ' . $cps . ').'); } else { self::push_message($state, 'success', 'Attack beendet', 'AntiBot hat den Attack-Mode beendet.'); } } $state['last_attack_mode'] = $attack_mode; } $memory_percent = null; if (isset($network['memory']) && is_array($network['memory']) && isset($network['memory']['usage_percent'])) { $memory_percent = (int) $network['memory']['usage_percent']; } $is_memory_high = $memory_percent !== null && $memory_percent >= 90; if ($is_memory_high && !$previous_memory_high) { self::push_message($state, 'warning', 'RAM-Warnung', 'RAM-Auslastung liegt bei ' . $memory_percent . '%.'); } if (!$is_memory_high && $previous_memory_high) { self::push_message($state, 'success', 'RAM normalisiert', 'RAM-Auslastung ist wieder unter 90%.'); } $state['last_memory_high'] = $is_memory_high; $occupancy = null; if (isset($network['players']) && is_array($network['players']) && isset($network['players']['occupancy_percent'])) { $occupancy = (int) $network['players']['occupancy_percent']; } $is_player_high = $occupancy !== null && $occupancy >= 95; if ($is_player_high && !$previous_player_high) { self::push_message($state, 'warning', 'Spieler-Auslastung hoch', 'Spieler-Auslastung liegt bei ' . $occupancy . '%.'); } if (!$is_player_high && $previous_player_high) { self::push_message($state, 'success', 'Spieler-Auslastung normalisiert', 'Spieler-Auslastung ist wieder unter 95%.'); } $state['last_player_high'] = $is_player_high; } if ($ok) { $state['last_success_at'] = $now; $state['last_success_code'] = $code; } self::save_state($state); return array( 'status' => $ok ? 'ok' : 'error', 'label' => $ok ? 'Online' : 'Fehler', 'meta' => $ok ? 'Antwort von ' . untrailingslashit($base_url) : 'HTTP ' . $code, 'http_code' => $code, 'checked_at_human' => self::format_timestamp($now), 'metrics' => $ok ? self::extract_metrics($decoded) : self::empty_metrics(), ); } private static function extract_metrics($decoded) { if (!is_array($decoded)) { return self::empty_metrics(); } $network = isset($decoded['network']) && is_array($decoded['network']) ? $decoded['network'] : array(); $players = isset($network['players']) && is_array($network['players']) ? $network['players'] : array(); $memory = isset($network['memory']) && is_array($network['memory']) ? $network['memory'] : array(); $online_players = self::array_value($players, 'online', self::count_players_fallback($decoded)); $max_players = self::array_value($players, 'max', self::array_value($decoded, 'max_players', 'n/a')); $occupancy = self::array_value($players, 'occupancy_percent', null); $uptime_human = self::array_value($network, 'uptime_human', 'n/a'); $uptime_seconds = self::array_value($network, 'uptime_seconds', null); $memory_percent = self::array_value($memory, 'usage_percent', 'n/a'); $memory_used = self::array_value($memory, 'used_mb', 'n/a'); $memory_max = self::array_value($memory, 'max_mb', 'n/a'); return array( 'online_players' => (string) $online_players, 'online_meta' => $occupancy !== null ? 'Auslastung ' . $occupancy . '%' : 'Aktuell verbundene Spieler', 'max_players' => (string) $max_players, 'max_meta' => 'Player-Limit laut Proxy', 'uptime' => (string) $uptime_human, 'uptime_meta' => $uptime_seconds !== null ? ((string) $uptime_seconds) . ' Sekunden' : 'Keine Uptime-Daten', 'memory' => ((string) $memory_percent) . '%', 'memory_meta' => $memory_used . ' MB von ' . $memory_max . ' MB', ); } private static function empty_metrics() { return array( 'online_players' => 'n/a', 'online_meta' => 'Keine Live-Daten', 'max_players' => 'n/a', 'max_meta' => 'Keine Live-Daten', 'uptime' => 'n/a', 'uptime_meta' => 'Keine Live-Daten', 'memory' => 'n/a', 'memory_meta' => 'Keine Live-Daten', ); } private static function array_value($array, $key, $fallback) { if (!is_array($array) || !array_key_exists($key, $array)) { return $fallback; } return $array[$key]; } private static function count_players_fallback($decoded) { if (isset($decoded['players']) && is_array($decoded['players'])) { return count($decoded['players']); } return 'n/a'; } private static function format_timestamp($timestamp) { $timestamp = (int) $timestamp; if ($timestamp <= 0) { return 'Noch kein Wert'; } return wp_date('d.m.Y H:i:s', $timestamp); } private static function build_notice() { if (!isset($_GET['statusapi_notice'])) { return null; } $code = sanitize_text_field(wp_unslash($_GET['statusapi_notice'])); if ($code === 'connection_ok') { return array('type' => 'success', 'message' => 'StatusAPI ist erreichbar.'); } if ($code === 'attack_ok') { return array('type' => 'success', 'message' => 'Test Attack-Meldung wurde erfolgreich an den Proxy gesendet.'); } if ($code === 'missing_url') { return array('type' => 'error', 'message' => 'Bitte zuerst die StatusAPI Basis-URL eintragen.'); } if ($code === 'missing_attack_key') { return array('type' => 'error', 'message' => 'Bitte zuerst einen Attack API-Key speichern.'); } if ($code === 'connection_failed') { return array('type' => 'error', 'message' => 'Verbindungstest fehlgeschlagen. Bitte Basis-URL und Erreichbarkeit prüfen.'); } if ($code === 'attack_failed') { return array('type' => 'error', 'message' => 'Test Attack-Meldung konnte nicht gesendet werden. API-Key und Proxy-Endpunkt prüfen.'); } if ($code === 'messages_cleared') { return array('type' => 'success', 'message' => 'Meldungen wurden erfolgreich geleert.'); } return null; } public static function handle_test_action() { if (!current_user_can('manage_options')) { wp_die('Nicht erlaubt.'); } check_admin_referer('statusapi_backend_helper_test'); $options = self::get_options(); $base_url = $options['statusapi_base_url']; if ($base_url === '') { self::redirect_with_notice('missing_url'); } if (isset($_POST['test_connection'])) { $ok = self::request_ok($base_url); $state = self::get_state(); self::push_message( $state, $ok ? 'success' : 'error', $ok ? 'Verbindungstest erfolgreich' : 'Verbindungstest fehlgeschlagen', $ok ? 'Manueller Verbindungstest im WordPress-Backend war erfolgreich.' : 'Manueller Verbindungstest im WordPress-Backend war nicht erfolgreich.' ); self::save_state($state); self::redirect_with_notice($ok ? 'connection_ok' : 'connection_failed'); } if (isset($_POST['test_attack'])) { if ($options['attack_api_key'] === '') { self::redirect_with_notice('missing_attack_key'); } $payload = array( 'event' => 'detected', 'source' => $options['attack_source'] !== '' ? $options['attack_source'] : 'WordPress', 'connectionsPerSecond' => 250, 'ipAddressesBlocked' => 12, 'connectionsBlocked' => 1800, ); $response = wp_remote_post($base_url . '/network/attack', array( 'timeout' => 8, 'headers' => array( 'Content-Type' => 'application/json; charset=utf-8', 'X-API-Key' => $options['attack_api_key'], ), 'body' => wp_json_encode($payload), )); if (is_wp_error($response)) { $state = self::get_state(); $state['last_check_at'] = time(); $state['last_http_code'] = 0; $state['last_status'] = 'error'; $state['last_error'] = $response->get_error_message(); self::push_message($state, 'error', 'Test-Attack fehlgeschlagen', 'Konnte keine Test-Meldung senden: ' . $response->get_error_message()); self::save_state($state); self::redirect_with_notice('attack_failed'); } $code = (int) wp_remote_retrieve_response_code($response); $state = self::get_state(); $state['last_check_at'] = time(); $state['last_http_code'] = $code; $state['last_status'] = ($code >= 200 && $code < 300) ? 'ok' : 'error'; $state['last_error'] = ($code >= 200 && $code < 300) ? '' : 'HTTP ' . $code; if ($code >= 200 && $code < 300) { $state['last_success_at'] = $state['last_check_at']; $state['last_success_code'] = $code; self::push_message($state, 'success', 'Test-Attack gesendet', 'Die Test-Attack-Meldung wurde erfolgreich an den Proxy gesendet.'); } else { self::push_message($state, 'error', 'Test-Attack fehlgeschlagen', 'Der Proxy antwortete mit HTTP ' . $code . '.'); } self::save_state($state); self::redirect_with_notice($code >= 200 && $code < 300 ? 'attack_ok' : 'attack_failed'); } self::redirect_with_notice('connection_failed'); } public static function handle_clear_messages() { if (!current_user_can('manage_options')) { wp_die('Nicht erlaubt.'); } check_admin_referer('statusapi_backend_helper_clear_messages'); $state = self::get_state(); $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); if ($redirect_page !== self::MESSAGES_SLUG) { if ($redirect_page !== self::LIST_SLUG) { $redirect_page = self::MENU_SLUG; } } self::redirect_with_notice('messages_cleared', $redirect_page); } public static function handle_ajax_refresh() { if (!current_user_can('manage_options')) { wp_send_json_error(array('message' => 'forbidden'), 403); } check_ajax_referer('statusapi_backend_helper_refresh'); $options = self::get_options(); $live_status = self::probe_statusapi($options['statusapi_base_url']); $state = self::get_state(); wp_send_json_success(array( 'refresh_state' => 'Aktualisiert ' . self::format_timestamp($state['last_check_at']), 'messages_html' => self::build_messages_markup($state), 'status_cards' => array( 'proxy' => array( 'value' => $live_status['label'], 'meta' => $live_status['meta'], 'status' => $live_status['status'], ), 'http' => array( 'value' => $live_status['http_code'] > 0 ? (string) $live_status['http_code'] : 'n/a', 'meta' => $live_status['checked_at_human'], 'status' => $live_status['status'], ), 'success' => array( 'value' => self::format_timestamp($state['last_success_at']), 'meta' => $state['last_success_code'] > 0 ? 'HTTP ' . $state['last_success_code'] : 'Noch kein erfolgreicher Check gespeichert', 'status' => $state['last_success_at'] > 0 ? 'ok' : 'unknown', ), 'lastcheck' => array( 'value' => self::format_timestamp($state['last_check_at']), 'meta' => $state['last_error'] !== '' ? $state['last_error'] : 'Keine Fehler gespeichert', 'status' => $state['last_status'], ), ), 'metric_cards' => array( 'online' => array( 'value' => $live_status['metrics']['online_players'], 'meta' => $live_status['metrics']['online_meta'], ), 'max' => array( 'value' => $live_status['metrics']['max_players'], 'meta' => $live_status['metrics']['max_meta'], ), 'uptime' => array( 'value' => $live_status['metrics']['uptime'], 'meta' => $live_status['metrics']['uptime_meta'], ), 'memory' => array( 'value' => $live_status['metrics']['memory'], 'meta' => $live_status['metrics']['memory_meta'], ), ), )); } private static function request_ok($url) { $response = wp_remote_get($url, array('timeout' => 8)); if (is_wp_error($response)) { return false; } $code = (int) wp_remote_retrieve_response_code($response); 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 = 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, 'statusapi_notice' => $code, ), admin_url('admin.php') ); wp_safe_redirect($url); exit; } private static function render_styles() { echo ''; } private static function render_scripts($refresh_nonce) { echo ''; } } StatusAPI_Backend_Helper::boot(); }