Statistics updates

This commit is contained in:
Sascha Leib
2025-10-31 10:18:41 +01:00
parent 68ccd2abf9
commit eba0eb7738
4 changed files with 217 additions and 59 deletions

View File

@@ -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 {

232
admin.js
View File

@@ -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,18 +664,22 @@ 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);
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 == 'Y') {
data.captcha.bots_blocked += 1;
} else if (captchaStr == 'YN') {
if (captchaStr.indexOf('YN') > -1) {
data.captcha.bots_passed += 1;
} else if (captchaStr == 'W') {
} 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);
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 == 'Y') {
data.captcha.sus_blocked += 1;
} else if (captchaStr == 'YN') {
if (captchaStr.indexOf('YN') > -1) {
data.captcha.sus_passed += 1;
} else if (captchaStr == 'W') {
} 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;
this.groups.humans.push(v);
if (v._seenBy.indexOf(BM_LOGTYPE.SERVER) > -1) { // not for ghost items!
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') {
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);
}
}
}
}
},

View File

@@ -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 {
<header id="botmon__today__title">Loading&nbsp;&hellip;</header>
<div id="botmon__today__content">
<details id="botmon__today__overview" open>
<summary>Overview</summary>
<summary>Bots overview</summary>
<div class="botmon_bots_grid" data-geoip="' . $geoIPconf . '">
<dl id="botmon__today__botsvshumans"></dl>
<dl id="botmon__botslist"></dl>
@@ -79,8 +80,18 @@ class admin_plugin_botmon extends AdminPlugin {
<dl id="botmon__today__wm_pages"></dl>
<dl id="botmon__today__wm_referers"></dl>
</div>
</details>
<details id="botmon__today__visitors">
</details>' . NL;
if ($useCaptchaConf) {
echo ' <details id="botmon__today__captcha">
<summary>Captcha statistics</summary>
<div class="botmon_captcha_grid">
<dl id="botmon__today__cp_humans"></dl>
<dl id="botmon__today__cp_sus"></dl>
<dl id="botmon__today__cp_bots"></dl>
</div>
</details>' . NL;
}
echo ' <details id="botmon__today__visitors">
<summary>Visitor logs</summary>
<div id="botmon__today__visitorlists"></div>
</details>

View File

@@ -3,7 +3,7 @@
"rules": [
{"func": "fromKnownBotIP",
"id": "botIpRange", "desc": "Common Bot IP range",
"bot": 50
"bot": 40
},
{"func": "matchesClient", "params": ["aol","msie","ffold","chromeold","oldedge","operaold"],
"id": "oldClient", "desc": "Obsolete browser version",
@@ -75,7 +75,7 @@
},
{"func": "whitelistedByCaptcha", "params": [],
"id": "whitelistedByCaptcha", "desc": "Visitor uses a whitelisted IP address",
"bot": -30
"bot": -20
}
]
}