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) { 229259d3b85SSascha Leib console.info("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 console.log(json.rules); 687b82cba27SSascha Leib this._rulesList = json.rules; 688b82cba27SSascha Leib } 689b82cba27SSascha Leib 690b82cba27SSascha Leib if (json.threshold) { 691b82cba27SSascha Leib this._threshold = json.threshold; 692b82cba27SSascha Leib } 693b82cba27SSascha Leib 694b82cba27SSascha Leib this._ready = true; 695b82cba27SSascha Leib 696b82cba27SSascha Leib } catch (error) { 697b82cba27SSascha Leib BotMon.live.gui.status.setError("Error while loading the ‘rules’ file: " + error.message); 698b82cba27SSascha Leib } finally { 699b82cba27SSascha Leib BotMon.live.gui.status.hideBusy("Status: Done."); 700b82cba27SSascha Leib BotMon.live.data._dispatch('rules') 701b82cba27SSascha Leib } 702b82cba27SSascha Leib }, 703b82cba27SSascha Leib 704b82cba27SSascha Leib _rulesList: [], // list of rules to find out if a visitor is a bot 705b82cba27SSascha Leib _threshold: 100, // above this, it is considered a bot. 706b82cba27SSascha Leib 707b82cba27SSascha Leib // returns a descriptive text for a rule id 708b82cba27SSascha Leib getRuleInfo: function(ruleId) { 709b82cba27SSascha Leib // console.info('getRuleInfo', ruleId); 710b82cba27SSascha Leib 711b82cba27SSascha Leib // shortcut for neater code: 712b82cba27SSascha Leib const me = BotMon.live.data.rules; 713b82cba27SSascha Leib 714b82cba27SSascha Leib for (let i=0; i<me._rulesList.length; i++) { 715b82cba27SSascha Leib const rule = me._rulesList[i]; 716b82cba27SSascha Leib if (rule.id == ruleId) { 717b82cba27SSascha Leib return rule; 718b82cba27SSascha Leib } 719b82cba27SSascha Leib } 720b82cba27SSascha Leib return null; 721b82cba27SSascha Leib 722b82cba27SSascha Leib }, 723b82cba27SSascha Leib 724b82cba27SSascha Leib // evaluate a visitor for lkikelihood of being a bot 725b82cba27SSascha Leib evaluate: function(visitor) { 726b82cba27SSascha Leib 727b82cba27SSascha Leib // shortcut for neater code: 728b82cba27SSascha Leib const me = BotMon.live.data.rules; 729b82cba27SSascha Leib 730b82cba27SSascha Leib let r = { // evaluation result 731b82cba27SSascha Leib 'val': 0, 732b82cba27SSascha Leib 'rules': [], 733b82cba27SSascha Leib 'isBot': false 734b82cba27SSascha Leib }; 735b82cba27SSascha Leib 736b82cba27SSascha Leib for (let i=0; i<me._rulesList.length; i++) { 737b82cba27SSascha Leib const rule = me._rulesList[i]; 738b82cba27SSascha Leib const params = ( rule.params ? rule.params : [] ); 739b82cba27SSascha Leib 740b82cba27SSascha Leib if (rule.func) { // rule is calling a function 741b82cba27SSascha Leib if (me.func[rule.func]) { 742b82cba27SSascha Leib if(me.func[rule.func](visitor, ...params)) { 743b82cba27SSascha Leib r.val += rule.bot; 744b82cba27SSascha Leib r.rules.push(rule.id) 745b82cba27SSascha Leib } 746b82cba27SSascha Leib } else { 747b82cba27SSascha Leib //console.warn("Unknown rule function: “${rule.func}”. Ignoring rule.") 748b82cba27SSascha Leib } 749b82cba27SSascha Leib } 750b82cba27SSascha Leib } 751b82cba27SSascha Leib 752b82cba27SSascha Leib // is a bot? 753b82cba27SSascha Leib r.isBot = (r.val >= me._threshold); 754b82cba27SSascha Leib 755b82cba27SSascha Leib return r; 756b82cba27SSascha Leib }, 757b82cba27SSascha Leib 758b82cba27SSascha Leib // list of functions that can be called by the rules list to evaluate a visitor: 759b82cba27SSascha Leib func: { 760b82cba27SSascha Leib 761b82cba27SSascha Leib // check if client is one of the obsolete ones: 762b82cba27SSascha Leib obsoleteClient: function(visitor) { 763b82cba27SSascha Leib 764b82cba27SSascha Leib const obsClients = ['aol', 'msie', 'chromeold']; 765b82cba27SSascha Leib const clientId = ( visitor._client ? visitor._client.id : ''); 766b82cba27SSascha Leib return obsClients.includes(clientId); 767b82cba27SSascha Leib }, 768b82cba27SSascha Leib 769b82cba27SSascha Leib // check if OS/Platform is one of the obsolete ones: 770b82cba27SSascha Leib obsoletePlatform: function(visitor) { 771b82cba27SSascha Leib 772b82cba27SSascha Leib const obsPlatforms = ['winold', 'macosold']; 773b82cba27SSascha Leib const platformId = ( visitor._platform ? visitor._platform.id : ''); 774b82cba27SSascha Leib return obsPlatforms.includes(platformId); 775b82cba27SSascha Leib }, 776b82cba27SSascha Leib 777b82cba27SSascha Leib // client does not use JavaScript: 778b82cba27SSascha Leib noJavaScript: function(visitor) { 779b82cba27SSascha Leib return (visitor._jsClient === false); 780b82cba27SSascha Leib }, 781b82cba27SSascha Leib 782b82cba27SSascha Leib // are there at lest num pages loaded? 783b82cba27SSascha Leib smallPageCount: function(visitor, num) { 784b82cba27SSascha Leib return (visitor._pageViews.length <= Number(num)); 785b82cba27SSascha Leib }, 786b82cba27SSascha Leib 787b82cba27SSascha Leib // there are no ticks recorded for a visitor 788b82cba27SSascha Leib // note that this will also trigger the "noJavaScript" rule: 789b82cba27SSascha Leib noTicks: function(visitor) { 7901c16f1b7SSascha Leib return !visitor._seenBy.includes('tck'); 791b82cba27SSascha Leib }, 792b82cba27SSascha Leib 793b82cba27SSascha Leib // there are no references in any of the page visits: 794b82cba27SSascha Leib noReferences: function(visitor) { 795b82cba27SSascha Leib return (visitor._hasReferrer === true); 796b82cba27SSascha Leib } 797b82cba27SSascha Leib } 798b82cba27SSascha Leib 799b82cba27SSascha Leib }, 800b82cba27SSascha Leib 8012f2bc93aSSascha Leib loadLogFile: async function(type, onLoaded = undefined) { 80243d9de6bSSascha Leib console.info('BotMon.live.data.loadLogFile(',type,')'); 803f125bc8dSSascha Leib 804f125bc8dSSascha Leib let typeName = ''; 805f125bc8dSSascha Leib let columns = []; 806f125bc8dSSascha Leib 807f125bc8dSSascha Leib switch (type) { 808f125bc8dSSascha Leib case "srv": 809f125bc8dSSascha Leib typeName = "Server"; 81093a5b18bSSascha Leib columns = ['ts','ip','pg','id','typ','usr','agent','ref']; 811f125bc8dSSascha Leib break; 812f125bc8dSSascha Leib case "log": 813f125bc8dSSascha Leib typeName = "Page load"; 81493a5b18bSSascha Leib columns = ['ts','ip','pg','id','usr','lt','ref','agent']; 815f125bc8dSSascha Leib break; 816f125bc8dSSascha Leib case "tck": 817f125bc8dSSascha Leib typeName = "Ticker"; 81893a5b18bSSascha Leib columns = ['ts','ip','pg','id','agent']; 819f125bc8dSSascha Leib break; 820f125bc8dSSascha Leib default: 821f125bc8dSSascha Leib console.warn(`Unknown log type ${type}.`); 822f125bc8dSSascha Leib return; 823f125bc8dSSascha Leib } 824f125bc8dSSascha Leib 8252f2bc93aSSascha Leib // Show the busy indicator and set the visible status: 8267bd08c30SSascha Leib BotMon.live.gui.status.showBusy(`Loading ${typeName} log file …`); 827f125bc8dSSascha Leib 8282f2bc93aSSascha Leib // compose the URL from which to load: 8297bd08c30SSascha Leib const url = BotMon._baseDir + `logs/${BotMon._today}.${type}.txt`; 8302f2bc93aSSascha Leib //console.log("Loading:",url); 831f125bc8dSSascha Leib 8322f2bc93aSSascha Leib // fetch the data: 833f125bc8dSSascha Leib try { 834f125bc8dSSascha Leib const response = await fetch(url); 835f125bc8dSSascha Leib if (!response.ok) { 836f125bc8dSSascha Leib throw new Error(`${response.status} ${response.statusText}`); 837f125bc8dSSascha Leib } 838f125bc8dSSascha Leib 8392f2bc93aSSascha Leib const logtxt = await response.text(); 840f125bc8dSSascha Leib 8412f2bc93aSSascha Leib logtxt.split('\n').forEach((line) => { 8422f2bc93aSSascha Leib if (line.trim() === '') return; // skip empty lines 8432f2bc93aSSascha Leib const cols = line.split('\t'); 8442f2bc93aSSascha Leib 8452f2bc93aSSascha Leib // assign the columns to an object: 8462f2bc93aSSascha Leib const data = {}; 8472f2bc93aSSascha Leib cols.forEach( (colVal,i) => { 8482f2bc93aSSascha Leib colName = columns[i] || `col${i}`; 84943d9de6bSSascha Leib const colValue = (colName == 'ts' ? new Date(colVal) : colVal.trim()); 8502f2bc93aSSascha Leib data[colName] = colValue; 8512f2bc93aSSascha Leib }); 8522f2bc93aSSascha Leib 8532f2bc93aSSascha Leib // register the visit in the model: 8542f2bc93aSSascha Leib switch(type) { 8552f2bc93aSSascha Leib case 'srv': 85643d9de6bSSascha Leib BotMon.live.data.model.registerVisit(data, type); 8572f2bc93aSSascha Leib break; 8582f2bc93aSSascha Leib case 'log': 85943d9de6bSSascha Leib data.typ = 'js'; 8607bd08c30SSascha Leib BotMon.live.data.model.updateVisit(data); 8612f2bc93aSSascha Leib break; 8629f1ee8c1SSascha Leib case 'tck': 86343d9de6bSSascha Leib data.typ = 'js'; 8647bd08c30SSascha Leib BotMon.live.data.model.updateTicks(data); 8659f1ee8c1SSascha Leib break; 8662f2bc93aSSascha Leib default: 8672f2bc93aSSascha Leib console.warn(`Unknown log type ${type}.`); 8682f2bc93aSSascha Leib return; 8692f2bc93aSSascha Leib } 8702f2bc93aSSascha Leib }); 8712f2bc93aSSascha Leib 8722f2bc93aSSascha Leib if (onLoaded) { 8732f2bc93aSSascha Leib onLoaded(); // callback after loading is finished. 8742f2bc93aSSascha Leib } 8752f2bc93aSSascha Leib 876f125bc8dSSascha Leib } catch (error) { 8777bd08c30SSascha Leib BotMon.live.gui.status.setError(`Error while loading the ${typeName} log file: ${error.message}.`); 878f125bc8dSSascha Leib } finally { 8797bd08c30SSascha Leib BotMon.live.gui.status.hideBusy("Status: Done."); 880f125bc8dSSascha Leib } 881f125bc8dSSascha Leib } 882f125bc8dSSascha Leib }, 883f125bc8dSSascha Leib 884294f6af8SSascha Leib gui: { 88593a5b18bSSascha Leib init: function() { 88693a5b18bSSascha Leib // init the lists view: 88793a5b18bSSascha Leib this.lists.init(); 88893a5b18bSSascha Leib }, 889294f6af8SSascha Leib 890294f6af8SSascha Leib overview: { 891294f6af8SSascha Leib make: function() { 892f4417fdeSSascha Leib 8937bd08c30SSascha Leib const data = BotMon.live.data.analytics.data; 8947bd08c30SSascha Leib const parent = document.getElementById('botmon__today__content'); 895f4417fdeSSascha Leib 896f4417fdeSSascha Leib // shortcut for neater code: 897f4417fdeSSascha Leib const makeElement = BotMon.t._makeElement; 898f4417fdeSSascha Leib 899294f6af8SSascha Leib if (parent) { 900ade4db36SSascha Leib 901b82cba27SSascha Leib const bounceRate = Math.round(data.totalVisits / data.totalPageViews * 100); 902ade4db36SSascha Leib 903294f6af8SSascha Leib jQuery(parent).prepend(jQuery(` 9047bd08c30SSascha Leib <details id="botmon__today__overview" open> 9059bc80cc5SSascha Leib <summary>Overview</summary> 9069bc80cc5SSascha Leib <div class="grid-3-columns"> 9079bc80cc5SSascha Leib <dl> 9089bc80cc5SSascha Leib <dt>Web metrics</dt> 909b82cba27SSascha Leib <dd><span>Total page views:</span><strong>${data.totalPageViews}</strong></dd> 910b82cba27SSascha Leib <dd><span>Total visitors (est.):</span><span>${data.totalVisits}</span></dd> 911b82cba27SSascha Leib <dd><span>Bounce rate (est.):</span><span>${bounceRate}%</span></dd> 9129bc80cc5SSascha Leib </dl> 9139bc80cc5SSascha Leib <dl> 9149bc80cc5SSascha Leib <dt>Bots vs. Humans</dt> 915b82cba27SSascha Leib <dd><span>Registered users:</span><strong>${data.bots.users}</strong></dd> 916b82cba27SSascha Leib <dd><span>Probably humans:</span><strong>${data.bots.human}</strong></dd> 917b82cba27SSascha Leib <dd><span>Suspected bots:</span><strong>${data.bots.suspected}</strong></dd> 918b82cba27SSascha Leib <dd><span>Known bots:</span><strong>${data.bots.known}</strong></dd> 9199bc80cc5SSascha Leib </dl> 920f4417fdeSSascha Leib <dl id="botmon__botslist"></dl> 9219bc80cc5SSascha Leib </div> 9229bc80cc5SSascha Leib </details> 923294f6af8SSascha Leib `)); 924f4417fdeSSascha Leib 925f4417fdeSSascha Leib // update known bots list: 926f4417fdeSSascha Leib const block = document.getElementById('botmon__botslist'); 927f4417fdeSSascha Leib block.innerHTML = "<dt>Top known bots</dt>"; 928f4417fdeSSascha Leib 929f4417fdeSSascha Leib let bots = BotMon.live.data.analytics.groups.knownBots.toSorted( (a, b) => { 930f4417fdeSSascha Leib return b._pageViews.length - a._pageViews.length; 931f4417fdeSSascha Leib }); 932f4417fdeSSascha Leib 933f4417fdeSSascha Leib for (let i=0; i < Math.min(bots.length, 4); i++) { 934f4417fdeSSascha Leib const dd = makeElement('dd'); 935f4417fdeSSascha Leib dd.appendChild(makeElement('span', {'class': 'bot bot_' + bots[i]._bot.id}, bots[i]._bot.n)); 936b82cba27SSascha Leib dd.appendChild(makeElement('strong', undefined, bots[i]._pageViews.length)); 937f4417fdeSSascha Leib block.appendChild(dd); 938f4417fdeSSascha Leib } 939294f6af8SSascha Leib } 940294f6af8SSascha Leib } 941294f6af8SSascha Leib }, 942f4417fdeSSascha Leib 943f125bc8dSSascha Leib status: { 944f125bc8dSSascha Leib setText: function(txt) { 9457bd08c30SSascha Leib const el = document.getElementById('botmon__today__status'); 9467bd08c30SSascha Leib if (el && BotMon.live.gui.status._errorCount <= 0) { 947f125bc8dSSascha Leib el.innerText = txt; 948f125bc8dSSascha Leib } 949f125bc8dSSascha Leib }, 950f125bc8dSSascha Leib 951f125bc8dSSascha Leib setTitle: function(html) { 9527bd08c30SSascha Leib const el = document.getElementById('botmon__today__title'); 953f125bc8dSSascha Leib if (el) { 954f125bc8dSSascha Leib el.innerHTML = html; 955f125bc8dSSascha Leib } 956f125bc8dSSascha Leib }, 957f125bc8dSSascha Leib 958f125bc8dSSascha Leib setError: function(txt) { 959f125bc8dSSascha Leib console.error(txt); 9607bd08c30SSascha Leib BotMon.live.gui.status._errorCount += 1; 9617bd08c30SSascha Leib const el = document.getElementById('botmon__today__status'); 962f125bc8dSSascha Leib if (el) { 963f125bc8dSSascha Leib el.innerText = "An error occured. See the browser log for details!"; 964f125bc8dSSascha Leib el.classList.add('error'); 965f125bc8dSSascha Leib } 966f125bc8dSSascha Leib }, 967f125bc8dSSascha Leib _errorCount: 0, 968f125bc8dSSascha Leib 969f125bc8dSSascha Leib showBusy: function(txt = null) { 9707bd08c30SSascha Leib BotMon.live.gui.status._busyCount += 1; 9717bd08c30SSascha Leib const el = document.getElementById('botmon__today__busy'); 972f125bc8dSSascha Leib if (el) { 973f125bc8dSSascha Leib el.style.display = 'inline-block'; 974f125bc8dSSascha Leib } 9757bd08c30SSascha Leib if (txt) BotMon.live.gui.status.setText(txt); 976f125bc8dSSascha Leib }, 977f125bc8dSSascha Leib _busyCount: 0, 978f125bc8dSSascha Leib 979f125bc8dSSascha Leib hideBusy: function(txt = null) { 9807bd08c30SSascha Leib const el = document.getElementById('botmon__today__busy'); 9817bd08c30SSascha Leib BotMon.live.gui.status._busyCount -= 1; 9827bd08c30SSascha Leib if (BotMon.live.gui.status._busyCount <= 0) { 983f125bc8dSSascha Leib if (el) el.style.display = 'none'; 9847bd08c30SSascha Leib if (txt) BotMon.live.gui.status.setText(txt); 985f125bc8dSSascha Leib } 986f125bc8dSSascha Leib } 98793a5b18bSSascha Leib }, 98893a5b18bSSascha Leib 98993a5b18bSSascha Leib lists: { 99093a5b18bSSascha Leib init: function() { 99193a5b18bSSascha 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: 101893a5b18bSSascha Leib console.warn('Unknwon list number.'); 1019f125bc8dSSascha Leib } 1020294f6af8SSascha Leib 102193a5b18bSSascha Leib const details = BotMon.t._makeElement('details', { 102293a5b18bSSascha Leib 'data-group': listId, 102393a5b18bSSascha Leib 'data-loaded': false 102493a5b18bSSascha Leib }); 102593a5b18bSSascha Leib details.appendChild(BotMon.t._makeElement('summary', 102693a5b18bSSascha Leib undefined, 102793a5b18bSSascha Leib listTitle 102893a5b18bSSascha Leib )); 102993a5b18bSSascha Leib details.addEventListener("toggle", this._onDetailsToggle); 103093a5b18bSSascha Leib 103193a5b18bSSascha Leib parent.appendChild(details); 103293a5b18bSSascha Leib 103393a5b18bSSascha Leib } 103493a5b18bSSascha Leib } 103593a5b18bSSascha Leib }, 103693a5b18bSSascha Leib 103793a5b18bSSascha Leib _onDetailsToggle: function(e) { 1038f4417fdeSSascha Leib //console.info('BotMon.live.gui.lists._onDetailsToggle()'); 103993a5b18bSSascha Leib 104093a5b18bSSascha Leib const target = e.target; 104193a5b18bSSascha Leib 104293a5b18bSSascha Leib if (target.getAttribute('data-loaded') == 'false') { // only if not loaded yet 104393a5b18bSSascha Leib target.setAttribute('data-loaded', 'loading'); 104493a5b18bSSascha Leib 104593a5b18bSSascha Leib const fillType = target.getAttribute('data-group'); 104693a5b18bSSascha Leib const fillList = BotMon.live.data.analytics.groups[fillType]; 104793a5b18bSSascha Leib if (fillList && fillList.length > 0) { 104893a5b18bSSascha Leib 104993a5b18bSSascha Leib const ul = BotMon.t._makeElement('ul'); 105093a5b18bSSascha Leib 105193a5b18bSSascha Leib fillList.forEach( (it) => { 105293a5b18bSSascha Leib ul.appendChild(BotMon.live.gui.lists._makeVisitorItem(it, fillType)); 105393a5b18bSSascha Leib }); 105493a5b18bSSascha Leib 105593a5b18bSSascha Leib target.appendChild(ul); 105693a5b18bSSascha Leib target.setAttribute('data-loaded', 'true'); 105793a5b18bSSascha Leib } else { 105893a5b18bSSascha Leib target.setAttribute('data-loaded', 'false'); 105993a5b18bSSascha Leib } 106093a5b18bSSascha Leib 106193a5b18bSSascha Leib } 106293a5b18bSSascha Leib }, 106393a5b18bSSascha Leib 106493a5b18bSSascha Leib _makeVisitorItem: function(data, type) { 106593a5b18bSSascha Leib 106693a5b18bSSascha Leib // shortcut for neater code: 106793a5b18bSSascha Leib const make = BotMon.t._makeElement; 106893a5b18bSSascha Leib 106943d9de6bSSascha Leib let ipType = ( data.ip.indexOf(':') >= 0 ? '6' : '4' ); 107043d9de6bSSascha Leib 107193a5b18bSSascha Leib const li = make('li'); // root list item 107293a5b18bSSascha Leib const details = make('details'); 107393a5b18bSSascha Leib const summary = make('summary'); 107493a5b18bSSascha Leib details.appendChild(summary); 107593a5b18bSSascha Leib 107693a5b18bSSascha Leib const span1 = make('span'); /* left-hand group */ 107793a5b18bSSascha Leib 1078f4417fdeSSascha Leib const platformName = (data._platform ? data._platform.n : 'Unknown'); 1079f4417fdeSSascha Leib const clientName = (data._client ? data._client.n: 'Unknown'); 1080f4417fdeSSascha Leib 1081f4417fdeSSascha Leib if (data._type == BM_USERTYPE.KNOWN_BOT) { /* Bot only */ 108293a5b18bSSascha Leib 1083259d3b85SSascha Leib const botName = ( data._bot && data._bot.n ? data._bot.n : "Unknown"); 108443d9de6bSSascha Leib span1.appendChild(make('span', { /* Bot */ 108543d9de6bSSascha Leib 'class': 'bot bot_' + (data._bot ? data._bot.id : 'unknown'), 1086259d3b85SSascha Leib 'title': "Bot: " + botName 1087259d3b85SSascha Leib }, botName)); 108843d9de6bSSascha Leib 1089f4417fdeSSascha Leib } else if (data._type == BM_USERTYPE.KNOWN_USER) { /* User only */ 109043d9de6bSSascha Leib 109143d9de6bSSascha Leib span1.appendChild(make('span', { /* User */ 1092f4417fdeSSascha Leib 'class': 'user_known', 109343d9de6bSSascha Leib 'title': "User: " + data.usr 109443d9de6bSSascha Leib }, data.usr)); 109543d9de6bSSascha Leib 109643d9de6bSSascha Leib } else { /* others */ 109743d9de6bSSascha Leib 109843d9de6bSSascha Leib if (data.ip == '127.0.0.1' || data.ip == '::1' ) ipType = '0'; 109943d9de6bSSascha Leib span1.appendChild(make('span', { /* IP-Address */ 110043d9de6bSSascha Leib 'class': 'ipaddr ip' + ipType, 110143d9de6bSSascha Leib 'title': "IP-Address: " + data.ip 110243d9de6bSSascha Leib }, data.ip)); 110343d9de6bSSascha Leib 110443d9de6bSSascha Leib } 110593a5b18bSSascha Leib 1106f4417fdeSSascha Leib if (data._type !== BM_USERTYPE.KNOWN_BOT) { /* Not for bots */ 110793a5b18bSSascha Leib span1.appendChild(make('span', { /* Platform */ 110893a5b18bSSascha Leib 'class': 'icon platform platform_' + (data._platform ? data._platform.id : 'unknown'), 110993a5b18bSSascha Leib 'title': "Platform: " + platformName 111093a5b18bSSascha Leib }, platformName)); 111193a5b18bSSascha Leib 111293a5b18bSSascha Leib span1.appendChild(make('span', { /* Client */ 111393a5b18bSSascha Leib 'class': 'icon client client_' + (data._client ? data._client.id : 'unknown'), 111493a5b18bSSascha Leib 'title': "Client: " + clientName 111593a5b18bSSascha Leib }, clientName)); 1116f4417fdeSSascha Leib } 111793a5b18bSSascha Leib 111893a5b18bSSascha Leib summary.appendChild(span1); 111993a5b18bSSascha Leib const span2 = make('span'); /* right-hand group */ 112093a5b18bSSascha Leib 1121f4417fdeSSascha Leib span2.appendChild(make('span', { /* page views */ 1122f4417fdeSSascha Leib 'class': 'pageviews' 1123f4417fdeSSascha Leib }, data._pageViews.length)); 112493a5b18bSSascha Leib 112593a5b18bSSascha Leib summary.appendChild(span2); 112693a5b18bSSascha Leib 112793a5b18bSSascha Leib // create expanable section: 112893a5b18bSSascha Leib 112993a5b18bSSascha Leib const dl = make('dl', {'class': 'visitor_details'}); 113093a5b18bSSascha Leib 1131f4417fdeSSascha Leib if (data._type == BM_USERTYPE.KNOWN_BOT) { 1132f4417fdeSSascha Leib 1133f4417fdeSSascha Leib dl.appendChild(make('dt', {}, "Bot name:")); /* bot info */ 113443d9de6bSSascha Leib dl.appendChild(make('dd', {'class': 'has_icon bot bot_' + (data._bot ? data._bot.id : 'unknown')}, 113543d9de6bSSascha Leib (data._bot ? data._bot.n : 'Unknown'))); 1136f4417fdeSSascha Leib 1137f4417fdeSSascha Leib if (data._bot && data._bot.url) { 1138f4417fdeSSascha Leib dl.appendChild(make('dt', {}, "Bot info:")); /* bot info */ 1139f4417fdeSSascha Leib const botInfoDd = dl.appendChild(make('dd')); 1140f4417fdeSSascha Leib botInfoDd.appendChild(make('a', { 1141f4417fdeSSascha Leib 'href': data._bot.url, 1142f4417fdeSSascha Leib 'target': '_blank' 1143f4417fdeSSascha Leib }, data._bot.url)); /* bot info link*/ 1144f4417fdeSSascha Leib 114543d9de6bSSascha Leib } 114643d9de6bSSascha Leib 1147f4417fdeSSascha Leib } else { /* not for bots */ 1148f4417fdeSSascha Leib 114993a5b18bSSascha Leib dl.appendChild(make('dt', {}, "Client:")); /* client */ 115093a5b18bSSascha Leib dl.appendChild(make('dd', {'class': 'has_icon client_' + (data._client ? data._client.id : 'unknown')}, 115193a5b18bSSascha Leib clientName + ( data._client.v > 0 ? ' (' + data._client.v + ')' : '' ) )); 115293a5b18bSSascha Leib 115393a5b18bSSascha Leib dl.appendChild(make('dt', {}, "Platform:")); /* platform */ 115493a5b18bSSascha Leib dl.appendChild(make('dd', {'class': 'has_icon platform_' + (data._platform ? data._platform.id : 'unknown')}, 115593a5b18bSSascha Leib platformName + ( data._platform.v > 0 ? ' (' + data._platform.v + ')' : '' ) )); 115693a5b18bSSascha Leib 115793a5b18bSSascha Leib dl.appendChild(make('dt', {}, "IP-Address:")); 115893a5b18bSSascha Leib dl.appendChild(make('dd', {'class': 'has_icon ip' + ipType}, data.ip)); 1159b2e3bd8bSSascha Leib 1160b2e3bd8bSSascha Leib dl.appendChild(make('dt', {}, "ID:")); 1161b2e3bd8bSSascha Leib dl.appendChild(make('dd', {'class': 'has_icon ip' + data.typ}, data.id)); 1162259d3b85SSascha Leib } 116393a5b18bSSascha Leib 116493a5b18bSSascha Leib if ((data._lastSeen - data._firstSeen) < 1) { 116593a5b18bSSascha Leib dl.appendChild(make('dt', {}, "Seen:")); 116693a5b18bSSascha Leib dl.appendChild(make('dd', {'class': 'seen'}, data._firstSeen.toLocaleString())); 116793a5b18bSSascha Leib } else { 116893a5b18bSSascha Leib dl.appendChild(make('dt', {}, "First seen:")); 116993a5b18bSSascha Leib dl.appendChild(make('dd', {'class': 'firstSeen'}, data._firstSeen.toLocaleString())); 117093a5b18bSSascha Leib dl.appendChild(make('dt', {}, "Last seen:")); 117193a5b18bSSascha Leib dl.appendChild(make('dd', {'class': 'lastSeen'}, data._lastSeen.toLocaleString())); 117293a5b18bSSascha Leib } 117393a5b18bSSascha Leib 117493a5b18bSSascha Leib dl.appendChild(make('dt', {}, "User-Agent:")); 117593a5b18bSSascha Leib dl.appendChild(make('dd', {'class': 'agent' + ipType}, data.agent)); 117693a5b18bSSascha Leib 1177f4417fdeSSascha Leib dl.appendChild(make('dt', {}, "Visitor Type:")); 1178f4417fdeSSascha Leib dl.appendChild(make('dd', undefined, data._type )); 1179f4417fdeSSascha Leib 1180f4417fdeSSascha Leib dl.appendChild(make('dt', {}, "Seen by:")); 1181f4417fdeSSascha Leib dl.appendChild(make('dd', undefined, data._seenBy.join(', ') )); 1182f4417fdeSSascha Leib 118393a5b18bSSascha Leib dl.appendChild(make('dt', {}, "Visited pages:")); 118493a5b18bSSascha Leib const pagesDd = make('dd', {'class': 'pages'}); 118593a5b18bSSascha Leib const pageList = make('ul'); 118693a5b18bSSascha Leib data._pageViews.forEach( (page) => { 118793a5b18bSSascha Leib const pgLi = make('li'); 118893a5b18bSSascha Leib 118993a5b18bSSascha Leib let visitTimeStr = "Bounce"; 119093a5b18bSSascha Leib const visitDuration = page._lastSeen.getTime() - page._firstSeen.getTime(); 119193a5b18bSSascha Leib if (visitDuration > 0) { 119293a5b18bSSascha Leib visitTimeStr = Math.floor(visitDuration / 1000) + "s"; 119393a5b18bSSascha Leib } 119493a5b18bSSascha Leib 119593a5b18bSSascha Leib pgLi.appendChild(make('span', {}, page.pg)); 1196259d3b85SSascha Leib // pgLi.appendChild(make('span', {}, page.ref)); 1197259d3b85SSascha Leib pgLi.appendChild(make('span', {}, ( page._seenBy ? page._seenBy.join(', ') : '—') + '; ' + page._tickCount)); 1198259d3b85SSascha Leib pgLi.appendChild(make('span', {}, page._firstSeen.toLocaleString())); 1199259d3b85SSascha Leib pgLi.appendChild(make('span', {}, page._lastSeen.toLocaleString())); 120093a5b18bSSascha Leib pageList.appendChild(pgLi); 120193a5b18bSSascha Leib }); 120293a5b18bSSascha Leib pagesDd.appendChild(pageList); 120393a5b18bSSascha Leib dl.appendChild(pagesDd); 120493a5b18bSSascha Leib 12051c16f1b7SSascha Leib if (data._eval) { 1206b82cba27SSascha Leib dl.appendChild(make('dt', {}, "Evaluation:")); 1207b82cba27SSascha Leib const evalDd = make('dd'); 1208*bc55f6a6SSascha Leib const testList = make('ul',{ 1209*bc55f6a6SSascha Leib 'class': 'eval' 1210*bc55f6a6SSascha Leib }); 1211b82cba27SSascha Leib data._eval.forEach( (test) => { 1212b82cba27SSascha Leib console.log(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 1225*bc55f6a6SSascha Leib const tst2Li = make('li', { 1226*bc55f6a6SSascha Leib 'class': 'total' 1227*bc55f6a6SSascha 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); 1234*bc55f6a6SSascha 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}