feat: send local history to central API dashboard
- Add SmartHoneypotAPIClient::send_history_batch(): reads local wp_honeypot_log in pages of 50, starting from hp_history_last_id option, sends to API, tracks progress - Two new options: hp_history_last_id, hp_history_total_sent - Action handlers: send_history (one batch), reset_history (start over) - Settings tab: new 'Send History to API' section showing: * Local record count, sent count, remaining count * Progress bar (blue WP style) * 'Send History' / 'Send Next Batch' button with remaining count * 'Reset Progress' to re-send from beginning * Section only shown when API is enabled and URL is configured - Notices: per-action feedback with 'Click Send Next Batch to continue'
This commit is contained in:
@@ -217,6 +217,85 @@ class SmartHoneypotAPIClient {
|
||||
return ['ok' => true, 'message' => 'Connection verified. API is reachable and token is accepted.'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends one batch of existing local log records to the central API.
|
||||
* Picks up from where it left off using the hp_history_last_id option.
|
||||
*
|
||||
* Returns ['ok', 'sent', 'remaining', 'has_more', 'message']
|
||||
*/
|
||||
public static function send_history_batch(int $batch_size = 50): array {
|
||||
$s = self::settings();
|
||||
if (empty($s['api_url'])) {
|
||||
return ['ok' => false, 'message' => 'No API URL configured.'];
|
||||
}
|
||||
|
||||
global $wpdb;
|
||||
$table = SmartHoneypotDB::table();
|
||||
$last_id = (int) get_option('hp_history_last_id', 0);
|
||||
$total = SmartHoneypotDB::count();
|
||||
|
||||
$rows = $wpdb->get_results(
|
||||
$wpdb->prepare(
|
||||
"SELECT * FROM {$table} WHERE id > %d ORDER BY id ASC LIMIT %d",
|
||||
$last_id, $batch_size
|
||||
),
|
||||
ARRAY_A
|
||||
);
|
||||
|
||||
if (empty($rows)) {
|
||||
return ['ok' => true, 'sent' => 0, 'remaining' => 0, 'has_more' => false,
|
||||
'message' => 'All records have already been sent.'];
|
||||
}
|
||||
|
||||
$blocks = array_map(fn($r) => [
|
||||
'ip' => $r['ip_address'],
|
||||
'form_type' => $r['form_type'],
|
||||
'reason' => $r['reason'],
|
||||
'user_agent' => $r['user_agent'],
|
||||
'blocked_at' => $r['blocked_at'],
|
||||
], $rows);
|
||||
|
||||
$headers = ['Content-Type' => 'application/json'];
|
||||
if (!empty($s['api_token'])) {
|
||||
$headers['Authorization'] = 'Bearer ' . $s['api_token'];
|
||||
}
|
||||
|
||||
$response = wp_remote_post(
|
||||
trailingslashit(esc_url_raw($s['api_url'])) . 'api/v1/submit',
|
||||
[
|
||||
'timeout' => 30,
|
||||
'headers' => $headers,
|
||||
'body' => wp_json_encode([
|
||||
'site_hash' => hash('sha256', home_url()),
|
||||
'blocks' => $blocks,
|
||||
]),
|
||||
]
|
||||
);
|
||||
|
||||
if (is_wp_error($response)) {
|
||||
return ['ok' => false, 'message' => 'Request failed: ' . $response->get_error_message()];
|
||||
}
|
||||
$code = wp_remote_retrieve_response_code($response);
|
||||
if ($code !== 200) {
|
||||
return ['ok' => false, 'message' => "API returned HTTP {$code}. Check connection settings."];
|
||||
}
|
||||
|
||||
$new_last_id = (int) end($rows)['id'];
|
||||
$sent_total = (int) get_option('hp_history_total_sent', 0) + count($rows);
|
||||
update_option('hp_history_last_id', $new_last_id);
|
||||
update_option('hp_history_total_sent', $sent_total);
|
||||
|
||||
$remaining = max(0, $total - $sent_total);
|
||||
|
||||
return [
|
||||
'ok' => true,
|
||||
'sent' => count($rows),
|
||||
'remaining' => $remaining,
|
||||
'has_more' => $remaining > 0,
|
||||
'message' => sprintf('Sent %d records. %d remaining.', count($rows), $remaining),
|
||||
];
|
||||
}
|
||||
|
||||
public static function settings(): array {
|
||||
return wp_parse_args(get_option(self::OPT_SETTINGS, []), self::defaults());
|
||||
}
|
||||
@@ -399,6 +478,20 @@ class SmartHoneypotAdmin {
|
||||
wp_redirect(add_query_arg(['page' => self::MENU_SLUG, 'tab' => 'settings', 'flushed' => 1], admin_url('admin.php')));
|
||||
exit;
|
||||
}
|
||||
|
||||
if ($_POST['hp_action'] === 'send_history') {
|
||||
$result = SmartHoneypotAPIClient::send_history_batch();
|
||||
set_transient('hp_history_result', $result, 60);
|
||||
wp_redirect(add_query_arg(['page' => self::MENU_SLUG, 'tab' => 'settings', 'history' => 1], admin_url('admin.php')));
|
||||
exit;
|
||||
}
|
||||
|
||||
if ($_POST['hp_action'] === 'reset_history') {
|
||||
delete_option('hp_history_last_id');
|
||||
delete_option('hp_history_total_sent');
|
||||
wp_redirect(add_query_arg(['page' => self::MENU_SLUG, 'tab' => 'settings', 'history_reset' => 1], admin_url('admin.php')));
|
||||
exit;
|
||||
}
|
||||
}
|
||||
|
||||
public static function render_page() {
|
||||
@@ -427,6 +520,22 @@ class SmartHoneypotAdmin {
|
||||
<div class="notice <?= $cls ?> is-dismissible"><p><?= esc_html($res['message']) ?></p></div>
|
||||
<?php endif;
|
||||
endif; ?>
|
||||
<?php if (!empty($_GET['history'])):
|
||||
$res = get_transient('hp_history_result');
|
||||
if ($res):
|
||||
$cls = $res['ok'] ? 'notice-success' : 'notice-error'; ?>
|
||||
<div class="notice <?= $cls ?> is-dismissible">
|
||||
<p><?= esc_html($res['message']) ?>
|
||||
<?php if (!empty($res['has_more'])): ?>
|
||||
<strong>Click "Send Next Batch" to continue.</strong>
|
||||
<?php endif; ?>
|
||||
</p>
|
||||
</div>
|
||||
<?php endif;
|
||||
endif; ?>
|
||||
<?php if (!empty($_GET['history_reset'])): ?>
|
||||
<div class="notice notice-info is-dismissible"><p>History sync progress has been reset. You can re-send from the beginning.</p></div>
|
||||
<?php endif; ?>
|
||||
|
||||
<nav class="nav-tab-wrapper hp-tabs">
|
||||
<a href="<?= esc_url(admin_url('admin.php?page=' . self::MENU_SLUG . '&tab=logs')) ?>"
|
||||
@@ -667,6 +776,65 @@ class SmartHoneypotAdmin {
|
||||
</form>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
<?php if ($s['api_url'] && $s['enabled']): ?>
|
||||
<hr>
|
||||
<h3>Send History to API</h3>
|
||||
<p style="color:#646970;margin-bottom:12px">
|
||||
Populate the central dashboard with your existing log so the charts and stats are meaningful right away,
|
||||
without waiting for new attacks. Records are sent in batches of 50.
|
||||
</p>
|
||||
<?php
|
||||
$local_total = SmartHoneypotDB::count();
|
||||
$history_sent = (int) get_option('hp_history_total_sent', 0);
|
||||
$remaining = max(0, $local_total - $history_sent);
|
||||
$pct = $local_total > 0 ? min(100, round($history_sent / $local_total * 100)) : 0;
|
||||
?>
|
||||
<table class="form-table">
|
||||
<tr>
|
||||
<th>Local Log</th>
|
||||
<td><?= number_format($local_total) ?> records in this site's database</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Sent to API</th>
|
||||
<td>
|
||||
<?= number_format($history_sent) ?> records (<?= $pct ?>%)
|
||||
<?php if ($local_total > 0): ?>
|
||||
<div style="margin-top:6px;background:#f0f0f1;border-radius:3px;height:8px;max-width:300px">
|
||||
<div style="background:#2271b1;height:100%;border-radius:3px;width:<?= $pct ?>%;transition:width .4s"></div>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Remaining</th>
|
||||
<td><?= number_format($remaining) ?> records not yet sent</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<div style="display:flex;gap:10px;margin-top:12px;flex-wrap:wrap;align-items:center">
|
||||
<?php if ($remaining > 0): ?>
|
||||
<form method="post">
|
||||
<?php wp_nonce_field(self::NONCE_ACTION); ?>
|
||||
<input type="hidden" name="hp_action" value="send_history">
|
||||
<button type="submit" class="button button-secondary">
|
||||
<?= $history_sent === 0 ? 'Send History' : 'Send Next Batch' ?>
|
||||
(<?= min(50, $remaining) ?> of <?= number_format($remaining) ?> remaining)
|
||||
</button>
|
||||
</form>
|
||||
<?php else: ?>
|
||||
<span style="color:#00a32a;font-weight:600">✓ All history sent</span>
|
||||
<?php endif; ?>
|
||||
<?php if ($history_sent > 0): ?>
|
||||
<form method="post" onsubmit="return confirm('Reset history sync progress? This allows re-sending all records from the beginning.')">
|
||||
<?php wp_nonce_field(self::NONCE_ACTION); ?>
|
||||
<input type="hidden" name="hp_action" value="reset_history">
|
||||
<button type="submit" class="button">Reset Progress</button>
|
||||
</form>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
</div>
|
||||
<?php
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user