diff --git a/admin.css b/admin.css
index c50d2ee..37e1952 100644
--- a/admin.css
+++ b/admin.css
@@ -426,7 +426,7 @@
}
/* grid layout for the overview: */
- .botmon_bots_grid, .botmon_webmetrics_grid, .botmon_traffic_grid {
+ .botmon_bots_grid, .botmon_webmetrics_grid, .botmon_traffic_grid, .botmon_captcha_grid {
& {
display: grid;
grid-gap: 0 .33em;
@@ -468,6 +468,9 @@
.botmon_traffic_grid {
grid-template-columns: 2fr 1fr;
}
+ .botmon_captcha_grid {
+ grid-template-columns: 1fr 1fr 1fr;
+ }
/* The tabs bar */
#botmon__tabs ul.tabs li {
diff --git a/admin.js b/admin.js
index 608225c..05a87a1 100644
--- a/admin.js
+++ b/admin.js
@@ -518,7 +518,7 @@ BotMon.live = {
let visitor = model.findVisitor(dat, type);
if (!visitor) {
console.info(`No visitor with ID “${dat.id}” found, registering as a new one.`);
- visitor = model.registerVisit(dat, type);
+ visitor = model.registerVisit(dat, type, true);
}
if (visitor) {
// update visitor:
@@ -623,6 +623,7 @@ BotMon.live = {
bots_whitelisted: 0,
humans_blocked: 0,
humans_passed: 0,
+ humans_whitelisted: 0,
sus_blocked: 0,
sus_passed: 0,
sus_whitelisted: 0
@@ -651,7 +652,7 @@ BotMon.live = {
// loop over all visitors:
model._visitors.forEach( (v) => {
- const captchaStr = v._captcha._str();
+ const captchaStr = v._captcha._str().replaceAll(/[^YNW]/g, '');
// count total visits and page views:
data.visits.total += 1;
@@ -663,17 +664,21 @@ BotMon.live = {
if (v._type == BM_USERTYPE.KNOWN_BOT) { // known bots
- data.visits.bots += 1;
- data.views.bots += v._viewCount;
this.groups.knownBots.push(v);
- // captcha counter
- if (captchaStr == 'Y') {
- data.captcha.bots_blocked += 1;
- } else if (captchaStr == 'YN') {
- data.captcha.bots_passed += 1;
- } else if (captchaStr == 'W') {
- data.captcha.bots_whitelisted += 1;
+ if (v._seenBy.indexOf(BM_LOGTYPE.SERVER) > -1) { // not for ghost items!
+ data.visits.bots += 1;
+ data.views.bots += v._viewCount;
+
+ // captcha counter
+ if (captchaStr.indexOf('YN') > -1) {
+ data.captcha.bots_passed += 1;
+ } else if (captchaStr.indexOf('Y') > -1) {
+ data.captcha.bots_blocked += 1;
+ }
+ if (captchaStr.indexOf('W') > -1) {
+ data.captcha.bots_whitelisted += 1;
+ }
}
} else if (v._type == BM_USERTYPE.KNOWN_USER) { // known users */
@@ -692,32 +697,42 @@ BotMon.live = {
if (e.isBot) { // likely bots
v._type = BM_USERTYPE.LIKELY_BOT;
- data.visits.suspected += 1;
- data.views.suspected += v._viewCount;
this.groups.suspectedBots.push(v);
- // captcha counter
- if (captchaStr == 'Y') {
- data.captcha.sus_blocked += 1;
- } else if (captchaStr == 'YN') {
- data.captcha.sus_passed += 1;
- } else if (captchaStr == 'W') {
- data.captcha.sus_whitelisted += 1;
+ if (v._seenBy.indexOf(BM_LOGTYPE.SERVER) > -1) { // not for ghost items!
+
+ data.visits.suspected += 1;
+ data.views.suspected += v._viewCount;
+
+ // captcha counter
+ if (captchaStr.indexOf('YN') > -1) {
+ data.captcha.sus_passed += 1;
+ } else if (captchaStr.indexOf('Y') > -1) {
+ data.captcha.sus_blocked += 1;
+ }
+ if (captchaStr.indexOf('W') > -1) {
+ data.captcha.sus_whitelisted += 1;
+ }
}
} else { // probably humans
v._type = BM_USERTYPE.PROBABLY_HUMAN;
- data.visits.humans += 1;
- data.views.humans += v._viewCount;
-
this.groups.humans.push(v);
- // captcha counter
- if (captchaStr == 'Y') {
- data.captcha.humans_blocked += 1;
- } else if (captchaStr == 'YN') {
- data.captcha.humans_passed += 1;
+ if (v._seenBy.indexOf(BM_LOGTYPE.SERVER) > -1) { // not for ghost items!
+ data.visits.humans += 1;
+ data.views.humans += v._viewCount;
+
+ // captcha counter
+ if (captchaStr.indexOf('YN') > -1) {
+ data.captcha.humans_passed += 1;
+ } else if (captchaStr.indexOf('Y') > -1) {
+ data.captcha.humans_blocked += 1;
+ }
+ if (captchaStr.indexOf('W') > -1) {
+ data.captcha.humans_whitelisted += 1;
+ }
}
}
}
@@ -2011,34 +2026,34 @@ BotMon.live = {
if (botsVsHumans) {
botsVsHumans.appendChild(makeElement('dt', {}, "Bot statistics"));
- for (let i = 0; i <= ( useCaptcha ? 5 : 3 ); i++) {
+ for (let i = 0; i <= 6; i++) {
const dd = makeElement('dd');
let title = '';
let value = '';
switch(i) {
case 0:
- title = "Total (loads / views / visits):";
- value = (data.loads.total || kNoData) + kSeparator + (data.views.total || kNoData) + kSeparator + (data.visits.total || kNoData);
+ title = "Known bots visits:";
+ value = data.visits.bots || kNoData;
break;
case 1:
- title = "Known bots (views / visits):";
- value = (data.views.bots || kNoData) + kSeparator + (data.visits.bots || kNoData);
+ title = "Suspected bots visits:";
+ value = data.visits.suspected || kNoData;
break;
case 2:
- title = "Suspected bots (views / visits):";
- value = (data.visits.suspected || kNoData) + kSeparator + (data.views.suspected || kNoData)
- break;
- case 3:
- title = "Bots-humans ratio (views / visits):";
- value = BotMon.t._getRatio(data.views.suspected + data.views.bots, data.views.users + data.views.humans, 100) + kSeparator + BotMon.t._getRatio(data.visits.suspected + data.visits.bots, data.visits.users + data.visits.humans, 100);
+ title = "Bots-humans ratio visits:";
+ value = BotMon.t._getRatio(data.visits.suspected + data.visits.bots, data.visits.users + data.visits.humans, 100);
break;
case 4:
- title = "Known bots blocked / passed / whitelisted:";
- value = data.captcha.bots_blocked + kSeparator + data.captcha.bots_passed + kSeparator + data.captcha.bots_whitelisted;
+ title = "Known bots views:";
+ value = data.views.bots || kNoData;
break;
case 5:
- title = "Suspected bots blocked / passed / whitelisted:";
- value = data.captcha.sus_blocked + kSeparator + data.captcha.sus_passed + kSeparator + data.captcha.sus_whitelisted;
+ title = "Suspected bots views:";
+ value = data.views.suspected || kNoData;
+ break;
+ case 6:
+ title = "Bots-humans ratio views:";
+ value = BotMon.t._getRatio(data.views.suspected + data.views.bots, data.views.users + data.views.humans, 100);
break;
default:
console.warn(`Unknown list type ${i}.`);
@@ -2095,32 +2110,36 @@ BotMon.live = {
const wmoverview = document.getElementById('botmon__today__wm_overview');
if (wmoverview) {
- const humanVisits = data.views.total;
+ const humanVisits = data.visits.users + data.visits.humans;
const bounceRate = Math.round(100 * (BotMon.live.data.analytics.getBounceCount('users') + BotMon.live.data.analytics.getBounceCount('humans')) / humanVisits);
wmoverview.appendChild(makeElement('dt', {}, "Humans’ metrics"));
- for (let i = 0; i <= 4; i++) {
+ for (let i = 0; i <= 5; i++) {
const dd = makeElement('dd');
let title = '';
let value = '';
switch(i) {
case 0:
- title = "Registered users (views / visits):";
- value = (data.views.users || kNoData) + kSeparator + (data.visits.users || kNoData);
+ title = "Registered users visits:";
+ value = data.visits.users || kNoData;
break;
case 1:
- title = "Probably humans (views / visits):";
- value = (data.views.humans || kNoData) + kSeparator + (data.visits.humans || kNoData);
+ title = "Registered users views:";
+ value = data.views.users || kNoData;
break;
case 2:
- title = "Total human page views:";
- value = (data.views.users + data.views.humans) || kNoData;
+ title = "Probably humans visits:";
+ value = data.visits.humans || kNoData;
break;
case 3:
- title = "Total human visits:";
- value = data.views.total || kNoData;
+ title = "Probably humans views:";
+ value = data.views.humans || kNoData;
break;
case 4:
+ title = "Total human visits / views";
+ value = (data.visits.users + data.visits.humans || kNoData) + kSeparator + ((data.views.users + data.views.humans) || kNoData);
+ break;
+ case 5:
title = "Humans’ bounce rate:";
value = bounceRate + '%';
break;
@@ -2230,6 +2249,131 @@ BotMon.live = {
});
}
}
+
+ // Update Captcha statistics:
+ const captchaStatsBlock = document.getElementById('botmon__today__captcha');
+ if (captchaStatsBlock) {
+
+ // first column:
+ const captchaHumans = document.getElementById('botmon__today__cp_humans');
+ if (captchaHumans) {
+ captchaHumans.appendChild(makeElement('dt', {}, "Probably humans:"));
+
+ for (let i = 0; i <= 4; i++) {
+ const dd = makeElement('dd');
+ let title = '';
+ let value = '';
+ switch(i) {
+ case 0:
+ title = "Solved:";
+ value = data.captcha.humans_passed;
+ break;
+ case 1:
+ title = "Blocked:";
+ value = data.captcha.humans_blocked;
+ break;
+ case 2:
+ title = "Whitelisted:";
+ value = data.captcha.humans_whitelisted;
+ break;
+ case 3:
+ title = "Total visits:";
+ value = data.visits.humans;
+ break;
+ case 4:
+ title = "Pct. blocked:";
+ value = (data.captcha.humans_blocked / data.visits.humans * 100).toFixed(0) + '%';
+ break;
+ default:
+ console.warn(`Unknown list type ${i}.`);
+ }
+ dd.appendChild(makeElement('span', {}, title));
+ dd.appendChild(makeElement('strong', {}, value || kNoData));
+ captchaHumans.appendChild(dd);
+ }
+
+ }
+
+ // second column:
+ const captchaSus = document.getElementById('botmon__today__cp_sus');
+ if (captchaSus) {
+ captchaSus.appendChild(makeElement('dt', {}, "Suspected bots:"));
+
+ for (let i = 0; i <= 4; i++) {
+ const dd = makeElement('dd');
+ let title = '';
+ let value = '';
+ switch(i) {
+ case 0:
+ title = "Solved:";
+ value = data.captcha.sus_passed;
+ break;
+ case 1:
+ title = "Blocked:";
+ value = data.captcha.sus_blocked;
+ break;
+ case 2:
+ title = "Whitelisted:";
+ value = data.captcha.sus_whitelisted;
+ break;
+ case 3:
+ title = "Total visits:";
+ value = data.visits.suspected;
+ break;
+ case 4:
+ title = "Pct. blocked:";
+ value = (data.captcha.sus_blocked / data.visits.suspected * 100).toFixed(0) + '%';
+ break;
+ default:
+ console.warn(`Unknown list type ${i}.`);
+ }
+ dd.appendChild(makeElement('span', {}, title));
+ dd.appendChild(makeElement('strong', {}, value || kNoData));
+ captchaSus.appendChild(dd);
+ }
+
+ }
+
+ // Third column:
+ const captchaBots = document.getElementById('botmon__today__cp_bots');
+ if (captchaBots) {
+ captchaBots.appendChild(makeElement('dt', {}, "Known bots:"));
+
+ for (let i = 0; i <= 4; i++) {
+ const dd = makeElement('dd');
+ let title = '';
+ let value = '';
+ switch(i) {
+ case 0:
+ title = "Solved:";
+ value = data.captcha.bots_passed;
+ break;
+ case 1:
+ title = "Blocked:";
+ value = data.captcha.bots_blocked;
+ break;
+ case 2:
+ title = "Whitelisted:";
+ value = data.captcha.bots_whitelisted;
+ break;
+ case 3:
+ title = "Total visits:";
+ value = data.visits.bots;
+ break;
+ case 4:
+ title = "Pct. blocked:";
+ value = (data.captcha.bots_blocked / data.visits.bots * 100).toFixed(0) + '%';
+ break;
+ default:
+ console.warn(`Unknown list type ${i}.`);
+ }
+ dd.appendChild(makeElement('span', {}, title));
+ dd.appendChild(makeElement('strong', {}, value || kNoData));
+ captchaBots.appendChild(dd);
+ }
+
+ }
+ }
}
},
diff --git a/admin.php b/admin.php
index e9eda49..0821f49 100644
--- a/admin.php
+++ b/admin.php
@@ -34,6 +34,7 @@ class admin_plugin_botmon extends AdminPlugin {
// display GeoIP data?
$geoIPconf = $this->getConf('geoiplib');
+ $useCaptchaConf = ($this->getConf('useCaptcha') !== 'disabled');
$hasOldLogFiles = $this->hasOldLogFiles();
@@ -56,7 +57,7 @@ class admin_plugin_botmon extends AdminPlugin {