Compare commits

2 Commits
1.0.0 ... main

Author SHA1 Message Date
Git Manager GUI
9c7c52e38a Upload via Git Manager GUI 2026-04-02 20:56:09 +02:00
20a61504a1 Upload via Git Manager GUI - statuspulse.php 2026-04-02 06:23:19 +00:00

View File

@@ -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 '<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 '<style>
.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); }
}
</style>';
echo '<div class="spw-widget">';
echo '<div class="spw-header">';
echo '<h3 class="spw-title">StatusPulse Monitor</h3>';
echo '<span class="spw-status">' . esc_html($status_text) . '</span>';
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>';
echo '<div class="spw-body">';
echo '<div class="spw-row">';
echo '<div class="spw-item">';
echo '<span class="spw-item-label">Status</span>';
echo '<div class="spw-item-value">' . esc_html($live_status['label']) . '</div>';
echo '<div class="spw-item-meta">' . esc_html($live_status['meta']) . '</div>';
echo '</div>';
echo '<div class="spw-item">';
echo '<span class="spw-item-label">Letzte Meldung</span>';
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>';
echo '<div class="spw-item-value" style="font-size: 14px;">' . esc_html($msg_title) . '</div>';
echo '<div class="spw-item-meta">' . esc_html(substr($msg_text, 0, 50)) . '</div>';
} else {
echo '<div style="font-size:14px;color:#334155;">Noch keine Meldungen vorhanden.</div>';
echo '<div class="spw-item-meta">Keine Meldungen</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 class="spw-metrics">';
echo '<div class="spw-metric"><div class="spw-metric-label">HTTP</div><div class="spw-metric-value">' . esc_html($live_status['http_code'] > 0 ? (string) $live_status['http_code'] : '—') . '</div></div>';
echo '<div class="spw-metric"><div class="spw-metric-label">Spieler</div><div class="spw-metric-value">' . esc_html($live_status['metrics']['online_players']) . '</div></div>';
echo '<div class="spw-metric"><div class="spw-metric-label">RAM</div><div class="spw-metric-value">' . esc_html($live_status['metrics']['memory']) . '</div></div>';
echo '<div class="spw-metric"><div class="spw-metric-label">Check</div><div class="spw-metric-value" style="font-size: 12px;">' . esc_html(self::format_timestamp($state['last_check_at'])) . '</div></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 class="spw-footer">';
echo '<a class="spw-link" href="' . esc_url(admin_url('admin.php?page=' . self::MENU_SLUG)) . '">Einstellungen</a>';
echo '<a class="spw-link" href="' . esc_url(admin_url('admin.php?page=' . self::MESSAGES_SLUG)) . '">Meldungen</a>';
echo '<a class="spw-link" href="' . esc_url(admin_url('admin.php?page=' . self::LIST_SLUG)) . '">Angriffe</a>';
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>';
}
@@ -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 '</div>';
}
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 '<div class="wrap statusapi-admin">';
self::render_styles();
echo '<h1>StatusPulse Angriffsversuche</h1>';
echo '<p>Zeigt nur Spielername und UUID von erkannten Angriffsversuchen (inkl. Datum/Uhrzeit).</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'])
);
}
echo '<div class="statusapi-card statusapi-card-messages">';
echo '<div class="statusapi-card-head"><h2>Angriffs-Ereignisse</h2><p>Neueste Einträge oben.</p></div>';
if ($list_error !== '') {
echo '<div class="notice notice-error inline"><p>' . esc_html($list_error) . '</p></div>';
}
if (empty($rows)) {
echo '<p>Keine Angriffs-Einträge mit Spielername/UUID vorhanden.</p>';
} else {
echo '<table class="widefat striped">';
echo '<thead><tr><th style="width:190px;">Datum / Uhrzeit</th><th style="width:220px;">Spielername</th><th style="width:180px;">IP</th><th>UUID</th></tr></thead>';
echo '<tbody>';
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 '<tr>';
echo '<td>' . esc_html($date) . '</td>';
echo '<td><strong>' . esc_html($player) . '</strong></td>';
echo '<td>' . esc_html($ip) . '</td>';
echo '<td>' . esc_html($uuid) . '</td>';
echo '</tr>';
}
echo '</tbody></table>';
}
echo '</div>';
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>';
@@ -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 {