From 53287868f786eb8679a3b278eb9c63a3d0705d21 Mon Sep 17 00:00:00 2001 From: Sascha Leib Date: Sat, 6 Dec 2025 13:57:58 +0100 Subject: [PATCH] GUI Improvements Better display of pages per visit. --- admin.css | 50 ++++++++----- admin.js | 145 +++++++++++++++++++++++-------------- config/default-config.json | 4 +- config/known-bots.json | 8 +- style.less | 7 ++ 5 files changed, 132 insertions(+), 82 deletions(-) diff --git a/admin.css b/admin.css index 8f3cad1..f050a96 100644 --- a/admin.css +++ b/admin.css @@ -701,20 +701,9 @@ &:nth-child(odd) { background-color: #DFDFDF; } - &.detailled { + /*&.detailled { 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] { @@ -733,6 +722,23 @@ padding: 0 1pt; 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 { min-width: 4.2em; text-align: right; @@ -741,20 +747,26 @@ font-size: smaller; } span.bounce { - width: 1.25em; height: 1.25em; + width: 1em; height: 1em; overflow: hidden; } span.bounce::before { display: inline-block; content: ''; - width: 1.25em; height: 1em; + width: 1em; height: 1em; background: transparent url('img/bounce.svg') center no-repeat; background-size: .95em; } - span.referer { + span.referer, span.views, span.ip-address, span.user-agent { font-size: smaller; + } + span.referer, span.ip-address, span.user-agent { margin-left: .67rem; } + span.user-agent { + line-break: anywhere; + text-wrap-mode: wrap; + } } } @@ -779,7 +791,7 @@ } /* pages seen */ - span.pageseen, span.pageviews { + span.pageseen/*, span.pageviews */{ border: #999 solid 1px; padding: 0 2px; font-size: smaller; @@ -794,13 +806,13 @@ background: transparent url('img/page.svg') center no-repeat; background-size: 1.25em; } - span.pageviews::before { + /*span.pageviews::before { content : ''; display: inline-block; width: 1.25em; height: 1.25em; background: transparent url('img/views.svg') center no-repeat; background-size: 1.25em; - } + }*/ } /* item footer */ diff --git a/admin.js b/admin.js index 896711b..fc3cbc1 100644 --- a/admin.js +++ b/admin.js @@ -105,7 +105,7 @@ const BotMon = { r.textContent = text.toString(); } } catch(e) { - console.error(e); + console.error('Botmon:', e); } return r; }, @@ -399,7 +399,7 @@ BotMon.live = { nv._country = countryName.of(nv.geo.substring(0,2)) ?? nv.geo; } } catch (err) { - console.error(err); + console.error('Botmon:', err); nv._country = 'Error'; } @@ -445,6 +445,8 @@ BotMon.live = { prereg = model._makePageView(nv, type); visitor._pageViews.push(prereg); } + prereg._loadCount += 1; + prereg._viewCount += (nv.captcha == 'Y' ? 0 : 1); // update last seen date prereg._lastSeen = nv.ts; @@ -517,7 +519,7 @@ BotMon.live = { // find the visit info: let visitor = model.findVisitor(dat, type); 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); } if (visitor) { @@ -528,7 +530,7 @@ BotMon.live = { // get the page view info: let pv = model._getPageView(visitor, dat); 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); visitor._pageViews.push(pv); } @@ -543,15 +545,14 @@ BotMon.live = { // helper function to create a new "page view" item: _makePageView: function(data, type) { - // console.info('_makePageView', data); + //console.info('_makePageView', data); // try to parse the referrer: let rUrl = null; try { rUrl = ( data.ref && data.ref !== '' ? new URL(data.ref) : null ); } catch (e) { - console.warn(`Invalid referer: “${data.ref}”.`); - console.info(data); + console.info(`Botmon: Ignoring invalid referer: “${data.ref}”.`); } return { @@ -564,9 +565,11 @@ BotMon.live = { _lastSeen: data.ts, _seenBy: [type], _jsClient: ( type !== BM_LOGTYPE.SERVER), + _agent: data.agent, _viewCount: 0, _loadCount: 0, - _tickCount: 0 + _tickCount: 0, + _captcha: data.captcha }; }, @@ -1025,7 +1028,7 @@ BotMon.live = { arr = me._countries.bot; break; default: - console.warn(`Unknown user type ${type} in function addToCountries.`); + console.warn(`Botmon: Unknown user type ${type} in function addToCountries.`); } if (arr) { @@ -1065,7 +1068,7 @@ BotMon.live = { arr = me._countries.bot; break; default: - console.warn(`Unknown user type ${type} in function getCountryList.`); + console.warn(`Botmon: Unknown user type ${type} in function getCountryList.`); return; } @@ -1652,7 +1655,7 @@ BotMon.live = { 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); }, @@ -1766,8 +1769,6 @@ BotMon.live = { 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); } }, @@ -1868,7 +1869,7 @@ BotMon.live = { columns = ['ts','ip','pg','id','agent']; break; default: - console.warn(`Unknown log type ${type}.`); + console.warn(`Botmon: Unknown log type ${type} in function “loadLogFile” (1).`); return; } @@ -1924,7 +1925,7 @@ BotMon.live = { BotMon.live.data.model.updateTicks(data); break; default: - console.warn(`Unknown log type ${type}.`); + console.warn(`Botmon: Unknown log type ${type} in function “loadLogFile” (2).`); return; } }); @@ -2068,7 +2069,7 @@ BotMon.live = { 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}.`); + console.warn(`Botmon: Unknown list type ${i} in function “overview.make” (1).`); } dd.appendChild(makeElement('span', {}, title)); dd.appendChild(makeElement('strong', {}, value)); @@ -2096,7 +2097,6 @@ BotMon.live = { botIps.appendChild(makeElement('dt', {}, "Top bot Networks")); const ispList = BotMon.live.data.analytics.getTopBotISPs(5); - //console.log(ispList); ispList.forEach( (netInfo) => { const li = makeElement('dd'); li.appendChild(makeElement('span', {'class': 'has_icon ipaddr ip' + netInfo.typ }, netInfo.name)); @@ -2156,7 +2156,7 @@ BotMon.live = { value = bounceRate + '%'; break; 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('strong', {}, value)); @@ -2297,7 +2297,7 @@ BotMon.live = { value = (data.captcha.humans_blocked / data.visits.humans * 100).toFixed(0) + '%'; break; 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('strong', {}, value || kNoData)); @@ -2337,7 +2337,7 @@ BotMon.live = { value = (data.captcha.sus_blocked / data.visits.suspected * 100).toFixed(0) + '%'; break; 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('strong', {}, value || kNoData)); @@ -2377,7 +2377,7 @@ BotMon.live = { value = (data.captcha.bots_blocked / data.visits.bots * 100).toFixed(0) + '%'; break; 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('strong', {}, value || kNoData)); @@ -2405,7 +2405,7 @@ BotMon.live = { }, setError: function(txt) { - console.error(txt); + console.error('Botmon:', txt); BotMon.live.gui.status._errorCount += 1; const el = document.getElementById('botmon__today__status'); if (el) { @@ -2470,7 +2470,7 @@ BotMon.live = { infolink = 'https://leib.be/sascha/projects/dokuwiki/botmon/info/known_bots'; break; default: - console.warn('Unknown list number.'); + console.warn(`Botmon: Unknown list number. ${i} in function “lists.init”.`); } const details = makeElement('details', { @@ -2737,13 +2737,14 @@ BotMon.live = { } dl.appendChild(make('dt', {}, "Actions:")); + dl.appendChild(make('dd', {'class': 'views'}, "Page loads: " + data._loadCount.toString() + ( data._captcha['Y'] > 0 ? ", captchas: " + data._captcha['Y'].toString() : '') + ", views: " + data._viewCount.toString() )); - if (data.ref && data.ref !== '') { + if (!combinedItem && data.ref && data.ref !== '') { dl.appendChild(make('dt', {}, "Referrer:")); 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 = '
' + JSON.stringify(data, null, 4) + '
'; - dl.appendChild(dbgDd);*/ - // return the element to add to the UI: return dl; }, // make a page view item: _makePageViewItem: function(page, moreInfo) { - console.log("makePageViewItem:",page); + //console.log("makePageViewItem:",page); // shortcut for neater code: const make = BotMon.t._makeElement; @@ -2856,16 +2851,15 @@ BotMon.live = { const rightGroup = row1.appendChild(make('div')); // right-hand group - // get the time difference: rightGroup.appendChild(make('span', { 'class': 'first-seen', 'title': "First visited: " + page._firstSeen.toLocaleString() + " UTC" }, BotMon.t._formatTime(page._firstSeen))); - - rightGroup.appendChild(make('span', { /* page loads */ - 'class': 'has_icon pageviews', - 'title': page._viewCount.toString() + " page load(s)" - }, page._viewCount.toString())); + + + rightGroup.appendChild(make('span', { // captcha status + 'class': 'icon_only captcha cap_' + page._captcha, + }, page._captcha)); pgLi.appendChild(row1); @@ -2876,31 +2870,74 @@ BotMon.live = { // page referrer: if (page._ref) { row2.appendChild(make('span', { - 'class': 'referer', - 'title': "Referrer: " + page._ref.href - }, page._ref.hostname)); + 'class': 'referer' + }, "Referrer: " + page._ref.hostname)); } else { row2.appendChild(make('span', { 'class': 'referer' }, "No referer")); } - // visit duration: - let visitTimeStr = "Bounce"; - const visitDuration = page._lastSeen.getTime() - page._firstSeen.getTime(); - if (visitDuration > 0) { - visitTimeStr = Math.floor(visitDuration / 1000) + "s"; - } - const tDiff = BotMon.t._formatTimeDiff(page._firstSeen, page._lastSeen); - if (tDiff) { - row2.appendChild(make('span', {'class': 'visit-length', 'title': 'Last seen: ' + page._lastSeen.toLocaleString()}, tDiff)); - } else { - row2.appendChild(make('span', { - 'class': 'bounce', - 'title': "Visitor bounced"}, "Bounce")); + const rightGroup2 = row2.appendChild(make('div')); // right-hand group + + // visit duration: + let visitTimeStr = "Bounce"; + const visitDuration = page._lastSeen.getTime() - page._firstSeen.getTime(); + if (visitDuration > 0) { + visitTimeStr = Math.floor(visitDuration / 1000) + "s"; + } + var tDiff = BotMon.t._formatTimeDiff(page._firstSeen, page._lastSeen); + if (tDiff) { + tDiff += " (" + page._tickCount.toString() + " ticks)"; + rightGroup2.appendChild(make('span', { + '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; } diff --git a/config/default-config.json b/config/default-config.json index 01e3a74..0591c77 100644 --- a/config/default-config.json +++ b/config/default-config.json @@ -75,11 +75,11 @@ }, {"func": "blockedByCaptcha", "params": [], "id": "blockedByCaptcha", "desc": "Visitor did not solve the captcha", - "bot": 20 + "bot": 50 }, {"func": "whitelistedByCaptcha", "params": [], "id": "whitelistedByCaptcha", "desc": "Visitor uses a whitelisted IP address", - "bot": -20 + "bot": -30 } ] } \ No newline at end of file diff --git a/config/known-bots.json b/config/known-bots.json index 31ec325..a733339 100644 --- a/config/known-bots.json +++ b/config/known-bots.json @@ -7,7 +7,7 @@ {"id": "googlebot", "n": "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" }, {"id": "googleads", @@ -22,12 +22,6 @@ "rx": ["APIs-Google"], "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", "n": "Google-InspectionTool", "r": ["Google-InspectionTool"], diff --git a/style.less b/style.less index 67a5325..9314c05 100644 --- a/style.less +++ b/style.less @@ -106,6 +106,12 @@ body.botmon_captcha { } } +@keyframes delayed-fade-in { + 0% { opacity: 0; } + 50% { opacity: 0; } + 100% { opacity: 1; } +} + // no js warning #BM__NoJSWarning { position: fixed; @@ -116,6 +122,7 @@ body.botmon_captcha { border-radius: .5rem; border: red solid 2pt; box-shadow: rgba(128, 0, 0, 0.5) .25rem .25rem .5rem; + animation: delayed-fade-in 4s forwards; } // captcha on smaller screens: