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