GUI Improvements

Better display of pages per visit.
This commit is contained in:
Sascha Leib
2025-12-06 13:57:58 +01:00
parent cb91697912
commit 53287868f7
5 changed files with 132 additions and 82 deletions

View File

@@ -701,20 +701,9 @@
&:nth-child(odd) { &:nth-child(odd) {
background-color: #DFDFDF; background-color: #DFDFDF;
} }
&.detailled { /*&.detailled {
outline: red dotted 1pt; outline: red dotted 1pt;
} }*/
div.row {
display: flex;
flex-direction: row;
justify-content: space-between;
align-items: baseline;
white-space: nowrap;
line-height: 1.35em;
}
span {
display: inline-block;
}
} }
} }
a[hreflang] { a[hreflang] {
@@ -733,6 +722,23 @@
padding: 0 1pt; padding: 0 1pt;
margin-left: .2em; margin-left: .2em;
} }
div.row {
& {
display: flex;
flex-direction: row;
justify-content: space-between;
align-items: baseline;
white-space: nowrap;
line-height: 1.35em;
}
& > div {
display: inline-flex;
column-gap: .4em;
}
}
span {
display: inline-block;
}
span.first-seen { span.first-seen {
min-width: 4.2em; min-width: 4.2em;
text-align: right; text-align: right;
@@ -741,20 +747,26 @@
font-size: smaller; font-size: smaller;
} }
span.bounce { span.bounce {
width: 1.25em; height: 1.25em; width: 1em; height: 1em;
overflow: hidden; overflow: hidden;
} }
span.bounce::before { span.bounce::before {
display: inline-block; display: inline-block;
content: ''; content: '';
width: 1.25em; height: 1em; width: 1em; height: 1em;
background: transparent url('img/bounce.svg') center no-repeat; background: transparent url('img/bounce.svg') center no-repeat;
background-size: .95em; background-size: .95em;
} }
span.referer { span.referer, span.views, span.ip-address, span.user-agent {
font-size: smaller; font-size: smaller;
}
span.referer, span.ip-address, span.user-agent {
margin-left: .67rem; margin-left: .67rem;
} }
span.user-agent {
line-break: anywhere;
text-wrap-mode: wrap;
}
} }
} }
@@ -779,7 +791,7 @@
} }
/* pages seen */ /* pages seen */
span.pageseen, span.pageviews { span.pageseen/*, span.pageviews */{
border: #999 solid 1px; border: #999 solid 1px;
padding: 0 2px; padding: 0 2px;
font-size: smaller; font-size: smaller;
@@ -794,13 +806,13 @@
background: transparent url('img/page.svg') center no-repeat; background: transparent url('img/page.svg') center no-repeat;
background-size: 1.25em; background-size: 1.25em;
} }
span.pageviews::before { /*span.pageviews::before {
content : ''; content : '';
display: inline-block; display: inline-block;
width: 1.25em; height: 1.25em; width: 1.25em; height: 1.25em;
background: transparent url('img/views.svg') center no-repeat; background: transparent url('img/views.svg') center no-repeat;
background-size: 1.25em; background-size: 1.25em;
} }*/
} }
/* item footer */ /* item footer */

145
admin.js
View File

