@anonympins/fingerprint 0.8.0 → 0.8.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -3,7 +3,7 @@
3
3
  * Plugin Name: Anonympins Bot Mitigation with Proof-of-Work
4
4
  * Plugin URI: https://github.com/anonympins/fingerprint
5
5
  * Description: High-performance client-side anti-bot protection and Proof-of-Work challenge verification for WordPress.
6
- * Version: 0.8.0
6
+ * Version: 0.8.2
7
7
  * Author: anonympins
8
8
  * Requires at least: 5.9
9
9
  * Requires PHP: 8.0
@@ -25,7 +25,11 @@ if (!defined('ABSPATH')) {
25
25
  }
26
26
 
27
27
  if (!defined('ANONYMPINS_BOT_MITIGATION_VERSION')) {
28
- define('ANONYMPINS_BOT_MITIGATION_VERSION', '0.8.0');
28
+ define('ANONYMPINS_BOT_MITIGATION_VERSION', '0.8.2');
29
+ }
30
+
31
+ if (!defined('ANONYMPINS_BOT_MITIGATION_DIR')) {
32
+ define('ANONYMPINS_BOT_MITIGATION_DIR', plugin_dir_path(__FILE__));
29
33
  }
30
34
 
31
35
  // =============================================================================
@@ -126,12 +130,50 @@ function fingerprint_get_sandbox_config(): array {
126
130
  ];
127
131
  }
128
132
 
133
+ /**
134
+ * Retrieves the configured direct endpoint path for Prometheus metrics scraping.
135
+ */
136
+ function fingerprint_get_metrics_endpoint(): string {
137
+ $saved = get_option('anonympins_security_options', get_option('fingerprint_security_options', []));
138
+ if (!isset($saved['metrics_endpoint'])) {
139
+ return '/metrics';
140
+ }
141
+ $path = trim((string)$saved['metrics_endpoint']);
142
+ if ($path === '') {
143
+ return '';
144
+ }
145
+ return str_starts_with($path, '/') ? $path : '/' . $path;
146
+ }
147
+
148
+ /**
149
+ * Returns the built-in default HTML challenge template (derived from fingerprint.js).
150
+ */
151
+ function fingerprint_get_default_challenge_template(): string {
152
+ return "<!DOCTYPE html>\n" .
153
+ "<html>\n" .
154
+ "<head>\n" .
155
+ "\t<meta charset=\"utf-8\">\n" .
156
+ "\t<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n" .
157
+ "\t<title>{{TITLE}}</title>\n" .
158
+ "\t<style>\n" .
159
+ "\t\t{{CUSTOM_CSS}}\n" .
160
+ "\t\tbody { font-family: sans-serif; text-align: center; padding-top: 50px; background: #fff; color: #222; }\n" .
161
+ "\t\t#loader { margin: 20px; font-size: 15px; color: #2271b1; }\n" .
162
+ "\t</style>\n" .
163
+ "</head>\n" .
164
+ "<body>\n" .
165
+ "\t<h1>{{TITLE}}</h1>\n" .
166
+ "\t<p>{{MESSAGE}}</p>\n" .
167
+ "\t<div id=\"loader\">⚙️ Initializing combined verification...</div>\n" .
168
+ "\t{{SOLVER_SCRIPT}}\n" .
169
+ "</body>\n" .
170
+ "</html>";
171
+ }
172
+
129
173
  // 1. PSR-4 autoloader for the Fingerprint engine.
