MIDAS
Loading...
Searching...
No Matches
mhttpd.js
Go to the documentation of this file.
1/********************************************************************\
2
3 Name: mhttpd.js
4 Created by: Stefan Ritt
5
6 Contents: JavaScript midas library used by mhttpd
7
8 Note: please load midas.js before loading mhttpd.js
9
10 \********************************************************************/
11
12let run_state_names = {1: "Stopped", 2: "Paused", 3: "Running"};
13
14let serverTimezoneOffset = undefined;
15
16let transition_names = {
17 1: "Starting run...",
18 2: "Stopping run...",
19 4: "Pausing run...",
20 8: "Resuming run...",
21 16: "Start abort",
22 4096: "Deferred"
23};
24
25// extend 2d canvas object
26CanvasRenderingContext2D.prototype.drawLine = function (x1, y1, x2, y2) {
27 this.beginPath();
28 this.moveTo(x1, y1);
29 this.lineTo(x2, y2);
30 this.stroke();
31};
32
33//
34// convert json dom values to text for display and editing
35// this is similar to db_sprintf()
36//
37function mie_to_string(tid, jvalue, format) {
38 if (tid === TID_BOOL) {
39
40 if (format && format[0] === "[") {
41 let col = "grey";
42
43 let cols = format.replace(/[\[\]]/g, "").split(",");
44 if (jvalue)
45 col = cols[1];
46 else
47 col = cols[0];
48
49 return "<span style=\"display: inline-block;" +
50 "width: 1em;height: 1em;" +
51 "border: 1px solid black;" +
52 "background-color: " + col + ";" +
53 "\"></span>";
54 }
55
56 if (jvalue)
57 return "y";
58 else
59 return "n";
60 }
61
62 if (Array.isArray(jvalue)) {
63 let s = "";
64 for (let jv of jvalue) {
65 s += mie_to_string(tid, jv, format);
66 s += ",";
67 }
68 s = s.slice(0, -1);
69 return s;
70 }
71
72 if (tid === TID_FLOAT || tid === TID_DOUBLE) {
73 if (jvalue === "NaN" || jvalue === "Infinity" || jvalue === "-Infinity")
74 return jvalue;
75
76 if (format && format.indexOf("%") !== -1) {
77 // new format
78 let i = format.indexOf('%');
79 let s1 = format.substring(0, format.indexOf('%'));
80 let s2, s3;
81 let p = parseInt(format[i+2]);
82 if (format[i+1] === "e")
83 s2 = Number(jvalue).toExponential(p);
84 if (format[i+1] === "p")
85 s2 = Number(jvalue).toPrecision(p);
86 if (format[i+1] === "f")
87 s2 = Number(jvalue).toFixed(p);
88 if (format[i+1] === "t")
89 s2 = jvalue.toLocaleString();
90 if (format[i+1] === "%")
91 s1 += '%';
92
93 i += 2;
94 while (format[i] >= '0' && format[i] <= '9')
95 i++;
96 s3 = format.substring(i);
97 return s1+s2+s3;
98
99 } else {
100
101 // old format
102 if (format && format.indexOf("e") !== -1) {
103 let p = parseInt(format.substring(format.indexOf("e") + 1));
104 return Number(jvalue).toExponential(p).toString();
105 }
106 if (format && format.indexOf("p") !== -1) {
107 let p = parseInt(format.substring(format.indexOf("p") + 1));
108 return Number(jvalue).toPrecision(p).toString();
109 }
110 if (format && format.indexOf("f") !== -1) {
111 let p = parseInt(format.substring(format.indexOf("f") + 1));
112 return Number(jvalue).toFixed(p).toString();
113 }
114 if (format && format.indexOf("t") !== -1) {
115 return jvalue.toLocaleString();
116 }
117 return jvalue.toString();
118 }
119 }
120
121 let t = typeof jvalue;
122
123 if (t === 'number') {
124 jvalue = "" + jvalue;
125 }
126
127 if (tid === TID_UINT64 || tid === TID_INT64 ||
128 tid === TID_DWORD || tid === TID_INT ||
129 tid === TID_WORD || tid === TID_SHORT || tid === TID_BYTE) {
130
131 if (jvalue.substring(0, 2) === '0x')
132 jvalue = "" + parseInt(jvalue, 16);
133
134 if (format && format.indexOf("%") !== -1) {
135 // new format
136 if (format.length === 1)
137 format = "%d";
138 let str = "";
139 for (let i = 0; i < format.length; i++) {
140 if (format[i] === '%') {
141 i++;
142
143 if (format[i] === 'd')
144 str += jvalue;
145 if (format[i] === 'e') {
146 let p = parseInt(format.substring(i+1));
147 str += Number(jvalue).toExponential(p).toString();
148 }
149 if (format[i] === 'p') {
150 let p = parseInt(format.substring(i+1));
151 str += Number(jvalue).toPrecision(p).toString();
152 }
153 if (format[i] === 'f') {
154 let p = parseInt(format.substring(i+1));
155 str += Number(jvalue).toFixed(p).toString();
156 }
157 if (format[i] === 'x')
158 str += "0x" + parseInt(jvalue).toString(16).toUpperCase();
159 if (format[i] === 'b')
160 str += parseInt(jvalue).toString(2) + "b";
161 if (format[i] === 't')
162 str += parseInt(jvalue).toLocaleString();
163 if (format[i] === '%')
164 str += '%';
165
166 i++;
167 while (format[i] >= '0' && format[i] <= '9')
168 i++;
169 i--;
170
171 } else
172 str += format[i];
173 }
174
175 return str;
176
177 } else {
178
179 //old format
180 if (format === undefined)
181 format = "d";
182 let str = "";
183 for (let i = 0; i < format.length; i++) {
184 if (format[i] === "d") {
185 if (str.length > 0)
186 str += " / " + jvalue;
187 else
188 str = jvalue;
189 }
190 if (format[i] === "x") {
191 let hex = parseInt(jvalue).toString(16).toUpperCase();
192 if (str.length > 0)
193 str += " / 0x" + hex;
194 else
195 str = "0x" + hex;
196 }
197 if (format[i] === "b") {
198 let bin = parseInt(jvalue).toString(2);
199 if (str.length > 0)
200 str += " / " + bin + "b";
201 else
202 str = bin + "b";
203 }
204 if (format[i] === "t") {
205 let loc = parseInt(jvalue).toLocaleString();
206 if (str.length > 0)
207 str += " / " + loc;
208 else
209 str = loc;
210 }
211 }
212
213 return str;
214 }
215 }
216
217 if (t === 'string') {
218 return jvalue;
219 }
220
221 return jvalue + " (" + t + ")";
222}
223
224//
225// stupid javascript does not have a function
226// to escape javascript and html characters
227// to make it safe to assign a json string
228// to p.innerHTML. What gives? K.O.
229//
230function mhttpd_escape(s) {
231 let ss = s;
232
233 if (typeof s !== 'string')
234 return ss;
235
236 while (ss.indexOf('"') >= 0)
237 ss = ss.replace('"', '&quot;');
238
239 while (ss.indexOf('>') >= 0)
240 ss = ss.replace('>', '&gt;');
241
242 while (ss.indexOf('<') >= 0)
243 ss = ss.replace('<', '&lt;');
244
245 //console.log("mhttpd_escape: [" + s + "] becomes [" + ss + "]");
246 return ss;
247}
248
249//
250// odb inline edit - make element a link to inline editor
251//
252function mie_back_to_link(p, path) {
253 let link = document.createElement('a');
254 link.href = path + "?cmd=Set";
255 link.innerHTML = "(loading...)";
256
257 mjsonrpc_db_get_values([path]).then(function (rpc) {
258 let value = rpc.result.data[0];
259 let tid = rpc.result.tid[0];
260 let mvalue = mie_to_string(tid, value, p.dataset.format);
261 if (mvalue === "")
262 mvalue = "(empty)";
263 link.innerHTML = mhttpd_escape(mvalue);
264 link.onclick = function () {
265 ODBInlineEdit(p, path);
266 return false;
267 };
268 link.onfocus = function () {
269 ODBInlineEdit(p, path);
270 };
271 link.style.color = p.oldStyle.color;
272
273 if (p.childNodes[1] !== undefined &&
274 p.childNodes[1].value !== undefined) //two values means it was editing an array
275 setTimeout(function () {
276 p.appendChild(link);
277 p.removeChild(p.childNodes[1])
278 }, 10);
279 else
280 setTimeout(function () {
281 p.appendChild(link);
282 p.removeChild(p.childNodes[0])
283 }, 10);
284
285
286 if (p.value !== value) {
287 p.value = value;
288
289 if (p.onchange !== null) {
290 p.onchange();
291 }
292 }
293 }).catch(function (error) {
294 mjsonrpc_error_alert(error);
295 });
296}
297
298//
299// called from ODBFinishInlineEdit if element has 'confirm' flag
300//
301function ODBConfirmInlineEdit(flag, p) {
302
303 let value;
304 if (p.childNodes[1] !== undefined && p.childNodes[1].value !== undefined)
305 value = p.childNodes[1].value;
306 else
307 value = p.childNodes[0].value;
308
309 if (flag === true) {
310 mjsonrpc_db_set_value(p.odbPath, value).then(function (rpc) {
311 if (parseInt(p.dataset.input) !== 1) {
312 p.ODBsent = true;
313 mie_back_to_link(p, p.odbPath);
314 }
315 }).catch(function (error) {
316 mjsonrpc_error_alert(error);
317 });
318 } else {
319 if (parseInt(p.dataset.input) !== 1) {
320 p.ODBsent = true;
321 mie_back_to_link(p, p.odbPath);
322 }
323 }
324}
325
326
327//
328// odb inline edit - write new value to odb
329//
330function ODBFinishInlineEdit(p, path) {
331 let value;
332
333 if (p.ODBsent === true)
334 return;
335
336 if (!p.inEdit)
337 return;
338
339 if (p.childNodes[1] !== undefined && p.childNodes[1].value !== undefined)
340 value = p.childNodes[1].value;
341 else
342 value = p.childNodes[0].value;
343
344 //console.log("mie_write odb [" + path + "] value [" + value + "]");
345
346 // check for validation
347 if (p.dataset.validate !== undefined) {
348 let flag = eval(p.dataset.validate)(value, p);
349 if (!flag) {
350 if (parseInt(p.dataset.input) !== 1) {
351 p.ODBsent = true;
352 p.inEdit = false;
353 mie_back_to_link(p, path);
354 }
355 return;
356 }
357 }
358
359 // validator might have changed the value
360 if (p.childNodes[1] !== undefined && p.childNodes[1].value !== undefined)
361 value = p.childNodes[1].value;
362 else
363 value = p.childNodes[0].value;
364
365 // check for confirm
366 if (p.dataset.confirm !== undefined) {
367 p.inEdit = false;
368 dlgConfirm(p.dataset.confirm, ODBConfirmInlineEdit, p);
369 return;
370 }
371
372 mjsonrpc_db_set_value(path, value).then(function (rpc) {
373 if (parseInt(p.dataset.input) !== 1) {
374 p.ODBsent = true;
375 p.inEdit = false;
376 mie_back_to_link(p, path);
377 }
378 }).catch(function (error) {
379 mjsonrpc_error_alert(error);
380 });
381}
382
383//
384// odb inline edit - key-press handler
385//
386function ODBInlineEditKeydown(event, p, path) {
387 let keyCode = ('which' in event) ? event.which : event.keyCode;
388
389 if (keyCode === 27) {
390 /* cancel editing */
391 if (parseInt(p.dataset.input) !== 1) {
392 p.ODBsent = true;
393 p.inEdit = false;
394 mie_back_to_link(p, path);
395 }
396 return false;
397 }
398
399 if (keyCode === 13) {
400 ODBFinishInlineEdit(p, path);
401 return false;
402 }
403
404 return true;
405}
406
407//
408// odb inline edit - convert link to edit field
409//
410function mie_link_to_edit(p, cur_val, size) {
411 let string_val = String(cur_val);
412
413 p.ODBsent = false;
414 if (p.tagName === 'A')
415 p.oldStyle = p.style;
416 else if (p.getElementsByTagName('a').length > 0)
417 p.oldStyle = p.getElementsByTagName('a')[0].style;
418
419 if (size === undefined)
420 size = 20;
421 let str = mhttpd_escape(string_val);
422
423 let index = "";
424 if (p.innerHTML[0] === '[' && p.innerHTML.indexOf(']') !== 0)
425 index = p.innerHTML.substring(0, p.innerHTML.indexOf(']')+1) + "&nbsp;";
426
427 p.innerHTML = index + "<input type='text' size='" + size + "' value='" + str +
428 "' onKeydown='return ODBInlineEditKeydown(event, this.parentNode,&quot;" +
429 p.odbPath + "&quot;," + ");' onBlur='ODBFinishInlineEdit(this.parentNode,&quot;" +
430 p.odbPath + "&quot;," + ");' >";
431
432 // needed for Firefox
433 if (parseInt(p.dataset.input) !== 1)
434 setTimeout(function () {
435 if (index === "") {
436 p.childNodes[0].focus();
437 p.childNodes[0].select();
438 } else {
439 p.childNodes[1].focus();
440 p.childNodes[1].select();
441 }
442 }, 10);
443}
444
445//
446// odb inline edit - start editing
447//
448function ODBInlineEdit(p, odb_path) {
449 if (p.inEdit)
450 return;
451 p.inEdit = true;
452 p.odbPath = odb_path;
453
454 mjsonrpc_db_get_values([odb_path]).then(function (rpc) {
455 let value = rpc.result.data[0];
456 if (Array.isArray(value))
457 value = value[0];
458 let tid = rpc.result.tid[0];
459 let format = p.dataset.format;
460 let size = p.dataset.size;
461 if (size === undefined) {
462 size = value.length;
463 if (size === undefined || size < 20)
464 size = 20;
465 }
466 if (format) {
467 if (format.length > 1) {
468 if (format[0] === 'd' || format[0] === 'x' || format[0] === 'b') {
469 //when going to edit consider only the first format specifier for integers
470 format = String(format[0]);
471 }
472 }
473 if (format == 't') {
474 // JSON parser can't cope with locale-based strings (thousands separators), so
475 // force the user to edit the value as a regular number.
476 format = undefined;
477 }
478 }
479 let mvalue = mie_to_string(tid, value, format);
480 mie_link_to_edit(p, mvalue, size);
481 }).catch(function (error) {
482 mjsonrpc_error_alert(error);
483 });
484}
485
486function dlgOdbEditKeydown(event, input) {
487 let keyCode = ('which' in event) ? event.which : event.keyCode;
488
489 if (keyCode === 27) {
490 // cancel editing
491 dlgMessageDestroy(input.parentElement.parentElement);
492 return false;
493 }
494
495 if (keyCode === 13) {
496 dlgOdbEditSend(input.parentElement);
497 dlgMessageDestroy(input.parentElement.parentElement);
498 return false;
499 }
500
501 return true;
502}
503
504function dlgOdbEditSend(b) {
505 let path = b.parentElement.parentElement.parentElement.odbPath;
506 let value = b.parentElement.parentElement.elements[0].value;
507
508 mjsonrpc_db_set_value(path, value).then(function (rpc) {
509 //mjsonrpc_debug_alert(rpc);
510 }).catch(function (error) {
511 mjsonrpc_error_alert(error);
512 });
513}
514
515//
516// odb edit single value dialog box
517//
518function dlgOdbEdit(path) {
519
520 mjsonrpc_db_get_value(path).then(function (rpc) {
521 let value = rpc.result.data[0];
522 let tid = rpc.result.tid[0];
523 value = mie_to_string(tid, value);
524
525 d = document.createElement("div");
526 d.className = "dlgFrame";
527 d.style.zIndex = 20;
528 d.odbPath = path;
529
530 d.innerHTML = "<div class=\"dlgTitlebar\" id=\"dlgMessageTitle\">Change ODB value</div>" +
531 "<form>" +
532 "<div class=\"dlgPanel\" style=\"padding: 30px;\">" +
533 "<div id=\"dlgMessageString\">" +
534 path + "&nbsp;:&nbsp;&nbsp;" +
535 "<input type='text' size='16' value='" + value + "' onkeydown='return dlgOdbEditKeydown(event, this);'>" +
536 "</div>" +
537 "<br /><br />" +
538 "<button class=\"dlgButton\" id=\"dlgMessageButton\" style=\"background-color:#F8F8F8\" type=\"button\" " +
539 " onClick=\"dlgOdbEditSend(this);dlgMessageDestroy(this.parentElement);\">Ok</button>" +
540 "<button class=\"dlgButton\" id=\"dlgMessageButton\" style=\"background-color:#F8F8F8\" type=\"button\" " +
541 " onClick=\"dlgMessageDestroy(this.parentElement)\">Cancel</button>" +
542 "</div>" +
543 "</form>";
544
545 document.body.appendChild(d);
546
547 dlgShow(d, false);
548
549 // needed for Firefox
550 setTimeout(function () {
551 d.childNodes[1].elements[0].focus();
552 d.childNodes[1].elements[0].select();
553 }, 10);
554
555
556 }).catch(function (error) {
557 mjsonrpc_error_alert(error);
558 });
559}
560
561/*---- mhttpd functions -------------------------------------*/
562
563//
564// Returns time to display in status bar and history.
565// By default show local time, but through the config page the
566// user can select the server time zone or any other time zone
567//
568function mhttpd_get_display_time(sec) {
569 // retrieve timezone of server on the first call
570 if (serverTimezoneOffset === undefined) {
571 mjsonrpc_get_timezone().then(function (rpc) {
572 serverTimezoneOffset = rpc.result;
573 }).catch(function (error) {
574 mjsonrpc_error_alert(error);
575 });
576 }
577
578 let d;
579 if (sec !== undefined)
580 d = new Date(sec * 1000);
581 else
582 d = new Date();
583 let o = 0;
584
585 if (mhttpdConfig().timezone === undefined)
586 mhttpdConfigSet('timezone', 'local');
587
588 if (mhttpdConfig().timezone === 'local') {
589 o = -d.getTimezoneOffset() / 60;
590 } else if (mhttpdConfig().timezone === 'server') {
591 if (serverTimezoneOffset === undefined)
592 return {
593 date: undefined,
594 string: "",
595 offset: 0
596 };
597 o = serverTimezoneOffset / 3600;
598 } else {
599 o = parseInt(mhttpdConfig().timezone);
600 }
601
602 d = new Date(d.getTime() + o * 3600 * 1000);
603
604 let s = d.toLocaleString("en-gb", {
605 timeZone: 'UTC',
606 hour12: false, day: 'numeric', month: 'short', year: 'numeric',
607 hour: 'numeric', minute: 'numeric', second: 'numeric'
608 });
609
610 if (o === 0)
611 s += " UTC";
612 else if (o > 0)
613 s += " UTC+"+o;
614 else
615 s += " UTC"+o;
616
617 return {
618 date: d,
619 string: s,
620 offset: o
621 };
622}
623
624function mhttpd_disable_button(button) {
625 if (!button.disabled) {
626 button.disabled = true;
627 }
628}
629
630function mhttpd_enable_button(button) {
631 if (button.disabled) {
632 button.disabled = false;
633 }
634}
635
636function mhttpd_set_style_visibility(e, v) {
637 if (e) {
638 if (e.style.visibility !== v) {
639 e.style.visibility = v;
640 }
641 }
642}
643
644function mhttpd_set_style_display(e, v) {
645 if (e) {
646 if (e.style.display !== v) {
647 e.style.display = v;
648 }
649 }
650}
651
652function mhttpd_set_firstChild_data(e, v) {
653 if (e) {
654 if (e.firstChild.data !== v) {
655 //console.log("mhttpd_set_firstChild_data for " + e + " from " + e.firstChild.data + " to " + v);
656 e.firstChild.data = v;
657 }
658 }
659}
660
661function mhttpd_set_innerHTML(e, v) {
662 if (e) {
663 if (e.innerHTML !== v) {
664 //console.log("mhttpd_set_innerHTML for " + e + " from " + e.innerHTML + " to " + v);
665 e.innerHTML = v;
666 }
667 }
668}
669
670function mhttpd_set_className(e, v) {
671 if (e) {
672 if (e.className !== v) {
673 //console.log("mhttpd_set_className for " + e + " from " + e.className + " to " + v);
674 e.className = v;
675 }
676 }
677}
678
679function mhttpd_hide_button(button) {
680 mhttpd_set_style_visibility(button, "hidden");
681 mhttpd_set_style_display(button, "none");
682}
683
684function mhttpd_unhide_button(button) {
685 mhttpd_set_style_visibility(button, "visible");
686 mhttpd_set_style_display(button, "");
687}
688
689function mhttpd_hide(id) {
690 let e = document.getElementById(id);
691 if (e) {
692 mhttpd_set_style_visibility(e, "hidden");
693 mhttpd_set_style_display(e, "none");
694 }
695}
696
697function mhttpd_unhide(id) {
698 let e = document.getElementById(id);
699 if (e) {
700 mhttpd_set_style_visibility(e, "visible");
701 mhttpd_set_style_display(e, "");
702 }
703}
704
705function mhttpd_enable(id) {
706 let e = document.getElementById(id);
707 if (e)
708 e.disabled = false;
709}
710
711function mhttpd_disable(id) {
712 let e = document.getElementById(id);
713 if (e)
714 e.disabled = true;
715}
716
717function mhttpd_init_overlay(overlay) {
718 mhttpd_hide_overlay(overlay);
719
720 // this element will hide the underlaying web page
721
722 overlay.style.zIndex = 10;
723 //overlay.style.backgroundColor = "rgba(0,0,0,0.5)"; /*dim the background*/
724 overlay.style.backgroundColor = "white";
725 overlay.style.position = "fixed";
726 overlay.style.top = "0%";
727 overlay.style.left = "0%";
728 overlay.style.width = "100%";
729 overlay.style.height = "100%";
730
731 return overlay.children[0];
732}
733
734function mhttpd_hide_overlay(overlay) {
735 overlay.style.visibility = "hidden";
736 overlay.style.display = "none";
737}
738
739function mhttpd_unhide_overlay(overlay) {
740 overlay.style.visibility = "visible";
741 overlay.style.display = "";
742}
743
744function mhttpd_getParameterByName(name) {
745 let match = RegExp('[?&]' + name + '=([^&]*)').exec(window.location.search);
746 return match && decodeURIComponent(match[1].replace(/\+/g, ' '));
747}
748
749function mhttpd_goto_page(page, param) {
750 if (!param) param = "";
751 window.location.href = '?cmd=' + page + param; // reloads the page from new URL
752 // DOES NOT RETURN
753}
754
755function mhttpd_navigation_bar(current_page, path) {
756 document.write("<div id='customHeader'>\n");
757 document.write("</div>\n");
758
759 document.write("<div class='mnav'>\n");
760 document.write(" <table>\n");
761 document.write(" <tr><td id='navigationTableButtons'></td></tr>\n");
762 document.write(" </table>\n\n");
763 document.write("</div>\n");
764
765 if (!path)
766 path = "";
767
768 if (localStorage.mNavigationButtons !== undefined) {
769 document.getElementById("navigationTableButtons").innerHTML = localStorage.mNavigationButtons;
770 let button = document.getElementById("navigationTableButtons").children;
771 for (let i = 0; i < button.length; i++)
772 if (button[i].value.toLowerCase() === current_page.toLowerCase())
773 button[i].className = "mnav mnavsel navButtonSel";
774 else
775 button[i].className = "mnav navButton";
776 return;
777 }
778
779 mjsonrpc_db_get_values(["/Custom/Header", "/Experiment/Menu", "/Experiment/Menu Buttons"]).then(function (rpc) {
780 let custom_header = rpc.result.data[0];
781
782 if (custom_header && custom_header.length > 0)
783 document.getElementById("customHeader").innerHTML = custom_header;
784
785 let menu = rpc.result.data[1];
786 let buttons = rpc.result.data[2];
787 let b = [];
788
789 if (menu) {
790 for (let k in menu) {
791 let kk = k + "/name";
792 if (kk in menu) {
793 if (menu[k]) {
794 b.push(menu[kk]);
795 }
796 }
797 }
798 } else if (buttons && buttons.length > 0) {
799 b = buttons.split(",");
800 }
801
802 if (!b || b.length < 1) {
803 b = ["Status", "ODB", "Messages", "Chat", "ELog", "Alarms", "Programs", "History", "MSCB", "Sequencer", "Config", "Help"];
804 }
805
806 let html = "";
807
808 for (let i = 0; i < b.length; i++) {
809 let bb = b[i].trim();
810 let cc = "mnav navButton";
811 if (bb.toLowerCase() === current_page.toLowerCase()) {
812 cc = "mnav mnavsel navButtonSel";
813 }
814 html += "<input type=button name=cmd value='" + bb + "' class='" + cc + "' onclick='window.location.href=\'" + path + "?cmd=" + bb + "\';return false;'>\n";
815 }
816 document.getElementById("navigationTableButtons").innerHTML = html;
817
818 // cache navigation buttons in browser local storage
819 localStorage.setItem("mNavigationButtons", html);
820
821 }).catch(function (error) {
822 mjsonrpc_error_alert(error);
823 });
824}
825
826function mhttpd_show_menu(flag) {
827 let m = document.getElementById("msidenav");
828
829 if (m.initialWidth === undefined)
830 m.initialWidth = m.clientWidth;
831
832 if (flag) {
833 m.style.width = m.initialWidth + "px";
834 document.getElementById("mmain").style.marginLeft = m.initialWidth + "px";
835 mhttpdConfigSet('hideMenu', false);
836 } else {
837 m.style.width = "0";
838 document.getElementById("mmain").style.marginLeft = "0";
839 }
840
841 mhttpdConfigSet('showMenu', flag);
842}
843
844function mhttpd_toggle_menu() {
845 let flag = mhttpdConfig().showMenu;
846 flag = !flag;
847 mhttpd_show_menu(flag);
848}
849
850function mhttpd_exec_script(name) {
851 //console.log("exec_script: " + name);
852 let params = new Object;
853 params.script = name;
854 mjsonrpc_call("exec_script", params).then(function (rpc) {
855 let status = rpc.result.status;
856 if (status != 1) {
857 dlgAlert("Exec script \"" + name + "\" status " + status);
858 }
859 }).catch(function (error) {
860 mjsonrpc_error_alert(error);
861 });
862}
863
864let mhttpd_refresh_id;
865let mhttpd_refresh_history_id;
866let mhttpd_refresh_interval;
867let mhttpd_refresh_paused;
868let mhttpd_refresh_history_interval;
869let mhttpd_spinning_wheel;
870let mhttpd_initialized = false;
871
872function mhttpd_init(current_page, interval, callback) {
873 /*
874 This function should be called from custom pages to initialize all ODB tags and refresh
875 them periodically every "interval" in ms
876
877 For possible ODB tags please refer to
878
879 https://midas.triumf.ca/MidasWiki/index.php/New_Custom_Pages_(2017)
880 */
881
882 if (mhttpd_initialized) {
883 dlgAlert("mhttpd_init() is called more than once from user code");
884 return;
885 }
886
887 // initialize URL
888 url = mhttpd_getParameterByName("URL");
889 if (url)
890 mjsonrpc_set_url(url);
891
892 // retrieve current page if not given
893 if (current_page === undefined) {
894 current_page = mhttpd_getParameterByName("page");
895 }
896 if (current_page === undefined || current_page === null) {
897 dlgAlert("Please specify name of page via mhttpd_init('&lt;name&gt;')<br>or make sure to have '&amp;page=&lt;name&gt;' in URL");
898 return;
899 }
900
901 // create header
902 let h = document.getElementById("mheader");
903 if (h === null) {
904 dlgAlert('Web page does not contain "mheader" element');
905 return;
906 }
907 let s = document.getElementById("msidenav");
908 if (s === null) {
909 dlgAlert('Web page does not contain "msidenav" element');
910 return;
911 }
912
913 mhttpd_initialized = true;
914
915 // set global font size
916 let config = mhttpdConfig();
917 document.body.style.fontSize = config.fontSize + "pt";
918
919 h.style.display = "flex";
920 h.innerHTML =
921 "<div style='display:inline-block; flex:none;'>" +
922 "<span class='mmenuitem' style='padding: 10px;margin-right: 20px;' onclick='mhttpd_toggle_menu()'>&#9776;</span>" +
923 "<span id='mheader_expt_name'></span>" +
924 "</div>" +
925
926 "<div style='flex:auto;'>" +
927 " <div id='mheader_message'></div>" +
928 "</div>" +
929
930 "<div style='display: inline; flex:none;'>" +
931 " <div id='mheader_alarm'>&nbsp;</div>" +
932 " <div style='display: inline; font-size: 75%; margin-right: 10px' id='mheader_last_updated'>mheader_last_updated</div>" +
933 "</div>";
934
935 mhttpd_resize_sidenav();
936 window.addEventListener('resize', mhttpd_resize_sidenav);
937
938 // put error header in front of header
939 let d = document.createElement('div');
940 d.id = 'mheader_error';
941 h.parentNode.insertBefore(d, h);
942
943 // update header and menu
944 if (document.getElementById("msidenav") !== undefined) {
945
946 // get it from session storage cache if present
947 if (sessionStorage.msidenav !== undefined && sessionStorage.mexpname !== undefined) {
948 let menu = document.getElementById("msidenav");
949 menu.innerHTML = sessionStorage.msidenav;
950 let item = menu.children;
951 for (let i = 0; i < item.length; i++) {
952 if (item[i].className !== "mseparator") {
953 if (current_page.search(item[i].innerHTML) !== -1)
954 item[i].className = "mmenuitem mmenuitemsel";
955 else
956 item[i].className = "mmenuitem";
957 }
958 }
959 document.getElementById("mheader_expt_name").innerHTML = sessionStorage.mexpname;
960
961 // now the side navigation has its full width, adjust the main body and make it visible
962 let m = document.getElementById("mmain");
963 if (m !== undefined) {
964 m.style.marginLeft = document.getElementById("msidenav").clientWidth + "px";
965 m.style.opacity = "1";
966 }
967 }
968
969 // request it from server, since it might have changed
970 mjsonrpc_db_get_values(["/Experiment/Name", "/Experiment/Menu", "/Experiment/Menu Buttons",
971 "/Custom", "/Script", "/Alias"]).then(function (rpc) {
972
973 let expt_name = rpc.result.data[0];
974 let menu = rpc.result.data[1];
975 let buttons = rpc.result.data[2];
976 let custom = rpc.result.data[3];
977 let script = rpc.result.data[4];
978 let alias = rpc.result.data[5];
979
980 document.getElementById("mheader_expt_name").innerHTML = expt_name;
981 sessionStorage.setItem("mexpname", expt_name);
982
983 // preload spinning wheel for later use
984 if (mhttpd_spinning_wheel === undefined) {
985 mhttpd_spinning_wheel = new Image();
986 mhttpd_spinning_wheel.src = "spinning-wheel.gif";
987 }
988
989 // menu buttons
990 let b = [];
991 if (menu) {
992 for (let k in menu) {
993 if (k.indexOf('/') >= 0) // skip <key>/last_written and <key>/name
994 continue;
995 if (menu[k]) // show button if not disabled
996 b.push(menu[k + "/name"]);
997 }
998 } else if (buttons && buttons.length > 0) {
999 b = buttons.split(",");
1000 }
1001
1002 if (!b || b.length < 1) {
1003 b = ["Status", "ODB", "Messages", "Chat", "ELog", "Alarms", "Programs", "History", "MSCB", "Sequencer", "Config", "Help"];
1004 }
1005
1006 let html = "";
1007
1008 for (let i = 0; i < b.length; i++) {
1009 let bb = b[i].trim();
1010 let cc = "mmenuitem";
1011 if (bb === current_page) {
1012 cc += " mmenuitemsel";
1013 }
1014 html += "<div class='" + cc + "'><a href='?cmd=" + bb + "' class='mmenulink'>" + bb + "</a></div>\n";
1015 }
1016
1017 // custom
1018 if (custom !== null && Object.keys(custom).length > 0) {
1019 // add separator
1020 html += "<div class='mseparator'></div>\n";
1021 // add menu items recursively
1022 html = mhttpd_add_menu_items(html, custom, current_page, "", 0);
1023 }
1024
1025 // script
1026 if (script !== null && Object.keys(script).length > 0) {
1027 // add separator
1028 html += "<div class='mseparator'></div>\n";
1029
1030 for (let b in script) {
1031 if (b.indexOf('/') >= 0) // skip <key>/last_written and <key>/name
1032 continue;
1033 let n = script[b + "/name"];
1034 //html += "<div class='mmenuitem'><a href='?script=" + b + "' class='mmenulink'>" + n + "</a></div>\n";
1035 html += "<div class='mmenuitem'><button class='mbutton' onclick='mhttpd_exec_script(\"" + n + "\")'>" + n + "</button></div>\n";
1036 }
1037
1038 }
1039
1040 // alias
1041 if (alias !== null && Object.keys(alias).length > 0) {
1042 // add separator
1043 html += "<div class='mseparator'></div>\n";
1044
1045 for (b in alias) {
1046 if (b.indexOf('/') >= 0) // skip <key>/last_written and <key>/name
1047 continue;
1048 n = alias[b + "/name"];
1049 if (n.substr(n.length - 1) === "&") {
1050 n = n.substr(0, n.length - 1);
1051 html += "<div class='mmenuitem'><a href='" + alias[b] + "' class='mmenulink' target='_blank'>" + n + "&#8599;</a></div>\n";
1052 } else {
1053 html += "<div class='mmenuitem'><a href='" + alias[b] + "' class='mmenulink'>" + n + "</a></div>\n";
1054 }
1055 }
1056
1057 }
1058
1059 // dummy spacer to fix scrolling to the bottom, must be at least header height
1060 html += "<div style='height: 64px;'></div>\n";
1061
1062 document.getElementById("msidenav").innerHTML = html;
1063
1064 // re-adjust size of mmain element if menu has changed
1065 let m = document.getElementById("mmain");
1066 if (m !== undefined) {
1067 m.style.marginLeft = document.getElementById("msidenav").clientWidth + "px";
1068 m.style.opacity = "1";
1069 }
1070
1071 // cache navigation buttons in browser local storage
1072 sessionStorage.setItem("msidenav", html);
1073
1074 // show/hide sidenav according to local storage settings
1075 mhttpd_show_menu(mhttpdConfig().showMenu);
1076
1077 // hide all invisible menu items after layout has been finished
1078 let mi = document.getElementsByClassName('mmenuitem');
1079 for (m of mi) {
1080 let p = m.parentElement;
1081 if (p.style.visibility === "hidden") {
1082 p.style.visibility = "visible";
1083 p.style.display = "none";
1084 }
1085 }
1086 mi = document.getElementsByClassName('msubmenuitem');
1087 for (m of mi) {
1088 let p = m.parentElement;
1089 if (p.style.visibility === "hidden") {
1090 p.style.visibility = "visible";
1091 p.style.display = "none";
1092 }
1093 }
1094
1095 }).then(function () {
1096 if (callback !== undefined)
1097 callback();
1098 }).catch(function (error) {
1099 mjsonrpc_error_alert(error);
1100 });
1101 }
1102
1103 // store refresh interval and do initial refresh
1104 if (interval === undefined)
1105 interval = 1000;
1106 mhttpd_refresh_interval = interval;
1107 mhttpd_refresh_paused = false;
1108
1109 // history interval is static for now
1110 mhttpd_refresh_history_interval = 30000;
1111
1112 // trigger first refresh
1113 mhttpd_refresh();
1114 mhttpd_refresh_history();
1115}
1116
1117function mhttpd_find_menuitem(current_page, submenu) {
1118 for (let b in submenu) {
1119
1120 if (typeof submenu[b] === "object") { // recurse into submenu
1121 let flag = mhttpd_find_menuitem(current_page, submenu[b]);
1122 if (flag)
1123 return true;
1124 }
1125
1126 if (typeof submenu[b] === "string") {
1127 let s = submenu[b];
1128 if (s[s.length-1] === '&')
1129 s = s.slice(0, -1);
1130
1131 if (b.indexOf('/name') !== -1 && s.toLowerCase() === current_page.toLowerCase())
1132 return true;
1133
1134 }
1135 }
1136
1137 return false;
1138}
1139
1140function mhttpd_add_menu_items(html, custom, current_page, path, level) {
1141 for (let b in custom) {
1142 if (b.indexOf('/') >= 0) // skip <key>/last_written and <key>/name
1143 continue;
1144 if (b === "path" || b === "images" || b === "default") // skip "path", "Images" and "default"
1145 continue;
1146
1147 if (typeof custom[b] === "object") { // submenu created by subdirectory in ODB
1148 let cc = "msubmenuitem";
1149 let l = "";
1150 let expand = mhttpd_find_menuitem(current_page, custom[b]);
1151
1152 // add blanks according to nesting level
1153 for (let i=0 ; i<level ; i++)
1154 l += "&nbsp;&nbsp;";
1155
1156 if (expand)
1157 l += "&#9662;&nbsp;"; // caret down
1158 else
1159 l += "&#9656;&nbsp;"; // caret right
1160
1161 l += "&nbsp;";
1162 l += custom[b + "/name"];
1163 let p = path;
1164 if (p !== "")
1165 p += "/";
1166 p += b;
1167
1168 html += "<div class='" + cc + "' onclick='mhttpd_submenu(this)'><div class='mmenulink'>" + l + "</div></div>\n";
1169
1170 if (expand)
1171 html += "<div>"; // do not hide submenu if current page is under it
1172 else
1173 html += "<div style='visibility: hidden'>";
1174 html = mhttpd_add_menu_items(html, custom[b], current_page, p, level+1);
1175 html += "</div>";
1176 } else if (typeof custom[b] === "string") { // skip any items that don't have type of string, since can't be valid links
1177 let cc = "mmenuitem";
1178 let mitem = custom[b + "/name"];
1179 if (mitem.slice(-1) === '&')
1180 mitem = mitem.slice(0, -1);
1181 if (current_page.search(mitem) !== -1 ||
1182 current_page.search(mitem.toLowerCase()) !== -1)
1183 cc += " mmenuitemsel";
1184 let ln = "";
1185 for (let i = 0; i < level; i++)
1186 ln += "&nbsp;&nbsp;";
1187 ln += custom[b + "/name"];
1188
1189 if (custom[b].substring(0, 5) === "link:") {
1190 let lt = custom[b].substring(5);
1191 html += "<div class='" + cc + "'><a href='" + lt + "' class='mmenulink'>" + ln + "</a></div>\n";
1192 } else {
1193 let lt = path;
1194 if (lt !== "")
1195 lt += "/";
1196 lt += custom[b + "/name"];
1197 if (ln.slice(-1) === '!')
1198 continue;
1199 let target = "";
1200 let style = "";
1201 let clicklink = false;
1202 // if (level > 0)
1203 // style = "style='display: none'";
1204
1205 // Accepted file extentions, if not create onclick call from value
1206 const validExtensionsRegex = /\.([a-zA-Z0-9]+)$/; // /\.(txt|jpg|jpeg|png|gif|pdf|csv|html|htm|js|css)$/i;
1207
1208 if (ln.slice(-1) === '&') {
1209 ln = ln.slice(0, -1);
1210 lt = lt.slice(0, -1);
1211 target += "target='_blank' ";
1212 } else if (!validExtensionsRegex.test(custom[b])) {
1213 clicklink = true;
1214 }
1215
1216 if (clicklink) {
1217 html += `<div ${style} class="mmenuitem" onclick="${custom[b]}"><a style="width: 100%;display: inline-block;text-decoration: none;color: #404040;" class="mmenulink">${ln}</a></div>\n`;
1218 } else {
1219 html += "<div class='" + cc + "' " + style + "><a " + target + " href='?cmd=custom&page=" + lt + "' class='mmenulink'>" + ln + "</a></div>\n";
1220 }
1221
1222 }
1223 }
1224 }
1225
1226 return html;
1227}
1228
1229function mhttpd_submenu(o) {
1230 let i;
1231 for (i=0 ; o.firstChild.innerHTML.charCodeAt(i) !== 9656 && o.firstChild.innerHTML.charCodeAt(i) !== 9662 ; i++);
1232 if (o.firstChild.innerHTML.charCodeAt(i) === 9656) {
1233 // expand
1234 o.firstChild.innerHTML =
1235 o.firstChild.innerHTML.substring(0, i-1) +
1236 String.fromCharCode(9662) +
1237 o.firstChild.innerHTML.substring(i+1);
1238 o.nextElementSibling.style.display = "inline";
1239 o.nextElementSibling.style.visibility = "visible";
1240 } else {
1241 // collapse
1242 o.firstChild.innerHTML =
1243 o.firstChild.innerHTML.substring(0, i-1) +
1244 String.fromCharCode(9656) +
1245 o.firstChild.innerHTML.substring(i+1);
1246 o.nextElementSibling.style.display = "none";
1247 }
1248}
1249
1250function mhttpd_refresh_pause(flag) {
1251 mhttpd_refresh_paused = flag;
1252}
1253
1254function mhttpd_set_refresh_interval(interval) {
1255 mhttpd_refresh_interval = interval;
1256}
1257
1258function getMElements(name) {
1259 // collect all <div name=[name] >
1260 let e = [];
1261 e.push(...document.getElementsByName(name));
1262
1263 // collect all <div class=[name] >
1264 e.push(...document.getElementsByClassName(name));
1265
1266 return e;
1267}
1268
1269function mhttpd_thermo_draw() {
1270 let ctx = this.firstChild.getContext("2d");
1271 ctx.save();
1272 let w = this.firstChild.width;
1273 let h = this.firstChild.height;
1274 ctx.clearRect(0, 0, w, h);
1275 w = w - 1; // space for full circles
1276 h = h - 1;
1277
1278 if (this.dataset.scale === "1") {
1279 w = w / 2;
1280 w = Math.floor(w / 4) * 4;
1281 }
1282
1283 if (this.dataset.printValue === "1") {
1284 h = h - 14;
1285 }
1286
1287 let x0 = Math.round(w / 4 * 0);
1288 let x1 = Math.round(w / 4 * 1);
1289 let x2 = Math.round(w / 4 * 2);
1290 let x3 = Math.round(w / 4 * 3);
1291
1292 let v = this.value;
1293 if (v < this.dataset.minValue)
1294 v = this.dataset.minValue;
1295 if (v > this.dataset.maxValue)
1296 v = this.dataset.maxValue;
1297 let yt = (h - 4 * x1) - (h - 5 * x1) * (v - this.dataset.minValue) / (this.dataset.maxValue - this.dataset.minValue);
1298
1299 ctx.translate(0.5, 0.5);
1300 ctx.strokeStyle = "#000000";
1301 ctx.fillStyle = "#FFFFFF";
1302 ctx.lineWidth = 1;
1303
1304 // outer "glass"
1305 ctx.beginPath();
1306 ctx.arc(x2, x1, x1, Math.PI, 0);
1307 ctx.lineTo(x3, h - x1 * 4);
1308 ctx.lineTo(x3, h - x1 * 2 * (1 + Math.sin(60 / 360 * 2 * Math.PI)));
1309 ctx.arc(x2, h - x2, x2, 300 / 360 * 2 * Math.PI, 240 / 360 * 2 * Math.PI);
1310 ctx.lineTo(x1, h - x1 * 2 * (1 + Math.sin(60 / 360 * 2 * Math.PI)));
1311 ctx.lineTo(x1, x1);
1312 ctx.stroke();
1313 if (this.dataset.backgroundColor !== undefined) {
1314 ctx.fillStyle = this.dataset.backgroundColor;
1315 ctx.fill();
1316 }
1317
1318 // inner "fluid"
1319 if (this.dataset.color === undefined) {
1320 ctx.strokeStyle = "#000000";
1321 ctx.fillStyle = "#000000";
1322 } else {
1323 ctx.strokeStyle = this.dataset.color;
1324 ctx.fillStyle = this.dataset.color;
1325 }
1326
1327 ctx.beginPath();
1328 ctx.moveTo(x1 + 3, yt);
1329 ctx.lineTo(x3 - 3, yt);
1330 ctx.lineTo(x3 - 3, h - x2);
1331 ctx.lineTo(x1 + 3, h - x2);
1332 ctx.lineTo(x1 + 3, yt);
1333 ctx.stroke();
1334 ctx.fill();
1335
1336 ctx.beginPath();
1337 ctx.arc(x2, h - x2, x2 - 4, 0, 2 * Math.PI);
1338 ctx.stroke();
1339 ctx.fill();
1340
1341 // re-draw outer "glass"
1342 ctx.strokeStyle = "#000000";
1343 ctx.fillStyle = "#FFFFFF";
1344 ctx.lineWidth = 1;
1345 ctx.beginPath();
1346 ctx.arc(x2, x1, x1, Math.PI, 0);
1347 ctx.lineTo(x3, h - x1 * 4);
1348 ctx.lineTo(x3, h - x1 * 2 * (1 + Math.sin(60 / 360 * 2 * Math.PI)));
1349 ctx.arc(x2, h - x2, x2, 300 / 360 * 2 * Math.PI, 240 / 360 * 2 * Math.PI);
1350 ctx.lineTo(x1, h - x1 * 2 * (1 + Math.sin(60 / 360 * 2 * Math.PI)));
1351 ctx.lineTo(x1, x1);
1352 ctx.stroke();
1353
1354 // optional scale
1355 if (this.dataset.scale === "1") {
1356 ctx.beginPath();
1357 ctx.moveTo(x3 + x1 / 2, x1);
1358 ctx.lineTo(x3 + x1, x1);
1359 ctx.moveTo(x3 + x1 / 2, h - 4 * x1);
1360 ctx.lineTo(x3 + x1, h - 4 * x1);
1361 ctx.stroke();
1362
1363 ctx.font = "12px sans-serif";
1364 ctx.fillStyle = "#000000";
1365 ctx.strokeStyle = "#000000";
1366 ctx.textBaseline = "middle";
1367
1368 let mintext = this.dataset.minValue;
1369 let maxtext = this.dataset.maxValue;
1370
1371 if (this.dataset.format !== undefined) {
1372 mintext = mie_to_string(parseInt(this.dataset.tid), this.dataset.minValue, this.dataset.format);
1373 maxtext = mie_to_string(parseInt(this.dataset.tid), this.dataset.maxValue, this.dataset.format);
1374 }
1375
1376 ctx.fillText(mintext, 4.5 * x1, h - 4 * x1, 3.5 * x1);
1377 ctx.fillText(maxtext, 4.5 * x1, x1, 3.5 * x1);
1378 }
1379
1380 // optional value display
1381 if (this.dataset.printValue === "1") {
1382 ctx.font = "12px sans-serif";
1383 ctx.fillStyle = "#000000";
1384 ctx.strokeStyle = "#000000";
1385 ctx.textBaseline = "bottom";
1386 ctx.textAlign = "center";
1387
1388 let valuetext = this.value;
1389
1390 if (this.dataset.format !== undefined) {
1391 valuetext = mie_to_string(parseInt(this.dataset.tid), this.value, this.dataset.format);
1392 }
1393
1394 ctx.fillText(valuetext, x2, this.firstChild.height, 4 * x1);
1395 }
1396
1397 ctx.restore();
1398}
1399
1400function mhttpd_gauge_draw() {
1401 let ctx = this.firstChild.getContext("2d");
1402 ctx.save();
1403 let w = this.firstChild.width;
1404 let h = this.firstChild.height;
1405 let y = h;
1406 if (this.dataset.scale === "1")
1407 y -= 15;
1408 else
1409 y -= 1;
1410 ctx.clearRect(0, 0, w, h);
1411
1412 let v = this.value;
1413 if (v < this.dataset.minValue)
1414 v = this.dataset.minValue;
1415 if (v > this.dataset.maxValue)
1416 v = this.dataset.maxValue;
1417 v = (v - this.dataset.minValue) / (this.dataset.maxValue - this.dataset.minValue);
1418
1419 ctx.translate(0.5, 0.5);
1420 ctx.strokeStyle = "#000000";
1421 ctx.fillStyle = "#FFFFFF";
1422 ctx.lineWidth = 1;
1423
1424 // outer ring
1425 ctx.beginPath();
1426 ctx.arc(w / 2, y, w / 2 - 1, Math.PI, 0);
1427 ctx.lineTo(w - 4, y);
1428 ctx.arc(w / 2, y, w / 2 - 4, 0, Math.PI, true);
1429 ctx.lineTo(4, y);
1430 ctx.fillStyle = this.dataset.color;
1431 ctx.fill();
1432
1433 // value bar
1434 ctx.beginPath();
1435 ctx.fillStyle = this.dataset.color;
1436 ctx.strokeStyle = this.dataset.color;
1437 ctx.arc(w / 2, y, w / 2 - 6, Math.PI, (1 + v) * Math.PI);
1438 ctx.arc(w / 2, y, w / 2 - w / 5, (1 + v) * Math.PI, Math.PI, true);
1439 ctx.fill();
1440
1441 // black frame
1442 if (this.dataset.frame) {
1443 ctx.strokeStyle = "#000000";
1444 ctx.lineWidth = 1;
1445 ctx.beginPath();
1446 ctx.arc(w / 2, y, w / 2 - 6, Math.PI, 0);
1447 ctx.lineTo(w - w / 5, y);
1448 ctx.arc(w / 2, y, w / 2 - w / 5, 0, Math.PI, true);
1449 ctx.lineTo(6, y);
1450 ctx.stroke();
1451 }
1452
1453 // optional value display
1454 if (this.dataset.printValue === "1") {
1455 ctx.font = "12px sans-serif";
1456 ctx.fillStyle = "#000000";
1457 ctx.strokeStyle = "#000000";
1458 ctx.textBaseline = "bottom";
1459 ctx.textAlign = "center";
1460
1461 let valtext = this.value;
1462
1463 if (this.dataset.format !== undefined) {
1464 valtext = mie_to_string(parseInt(this.dataset.tid), this.value, this.dataset.format);
1465 }
1466
1467 ctx.fillText(valtext, w / 2, y, w);
1468 }
1469
1470 // optional scale display
1471 if (this.dataset.scale === "1") {
1472 ctx.font = "12px sans-serif";
1473 ctx.fillStyle = "#000000";
1474 ctx.strokeStyle = "#000000";
1475 ctx.textBaseline = "bottom";
1476 ctx.textAlign = "center";
1477
1478 let mintext = this.dataset.minValue;
1479 let maxtext = this.dataset.maxValue;
1480
1481 if (this.dataset.format !== undefined) {
1482 mintext = mie_to_string(parseInt(this.dataset.tid), this.dataset.minValue, this.dataset.format);
1483 maxtext = mie_to_string(parseInt(this.dataset.tid), this.dataset.maxValue, this.dataset.format);
1484 }
1485
1486 ctx.fillText(mintext, 0.1 * w, h, 0.2 * w);
1487 ctx.fillText(maxtext, 0.9 * w, h, 0.2 * w);
1488 }
1489
1490 ctx.restore();
1491}
1492
1493function mhttpd_vaxis_draw() {
1494 let ctx = this.firstChild.getContext("2d");
1495 ctx.save();
1496 let w = this.firstChild.width;
1497 let h = this.firstChild.height;
1498 ctx.clearRect(0, 0, w, h);
1499
1500 let line = true;
1501 if (this.dataset.line === "0")
1502 line = false;
1503 let log = false;
1504 if (this.dataset.log === "1")
1505 log = true;
1506
1507 let scaleMin = 0;
1508 let scaleMax = 1;
1509 if (this.dataset.minValue !== undefined)
1510 scaleMin = parseFloat(this.dataset.minValue);
1511 if (log && scaleMin === 0)
1512 scaleMin = 1E-3;
1513 if (this.dataset.maxValue !== undefined)
1514 scaleMax = parseFloat(this.dataset.maxValue);
1515 if (scaleMin === scaleMax)
1516 scaleMax += 1;
1517
1518 ctx.translate(0.5, 0.5);
1519 ctx.strokeStyle = "#000000";
1520 ctx.fillStyle = "#FFFFFF";
1521 ctx.lineWidth = 1;
1522
1523 if (this.style.textAlign === "left") {
1524 ctx.translate(-0.5, -0.5);
1525 vaxisDraw(ctx, 0, h - 1, h - 2, line, 4, 8, 10, 12, 0, scaleMin, scaleMax, log);
1526 } else {
1527 ctx.translate(-0.5, -0.5);
1528 vaxisDraw(ctx, w, h - 1, h - 2, line, 4, 8, 10, 12, 0, scaleMin, scaleMax, log);
1529 }
1530
1531 ctx.restore();
1532}
1533
1534function mhttpd_haxis_draw() {
1535 let ctx = this.firstChild.getContext("2d");
1536 ctx.save();
1537 let w = this.firstChild.width;
1538 let h = this.firstChild.height;
1539 ctx.clearRect(0, 0, w, h);
1540
1541 let line = true;
1542 if (this.dataset.line === "0")
1543 line = false;
1544 let log = false;
1545 if (this.dataset.log === "1")
1546 log = true;
1547
1548 let scaleMin = 0;
1549 let scaleMax = 1;
1550 if (this.dataset.minValue !== undefined)
1551 scaleMin = parseFloat(this.dataset.minValue);
1552 if (log && scaleMin === 0)
1553 scaleMin = 1E-3;
1554 if (this.dataset.maxValue !== undefined)
1555 scaleMax = parseFloat(this.dataset.maxValue);
1556 if (scaleMin === scaleMax)
1557 scaleMax += 1;
1558
1559 ctx.translate(-0.5, 0);
1560 ctx.strokeStyle = "#000000";
1561 ctx.fillStyle = "#FFFFFF";
1562 ctx.lineWidth = 1;
1563
1564 if (this.style.verticalAlign === "top") {
1565 ctx.translate(0.5, 0.5);
1566 haxisDraw(ctx, 1, 0, w - 2, line, 4, 8, 10, 10, 0, scaleMin, scaleMax, log);
1567 } else {
1568 ctx.translate(0.5, -0.5);
1569 haxisDraw(ctx, 1, h, w - 2, line, 4, 8, 10, 20, 0, scaleMin, scaleMax, log);
1570 }
1571 ctx.restore();
1572}
1573
1574String.prototype.stripZeros = function () {
1575 let s = this.trim();
1576 if (s.search("[.]") >= 0) {
1577 let i = s.search("[e]");
1578 if (i >= 0) {
1579 while (s.charAt(i - 1) === "0") {
1580 s = s.substring(0, i - 1) + s.substring(i);
1581 i--;
1582 }
1583 if (s.charAt(i - 1) === ".")
1584 s = s.substring(0, i - 1) + s.substring(i);
1585 } else {
1586 while (s.charAt(s.length - 1) === "0")
1587 s = s.substring(0, s.length - 1);
1588 if (s.charAt(s.length - 1) === ".")
1589 s = s.substring(0, s.length - 1);
1590 }
1591 }
1592 return s;
1593};
1594
1595function haxisDraw(ctx, x1, y1, width, line, minor, major, text, label, grid, xmin, xmax, logaxis) {
1596 let dx, int_dx, frac_dx, x_act, label_dx, major_dx, x_screen, maxwidth;
1597 let tick_base, major_base, label_base, n_sig1, n_sig2, xs;
1598 let base = [1, 2, 5, 10, 20, 50, 100, 200, 500, 1000];
1599
1600 ctx.textAlign = "center";
1601 ctx.textBaseline = "top";
1602
1603 if (xmax <= xmin || width <= 0)
1604 return;
1605
1606 if (logaxis) {
1607 dx = Math.pow(10, Math.floor(Math.log(xmin) / Math.log(10)));
1608 if (dx === 0)
1609 dx = 1E-10;
1610 label_dx = dx;
1611 major_dx = dx * 10;
1612 n_sig1 = 4;
1613 } else {
1614 /* use 6 as min tick distance */
1615 dx = (xmax - xmin) / (width / 6);
1616
1617 int_dx = Math.floor(Math.log(dx) / Math.log(10));
1618 frac_dx = Math.log(dx) / Math.log(10) - int_dx;
1619
1620 if (frac_dx < 0) {
1621 frac_dx += 1;
1622 int_dx -= 1;
1623 }
1624
1625 tick_base = frac_dx < (Math.log(2) / Math.log(10)) ? 1 : frac_dx < (Math.log(5) / Math.log(10)) ? 2 : 3;
1626 major_base = label_base = tick_base + 1;
1627
1628 /* rounding up of dx, label_dx */
1629 dx = Math.pow(10, int_dx) * base[tick_base];
1630 major_dx = Math.pow(10, int_dx) * base[major_base];
1631 label_dx = major_dx;
1632
1633 do {
1634 /* number of significant digits */
1635 if (xmin === 0)
1636 n_sig1 = 0;
1637 else
1638 n_sig1 = Math.floor(Math.log(Math.abs(xmin)) / Math.log(10)) - Math.floor(Math.log(Math.abs(label_dx)) / Math.log(10)) + 1;
1639
1640 if (xmax === 0)
1641 n_sig2 = 0;
1642 else
1643 n_sig2 = Math.floor(Math.log(Math.abs(xmax)) / Math.log(10)) - Math.floor(Math.log(Math.abs(label_dx)) / Math.log(10)) + 1;
1644
1645 n_sig1 = Math.max(n_sig1, n_sig2);
1646
1647 // toPrecision displays 1050 with 3 digits as 1.05e+3, so increase presicion to number of digits
1648 if (Math.abs(xmin) < 100000)
1649 n_sig1 = Math.max(n_sig1, Math.floor(Math.log(Math.abs(xmin)) / Math.log(10)) + 1);
1650 if (Math.abs(xmax) < 100000)
1651 n_sig1 = Math.max(n_sig1, Math.floor(Math.log(Math.abs(xmax)) / Math.log(10)) + 1);
1652
1653 /* determination of maximal width of labels */
1654 let str = (Math.floor(xmin / dx) * dx).toPrecision(n_sig1);
1655 let ext = ctx.measureText(str);
1656 maxwidth = ext.width;
1657
1658 str = (Math.floor(xmax / dx) * dx).toPrecision(n_sig1).stripZeros();
1659 ext = ctx.measureText(str);
1660 maxwidth = Math.max(maxwidth, ext.width);
1661 str = (Math.floor(xmax / dx) * dx + label_dx).toPrecision(n_sig1).stripZeros();
1662 maxwidth = Math.max(maxwidth, ext.width);
1663
1664 /* increasing label_dx, if labels would overlap */
1665 if (maxwidth > 0.5 * label_dx / (xmax - xmin) * width) {
1666 label_base++;
1667 label_dx = Math.pow(10, int_dx) * base[label_base];
1668 if (label_base % 3 === 2 && major_base % 3 === 1) {
1669 major_base++;
1670 major_dx = Math.pow(10, int_dx) * base[major_base];
1671 }
1672 } else
1673 break;
1674
1675 } while (true);
1676 }
1677
1678 if (y1 > 0) {
1679 minor = -minor;
1680 major = -major;
1681 text = -text;
1682 label = -label;
1683 }
1684
1685 x_act = Math.floor(xmin / dx) * dx;
1686
1687 let last_label_x = x1;
1688
1689 if (line === true)
1690 ctx.drawLine(x1, y1, x1 + width, y1);
1691
1692 do {
1693 if (logaxis)
1694 x_screen = (Math.log(x_act) - Math.log(xmin)) / (Math.log(xmax) - Math.log(xmin)) * width + x1;
1695 else
1696 x_screen = (x_act - xmin) / (xmax - xmin) * width + x1;
1697 xs = Math.floor(x_screen + 0.5);
1698
1699 if (x_screen > x1 + width + 0.001)
1700 break;
1701
1702 if (x_screen >= x1) {
1703 if (Math.abs(Math.floor(x_act / major_dx + 0.5) - x_act / major_dx) <
1704 dx / major_dx / 10.0) {
1705
1706 if (Math.abs(Math.floor(x_act / label_dx + 0.5) - x_act / label_dx) <
1707 dx / label_dx / 10.0) {
1708 /* label tick mark */
1709 ctx.drawLine(xs, y1, xs, y1 + text);
1710
1711 /* grid line */
1712 if (grid != 0 && xs > x1 && xs < x1 + width)
1713 ctx.drawLine(xs, y1, xs, y1 + grid);
1714
1715 /* label */
1716 if (label != 0) {
1717 str = x_act.toPrecision(n_sig1).stripZeros();
1718 ext = ctx.measureText(str);
1719 ctx.save();
1720 ctx.fillStyle = "black";
1721 if (xs - ext.width / 2 > x1 &&
1722 xs + ext.width / 2 < x1 + width)
1723 ctx.fillText(str, xs, y1 + label);
1724 ctx.restore();
1725 last_label_x = xs + ext.width / 2;
1726 }
1727 } else {
1728 /* major tick mark */
1729 ctx.drawLine(xs, y1, xs, y1 + major);
1730
1731 /* grid line */
1732 if (grid != 0 && xs > x1 && xs < x1 + width)
1733 ctx.drawLine(xs, y1 - 1, xs, y1 + grid);
1734 }
1735
1736 if (logaxis) {
1737 dx *= 10;
1738 major_dx *= 10;
1739 label_dx *= 10;
1740 }
1741 } else
1742 /* minor tick mark */
1743 ctx.drawLine(xs, y1, xs, y1 + minor);
1744
1745 /* for logaxis, also put labes on minor tick marks */
1746 if (logaxis) {
1747 if (label != 0) {
1748 str = x_act.toPrecision(n_sig1).stripZeros();
1749 ext = ctx.measureText(str);
1750 ctx.save();
1751 ctx.fillStyle = "black";
1752 if (xs - ext.width / 2 > x1 &&
1753 xs + ext.width / 2 < x1 + width &&
1754 xs - ext.width / 2 > last_label_x + 2)
1755 ctx.fillText(str, xs, y1 + label);
1756 ctx.restore();
1757
1758 last_label_x = xs + ext.width / 2;
1759 }
1760 }
1761 }
1762
1763 x_act += dx;
1764
1765 /* supress 1.23E-17 ... */
1766 if (Math.abs(x_act) < dx / 100)
1767 x_act = 0;
1768
1769 } while (1);
1770}
1771
1772
1773function vaxisDraw(ctx, x1, y1, height, line, minor, major, text, label, grid, ymin, ymax, logaxis) {
1774 let dy, int_dy, frac_dy, y_act, label_dy, major_dy, y_screen, maxwidth;
1775 let tick_base, major_base, label_base, n_sig1, n_sig2, ys;
1776 let base = [1, 2, 5, 10, 20, 50, 100, 200, 500, 1000];
1777
1778 if (x1 > 0)
1779 ctx.textAlign = "right";
1780 else
1781 ctx.textAlign = "left";
1782 ctx.textBaseline = "middle";
1783 let textHeight = parseInt(ctx.font.match(/\d+/)[0]);
1784
1785 if (ymax <= ymin || height <= 0)
1786 return;
1787
1788 if (logaxis) {
1789 dy = Math.pow(10, Math.floor(Math.log(ymin) / Math.log(10)));
1790 if (dy === 0)
1791 dy = 1E-10;
1792 label_dy = dy;
1793 major_dy = dy * 10;
1794 n_sig1 = 4;
1795 } else {
1796 /* use 6 as min tick distance */
1797 dy = (ymax - ymin) / (height / 6);
1798
1799 int_dy = Math.floor(Math.log(dy) / Math.log(10));
1800 frac_dy = Math.log(dy) / Math.log(10) - int_dy;
1801
1802 if (frac_dy < 0) {
1803 frac_dy += 1;
1804 int_dy -= 1;
1805 }
1806
1807 tick_base = frac_dy < (Math.log(2) / Math.log(10)) ? 1 : frac_dy < (Math.log(5) / Math.log(10)) ? 2 : 3;
1808 major_base = label_base = tick_base + 1;
1809
1810 /* rounding up of dy, label_dy */
1811 dy = Math.pow(10, int_dy) * base[tick_base];
1812 major_dy = Math.pow(10, int_dy) * base[major_base];
1813 label_dy = major_dy;
1814
1815 /* number of significant digits */
1816 if (ymin === 0)
1817 n_sig1 = 0;
1818 else
1819 n_sig1 = Math.floor(Math.log(Math.abs(xmin)) / Math.log(10)) - Math.floor(Math.log(Math.abs(label_dy)) / Math.log(10)) + 1;
1820
1821 if (ymax === 0)
1822 n_sig2 = 0;
1823 else
1824 n_sig2 = Math.floor(Math.log(Math.abs(ymax)) / Math.log(10)) - Math.floor(Math.log(Math.abs(label_dy)) / Math.log(10)) + 1;
1825
1826 n_sig1 = Math.max(n_sig1, n_sig2);
1827
1828 // toPrecision displays 1050 with 3 digits as 1.05e+3, so increase presicion to number of digits
1829 if (Math.abs(ymin) < 100000)
1830 n_sig1 = Math.max(n_sig1, Math.floor(Math.log(Math.abs(ymin)) / Math.log(10)) + 1);
1831 if (Math.abs(ymax) < 100000)
1832 n_sig1 = Math.max(n_sig1, Math.floor(Math.log(Math.abs(ymax)) / Math.log(10)) + 1);
1833
1834 /* increase label_dy if labels would overlap */
1835 while (label_dy / (ymax - ymin) * height < 1.5 * textHeight) {
1836 label_base++;
1837 label_dy = Math.pow(10, int_dy) * base[label_base];
1838 if (label_base % 3 === 2 && major_base % 3 === 1) {
1839 major_base++;
1840 major_dy = Math.pow(10, int_dy) * base[major_base];
1841 }
1842 }
1843 }
1844
1845 if (x1 > 0) {
1846 minor = -minor;
1847 major = -major;
1848 text = -text;
1849 label = -label;
1850 }
1851
1852 y_act = Math.floor(ymin / dy) * dy;
1853
1854 let last_label_y = y1;
1855
1856 if (line === true)
1857 ctx.drawLine(x1, y1, x1, y1 - height);
1858
1859 do {
1860 if (logaxis)
1861 y_screen = y1 - (Math.log(y_act) - Math.log(ymin)) / (Math.log(ymax) - Math.log(ymin)) * height;
1862 else
1863 y_screen = y1 - (y_act - ymin) / (ymax - ymin) * height;
1864 ys = Math.floor(y_screen + 0.5);
1865
1866 if (y_screen < y1 - height - 0.001)
1867 break;
1868
1869 if (y_screen <= y1 + 0.001) {
1870 if (Math.abs(Math.floor(y_act / major_dy + 0.5) - y_act / major_dy) <
1871 dy / major_dy / 10.0) {
1872
1873 if (Math.abs(Math.floor(y_act / label_dy + 0.5) - y_act / label_dy) <
1874 dy / label_dy / 10.0) {
1875 /* label tick mark */
1876 ctx.drawLine(x1, ys, x1 + text, ys);
1877
1878 /* grid line */
1879 if (grid != 0 && ys < y1 && ys > y1 - height)
1880 ctx.drawLine(x1, ys, x1 + grid, ys);
1881
1882 /* label */
1883 if (label != 0) {
1884 str = y_act.toPrecision(n_sig1).stripZeros();
1885 ctx.save();
1886 ctx.fillStyle = "black";
1887 if (ys - textHeight / 2 > y1 - height &&
1888 ys + textHeight / 2 < y1)
1889 ctx.fillText(str, x1 + label, ys);
1890 ctx.restore();
1891 last_label_y = ys - textHeight / 2;
1892 }
1893 } else {
1894 /* major tick mark */
1895 cts.drawLine(x1, ys, x1 + major, ys);
1896
1897 /* grid line */
1898 if (grid != 0 && ys < y1 && ys > y1 - height)
1899 ctx.drawLine(x1, ys, x1 + grid, ys);
1900 }
1901
1902 if (logaxis) {
1903 dy *= 10;
1904 major_dy *= 10;
1905 label_dy *= 10;
1906 }
1907
1908 } else
1909 /* minor tick mark */
1910 ctx.drawLine(x1, ys, x1 + minor, ys);
1911
1912 /* for logaxis, also put labels on minor tick marks */
1913 if (logaxis) {
1914 if (label != 0) {
1915 str = y_act.toPrecision(n_sig1).stripZeros();
1916 ctx.save();
1917 ctx.fillStyle = "black";
1918 if (ys - textHeight / 2 > y1 - height &&
1919 ys + textHeight / 2 < y1 &&
1920 ys + textHeight < last_label_y + 2)
1921 ctx.fillText(str, x1 + label, ys);
1922 ctx.restore();
1923
1924 last_label_y = ys;
1925 }
1926 }
1927 }
1928
1929 y_act += dy;
1930
1931 /* supress 1.23E-17 ... */
1932 if (Math.abs(y_act) < dy / 100)
1933 y_act = 0;
1934
1935 } while (1);
1936}
1937
1938function mhttpd_resize_sidenav() {
1939 //console.log("mhttpd_resize_sidenav!");
1940 let h = document.getElementById('mheader');
1941 let s = document.getElementById('msidenav');
1942 let top = h.clientHeight + 1 + "px";
1943 if (s.style.top !== top) {
1944 //console.log("httpd_resize_sidenav: top changed from " + s.style.top + " to " + top);
1945 s.style.top = top;
1946 }
1947 let m = document.getElementById('mmain');
1948 let paddingTop = h.clientHeight + 1 + "px";
1949 if (m.style.paddingTop !== paddingTop) {
1950 //console.log("httpd_resize_sidenav: paddingTop changed from " + m.style.paddingTop + " to " + paddingTop);
1951 m.style.paddingTop = paddingTop;
1952 }
1953}
1954
1955function mhttpd_resize_header() {
1956
1957}
1958
1959let mhttpd_last_alarm = 0;
1960
1961function get_options_path(odb_path) {
1962 // Convert "/Path/to/something" to "/Path/to/Options something"
1963 let bits = odb_path.split("/");
1964 bits[bits.length - 1] = "Options " + bits[bits.length - 1];
1965 return bits.join("/");
1966}
1967
1968function evalPath(path) {
1969 let p = path;
1970 // if path is a function, evaluate it
1971 if (p && !p.startsWith('/') && p.includes('(') && p.includes(')'))
1972 p = eval(p);
1973 return p;
1974}
1975
1976function mhttpd_refresh() {
1977
1978 if (!mhttpd_initialized)
1979 return;
1980
1981 if (mhttpd_refresh_id !== undefined)
1982 window.clearTimeout(mhttpd_refresh_id);
1983
1984 // don't do a refresh if we are paused
1985 if (mhttpd_refresh_paused) {
1986 mhttpd_refresh_id = window.setTimeout(mhttpd_refresh, mhttpd_refresh_interval);
1987 return;
1988 }
1989
1990 // don't update page if document is hidden (minimized, covered tab etc), only play alarms
1991 if (document.hidden) {
1992
1993 // only check every 10 seconds for alarm
1994 if (new Date().getTime() > mhttpd_last_alarm + 10000) {
1995
1996 // request current alarms
1997 let req = mjsonrpc_make_request("get_alarms");
1998 mjsonrpc_send_request([req]).then(function (rpc) {
1999
2000 let alarms = rpc[0].result;
2001
2002 // update alarm display
2003 if (alarms.alarm_system_active) {
2004 let n = 0;
2005 for (let a in alarms.alarms)
2006 if (a.alarm_sound)
2007 n++;
2008 if (n > 0)
2009 mhttpd_alarm_play();
2010 }
2011
2012 }).catch(function (error) {
2013 if (error.xhr && (error.xhr.readyState === 4) && ((error.xhr.status === 0) || (error.xhr.status === 503))) {
2014 mhttpd_error('Connection to server broken. Trying to reconnect&nbsp;&nbsp;');
2015 document.getElementById("mheader_error").appendChild(mhttpd_spinning_wheel);
2016 mhttpd_reconnect_id = window.setTimeout(mhttpd_reconnect, 1000);
2017 } else {
2018 mjsonrpc_error_alert(error);
2019 }
2020 });
2021
2022 mhttpd_last_alarm = new Date().getTime();
2023 }
2024
2025 mhttpd_refresh_id = window.setTimeout(mhttpd_refresh, 500);
2026 return;
2027 }
2028
2029 // go through all modb* tags and refresh them -------------------------------------
2030
2031 let modb = getMElements("modb");
2032
2033 let modbvalue = getMElements("modbvalue");
2034 for (let i = 0; i < modbvalue.length; i++) {
2035
2036 let o = modbvalue[i];
2037 if (parseInt(o.dataset.input)) {
2038 if (o.childNodes[0] === undefined) {
2039 ODBInlineEdit(o, evalPath(o.dataset.odbPath), 0);
2040 }
2041 } else {
2042 if (parseInt(o.dataset.odbEditable)) {
2043 if (o.childNodes[0] === undefined || o.childNodes[0].tagName === undefined) {
2044
2045 // add link and event handler if tag is editable
2046 let link = document.createElement('a');
2047 link.href = "#";
2048 link.onclick = function () {
2049 ODBInlineEdit(this.parentElement, evalPath(this.parentElement.dataset.odbPath), 0);
2050 };
2051 link.onfocus = function () {
2052 ODBInlineEdit(this.parentElement, evalPath(this.parentElement.dataset.odbPath), 0);
2053 };
2054
2055 o.innerHTML = "";
2056 o.appendChild(link);
2057 }
2058 } else {
2059
2060 // remove link if present
2061 if (o.childNodes[0] && o.childNodes[0].tagName === 'A')
2062 o.innerHTML = o.childNodes[0].innerHTML;
2063 }
2064 }
2065
2066 modbvalue[i].setValue = function (x, tid) {
2067 if (this.dataset.formula !== undefined)
2068 x = eval(this.dataset.formula);
2069 if (tid === undefined)
2070 tid = TID_DOUBLE;
2071 this.value = x;
2072 this.dataset.tid = tid;
2073
2074 if (this.onchange !== null)
2075 this.onchange();
2076 if (this.dataset.odbLoaded === undefined && this.onload !== null) {
2077 this.onload();
2078 this.dataset.odbLoaded = "1";
2079 }
2080 }
2081
2082 modbvalue[i].sendValueToOdb = function () {
2083 mhttpd_refresh_pause(true);
2084 mjsonrpc_db_set_value(evalPath(this.dataset.odbPath), this.value).then(() =>
2085 mhttpd_refresh_pause(false));
2086 }
2087 }
2088
2089 let modbcheckbox = getMElements("modbcheckbox");
2090 for (let i = 0; i < modbcheckbox.length; i++) {
2091
2092 // add event listener if missing
2093 if (!modbcheckbox[i].mEventListener) {
2094 modbcheckbox[i].addEventListener("click", function () {
2095 if (this.dataset.validate !== undefined) {
2096 let flag = eval(this.dataset.validate)(this.checked, this);
2097 if (!flag) {
2098 mhttpd_refresh();
2099 return;
2100 }
2101 }
2102 mjsonrpc_db_set_value(evalPath(this.dataset.odbPath), this.checked ? 1 : 0).then(rpc =>
2103 mhttpd_refresh());
2104 });
2105 modbcheckbox[i].mEventListener = true;
2106 }
2107 }
2108
2109 let modbselect = getMElements("modbselect");
2110 for (let i = 0; i < modbselect.length; i++) {
2111 if (modbselect[i].dataset.odbPath) {
2112
2113 // add event listener if missing
2114 if (!modbselect[i].mEventListener) {
2115 modbselect[i].addEventListener("change", function (flag) {
2116
2117 if (flag !== true) { // flag === true if control changed by ODB update
2118 if (this.dataset.validate !== undefined) {
2119 let flag = eval(this.dataset.validate)(this.value, this);
2120 if (!flag) {
2121 mhttpd_refresh();
2122 return;
2123 }
2124 }
2125 mjsonrpc_db_set_value(evalPath(this.dataset.odbPath), this.value).then(rpc =>
2126 mhttpd_refresh());
2127 }
2128 });
2129 modbselect[i].mEventListener = true;
2130 }
2131 }
2132
2133 modbselect[i].setValue = function (x) {
2134 this.value = x;
2135
2136 if (this.onchange !== null)
2137 this.onchange();
2138 }
2139
2140 modbselect[i].sendValueToOdb = function () {
2141 mhttpd_refresh_pause(true);
2142 mjsonrpc_db_set_value(evalPath(this.dataset.odbPath), this.value).then(() =>
2143 mhttpd_refresh_pause(false));
2144 }
2145
2146 }
2147
2148 let modbbox = getMElements("modbbox");
2149 for (let i = 0; i < modbbox.length; i++) {
2150 modbbox[i].style.border = "1px solid #808080";
2151 }
2152
2153 // attach "set" function to all ODB buttons
2154 let modbbutton = getMElements("modbbutton");
2155 for (let i = 0; i < modbbutton.length; i++) {
2156 if (!modbbutton[i].mEventListener) {
2157 modbbutton[i].addEventListener("click", function () {
2158
2159 if (this.dataset.validate !== undefined) {
2160 let flag = eval(this.dataset.validate)(this);
2161 if (!flag) {
2162 mhttpd_refresh();
2163 return;
2164 }
2165 }
2166
2167 mjsonrpc_db_set_value(evalPath(this.dataset.odbPath), this.dataset.odbValue).then(rpc =>
2168 mhttpd_refresh());
2169 });
2170 modbbutton[i].mEventListener = true;
2171 }
2172 }
2173
2174 // replace all horizontal bars with proper <div>'s
2175 let modbhbar = getMElements("modbhbar");
2176 for (let i = 0; i < modbhbar.length; i++) {
2177 if (modbhbar[i].childNodes === undefined || modbhbar[i].childNodes[0] === undefined ||
2178 modbhbar[i].childNodes[0].tagName !== 'DIV') {
2179 modbhbar[i].style.display = "block";
2180 if (modbhbar[i].style.position === "")
2181 modbhbar[i].style.position = "relative";
2182 modbhbar[i].style.border = "1px solid #808080";
2183 let color = modbhbar[i].style.color;
2184 modbhbar[i].innerHTML = "<div style='background-color:" + color + ";" + "color:black;" +
2185 "width:0;height:" + modbhbar[i].clientHeight + "px;" +
2186 "position:relative; display:inline-block;border-right:1px solid #808080'>&nbsp;</div>";
2187
2188 modbhbar[i].setValue = function (x, tid) {
2189 if (this.dataset.formula !== undefined)
2190 x = eval(this.dataset.formula);
2191 if (tid === undefined)
2192 tid = TID_DOUBLE;
2193 let mvalue = mie_to_string(tid, x, this.dataset.format);
2194 if (mvalue === "")
2195 mvalue = "(empty)";
2196 let html = mhttpd_escape("&nbsp;" + mvalue);
2197 this.value = x;
2198 if (this.dataset.printValue === "1")
2199 this.children[0].innerHTML = html;
2200 let minValue = parseFloat(this.dataset.minValue);
2201 let maxValue = parseFloat(this.dataset.maxValue);
2202 if (isNaN(minValue))
2203 minValue = 0;
2204 if (this.dataset.log === "1" &&
2205 minValue === 0)
2206 minValue = 1E-3;
2207 if (isNaN(maxValue))
2208 maxValue = 1;
2209 let percent;
2210 if (this.dataset.log === "1")
2211 percent = Math.round(100 * (Math.log(x) - Math.log(minValue)) /
2212 (Math.log(maxValue) - Math.log(minValue)));
2213 else
2214 percent = Math.round(100 * (x - minValue) /
2215 (maxValue - minValue));
2216 if (percent < 0)
2217 percent = 0;
2218 if (percent > 100)
2219 percent = 100;
2220 this.children[0].style.width = percent + "%";
2221 if (this.onchange !== null)
2222 this.onchange();
2223 if (this.dataset.odbLoaded === undefined && this.onload !== null) {
2224 this.onload();
2225 this.dataset.odbLoaded = "1";
2226 }
2227 this.children[0].style.backgroundColor = this.style.color;
2228 }
2229 }
2230 }
2231
2232 // replace all vertical bars with proper <div>'s
2233 let modbvbar = getMElements("modbvbar");
2234 for (let i = 0; i < modbvbar.length; i++) {
2235 if (modbvbar[i].childNodes === undefined || modbvbar[i].childNodes[0] === undefined ||
2236 modbvbar[i].childNodes[0].tagName !== 'DIV') {
2237 modbvbar[i].style.display = "inline-block";
2238 if (modbvbar[i].style.position === "")
2239 modbvbar[i].style.position = "relative";
2240 modbvbar[i].style.border = "1px solid #808080";
2241 let color = modbvbar[i].style.color;
2242 modbvbar[i].innerHTML = "<div style='background-color:" + color + "; height:0; width:100%; position:absolute; bottom:0; left:0; display:inline-block; border-top:1px solid #808080'>&nbsp;</div>";
2243
2244 modbvbar[i].setValue = function (x, tid) {
2245 if (this.dataset.formula !== undefined)
2246 x = eval(this.dataset.formula);
2247 if (tid === undefined)
2248 tid = TID_DOUBLE;
2249 let mvalue = mie_to_string(tid, x, this.dataset.format);
2250 if (mvalue === "")
2251 mvalue = "(empty)";
2252 let html = mhttpd_escape("&nbsp;" + mvalue);
2253 this.value = x;
2254 if (this.dataset.printValue === "1")
2255 this.children[0].innerHTML = html;
2256 let minValue = parseFloat(this.dataset.minValue);
2257 let maxValue = parseFloat(this.dataset.maxValue);
2258 if (isNaN(minValue))
2259 minValue = 0;
2260 if (this.dataset.log === "1" &&
2261 minValue === 0)
2262 minValue = 1E-3;
2263 if (isNaN(maxValue))
2264 maxValue = 1;
2265 let percent;
2266 if (this.dataset.log === "1")
2267 percent = Math.round(100 * (Math.log(x) - Math.log(minValue)) /
2268 (Math.log(maxValue) - Math.log(minValue)));
2269 else
2270 percent = Math.round(100 * (x - minValue) /
2271 (maxValue - minValue));
2272 if (percent < 0)
2273 percent = 0;
2274 if (percent > 100)
2275 percent = 100;
2276 this.children[0].style.height = percent + "%";
2277 if (this.onchange !== null)
2278 this.onchange();
2279 if (this.dataset.odbLoaded === undefined && this.onload !== null) {
2280 this.onload();
2281 this.dataset.odbLoaded = "1";
2282 }
2283 this.children[0].style.backgroundColor = this.style.color;
2284 }
2285 }
2286 }
2287
2288 // replace all thermometers with canvas
2289 let modbthermo = getMElements("modbthermo");
2290 for (let i = 0; i < modbthermo.length; i++) {
2291 if (modbthermo[i].childNodes === undefined || modbthermo[i].childNodes[0] === undefined ||
2292 modbthermo[i].childNodes[0].tagName !== 'CANVAS') {
2293 modbthermo[i].style.display = "inline-block";
2294 if (modbthermo[i].style.position === "")
2295 modbthermo[i].style.position = "relative";
2296
2297 cvs = document.createElement("canvas");
2298 let w = modbthermo[i].clientWidth;
2299 let h = modbthermo[i].clientHeight;
2300 w = Math.floor(w / 4) * 4; // 2 must be devidable by 4
2301 cvs.width = w + 1;
2302 cvs.height = h;
2303 modbthermo[i].appendChild(cvs);
2304 modbthermo[i].draw = mhttpd_thermo_draw;
2305
2306 modbthermo[i].setValue = function (x, tid) {
2307 if (this.dataset.formula !== undefined)
2308 x = eval(this.dataset.formula);
2309 if (tid === undefined)
2310 tid = TID_DOUBLE;
2311 this.value = x;
2312 this.dataset.tid = tid;
2313
2314 if (this.onchange !== null)
2315 this.onchange();
2316 if (this.dataset.odbLoaded === undefined && this.onload !== null) {
2317 this.onload();
2318 this.dataset.odbLoaded = "1";
2319 }
2320
2321 this.draw();
2322 }
2323
2324 modbthermo[i].draw();
2325 }
2326 }
2327
2328 // replace all gauges with canvas
2329 let modbgauge = getMElements("modbgauge");
2330 for (let i = 0; i < modbgauge.length; i++) {
2331 if (modbgauge[i].childNodes === undefined || modbgauge[i].childNodes[0] === undefined ||
2332 modbgauge[i].childNodes[0].tagName !== 'CANVAS') {
2333 modbgauge[i].style.display = "inline-block";
2334 if (modbgauge[i].style.position === "")
2335 modbgauge[i].style.position = "relative";
2336
2337 let cvs = document.createElement("canvas");
2338 cvs.width = modbgauge[i].clientWidth;
2339 cvs.height = modbgauge[i].clientHeight;
2340 modbgauge[i].appendChild(cvs);
2341 modbgauge[i].draw = mhttpd_gauge_draw;
2342
2343 modbgauge[i].setValue = function (x, tid) {
2344 if (this.dataset.formula !== undefined)
2345 x = eval(this.dataset.formula);
2346 if (tid === undefined)
2347 tid = TID_DOUBLE;
2348 this.value = x;
2349 this.dataset.tid = tid;
2350
2351 if (this.onchange !== null)
2352 this.onchange();
2353 if (this.dataset.odbLoaded === undefined && this.onload !== null) {
2354 this.onload();
2355 this.dataset.odbLoaded = "1";
2356 }
2357
2358 this.draw();
2359 }
2360
2361 modbgauge[i].draw();
2362 }
2363 }
2364
2365 // replace all haxis with canvas
2366 let mhaxis = getMElements("mhaxis");
2367 for (let i = 0; i < mhaxis.length; i++) {
2368 if (mhaxis[i].childNodes === undefined || mhaxis[i].childNodes[0] === undefined ||
2369 mhaxis[i].childNodes[0].tagName !== 'CANVAS') {
2370 mhaxis[i].style.display = "block";
2371 if (mhaxis[i].style.position === "")
2372 mhaxis[i].style.position = "relative";
2373
2374 let cvs = document.createElement("canvas");
2375 cvs.width = mhaxis[i].clientWidth + 2;
2376 cvs.height = mhaxis[i].clientHeight;
2377 mhaxis[i].appendChild(cvs);
2378 mhaxis[i].draw = mhttpd_haxis_draw;
2379 mhaxis[i].draw();
2380 }
2381 }
2382
2383 // replace all vaxis with canvas
2384 let mvaxis = getMElements("mvaxis");
2385 for (let i = 0; i < mvaxis.length; i++) {
2386 if (mvaxis[i].childNodes === undefined || mvaxis[i].childNodes[0] === undefined ||
2387 mvaxis[i].childNodes[0].tagName !== 'CANVAS') {
2388 mvaxis[i].style.display = "inline-block";
2389 if (mvaxis[i].style.position === "")
2390 mvaxis[i].style.position = "relative";
2391
2392 let cvs = document.createElement("canvas");
2393 cvs.width = mvaxis[i].clientWidth;
2394 cvs.height = mvaxis[i].clientHeight + 2; // leave space for vbar border
2395 mvaxis[i].appendChild(cvs);
2396 mvaxis[i].draw = mhttpd_vaxis_draw;
2397 mvaxis[i].draw();
2398 }
2399 }
2400
2401 // replace all mhistory tags with history plots
2402 let mhist = getMElements("mhistory");
2403 for (let i = 0; i < mhist.length; i++) {
2404 if (mhist[i].childNodes === undefined || mhist[i].childNodes[0] === undefined ||
2405 mhist[i].childNodes[0].tagName !== 'IMG') {
2406 let w = mhist[i].style.width;
2407 if (w === "")
2408 w = 320;
2409 else
2410 w = parseInt(w);
2411 let h = mhist[i].style.height;
2412 if (h === "")
2413 h = 200;
2414 else
2415 h = parseInt(h);
2416 mhist[i].innerHTML = "<img src=\"graph.gif?cmd=oldhistory&group=" +
2417 mhist[i].dataset.group +
2418 "&panel=" + mhist[i].dataset.panel +
2419 "&scale=" + mhist[i].dataset.scale +
2420 "&width=" + w +
2421 "&height=" + h +
2422 "&rnd=" + (new Date().getTime()) +
2423 "\">";
2424 }
2425 }
2426
2427 // request all modb* elements
2428 let paths = [];
2429 let modbs = ["modb", "modbvalue", "modbcheckbox", "modbselect", "modbbox", "modbhbar",
2430 "modbvbar", "modbthermo", "modbgauge"];
2431
2432 modbs.forEach((mstr) => {
2433 getMElements(mstr).forEach((m) => {
2434 let p = m.dataset.odbPath;
2435 if (p) {
2436 p = evalPath(p);
2437 if (p.includes('[*]'))
2438 p = p.replace('[*]', '');
2439 paths.push(p);
2440 }
2441
2442 if (mstr === "modbselect" && m.dataset.autoOptions === "1")
2443 paths.push(get_options_path(evalPath(m.dataset.odbPath)));
2444
2445 })
2446 });
2447
2448 // request ODB contents for all variables
2449 let req1 = mjsonrpc_make_request("db_get_values", {"paths": paths});
2450
2451 // request current alarms
2452 let req2 = mjsonrpc_make_request("get_alarms");
2453
2454 // request new messages
2455 let req3 = mjsonrpc_make_request("cm_msg_retrieve", {
2456 "facility": "midas",
2457 "time": 0,
2458 "min_messages": 1
2459 });
2460
2461 // request new char messages
2462 let req4 = mjsonrpc_make_request("cm_msg_retrieve", {
2463 "facility": "chat",
2464 "time": 0,
2465 "min_messages": 1
2466 });
2467
2468 mjsonrpc_send_request([req1, req2, req3, req4]).then(function (rpc) {
2469
2470 // update time in header
2471 let dstr = mhttpd_get_display_time().string;
2472
2473 if (document.getElementById("mheader_last_updated") !== undefined) {
2474 //mhttpd_set_innerHTML(document.getElementById("mheader_last_updated"), dstr);
2475 mhttpd_set_firstChild_data(document.getElementById("mheader_last_updated"), dstr);
2476 }
2477
2478 let idata = 0;
2479
2480 for (let i = 0; i < modb.length; i++, idata++) {
2481 if (modb[i].dataset.odbPath === undefined) {
2482 idata--;
2483 continue;
2484 }
2485 let x = rpc[0].result.data[idata];
2486 if (typeof x === 'object' && x !== null) {
2487 // subdircectory
2488 if (modb[i].value === undefined)
2489 modb[i].value = JSON.stringify(x);
2490 if (modb[i].onchange !== null && JSON.stringify(x) !== modb[i].value) {
2491 modb[i].value = JSON.stringify(x);
2492 modb[i].onchange();
2493 }
2494 if (modb[i].dataset.odbLoaded === undefined && modb[i].onload !== null) {
2495 modb[i].onload();
2496 modb[i].dataset.odbLoaded = "1";
2497 }
2498 } else {
2499 // individual value
2500 if (modb[i].value === undefined)
2501 modb[i].value = x;
2502 if (modb[i].onchange !== null && x !== modb[i].value) {
2503 modb[i].value = x;
2504 modb[i].onchange();
2505 }
2506 if (modb[i].dataset.odbLoaded === undefined && modb[i].onload !== null) {
2507 modb[i].onload();
2508 modb[i].dataset.odbLoaded = "1";
2509 }
2510 }
2511 }
2512
2513 for (let i = 0; i < modbvalue.length; i++, idata++) {
2514 if (modbvalue[i].dataset.odbPath === undefined) {
2515 idata--;
2516 continue;
2517 }
2518
2519 if (rpc[0].result.status[idata] === 312) {
2520 modbvalue[i].innerHTML = "ODB key \"" +
2521 evalPath(modbvalue[i].dataset.odbPath) + "\" not found";
2522 } else {
2523 let x = rpc[0].result.data[idata];
2524 let tid = rpc[0].result.tid[idata];
2525
2526 modbvalue[i].tid = tid;
2527 if (modbvalue[i].dataset.formula !== undefined)
2528 x = eval(modbvalue[i].dataset.formula);
2529 let mvalue = mie_to_string(tid, x, modbvalue[i].dataset.format);
2530 if (mvalue === "")
2531 mvalue = "(empty)";
2532 else if (typeof mvalue === 'string' && mvalue.trim() === "")
2533 mvalue = "(spaces)";
2534 let html;
2535 if (tid === TID_BOOL)
2536 html = mvalue;
2537 else
2538 html = mhttpd_escape(mvalue);
2539 if (parseInt(modbvalue[i].dataset.odbEditable) || parseInt(modbvalue[i].dataset.input)) {
2540 if (modbvalue[i].childNodes[1] !== undefined &&
2541 modbvalue[i].childNodes[1].innerHTML !== undefined) {
2542 modbvalue[i].childNodes[1].innerHTML = html;
2543 } else {
2544 if (modbvalue[i].childNodes[0]) // dataset.input could not yet be initialized (AJAX)
2545 modbvalue[i].childNodes[0].innerHTML = html;
2546 }
2547 } else
2548 modbvalue[i].innerHTML = html;
2549
2550 if (modbvalue[i].value !== x && modbvalue[i].onchange !== null) {
2551 modbvalue[i].value = x;
2552 if (modbvalue[i].value !== undefined)
2553 modbvalue[i].onchange();
2554 }
2555
2556 modbvalue[i].value = x;
2557
2558 if (modbvalue[i].dataset.odbLoaded === undefined && modbvalue[i].onload !== null) {
2559 modbvalue[i].onload();
2560 modbvalue[i].dataset.odbLoaded = "1";
2561 }
2562 }
2563 }
2564
2565 for (let i = 0; i < modbcheckbox.length; i++, idata++) {
2566 if (modbcheckbox[i].dataset.odbPath === undefined) {
2567 idata--;
2568 continue;
2569 }
2570
2571 let x = rpc[0].result.data[idata];
2572 let tid = rpc[0].result.tid[idata];
2573 let mvalue = mie_to_string(tid, x);
2574 if (typeof x === "boolean")
2575 modbcheckbox[i].checked = x;
2576 else
2577 modbcheckbox[i].checked = (mvalue !== "0");
2578 if (modbcheckbox[i].onchange !== null)
2579 modbcheckbox[i].onchange();
2580 if (modbcheckbox[i].dataset.odbLoaded === undefined && modbcheckbox[i].onload !== null) {
2581 modbcheckbox[i].onload();
2582 modbcheckbox[i].dataset.odbLoaded = "1";
2583 }
2584 }
2585
2586 for (let i = 0; i < modbselect.length; i++, idata++) {
2587 if (modbselect[i].dataset.odbPath === undefined) {
2588 idata--;
2589 continue;
2590 }
2591
2592 let x = rpc[0].result.data[idata];
2593 let auto_options = [];
2594
2595 if (modbselect[i].dataset.autoOptions == "1") {
2596 idata++;
2597 auto_options = rpc[0].result.data[idata];
2598
2599 if (auto_options !== null && modbselect[i].options.length == 0) {
2600 for (let opt = 0; opt < auto_options.length; opt++) {
2601 modbselect[i].add(new Option(auto_options[opt], auto_options[opt]));
2602 }
2603 }
2604 }
2605
2606 if (rpc[0].result.status[idata] === 312) {
2607 modbselect[i].parentElement.innerHTML = "ODB key \"" +
2608 evalPath(modbselect[i].dataset.odbPath) + "\" not found";
2609 } else {
2610 if (x !== null && modbselect[i].value !== x.toString()) {
2611
2612 if (modbselect[i].value !== undefined) {
2613 // check if option is available
2614 let j;
2615 for (j = 0; j < modbselect[i].options.length; j++)
2616 if (modbselect[i].options[j].value === x.toString())
2617 break;
2618
2619 // if option is available, set it
2620 if (j < modbselect[i].options.length) {
2621 modbselect[i].value = x.toString();
2622 if (modbselect[i].onchange !== null)
2623 modbselect[i].onchange(true);
2624 } else {
2625 // if not, set blank
2626 if (modbselect[i].value !== "")
2627 modbselect[i].value = "";
2628 }
2629 }
2630
2631 if (Array.isArray(x)) {
2632 // check if all array values are the same
2633 let s = new Set(x);
2634 if (s.size === 1)
2635 modbselect[i].value = s.values().next().value;
2636 else
2637 modbselect[i].value = "...";
2638 }
2639 }
2640 if (modbselect[i].dataset.odbLoaded === undefined && modbselect[i].onload !== null) {
2641 modbselect[i].onload();
2642 modbselect[i].dataset.odbLoaded = "1";
2643 }
2644 }
2645 }
2646
2647 for (let i = 0; i < modbbox.length; i++, idata++) {
2648 if (modbbox[i].dataset.odbPath === undefined) {
2649 idata--;
2650 continue;
2651 }
2652
2653 let x = rpc[0].result.data[idata];
2654 if (modbbox[i].dataset.formula !== undefined)
2655 x = eval(modbbox[i].dataset.formula);
2656 if (x > 0 || x === true) {
2657 modbbox[i].style.backgroundColor = modbbox[i].dataset.color;
2658 } else {
2659 if (modbbox[i].dataset.backgroundColor !== undefined)
2660 modbbox[i].style.backgroundColor = modbbox[i].dataset.backgroundColor;
2661 else
2662 modbbox[i].style.backgroundColor = "";
2663 }
2664 if (modbbox[i].onchange !== null)
2665 modbbox[i].onchange();
2666 if (modbbox[i].dataset.odbLoaded === undefined && modbbox[i].onload !== null) {
2667 modbbox[i].onload();
2668 modbbox[i].dataset.odbLoaded = "1";
2669 }
2670 }
2671
2672 for (let i = 0; i < modbhbar.length; i++, idata++) {
2673 if (modbhbar[i].dataset.odbPath === undefined) {
2674 idata--;
2675 continue;
2676 }
2677
2678 let x = rpc[0].result.data[idata];
2679 let tid = rpc[0].result.tid[idata];
2680
2681 modbhbar[i].setValue(x, tid);
2682 }
2683
2684 for (let i = 0; i < modbvbar.length; i++, idata++) {
2685 if (modbvbar[i].dataset.odbPath === undefined) {
2686 idata--;
2687 continue;
2688 }
2689
2690 let x = rpc[0].result.data[idata];
2691 let tid = rpc[0].result.tid[idata];
2692
2693 modbvbar[i].setValue(x, tid);
2694 }
2695
2696 for (let i = 0; i < modbthermo.length; i++, idata++) {
2697 if (modbthermo[i].dataset.odbPath === undefined) {
2698 idata--;
2699 continue;
2700 }
2701
2702 let x = rpc[0].result.data[idata];
2703 let tid = rpc[0].result.tid[idata];
2704
2705 modbthermo[i].setValue(x, tid);
2706 }
2707
2708 for (let i = 0; i < modbgauge.length; i++, idata++) {
2709 if (modbgauge[i].dataset.odbPath === undefined) {
2710 idata--;
2711 continue;
2712 }
2713
2714 let x = rpc[0].result.data[idata];
2715 let tid = rpc[0].result.tid[idata];
2716
2717 modbgauge[i].setValue(x, tid);
2718 }
2719
2720 let alarms = rpc[1].result;
2721
2722 // update alarm display
2723 let e = document.getElementById('mheader_alarm');
2724 if (!alarms.alarm_system_active) {
2725 mhttpd_set_innerHTML(e, "<a href=\"?cmd=Alarms\">Alarms: Off</a>");
2726 mhttpd_set_className(e, "mgray mbox");
2727 } else {
2728 let s = "";
2729 let n = 0;
2730 let sound = 0;
2731 for (let a in alarms.alarms) {
2732 s += a + ", ";
2733 n++;
2734
2735 if (alarms.alarms[a].alarm_sound)
2736 sound++;
2737 }
2738 if (n < 1) {
2739 mhttpd_set_innerHTML(e, "<a href=\"?cmd=Alarms\">Alarms: None</a>");
2740 mhttpd_set_className(e, "mgreen mbox");
2741 } else {
2742 s = s.slice(0, -2);
2743 if (n > 1)
2744 mhttpd_set_innerHTML(e, "<a href=\"?cmd=Alarms\">Alarms: " + s + "</a>");
2745 else
2746 mhttpd_set_innerHTML(e, "<a href=\"?cmd=Alarms\">Alarm: " + s + "</a>");
2747 mhttpd_set_className(e, "mred mbox");
2748
2749 if (sound > 0)
2750 mhttpd_alarm_play();
2751 }
2752 }
2753
2754 // update messages
2755 let msg;
2756 if (rpc[2].result.messages !== undefined) {
2757 msg = rpc[2].result.messages.split("\n");
2758 if (msg[msg.length - 1] === "")
2759 msg = msg.slice(0, -1);
2760 } else
2761 msg = undefined;
2762
2763 // update chat messages
2764 let chat;
2765 if (rpc[3].result.messages !== undefined) {
2766 chat = rpc[3].result.messages.split("\n");
2767 if (chat[chat.length - 1] === "")
2768 chat = chat.slice(0, -1);
2769 } else
2770 chat = undefined;
2771
2772 mhttpd_message(msg, chat);
2773 mhttpd_resize_sidenav();
2774
2775 if (mhttpd_refresh_interval !== undefined && mhttpd_refresh_interval > 0)
2776 mhttpd_refresh_id = window.setTimeout(mhttpd_refresh, mhttpd_refresh_interval);
2777
2778 }).catch(function (error) {
2779
2780 if (error.xhr && (error.xhr.readyState === 4) && ((error.xhr.status === 0) || (error.xhr.status === 503))) {
2781 mhttpd_error('Connection to server broken. Trying to reconnect&nbsp;&nbsp;');
2782 document.getElementById("mheader_error").appendChild(mhttpd_spinning_wheel);
2783 mhttpd_reconnect_id = window.setTimeout(mhttpd_reconnect, 1000);
2784 } else {
2785 mjsonrpc_error_alert(error);
2786 }
2787 });
2788}
2789
2790function mhttpd_refresh_history() {
2791 if (mhttpd_refresh_history_id !== undefined)
2792 window.clearTimeout(mhttpd_refresh_history_id);
2793
2794 /* this fuction gets called by mhttpd_init to periodically refresh all history panels */
2795
2796 let mhist = document.getElementsByName("mhistory");
2797 for (let i = 0; i < mhist.length; i++) {
2798 let s = mhist[i].childNodes[0].src;
2799
2800 if (s.lastIndexOf("&rnd=") !== -1) {
2801 s = s.substr(0, s.lastIndexOf('&rnd='));
2802 s += "&rnd=" + new Date().getTime();
2803 }
2804 mhist[i].childNodes[0].src = s;
2805 }
2806
2807 if (mhttpd_refresh_history_interval !== undefined && mhttpd_refresh_history_interval > 0)
2808 mhttpd_refresh_history_id = window.setTimeout(mhttpd_refresh_history, mhttpd_refresh_history_interval);
2809}
2810
2811function mhttpd_reconnect() {
2812 mjsonrpc_db_ls(["/"]).then(function (rpc) {
2813 // on successful connection remove error and schedule refresh
2814 mhttpd_error_clear();
2815 if (mhttpd_refresh_id !== undefined)
2816 window.clearTimeout(mhttpd_refresh_id);
2817 if (mhttpd_refresh_history_id !== undefined)
2818 window.clearTimeout(mhttpd_refresh_history_id);
2819 mhttpd_refresh_id = window.setTimeout(mhttpd_refresh, mhttpd_refresh_interval);
2820 mhttpd_refresh_history_id = window.setTimeout(mhttpd_refresh_history, mhttpd_refresh_history_interval);
2821 }).catch(function (error) {
2822 mhttpd_reconnect_id = window.setTimeout(mhttpd_reconnect, 1000);
2823 });
2824}
2825
2826window.addEventListener('resize', mhttpd_resize_message);
2827
2828function mhttpd_resize_message() {
2829 //console.log("mhttpd_resize_message() via resize event listener");
2830 let d = document.getElementById("mheader_message");
2831 if (d.currentMessage !== undefined && d.style.display !== 'none')
2832 mhttpd_fit_message(d.currentMessage);
2833}
2834
2835function mhttpd_close_message() {
2836 let d = document.getElementById("mheader_message");
2837
2838 // remember time of messages to suppress
2839 mhttpdConfigSet('suppressMessageBefore', d.currentMessageT);
2840
2841 d.style.display = "none";
2842 mhttpd_resize_sidenav();
2843}
2844
2845function mhttpd_fit_message(m) {
2846 let d = document.getElementById("mheader_message");
2847 let cross = "&nbsp;&nbsp;&nbsp;<span style=\"cursor: pointer;\" onclick=\"mhttpd_close_message();\">&#9587;</span>";
2848 let link1 = "<span style=\"cursor: pointer;\" onclick=\"window.location.href='?cmd=Messages'\">";
2849 let link2 = "</span>";
2850 d.style.display = "inline-block";
2851
2852 // limit message to fit parent element
2853
2854 let parentWidth = d.parentNode.offsetWidth;
2855 let maxWidth = parentWidth - 30;
2856
2857 // check if the full message fits
2858
2859 d.innerHTML = link1 + m + link2 + cross;
2860 //console.log("mhttpd_fit_message: len: " + d.offsetWidth + ", max: " + maxWidth + ", message: " + m);
2861 if (d.offsetWidth <= maxWidth) {
2862 return;
2863 }
2864
2865 // check if the message minus timestamp and type fits
2866
2867 m = m.substr(m.indexOf(']')+1);
2868 d.innerHTML = link1 + m + link2 + cross;
2869 let w = d.offsetWidth;
2870 //console.log("mhttpd_fit_message: len: " + w + ", max: " + maxWidth + ", message: " + m);
2871 if (w <= maxWidth) {
2872 return;
2873 }
2874
2875 // guess the length assuming fix pixels per char
2876
2877 let charWidth = w/m.length;
2878 let guessLength = maxWidth/charWidth - 3; // 3 chars of "..."
2879
2880 let g = m.substr(0, guessLength);
2881 d.innerHTML = link1 + g + "..." + link2 + cross;
2882 w = d.offsetWidth;
2883 //console.log("mhttpd_fit_message: char: " + charWidth + ", guess: " + guessLength + ", len: " + w + ", max: " + maxWidth);
2884
2885 // grow or shrink our guess
2886
2887 if (w < maxWidth) {
2888 //console.log("mhttpd_fit_message: too short, grow");
2889 for (let i=guessLength+1; i<=m.length; i++) {
2890 let s = m.substr(0, i);
2891 d.innerHTML = link1 + s + "..." + link2 + cross;
2892 w = d.offsetWidth;
2893 //console.log("mhttpd_fit_message: len: " + w + ", max: " + maxWidth + ", message: " + s);
2894 if (w <= maxWidth)
2895 break;
2896 }
2897 } else {
2898 //console.log("mhttpd_fit_message: too long, shrink");
2899 while (g.length > 0) {
2900 g = g.substr(0, g.length-1);
2901 d.innerHTML = link1 + g + "..." + link2 + cross;
2902 w = d.offsetWidth;
2903 //console.log("mhttpd_fit_message: len: " + w + ", max: " + maxWidth + ", message: " + g);
2904 if (w <= maxWidth)
2905 break;
2906 }
2907 }
2908}
2909
2910function mhttpd_message(msg, chat) {
2911
2912 let mTalk = "";
2913 let mType = "";
2914 let chatName = "";
2915 let talkTime = 0;
2916 let notifyTime = 0;
2917 let lastMsg = "";
2918 let lastMsgT = 0;
2919 let lastChat = "";
2920 let lastChatT = 0;
2921 let lastT = 0;
2922 let m = "";
2923 let c = "";
2924
2925 if (msg !== undefined) {
2926 lastMsg = msg[0].substr(msg[0].indexOf(" ") + 1);
2927 lastMsgT = parseInt(msg[0]);
2928 }
2929
2930 if (chat !== undefined) {
2931 lastChat = chat[0].substr(chat[0].indexOf(" ") + 1);
2932 lastChatT = parseInt(chat[0]);
2933 if (chat[0].length > 0)
2934 mTalk = chat[0].substr(chat[0].indexOf("]") + 2);
2935
2936 chatName = lastChat.substring(lastChat.indexOf("[") + 1, lastChat.indexOf(","));
2937 lastChat = lastChat.substr(0, lastChat.indexOf("[")) +
2938 "<b>" + chatName + ":</b>" +
2939 lastChat.substr(lastChat.indexOf("]") + 1);
2940 }
2941
2942 if (lastChatT > lastMsgT) {
2943 m = lastChat;
2944 c = "var(--mblue)";
2945 mType = "USER";
2946 talkTime = lastChatT;
2947 notifyTime = lastChatT;
2948 lastT = lastChatT;
2949 } else {
2950 m = lastMsg;
2951 c = "var(--myellow)";
2952 mTalk = lastMsg.substr(lastMsg.indexOf("]") + 1);
2953 mType = m.substring(m.indexOf(",") + 1, m.indexOf("]"));
2954 talkTime = lastMsgT;
2955 notifyTime = lastMsgT;
2956 lastT = lastMsgT;
2957 }
2958
2959 if (m !== "") {
2960 let d = document.getElementById("mheader_message");
2961 if (d !== undefined && d !== null && d.currentMessage !== m &&
2962 (mhttpdConfig().suppressMessageBefore === undefined || lastT > mhttpdConfig().suppressMessageBefore)) {
2963
2964 d.style.removeProperty("-webkit-transition");
2965 d.style.removeProperty("transition");
2966
2967 if (mType === "USER" && mhttpdConfig().displayChat ||
2968 mType === "TALK" && mhttpdConfig().displayTalk ||
2969 mType === "ERROR" && mhttpdConfig().displayError ||
2970 mType === "INFO" && mhttpdConfig().displayInfo ||
2971 mType === "LOG" && mhttpdConfig().displayLog) {
2972
2973 let first = (d.currentMessage === undefined);
2974 d.currentMessage = m; // store full message in user-defined attribute
2975 d.currentMessageT = lastMsgT; // store message time in user-defined attribute
2976
2977 mhttpd_fit_message(m);
2978 d.age = new Date() / 1000;
2979
2980 if (first) {
2981 if (m.search("ERROR]") > 0) {
2982 d.style.backgroundColor = "var(--mred)";
2983 d.style.color = "white";
2984 }
2985 } else {
2986
2987 // manage backgroud color (red for errors, fading yellow for others)
2988 if (m.search("ERROR]") > 0) {
2989 d.style.removeProperty("-webkit-transition");
2990 d.style.removeProperty("transition");
2991 d.style.backgroundColor = "var(--mred)";
2992 } else {
2993 d.age = new Date() / 1000;
2994 d.style.removeProperty("-webkit-transition");
2995 d.style.removeProperty("transition");
2996 d.style.backgroundColor = c;
2997 }
2998 }
2999 }
3000
3001 if (mTalk !== "") {
3002 if (mType === "USER" && mhttpdConfig().speakChat) {
3003 // do not speak own message
3004 if (document.getElementById("chatName") === undefined ||
3005 document.getElementById("chatName") === null ||
3006 document.getElementById("chatName").value !== chatName) {
3007 mhttpd_speak(talkTime, mTalk);
3008 }
3009 } else if (mType === "TALK" && mhttpdConfig().speakTalk) {
3010 mhttpd_speak(talkTime, mTalk);
3011 } else if (mType === "ERROR" && mhttpdConfig().speakError) {
3012 mhttpd_speak(talkTime, mTalk);
3013 } else if (mType === "INFO" && mhttpdConfig().speakInfo) {
3014 mhttpd_speak(talkTime, mTalk);
3015 } else if (mType === "LOG" && mhttpdConfig().speakLog) {
3016 mhttpd_speak(talkTime, mTalk);
3017 }
3018 }
3019
3020 if (mType === "USER" && mhttpdConfig().notifyChat ||
3021 mType === "TALK" && mhttpdConfig().notifyTalk ||
3022 mType === "ERROR" && mhttpdConfig().notifyError ||
3023 mType === "INFO" && mhttpdConfig().notifyInfo ||
3024 mType === "LOG" && mhttpdConfig().notifyLog) {
3025
3026 // avoid double notification if page gets reloaded
3027 // console.log("notifyTime: " + notifyTime + " last: " + mhttpdConfig().var.lastNotify);
3028 if (mhttpdConfig().var.lastNotify === undefined || notifyTime > mhttpdConfig().var.lastNotify) {
3029 mhttpdConfigSet("var.lastNotify", notifyTime);
3030
3031 if (("Notification" in window)) {
3032 if (Notification.permission === "granted") {
3033 const n = new Notification("MIDAS", {
3034 body: mTalk,
3035 icon: "./apple-touch-icon.png"
3036 });
3037 } else if (Notification.permission !== "denied") {
3038 Notification.requestPermission().then((p) => {
3039 if (p === "granted") {
3040 const n = new Notification("MIDAS", {
3041 body: mTalk,
3042 icon: "./apple-touch-icon.png"
3043 });
3044 }
3045 });
3046 }
3047 }
3048 }
3049 }
3050
3051
3052 }
3053 let t = new Date() / 1000;
3054 if (t > d.age + 5 && d.style.backgroundColor === "var(--myellow)") {
3055 let backgroundColor = "var(--mgray)";
3056 d.style.setProperty("-webkit-transition", "background-color 3s", "");
3057 d.style.setProperty("transition", "background-color 3s", "");
3058 d.style.backgroundColor = backgroundColor;
3059 }
3060 }
3061}
3062
3063function mhttpd_error(error) {
3064 let d = document.getElementById("mheader_error");
3065 if (d !== undefined) {
3066 error += "<div style=\"display: inline; float: right; padding-right: 10px; cursor: pointer;\"" +
3067 " onclick=\"document.getElementById(&quot;mheader_error&quot;).style.zIndex = 0;\">&#9587;</div>";
3068 d.innerHTML = error;
3069 d.style.zIndex = 3; // above header
3070 }
3071}
3072
3073function mhttpd_error_clear() {
3074 if (document.getElementById("mheader_error")) {
3075 document.getElementById("mheader_error").innerHTML = "";
3076 document.getElementById("mheader_error").style.zIndex = 0; // below header
3077 }
3078}
3079
3080function mhttpd_create_page_handle_create(mouseEvent) {
3081 let path = "";
3082 let type = "";
3083 let name = "";
3084 let arraylength = "";
3085 let stringlength = "";
3086
3087 let form = document.getElementsByTagName('form')[0];
3088
3089 if (form) {
3090 path = form.elements['odb'].value;
3091 type = form.elements['type'].value;
3092 name = form.elements['value'].value;
3093 arraylength = form.elements['index'].value;
3094 stringlength = form.elements['strlen'].value;
3095 } else {
3096 let e = document.getElementById("odbpath");
3097 path = JSON.parse(e.innerHTML);
3098 if (path === "/") path = "";
3099
3100 type = document.getElementById("create_tid").value;
3101 name = document.getElementById("create_name").value;
3102 arraylength = document.getElementById("create_array_length").value;
3103 stringlength = document.getElementById("create_strlen").value;
3104
3105 //alert("Path: " + path + " Name: " + name);
3106 }
3107
3108 if (path === "/") path = "";
3109
3110 if (name.length < 1) {
3111 dlgAlert("Name is too short");
3112 return false;
3113 }
3114
3115 let int_array_length = parseInt(arraylength);
3116
3117 //alert("int_array_length: " + int_array_length);
3118
3119 if (!int_array_length || int_array_length < 1) {
3120 dlgAlert("Bad array length: " + arraylength);
3121 return false;
3122 }
3123
3124 let int_string_length = parseInt(stringlength);
3125
3126 if (!int_string_length || int_string_length < 1) {
3127 dlgAlert("Bad string length " + stringlength);
3128 return false;
3129 }
3130
3131 let param = {};
3132 param.path = path + "/" + name;
3133 param.type = parseInt(type);
3134 if (int_array_length > 1)
3135 param.array_length = int_array_length;
3136 if (int_string_length > 0)
3137 param.string_length = int_string_length;
3138
3139 mjsonrpc_db_create([param]).then(function (rpc) {
3140 let status = rpc.result.status[0];
3141 if (status === 311) {
3142 dlgMessage("Error", "ODB entry with this name already exists.", true, true, function () {
3143 location.search = "?cmd=odb&odb_path=" + path; // reloads the document
3144 });
3145 } else if (status !== 1) {
3146 dlgMessage("Error", "db_create_key() error " + status + ", see MIDAS messages.", true, true, function () {
3147 location.search = "?cmd=odb&odb_path=" + path; // reloads the document
3148 });
3149 } else {
3150 location.search = "?cmd=odb&odb_path=" + path; // reloads the document
3151 }
3152 }).catch(function (error) {
3153 mjsonrpc_error_alert(error);
3154 //location.search = "?cmd=odb&odb_path="+path; // reloads the document
3155 });
3156
3157 return false;
3158}
3159
3160function mhttpd_create_page_handle_cancel(mouseEvent) {
3161 dlgHide('dlgCreate');
3162 return false;
3163}
3164
3165function mhttpd_link_page_handle_link(mouseEvent) {
3166 let e = document.getElementById("link_odbpath");
3167 let path = JSON.parse(e.innerHTML);
3168 if (path === "/") path = "";
3169 //let path = document.getElementById("odb_path").value;
3170 let name = document.getElementById("link_name").value;
3171 let target = document.getElementById("link_target").value;
3172
3173 //console.log("Path: " + path + " Name: " + name + " Target: [" + target + "]");
3174
3175 if (name.length < 1) {
3176 dlgAlert("Name is too short");
3177 return false;
3178 }
3179
3180 if (target.length <= 1) {
3181 dlgAlert("Link target is too short");
3182 return false;
3183 }
3184
3185 let param = {};
3186 param.new_links = [path + "/" + name];
3187 param.target_paths = [target];
3188
3189 mjsonrpc_call("db_link", param).then(function (rpc) {
3190 let status = rpc.result.status[0];
3191 if (status === 304) {
3192 dlgMessage("Error", "Invalid link, see MIDAS messages.", true, true, function () {
3193 //location.search = "?cmd=odb&odb_path="+path; // reloads the document
3194 });
3195 } else if (status === 311) {
3196 dlgMessage("Error", "ODB entry with this name already exists.", true, true, function () {
3197 //location.search = "?cmd=odb&odb_path="+path; // reloads the document
3198 });
3199 } else if (status === 312) {
3200 dlgMessage("Error", "Target path " + target + " does not exist in ODB.", true, true, function () {
3201 //location.search = "?cmd=odb&odb_path="+path; // reloads the document
3202 });
3203 } else if (status === 315) {
3204 dlgMessage("Error", "ODB data type mismatch, see MIDAS messages.", true, true, function () {
3205 //location.search = "?cmd=odb&odb_path="+path; // reloads the document
3206 });
3207 } else if (status !== 1) {
3208 dlgMessage("Error", "db_create_link() error " + status + ", see MIDAS messages.", true, true, function () {
3209 location.search = "?cmd=odb&odb_path=" + path; // reloads the document
3210 });
3211 } else {
3212 location.search = "?cmd=odb&odb_path=" + path; // reloads the document
3213 }
3214 }).catch(function (error) {
3215 mjsonrpc_error_alert(error);
3216 //location.search = "?cmd=odb&odb_path="+path; // reloads the document
3217 });
3218
3219 return false;
3220}
3221
3222function mhttpd_link_page_handle_cancel(mouseEvent) {
3223 dlgHide('dlgLink');
3224 return false;
3225}
3226
3227function mhttpd_delete_page_handle_delete(mouseEvent, xpath) {
3228 let form = document.getElementsByTagName('form')[0];
3229 let path;
3230 let names = [];
3231
3232 if (form) {
3233 path = form.elements['odb'].value;
3234
3235 if (path === "/") path = "";
3236
3237 for (let i = 0; ; i++) {
3238 let n = "name" + i;
3239 let v = form.elements[n];
3240 if (v === undefined)
3241 break;
3242 if (v.checked)
3243 names.push(path + "/" + v.value);
3244 }
3245 } else {
3246 let e = document.getElementById("odbpath");
3247 path = JSON.parse(e.innerHTML);
3248 if (path === "/") path = "";
3249
3250 //alert("Path: " + path);
3251
3252 for (i = 0; ; i++) {
3253 let v = document.getElementById("delete" + i);
3254 if (v === undefined || v === null)
3255 break;
3256 if (v.checked) {
3257 let name = JSON.parse(v.value);
3258 if (name.length > 0) {
3259 names.push(path + "/" + name);
3260 }
3261 }
3262 }
3263
3264 //alert("Names: " + names);
3265 //return false;
3266 }
3267
3268 if (names.length < 1) {
3269 dlgAlert("Please select at least one ODB entry to delete.");
3270 return false;
3271 }
3272
3273 //alert(names);
3274
3275 let params = {};
3276 params.paths = names;
3277 mjsonrpc_call("db_delete", params).then(function (rpc) {
3278 let message = "";
3279 let status = rpc.result.status;
3280 //alert(JSON.stringify(status));
3281 for (let i = 0; i < status.length; i++) {
3282 if (status[i] !== 1) {
3283 message += "Cannot delete \"" + rpc.request.params.paths[i] + "\", db_delete_key() status " + status[i] + "\n";
3284 }
3285 }
3286 if (message.length > 0) {
3287 dlgAlert(message);
3288 } else {
3289 location.search = "?cmd=odb&odb_path=" + path; // reloads the document
3290 }
3291 }).catch(function (error) {
3292 mjsonrpc_error_alert(error);
3293 //location.search = "?cmd=odb&odb_path="+path; // reloads the document
3294 });
3295
3296 return false;
3297}
3298
3299function mhttpd_delete_page_handle_cancel(mouseEvent) {
3300 dlgHide('dlgDelete');
3301 return false;
3302}
3303
3304function mhttpd_start_run(ret) {
3305 mhttpd_goto_page("Start", ret); // DOES NOT RETURN
3306}
3307
3308function mhttpd_stop_run(ret) {
3309 dlgConfirm('Are you sure to stop the run?', function (flag) {
3310 if (flag === true) {
3311 mjsonrpc_call("cm_transition", {"transition": "TR_STOP"}).then(function (rpc) {
3312 //mjsonrpc_debug_alert(rpc);
3313 if (rpc.result.status !== 1) {
3314 throw new Error("Cannot stop run, cm_transition() status " + rpc.result.status + ", see MIDAS messages");
3315 }
3316 mhttpd_goto_page("Transition", ret); // DOES NOT RETURN
3317 }).catch(function (error) {
3318 mjsonrpc_error_alert(error);
3319 });
3320 }
3321
3322 });
3323}
3324
3325function mhttpd_pause_run(ret) {
3326 dlgConfirm('Are you sure to pause the run?', function (flag) {
3327 if (flag === true) {
3328 mjsonrpc_call("cm_transition", {"transition": "TR_PAUSE"}).then(function (rpc) {
3329 //mjsonrpc_debug_alert(rpc);
3330 if (rpc.result.status !== 1) {
3331 throw new Error("Cannot pause run, cm_transition() status " + rpc.result.status + ", see MIDAS messages");
3332 }
3333 mhttpd_goto_page("Transition", ret); // DOES NOT RETURN
3334 }).catch(function (error) {
3335 mjsonrpc_error_alert(error);
3336 });
3337 }
3338 });
3339}
3340
3341
3342function mhttpd_resume_run(ret) {
3343 dlgConfirm('Are you sure to resume the run?', function (flag) {
3344 if (flag === true) {
3345 mjsonrpc_call("cm_transition", {"transition": "TR_RESUME"}).then(function (rpc) {
3346 //mjsonrpc_debug_alert(rpc);
3347 if (rpc.result.status !== 1) {
3348 throw new Error("Cannot resume run, cm_transition() status " + rpc.result.status + ", see MIDAS messages");
3349 }
3350 mhttpd_goto_page("Transition", ret); // DOES NOT RETURN
3351 }).catch(function (error) {
3352 mjsonrpc_error_alert(error);
3353 });
3354 }
3355 });
3356}
3357
3358function mhttpd_cancel_transition() {
3359 dlgConfirm('Are you sure to cancel the currently active run transition?', function (flag) {
3360 if (flag === true) {
3361 let paths = [];
3362 let values = [];
3363
3364 paths.push("/Runinfo/Requested Transition");
3365 values.push(0);
3366 paths.push("/Runinfo/Transition in progress");
3367 values.push(0);
3368
3369 let params = {};
3370 params.paths = paths;
3371 params.values = values;
3372
3373 mjsonrpc_call("db_paste", params).then(function (rpc) {
3374 //mjsonrpc_debug_alert(rpc);
3375 if ((rpc.result.status[0] !== 1) || (rpc.result.status[1] !== 1)) {
3376 throw new Error("Cannot cancel transition, db_paste() status " + rpc.result.status + ", see MIDAS messages");
3377 }
3378 mhttpd_goto_page("Transition"); // DOES NOT RETURN
3379 }).catch(function (error) {
3380 mjsonrpc_error_alert(error);
3381 });
3382 }
3383 });
3384}
3385
3386function mhttpd_reset_alarm(alarm_name) {
3387 mjsonrpc_call("al_reset_alarm", {"alarms": [alarm_name]}).then(function (rpc) {
3388 //mjsonrpc_debug_alert(rpc);
3389 if (rpc.result.status[0] !== 1 && rpc.result.status[0] !== 1004) {
3390 throw new Error("Cannot reset alarm, status " + rpc.result.status + ", see MIDAS messages");
3391 }
3392 }).catch(function (error) {
3393 mjsonrpc_error_alert(error);
3394 });
3395}
3396
3397/*---- site and session storage ----------------------------*/
3398
3399/*
3400 Usage:
3401
3402 flag = mhttpdConfig().speakChat; // read
3403
3404 mhttpdConfigSet('speakChat', false); // write individual config
3405
3406 let c = mhttpdConfig(); // write whole config
3407 c.speakChat = false;
3408 c.... = ...;
3409 mhttpdConfigSetAll(c);
3410
3411
3412 Saves settings are kept in local storage, which gets
3413 cleared when the browser session ends. Then the default
3414 values are returned.
3415 */
3416
3417let mhttpd_config_defaults = {
3418 'chatName': "",
3419
3420 'pageTalk': true,
3421 'pageError': true,
3422 'pageInfo': true,
3423 'pageLog': false,
3424
3425 'displayChat': true,
3426 'displayTalk': true,
3427 'displayError': true,
3428 'displayInfo': false,
3429 'displayLog': false,
3430
3431 'speakChat': true,
3432 'speakTalk': true,
3433 'speakError': false,
3434 'speakInfo': false,
3435 'speakLog': false,
3436
3437 'displayChat': true,
3438 'displayTalk': true,
3439 'displayError': true,
3440 'displayInfo': true,
3441 'displayLog': false,
3442
3443 'speakVoice': 'Alex',
3444 'speakVolume': 1,
3445
3446 'alarmSound': true,
3447 'alarmSoundFile': 'beep.mp3',
3448 'alarmRepeat': 60,
3449 'alarmVolume': 1,
3450
3451 'timezone': 'local',
3452
3453 'var': {
3454 'lastSpeak': 0,
3455 'lastAlarm': 0
3456 },
3457
3458 'suppressMessageBefore': 0,
3459 'showMenu': true,
3460
3461 'facility': 'midas',
3462
3463 'fontSize': '10'
3464};
3465
3466function mhttpdConfigODB(callback) {
3467 let c = mhttpd_config_defaults;
3468 try {
3469 if (localStorage.mhttpd) {
3470 c = JSON.parse(localStorage.mhttpd);
3471 callback();
3472 } else {
3473 // obtain some defaults from ODB
3474 mjsonrpc_db_get_value("/Experiment/Enable sound").then(function (rpc) {
3475 let flag = rpc.result.data[0];
3476 if (flag === null) {
3477 mjsonrpc_db_create([{"path" : "/Experiment/Enable sound", "type" : TID_BOOL}]).then(function (rpc) {
3478 mjsonrpc_db_paste(["/Experiment/Enable sound"],[true]).then(function(rpc) {}).catch(function(error) {
3479 mjsonrpc_error_alert(error); });
3480 }).catch(function (error) {
3481 mjsonrpc_error_alert(error);
3482 });
3483 } else if (flag === false) {
3484 c.speakChat = false;
3485 c.speakTalk = false;
3486 c.speakError = false;
3487 c.speakInfo = false;
3488 c.speakLog = false;
3489 c.alarmSound = false;
3490 localStorage.setItem('mhttpd', JSON.stringify(c));
3491 }
3492 callback();
3493 }).catch(function (error) {
3494 mjsonrpc_error_alert(error);
3495 });
3496 }
3497 } catch (e) {
3498 }
3499}
3500
3501function mhttpdConfig() {
3502 let c = mhttpd_config_defaults;
3503 try {
3504 if (localStorage.mhttpd)
3505 c = JSON.parse(localStorage.mhttpd);
3506
3507 // if element has been added to mhttpd_config_defaults, merge it
3508 if (Object.keys(c).length !== Object.keys(mhttpd_config_defaults).length) {
3509 for (let o in mhttpd_config_defaults)
3510 if (!(o in c))
3511 c[o] = mhttpd_config_defaults[o];
3512 }
3513 } catch (e) {
3514 }
3515
3516 return c;
3517}
3518
3519function mhttpdConfigSet(item, value) {
3520 try {
3521 let c = mhttpdConfig();
3522 if (item.indexOf('.') > 0) {
3523 let c1 = item.substring(0, item.indexOf('.'));
3524 let c2 = item.substring(item.indexOf('.') + 1);
3525 c[c1][c2] = value;
3526 } else
3527 c[item] = value;
3528 localStorage.setItem('mhttpd', JSON.stringify(c));
3529 } catch (e) {
3530 }
3531}
3532
3533function mhttpdConfigSetAll(new_config) {
3534 try {
3535 localStorage.setItem('mhttpd', JSON.stringify(new_config));
3536 } catch (e) {
3537 }
3538}
3539
3540/*---- sound and speak functions --------------------------*/
3541
3542let last_audio = null;
3543let inside_new_audio = false;
3544let count_audio = 0;
3545
3546//
3547// Do not delete the following code. It is not used at this moment,
3548// but may become necessary in the future. Included
3549// is an alternative method of allocating and releasing Audio objects
3550// and code for tracing Audio object life time and activity. It is needed
3551// to debug interactions between Audio objects and throttled javascript code
3552// in inactive tabs; and with the "user did not interact with this tab" business.
3553// K.O. Jan 2021.
3554//
3555//function mhttpd_alarm_done() {
3556// let ended;
3557// if (last_audio) {
3558// ended = last_audio.ended;
3559// last_audio = null;
3560// }
3561// count_audio_done++;
3562// console.log(Date() + ": mhttpd_alarm_done: created: " + count_audio_created + ", done: " + count_audio_done + ", last_ended: " + ended);
3563//}
3564//
3565//function mhttpd_audio_loadeddata(e) {
3566// console.log(Date() + ": mhttpd_audio_loadeddata: counter " + e.target.counter);
3567//}
3568//
3569//function mhttpd_audio_canplay(e) {
3570// console.log(Date() + ": mhttpd_audio_canplay: counter " + e.target.counter);
3571//}
3572//
3573//function mhttpd_audio_canplaythrough(e) {
3574// console.log(Date() + ": mhttpd_audio_canplaythrough: counter " + e.target.counter);
3575//}
3576//
3577//function mhttpd_audio_ended(e) {
3578// console.log(Date() + ": mhttpd_audio_ended: counter " + e.target.counter);
3579//}
3580//
3581//function mhttpd_audio_paused(e) {
3582// console.log(Date() + ": mhttpd_audio_paused: counter " + e.target.counter);
3583//}
3584
3585function mhttpd_alarm_play_now() {
3586 if (last_audio) {
3587 //
3588 // NOTE:
3589 // check for playing the alarm sound is done every few minutes.
3590 // it takes 3 seconds to play the alarm sound
3591 // so in theory, this check is not needed: previous alarm sound should
3592 // always have finished by the time we play a new sound.
3593 //
3594 // However, observed with google-chrome, in inactive and/or iconized tabs
3595 // javascript code is "throttled" and the above time sequence is not necessarily
3596 // observed.
3597 //
3598 // Specifically, I see the following sequence: after 1-2 days of inactivity,
3599 // (i.e.) the "programs" page starts consuming 10-15% CPU, if I open it,
3600 // CPU use goes to 100% for a short while, memory use goes from ~50 Mbytes
3601 // to ~150 Mbytes. console.log() debug print out shows that there are ~400
3602 // accumulated Audio() objects that have been created but did not finish playing
3603 // (because google-chrome is throttling javascript in inactive tabs?), and now
3604 // they all try to load the mp3 file and play all at the same time.
3605 //
3606 // This fix for this observed behaviour is to only create new Audio() objects
3607 // if previous one finished playing.
3608 //
3609 // K.O.
3610 //
3611 if (!last_audio.ended) {
3612 console.log(Date() + ": mhttpd_alarm_play: Cannot play alarm sound: previous alarm sound did not finish playing yet");
3613 return;
3614 }
3615 }
3616
3617 if (inside_new_audio) {
3618 console.log(Date() + ": mhttpd_alarm_play: Cannot play alarm sound: already inside \"new Audio()\"");
3619 return;
3620 }
3621 inside_new_audio = true;
3622
3623 // reference counting of Audio objects. Not in use today. Do not remove. K.O. Jan 2021
3624 //console.log(Date() + ": mhttpd_alarm_play: created: " + count_audio_created + ", done: " + count_audio_done + ", last_ended: " + ended + ", audio.play!");
3625 //count_audio_created++;
3626
3627 let audio = new Audio(mhttpdConfig().alarmSoundFile);
3628 audio.volume = mhttpdConfig().alarmVolume;
3629 audio.counter = ++count_audio;
3630
3631 last_audio = audio;
3632 inside_new_audio = false;
3633
3634 // code for tracing activity of Audio objects. do not remove. K.O. Jan 2021
3635 //audio.addEventListener("loadeddata", mhttpd_audio_loadeddata);
3636 //audio.addEventListener("canplay", mhttpd_audio_canplay);
3637 //audio.addEventListener("canplaythrough", mhttpd_audio_canplaythrough);
3638 //audio.addEventListener("ended", mhttpd_audio_ended);
3639 //audio.addEventListener("paused", mhttpd_audio_paused);
3640
3641 let promise = audio.play();
3642 if (promise) {
3643 promise.then(function(e) {
3644 //console.log(Date() + ": mhttpd_alarm_play: promise fulfilled, counter " + audio.counter);
3645 }).catch(function(e) {
3646 //count_audio_done++;
3647 //console.log(Date() + ": mhttpd_alarm_play: audio.play() exception: " + e + ", created: " + count_audio_created + ", done: " + count_audio_done);
3648 console.log(Date() + ": mhttpd_alarm_play: Cannot play alarm sound: audio.play() exception: " + e + ", counter " + audio.counter);
3649 // NB: must clear the URL of the sound file, otherwise, the sound file is still loaded (but not played)
3650 // the loading of sound files is observed to be delayed in inactive tabs
3651 // resulting in many (100-1000) pending loads getting queued, observed
3652 // to all of them attempt to run (load the sound file) in parallel,
3653 // consuming memory (100-300 Mbytes) and CPU (10-15% CPU per inactive tab).
3654 audio.src = "";
3655 // NB: setting audio to pause() does not seem to do anything.
3656 audio.pause();
3657 last_audio = null;
3658 });
3659 }
3660 //audio.pause();
3661}
3662
3663function mhttpd_alarm_play() {
3664 if (mhttpdConfig().alarmSound && mhttpdConfig().alarmSoundFile) {
3665 let now = new Date() / 1000;
3666 let last = mhttpdConfig().var.lastAlarm;
3667 let next = last + parseFloat(mhttpdConfig().alarmRepeat);
3668 let wait = next - now;
3669 let do_play = (now > next);
3670 //console.log("mhttpd_alarm_play: now: " + now + ", next: " + next + ", last: " + last + ", wait: " + wait + ", play: " + do_play);
3671 if (do_play) {
3672 mhttpdConfigSet("var.lastAlarm", now);
3673 mhttpd_alarm_play_now();
3674 }
3675 }
3676}
3677
3678function mhttpd_speak_now(text) {
3679 let u = new SpeechSynthesisUtterance(text);
3680 u.voice = speechSynthesis.getVoices().filter(function (voice) {
3681 return voice.name === mhttpdConfig().speakVoice;
3682 })[0];
3683 u.volume = mhttpdConfig().speakVolume;
3684 speechSynthesis.speak(u);
3685}
3686
3687function mhttpd_speak(time, text) {
3688
3689 if (!('speechSynthesis' in window))
3690 return;
3691
3692 if (mhttpdConfig().speakChat) {
3693 if (time > mhttpdConfig().var.lastSpeak) {
3694 mhttpdConfigSet("var.lastSpeak", time);
3695 mhttpd_speak_now(text);
3696 }
3697 }
3698}
3699
3700/* emacs
3701 * Local Variables:
3702 * tab-width: 8
3703 * c-basic-offset: 3
3704 * js-indent-level: 3
3705 * indent-tabs-mode: nil
3706 * End:
3707 */