@@ -105,7 +105,7 @@ const BotMon = {
r.textContent = text.toString(); r.textContent = text.toString();
} }
} catch(e) { } catch(e) {
console.error(e); console.error('Botmon:', e);
} }
return r; return r;
}, },
@@ -399,7 +399,7 @@ BotMon.live = {
nv._country = countryName.of(nv.geo.substring(0,2)) ?? nv.geo; nv._country = countryName.of(nv.geo.substring(0,2)) ?? nv.geo;
} }
} catch (err) { } catch (err) {
console.error(err); console.error('Botmon:', err);
nv._country = 'Error'; nv._country = 'Error';
} }
@@ -445,6 +445,8 @@ BotMon.live = {
prereg = model._makePageView(nv, type); prereg = model._makePageView(nv, type);
visitor._pageViews.push(prereg); visitor._pageViews.push(prereg);
} }
prereg._loadCount += 1;
prereg._viewCount += (nv.captcha == 'Y' ? 0 : 1);
// update last seen date // update last seen date
prereg._lastSeen = nv.ts; prereg._lastSeen = nv.ts;
@@ -517,7 +519,7 @@ BotMon.live = {
// find the visit info: // find the visit info:
let visitor = model.findVisitor(dat, type); let visitor = model.findVisitor(dat, type);
if (!visitor) { if (!visitor) {
console.info(`No visitor with ID “${dat.id}” found, registering as a new one.`); console.info(`Botmon: No visitor with ID “${dat.id}” found, registering as a new one.`);
visitor = model.registerVisit(dat, type, true); visitor = model.registerVisit(dat, type, true);
} }
if (visitor) { if (visitor) {
@@ -528,7 +530,7 @@ BotMon.live = {
// get the page view info: // get the page view info:
let pv = model._getPageView(visitor, dat); let pv = model._getPageView(visitor, dat);
if (!pv) { if (!pv) {
console.info(`No page view for visit ID “${dat.id}”, page “${dat.pg}”, registering a new one.`); console.info(`Botmon: No page view for visit ID “${dat.id}”, page “${dat.pg}”, registering a new one.`);
pv = model._makePageView(dat, type); pv = model._makePageView(dat, type);
visitor._pageViews.push(pv); visitor._pageViews.push(pv);
} }
@@ -543,15 +545,14 @@ BotMon.live = {
// helper function to create a new "page view" item: // helper function to create a new "page view" item:
_makePageView: function(data, type) { _makePageView: function(data, type) {
// console.info('_makePageView', data); //console.info('_makePageView', data);
// try to parse the referrer: // try to parse the referrer:
let rUrl = null; let rUrl = null;
try { try {
rUrl = ( data.ref && data.ref !== '' ? new URL(data.ref) : null ); rUrl = ( data.ref && data.ref !== '' ? new URL(data.ref) : null );
} catch (e) { } catch (e) {
console.warn(`Invalid referer: “${data.ref}”.`); console.info(`Botmon: Ignoring invalid referer: “${data.ref}”.`);
console.info(data);
} }
return { return {
@@ -564,9 +565,11 @@ BotMon.live = {
_lastSeen: data.ts, _lastSeen: data.ts,
_seenBy: [type], _seenBy: [type],
_jsClient: ( type !== BM_LOGTYPE.SERVER), _jsClient: ( type !== BM_LOGTYPE.SERVER),
_agent: data.agent,
_viewCount: 0, _viewCount: 0,
_loadCount: 0, _loadCount: 0,
_tickCount: 0 _tickCount: 0,
_captcha: data.captcha
}; };
}, },
@@ -1025,7 +1028,7 @@ BotMon.live = {
arr = me._countries.bot; arr = me._countries.bot;
break; break;
default: default:
console.warn(`Unknown user type ${type} in function addToCountries.`); console.warn(`Botmon: Unknown user type ${type} in function addToCountries.`);
} }
if (arr) { if (arr) {
@@ -1065,7 +1068,7 @@ BotMon.live = {
arr = me._countries.bot; arr = me._countries.bot;
break; break;
default: default:
console.warn(`Unknown user type ${type} in function getCountryList.`); console.warn(`Botmon: Unknown user type ${type} in function getCountryList.`);
return; return;
} }
@@ -1652,7 +1655,7 @@ BotMon.live = {
const pId = ( visitor._platform ? visitor._platform.id : ''); const pId = ( visitor._platform ? visitor._platform.id : '');
if (visitor._platform.id == null) console.log(visitor._platform); //if (visitor._platform.id == null) console.log(visitor._platform);
return platforms.includes(pId); return platforms.includes(pId);
}, },
@@ -1766,8 +1769,6 @@ BotMon.live = {
totalTime += (pvArr[i] - pvArr[i-1]); totalTime += (pvArr[i] - pvArr[i-1]);
} }
//console.log(' ', totalTime , Math.round(totalTime / (pvArr.length * 1000)), (( totalTime / pvArr.length ) <= maxTime * 1000), visitor.ip);
return (( totalTime / pvArr.length ) <= maxTime * 1000); return (( totalTime / pvArr.length ) <= maxTime * 1000);
} }
}, },
@@ -1868,7 +1869,7 @@ BotMon.live = {
columns = ['ts','ip','pg','id','agent']; columns = ['ts','ip','pg','id','agent'];
break; break;
default: default:
console.warn(`Unknown log type ${type}.`); console.warn(`Botmon: Unknown log type ${type} in function “loadLogFile” (1).`);
return; return;
} }
@@ -1924,7 +1925,7 @@ BotMon.live = {
BotMon.live.data.model.updateTicks(data); BotMon.live.data.model.updateTicks(data);
break; break;
default: default:
console.warn(`Unknown log type ${type}.`); console.warn(`Botmon: Unknown log type ${type} in function “loadLogFile” (2).`);
return; return;
} }
}); });
@@ -2068,7 +2069,7 @@ BotMon.live = {
value = BotMon.t._getRatio(data.views.suspected + data.views.bots, data.views.users + data.views.humans, 100); value = BotMon.t._getRatio(data.views.suspected + data.views.bots, data.views.users + data.views.humans, 100);
break; break;
default: default:
console.warn(`Unknown list type ${i}.`); console.warn(`Botmon: Unknown list type ${i} in function “overview.make” (1).`);
} }
dd.appendChild(makeElement('span', {}, title)); dd.appendChild(makeElement('span', {}, title));
dd.appendChild(makeElement('strong', {}, value)); dd.appendChild(makeElement('strong', {}, value));
@@ -2096,7 +2097,6 @@ BotMon.live = {
botIps.appendChild(makeElement('dt', {}, "Top bot Networks")); botIps.appendChild(makeElement('dt', {}, "Top bot Networks"));
const ispList = BotMon.live.data.analytics.getTopBotISPs(5); const ispList = BotMon.live.data.analytics.getTopBotISPs(5);
//console.log(ispList);
ispList.forEach( (netInfo) => { ispList.forEach( (netInfo) => {
const li = makeElement('dd'); const li = makeElement('dd');
li.appendChild(makeElement('span', {'class': 'has_icon ipaddr ip' + netInfo.typ }, netInfo.name)); li.appendChild(makeElement('span', {'class': 'has_icon ipaddr ip' + netInfo.typ }, netInfo.name));
@@ -2156,7 +2156,7 @@ BotMon.live = {
value = bounceRate + '%'; value = bounceRate + '%';
break; break;
default: default:
console.warn(`Unknown list type ${i}.`); console.warn(`Botmon: Unknown list type ${i} in function “overview.make” (2).`);
} }
dd.appendChild(makeElement('span', {}, title)); dd.appendChild(makeElement('span', {}, title));
dd.appendChild(makeElement('strong', {}, value)); dd.appendChild(makeElement('strong', {}, value));
@@ -2297,7 +2297,7 @@ BotMon.live = {
value = (data.captcha.humans_blocked / data.visits.humans * 100).toFixed(0) + '%'; value = (data.captcha.humans_blocked / data.visits.humans * 100).toFixed(0) + '%';
break; break;
default: default:
console.warn(`Unknown list type ${i}.`); console.warn(`Botmon: Unknown list type ${i} in function “overview.make” (3).`);
} }
dd.appendChild(makeElement('span', {}, title)); dd.appendChild(makeElement('span', {}, title));
dd.appendChild(makeElement('strong', {}, value || kNoData)); dd.appendChild(makeElement('strong', {}, value || kNoData));
@@ -2337,7 +2337,7 @@ BotMon.live = {
value = (data.captcha.sus_blocked / data.visits.suspected * 100).toFixed(0) + '%'; value = (data.captcha.sus_blocked / data.visits.suspected * 100).toFixed(0) + '%';
break; break;
default: default:
console.warn(`Unknown list type ${i}.`); console.warn(`Botmon: Unknown list type ${i} in function “BotMon.live.gui.overview.make” (4).`);
} }
dd.appendChild(makeElement('span', {}, title)); dd.appendChild(makeElement('span', {}, title));
dd.appendChild(makeElement('strong', {}, value || kNoData)); dd.appendChild(makeElement('strong', {}, value || kNoData));
@@ -2377,7 +2377,7 @@ BotMon.live = {
value = (data.captcha.bots_blocked / data.visits.bots * 100).toFixed(0) + '%'; value = (data.captcha.bots_blocked / data.visits.bots * 100).toFixed(0) + '%';
break; break;
default: default:
console.warn(`Unknown list type ${i}.`); console.warn(`Botmon: Unknown list type ${i} in function “BotMon.live.gui.overview.make” (5).`);
} }
dd.appendChild(makeElement('span', {}, title)); dd.appendChild(makeElement('span', {}, title));
dd.appendChild(makeElement('strong', {}, value || kNoData)); dd.appendChild(makeElement('strong', {}, value || kNoData));
@@ -2405,7 +2405,7 @@ BotMon.live = {
}, },
setError: function(txt) { setError: function(txt) {
console.error(txt); console.error('Botmon:', txt);
BotMon.live.gui.status._errorCount += 1; BotMon.live.gui.status._errorCount += 1;
const el = document.getElementById('botmon__today__status'); const el = document.getElementById('botmon__today__status');
if (el) { if (el) {
@@ -2470,7 +2470,7 @@ BotMon.live = {
infolink = 'https://leib.be/sascha/projects/dokuwiki/botmon/info/known_bots'; infolink = 'https://leib.be/sascha/projects/dokuwiki/botmon/info/known_bots';
break; break;
default: default:
console.warn('Unknown list number.'); console.warn(`Botmon: Unknown list number. ${i} in function “lists.init”.`);
} }
const details = makeElement('details', { const details = makeElement('details', {
@@ -2737,13 +2737,14 @@ BotMon.live = {
} }
dl.appendChild(make('dt', {}, "Actions:")); dl.appendChild(make('dt', {}, "Actions:"));
dl.appendChild(make('dd', {'class': 'views'}, dl.appendChild(make('dd', {'class': 'views'},
"Page loads: " + data._loadCount.toString() + "Page loads: " + data._loadCount.toString() +
( data._captcha['Y'] > 0 ? ", captchas: " + data._captcha['Y'].toString() : '') + ( data._captcha['Y'] > 0 ? ", captchas: " + data._captcha['Y'].toString() : '') +
", views: " + data._viewCount.toString() ", views: " + data._viewCount.toString()
)); ));
if (data.ref && data.ref !== '') { if (!combinedItem && data.ref && data.ref !== '') {
dl.appendChild(make('dt', {}, "Referrer:")); dl.appendChild(make('dt', {}, "Referrer:"));
const refInfo = BotMon.live.data.analytics.getRefererInfo(data.ref); const refInfo = BotMon.live.data.analytics.getRefererInfo(data.ref);
@@ -2824,19 +2825,13 @@ BotMon.live = {
} }
} }
// for debugging only. Disable on production:
/*dl.appendChild(make('dt', {}, "Debug info:"));
const dbgDd = make('dd', {'class': 'debug'});
dbgDd.innerHTML = '<pre>' + JSON.stringify(data, null, 4) + '</pre>';
dl.appendChild(dbgDd);*/
// return the element to add to the UI: // return the element to add to the UI:
return dl; return dl;
}, },
// make a page view item: // make a page view item:
_makePageViewItem: function(page, moreInfo) { _makePageViewItem: function(page, moreInfo) {
console.log("makePageViewItem:",page); //console.log("makePageViewItem:",page);
// shortcut for neater code: // shortcut for neater code:
const make = BotMon.t._makeElement; const make = BotMon.t._makeElement;
@@ -2856,16 +2851,15 @@ BotMon.live = {
const rightGroup = row1.appendChild(make('div')); // right-hand group const rightGroup = row1.appendChild(make('div')); // right-hand group
// get the time difference:
rightGroup.appendChild(make('span', { rightGroup.appendChild(make('span', {
'class': 'first-seen', 'class': 'first-seen',
'title': "First visited: " + page._firstSeen.toLocaleString() + " UTC" 'title': "First visited: " + page._firstSeen.toLocaleString() + " UTC"
}, BotMon.t._formatTime(page._firstSeen))); }, BotMon.t._formatTime(page._firstSeen)));
rightGroup.appendChild(make('span', { /* page loads */
'class': 'has_icon pageviews', rightGroup.appendChild(make('span', { // captcha status
'title': page._viewCount.toString() + " page load(s)" 'class': 'icon_only captcha cap_' + page._captcha,
}, page._viewCount.toString())); }, page._captcha));
pgLi.appendChild(row1); pgLi.appendChild(row1);
@@ -2876,31 +2870,74 @@ BotMon.live = {
// page referrer: // page referrer:
if (page._ref) { if (page._ref) {
row2.appendChild(make('span', { row2.appendChild(make('span', {
'class': 'referer', 'class': 'referer'
'title': "Referrer: " + page._ref.href }, "Referrer: " + page._ref.hostname));
}, page._ref.hostname));
} else { } else {
row2.appendChild(make('span', { row2.appendChild(make('span', {
'class': 'referer' 'class': 'referer'
}, "No referer")); }, "No referer"));
} }
// visit duration: const rightGroup2 = row2.appendChild(make('div')); // right-hand group
let visitTimeStr = "Bounce";
const visitDuration = page._lastSeen.getTime() - page._firstSeen.getTime(); // visit duration:
if (visitDuration > 0) { let visitTimeStr = "Bounce";
visitTimeStr = Math.floor(visitDuration / 1000) + "s"; const visitDuration = page._lastSeen.getTime() - page._firstSeen.getTime();
} if (visitDuration > 0) {
const tDiff = BotMon.t._formatTimeDiff(page._firstSeen, page._lastSeen); visitTimeStr = Math.floor(visitDuration / 1000) + "s";
if (tDiff) { }
row2.appendChild(make('span', {'class': 'visit-length', 'title': 'Last seen: ' + page._lastSeen.toLocaleString()}, tDiff)); var tDiff = BotMon.t._formatTimeDiff(page._firstSeen, page._lastSeen);
} else { if (tDiff) {
row2.appendChild(make('span', { tDiff += " (" + page._tickCount.toString() + " ticks)";
'class': 'bounce', rightGroup2.appendChild(make('span', {
'title': "Visitor bounced"}, "Bounce")); 'class': 'visit-length',
'title': "Last seen: " + page._lastSeen.toLocaleString()},
tDiff));
} else {
rightGroup2.appendChild(make('span', {
'class': 'bounce',
'title': "Visitor bounced (no ticks)"},
"Bounce"));
}
pgLi.appendChild(row2);
if (moreInfo) { /* LINE 3 */
const row3 = make('div', {'class': 'row'});
const leftGroup3 = row3.appendChild(make('div')); // left-hand group
leftGroup3.appendChild(make('span', {
'class': 'ip-address'
}, "IP: " + page.ip + ': '));
const rightGroup3 = row3.appendChild(make('div')); // right-hand group
rightGroup3.appendChild(make('span', {
'class': 'views'
}, page._loadCount.toString() + " loads, " + page._viewCount.toString() + " views"));
pgLi.appendChild(row3);
/* LINE 4 */
if (page._agent) {
const row4 = make('div', {'class': 'row'});
row4.appendChild(make('span', {
'class': 'user-agent'
}, "User-agent: " + page._agent));
pgLi.appendChild(row4);
} }
pgLi.appendChild(row2); /* LINE X (DEBUG ONLY!)
const rowx = make('div', {'class': 'row'});
rowx.appendChild(make('pre', {'style': 'white-space: normal;width:calc(100% - 24rem)'}, JSON.stringify(page)));
pgLi.appendChild(rowx); */
}
return pgLi; return pgLi;
} }

View File

@@ -75,11 +75,11 @@
}, },
{"func": "blockedByCaptcha", "params": [], {"func": "blockedByCaptcha", "params": [],
"id": "blockedByCaptcha", "desc": "Visitor did not solve the captcha", "id": "blockedByCaptcha", "desc": "Visitor did not solve the captcha",
"bot": 20 "bot": 50
}, },
{"func": "whitelistedByCaptcha", "params": [], {"func": "whitelistedByCaptcha", "params": [],
"id": "whitelistedByCaptcha", "desc": "Visitor uses a whitelisted IP address", "id": "whitelistedByCaptcha", "desc": "Visitor uses a whitelisted IP address",
"bot": -20 "bot": -30
} }
] ]
} }