130
174
  if (!class_exists(DirectFingerprint::class)) {
131
175
  $fingerprint_composer_paths = [
132
176
  __DIR__ . '/vendor/autoload.php',
133
- dirname(__DIR__, 2) . '/vendor/autoload.php',
134
- dirname(__DIR__, 3) . '/vendor/autoload.php',
135
177
  (defined('ABSPATH') ? ABSPATH . 'vendor/autoload.php' : ''),
136
178
  ];
137
179
  foreach ($fingerprint_composer_paths as $fingerprint_composer_path) {
@@ -202,7 +244,7 @@ add_action('fingerprint_prune_expired_entries', function () {
202
244
  add_action('admin_notices', function () {
203
245
  if (!is_ssl()) {
204
246
  echo '<div class="notice notice-warning is-dismissible">';
205
- echo '<p><strong>' . esc_html__('[Anonympins Bot Mitigation Security Warning]', 'anonympins-bot-mitigation-pow') . '</strong> : ' .
247
+ echo '<p><strong>' . esc_html__('[Anonympins bot mitigation security warning]', 'anonympins-bot-mitigation-pow') . '</strong> : ' .
206
248
  esc_html__('Your website is currently running on unencrypted HTTP. In this mode, modern web browsers disable the native Web Cryptography API (crypto.subtle) for security reasons, forcing a JavaScript fallback simulation that is slower and vulnerable to Man-in-the-Middle (MitM) attacks. We strongly recommend deploying a TLS/SSL certificate and enforcing HTTPS to ensure the cryptographic integrity of Proof-of-Work computations and identity protection.', 'anonympins-bot-mitigation-pow') .
207
249
  '</p>';
208
250
  echo '</div>';
@@ -217,9 +259,9 @@ add_action('admin_notices', function () {
217
259
  : esc_html__('Enforced test mode: suspicion analysis and PoW challenges are actively triggered.', 'anonympins-bot-mitigation-pow');
218
260
 
219
261
  echo '<div class="notice notice-warning is-dismissible">';
220
- echo '<p><strong>' . esc_html__('[Anonympins Bot Mitigation Sandbox Active]', 'anonympins-bot-mitigation-pow') . '</strong> : ' .
221
- esc_html__('Sandbox Mode is currently ENABLED.', 'anonympins-bot-mitigation-pow') . ' ' . esc_html($modeDesc) .
222
- ' <a href="' . esc_url(admin_url('options-general.php?page=anonympins-bot-mitigation#tab-sandbox')) . '">' . esc_html__('Configure Sandbox', 'anonympins-bot-mitigation-pow') . '</a></p>';
262
+ echo '<p><strong>' . esc_html__('[Anonympins bot mitigation sandbox active]', 'anonympins-bot-mitigation-pow') . '</strong> : ' .
263
+ esc_html__('Sandbox mode is currently ENABLED.', 'anonympins-bot-mitigation-pow') . ' ' . esc_html($modeDesc) .
264
+ ' <a href="' . esc_url(admin_url('options-general.php?page=anonympins-bot-mitigation#tab-sandbox')) . '">' . esc_html__('Configure sandbox', 'anonympins-bot-mitigation-pow') . '</a></p>';
223
265
  echo '</div>';
224
266
  }
225
267
  });
@@ -279,6 +321,11 @@ add_action('plugins_loaded', function () use ($fingerprint_security_profiles) {
279
321
  $contextConfig = $configs['frontend'] ?? $fingerprint_security_profiles['frontend'];
280
322
  }
281
323
 
324
+ $savedOptions = get_option('anonympins_security_options', get_option('fingerprint_security_options', []));
325
+ $contextConfig['overrides']['challengeTemplate'] = (is_array($savedOptions) && isset($savedOptions['challenge_template']))
326
+ ? (string)$savedOptions['challenge_template']
327
+ : fingerprint_get_default_challenge_template();
328
+
282
329
  $sandboxConfig = fingerprint_get_sandbox_config();
283
330
  $isSandboxActive = false;
284
331
 
@@ -361,6 +408,21 @@ add_action('plugins_loaded', function () use ($fingerprint_security_profiles) {
361
408
 
362
409
  $securityConfig = SecurityProfiles::createSecurityProfile($contextConfig['profile'], $contextConfig['overrides'] ?? []);
363
410
  $guard = new DirectFingerprint($securityConfig);
411
+
412
+ // Handle direct metrics endpoint if requested
413
+ $metricsPath = fingerprint_get_metrics_endpoint();
414
+ $path = (string)wp_parse_url($requestUri, PHP_URL_PATH);
415
+ if ($metricsPath !== '' && ($path === $metricsPath || str_ends_with($path, $metricsPath))) {
416
+ $context = $guard->buildRequestContext();
417
+ $isAuth = current_user_can('manage_options') || apply_filters('fingerprint_metrics_authorization', false, $context);
418
+ if (!$isAuth) {
419
+ status_header(403);
420
+ echo "Access to metrics denied.";
421
+ exit;
422
+ }
423
+ $guard->handleMetricsRequest($context);
424
+ return;
425
+ }
364
426
  $guard->protect();
365
427
  }, 0);
366
428
 
@@ -431,19 +493,37 @@ function fingerprint_inject_client_suspicion_event(): void {
431
493
  // SPECIAL SANDBOX ROUTES: REST & SERVER-SENT EVENTS (SSE)
432
494
  // =============================================================================
433
495
  add_action('rest_api_init', function () {
496
+ // 1. Prometheus Metrics (registered under plugin namespace and legacy namespace)
497
+ register_rest_route('anonympins/v1', '/metrics', [
498
+ 'methods' => 'GET',
499
+ 'callback' => 'fingerprint_rest_prometheus_metrics',
500
+ 'permission_callback' => function () {
501
+ return current_user_can('manage_options') || apply_filters('fingerprint_metrics_access', false);
502
+ },
503
+ ]);
504
+
434
505
  register_rest_route('fingerprint/v1', '/sandbox/telemetry', [
435
506
  'methods' => 'GET',
436
507
  'callback' => 'fingerprint_rest_sandbox_telemetry',
437
508
  'permission_callback' => function () {
438
- return current_user_can('manage_options') || !empty(fingerprint_get_sandbox_config()['enabled']);
509
+ return current_user_can('manage_options');
439
510
  },
440
511
  ]);
441
512
 
513
+ // 2. Sandbox Inspection Routes (Strictly restricted to manage_options)
442
514
  register_rest_route('fingerprint/v1', '/sandbox/sse', [
443
515
  'methods' => 'GET',
444
516
  'callback' => 'fingerprint_rest_sandbox_sse',
445
517
  'permission_callback' => function () {
446
- return current_user_can('manage_options') || !empty(fingerprint_get_sandbox_config()['enabled']);
518
+ return current_user_can('manage_options');
519
+ },
520
+ ]);
521
+
522
+ register_rest_route('fingerprint/v1', '/metrics', [
523
+ 'methods' => 'GET',
524
+ 'callback' => 'fingerprint_rest_prometheus_metrics',
525
+ 'permission_callback' => function () {
526
+ return current_user_can('manage_options') || apply_filters('fingerprint_metrics_access', false);
447
527
  },
448
528
  ]);
449
529
 
@@ -478,7 +558,21 @@ function fingerprint_rest_sandbox_clear_challenges(): \WP_REST_Response {
478
558
  return new \WP_REST_Response(['cleared' => true], 200);
479
559
  }
480
560
 
561
+ function fingerprint_rest_prometheus_metrics(\WP_REST_Request $request): \WP_REST_Response {
562
+ $effectiveProfiles = fingerprint_get_effective_profiles($GLOBALS['fingerprint_security_profiles'] ?? []);
563
+ $securityConfig = SecurityProfiles::createSecurityProfile($effectiveProfiles['frontend']['profile'] ?? 'blog', $effectiveProfiles['frontend']['overrides'] ?? []);
564
+ $metricsOutput = MetricsManager::getPrometheusMetrics($securityConfig);
565
+
566
+ $response = new \WP_REST_Response($metricsOutput, 200);
567
+ $response->header('Content-Type', 'text/plain; version=0.0.4; charset=utf-8');
568
+ return $response;
569
+ }
570
+
481
571
  function fingerprint_rest_sandbox_sse(): void {
572
+ if (!current_user_can('manage_options')) {
573
+ wp_die(esc_html__('Unauthorized access.', 'anonympins-bot-mitigation-pow'), '', ['response' => 403]);
574
+ }
575
+
482
576
  if (function_exists('apache_setenv')) {
483
577
  apache_setenv('no-gzip', '1');
484
578
  }
@@ -539,8 +633,8 @@ function fingerprint_rest_sandbox_sse(): void {
539
633
  // =============================================================================
540
634
  add_action('admin_menu', function () {
541
635
  add_options_page(
542
- __('Anonympins Bot Mitigation', 'anonympins-bot-mitigation-pow'),
543
- __('Anonympins Bot Mitigation', 'anonympins-bot-mitigation-pow'),
636
+ __('Anonympins bot mitigation', 'anonympins-bot-mitigation-pow'),
637
+ __('Anonympins bot mitigation', 'anonympins-bot-mitigation-pow'),
544
638
  'manage_options',
545
639
  'anonympins-bot-mitigation',
546
640
  'fingerprint_render_admin_page'
@@ -555,6 +649,9 @@ add_action('admin_enqueue_scripts', function (string $hook) {
555
649
  return;
556
650
  }
557
651
 
652
+ // Enqueue native WordPress CodeMirror editor for HTML template editing
653
+ $editorSettings = wp_enqueue_code_editor(['type' => 'text/html']);
654
+
558
655
  wp_register_script('anonympins-admin-settings', false, [], ANONYMPINS_BOT_MITIGATION_VERSION, true);
559
656
  wp_enqueue_script('anonympins-admin-settings');
560
657
 
@@ -563,8 +660,13 @@ add_action('admin_enqueue_scripts', function (string $hook) {
563
660
  evt.preventDefault();
564
661
  document.querySelectorAll(".fingerprint-tab-content").forEach(function(t) { t.style.display = "none"; });
565
662
  document.querySelectorAll(".nav-tab-wrapper a").forEach(function(n) { n.classList.remove("nav-tab-active"); });
566
- document.getElementById(tabId).style.display = "block";
663
+ var target = document.getElementById(tabId);
664
+ if (target) { target.style.display = "block"; }
567
665
  evt.currentTarget.classList.add("nav-tab-active");
666
+
667
+ if (tabId === "tab-template" && window.fingerprintCodeMirrorInstance) {
668
+ window.fingerprintCodeMirrorInstance.codemirror.refresh();
669
+ }
568
670
  if (window.location.hash !== "#" + tabId && history.pushState) {
569
671
  history.pushState(null, null, "#" + tabId);
570
672
  }
@@ -581,6 +683,16 @@ add_action('admin_enqueue_scripts', function (string $hook) {
581
683
  document.querySelectorAll(".nav-tab-wrapper a").forEach(function(n) { n.classList.remove("nav-tab-active"); });
582
684
  targetContent.style.display = "block";
583
685
  targetLink.classList.add("nav-tab-active");
686
+ if (targetId === "tab-template" && window.fingerprintCodeMirrorInstance) {
687
+ setTimeout(function() { window.fingerprintCodeMirrorInstance.codemirror.refresh(); }, 50);
688
+ }
689
+ }
690
+ }
691
+
692
+ if (window.wp && wp.codeEditor && document.getElementById("challenge_template")) {
693
+ var editorConfig = ' . wp_json_encode($editorSettings) . ';
694
+ if (editorConfig) {
695
+ window.fingerprintCodeMirrorInstance = wp.codeEditor.initialize(document.getElementById("challenge_template"), editorConfig);
584
696
  }
585
697
  }
586
698
  });
@@ -596,9 +708,9 @@ add_action('admin_enqueue_scripts', function (string $hook) {
596
708
  if (sseSource) {
597
709
  sseSource.close();
598
710
  sseSource = null;
599
- statusEl.textContent = ' . wp_json_encode(__('SSE Disconnected', 'anonympins-bot-mitigation-pow')) . ';
711
+ statusEl.textContent = ' . wp_json_encode(__('SSE disconnected', 'anonympins-bot-mitigation-pow')) . ';
600
712
  statusEl.style.color = "#646970";
601
- btnText.textContent = ' . wp_json_encode(__('Start Real-Time SSE Stream', 'anonympins-bot-mitigation-pow')) . ';
713
+ btnText.textContent = ' . wp_json_encode(__('Start real-time SSE stream', 'anonympins-bot-mitigation-pow')) . ';
602
714
  return;
603
715
  }
604
716
 
@@ -607,9 +719,9 @@ add_action('admin_enqueue_scripts', function (string $hook) {
607
719
  sseSource = new EventSource(' . wp_json_encode(rest_url('fingerprint/v1/sandbox/sse')) . ' + "?_wpnonce=" + wpRestNonce);
608
720
 
609
721
  sseSource.onopen = function() {
610
- statusEl.textContent = ' . wp_json_encode(__('SSE Live Connected', 'anonympins-bot-mitigation-pow')) . ';
722
+ statusEl.textContent = ' . wp_json_encode(__('SSE live connected', 'anonympins-bot-mitigation-pow')) . ';
611
723
  statusEl.style.color = "#007017";
612
- btnText.textContent = ' . wp_json_encode(__('Stop Stream', 'anonympins-bot-mitigation-pow')) . ';
724
+ btnText.textContent = ' . wp_json_encode(__('Stop stream', 'anonympins-bot-mitigation-pow')) . ';
613
725
  };
614
726
 
615
727
  sseSource.addEventListener("challenge", function(e) {
@@ -625,7 +737,7 @@ add_action('admin_enqueue_scripts', function (string $hook) {
625
737
  });
626
738
 
627
739
  sseSource.onerror = function() {
628
- statusEl.textContent = ' . wp_json_encode(__('SSE Reconnecting...', 'anonympins-bot-mitigation-pow')) . ';
740
+ statusEl.textContent = ' . wp_json_encode(__('SSE reconnecting...', 'anonympins-bot-mitigation-pow')) . ';
629
741
  statusEl.style.color = "#d63638";
630
742
  };
631
743
  }
@@ -803,6 +915,43 @@ function fingerprint_render_admin_page(): void {
803
915
  echo '<div class="notice notice-success is-dismissible"><p><strong>' . esc_html__('Sandbox settings updated successfully.', 'anonympins-bot-mitigation-pow') . '</strong></p></div>';
804
916
  }
805
917
 
918
+ if (isset($_POST['fingerprint_save_prometheus']) && check_admin_referer('fingerprint_prometheus_nonce', 'fingerprint_nonce_prometheus')) {
919
+ $saved = get_option('anonympins_security_options', get_option('fingerprint_security_options', []));
920
+ if (!is_array($saved)) {
921
+ $saved = [];
922
+ }
923
+
924
+ $rawEndpoint = isset($_POST['metrics_endpoint']) ? sanitize_text_field(wp_unslash($_POST['metrics_endpoint'])) : '/metrics';
925
+ $rawEndpoint = trim($rawEndpoint);
926
+ if ($rawEndpoint !== '' && !str_starts_with($rawEndpoint, '/')) {
927
+ $rawEndpoint = '/' . $rawEndpoint;
928
+ }
929
+ $saved['metrics_endpoint'] = $rawEndpoint;
930
+ update_option('anonympins_security_options', $saved);
931
+ echo '<div class="notice notice-success is-dismissible"><p><strong>' . esc_html__('Prometheus settings updated successfully.', 'anonympins-bot-mitigation-pow') . '</strong></p></div>';
932
+ }
933
+
934
+ if ((isset($_POST['fingerprint_save_template']) || isset($_POST['fingerprint_reset_template'])) && check_admin_referer('fingerprint_template_nonce', 'fingerprint_nonce_template')) {
935
+ $saved = get_option('anonympins_security_options', get_option('fingerprint_security_options', []));
936
+ if (!is_array($saved)) {
937
+ $saved = [];
938
+ }
939
+ if (isset($_POST['fingerprint_reset_template'])) {
940
+ unset($saved['challenge_template']);
941
+ echo '<div class="notice notice-info is-dismissible"><p><strong>' . esc_html__('Template reset to default successfully.', 'anonympins-bot-mitigation-pow') . '</strong></p></div>';
942
+ } else {
943
+ // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Administrator code template editor with script/style placeholders.
944
+ $rawTemplate = isset($_POST['challenge_template']) ? wp_unslash($_POST['challenge_template']) : '';
945
+ if (!current_user_can('unfiltered_html')) {
946
+ $saved['challenge_template'] = wp_kses_post($rawTemplate);
947
+ } else {
948
+ $saved['challenge_template'] = (string)$rawTemplate;
949
+ }
950
+ echo '<div class="notice notice-success is-dismissible"><p><strong>' . esc_html__('Challenge template updated successfully.', 'anonympins-bot-mitigation-pow') . '</strong></p></div>';
951
+ }
952
+ update_option('anonympins_security_options', $saved);
953
+ }
954
+
806
955
  if (isset($_POST['fingerprint_clear_store']) && check_admin_referer('fingerprint_clear_nonce', 'fingerprint_nonce_clear')) {
807
956
  $store = new WpDbStore();
808
957
  $store->clear();
@@ -812,28 +961,33 @@ function fingerprint_render_admin_page(): void {
812
961
  $sandboxConfig = fingerprint_get_sandbox_config();
813
962
  $effective = fingerprint_get_effective_profiles($fingerprint_security_profiles);
814
963
  $frontendConfig = SecurityProfiles::createSecurityProfile($effective['frontend']['profile'], $effective['frontend']['overrides'] ?? []);
964
+ $savedOptions = get_option('anonympins_security_options', get_option('fingerprint_security_options', []));
965
+ $currentTemplate = (is_array($savedOptions) && !empty($savedOptions['challenge_template']))
966
+ ? (string)$savedOptions['challenge_template']
967
+ : fingerprint_get_default_challenge_template();
815
968
 
969
+ $store = new WpDbStore();
970
+ $totalRows = $store->getTotalCount();
971
+ $expiredRows = $store->getExpiredCount();
816
972
  $tableName = esc_sql($wpdb->prefix . 'fingerprint_store');
817
973
  // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching,WordPress.DB.PreparedSQL.InterpolatedNotPrepared,PluginCheck.Security.DirectDB.UnescapedDBParameter
818
- $totalRows = (int)$wpdb->get_var("SELECT COUNT(*) FROM `{$tableName}`");
819
974
  $now = time();
820
975
  // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching,WordPress.DB.PreparedSQL.InterpolatedNotPrepared,PluginCheck.Security.DirectDB.UnescapedDBParameter
821
- $expiredRows = (int)$wpdb->get_var($wpdb->prepare("SELECT COUNT(*) FROM `{$tableName}` WHERE expires_at IS NOT NULL AND expires_at < %d", $now));
822
976
 
823
977
  $prometheusRaw = MetricsManager::getPrometheusMetrics($frontendConfig);
824
978
  ?>
825
979
  <div class="wrap">
826
- <h1><span class="dashicons dashicons-shield-alt" style="font-size:32px;vertical-align:middle;margin-right:8px;"></span> Anonympins Bot Mitigation with Proof-of-Work</h1>
980
+ <h1><span class="dashicons dashicons-shield-alt" style="font-size:32px;vertical-align:middle;margin-right:8px;"></span> Anonympins bot mitigation with proof-of-work</h1>
827
981
  <p class="description"><?php esc_html_e('Client-side behavioral and cryptographic anti-bot protection without third-party CAPTCHA for WordPress.', 'anonympins-bot-mitigation-pow'); ?></p>
828
982
 
829
983
  <div style="background:#fff;border-left:4px solid #2271b1;padding:12px 18px;margin:18px 0;box-shadow:0 1px 1px rgba(0,0,0,.04);">
830
984
  <p style="margin:4px 0;">
831
985
  <strong><?php esc_html_e('Official documentation reference:', 'anonympins-bot-mitigation-pow'); ?></strong>
832
986
  <a href="https://github.com/anonympins/fingerprint/blob/main/doc/full_options.md" target="_blank" rel="noopener noreferrer" class="button button-secondary" style="margin-left:8px;">
833
- <span class="dashicons dashicons-external" style="vertical-align:middle;"></span> <?php esc_html_e('View Full Configuration Options on GitHub', 'anonympins-bot-mitigation-pow'); ?>
987
+ <span class="dashicons dashicons-external" style="vertical-align:middle;"></span> <?php esc_html_e('View full configuration options on GitHub', 'anonympins-bot-mitigation-pow'); ?>
834
988
  </a>
835
989
  <a href="https://github.com/anonympins/fingerprint" target="_blank" rel="noopener noreferrer" class="button button-secondary" style="margin-left:4px;">
836
- <span class="dashicons dashicons-admin-plugins" style="vertical-align:middle;"></span> <?php esc_html_e('GitHub Repository', 'anonympins-bot-mitigation-pow'); ?>
990
+ <span class="dashicons dashicons-admin-plugins" style="vertical-align:middle;"></span> <?php esc_html_e('GitHub repository', 'anonympins-bot-mitigation-pow'); ?>
837
991
  </a>
838
992
  </p>
839
993
  </div>
@@ -841,7 +995,7 @@ function fingerprint_render_admin_page(): void {
841
995
  <!-- STATISTICS & STATUS DASHBOARD -->
842
996
  <div style="display:grid;grid-template-columns:repeat(auto-fit, minmax(240px, 1fr));gap:16px;margin-bottom:24px;">
843
997
  <div style="background:#fff;padding:16px;border-radius:4px;border:1px solid #ccd0d4;">
844
- <h3 style="margin-top:0;"><?php esc_html_e('HTTPS Status', 'anonympins-bot-mitigation-pow'); ?></h3>
998
+ <h3 style="margin-top:0;"><?php esc_html_e('HTTPS status', 'anonympins-bot-mitigation-pow'); ?></h3>
845
999
  <?php if (is_ssl()): ?>
846
1000
  <p style="color:#007017;font-weight:bold;font-size:16px;">
847
1001
  <span class="dashicons dashicons-yes-alt"></span> <?php esc_html_e('Active (WebCrypto Subtle available)', 'anonympins-bot-mitigation-pow'); ?>
@@ -853,7 +1007,7 @@ function fingerprint_render_admin_page(): void {
853
1007
  <?php endif; ?>
854
1008
  </div>
855
1009
  <div style="background:#fff;padding:16px;border-radius:4px;border:1px solid #ccd0d4;">
856
- <h3 style="margin-top:0;"><?php esc_html_e('SQL Cache ($wpdb)', 'anonympins-bot-mitigation-pow'); ?></h3>
1010
+ <h3 style="margin-top:0;"><?php esc_html_e('SQL cache ($wpdb)', 'anonympins-bot-mitigation-pow'); ?></h3>
857
1011
  <p style="font-size:22px;margin:0;font-weight:600;"><?php echo esc_html((string)$totalRows); ?> <span style="font-size:14px;color:#646970;font-weight:normal;"><?php esc_html_e('keys in database', 'anonympins-bot-mitigation-pow'); ?></span></p>
858
1012
  <small style="color:#8c8f94;"><?php
859
1013
  /* translators: %d: number of expired keys awaiting cron purge */
@@ -861,7 +1015,7 @@ function fingerprint_render_admin_page(): void {
861
1015
  ?></small>
862
1016
  </div>
863
1017
  <div style="background:#fff;padding:16px;border-radius:4px;border:1px solid #ccd0d4;">
864
- <h3 style="margin-top:0;"><?php esc_html_e('Active Profiles', 'anonympins-bot-mitigation-pow'); ?></h3>
1018
+ <h3 style="margin-top:0;"><?php esc_html_e('Active profiles', 'anonympins-bot-mitigation-pow'); ?></h3>
865
1019
  <p style="margin:0;">
866
1020
  <?php esc_html_e('Frontend:', 'anonympins-bot-mitigation-pow'); ?> <strong><?php echo esc_html($effective['frontend']['profile']); ?></strong><br>
867
1021
  <?php esc_html_e('Admin:', 'anonympins-bot-mitigation-pow'); ?> <strong><?php echo esc_html($effective['admin']['profile']); ?></strong><br>
@@ -869,7 +1023,7 @@ function fingerprint_render_admin_page(): void {
869
1023
  </p>
870
1024
  </div>
871
1025
  <div style="background:#fff;padding:16px;border-radius:4px;border:1px solid #ccd0d4;">
872
- <h3 style="margin-top:0;"><?php esc_html_e('Sandbox Mode', 'anonympins-bot-mitigation-pow'); ?></h3>
1026
+ <h3 style="margin-top:0;"><?php esc_html_e('Sandbox mode', 'anonympins-bot-mitigation-pow'); ?></h3>
873
1027
  <?php if ($sandboxConfig['enabled']): ?>
874
1028
  <p style="color:#dba617;font-weight:bold;font-size:16px;margin:0;">
875
1029
  <span class="dashicons dashicons-warning"></span> <?php esc_html_e('Active (Simulation)', 'anonympins-bot-mitigation-pow'); ?>
@@ -886,30 +1040,31 @@ function fingerprint_render_admin_page(): void {
886
1040
 
887
1041
  <!-- NAVIGATION TABS -->
888
1042
  <h2 class="nav-tab-wrapper">
889
- <a href="#tab-settings" class="nav-tab nav-tab-active" onclick="fingerprintSwitchTab(event, 'tab-settings')"><?php esc_html_e('Settings & Profiles', 'anonympins-bot-mitigation-pow'); ?></a>
890
- <a href="#tab-sandbox" class="nav-tab" onclick="fingerprintSwitchTab(event, 'tab-sandbox')"><?php esc_html_e('Sandbox / Test Mode', 'anonympins-bot-mitigation-pow'); ?></a>
891
- <a href="#tab-metrics" class="nav-tab" onclick="fingerprintSwitchTab(event, 'tab-metrics')"><?php esc_html_e('Metrics & Weights View', 'anonympins-bot-mitigation-pow'); ?></a>
892
- <a href="#tab-prometheus" class="nav-tab" onclick="fingerprintSwitchTab(event, 'tab-prometheus')"><?php esc_html_e('Prometheus Stream', 'anonympins-bot-mitigation-pow'); ?></a>
1043
+ <a href="#tab-settings" class="nav-tab nav-tab-active" onclick="fingerprintSwitchTab(event, 'tab-settings')"><?php esc_html_e('Settings & profiles', 'anonympins-bot-mitigation-pow'); ?></a>
1044
+ <a href="#tab-template" class="nav-tab" onclick="fingerprintSwitchTab(event, 'tab-template')"><?php esc_html_e('Challenge HTML template', 'anonympins-bot-mitigation-pow'); ?></a>
1045
+ <a href="#tab-sandbox" class="nav-tab" onclick="fingerprintSwitchTab(event, 'tab-sandbox')"><?php esc_html_e('Sandbox / test mode', 'anonympins-bot-mitigation-pow'); ?></a>
1046
+ <a href="#tab-metrics" class="nav-tab" onclick="fingerprintSwitchTab(event, 'tab-metrics')"><?php esc_html_e('Metrics & weights view', 'anonympins-bot-mitigation-pow'); ?></a>
1047
+ <a href="#tab-prometheus" class="nav-tab" onclick="fingerprintSwitchTab(event, 'tab-prometheus')"><?php esc_html_e('Prometheus stream', 'anonympins-bot-mitigation-pow'); ?></a>
893
1048
  </h2>
894
1049
 
895
1050
  <div id="tab-settings" class="fingerprint-tab-content" style="background:#fff;padding:20px;border:1px solid #ccd0d4;border-top:none;">
896
1051
  <form method="post" action="">
897
1052
  <?php wp_nonce_field('fingerprint_settings_nonce', 'fingerprint_nonce'); ?>
898
- <h3><?php esc_html_e('1. Target Security Profiles', 'anonympins-bot-mitigation-pow'); ?></h3>
1053
+ <h3><?php esc_html_e('1. Target security profiles', 'anonympins-bot-mitigation-pow'); ?></h3>
899
1054
  <table class="form-table">
900
1055
  <tr>
901
- <th scope="row"><label for="frontend_profile"><?php esc_html_e('Visitors & Frontend', 'anonympins-bot-mitigation-pow'); ?></label></th>
1056
+ <th scope="row"><label for="frontend_profile"><?php esc_html_e('Visitors & frontend', 'anonympins-bot-mitigation-pow'); ?></label></th>
902
1057
  <td>
903
1058
  <select name="frontend_profile" id="frontend_profile">
904
1059
  <option value="blog" <?php selected($effective['frontend']['profile'], 'blog'); ?>><?php esc_html_e('Blog (Optimal for content, smooth UX)', 'anonympins-bot-mitigation-pow'); ?></option>
905
1060
  <option value="balanced" <?php selected($effective['frontend']['profile'], 'balanced'); ?>><?php esc_html_e('Balanced (General purpose)', 'anonympins-bot-mitigation-pow'); ?></option>
906
1061
  <option value="strict" <?php selected($effective['frontend']['profile'], 'strict'); ?>><?php esc_html_e('Strict (Maximum protection)', 'anonympins-bot-mitigation-pow'); ?></option>
907
- <option value="ecommerce" <?php selected($effective['frontend']['profile'], 'ecommerce'); ?>><?php esc_html_e('E-commerce (Anti-scraping / scalping)', 'anonympins-bot-mitigation-pow'); ?></option>
1062
+ <option value="ecommerce" <?php selected($effective['frontend']['profile'], 'ecommerce'); ?>><?php esc_html_e('E-commerce (anti-scraping / scalping)', 'anonympins-bot-mitigation-pow'); ?></option>
908
1063
  </select>
909
1064
  </td>
910
1065
  </tr>
911
1066
  <tr>
912
- <th scope="row"><label for="admin_profile"><?php esc_html_e('Administration Area (wp-login / wp-admin)', 'anonympins-bot-mitigation-pow'); ?></label></th>
1067
+ <th scope="row"><label for="admin_profile"><?php esc_html_e('Administration area (wp-login / wp-admin)', 'anonympins-bot-mitigation-pow'); ?></label></th>
913
1068
  <td>
914
1069
  <select name="admin_profile" id="admin_profile">
915
1070
  <option value="strict" <?php selected($effective['admin']['profile'], 'strict'); ?>><?php esc_html_e('Strict (Recommended to secure logins)', 'anonympins-bot-mitigation-pow'); ?></option>
@@ -928,27 +1083,27 @@ function fingerprint_render_admin_page(): void {
928
1083
  </tr>
929
1084
  </table>
930
1085
  <hr>
931
- <h3><?php esc_html_e('2. Action Thresholds (Frontend)', 'anonympins-bot-mitigation-pow'); ?></h3>
1086
+ <h3><?php esc_html_e('2. Action thresholds (frontend)', 'anonympins-bot-mitigation-pow'); ?></h3>
932
1087
  <table class="form-table">
933
1088
  <tr>
934
- <th scope="row"><?php esc_html_e('Suspicion Score Thresholds (0 - 100)', 'anonympins-bot-mitigation-pow'); ?></th>
1089
+ <th scope="row"><?php esc_html_e('Suspicion score thresholds (0 - 100)', 'anonympins-bot-mitigation-pow'); ?></th>
935
1090
  <td>
936
- <label><?php esc_html_e('Low (Initial challenge):', 'anonympins-bot-mitigation-pow'); ?>
1091
+ <label><?php esc_html_e('Low (initial challenge):', 'anonympins-bot-mitigation-pow'); ?>
937
1092
  <input type="number" name="frontend_threshold_low" value="<?php echo esc_attr((string)($frontendConfig['thresholds']['low'] ?? 20)); ?>" min="1" max="50" style="width:80px;">
938
1093
  </label><br><br>
939
- <label><?php esc_html_e('Medium (Hardened challenge):', 'anonympins-bot-mitigation-pow'); ?>
1094
+ <label><?php esc_html_e('Medium (hardened challenge):', 'anonympins-bot-mitigation-pow'); ?>
940
1095
  <input type="number" name="frontend_threshold_medium" value="<?php echo esc_attr((string)($frontendConfig['thresholds']['medium'] ?? 45)); ?>" min="10" max="75" style="width:80px;">
941
1096
  </label><br><br>
942
- <label><?php esc_html_e('High (Heavy Proof-of-Work):', 'anonympins-bot-mitigation-pow'); ?>
1097
+ <label><?php esc_html_e('High (heavy proof-of-work):', 'anonympins-bot-mitigation-pow'); ?>
943
1098
  <input type="number" name="frontend_threshold_high" value="<?php echo esc_attr((string)($frontendConfig['thresholds']['high'] ?? 75)); ?>" min="30" max="95" style="width:80px;">
944
1099
  </label><br><br>
945
- <label><?php esc_html_e('Block (Immediate 403 Forbidden):', 'anonympins-bot-mitigation-pow'); ?>
1100
+ <label><?php esc_html_e('Block (immediate 403 forbidden):', 'anonympins-bot-mitigation-pow'); ?>
946
1101
  <input type="number" name="frontend_threshold_block" value="<?php echo esc_attr((string)($frontendConfig['thresholds']['block'] ?? 95)); ?>" min="50" max="100" style="width:80px;">
947
1102
  </label>
948
1103
  </td>
949
1104
  </tr>
950
1105
  <tr>
951
- <th scope="row"><?php esc_html_e('Advanced Behaviors', 'anonympins-bot-mitigation-pow'); ?></th>
1106
+ <th scope="row"><?php esc_html_e('Advanced behaviors', 'anonympins-bot-mitigation-pow'); ?></th>
952
1107
  <td>
953
1108
  <label>
954
1109
  <input type="checkbox" name="frontend_challenge_new" value="1" <?php checked(!empty($frontendConfig['challengeNewDevices'])); ?>>
@@ -965,13 +1120,13 @@ function fingerprint_render_admin_page(): void {
965
1120
  </td>
966
1121
  </tr>
967
1122
  </table>
968
- <?php submit_button(esc_html__('Save Changes', 'anonympins-bot-mitigation-pow'), 'primary', 'fingerprint_save_settings'); ?>
1123
+ <?php submit_button(esc_html__('Save changes', 'anonympins-bot-mitigation-pow'), 'primary', 'fingerprint_save_settings'); ?>
969
1124
  </form>
970
1125
  <hr>
971
1126
  <form method="post" action="" style="margin-top:16px;">
972
1127
  <?php wp_nonce_field('fingerprint_clear_nonce', 'fingerprint_nonce_clear'); ?>
973
1128
  <p>
974
- <strong><?php esc_html_e('Store Maintenance:', 'anonympins-bot-mitigation-pow'); ?></strong>
1129
+ <strong><?php esc_html_e('Store maintenance:', 'anonympins-bot-mitigation-pow'); ?></strong>
975
1130
  <button type="submit" name="fingerprint_clear_store" class="button button-secondary" onclick="return confirm('<?php echo esc_js(__('Purge all stored sessions and nonces?', 'anonympins-bot-mitigation-pow')); ?>');">
976
1131
  <?php esc_html_e('Clear SQL cache table ($wpdb)', 'anonympins-bot-mitigation-pow'); ?>
977
1132
  </button>
@@ -979,26 +1134,90 @@ function fingerprint_render_admin_page(): void {
979
1134
  </form>
980
1135
  </div>
981
1136
 
1137
+ <div id="tab-template" class="fingerprint-tab-content" style="display:none;background:#fff;padding:20px;border:1px solid #ccd0d4;border-top:none;">
1138
+ <h3><?php esc_html_e('Challenge HTML template editor', 'anonympins-bot-mitigation-pow'); ?></h3>
1139
+ <p class="description">
1140
+ <?php esc_html_e('Customize the HTML markup presented to challenged visitors. Leave empty or reset to use the built-in responsive default template.', 'anonympins-bot-mitigation-pow'); ?>
1141
+ </p>
1142
+
1143
+ <div style="background:#f0f6fc;border-left:4px solid #2271b1;padding:12px 16px;margin:16px 0;">
1144
+ <h4 style="margin:0 0 8px 0;"><?php esc_html_e('Available template placeholders', 'anonympins-bot-mitigation-pow'); ?></h4>
1145
+ <table class="widefat striped" style="background:#fff;font-size:12px;">
1146
+ <thead>
1147
+ <tr>
1148
+ <th style="width:200px;"><?php esc_html_e('Placeholder', 'anonympins-bot-mitigation-pow'); ?></th>
1149
+ <th><?php esc_html_e('Description & content injected', 'anonympins-bot-mitigation-pow'); ?></th>
1150
+ </tr>
1151
+ </thead>
1152
+ <tbody>
1153
+ <tr>
1154
+ <td><code>{{TITLE}}</code></td>
1155
+ <td><?php esc_html_e('Challenge page title (e.g., "Security verification")', 'anonympins-bot-mitigation-pow'); ?></td>
1156
+ </tr>
1157
+ <tr>
1158
+ <td><code>{{MESSAGE}}</code></td>
1159
+ <td><?php esc_html_e('Human-readable instruction or explanation message', 'anonympins-bot-mitigation-pow'); ?></td>
1160
+ </tr>
1161
+ <tr>
1162
+ <td><code>{{NONCE}}</code></td>
1163
+ <td><?php esc_html_e('Unique cryptographic challenge nonce generated by the server', 'anonympins-bot-mitigation-pow'); ?></td>
1164
+ </tr>
1165
+ <tr>
1166
+ <td><code>{{DIFFICULTY}}</code></td>
1167
+ <td><?php esc_html_e('Target computational difficulty complexity number', 'anonympins-bot-mitigation-pow'); ?></td>
1168
+ </tr>
1169
+ <tr>
1170
+ <td><code>{{ALGORITHM}}</code></td>
1171
+ <td><?php esc_html_e('Selected hashing algorithm (e.g., "SHA-256")', 'anonympins-bot-mitigation-pow'); ?></td>
1172
+ </tr>
1173
+ <tr>
1174
+ <td><code>{{FORM_ACTION}}</code></td>
1175
+ <td><?php esc_html_e('Target POST URI destination where the PoW result must be submitted', 'anonympins-bot-mitigation-pow'); ?></td>
1176
+ </tr>
1177
+ <tr>
1178
+ <td><code>{{SOLVER_SCRIPT}}</code></td>
1179
+ <td><?php esc_html_e('Mandatory: inlined WebAssembly/WebWorker solver script block required for verification', 'anonympins-bot-mitigation-pow'); ?></td>
1180
+ </tr>
1181
+ <tr>
1182
+ <td><code>{{CUSTOM_CSS}}</code></td>
1183
+ <td><?php esc_html_e('Optional: default reset styling and challenge animations', 'anonympins-bot-mitigation-pow'); ?></td>
1184
+ </tr>
1185
+ </tbody>
1186
+ </table>
1187
+ </div>
1188
+
1189
+ <form method="post" action="">
1190
+ <?php wp_nonce_field('fingerprint_template_nonce', 'fingerprint_nonce_template'); ?>
1191
+ <p>
1192
+ <textarea name="challenge_template" id="challenge_template" rows="18" style="width:100%;font-family:monospace;"><?php echo esc_textarea($currentTemplate); ?></textarea>
1193
+ </p>
1194
+ <p style="display:flex;gap:10px;">
1195
+ <?php submit_button(esc_html__('Save template', 'anonympins-bot-mitigation-pow'), 'primary', 'fingerprint_save_template', false); ?>
1196
+ <?php submit_button(esc_html__('Reset to default template', 'anonympins-bot-mitigation-pow'), 'secondary', 'fingerprint_reset_template', false, ['onclick' => "return confirm('" . esc_js(__('Reset challenge template to default?', 'anonympins-bot-mitigation-pow')) . "');"]); ?>
1197
+ </p>
1198
+ </form>
1199
+ </div>
1200
+
982
1201
  <div id="tab-sandbox" class="fingerprint-tab-content" style="display:none;background:#fff;padding:20px;border:1px solid #ccd0d4;border-top:none;">
983
- <h3><?php esc_html_e('Sandbox Mode & Dry-Run Configuration', 'anonympins-bot-mitigation-pow'); ?></h3>
1202
+ <h3><?php esc_html_e('Sandbox mode & dry-run configuration', 'anonympins-bot-mitigation-pow'); ?></h3>
984
1203
  <form method="post" action="">
985
1204
  <?php wp_nonce_field('fingerprint_sandbox_nonce', 'fingerprint_nonce_sandbox'); ?>
986
1205
  <table class="form-table">
987
1206
  <tr>
988
- <th scope="row"><?php esc_html_e('Sandbox Activation', 'anonympins-bot-mitigation-pow'); ?></th>
1207
+ <th scope="row"><?php esc_html_e('Sandbox activation', 'anonympins-bot-mitigation-pow'); ?></th>
989
1208
  <td>
990
1209
  <label>
991
1210
  <input type="checkbox" name="sandbox_enabled" value="1" <?php checked($sandboxConfig['enabled']); ?>>
992
- <strong><?php esc_html_e('Enable Sandbox Mode', 'anonympins-bot-mitigation-pow'); ?></strong>
1211
+ <strong><?php esc_html_e('Enable sandbox mode', 'anonympins-bot-mitigation-pow'); ?></strong>
993
1212
  </label>
994
1213
  </td>
995
1214
  </tr>
996
1215
  <tr>
997
- <th scope="row"><?php esc_html_e('Behavior & Enforcement', 'anonympins-bot-mitigation-pow'); ?></th>
1216
+ <th scope="row"><?php esc_html_e('Behavior & enforcement', 'anonympins-bot-mitigation-pow'); ?></th>
998
1217
  <td>
999
1218
  <label>
1000
1219
  <input type="checkbox" name="sandbox_audit_only" value="1" <?php checked($sandboxConfig['audit_only']); ?>>
1001
- <strong><?php esc_html_e('Audit Only / Dry-Run (Never block or challenge visitors)', 'anonympins-bot-mitigation-pow'); ?></strong>
1220
+ <strong><?php esc_html_e('Audit only / dry-run (never block or challenge visitors)', 'anonympins-bot-mitigation-pow'); ?></strong>
1002
1221
  </label><br><br>
1003
1222
  <label>
1004
1223
  <input type="checkbox" name="sandbox_add_headers" value="1" <?php checked($sandboxConfig['add_headers']); ?>>
@@ -1011,7 +1230,7 @@ function fingerprint_render_admin_page(): void {
1011
1230
  </td>
1012
1231
  </tr>
1013
1232
  <tr>
1014
- <th scope="row"><label for="sandbox_ip_filter"><?php esc_html_e('Test IP Whitelist Filter', 'anonympins-bot-mitigation-pow'); ?></label></th>
1233
+ <th scope="row"><label for="sandbox_ip_filter"><?php esc_html_e('Test IP whitelist filter', 'anonympins-bot-mitigation-pow'); ?></label></th>
1015
1234
  <td>
1016
1235
  <?php
1017
1236
  // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
@@ -1026,32 +1245,32 @@ function fingerprint_render_admin_page(): void {
1026
1245
  </td>
1027
1246
  </tr>
1028
1247
  </table>
1029
- <?php submit_button(esc_html__('Save Sandbox Settings', 'anonympins-bot-mitigation-pow'), 'primary', 'fingerprint_save_sandbox'); ?>
1248
+ <?php submit_button(esc_html__('Save sandbox settings', 'anonympins-bot-mitigation-pow'), 'primary', 'fingerprint_save_sandbox'); ?>
1030
1249
  </form>
1031
1250
  <hr>
1032
1251
  <div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:8px;">
1033
- <h3 style="margin:0;"><span class="dashicons dashicons-shield" style="vertical-align:text-bottom;"></span> <?php esc_html_e('Live Challenged Visitors & Suspicion Monitor', 'anonympins-bot-mitigation-pow'); ?></h3>
1252
+ <h3 style="margin:0;"><span class="dashicons dashicons-shield" style="vertical-align:text-bottom;"></span> <?php esc_html_e('Live challenged visitors & suspicion monitor', 'anonympins-bot-mitigation-pow'); ?></h3>
1034
1253
  <div>
1035
- <span style="font-weight:600;color:#646970;"><?php esc_html_e('Recent Challenges:', 'anonympins-bot-mitigation-pow'); ?></span>
1254
+ <span style="font-weight:600;color:#646970;"><?php esc_html_e('Recent challenges:', 'anonympins-bot-mitigation-pow'); ?></span>
1036
1255
  <span id="live-challenges-count" style="display:inline-block;padding:2px 8px;background:#2271b1;color:#fff;border-radius:10px;font-weight:bold;font-size:12px;">0</span>
1037
1256
  </div>
1038
1257
  </div>
1039
1258
  <div style="background:#f6f7f7;padding:16px;border:1px solid #c3c4c7;border-radius:4px;margin-bottom:15px;">
1040
1259
  <div style="display:flex;gap:12px;align-items:center;margin-bottom:12px;flex-wrap:wrap;">
1041
1260
  <button type="button" class="button button-primary" id="btn-toggle-sse" onclick="fingerprintToggleSSE();">
1042
- <span class="dashicons dashicons-controls-play" style="vertical-align:middle;"></span> <span id="sse-btn-text"><?php esc_html_e('Start Real-Time SSE Stream', 'anonympins-bot-mitigation-pow'); ?></span>
1261
+ <span class="dashicons dashicons-controls-play" style="vertical-align:middle;"></span> <span id="sse-btn-text"><?php esc_html_e('Start real-time SSE stream', 'anonympins-bot-mitigation-pow'); ?></span>
1043
1262
  </button>
1044
1263
  <button type="button" class="button button-secondary" onclick="fingerprintFetchTelemetry();">
1045
- <span class="dashicons dashicons-update" style="vertical-align:middle;"></span> <?php esc_html_e('Poll Now (REST)', 'anonympins-bot-mitigation-pow'); ?>
1264
+ <span class="dashicons dashicons-update" style="vertical-align:middle;"></span> <?php esc_html_e('Poll now (REST)', 'anonympins-bot-mitigation-pow'); ?>
1046
1265
  </button>
1047
1266
  <button type="button" class="button button-secondary" onclick="fingerprintClearFeed();">
1048
- <span class="dashicons dashicons-trash" style="vertical-align:middle;"></span> <?php esc_html_e('Clear Feed', 'anonympins-bot-mitigation-pow'); ?>
1267
+ <span class="dashicons dashicons-trash" style="vertical-align:middle;"></span> <?php esc_html_e('Clear feed', 'anonympins-bot-mitigation-pow'); ?>
1049
1268
  </button>
1050
1269
  <span id="sse-connection-status" style="font-weight:bold;color:#646970;"></span>
1051
1270
  </div>
1052
1271
  <div style="display:grid;grid-template-columns: 1.4fr 1fr;gap:16px;">
1053
1272
  <div style="background:#fff;padding:12px;border:1px solid #dcdcde;border-radius:4px;overflow-x:auto;">
1054
- <h4 style="margin:0 0 8px 0;"><?php esc_html_e('Challenged Visitors Stream', 'anonympins-bot-mitigation-pow'); ?></h4>
1273
+ <h4 style="margin:0 0 8px 0;"><?php esc_html_e('Challenged visitors stream', 'anonympins-bot-mitigation-pow'); ?></h4>
1055
1274
  <table class="wp-list-table widefat fixed striped" style="font-size:12px;">
1056
1275
  <thead>
1057
1276
  <tr>
@@ -1070,7 +1289,7 @@ function fingerprint_render_admin_page(): void {
1070
1289
  </div>
1071
1290
  <div style="background:#fff;padding:14px;border:1px solid #dcdcde;border-radius:4px;">
1072
1291
  <div style="display:flex;justify-content:space-between;align-items:flex-start;margin-bottom:8px;">
1073
- <h4 style="margin:0;"><?php esc_html_e('Inspection & Suspicion Vector', 'anonympins-bot-mitigation-pow'); ?></h4>
1292
+ <h4 style="margin:0;"><?php esc_html_e('Inspection & suspicion vector', 'anonympins-bot-mitigation-pow'); ?></h4>
1074
1293
  <div id="live-score-val" style="font-size:28px;font-weight:bold;color:#2271b1;line-height:1;">--</div>
1075
1294
  </div>
1076
1295
  <div id="live-score-sub" style="font-size:12px;color:#646970;background:#f6f7f7;padding:8px;border-radius:3px;margin-bottom:10px;">
@@ -1085,15 +1304,15 @@ function fingerprint_render_admin_page(): void {
1085
1304
  </div>
1086
1305
 
1087
1306
  <div id="tab-metrics" class="fingerprint-tab-content" style="display:none;background:#fff;padding:20px;border:1px solid #ccd0d4;border-top:none;">
1088
- <h3><?php esc_html_e('Behavioral & Transport Indicator Weights', 'anonympins-bot-mitigation-pow'); ?></h3>
1307
+ <h3><?php esc_html_e('Behavioral & transport indicator weights', 'anonympins-bot-mitigation-pow'); ?></h3>
1089
1308
  <form method="post" action="">
1090
1309
  <?php wp_nonce_field('fingerprint_settings_nonce', 'fingerprint_nonce'); ?>
1091
1310
  <table class="wp-list-table widefat fixed striped">
1092
1311
  <thead>
1093
1312
  <tr>
1094
- <th style="width:280px;"><?php esc_html_e('Indicator / Metric', 'anonympins-bot-mitigation-pow'); ?></th>
1095
- <th style="width:120px;"><?php esc_html_e('Current Weight', 'anonympins-bot-mitigation-pow'); ?></th>
1096
- <th><?php esc_html_e('Description & Detection Role', 'anonympins-bot-mitigation-pow'); ?></th>
1313
+ <th style="width:280px;"><?php esc_html_e('Indicator / metric', 'anonympins-bot-mitigation-pow'); ?></th>
1314
+ <th style="width:120px;"><?php esc_html_e('Current weight', 'anonympins-bot-mitigation-pow'); ?></th>
1315
+ <th><?php esc_html_e('Description & detection role', 'anonympins-bot-mitigation-pow'); ?></th>
1097
1316
  </tr>
1098
1317
  </thead>
1099
1318
  <tbody>
@@ -1107,13 +1326,33 @@ function fingerprint_render_admin_page(): void {
1107
1326
  </tbody>
1108
1327
  </table>
1109
1328
  <p style="margin-top:16px;">
1110
- <?php submit_button(esc_html__('Update Metric Weights', 'anonympins-bot-mitigation-pow'), 'primary', 'fingerprint_save_settings', false); ?>
1329
+ <?php submit_button(esc_html__('Update metric weights', 'anonympins-bot-mitigation-pow'), 'primary', 'fingerprint_save_settings', false); ?>
1111
1330
  </p>
1112
1331
  </form>
1113
1332
  </div>
1114
1333
 
1115
1334
  <div id="tab-prometheus" class="fingerprint-tab-content" style="display:none;background:#fff;padding:20px;border:1px solid #ccd0d4;border-top:none;">
1116
- <h3><?php esc_html_e('Real-Time Prometheus Metrics', 'anonympins-bot-mitigation-pow'); ?></h3>
1335
+ <h3><?php esc_html_e('Real-time prometheus metrics', 'anonympins-bot-mitigation-pow'); ?></h3>
1336
+ <form method="post" action="" style="margin-bottom:20px;">
1337
+ <?php wp_nonce_field('fingerprint_prometheus_nonce', 'fingerprint_nonce_prometheus'); ?>
1338
+ <table class="form-table">
1339
+ <tr>
1340
+ <th scope="row"><label for="metrics_endpoint"><?php esc_html_e('Metrics endpoint path', 'anonympins-bot-mitigation-pow'); ?></label></th>
1341
+ <td>
1342
+ <input type="text" name="metrics_endpoint" id="metrics_endpoint" value="<?php echo esc_attr(fingerprint_get_metrics_endpoint()); ?>" class="regular-text" placeholder="/metrics">
1343
+ <p class="description">
1344
+ <?php esc_html_e('Custom path for direct Prometheus scraping (default: /metrics). Leave empty to disable direct path scraping.', 'anonympins-bot-mitigation-pow'); ?>
1345
+ </p>
1346
+ <?php if (fingerprint_get_metrics_endpoint() !== ''): ?>
1347
+ <p class="description">
1348
+ <strong><?php esc_html_e('Scrape URL:', 'anonympins-bot-mitigation-pow'); ?></strong> <code><?php echo esc_html(home_url(fingerprint_get_metrics_endpoint())); ?></code>
1349
+ </p>
1350
+ <?php endif; ?>
1351
+ </td>
1352
+ </tr>
1353
+ </table>
1354
+ <?php submit_button(esc_html__('Save prometheus settings', 'anonympins-bot-mitigation-pow'), 'primary', 'fingerprint_save_prometheus'); ?>
1355
+ </form>
1117
1356
  <textarea readonly style="width:100%;height:380px;font-family:monospace;background:#f6f7f7;padding:12px;"><?php echo esc_textarea($prometheusRaw); ?></textarea>
1118
1357
  </div>
1119
1358
  </div>