MIDAS
Loading...
Searching...
No Matches
messages.js
Go to the documentation of this file.
1
2// search messages
3const date_regex = /(\d{2}):\d{2}:\d{2}\.\d+\s(\d{4}\/\d{2}\/\d{2})/;
4
5let recentFilters = [];
6let filteredMessages = [];
7let unfilteredMessages = [];
8let isFiltering = false;
9let stoppedFiltering = false;
10let filterTimeout;
11
12function reset_background_color() {
13
14 document.getElementById("loadingOverlay").style.display = "flex";
15 document.getElementById("loadingOverlay").innerHTML = "Reseting colors...";
16
17 // Loop through each child element and remove the background color
18 filteredMessages.forEach(element => {
19 element.style.backgroundColor = "";
20 });
21 filteredMessages = [];
22
23 unfilteredMessages.forEach(element => {
24 element.style.display = "";
25 });
26 unfilteredMessages = [];
27 document.getElementById("loadingOverlay").style.display = "none";
28 document.getElementById("loadingOverlay").innerHTML = "Searching messages... Mouse Click to stop.";
29
30}
31
32function reset() {
33 // reset to default
34 isFiltering = false;
35 document.getElementById("loadingOverlay").style.display = "none";
36}
37
38// Initialize messagesByDay as a map of messages grouped by date
39const messagesByDay = Array.from(document.querySelectorAll('#messageFrame p')).reduce((acc, message) => {
40 const messageText = message.innerText;
41 const dateMatch = date_regex.exec(messageText);
42
43 if (dateMatch) {
44 // Construct the date key in the format YYYY-MM-DD
45 const dateKey = `${dateMatch[1]}`;
46
47 // Initialize the array if it doesn't exist, then add the message
48 if (!acc[dateKey]) {
49 acc[dateKey] = [];
50 }
51 acc[dateKey].push(message);
52 }
53
54 return acc;
55}, {});
56
57// Update function for messagesByDay while new messages are coming in
58function updateMessagesArray(mutationsList) {
59 mutationsList.forEach(mutation => {
60 if (mutation.type === 'childList') {
61 // Process added nodes
62 mutation.addedNodes.forEach(node => {
63 if (node.nodeType === Node.ELEMENT_NODE && node.tagName === 'P') {
64 const messageText = node.innerText;
65 const dateMatch = date_regex.exec(messageText);
66
67 if (dateMatch) {
68 const dateKey = `${dateMatch[2]}` + "-" + `${dateMatch[1]}`; // Format: YYYY/MM/DD-h
69 // console.log(dateKey);
70 if (!messagesByDay[dateKey]) {
71 messagesByDay[dateKey] = [];
72 }
73 messagesByDay[dateKey].push(node);
74 }
75 }
76 });
77
78 // Process removed nodes
79 mutation.removedNodes.forEach(node => {
80 if (node.nodeType === Node.ELEMENT_NODE && node.tagName === 'P') {
81 const messageText = node.innerText;
82 const dateMatch = date_regex.exec(messageText);
83
84 if (dateMatch) {
85 const dateKey = `${dateMatch[1]}`;
86 if (messagesByDay[dateKey]) {
87 messagesByDay[dateKey] = messagesByDay[dateKey].filter(msg => msg !== node);
88
89 // Clean up the date entry if no messages are left
90 if (messagesByDay[dateKey].length === 0) {
91 delete messagesByDay[dateKey];
92 }
93 }
94 }
95 }
96 });
97 }
98 });
99}
100
101// Set up the observer
102const observer = new MutationObserver(updateMessagesArray);
103
104// Target the message container
105const messageFrame = document.getElementById('messageFrame');
106if (messageFrame) {
107 observer.observe(messageFrame, { childList: true, subtree: true });
108}
109
110// wrapper to reset the stoppedFiltering variable when we had an input
111function applyFilter_input() {
112 document.getElementById("messageFrame").className = "mmessagebox mfont";
113 stoppedFiltering = false;
114 reset();
115 reset_background_color();
116 applyFilter();
117}
118
119// Function to apply filters and manage the dynamic loading of new messages
120function applyFilter() {
121
122 const nameFilter = document.getElementById('nameFilter').value.trim();
123 const typeFilter = document.getElementById('typeFilter').value.trim();
124 const timeFilter = document.getElementById('timeFilter').value.trim();
125 const textFilter = document.getElementById('textFilter').value.trim();
126 const timeRangeFilter = document.getElementById('timeRangeFilter').value.trim();
127
128 let counter = 0;
129
130 // Check if any filter field has input
131 const anyFilterActive = Boolean(nameFilter || typeFilter || timeFilter || textFilter);
132
133 // console.log("LOG", anyFilterActive, !isFiltering, !stoppedFiltering);
134 // stop if we have no filter or the filtering was stopped
135 if ( stoppedFiltering ) {
136 // console.log("Stopped filtering on start", stoppedFiltering, !anyFilterActive);
137 reset();
138 clearTimeout(filterTimeout);
139 return;
140 }
141
142 if ( !anyFilterActive ) {
143 reset();
144 return;
145 }
146
147 // start the filtering if we are not filtering and no stop was triggered
148 if (anyFilterActive && !isFiltering && !stoppedFiltering) {
149 // console.log("Trigger turn on overlay start", !stoppedFiltering, anyFilterActive, !isFiltering);
150 isFiltering = true;
151 document.getElementById("loadingOverlay").style.display = "flex";
152 }
153
154 // Get the day of the earlist entries
155 var keys = Object.keys(messagesByDay);
156 const earlistDay = keys.sort()[keys.length-1];
157 // revert keys to start with the earlist
158 keys = keys.map((e, i, a)=> a[(a.length - 1) - i]);
159
160 // console.log(keys.length);
161 var filtered_keys = 0;
162 for ( let i = 0; i < keys.length; i++ ) {
163 if (
164 (timeRangeFilter === "24 h" && i >= 24) ||
165 (timeRangeFilter === "48 h" && i >= 48) ||
166 (timeRangeFilter === "72 h" && i >= 72) ||
167 (timeRangeFilter === "7 d" && i >= 7*24) ||
168 (timeRangeFilter === "30 d" && i >= 30*24)
169 ) {
170 // console.log(i, keys.length);
171 reset();
172 break;
173 }
174 filtered_keys++;
175
176 // cast to text to be faster with the matching later
177 const messagesTextArray = messagesByDay[keys[i]].map(msg => msg.innerText || '');
178
179 for ( let j = 0; j < messagesTextArray.length; j++) {
180 counter++;
181
182 const message = messagesByDay[keys[i]][j];
183 if (!message) {
184 // console.log("Empty message continue.");
185 continue;
186 }
187
188 const messageText = messagesTextArray[j];
189 if (!messageText) {
190 // console.log("Empty string continue.");
191 continue;
192 }
193 const dateMatch = messageText.match(date_regex);
194
195 if (!dateMatch) {
196 // console.log("Date not found in message.");
197 continue;
198 }
199
200 // Regex to match the structure
201 const regex = /(\d{2}:\d{2}:\d{2}\.\d{3}) (\d{4}\/\d{2}\/\d{2}) \[(.+?),(.+?)\] (.+)/;
202 const match = messageText.match(regex);
203
204 if (match) {
205 const [fullMatch, time, date, name, state, text] = match;
206
207 // Filters check
208 const nameMatch = nameFilter ? name.includes(nameFilter) : true;
209 const typeMatch = typeFilter ? state.includes(typeFilter) : true;
210 const timeMatch = timeFilter ? time.startsWith(timeFilter) : true;
211 const textMatch = textFilter ? text.toLowerCase().includes(textFilter.toLowerCase()) : true;
212
213 // Display or hide the message based on filters
214 if (nameMatch && typeMatch && timeMatch && textMatch) {
215 // for new messages we have to reset the transition and remove the age property
216 message.style.transition = "none";
217 message.style.webkitTransition = "none";
218 delete message.age;
219
220 if (!(message.style.display === "var(--mgreen)"))
221 filteredMessages.push(message);
222 message.style.backgroundColor = "var(--mgreen)";
223
224 } else {
225 if (!(message.style.display === "none"))
226 unfilteredMessages.push(message);
227 message.style.display = "none";
228 }
229 }
230 }
231 }
232 // console.log("Counters: ", counter, n_messages, keys.length, stoppedFiltering);
233
234 // Store the current filter in the recent filters array
235 const currentFilter = {
236 name: nameFilter,
237 type: typeFilter,
238 time: timeFilter,
239 text: textFilter,
240 timeRange: timeRangeFilter
241 };
242
243 // Check if the current filter already exists in the recentFilters array
244 const filterExists = recentFilters.some(filter =>
245 filter.name === currentFilter.name &&
246 filter.type === currentFilter.type &&
247 filter.time === currentFilter.time &&
248 filter.text === currentFilter.text &&
249 filter.timeRange === currentFilter.timeRange
250 );
251
252 // Add the current filter only if it doesn't already exist
253 if (!filterExists && Object.values(currentFilter).some(value => value !== "")) {
254 recentFilters.push(currentFilter);
255 updateRecentsDropdown();
256 }
257
258 if (
259 (timeRangeFilter === "24 h" && filtered_keys >= 24) ||
260 (timeRangeFilter === "48 h" && filtered_keys >= 48) ||
261 (timeRangeFilter === "72 h" && filtered_keys >= 72) ||
262 (timeRangeFilter === "7 d" && filtered_keys >= 7*24) ||
263 (timeRangeFilter === "30 d" && filtered_keys >= 30*24) ||
264 stoppedFiltering
265 ) {
266 // console.log(keys.length, filtered_keys, keys);
267 reset();
268 return;
269 } else {
270 // console.log("Trigger turn on overlay");
271 document.getElementById("loadingOverlay").style.display = "flex";
272 filterTimeout = setTimeout(applyFilter, 1000); // Retry after 1 second
273 }
274
275}
276
277// Function to stop filtering and reset
278function stopFiltering() {
279 clearTimeout(filterTimeout);
280 isFiltering = false;
281 stoppedFiltering = true;
282 // console.log("Trigger stop");
283 document.getElementById("loadingOverlay").style.display = "none";
284}
285
286function updateRecentsDropdown() {
287 const dropdown = document.getElementById('recentsDropdown');
288 dropdown.innerHTML = '<option value="">Select a recent filter</option>';
289
290 recentFilters.forEach((filter, index) => {
291 const option = document.createElement('option');
292 option.value = index;
293 option.text = `Name: ${filter.name || 'N/A'}, Type: ${filter.type || 'N/A'}, Time: ${filter.time || 'N/A'}, Text: ${filter.text || 'N/A'}, TimeRange: ${filter.timeRange || 'N/A'}`;
294 dropdown.appendChild(option);
295 });
296}
297
298function add_filter() {
299 const selectedFilterIndex = this.value;
300
301 if (selectedFilterIndex !== "") {
302 const selectedFilter = recentFilters[selectedFilterIndex];
303 document.getElementById('nameFilter').value = selectedFilter.name;
304 document.getElementById('typeFilter').value = selectedFilter.type;
305 document.getElementById('timeFilter').value = selectedFilter.time;
306 document.getElementById('textFilter').value = selectedFilter.text;
307 document.getElementById('timeRangeFilter').value = selectedFilter.timeRange;
308 applyFilter();
309 }
310}
311
312function grey_table() {
313 document.getElementById("messageFrame").className = "mmessagebox mfont grayout";
314
315 // if there is no filter active we just show all messages
316 const nameFilter = document.getElementById('nameFilter').value.trim();
317 const typeFilter = document.getElementById('typeFilter').value.trim();
318 const timeFilter = document.getElementById('timeFilter').value.trim();
319 const textFilter = document.getElementById('textFilter').value.trim();
320 const anyFilterActive = Boolean(nameFilter || typeFilter || timeFilter || textFilter);
321
322 if (!anyFilterActive) {
323 // console.log("We are clear again");
324 // reset filtered messages
325 filteredMessages.forEach(element => {
326 element.style.backgroundColor = "";
327 });
328 filteredMessages = [];
329
330 unfilteredMessages.forEach(element => {
331 element.style.display = "";
332 });
333 unfilteredMessages = [];
334
335 // no grey background
336 document.getElementById("messageFrame").className = "mmessagebox mfont";
337 }
338}
339
340// Set event listeners
341document.getElementById("loadingOverlay").addEventListener("click", stopFiltering);
342document.addEventListener("keypress", function (e) {
343 if (e.key === 'Enter') {
344 applyFilter_input();
345 }
346});
347document.getElementById('filterBtn').addEventListener("click", applyFilter_input);
348document.getElementById('nameFilter').addEventListener('input', grey_table);
349document.getElementById('typeFilter').addEventListener('input', grey_table);
350document.getElementById('timeFilter').addEventListener('input', grey_table);
351document.getElementById('textFilter').addEventListener('input', grey_table);
352document.getElementById('timeRangeFilter').addEventListener('input', grey_table);
353document.getElementById('recentsDropdown').addEventListener('change', add_filter);
354
355
356// reload messages
357let facility;
358let first_tstamp;
359let last_tstamp;
360let n_messages;
361let end_of_messages;
362let timer_front;
363let timer_tail;
364
365function show_facilities() {
366 if (localStorage.mNavigationButtons !== undefined) {
367 document.getElementById("navigationFacilitiesButtons").innerHTML = localStorage.mFacilitiesButtons;
368 }
369
370 mjsonrpc_call("cm_msg_facilities").then(function (rpc) {
371 var f = rpc.result.facilities;
372 var html = "";
373 for (var i = 0; i < f.length; i++) {
374 var c = "mnav navButton";
375 if (f[i] === facility) {
376 c = "mnav mnavsel navButtonSel";
377 }
378 html += "<input type=button name=cmd value=\"" + f[i] + "\" class=\"" + c + "\" onclick=\"msg_load(\'" + f[i] + "\');return false;\">\n";
379 }
380 document.getElementById("navigationFacilitiesButtons").innerHTML = html;
381
382 // cache navigation buttons in browser local storage
383 localStorage.setItem("mFacilitiesButtons", html);
384
385 }).catch(function (error) {
386 document.getElementById("dlgErrorText").innerHTML = mjsonrpc_decode_error(error);
387 dlgShow('dlgError');
388 //mjsonrpc_error_alert(error);
389 });
390}
391
392function resize_message_box() {
393 // set message window height to fit browser window
394 var h = window.innerHeight;
395 var mf = document.getElementById('messageTable');
396 h -= findPos(mf)[1];
397 h -= 10; // top and bottom margin of .messagebox
398 mf.style.maxHeight = h + "px";
399}
400
401function getUrlVars() {
402 let vars = {};
403 window.location.href.replace(/[?&]+([^=&]+)=([^&]*)/gi, function (m, key, value) {
404 vars[key] = value;
405 });
406 return vars;
407}
408
409function msg_load(f) {
410 // if facility comes from the button, use it
411 if (f !== undefined)
412 facility = f;
413 else {
414 // if facility is in URL, use it
415 let urlfacility = decodeURI(getUrlVars()["facility"]);
416 if (urlfacility !== "undefined")
417 facility = urlfacility;
418 // if facility is in local storage, use it
419 else if (mhttpdConfig().mFacility !== undefined)
420 facility = mhttpdConfig().mFacility;
421 else
422 // use 'midas' if no other source
423 facility = 'midas';
424 }
425
426 // put facility in URL
427 let url = window.location.href;
428 if (url.search("&facility=") !== -1)
429 url = url.slice(0, url.search("&facility="));
430 url += "&facility=" + facility;
431 if (url !== window.location.href)
432 window.history.replaceState({}, "Midas History", url);
433
434 mhttpdConfigSet("mFacility", f);
435 first_tstamp = 0;
436 last_tstamp = 0;
437 n_messages = 0;
438 end_of_messages = false;
439
440 if (timer_front !== undefined)
441 window.clearTimeout(timer_front);
442 if (timer_tail !== undefined)
443 window.clearTimeout(timer_tail);
444
445 // manage selection of facility button
446 let button = document.getElementById("navigationFacilitiesButtons").children;
447 for (let i = 0; i < button.length; i++)
448 if (button[i].value === facility)
449 button[i].className = "mnav mnavsel navButtonSel";
450 else
451 button[i].className = "mnav navButton";
452
453 // remove old messages
454 var mf = document.getElementById('messageFrame');
455 for (var i = mf.childNodes.length - 1; i > 0; i--)
456 mf.removeChild(mf.childNodes[i]);
457
458 mjsonrpc_call("cm_msg_retrieve", { "facility": facility, "time" : 0, "min_messages" : 100 }).then(function(rpc) {
459 if(rpc.result.messages){
460 var msg = rpc.result.messages.split("\n");
461 msg_append(msg);
462 }
463 // check for new messages
464 timer_front = window.setTimeout(msg_extend_front, 1000);
465 // extend messages on scrolling down
466 timer_tail = window.setTimeout(msg_extend_tail, 1000);
467 resize_message_box();
468 }).catch(function(error) {
469 document.getElementById("dlgErrorText").innerHTML = mjsonrpc_decode_error(error);
470 dlgShow('dlgError');
471 });
472 window.onresize = resize_message_box;
473}
474
475function msg_filter(msg) {
476 let c = mhttpdConfig();
477 for (let i = 0; i < msg.length; i++) {
478
479 let skip = false;
480 if (!c.pageTalk && msg[i].search(",TALK]") > 0)
481 skip = true;
482 if (!c.pageError && msg[i].search(",ERROR]") > 0)
483 skip = true;
484 if (!c.pageInfo && msg[i].search(",INFO]") > 0)
485 skip = true;
486 if (!c.pageLog && msg[i].search(",LOG]") > 0)
487 skip = true;
488
489 if (skip) {
490 let t = parseInt(msg[i]);
491
492 // skip messages in the future
493 if (t < Math.floor(Date.now() / 1000)) {
494
495 if (t > first_tstamp)
496 first_tstamp = t;
497
498 msg.splice(i, 1);
499 i--;
500 }
501 }
502 }
503}
504
505function msg_prepend(msg) {
506 var mf = document.getElementById('messageFrame');
507 if (mf.childNodes.length < 2) {
508 msg_append(msg);
509 return;
510 }
511
512 msg_filter(msg);
513
514 for (var i = 0; i < msg.length; i++) {
515 var line = msg[i];
516 var t = parseInt(line);
517
518 // skip messages in the future
519 if (t > Math.floor(Date.now() / 1000))
520 continue;
521
522 if (line.indexOf(" ") && (t > 0 || t === -1))
523 line = line.substr(line.indexOf(" ") + 1);
524 var e = document.createElement("p");
525 e.className = "mmessageline";
526 e.appendChild(document.createTextNode(line));
527
528 if (e.innerHTML === mf.childNodes[1 + i].innerHTML)
529 break;
530 mf.insertBefore(e, mf.childNodes[1 + i]);
531 if (t > first_tstamp)
532 first_tstamp = t;
533 n_messages++;
534
535 if (line.search("ERROR]") > 0) {
536 e.style.backgroundColor = "var(--mred)";
537 } else {
538 e.style.backgroundColor = "var(--myellow)";
539 e.age = new Date() / 1000;
540 e.style.setProperty("-webkit-transition", "background-color 3s", "");
541 e.style.setProperty("transition", "background-color 3s", "");
542 }
543
544 }
545}
546
547function msg_append(msg) {
548
549 msg_filter(msg);
550
551 if (msg[0] === "")
552 end_of_messages = true;
553 if (end_of_messages)
554 return;
555 var mf = document.getElementById('messageFrame');
556 for (var i = 0; i < msg.length; i++) {
557 var line;
558 line = msg[i];
559 var t = parseInt(line);
560
561 // skip messages in the future
562 if (t > Math.floor(Date.now() / 1000))
563 continue;
564
565 if (!(t <= -1) && t > first_tstamp)
566 first_tstamp = t;
567 if (!(t <= -1) && (last_tstamp === 0 || t < last_tstamp))
568 last_tstamp = t;
569 if (line.indexOf(" ") && (t > 0 || t === -1))
570 line = line.substr(line.indexOf(" ") + 1);
571 var e = document.createElement("p");
572 e.className = "mmessageline";
573 e.appendChild(document.createTextNode(line));
574 if (line.search("ERROR]") > 0) {
575 e.style.backgroundColor = "var(--mred)";
576 }
577
578 mf.appendChild(e);
579 n_messages++;
580 }
581}
582
583function findPos(obj) {
584 var cursorleft = 0;
585 var cursortop = 0;
586 if (obj.offsetParent) {
587 do {
588 cursorleft += obj.offsetLeft;
589 cursortop += obj.offsetTop;
590 obj = obj.offsetParent;
591 } while (obj);
592 return [cursorleft, cursortop];
593 }
594}
595
596function msg_extend_tail() {
597 // if scroll bar is close to end, append messages
598 var mf = document.getElementById('messageFrame');
599
600 if (mf.scrollHeight - mf.scrollTop - mf.clientHeight < 2000) {
601 if (!end_of_messages) {
602 if (last_tstamp > 0) {
603 mjsonrpc_call("cm_msg_retrieve", { "facility": facility, "time" : last_tstamp-1, "min_messages" : 100 }).then(function(rpc) {
604 var msg = [""]; // empty first element; will indicate last lines if call failed.
605 if(rpc.result.messages){
606 msg = rpc.result.messages.split("\n");
607 }
608 msg_append(msg);
609 timer_tail = window.setTimeout(msg_extend_tail, 1000);
610 }).catch(function(error) {
611 document.getElementById("dlgErrorText").innerHTML = mjsonrpc_decode_error(error);
612 dlgShow('dlgError');
613 });
614 } else {
615 // in non-timestamped mode, simply load full message list
616 mjsonrpc_call("cm_msg_retrieve", { "facility": facility, "time" : 0, "min_messages" : n_messages+100 }).then(function(rpc) {
617 var msg = []; // empty first element; will indicate last lines if call failed.
618 if(rpc.result.messages){
619 msg = rpc.result.messages.split("\n");
620 }
621 n_messages = 0;
622 var mf = document.getElementById('messageFrame');
623 for (var i = mf.childNodes.length - 1; i > 0; i--)
624 mf.removeChild(mf.childNodes[i]);
625 msg_append(msg);
626 timer_tail = window.setTimeout(msg_extend_tail, 1000);
627 }).catch(function(error) {
628 document.getElementById("dlgErrorText").innerHTML = mjsonrpc_decode_error(error);
629 dlgShow('dlgError');
630 });
631 }
632 }
633 } else
634 timer_tail = window.setTimeout(msg_extend_tail, 1000);
635}
636
637function msg_extend_front() {
638
639 if (timer_front !== undefined)
640 window.clearTimeout(timer_front);
641
642 // remove color of new elements after a while
643 var mf = document.getElementById('messageFrame');
644 for (i = 1; i < mf.childNodes.length; i++) {
645 if (mf.childNodes[i].age !== undefined) {
646 var t = new Date() / 1000;
647 if (t > mf.childNodes[i].age + 5)
648 mf.childNodes[i].style.backgroundColor = "";
649 }
650 }
651
652 // check for new message if time stamping is on
653 if (first_tstamp) {
654
655 mjsonrpc_call("cm_msg_retrieve", { "facility": facility, "time" : first_tstamp, "min_messages" : 0 }).then(function(rpc) {
656 if (rpc.result.messages !== undefined) {
657 var msg = rpc.result.messages.split("\n");
658 msg_prepend(msg);
659 }
660 timer_front = window.setTimeout(msg_extend_front, 1000);
661 }).catch(function(error) {
662
663 if (error.xhr && error.xhr.readyState === 4 && error.xhr.status === 0) {
664 // don't display error, since this one is shown on the header automatically
665
666 // retry communication
667 timer_front = window.setTimeout(msg_extend_front, 1000);
668 } else {
669 document.getElementById("dlgErrorText").innerHTML = mjsonrpc_decode_error(error);
670 dlgShow('dlgError');
671 }
672 });
673 }else{
674 timer_front = window.setTimeout(msg_extend_front, 1000);
675 }
676}