xref: /plugin/botmon/script.js (revision f125bc8d0f12eb13e231258d9c31753aa4319b48)
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&nbsp;&hellip;");
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 &nbsp;&hellip;`);
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}