3const date_regex = /(\d{2}):\d{2}:\d{2}\.\d+\s(\d{4}\/\d{2}\/\d{2})/;
6let filteredMessages = [];
7let unfilteredMessages = [];
8let isFiltering = false;
9let stoppedFiltering = false;
12function reset_background_color() {
14 document.getElementById("loadingOverlay").style.display = "flex";
15 document.getElementById("loadingOverlay").innerHTML = "Reseting colors...";
17 // Loop through each child element and remove the background color
18 filteredMessages.forEach(element => {
19 element.style.backgroundColor = "";
21 filteredMessages = [];
23 unfilteredMessages.forEach(element => {
24 element.style.display = "";
26 unfilteredMessages = [];
27 document.getElementById("loadingOverlay").style.display = "none";
28 document.getElementById("loadingOverlay").innerHTML = "Searching messages... Mouse Click to stop.";
35 document.getElementById("loadingOverlay").style.display = "none";
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);
44 // Construct the date key in the format YYYY-MM-DD
45 const dateKey = `${dateMatch[1]}`;
47 // Initialize the array if it doesn't exist, then add the message
51 acc[dateKey].push(message);
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);
68 const dateKey = `${dateMatch[2]}` + "-" + `${dateMatch[1]}`; // Format: YYYY/MM/DD-h
69 // console.log(dateKey);
70 if (!messagesByDay[dateKey]) {
71 messagesByDay[dateKey] = [];
73 messagesByDay[dateKey].push(node);
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);
85 const dateKey = `${dateMatch[1]}`;
86 if (messagesByDay[dateKey]) {
87 messagesByDay[dateKey] = messagesByDay[dateKey].filter(msg => msg !== node);
89 // Clean up the date entry if no messages are left
90 if (messagesByDay[dateKey].length === 0) {
91 delete messagesByDay[dateKey];
101// Set up the observer
102const observer = new MutationObserver(updateMessagesArray);
104// Target the message container
105const messageFrame = document.getElementById('messageFrame');
107 observer.observe(messageFrame, { childList: true, subtree: true });
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;
115 reset_background_color();
119// Function to apply filters and manage the dynamic loading of new messages
120function applyFilter() {
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();
130 // Check if any filter field has input
131 const anyFilterActive = Boolean(nameFilter || typeFilter || timeFilter || textFilter);
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);
138 clearTimeout(filterTimeout);
142 if ( !anyFilterActive ) {
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);
151 document.getElementById("loadingOverlay").style.display = "flex";
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]);
160 // console.log(keys.length);
161 var filtered_keys = 0;
162 for ( let i = 0; i < keys.length; i++ ) {
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)
170 // console.log(i, keys.length);
176 // cast to text to be faster with the matching later
177 const messagesTextArray = messagesByDay[keys[i]].map(msg => msg.innerText || '');
179 for ( let j = 0; j < messagesTextArray.length; j++) {
182 const message = messagesByDay[keys[i]][j];
184 // console.log("Empty message continue.");
188 const messageText = messagesTextArray[j];
190 // console.log("Empty string continue.");
193 const dateMatch = messageText.match(date_regex);
196 // console.log("Date not found in message.");
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);
205 const [fullMatch, time, date, name, state, text] = match;
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;
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";
220 if (!(message.style.display === "var(--mgreen)"))
221 filteredMessages.push(message);
222 message.style.backgroundColor = "var(--mgreen)";
225 if (!(message.style.display === "none"))
226 unfilteredMessages.push(message);
227 message.style.display = "none";
232 // console.log("Counters: ", counter, n_messages, keys.length, stoppedFiltering);
234 // Store the current filter in the recent filters array
235 const currentFilter = {
240 timeRange: timeRangeFilter
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
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();
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) ||
266 // console.log(keys.length, filtered_keys, keys);
270 // console.log("Trigger turn on overlay");
271 document.getElementById("loadingOverlay").style.display = "flex";
272 filterTimeout = setTimeout(applyFilter, 1000); // Retry after 1 second
277// Function to stop filtering and reset
278function stopFiltering() {
279 clearTimeout(filterTimeout);
281 stoppedFiltering = true;
282 // console.log("Trigger stop");
283 document.getElementById("loadingOverlay").style.display = "none";
286function updateRecentsDropdown() {
287 const dropdown = document.getElementById('recentsDropdown');
288 dropdown.innerHTML = '<option value="">Select a recent filter</option>';
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);
298function add_filter() {
299 const selectedFilterIndex = this.value;
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;
312function grey_table() {
313 document.getElementById("messageFrame").className = "mmessagebox mfont grayout";
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);
322 if (!anyFilterActive) {
323 // console.log("We are clear again");
324 // reset filtered messages
325 filteredMessages.forEach(element => {
326 element.style.backgroundColor = "";
328 filteredMessages = [];
330 unfilteredMessages.forEach(element => {
331 element.style.display = "";
333 unfilteredMessages = [];
335 // no grey background
336 document.getElementById("messageFrame").className = "mmessagebox mfont";
340// Set event listeners
341document.getElementById("loadingOverlay").addEventListener("click", stopFiltering);
342document.addEventListener("keypress", function (e) {
343 if (e.key === 'Enter') {
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);
365function show_facilities() {
366 if (localStorage.mNavigationButtons !== undefined) {
367 document.getElementById("navigationFacilitiesButtons").innerHTML = localStorage.mFacilitiesButtons;
370 mjsonrpc_call("cm_msg_facilities").then(function (rpc) {
371 var f = rpc.result.facilities;
373 for (var i = 0; i < f.length; i++) {
374 var c = "mnav navButton";
375 if (f[i] === facility) {
376 c = "mnav mnavsel navButtonSel";
378 html += "<input type=button name=cmd value=\"" + f[i] + "\" class=\"" + c + "\" onclick=\"msg_load(\'" + f[i] + "\');return false;\">\n";
380 document.getElementById("navigationFacilitiesButtons").innerHTML = html;
382 // cache navigation buttons in browser local storage
383 localStorage.setItem("mFacilitiesButtons", html);
385 }).catch(function (error) {
386 document.getElementById("dlgErrorText").innerHTML = mjsonrpc_decode_error(error);
388 //mjsonrpc_error_alert(error);
392function resize_message_box() {
393 // set message window height to fit browser window
394 var h = window.innerHeight;
395 var mf = document.getElementById('messageTable');
397 h -= 10; // top and bottom margin of .messagebox
398 mf.style.maxHeight = h + "px";
401function getUrlVars() {
403 window.location.href.replace(/[?&]+([^=&]+)=([^&]*)/gi, function (m, key, value) {
409function msg_load(f) {
410 // if facility comes from the button, use it
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;
422 // use 'midas' if no other source
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);
434 mhttpdConfigSet("mFacility", f);
438 end_of_messages = false;
440 if (timer_front !== undefined)
441 window.clearTimeout(timer_front);
442 if (timer_tail !== undefined)
443 window.clearTimeout(timer_tail);
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";
451 button[i].className = "mnav navButton";
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]);
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");
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);
472 window.onresize = resize_message_box;
475function msg_filter(msg) {
476 let c = mhttpdConfig();
477 for (let i = 0; i < msg.length; i++) {
480 if (!c.pageTalk && msg[i].search(",TALK]") > 0)
482 if (!c.pageError && msg[i].search(",ERROR]") > 0)
484 if (!c.pageInfo && msg[i].search(",INFO]") > 0)
486 if (!c.pageLog && msg[i].search(",LOG]") > 0)
490 let t = parseInt(msg[i]);
492 // skip messages in the future
493 if (t < Math.floor(Date.now() / 1000)) {
495 if (t > first_tstamp)
505function msg_prepend(msg) {
506 var mf = document.getElementById('messageFrame');
507 if (mf.childNodes.length < 2) {
514 for (var i = 0; i < msg.length; i++) {
516 var t = parseInt(line);
518 // skip messages in the future
519 if (t > Math.floor(Date.now() / 1000))
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));
528 if (e.innerHTML === mf.childNodes[1 + i].innerHTML)
530 mf.insertBefore(e, mf.childNodes[1 + i]);
531 if (t > first_tstamp)
535 if (line.search("ERROR]") > 0) {
536 e.style.backgroundColor = "var(--mred)";
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", "");
547function msg_append(msg) {
552 end_of_messages = true;
555 var mf = document.getElementById('messageFrame');
556 for (var i = 0; i < msg.length; i++) {
559 var t = parseInt(line);
561 // skip messages in the future
562 if (t > Math.floor(Date.now() / 1000))
565 if (!(t <= -1) && t > first_tstamp)
567 if (!(t <= -1) && (last_tstamp === 0 || t < last_tstamp))
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)";
583function findPos(obj) {
586 if (obj.offsetParent) {
588 cursorleft += obj.offsetLeft;
589 cursortop += obj.offsetTop;
590 obj = obj.offsetParent;
592 return [cursorleft, cursortop];
596function msg_extend_tail() {
597 // if scroll bar is close to end, append messages
598 var mf = document.getElementById('messageFrame');
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");
609 timer_tail = window.setTimeout(msg_extend_tail, 1000);
610 }).catch(function(error) {
611 document.getElementById("dlgErrorText").innerHTML = mjsonrpc_decode_error(error);
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");
622 var mf = document.getElementById('messageFrame');
623 for (var i = mf.childNodes.length - 1; i > 0; i--)
624 mf.removeChild(mf.childNodes[i]);
626 timer_tail = window.setTimeout(msg_extend_tail, 1000);
627 }).catch(function(error) {
628 document.getElementById("dlgErrorText").innerHTML = mjsonrpc_decode_error(error);
634 timer_tail = window.setTimeout(msg_extend_tail, 1000);
637function msg_extend_front() {
639 if (timer_front !== undefined)
640 window.clearTimeout(timer_front);
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 = "";
652 // check for new message if time stamping is on
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");
660 timer_front = window.setTimeout(msg_extend_front, 1000);
661 }).catch(function(error) {
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
666 // retry communication
667 timer_front = window.setTimeout(msg_extend_front, 1000);
669 document.getElementById("dlgErrorText").innerHTML = mjsonrpc_decode_error(error);
674 timer_front = window.setTimeout(msg_extend_front, 1000);