1*f125bc8dSSascha Leib/* DokuWiki Monitor Plugin Script file */ 2*f125bc8dSSascha Leib/* 29.08.2025 - 1.0.5 - initial release */ 3*f125bc8dSSascha Leib/* Authors: Sascha Leib <ad@hominem.info> */ 4*f125bc8dSSascha Leib 5*f125bc8dSSascha Leibconst Monitor = { 6*f125bc8dSSascha Leib 7*f125bc8dSSascha Leib init: function() { 8*f125bc8dSSascha Leib //console.info('Monitor.init()'); 9*f125bc8dSSascha Leib 10*f125bc8dSSascha Leib // find the plugin basedir: 11*f125bc8dSSascha Leib this._baseDir = document.currentScript.src.substring(0, document.currentScript.src.indexOf('/exe/')) 12*f125bc8dSSascha Leib + '/plugins/monitor/'; 13*f125bc8dSSascha Leib 14*f125bc8dSSascha Leib // read the page language from the DOM: 15*f125bc8dSSascha Leib this._lang = document.getRootNode().documentElement.lang || this._lang; 16*f125bc8dSSascha Leib 17*f125bc8dSSascha Leib // get the time offset: 18*f125bc8dSSascha Leib this._timeDiff = Monitor.t._getTimeOffset(); 19*f125bc8dSSascha Leib 20*f125bc8dSSascha Leib // init the sub-objects: 21*f125bc8dSSascha Leib Monitor.t._callInit(this); 22*f125bc8dSSascha Leib }, 23*f125bc8dSSascha Leib 24*f125bc8dSSascha Leib _baseDir: null, 25*f125bc8dSSascha Leib _lang: 'en', 26*f125bc8dSSascha Leib _today: (new Date()).toISOString().slice(0, 10), 27*f125bc8dSSascha Leib _timeDiff: '', 28*f125bc8dSSascha Leib 29*f125bc8dSSascha Leib /* internal tools */ 30*f125bc8dSSascha Leib t: { 31*f125bc8dSSascha Leib 32*f125bc8dSSascha Leib /* helper function to call inits of sub-objects */ 33*f125bc8dSSascha Leib _callInit: function(obj) { 34*f125bc8dSSascha Leib //console.info('Monitor.t._callInit(obj=',obj,')'); 35*f125bc8dSSascha Leib 36*f125bc8dSSascha Leib /* call init / _init on each sub-object: */ 37*f125bc8dSSascha Leib Object.keys(obj).forEach( (key,i) => { 38*f125bc8dSSascha Leib const sub = obj[key]; 39*f125bc8dSSascha Leib let init = null; 40*f125bc8dSSascha Leib if (typeof sub === 'object' && sub.init) { 41*f125bc8dSSascha Leib init = sub.init; 42*f125bc8dSSascha Leib } 43*f125bc8dSSascha Leib 44*f125bc8dSSascha Leib // bind to object 45*f125bc8dSSascha Leib if (typeof init == 'function') { 46*f125bc8dSSascha Leib const init2 = init.bind(sub); 47*f125bc8dSSascha Leib init2(obj); 48*f125bc8dSSascha Leib } 49*f125bc8dSSascha Leib }); 50*f125bc8dSSascha Leib }, 51*f125bc8dSSascha Leib 52*f125bc8dSSascha Leib /* helper function to calculate the time difference to UTC: */ 53*f125bc8dSSascha Leib _getTimeOffset: function() { 54*f125bc8dSSascha Leib const now = new Date(); 55*f125bc8dSSascha Leib let offset = now.getTimezoneOffset(); // in minutes 56*f125bc8dSSascha Leib const sign = Math.sign(offset); // +1 or -1 57*f125bc8dSSascha Leib offset = Math.abs(offset); // always positive 58*f125bc8dSSascha Leib 59*f125bc8dSSascha Leib let hours = 0; 60*f125bc8dSSascha Leib while (offset >= 60) { 61*f125bc8dSSascha Leib hours += 1; 62*f125bc8dSSascha Leib offset -= 60; 63*f125bc8dSSascha Leib } 64*f125bc8dSSascha Leib return ( hours > 0 ? sign * hours + ' h' : '') + (offset > 0 ? ` ${offset} min` : ''); 65*f125bc8dSSascha Leib } 66*f125bc8dSSascha Leib } 67*f125bc8dSSascha Leib} 68*f125bc8dSSascha Leib 69*f125bc8dSSascha Leib/* everything specific to the "Today" tab is self-contained here: */ 70*f125bc8dSSascha LeibMonitor.today = { 71*f125bc8dSSascha Leib init: function() { 72*f125bc8dSSascha Leib //console.info('Monitor.today.init()'); 73*f125bc8dSSascha Leib 74*f125bc8dSSascha Leib // set the title: 75*f125bc8dSSascha Leib const tDiff = (Monitor._timeDiff != '' ? ` (${Monitor._timeDiff})` : ' (<abbr>UTC</abbr>)' ); 76*f125bc8dSSascha Leib Monitor.today.status.setTitle(`Showing visits for <time datetime=${Monitor._today}>${Monitor._today}</time>${tDiff}`); 77*f125bc8dSSascha Leib 78*f125bc8dSSascha Leib // init sub-objects: 79*f125bc8dSSascha Leib Monitor.t._callInit(this); 80*f125bc8dSSascha Leib }, 81*f125bc8dSSascha Leib 82*f125bc8dSSascha Leib data: { 83*f125bc8dSSascha Leib init: function() { 84*f125bc8dSSascha Leib //console.info('Monitor.today.data.init()'); 85*f125bc8dSSascha Leib 86*f125bc8dSSascha Leib // call sub-inits: 87*f125bc8dSSascha Leib Monitor.t._callInit(this); 88*f125bc8dSSascha Leib 89*f125bc8dSSascha Leib // load the first log file: 90*f125bc8dSSascha Leib Monitor.today.data.loadLogFile('srv'); 91*f125bc8dSSascha Leib }, 92*f125bc8dSSascha Leib 93*f125bc8dSSascha Leib bots: { 94*f125bc8dSSascha Leib // loads the list of known bots from a JSON file: 95*f125bc8dSSascha Leib init: async function() { 96*f125bc8dSSascha Leib //console.info('Monitor.today.data.bots.init()'); 97*f125bc8dSSascha Leib 98*f125bc8dSSascha Leib // Load the list of known bots: 99*f125bc8dSSascha Leib Monitor.today.status.showBusy("Loading known bots …"); 100*f125bc8dSSascha Leib const url = Monitor._baseDir + 'data/known-bots.json'; 101*f125bc8dSSascha Leib 102*f125bc8dSSascha Leib console.log(url); 103*f125bc8dSSascha Leib try { 104*f125bc8dSSascha Leib const response = await fetch(url); 105*f125bc8dSSascha Leib if (!response.ok) { 106*f125bc8dSSascha Leib throw new Error(`${response.status} ${response.statusText}`); 107*f125bc8dSSascha Leib } 108*f125bc8dSSascha Leib 109*f125bc8dSSascha Leib Monitor.today.data.bots._list = await response.json(); 110*f125bc8dSSascha Leib Monitor.today.data.bots._ready = true; 111*f125bc8dSSascha Leib 112*f125bc8dSSascha Leib // TODO: allow using the bots list... 113*f125bc8dSSascha Leib } catch (error) { 114*f125bc8dSSascha Leib Monitor.today.status.setError("Error while loading the ’known bots’ file: " + error.message); 115*f125bc8dSSascha Leib } finally { 116*f125bc8dSSascha Leib Monitor.today.status.hideBusy("Done."); 117*f125bc8dSSascha Leib } 118*f125bc8dSSascha Leib }, 119*f125bc8dSSascha Leib 120*f125bc8dSSascha Leib // returns bot info if the clientId matches a known bot, null otherwise: 121*f125bc8dSSascha Leib match: function(clientId) { 122*f125bc8dSSascha Leib 123*f125bc8dSSascha Leib // TODO! 124*f125bc8dSSascha Leib }, 125*f125bc8dSSascha Leib 126*f125bc8dSSascha Leib // indicates if the list is loaded and ready to use: 127*f125bc8dSSascha Leib _ready: false, 128*f125bc8dSSascha Leib 129*f125bc8dSSascha Leib // the actual bot list is stored here: 130*f125bc8dSSascha Leib _list: [] 131*f125bc8dSSascha Leib }, 132*f125bc8dSSascha Leib 133*f125bc8dSSascha Leib loadLogFile: async function(type) { 134*f125bc8dSSascha Leib console.info('Monitor.today.data.loadLogFile(',type,')'); 135*f125bc8dSSascha Leib 136*f125bc8dSSascha Leib let typeName = ''; 137*f125bc8dSSascha Leib let columns = []; 138*f125bc8dSSascha Leib 139*f125bc8dSSascha Leib switch (type) { 140*f125bc8dSSascha Leib case "srv": 141*f125bc8dSSascha Leib typeName = "Server"; 142*f125bc8dSSascha Leib columns = ['ts','ip','pg','id','usr','client']; 143*f125bc8dSSascha Leib break; 144*f125bc8dSSascha Leib break; 145*f125bc8dSSascha Leib case "log": 146*f125bc8dSSascha Leib typeName = "Page load"; 147*f125bc8dSSascha Leib columns = ['ts','ip','pg','id','usr']; 148*f125bc8dSSascha Leib break; 149*f125bc8dSSascha Leib case "tck": 150*f125bc8dSSascha Leib typeName = "Ticker"; 151*f125bc8dSSascha Leib columns = ['ts','ip','pg','id']; 152*f125bc8dSSascha Leib break; 153*f125bc8dSSascha Leib default: 154*f125bc8dSSascha Leib console.warn(`Unknown log type ${type}.`); 155*f125bc8dSSascha Leib return; 156*f125bc8dSSascha Leib } 157*f125bc8dSSascha Leib 158*f125bc8dSSascha Leib // Load the list of known bots: 159*f125bc8dSSascha Leib Monitor.today.status.showBusy(`Loading ${typeName} log file …`); 160*f125bc8dSSascha Leib 161*f125bc8dSSascha Leib const url = Monitor._baseDir + `logs/${Monitor._today}.${type}`; 162*f125bc8dSSascha Leib console.log("Loading:",url); 163*f125bc8dSSascha Leib 164*f125bc8dSSascha Leib try { 165*f125bc8dSSascha Leib const response = await fetch(url); 166*f125bc8dSSascha Leib if (!response.ok) { 167*f125bc8dSSascha Leib throw new Error(`${response.status} ${response.statusText}`); 168*f125bc8dSSascha Leib } 169*f125bc8dSSascha Leib 170*f125bc8dSSascha Leib const events = await response.text(); 171*f125bc8dSSascha Leib console.log(events); 172*f125bc8dSSascha Leib //Monitor.today.data.serverEvents._ready = true; 173*f125bc8dSSascha Leib 174*f125bc8dSSascha Leib // TODO: parse the file... 175*f125bc8dSSascha Leib } catch (error) { 176*f125bc8dSSascha Leib Monitor.today.status.setError(`Error while loading the ${typeName} log file: ${error.message}.`); 177*f125bc8dSSascha Leib } finally { 178*f125bc8dSSascha Leib Monitor.today.status.hideBusy("Done."); 179*f125bc8dSSascha Leib } 180*f125bc8dSSascha Leib } 181*f125bc8dSSascha Leib }, 182*f125bc8dSSascha Leib 183*f125bc8dSSascha Leib status: { 184*f125bc8dSSascha Leib setText: function(txt) { 185*f125bc8dSSascha Leib const el = document.getElementById('monitor__today__status'); 186*f125bc8dSSascha Leib if (el && Monitor.today.status._errorCount <= 0) { 187*f125bc8dSSascha Leib el.innerText = txt; 188*f125bc8dSSascha Leib } 189*f125bc8dSSascha Leib }, 190*f125bc8dSSascha Leib 191*f125bc8dSSascha Leib setTitle: function(html) { 192*f125bc8dSSascha Leib const el = document.getElementById('monitor__today__title'); 193*f125bc8dSSascha Leib if (el) { 194*f125bc8dSSascha Leib el.innerHTML = html; 195*f125bc8dSSascha Leib } 196*f125bc8dSSascha Leib }, 197*f125bc8dSSascha Leib 198*f125bc8dSSascha Leib setError: function(txt) { 199*f125bc8dSSascha Leib console.error(txt); 200*f125bc8dSSascha Leib Monitor.today.status._errorCount += 1; 201*f125bc8dSSascha Leib const el = document.getElementById('monitor__today__status'); 202*f125bc8dSSascha Leib if (el) { 203*f125bc8dSSascha Leib el.innerText = "An error occured. See the browser log for details!"; 204*f125bc8dSSascha Leib el.classList.add('error'); 205*f125bc8dSSascha Leib } 206*f125bc8dSSascha Leib }, 207*f125bc8dSSascha Leib _errorCount: 0, 208*f125bc8dSSascha Leib 209*f125bc8dSSascha Leib showBusy: function(txt = null) { 210*f125bc8dSSascha Leib Monitor.today.status._busyCount += 1; 211*f125bc8dSSascha Leib const el = document.getElementById('monitor__today__busy'); 212*f125bc8dSSascha Leib if (el) { 213*f125bc8dSSascha Leib el.style.display = 'inline-block'; 214*f125bc8dSSascha Leib } 215*f125bc8dSSascha Leib if (txt) Monitor.today.status.setText(txt); 216*f125bc8dSSascha Leib }, 217*f125bc8dSSascha Leib _busyCount: 0, 218*f125bc8dSSascha Leib 219*f125bc8dSSascha Leib hideBusy: function(txt = null) { 220*f125bc8dSSascha Leib const el = document.getElementById('monitor__today__busy'); 221*f125bc8dSSascha Leib Monitor.today.status._busyCount -= 1; 222*f125bc8dSSascha Leib if (Monitor.today.status._busyCount <= 0) { 223*f125bc8dSSascha Leib if (el) el.style.display = 'none'; 224*f125bc8dSSascha Leib if (txt) Monitor.today.status.setText(txt); 225*f125bc8dSSascha Leib } 226*f125bc8dSSascha Leib } 227*f125bc8dSSascha Leib } 228*f125bc8dSSascha Leib} 229*f125bc8dSSascha Leib 230*f125bc8dSSascha Leib/* check if the nustat admin panel is open: */ 231*f125bc8dSSascha Leibif (document.getElementById('monitor__admin')) { 232*f125bc8dSSascha Leib Monitor.init(); 233*f125bc8dSSascha Leib}