View File

@@ -7,7 +7,7 @@
{"id": "googlebot", {"id": "googlebot",
"n": "GoogleBot", "n": "GoogleBot",
"r": ["Googlebot"], "r": ["Googlebot"],
"rx": ["Googlebot\\/(\\d+\\.\\d+)", "Googlebot-Image\\/(\\d+\\.\\d+)"], "rx": ["Googlebot\\/(\\d+\\.\\d+)", "Googlebot-Image\\/(\\d+\\.\\d+)","\\sGoogleOther(\\-\\w+)?[\\)\\/]"],
"url": "http://www.google.com/bot.html" "url": "http://www.google.com/bot.html"
}, },
{"id": "googleads", {"id": "googleads",
@@ -22,12 +22,6 @@
"rx": ["APIs-Google"], "rx": ["APIs-Google"],
"url": "https://developers.google.com/search/docs/crawling-indexing/google-special-case-crawlers" "url": "https://developers.google.com/search/docs/crawling-indexing/google-special-case-crawlers"
}, },
{"id": "googleother",
"n": "GoogleOther",
"r": ["GoogleOther"],
"rx": ["\\sGoogleOther(\\-\\w+)?[\\)\\/]"],
"url": "https://developers.google.com/search/docs/crawling-indexing/google-common-crawlers#googleother"
},
{"id": "googinspct", {"id": "googinspct",
"n": "Google-InspectionTool", "n": "Google-InspectionTool",
"r": ["Google-InspectionTool"], "r": ["Google-InspectionTool"],

View File

@@ -106,6 +106,12 @@ body.botmon_captcha {
} }
} }
@keyframes delayed-fade-in {
0% { opacity: 0; }
50% { opacity: 0; }
100% { opacity: 1; }
}
// no js warning // no js warning
#BM__NoJSWarning { #BM__NoJSWarning {
position: fixed; position: fixed;
@@ -116,6 +122,7 @@ body.botmon_captcha {
border-radius: .5rem; border-radius: .5rem;
border: red solid 2pt; border: red solid 2pt;
box-shadow: rgba(128, 0, 0, 0.5) .25rem .25rem .5rem; box-shadow: rgba(128, 0, 0, 0.5) .25rem .25rem .5rem;
animation: delayed-fade-in 4s forwards;
} }
// captcha on smaller screens: // captcha on smaller screens: