Files
StatusPulse/statuspulse.php
2026-04-02 05:59:18 +00:00

1425 lines
66 KiB
PHP

<?php
/*
Plugin Name: StatusPulse
Description: Moderne WordPress-Admin-Seite für StatusAPI-Konfiguration, Attack-API-Key und Proxy-Tests.
Version: 1.0.0
Author: M_Viper
*/
if (!defined('ABSPATH')) {
exit;
}
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 MENU_SLUG = 'statuspulse';
const MESSAGES_SLUG = 'statuspulse-meldungen';
public static function boot() {
add_action('admin_menu', array(__CLASS__, 'register_menu'));
add_action('admin_init', array(__CLASS__, 'register_settings'));
add_action('wp_dashboard_setup', array(__CLASS__, 'register_dashboard_widget'));
add_action('admin_post_statusapi_backend_helper_test', array(__CLASS__, 'handle_test_action'));
add_action('admin_post_statusapi_backend_helper_clear_messages', array(__CLASS__, 'handle_clear_messages'));
add_action('wp_ajax_statusapi_backend_helper_refresh', array(__CLASS__, 'handle_ajax_refresh'));
}
public static function register_dashboard_widget() {
if (!current_user_can('manage_options')) {
return;
}
wp_add_dashboard_widget(
'statuspulse_dashboard_widget',
'StatusPulse - Live-Status',
array(__CLASS__, 'render_dashboard_widget')
);
}
public static function render_dashboard_widget() {
$options = self::get_options();
$live_status = self::probe_statusapi($options['statusapi_base_url']);
$state = self::get_state();
$messages = isset($state['messages']) && is_array($state['messages']) ? $state['messages'] : array();
$last_message = !empty($messages) ? $messages[0] : null;
$status_color = '#f59e0b';
if ($live_status['status'] === 'ok') {
$status_color = '#16a34a';
} elseif ($live_status['status'] === 'error') {
$status_color = '#dc2626';
}
echo '<div style="font-family:Segoe UI, Arial, sans-serif;">';
echo '<div style="display:grid;grid-template-columns:1fr 1fr;gap:10px;margin-bottom:10px;">';
echo '<div style="border:1px solid #dbe4f0;border-left:4px solid ' . esc_attr($status_color) . ';border-radius:10px;padding:10px;background:#f8fafc;">';
echo '<div style="font-size:11px;color:#64748b;text-transform:uppercase;letter-spacing:.05em;">Verbindungsstatus</div>';
echo '<div style="font-size:22px;color:#0f172a;font-weight:700;">' . esc_html($live_status['label']) . '</div>';
echo '<div style="font-size:12px;color:#334155;">' . esc_html($live_status['meta']) . '</div>';
echo '</div>';
echo '<div style="border:1px solid #dbe4f0;border-left:4px solid #2563eb;border-radius:10px;padding:10px;background:#f8fafc;">';
echo '<div style="font-size:11px;color:#64748b;text-transform:uppercase;letter-spacing:.05em;">Letzte Meldung</div>';
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 '<div style="font-size:17px;color:#0f172a;font-weight:700;line-height:1.2;">' . esc_html($msg_title) . '</div>';
echo '<div style="font-size:12px;color:#334155;">' . esc_html($msg_text) . '</div>';
echo '<div style="font-size:12px;color:#64748b;margin-top:4px;">' . esc_html(self::format_timestamp($msg_ts)) . '</div>';
} else {
echo '<div style="font-size:14px;color:#334155;">Noch keine Meldungen vorhanden.</div>';
}
echo '</div>';
echo '</div>';
echo '<div style="display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:8px;">';
echo '<div style="border:1px solid #e2e8f0;border-radius:10px;padding:8px;background:#fff;">';
echo '<div style="font-size:11px;color:#64748b;">HTTP</div>';
echo '<div style="font-size:18px;color:#0f172a;font-weight:700;">' . esc_html($live_status['http_code'] > 0 ? (string) $live_status['http_code'] : 'n/a') . '</div>';
echo '</div>';
echo '<div style="border:1px solid #e2e8f0;border-radius:10px;padding:8px;background:#fff;">';
echo '<div style="font-size:11px;color:#64748b;">Spieler online</div>';
echo '<div style="font-size:18px;color:#0f172a;font-weight:700;">' . esc_html($live_status['metrics']['online_players']) . '</div>';
echo '</div>';
echo '<div style="border:1px solid #e2e8f0;border-radius:10px;padding:8px;background:#fff;">';
echo '<div style="font-size:11px;color:#64748b;">RAM</div>';
echo '<div style="font-size:18px;color:#0f172a;font-weight:700;">' . esc_html($live_status['metrics']['memory']) . '</div>';
echo '</div>';
echo '<div style="border:1px solid #e2e8f0;border-radius:10px;padding:8px;background:#fff;">';
echo '<div style="font-size:11px;color:#64748b;">Zuletzt geprüft</div>';
echo '<div style="font-size:13px;color:#0f172a;font-weight:700;line-height:1.2;">' . esc_html(self::format_timestamp($state['last_check_at'])) . '</div>';
echo '</div>';
echo '</div>';
echo '<p style="margin-top:10px;">';
echo '<a class="button button-secondary" href="' . esc_url(admin_url('admin.php?page=' . self::MENU_SLUG)) . '">StatusPulse öffnen</a> ';
echo '<a class="button button-secondary" href="' . esc_url(admin_url('admin.php?page=' . self::MESSAGES_SLUG)) . '">Meldungen öffnen</a>';
echo '</p>';
echo '</div>';
}
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')
);
}
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);
}
}
private static function build_messages_markup($state) {
$messages = isset($state['messages']) && is_array($state['messages']) ? $state['messages'] : array();
if (empty($messages)) {
return '<li data-level="empty" class="statusapi-message-item statusapi-message-empty"><div><strong>Noch keine Meldungen</strong><p>Sobald Statuswechsel oder Attack-Events erkannt werden, erscheinen sie hier.</p></div></li>';
}
$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 .= '<li data-level="' . esc_attr($level) . '" class="statusapi-message-item statusapi-level-' . esc_attr($level) . '">';
$html .= '<div class="statusapi-message-head"><strong>' . esc_html($title) . '</strong><span>' . esc_html(self::format_timestamp($ts)) . '</span></div>';
$html .= '<p>' . esc_html($text) . '</p>';
$html .= '</li>';
}
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 '<div class="wrap statusapi-admin">';
self::render_styles();
self::render_scripts($refresh_nonce);
echo '<div class="statusapi-hero">';
echo '<div>';
echo '<span class="statusapi-kicker">Proxy Control Surface</span>';
echo '<h1>StatusPulse</h1>';
echo '<p>Reduzierte Admin-Seite für Proxy-URL, Attack-Key und direkte Tests gegen dein StatusAPI-Plugin.</p>';
echo '</div>';
echo '<div class="statusapi-hero-badge">';
echo '<span>Live Refresh</span>';
echo '<strong>alle 15s</strong>';
echo '<em id="statusapi-refresh-state">wartet auf erstes Update</em>';
echo '</div>';
echo '</div>';
if ($notice !== null) {
printf(
'<div class="notice notice-%1$s statusapi-notice"><p>%2$s</p></div>',
esc_attr($notice['type']),
esc_html($notice['message'])
);
}
echo '<div class="statusapi-status-grid">';
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 '</div>';
echo '<div class="statusapi-metric-grid">';
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 '</div>';
echo '<div class="statusapi-grid">';
echo '<div class="statusapi-card statusapi-card-form">';
echo '<div class="statusapi-card-head"><h2>Konfiguration</h2><p>Die Werte werden lokal in WordPress gespeichert und als Snippets für deine Proxy-Dateien angezeigt.</p></div>';
echo '<form method="post" action="options.php">';
settings_fields('statusapi_backend_helper_group');
echo '<div class="statusapi-fields">';
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 '</div>';
echo '<div class="statusapi-actions">';
submit_button('Einstellungen speichern', 'primary', 'submit', false);
echo '</div>';
echo '</form>';
echo '</div>';
echo '<div class="statusapi-card">';
echo '<div class="statusapi-card-head"><h2>Snippets</h2><p>Direkt für deine Proxy-Konfiguration und externe Tests vorbereitet.</p></div>';
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 '</div>';
echo '<div class="statusapi-card">';
echo '<div class="statusapi-card-head"><h2>Aktionen</h2><p>Teste direkt aus dem Backend, ob dein Proxy erreichbar ist und Attack-Meldungen akzeptiert.</p></div>';
echo '<form method="post" action="' . esc_url(admin_url('admin-post.php')) . '" class="statusapi-test-form">';
wp_nonce_field('statusapi_backend_helper_test');
echo '<input type="hidden" name="action" value="statusapi_backend_helper_test" />';
echo '<div class="statusapi-test-buttons">';
submit_button('StatusAPI Verbindung prüfen', 'secondary', 'test_connection', false);
submit_button('Test Attack-Meldung senden', 'secondary', 'test_attack', false);
echo '</div>';
echo '</form>';
echo '<div class="statusapi-meta">';
echo '<div><span>Gespeicherte Basis-URL</span><strong>' . esc_html($statusapi_base_url !== '' ? $statusapi_base_url : 'nicht gesetzt') . '</strong></div>';
echo '<div><span>Attack-Key</span><strong>' . esc_html($attack_api_key !== '' ? 'gesetzt' : 'leer') . '</strong></div>';
echo '<div><span>Quelle</span><strong>' . esc_html($attack_source !== '' ? $attack_source : 'WordPress') . '</strong></div>';
echo '</div>';
echo '</div>';
echo '</div>';
}
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 '<div class="wrap statusapi-admin">';
self::render_styles();
self::render_scripts($refresh_nonce);
echo '<h1>StatusPulse Meldungen</h1>';
echo '<p>Dedizierte Seite für alle laufenden Status-, Attack- und Warnmeldungen.</p>';
if ($notice !== null) {
printf(
'<div class="notice notice-%1$s statusapi-notice"><p>%2$s</p></div>',
esc_attr($notice['type']),
esc_html($notice['message'])
);
}
self::render_messages_center($messages_markup, self::MESSAGES_SLUG);
echo '</div>';
}
private static function render_messages_center($messages_markup, $redirect_page) {
echo '<div class="statusapi-card statusapi-card-messages">';
echo '<div class="statusapi-card-head"><h2>Meldungs-Center</h2><p>Hier siehst du laufend erkannte Status-, Attack- und Warnmeldungen aus deinem Proxy.</p></div>';
echo '<div class="statusapi-message-filters">';
echo '<button type="button" class="button statusapi-filter-btn is-active" data-filter="all">Alle</button>';
echo '<button type="button" class="button statusapi-filter-btn" data-filter="error">Fehler</button>';
echo '<button type="button" class="button statusapi-filter-btn" data-filter="warning">Warnung</button>';
echo '<button type="button" class="button statusapi-filter-btn" data-filter="success">Erfolg</button>';
echo '<button type="button" class="button statusapi-filter-btn" data-filter="info">Info</button>';
echo '<form method="post" action="' . esc_url(admin_url('admin-post.php')) . '" class="statusapi-clear-form">';
wp_nonce_field('statusapi_backend_helper_clear_messages');
echo '<input type="hidden" name="action" value="statusapi_backend_helper_clear_messages" />';
echo '<input type="hidden" name="redirect_page" value="' . esc_attr($redirect_page) . '" />';
echo '<button type="submit" class="button statusapi-clear-btn">Meldungen leeren</button>';
echo '</form>';
echo '</div>';
echo '<ul id="statusapi-message-list" class="statusapi-message-list">' . $messages_markup . '</ul>';
echo '</div>';
}
private static function render_input_field($label, $name, $value, $description) {
echo '<label class="statusapi-field" for="' . esc_attr($name) . '">';
echo '<span class="statusapi-field-label">' . esc_html($label) . '</span>';
printf(
'<input class="statusapi-input" type="text" id="%1$s" name="%1$s" value="%2$s" />',
esc_attr($name),
esc_attr($value)
);
if ($description !== '') {
echo '<span class="statusapi-field-description">' . esc_html($description) . '</span>';
}
echo '</label>';
}
private static function render_snippet_card($title, $content, $rows) {
echo '<div class="statusapi-snippet-card">';
echo '<div class="statusapi-snippet-head">';
echo '<h3>' . esc_html($title) . '</h3>';
echo '<button type="button" class="button button-secondary statusapi-copy" data-copy-target="statusapi-copy-' . esc_attr(md5($title . $content)) . '">Kopieren</button>';
echo '</div>';
printf(
'<textarea readonly rows="%1$d" id="%2$s" class="statusapi-snippet">%3$s</textarea>',
(int) $rows,
esc_attr('statusapi-copy-' . md5($title . $content)),
esc_textarea($content)
);
echo '</div>';
}
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 '<div' . $id_attr . ' class="statusapi-status-card ' . esc_attr($status_class) . '">';
echo '<span class="statusapi-status-title">' . esc_html($title) . '</span>';
echo '<strong class="statusapi-status-value" data-role="value">' . esc_html($value) . '</strong>';
echo '<span class="statusapi-status-meta" data-role="meta">' . esc_html($meta) . '</span>';
echo '</div>';
}
private static function render_metric_card($id, $title, $value, $meta) {
echo '<div id="' . esc_attr($id) . '" class="statusapi-metric-card">';
echo '<span class="statusapi-metric-title">' . esc_html($title) . '</span>';
echo '<strong class="statusapi-metric-value" data-role="value">' . esc_html($value) . '</strong>';
echo '<span class="statusapi-metric-meta" data-role="meta">' . esc_html($meta) . '</span>';
echo '</div>';
}
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();
$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;
}
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 redirect_with_notice($code, $page = self::MENU_SLUG) {
$target_page = ($page === self::MESSAGES_SLUG) ? self::MESSAGES_SLUG : self::MENU_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 '<style>
.statusapi-admin {
max-width: 1280px;
}
.statusapi-hero {
display: flex;
justify-content: space-between;
gap: 24px;
align-items: flex-end;
padding: 32px;
margin: 20px 0 24px;
border-radius: 24px;
background: linear-gradient(135deg, #0f172a 0%, #1d4ed8 55%, #38bdf8 100%);
color: #f8fafc;
box-shadow: 0 24px 60px rgba(15, 23, 42, 0.22);
}
.statusapi-hero h1 {
margin: 6px 0 10px;
color: #ffffff;
font-size: 34px;
line-height: 1.1;
}
.statusapi-hero p {
margin: 0;
max-width: 760px;
color: rgba(248, 250, 252, 0.96);
font-size: 15px;
}
.statusapi-kicker {
display: inline-block;
padding: 6px 10px;
border-radius: 999px;
background: rgba(255, 255, 255, 0.14);
color: #eff6ff;
font-size: 11px;
font-weight: 700;
letter-spacing: 0.08em;
text-transform: uppercase;
}
.statusapi-hero-badge {
min-width: 220px;
padding: 18px 20px;
border-radius: 18px;
background: rgba(255, 255, 255, 0.12);
backdrop-filter: blur(8px);
}
.statusapi-hero-badge span {
display: block;
margin-bottom: 6px;
color: #dbeafe;
font-size: 12px;
text-transform: uppercase;
letter-spacing: 0.08em;
}
.statusapi-hero-badge strong {
font-size: 22px;
color: #ffffff;
}
.statusapi-hero-badge em {
display: block;
margin-top: 6px;
color: rgba(255, 255, 255, 0.92);
font-style: normal;
font-size: 12px;
}
.statusapi-grid {
display: grid;
grid-template-columns: 1.05fr 0.95fr;
gap: 24px;
}
.statusapi-metric-grid {
display: grid;
grid-template-columns: repeat(4, minmax(0, 1fr));
gap: 16px;
margin: 0 0 24px;
}
.statusapi-status-grid {
display: grid;
grid-template-columns: repeat(4, minmax(0, 1fr));
gap: 16px;
margin: 0 0 24px;
}
.statusapi-status-card {
position: relative;
overflow: hidden;
padding: 18px 20px;
border-radius: 20px;
background: #ffffff;
border: 1px solid #dbe4f0;
box-shadow: 0 10px 24px rgba(15, 23, 42, 0.05);
}
.statusapi-status-card::after {
content: "";
position: absolute;
inset: 0 auto 0 0;
width: 5px;
border-radius: 20px 0 0 20px;
background: #94a3b8;
}
.statusapi-state-ok::after {
background: #16a34a;
}
.statusapi-state-error::after {
background: #dc2626;
}
.statusapi-state-unknown::after {
background: #f59e0b;
}
.statusapi-status-title {
display: block;
margin-bottom: 10px;
color: #64748b;
font-size: 12px;
text-transform: uppercase;
letter-spacing: 0.07em;
}
.statusapi-status-value {
display: block;
margin-bottom: 8px;
color: #0f172a;
font-size: 24px;
line-height: 1.2;
}
.statusapi-status-meta {
display: block;
color: #334155;
font-size: 13px;
line-height: 1.5;
}
.statusapi-metric-card {
padding: 20px;
border-radius: 20px;
background: linear-gradient(180deg, #ffffff 0%, #f8fafc 100%);
border: 1px solid #dbe4f0;
box-shadow: 0 10px 24px rgba(15, 23, 42, 0.05);
}
.statusapi-metric-title {
display: block;
margin-bottom: 10px;
color: #64748b;
font-size: 12px;
text-transform: uppercase;
letter-spacing: 0.07em;
}
.statusapi-metric-value {
display: block;
margin-bottom: 8px;
color: #0f172a;
font-size: 24px;
line-height: 1.2;
}
.statusapi-metric-meta {
display: block;
color: #334155;
font-size: 13px;
line-height: 1.5;
}
.statusapi-card {
background: #ffffff;
border: 1px solid #dbe4f0;
border-radius: 22px;
padding: 24px;
box-shadow: 0 10px 30px rgba(15, 23, 42, 0.06);
}
.statusapi-card-form {
grid-row: span 2;
}
.statusapi-card-head h2 {
margin: 0 0 6px;
font-size: 22px;
}
.statusapi-card-head p {
margin: 0 0 20px;
color: #334155;
}
.statusapi-fields {
display: grid;
gap: 18px;
}
.statusapi-field {
display: block;
}
.statusapi-field-label {
display: block;
margin-bottom: 8px;
color: #0f172a;
font-weight: 600;
}
.statusapi-field-description {
display: block;
margin-top: 8px;
color: #334155;
font-size: 13px;
}
.statusapi-input {
width: 100%;
min-height: 48px;
padding: 12px 14px;
border: 1px solid #cbd5e1;
border-radius: 14px;
background: #f8fafc;
font-size: 14px;
}
.statusapi-input:focus {
border-color: #2563eb;
box-shadow: 0 0 0 3px rgba(37, 99, 235, 0.16);
outline: none;
background: #ffffff;
}
.statusapi-actions {
margin-top: 20px;
}
.statusapi-snippet-card + .statusapi-snippet-card {
margin-top: 18px;
}
.statusapi-snippet-head {
display: flex;
justify-content: space-between;
gap: 12px;
align-items: center;
margin-bottom: 10px;
}
.statusapi-snippet-head h3 {
margin: 0;
font-size: 15px;
}
.statusapi-snippet {
width: 100%;
padding: 14px;
border: 1px solid #dbe4f0;
border-radius: 16px;
background: #0b1220 !important;
color: #f8fafc !important;
font-family: Consolas, Monaco, monospace;
resize: vertical;
opacity: 1 !important;
-webkit-text-fill-color: #f8fafc;
}
.statusapi-snippet:focus {
background: #0b1220 !important;
color: #f8fafc !important;
}
.statusapi-test-form {
margin-top: 4px;
}
.statusapi-test-buttons {
display: flex;
flex-wrap: wrap;
gap: 10px;
margin-bottom: 20px;
}
.statusapi-meta {
display: grid;
gap: 12px;
}
.statusapi-meta div {
padding: 14px 16px;
border-radius: 16px;
background: #f8fafc;
border: 1px solid #e2e8f0;
}
.statusapi-meta span {
display: block;
margin-bottom: 6px;
color: #334155;
font-size: 12px;
text-transform: uppercase;
letter-spacing: 0.06em;
}
.statusapi-meta strong {
color: #0f172a;
font-size: 15px;
}
.statusapi-notice {
margin: 0 0 16px;
border-radius: 14px;
overflow: hidden;
}
.statusapi-notice p {
color: #0f172a !important;
font-weight: 600;
}
.statusapi-card-messages {
margin-bottom: 24px;
}
.statusapi-message-list {
margin: 0;
padding: 0;
list-style: none;
display: grid;
gap: 10px;
max-height: 300px;
overflow: auto;
}
.statusapi-message-filters {
display: flex;
flex-wrap: wrap;
gap: 8px;
margin-bottom: 12px;
}
.statusapi-filter-btn {
border-radius: 999px !important;
}
.statusapi-filter-btn.is-active {
background: #0f172a !important;
border-color: #0f172a !important;
color: #ffffff !important;
}
.statusapi-clear-form {
margin-left: auto;
}
.statusapi-clear-btn {
border-color: #dc2626 !important;
color: #dc2626 !important;
}
.statusapi-clear-btn:hover {
border-color: #b91c1c !important;
color: #b91c1c !important;
}
.statusapi-message-item {
border: 1px solid #dbe4f0;
border-left-width: 5px;
border-radius: 12px;
background: #f8fafc;
padding: 10px 12px;
}
.statusapi-message-item.is-hidden {
display: none;
}
.statusapi-message-item p {
margin: 6px 0 0;
color: #334155;
}
.statusapi-message-head {
display: flex;
justify-content: space-between;
gap: 10px;
align-items: baseline;
}
.statusapi-message-head strong {
color: #0f172a;
}
.statusapi-message-head span {
color: #475569;
font-size: 12px;
}
.statusapi-level-success {
border-left-color: #16a34a;
}
.statusapi-level-warning {
border-left-color: #d97706;
}
.statusapi-level-error {
border-left-color: #dc2626;
}
.statusapi-level-info,
.statusapi-message-empty {
border-left-color: #2563eb;
}
@media (max-width: 1100px) {
.statusapi-metric-grid,
.statusapi-status-grid {
grid-template-columns: repeat(2, minmax(0, 1fr));
}
.statusapi-grid {
grid-template-columns: 1fr;
}
.statusapi-card-form {
grid-row: auto;
}
}
@media (max-width: 782px) {
.statusapi-metric-grid,
.statusapi-status-grid {
grid-template-columns: 1fr;
}
.statusapi-hero {
flex-direction: column;
align-items: stretch;
}
.statusapi-snippet-head,
.statusapi-test-buttons {
flex-direction: column;
align-items: stretch;
}
.statusapi-message-filters {
gap: 6px;
}
.statusapi-clear-form {
margin-left: 0;
}
}
</style>';
}
private static function render_scripts($refresh_nonce) {
echo '<script>
window.StatusApiBridgeConfig = {
ajaxUrl: ' . wp_json_encode(admin_url('admin-ajax.php')) . ',
nonce: ' . wp_json_encode($refresh_nonce) . ',
intervalMs: 15000
};
document.addEventListener("click", function(event) {
var button = event.target.closest(".statusapi-copy");
if (!button) {
return;
}
var targetId = button.getAttribute("data-copy-target");
var target = document.getElementById(targetId);
if (!target) {
return;
}
target.select();
target.setSelectionRange(0, 99999);
try {
document.execCommand("copy");
button.textContent = "Kopiert";
window.setTimeout(function() {
button.textContent = "Kopieren";
}, 1400);
} catch (error) {
button.textContent = "Fehlgeschlagen";
}
});
document.addEventListener("DOMContentLoaded", function() {
var config = window.StatusApiBridgeConfig || null;
if (!config) {
return;
}
var refreshState = document.getElementById("statusapi-refresh-state");
var currentFilter = "all";
try {
var remembered = window.localStorage.getItem("statuspulse_message_filter");
if (remembered) {
currentFilter = remembered;
}
} catch (error) {
}
function applyStatusCard(id, payload) {
var card = document.getElementById(id);
if (!card || !payload) {
return;
}
var valueNode = card.querySelector("[data-role=value]");
var metaNode = card.querySelector("[data-role=meta]");
if (valueNode && typeof payload.value !== "undefined") {
valueNode.textContent = payload.value;
}
if (metaNode && typeof payload.meta !== "undefined") {
metaNode.textContent = payload.meta;
}
card.classList.remove("statusapi-state-ok", "statusapi-state-error", "statusapi-state-unknown");
if (payload.status === "ok") {
card.classList.add("statusapi-state-ok");
} else if (payload.status === "error") {
card.classList.add("statusapi-state-error");
} else {
card.classList.add("statusapi-state-unknown");
}
}
function applyMetricCard(id, payload) {
var card = document.getElementById(id);
if (!card || !payload) {
return;
}
var valueNode = card.querySelector("[data-role=value]");
var metaNode = card.querySelector("[data-role=meta]");
if (valueNode && typeof payload.value !== "undefined") {
valueNode.textContent = payload.value;
}
if (metaNode && typeof payload.meta !== "undefined") {
metaNode.textContent = payload.meta;
}
}
function applyMessageFilter(filter) {
currentFilter = filter || "all";
try {
window.localStorage.setItem("statuspulse_message_filter", currentFilter);
} catch (error) {
}
var buttons = document.querySelectorAll(".statusapi-filter-btn");
buttons.forEach(function(btn) {
var isActive = btn.getAttribute("data-filter") === currentFilter;
btn.classList.toggle("is-active", isActive);
});
var items = document.querySelectorAll("#statusapi-message-list .statusapi-message-item");
items.forEach(function(item) {
var level = item.getAttribute("data-level") || "info";
var show = currentFilter === "all" || level === "empty" || level === currentFilter;
item.classList.toggle("is-hidden", !show);
});
}
document.addEventListener("click", function(event) {
var filterBtn = event.target.closest(".statusapi-filter-btn");
if (!filterBtn) {
return;
}
applyMessageFilter(filterBtn.getAttribute("data-filter") || "all");
});
function refreshDashboard() {
var formData = new window.FormData();
formData.append("action", "statusapi_backend_helper_refresh");
formData.append("_ajax_nonce", config.nonce);
if (refreshState) {
refreshState.textContent = "aktualisiert...";
}
window.fetch(config.ajaxUrl, {
method: "POST",
credentials: "same-origin",
body: formData
}).then(function(response) {
return response.json();
}).then(function(result) {
if (!result || !result.success || !result.data) {
throw new Error("invalid_response");
}
applyStatusCard("statusapi-card-proxy", result.data.status_cards.proxy);
applyStatusCard("statusapi-card-http", result.data.status_cards.http);
applyStatusCard("statusapi-card-success", result.data.status_cards.success);
applyStatusCard("statusapi-card-lastcheck", result.data.status_cards.lastcheck);
applyMetricCard("statusapi-metric-online", result.data.metric_cards.online);
applyMetricCard("statusapi-metric-max", result.data.metric_cards.max);
applyMetricCard("statusapi-metric-uptime", result.data.metric_cards.uptime);
applyMetricCard("statusapi-metric-memory", result.data.metric_cards.memory);
var messageList = document.getElementById("statusapi-message-list");
if (messageList && typeof result.data.messages_html !== "undefined") {
messageList.innerHTML = result.data.messages_html;
applyMessageFilter(currentFilter);
}
if (refreshState && result.data.refresh_state) {
refreshState.textContent = result.data.refresh_state;
}
}).catch(function() {
if (refreshState) {
refreshState.textContent = "Auto-Refresh fehlgeschlagen";
}
});
}
applyMessageFilter(currentFilter);
window.setTimeout(refreshDashboard, 1200);
window.setInterval(refreshDashboard, config.intervalMs);
});
</script>';
}
}
StatusAPI_Backend_Helper::boot();
}