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