Changeset 3488602
- Timestamp:
- 03/23/2026 06:34:16 AM (10 days ago)
- Location:
- ai-discovery-files
- Files:
-
- 58 added
- 8 edited
-
tags/1.3.4 (added)
-
tags/1.3.4/LICENSE (added)
-
tags/1.3.4/admin (added)
-
tags/1.3.4/admin/class-admin.php (added)
-
tags/1.3.4/admin/class-settings.php (added)
-
tags/1.3.4/admin/css (added)
-
tags/1.3.4/admin/css/admin.css (added)
-
tags/1.3.4/admin/css/crawlers.css (added)
-
tags/1.3.4/admin/js (added)
-
tags/1.3.4/admin/js/admin.js (added)
-
tags/1.3.4/admin/js/chart.min.js (added)
-
tags/1.3.4/admin/js/crawlers.js (added)
-
tags/1.3.4/admin/views (added)
-
tags/1.3.4/admin/views/partials (added)
-
tags/1.3.4/admin/views/partials/crawler-bot-detail.php (added)
-
tags/1.3.4/admin/views/partials/crawler-bot-selector.php (added)
-
tags/1.3.4/admin/views/partials/crawler-file-access-widget.php (added)
-
tags/1.3.4/admin/views/partials/crawler-log-viewer.php (added)
-
tags/1.3.4/admin/views/partials/directory-cta.php (added)
-
tags/1.3.4/admin/views/partials/review-banner.php (added)
-
tags/1.3.4/admin/views/partials/tier-progress.php (added)
-
tags/1.3.4/admin/views/partials/verify-modal.php (added)
-
tags/1.3.4/admin/views/settings-page.php (added)
-
tags/1.3.4/admin/views/tab-advanced.php (added)
-
tags/1.3.4/admin/views/tab-content.php (added)
-
tags/1.3.4/admin/views/tab-crawlers.php (added)
-
tags/1.3.4/admin/views/tab-identity.php (added)
-
tags/1.3.4/admin/views/tab-permissions.php (added)
-
tags/1.3.4/admin/views/tab-preview.php (added)
-
tags/1.3.4/admin/views/tab-status.php (added)
-
tags/1.3.4/ai-discovery-files.php (added)
-
tags/1.3.4/includes (added)
-
tags/1.3.4/includes/class-crawler-analytics.php (added)
-
tags/1.3.4/includes/class-crawler-conflicts.php (added)
-
tags/1.3.4/includes/class-crawler-logger.php (added)
-
tags/1.3.4/includes/class-crawler-registry.php (added)
-
tags/1.3.4/includes/class-data-collector.php (added)
-
tags/1.3.4/includes/class-generator.php (added)
-
tags/1.3.4/includes/class-plugin.php (added)
-
tags/1.3.4/includes/class-server.php (added)
-
tags/1.3.4/includes/class-validator.php (added)
-
tags/1.3.4/languages (added)
-
tags/1.3.4/languages/ai-discovery-files.pot (added)
-
tags/1.3.4/languages/index.php (added)
-
tags/1.3.4/readme.txt (added)
-
tags/1.3.4/templates (added)
-
tags/1.3.4/templates/ai-json.php (added)
-
tags/1.3.4/templates/ai-txt.php (added)
-
tags/1.3.4/templates/brand-txt.php (added)
-
tags/1.3.4/templates/developer-ai-txt.php (added)
-
tags/1.3.4/templates/faq-ai-txt.php (added)
-
tags/1.3.4/templates/identity-json.php (added)
-
tags/1.3.4/templates/llm-txt.php (added)
-
tags/1.3.4/templates/llms-html.php (added)
-
tags/1.3.4/templates/llms-txt.php (added)
-
tags/1.3.4/templates/robots-ai-txt.php (added)
-
tags/1.3.4/uninstall.php (added)
-
trunk/admin/class-admin.php (modified) (3 diffs)
-
trunk/admin/css/crawlers.css (modified) (4 diffs)
-
trunk/admin/js/chart.min.js (added)
-
trunk/admin/js/crawlers.js (modified) (13 diffs)
-
trunk/admin/views/partials/crawler-bot-detail.php (modified) (1 diff)
-
trunk/admin/views/tab-crawlers.php (modified) (2 diffs)
-
trunk/ai-discovery-files.php (modified) (2 diffs)
-
trunk/includes/class-crawler-analytics.php (modified) (1 diff)
-
trunk/readme.txt (modified) (2 diffs)
Legend:
- Unmodified
- Added
- Removed
-
ai-discovery-files/trunk/admin/class-admin.php
r3488558 r3488602 44 44 add_action( 'wp_ajax_aidf_crawler_export_csv', array( __CLASS__, 'ajax_crawler_export_csv' ) ); 45 45 add_action( 'wp_ajax_aidf_crawler_clear_log', array( __CLASS__, 'ajax_crawler_clear_log' ) ); 46 add_action( 'wp_ajax_aidf_crawler_rebuild_summary', array( __CLASS__, 'ajax_crawler_rebuild_summary' ) ); 46 47 add_action( 'wp_ajax_aidf_crawler_conflicts', array( __CLASS__, 'ajax_crawler_conflicts' ) ); 47 48 add_action( 'wp_ajax_aidf_crawler_file_access', array( __CLASS__, 'ajax_crawler_file_access' ) ); … … 152 153 if ( 'crawlers' === $current_tab ) { 153 154 wp_enqueue_style( 'aidf-crawlers', AIDF_PLUGIN_URL . 'admin/css/crawlers.css', array( 'aidf-admin' ), AIDF_VERSION ); 154 wp_enqueue_script( 'aidf-crawlers', AIDF_PLUGIN_URL . 'admin/js/crawlers.js', array( 'jquery' ), AIDF_VERSION, true ); 155 wp_enqueue_script( 'aidf-chartjs', AIDF_PLUGIN_URL . 'admin/js/chart.min.js', array(), '4.4.8', true ); 156 wp_enqueue_script( 'aidf-crawlers', AIDF_PLUGIN_URL . 'admin/js/crawlers.js', array( 'jquery', 'aidf-chartjs' ), AIDF_VERSION, true ); 155 157 } 156 158 } … … 881 883 882 884 /** 885 * AJAX: Rebuild the crawler summary table from the raw log. 886 * 887 * Triggers an immediate summary rebuild so the dashboard reflects the 888 * latest data without waiting for the daily cron job. 889 * 890 * @since 1.3.2 891 */ 892 public static function ajax_crawler_rebuild_summary() { 893 check_ajax_referer( 'aidf_admin_nonce', 'nonce' ); 894 if ( ! current_user_can( 'manage_options' ) ) { 895 wp_send_json_error( __( 'Permission denied.', 'ai-discovery-files' ) ); 896 } 897 898 AIDF_Crawler_Analytics::rebuild_summary(); 899 900 wp_send_json_success( array( 901 'message' => __( 'Summary rebuilt successfully.', 'ai-discovery-files' ), 902 ) ); 903 } 904 905 /** 883 906 * AJAX: Crawler conflicts data. 884 907 * -
ai-discovery-files/trunk/admin/css/crawlers.css
r3488445 r3488602 130 130 131 131 /* ========================================================== 132 4. Bar Chart 133 ========================================================== */ 134 135 .aidf-bar-chart { 136 background: var(--aidf-panel); 137 border: 1px solid var(--aidf-border); 138 border-radius: var(--aidf-radius-md); 139 padding: var(--aidf-sp-6); 140 box-shadow: var(--aidf-shadow-sm); 141 min-height: 240px; 132 4. Chart.js Canvas 133 ========================================================== */ 134 135 .aidf-chart-canvas-wrap { 142 136 position: relative; 143 } 144 145 .aidf-bar-chart__grid { 146 display: grid; 147 grid-auto-columns: 1fr; 148 grid-auto-flow: column; 149 gap: var(--aidf-sp-2); 150 align-items: end; 151 height: 200px; 152 padding-bottom: var(--aidf-sp-6); 153 } 154 155 .aidf-bar-chart__bar { 156 display: flex; 157 flex-direction: column-reverse; 158 align-items: stretch; 159 height: 100%; 160 position: relative; 161 gap: 1px; 162 } 163 164 .aidf-bar-chart__segment { 165 border-radius: var(--aidf-radius-sm) var(--aidf-radius-sm) 0 0; 166 min-height: 2px; 167 transition: height var(--aidf-transition-slow); 168 cursor: pointer; 169 } 170 171 .aidf-bar-chart__segment:first-child { 172 border-radius: 0 0 var(--aidf-radius-sm) var(--aidf-radius-sm); 173 } 174 175 .aidf-bar-chart__segment:only-child { 176 border-radius: var(--aidf-radius-sm); 177 } 178 179 .aidf-bar-chart__label { 180 position: absolute; 181 bottom: calc(-1 * var(--aidf-sp-6)); 182 left: 50%; 183 transform: translateX(-50%); 184 font-size: var(--aidf-text-xs); 137 height: 250px; 138 width: 100%; 139 padding: var(--aidf-sp-4) var(--aidf-sp-6) var(--aidf-sp-6); 140 } 141 142 .aidf-chart-canvas-wrap--detail { 143 height: 180px; 144 padding: 0; 145 } 146 147 .aidf-chart-empty { 148 text-align: center; 149 padding: var(--aidf-sp-8) var(--aidf-sp-4); 185 150 color: var(--aidf-text-tertiary); 186 white-space: nowrap; 187 } 188 189 .aidf-bar-chart__tooltip { 190 position: absolute; 191 bottom: calc(100% + var(--aidf-sp-2)); 192 left: 50%; 193 transform: translateX(-50%); 194 background: var(--aidf-dark); 195 color: var(--aidf-text-inverse); 196 padding: var(--aidf-sp-2) var(--aidf-sp-3); 197 border-radius: var(--aidf-radius); 198 font-size: var(--aidf-text-xs); 199 white-space: nowrap; 200 z-index: 10; 201 box-shadow: var(--aidf-shadow-md); 202 pointer-events: none; 203 opacity: 0; 204 transition: opacity var(--aidf-transition); 205 } 206 207 .aidf-bar-chart__bar:hover .aidf-bar-chart__tooltip { 208 opacity: 1; 209 } 210 211 .aidf-bar-chart__tooltip::after { 212 content: ''; 213 position: absolute; 214 top: 100%; 215 left: 50%; 216 transform: translateX(-50%); 217 border: 5px solid transparent; 218 border-top-color: var(--aidf-dark); 151 font-size: var(--aidf-text-sm); 152 margin: 0; 219 153 } 220 154 … … 1018 952 1019 953 /* ========================================================== 1020 22. Chart Enhancements (inside dashboard panel) 1021 ========================================================== */ 1022 1023 .aidf-dashboard-panel .aidf-bar-chart { 1024 display: flex; 1025 gap: var(--aidf-sp-4); 1026 padding: var(--aidf-sp-4) var(--aidf-sp-6) var(--aidf-sp-6); 1027 background: transparent; 1028 border: none; 1029 box-shadow: none; 1030 border-radius: 0; 1031 min-height: auto; 1032 } 1033 1034 .aidf-bar-chart__y-axis { 1035 display: flex; 1036 flex-direction: column; 1037 justify-content: space-between; 1038 align-items: flex-end; 1039 width: 40px; 1040 flex-shrink: 0; 1041 padding-bottom: var(--aidf-sp-6); 1042 } 1043 1044 .aidf-bar-chart__y-label { 1045 font-size: var(--aidf-text-xs); 1046 color: var(--aidf-text-tertiary); 1047 line-height: 1; 1048 font-variant-numeric: tabular-nums; 1049 } 1050 1051 .aidf-bar-chart__empty { 1052 display: flex; 1053 align-items: center; 1054 justify-content: center; 1055 width: 100%; 1056 height: 200px; 1057 font-size: var(--aidf-text-sm); 1058 color: var(--aidf-text-tertiary); 1059 } 954 22. (Reserved — previously CSS bar chart enhancements) 955 ========================================================== */ 1060 956 1061 957 /* ========================================================== … … 1269 1165 } 1270 1166 1271 .aidf-bar-chart__grid {1272 gap: 1px;1273 }1274 1275 .aidf-bar-chart__y-axis {1276 display: none;1277 }1278 1279 1167 .aidf-log-filters { 1280 1168 flex-direction: column; … … 1389 1277 } 1390 1278 1391 /* Trend chart (single-colour bars) */ 1392 .aidf-detail-trend { 1393 display: flex; 1394 align-items: flex-end; 1395 gap: 3px; 1396 height: 120px; 1397 padding-bottom: var(--aidf-sp-4); /* room for labels */ 1398 position: relative; 1399 } 1400 1401 .aidf-detail-trend__bar { 1402 flex: 1; 1403 display: flex; 1404 flex-direction: column; 1405 align-items: center; 1406 justify-content: flex-end; 1407 height: 100%; 1408 cursor: default; 1409 } 1410 1411 .aidf-detail-trend__fill { 1412 width: 100%; 1413 background: var(--aidf-orange); 1414 border-radius: var(--aidf-radius-sm) var(--aidf-radius-sm) 0 0; 1415 min-height: 2px; 1416 transition: opacity 0.15s; 1417 } 1418 1419 .aidf-detail-trend__bar:hover .aidf-detail-trend__fill { 1420 opacity: 0.75; 1421 } 1422 1423 .aidf-detail-trend__label { 1424 font-size: 10px; 1425 color: var(--aidf-text-tertiary); 1426 margin-top: var(--aidf-sp-1); 1427 white-space: nowrap; 1428 overflow: hidden; 1429 text-overflow: ellipsis; 1430 max-width: 100%; 1431 } 1279 /* Trend chart — now rendered via Chart.js canvas (see .aidf-chart-canvas-wrap--detail) */ 1432 1280 1433 1281 /* Status code horizontal stacked bar */ -
ai-discovery-files/trunk/admin/js/crawlers.js
r3488558 r3488602 69 69 */ 70 70 var botColors = [ 71 ' var(--aidf-orange)',72 ' var(--aidf-blue)',73 ' var(--aidf-green)',74 ' var(--aidf-amber)',75 ' var(--aidf-red)',71 '#e77d15', 72 '#2563eb', 73 '#16a34a', 74 '#d97706', 75 '#dc2626', 76 76 '#8b5cf6' 77 77 ]; 78 var otherColor = ' var(--aidf-gray-border)';78 var otherColor = '#e5e7eb'; 79 79 80 80 /* ========================================================== … … 84 84 var currentDays = parseInt($('#aidf-crawlers-dashboard').data('days'), 10) || 30; 85 85 var conflictsData = null; // Loaded once on init. 86 var activityChart = null; // Chart.js instance for main activity chart. 87 var detailTrendChart = null; // Chart.js instance for bot detail trend chart. 86 88 87 89 /* ========================================================== … … 135 137 136 138 (function initChart() { 137 var $ grid = $('#aidf-chart-grid');138 if (!$ grid.length) {139 var $canvas = $('#aidf-activity-chart'); 140 if (!$canvas.length) { 139 141 return; 140 142 } … … 260 262 261 263 function renderChart(chartData, days) { 262 var $grid = $('#aidf-chart-grid'); 263 var $yAxis = $('#aidf-chart-y-axis'); 264 var $canvas = $('#aidf-activity-chart'); 264 265 var $legend = $('#aidf-chart-legend'); 265 266 var $tbody = $('#aidf-chart-data-tbody'); 266 267 if (!$grid.length) { 268 return; 267 var $empty = $('#aidf-chart-empty'); 268 var $wrap = $canvas.closest('.aidf-chart-canvas-wrap'); 269 270 if (!$canvas.length) { 271 return; 272 } 273 274 // Destroy previous Chart.js instance. 275 if (activityChart) { 276 activityChart.destroy(); 277 activityChart = null; 269 278 } 270 279 … … 272 281 var buckets = Object.keys(chartData); 273 282 if (!buckets.length) { 274 $ grid.html('<div class="aidf-bar-chart__empty">' + escHtml('No activity in this period') + '</div>');275 $ yAxis.empty();283 $wrap.hide(); 284 $empty.show(); 276 285 $legend.empty(); 277 286 $tbody.empty(); … … 279 288 } 280 289 290 // Show canvas, hide empty message. 291 $wrap.show(); 292 $empty.hide(); 293 281 294 // Determine all bot names across all buckets and find the top 6. 282 295 var botTotals = {}; 283 var i, j, b ucket, bots, botName;296 var i, j, bots, botName; 284 297 285 298 for (i = 0; i < buckets.length; i++) { … … 313 326 } 314 327 315 // Calculate max bar total.316 var maxTotal = 0;328 // Build labels (date buckets formatted for display). 329 var labels = []; 317 330 for (i = 0; i < buckets.length; i++) { 318 var barTotal = 0; 331 var label = buckets[i]; 332 if (days <= 30 && label.length === 10) { 333 var parts = label.split('-'); 334 label = parseInt(parts[1], 10) + '/' + parseInt(parts[2], 10); 335 } 336 labels.push(label); 337 } 338 339 // Build datasets — one per bot in legend order. 340 var datasets = []; 341 var allBots = legendBots.slice(); // Copy so we include all mapped bots. 342 343 // Also include bots beyond top 6 that are not 'Other' — they go into Other's data. 344 // Actually, the server already groups beyond top N into 'Other'. 345 // Build one dataset per legendBot. 346 for (i = 0; i < allBots.length; i++) { 347 botName = allBots[i]; 348 var data = []; 349 for (j = 0; j < buckets.length; j++) { 350 data.push(chartData[buckets[j]][botName] || 0); 351 } 352 datasets.push({ 353 label: botName, 354 data: data, 355 backgroundColor: botName === 'Other' ? otherColor : (colorMap[botName] || otherColor), 356 borderRadius: 3, 357 borderSkipped: false 358 }); 359 } 360 361 // Populate screen-reader data table. 362 var srHtml = ''; 363 for (i = 0; i < buckets.length; i++) { 319 364 bots = chartData[buckets[i]]; 320 365 for (botName in bots) { 321 if (bots.hasOwnProperty(botName) ) {322 barTotal += bots[botName];366 if (bots.hasOwnProperty(botName) && bots[botName] > 0) { 367 srHtml += '<tr><td>' + escHtml(buckets[i]) + '</td><td>' + escHtml(botName) + '</td><td>' + formatNumber(bots[botName]) + '</td></tr>'; 323 368 } 324 369 } 325 if (barTotal > maxTotal) { 326 maxTotal = barTotal; 327 } 328 } 329 if (maxTotal < 1) { 330 maxTotal = 1; 331 } 332 333 // Render Y-axis labels. 334 var yLabels = [0, Math.round(maxTotal * 0.25), Math.round(maxTotal * 0.5), Math.round(maxTotal * 0.75), maxTotal]; 335 var yHtml = ''; 336 for (i = yLabels.length - 1; i >= 0; i--) { 337 yHtml += '<span class="aidf-bar-chart__y-label">' + formatNumber(yLabels[i]) + '</span>'; 338 } 339 $yAxis.html(yHtml); 340 341 // Render bars. 342 var gridHtml = ''; 343 var srHtml = ''; 344 345 for (i = 0; i < buckets.length; i++) { 346 bucket = buckets[i]; 347 bots = chartData[bucket]; 348 349 // Format label. 350 var label = bucket; 351 if (days <= 30 && bucket.length === 10) { 352 // Format YYYY-MM-DD to shorter display. 353 var parts = bucket.split('-'); 354 label = parseInt(parts[1], 10) + '/' + parseInt(parts[2], 10); 355 } 356 357 // Build segments (from bottom to top = reverse of sorted order). 358 var segments = ''; 359 var tooltipLines = []; 360 var barTotal = 0; 361 362 // Process bots in sorted order. 363 for (j = 0; j < sortedBots.length; j++) { 364 botName = sortedBots[j]; 365 var count = bots[botName] || 0; 366 if (count <= 0) { 367 continue; 368 } 369 barTotal += count; 370 371 var color = colorMap[botName] || otherColor; 372 var pct = (count / maxTotal) * 100; 373 segments += '<div class="aidf-bar-chart__segment" style="height:' + pct.toFixed(1) + '%;background:' + color + ';" title="' + escHtml(botName) + ': ' + formatNumber(count) + '"></div>'; 374 tooltipLines.push(escHtml(botName) + ': ' + formatNumber(count)); 375 376 // Screen reader data. 377 srHtml += '<tr><td>' + escHtml(bucket) + '</td><td>' + escHtml(botName) + '</td><td>' + formatNumber(count) + '</td></tr>'; 378 } 379 380 // Build tooltip. 381 var tooltip = '<div class="aidf-bar-chart__tooltip"><strong>' + escHtml(label) + '</strong>'; 382 if (tooltipLines.length) { 383 tooltip += '<br>' + tooltipLines.join('<br>'); 384 } 385 tooltip += '<br><strong>Total: ' + formatNumber(barTotal) + '</strong></div>'; 386 387 gridHtml += '<div class="aidf-bar-chart__bar">' + 388 tooltip + segments + 389 '<span class="aidf-bar-chart__label">' + escHtml(label) + '</span>' + 390 '</div>'; 391 } 392 393 $grid.html(gridHtml); 370 } 394 371 $tbody.html(srHtml); 395 372 396 // Render legend.373 // Render custom legend. 397 374 var legendHtml = ''; 398 375 for (i = 0; i < legendBots.length; i++) { … … 404 381 } 405 382 $legend.html(legendHtml); 383 384 // Create the Chart.js stacked bar chart. 385 activityChart = new Chart($canvas[0].getContext('2d'), { 386 type: 'bar', 387 data: { 388 labels: labels, 389 datasets: datasets 390 }, 391 options: { 392 responsive: true, 393 maintainAspectRatio: false, 394 plugins: { 395 legend: { display: false }, 396 tooltip: { 397 mode: 'index', 398 intersect: false, 399 callbacks: { 400 label: function(ctx) { 401 if (ctx.parsed.y === 0) { 402 return null; 403 } 404 return ctx.dataset.label + ': ' + ctx.parsed.y.toLocaleString(); 405 } 406 } 407 } 408 }, 409 scales: { 410 x: { 411 stacked: true, 412 grid: { display: false }, 413 ticks: { 414 maxRotation: 0, 415 autoSkip: true, 416 maxTicksLimit: 10, 417 font: { size: 11 } 418 } 419 }, 420 y: { 421 stacked: true, 422 beginAtZero: true, 423 grid: { color: 'rgba(0,0,0,0.04)' }, 424 ticks: { 425 precision: 0, 426 font: { size: 11 } 427 } 428 } 429 }, 430 interaction: { 431 mode: 'index', 432 intersect: false 433 } 434 } 435 }); 406 436 } 407 437 … … 688 718 } else { 689 719 showNotice('error', response.data || 'Save failed.'); 720 } 721 }).fail(function() { 722 $btn.prop('disabled', false).text(original); 723 showNotice('error', 'Network error. Please try again.'); 724 }); 725 }); 726 727 /* ========================================================== 728 Rebuild Summary 729 ========================================================== */ 730 731 $('#aidf-rebuild-summary').on('click', function() { 732 var $btn = $(this); 733 var original = $btn.text(); 734 735 $btn.prop('disabled', true).text('Rebuilding\u2026'); 736 737 $.post(aidf.ajaxUrl, { 738 action: 'aidf_crawler_rebuild_summary', 739 nonce: aidf.nonce 740 }, function(response) { 741 $btn.prop('disabled', false).text(original); 742 if (response.success) { 743 showNotice('success', response.data.message); 744 // Reload the dashboard to reflect rebuilt data. 745 if ($('#aidf-summary-cards').length) { 746 loadDashboard(currentDays); 747 } 748 } else { 749 showNotice('error', response.data || 'Failed to rebuild summary.'); 690 750 } 691 751 }).fail(function() { … … 824 884 $(document).on('click', '#aidf-back-to-overview', function(e) { 825 885 e.preventDefault(); 826 // Clear the bot detail panels before hiding, so stale data isn't shown. 827 $('#aidf-bot-detail-chart, #aidf-bot-detail-status, #aidf-bot-detail-pages, #aidf-bot-detail-files').empty(); 886 // Destroy the detail trend chart to prevent stale data. 887 if (detailTrendChart) { 888 detailTrendChart.destroy(); 889 detailTrendChart = null; 890 } 891 // Clear the other detail panels before hiding. 892 $('#aidf-bot-detail-status, #aidf-bot-detail-pages, #aidf-bot-detail-files').empty(); 828 893 hideBotDetail(); 829 894 }); … … 858 923 // Show loading state inside each panel. 859 924 var loadingHtml = '<div class="aidf-detail-loading"><span class="spinner is-active" style="float:none;"></span></div>'; 860 $('#aidf-bot-detail-chart, #aidf-bot-detail-status, #aidf-bot-detail-pages, #aidf-bot-detail-files').html(loadingHtml); 925 // Trend chart panel has a persistent canvas; destroy chart and hide canvas during load. 926 if (detailTrendChart) { 927 detailTrendChart.destroy(); 928 detailTrendChart = null; 929 } 930 $('#aidf-detail-trend-chart').closest('.aidf-chart-canvas-wrap').hide(); 931 $('#aidf-detail-trend-empty').hide(); 932 $('#aidf-bot-detail-status, #aidf-bot-detail-pages, #aidf-bot-detail-files').html(loadingHtml); 861 933 $('#aidf-bot-detail-first-seen, #aidf-bot-detail-last-seen').text(''); 862 934 … … 874 946 if (!response.success) { 875 947 var errHtml = '<p class="aidf-text-muted">' + escHtml(response.data || 'Failed to load.') + '</p>'; 876 $('#aidf-bot-detail-chart, #aidf-bot-detail-status, #aidf-bot-detail-pages, #aidf-bot-detail-files').html(errHtml); 948 $('#aidf-detail-trend-empty').text(response.data || 'Failed to load.').show(); 949 $('#aidf-bot-detail-status, #aidf-bot-detail-pages, #aidf-bot-detail-files').html(errHtml); 877 950 return; 878 951 } … … 898 971 }).fail(function() { 899 972 var errHtml = '<p class="aidf-text-muted">Network error. Please try again.</p>'; 900 $('#aidf- bot-detail-chart, #aidf-bot-detail-status, #aidf-bot-detail-pages, #aidf-bot-detail-files').html(errHtml);901 });902 }903 904 /** 905 * Render a single-colour bar chart for daily access trend.906 * Bars are proportional to the day with the highest count.973 $('#aidf-detail-trend-empty').text('Network error. Please try again.').show(); 974 $('#aidf-bot-detail-status, #aidf-bot-detail-pages, #aidf-bot-detail-files').html(errHtml); 975 }); 976 } 977 978 /** 979 * Render a single-colour bar chart for daily access trend using Chart.js. 907 980 */ 908 981 function renderDetailTrendChart(dailyTrend) { 909 var $container = $('#aidf-bot-detail-chart'); 982 var $canvas = $('#aidf-detail-trend-chart'); 983 var $empty = $('#aidf-detail-trend-empty'); 984 var $wrap = $canvas.closest('.aidf-chart-canvas-wrap'); 985 986 // Destroy previous Chart.js instance. 987 if (detailTrendChart) { 988 detailTrendChart.destroy(); 989 detailTrendChart = null; 990 } 910 991 911 992 var dates = Object.keys(dailyTrend).sort(); 912 993 if (!dates.length) { 913 $container.html('<p class="aidf-text-muted">No access data in this period.</p>'); 914 return; 915 } 916 917 // Find max. 918 var maxVal = 0; 919 var i; 920 for (i = 0; i < dates.length; i++) { 921 if (dailyTrend[dates[i]] > maxVal) { 922 maxVal = dailyTrend[dates[i]]; 923 } 924 } 925 if (maxVal < 1) { 926 maxVal = 1; 927 } 928 929 var html = '<div class="aidf-detail-trend">'; 930 for (i = 0; i < dates.length; i++) { 931 var date = dates[i]; 932 var count = dailyTrend[date] || 0; 933 var pct = Math.max(2, Math.round((count / maxVal) * 100)); 934 935 // Format date label: strip year, show MM/DD. 936 var parts = date.split('-'); 937 var label = parts.length === 3 ? parseInt(parts[1], 10) + '/' + parseInt(parts[2], 10) : date; 938 939 html += '<div class="aidf-detail-trend__bar" title="' + escHtml(date) + ': ' + formatNumber(count) + ' accesses">' + 940 '<div class="aidf-detail-trend__fill" style="height:' + pct + '%;"></div>' + 941 '<span class="aidf-detail-trend__label">' + escHtml(label) + '</span>' + 942 '</div>'; 943 } 944 html += '</div>'; 945 946 $container.html(html); 994 $wrap.hide(); 995 $empty.show(); 996 return; 997 } 998 999 $wrap.show(); 1000 $empty.hide(); 1001 1002 // Build labels and data arrays. 1003 var labels = []; 1004 var data = []; 1005 for (var i = 0; i < dates.length; i++) { 1006 var parts = dates[i].split('-'); 1007 labels.push(parts.length === 3 ? parseInt(parts[1], 10) + '/' + parseInt(parts[2], 10) : dates[i]); 1008 data.push(dailyTrend[dates[i]] || 0); 1009 } 1010 1011 detailTrendChart = new Chart($canvas[0].getContext('2d'), { 1012 type: 'bar', 1013 data: { 1014 labels: labels, 1015 datasets: [{ 1016 label: 'Accesses', 1017 data: data, 1018 backgroundColor: '#e77d15', 1019 borderRadius: 3, 1020 borderSkipped: false 1021 }] 1022 }, 1023 options: { 1024 responsive: true, 1025 maintainAspectRatio: false, 1026 plugins: { 1027 legend: { display: false }, 1028 tooltip: { 1029 callbacks: { 1030 label: function(ctx) { 1031 return ctx.parsed.y.toLocaleString() + ' accesses'; 1032 } 1033 } 1034 } 1035 }, 1036 scales: { 1037 x: { 1038 grid: { display: false }, 1039 ticks: { 1040 maxRotation: 0, 1041 autoSkip: true, 1042 maxTicksLimit: 10, 1043 font: { size: 11 } 1044 } 1045 }, 1046 y: { 1047 beginAtZero: true, 1048 grid: { color: 'rgba(0,0,0,0.04)' }, 1049 ticks: { 1050 precision: 0, 1051 font: { size: 11 } 1052 } 1053 } 1054 } 1055 } 1056 }); 947 1057 } 948 1058 -
ai-discovery-files/trunk/admin/views/partials/crawler-bot-detail.php
r3488558 r3488602 40 40 <h4 class="aidf-panel-title"><?php esc_html_e( 'Access Trend', 'ai-discovery-files' ); ?></h4> 41 41 </div> 42 <div class="aidf-panel-body" id="aidf-bot-detail-chart"></div> 42 <div class="aidf-panel-body" id="aidf-bot-detail-chart"> 43 <div class="aidf-chart-canvas-wrap aidf-chart-canvas-wrap--detail"> 44 <canvas id="aidf-detail-trend-chart"></canvas> 45 </div> 46 <p class="aidf-chart-empty" id="aidf-detail-trend-empty" style="display:none;"><?php esc_html_e( 'No access data in this period.', 'ai-discovery-files' ); ?></p> 47 </div> 43 48 </div> 44 49 -
ai-discovery-files/trunk/admin/views/tab-crawlers.php
r3488558 r3488602 153 153 <div class="aidf-chart-legend" id="aidf-chart-legend"></div> 154 154 </div> 155 <div class="aidf- bar-chart" id="aidf-activity-chart">156 < div class="aidf-bar-chart__y-axis" id="aidf-chart-y-axis"></div>157 <div class="aidf-bar-chart__grid" id="aidf-chart-grid"></div>158 < /div>155 <div class="aidf-chart-canvas-wrap"> 156 <canvas id="aidf-activity-chart"></canvas> 157 </div> 158 <p class="aidf-chart-empty" id="aidf-chart-empty" style="display:none;"><?php esc_html_e( 'No activity in this period.', 'ai-discovery-files' ); ?></p> 159 159 <!-- Accessible data table (screen-reader only) --> 160 160 <table class="aidf-sr-only" id="aidf-chart-data-table"> … … 364 364 <?php esc_html_e( 'Save Settings', 'ai-discovery-files' ); ?> 365 365 </button> 366 <button type="button" class="aidf-btn aidf-btn--secondary" id="aidf-rebuild-summary"> 367 <?php esc_html_e( 'Rebuild Summary', 'ai-discovery-files' ); ?> 368 </button> 366 369 <button type="button" class="aidf-btn aidf-btn--danger" id="aidf-clear-log"> 367 370 <?php esc_html_e( 'Clear Log Data', 'ai-discovery-files' ); ?> 368 371 </button> 369 372 </div> 373 <p class="aidf-setting-row__help" style="margin-top: var(--aidf-sp-2);"> 374 <?php esc_html_e( 'The dashboard uses a pre-built summary for fast loading. It rebuilds automatically each day. Use "Rebuild Summary" if you have just enabled logging and want to see data immediately, or if the dashboard appears out of date.', 'ai-discovery-files' ); ?> 375 </p> 370 376 371 377 </div> -
ai-discovery-files/trunk/ai-discovery-files.php
r3488558 r3488602 4 4 * Plugin URI: https://www.ai-visibility.org.uk/wordpress-plugin/ai-discovery-files/ 5 5 * Description: The only WordPress plugin that generates all 10 AI Discovery Files and tracks which AI bots visit your site. Built by 365i. 6 * Version: 1.3. 16 * Version: 1.3.4 7 7 * Requires at least: 6.2 8 8 * Requires PHP: 8.0 … … 24 24 * Plugin constants. 25 25 */ 26 define( 'AIDF_VERSION', '1.3. 1' );26 define( 'AIDF_VERSION', '1.3.4' ); 27 27 define( 'AIDF_PLUGIN_FILE', __FILE__ ); 28 28 define( 'AIDF_PLUGIN_DIR', plugin_dir_path( __FILE__ ) ); -
ai-discovery-files/trunk/includes/class-crawler-analytics.php
r3488445 r3488602 437 437 : $count; 438 438 } 439 } 440 441 // Pad with empty buckets so the chart always shows the full date range. 442 // This ensures bars have proper width context even with sparse data. 443 if ( ! $use_weekly ) { 444 $padded = array(); 445 $date = new \DateTime( 'now', new \DateTimeZone( 'UTC' ) ); 446 $date->modify( "-{$days} days" ); 447 448 for ( $d = 0; $d < $days; $d++ ) { 449 $date->modify( '+1 day' ); 450 $key = $date->format( 'Y-m-d' ); 451 $padded[ $key ] = isset( $chart[ $key ] ) ? $chart[ $key ] : new \stdClass(); 452 } 453 $chart = $padded; 439 454 } 440 455 -
ai-discovery-files/trunk/readme.txt
r3488558 r3488602 5 5 Tested up to: 6.9 6 6 Requires PHP: 8.0 7 Stable tag: 1.3. 17 Stable tag: 1.3.4 8 8 License: GPLv2 or later 9 9 License URI: https://www.gnu.org/licenses/gpl-2.0.html … … 179 179 == Changelog == 180 180 181 = 1.3.4 = 182 * New: Replace CSS charts with Chart.js for polished, responsive visualisations 183 * New: Stacked bar chart with animated transitions, index tooltips, and proper scaling 184 * New: Bot detail trend chart rendered with Chart.js 185 186 = 1.3.3 = 187 * New: Rebuild Summary button in Crawler Logging Settings for immediate dashboard updates 188 * Fix: Chart now shows full date range with proper bar sizing even with sparse data 189 181 190 = 1.3.1 = 182 191 * Fix: Move bot detection into discovery file server for reliable CDN/cache compatibility
Note: See TracChangeset
for help on using the changeset viewer.