xref: /plugin/botmon/script.js (revision 4c5062c1b60b1c18019c60dcd865f7038b65e5a3)
143d9de6bSSascha Leib"use strict";
27bd08c30SSascha Leib/* DokuWiki BotMon Plugin Script file */
3abfc901fSSascha Leib/* 04.09.2025 - 0.1.8 - pre-release */
4f125bc8dSSascha Leib/* Authors: Sascha Leib <ad@hominem.info> */
5f125bc8dSSascha Leib
6f4417fdeSSascha Leib// enumeration of user types:
7f4417fdeSSascha Leibconst BM_USERTYPE = Object.freeze({
8f4417fdeSSascha Leib	'UNKNOWN': 'unknown',
9f4417fdeSSascha Leib	'KNOWN_USER': 'user',
10f4417fdeSSascha Leib	'HUMAN': 'human',
11f4417fdeSSascha Leib	'LIKELY_BOT': 'likely_bot',
12f4417fdeSSascha Leib	'KNOWN_BOT': 'known_bot'
13f4417fdeSSascha Leib});
14f4417fdeSSascha Leib
15f4417fdeSSascha Leib/* BotMon root object */
167bd08c30SSascha Leibconst BotMon = {
17f125bc8dSSascha Leib
18f125bc8dSSascha Leib	init: function() {
1993a5b18bSSascha Leib		//console.info('BotMon.init()');
20f125bc8dSSascha Leib
21f125bc8dSSascha Leib		// find the plugin basedir:
22f125bc8dSSascha Leib		this._baseDir = document.currentScript.src.substring(0, document.currentScript.src.indexOf('/exe/'))
237bd08c30SSascha Leib			+ '/plugins/botmon/';
24f125bc8dSSascha Leib
25f125bc8dSSascha Leib		// read the page language from the DOM:
26f125bc8dSSascha Leib		this._lang = document.getRootNode().documentElement.lang || this._lang;
27f125bc8dSSascha Leib
28f125bc8dSSascha Leib		// get the time offset:
297bd08c30SSascha Leib		this._timeDiff = BotMon.t._getTimeOffset();
30f125bc8dSSascha Leib
31f125bc8dSSascha Leib		// init the sub-objects:
327bd08c30SSascha Leib		BotMon.t._callInit(this);
33f125bc8dSSascha Leib	},
34f125bc8dSSascha Leib
35f125bc8dSSascha Leib	_baseDir: null,
36f125bc8dSSascha Leib	_lang: 'en',
37f125bc8dSSascha Leib	_today: (new Date()).toISOString().slice(0, 10),
38f125bc8dSSascha Leib	_timeDiff: '',
39f125bc8dSSascha Leib
40f125bc8dSSascha Leib	/* internal tools */
41f125bc8dSSascha Leib	t: {
42f125bc8dSSascha Leib
43f125bc8dSSascha Leib		/* helper function to call inits of sub-objects */
44f125bc8dSSascha Leib		_callInit: function(obj) {
457bd08c30SSascha Leib			//console.info('BotMon.t._callInit(obj=',obj,')');
46f125bc8dSSascha Leib
47f125bc8dSSascha Leib			/* call init / _init on each sub-object: */
48f125bc8dSSascha Leib			Object.keys(obj).forEach( (key,i) => {
49f125bc8dSSascha Leib				const sub = obj[key];
50f125bc8dSSascha Leib				let init = null;
51f125bc8dSSascha Leib				if (typeof sub === 'object' && sub.init) {
52f125bc8dSSascha Leib					init = sub.init;
53f125bc8dSSascha Leib				}
54f125bc8dSSascha Leib
55f125bc8dSSascha Leib				// bind to object
56f125bc8dSSascha Leib				if (typeof init == 'function') {
57f125bc8dSSascha Leib					const init2 = init.bind(sub);
58f125bc8dSSascha Leib					init2(obj);
59f125bc8dSSascha Leib				}
60f125bc8dSSascha Leib			});
61f125bc8dSSascha Leib		},
62f125bc8dSSascha Leib
63f125bc8dSSascha Leib		/* helper function to calculate the time difference to UTC: */
64f125bc8dSSascha Leib		_getTimeOffset: function() {
65f125bc8dSSascha Leib			const now = new Date();
66f125bc8dSSascha Leib			let offset = now.getTimezoneOffset(); // in minutes
67f125bc8dSSascha Leib			const sign = Math.sign(offset); // +1 or -1
68f125bc8dSSascha Leib			offset = Math.abs(offset); // always positive
69f125bc8dSSascha Leib
70f125bc8dSSascha Leib			let hours = 0;
71f125bc8dSSascha Leib			while (offset >= 60) {
72f125bc8dSSascha Leib				hours += 1;
73f125bc8dSSascha Leib				offset -= 60;
74f125bc8dSSascha Leib			}
75f125bc8dSSascha Leib			return ( hours > 0 ? sign * hours + ' h' : '') + (offset > 0 ? ` ${offset} min` : '');
7693a5b18bSSascha Leib		},
7793a5b18bSSascha Leib
7893a5b18bSSascha Leib		/* helper function to create a new element with all attributes and text content */
7993a5b18bSSascha Leib		_makeElement: function(name, atlist = undefined, text = undefined) {
8093a5b18bSSascha Leib			var r = null;
8193a5b18bSSascha Leib			try {
8293a5b18bSSascha Leib				r = document.createElement(name);
8393a5b18bSSascha Leib				if (atlist) {
8493a5b18bSSascha Leib					for (let attr in atlist) {
8593a5b18bSSascha Leib						r.setAttribute(attr, atlist[attr]);
8693a5b18bSSascha Leib					}
8793a5b18bSSascha Leib				}
8893a5b18bSSascha Leib				if (text) {
8993a5b18bSSascha Leib					r.textContent = text.toString();
9093a5b18bSSascha Leib				}
9193a5b18bSSascha Leib			} catch(e) {
9293a5b18bSSascha Leib				console.error(e);
9393a5b18bSSascha Leib			}
9493a5b18bSSascha Leib			return r;
95f125bc8dSSascha Leib		}
96f125bc8dSSascha Leib	}
97c7931771SSascha Leib};
98f125bc8dSSascha Leib
999f1ee8c1SSascha Leib/* everything specific to the "Today" tab is self-contained in the "live" object: */
1007bd08c30SSascha LeibBotMon.live = {
101f125bc8dSSascha Leib	init: function() {
1027bd08c30SSascha Leib		//console.info('BotMon.live.init()');
103f125bc8dSSascha Leib
104f125bc8dSSascha Leib		// set the title:
1057bd08c30SSascha Leib		const tDiff = '(<abbr title="Coordinated Universal Time">UTC</abbr>' + (BotMon._timeDiff != '' ? `, ${BotMon._timeDiff}` : '' ) + ')';
1067bd08c30SSascha Leib		BotMon.live.gui.status.setTitle(`Data for <time datetime=${BotMon._today}>${BotMon._today}</time> ${tDiff}`);
107f125bc8dSSascha Leib
108f125bc8dSSascha Leib		// init sub-objects:
1097bd08c30SSascha Leib		BotMon.t._callInit(this);
110f125bc8dSSascha Leib	},
111f125bc8dSSascha Leib
112f125bc8dSSascha Leib	data: {
113f125bc8dSSascha Leib		init: function() {
1147bd08c30SSascha Leib			//console.info('BotMon.live.data.init()');
115f125bc8dSSascha Leib
116f125bc8dSSascha Leib			// call sub-inits:
1177bd08c30SSascha Leib			BotMon.t._callInit(this);
1189f1ee8c1SSascha Leib		},
119f125bc8dSSascha Leib
1209f1ee8c1SSascha Leib		// this will be called when the known json files are done loading:
1219f1ee8c1SSascha Leib		_dispatch: function(file) {
1227bd08c30SSascha Leib			//console.info('BotMon.live.data._dispatch(,',file,')');
1239f1ee8c1SSascha Leib
1249f1ee8c1SSascha Leib			// shortcut to make code more readable:
1257bd08c30SSascha Leib			const data = BotMon.live.data;
1269f1ee8c1SSascha Leib
1279f1ee8c1SSascha Leib			// set the flags:
1289f1ee8c1SSascha Leib			switch(file) {
1299f1ee8c1SSascha Leib				case 'bots':
1309f1ee8c1SSascha Leib					data._dispatchBotsLoaded = true;
1319f1ee8c1SSascha Leib					break;
1329f1ee8c1SSascha Leib				case 'clients':
1339f1ee8c1SSascha Leib					data._dispatchClientsLoaded = true;
1349f1ee8c1SSascha Leib					break;
1359f1ee8c1SSascha Leib				case 'platforms':
1369f1ee8c1SSascha Leib					data._dispatchPlatformsLoaded = true;
1379f1ee8c1SSascha Leib					break;
138b82cba27SSascha Leib				case 'rules':
139b82cba27SSascha Leib					data._dispatchRulesLoaded = true;
140b82cba27SSascha Leib					break;
1419f1ee8c1SSascha Leib				default:
1429f1ee8c1SSascha Leib					// ignore
1439f1ee8c1SSascha Leib			}
1449f1ee8c1SSascha Leib
1459f1ee8c1SSascha Leib			// are all the flags set?
146b82cba27SSascha Leib			if (data._dispatchBotsLoaded && data._dispatchClientsLoaded && data._dispatchPlatformsLoaded && data._dispatchRulesLoaded) {
1479f1ee8c1SSascha Leib				// chain the log files loading:
1487bd08c30SSascha Leib				BotMon.live.data.loadLogFile('srv', BotMon.live.data._onServerLogLoaded);
1499f1ee8c1SSascha Leib			}
1502f2bc93aSSascha Leib		},
1519f1ee8c1SSascha Leib		// flags to track which data files have been loaded:
1529f1ee8c1SSascha Leib		_dispatchBotsLoaded: false,
1539f1ee8c1SSascha Leib		_dispatchClientsLoaded: false,
1549f1ee8c1SSascha Leib		_dispatchPlatformsLoaded: false,
155b82cba27SSascha Leib		_dispatchRulesLoaded: false,
1562f2bc93aSSascha Leib
1579f1ee8c1SSascha Leib		// event callback, after the server log has been loaded:
1582f2bc93aSSascha Leib		_onServerLogLoaded: function() {
1597bd08c30SSascha Leib			//console.info('BotMon.live.data._onServerLogLoaded()');
1602f2bc93aSSascha Leib
1619f1ee8c1SSascha Leib			// chain the client log file to load:
1627bd08c30SSascha Leib			BotMon.live.data.loadLogFile('log', BotMon.live.data._onClientLogLoaded);
1632f2bc93aSSascha Leib		},
1642f2bc93aSSascha Leib
1659f1ee8c1SSascha Leib		// event callback, after the client log has been loaded:
1662f2bc93aSSascha Leib		_onClientLogLoaded: function() {
16793a5b18bSSascha Leib			//console.info('BotMon.live.data._onClientLogLoaded()');
1682f2bc93aSSascha Leib
1699f1ee8c1SSascha Leib			// chain the ticks file to load:
1707bd08c30SSascha Leib			BotMon.live.data.loadLogFile('tck', BotMon.live.data._onTicksLogLoaded);
1712f2bc93aSSascha Leib
1729f1ee8c1SSascha Leib		},
1739f1ee8c1SSascha Leib
1749f1ee8c1SSascha Leib		// event callback, after the tiker log has been loaded:
1759f1ee8c1SSascha Leib		_onTicksLogLoaded: function() {
17693a5b18bSSascha Leib			//console.info('BotMon.live.data._onTicksLogLoaded()');
1779f1ee8c1SSascha Leib
1789f1ee8c1SSascha Leib			// analyse the data:
1797bd08c30SSascha Leib			BotMon.live.data.analytics.analyseAll();
1809f1ee8c1SSascha Leib
1819f1ee8c1SSascha Leib			// sort the data:
1829f1ee8c1SSascha Leib			// #TODO
1839f1ee8c1SSascha Leib
1849f1ee8c1SSascha Leib			// display the data:
1857bd08c30SSascha Leib			BotMon.live.gui.overview.make();
1869f1ee8c1SSascha Leib
18793a5b18bSSascha Leib			//console.log(BotMon.live.data.model._visitors);
1889f1ee8c1SSascha Leib
1892f2bc93aSSascha Leib		},
1902f2bc93aSSascha Leib
1912f2bc93aSSascha Leib		model: {
1929f1ee8c1SSascha Leib			// visitors storage:
1932f2bc93aSSascha Leib			_visitors: [],
1942f2bc93aSSascha Leib
1952f2bc93aSSascha Leib			// find an already existing visitor record:
196f4417fdeSSascha Leib			findVisitor: function(visitor) {
197f4417fdeSSascha Leib				//console.info('BotMon.live.data.model.findVisitor()');
198f4417fdeSSascha Leib				//console.log(visitor);
1992f2bc93aSSascha Leib
2002f2bc93aSSascha Leib				// shortcut to make code more readable:
2017bd08c30SSascha Leib				const model = BotMon.live.data.model;
2022f2bc93aSSascha Leib
2032f2bc93aSSascha Leib				// loop over all visitors already registered:
2042f2bc93aSSascha Leib				for (let i=0; i<model._visitors.length; i++) {
2052f2bc93aSSascha Leib					const v = model._visitors[i];
206f4417fdeSSascha Leib
207f4417fdeSSascha Leib					if (visitor._type == BM_USERTYPE.KNOWN_BOT) { /* known bots */
208f4417fdeSSascha Leib
209f4417fdeSSascha Leib						// bots match when their ID matches:
210f4417fdeSSascha Leib						if (v._bot && v._bot.id == visitor._bot.id) {
211f4417fdeSSascha Leib							return v;
212f4417fdeSSascha Leib						}
213f4417fdeSSascha Leib
214f4417fdeSSascha Leib					} else if (visitor._type == BM_USERTYPE.KNOWN_USER) { /* registered users */
215f4417fdeSSascha Leib
216259d3b85SSascha Leib						//if (visitor.id == 'fsmoe7lgqb89t92vt4ju8vdl0q') console.log(visitor);
217259d3b85SSascha Leib
218f4417fdeSSascha Leib						// visitors match when their names match:
219f4417fdeSSascha Leib						if ( v.usr == visitor.usr
220f4417fdeSSascha Leib						 && v.ip == visitor.ip
221f4417fdeSSascha Leib						 && v.agent == visitor.agent) {
222f4417fdeSSascha Leib							return v;
223f4417fdeSSascha Leib						}
224f4417fdeSSascha Leib					} else { /* any other visitor */
225f4417fdeSSascha Leib
226f4417fdeSSascha Leib						if ( v.id == visitor.id) { /* match the pre-defined IDs */
227f4417fdeSSascha Leib							return v;
228259d3b85SSascha Leib						} else if (v.ip == visitor.ip && v.agent == visitor.agent) {
22913592cacSSascha Leib							console.warn("Visitor ID not found, using matchin IP + User-Agent instead.");
230259d3b85SSascha Leib							return v;
231f4417fdeSSascha Leib						}
232f4417fdeSSascha Leib
233f4417fdeSSascha Leib					}
2342f2bc93aSSascha Leib				}
2352f2bc93aSSascha Leib				return null; // nothing found
2362f2bc93aSSascha Leib			},
2372f2bc93aSSascha Leib
238259d3b85SSascha Leib			/* if there is already this visit registered, return the page view item */
239259d3b85SSascha Leib			_getPageView: function(visit, view) {
2402f2bc93aSSascha Leib
241294f6af8SSascha Leib				// shortcut to make code more readable:
2427bd08c30SSascha Leib				const model = BotMon.live.data.model;
243294f6af8SSascha Leib
2442f2bc93aSSascha Leib				for (let i=0; i<visit._pageViews.length; i++) {
245294f6af8SSascha Leib					const pv = visit._pageViews[i];
246259d3b85SSascha Leib					if (pv.pg == view.pg) {
247259d3b85SSascha Leib						return pv;
2482f2bc93aSSascha Leib					}
2492f2bc93aSSascha Leib				}
2502f2bc93aSSascha Leib				return null; // not found
2512f2bc93aSSascha Leib			},
2522f2bc93aSSascha Leib
2532f2bc93aSSascha Leib			// register a new visitor (or update if already exists)
254f4417fdeSSascha Leib			registerVisit: function(nv, type) {
255f4417fdeSSascha Leib				//console.info('registerVisit', nv, type);
2562f2bc93aSSascha Leib
2572f2bc93aSSascha Leib				// shortcut to make code more readable:
2587bd08c30SSascha Leib				const model = BotMon.live.data.model;
2592f2bc93aSSascha Leib
26043d9de6bSSascha Leib				// is it a known bot?
261f4417fdeSSascha Leib				const bot = BotMon.live.data.bots.match(nv.agent);
2629f1ee8c1SSascha Leib
263f4417fdeSSascha Leib				// enrich new visitor with relevant data:
264f4417fdeSSascha Leib				if (!nv._bot) nv._bot = bot ?? null; // bot info
265259d3b85SSascha Leib				nv._type = ( bot ? BM_USERTYPE.KNOWN_BOT : ( nv.usr && nv.usr !== '' ? BM_USERTYPE.KNOWN_USER : BM_USERTYPE.UNKNOWN ) );
266f4417fdeSSascha Leib				if (!nv._firstSeen) nv._firstSeen = nv.ts;
267259d3b85SSascha Leib				nv._lastSeen = nv.ts;
26843d9de6bSSascha Leib
269abfc901fSSascha Leib				// check if it already exists:
270f4417fdeSSascha Leib				let visitor = model.findVisitor(nv);
271abfc901fSSascha Leib				if (!visitor) {
272f4417fdeSSascha Leib					visitor = nv;
27343d9de6bSSascha Leib					visitor._seenBy = [type];
2742f2bc93aSSascha Leib					visitor._pageViews = []; // array of page views
2752f2bc93aSSascha Leib					visitor._hasReferrer = false; // has at least one referrer
2762f2bc93aSSascha Leib					visitor._jsClient = false; // visitor has been seen logged by client js as well
277f4417fdeSSascha Leib					visitor._client = BotMon.live.data.clients.match(nv.agent) ?? null; // client info
278f4417fdeSSascha Leib					visitor._platform = BotMon.live.data.platforms.match(nv.agent); // platform info
279f4417fdeSSascha Leib					model._visitors.push(visitor);
2802f2bc93aSSascha Leib				}
2812f2bc93aSSascha Leib
2829f1ee8c1SSascha Leib				// find browser
2839f1ee8c1SSascha Leib
2842f2bc93aSSascha Leib				// is this visit already registered?
285259d3b85SSascha Leib				let prereg = model._getPageView(visitor, nv);
2862f2bc93aSSascha Leib				if (!prereg) {
287259d3b85SSascha Leib					// add new page view:
288259d3b85SSascha Leib					prereg = model._makePageView(nv, type);
2892f2bc93aSSascha Leib					visitor._pageViews.push(prereg);
290259d3b85SSascha Leib				} else {
291259d3b85SSascha Leib					// update last seen date
292259d3b85SSascha Leib					prereg._lastSeen = nv.ts;
293259d3b85SSascha Leib					// increase view count:
294259d3b85SSascha Leib					prereg._viewCount += 1;
2952f2bc93aSSascha Leib				}
2962f2bc93aSSascha Leib
2972f2bc93aSSascha Leib				// update referrer state:
2982f2bc93aSSascha Leib				visitor._hasReferrer = visitor._hasReferrer ||
2992f2bc93aSSascha Leib					(prereg.ref !== undefined && prereg.ref !== '');
3002f2bc93aSSascha Leib
3012f2bc93aSSascha Leib				// update time stamp for last-seen:
302f4417fdeSSascha Leib				visitor._lastSeen = nv.ts;
3039f1ee8c1SSascha Leib
3049f1ee8c1SSascha Leib				// if needed:
3059f1ee8c1SSascha Leib				return visitor;
3062f2bc93aSSascha Leib			},
3072f2bc93aSSascha Leib
3082f2bc93aSSascha Leib			// updating visit data from the client-side log:
3092f2bc93aSSascha Leib			updateVisit: function(dat) {
3109f1ee8c1SSascha Leib				//console.info('updateVisit', dat);
3112f2bc93aSSascha Leib
3122f2bc93aSSascha Leib				// shortcut to make code more readable:
3137bd08c30SSascha Leib				const model = BotMon.live.data.model;
3142f2bc93aSSascha Leib
31543d9de6bSSascha Leib				const type = 'log';
31643d9de6bSSascha Leib
317f4417fdeSSascha Leib				let visitor = BotMon.live.data.model.findVisitor(dat);
3189f1ee8c1SSascha Leib				if (!visitor) {
31943d9de6bSSascha Leib					visitor = model.registerVisit(dat, type);
3209f1ee8c1SSascha Leib				}
3212f2bc93aSSascha Leib				if (visitor) {
32243d9de6bSSascha Leib
3232f2bc93aSSascha Leib					visitor._lastSeen = dat.ts;
324259d3b85SSascha Leib					if (!visitor._seenBy.includes(type)) {
325259d3b85SSascha Leib						visitor._seenBy.push(type);
326259d3b85SSascha Leib					}
3272f2bc93aSSascha Leib					visitor._jsClient = true; // seen by client js
3282f2bc93aSSascha Leib				}
3292f2bc93aSSascha Leib
3302f2bc93aSSascha Leib				// find the page view:
331259d3b85SSascha Leib				let prereg = BotMon.live.data.model._getPageView(visitor, dat);
3322f2bc93aSSascha Leib				if (prereg) {
3332f2bc93aSSascha Leib					// update the page view:
3342f2bc93aSSascha Leib					prereg._lastSeen = dat.ts;
335259d3b85SSascha Leib					if (!prereg._seenBy.includes(type)) prereg._seenBy.push(type);
3362f2bc93aSSascha Leib					prereg._jsClient = true; // seen by client js
3372f2bc93aSSascha Leib				} else {
3382f2bc93aSSascha Leib					// add the page view to the visitor:
339259d3b85SSascha Leib					prereg = model._makePageView(dat, type);
3402f2bc93aSSascha Leib					visitor._pageViews.push(prereg);
3412f2bc93aSSascha Leib				}
3429f1ee8c1SSascha Leib			},
3439f1ee8c1SSascha Leib
3449f1ee8c1SSascha Leib			// updating visit data from the ticker log:
3459f1ee8c1SSascha Leib			updateTicks: function(dat) {
346294f6af8SSascha Leib				//console.info('updateTicks', dat);
3479f1ee8c1SSascha Leib
3489f1ee8c1SSascha Leib				// shortcut to make code more readable:
3497bd08c30SSascha Leib				const model = BotMon.live.data.model;
3509f1ee8c1SSascha Leib
351259d3b85SSascha Leib				const type = 'tck';
352259d3b85SSascha Leib
3539f1ee8c1SSascha Leib				// find the visit info:
354f4417fdeSSascha Leib				let visitor = model.findVisitor(dat);
355294f6af8SSascha Leib				if (!visitor) {
356259d3b85SSascha Leib					console.warn(`No visitor with ID ${dat.id}, registering a new one.`);
357259d3b85SSascha Leib					visitor = model.registerVisit(dat, type);
358294f6af8SSascha Leib				}
3599f1ee8c1SSascha Leib				if (visitor) {
36043d9de6bSSascha Leib					// update visitor:
361294f6af8SSascha Leib					if (visitor._lastSeen < dat.ts) visitor._lastSeen = dat.ts;
362259d3b85SSascha Leib					if (!visitor._seenBy.includes(type)) visitor._seenBy.push(type);
363294f6af8SSascha Leib
364294f6af8SSascha Leib					// get the page view info:
365259d3b85SSascha Leib					let pv = model._getPageView(visitor, dat);
366259d3b85SSascha Leib					if (!pv) {
367259d3b85SSascha Leib						console.warn(`No page view for visit ID ${dat.id}, page ${dat.pg}, registering a new one.`);
368259d3b85SSascha Leib						pv = model._makePageView(dat, type);
369259d3b85SSascha Leib						visitor._pageViews.push(pv);
370259d3b85SSascha Leib					}
371294f6af8SSascha Leib
372259d3b85SSascha Leib					// update the page view info:
373259d3b85SSascha Leib					if (!pv._seenBy.includes(type)) pv._seenBy.push(type);
374259d3b85SSascha Leib					if (pv._lastSeen.getTime() < dat.ts.getTime()) pv._lastSeen = dat.ts;
375259d3b85SSascha Leib					pv._tickCount += 1;
376259d3b85SSascha Leib
377259d3b85SSascha Leib				}
378259d3b85SSascha Leib			},
379259d3b85SSascha Leib
380259d3b85SSascha Leib			// helper function to create a new "page view" item:
381259d3b85SSascha Leib			_makePageView: function(data, type) {
382259d3b85SSascha Leib				return {
383259d3b85SSascha Leib					_by: type,
384259d3b85SSascha Leib					ip: data.ip,
385259d3b85SSascha Leib					pg: data.pg,
386259d3b85SSascha Leib					ref: data.ref || '',
387259d3b85SSascha Leib					_firstSeen: data.ts,
388259d3b85SSascha Leib					_lastSeen: data.ts,
389259d3b85SSascha Leib					_seenBy: [type],
390259d3b85SSascha Leib					_jsClient: ( type !== 'srv'),
391259d3b85SSascha Leib					_viewCount: 1,
392259d3b85SSascha Leib					_tickCount: 0
393294f6af8SSascha Leib				};
3942f2bc93aSSascha Leib			}
395f125bc8dSSascha Leib		},
396f125bc8dSSascha Leib
397294f6af8SSascha Leib		analytics: {
398294f6af8SSascha Leib
399294f6af8SSascha Leib			init: function() {
400f4417fdeSSascha Leib				//console.info('BotMon.live.data.analytics.init()');
401294f6af8SSascha Leib			},
402294f6af8SSascha Leib
403294f6af8SSascha Leib			// data storage:
404294f6af8SSascha Leib			data: {
405294f6af8SSascha Leib				totalVisits: 0,
406294f6af8SSascha Leib				totalPageViews: 0,
407294f6af8SSascha Leib				bots: {
408294f6af8SSascha Leib					known: 0,
40993a5b18bSSascha Leib					suspected: 0,
4109bc80cc5SSascha Leib					human: 0,
4119bc80cc5SSascha Leib					users: 0
412294f6af8SSascha Leib				}
413294f6af8SSascha Leib			},
414294f6af8SSascha Leib
415294f6af8SSascha Leib			// sort the visits by type:
416294f6af8SSascha Leib			groups: {
417294f6af8SSascha Leib				knownBots: [],
41893a5b18bSSascha Leib				suspectedBots: [],
419294f6af8SSascha Leib				humans: [],
420294f6af8SSascha Leib				users: []
421294f6af8SSascha Leib			},
422294f6af8SSascha Leib
423294f6af8SSascha Leib			// all analytics
424294f6af8SSascha Leib			analyseAll: function() {
4257bd08c30SSascha Leib				//console.info('BotMon.live.data.analytics.analyseAll()');
426294f6af8SSascha Leib
427294f6af8SSascha Leib				// shortcut to make code more readable:
4287bd08c30SSascha Leib				const model = BotMon.live.data.model;
429294f6af8SSascha Leib
430294f6af8SSascha Leib				// loop over all visitors:
431294f6af8SSascha Leib				model._visitors.forEach( (v) => {
432294f6af8SSascha Leib
433294f6af8SSascha Leib					// count visits and page views:
434294f6af8SSascha Leib					this.data.totalVisits += 1;
435294f6af8SSascha Leib					this.data.totalPageViews += v._pageViews.length;
436294f6af8SSascha Leib
437294f6af8SSascha Leib					// check for typical bot aspects:
43843d9de6bSSascha Leib					let botScore = 0;
439294f6af8SSascha Leib
440f4417fdeSSascha Leib					if (v._type == BM_USERTYPE.KNOWN_BOT) { // known bots
441294f6af8SSascha Leib
442b82cba27SSascha Leib						this.data.bots.known += v._pageViews.length;
443294f6af8SSascha Leib						this.groups.knownBots.push(v);
444294f6af8SSascha Leib
445f4417fdeSSascha Leib					} else if (v._type == BM_USERTYPE.KNOWN_USER) { // known users */
446f4417fdeSSascha Leib
447b82cba27SSascha Leib						this.data.bots.users += v._pageViews.length;
448294f6af8SSascha Leib						this.groups.users.push(v);
449294f6af8SSascha Leib
450f4417fdeSSascha Leib					} else {
451294f6af8SSascha Leib
452b82cba27SSascha Leib						// get evaluation:
453b82cba27SSascha Leib						const e = BotMon.live.data.rules.evaluate(v);
454b82cba27SSascha Leib						v._eval = e.rules;
455b82cba27SSascha Leib						v._botVal = e.val;
456b82cba27SSascha Leib
457b82cba27SSascha Leib						if (e.isBot) { // likely bots
458b82cba27SSascha Leib							v._type = BM_USERTYPE.LIKELY_BOT;
459b82cba27SSascha Leib							this.data.bots.suspected += v._pageViews.length;
46093a5b18bSSascha Leib							this.groups.suspectedBots.push(v);
461b82cba27SSascha Leib						} else { // probably humans
462b82cba27SSascha Leib							v._type = BM_USERTYPE.HUMAN;
463b82cba27SSascha Leib							this.data.bots.human += v._pageViews.length;
464b82cba27SSascha Leib							this.groups.humans.push(v);
465b82cba27SSascha Leib						}
466b82cba27SSascha Leib						// TODO: find suspected bots
467f4417fdeSSascha Leib
468294f6af8SSascha Leib					}
469294f6af8SSascha Leib				});
470294f6af8SSascha Leib
471f4417fdeSSascha Leib				//console.log(this.data);
472f4417fdeSSascha Leib				//console.log(this.groups);
473294f6af8SSascha Leib			}
474294f6af8SSascha Leib
475294f6af8SSascha Leib		},
476294f6af8SSascha Leib
477f125bc8dSSascha Leib		bots: {
478f125bc8dSSascha Leib			// loads the list of known bots from a JSON file:
479f125bc8dSSascha Leib			init: async function() {
4807bd08c30SSascha Leib				//console.info('BotMon.live.data.bots.init()');
481f125bc8dSSascha Leib
482f125bc8dSSascha Leib				// Load the list of known bots:
4837bd08c30SSascha Leib				BotMon.live.gui.status.showBusy("Loading known bots …");
4847bd08c30SSascha Leib				const url = BotMon._baseDir + 'data/known-bots.json';
485f125bc8dSSascha Leib				try {
486f125bc8dSSascha Leib					const response = await fetch(url);
487f125bc8dSSascha Leib					if (!response.ok) {
488f125bc8dSSascha Leib						throw new Error(`${response.status} ${response.statusText}`);
489f125bc8dSSascha Leib					}
490f125bc8dSSascha Leib
49143d9de6bSSascha Leib					this._list = await response.json();
49243d9de6bSSascha Leib					this._ready = true;
493f125bc8dSSascha Leib
494f125bc8dSSascha Leib				} catch (error) {
4957bd08c30SSascha Leib					BotMon.live.gui.status.setError("Error while loading the ‘known bots’ file: " + error.message);
496f125bc8dSSascha Leib				} finally {
4977bd08c30SSascha Leib					BotMon.live.gui.status.hideBusy("Status: Done.");
4987bd08c30SSascha Leib					BotMon.live.data._dispatch('bots')
499f125bc8dSSascha Leib				}
500f125bc8dSSascha Leib			},
501f125bc8dSSascha Leib
502f125bc8dSSascha Leib			// returns bot info if the clientId matches a known bot, null otherwise:
50343d9de6bSSascha Leib			match: function(agent) {
50443d9de6bSSascha Leib				//console.info('BotMon.live.data.bots.match(',agent,')');
505f125bc8dSSascha Leib
50643d9de6bSSascha Leib				const BotList = BotMon.live.data.bots._list;
50743d9de6bSSascha Leib
50843d9de6bSSascha Leib				// default is: not found!
50943d9de6bSSascha Leib				let botInfo = null;
51043d9de6bSSascha Leib
51143d9de6bSSascha Leib				// check for known bots:
51243d9de6bSSascha Leib				BotList.find(bot => {
51343d9de6bSSascha Leib					let r = false;
5149f1ee8c1SSascha Leib					for (let j=0; j<bot.rx.length; j++) {
51543d9de6bSSascha Leib						const rxr = agent.match(new RegExp(bot.rx[j]));
51643d9de6bSSascha Leib						if (rxr) {
51743d9de6bSSascha Leib							botInfo = {
51843d9de6bSSascha Leib								n : bot.n,
51943d9de6bSSascha Leib								id: bot.id,
52043d9de6bSSascha Leib								url: bot.url,
52143d9de6bSSascha Leib								v: (rxr.length > 1 ? rxr[1] : -1)
5220359f250SSascha Leib							};
52343d9de6bSSascha Leib							r = true;
52443d9de6bSSascha Leib							break;
5259f1ee8c1SSascha Leib						}
5260359f250SSascha Leib					};
52743d9de6bSSascha Leib					return r;
52843d9de6bSSascha Leib				});
529259d3b85SSascha Leib
530259d3b85SSascha Leib				// check for unknown bots:
531259d3b85SSascha Leib				if (!botInfo) {
532259d3b85SSascha Leib					const botmatch = agent.match(/[^\s](\w*bot)[\/\s;\),$]/i);
533259d3b85SSascha Leib					if(botmatch) {
534259d3b85SSascha Leib						botInfo = {'id': "other", 'n': "Other", "bot": botmatch[0] };
535259d3b85SSascha Leib					}
5369f1ee8c1SSascha Leib				}
53743d9de6bSSascha Leib
53843d9de6bSSascha Leib				//console.log("botInfo:", botInfo);
53943d9de6bSSascha Leib				return botInfo;
540f125bc8dSSascha Leib			},
541f125bc8dSSascha Leib
54243d9de6bSSascha Leib
543f125bc8dSSascha Leib			// indicates if the list is loaded and ready to use:
544f125bc8dSSascha Leib			_ready: false,
545f125bc8dSSascha Leib
546f125bc8dSSascha Leib			// the actual bot list is stored here:
547f125bc8dSSascha Leib			_list: []
548f125bc8dSSascha Leib		},
549f125bc8dSSascha Leib
5509f1ee8c1SSascha Leib		clients: {
5519f1ee8c1SSascha Leib			// loads the list of known clients from a JSON file:
5529f1ee8c1SSascha Leib			init: async function() {
5537bd08c30SSascha Leib				//console.info('BotMon.live.data.clients.init()');
5549f1ee8c1SSascha Leib
5559f1ee8c1SSascha Leib				// Load the list of known bots:
5567bd08c30SSascha Leib				BotMon.live.gui.status.showBusy("Loading known clients");
5577bd08c30SSascha Leib				const url = BotMon._baseDir + 'data/known-clients.json';
5589f1ee8c1SSascha Leib				try {
5599f1ee8c1SSascha Leib					const response = await fetch(url);
5609f1ee8c1SSascha Leib					if (!response.ok) {
5619f1ee8c1SSascha Leib						throw new Error(`${response.status} ${response.statusText}`);
5629f1ee8c1SSascha Leib					}
5639f1ee8c1SSascha Leib
5647bd08c30SSascha Leib					BotMon.live.data.clients._list = await response.json();
5657bd08c30SSascha Leib					BotMon.live.data.clients._ready = true;
5669f1ee8c1SSascha Leib
5679f1ee8c1SSascha Leib				} catch (error) {
5687bd08c30SSascha Leib					BotMon.live.gui.status.setError("Error while loading the known clients file: " + error.message);
5699f1ee8c1SSascha Leib				} finally {
5707bd08c30SSascha Leib					BotMon.live.gui.status.hideBusy("Status: Done.");
5717bd08c30SSascha Leib					BotMon.live.data._dispatch('clients')
5729f1ee8c1SSascha Leib				}
5739f1ee8c1SSascha Leib			},
5749f1ee8c1SSascha Leib
57593a5b18bSSascha Leib			// returns bot info if the user-agent matches a known bot, null otherwise:
57643d9de6bSSascha Leib			match: function(agent) {
57743d9de6bSSascha Leib				//console.info('BotMon.live.data.clients.match(',agent,')');
5789f1ee8c1SSascha Leib
5799f1ee8c1SSascha Leib				let match = {"n": "Unknown", "v": -1, "id": null};
5809f1ee8c1SSascha Leib
58143d9de6bSSascha Leib				if (agent) {
5827bd08c30SSascha Leib					BotMon.live.data.clients._list.find(client => {
5839f1ee8c1SSascha Leib						let r = false;
5849f1ee8c1SSascha Leib						for (let j=0; j<client.rx.length; j++) {
58543d9de6bSSascha Leib							const rxr = agent.match(new RegExp(client.rx[j]));
5869f1ee8c1SSascha Leib							if (rxr) {
5879f1ee8c1SSascha Leib								match.n = client.n;
5889f1ee8c1SSascha Leib								match.v = (rxr.length > 1 ? rxr[1] : -1);
5899f1ee8c1SSascha Leib								match.id = client.id || null;
5909f1ee8c1SSascha Leib								r = true;
5919f1ee8c1SSascha Leib								break;
5929f1ee8c1SSascha Leib							}
5939f1ee8c1SSascha Leib						}
5949f1ee8c1SSascha Leib						return r;
5959f1ee8c1SSascha Leib					});
5969f1ee8c1SSascha Leib				}
5979f1ee8c1SSascha Leib
59843d9de6bSSascha Leib				//console.log(match)
5999f1ee8c1SSascha Leib				return match;
6009f1ee8c1SSascha Leib			},
6019f1ee8c1SSascha Leib
6029f1ee8c1SSascha Leib			// indicates if the list is loaded and ready to use:
6039f1ee8c1SSascha Leib			_ready: false,
6049f1ee8c1SSascha Leib
6059f1ee8c1SSascha Leib			// the actual bot list is stored here:
6069f1ee8c1SSascha Leib			_list: []
6079f1ee8c1SSascha Leib
6089f1ee8c1SSascha Leib		},
6099f1ee8c1SSascha Leib
6109f1ee8c1SSascha Leib		platforms: {
6119f1ee8c1SSascha Leib			// loads the list of known platforms from a JSON file:
6129f1ee8c1SSascha Leib			init: async function() {
6137bd08c30SSascha Leib				//console.info('BotMon.live.data.platforms.init()');
6149f1ee8c1SSascha Leib
6159f1ee8c1SSascha Leib				// Load the list of known bots:
6167bd08c30SSascha Leib				BotMon.live.gui.status.showBusy("Loading known platforms");
6177bd08c30SSascha Leib				const url = BotMon._baseDir + 'data/known-platforms.json';
6189f1ee8c1SSascha Leib				try {
6199f1ee8c1SSascha Leib					const response = await fetch(url);
6209f1ee8c1SSascha Leib					if (!response.ok) {
6219f1ee8c1SSascha Leib						throw new Error(`${response.status} ${response.statusText}`);
6229f1ee8c1SSascha Leib					}
6239f1ee8c1SSascha Leib
6247bd08c30SSascha Leib					BotMon.live.data.platforms._list = await response.json();
6257bd08c30SSascha Leib					BotMon.live.data.platforms._ready = true;
6269f1ee8c1SSascha Leib
6279f1ee8c1SSascha Leib				} catch (error) {
6287bd08c30SSascha Leib					BotMon.live.gui.status.setError("Error while loading the known platforms file: " + error.message);
6299f1ee8c1SSascha Leib				} finally {
6307bd08c30SSascha Leib					BotMon.live.gui.status.hideBusy("Status: Done.");
6317bd08c30SSascha Leib					BotMon.live.data._dispatch('platforms')
6329f1ee8c1SSascha Leib				}
6339f1ee8c1SSascha Leib			},
6349f1ee8c1SSascha Leib
6359f1ee8c1SSascha Leib			// returns bot info if the browser id matches a known platform:
6369f1ee8c1SSascha Leib			match: function(cid) {
6377bd08c30SSascha Leib				//console.info('BotMon.live.data.platforms.match(',cid,')');
6389f1ee8c1SSascha Leib
6399f1ee8c1SSascha Leib				let match = {"n": "Unknown", "id": null};
6409f1ee8c1SSascha Leib
6419f1ee8c1SSascha Leib				if (cid) {
6427bd08c30SSascha Leib					BotMon.live.data.platforms._list.find(platform => {
6439f1ee8c1SSascha Leib						let r = false;
6449f1ee8c1SSascha Leib						for (let j=0; j<platform.rx.length; j++) {
6459f1ee8c1SSascha Leib							const rxr = cid.match(new RegExp(platform.rx[j]));
6469f1ee8c1SSascha Leib							if (rxr) {
6479f1ee8c1SSascha Leib								match.n = platform.n;
6489f1ee8c1SSascha Leib								match.v = (rxr.length > 1 ? rxr[1] : -1);
6499f1ee8c1SSascha Leib								match.id = platform.id || null;
6509f1ee8c1SSascha Leib								r = true;
6519f1ee8c1SSascha Leib								break;
6529f1ee8c1SSascha Leib							}
6539f1ee8c1SSascha Leib						}
6549f1ee8c1SSascha Leib						return r;
6559f1ee8c1SSascha Leib					});
6569f1ee8c1SSascha Leib				}
6579f1ee8c1SSascha Leib
6589f1ee8c1SSascha Leib				return match;
6599f1ee8c1SSascha Leib			},
6609f1ee8c1SSascha Leib
6619f1ee8c1SSascha Leib			// indicates if the list is loaded and ready to use:
6629f1ee8c1SSascha Leib			_ready: false,
6639f1ee8c1SSascha Leib
6649f1ee8c1SSascha Leib			// the actual bot list is stored here:
6659f1ee8c1SSascha Leib			_list: []
6669f1ee8c1SSascha Leib
6679f1ee8c1SSascha Leib		},
6689f1ee8c1SSascha Leib
669b82cba27SSascha Leib		rules: {
670b82cba27SSascha Leib			// loads the list of rules and settings from a JSON file:
671b82cba27SSascha Leib			init: async function() {
672b82cba27SSascha Leib				//console.info('BotMon.live.data.rules.init()');
673b82cba27SSascha Leib
674b82cba27SSascha Leib				// Load the list of known bots:
675b82cba27SSascha Leib				BotMon.live.gui.status.showBusy("Loading list of rules …");
676b82cba27SSascha Leib				const url = BotMon._baseDir + 'data/rules.json';
677b82cba27SSascha Leib				try {
678b82cba27SSascha Leib					const response = await fetch(url);
679b82cba27SSascha Leib					if (!response.ok) {
680b82cba27SSascha Leib						throw new Error(`${response.status} ${response.statusText}`);
681b82cba27SSascha Leib					}
682b82cba27SSascha Leib
683b82cba27SSascha Leib					const json = await response.json();
684b82cba27SSascha Leib
685b82cba27SSascha Leib					if (json.rules) {
686b82cba27SSascha Leib						this._rulesList = json.rules;
687b82cba27SSascha Leib					}
688b82cba27SSascha Leib
689b82cba27SSascha Leib					if (json.threshold) {
690b82cba27SSascha Leib						this._threshold = json.threshold;
691b82cba27SSascha Leib					}
692b82cba27SSascha Leib
693b82cba27SSascha Leib					this._ready = true;
694b82cba27SSascha Leib
695b82cba27SSascha Leib				} catch (error) {
696b82cba27SSascha Leib					BotMon.live.gui.status.setError("Error while loading the ‘rules’ file: " + error.message);
697b82cba27SSascha Leib				} finally {
698b82cba27SSascha Leib					BotMon.live.gui.status.hideBusy("Status: Done.");
699b82cba27SSascha Leib					BotMon.live.data._dispatch('rules')
700b82cba27SSascha Leib				}
701b82cba27SSascha Leib			},
702b82cba27SSascha Leib
703b82cba27SSascha Leib			_rulesList: [], // list of rules to find out if a visitor is a bot
704b82cba27SSascha Leib			_threshold: 100, // above this, it is considered a bot.
705b82cba27SSascha Leib
706b82cba27SSascha Leib			// returns a descriptive text for a rule id
707b82cba27SSascha Leib			getRuleInfo: function(ruleId) {
708b82cba27SSascha Leib				// console.info('getRuleInfo', ruleId);
709b82cba27SSascha Leib
710b82cba27SSascha Leib				// shortcut for neater code:
711b82cba27SSascha Leib				const me = BotMon.live.data.rules;
712b82cba27SSascha Leib
713b82cba27SSascha Leib				for (let i=0; i<me._rulesList.length; i++) {
714b82cba27SSascha Leib					const rule = me._rulesList[i];
715b82cba27SSascha Leib					if (rule.id == ruleId) {
716b82cba27SSascha Leib						return rule;
717b82cba27SSascha Leib					}
718b82cba27SSascha Leib				}
719b82cba27SSascha Leib				return null;
720b82cba27SSascha Leib
721b82cba27SSascha Leib			},
722b82cba27SSascha Leib
723b82cba27SSascha Leib			// evaluate a visitor for lkikelihood of being a bot
724b82cba27SSascha Leib			evaluate: function(visitor) {
725b82cba27SSascha Leib
726b82cba27SSascha Leib				// shortcut for neater code:
727b82cba27SSascha Leib				const me = BotMon.live.data.rules;
728b82cba27SSascha Leib
729b82cba27SSascha Leib				let r =  {	// evaluation result
730b82cba27SSascha Leib					'val': 0,
731b82cba27SSascha Leib					'rules': [],
732b82cba27SSascha Leib					'isBot': false
733b82cba27SSascha Leib				};
734b82cba27SSascha Leib
735b82cba27SSascha Leib				for (let i=0; i<me._rulesList.length; i++) {
736b82cba27SSascha Leib					const rule = me._rulesList[i];
737b82cba27SSascha Leib					const params = ( rule.params ? rule.params : [] );
738b82cba27SSascha Leib
739b82cba27SSascha Leib					if (rule.func) { // rule is calling a function
740b82cba27SSascha Leib						if (me.func[rule.func]) {
741b82cba27SSascha Leib							if(me.func[rule.func](visitor, ...params)) {
742b82cba27SSascha Leib								r.val += rule.bot;
743b82cba27SSascha Leib								r.rules.push(rule.id)
744b82cba27SSascha Leib							}
745b82cba27SSascha Leib						} else {
746b82cba27SSascha Leib							//console.warn("Unknown rule function: “${rule.func}”. Ignoring rule.")
747b82cba27SSascha Leib						}
748b82cba27SSascha Leib					}
749b82cba27SSascha Leib				}
750b82cba27SSascha Leib
751b82cba27SSascha Leib				// is a bot?
752b82cba27SSascha Leib				r.isBot = (r.val >= me._threshold);
753b82cba27SSascha Leib
754b82cba27SSascha Leib				return r;
755b82cba27SSascha Leib			},
756b82cba27SSascha Leib
757b82cba27SSascha Leib			// list of functions that can be called by the rules list to evaluate a visitor:
758b82cba27SSascha Leib			func: {
759b82cba27SSascha Leib
760b82cba27SSascha Leib				// check if client is one of the obsolete ones:
76113592cacSSascha Leib				obsoleteClient: function(visitor, ...clients) {
762b82cba27SSascha Leib
763b82cba27SSascha Leib					const clientId = ( visitor._client ? visitor._client.id : '');
76413592cacSSascha Leib					return clients.includes(clientId);
765b82cba27SSascha Leib				},
766b82cba27SSascha Leib
767b82cba27SSascha Leib				// check if OS/Platform is one of the obsolete ones:
76813592cacSSascha Leib				obsoletePlatform: function(visitor, ...platforms) {
769b82cba27SSascha Leib
77013592cacSSascha Leib					const pId = ( visitor._platform ? visitor._platform.id : '');
77113592cacSSascha Leib					return platforms.includes(pId);
772b82cba27SSascha Leib				},
773b82cba27SSascha Leib
774b82cba27SSascha Leib				// client does not use JavaScript:
775b82cba27SSascha Leib				noJavaScript: function(visitor) {
776b82cba27SSascha Leib					return (visitor._jsClient === false);
777b82cba27SSascha Leib				},
778b82cba27SSascha Leib
779b82cba27SSascha Leib				// are there at lest num pages loaded?
780b82cba27SSascha Leib				smallPageCount: function(visitor, num) {
781b82cba27SSascha Leib					return (visitor._pageViews.length <= Number(num));
782b82cba27SSascha Leib				},
783b82cba27SSascha Leib
784b82cba27SSascha Leib				// there are no ticks recorded for a visitor
785b82cba27SSascha Leib				// note that this will also trigger the "noJavaScript" rule:
786b82cba27SSascha Leib				noTicks: function(visitor) {
7871c16f1b7SSascha Leib					return !visitor._seenBy.includes('tck');
788b82cba27SSascha Leib				},
789b82cba27SSascha Leib
790b82cba27SSascha Leib				// there are no references in any of the page visits:
791b82cba27SSascha Leib				noReferences: function(visitor) {
792b82cba27SSascha Leib					return (visitor._hasReferrer === true);
793b82cba27SSascha Leib				}
794b82cba27SSascha Leib			}
795b82cba27SSascha Leib
796b82cba27SSascha Leib		},
797b82cba27SSascha Leib
7982f2bc93aSSascha Leib		loadLogFile: async function(type, onLoaded = undefined) {
79913592cacSSascha Leib			//console.info('BotMon.live.data.loadLogFile(',type,')');
800f125bc8dSSascha Leib
801f125bc8dSSascha Leib			let typeName = '';
802f125bc8dSSascha Leib			let columns = [];
803f125bc8dSSascha Leib
804f125bc8dSSascha Leib			switch (type) {
805f125bc8dSSascha Leib				case "srv":
806f125bc8dSSascha Leib					typeName = "Server";
80793a5b18bSSascha Leib					columns = ['ts','ip','pg','id','typ','usr','agent','ref'];
808f125bc8dSSascha Leib					break;
809f125bc8dSSascha Leib				case "log":
810f125bc8dSSascha Leib					typeName = "Page load";
81193a5b18bSSascha Leib					columns = ['ts','ip','pg','id','usr','lt','ref','agent'];
812f125bc8dSSascha Leib					break;
813f125bc8dSSascha Leib				case "tck":
814f125bc8dSSascha Leib					typeName = "Ticker";
81593a5b18bSSascha Leib					columns = ['ts','ip','pg','id','agent'];
816f125bc8dSSascha Leib					break;
817f125bc8dSSascha Leib				default:
818f125bc8dSSascha Leib					console.warn(`Unknown log type ${type}.`);
819f125bc8dSSascha Leib					return;
820f125bc8dSSascha Leib			}
821f125bc8dSSascha Leib
8222f2bc93aSSascha Leib			// Show the busy indicator and set the visible status:
8237bd08c30SSascha Leib			BotMon.live.gui.status.showBusy(`Loading ${typeName} log file …`);
824f125bc8dSSascha Leib
8252f2bc93aSSascha Leib			// compose the URL from which to load:
8267bd08c30SSascha Leib			const url = BotMon._baseDir + `logs/${BotMon._today}.${type}.txt`;
8272f2bc93aSSascha Leib			//console.log("Loading:",url);
828f125bc8dSSascha Leib
8292f2bc93aSSascha Leib			// fetch the data:
830f125bc8dSSascha Leib			try {
831f125bc8dSSascha Leib				const response = await fetch(url);
832f125bc8dSSascha Leib				if (!response.ok) {
833f125bc8dSSascha Leib					throw new Error(`${response.status} ${response.statusText}`);
834f125bc8dSSascha Leib				}
835f125bc8dSSascha Leib
8362f2bc93aSSascha Leib				const logtxt = await response.text();
837f125bc8dSSascha Leib
8382f2bc93aSSascha Leib				logtxt.split('\n').forEach((line) => {
8392f2bc93aSSascha Leib					if (line.trim() === '') return; // skip empty lines
8402f2bc93aSSascha Leib					const cols = line.split('\t');
8412f2bc93aSSascha Leib
8422f2bc93aSSascha Leib					// assign the columns to an object:
8432f2bc93aSSascha Leib					const data = {};
8442f2bc93aSSascha Leib					cols.forEach( (colVal,i) => {
8452f2bc93aSSascha Leib						colName = columns[i] || `col${i}`;
84643d9de6bSSascha Leib						const colValue = (colName == 'ts' ? new Date(colVal) : colVal.trim());
8472f2bc93aSSascha Leib						data[colName] = colValue;
8482f2bc93aSSascha Leib					});
8492f2bc93aSSascha Leib
8502f2bc93aSSascha Leib					// register the visit in the model:
8512f2bc93aSSascha Leib					switch(type) {
8522f2bc93aSSascha Leib						case 'srv':
85343d9de6bSSascha Leib							BotMon.live.data.model.registerVisit(data, type);
8542f2bc93aSSascha Leib							break;
8552f2bc93aSSascha Leib						case 'log':
85643d9de6bSSascha Leib							data.typ = 'js';
8577bd08c30SSascha Leib							BotMon.live.data.model.updateVisit(data);
8582f2bc93aSSascha Leib							break;
8599f1ee8c1SSascha Leib						case 'tck':
86043d9de6bSSascha Leib							data.typ = 'js';
8617bd08c30SSascha Leib							BotMon.live.data.model.updateTicks(data);
8629f1ee8c1SSascha Leib							break;
8632f2bc93aSSascha Leib						default:
8642f2bc93aSSascha Leib							console.warn(`Unknown log type ${type}.`);
8652f2bc93aSSascha Leib							return;
8662f2bc93aSSascha Leib					}
8672f2bc93aSSascha Leib				});
8682f2bc93aSSascha Leib
8692f2bc93aSSascha Leib				if (onLoaded) {
8702f2bc93aSSascha Leib					onLoaded(); // callback after loading is finished.
8712f2bc93aSSascha Leib				}
8722f2bc93aSSascha Leib
873f125bc8dSSascha Leib			} catch (error) {
8747bd08c30SSascha Leib				BotMon.live.gui.status.setError(`Error while loading the ${typeName} log file: ${error.message}.`);
875f125bc8dSSascha Leib			} finally {
8767bd08c30SSascha Leib				BotMon.live.gui.status.hideBusy("Status: Done.");
877f125bc8dSSascha Leib			}
878f125bc8dSSascha Leib		}
879f125bc8dSSascha Leib	},
880f125bc8dSSascha Leib
881294f6af8SSascha Leib	gui: {
88293a5b18bSSascha Leib		init: function() {
88393a5b18bSSascha Leib			// init the lists view:
88493a5b18bSSascha Leib			this.lists.init();
88593a5b18bSSascha Leib		},
886294f6af8SSascha Leib
887294f6af8SSascha Leib		overview: {
888294f6af8SSascha Leib			make: function() {
889f4417fdeSSascha Leib
8907bd08c30SSascha Leib				const data = BotMon.live.data.analytics.data;
8917bd08c30SSascha Leib				const parent = document.getElementById('botmon__today__content');
892f4417fdeSSascha Leib
893f4417fdeSSascha Leib				// shortcut for neater code:
894f4417fdeSSascha Leib				const makeElement = BotMon.t._makeElement;
895f4417fdeSSascha Leib
896294f6af8SSascha Leib				if (parent) {
897ade4db36SSascha Leib
898b82cba27SSascha Leib					const bounceRate = Math.round(data.totalVisits / data.totalPageViews * 100);
899ade4db36SSascha Leib
900294f6af8SSascha Leib					jQuery(parent).prepend(jQuery(`
9017bd08c30SSascha Leib						<details id="botmon__today__overview" open>
9029bc80cc5SSascha Leib							<summary>Overview</summary>
9039bc80cc5SSascha Leib							<div class="grid-3-columns">
9049bc80cc5SSascha Leib								<dl>
9059bc80cc5SSascha Leib									<dt>Web metrics</dt>
906b82cba27SSascha Leib									<dd><span>Total page views:</span><strong>${data.totalPageViews}</strong></dd>
907b82cba27SSascha Leib									<dd><span>Total visitors (est.):</span><span>${data.totalVisits}</span></dd>
908b82cba27SSascha Leib									<dd><span>Bounce rate (est.):</span><span>${bounceRate}%</span></dd>
9099bc80cc5SSascha Leib								</dl>
9109bc80cc5SSascha Leib								<dl>
91113592cacSSascha Leib									<dt>Bots vs. Humans (page views)</dt>
912b82cba27SSascha Leib									<dd><span>Registered users:</span><strong>${data.bots.users}</strong></dd>
913b82cba27SSascha Leib									<dd><span>Probably humans:</span><strong>${data.bots.human}</strong></dd>
914b82cba27SSascha Leib									<dd><span>Suspected bots:</span><strong>${data.bots.suspected}</strong></dd>
915b82cba27SSascha Leib									<dd><span>Known bots:</span><strong>${data.bots.known}</strong></dd>
9169bc80cc5SSascha Leib								</dl>
917f4417fdeSSascha Leib								<dl id="botmon__botslist"></dl>
9189bc80cc5SSascha Leib							</div>
9199bc80cc5SSascha Leib						</details>
920294f6af8SSascha Leib					`));
921f4417fdeSSascha Leib
922f4417fdeSSascha Leib					// update known bots list:
923f4417fdeSSascha Leib					const block = document.getElementById('botmon__botslist');
92413592cacSSascha Leib					block.innerHTML = "<dt>Top known bots (page views)</dt>";
925f4417fdeSSascha Leib
926f4417fdeSSascha Leib					let bots = BotMon.live.data.analytics.groups.knownBots.toSorted( (a, b) => {
927f4417fdeSSascha Leib						return b._pageViews.length - a._pageViews.length;
928f4417fdeSSascha Leib					});
929f4417fdeSSascha Leib
930f4417fdeSSascha Leib					for (let i=0; i < Math.min(bots.length, 4); i++) {
931f4417fdeSSascha Leib						const dd = makeElement('dd');
932f4417fdeSSascha Leib						dd.appendChild(makeElement('span', {'class': 'bot bot_' + bots[i]._bot.id}, bots[i]._bot.n));
933b82cba27SSascha Leib						dd.appendChild(makeElement('strong', undefined, bots[i]._pageViews.length));
934f4417fdeSSascha Leib						block.appendChild(dd);
935f4417fdeSSascha Leib					}
936294f6af8SSascha Leib				}
937294f6af8SSascha Leib			}
938294f6af8SSascha Leib		},
939f4417fdeSSascha Leib
940f125bc8dSSascha Leib		status: {
941f125bc8dSSascha Leib			setText: function(txt) {
9427bd08c30SSascha Leib				const el = document.getElementById('botmon__today__status');
9437bd08c30SSascha Leib				if (el && BotMon.live.gui.status._errorCount <= 0) {
944f125bc8dSSascha Leib					el.innerText = txt;
945f125bc8dSSascha Leib				}
946f125bc8dSSascha Leib			},
947f125bc8dSSascha Leib
948f125bc8dSSascha Leib			setTitle: function(html) {
9497bd08c30SSascha Leib				const el = document.getElementById('botmon__today__title');
950f125bc8dSSascha Leib				if (el) {
951f125bc8dSSascha Leib					el.innerHTML = html;
952f125bc8dSSascha Leib				}
953f125bc8dSSascha Leib			},
954f125bc8dSSascha Leib
955f125bc8dSSascha Leib			setError: function(txt) {
956f125bc8dSSascha Leib				console.error(txt);
9577bd08c30SSascha Leib				BotMon.live.gui.status._errorCount += 1;
9587bd08c30SSascha Leib				const el = document.getElementById('botmon__today__status');
959f125bc8dSSascha Leib				if (el) {
960f125bc8dSSascha Leib					el.innerText = "An error occured. See the browser log for details!";
961f125bc8dSSascha Leib					el.classList.add('error');
962f125bc8dSSascha Leib				}
963f125bc8dSSascha Leib			},
964f125bc8dSSascha Leib			_errorCount: 0,
965f125bc8dSSascha Leib
966f125bc8dSSascha Leib			showBusy: function(txt = null) {
9677bd08c30SSascha Leib				BotMon.live.gui.status._busyCount += 1;
9687bd08c30SSascha Leib				const el = document.getElementById('botmon__today__busy');
969f125bc8dSSascha Leib				if (el) {
970f125bc8dSSascha Leib					el.style.display = 'inline-block';
971f125bc8dSSascha Leib				}
9727bd08c30SSascha Leib				if (txt) BotMon.live.gui.status.setText(txt);
973f125bc8dSSascha Leib			},
974f125bc8dSSascha Leib			_busyCount: 0,
975f125bc8dSSascha Leib
976f125bc8dSSascha Leib			hideBusy: function(txt = null) {
9777bd08c30SSascha Leib				const el = document.getElementById('botmon__today__busy');
9787bd08c30SSascha Leib				BotMon.live.gui.status._busyCount -= 1;
9797bd08c30SSascha Leib				if (BotMon.live.gui.status._busyCount <= 0) {
980f125bc8dSSascha Leib					if (el) el.style.display = 'none';
9817bd08c30SSascha Leib					if (txt) BotMon.live.gui.status.setText(txt);
982f125bc8dSSascha Leib				}
983f125bc8dSSascha Leib			}
98493a5b18bSSascha Leib		},
98593a5b18bSSascha Leib
98693a5b18bSSascha Leib		lists: {
98793a5b18bSSascha Leib			init: function() {
98893a5b18bSSascha Leib
98913592cacSSascha Leib				// function shortcut:
99013592cacSSascha Leib				const makeElement = BotMon.t._makeElement;
99113592cacSSascha Leib
99293a5b18bSSascha Leib				const parent = document.getElementById('botmon__today__visitorlists');
99393a5b18bSSascha Leib				if (parent) {
99493a5b18bSSascha Leib
99593a5b18bSSascha Leib					for (let i=0; i < 4; i++) {
99693a5b18bSSascha Leib
99793a5b18bSSascha Leib						// change the id and title by number:
99893a5b18bSSascha Leib						let listTitle = '';
99993a5b18bSSascha Leib						let listId = '';
100093a5b18bSSascha Leib						switch (i) {
100193a5b18bSSascha Leib							case 0:
100293a5b18bSSascha Leib								listTitle = "Registered users";
100393a5b18bSSascha Leib								listId = 'users';
100493a5b18bSSascha Leib								break;
100593a5b18bSSascha Leib							case 1:
100693a5b18bSSascha Leib								listTitle = "Probably humans";
100793a5b18bSSascha Leib								listId = 'humans';
100893a5b18bSSascha Leib								break;
100993a5b18bSSascha Leib							case 2:
101093a5b18bSSascha Leib								listTitle = "Suspected bots";
101193a5b18bSSascha Leib								listId = 'suspectedBots';
101293a5b18bSSascha Leib								break;
101393a5b18bSSascha Leib							case 3:
101493a5b18bSSascha Leib								listTitle = "Known bots";
101593a5b18bSSascha Leib								listId = 'knownBots';
101693a5b18bSSascha Leib								break;
101793a5b18bSSascha Leib							default:
1018*4c5062c1SSascha Leib								console.warn('Unknown list number.');
1019f125bc8dSSascha Leib						}
1020294f6af8SSascha Leib
102113592cacSSascha Leib						const details = makeElement('details', {
102293a5b18bSSascha Leib							'data-group': listId,
102393a5b18bSSascha Leib							'data-loaded': false
102493a5b18bSSascha Leib						});
102513592cacSSascha Leib						const title = details.appendChild(makeElement('summary'));
1026*4c5062c1SSascha Leib						title.appendChild(makeElement('span', {'class':'title'}, listTitle));
1027*4c5062c1SSascha Leib						title.appendChild(makeElement('span', {'class':'counter'}, '–'));
102893a5b18bSSascha Leib						details.addEventListener("toggle", this._onDetailsToggle);
102993a5b18bSSascha Leib
103093a5b18bSSascha Leib						parent.appendChild(details);
103193a5b18bSSascha Leib
103293a5b18bSSascha Leib					}
103393a5b18bSSascha Leib				}
103493a5b18bSSascha Leib			},
103593a5b18bSSascha Leib
103693a5b18bSSascha Leib			_onDetailsToggle: function(e) {
1037f4417fdeSSascha Leib				//console.info('BotMon.live.gui.lists._onDetailsToggle()');
103893a5b18bSSascha Leib
103993a5b18bSSascha Leib				const target = e.target;
104093a5b18bSSascha Leib
104193a5b18bSSascha Leib				if (target.getAttribute('data-loaded') == 'false') { // only if not loaded yet
104293a5b18bSSascha Leib					target.setAttribute('data-loaded', 'loading');
104393a5b18bSSascha Leib
104493a5b18bSSascha Leib					const fillType = target.getAttribute('data-group');
104593a5b18bSSascha Leib					const fillList = BotMon.live.data.analytics.groups[fillType];
104693a5b18bSSascha Leib					if (fillList && fillList.length > 0) {
104793a5b18bSSascha Leib
104893a5b18bSSascha Leib						const ul = BotMon.t._makeElement('ul');
104993a5b18bSSascha Leib
105093a5b18bSSascha Leib						fillList.forEach( (it) => {
105193a5b18bSSascha Leib							ul.appendChild(BotMon.live.gui.lists._makeVisitorItem(it, fillType));
105293a5b18bSSascha Leib						});
105393a5b18bSSascha Leib
105493a5b18bSSascha Leib						target.appendChild(ul);
105593a5b18bSSascha Leib						target.setAttribute('data-loaded', 'true');
105693a5b18bSSascha Leib					} else {
105793a5b18bSSascha Leib						target.setAttribute('data-loaded', 'false');
105893a5b18bSSascha Leib					}
105993a5b18bSSascha Leib
106093a5b18bSSascha Leib				}
106193a5b18bSSascha Leib			},
106293a5b18bSSascha Leib
106393a5b18bSSascha Leib			_makeVisitorItem: function(data, type) {
106493a5b18bSSascha Leib
106593a5b18bSSascha Leib				// shortcut for neater code:
106693a5b18bSSascha Leib				const make = BotMon.t._makeElement;
106793a5b18bSSascha Leib
106843d9de6bSSascha Leib				let ipType = ( data.ip.indexOf(':') >= 0 ? '6' : '4' );
106943d9de6bSSascha Leib
107093a5b18bSSascha Leib				const li = make('li'); // root list item
107193a5b18bSSascha Leib				const details = make('details');
107293a5b18bSSascha Leib				const summary = make('summary');
107393a5b18bSSascha Leib				details.appendChild(summary);
107493a5b18bSSascha Leib
107593a5b18bSSascha Leib				const span1 = make('span'); /* left-hand group */
107693a5b18bSSascha Leib
1077f4417fdeSSascha Leib				const platformName = (data._platform ? data._platform.n : 'Unknown');
1078f4417fdeSSascha Leib				const clientName = (data._client ? data._client.n: 'Unknown');
1079f4417fdeSSascha Leib
1080f4417fdeSSascha Leib				if (data._type == BM_USERTYPE.KNOWN_BOT) { /* Bot only */
108193a5b18bSSascha Leib
1082259d3b85SSascha Leib					const botName = ( data._bot && data._bot.n ? data._bot.n : "Unknown");
108343d9de6bSSascha Leib					span1.appendChild(make('span', { /* Bot */
108443d9de6bSSascha Leib						'class': 'bot bot_' + (data._bot ? data._bot.id : 'unknown'),
1085259d3b85SSascha Leib						'title': "Bot: " + botName
1086259d3b85SSascha Leib					}, botName));
108743d9de6bSSascha Leib
1088f4417fdeSSascha Leib				} else if (data._type == BM_USERTYPE.KNOWN_USER) { /* User only */
108943d9de6bSSascha Leib
109043d9de6bSSascha Leib					span1.appendChild(make('span', { /* User */
1091f4417fdeSSascha Leib						'class': 'user_known',
109243d9de6bSSascha Leib						'title': "User: " + data.usr
109343d9de6bSSascha Leib					}, data.usr));
109443d9de6bSSascha Leib
109543d9de6bSSascha Leib				} else { /* others */
109643d9de6bSSascha Leib
109743d9de6bSSascha Leib					if (data.ip == '127.0.0.1' || data.ip == '::1' ) ipType = '0';
109843d9de6bSSascha Leib					span1.appendChild(make('span', { /* IP-Address */
109943d9de6bSSascha Leib						'class': 'ipaddr ip' + ipType,
110043d9de6bSSascha Leib						'title': "IP-Address: " + data.ip
110143d9de6bSSascha Leib					}, data.ip));
110243d9de6bSSascha Leib
110343d9de6bSSascha Leib				}
110493a5b18bSSascha Leib
1105f4417fdeSSascha Leib				if (data._type !== BM_USERTYPE.KNOWN_BOT) { /* Not for bots */
110693a5b18bSSascha Leib					span1.appendChild(make('span', { /* Platform */
110793a5b18bSSascha Leib						'class': 'icon platform platform_' + (data._platform ? data._platform.id : 'unknown'),
110893a5b18bSSascha Leib						'title': "Platform: " + platformName
110993a5b18bSSascha Leib					}, platformName));
111093a5b18bSSascha Leib
111193a5b18bSSascha Leib					span1.appendChild(make('span', { /* Client */
111293a5b18bSSascha Leib						'class': 'icon client client_' + (data._client ? data._client.id : 'unknown'),
111393a5b18bSSascha Leib						'title': "Client: " + clientName
111493a5b18bSSascha Leib					}, clientName));
1115f4417fdeSSascha Leib				}
111693a5b18bSSascha Leib
111793a5b18bSSascha Leib				summary.appendChild(span1);
111893a5b18bSSascha Leib				const span2 = make('span'); /* right-hand group */
111993a5b18bSSascha Leib
1120f4417fdeSSascha Leib				span2.appendChild(make('span', { /* page views */
1121f4417fdeSSascha Leib					'class': 'pageviews'
1122f4417fdeSSascha Leib				}, data._pageViews.length));
112393a5b18bSSascha Leib
112493a5b18bSSascha Leib				summary.appendChild(span2);
112593a5b18bSSascha Leib
112693a5b18bSSascha Leib				// create expanable section:
112793a5b18bSSascha Leib
112893a5b18bSSascha Leib				const dl = make('dl', {'class': 'visitor_details'});
112993a5b18bSSascha Leib
1130f4417fdeSSascha Leib				if (data._type == BM_USERTYPE.KNOWN_BOT) {
1131f4417fdeSSascha Leib
1132f4417fdeSSascha Leib					dl.appendChild(make('dt', {}, "Bot name:")); /* bot info */
113343d9de6bSSascha Leib					dl.appendChild(make('dd', {'class': 'has_icon bot bot_' + (data._bot ? data._bot.id : 'unknown')},
113443d9de6bSSascha Leib						(data._bot ? data._bot.n : 'Unknown')));
1135f4417fdeSSascha Leib
1136f4417fdeSSascha Leib					if (data._bot && data._bot.url) {
1137f4417fdeSSascha Leib						dl.appendChild(make('dt', {}, "Bot info:")); /* bot info */
1138f4417fdeSSascha Leib						const botInfoDd = dl.appendChild(make('dd'));
1139f4417fdeSSascha Leib						botInfoDd.appendChild(make('a', {
1140f4417fdeSSascha Leib							'href': data._bot.url,
1141f4417fdeSSascha Leib							'target': '_blank'
1142f4417fdeSSascha Leib						}, data._bot.url)); /* bot info link*/
1143f4417fdeSSascha Leib
114443d9de6bSSascha Leib					}
114543d9de6bSSascha Leib
1146f4417fdeSSascha Leib				} else { /* not for bots */
1147f4417fdeSSascha Leib
114893a5b18bSSascha Leib					dl.appendChild(make('dt', {}, "Client:")); /* client */
114993a5b18bSSascha Leib					dl.appendChild(make('dd', {'class': 'has_icon client_' + (data._client ? data._client.id : 'unknown')},
115093a5b18bSSascha Leib						clientName + ( data._client.v > 0 ? ' (' + data._client.v + ')' : '' ) ));
115193a5b18bSSascha Leib
115293a5b18bSSascha Leib					dl.appendChild(make('dt', {}, "Platform:")); /* platform */
115393a5b18bSSascha Leib					dl.appendChild(make('dd', {'class': 'has_icon platform_' + (data._platform ? data._platform.id : 'unknown')},
115493a5b18bSSascha Leib						platformName + ( data._platform.v > 0 ? ' (' + data._platform.v + ')' : '' ) ));
115593a5b18bSSascha Leib
115693a5b18bSSascha Leib					dl.appendChild(make('dt', {}, "IP-Address:"));
115793a5b18bSSascha Leib					dl.appendChild(make('dd', {'class': 'has_icon ip' + ipType}, data.ip));
1158b2e3bd8bSSascha Leib
1159b2e3bd8bSSascha Leib					dl.appendChild(make('dt', {}, "ID:"));
1160b2e3bd8bSSascha Leib					dl.appendChild(make('dd', {'class': 'has_icon ip' + data.typ}, data.id));
1161259d3b85SSascha Leib				}
116293a5b18bSSascha Leib
116393a5b18bSSascha Leib				if ((data._lastSeen - data._firstSeen) < 1) {
116493a5b18bSSascha Leib					dl.appendChild(make('dt', {}, "Seen:"));
116593a5b18bSSascha Leib					dl.appendChild(make('dd', {'class': 'seen'}, data._firstSeen.toLocaleString()));
116693a5b18bSSascha Leib				} else {
116793a5b18bSSascha Leib					dl.appendChild(make('dt', {}, "First seen:"));
116893a5b18bSSascha Leib					dl.appendChild(make('dd', {'class': 'firstSeen'}, data._firstSeen.toLocaleString()));
116993a5b18bSSascha Leib					dl.appendChild(make('dt', {}, "Last seen:"));
117093a5b18bSSascha Leib					dl.appendChild(make('dd', {'class': 'lastSeen'}, data._lastSeen.toLocaleString()));
117193a5b18bSSascha Leib				}
117293a5b18bSSascha Leib
117393a5b18bSSascha Leib				dl.appendChild(make('dt', {}, "User-Agent:"));
117493a5b18bSSascha Leib				dl.appendChild(make('dd', {'class': 'agent' + ipType}, data.agent));
117593a5b18bSSascha Leib
1176f4417fdeSSascha Leib				dl.appendChild(make('dt', {}, "Visitor Type:"));
1177f4417fdeSSascha Leib				dl.appendChild(make('dd', undefined, data._type ));
1178f4417fdeSSascha Leib
1179f4417fdeSSascha Leib				dl.appendChild(make('dt', {}, "Seen by:"));
1180f4417fdeSSascha Leib				dl.appendChild(make('dd', undefined, data._seenBy.join(', ') ));
1181f4417fdeSSascha Leib
118293a5b18bSSascha Leib				dl.appendChild(make('dt', {}, "Visited pages:"));
118393a5b18bSSascha Leib				const pagesDd = make('dd', {'class': 'pages'});
118493a5b18bSSascha Leib				const pageList = make('ul');
118593a5b18bSSascha Leib				data._pageViews.forEach( (page) => {
118693a5b18bSSascha Leib					const pgLi = make('li');
118793a5b18bSSascha Leib
118893a5b18bSSascha Leib					let visitTimeStr = "Bounce";
118993a5b18bSSascha Leib					const visitDuration = page._lastSeen.getTime() - page._firstSeen.getTime();
119093a5b18bSSascha Leib					if (visitDuration > 0) {
119193a5b18bSSascha Leib						visitTimeStr = Math.floor(visitDuration / 1000) + "s";
119293a5b18bSSascha Leib					}
119393a5b18bSSascha Leib
119413592cacSSascha Leib					console.log(page);
119513592cacSSascha Leib
119693a5b18bSSascha Leib					pgLi.appendChild(make('span', {}, page.pg));
1197259d3b85SSascha Leib					// pgLi.appendChild(make('span', {}, page.ref));
1198259d3b85SSascha Leib					pgLi.appendChild(make('span', {}, ( page._seenBy ? page._seenBy.join(', ') : '—') + '; ' + page._tickCount));
1199259d3b85SSascha Leib					pgLi.appendChild(make('span', {}, page._firstSeen.toLocaleString()));
1200259d3b85SSascha Leib					pgLi.appendChild(make('span', {}, page._lastSeen.toLocaleString()));
120193a5b18bSSascha Leib					pageList.appendChild(pgLi);
120293a5b18bSSascha Leib				});
120393a5b18bSSascha Leib				pagesDd.appendChild(pageList);
120493a5b18bSSascha Leib				dl.appendChild(pagesDd);
120593a5b18bSSascha Leib
12061c16f1b7SSascha Leib				if (data._eval) {
1207b82cba27SSascha Leib					dl.appendChild(make('dt', {}, "Evaluation:"));
1208b82cba27SSascha Leib					const evalDd = make('dd');
1209bc55f6a6SSascha Leib					const testList = make('ul',{
1210bc55f6a6SSascha Leib						'class': 'eval'
1211bc55f6a6SSascha Leib					});
1212b82cba27SSascha Leib					data._eval.forEach( (test) => {
1213b82cba27SSascha Leib
1214b82cba27SSascha Leib						const tObj = BotMon.live.data.rules.getRuleInfo(test);
1215b82cba27SSascha Leib						const tDesc = tObj ? tObj.desc : test;
1216b82cba27SSascha Leib
1217b82cba27SSascha Leib						const tstLi = make('li');
1218b82cba27SSascha Leib						tstLi.appendChild(make('span', {
1219b82cba27SSascha Leib							'class': 'test test_' . test
1220b82cba27SSascha Leib						}, ( tObj ? tObj.desc : test )));
1221b82cba27SSascha Leib						tstLi.appendChild(make('span', {}, ( tObj ? tObj.bot : '—') ));
1222b82cba27SSascha Leib						testList.appendChild(tstLi);
1223b82cba27SSascha Leib					});
1224b82cba27SSascha Leib
1225bc55f6a6SSascha Leib					const tst2Li = make('li', {
1226bc55f6a6SSascha Leib						'class': 'total'
1227bc55f6a6SSascha Leib					});
1228b82cba27SSascha Leib					tst2Li.appendChild(make('span', {}, "Total:"));
1229b82cba27SSascha Leib					tst2Li.appendChild(make('span', {}, data._botVal));
1230b82cba27SSascha Leib					testList.appendChild(tst2Li);
1231b82cba27SSascha Leib
1232b82cba27SSascha Leib					evalDd.appendChild(testList);
1233b82cba27SSascha Leib					dl.appendChild(evalDd);
1234bc55f6a6SSascha Leib				}
1235b82cba27SSascha Leib
123693a5b18bSSascha Leib				details.appendChild(dl);
123793a5b18bSSascha Leib
123893a5b18bSSascha Leib				li.appendChild(details);
123993a5b18bSSascha Leib				return li;
124093a5b18bSSascha Leib			}
1241f4417fdeSSascha Leib
124293a5b18bSSascha Leib		}
1243294f6af8SSascha Leib	}
1244c7931771SSascha Leib};
1245f125bc8dSSascha Leib
12467bd08c30SSascha Leib/* launch only if the BotMon admin panel is open: */
12477bd08c30SSascha Leibif (document.getElementById('botmon__admin')) {
12487bd08c30SSascha Leib	BotMon.init();
1249f125bc8dSSascha Leib}