master
handlebars 7,991 lines 497 KB
Raw
1 <!DOCTYPE html>
2 <html lang="en" dir="ltr" xmlns="http://www.w3.org/1999/xhtml">
3 <head>
4 <meta charset="utf-8">
5 <meta http-equiv="X-UA-Compatible" content="IE=edge" />
6 <meta content="text/html; charset=utf-8" http-equiv="Content-Type" />
7 <meta name="viewport" content="user-scalable=1.0,initial-scale=1.0,minimum-scale=1.0,maximum-scale=1.0" />
8 <meta name="format-detection" content="telephone=no" />
9 <meta name="robots" content="noindex,nofollow">
10 <link rel="manifest" href="{{{domainurl}}}manifest.json">
11 <link rel="shortcut icon" href="{{{domainurl}}}favicon.ico" />
12 <link rel="icon" type="image/png" sizes="16x16" href="{{{domainurl}}}favicon-16x16.png">
13 <link rel="icon" type="image/png" sizes="32x32" href="{{{domainurl}}}favicon-32x32.png">
14 <link rel="apple-touch-icon" href="/favicon-303x303.png" />
15 <link type="text/css" href="styles/xterm.css" media="screen" rel="stylesheet" title="CSS" />
16 {{{customCSSTags}}}
17 <meta name="apple-mobile-web-app-capable" content="yes">
18 <meta name="apple-mobile-web-app-status-bar-style" content="#ffffff">
19 <meta name="apple-mobile-web-app-title" content="{{{title}}}">
20 <script type="text/javascript" src="scripts/common-0.0.1{{{min}}}.js"></script>
21 <script type="text/javascript" src="scripts/meshcentral{{{min}}}.js"></script>
22 <script type="text/javascript" src="scripts/agent-redir-ws-0.1.1{{{min}}}.js"></script>
23 <script type="text/javascript" src="scripts/agent-desktop-0.0.2{{{min}}}.js"></script>
24 <script type="text/javascript" src="scripts/amt-0.2.0{{{min}}}.js"></script>
25 <script type="text/javascript" src="scripts/amt-redir-ws-0.1.0{{{min}}}.js"></script>
26 <script type="text/javascript" src="scripts/amt-desktop-0.0.2{{{min}}}.js"></script>
27 <script type="text/javascript" src="scripts/xterm{{{min}}}.js"></script>
28 <script type="text/javascript" src="scripts/xterm-addon-fit{{{min}}}.js"></script>
29 <script type="text/javascript" src="scripts/xterm-addon-image{{{min}}}.js"></script>
30 <script type="text/javascript" src="scripts/zlib{{{min}}}.js"></script>
31 <script type="text/javascript" src="scripts/zlib-inflate{{{min}}}.js"></script>
32 <script type="text/javascript" src="scripts/zlib-adler32{{{min}}}.js"></script>
33 <script type="text/javascript" src="scripts/zlib-crc32{{{min}}}.js"></script>
34 <script keeplink=1 type="text/javascript" src="scripts/filesaver.min.js"></script>
35 {{{customJSTags}}}
36 <meta name="msapplication-TileColor" content="#00aba9">
37 <meta name="theme-color" content="#ffffff">
38 <title>{{{title}}}</title>
39 <style>
40 body {
41 background-color: white;
42 }
43
44 .night body {
45 background-color: black;
46 }
47
48 #MxMESH {
49 color: black;
50 }
51
52 .night #MxMESH {
53 color: lightgray;
54 }
55
56 .textOverGray { color: black; }
57
58 #dialog {
59 z-index:1000;
60 background-color:#EEE;
61 box-shadow:0px 0px 15px #666;
62 font-family:Arial,Helvetica,sans-serif;
63 border-radius:5px;
64 position:fixed;
65 top:90px;
66 width:300px;
67 }
68
69 .night #dialog {
70 color: black;
71 background-color:#AAA;
72 }
73
74 :focus {
75 outline: 0;
76 }
77
78 a {
79 color: #036;
80 text-decoration: underline;
81 }
82
83 .night a {
84 color: #99F;
85 }
86
87 #footer a {
88 color: #fff;
89 text-decoration: underline;
90 }
91
92 #footer a:hover {
93 text-decoration: none;
94 }
95
96 .night #footer {
97 color: gray;
98 }
99
100 .i1 {
101 background: url(../images/icons50.png) 0px 0px;
102 background-image: image-set(url(../images/icons50.png) 1x, url(../images/icons100.png) 2x);
103 height: 50px;
104 width: 50px;
105 border: none;
106 }
107
108 .i2 {
109 background: url(../images/icons50.png) -50px 0px;
110 background-image: image-set(url(../images/icons50.png) 1x, url(../images/icons100.png) 2x);
111 height: 50px;
112 width: 50px;
113 border: none;
114 }
115
116 .i3 {
117 background: url(../images/icons50.png) -100px 0px;
118 background-image: image-set(url(../images/icons50.png) 1x, url(../images/icons100.png) 2x);
119 height: 50px;
120 width: 50px;
121 border: none;
122 }
123
124 .i4 {
125 background: url(../images/icons50.png) -150px 0px;
126 background-image: image-set(url(../images/icons50.png) 1x, url(../images/icons100.png) 2x);
127 height: 50px;
128 width: 50px;
129 border: none;
130 }
131
132 .i5 {
133 background: url(../images/icons50.png) -200px 0px;
134 background-image: image-set(url(../images/icons50.png) 1x, url(../images/icons100.png) 2x);
135 height: 50px;
136 width: 50px;
137 border: none;
138 }
139
140 .i6 {
141 background: url(../images/icons50.png) -250px 0px;
142 background-image: image-set(url(../images/icons50.png) 1x, url(../images/icons100.png) 2x);
143 height: 50px;
144 width: 50px;
145 border: none;
146 }
147
148 .i7 {
149 background: url(../images/icons50.png) -300px 0px;
150 background-image: image-set(url(../images/icons50.png) 1x, url(../images/icons100.png) 2x);
151 height: 50px;
152 width: 50px;
153 border: none;
154 }
155
156 .i8 {
157 background: url(../images/icons50.png) -350px 0px;
158 background-image: image-set(url(../images/icons50.png) 1x, url(../images/icons100.png) 2x);
159 height: 50px;
160 width: 50px;
161 border: none;
162 }
163
164 .m0 {
165 background: url(../images/images16.png) -32px 0px;
166 height: 16px;
167 width: 16px;
168 border: none;
169 float: left;
170 }
171
172 .m1 {
173 background: url(../images/images16.png) -16px 0px;
174 height: 16px;
175 width: 16px;
176 border: none;
177 float: left;
178 }
179
180 .m2 {
181 background: url(../images/images16.png) -96px 0px;
182 height: 16px;
183 width: 16px;
184 border: none;
185 float: left;
186 }
187
188 .m3 {
189 background: url(../images/images16.png) -112px 0px;
190 height: 16px;
191 width: 16px;
192 border: none;
193 float: left;
194 }
195
196 .m4 {
197 background: url(../images/images16.png) -128px 0px;
198 height: 16px;
199 width: 16px;
200 border: none;
201 float: left;
202 }
203
204 .NotifyIconSmall1 { width:24px; height:24px; background: url(../images/notify24.png) 0px 0px; background-image: image-set(url(../images/notify24.png) 1x, url(../images/notify48.png) 2x); }
205 .NotifyIconSmall2 { width:24px; height:24px; background: url(../images/notify24.png) -24px 0px; background-image: image-set(url(../images/notify24.png) 1x, url(../images/notify48.png) 2x); }
206 .NotifyIconSmall3 { width:24px; height:24px; background: url(../images/notify24.png) -48px 0px; background-image: image-set(url(../images/notify24.png) 1x, url(../images/notify48.png) 2x); }
207 .NotifyIconSmall4 { width:24px; height:24px; background: url(../images/notify24.png) -72px 0px; background-image: image-set(url(../images/notify24.png) 1x, url(../images/notify48.png) 2x); }
208 .NotifyIconSmall5 { width:24px; height:24px; background: url(../images/notify24.png) -96px 0px; background-image: image-set(url(../images/notify24.png) 1x, url(../images/notify48.png) 2x); }
209 .NotifyIconSmall6 { width:24px; height:24px; background: url(../images/notify24.png) -120px 0px; background-image: image-set(url(../images/notify24.png) 1x, url(../images/notify48.png) 2x); }
210 .NotifyIconSmall7 { width:24px; height:24px; background: url(../images/notify24.png) -144px 0px; background-image: image-set(url(../images/notify24.png) 1x, url(../images/notify48.png) 2x); }
211 .NotifyIconSmall8 { width:24px; height:24px; background: url(../images/notify24.png) -168px 0px; background-image: image-set(url(../images/notify24.png) 1x, url(../images/notify48.png) 2x); }
212 .NotifyIconSmall9 { width:24px; height:24px; background: url(../images/notify24.png) -192px 0px; background-image: image-set(url(../images/notify24.png) 1x, url(../images/notify48.png) 2x); }
213
214 .gray {
215 /*filter: url("data:image/svg+xml;utf8,&lt;svg xmlns=\'http://www.w3.org/2000/svg\'&gt;&lt;filter id=\'grayscale\'&gt;&lt;feColorMatrix type=\'matrix\' values=\'0.3333 0.3333 0.3333 0 0 0.3333 0.3333 0.3333 0 0 0.3333 0.3333 0.3333 0 0 0 0 0 1 0\'/&gt;&lt;/filter&gt;&lt;/svg&gt;#grayscale");*/ /* Firefox 10+, Firefox on Android */
216 filter: gray; /* IE6-9 */
217 -webkit-filter: grayscale(100%) opacity(60%); /* Chrome 19+, Safari 6+, Safari 6+ iOS */
218 }
219
220 .DevSt {
221 padding-left: 5px;
222 border-bottom-style: solid;
223 border-bottom-width: 1px;
224 border-bottom-color: #DDDDDD;
225 }
226
227 .noselect {
228 -webkit-touch-callout: none;
229 -webkit-user-select: none;
230 -khtml-user-select: none;
231 -moz-user-select: none;
232 -ms-user-select: none;
233 user-select: none;
234 }
235
236 .fileIcon1 {
237 background: url(data:image/gif;base64,R0lGODlhEAAQAJEDAPb49Y2Sj9LT2f///yH5BAEAAAMALAAAAAAQABAAAAImnI+py+1vhJwyUYAzHTL4D3qdlJWaIFJqmKod607sDKIiDUP63hQAOw==);
238 height: 16px;
239 width: 16px;
240 cursor: pointer;
241 border: none;
242 float: left;
243 margin-top: 1px;
244 }
245
246 .fileIcon2 {
247 background: url(data:image/gif;base64,R0lGODlhEAAQAJEDAM2xV/Xur+XPgP///yH5BAEAAAMALAAAAAAQABAAAAJD3ISZIGHWUGihznesYDYATFVM+D2hJ4lgN1olxALAtAlmPCJvuMmJd6PJckDYwicrHhTD5o7plJmg0Uc0asNMkphHAQA7);
248 height: 16px;
249 width: 16px;
250 cursor: pointer;
251 border: none;
252 float: left;
253 margin-top: 1px;
254 }
255
256 .fileIcon3 {
257 background: url(data:image/gif;base64,R0lGODlhEAAQAJEDAPb19IGBgbq6uv///yH5BAEAAAMALAAAAAAQABAAAAIy3ISpxgcPH2ouQgFEw1YmxnUXKEaaEZZnVWZk66JwzKpvuwZzwOgwb/C1gIOA8Yg8DgoAOw==);
258 height: 16px;
259 width: 16px;
260 cursor: pointer;
261 border: none;
262 float: left;
263 margin-top: 1px;
264 }
265
266 .fileIcon4 {
267 background: url(../images/meshicon16.png);
268 height: 16px;
269 width: 16px;
270 cursor: pointer;
271 border: none;
272 float: left;
273 margin-top: 1px;
274 }
275
276 .filelist {
277 -moz-user-select: none;
278 -khtml-user-select: none;
279 -webkit-user-select: none;
280 -o-user-select: none;
281 cursor: default;
282 -khtml-user-drag: element;
283 clear: both;
284 }
285
286 .deviceNotifyDot {
287 position:absolute;
288 right:10px;
289 top:0px;
290 height:16px;
291 }
292
293 .deviceNotifyDotSub {
294 text-align:center;
295 color:#FFF;
296 width:16px;
297 background-color:#00F;
298 padding:2px;
299 border-radius:10px;
300 box-shadow: 2px 2px 10px black;
301 cursor:pointer;
302 margin-left:3px;
303 float:left;
304 }
305
306 .deviceNotifyDotSub:hover {
307 background-color:#44F;
308 }
309
310 .deviceNotifySmallDot {
311 position:absolute;
312 right:10px;
313 top:0px;
314 height:10px;
315 }
316
317 .deviceNotifySmallDotSub {
318 text-align:center;
319 color:#FFF;
320 width:10px;
321 padding:2px;
322 background-color:#00F;
323 border-radius:10px;
324 box-shadow: 2px 2px 10px black;
325 cursor:pointer;
326 margin-left:2px;
327 float:left;
328 }
329
330 .deviceNotifySmallDotSub:hover {
331 background-color:#44F;
332 }
333
334 .deviceNotifyLargeDot {
335 position:absolute;
336 right:10px;
337 top:10px;
338 height:40px;
339 }
340
341 .deviceNotifyLargeDotSub {
342 text-align:center;
343 width:35px;
344 height:35px;
345 color:#FFF;
346 padding:2px;
347 background-color:#00F;
348 border-radius:20px;
349 box-shadow: 2px 2px 10px black;
350 cursor:pointer;
351 margin-left:4px;
352 font-size:30px;
353 float:left;
354 }
355
356 .deviceNotifyLargeDotSub:hover {
357 background-color:#44F;
358 }
359
360 .style10 {
361 background-color: #C9C9C9;
362 color: #000;
363 }
364
365 .night .style10 {
366 background-color: #888;
367 }
368
369 .deviceBatteryLarge {
370 position:absolute;
371 right:10px;
372 top:0px;
373 width:28px;
374 height:48px;
375 border:none;
376 box-shadow:none;
377 }
378
379 .deviceBatteryLarge1 { background: url(../images/batteries48.png) 0px 0px; }
380 .deviceBatteryLarge2 { background: url(../images/batteries48.png) -28px 0px; }
381 .deviceBatteryLarge3 { background: url(../images/batteries48.png) -56px 0px; }
382 .deviceBatteryLarge4 { background: url(../images/batteries48.png) -84px 0px; }
383 .deviceBatteryLarge5 { background: url(../images/batteries48.png) -112px 0px; }
384 .deviceBatteryLarge6 { background: url(../images/batteries48.png) -140px 0px; }
385 .deviceBatteryLarge7 { background: url(../images/batteries48.png) -168px 0px; }
386 .deviceBatteryLarge8 { background: url(../images/batteries48.png) -196px 0px; }
387 .deviceBatteryLarge9 { background: url(../images/batteries48.png) -224px 0px; }
388 .deviceBatteryLarge10 { background: url(../images/batteries48.png) -252px 0px; }
389 .deviceBatteryLarge11 { background: url(../images/batteries48.png) -280px 0px; }
390
391 .deviceBatterySmall {
392 position:absolute;
393 left:6px;
394 top:22px;
395 width:14px;
396 height:24px;
397 border:none;
398 box-shadow:none;
399 }
400
401 .deviceBatterySmall1 { background: url(../images/batteries24.png) 0px 0px; }
402 .deviceBatterySmall2 { background: url(../images/batteries24.png) -14px 0px; }
403 .deviceBatterySmall3 { background: url(../images/batteries24.png) -28px 0px; }
404 .deviceBatterySmall4 { background: url(../images/batteries24.png) -42px 0px; }
405 .deviceBatterySmall5 { background: url(../images/batteries24.png) -56px 0px; }
406 .deviceBatterySmall6 { background: url(../images/batteries24.png) -70px 0px; }
407 .deviceBatterySmall7 { background: url(../images/batteries24.png) -84px 0px; }
408 .deviceBatterySmall8 { background: url(../images/batteries24.png) -98px 0px; }
409 .deviceBatterySmall9 { background: url(../images/batteries24.png) -112px 0px; }
410 .deviceBatterySmall10 { background: url(../images/batteries24.png) -126px 0px; }
411 .deviceBatterySmall11 { background: url(../images/batteries24.png) -140px 0px; }
412
413 .meshList {
414 width:auto;
415 height:40px;
416 background-color:lightgray;
417 margin-top:5px;
418 margin-bottom:5px;
419 margin-left:60px;
420 padding-top:5px;
421 padding-bottom:5px;
422 border-radius:8px 0px 0px 8px;
423 }
424
425 .night .meshList {
426 background-color: gray;
427 }
428
429 .devList1 {
430 height: 50px;
431 cursor: pointer;
432 position: relative;
433 margin-top: 5px;
434 margin-bottom: 5px;
435 }
436
437 .devList2 {
438 float: left;
439 margin-left: 4px
440 }
441
442 .devList3 {
443 width: auto;
444 height: 40px;
445 background-color: lightgray;
446 margin-left: 60px;
447 padding-top: 5px;
448 padding-bottom: 5px;
449 border-radius: 8px 0px 0px 8px;
450 }
451
452 .night .devList3 {
453 background-color: gray;
454 }
455
456 .devList4 {
457 padding-left: 12px;
458 padding-top: 2px;
459 color: black;
460 }
461
462 .devList5 {
463 padding-left: 12px;
464 padding-top: 3px;
465 color: #444
466 }
467
468 .night .devList5 {
469 color: black;
470 }
471
472 .deskButton {
473 box-shadow: 0px 0px 10px #000;
474 border-radius:20px;
475 position:absolute;
476 right:10px;
477 top:10px;
478 cursor:pointer;
479 background-color:#AAA;
480 z-index:1000;
481 }
482
483 .menuButton{
484 box-shadow: 0px 0px 10px #000;
485 border-radius:10px;
486 display:inline-block;
487 width:120px;
488 background-color:#AAA;
489 text-align:center;
490 padding:8px;
491 cursor:pointer;
492 margin:10px;
493 z-index:1000;
494 }
495
496 #notificationCount {
497 min-width: 28px;
498 font-size: 20px;
499 background-color: orange;
500 text-align: center;
501 cursor: pointer;
502 color: black;
503 }
504
505 .notifiyBox {
506 font-size: 16px;
507 position: absolute;
508 z-index: 1000;
509 top: 60px;
510 right: 76px;
511 width: 300px;
512 text-align: left;
513 background-color: #F0ECCD;
514 border: 4px solid #666;
515 -webkit-border-radius: 10px;
516 -moz-border-radius: 10px;
517 border-radius: 10px;
518 -webkit-box-shadow: 2px 2px 4px #888;
519 -moz-box-shadow: 2px 2px 4px #888;
520 box-shadow: 2px 2px 4px #888;
521 max-height: 200px;
522 }
523
524 .night .notifiyBox {
525 color: black;
526 }
527
528 .notifiyBox:before {
529 content: ' ';
530 position: absolute;
531 width: 0;
532 height: 0;
533 right: 5px;
534 top: -30px;
535 border: 15px solid;
536 border-color: transparent #666 #666 transparent;
537 }
538
539 .notifiyBox:after {
540 content: ' ';
541 position: absolute;
542 width: 0;
543 height: 0;
544 right: 7px;
545 top: -24px;
546 border: 12px solid;
547 border-color: transparent #F0ECCD #F0ECCD transparent;
548 }
549
550 #p15statetext {
551 padding: 4px;
552 height: 15px;
553 }
554
555 #p15agentConsole {
556 background: black;
557 margin: 0;
558 padding: 0;
559 color: lightgray;
560 width: 100%;
561 position: relative;
562 }
563
564 #p15coreName {
565 padding: 4px;
566 display: inline-block;
567 }
568
569 #p15agentConsoleText {
570 position:absolute;
571 margin: 0;
572 padding: 0;
573 top: 0;
574 bottom: 0;
575 left:0;
576 right: 0;
577 overflow-y: scroll;
578 overflow-x: auto;
579 }
580
581 .areaHead {
582 padding-top: 2px;
583 padding-bottom: 2px;
584 background: #C0C0C0;
585 }
586
587 .night .areaHead {
588 color: #CCC;
589 background: #333;
590 }
591
592 .areaFoot {
593 padding-top: 2px;
594 padding-bottom: 2px;
595 background: #C0C0C0;
596 }
597
598 .night .areaFoot {
599 color: #CCC;
600 background: #333;
601 }
602
603 .toright2 {
604 float: right;
605 text-align: right;
606 }
607
608 #consoleTable {
609 width: 100%;
610 height: 100%;
611 padding: 0px;
612 margin-top: 0px;
613 }
614
615 .night #consoleTable {
616 color: black;
617 }
618
619 .menucurve {
620 background-color:white;
621 width:10px;
622 height:10px;
623 border-radius:10px 0 0 0;
624 border-right:1px solid white;
625 border-bottom:1px solid white;
626 }
627
628 .night .menucurve {
629 background-color:black;
630 border-right:1px solid black;
631 border-bottom:1px solid black;
632 }
633
634 #termTable {
635 width: 100%;
636 padding: 0px;
637 margin-top: 0px;
638 }
639
640 .fulldesk #termTable {
641 position: absolute;
642 top: 0;
643 bottom: 0;
644 left: 0;
645 right: 0;
646 }
647
648 #termarea3x {
649 background: black;
650 text-align: center;
651 height: 400px;
652 position: relative;
653 }
654
655 .viewSelector10 {
656 margin-left: 2px;
657 margin-top: 2px;
658 background: url(../images/views.png) -476px 0px;
659 height: 28px;
660 width: 28px;
661 }
662
663 .viewSelector11 {
664 margin-left: 2px;
665 margin-top: 2px;
666 background: url(../images/views.png) -504px 0px;
667 height: 28px;
668 width: 28px;
669 }
670
671 .tagSpan {
672 background-color: lightgray;
673 padding: 3px;
674 border-radius: 5px;
675 }
676
677 .night .tagSpan {
678 color: black;
679 }
680
681 #d3serveraction, #d2serveraction {
682 width: 100%;
683 background-color: #d3d9d6;
684 text-align: left;
685 padding: 3px;
686 }
687
688 #d3serverfiles, #d2serverfiles {
689 width: 100%;
690 height: 150px;
691 background-color: white;
692 padding: 2px;
693 border: 1px solid gray;
694 overflow-y: scroll;
695 }
696
697 </style>
698 </head>
699 <body id="body" onload="if (typeof(startup) !== 'undefined') startup();" style="overflow-y:hidden;margin:0;padding:0;border:0;font-size:13px;font-family:\'Trebuchet MS\', Arial, Helvetica, sans-serif">
700 <div id=container>
701 <div id="notifiyBox" class="notifiyBox" style="display:none"></div>
702 <div id=mastheadx></div>
703 <div id=masthead style="background:url(logo.png) 0px 0px;background-size:341px 50px;background-color:#036;background-repeat:no-repeat;height:50px;width:100%;overflow:hidden">
704 <div style="width:calc(100% - 50px);overflow:hidden">
705 <div style="float:left;height:66px;color:#c8c8c8;padding-left:10px;padding-top:6px" onclick="go(2)">
706 <strong><font style="font-size:36px;font-family:Arial,Helvetica,sans-serif;text-shadow: 1px 1px 2px #000;">{{{title1}}}</font></strong>
707 </div>
708 <div style="float:left;height:66px;color:#c8c8c8;padding-left:5px;padding-top:10px">
709 <strong><font style="font-size:12px;font-family:Arial,Helvetica,sans-serif;text-shadow: 1px 1px 2px #000;">{{{title2}}}</font></strong>
710 </div>
711 </div>
712 <div id="devViewPageState" class=noselect style="position:absolute;right:160px;top:10px;height:30px;line-height:30px;color:#c8c8c8;font-size:16px;display:none"></div>
713 <img id="devViewPageButton2" class=noselect style="position:absolute;right:130px;top:10px;cursor:pointer;display:none" onclick=onDeviceViewPageChange(2) src="/images/left-30.png" width=20 height=30 />
714 <img id="devViewPageButton3" class=noselect style="position:absolute;right:100px;top:10px;cursor:pointer;display:none" onclick=onDeviceViewPageChange(3) src="/images/right-30.png" width=20 height=30 />
715 <div id=notificationCount onclick="clickNotificationIcon()" class="unselectable" style="position:absolute;right:50px;top:0px;font-size:28px;width:50px;height:50px;cursor:pointer;display:none" title="Click to view current notifications"><div id="notificationCount2" style="padding-top:8px">0</div></div>
716 <img id="topMenuIcon" class=noselect style="position:absolute;right:0;top:10px;color:#c8c8c8;font-size:44px;margin-right:8px;cursor:pointer;display:none" onclick=topMenu() src="/images/3bars-30.png" width=30 height=30 />
717 </div>
718 <div id=page_content style="position:absolute;bottom:32px;top:50px;width:100%">
719 <div id=column_l style="width:100%;padding:0;position:absolute;bottom:0px;top:0px">
720 <div id=p0 style=display:none;width:100%;height:100%>
721 <div style="display:flex;align-items:center;width:100%;height:100%">
722 <div id=p0message style=text-align:center;width:100%><span id="p0span">Server disconnected</span>, <href onclick=reload() style=cursor:pointer><u>click to reconnect</u></href>.</div>
723 </div>
724 </div>
725 <div id=p1 style=display:none;width:100%;height:100%>
726 <div style="display:flex;align-items:center;width:100%;height:100%">
727 <div id=p1message style=text-align:center;width:100%></div>
728 </div>
729 </div>
730 <div id=p2 style="display:none;position:absolute;top:0;left:0;right:0;bottom:0">
731 <div id=xdevices style="position:absolute;overflow-y:auto;top:0;left:0;right:0;bottom:34px" onscroll="onDevicesScroll()" ontouchstart="onDeviceTouch(true)" ontouchend="onDeviceTouch(false)"></div>
732 <div id=xdevicesBar style="position:absolute;overflow-y:auto;height:34px;left:0;right:0;bottom:0px;background-color:#aaa;color:black">
733 <div style="margin:4px">
734 <span style="width:20px;display:inline-block;text-align:center;cursor:pointer;font-size:16px" onclick=clearSearchInput()><b>X</b></span>
735 <input id=SearchInput autocomplete=off type=search placeholder=Filter onchange=onDeviceSearchChanged(event) onclick=onDeviceSearchChanged(event) onkeyup=onDeviceSearchChanged(event) style="padding:2px;margin:0;height:20px;background-color:#FFF" />&nbsp;
736 <label class=noselect><input type=checkbox id=RealNameCheckBox onclick=onRealNameCheckBox() />OS Name</label>
737 <label class=noselect><input type=checkbox id=OnlineCheckBox onclick=onOnlineCheckBox(event) />Online</label>
738 </div>
739 </div>
740 </div>
741 <div id=p3 style=display:none;position:absolute;bottom:0;top:0;width:100%>
742 <table cellspacing=0 style="margin:0;padding:0;border-spacing:0;border:0">
743 <tr style=padding:0>
744 <td style="padding:0;color:#c8c8c8;text-align:center;cursor:pointer" width=60px valign=top onclick=goBack()>
745 <div style="padding:0;background-color:#036;width:10px;height:10px;float:right;border:0">
746 <div class="menucurve"></div>
747 </div>
748 <div style="padding:0;font-size:25px;background-color:#036;width:50px;border-radius:0 0 10px 0;height:36px">&#9664;</div>
749 </td>
750 <td>
751 <div style=margin-left:5px>
752 <strong style="font-size:large"><span id=p3userName></span></strong><br />
753 </div>
754 </td>
755 </tr>
756 </table>
757 <div id=p3info style="overflow-y:auto;position:absolute;top:55px;bottom:0px;width:100%">
758 <img id="p2AccountImage" alt="" width="128" height="128" onclick="account_manageImage(0)" src="images/user-256.png" style="position:absolute;right:8px;top:7px;border-radius:8px;box-shadow:0px 0px 7px #000" />
759 <div style="margin-left:8px">
760 <div id="p3AccountActions">
761 <div id="p2AccountSecurity" style="display:none">
762 <p><strong>Account Security</strong></p>
763 <div style="margin-left:9px;margin-bottom:8px">
764 <div id="managePhoneNumber1" style="margin-top:5px;display:none"><a onclick="account_managePhone()" style="cursor:pointer">Manage phone number</a> <span id="authPhoneNumberCheck"><strong>&#x2713;</strong></span></div>
765 <div id="manageEmail2FA" style="margin-top:5px;display:none"><a onclick="account_manageAuthEmail()" style="cursor:pointer">Manage email authentication</a> <span id="authEmailSetupCheck"><strong>&#x2713;</strong></span></div>
766 <div style="margin-top:5px"><a href=# onclick="account_showLocalizationSettings()">Localization Settings</a></div>
767 <div id="manageAuthApp" style="margin-top:5px;display:none"><a onclick="account_manageAuthApp()" style="cursor:pointer">Manage authenticator app</a> <span id="authAppSetupCheck"><strong>&#x2713;</strong></span></div>
768 <div id="manageOtp" style="margin-top:5px;display:none"><a onclick="account_manageOtp(0)" style="cursor:pointer">Manage backup codes</a> <span id="authCodesSetupCheck"><strong>&#x2713;</strong></span></div>
769 </div>
770 </div>
771 <div id="p2AccountActions" style="display:none">
772 <p><strong>Account Actions</strong></p>
773 <div style="margin-left:9px;margin-bottom:8px">
774 <div style="margin-top:5px"><span id="viewPreviousLogins"><a onclick="return account_viewPreviousLogins()" style="cursor:pointer">View previous logins</a></span></div>
775 <div style="margin-top:5px"><span id="managePhoneNumber2" style="display:none"><a onclick="account_managePhone()" style="cursor:pointer">Manage phone number</a></span></div>
776 <div style="margin-top:5px"><span id="verifyEmailId" style="display:none"><a onclick="account_showVerifyEmail()" style="cursor:pointer">Verify email</a></span></div>
777 <span id="p2AccountPassActions">
778 <div style="margin-top:5px"><span id="changeEmailId" style="display:none"><a onclick="account_showChangeEmail()" style="cursor:pointer">Change email address</a></span></div>
779 <div style="margin-top:5px"><a onclick="account_showChangePassword()" style="cursor:pointer">Change password</a><span id="p2nextPasswordUpdateTime"></span></div>
780 <div style="margin-top:5px"><a onclick="account_showDeleteAccount()" style="cursor:pointer">Delete account</a></div>
781 </span>
782 <div style="margin-top:5px" id="setDarkModeLink"><a onclick="toggleNightMode()" style="cursor:pointer">Set dark mode</a></div>
783 <div style="margin-top:5px"><a onclick="showNotes(false)" style="cursor:pointer">Personal notes</a></div>
784 </div>
785 <br style=clear:both />
786 </div>
787 </div>
788 <strong>Device Groups</strong>
789 <span id="p3createMeshLink1">( <a onclick=account_createMesh() style=cursor:pointer><img src="images/icon-addnew.png" width=12 height=12 border=0 /> New</a> )</span>
790 <br /><br />
791 <div id=p3meshes></div>
792 <div id=p3noMeshFound style=margin-left:9px;display:none>No device groups.<span id="p3createMeshLink2"> <a onclick=account_createMesh() style=cursor:pointer><strong>Get started here!</strong></a></span></div>
793 <br style=clear:both />
794 </div>
795 </div>
796 </div>
797 <div id=p5 style=display:none>
798 <table cellspacing=0 style="margin:0;padding:0;border-spacing:0;border:0;">
799 <tr style=padding:0>
800 <td style="padding:0;color:#c8c8c8;text-align:center;cursor:pointer" width=60px valign=top onclick=goBack()>
801 <div style="padding:0;background-color:#036;width:10px;height:10px;float:right;border:0">
802 <div class="menucurve"></div>
803 </div>
804 <div style="padding:0;font-size:25px;background-color:#036;width:50px;border-radius:0 0 10px 0;height:36px">&#9664;</div>
805 </td>
806 <td>
807 <img src="/images/user-50.png" width=50 height=50 />
808 </td>
809 <td>
810 <div style=margin-left:5px>
811 <strong style="font-size:large">My Files</strong><br />
812 </div>
813 </td>
814 </tr>
815 </table>
816 <div id=p5myfiles style="position:absolute;top:55px;bottom:0px;width:100%">
817 <table id="p5toolbar" style="width:100%;height:78px" cellpadding="0" cellspacing="0">
818 <tr>
819 <td style="width:100%;background-color:#d3d9d6;text-align:left;padding:4px" valign=bottom>
820 <div style="width:100%;text-align:center">
821 <input type=button style="width:calc(100%/5 - 5px)" id=p5FolderUp disabled="disabled" onclick="p5folderup()" value="Up" />
822 <input type=button style="width:calc(100%/5 - 5px)" id=p5SelectAllButton disabled="disabled" onclick="p5selectallfile()" value="SelectAll" onkeypress="return false" onkeydown="return false" />
823 <input type=button style="width:calc(100%/5 - 5px)" id=p5RenameFileButton disabled="disabled" value="Rename" onclick="p5renamefile()" onkeypress="return false" onkeydown="return false" />
824 <input type=button style="width:calc(100%/5 - 5px)" id=p5DeleteFileButton disabled="disabled" value="Delete" onclick="p5deletefile()" onkeypress="return false" onkeydown="return false" />
825 <input type=button style="width:calc(100%/5 - 5px)" id=p5NewFolderButton disabled="disabled" value="Folder" onclick="p5createfolder()" onkeypress="return false" onkeydown="return false" />
826 </div>
827 <div style="width:100%;text-align:center">
828 <input type=button style="width:calc(100%/5 - 5px)" id=p5UploadButton disabled="disabled" value="Upload" onclick="p5uploadFile()" onkeypress="return false" onkeydown="return false" />
829 <input type=button style="width:calc(100%/5 - 5px)" id=p5CutButton disabled="disabled" value="Cut" onclick="p5copyFile(1)" onkeypress="return false" onkeydown="return false" />
830 <input type=button style="width:calc(100%/5 - 5px)" id=p5CopyButton disabled="disabled" value="Copy" onclick="p5copyFile(0)" onkeypress="return false" onkeydown="return false" />
831 <input type=button style="width:calc(100%/5 - 5px)" id=p5PasteButton disabled="disabled" value="Paste" onclick="p5pasteFile()" onkeypress="return false" onkeydown="return false" />
832 <input type=button style="width:calc(100%/5 - 5px)" id=p5RefreshButton value="Refresh" onclick="p5refreshFiles()" onkeypress="return false" onkeydown="return false" />
833 </div>
834 </td>
835 </tr>
836 <tr>
837 <td style="background-color:#E4E9E7;height:28px">
838 <table style="width:100%">
839 <tr>
840 <td id=p5currentpath style="overflow:hidden;padding-left:4px;padding-top:2px;color:black"></td>
841 <td style="text-align:right;padding-right:4px">
842 <select id=p5sortdropdown onchange=updateFiles()>
843 <option value=1 selected="selected">Sort by name</option>
844 <option value=2>Sort by size</option>
845 <option value=3>Sort by date</option>
846 <option value=4>Descend by name</option>
847 <option value=5>Descend by size</option>
848 <option value=6>Descend by date</option>
849 </select>
850 </td>
851 </tr>
852 </table>
853 </td>
854 </tr>
855 </table>
856 <div id="p5filetable" style="width:100%;height:calc(100% - 102px);overflow:auto;-webkit-user-select:none">
857 <!--
858 <div id="p5bigok" style="width:256px;overflow:hidden;position:absolute;left:337px;top:200px;text-align:center;font-size:1600%;color:#AAAAAA;display:none"><b>&checkmark;</b></div>
859 <div id="p5bigfail" style="width:256px;overflow:hidden;position:absolute;left:337px;top:200px;text-align:center;font-size:1600%;color:#AAAAAA;display:none"><b>&#10007;</b></div>
860 -->
861 <span id="p5files"></span>
862 </div>
863 <table id="p5toolbarBottom" style="width:100%;height:22px;position:absolute;bottom:0px;background-color:#D3D9D6" cellpadding=0 cellspacing=0>
864 <tr>
865 <td style="text-align:left;padding:3px">&nbsp;<span id="p5bottomstatus"></span></td>
866 <td id="p5rightOfButtons" style="text-align:right;padding:3px"></td>
867 </tr>
868 </table>
869 </div>
870 </div>
871 <div id=p10 style=display:none;position:absolute;bottom:0;top:0;width:100%;overflow:hidden>
872 <table id=p10deskTopTable cellspacing=0 style="margin:0;padding:0;border-spacing:0;border:0;position:absolute;top:0">
873 <tr style=padding:0>
874 <td style="padding:0;color:#c8c8c8;text-align:center;cursor:pointer" width=60px valign=top onclick=goBack()>
875 <div style="padding:0;background-color:#036;width:10px;height:10px;float:right;border:0">
876 <div class="menucurve"></div>
877 </div>
878 <div style="padding:0;font-size:25px;background-color:#036;width:50px;border-radius:0 0 10px 0;height:36px">&#9664;</div>
879 </td>
880 <td>
881 <a id=MainComputerImage style=cursor:pointer onclick=p10showiconselector()></a>
882 </td>
883 <td>
884 <div style=margin-left:5px>
885 <strong><span id=p10deviceName></span></strong><br />
886 <span id=MainComputerState></span>
887 </div>
888 </td>
889 </tr>
890 </table>
891 <div id=p10dialog style="z-index:1000;background-color:#EEE;box-shadow:0px 0px 15px #666;font-family:Arial,Helvetica,sans-serif;border-radius:5px;position:fixed;top:30px;width:300px;left:30px;display:none">
892 <div style="width:100%;background-color:#003366;color:#FFF;border-radius:5px 5px 0 0">
893 <div style=padding:5px>Keyboard Shortcuts Customization</div>
894 <div style=width:100%;margin:6px></div>
895 </div>
896 <div style="margin-right:16px;margin-left:8px"><div id=p10dialog2 style="margin:auto;margin:3px"></div></div>
897 <div style="width:100%;padding:2px;text-align:center"><input type=button value="Restore Default Keyboard Shortcuts" onclick=restoreDeskCustomizeKey() /></div>
898 <div style="padding:10px;margin-bottom:20px"><input type="button" value="OK" style="float:right;width:80px" onclick="deskCustomizeKeysEx()"></div>
899 </div>
900 <div id=p10general style="overflow-y:scroll;position:absolute;top:55px;bottom:0px;width:100%">
901 <div class="deviceNotifyLargeDot">
902 <img id="p10deviceStar" class=deviceNotifyLargeDotSub src=images/icon-star-notify-40.png width=35 height=35>
903 <div id="p10deviceMsg" onclick=showDeviceMessages(null,null,event) class=deviceNotifyLargeDotSub></div>
904 <img id="p10deviceNotify" onclick=showDeviceSessions() class=deviceNotifyLargeDotSub src=images/icon-relay-notify-40.png width=35 height=35>
905 <img id="p10deviceHelp" onclick=showDeviceHelpRequests(null,null,event) class=deviceNotifyLargeDotSub src=images/icon-help-notify-40.png width=35 height=35>
906 </div>
907 <div id="p10deviceBattery" class="deviceBatteryLarge deviceBatteryLarge1"></div>
908 <div id=p10html style="margin-left:8px;margin-right:8px"></div>
909 <div id=p10html2></div>
910 <div id=p10html3 style="margin-left:8px"></div>
911 </div>
912 <img id="deskkeybutton1" src="images/mobile-desk-exit.png" class="deskButton" style="top:10px;display:none" onclick="exitButton()" />
913 <img id="deskkeybutton3a" src="images/mobile-desk-menu-open.png" class="deskButton" style="top:60px;display:none" onclick="toggleMenu(false)" />
914 <img id="deskkeybutton3b" src="images/mobile-desk-menu-close.png" class="deskButton" style="top:60px;display:none" onclick="toggleMenu(true)" />
915 <img id="deskkeybutton4a" src="images/mobile-desk-mouse-left.png" class="deskButton" style="top:110px;display:none" onclick="deskChangeMouseButton(0)" />
916 <img id="deskkeybutton4b" src="images/mobile-desk-mouse-right.png" class="deskButton" style="top:110px;display:none" onclick="deskChangeMouseButton(1)" />
917 <img id="deskkeybutton5a" src="images/mobile-desk-scale-out.png" class="deskButton" style="top:160px;display:none" onclick="deskChangeFullscreenZoom()" />
918 <img id="deskkeybutton5b" src="images/mobile-desk-scale-in.png" class="deskButton" style="top:160px;display:none" onclick="deskChangeFullscreenZoom()" />
919 <img id="deskkeybutton2a" src="images/mobile-desk-keyboard-open.png" class="deskButton" style="top:210px;display:none" onclick="toggleKeyboard()" />
920 <img id="deskkeybutton2b" src="images/mobile-desk-keyboard-close.png" class="deskButton" style="top:210px;display:none" onclick="toggleKeyboard()" />
921 <img id="deskkeybutton6a" src="images/mobile-touch.png" class="deskButton" style="top:260px;display:none" onclick="deskToggleTrackpad()" title="Switch to trackpad mode" />
922 <img id="deskkeybutton6b" src="images/mobile-desk-mouse.png" class="deskButton" style="top:260px;display:none" onclick="deskToggleTrackpad()" title="Switch to direct touch mode" />
923 <img id="trackpadCursor" src="images/mobile-pointer.png" style="display:none;position:absolute;pointer-events:none;z-index:999;width:24px;height:24px" />
924 <div style="position:absolute;top:0;left:0;z-index:200;opacity:0;width:1px;height:1px">
925 <input id="softKeyboard" autocapitalize="off" autocomplete="off" type="text" inputmode="text" spellcheck="false" style="z-index:200;opacity:0;width:1px;height:1px" onfocus="keyboardFocusChange()" onblur="keyboardFocusChange()" />
926 </div>
927 <div id="deskButtonMenu" style="display:none;position:absolute;top:10px;left:10px;right:55px;bottom:10px;z-index:1000"></div>
928 <div id=p10desktop style="overflow:hidden;position:absolute;top:55px;bottom:0px;width:100%;display:none">
929 <div id=deskarea1 style="position:absolute;top:0px;width:100%;height:32px">
930 <div style="padding-top:2px;padding-bottom:2px;background:#C0C0C0;height:32px">
931 <div style="float:right;text-align:right">
932 <span id="p14power"></span>&nbsp;
933 <input type=button id=deskFullScreen value="Full Screen" onclick=deskToggleFull(event) onkeypress="return false" onkeydown="return false" disabled="disabled" style="height:28px;margin-right:3px;">
934 </div>
935 <div style="margin-left:3px">
936 <input type=button id=connectbutton1 value="Connect" onclick=connectDesktop(event,3) onkeypress="return false" onkeydown="return false" disabled="disabled" style="height:28px">
937 <input type=button id=connectbutton1h value="HW Connect" onclick=connectDesktop(event,2) onkeypress="return false" onkeydown="return false" disabled="disabled" style="height:28px">
938 <input type=button id=disconnectbutton1 value="Disconnect" onclick=connectDesktop(event,0) onkeypress="return false" onkeydown="return false" style="height:28px">
939 <span id="deskstatus" style="color:black">Disconnected</span>
940 </div>
941 </div>
942 </div>
943 <div id=deskarea3 style="position:absolute;top:32px;width:100%;height:calc(100% - 64px);background-color:#000;text-align:center">
944 <div id=DeskParent style="height:100%">
945 <canvas id=Desk width=640 height=200 style="width:100%;-ms-touch-action:none;margin-left:0px" oncontextmenu="return false" onmousedown=dmousedown(event) onmouseup=dmouseup(event) onmousemove=dmousemove(event) onmousewheel=dmousewheel(event) ontouchstart=dtouchstart(event) ontouchmove=dtouchmove(event) ontouchend=dtouchend(event)></canvas>
946 </div>
947 <div id=p11DeskConsoleMsg style="display:none;cursor:pointer;position:absolute;left:30px;top:17px;color:yellow;background-color:rgba(0,0,0,0.6);padding:10px;border-radius:5px;text-align:left" onclick=p11clearConsoleMsg()></div>
948 <div id=p11DeskSessionSelector style="display:none;position:absolute;left:30px;top:17px;right:30px;bottom:17px;overflow-y:auto"></div>
949 </div>
950 <div id=deskarea4 style="position:absolute;bottom:0px;width:100%;height:32px">
951 <div style=padding-top:2px;padding-bottom:2px;background:#C0C0C0>
952 <div style=float:right;text-align:right;padding-right:2px>
953 <span id=DeskLockButton><img src='images/icon-lock.png' onclick=deviceLockFunction() height=16 width=16 style=padding-top:5px;cursor:pointer /></span>
954 <span id=DeskChatButton><img src='images/icon-chat.png' onclick=deviceChat(event) height=16 width=16 style=padding-top:5px;cursor:pointer /></span>&nbsp;
955 <span id=DeskToastButton><img src='images/icon-notify.png' onclick=deviceToastFunction() height=16 width=16 style=padding-top:5px;cursor:pointer /></span>&nbsp;
956 <span id=DeskOpenWebButton><img src='images/icon-url2.png' onclick=deviceUrlFunction() height=16 width=16 style=padding-top:5px;cursor:pointer /></span>&nbsp;
957 <span id=DeskRunButton><img src='images/icon-play.png' onclick=runDeviceCmd() height=16 width=16 style=padding-top:5px;cursor:pointer /></span>
958 <!--<input id=DeskToolsButton type=button value=Tools onkeypress="return false" onkeydown="return false" onclick="toggleDeskTools()">&nbsp;-->
959 </div>
960 <div>
961 <input id="deskActionsBtn" type=button style="margin-left:3px;height:28px" onkeypress="return false" onkeydown="return false" value=Actions onclick=deviceActionFunction() />
962 <input type="button" value="Settings" onkeypress="return false" onkeydown="return false" onclick="showDesktopSettings()" style="height:28px">
963 <input type="button" onkeypress="return false" onkeydown="return false" value="Power Actions..." onclick="showPowerActionDlg()" style="display:none;height:28px">
964 <!--<input type="button" id="DeskSpecialKeys" value="Keys" onkeypress="return false" onkeydown="return false" onclick="sendSpecialKeys()" style="height:28px">-->
965 <input type="button" id="DeskScreens" value="Screens" onkeypress="return false" onkeydown="return false" onclick="deskSelectScreens()" style="display:none;height:28px">
966 <label><span id="DeskControlSpan" style="display:none"><input id="DeskControl" type="checkbox" onkeypress="return false" onkeydown="return false">Input</span></label>
967 </div>
968 </div>
969 </div>
970 </div>
971 <div id="termButtonMenu" style="display:none;position:absolute;top:10px;left:10px;right:55px;bottom:10px;z-index:1000"></div>
972 <div id=p10terminal style="overflow:hidden;position:absolute;top:55px;bottom:0px;width:100%;display:none;background-color:#333">
973 <div id=termTable style="position:absolute;top:0;bottom:0;left:0;right:0">
974 <div id="termarea1">
975 <div class="areaHead" style="line-height:24px">
976 <div class="toright2">
977 <input type=button id=termFullScreen value="Full Screen" onclick=deskToggleFull(event) onkeypress="return false" onkeydown="return false" disabled="disabled" style="height:28px;margin-right:3px;">
978 <div id="terminalCustomUpperRight" style="float:left;margin-right:6px"></div>
979 </div>
980 <div>
981 <span id="connectbutton2span" style="margin-left:3px"><input type="button" id="connectbutton2" value="Connect" style="height:28px" onclick=connectTerminal(event,1) onkeypress="return false" onkeydown="return false" disabled="disabled" /></span>
982 <span id="connectbutton2sspan" style="margin-right:4px"><input type="button" id="connectbutton2s" value="SSH Connect" style="height:28px" onclick=connectTerminal(event,3) onkeypress="return false" onkeydown="return false" disabled="disabled" /></span>
983 <span id="disconnectbutton2span" style="margin-left:3px"><input type="button" id="disconnectbutton2" value="Disconnect" style="height:28px" onclick=connectTerminal(event,0) onkeypress="return false" onkeydown="return false" /></span>
984 <span id="termstatus" style="line-height:22px">Disconnected</span><span id="termtitle"></span>
985 </div>
986 </div>
987 </div>
988 <div id="termarea3" style="width:100%;height: calc(100% - 60px);" cellpadding=0 cellspacing=0>
989 <div id="termarea3x" style="width:100%;height:100%">
990 <div style="width:100%;height:100%;text-align:left" id="termarea3xdiv"></div>
991 </div>
992 </div>
993 <div id="termarea4" style="position:relative;height:32px;">
994 <div class="areaFoot">
995 <div class="toright2"></div>
996 <div style="height:28px">
997 <input id="termActionsBtn" style="margin-left:3px;height:28px" type=button title="Perform power actions on the device" onkeypress="return false" onkeydown="return false" value=Actions onclick=deviceActionFunction() />
998 <input id="ctrlcbutton" style="margin-left:3px;height:28px" type=button onkeypress="return false" onkeydown="return false" value="Ctl-C" onclick="termSendKey(3,'ctrlcbutton')" />
999 <input id="ctrlxbutton" style="margin-left:3px;height:28px" type=button onkeypress="return false" onkeydown="return false" value="Ctl-X" onclick="termSendKey(24,'ctrlxbutton')" />
1000 <input id="escbutton" style="margin-left:3px;height:28px" type=button onkeypress="return false" onkeydown="return false" value="ESC" onclick="termSendKey(27,'escbutton')" />
1001 </div>
1002 </div>
1003 </div>
1004 <div id=p12TermConsoleMsg style="display:none;cursor:pointer;position:absolute;left:30px;top:45px;color:yellow;background-color:rgba(0,0,0,0.6);padding:10px;border-radius:5px" onclick=p12clearConsoleMsg()></div>
1005 </div>
1006 </div>
1007 <div id=p10files style="position:absolute;top:55px;bottom:0px;width:100%;display:none">
1008 <table id="p13toolbar" style="width:100%;height:111px" cellpadding="0" cellspacing="0">
1009 <tr>
1010 <td style="background-color:#C0C0C0;border-bottom:2px solid black;padding:2px;line-height:24px">
1011 <div style="float:right;text-align:right">
1012 <input id="filesActionsBtn" type=button onkeypress="return false" onkeydown="return false" value=Actions onclick=deviceActionFunction() style=margin-right:2px />
1013 <div id="filesCustomUpperRight" style="float:left;margin-right:6px"></div>
1014 </div>
1015 <div style="margin-left:2px">
1016 <input id=p13AutoConnect value="AutoConnect" onclick=autoConnectFiles(event) onkeypress="return false" onkeydown="return false" type="button" style="display:none">
1017 <input id=p13Connect value="Connect" onclick=connectFiles(event,1) onkeypress="return false" onkeydown="return false" type="button" />
1018 <input id=p13Connects value="SFTP Connect" onclick=connectFiles(event,2) onkeypress="return false" onkeydown="return false" type="button" />
1019 <input id=p13Disconnect value="Disconnect" onclick=connectFiles(event) onkeypress="return false" onkeydown="return false" type="button" />
1020 <span class=textOverGray id=p13Status>Disconnected</span>
1021 </div>
1022 </td>
1023 </tr>
1024 <tr>
1025 <td style="width:100%;background-color:#d3d9d6;text-align:left;padding:4px" valign=bottom>
1026 <div style="width:100%;text-align:center">
1027 <input type=button style="width:calc(100%/5 - 5px)" id=p13FolderUp disabled="disabled" onclick="p13folderup()" value="Up" />
1028 <input type=button style="width:calc(100%/5 - 5px)" id=p13SelectAllButton disabled="disabled" onclick="p13selectallfile()" value="SelectAll" onkeypress="return false" onkeydown="return false" />
1029 <input type=button style="width:calc(100%/5 - 5px)" id=p13RenameFileButton disabled="disabled" value="Rename" onclick="p13renamefile()" onkeypress="return false" onkeydown="return false" />
1030 <input type=button style="width:calc(100%/5 - 5px)" id=p13DeleteFileButton disabled="disabled" value="Delete" onclick="p13deletefile()" onkeypress="return false" onkeydown="return false" />
1031 <input type=button style="width:calc(100%/5 - 5px)" id=p13NewFolderButton disabled="disabled" value="Folder" onclick="p13createfolder()" onkeypress="return false" onkeydown="return false" />
1032 </div>
1033 <div style="width:100%;text-align:center">
1034 <input type=button style="width:calc(100%/5 - 5px)" id=p13UploadButton disabled="disabled" value="Upload" onclick="p13uploadFile()" onkeypress="return false" onkeydown="return false" />
1035 <input type=button style="width:calc(100%/5 - 5px)" id=p13CutButton disabled="disabled" value="Cut" onclick="p13copyFile(1)" onkeypress="return false" onkeydown="return false" />
1036 <input type=button style="width:calc(100%/5 - 5px)" id=p13CopyButton disabled="disabled" value="Copy" onclick="p13copyFile(0)" onkeypress="return false" onkeydown="return false" />
1037 <input type=button style="width:calc(100%/5 - 5px)" id=p13PasteButton disabled="disabled" value="Paste" onclick="p13pasteFile()" onkeypress="return false" onkeydown="return false" />
1038 <input type=button style="width:calc(100%/5 - 5px)" id=p13RefreshButton disabled="disabled" value="Refresh" onclick="p13folderup(9999)" onkeypress="return false" onkeydown="return false" />
1039 </div>
1040 </td>
1041 </tr>
1042 <tr>
1043 <td style="background-color:#E4E9E7;height:28px">
1044 <table style="width:100%">
1045 <tr>
1046 <td id=p13currentpath style="overflow:hidden;padding-left:4px;padding-top:2px;color:black"></td>
1047 <td style="text-align:right;padding-right:4px">
1048 <select id=p13sortdropdown onchange=p13updateFiles()>
1049 <option value=1 selected="selected">Sort by name</option>
1050 <option value=2>Sort by size</option>
1051 <option value=3>Sort by date</option>
1052 <option value=4>Descend by name</option>
1053 <option value=5>Descend by size</option>
1054 <option value=6>Descend by date</option>
1055 </select>
1056 </td>
1057 </tr>
1058 </table>
1059 </td>
1060 </tr>
1061 </table>
1062 <div id=p13FilesConsoleMsg style="display:none;cursor:pointer;position:absolute;left:30px;top:165px;color:yellow;background-color:rgba(0,0,0,0.6);padding:10px;border-radius:5px" onclick=p13clearConsoleMsg()></div>
1063 <div id="p13filetable" style="width:100%;height:calc(100% - 133px);overflow:auto;-webkit-user-select:none">
1064 <!--
1065 <div id="p13bigok" style="width:256px;overflow:hidden;position:absolute;left:337px;top:200px;text-align:center;font-size:1600%;color:#AAAAAA;display:none"><b>&checkmark;</b></div>
1066 <div id="p13bigfail" style="width:256px;overflow:hidden;position:absolute;left:337px;top:200px;text-align:center;font-size:1600%;color:#AAAAAA;display:none"><b>&#10007;</b></div>
1067 -->
1068 <span id="p13files"></span>
1069 </div>
1070 <table id="p13toolbarBottom" style="width:100%;height:22px;position:absolute;bottom:0px" cellpadding=0 cellspacing=0>
1071 <tr><td style="text-align:left;padding:3px;text-align:center;background-color:#D3D9D6;color:black">&nbsp;<span id="p13bottomstatus"></span></td></tr>
1072 </table>
1073 </div>
1074 <div id=p10details style="overflow-y:scroll;position:absolute;top:55px;bottom:0px;width:100%">
1075 <div id=p10detailshtml style="margin-left:-3px"></div>
1076 </div>
1077 <div id=p10console style="overflow:hidden;position:absolute;top:55px;bottom:0px;width:100%">
1078 <table id="consoleTable" cellpadding=0 cellspacing=0>
1079 <tr style="height:28px">
1080 <td class="areaHead">
1081 <div class="toright2">
1082 <div id=p15coreName></div>
1083 <input type=button id=p15uploadCore value="Agent Action" onclick=p15uploadCore(event) />
1084 </div>
1085 <div id="p15statetext"></div>
1086 </td>
1087 </tr>
1088 <tr>
1089 <td id=p15agentConsole style="position:relative">
1090 <pre id=p15agentConsoleText></pre>
1091 </td>
1092 </tr>
1093 <tr style="height:28px">
1094 <td class="areaFoot">
1095 <table style="width:100%">
1096 <tr>
1097 <td style="width:99%">
1098 <input id=p15consoleText style=width:100%;box-sizing:border-box onkeyup=p15consoleSend(event) />
1099 </td>
1100 <td id="p15outputselecttd">
1101 <select id=p15outputselect onchange="setupConsole()">
1102 <option id="p15outputselect1" value=1>Agent</option>
1103 <option id="p15outputselect3" value=3>Push</option>
1104 <option id="p15outputselect2" value=2>MQTT</option>
1105 </select>
1106 </td>
1107 <td style="width:1%"><input id="id_p15consoleClear" type="button" class="bottombutton" value="Clear" onclick="p15consoleClear()" /></td>
1108 </tr>
1109 </table>
1110 </td>
1111 </tr>
1112 </table>
1113 </div>
1114 </div>
1115 <div id=p20 style="display:none;position:absolute;bottom:0;top:0;width:100%">
1116 <table cellspacing=0 style="margin:0;padding:0;border-spacing:0;border:0;position:absolute;top:0">
1117 <tr style=padding:0>
1118 <td style="padding:0;color:#c8c8c8;text-align:center;cursor:pointer" width=60px valign=top onclick=goBack()>
1119 <div style="padding:0;background-color:#036;width:10px;height:10px;float:right;border:0">
1120 <div class="menucurve"></div>
1121 </div>
1122 <div style="padding:0;font-size:25px;background-color:#036;width:50px;border-radius:0 0 10px 0;height:36px">&#9664;</div>
1123 </td>
1124 <td onclick="p20editmesh(1)">
1125 <img src="/images/meshicon50.png" width=50 height=50 />
1126 </td>
1127 <td onclick="p20editmesh(1)">
1128 <div style=margin-left:5px>
1129 <strong style="font-size:large"><span id=p20meshName></span></strong><br />
1130 </div>
1131 </td>
1132 </tr>
1133 </table>
1134 <div style="overflow-y:auto;position:absolute;top:55px;bottom:0px;left:0px;right:0px">
1135 <div id=p20info style="margin-left:8px;margin-right:8px"></div>
1136 </div>
1137 </div>
1138 </div>
1139 </div>
1140 <div id=footer style="height:32px;width:100%;text-align:center;background-color:#113962;position:absolute;bottom:0px">
1141 <table id=footerMenu cellpadding=0 cellspacing=0 style="height:32px;width:100%;color:white;cursor:pointer;table-layout:fixed"></table>
1142 </div>
1143 </div>
1144 <div id=dialog style="display:none">
1145 <div style="width:100%;background-color:#003366;color:#FFF;border-radius:5px 5px 0 0">
1146 <div id=id_dialogclose style=float:right;padding:5px;cursor:pointer onclick=setDialogMode()><b>X</b></div>
1147 <div id=id_dialogtitle style=padding:5px></div>
1148 <div style=width:100%;margin:6px></div>
1149 </div>
1150 <div style="margin-right:16px;margin-left:8px">
1151 <div id=dialog1 style="margin:auto;text-align:center;margin:3px">
1152 <div id=id_dialogMessage style="padding:10px"></div>
1153 </div>
1154 <div id=dialog2 style="margin:auto;margin:3px">
1155 <div id=id_dialogOptions></div>
1156 </div>
1157 <div id=dialog3 style="margin:auto;margin:3px">
1158 <select id="deskkeys" style="width:100%">
1159 <option value=10>Ctrl+Alt+Del</option>
1160 <option value=11>Tab</option>
1161 <option value=5>Win</option>
1162 <option value=0>Win+Down</option>
1163 <option value=1>Win+Up</option>
1164 <option value=2>Win+L</option>
1165 <option value=3>Win+M</option>
1166 <option value=4>Shift+Win+M</option>
1167 <option value=6>Win+R</option>
1168 <option value=7>Alt-F4</option>
1169 <option value=8>Ctrl-W</option>
1170 <option value=9>Alt-Tab</option>
1171 <option value=12>Shift-F10</option>
1172 </select>
1173 </div>
1174 <div id=dialog4 style="margin:auto;margin:3px">
1175 <div id=d3upload>
1176 <div>File Selection</div>
1177 <select id=d3uploadMode onchange=d3modechange()>
1178 <option value=1>Local file upload</option>
1179 <option value=2>Server file selection</option>
1180 </select>
1181 </div>
1182 <div id=d3localmode style="display:none">
1183 <div>Upload File</div>
1184 <form id=d3localmodeform method=post enctype=multipart/form-data action=uploadfile.ashx target=fileUploadFrame>
1185 <input type=text id=d3auth name=auth style="display:none" />
1186 <input type=text id=d3filter name=filter style="display:none" />
1187 <input type=text id=d3attrib name=attrib style="display:none" />
1188 <input type=file id=d3localFile name=files onchange=d3setActions() />
1189 <input type=submit id=d3submit style="display:none" />
1190 </form>
1191 </div>
1192 <div id=d3servermode>
1193 <div id=d3serveraction valign=bottom>
1194 <input type=button id=p3FolderUp disabled="disabled" onclick=d3folderup() value="Up" />&nbsp;<span id=p3CurrentFolder></span>
1195 </div>
1196 <div id=d3serverfiles></div>
1197 </div>
1198 </div>
1199 <div id=dialog7 style="margin:auto;margin:3px">
1200 <div id="d7meshkvm">
1201 <h4 style="width:100%;border-bottom:1px solid gray">Agent Remote Desktop</h4>
1202 <table style="width:100%">
1203 <tr>
1204 <td>
1205 Quality
1206 </td>
1207 <td style="width:100px">
1208 <select id="d7bitmapquality" style="float:right;width:200px" dir="rtl"></select>
1209 </td>
1210 </tr>
1211 <tr>
1212 <td>
1213 Scaling
1214 </td>
1215 <td style="width:100px">
1216 <select id="d7bitmapscaling" style="float:right;width:200px" dir="rtl">
1217 <option selected=selected value=1024>100%</option>
1218 <option value=896>87.5%</option>
1219 <option value=768>75%</option>
1220 <option value=640>62.5%</option>
1221 <option value=512>50%</option>
1222 <option value=384>37.5%</option>
1223 <option value=256>25%</option>
1224 <option value=128>12.5%</option>
1225 </select>
1226 </td>
1227 </tr>
1228 <tr>
1229 <td>
1230 Rate
1231 </td>
1232 <td style="width:100px">
1233 <select id="d7framelimiter" style="float:right;width:200px" dir="rtl">
1234 <option selected=selected value=50>Fast</option>
1235 <option value=100>Medium</option>
1236 <option value=400>Slow</option>
1237 <option value=1000>Very slow</option>
1238 </select>
1239 </td>
1240 </tr>
1241 <tr>
1242 <td>
1243 Encoding
1244 </td>
1245 <td style="width:100px">
1246 <select id="d7encoding" style="float:right;width:200px" dir="rtl">
1247 <option value=1>JPEG</option>
1248 <option value=2>PNG</option>
1249 <option value=3>TIFF</option>
1250 <option selected=selected value=4>WEBP</option>
1251 </select>
1252 </td>
1253 </tr>
1254 <tr>
1255 <td></td>
1256 <td>
1257 <label style="display:block" id="d7deskAutoLockLabel"><input type="checkbox" id="d7deskAutoLock" />Lock on Disconnect</label>
1258 </td>
1259 </tr>
1260 </table>
1261 </div>
1262 <div id="d7amtkvm">
1263 <h4 style="width:100%;border-bottom:1px solid gray">Intel&reg; AMT Hardware KVM</h4>
1264 <table style="width:100%">
1265 <tr>
1266 <td>Encoding</td>
1267 <td style="width:100px">
1268 <select id="d7desktopmode" style="float:right;width:200px">
1269 <option value="1">RLE8, Fastest</option>
1270 <option value="2">RLE16, Recommended</option>
1271 <option value="3">RAW8, Slow</option>
1272 <option value="4">RAW16, Very Slow</option>
1273 </select>
1274 </td>
1275 </tr>
1276 </table>
1277 </div>
1278 </div>
1279 </div>
1280 <div id="idx_dlgButtonBar" style="padding:10px;margin-bottom:20px">
1281 <input id="idx_dlgCancelButton" type="button" value="Cancel" style="float:right;width:80px;margin-left:5px" onclick="dialogclose(0)">
1282 <input id="idx_dlgOkButton" type="button" value="OK" style="float:right;width:80px" onclick="dialogclose(1)">
1283 <div><input id="idx_dlgDeleteButton" type="button" value="Delete" style="display:none" onclick="dialogclose(2)" /></div>
1284 </div>
1285 </div>
1286 <div id=topMenu style="z-index:1000;background-color:#EEE;box-shadow:0px 0px 15px #666;font-family:Arial,Helvetica,sans-serif;border-radius:0px 0px 5px 5px;position:fixed;top:50px;right:5px;width:170px;display:none">
1287 <div style="padding:12px;border-top:1px solid gray;color:black;cursor:pointer" onclick=topMenu(2)>My Files</div>
1288 <div style="padding:12px;border-top:1px solid gray;color:black;cursor:pointer" onclick=topMenu(1)>My Account</div>
1289 <div id="logoutMenuOption"><a id="logoutMenuOptionRef" href=/logout><div style="padding:12px;border-top:1px solid gray;color:black;cursor:pointer">Logout</div></a></div>
1290 </div>
1291 <audio id="chimes"><source src="sounds/chimes.mp3" type="audio/mp3" /></audio>
1292 <iframe name="fileUploadFrame" style=display:none></iframe>
1293 <script>
1294 'use strict';
1295 var random = '{{{randomlength}}}' // Random length string for BREACH mitigation
1296
1297 // Process server-side web state
1298 var webState = '{{{webstate}}}';
1299 if (webState != '') { webState = JSON.parse(decodeURIComponent(webState)); }
1300 for (var i in webState) { try { localStorage.setItem(i, webState[i]); } catch (ex) { } }
1301 if (webState && !webState.loctag) { try { delete localStorage.removeItem('loctag'); } catch (ex) { } }
1302
1303 // Fetch URL arguments & do sanitation
1304 var urlargs = parseUriArgs();
1305 if (urlargs.key != null) { urlargs.key = "" + urlargs.key; }
1306 if (urlargs.key && (isAlphaNumeric(urlargs.key) == false)) { delete urlargs.key; }
1307 if (urlargs.locale && (isAlphaNumeric(urlargs.locale) == false)) { delete urlargs.locale; }
1308 delete urlargs.user;
1309 delete urlargs.pass;
1310 delete urlargs.viewmode;
1311 delete urlargs.gotonode;
1312 delete urlargs.gotodevicename;
1313 delete urlargs.gotodevicername;
1314 delete urlargs.gotodeviceip;
1315 delete urlargs.gotomesh;
1316 delete urlargs.panel;
1317
1318 // Check if we are in debug mode
1319 var args = parseUriArgs();
1320 if (args.key && (isAlphaNumeric(args.key) == false)) { delete args.key; }
1321 if (args.locale && (isAlphaNumeric(args.locale) == false)) { delete args.locale; }
1322
1323 var debugLevel = parseInt('{{{debuglevel}}}');
1324 var features = parseInt('{{{features}}}');
1325 var features2 = parseInt('{{{features2}}}');
1326 var features3 = parseInt('{{{features3}}}');
1327 var sessionTime = parseInt('{{{sessiontime}}}');
1328 var sessionRefreshTimer = null;
1329 var domain = '{{{domain}}}';
1330 var domainUrl = '{{{domainurl}}}';
1331 var authCookie = '{{{authCookie}}}';
1332 var authRelayCookie = '{{{authRelayCookie}}}';
1333 var logoutControls = JSON.parse(decodeURIComponent('{{{logoutControls}}}'));
1334 var authCookieRenewTimer = null;
1335 var webRelayPort = parseInt('{{{webRelayPort}}}');
1336 var hidePowerTimeline = '{{{hidePowerTimeline}}}';
1337 var webRelayDns = '{{{webRelayDns}}}';
1338 var meshserver = null;
1339 var xdr = null;
1340 var usergroups = null;
1341 var stars = {}; // Devices that have been "stared" by the user.
1342 var serverinfo = null;
1343 var nodes = [];
1344 var meshes = {};
1345 var filetree = {};
1346 var userinfo = null;
1347 var serverinfo = null;
1348 var users = null;
1349 var nodeShortIdent = 0;
1350 var serverPublicNamePort = '{{{serverDnsName}}}:{{{serverPublicPort}}}';
1351 var debugmode = false;
1352 var attemptWebRTC = ((features & 128) != 0);
1353 var webrtcconfiguration = '{{{webrtcconfig}}}';
1354 if (webrtcconfiguration == '') { webrtcconfiguration = null; } else { try { webrtcconfiguration = JSON.parse(decodeURIComponent(webrtcconfiguration)); } catch (ex) { console.log('Invalid WebRTC config: "' + webrtcconfiguration + '".'); webrtcconfiguration = null; } }
1355 var StatusStrs = ["Disconnected", "Connecting...", "Setup...", "Connected", "Intel&reg; AMT Connected"];
1356 var agentsStr = ["Unknown", "Windows 32bit console", "Windows 64bit console", "Windows 32bit service", "Windows 64bit service", "Linux 32bit", "Linux 64bit", "MIPS", "XENx86", "Android", "Linux ARM", "macOS x86-32bit", "Android x86", "PogoPlug ARM", "Android", "Linux Poky x86-32bit", "macOS x86-64bit", "ChromeOS", "Linux Poky x86-64bit", "Linux NoKVM x86-32bit", "Linux NoKVM x86-64bit", "Windows MinCore console", "Windows MinCore service", "NodeJS", "ARM-Linaro", "ARMv6l / ARMv7l", "ARMv8 64bit", "ARMv6l / ARMv7l / NoKVM", "MIPS24KC (OpenWRT)", "Apple Silicon", "FreeBSD x86-64", "Unknown", "Linux ARM 64 bit", "Alpine Linux x86 64 Bit (MUSL)", "Assistant (Windows)", "Armada370 - ARM32/HF (libc/2.26)", "OpenWRT x86-64", "OpenBSD x86-64", "Unknown", "Unknown", "MIPSEL24KC (OpenWRT)", "ARMADA/CORTEX-A53/MUSL (OpenWRT)", "Windows ARM 64bit console", "Windows ARM 64bit service", "ARMVIRT32 (OpenWRT)", "RISC-V x86-64"];
1357 var domainStates = ["None", "Azure AD", "On-Premises AD", "Hybrid AD", "Microsoft Account"];
1358 var files;
1359 var terminal;
1360 var passRequirements = '{{{passRequirements}}}';
1361 if (passRequirements != '') { passRequirements = JSON.parse(decodeURIComponent(passRequirements)); }
1362 var sessionActivity = Date.now();
1363 var deskPinchZoom;
1364 var deskKeyboardShortcuts = [];
1365 var nightMode = setNightMode();
1366 var xterm = null;
1367 var xtermfit = null;
1368 var xtermimage = null;
1369 var xtermResizeTimer = null;
1370 var devicePagingState = null;
1371
1372 // Console Message Display Timers
1373 var p11DeskConsoleMsgTimer = null;
1374 var p12TermConsoleMsgTimer = null;
1375 var p13FilesConsoleMsgTimer = null;
1376
1377 // Check if WebP is supported
1378 var webpSupport = false;
1379 check_webp_feature('lossy', function (f, x) {
1380 webpSupport = x;
1381 if (!x) {
1382 d7encoding.options[1].disabled = true;
1383 d7encoding.value = 1;
1384 }
1385 });
1386
1387 function startup() {
1388 if ((features & 32) == 0) {
1389 // Guard against other site's top frames (web bugs).
1390 var loc = null;
1391 try { loc = top.location.toString().toLowerCase(); } catch (e) { }
1392 if (top != self && (loc == null || top.active == false)) { top.location = self.location; return; }
1393 }
1394
1395 if (!args.locale) { var x = getstore('loctag', 0); if ((x != null) && (x != '*')) { args.locale = x; } }
1396
1397 window.onresize = center;
1398 center();
1399 QV('changeEmailId', (features & 0x200000) == 0);
1400 QH('p1message', "Connecting...");
1401 go(1);
1402
1403 // Document keys
1404 document.onkeypress = ondeskkeypress;
1405 document.onkeydown = ondeskkeydown;
1406 document.onkeyup = ondeskkeyup;
1407 document.onclick = function (e) { if ((xxdialogMode == 999) && (e.target.id != 'topMenuIcon')) { QV('topMenu', false); xxdialogMode = 0; } }
1408
1409 // Connect to the mesh server
1410 meshserver = MeshServerCreateControl(domainUrl);
1411 meshserver.onStateChanged = onStateChanged;
1412 meshserver.onMessage = onMessage;
1413 meshserver.trace = args.trace;
1414 meshserver.Start();
1415
1416 // Setup stared devices
1417 try { stars = JSON.parse(getstore('stars', '{}')); } catch (ex) { }
1418
1419 // Setup logout control
1420 if (logoutControls && logoutControls.logoutUrl) { Q('logoutMenuOptionRef').href = logoutControls.logoutUrl; }
1421
1422 // Load desktop settings
1423 var t = localStorage.getItem('desktopsettings');
1424 if (t != null) { desktopsettings = JSON.parse(t); }
1425 applyDesktopSettings();
1426
1427 //attemptWebRTC = false; // For now, default WebRTC off unless we set it in the URL.
1428 if (args.webrtc != null) { attemptWebRTC = (args.webrtc == 1); }
1429
1430 // Session Refresh Timer
1431 if (sessionTime >= 10) { sessionRefreshTimer = setTimeout(refreshCookieSession, Math.round((sessionTime * 60000) * 0.8)); }
1432
1433 // Hide night mode button if needed
1434 QV('setDarkModeLink', (features2 & 0x00300000) == 0);
1435
1436 // Set the user's desktop shortcut keys
1437 deskKeyboardShortcuts = [];
1438 var deskKeyboardShortcutsStr = getstore('deskKeyShortcuts', '0x0A002E,0x100000,0x100028,0x100026,0x10004C,0x10004D,0x11004D,0x100052,0x020073,0x080057,0x020009,0x100025,0x100027').split(',');
1439 for (var i in deskKeyboardShortcutsStr) { deskKeyboardShortcuts.push(parseInt(deskKeyboardShortcutsStr[i])); }
1440 updateDeskShortcutKeys();
1441 updateTermShortcutKeys();
1442 }
1443
1444 function refreshCookieSession() {
1445 var xdr = null;
1446 try { xdr = new XDomainRequest(); } catch (e) { }
1447 if (!xdr) xdr = new XMLHttpRequest();
1448 xdr.open('GET', window.location.origin + domainUrl + 'refresh.ashx');
1449 xdr.timeout = 15000;
1450 xdr.onload = function () { sessionRefreshTimer = setTimeout(refreshCookieSession, Math.round((sessionTime * 60000) * 0.8)); };
1451 xdr.onerror = xdr.ontimeout = function () { sessionRefreshTimer = null; };
1452 xdr.send();
1453 }
1454
1455 function onStateChanged(server, state, prevState, errorCode) {
1456 if (state == 0) {
1457 // Control web socket disconnected
1458 setDialogMode(0); // Close any dialog boxes if present
1459 go(0); // Go to disconnection panel
1460 deleteAllNotifications(); // Close and clear notifications if present
1461 if (errorCode == 'noauth') { QH('p0span', "Unable to perform authentication"); return; }
1462 if (prevState == 2) { setTimeout(serverPoll, 5000); } else { QH('p0span', "Unable to connect web socket"); }
1463 // Clean up here
1464 if (authCookieRenewTimer != null) { clearInterval(authCookieRenewTimer); authCookieRenewTimer = null; }
1465 devicePagingState = null;
1466 updateDevicePageState();
1467 } else if (state == 2) {
1468 // Fetch list of meshes, nodes, files
1469 meshserver.send({ action: 'usergroups' });
1470 meshserver.send({ action: 'meshes' });
1471 meshserver.send({ action: 'nodes', skip: (devicePagingState == null) ? 0 : devicePagingState.skip });
1472 meshserver.send({ action: 'files' });
1473 authCookieRenewTimer = setInterval(function () { meshserver.send({ action: 'authcookie' }); }, 1800000); // Request a cookie refresh every 30 minutes.
1474 }
1475 QV('topMenuIcon', state == 2);
1476 }
1477
1478 // Poll the server, if it responds, refresh the page.
1479 function serverPoll() {
1480 xdr = null;
1481 try { xdr = new XDomainRequest(); } catch (e) { }
1482 if (!xdr) xdr = new XMLHttpRequest();
1483 xdr.open('HEAD', window.location.href);
1484 xdr.timeout = 15000;
1485 // Make sure there isn't a reverse proxy in front that may just be returning 5xx codes
1486 // Status code 4xx should still be allowed, since a page could potentially be removed, etc
1487 xdr.onload = function () { if (xdr.status < 500) reload(); else setTimeout(serverPoll, 10000); };
1488 xdr.onerror = xdr.ontimeout = function () { setTimeout(serverPoll, 10000); };
1489 xdr.send();
1490 }
1491
1492 function updateSelf() {
1493 var accountSettingsLocked = ((features2 & 0x100) != 0);
1494 if (userinfo) { accountSettingsLocked = ((userinfo.siteadmin != 0xFFFFFFFF) && ((userinfo.siteadmin & 1024) != 0)) || ((features2 & 0x100) != 0); } // Not admin and have account features locked, or using a loginToken
1495 QV('p3AccountActions', ((features & 4) == 0) && (serverinfo.domainauth == false) && (accountSettingsLocked == false)); // Hide Account Actions if in single user mode or domain authentication
1496 QV('logoutMenuOption', ((features & 4) == 0) && (serverinfo.domainauth == false)); // Hide logout if in single user mode or domain authentication
1497 QV('p2AccountSecurity', ((features & 4) == 0) && (serverinfo.domainauth == false) && ((features & 4096) != 0) && (accountSettingsLocked == false)); // Hide Account Security if in single user mode or domain authentication, 2 factor auth not supported.
1498 QV('p2AccountImage', !accountSettingsLocked);
1499 QV('verifyEmailId', (userinfo.emailVerified !== true) && (userinfo.email != null) && (serverinfo.emailcheck == true));
1500 QV('manageAuthApp', (serverinfo.lock2factor != true) && (features & 4096) && ((userinfo.otpsecret == 1) || ((features2 & 0x00020000) == 0)));
1501 QV('manageOtp', (serverinfo.lock2factor != true) && ((features2 & 0x40000) == 0) && (features & 4096) && ((userinfo.otpsecret == 1) || (userinfo.otphkeys > 0)));
1502 QV('authPhoneNumberCheck', (userinfo.phone != null));
1503 QV('authEmailSetupCheck', (userinfo.otpekey == 1) && (userinfo.email != null) && (userinfo.emailVerified == true));
1504 QV('authAppSetupCheck', userinfo.otpsecret == 1);
1505 //QV('authKeySetupCheck', userinfo.otphkeys > 0);
1506 QV('authCodesSetupCheck', userinfo.otpkeys > 0);
1507 QV('p2AccountActions', ((features & 4) == 0) && (serverinfo.domainauth == false) && (userinfo != null));
1508 QV('p2AccountPassActions', ((features & 4) == 0) && (serverinfo.domainauth == false) && (userinfo != null) && (userinfo._id.split('/')[2].startsWith('~') == false)); // Hide Account Actions if in single user mode or domain authentication
1509
1510 // On the mobile app, don't allow group creation (for now).
1511 QV('p3createMeshLink1', false);
1512 QV('p3createMeshLink2', false);
1513
1514 // Update user image
1515 if ((userinfo.flags != null) && (userinfo.flags & 1)) {
1516 if (userinfo.accountImageRnd == null) { userinfo.accountImageRnd = Math.floor(Math.random() * 9999999999); }
1517 Q('p2AccountImage').src = 'userimage.ashx?rnd=' + userinfo.accountImageRnd;
1518 } else {
1519 Q('p2AccountImage').src = 'images/user-256.png';
1520 }
1521
1522 if (typeof userinfo.passchange == 'number') {
1523 if (userinfo.passchange == -1) { QH('p2nextPasswordUpdateTime', " - Reset on next login."); }
1524 else if ((passRequirements != null) && (typeof passRequirements.reset == 'number')) {
1525 var seconds = (userinfo.passchange) + (passRequirements.reset * 86400) - Math.floor(Date.now() / 1000);
1526 if (seconds < 0) { QH('p2nextPasswordUpdateTime', " - Reset on next login."); }
1527 else if (seconds < 3600) { var secs = Math.floor(seconds / 60); QH('p2nextPasswordUpdateTime',format((secs == 1)?" - Reset in 1 minute.":" - Reset in {0} minutes.", secs)); }
1528 else if (seconds < 86400) { var hours = Math.floor(seconds / 3600); QH('p2nextPasswordUpdateTime', format((hours == 1) ? " - Reset in 1 hour." : " - Reset in {0} hours.", hours)); }
1529 else { var days = Math.floor(seconds / 86400); QH('p2nextPasswordUpdateTime', format((hours == 1) ? " - Reset in 1 day." : " - Reset in {0} days.", days)); }
1530 }
1531 }
1532 }
1533
1534 function setSessionActivity() { sessionActivity = Date.now(); }
1535 function checkIdleSessionTimeout() {
1536 var delta = (Date.now() - sessionActivity);
1537 if (delta > serverinfo.timeout) {
1538 if (desktop != null) { // Disconnect remote desktop
1539 desktop.Stop();
1540 desktopNode = desktop = null;
1541 }
1542 if (terminal != null) { // Disconnect terminal
1543 terminal.Stop();
1544 terminal = null;
1545 }
1546 if (files != null) { // Disconnect files
1547 files.Stop();
1548 files = null;
1549 }
1550 if (serverinfo.logoutonidlesessiontimeout) {
1551 if (urlargs.key) { window.location.href = 'logout?key=' + urlargs.key; }
1552 else { window.location.href = 'logout'; }
1553 }
1554 }
1555 }
1556
1557 function onMessage(server, message) {
1558 switch (message.action) {
1559 case 'serverinfo': {
1560 serverinfo = message.serverinfo;
1561 if (serverinfo.timeout) { setInterval(checkIdleSessionTimeout, 10000); checkIdleSessionTimeout(); }
1562 if (userinfo != null) updateSelf();
1563 if (serverinfo.certExpire != null) {
1564 var days = Math.floor((serverinfo.certExpire - Date.now()) / 86400000);
1565 if ((days >= 0) && (days < 20)) {
1566 addNotification({ text: format("Certificate expires in {0} day(s)", days) });
1567 }
1568 }
1569
1570 // Arrange the user interface
1571 QV('manageEmail2FA', (features & 0x00800000) && (serverinfo.lock2factor != true));
1572 QV('managePhoneNumber1', (features & 0x02000000) && (features & 0x04000000) && (serverinfo.lock2factor != true));
1573 QV('managePhoneNumber2', (features & 0x02000000) && !(features & 0x04000000) && (serverinfo.lock2factor != true));
1574
1575 break;
1576 }
1577 case 'authcookie': {
1578 // Got an authentication cookie refresh
1579 authCookie = message.cookie;
1580 authRelayCookie = message.rcookie;
1581 break;
1582 }
1583 case 'userinfo': {
1584 userinfo = message.userinfo;
1585 QH('p3userName', userinfo.name);
1586 //updateSiteAdmin();
1587 if (serverinfo != null) updateSelf();
1588 break;
1589 }
1590 case 'users': {
1591 users = {};
1592 for (var m in message.users) { users[message.users[m]._id] = message.users[m]; }
1593 if (currentUser != null) { currentUser = users[currentUser._id]; }
1594 updateUsers();
1595 break;
1596 }
1597 case 'wssessioncount': {
1598 wssessions = message.wssessions;
1599 updateUsers();
1600 break;
1601 }
1602 case 'meshes': {
1603 meshes = {};
1604 for (var m in message.meshes) { meshes[message.meshes[m]._id] = message.meshes[m]; }
1605 if (currentMesh != null) { currentMesh = meshes[currentMesh._id]; }
1606 updateMeshes();
1607 mainUpdate(4);
1608 break;
1609 }
1610 case 'usergroups': {
1611 var groupCount = 0;
1612 if (Array.isArray(message.ugroups)) {
1613 usergroups = {};
1614 for (var i in message.ugroups) { groupCount++; usergroups[message.ugroups[i]._id] = message.ugroups[i]; }
1615 if (groupCount == 0) { usergroups = null; }
1616 } else {
1617 usergroups = message.ugroups;
1618 for (var i in message.ugroups) { groupCount++; }
1619 if (groupCount == 0) { usergroups = null; }
1620 }
1621 //mainUpdate(8192);
1622 break;
1623 }
1624 case 'files': {
1625 filetree = setupBackPointers(message.filetree);
1626 updateFiles();
1627 //d3updatefiles();
1628 break;
1629 }
1630 case 'nodes': {
1631 nodes = [];
1632 for (var m in message.nodes) {
1633 for (var n in message.nodes[m]) {
1634 message.nodes[m][n].namel = message.nodes[m][n].name.toLowerCase();
1635 if (message.nodes[m][n].rname) { message.nodes[m][n].rnamel = message.nodes[m][n].rname.toLowerCase(); } else { message.nodes[m][n].rnamel = message.nodes[m][n].namel; }
1636 message.nodes[m][n].meshnamel = meshes[m]?meshes[m].name.toLowerCase():'*';
1637 message.nodes[m][n].meshid = m;
1638 message.nodes[m][n].state = (message.nodes[m][n].state) ? (message.nodes[m][n].state) : 0;
1639 message.nodes[m][n].desc = message.nodes[m][n].desc;
1640 if (!message.nodes[m][n].icon) message.nodes[m][n].icon = 1;
1641 message.nodes[m][n].ident = ++nodeShortIdent;
1642 nodes.push(message.nodes[m][n]);
1643 }
1644 }
1645
1646 // If we are currently looking at a node this is now gone, change the view.
1647 if ((currentNode != null) && (IsNodeViewable(currentNode) == false)) { currentNode = null; go(2); }
1648
1649 // Change the reference to the current node
1650 if (currentNode != null) { currentNode = getNodeFromId(currentNode._id); if (currentNode != null) { gotoDevice(currentNode._id, xxcurrentView, true); } else { go(2); } }
1651
1652 // Update device paging
1653 devicePagingState = (message.totalcount == null) ? null : { total: message.totalcount, skip: message.skip, limit: message.limit };
1654 updateDevicePageState();
1655
1656 //onSortSelectChange();
1657 //onSearchInputChanged();
1658 mainUpdate(4);
1659 //refreshMap(false, true);
1660 if (xxcurrentView == 0) { if ('{{viewmode}}' != '') { go(parseInt('{{viewmode}}')); } else { setDialogMode(0); go(2); } }
1661 if ('{{currentNode}}' != '') { gotoDevice('{{currentNode}}', parseInt('{{viewmode}}')); }
1662 break;
1663 }
1664 case 'powertimeline': {
1665 if (message.nodeid != powerTimelineReq) break;
1666 powerTimelineNode = message.nodeid;
1667 powerTimeline = message.timeline;
1668 powerTimelineUpdate = Date.now() + 300000; // Update every 5 minutes
1669 for (var i in powerTimeline) { if (i % 2 == 1) { powerTimeline[i] = powerTimeline[i] * 1000; } } // Decompress time
1670 if (currentNode._id == message.nodeid) { drawDeviceTimeline(); }
1671 break;
1672 }
1673 case 'getsysinfo': {
1674 if (message.nodeid != powerTimelineReq) break;
1675 if (message.noinfo === true) {
1676 updateDeviceDetails(getNodeFromId(message.nodeid));
1677 } else {
1678 message.hardware.time = message.time;
1679 updateDeviceDetails(getNodeFromId(message.nodeid), message.hardware);
1680 }
1681 break;
1682 }
1683 case 'lastconnect': {
1684 var node = getNodeFromId(message.nodeid);
1685 if (node != null) {
1686 node.lastconnect = message.time;
1687 node.lastaddr = message.addr;
1688 }
1689 break;
1690 }
1691 case 'msg': {
1692 // Check if this is a message from a node
1693 if (message.nodeid != null) {
1694 var index = -1;
1695 if (nodes != null) { for (var i in nodes) { if (nodes[i]._id == message.nodeid) { index = i; break; } } }
1696 if (index != -1) {
1697 if (message.type == 'console') { p15consoleReceive(nodes[index], message.value, message.source); } // This is a console message.
1698 else if (message.type == 'notify') { // This is a notification message.
1699 var n = getstore('notifications', 0);
1700 if (((n & 8) == 0) && (message.amtMessage != null)) { break; } // Intel AMT desktop & terminal messages should be ignored.
1701 var n = { text: message.value, title: message.title, icon: message.icon, titleid: message.titleid, msgid: message.msgid, args: message.args };
1702 if (message.id != null) { n.id = message.id; }
1703 if (message.nodeid != null) { n.nodeid = message.nodeid; }
1704 if (message.tag != null) { n.tag = message.tag; }
1705 if (message.url != null) { n.url = message.url; }
1706 if (message.username != null) { n.username = message.username; }
1707 if (typeof message.maxtime == 'number') { n.maxtime = message.maxtime; }
1708 addNotification(n);
1709 } else if ((message.type == 'userSessions') && (currentNode != null) && (currentNode._id == message.nodeid) && (desktop == null)) {
1710 // Got list of user sessions
1711 var userSessions = [];
1712 if (message.data != null) { for (var i in message.data) { if ((message.data[i].State == 'Active') || (message.data[i].StationName == 'Console') || (debugmode == 3)) { userSessions.push(message.data[i]); } } }
1713 if (userSessions.length == 0) { connectDesktop(null, 1, null, message.tag); } // No active sessions, do a normal connection.
1714 else if (userSessions.length == 1) { connectDesktop(null, 1, userSessions[0].SessionId, message.tag); } // One active session, connect to it
1715 else {
1716 var x = '';
1717 var sortBy = "{{{userSessionsSort}}}";
1718 if (sortBy != '') {
1719 userSessions.sort(function(a, b) {
1720 if (!a[sortBy]) return -1; // a comes before b
1721 if (!b[sortBy]) return 1; // b comes before a
1722 if (a[sortBy] < b[sortBy]) return -1;
1723 if (a[sortBy] > b[sortBy]) return 1;
1724 return 0;
1725 });
1726 }
1727 for (var i in userSessions) {
1728 x += '<div style="text-align:left;cursor:pointer;background-color:gray;margin:5px;padding:5px;border-radius:5px" onclick=connectDesktop(event,1,' + userSessions[i].SessionId + ',' + message.tag + ')>' + userSessions[i].State + ', ' + userSessions[i].StationName;
1729 if (userSessions[i].Username) { if (userSessions[i].Domain) { x += ' - ' + userSessions[i].Domain + '/' + userSessions[i].Username; } else { x += ' - ' + userSessions[i].Username; } }
1730 x += '</div>';
1731 }
1732 QH('p11DeskSessionSelector', x);
1733 QV('p11DeskSessionSelector', true);
1734 }
1735 }
1736 }
1737 } else {
1738 if (message.type == 'notify') { // This is a notification message.
1739 var n = { text: message.value, title: message.title, icon: message.icon, titleid: message.titleid, msgid: message.msgid, args: message.args };
1740 if (message.id != null) { n.id = message.id; }
1741 if (message.tag != null) { n.tag = message.tag; }
1742 if (message.url != null) { n.url = message.url; }
1743 if (message.username != null) { n.username = message.username; }
1744 if (typeof message.maxtime == 'number') { n.maxtime = message.maxtime; }
1745 addNotification(n);
1746 }
1747 }
1748 break;
1749 }
1750 case 'getnetworkinfo': {
1751 if (currentNode._id != message.nodeid) return;
1752 updateDeviceDetails(getNodeFromId(message.nodeid), null, message);
1753 break;
1754 }
1755 case 'getNotes': {
1756 var n = Q('d2devNotes');
1757 if (n && (message.id == decodeURIComponent(n.attributes['noteid'].value))) {
1758 if (message.notes) { QH('d2devNotes', decodeURIComponent(message.notes)); } else { QH('d2devNotes', ''); }
1759 var ro = (n.attributes['ro'].value == 'true');
1760 if (ro == false) { // If we have permissions, set read/write on this note.
1761 n.removeAttribute('readonly');
1762 QE('idx_dlgOkButton', true);
1763 QV('idx_dlgOkButton', true);
1764 focusTextBox('d2devNotes');
1765 }
1766 }
1767 break;
1768 }
1769 case 'otpauth-request': {
1770 if ((xxdialogMode == 2) && (xxdialogTag == 'otpauth-request')) {
1771 if (message.err != null) {
1772 var otpauthErrors = ['', "2FA is locked", "Backup codes are locked", "Login token in use", "OTP 2FA not allowed", "Account is locked", "Unable to load OTPLIB"];
1773 if ((message.err > 0) && (message.err < otpauthErrors.length)) { QH('d2optinfo', otpauthErrors[message.err]); } else { QH('d2optinfo', format("Error #{0}", message.err)); }
1774 } else {
1775 var secret = message.secret;
1776 if (secret.length == 52) { secret = secret.split(/(.............)/).filter(Boolean).join(' '); }
1777 else if (secret.length == 32) { secret = secret.split(/(....)/).filter(Boolean).join(' '); secret = secret.substring(0, 20) + '<br/>' + secret.substring(20) }
1778 QH('d2optinfo', format("Install" + ' <a href="https://play.google.com/store/apps/details?id=com.google.android.apps.authenticator2" rel="noreferrer noopener" target=_blank>' + "Google Authenticator" + '</a> ' + "or a compatible application, use <a href=\"{0}\" rel=\"noreferrer noopener\" target=_blank> this link</a> or enter the secret below. Then, enter the current 6 digit token to activate 2-Step login.", message.url) + '<br /><br /><div style=width:100%;text-align:center><tt id=d2optsecret secret="' + message.secret + '" style=font-size:15px>' + secret + '</tt><br /><br />Token: <input type=text autocomplete="one-time-code" inputmode="numeric" pattern="[0-9]*" onkeypress=\"return (event.keyCode == 8) || (event.charCode >= 48 && event.charCode <= 57)\" onkeyup=account_addOtpCheck(event) onkeydown=account_addOtpCheck() maxlength=6 id=d2otpauthinput type=text></div>');
1779 QV('idx_dlgOkButton', true);
1780 QE('idx_dlgOkButton', false);
1781 Q('d2otpauthinput').focus();
1782 }
1783 }
1784 break;
1785 }
1786 case 'otpauth-setup': {
1787 if (xxdialogMode) return;
1788 setDialogMode(2, "Authenticator App", 1, null, message.success ? ('<b style=color:green>' + "Authenticator app activation successful." + '</b> ' + "You will now need a valid token to login again.") : ('<b style=color:red>' + "2-step login activation failed." + '</b> ' + "Clear the secret from the application and try again. You only have a few minutes to enter the proper code."));
1789 break;
1790 }
1791 case 'otpauth-clear': {
1792 if (xxdialogMode) return;
1793 setDialogMode(2, "Authenticator App", 1, null, message.success ? ('<b>' + "Authenticator application removed." + '</b> ' + "You can reactivate this feature at any time.") : ('<b style=color:red>' + "2-step login activation removal failed." + '</b> ' + "Try again."));
1794 break;
1795 }
1796 case 'otpauth-getpasswords': {
1797 if (xxdialogMode) return;
1798 var x = "One time tokens can be used as secondary authentication. Generate a set, print them and keep them in a safe place.";
1799 x += '<div style=\'border-radius:6px;border: 2px dashed #888;width:100%;margin-top:8px\'><div style=\'padding:8px;font-family:Arial, Helvetica, sans-serif;font-size:20px;font-weight:bold\'><table style=width:100%;text-align:center>';
1800 if (message.passwords) {
1801 var j = 0;
1802 for (var i in message.passwords) {
1803 if (++j % 2) { x += '<tr>'; }
1804 var p = '' + message.passwords[i].p;
1805 while (p.length < 8) { p = '0' + p; }
1806 if (message.passwords[i].u === true) { x += '<td>' + p.substring(0, 4) + '&nbsp;' + p.substring(4); } else { x += '<td><strike style=color:#BBB>' + p.substring(0, 4) + '&nbsp;' + p.substring(4); + '</strike>'; }
1807 }
1808 } else {
1809 x += '<tr><td>' + "No Active Tokens";
1810 }
1811 x += '</table></div></div><br />';
1812 x += '<div><input type=button value=\'' + "Close" + '\' onclick=setDialogMode(0) style=float:right></input>';
1813 x += '<input type=button value=\'' + "New Tokens" + '\' onclick=\'account_manageOtp(1);\'></input>';
1814 if (message.passwords != null) { x += '<input type=button value=\'' + "Clear" + '\' onclick=\'account_manageOtp(2);\'></input>'; }
1815 x += '</div><br />';
1816 setDialogMode(2, "Manage Backup Codes", 8, null, x, 'otpauth-manage');
1817 break;
1818 }
1819 case 'verifyPhone': {
1820 if (xxdialogMode && (xxdialogTag != 'verifyPhone')) return;
1821 var x = '<table><tr><td><img src="images/phone80.png" style=padding:8px>';
1822 x += '<td>Check your phone and enter the verification code.';
1823 x += '<br /><br /><div style=width:100%;text-align:center>' + "Verification code:" + ' <input type=tel pattern="[0-9]" inputmode="number" maxlength=6 id=d2phoneCodeInput onKeyUp=account_managePhoneCodeValidate() onkeypress="if (event.key==\'Enter\') account_managePhoneCodeValidate(1)"></div></table>';
1824 setDialogMode(2, "Phone Notifications", 3, account_managePhoneConfirm, x, message.cookie);
1825 Q('d2phoneCodeInput').focus();
1826 account_managePhoneCodeValidate();
1827 break;
1828 }
1829 case 'previousLogins': {
1830 if ((xxdialogMode == 2) && (xxdialogTag == 'previousLogins')) {
1831 var x = '', c = 'BBB', xx = '';
1832 if (message.events.length == 0) {
1833 x += 'No previous login.';
1834 } else {
1835 x += '<div style=max-height:260px;overflow-y:scroll;overflow-x:hidden>';
1836 for (var i in message.events) {
1837 var m = message.events[i].m;
1838 if (m == 107) { m = "Valid login"; c = 'BBD1BB'; xx = ''; }
1839 else if (m == 108) { m = "Invalid 2FA"; c = 'DD9DC3'; xx = 'x'; }
1840 else if (m == 109) { m = "Locked account"; c = 'E1BBBB'; xx = 'x'; }
1841 else if (m == 110) { m = "Invalid password"; c = 'E1BBBB'; xx = 'x'; }
1842 x += '<div style=width:260px;background-color:#' + c + ';border-radius:6px;margin-bottom:4px;padding:4px><div><b>' + EscapeHtml(m) + '</b><br />' + printDateTime(new Date(message.events[i].t)) + '</div><div style=font-size:x-small>' + EscapeHtml(message.events[i].a.join(', ')) + '</div></div></tr>';
1843 }
1844 x += '</div>';
1845 }
1846 setDialogMode(2, "Previous Logins", 1, null, x);
1847 }
1848 break;
1849 }
1850 case 'event': {
1851 /*
1852 if (!message.event.nolog) {
1853 events.unshift(message.event);
1854 var eventLimit = parseInt(p3limitdropdown.value);
1855 while (events.length > eventLimit) { events.pop(); } // Remove element(s) at the end
1856 events_update();
1857 }
1858 */
1859 if (message.event.noact) break; // Take no action on this event
1860 switch (message.event.action) {
1861 case 'serverinfochange': {
1862 if (message.event.lock2factor != null) { serverinfo.lock2factor = message.event.lock2factor; updateSelf(); }
1863 break;
1864 }
1865 case 'userWebState': {
1866 // New user web state, update the web page as needed
1867 if (localStorage != null) {
1868 var webstate = JSON.parse(message.event.state);
1869 for (var i in webstate) { localStorage.setItem(i, webstate[i]); }
1870
1871 // Update stars
1872 if (webstate.stars != null) { stars = JSON.parse(webstate.stars); }
1873
1874 // Update the web page
1875 if ((webstate.loctag != null) && (webstate.loctag != oldLoctag)) {
1876 if (webstate.loctag != null) { args.locale = webstate.loctag; } else { delete args.locale; }
1877 mainUpdate(4 + 128);
1878 } else if (webstate.stars != null) {
1879 mainUpdate(4);
1880 if (Q('SearchInput').value == '*') { onSearchInputChanged(); }
1881 }
1882 if (currentNode) { refreshDevice(currentNode._id); }
1883
1884 // Set the user's desktop shortcut keys
1885 if (webstate.deskKeyShortcuts != null) {
1886 deskKeyboardShortcuts = [];
1887 var deskKeyboardShortcutsStr = webstate.deskKeyShortcuts.split(',');
1888 for (var i in deskKeyboardShortcutsStr) { deskKeyboardShortcuts.push(parseInt(deskKeyboardShortcutsStr[i])); }
1889 updateDeskShortcutKeys();
1890 }
1891 }
1892 break;
1893 }
1894 case 'accountchange': {
1895 // An account was created or changed
1896 if ((typeof message.event.account != 'object') || (message.event.account == null)) { console.log(message.event); return; };
1897 if (userinfo.name == message.event.account.name) {
1898 var newsiteadmin = message.event.account.siteadmin ? message.event.account.siteadmin : 0;
1899 var oldsiteadmin = userinfo.siteadmin ? userinfo.siteadmin : 0;
1900 if ((message.event.account.quota != userinfo.quota) || (((userinfo.siteadmin & 8) == 0) && ((message.event.account.siteadmin & 8) != 0))) { meshserver.send({ action: 'files' }); }
1901 userinfo = message.event.account;
1902 //if (oldsiteadmin != newsiteadmin) updateSiteAdmin();
1903 updateSelf();
1904
1905 // If our list of nodes may have changes, request the new list now.
1906 if (message.event.nodeListChange == userinfo._id) { meshserver.send({ action: 'nodes', skip: (devicePagingState == null) ? 0 : devicePagingState.skip }); }
1907 }
1908 break;
1909 }
1910 case 'createusergroup':
1911 case 'usergroupchange': {
1912 // User group changed
1913 if (usergroups == null) { usergroups = {}; }
1914 var ugroup = usergroups[message.event.ugrpid];
1915 if (ugroup == null) {
1916 // This is a new user group for us
1917 usergroups[message.event.ugrpid] = { _id: message.event.ugrpid, name: message.event.name, desc: message.event.desc, domain: message.event.domain, links: message.event.links };
1918 } else {
1919 // This is an existing user group
1920 ugroup.name = message.event.name;
1921 ugroup.desc = message.event.desc;
1922 ugroup.links = message.event.links;
1923 ugroup.flags = message.event.flags;
1924 }
1925 //mainUpdate(8192 + 16384);
1926
1927 // Group update, refresh all our device groups and nodes. TODO: Optimize this to only do this when needed.
1928 meshserver.send({ action: 'meshes' });
1929 meshserver.send({ action: 'nodes', skip: (devicePagingState == null) ? 0 : devicePagingState.skip });
1930 break;
1931 }
1932 case 'deleteusergroup': {
1933 // User group removed
1934 if ((usergroups != null) && (usergroups[message.event.ugrpid] != null)) {
1935 delete usergroups[message.event.ugrpid];
1936 var c = 0;
1937 for (var i in usergroups) { c++; }
1938 if (c == 0) { usergroups = null; } // If user groups is empty, set it to null.
1939 //mainUpdate(8192 + 16384);
1940 }
1941 break;
1942 }
1943 case 'createmesh': {
1944 // A new mesh was created
1945 if ((meshes[message.event.meshid] == null) && ((userinfo.manageAllDeviceGroups) || (message.event.mesh.links[userinfo._id] != null))) { // Check if this is a mesh create for a mesh we own. If site administrator, we get all messages so need to ignore some.
1946 meshes[message.event.meshid] = message.event.mesh;
1947 mainUpdate(4 + 128);
1948 meshserver.send({ action: 'files' });
1949 }
1950 break;
1951 }
1952 case 'meshchange': {
1953 // Update mesh information
1954 if (meshes[message.event.meshid] == null) {
1955 // Check if we have any access to this device group
1956 var add = false;
1957 if (message.event.links[userinfo._id] != null) { add = true; }
1958 if (userinfo.links[message.event.meshid] != null) { add = true; }
1959 for (var i in userinfo.links) { if ((i.startsWith('ugrp/')) && (message.event.links[i] != null)) { add = true; } }
1960
1961 // This is a new mesh for us
1962 if (add) {
1963 meshes[message.event.meshid] = { _id: message.event.meshid, name: message.event.name, mtype: message.event.mtype, desc: message.event.desc, links: message.event.links, relayid: message.event.relayid };
1964 meshserver.send({ action: 'nodes', skip: (devicePagingState == null) ? 0 : devicePagingState.skip }); // Request a refresh of all nodes (TODO: We could optimize this to only request nodes for the new mesh).
1965 }
1966 } else {
1967 // This is an existing mesh
1968 if (meshes[message.event.meshid].name != message.event.name) {
1969 meshes[message.event.meshid].name = message.event.name;
1970 for (var i in nodes) { if (nodes[i].meshid == message.event.meshid) { nodes[i].meshnamel = message.event.name.toLowerCase(); } }
1971 }
1972 meshes[message.event.meshid].desc = message.event.desc;
1973 meshes[message.event.meshid].links = message.event.links;
1974 if (message.event.relayid != null) { meshes[message.event.meshid].relayid = message.event.relayid; }
1975
1976 // Check if we lost rights to this mesh in this change.
1977 if (IsMeshViewable(message.event.meshid) == false) {
1978 if ((xxcurrentView == 20) && (currentMesh == meshes[message.event.meshid])) go(2);
1979 delete meshes[message.event.meshid];
1980
1981 // Delete all nodes in that mesh, except ones with direct links
1982 var newnodes = [];
1983 for (var i in nodes) { if ((nodes[i].meshid != message.event.meshid) || ((userinfo.links != null) && (userinfo.links[nodes[i]._id] != null))) { newnodes.push(nodes[i]); } }
1984 nodes = newnodes;
1985
1986 // If we are looking at a node in the deleted mesh, move back to "My Devices"
1987 if (xxcurrentView >= 10 && xxcurrentView < 20 && currentNode && !IsNodeViewable(currentNode)) { setDialogMode(0); go(2); currentNode = null; }
1988 }
1989 }
1990 mainUpdate(4 + 128);
1991 meshserver.send({ action: 'files' });
1992
1993 // If we are looking at a mesh that is now deleted, move back to "My Account"
1994 if (xxcurrentView == 20 && currentMesh._id == message.event.meshid) { p20updateMesh(); }
1995 break;
1996 }
1997 case 'deletemesh': {
1998 // Delete the mesh
1999 if (meshes[message.event.meshid]) {
2000 delete meshes[message.event.meshid];
2001 updateMeshes();
2002 meshserver.send({ action: 'files' });
2003 }
2004
2005 // Delete all nodes in that mesh
2006 var newnodes = [];
2007 for (var i in nodes) { if (nodes[i].meshid != message.event.meshid) { newnodes.push(nodes[i]); } }
2008 nodes = newnodes;
2009 mainUpdate(4);
2010
2011 // If we are looking at a mesh that is now deleted, move back to "My Account"
2012 if (xxcurrentView >= 20 && xxcurrentView < 30 && currentMesh._id == message.event.meshid) { setDialogMode(0); go(2); }
2013 // If we are looking at a node in the deleted mesh, move back to "My Devices"
2014 if (xxcurrentView >= 10 && xxcurrentView < 20 && currentNode && !IsNodeViewable(currentNode)) { setDialogMode(0); go(2); }
2015
2016 break;
2017 }
2018 case 'addnode': {
2019 var node = message.event.node;
2020 if (!meshes[node.meshid]) break; // This is a node for a mesh we don't know. Happens when we are site administrator, we get all messages.
2021 if (getNodeFromId(node._id) != null) break; // This node is already known.
2022 node.namel = node.name.toLowerCase();
2023 if (node.rname) { node.rnamel = node.rname.toLowerCase(); } else { node.rnamel = node.namel; }
2024 node.meshnamel = meshes[node.meshid]?meshes[node.meshid].name.toLowerCase():'*';
2025 node.state = 0;
2026 if (!node.icon) node.icon = 1;
2027 node.ident = ++nodeShortIdent;
2028 nodes.push(node);
2029 //onSortSelectChange();
2030 //onSearchInputChanged();
2031 mainUpdate(4);
2032 //updateMapMarkers();
2033 break;
2034 }
2035 case 'removenode': {
2036 var index = -1;
2037 for (var i in nodes) { if (nodes[i]._id == message.event.nodeid) { index = i; break; } }
2038 if (index != -1) {
2039 var node = nodes[index];
2040 if (currentNode == node) {
2041 if (xxcurrentView >= 10 && xxcurrentView < 20) { setDialogMode(0); go(2); }
2042 currentNode = null;
2043 // TODO: Correctly disconnect from this node (Desktop/Terminal/Files...)
2044 }
2045 nodes.splice(index, 1);
2046 mainUpdate(4);
2047 //updateMapMarkers();
2048 }
2049 break;
2050 }
2051 case 'changenode': {
2052 var index = -1;
2053 for (var i in nodes) { if (nodes[i]._id == message.event.nodeid) { index = i; break; } }
2054 if (index != -1) {
2055 var node = nodes[index];
2056
2057 // Change the node
2058 node.name = message.event.node.name;
2059 node.rname = message.event.node.rname;
2060 node.lusers = message.event.node.lusers;
2061 node.users = message.event.node.users;
2062 node.host = message.event.node.host;
2063 node.desc = message.event.node.desc;
2064 node.publicip = message.event.node.publicip;
2065 node.iploc = message.event.node.iploc;
2066 node.wifiloc = message.event.node.wifiloc;
2067 node.gpsloc = message.event.node.gpsloc;
2068 node.tags = message.event.node.tags;
2069 node.ssh = message.event.node.ssh;
2070 node.rdp = message.event.node.rdp;
2071 node.userloc = message.event.node.userloc;
2072 node.rdpport = message.event.node.rdpport;
2073 node.rfbport = message.event.node.rfbport;
2074 node.sshport = message.event.node.sshport;
2075 node.httpport = message.event.node.httpport;
2076 node.httpsport = message.event.node.httpsport;
2077 node.consent = message.event.node.consent;
2078 node.pmt = message.event.node.pmt;
2079 if (message.event.node.agent != null) {
2080 if (node.agent == null) node.agent = {};
2081 if (message.event.node.agent.ver != null) { node.agent.ver = message.event.node.agent.ver; }
2082 if (message.event.node.agent.id != null) { node.agent.id = message.event.node.agent.id; }
2083 if (message.event.node.agent.caps != null) { node.agent.caps = message.event.node.agent.caps; }
2084 if (message.event.node.agent.root != null) { node.agent.root = message.event.node.agent.root; }
2085 if (message.event.node.agent.core != null) { node.agent.core = message.event.node.agent.core; } else { if (node.agent.core) { delete node.agent.core; } }
2086 node.agent.tag = message.event.node.agent.tag;
2087 }
2088 if (message.event.node.intelamt != null) {
2089 if (node.intelamt == null) node.intelamt = {};
2090 if (message.event.node.intelamt.state != null) { node.intelamt.state = message.event.node.intelamt.state; }
2091 if (message.event.node.intelamt.host != null) { node.intelamt.user = message.event.node.intelamt.host; }
2092 if (message.event.node.intelamt.user != null) { node.intelamt.user = message.event.node.intelamt.user; }
2093 if (message.event.node.intelamt.tls != null) { node.intelamt.tls = message.event.node.intelamt.tls; }
2094 if (message.event.node.intelamt.ver != null) { node.intelamt.ver = message.event.node.intelamt.ver; }
2095 if (message.event.node.intelamt.tag != null) { node.intelamt.tag = message.event.node.intelamt.tag; }
2096 if (message.event.node.intelamt.uuid != null) { node.intelamt.uuid = message.event.node.intelamt.uuid; }
2097 if (message.event.node.intelamt.realm != null) { node.intelamt.realm = message.event.node.intelamt.realm; }
2098 if (message.event.node.intelamt.flags != null) { node.intelamt.flags = message.event.node.intelamt.flags; }
2099 if (message.event.node.intelamt.warn != null) { node.intelamt.warn = message.event.node.intelamt.warn; } else { delete node.intelamt.warn; }
2100 }
2101 if (message.event.node.av != null) { node.av = message.event.node.av; }
2102 if (message.event.node.lsc != null) { node.lsc = message.event.node.lsc; }
2103 if (message.event.node.wsc != null) { node.wsc = message.event.node.wsc; }
2104 if (message.event.node.defender != null) { node.defender = message.event.node.defender; }
2105 node.namel = node.name.toLowerCase();
2106 if (node.rname) { node.rnamel = node.rname.toLowerCase(); } else { node.rnamel = node.namel; }
2107 if (message.event.node.icon) { node.icon = message.event.node.icon; }
2108 if (message.event.node.lastbootuptime != null) { node.lastbootuptime = message.event.node.lastbootuptime; }
2109 if (message.event.node.idletime != null) { node.idletime = message.event.node.idletime; }
2110
2111 //onSortSelectChange(true);
2112 //drawNotifications();
2113 refreshDevice(node._id);
2114 updateDeviceViewDevice(node);
2115 if (currentNode == node) { updateDeviceDetails(); }
2116
2117 //if ((currentNode == node) && (xxdialogMode != null) && (xxdialogTag == '@xxmap')) { p10showNodeLocationDialog(); }
2118 }
2119 break;
2120 }
2121 case 'nodemeshchange': {
2122 var index = -1;
2123 for (var i in nodes) { if (nodes[i]._id == message.event.nodeid) { index = i; break; } }
2124 if (index != -1) {
2125 var node = nodes[index];
2126 if ((meshes[message.event.newMeshId] == null) && ((userinfo.links == null) || (userinfo.links[node._id] == null))) {
2127 // We don't see the new mesh, remove this device
2128
2129 // TODO: Correctly disconnect from this node (Desktop/Terminal/Files...)
2130 if (xxcurrentView >= 10 && xxcurrentView < 20 && currentNode && !IsNodeViewable(currentNode)) { setDialogMode(0); go(2); currentNode = null; }
2131 nodes.splice(index, 1);
2132 } else {
2133 // We see the new mesh, move this device
2134 node.meshid = message.event.newMeshId;
2135 node.meshnamel = meshes[message.event.newMeshId]?meshes[message.event.newMeshId].name.toLowerCase():'*';
2136 }
2137 mainUpdate(4);
2138 refreshDevice(message.event.nodeid);
2139 } else {
2140 // This is a new device, add it.
2141 var node = message.event.node;
2142 if (!meshes[node.meshid]) break; // This is a node for a mesh we don't know. Happens when we are site administrator, we get all messages.
2143 node.namel = node.name.toLowerCase();
2144 if (node.rname) { node.rnamel = node.rname.toLowerCase(); } else { node.rnamel = node.namel; }
2145 node.meshnamel = meshes[node.meshid]?meshes[node.meshid].name.toLowerCase():'*';
2146 node.state = 0;
2147 if (!node.icon) node.icon = 1;
2148 node.ident = ++nodeShortIdent;
2149 if (nodes == null) { }
2150 nodes.push(node);
2151
2152 // Web page update
2153 //mainUpdate(1 | 2 | 4 | 16);
2154 mainUpdate(4);
2155 }
2156 break;
2157 }
2158 case 'nodeconnect': {
2159 // Indicated a node has changed connectivity state
2160 var index = -1;
2161 for (var i in nodes) { if (nodes[i]._id == message.event.nodeid) { index = i; break; } }
2162 if (index != -1) {
2163 var node = nodes[index];
2164
2165 // Change the node connection state
2166 node.conn = message.event.conn;
2167 node.pwr = message.event.pwr;
2168
2169 // Clear sesssion information if needed
2170 if ((node.conn & 1) == 0) { delete node.sessions; }
2171
2172 refreshDevice(node._id);
2173 updateDeviceViewDevice(node);
2174 }
2175 break;
2176 }
2177 case 'login': {
2178 // Update the last login time
2179 if (users != null && users['user/' + domain + '/' + message.event.username.toLowerCase()]) { users['user/' + domain + '/' + message.event.username.toLowerCase()].login = message.event.time; }
2180 break;
2181 }
2182 case 'notify': {
2183 var n = { text: message.event.value, title: message.event.title, icon: message.event.icon, titleid: message.titleid, msgid: message.msgid, args: message.args };
2184 if (message.id != null) { n.id = message.id; }
2185 if (message.event.tag != null) { n.tag = message.event.tag; }
2186 if (typeof message.maxtime == 'number') { n.maxtime = message.maxtime; }
2187 addNotification(n);
2188 break;
2189 }
2190 case 'sysinfohash': {
2191 // If the sysinfo document has changed and we are looking at it, request an update.
2192 if ((currentNode != null) && (message.event.nodeid == powerTimelineReq)) { meshserver.send({ action: 'getsysinfo', nodeid: message.event.nodeid }); }
2193 break;
2194 }
2195 case 'ifchange': {
2196 // Network interface changed for a device, if we are currently viewing this device, ask for an update.
2197 if ((currentNode != null) && (currentNode._id == message.event.nodeid)) { meshserver.send({ action: 'getnetworkinfo', nodeid: currentNode._id }); }
2198 break;
2199 }
2200 case 'devicesessions': {
2201 // List of sessions for a given device
2202 var node = getNodeFromId(message.event.nodeid);
2203 if (node == null) break; // Unknown node
2204 node.sessions = message.event.sessions;
2205 if (node.sessions != null) {
2206 for (var i in node.sessions) { if (Object.keys(node.sessions[i]).length == 0) { delete node.sessions[i]; } }
2207 if (Object.keys(node.sessions).length == 0) { delete node.sessions; }
2208 }
2209
2210 refreshDevice(message.event.nodeid);
2211 updateDeviceViewDevice(node);
2212 //if ((currentNode != null) && (currentNode._id == message.event.nodeid)) { gotoDevice(currentNode._id, xxcurrentView, true); }
2213
2214 // If we are looking at the sessions dialog box for this device now, update it
2215 if (xxdialogTag == ('SESSIONS-' + message.event.nodeid)) { showDeviceSessions(message.event.nodeid, true); }
2216 //if (xxdialogTag == ('MESSAGES-' + message.event.nodeid)) { showDeviceMessages(message.event.nodeid, true); }
2217 if (xxdialogTag == ('HELPREQ-' + message.event.nodeid)) { showDeviceHelpRequests(message.event.nodeid, true); }
2218
2219 break;
2220 }
2221 case 'stopped': { // Server is stopping.
2222 // TODO: Disconnect
2223 break;
2224 }
2225 default:
2226 //console.log('Unknown message.event.action', message.event.action);
2227 break;
2228 }
2229 break;
2230 }
2231 case 'getcookie': {
2232 if (message.tag == 'novnc') {
2233 var vncurl = window.location.origin + domainUrl + 'novnc/vnc.html?ws=wss%3A%2F%2F' + window.location.host + encodeURIComponentEx(domainUrl) + (message.localRelay?'local':'mesh') + 'relay.ashx%3Fauth%3D' + message.cookie + '&show_dot=1' + (urlargs.key?('&key=' + urlargs.key):'') + '&l={{{lang}}}';
2234 var node = getNodeFromId(message.nodeid);
2235 if (node != null) { vncurl += '&name=' + encodeURIComponentEx(node.name); }
2236 safeNewWindow(vncurl, 'mcnovnc/' + message.nodeid);
2237 } else if (message.tag == 'mstsc') {
2238 var rdpurl = window.location.origin + domainUrl + 'mstsc.html?ws=' + message.cookie + (urlargs.key?('&key=' + urlargs.key):'');
2239 var node = getNodeFromId(message.nodeid);
2240 if (node != null) { rdpurl += '&name=' + encodeURIComponentEx(node.name); }
2241 if (message.localRelay) { rdpurl += '&local=1'; }
2242 safeNewWindow(rdpurl, 'mcmstsc/' + message.nodeid);
2243 } else if (message.tag == 'ssh') {
2244 var sshurl = window.location.origin + domainUrl + 'ssh.html?ws=' + message.cookie + (urlargs.key?('&key=' + urlargs.key):'');
2245 var node = getNodeFromId(message.nodeid);
2246 if (node != null) { sshurl += '&name=' + encodeURIComponentEx(node.name); }
2247 if (message.localRelay) { sshurl += '&local=1'; }
2248 safeNewWindow(sshurl, 'mcssh/' + message.nodeid);
2249 }
2250 break;
2251 }
2252 default:
2253 //console.log('Unknown message.action', message.action);
2254 break;
2255 }
2256 }
2257
2258 // To boost the speed of the web page when even floods occur, this method perform a delayed update on the web page.
2259 var updateNaggleTimer = null;
2260 var updateNaggleFlags = 0;
2261 function mainUpdate(flags) {
2262 updateNaggleFlags |= flags;
2263 if (updateNaggleTimer == null) {
2264 updateNaggleTimer = setTimeout(function () {
2265 if (updateNaggleFlags & 1) { onSearchInputChanged(); }
2266 if (updateNaggleFlags & 4) { updateDevices(); updateDeviceDetails(); }
2267 if (updateNaggleFlags & 128) { updateMeshes(); }
2268 updateNaggleTimer = null;
2269 updateNaggleFlags = 0;
2270 gotoStartViewPage();
2271 }, 150);
2272 }
2273 }
2274
2275 // Go to the correct starting view page
2276 function gotoStartViewPage() {
2277 var xviewmode = parseInt('{{viewmode}}');
2278 if (xxcurrentView > 1) return;
2279 if ('{{currentNode}}'.toLowerCase() != '') { // The .toLowerCase here is the minifier will not optimize this out.
2280 if (getNodeFromId('{{currentNode}}') == null) return; // This node is not loaded yet
2281 gotoDevice('{{currentNode}}', xviewmode);
2282 } else if (args.gotonode != null) {
2283 if (args.gotonode.length == 96) { args.gotonode = btoa(hex2rstr(args.gotonode)).split('+').join('@').split('/').join('$'); } // This is a HEX encoded NodeID, convert it to Base64
2284 if (getNodeFromId('node/' + domain + '/' + args.gotonode) == null) return; // This node is not loaded yet
2285 if (args.panel) { currentDevicePanel = parseInt(args.panel); }
2286 gotoDevice('node/' + domain + '/' + args.gotonode, xviewmode);
2287 } else if (args.gotodevicename != null) {
2288 var foundNode = null;
2289 if (nodes != null) { for (var i in nodes) { if (nodes[i].name == args.gotodevicename) { foundNode = nodes[i]._id; } } }
2290 if (foundNode) { gotoDevice(foundNode, xviewmode); go(xviewmode); }
2291 } else if (args.gotodevicername != null) {
2292 var foundNode = null;
2293 if (nodes != null) { for (var i in nodes) { if (nodes[i].rname == args.gotodevicername) { foundNode = nodes[i]._id; } } }
2294 if (foundNode) { gotoDevice(foundNode, xviewmode); goBackStack.push(1); }
2295 } else if (args.gotodeviceip != null) {
2296 var foundNode = null;
2297 if (nodes != null) { for (var i in nodes) { if (nodes[i].ip == args.gotodeviceip) { foundNode = nodes[i]._id; } } }
2298 if (foundNode) { gotoDevice(foundNode, xviewmode); go(xviewmode); }
2299 } else if (args.gotomesh != null) {
2300 if (meshes['mesh/' + domain + '/' + args.gotomesh] == null) return; // This device group is not loaded yet
2301 gotoMesh('mesh/' + domain + '/' + args.gotomesh);
2302 go(xviewmode);
2303 } else if (!isNaN(xviewmode)) {
2304 go(xviewmode);
2305 } else {
2306 setDialogMode(0);
2307 go(1);
2308 }
2309 delete args.gotonode;
2310 delete args.gotomesh;
2311 delete args.panel;
2312 if (xxcurrentView < 2) { go(2); }
2313 }
2314
2315 //
2316 // Menu System
2317 //
2318
2319 function topMenu(select) {
2320 if ((xxdialogMode != null) && (xxdialogMode != 0) && (xxdialogMode != 999)) return;
2321 if (select === undefined) {
2322 var x = (QS('topMenu').display == 'none');
2323 if (x == true) { if ((xxdialogMode == 0) || (xxdialogMode == null)) { QV('topMenu', true); xxdialogMode = 999; } } else { QV('topMenu', false); xxdialogMode = 0; }
2324 } else {
2325 QV('topMenu', false);
2326 xxdialogMode = 0;
2327 if ((select == 1) && (xxcurrentView != 3)) { goForward('account'); } // My Account
2328 if ((select == 2) && (xxcurrentView != 5)) { goForward('files'); } // My Files
2329 }
2330 }
2331
2332 var backStack = [];
2333 function goBack() { if (xxdialogMode) return; if (backStack.length > 0) { backStack.pop(); } goStack(); }
2334 function goForward(id) { if (xxdialogMode) return; backStack.push(id); goStack(); }
2335 function goStack() {
2336 if (backStack.length == 0) { go(2); return; }
2337 var id = backStack[backStack.length - 1], idtype = id.split('/')[0];
2338 if (idtype == 'node') { setupDeviceMenu(0); gotoDevice(id); }
2339 if (idtype == 'mesh') { gotoMesh(id); }
2340 if (idtype == 'account') { go(3); }
2341 if (idtype == 'devices') { go(2); }
2342 if (idtype == 'files') {
2343 // Remind the user to add two factor authentication
2344 if ((features & 0x00040000) && !((userinfo.otpsecret == 1) || (userinfo.otphkeys > 0) || (userinfo.otpkeys > 0) || (userinfo.otpduo > 0) || (userinfo.otpdev > 0) || ((features & 0x00800000) && (userinfo.otpekey == 1)))) { setDialogMode(2, "Account Security", 1, null, "Unable to access this feature until two-factor authentication is enabled. This is required for extra security. Go to the \"My Account\" and look at the \"Account Security\" section."); return; }
2345 go(5);
2346 }
2347 }
2348
2349 function updateFooterMenu(options) {
2350 while (options != null && options.length < 3) { options.push({ n: '' }); }
2351 var x = '', prev = '';
2352 if (options != null) { for (var i in options) { x += '<td style="cursor:pointer' + ((prev == '') ? '' : ';border-left:solid 1px white') + '" onclick="' + options[i].f + '">' + options[i].n; prev = options[i].n; } }
2353 QH('footerMenu', '<tr>' + x);
2354 }
2355
2356 //
2357 // MY ACCOUNT
2358 //
2359
2360 function account_viewPreviousLogins() {
2361 if (xxdialogMode) return;
2362 setDialogMode(2, "Previous Logins", 1, null, "Loading...", 'previousLogins');
2363 meshserver.send({ action: 'previousLogins' });
2364 }
2365
2366 function account_manageImage(mode) {
2367 if (xxdialogMode) return;
2368 var user = (mode == 0) ? userinfo : currentUser;
2369 var x = '<input id=p2file type=file style=width:100% accept="image/*" onchange=account_manageImageEx()><div style=width:100%><canvas id=p2canvas width=256 height=256 style="width:256px;height:256px;margin-left:12px;margin-top:8px;border-radius:16px;box-shadow: 0px 0px 15px #000" onclick=account_canvasClick() /></div>';
2370 setDialogMode(2, "Manage Account Image", 7, account_manageImageEx2, x, user._id);
2371 var ctx = Q('p2canvas').getContext('2d');
2372 if (user.accountImageRnd == null) { user.accountImageRnd = Math.floor(Math.random() * 9999999999); }
2373 var arg = '';
2374 if (mode == 1) { arg = '&id=' + user._id.split('/')[2]; }
2375 var myImg = new Image();
2376 myImg.onload = function () { ctx.clearRect(0, 0, 256, 256); ctx.drawImage(myImg, 0, 0); };
2377 myImg.src = ((user.flags != null) && (user.flags & 1)) ? ('userimage.ashx?rnd=' + user.accountImageRnd + arg) : 'images/user-256.png';
2378 QE('idx_dlgDeleteButton', (user.flags != null) && (user.flags & 1));
2379 QE('idx_dlgOkButton', false);
2380 }
2381
2382 function account_canvasClick() { Q('p2file').click(); }
2383
2384 function account_manageImageEx() {
2385 var file = Q('p2file').files[0];
2386 var img = new Image;
2387 img.onload = function () {
2388 var cx = 0, cy = 0, min = Math.min(img.width, img.height);
2389 if (img.width > min) { cx = (img.width - min) / 2; }
2390 if (img.height > min) { cy = (img.height - min) / 2; }
2391 var ctx = Q('p2canvas').getContext('2d');
2392 ctx.imageSmoothingEnabled = true;
2393 ctx.webkitImageSmoothingEnabled = true;
2394 ctx.mozImageSmoothingEnabled = true;
2395 ctx.clearRect(0, 0, 256, 256);
2396 ctx.drawImage(img, cx, cy, min, min, 0, 0, 256, 256);
2397 QE('idx_dlgOkButton', true);
2398 }
2399 img.src = URL.createObjectURL(file);
2400 }
2401
2402 function account_manageImageEx2(b, userid) {
2403 // Send updated image, or 0 if we pressed the delete button
2404 meshserver.send({ action: 'updateUserImage', userid: userid, image: (b == 2) ? 0 : Q('p2canvas').toDataURL('image/jpeg', 0.8) });
2405 //meshserver.send({ action: 'updateUserImage', image: (b == 2)?0:Q('p2canvas').toDataURL('image/png', 0.8) });
2406 }
2407
2408 function toggleNightMode() {
2409 if (xxdialogMode) return;
2410 var cNightMode = getstore('nightMode', '0');
2411 var x = '<input type=radio id=night0 name=nightmoderadio value=0 ' + ((cNightMode == 0) ? 'checked' : '') + '><label for=night0>' + "Browser default" + '</label><br>';
2412 x += '<input type=radio id=night2 name=nightmoderadio value=2 ' + ((cNightMode == 2) ? 'checked' : '') + '><label for=night2>' + "Light mode" + '</label><br>';
2413 x += '<input type=radio id=night1 name=nightmoderadio value=1 ' + ((cNightMode == 1) ? 'checked' : '') + '><label for=night1>' + "Dark mode" + '</label><br>';
2414 setDialogMode(2, "Night Mode", 3, toggleNightModeEx, x);
2415 QV('uiMenu', false);
2416 }
2417
2418 function toggleNightModeEx() {
2419 // Save new night mode
2420 var nNightMode = '0';
2421 if (Q('night1').checked) { nNightMode = '1'; }
2422 if (Q('night2').checked) { nNightMode = '2'; }
2423 putstore('nightMode', nNightMode);
2424 setNightMode();
2425 }
2426
2427 function setNightMode() {
2428 // Set night mode
2429 var nNightMode = getstore('nightMode', '0')
2430 nightMode = false;
2431 if ((features2 & 0x00100000) != 0) { nNightMode = '1'; }
2432 if ((features2 & 0x00200000) != 0) { nNightMode = '2'; }
2433 if (nNightMode == '1') { nightMode = true; }
2434 else if ((nNightMode == '0') && (window.matchMedia)) { nightMode = window.matchMedia('(prefers-color-scheme: dark)').matches }
2435 if (nightMode) { QC('body').add('night'); QS('body')['background-color'] = '#000'; QS('body')['color'] = 'lightgray'; } else { QC('body').remove('night'); QS('body')['background-color'] = '#FFF'; QS('body')['color'] = 'black'; }
2436 return nightMode;
2437 }
2438
2439 function account_managePhone() {
2440 if (xxdialogMode || ((features & 0x02000000) == 0)) return;
2441 var x;
2442 if (userinfo.phone != null) {
2443 x = '<table style=width:100%><tr><td style=width:56px><img src="images/phone80.png" style=padding:8px>';
2444 x += '<td style=text-align:center><div style=padding:6px>' + "Verified phone number" + '</div><div style=font-size:20px>' + userinfo.phone + '</div>';
2445 x += '<div style=margin:10px><label><input id=d2delPhone type=checkbox onclick=account_managePhoneRemoveValidate() />' + "Remove phone number" + '</label></div>';
2446 setDialogMode(2, "Phone Notifications", 3, account_managePhoneRemove, x);
2447 account_managePhoneRemoveValidate();
2448 } else {
2449 x = '<table style=width:100%><tr><td style=width:56px><img src="images/phone80.png" style=padding:8px>';
2450 x += '<td>Enter your SMS capable phone number. Once verified, the number may be used for login verification and other notifications.';
2451 x += '<br /><br /><div style=width:100%;text-align:center>' + "Phone number:" + ' <input type=tel pattern="[0-9]" autocomplete="tel" inputmode="tel" maxlength=18 id=d2phoneinput onKeyUp=account_managePhoneValidate() onkeypress="if (event.key==\'Enter\') account_managePhoneValidate(1)"></div></table>';
2452 setDialogMode(2, "Phone Notifications", 3, account_managePhoneAdd, x, 'verifyPhone');
2453 Q('d2phoneinput').focus();
2454 account_managePhoneValidate();
2455 }
2456 }
2457
2458 function isPhoneNumber(x) { return x.match(/^\(?([0-9]{3,4})\)?[-. ]?([0-9]{3})[-. ]?([0-9]{4})$/) }
2459 function account_managePhoneValidate(x) { var ok = isPhoneNumber(Q('d2phoneinput').value); QE('idx_dlgOkButton', ok); if ((x == 1) && ok) { dialogclose(1); } }
2460 function account_managePhoneCodeValidate(x) { var ok = (Q('d2phoneCodeInput').value.length == 6) && Q('d2phoneCodeInput').value.match(/[0-9]/); QE('idx_dlgOkButton', ok); if ((x == 1) && ok) { dialogclose(1); } }
2461 function account_managePhoneConfirm(b, tag) { meshserver.send({ action: 'confirmPhone', code: Q('d2phoneCodeInput').value, cookie: tag }); }
2462 function account_managePhoneAdd() { if (isPhoneNumber(Q('d2phoneinput').value) == false) return; QE('d2phoneinput', false); meshserver.send({ action: 'verifyPhone', phone: Q('d2phoneinput').value }); }
2463 function account_managePhoneRemove() { if (Q('d2delPhone').checked) { meshserver.send({ action: 'removePhone' }); } }
2464 function account_managePhoneRemoveValidate() { QE('idx_dlgOkButton', Q('d2delPhone').checked); }
2465
2466 function account_manageAuthEmail() {
2467 if (xxdialogMode || ((features & 0x00800000) == 0)) return;
2468 var emailU2Fenabled = ((userinfo.otpekey == 1) && (userinfo.email != null) && (userinfo.emailVerified == true));
2469 setDialogMode(2, "Email Authentication", 1, function () {
2470 if (emailU2Fenabled != Q('email2facheck').checked) { meshserver.send({ action: 'otpemail', enabled: Q('email2facheck').checked }); }
2471 }, "When enabled, on each login, you will be given the option to receive a login token to you email account for added security." + '<br /><br /><label><input id=email2facheck type=checkbox ' + (emailU2Fenabled ? 'checked' : '') + '/>' + "Enable email two-factor authentication." + '</label>');
2472 }
2473
2474 var loclist = { 'af': "Afrikaans", 'sq': "Albanian", 'ar': "Arabic (Standard)", 'ar-dz': "Arabic (Algeria)", 'ar-bh': "Arabic (Bahrain)", 'ar-eg': "Arabic (Egypt)", 'ar-iq': "Arabic (Iraq)", 'ar-jo': "Arabic (Jordan)", 'ar-kw': "Arabic (Kuwait)", 'ar-lb': "Arabic (Lebanon)", 'ar-ly': "Arabic (Libya)", 'ar-ma': "Arabic (Morocco)", 'ar-om': "Arabic (Oman)", 'ar-qa': "Arabic (Qatar)", 'ar-sa': "Arabic (Saudi Arabia)", 'ar-sy': "Arabic (Syria)", 'ar-tn': "Arabic (Tunisia)", 'ar-ae': "Arabic (U.A.E.)", 'ar-ye': "Arabic (Yemen)", 'an': "Aragonese", 'hy': "Armenian", 'as': "Assamese", 'ast': "Asturian", 'az': "Azerbaijani", 'eu': "Basque", 'bg': "Bulgarian", 'be': "Belarusian", 'bn': "Bengali", 'bs': "Bosnian", 'br': "Breton", 'my': "Burmese", 'ca': "Catalan", 'ch': "Chamorro", 'ce': "Chechen", 'zh': "Chinese", 'zh-hk': "Chinese (Hong Kong)", 'zh-cn': "Chinese (PRC)", 'zh-sg': "Chinese (Singapore)", 'zh-tw': "Chinese (Taiwan)", 'cv': "Chuvash", 'co': "Corsican", 'cr': "Cree", 'hr': "Croatian", 'cs': "Czech", 'da': "Danish", 'nl': "Dutch (Standard)", 'nl-be': "Dutch (Belgian)", 'en': "English", 'en-au': "English (Australia)", 'en-bz': "English (Belize)", 'en-ca': "English (Canada)", 'en-ie': "English (Ireland)", 'en-jm': "English (Jamaica)", 'en-nz': "English (New Zealand)", 'en-ph': "English (Philippines)", 'en-za': "English (South Africa)", 'en-tt': "English (Trinidad & Tobago)", 'en-gb': "English (United Kingdom)", 'en-us': "English (United States)", 'en-zw': "English (Zimbabwe)", 'eo': "Esperanto", 'et': "Estonian", 'fo': "Faeroese", 'fa': "Farsi (Persian)", 'fj': "Fijian", 'fi': "Finnish", 'fr': "French (Standard)", 'fr-be': "French (Belgium)", 'fr-ca': "French (Canada)", 'fr-fr': "French (France)", 'fr-lu': "French (Luxembourg)", 'fr-mc': "French (Monaco)", 'fr-ch': "French (Switzerland)", 'fy': "Frisian", 'fur': "Friulian", 'gd': "Gaelic (Scots)", 'gd-ie': "Gaelic (Irish)", 'gl': "Galacian", 'ka': "Georgian", 'de': "German (Standard)", 'de-at': "German (Austria)", 'de-de': "German (Germany)", 'de-li': "German (Liechtenstein)", 'de-lu': "German (Luxembourg)", 'de-ch': "German (Switzerland)", 'el': "Greek", 'gu': "Gujurati", 'ht': "Haitian", 'he': "Hebrew", 'hi': "Hindi", 'hu': "Hungarian", 'is': "Icelandic", 'id': "Indonesian", 'iu': "Inuktitut", 'ga': "Irish", 'it': "Italian (Standard)", 'it-ch': "Italian (Switzerland)", 'ja': "Japanese", 'kn': "Kannada", 'ks': "Kashmiri", 'kk': "Kazakh", 'km': "Khmer", 'ky': "Kirghiz", 'tlh': "Klingon", 'ko': "Korean", 'ko-kp': "Korean (North Korea)", 'ko-kr': "Korean (South Korea)", 'la': "Latin", 'lv': "Latvian", 'lt': "Lithuanian", 'lb': "Luxembourgish", 'mk': "FYRO Macedonian", 'ms': "Malay", 'ml': "Malayalam", 'mt': "Maltese", 'mi': "Maori", 'mr': "Marathi", 'mo': "Moldavian", 'nv': "Navajo", 'ng': "Ndonga", 'ne': "Nepali", 'no': "Norwegian", 'nb': "Norwegian (Bokmal)", 'nn': "Norwegian (Nynorsk)", 'oc': "Occitan", 'or': "Oriya", 'om': "Oromo", 'fa-ir': "Persian/Iran", 'pl': "Polish", 'pt': "Portuguese", 'pt-br': "Portuguese (Brazil)", 'pa': "Punjabi", 'pa-in': "Punjabi (India)", 'pa-pk': "Punjabi (Pakistan)", 'qu': "Quechua", 'rm': "Rhaeto-Romanic", 'ro': "Romanian", 'ro-mo': "Romanian (Moldavia)", 'ru': "Russian", 'ru-mo': "Russian (Moldavia)", 'sz': "Sami (Lappish)", 'sg': "Sango", 'sa': "Sanskrit", 'sc': "Sardinian", 'sd': "Sindhi", 'si': "Singhalese", 'sr': "Serbian", 'sk': "Slovak", 'sl': "Slovenian", 'so': "Somani", 'sb': "Sorbian", 'es': "Spanish", 'es-ar': "Spanish (Argentina)", 'es-bo': "Spanish (Bolivia)", 'es-cl': "Spanish (Chile)", 'es-co': "Spanish (Colombia)", 'es-cr': "Spanish (Costa Rica)", 'es-do': "Spanish (Dominican Republic)", 'es-ec': "Spanish (Ecuador)", 'es-sv': "Spanish (El Salvador)", 'es-gt': "Spanish (Guatemala)", 'es-hn': "Spanish (Honduras)", 'es-mx': "Spanish (Mexico)", 'es-ni': "Spanish (Nicaragua)", 'es-pa': "Spanish (Panama)", 'es-py': "Spanish (Paraguay)", 'es-pe': "Spanish (Peru)", 'es-pr': "Spanish (Puerto Rico)", 'es-es': "Spanish (Spain)", 'es-uy': "Spanish (Uruguay)", 'es-ve': "Spanish (Venezuela)", 'sx': "Sutu", 'sw': "Swahili", 'sv': "Swedish", 'sv-fi': "Swedish (Finland)", 'sv-sv': "Swedish (Sweden)", 'ta': "Tamil", 'tt': "Tatar", 'te': "Teluga", 'th': "Thai", 'tig': "Tigre", 'ts': "Tsonga", 'tn': "Tswana", 'tr': "Turkish", 'tk': "Turkmen", 'uk': "Ukrainian", 'hsb': "Upper Sorbian", 'ur': "Urdu", 've': "Venda", 'vi': "Vietnamese", 'vo': "Volapuk", 'wa': "Walloon", 'cy': "Welsh", 'xh': "Xhosa", 'ji': "Yiddish", 'zu': "Zulu" };
2475 var loclistex = { 'zh-chs': "Chinese (Simplified)", 'zh-cht': "Chinese (Traditional)" };
2476 function account_showLocalizationSettings() {
2477 if (xxdialogMode) return false;
2478 var n = getstore('loctag', 0), y = '';
2479 var x = '<select id=d2locselect style=width:180px><option value="*">' + "User browser value" + '</option>';
2480 for (var i in loclist) { x += '<option value="' + i + '"' + ((n == i)?' selected':'') + '>' + i + ' - ' + loclist[i] + '</option>'; }
2481 x += '</select>';
2482 if (serverinfo.languages && serverinfo.languages.length > 0) {
2483 y += "Changing the language will require a refresh of the page." + '<br /><br />';
2484 var z = '<select id=d2langselect style=width:180px><option value="*">' + "User browser value" + '</option>';
2485 for (var i in serverinfo.languages) {
2486 var lang = serverinfo.languages[i];
2487 z += '<option value="' + lang + '"' + ((userinfo.lang == lang)?' selected':'') + '>' + lang + ' - ' + (loclist[lang]?loclist[lang]:loclistex[lang]) + '</option>';
2488 }
2489 z += '</select>';
2490 y += addHtmlValue("Language", z);
2491 }
2492 y += addHtmlValue("Dates & Time", x);
2493
2494 if ((userinfo.siteadmin == 0xFFFFFFFF) && (domain == '')) {
2495 y += '<br /><a rel="noreferrer noopener" target="_blank" href="translator.htm">' + "Help translate MeshCentral" + '</a>';
2496 }
2497
2498 setDialogMode(2, "Localization Settings", 3, account_showLocalizationSettingsEx, y);
2499 return false;
2500 }
2501
2502 function account_showLocalizationSettingsEx() {
2503 // Set user language
2504 var lang = Q('d2langselect').value;
2505 if ((lang == '*') && (userinfo.lang == null)) { lang = userinfo.lang; }
2506 if (lang != userinfo.lang) { meshserver.send({ action: 'changelang', lang: lang }); }
2507
2508 // Set date localization
2509 var n = getstore('loctag', 0);
2510 var m = Q('d2locselect').value;
2511 if (n != m) {
2512 if (m != '*') { args.locale = m; } else { delete args.locale; }
2513 putstore('loctag', args.locale);
2514 mainUpdate(0xFFFFFFFF); // Refresh everything.
2515 }
2516 }
2517
2518
2519 function account_manageAuthApp() {
2520 if (xxdialogMode || ((features & 4096) == 0)) return;
2521 if (userinfo.otpsecret == 1) { account_removeOtp(); } else { account_addOtp(); }
2522 }
2523
2524 function account_addOtp() {
2525 if (xxdialogMode || (userinfo.otpsecret == 1) || ((features & 4096) == 0)) return;
2526 setDialogMode(2, "Authenticator App", 2, function () { meshserver.send({ action: 'otpauth-setup', secret: Q('d2optsecret').attributes.secret.value, token: Q('d2otpauthinput').value }); }, '<div id=d2optinfo>' + "Loading..." + '</div>', 'otpauth-request');
2527 meshserver.send({ action: 'otpauth-request' });
2528 }
2529
2530 function account_addOtpCheck(e) {
2531 var tokenIsValid = (Q('d2otpauthinput').value.length == 6);
2532 QE('idx_dlgOkButton', tokenIsValid);
2533 if (e && (e.keyCode == 13) && tokenIsValid) { dialogclose(1); }
2534 }
2535
2536 function account_removeOtp() {
2537 if (xxdialogMode || (userinfo.otpsecret != 1) || ((features & 4096) == 0)) return;
2538 setDialogMode(2, "Authenticator App", 3, function () { meshserver.send({ action: 'otpauth-clear' }); }, "Confirm removal of authenticator application 2-step login?");
2539 }
2540
2541 function account_manageOtp(action) {
2542 if ((xxdialogMode == 2) && (xxdialogTag == 'otpauth-manage')) { dialogclose(0); }
2543 if (xxdialogMode || ((features & 4096) == 0) || ((userinfo.otpsecret != 1) && (userinfo.otphkeys < 1))) return;
2544 meshserver.send({ action: 'otpauth-getpasswords', subaction: action });
2545 }
2546
2547 function account_showVerifyEmail() {
2548 if (xxdialogMode || (userinfo.emailVerified == true) || (serverinfo.emailcheck != true)) return;
2549 var x = "Click ok to send a verification mail to:" + '<br /><div style=padding:8px><b>' + EscapeHtml(userinfo.email) + '</b></div>' + "Please wait a few minute to receive the verification.";
2550 setDialogMode(2, "Email Verification", 3, account_showVerifyEmailEx, x);
2551 }
2552
2553 function account_showVerifyEmailEx() {
2554 meshserver.send({ action: 'verifyemail', email: userinfo.email });
2555 }
2556
2557 function account_showChangeEmail() {
2558 if (xxdialogMode) return;
2559 var x = addHtmlValue("Email", '<input id=dp3email style=width:170px maxlength=256 onchange=account_validateEmail() onkeyup=account_validateEmail(event) />');
2560 setDialogMode(2, "Email Address Change", 3, account_changeEmail, x);
2561 if (userinfo.email != null) { Q('dp3email').value = userinfo.email; }
2562 account_validateEmail();
2563 Q('dp3email').focus();
2564 }
2565
2566 function account_validateEmail(e, email) {
2567 QE('idx_dlgOkButton', validateEmail(Q('dp3email').value) && (Q('dp3email').value != userinfo.email));
2568 if ((e != null) && (e.keyCode == 13)) { dialogclose(1); }
2569 }
2570
2571 function account_changeEmail() {
2572 meshserver.send({ action: 'changeemail', email: Q('dp3email').value });
2573 }
2574
2575 function account_showDeleteAccount() {
2576 if (xxdialogMode) return;
2577 var x = '<form method=post><table style=margin-left:10px><input type=hidden name=action value=deleteaccount /><input type=hidden name=authcookie value=' + authCookie + ' /><tr>';
2578 x += '<td align=right>' + "Password:" + '</td><td><input id=apassword1 type=password name=apassword1 autocomplete=off onchange=account_validateDeleteAccount() onkeyup=account_validateDeleteAccount() /></td>';
2579 x += '</tr><tr><td align=right>' + "Password:" + '</td><td><input id=apassword2 type=password name=apassword2 autocomplete=off onchange=account_validateDeleteAccount() onkeyup=account_validateDeleteAccount() /></td>';
2580 x += '</tr></table><div style=padding:10px;margin-bottom:4px>';
2581 x += '<input id=account_dlgCancelButton type=button value="' + "Cancel" + '" style=float:right;width:80px;margin-left:5px onclick=dialogclose(0)>';
2582 x += '<input id=account_dlgOkButton type=submit value="' + "OK" + '" style="float:right;width:80px" onclick=dialogclose(1)>';
2583 x += '</div><br /></form>';
2584 setDialogMode(2, "Delete Account", 0, null, x);
2585 account_validateDeleteAccount();
2586 Q('apassword1').focus();
2587 }
2588
2589 function account_showChangePassword() {
2590 if (xxdialogMode) return false;
2591 var x = '<table style=margin-left:10px>';
2592 x += '<tr><td align=right>' + nobreak("Old password:") + '</td><td><input id=apassword0 type=password name=apassword0 autocomplete=off onchange=account_validateNewPassword() onkeyup=account_validateNewPassword() onkeydown=account_validateNewPassword() /> <b></b></td></tr>';
2593 x += '<tr><td align=right>' + nobreak("New password:") + '</td><td><input id=apassword1 type=password name=apassword1 autocomplete=off onchange=account_validateNewPassword() onkeyup=account_validateNewPassword() onkeydown=account_validateNewPassword() /> <b><span id=dxPassWarn></span></b></td></tr>';
2594 x += '<tr><td align=right>' + nobreak("New password:") + '</td><td><input id=apassword2 type=password name=apassword2 autocomplete=off onchange=account_validateNewPassword() onkeyup=account_validateNewPassword() onkeydown=account_validateNewPassword() /></td></tr>';
2595 if (features & 0x00010000) { x += '<tr><td align=right>' + "Password hint:" + '</td><td><input id=apasswordhint name=apasswordhint maxlength=250 type=text autocomplete=off onchange=account_validateNewPassword() onkeyup=account_validateNewPassword() onkeydown=account_validateNewPassword() /></td></tr>'; }
2596 x += '</table>'
2597 if (passRequirements) {
2598 var r = [], rc = 0;
2599 for (var i in passRequirements) { if ((i != 'reset') && (i != 'hint')) { r.push(i + ':' + passRequirements[i]); rc++; } }
2600 if (rc > 0) { x += '<br /><span style=font-size:x-small>' + format("Requirements: {0}.", r.join(', ')) + '</span>'; }
2601 }
2602 x += '<br />';
2603 setDialogMode(2, "Change Password", 3, account_showChangePasswordEx, x);
2604 Q('apassword0').focus();
2605 account_validateNewPassword();
2606 return false;
2607 }
2608
2609 function account_showChangePasswordEx() {
2610 if (Q('apassword1').value == Q('apassword2').value) {
2611 var r = { action: 'changepassword', oldpass: Q('apassword0').value, newpass: Q('apassword1').value };
2612 if (features & 0x00010000) { r.hint = Q('apasswordhint').value; }
2613 meshserver.send(r);
2614 }
2615 }
2616
2617 function account_createMesh() {
2618 if (xxdialogMode) return;
2619
2620 // Check if we are disallowed from creating a device group
2621 if ((userinfo.siteadmin != 0xFFFFFFFF) && ((userinfo.siteadmin & 64) != 0)) { setDialogMode(2, "New Device Group", 1, null, "This account does not have the rights to create a new device group."); return; }
2622
2623 // Remind the user to verify the email address
2624 if ((userinfo.emailVerified !== true) && (serverinfo.emailcheck == true) && (userinfo.siteadmin != 0xFFFFFFFF)) { setDialogMode(2, "Account Security", 1, null, "Unable to access a device until a email address is verified. This is required for password recovery. Go to the \"My Account\" to change and verify an email address."); return; }
2625
2626 // Remind the user to add two factor authentication
2627 if ((features & 0x00040000) && !((userinfo.otpsecret == 1) || (userinfo.otphkeys > 0) || (userinfo.otpkeys > 0) || (userinfo.otpduo > 0) || (userinfo.otpdev > 0) || ((features & 0x00800000) && (userinfo.otpekey == 1)))) { setDialogMode(2, "Account Security", 1, null, "Unable to access a device until two-factor authentication is enabled. This is required for extra security. Go to the \"My Account\" and look at the \"Account Security\" section."); return; }
2628
2629 // We are allowed, let's prompt to information
2630 var x = addHtmlValue("Name", '<input id=dp3meshname style=width:170px maxlength=64 onchange=account_validateMeshCreate() onkeyup=account_validateMeshCreate() />');
2631 x += addHtmlValue("Type", '<div style=width:170px;margin:0;padding:0><select id=dp3meshtype style=width:100% onchange=account_validateMeshCreate() ><option value=2>' + "Software Agent Group" + '</option><option value=1>' + "Intel&reg; AMT only" + '</option></select></div>');
2632 x += addHtmlValue("Description", '<div style=width:170px;margin:0;padding:0><textarea id=dp3meshdesc maxlength=1024 style=width:100%;resize:none></textarea></div>');
2633 setDialogMode(2, "Create Device Group", 3, account_createMeshEx, x);
2634 account_validateMeshCreate();
2635 Q('dp3meshname').focus();
2636 }
2637
2638 function account_validateMeshCreate() {
2639 QE('idx_dlgOkButton', Q('dp3meshname').value.length > 0);
2640 }
2641
2642 function account_createMeshEx(button, tag) {
2643 meshserver.send({ action: 'createmesh', meshname: Q('dp3meshname').value, meshtype: parseInt(Q('dp3meshtype').value), desc: Q('dp3meshdesc').value });
2644 }
2645
2646 function account_validateDeleteAccount() {
2647 QE('account_dlgOkButton', (Q('apassword1').value.length > 0) && (Q('apassword1').value == Q('apassword2').value));
2648 }
2649
2650 function account_validateNewPassword() {
2651 var r = '', ok = (Q('apassword0').value.length > 0) && (Q('apassword1').value.length > 0) && (Q('apassword1').value == Q('apassword2').value) && (Q('apassword0').value != Q('apassword1').value);
2652 if ((features & 0x00010000) && (Q('apasswordhint').value == Q('apassword1').value)) { ok = false; }
2653 if (Q('apassword1').value != '') {
2654 if (passRequirements == null || passRequirements == '') {
2655 // No password requirements, display password strength
2656 var passStrength = checkPasswordStrength(Q('apassword1').value);
2657 if (passStrength >= 80) { r = '<span style=color:green>Strong<span>'; } else if (passStrength >= 60) { r = '<span style=color:blue>&#9679;<span>'; } else { r = '<span style=color:red>&#9679;<span>'; }
2658 } else {
2659 // Password requirements provided, use that
2660 var passReq = checkPasswordRequirements(Q('apassword1').value, passRequirements);
2661 if (passReq == false) { ok = false; r = '<span style=color:red>' + "Policy" + '<span>' }
2662 }
2663 }
2664 QH('dxPassWarn', r);
2665 //QE('account_dlgOkButton', ok);
2666 QE('idx_dlgOkButton', ok);
2667 }
2668
2669 // Return a password strength score
2670 function checkPasswordStrength(password) {
2671 var r = 0, letters = {}, varCount = 0, variations = { digits: /\d/.test(password), lower: /[a-z]/.test(password), upper: /[A-Z]/.test(password), nonWords: /\W/.test(password) }
2672 if (!password) return 0;
2673 for (var i = 0; i < password.length; i++) { letters[password[i]] = (letters[password[i]] || 0) + 1; r += 5.0 / letters[password[i]]; }
2674 for (var c in variations) { varCount += (variations[c] == true) ? 1 : 0; }
2675 return parseInt(r + (varCount - 1) * 10);
2676 }
2677
2678 // Check password requirements
2679 function checkPasswordRequirements(password, requirements) {
2680 if ((requirements == null) || (requirements == '') || (typeof requirements != 'object')) return true;
2681 if (requirements.min) { if (password.length < requirements.min) return false; }
2682 if (requirements.max) { if (password.length > requirements.max) return false; }
2683 var numeric = 0, lower = 0, upper = 0, nonalpha = 0;
2684 for (var i = 0; i < password.length; i++) {
2685 if (/\d/.test(password[i])) { numeric++; }
2686 if (/[a-z]/.test(password[i])) { lower++; }
2687 if (/[A-Z]/.test(password[i])) { upper++; }
2688 if (/\W/.test(password[i])) { nonalpha++; }
2689 }
2690 if (requirements.numeric && (numeric < requirements.numeric)) return false;
2691 if (requirements.lower && (lower < requirements.lower)) return false;
2692 if (requirements.upper && (upper < requirements.upper)) return false;
2693 if (requirements.nonalpha && (nonalpha < requirements.nonalpha)) return false;
2694 return true;
2695 }
2696
2697 function updateMeshes() {
2698 var r = '', count = 0;
2699 for (i in meshes) {
2700 count++;
2701
2702 // Mesh rights
2703 var meshrights = GetMeshRights(meshes[i]);
2704 var rights = "Partial Rights";
2705 if (meshrights == 0xFFFFFFFF) rights = "Full Administrator"; else if (meshrights == 0) rights = "No Rights";
2706
2707 // Print the mesh information
2708 r += '<div style=cursor:pointer onclick=goForward(\'' + i + '\')>';
2709 r += '<div style="float:left;margin-left:4px"><img src="/images/meshicon50.png" width=50 height=50 /></div>';
2710 r += '<div class=meshList>';
2711 r += '<div><div style=color:black;padding-left:12px;padding-top:2px><b>' + EscapeHtml(meshes[i].name) + '</b></div><div style=padding-left:12px;padding-top:3px;color:black>' + rights + '</div></div>';
2712 r += '</div></div>';
2713 }
2714
2715 QH('p3meshes', r);
2716 QV('p3noMeshFound', count == 0);
2717 }
2718
2719 function gotoMesh(meshid) {
2720 // Remind the user to add two factor authentication
2721 if ((features & 0x00040000) && !((userinfo.otpsecret == 1) || (userinfo.otphkeys > 0) || (userinfo.otpkeys > 0) || (userinfo.otpduo > 0) || (userinfo.otpdev > 0) || ((features & 0x00800000) && (userinfo.otpekey == 1)))) { setDialogMode(2, "Account Security", 1, null, "Unable to access this feature until two-factor authentication is enabled. This is required for extra security. Go to the \"My Account\" and look at the \"Account Security\" section."); return; }
2722 currentMesh = meshes[meshid];
2723 if (currentMesh == null) { goBack(); }
2724 p20updateMesh();
2725 go(20);
2726 }
2727
2728 //
2729 // FILE SELECTOR, DIALOG 3
2730 //
2731
2732 function d3init() {
2733 d3fileoptions = { dialog: 1, filter: 'd3filter', files: 'd3serverfiles', folderup: 'p3FolderUp', currentFolder: 'p3CurrentFolder', func: d3setActions };
2734 Q('d3localFile').value = '';
2735 Q('d3localFile').accept = Q('d3filter').value;
2736 d3modechange();
2737 }
2738
2739 function d3modechange() {
2740 var mode = Q('d3uploadMode').value;
2741 QV('d3localmode', mode == 1);
2742 QV('d3servermode', mode == 2);
2743 if (mode == 1) { d3setActions(); } else { d3updatefiles(); }
2744 }
2745
2746 var d3filetreelinkpath;
2747 var d3filetreelocation = [];
2748 var d3fileoptions = null;
2749 function d3updatefiles() {
2750 if (d3fileoptions == null) return;
2751 if ((d3fileoptions.filter == 'd3filter') && (Q('d3uploadMode').value == 1)) return;
2752 var html1 = '', html2 = '', filetreex = filetree, folderdepth = 1, publicPath = null, lastFolderName = '';
2753
2754 // Navigate to path location, build the paths at the same time
2755 var d3filetreelocation2 = [], oldlinkpath = d3filetreelinkpath, checkedBoxes = [], checkboxes = document.getElementsByName('fc');
2756 for (var i = 0; i < checkboxes.length; i++) { if (checkboxes[i].checked) { checkedBoxes.push(checkboxes[i].value) }; } // Save all existing checked boxes
2757
2758 d3filetreelinkpath = '';
2759 for (var i in d3filetreelocation) {
2760 if ((filetreex.f != null) && (filetreex.f[d3filetreelocation[i]] != null)) {
2761 d3filetreelocation2.push(d3filetreelocation[i]);
2762 if ((folderdepth == 1)) {
2763 var sp = d3filetreelocation[i].split('/');
2764 publicPath = window.location.origin + domainUrl + sp[0] + 'files/' + sp[2];
2765 if (d3filetreelocation[i] === userinfo._id) { d3filetreelinkpath += 'self'; } else { d3filetreelinkpath += (sp[0] + '/' + sp[2]); }
2766 } else {
2767 if (d3filetreelinkpath != '') { d3filetreelinkpath += '/' + d3filetreelocation[i]; if (folderdepth > 2) { publicPath += '/' + d3filetreelocation[i]; } }
2768 }
2769 filetreex = filetreex.f[d3filetreelocation[i]];
2770 lastFolderName = filetreex.n;
2771 folderdepth++;
2772 } else {
2773 break;
2774 }
2775 }
2776 d3filetreelocation = d3filetreelocation2; // In case we could not go down the full path, we set the new path location here.
2777
2778 // Sort the files
2779 var filetreexx = p5sort_files(filetreex.f);
2780
2781 // File filter
2782 var fileFilter = '';
2783 if (d3fileoptions.filter) { fileFilter = Q(d3fileoptions.filter).value };
2784
2785 // Display all files and folders at this location
2786 for (var i in filetreexx) {
2787 // Figure out the name and shortname
2788 var f = filetreexx[i], name = f.n, shortname;
2789
2790 // Filter out files
2791 if ((f.t == 3) && (fileFilter != '') && (f.nx.toLowerCase().endsWith(fileFilter) == false)) { continue; }
2792 // if (name.length > 70) { shortname = '<span title="' + EscapeHtml(name) + '">' + EscapeHtml(name.substring(0, 70)) + ("..." + '</span>'); } else { shortname = EscapeHtml(name); }
2793 // Removed redundant filename length check because we handle it in the CSS
2794 shortname = EscapeHtml(name);
2795
2796 // Figure out the size
2797 var fsize = '';
2798 if (f.s != null) { fsize = getFileSizeStr(f.s); }
2799
2800 var h = '';
2801 if (f.t != 3) {
2802 var title = '';
2803 h = '<div class=filelist file=999><span style=float:right title="' + title + '"></span><span title="' + shortname + '"><div class=fileIcon' + f.t + ' onclick=d3folderset("' + encodeURIComponentEx(f.nx) + '")></div>&nbsp;<a href=# style=cursor:pointer onclick=\'return d3folderset("' + encodeURIComponentEx(f.nx) + '")\'>' + shortname + '</a></span></div>';
2804 } else {
2805 var link = shortname;
2806 //if (f.s > 0) { link = "<a rel=\"noreferrer noopener\" target=\"_blank\" href=\"downloadfile.ashx?link=" + encodeURIComponentEx(filetreelinkpath + '/' + f.nx) + "\">" + shortname + "</a>"; }
2807 h = '<div class=filelist file=3><input style=float:left name=fcx class=fcb type=checkbox onchange=d3setActions() value="' + f.nx + '">&nbsp;<span style=float:right>' + EscapeHtml(fsize) + '</span><span title="' + shortname + '"><div class=fileIcon' + f.t + '></div>' + link + '</span></div>';
2808 }
2809
2810 if (f.t < 3) { html1 += h; } else { html2 += h; }
2811 }
2812
2813 if (d3fileoptions.currentFolder) { QH(d3fileoptions.currentFolder, lastFolderName); }
2814 QH(d3fileoptions.files, html1 + html2);
2815 QE(d3fileoptions.folderup, d3filetreelocation.length > 0);
2816 if (d3fileoptions.func) { d3fileoptions.func(); }
2817 }
2818
2819 function d3folderset(x) { d3filetreelocation.push(decodeURIComponent(x)); d3updatefiles(); return false; }
2820 function d3folderup(x) { if (x == null) { d3filetreelocation.pop(); } else { while (d3filetreelocation.length > x) { d3filetreelocation.pop(); } } d3updatefiles(); }
2821 function d3getFileSel() { var cc = []; var checkboxes = document.getElementsByName('fcx'); for (var i = 0; i < checkboxes.length; i++) { if (checkboxes[i].checked) { cc.push(checkboxes[i].value) } } return cc; }
2822 function d3setActions() {
2823 if (d3fileoptions.dialog == 1) {
2824 var mode = Q('d3uploadMode').value;
2825 if (mode == 1) {
2826 QE('idx_dlgOkButton', Q('d3localFile').value.length > 0);
2827 } else {
2828 QE('idx_dlgOkButton', d3getFileSel().length == 1);
2829 }
2830 } else if (d3fileoptions.dialog == 2) {
2831 QE('idx_dlgOkButton', d3getFileSel().length == 1);
2832 }
2833 }
2834
2835 //
2836 // MY FILES
2837 //
2838
2839 var filetreelinkpath;
2840 var filetreelocation = [];
2841
2842 function p5refreshFiles() { meshserver.send({ action: 'files' }); }
2843
2844 function updateFiles() {
2845 QV('MainMenuMyFiles', ((features & 8) == 0));
2846 if ((features & 8) != 0) return; // If running on a server without files, exit now.
2847 var html1 = '', html2 = '', displayPath = '<a style=cursor:pointer;color:black onclick=p5folderup(0)>' + "Root" + '</a>', fullPath = 'Root', publicPath, filetreex = filetree, folderdepth = 1;
2848
2849 // Navigate to path location, build the paths at the same time
2850 var filetreelocation2 = [], oldlinkpath = filetreelinkpath, checkedBoxes = [], checkboxes = document.getElementsByName('fc');
2851 for (var i = 0; i < checkboxes.length; i++) { if (checkboxes[i].checked) { checkedBoxes.push(checkboxes[i].value) }; } // Save all existing checked boxes
2852
2853 filetreelinkpath = '';
2854 for (var i in filetreelocation) {
2855 if ((filetreex.f != null) && (filetreex.f[filetreelocation[i]] != null)) {
2856 filetreelocation2.push(filetreelocation[i]);
2857 fullPath += ' / ' + filetreelocation[i];
2858 if ((folderdepth == 1)) {
2859 var sp = filetreelocation[i].split('/');
2860 publicPath = window.location.origin + domainUrl + sp[0] + 'files/' + sp[2];
2861 //if (filetreelocation[i] === userinfo._id) { filetreelinkpath += 'self'; } else { filetreelinkpath += (sp[0] + '/' + sp[2]); }
2862 filetreelinkpath += filetreelocation[i];
2863 } else {
2864 if (filetreelinkpath != '') { filetreelinkpath += '/' + filetreelocation[i]; if (folderdepth > 2) { publicPath += '/' + filetreelocation[i]; } }
2865 }
2866 filetreex = filetreex.f[filetreelocation[i]];
2867 displayPath += ' / <a style=cursor:pointer;color:black onclick=p5folderup(' + folderdepth + ')>' + EscapeHtml(filetreex.n != null ? filetreex.n : filetreelocation[i]) + '</a>';
2868 folderdepth++;
2869 } else {
2870 break;
2871 }
2872 }
2873 filetreelocation = filetreelocation2; // In case we could not go down the full path, we set the new path location here.
2874 var publicfolder = fullPath.toLowerCase().startsWith('root / ' + userinfo._id + ' / public');
2875
2876 // Sort the files
2877 var filetreexx = p5sort_files(filetreex.f);
2878
2879 // Display all files and folders at this location
2880 for (var i in filetreexx) {
2881 // Figure out the name and shortname
2882 var f = filetreexx[i], name = f.n, shortname;
2883 // if (name.length > 40) { shortname = EscapeHtml(name.substring(0, 40)) + "..."; } else { shortname = EscapeHtml(name); }
2884 // Removed redundant filename length check because we handle it in the CSS
2885 shortname = EscapeHtml(name);
2886
2887 // Figure out the date
2888 //var fdatestr = '';
2889 //if (f.d != null) { var fdate = new Date(f.d), fdatestr = (fdate.getMonth() + 1) + '/' + (fdate.getDate()) + '/' + fdate.getFullYear() + ' ' + printTime(fdate) + '&nbsp;'; }
2890
2891 // Figure out the size
2892 var fsize = '';
2893 if (f.s != null) { fsize = getFileSizeStr(f.s); }
2894
2895 var h = '';
2896 if (f.t < 3 || f.t == 4) {
2897 var right = (f.t == 1 || f.t == 4) ? p5getQuotabar(f) : '';
2898 h = '<div class=filelist file=999><input file=999 style=float:left name=fc class=fcb type=checkbox onchange=p5setActions() value=\'' + EscapeHtml(name) + '\'>&nbsp;<span style=float:right;padding-right:4px>' + right + '</span><span title="' + shortname + '"><div class=fileIcon' + f.t + '></div><a style=cursor:pointer onclick=p5folderset("' + encodeURIComponent(f.nx) + '")>' + shortname + '</a></span></div>';
2899 } else {
2900 var link = shortname;
2901 var publiclink = '';
2902 if (publicfolder) { publiclink = ' (<a style=cursor:pointer onclick=\'p5showPublicLink("' + publicPath + '/' + f.nx + '")\'>' + "Link" + '</a>)'; }
2903 if (f.s > 0) { link = '<a rel=\"noreferrer noopener\" target=\"_blank\" href=\"downloadfile.ashx?link=' + encodeURIComponent(filetreelinkpath + '/' + f.nx) + '\">' + shortname + '</a>' + publiclink; }
2904 h = '<div class=filelist file=3><input file=3 style=float:left name=fc class=fcb type=checkbox onchange=p5setActions() value=\'' + f.nx + '\'>&nbsp;<span style=float:right;padding-right:4px>' + EscapeHtml(fsize) + '</span><span title="' + shortname + '"><div class=fileIcon' + f.t + '></div>' + link + '</span></div>';
2905 }
2906
2907 if (f.t < 3) { html1 += h; } else { html2 += h; }
2908 }
2909
2910 //if (f.parent == null) { }
2911 QH('p5rightOfButtons', p5getQuotabar(filetreex));
2912
2913 QH('p5files', html1 + html2);
2914 QH('p5currentpath', displayPath);
2915 QE('p5FolderUp', filetreelocation.length != 0);
2916 QV('p5PublicShare', publicfolder);
2917
2918 // Re-check all boxes if needed
2919 if (oldlinkpath == filetreelinkpath) {
2920 checkboxes = document.getElementsByName('fc');
2921 for (var i = 0; i < checkboxes.length; i++) {
2922 checkboxes[i].checked = (checkedBoxes.indexOf(checkboxes[i].value) >= 0);
2923 }
2924 }
2925
2926 p5setActions();
2927 }
2928
2929 function getNiceSize(bytes) {
2930 if (bytes <= 0) return "Storage exceed";
2931 if (bytes < 2048) return format("{0}b left", bytes);
2932 if (bytes < 2097152) return format("{0}k left", Math.round(bytes / 1024));
2933 if (bytes < 2147483648) return format("{0}m left", Math.round(bytes / 1024 / 1024));
2934 return format("{0}g left", Math.round(bytes / 1024 / 1024 / 1024));
2935 }
2936
2937 function getNetworkSpeed(bitsPerSecond) {
2938 if (bitsPerSecond <= 0) return "0 bps";
2939 if (bitsPerSecond < 1000) return format("{0} bps", bitsPerSecond);
2940 if (bitsPerSecond < 1000000) return format("{0} Kbps", Math.round(bitsPerSecond / 1000));
2941 if (bitsPerSecond < 1000000000) return format("{0} Mbps", Math.round(bitsPerSecond / 1000000));
2942 return format("{0} Gbps", (bitsPerSecond / 1000000000).toFixed(1));
2943 }
2944
2945 function p5getQuotabar(f) {
2946 while (f.t > 1 && f.t != 4) { f = f.parent; }
2947 if ((f.t != 1 && f.t != 4) || (f.maxbytes == null)) return '';
2948 return getNiceSize(f.maxbytes - f.s) + ' <progress style=height:10px;width:100px value=' + f.s + ' max=' + f.maxbytes + ' />';
2949 }
2950
2951 function p5showPublicLink(u) { setDialogMode(2, "Public Link", 1, null, '<input type=text style=width:100% value="' + u + '" readonly />'); }
2952
2953 var sortorder;
2954 function p5sort_filename(a, b) { if (a.ln > b.ln) return (1 * sortorder); if (a.ln < b.ln) return (-1 * sortorder); return 0; }
2955 function p5sort_timestamp(a, b) { if (a.d > b.d) return (1 * sortorder); if (a.d < b.d) return (-1 * sortorder); return 0; }
2956 function p5sort_bysize(a, b) { if (a.s == b.s) return p5sort_filename(a, b); return (((a.s - b.s)) * sortorder); }
2957
2958 function p5sort_files(files) {
2959 var r = [], sortselection = Q('p5sortdropdown').value;
2960 for (var i in files) { files[i].nx = i; if (files[i].n == null) { files[i].n = i; } files[i].ln = files[i].n.toLowerCase(); r.push(files[i]); }
2961 sortorder = 1;
2962 if (sortselection > 3) { sortorder = -1; sortselection -= 3; }
2963 if (sortselection == 1) { r.sort(p5sort_filename); }
2964 else if (sortselection == 2) { r.sort(p5sort_bysize); }
2965 else if (sortselection == 3) { r.sort(p5sort_timestamp); }
2966 return r;
2967 }
2968
2969 function p5setActions() {
2970 var cc = getFileSelCount(), tc = getFileCount(), sfc = getFileSelCount(false); // In order: number of entires selected, number of total entries, number of selected entires that are files (not folders)
2971 QE('p5DeleteFileButton', (cc > 0) && (filetreelocation.length > 0));
2972 QE('p5NewFolderButton', filetreelocation.length > 0);
2973 QE('p5UploadButton', filetreelocation.length > 0);
2974 QE('p5RenameFileButton', (cc == 1) && (filetreelocation.length > 0));
2975 QE('p5SelectAllButton', tc > 0);
2976 Q('p5SelectAllButton').value = (cc > 0 ? "None" : "All");
2977 QE('p5CutButton', (sfc > 0) && (cc == sfc));
2978 QE('p5CopyButton', (sfc > 0) && (cc == sfc));
2979 QE('p5PasteButton', (p5clipboard != null) && (p5clipboard.length > 0) && (filetreelocation.length > 0));
2980 }
2981
2982 function getFileSelCount(includeDirs) { var cc = 0, checkboxes = document.getElementsByName('fc'); for (var i = 0; i < checkboxes.length; i++) { if ((checkboxes[i].checked) && ((includeDirs != false) || (checkboxes[i].attributes.file.value == '3'))) cc++; } return cc; }
2983 function getFileSelDirCount() { var cc = 0, checkboxes = document.getElementsByName('fc'); for (var i = 0; i < checkboxes.length; i++) { if ((checkboxes[i].checked) && (checkboxes[i].attributes.file.value == '999')) cc++; } return cc; }
2984 function getFileCount() { var cc = 0; var checkboxes = document.getElementsByName('fc'); return checkboxes.length; }
2985 function p5selectallfile() { var nv = (getFileSelCount() == 0), checkboxes = document.getElementsByName('fc'); for (var i = 0; i < checkboxes.length; i++) { checkboxes[i].checked = nv; } p5setActions(); }
2986 function setupBackPointers(x) { if (x.f != null) { var fs = 0, fc = 0; for (var i in x.f) { setupBackPointers(x.f[i]); x.f[i].parent = x; if (x.f[i].s) { fs += x.f[i].s; } if (x.f[i].c) { fc += x.f[i].c; } if (x.f[i].t == 3) { fc++; } } x.s = fs; x.c = fc; } return x; }
2987 function getFileSizeStr(size) { if (size == 1) return "1 byte"; return format("{0} bytes", size); }
2988 function p5folderup(x) { if (x == null) { filetreelocation.pop(); } else { while (filetreelocation.length > x) { filetreelocation.pop(); } } updateFiles(); return false; }
2989 function p5folderset(x) { filetreelocation.push(decodeURIComponent(x)); updateFiles(); return false; }
2990 function p5createfolder() { setDialogMode(2, "New Folder", 3, p5createfolderEx, '<input type=text id=p5renameinput maxlength=64 onkeyup=p5fileNameCheck(event) style=width:100% />'); focusTextBox('p5renameinput'); p5fileNameCheck(); }
2991 function p5createfolderEx() { meshserver.send({ action: 'fileoperation', fileop: 'createfolder', path: filetreelocation, newfolder: Q('p5renameinput').value }); }
2992 function p5deletefile() { var cc = getFileSelCount(), rec = (getFileSelDirCount() > 0) ? '<br /><br /><label><input type=checkbox id=p5recdeleteinput>' + "Recursive delete" + '</label><br>' : '<input type=checkbox id=p5recdeleteinput style=\'display:none\'>'; setDialogMode(2, "Delete", 3, p5deletefileEx, (cc > 1) ? (format("Delete {0} selected items?", cc) + rec) : ("Delete selected item?" + rec)); }
2993 function p5deletefileEx() { var delfiles = [], checkboxes = document.getElementsByName('fc'); for (var i = 0; i < checkboxes.length; i++) { if (checkboxes[i].checked) { delfiles.push(checkboxes[i].value); } } meshserver.send({ action: 'fileoperation', fileop: 'delete', path: filetreelocation, delfiles: delfiles, rec: Q('p5recdeleteinput').checked }); }
2994 function p5renamefile() { var renamefile, checkboxes = document.getElementsByName('fc'); for (var i = 0; i < checkboxes.length; i++) { if (checkboxes[i].checked) { renamefile = checkboxes[i].value; } } setDialogMode(2, "Rename", 3, p5renamefileEx, '<input type=text id=p5renameinput maxlength=64 onkeyup=p5fileNameCheck(event) style=width:100% value="' + renamefile + '" />', { action: 'fileoperation', fileop: 'rename', path: filetreelocation, oldname: renamefile }); focusTextBox('p5renameinput'); p5fileNameCheck(); }
2995 function p5renamefileEx(b, t) { t.newname = Q('p5renameinput').value; meshserver.send(t); }
2996 function p5fileNameCheck(e) { var x = isFilenameValid(Q('p5renameinput').value); QE('idx_dlgOkButton', x); if ((x == true) && (e && e.keyCode == 13)) { dialogclose(1); } }
2997 var isFilenameValid = (function () { var x1 = /^[^\\/:\*\?"<>\|]+$/, x2 = /^\./, x3 = /^(nul|prn|con|lpt[0-9]|com[0-9])(\.|$)/i; return function isFilenameValid(fname) { return x1.test(fname) && !x2.test(fname) && !x3.test(fname) && (fname[0] != '.'); } })();
2998 function p5uploadFile() { setDialogMode(2, "Upload File", 3, p5uploadFileEx, '<form method=post enctype=multipart/form-data action=uploadfile.ashx target=fileUploadFrame><input type=text name=link style=display:none id=p5uploadpath value="' + encodeURIComponent(filetreelinkpath) + '" /><input type=file name=files id=p5uploadinput style=width:100% multiple=multiple onchange="updateUploadDialogOk(\'p5uploadinput\')" /><input type=hidden name=authCookie value=' + authCookie + ' /><input type=submit id=p5loginSubmit style=display:none /></form>'); updateUploadDialogOk('p5uploadinput'); }
2999 function p5uploadFileEx() { Q('p5loginSubmit').click(); }
3000 function updateUploadDialogOk(x) { QE('idx_dlgOkButton', Q(x).value != ''); }
3001
3002 var p5clipboard = null, p5clipboardFolder = null, p5clipboardCut = 0;
3003 function p5copyFile(cut) { var checkboxes = document.getElementsByName('fc'); p5clipboard = []; p5clipboardCut = cut, p5clipboardFolder = Clone(filetreelocation); for (var i = 0; i < checkboxes.length; i++) { if ((checkboxes[i].checked) && (checkboxes[i].attributes.file.value == '3')) { p5clipboard.push(checkboxes[i].value); } } p5updateClipview(); }
3004 function p5pasteFile() { var x = ''; if ((p5clipboard != null) && (p5clipboard.length > 0)) { x = format("Confirm {0} of {1} entrie{2} to this location?", (p5clipboardCut == 0 ? 'copy' : 'move'), p5clipboard.length, ((p5clipboard.length > 1) ? 's' : '')) } setDialogMode(2, "Paste", 3, p5pasteFileEx, x); }
3005 function p5pasteFileEx() { meshserver.send({ action: 'fileoperation', fileop: (p5clipboardCut == 0 ? 'copy' : 'move'), scpath: p5clipboardFolder, path: filetreelocation, names: p5clipboard }); p5folderup(999); if (p5clipboardCut == 1) { p5clipboard = null, p5clipboardFolder = null, p5clipboardCut = 0; p5updateClipview(); } }
3006 function p5updateClipview() { var x = ''; if ((p5clipboard != null) && (p5clipboard.length > 0)) { x = format("Holding {0} entrie{1} for {2}", p5clipboard.length, ((p5clipboard.length > 1) ? 's' : ''), (p5clipboardCut == 0 ? "copy" : "move")) + ', <a href=# onclick="return p5clearClip()" style=cursor:pointer>' + "Clear" + '</a>.' } QH('p5bottomstatus', x); p5setActions(); }
3007 function p5clearClip() { p5clipboard = null; p5clipboardFolder = null; p5clipboardCut = 0; p5updateClipview(); return false; }
3008
3009 function p5fileDragDrop(e) {
3010 haltEvent(e);
3011 QV('bigfail', false);
3012 QV('bigok', false);
3013 //QV('p5fileCatchAllInput', false);
3014 if (e.dataTransfer == null || e.dataTransfer.files.length == 0 || filetreelocation.length == 0) return;
3015 var names = [], sizes = [], types = [], datas = [], readercount = e.dataTransfer.files.length;
3016 for (var i = 0; i < e.dataTransfer.files.length; i++) {
3017 var reader = new FileReader(), file = e.dataTransfer.files[i];
3018 names.push(file.name);
3019 sizes.push(file.size);
3020 types.push(file.type);
3021 reader.onload = function (event) {
3022 datas.push(event.target.result);
3023 if (--readercount == 0) {
3024 Q('p5fileDragName').value = names.join('*');
3025 Q('p5fileDragSize').value = sizes.join('*');
3026 Q('p5fileDragType').value = types.join('*');
3027 Q('p5fileDragData').value = datas.join('*');
3028 Q('p5fileDragLink').value = encodeURIComponent(filetreelinkpath);
3029 Q('p5loginSubmit2').click();
3030 }
3031 }
3032 reader.readAsDataURL(file);
3033 }
3034 }
3035
3036 var p5dragtimer = null;
3037 function p5fileDragOver(e) {
3038 haltEvent(e);
3039 if (p5dragtimer != null) { clearTimeout(p5dragtimer); p5dragtimer = null; }
3040 var ac = true; // TODO: Set to true if we can accept the file
3041 if (filetreelocation.length == 0) { ac = false; }
3042 QV('bigok', ac);
3043 QV('bigfail', !ac);
3044 //QV('p5fileCatchAllInput', ac);
3045 }
3046
3047 function p5fileDragLeave(e) {
3048 haltEvent(e);
3049 if (e.target.id != 'p5filetable') {
3050 QV('bigfail', false);
3051 QV('bigok', false);
3052 //QV('p5fileCatchAllInput', false);
3053 } else {
3054 p5dragtimer = setTimeout('QV(\'bigfail\',false);QV(\'bigok\',false);p5dragtimer=null;', 200);
3055 }
3056 }
3057
3058 //
3059 // MY DEVICES
3060 //
3061
3062 function onRealNameCheckBox() {
3063 showRealNames = Q('RealNameCheckBox').checked;
3064 putstore('showRealNames', showRealNames ? 1 : 0);
3065 mainUpdate(5);
3066 }
3067
3068 function onOnlineCheckBox(e) {
3069 putstore('onlineOnly', Q('OnlineCheckBox').checked ? 1 : 0);
3070 onSearchInputChanged();
3071 }
3072
3073 function updateDevicePageState() {
3074 if ((devicePagingState == null) || (devicePagingState.total <= devicePagingState.limit)) {
3075 QV('devViewPageState', false);
3076 QV('devViewPageButton2', false);
3077 QV('devViewPageButton3', false);
3078 } else {
3079 var currentPage = Math.floor((devicePagingState.skip + devicePagingState.limit) / devicePagingState.limit);
3080 var maxPage = Math.ceil(devicePagingState.total / devicePagingState.limit);
3081 QV('devViewPageState', true);
3082 QV('devViewPageButton2', true);
3083 QV('devViewPageButton3', true);
3084 QH('devViewPageState', currentPage + '/' + maxPage);
3085 }
3086 }
3087
3088 function onDeviceViewPageChange(i) {
3089 if (devicePagingState == null) return;
3090 var currentPage = (Math.floor((devicePagingState.skip + devicePagingState.limit) / devicePagingState.limit));
3091 var maxPage = Math.ceil(devicePagingState.total / devicePagingState.limit);
3092 switch (i) {
3093 case 2: { if (currentPage > 1) meshserver.send({ action: 'nodes', skip: (currentPage - 2) * devicePagingState.limit }); break; } // Goto previous page
3094 case 3: { if (currentPage < maxPage) meshserver.send({ action: 'nodes', skip: currentPage * devicePagingState.limit }); break; } // Goto next page
3095 }
3096 }
3097
3098 function onDeviceSearchChanged(e) {
3099 setTimeout(function () { onSearchInputChanged(); }, 10);
3100 }
3101
3102 function clearSearchInput() {
3103 Q('SearchInput').value = '';
3104 Q('OnlineCheckBox').checked = false;
3105 onSearchInputChanged();
3106 }
3107
3108 function onSearchInputChanged() {
3109 var x = Q('SearchInput').value.toLowerCase().trim(); putstore('_search', Q('SearchInput').value);
3110 QS('SearchInput')['background-color'] = (x == '') ? '#FFFFFF' : '#FDFFBE';
3111 var negate = x.startsWith('!');
3112 if (negate) x = x.substring(1);
3113 var rawVal = Q('SearchInput').value.trim();
3114 if (negate) rawVal = rawVal.substring(1);
3115 var userSearch = null, ipSearch = null, groupSearch = null, tagSearch = null, agentTagSearch = null, wscSearch = null, osSearch = null, amtSearch = null;
3116 if (x.startsWith('user:'.toLowerCase())) { userSearch = x.substring('user:'.length); }
3117 else if (x.startsWith('u:'.toLowerCase())) { userSearch = x.substring('u:'.length); }
3118 else if (x.startsWith('ip:'.toLowerCase())) { ipSearch = x.substring('ip:'.length); }
3119 else if (x.startsWith('group:'.toLowerCase())) { groupSearch = x.substring('group:'.length); }
3120 else if (x.startsWith('g:'.toLowerCase())) { groupSearch = x.substring('g:'.length); }
3121 else if (x.startsWith('tag:'.toLowerCase())) { tagSearch = rawVal.substring('tag:'.length); }
3122 else if (x.startsWith('t:'.toLowerCase())) { tagSearch = rawVal.substring('t:'.length); }
3123 else if (x.startsWith('atag:'.toLowerCase())) { agentTagSearch = rawVal.substring('atag:'.length).toLowerCase(); }
3124 else if (x.startsWith('a:'.toLowerCase())) { agentTagSearch = rawVal.substring('a:'.length).toLowerCase(); }
3125 else if (x.startsWith('os:'.toLowerCase())) { osSearch = rawVal.substring('os:'.length).toLowerCase(); }
3126 else if (x.startsWith('amt:'.toLowerCase())) { amtSearch = rawVal.substring('amt:'.length).toLowerCase(); }
3127 else if (x == 'wsc:ok') { wscSearch = 1; }
3128 else if (x == 'wsc:noav') { wscSearch = 2; }
3129 else if (x == 'wsc:noupdate') { wscSearch = 3; }
3130 else if (x == 'wsc:nofirewall') { wscSearch = 4; }
3131 else if (x == 'wsc:any') { wscSearch = 5; }
3132
3133 if (x == '') {
3134 // No search
3135 for (var d in nodes) { nodes[d].v = true; }
3136 } else if (ipSearch != null) {
3137 // IP address search
3138 for (var d in nodes) { nodes[d].v = ((nodes[d].ip != null) && (nodes[d].ip.indexOf(ipSearch) >= 0)); }
3139 } else if (groupSearch != null) {
3140 // Group filter
3141 for (var d in nodes) { nodes[d].v = (meshes[nodes[d].meshid].name.toLowerCase().indexOf(groupSearch) >= 0); }
3142 } else if (tagSearch != null) {
3143 // Tag filter
3144 for (var d in nodes) {
3145 nodes[d].v = ((nodes[d].tags == null) && (tagSearch == '')) || ((nodes[d].tags != null) && (nodes[d].tags.indexOf(tagSearch) >= 0));
3146 }
3147 } else if (agentTagSearch != null) {
3148 // Agent Tag filter
3149 for (var d in nodes) {
3150 nodes[d].v = (((nodes[d].agent != null) && (nodes[d].agent.tag == null)) && (agentTagSearch == '')) || ((nodes[d].agent != null) && (nodes[d].agent.tag != null) && (nodes[d].agent.tag.toLowerCase().indexOf(agentTagSearch) >= 0));
3151 }
3152 } else if (userSearch != null) {
3153 // User search
3154 for (var d in nodes) {
3155 nodes[d].v = false;
3156 if (nodes[d].users && nodes[d].users.length > 0) { for (var i in nodes[d].users) { if (nodes[d].users[i].toLowerCase().indexOf(userSearch) >= 0) { nodes[d].v = true; } } }
3157 }
3158 } else if (osSearch != null) {
3159 // OS search
3160 for (var d in nodes) { nodes[d].v = ((nodes[d].osdesc != null) && (nodes[d].osdesc.toLowerCase().indexOf(osSearch) >= 0)); }
3161 } else if (amtSearch != null) {
3162 // Intel AMT search
3163 for (var d in nodes) { nodes[d].v = (nodes[d].intelamt != null) && ((amtSearch == '') || (nodes[d].intelamt.state == amtSearch)); }
3164 } else if (wscSearch != null) {
3165 // Windows Security Center
3166 for (var d in nodes) {
3167 nodes[d].v = false;
3168 if (nodes[d].wsc) {
3169 if ((wscSearch == 1) && (nodes[d].wsc.antiVirus == 'OK') && (nodes[d].wsc.autoUpdate == 'OK') && (nodes[d].wsc.firewall == 'OK')) { nodes[d].v = true; }
3170 else if (((wscSearch == 2) || (wscSearch == 5)) && (nodes[d].wsc.antiVirus != 'OK')) { nodes[d].v = true; }
3171 else if (((wscSearch == 3) || (wscSearch == 5)) && (nodes[d].wsc.autoUpdate != 'OK')) { nodes[d].v = true; }
3172 else if (((wscSearch == 4) || (wscSearch == 5)) && (nodes[d].wsc.firewall != 'OK')) { nodes[d].v = true; }
3173 }
3174 }
3175 } else if (x == '*') {
3176 // Star filter
3177 for (var d in nodes) { nodes[d].v = (stars[nodes[d]._id] == 1); }
3178 } else {
3179 // Device name search
3180 try {
3181 var rs = x.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'), rx = new RegExp(rs); // In some cases (like +), this can throw an exception.
3182 for (var d in nodes) {
3183 if (features2 & 0x00008000) {
3184 if(features2 & 0x10000000){
3185 nodes[d].v = (rx.test(nodes[d].name.toLowerCase())) || (rx.test(meshes[nodes[d].meshid].name.toLowerCase())) || ((nodes[d].rnamel != null) && rx.test(nodes[d].rnamel.toLowerCase()));
3186 }else {
3187 nodes[d].v = (rx.test(nodes[d].name.toLowerCase())) || ((nodes[d].rnamel != null) && rx.test(nodes[d].rnamel.toLowerCase()));
3188 }
3189 } else {
3190 if(features2 & 0x10000000){
3191 if (showRealNames) {
3192 nodes[d].v = (nodes[d].rnamel != null) && rx.test(nodes[d].rnamel.toLowerCase()) || (rx.test(meshes[nodes[d].meshid].name.toLowerCase()));
3193 } else {
3194 nodes[d].v = rx.test(nodes[d].name.toLowerCase()) || (rx.test(meshes[nodes[d].meshid].name.toLowerCase()));
3195 }
3196 }else{
3197 if (showRealNames) {
3198 nodes[d].v = (nodes[d].rnamel != null) && rx.test(nodes[d].rnamel.toLowerCase());
3199 } else {
3200 nodes[d].v = rx.test(nodes[d].name.toLowerCase());
3201 }
3202 }
3203 }
3204 }
3205 } catch (ex) { for (var d in nodes) { nodes[d].v = true; } }
3206 }
3207
3208 if (negate) { for (var d in nodes) { nodes[d].v = !nodes[d].v; } }
3209
3210 // Check power state
3211 var onlineOnly = Q('OnlineCheckBox').checked;
3212 if (onlineOnly) { for (var d in nodes) { if ((nodes[d].conn == null) || (nodes[d].conn == 0)) { nodes[d].v = false; } } }
3213 mainUpdate(4);
3214 }
3215
3216 var gotKeyPressEvent = false;
3217 function ondeskkeypress(e, t) {
3218 setSessionActivity();
3219 if (desktop && !xxdialogMode && (xxcurrentView == 10) && (currentDevicePanel == 1)) {
3220 gotKeyPressEvent = true;
3221 Q('softKeyboard').value = '';
3222 // Check what keys we are allows to send
3223 if (currentNode != null) {
3224 var meshrights = GetMeshRights(currentNode.meshid);
3225 var inputAllowed = ((features2 & 0x2000) == 0) && ((meshrights == 0xFFFFFFFF) || (((meshrights & 8) != 0) && ((meshrights & 256) == 0)));
3226 if (inputAllowed == false) return false;
3227 var limitedInputAllowed = ((meshrights != 0xFFFFFFFF) && (((meshrights & 8) != 0) && ((meshrights & 256) == 0) && ((meshrights & 4096) != 0)));
3228 if (limitedInputAllowed == true) { if ((e.altKey == true) || (e.ctrlKey == true) || ((e.keyCode < 32) && (e.keyCode != 8) && (e.keyCode != 13)) || (e.keyCode > 90)) return false; }
3229 }
3230 return desktop.m.handleKeys(e);
3231 }
3232 if (terminal && !xxdialogMode && (xxcurrentView == 10) && (currentDevicePanel == 5) && (t !== 1)) {
3233 if (e.altKey == true) { return true; }
3234 gotKeyPressEvent = true;
3235 Q('softKeyboard').value = '';
3236 var k = 0;
3237 if (e.charCode != 0) { k = e.charCode; } else if (e.keyCode != 0) { k = e.keyCode; }
3238 if (k != 0) {
3239 if (terminal.urlname == 'sshterminalrelay.ashx') {
3240 // SSH
3241 terminal.socket.send('~' + String.fromCharCode(k));
3242 } else {
3243 // Agent
3244 terminal.sendText(String.fromCharCode(k));
3245 }
3246 }
3247 return false;
3248 }
3249 }
3250
3251 function ondeskkeydown(e, t) {
3252 setSessionActivity();
3253 if (desktop && !xxdialogMode && (xxcurrentView == 10) && (currentDevicePanel == 1)) {
3254 gotKeyPressEvent = false;
3255 Q('softKeyboard').value = '';
3256 // Check what keys we are allows to send
3257 if (currentNode != null) {
3258 var meshrights = GetMeshRights(currentNode.meshid);
3259 var inputAllowed = ((features2 & 0x2000) == 0) && ((meshrights == 0xFFFFFFFF) || (((meshrights & 8) != 0) && ((meshrights & 256) == 0)));
3260 if (inputAllowed == false) return false;
3261 var limitedInputAllowed = ((meshrights != 0xFFFFFFFF) && (((meshrights & 8) != 0) && ((meshrights & 256) == 0) && ((meshrights & 4096) != 0)));
3262 if (limitedInputAllowed == true) { if ((e.altKey == true) || (e.ctrlKey == true) || ((e.keyCode < 32) && (e.keyCode != 8) && (e.keyCode != 13)) || (e.keyCode > 90)) return false; }
3263 }
3264 return desktop.m.handleKeyDown(e);
3265 }
3266 if (terminal && !xxdialogMode && (xxcurrentView == 10) && (currentDevicePanel == 5) && (t !== 1)) {
3267 if (e.altKey == true) { return true; }
3268 Q('softKeyboard').value = '';
3269 gotKeyPressEvent = false;
3270 var k = 0;
3271 if (e.charCode != 0) { k = e.charCode; } else if (e.keyCode != 0) { k = e.keyCode; }
3272 if (k == 8) { // Enter and backspace
3273 if (terminal.urlname == 'sshterminalrelay.ashx') {
3274 // SSH
3275 terminal.socket.send('~' + String.fromCharCode(k));
3276 } else {
3277 // Agent
3278 terminal.sendText(String.fromCharCode(k));
3279 }
3280 }
3281 else if (e.ctrlKey && (k >= 64) && (k <= 95)) {
3282 // Ctrl keys
3283 if (terminal.urlname == 'sshterminalrelay.ashx') {
3284 // SSH
3285 terminal.socket.send('~' + String.fromCharCode(k - 64));
3286 } else {
3287 // Agent
3288 terminal.sendText(String.fromCharCode(k - 64));
3289 }
3290 }
3291 }
3292 }
3293
3294 function ondeskkeyup(e, t) {
3295 setSessionActivity();
3296 if (desktop && !xxdialogMode && (xxcurrentView == 10) && (currentDevicePanel == 1)) {
3297 var inputStr = Q('softKeyboard').value;
3298 Q('softKeyboard').value = '';
3299 // Check what keys we are allows to send
3300 if (currentNode != null) {
3301 var meshrights = GetMeshRights(currentNode.meshid);
3302 var inputAllowed = ((features2 & 0x2000) == 0) && ((meshrights == 0xFFFFFFFF) || (((meshrights & 8) != 0) && ((meshrights & 256) == 0)));
3303 if (inputAllowed == false) return false;
3304 var limitedInputAllowed = ((meshrights != 0xFFFFFFFF) && (((meshrights & 8) != 0) && ((meshrights & 256) == 0) && ((meshrights & 4096) != 0)));
3305 if (limitedInputAllowed == true) { if ((e.altKey == true) || (e.ctrlKey == true) || ((e.keyCode < 32) && (e.keyCode != 8) && (e.keyCode != 13)) || (e.keyCode > 90)) return false; }
3306 }
3307 if ((gotKeyPressEvent == false) && (inputStr.length > 0) && desktop.m.SendKeyUnicode) {
3308 // This is a mobile keyboard, we need to send that is in the input control.
3309 var inputchar = inputStr[inputStr.length - 1].charCodeAt(0);
3310 desktop.m.SendKeyUnicode(desktop.m.KeyAction.DOWN, inputchar);
3311 desktop.m.SendKeyUnicode(desktop.m.KeyAction.UP, inputchar);
3312 } else {
3313 return desktop.m.handleKeyUp(e);
3314 }
3315 }
3316 if (terminal && !xxdialogMode && (xxcurrentView == 10) && (currentDevicePanel == 5) && (gotKeyPressEvent == false) && (t !== 1)) {
3317 if (e.altKey == true) { return true; }
3318 var inputStr = Q('softKeyboard').value;
3319 Q('softKeyboard').value = '';
3320 if (terminal.urlname == 'sshterminalrelay.ashx') {
3321 // SSH
3322 terminal.socket.send('~' + inputStr);
3323 } else {
3324 // Agent
3325 if (inputStr)
3326 terminal.sendText(inputStr);
3327 }
3328 return false;
3329 }
3330 }
3331
3332 var sort = 0;
3333 var deviceHeaderId = 0;
3334 var deviceHeaderCount;
3335 var deviceHeaders = {};
3336 var showRealNames = false;
3337 var deviceHeaderTotal = 0;
3338 var deviceHeaders = {};
3339 var deviceHeadersTitles = {};
3340 function updateDevices() {
3341 var r = '', c = 0, current = null, count = 0, displayedMeshes = {}, groups = {}, groupCount = {};
3342
3343 // 3 wide, list view or desktop view
3344 deviceHeaderId = 0;
3345 deviceHeaderCount = {};
3346 deviceHeaderTotal = 0;
3347 deviceHeaders = {};
3348 deviceHeadersTitles = {};
3349 var current;
3350
3351 // Perform node sort
3352 if (sort == 0) { nodes.sort(meshSort); }
3353 else if (sort == 1) { nodes.sort(powerSort); }
3354 else if (sort == 2) { if (showRealNames == true) { nodes.sort(deviceHostSort); } else { nodes.sort(deviceSort); } }
3355
3356 // Go thru the list of nodes and display them
3357 for (var i in nodes) {
3358 if (nodes[i].v == false) continue;
3359 //var meshrights = GetNodeRights(nodes[i]);
3360
3361 if (sort == 0) {
3362 // Mesh header
3363 nodes.sort(meshSort);
3364 //if (nodes[i].meshid != current) {
3365 if (((meshes[nodes[i].meshid]?nodes[i].meshid:'*') != current)) {
3366 deviceHeaderSet();
3367 var extra = '';
3368 if ((meshes[nodes[i].meshid] != null) && (meshes[nodes[i].meshid].mtype == 1)) { extra = '<span style=color:lightgray>' + ", Intel&reg; AMT only" + '</span>'; }
3369 if (current != null) { if (c == 2) { r += '<td><div style=width:301px></div></td>'; } if (r != '') { r += '</tr></table>'; } }
3370 r += '<div class=DevSt style=padding-top:4px><span style=float:right>';
3371 //r += getMeshActions(meshes[nodes[i].meshid], meshrights);
3372
3373 if (meshes[nodes[i].meshid]) {
3374 r += '</span><span id=MxMESH style=cursor:pointer onclick=goForward("' + nodes[i].meshid + '")>' + EscapeHtml(meshes[nodes[i].meshid].name) + '</span>' + extra + '<span id=DevxHeader' + deviceHeaderId + ' style=color:lightgray></span></div>';
3375 current = nodes[i].meshid;
3376 } else {
3377 r += '</span><span id=MxMESH><i>' + "Individual Devices" + '</i></span><span id=DevxHeader' + deviceHeaderId + ' style=color:lightgray></span></div>';
3378 current = '*';
3379 }
3380
3381 displayedMeshes[current] = 1;
3382 c = 0;
3383 }
3384 } else if (sort == 1) {
3385 // Power header
3386 if (nodes[i].pwr !== current) {
3387 deviceHeaderSet();
3388 if (current !== null) { if (c == 2) { r += '<td><div style=width:301px></div></td>'; } if (r != '') { r += '</tr></table>'; } }
3389 r += '<div class=DevSt style=width:100%;padding-top:4px><span>' + PowerStateStr2(nodes[i].pwr) + '</span><span id=DevxHeader' + deviceHeaderId + ' style=color:lightgray></span></div>';
3390 current = nodes[i].pwr;
3391 c = 0;
3392 }
3393 } else if (sort == 2) {
3394 // Device header
3395 if (current == null) { current = '1'; }
3396 }
3397
3398 count++;
3399 r += '<div name=xxdevice onclick=goForward(\'' + nodes[i]._id + '\') class=devList1 id=\'' + nodes[i]._id + '\'></div>'; // This is a standin for the device, it gets rendered only if visible.
3400
3401 // If we are displaying devices by group, put the device in the right group.
3402 /*
3403 if ((sort == 3) && (r != '')) {
3404 if (nodes[i].tags) {
3405 for (var j in nodes[i].tags) {
3406 var tag = nodes[i].tags[j];
3407 if (groups[tag] == null) { groups[tag] = r; groupCount[tag] = 1; } else { groups[tag] += r; groupCount[tag] += 1; }
3408 if (view == 3) break;
3409 }
3410 }
3411 r = '';
3412 }
3413 */
3414
3415 deviceHeaderTotal++;
3416 if (typeof deviceHeaderCount[nodes[i].state] == 'undefined') { deviceHeaderCount[nodes[i].state] = 1; } else { deviceHeaderCount[nodes[i].state]++; }
3417 }
3418
3419 // If there is nothing to display, explain the problem
3420 var viewNothing = false;
3421 if ((r == '') && (nodes.length > 0) && (Q('SearchInput').value != '')) {
3422 viewNothing = true;
3423 r = '<div style="margin:30px">' + "No devices matching this search." + '</div>';
3424 }
3425
3426 // Display all empty device groups, we need to do this because users can add devices to these at any time.
3427 if ((sort == 0) && (Q('SearchInput').value == '')) {
3428 for (var i in meshes) {
3429 var mesh = meshes[i];
3430 if ((displayedMeshes[mesh._id] == null) && (IsMeshViewable(mesh))) {
3431 if ((current != '') && (r != '')) { r += '</tr></table>'; }
3432 r += '<div><div colspan=3 class=DevSt><span style=float:right>';
3433 //r += getMeshActions(mesh, meshrights);
3434 r += '</span><span id=MxMESH style=cursor:pointer onclick=goForward("' + mesh._id + '")>' + EscapeHtml(mesh.name) + '</span></div>';
3435 if (mesh.mtype == 1) { r += '<div style=padding:10px><i>' + "No Intel&reg; AMT devices in this group"; }
3436 if (mesh.mtype > 1) { r += '<div style=padding:10px><i>' + "No devices in this group"; }
3437 r += '.</i></div></div>';
3438 current = mesh._id;
3439 count++;
3440 }
3441 }
3442 }
3443
3444 if (count == 0) {
3445 if ((Q('SearchInput').value != '') || (Q('OnlineCheckBox').checked)) {
3446 QH('xdevices', '<div style="margin-top:50px;text-align:center"><span style="font-size:30px">' + "No devices" + '</span><br /><br />' + "No devices matching this search." + ' <a onclick=clearSearchInput() style=cursor:pointer>' + "Clear search filter" + '</a></div>');
3447 } else {
3448 QH('xdevices', '<div style="margin-top:50px;text-align:center"><span style="font-size:30px">' + "No devices" + '</span><br /><br />' + "Use the desktop version of this website to add devices." + '</div>');
3449 }
3450 } else {
3451 QH('xdevices', r);
3452 }
3453 deviceHeaderSet();
3454 for (var i in deviceHeaders) { QH(i, deviceHeaders[i]); }
3455 for (var i in deviceHeadersTitles) { Q(i).title = deviceHeadersTitles[i]; }
3456 onDevicesScrollEx();
3457 }
3458
3459 var onDevicesTouchActive = false;
3460 var onDevicesScrollnagleTimer = null;
3461 function onDevicesScroll() {
3462 if (onDevicesScrollnagleTimer == null) { onDevicesScrollnagleTimer = setTimeout(onDevicesScrollEx, 250); }
3463 }
3464
3465 function onDeviceTouch(x) {
3466 if (onDevicesTouchActive == x) return;
3467 onDevicesTouchActive = x;
3468 if (x == false) onDevicesScrollEx();
3469 }
3470
3471 function onDevicesScrollEx() {
3472 var devdivs = document.getElementsByName('xxdevice');
3473 onDevicesScrollnagleTimer = null;
3474 for (var i = 0; i < devdivs.length; i++) {
3475 // Show
3476 var node = getNodeFromId(devdivs[i].id)
3477 if (node == null) break;
3478 updateDeviceViewHtml(devdivs[i], node);
3479 }
3480 }
3481
3482 // Update a single device in the current view
3483 function updateDeviceViewDevice(node) {
3484 if (node == null) return;
3485 var devdiv = Q(node._id);
3486 if ((devdiv != null) && (devdiv.innerHTML != '')) { updateDeviceViewHtml(devdiv, node); } // Only update if the device is visible
3487 }
3488
3489 function updateDeviceViewHtml(div, node) {
3490 var visibleTop = Q('xdevices').scrollTop - 250, visibleBottom = Q('xdevices').scrollTop + Q('xdevices').clientHeight + 250;
3491 if ((div.offsetTop >= visibleTop) && (div.offsetTop < visibleBottom)) {
3492 var title = EscapeHtml(node.name);
3493 if (title.length == 0) { title = '<i>' + "None" + '</i>'; }
3494 if ((node.rname != null) && (node.rname.length > 0)) { title += ' / ' + EscapeHtml(node.rname); }
3495 var name = EscapeHtml(node.name);
3496 if (showRealNames == true && node.rname != null) name = EscapeHtml(node.rname);
3497 if (name.length == 0) { name = '<i>' + "None" + '</i>'; }
3498
3499 // Add device notification icons
3500 var devNotify = '', devNotifySub = '';
3501
3502 // This device is "starred"
3503 if (stars[node._id] == 1) {
3504 devNotifySub += '<img class=deviceNotifyDotSub src=images/icon-star-notify-16.png width=16 height=16>';
3505 }
3506
3507 // This device has session information
3508 if (node.sessions != null) {
3509 // Display any agent messages
3510 if (node.sessions.msg != null) {
3511 devNotifySub += '<div style="width:16;height:16" class=deviceNotifyDotSub>' + Object.keys(node.sessions.msg).length + '</div>';
3512 }
3513
3514 // Sessions are active
3515 if ((node.sessions.kvm != null) || (node.sessions.terminal != null) || (node.sessions.files != null) || (node.sessions.tcp != null) || (node.sessions.udp != null)) {
3516 devNotifySub += '<img class=deviceNotifyDotSub src=images/icon-relay-notify.png width=16 height=16>';
3517 }
3518
3519 // Help is required
3520 if (node.sessions.help != null) {
3521 devNotifySub += '<img class=deviceNotifyDotSub src=images/icon-help-notify-16.png width=16 height=16>';
3522 }
3523
3524 // Battery state
3525 if (node.sessions.battery != null) {
3526 var bat = node.sessions.battery;
3527 var statestr = '';
3528 if (bat.state == 'ac') { statestr = "Device is plugged-in"; }
3529 else if (bat.state == 'dc') { statestr = "Device is battery powered"; }
3530
3531 var levelstr = '', levelnum = -1;
3532 if ((typeof bat.level == 'number') && (bat.level >= 0) && (bat.level <= 100)) {
3533 levelstr = bat.level + '%';
3534 levelnum = (Math.floor((bat.level + 10) / 25) + 1);
3535 if (levelnum > 5) { lvl = 5; }
3536 if (bat.state == 'ac') { if (bat.level == 100) { levelnum = 11; } else { levelnum += 5; } }
3537 }
3538
3539 if (levelnum > 0) {
3540 devNotify += '<div class="deviceBatterySmall deviceBatterySmall' + levelnum + '" title="' + ((statestr != null) ? (statestr + ', ' + levelstr) : levelstr) + '"></div>';
3541 }
3542 }
3543 }
3544
3545 // Add any device icons
3546 if (devNotifySub != '') { devNotify += '<div class=deviceNotifyDot>' + devNotifySub + '</div>'; }
3547
3548 // Node
3549 var icon = node.icon, nodestate = NodeStateStr(node);
3550 if (((!node.conn) || (node.conn == 0)) && (node.mtype != 3)) { icon += ' gray'; }
3551 div.innerHTML = '<div>' + devNotify + '<div class="i' + icon + ' devList2"></div><div class=devList3><div class=devList4><b>' + name + '</b></div><div class=devList5>' + nodestate + '</div></div></div>';
3552 } else {
3553 div.innerHTML = ''; // Hide
3554 }
3555 }
3556
3557 // Show device help requests
3558 function showDeviceHelpRequests(nodeid, force, e) {
3559 if (e) haltEvent(e);
3560 if (xxdialogMode && !force) return false;
3561 var node = null, x = '';
3562 if (nodeid == null) { node = currentNode; } else { node = getNodeFromId(nodeid); }
3563 if ((node == null) || (node.sessions == null)) { setDialogMode(0); return false; }
3564 if (node.sessions.help != null) { for (var j in node.sessions.help) { x += '<div style=margin-bottom:6px><b>' + EscapeHtml(j) + '</b></div><div style=margin-bottom:6px>' + EscapeHtml(node.sessions.help[j]) + '</div>'; } }
3565 if (x != '') { setDialogMode(2, "Help Requests" + ' - ' + EscapeHtml(node.name), 1, null, x, 'HELPREQ-' + node._id); } else { setDialogMode(0); }
3566 return false;
3567 }
3568
3569 // Show currently active sessions on this device
3570 function showDeviceSessions(nodeid, force, e) {
3571 if (((force !== true) && xxdialogMode) || (currentNode == null)) return;
3572 var node = currentNode, x = '';
3573 for (var i in node.sessions) {
3574 if ((i == 'kvm') && (node.sessions.multidesk == null)) {
3575 x += '<u>' + "Remote Desktop" + '</u>';
3576 for (var j in node.sessions.kvm) {
3577 if (j.startsWith('user/')) {
3578 var trash = '';
3579 if ((j == userinfo._id) || (GetNodeRights(node) == 0xFFFFFFFF)) { trash = ' <a href=# onclick=\'return endDeviceSession("kvm", "' + encodeURIComponentEx(node._id) + '", "' + encodeURIComponentEx(j) + '")\' title="' + "Disconnect this session" + '" style=cursor:pointer><img src=images/trash.png border=0 height=10 width=10></a>'; }
3580 x += addHtmlValue4(getUserName(j), ((node.sessions.kvm[j] == 1) ? "1 session" : nobreak(format("{0} sessions", node.sessions.kvm[j]))) + trash);
3581 } else if (j == 'busy') {
3582 x += addHtmlValue2("Device is busy", ((node.sessions.kvm[j] == 1) ? "1 session" : nobreak(format("{0} sessions", node.sessions.kvm[j]))));
3583 }
3584 }
3585 } else if (i == 'multidesk') {
3586 x += '<u>' + "Remote Desktop" + '</u>';
3587 for (var j in node.sessions.multidesk) {
3588 var trash = '';
3589 if ((j == userinfo._id) || (GetNodeRights(node) == 0xFFFFFFFF)) { trash = ' <a href=# onclick=\'return endDeviceSession("multidesk", "' + encodeURIComponentEx(node._id) + '", "' + encodeURIComponentEx(j) + '")\' title="' + "Disconnect this session" + '" style=cursor:pointer><img src=images/trash.png border=0 height=10 width=10></a>'; }
3590 x += addHtmlValue4(getUserName(j), ((node.sessions.multidesk[j] == 1) ? "1 session" : nobreak(format("{0} sessions", node.sessions.multidesk[j]))) + trash);
3591 }
3592 } else if (i == 'terminal') {
3593 x += '<u>' + "Terminal" + '</u>';
3594 for (var j in node.sessions.terminal) {
3595 var trash = '';
3596 if ((j == userinfo._id) || (GetNodeRights(node) == 0xFFFFFFFF)) { trash = ' <a href=# onclick=\'return endDeviceSession("terminal", "' + encodeURIComponentEx(node._id) + '", "' + encodeURIComponentEx(j) + '")\' title="' + "Disconnect this session" + '" style=cursor:pointer><img src=images/trash.png border=0 height=10 width=10></a>'; }
3597 x += addHtmlValue4(getUserName(j), ((node.sessions.terminal[j] == 1) ? "1 session" : nobreak(format("{0} sessions", node.sessions.terminal[j]))) + trash);
3598 }
3599 } else if (i == 'files') {
3600 x += '<u>' + "Files" + '</u>';
3601 for (var j in node.sessions.files) {
3602 var trash = '';
3603 if ((j == userinfo._id) || (GetNodeRights(node) == 0xFFFFFFFF)) { trash = ' <a href=# onclick=\'return endDeviceSession("files", "' + encodeURIComponentEx(node._id) + '", "' + encodeURIComponentEx(j) + '")\' title="' + "Disconnect this session" + '" style=cursor:pointer><img src=images/trash.png border=0 height=10 width=10></a>'; }
3604 x += addHtmlValue4(getUserName(j), ((node.sessions.files[j] == 1) ? "1 session" : nobreak(format("{0} sessions", node.sessions.files[j]))) + trash);
3605 }
3606 } else if (i == 'tcp') {
3607 x += '<u>' + "TCP Routing" + '</u>';
3608 for (var j in node.sessions.tcp) {
3609 var trash = '';
3610 if ((j == userinfo._id) || (GetNodeRights(node) == 0xFFFFFFFF)) { trash = ' <a href=# onclick=\'return endDeviceSession("tcp", "' + encodeURIComponentEx(node._id) + '", "' + encodeURIComponentEx(j) + '")\' title="' + "Disconnect this session" + '" style=cursor:pointer><img src=images/trash.png border=0 height=10 width=10></a>'; }
3611 x += addHtmlValue4(getUserName(j), ((node.sessions.tcp[j] == 1) ? "1 session" : nobreak(format("{0} sessions", node.sessions.tcp[j]))) + trash);
3612 }
3613 } else if (i == 'udp') {
3614 x += '<u>' + "UDP Routing" + '</u>';
3615 for (var j in node.sessions.udp) {
3616 var trash = '';
3617 if ((j == userinfo._id) || (GetNodeRights(node) == 0xFFFFFFFF)) { trash = ' <a href=# onclick=\'return endDeviceSession("udp", "' + encodeURIComponentEx(node._id) + '", "' + encodeURIComponentEx(j) + '")\' title="' + "Disconnect this session" + '" style=cursor:pointer><img src=images/trash.png border=0 height=10 width=10></a>'; }
3618 x += addHtmlValue4(getUserName(j), ((node.sessions.udp[j] == 1) ? "1 session" : nobreak(format("{0} sessions", node.sessions.udp[j]))) + trash);
3619 }
3620 }
3621 }
3622 if (x != '') { setDialogMode(2, "Sessions" + ' - ' + EscapeHtml(node.name), 1, null, x, 'SESSIONS-' + node._id); } else { setDialogMode(0); }
3623 }
3624
3625 function endDeviceSession(protocol, nodeid, userid) {
3626 var userIdSplit = decodeURIComponent(userid).split('/'), uid = userIdSplit[0] + '/' + userIdSplit[1] + '/' + userIdSplit[2], guestname = null;
3627 if ((userIdSplit.length == 4) && (userIdSplit[3].startsWith('guest:'))) { guestname = atob(userIdSplit[3].substring(6)); }
3628 if (protocol == 'multidesk') {
3629 meshserver.send({ action: 'endDesktopMultiplex', nodeid: decodeURIComponent(nodeid), xuserid: uid, guestname, guestname });
3630 } else {
3631 meshserver.send({ action: 'msg', type: 'endtunnel', nodeid: decodeURIComponent(nodeid), xuserid: uid, guestname, guestname, protocol: protocol });
3632 }
3633 }
3634
3635 // Show currently active sessions on this device
3636 function showDeviceMessages(nodeid, force, e) {
3637 if (e) haltEvent(e);
3638 if (xxdialogMode && !force) return false;
3639 var node = null, x = '<div style=max-height:200px;width:100%;overflow-y:auto;overflow-x:hidden>', count = 0;
3640 if (nodeid == null) { node = currentNode; } else { node = getNodeFromId(nodeid); }
3641 if ((node == null) || (node.sessions == null) || (node.sessions.msg == null)) { setDialogMode(0); return false; }
3642 for (var i in node.sessions.msg) {
3643 var msg = i, icon = 5;
3644 if (typeof node.sessions.msg[i].msg == 'string') { msg = node.sessions.msg[i].msg; }
3645 if (typeof node.sessions.msg[i].icon == 'number') { icon = node.sessions.msg[i].icon; }
3646 if ((icon < 1) || (icon > 9)) { icon = 5; }
3647 x += '<table style=width:100%><td style=width:24px><div class=NotifyIconSmall' + icon + '></div><td><div style="border-radius:5px;background-color:#BBB;width:calc(100% - 18px);padding:8px">' + EscapeHtml(msg) + '</div></table>';
3648 count++;
3649 }
3650 x += '</div>';
3651 if (count > 0) setDialogMode(2, "Agent Messages" + ' - ' + EscapeHtml(node.name), 1, null, x, 'MESSAGES-' + node._id);
3652 return false;
3653 }
3654
3655 var powerStatetable = ['', "Powered", "Sleep", "Sleep", "Sleep", "Hibernating", "Power off", "Present", "Off"];
3656 var powerStateStrings = ['', "Powered", "Sleeping", "Sleeping", "Deep Sleep", "Hibernating", "Soft-Off", "Present", "Off"];
3657 var powerStateStrings2 = ['', "Device is powered", "Device is in sleep state (S1)", "Device is in sleep state (S2)", "Device is in deep sleep state (S3)", "Device is hibernating (S4)", "Device is in soft-off state (S5)", "Device is present, but power state cannot be determined", "The device is powered off"];
3658 var powerColorTable = ['#00000000', 'black', 'blue', 'blue', 'lightblue', 'blueviolet', 'darkgreen', 'lightseagreen', 'lightseagreen'];
3659 function NodeStateStr(node) {
3660 var states = [];
3661 if (node.state > 0 && node.state < powerStatetable.length) state.push(powerStatetable[node.state]);
3662 if (node.conn) {
3663 if ((node.conn & 1) != 0) { states.push('<span>' + ((node.mtype == 4) ? ((node.porttype == 'PDU') ? "Switch" : "IP-KVM") : "Agent") + '</span>'); }
3664 if ((node.conn & 2) != 0) { states.push('<span>' + "CIRA" + '</span>'); }
3665 else if ((node.conn & 4) != 0) { states.push('<span>' + "Intel&reg; AMT" + '</span>'); }
3666 if ((node.conn & 8) != 0) { states.push('<span>' + "Relay" + '</span>'); }
3667 if ((node.conn & 16) != 0) { states.push('<span>' + "MQTT" + '</span>'); }
3668 }
3669 if ((node.pwr != null) && (node.pwr != 0)) { states.push(powerStateStrings[node.pwr]); }
3670 return states.join(', ');
3671 }
3672
3673 function PowerStateStr(x) {
3674 if (x < powerStatetable.length) return powerStatetable[x];
3675 return '';
3676 }
3677
3678 function PowerStateStr2(x) {
3679 if ((x != 0) && (x < powerStatetable.length)) return powerStatetable[x];
3680 return "Unknown";
3681 }
3682
3683 function onSortSelectChange(skipsave) {
3684 sort = document.getElementById('sortselect').selectedIndex;
3685 if (!skipsave) { putstore('sort', sort); }
3686 mainUpdate(4);
3687 }
3688
3689 function deviceHeaderSet() {
3690 if (deviceHeaderId == 0) { deviceHeaderId = 1; return; }
3691 deviceHeaders['DevxHeader' + deviceHeaderId] = ', ' + deviceHeaderTotal + ((deviceHeaderTotal == 1) ? " node" : " nodes");
3692 var title = '';
3693 for (var x in deviceHeaderCount) { if (title.length > 0) title += ', '; title += deviceHeaderCount[x] + ' ' + PowerStateStr2(x); }
3694 deviceHeadersTitles['DevxHeader' + deviceHeaderId] = title;
3695 deviceHeaderId++;
3696 deviceHeaderCount = {};
3697 deviceHeaderTotal = 0;
3698 }
3699
3700 /*
3701 function meshSort(a, b) { if (a.meshnamel > b.meshnamel) return 1; if (a.meshnamel < b.meshnamel) return -1; if (a.meshid == b.meshid) { if (showRealNames == true) { if (a.rnamel > b.rnamel) return 1; if (a.rnamel < b.rnamel) return -1; return 0; } else { if (a.namel > b.namel) return 1; if (a.namel < b.namel) return -1; return 0; } } return 0; }
3702 function powerSort(a, b) { var ap = a.pwr ? a.pwr : 0; var bp = b.pwr ? b.pwr : 0; if (ap == bp) { if (showRealNames == true) { if (a.rnamel > b.rnamel) return 1; if (a.rnamel < b.rnamel) return -1; return 0; } else { if (a.namel > b.namel) return 1; if (a.namel < b.namel) return -1; return 0; } } if (ap > bp) return 1; if (ap < bp) return -1; return 0; }
3703 function deviceSort(a, b) { if (a.namel > b.namel) return 1; if (a.namel < b.namel) return -1; return 0; }
3704 function deviceHostSort(a, b) { if (a.rnamel > b.rnamel) return 1; if (a.rnamel < b.rnamel) return -1; return 0; }
3705 */
3706
3707 var sortCollator = new Intl.Collator(undefined, { numeric: true, sensitivity: 'base' })
3708 function meshSort(a, b) {
3709 var x = sortCollator.compare(a.meshnamel, b.meshnamel);
3710 if (x != 0) return x;
3711 x = sortCollator.compare(a.meshid, b.meshid);
3712 if (x != 0) return x;
3713 if (showRealNames == true) { return sortCollator.compare(a.rnamel, b.rnamel); }
3714 return sortCollator.compare(a.namel, b.namel);
3715 }
3716 function powerSort(a, b) { var ap = a.pwr ? a.pwr : 0; var bp = b.pwr ? b.pwr : 0; if (ap > bp) return -1; if (ap < bp) return 1; if (ap == bp) { if (showRealNames == true) { return sortCollator.compare(a.rnamel, b.rnamel); } else { return sortCollator.compare(a.namel, b.namel); } } }
3717 function deviceSort(a, b) { return sortCollator.compare(a.namel, b.namel); }
3718 function deviceHostSort(a, b) { return sortCollator.compare(a.rnamel, b.rnamel); }
3719
3720
3721 //
3722 // MY DEVICE
3723 //
3724
3725 function refreshDevice(nodeid) {
3726 if (!currentNode || currentNode._id != nodeid) return;
3727 gotoDevice(nodeid, xxcurrentView, true);
3728 }
3729
3730 var currentDevicePanel = 0;
3731 var currentNode;
3732 var powerTimelineNode = null;
3733 var powerTimelineReq = null;
3734 var powerTimelineUpdate = null;
3735 var powerTimeline = null;
3736 function getCurrentNode() { return currentNode; };
3737 function gotoDevice(nodeid, panel, refresh) {
3738
3739 // Remind the user to verify the email address
3740 if ((userinfo.emailVerified !== true) && (serverinfo.emailcheck == true) && (userinfo.siteadmin != 0xFFFFFFFF)) { setDialogMode(2, "Account Security", 1, null, "Unable to access a device until a email address is verified. This is required for password recovery. Go to the \"My Account\" to change and verify an email address."); return; }
3741
3742 // Remind the user to add two factor authentication
3743 if ((features & 0x00040000) && !((userinfo.otpsecret == 1) || (userinfo.otphkeys > 0) || (userinfo.otpkeys > 0) || (userinfo.otpduo > 0) || (userinfo.otpdev > 0) || ((features & 0x00800000) && (userinfo.otpekey == 1)))) { setDialogMode(2, "Account Security", 1, null, "Unable to access a device until two-factor authentication is enabled. This is required for extra security. Go to the \"My Account\" and look at the \"Account Security\" section."); return; }
3744
3745 var node = getNodeFromId(nodeid);
3746 if (node == null) { goBack(); return; }
3747 var mesh = meshes[node.meshid];
3748 var meshrights = GetNodeRights(node);
3749 var deviceSwitch = ((currentNode == null) || (currentNode._id != nodeid));
3750 if (!currentNode || currentNode._id != node._id || refresh == true) {
3751 currentNode = node;
3752
3753 // Setup session notification
3754 QV('p10deviceNotify', (currentNode.sessions != null) && ((node.sessions.kvm != null) || (node.sessions.terminal != null) || (node.sessions.files != null) || (node.sessions.tcp != null) || (node.sessions.udp != null)));
3755 QV('p10deviceStar', stars[currentNode._id] == 1);
3756 QV('p10deviceHelp', (currentNode.sessions != null) && (currentNode.sessions.help != null))
3757 if ((currentNode.sessions != null) && (currentNode.sessions.msg != null)) { QV('p10deviceMsg', true); QH('p10deviceMsg', Object.keys(currentNode.sessions.msg).length); } else { QV('p10deviceMsg', false); }
3758
3759 // Device Battery
3760 QV('p10deviceBattery', false);
3761 if ((currentNode.sessions != null) && (currentNode.sessions.battery != null)) {
3762 var bat = currentNode.sessions.battery;
3763
3764 var statestr = '';
3765 if (bat.state == 'ac') { statestr = "Device is plugged-in"; }
3766 if (bat.state == 'dc') { statestr = "Device is battery powered"; }
3767
3768 var levelstr = '', levelnum = -1;
3769 if ((typeof bat.level == 'number') && (bat.level >= 0) && (bat.level <= 100)) {
3770 levelstr = bat.level + '%';
3771 levelnum = (Math.floor((bat.level + 10) / 25) + 1);
3772 if (levelnum > 5) { lvl = 5; }
3773 if (bat.state == 'ac') { if (bat.level == 100) { levelnum = 11; } else { levelnum += 5; } }
3774 }
3775
3776 if (levelnum > 0) {
3777 Q('p10deviceBattery').title = (statestr != null) ? (statestr + ', ' + levelstr) : levelstr;
3778 QV('p10deviceBattery', true);
3779 Q('p10deviceBattery').className = 'deviceBatteryLarge deviceBatteryLarge' + levelnum;
3780 }
3781 } else {
3782 QV('p10deviceBattery', false);
3783 }
3784
3785 // Add node name
3786 var nname = EscapeHtml(node.name);
3787 if (nname.length == 0) { nname = '<i>' + "None" + '</i>'; }
3788 if ((meshrights & 4) != 0) { nname = '<span onclick=showEditNodeValueDialog(0) style=cursor:pointer>' + nname + '</span>'; }
3789 QH('p10deviceName', nname);
3790
3791 // Node attributes
3792 var x = '<table style=width:100%>';
3793
3794 // Attribute: Mesh
3795 if (mesh) { x += addDeviceAttribute('<span>' + "Group" + '</span>', '<a onclick=goForward("' + node.meshid + '") style=cursor:pointer>' + EscapeHtml(meshes[node.meshid].name) + '</a>'); }
3796
3797 // Attribute: Name
3798 if (node.rname != null) { x += addDeviceAttribute('<span>' + "Name" + '</span>', '<span>' + EscapeHtml(node.rname) + '</span>'); }
3799
3800 // Attribute: Host
3801 if ((((features & 1) == 0) && (node.mtype != 4)) || (node.mtype == 3)) { // If not WAN-only, local hostname is in use
3802 if ((meshrights & 4) != 0) {
3803 if (node.host) {
3804 x += addDeviceAttribute("Hostname", '<span onclick=showEditNodeValueDialog(1) style=cursor:pointer>' + EscapeHtml(node.host) + '</span>');
3805 } else {
3806 x += addDeviceAttribute("Hostname", '<span onclick=showEditNodeValueDialog(1) style=cursor:pointer><i>' + "None" + '</i></span>');
3807 }
3808 } else {
3809 x += addDeviceAttribute("Hostname", EscapeHtml(node.host));
3810 }
3811 }
3812
3813 // Attribute: Description
3814 var description = node.desc ? EscapeHtml(node.desc) : '<i>' + "None" + '</i>';
3815 if ((meshrights & 4) != 0) {
3816 x += addDeviceAttribute("Description", '<span onclick=showEditNodeValueDialog(2) style=cursor:pointer>' + description + '</span>');
3817 } else {
3818 x += addDeviceAttribute("Description", description);
3819 }
3820
3821 // IP-KVM information
3822 if (node.mtype == 4) {
3823 if (node.portnum != null) { x += addDeviceAttribute("Port Number", node.portnum); }
3824 if (node.porttype != null) { x += addDeviceAttribute("Port Type", node.porttype); }
3825 }
3826
3827 // Attribute: Mesh Agent
3828 if ((node.agent != null) && (node.agent.id != null) && (node.mtype == 3)) {
3829 if (node.agent.id == 4) { x += addDeviceAttribute("Device Type", "Windows"); }
3830 if (node.agent.id == 6) { x += addDeviceAttribute("Device Type", "Linux"); }
3831 if (node.agent.id == 29) { x += addDeviceAttribute("Device Type", "macOS"); }
3832 } else if ((node.agent != null) && (node.agent.id != null) && (node.agent.ver != null)) {
3833 var str = '';
3834 if (node.agent.id <= agentsStr.length) { str = agentsStr[node.agent.id]; } else { str = agentsStr[0]; }
3835 if (node.agent.ver != 0) { str += ' v' + node.agent.ver; }
3836 if (node.agent.id == 14) { str = node.agent.core; }
3837 if ((node.agent.root === false) && ((node.conn & 1) != 0)) { str += ', ' + "Restricted"; }
3838 x += addDeviceAttribute("Mesh Agent", str);
3839 }
3840
3841 // Attribute: Intel AMT
3842 if (node.intelamt != null) {
3843 var str = '';
3844 var provisioningStates = { 0: nobreak("Not Activated (Pre)"), 1: nobreak("Not Activated (In)"), 2: nobreak("Activated") };
3845 if (node.intelamt.ver != null && node.intelamt.state == null) { str += '<i>' + nobreak("Unknown State") + '</i>, v' + EscapeHtml(node.intelamt.ver); }
3846 else if ((node.intelamt.ver == null) && (node.intelamt.state == 2)) { str += '<i>' + "Activated" + '</i>'; }
3847 else if ((node.intelamt.ver == null) || (node.intelamt.state == null)) { str += '<i>' + "Unknown Version & State" + '</i>'; }
3848 else {
3849 str += provisioningStates[node.intelamt.state];
3850 if (node.intelamt.flags) { if (node.intelamt.flags & 2) { str = ' <span>' + "CCM" + '</span>'; } else if (node.intelamt.flags & 4) { str = ' <span>' + "ACM" + '</span>'; } }
3851 str += (', v' + EscapeHtml(node.intelamt.ver));
3852 }
3853
3854 // If Intel AMT is activated, show additional options
3855 if (node.intelamt.state == 2) {
3856 if (node.intelamt.tls == 1) { str += ', <span title="' + "Intel&reg; AMT is setup with TLS network security" + '">' + "TLS" + '</span>'; }
3857
3858 var editUserCredentialsIcon = false;
3859 if (node.intelamt.user == null || node.intelamt.user == '') { // If credentials are not set, allow setting them.
3860 if ((meshrights & 4) != 0) {
3861 str += ', <i style=color:#FF0000;cursor:pointer title="' + "Edit Intel&reg; AMT credentials" + '" onclick=editDeviceAmtSettings("' + node._id + '")>' + "No Credentials" + '</i>';
3862 editUserCredentialsIcon = true;
3863 } else {
3864 str += ', <i style=color:#FF0000>' + "No Credentials" + '</i>';
3865 }
3866 } else if (((features2 & 1) != 0) && (node.intelamt.warn != null)) { // If AMT manager is running and warned of invalid credentials, allow setting them.
3867 var warn = null;
3868 if ((node.intelamt.warn & 1) != 0) { warn = "Invalid Credentials"; }
3869 if ((node.intelamt.warn & 8) != 0) { warn = "Trying Credentials"; }
3870 if (warn != null) {
3871 if ((meshrights & 4) != 0) {
3872 str += ', <i style=color:#FF0000;cursor:pointer title="' + "Edit Intel&reg; AMT credentials" + '" onclick=editDeviceAmtSettings("' + node._id + '")>' + warn + '</i>';
3873 editUserCredentialsIcon = true;
3874 } else {
3875 str += ', <i style=color:#FF0000>' + warn + '</i>';
3876 }
3877 }
3878 }
3879
3880 // If the AMT manager is not running, always allow Intel AMT credentials to be edited.
3881 if (((meshrights & 4) != 0) && ((features2 & 1) == 0)) { editUserCredentialsIcon = true; }
3882
3883 str += ' ';
3884 if (editUserCredentialsIcon) {
3885 str += '<img src=images/link4.png height=10 width=10 title="' + "Edit Intel&reg; AMT credentials" + '" style=cursor:pointer onclick=editDeviceAmtSettings("' + node._id + '")>';
3886 }
3887 }
3888
3889 /*
3890 if (node.intelamt.state == 2) {
3891 if (node.intelamt.user == null || node.intelamt.user == '') {
3892 if ((meshrights & 4) != 0) {
3893 str += ', <i style=color:#FF0000;cursor:pointer onclick=editDeviceAmtSettings("' + node._id + '")>' + nobreak("No Credentials") + '</i>';
3894 } else {
3895 str += ', <i style=color:#FF0000>' + "No Credentials" + '</i>';
3896 }
3897 }
3898 str += ' ';
3899 if ((meshrights & 4) != 0) {
3900 str += '<img src=images/link4.png height=10 width=10 style=cursor:pointer onclick=editDeviceAmtSettings("' + node._id + '")>';
3901 }
3902 }
3903 */
3904
3905 var meName = "Intel&reg; ME";
3906 if (typeof node.intelamt.sku == 'number') {
3907 if ((node.intelamt.sku & 8) != 0) { meName = "Intel&reg; AMT"; }
3908 else if ((node.intelamt.sku & 16) != 0) { meName = "Intel&reg; SM"; }
3909 }
3910 x += addDeviceAttribute(meName, str);
3911 }
3912
3913 // Attribute: Mesh Agent Tag
3914 if ((node.agent != null) && (node.agent.tag != null) && (node.agent.tag != 'mailto:')) {
3915 var tag = EscapeHtml(node.agent.tag);
3916 if (tag.startsWith('mailto:')) { tag = '<a href="' + tag + '">' + tag.substring(7) + '</a>'; }
3917 x += addDeviceAttribute("Agent Tag", tag);
3918 }
3919
3920 // Attribute: Intel AMT
3921 //if (node.intelamt && node.intelamt.user) { x += addDeviceAttribute('Intel&reg; AMT', node.intelamt.user); }
3922
3923 // Attribute: Connectivity (Only show this if more than just the agent is connected).
3924 var connectivity = node.conn;
3925 if (connectivity && connectivity > 1) {
3926 var cstate = [];
3927 if ((node.conn & 1) != 0) cstate.push('<span>' + ((node.mtype == 4) ? ((node.porttype == 'PDU') ? "Switch" : "IP-KVM") : "Agent") + '</span>');
3928 if ((node.conn & 2) != 0) cstate.push('<span>' + "Intel&reg; AMT CIRA" + '</span>');
3929 else if ((node.conn & 4) != 0) cstate.push('<span>' + "Intel&reg; AMT" + '</span>');
3930 if ((node.conn & 8) != 0) cstate.push('<span>' + "Agent Relay" + '</span>');
3931 if ((node.conn & 16) != 0) cstate.push('<span>' + "MQTT" + '</span>');
3932 x += addDeviceAttribute("Connectivity", cstate.join(', '));
3933 }
3934
3935 // Node tags
3936 var groupingTags = '<i>' + "None" + '</i>';
3937 if (node.tags != null) { groupingTags = ''; for (var i in node.tags) { groupingTags += '<span class=tagSpan>' + EscapeHtml(node.tags[i]) + '</span> '; } }
3938 if ((meshrights & 4) != 0) {
3939 x += addDeviceAttribute("Tags", '<span onclick=showEditNodeValueDialog(3) style=cursor:pointer;color:black>' + groupingTags + '</span>');
3940 } else {
3941 x += addDeviceAttribute("Tags", '<span style=line-height:26px;color:black>' + groupingTags + '</span>');
3942 }
3943
3944 // SSH & RDP Credentials
3945 if ((node.ssh != null) || (node.rdp != null)) {
3946 var y = [];
3947 if ((meshrights & 4) != 0) {
3948 if (node.ssh != null) { y.push('<span onclick=showClearSshDialog(3) style=cursor:pointer>' + ((node.ssh == 1) ? "SSH-User+Pass" : ((node.ssh == 2) ? "SSH-User+Key+Pass" : "SSH-User+Key")) + ' <img class=hoverButton src="images/link5.png" width=10 height=10 /></span>'); }
3949 if (node.rdp != null) { y.push('<span onclick=showClearRdpDialog(3) style=cursor:pointer>' + "RDP" + ' <img class=hoverButton src="images/link5.png" width=10 height=10 /></span>'); }
3950 } else {
3951 if (node.ssh != null) { y.push(((node.ssh == 1) ? "SSH-User+Pass" : ((node.ssh == 2) ? "SSH-User+Key+Pass" : "SSH-User+Key"))); }
3952 if (node.rdp != null) { y.push("RDP"); }
3953 }
3954 x += addDeviceAttribute("Credentials", y.join(', '));
3955 }
3956
3957 x += '</table><br />';
3958 // Show action button, only show if we have permissions 4, 8, 64
3959 if (((meshrights & (4 + 8 + 64 + 262144)) != 0) && (node.mtype < 3)) { x += '<input type=button value="' + "Actions" + '" onclick=deviceActionFunction() />'; }
3960 x += '<input type=button value="' + "Notes" + '" onclick=showNotes(' + ((meshrights & 128) == 0) + ',"' + encodeURIComponent(node._id) + '") />';
3961 //if ((connectivity & 1) && (meshrights & 8) && (node.agent.id < 5)) { x += '<input type=button value=Toast onclick=deviceToastFunction() />'; }
3962
3963 if ((node.mtype == 4) && (connectivity & 1)) {
3964 if (node.porttype == 'PDU') {
3965 if (node.pwr == 1) {
3966 if (meshrights & 0x40000) { x += '<input type=button value="' + "Turn off" + '" title="' + "Turn off" + '" onclick=setIpPduState(0) />'; }
3967 } else if (node.pwr == 8) {
3968 if (meshrights & 0x40) { x += '<input type=button value="' + "Turn on" + '" title="' + "Turn on" + '" onclick=setIpPduState(1) />'; }
3969 }
3970 } else {
3971 if (meshrights & 8) { x += '<input type=button value="' + "Remote Control" + '" title="' + "Remote Control" + '" onclick=openIpKvmRemoteControl("' + encodeURIComponentEx(node._id) + '") />'; }x
3972 }
3973 }
3974
3975 QH('p10html', x);
3976
3977 // If we are looking at a local non-windows device, enable terminal and files capability.
3978 if ((node.mtype == 3) && (node.agent != null) && (node.agent.id > 4) && (features2 & 0x00000200)) { node.agent.caps = 6; }
3979
3980 // Show node last 7 days timeline
3981 //drawDeviceTimeline();
3982 setupTerminal();
3983 setupFiles();
3984 if (meshrights & 16) { setupConsole(); }
3985
3986 // Show bottom buttons
3987 x = '<div style=float:right;font-size:x-small;margin-right:10px>';
3988 if ((meshrights & 0x8000) != 0) { x += '<a style=cursor:pointer onclick=p10showDeleteNodeDialog("' + node._id + '")>' + "Delete Device" + '</a>'; }
3989 x += '</div><div style=font-size:x-small>';
3990 if (webRelayPort != 0) {
3991 x += '<a onclick=p10WebRouter("' + node._id + '",1,' + (node.httpport ? node.httpport : 80) + ')>' + "HTTP" + ((node.httpport && (node.httpport != 80)) ? '/' + node.httpport : '') + '</a>&nbsp;';
3992 x += '<a onclick=p10WebRouter("' + node._id + '",2,' + (node.httpsport ? node.httpsport : 443) + ')>' + "HTTPS" + ((node.httpsport && (node.httpsport != 443)) ? '/' + node.httpsport : '') + '</a>&nbsp;';
3993 }
3994
3995 // noVNC link
3996 if ((((connectivity & 1) != 0) || (node.mtype == 3)) && (node.agent) && ((meshrights & 8) != 0) && ((features & 0x20000000) == 0)) {
3997 x += '<a id=rfbLink onclick=p10rfb("' + node._id + '")>' + "Web-VNC" + '</a>&nbsp;';
3998 }
3999
4000 // MSTSC.js link
4001 if ((((connectivity & 1) != 0) || (node.mtype == 3)) && (node.agent) && ((meshrights & 8) != 0) && ((features & 0x40000000) == 0)) {
4002 x += '<a id=mstscLink onclick=p10mstsc("' + node._id + '")>' + "Web-RDP" + '</a>&nbsp;';
4003 }
4004
4005 // SSH link
4006 if ((features2 & 0x200) && (((connectivity & 1) != 0) || (node.mtype == 3)) && (node.agent) && ((meshrights & 8) != 0)) {
4007 x += '<a id=sshLink onclick=p10ssh("' + node._id + '")>' + "Web-SSH" + '</a>&nbsp;';
4008 }
4009
4010 //if (mesh.mtype == 2) x += '<a style=cursor:pointer onclick=p10showNodeNetInfoDialog("' + node._id + '")>Interfaces</a>&nbsp;';
4011 //if (xxmap != null) x += '<a style=cursor:pointer onclick=p10showNodeLocationDialog("' + node._id + '")>Location</a>&nbsp;';
4012 x += '</div><br>'
4013
4014 QH('p10html3', x);
4015
4016 // Set the node power state
4017 var powerstate = PowerStateStr(node.state);
4018 //if (node.state == 0) { powerstate = 'Unknown State'; }
4019 if ((connectivity & 1) != 0) { if (powerstate.length > 0) { powerstate += ', '; } powerstate += ((node.mtype == 4) ? ((node.porttype == 'PDU') ? "Switch" : "IP-KVM") : "Mesh Agent"); }
4020 if ((connectivity & 2) != 0) { if (powerstate.length > 0) { powerstate += ', '; } powerstate += "Intel&reg; AMT connected"; }
4021 else if ((connectivity & 4) != 0) { if (powerstate.length > 0) { powerstate += ', '; } powerstate += "Intel&reg; AMT detected"; }
4022 if ((connectivity & 16) != 0) { if (powerstate.length > 0) { powerstate += ', '; } powerstate += "MQTT channel connected"; }
4023 if ((node.porttype == 'PDU') || ((node.pwr > 1) && (node.pwr != 7))) { if (powerstate.length > 0) { powerstate += ', '; } powerstate += powerStateStrings[node.pwr]; }
4024 QH('MainComputerState', '<span style=font-size:12px>' + powerstate + '</span>');
4025
4026 // Set the node icon
4027 var icon = node.icon;
4028 if (((!node.conn) || (node.conn == 0)) && (node.mtype != 3)) { icon += ' gray'; }
4029 QH('MainComputerImage', '<div class="i' + icon + '"></div>');
4030
4031 // Request the power timeline
4032 if ((powerTimelineNode != currentNode._id) && (powerTimelineReq != currentNode._id)) {
4033 QH('p10html2', '');
4034 powerTimelineReq = currentNode._id;
4035 meshserver.send({ action: 'powertimeline', nodeid: currentNode._id });
4036 meshserver.send({ action: 'lastconnect', nodeid: currentNode._id });
4037 meshserver.send({ action: 'getsysinfo', nodeid: currentNode._id });
4038 meshserver.send({ action: 'getnetworkinfo', nodeid: currentNode._id });
4039 QH('p10detailshtml', '');
4040 }
4041
4042 // Clear user consent status if present
4043 if (deviceSwitch) {
4044 p11clearConsoleMsg();
4045 p13clearConsoleMsg();
4046 }
4047
4048 // Clear the desktop session selector
4049 QV('p11DeskSessionSelector', false);
4050 QH('p11DeskSessionSelector', '');
4051 }
4052 setupDesktop(); // Always refresh the desktop, even if we are on the same device, we need to do some canvas switching.
4053 if (!panel) panel = 10;
4054 go(panel);
4055
4056 // Update the footer menu
4057 if (xxcurrentView == 10) { setupDeviceMenu(); }
4058 }
4059
4060 function setIpPduState(op) {
4061 if (op == 0) {
4062 setDialogMode(2, "Power Operation", 3, function () { meshserver.send({ action: 'poweraction', nodeids: [currentNode._id], actiontype: 2 }); }, "Perform power off?"); // Turn off
4063 } else {
4064 setDialogMode(2, "Power Operation", 3, function () { meshserver.send({ action: 'wakedevices', nodeids: [currentNode._id] }); }, "Perform power on?"); // Turn on
4065 }
4066 }
4067
4068 function openIpKvmRemoteControl(nodeid) {
4069 if (xxdialogMode) return;
4070 var nid = decodeURIComponent(nodeid).split('/')[2];
4071 safeNewWindow('/ipkvm.ashx/' + nid + '/', 'ipkvm:' + nid);
4072 }
4073
4074 function deviceToastFunction() {
4075 if (xxdialogMode) return;
4076 setDialogMode(2, "Device Toast", 3, deviceToastFunctionEx, '<textarea id=d2devToast style=width:100%;height:80px;resize:none;overflow-y:scroll></textarea>');
4077 }
4078
4079 function deviceToastFunctionEx() {
4080 meshserver.send({ action: 'toast', nodeids: [currentNode._id], title: 'MeshCentral', msg: Q('d2devToast').value });
4081 }
4082
4083 // && ((meshrights == 0xFFFFFFFF) || ((meshrights & 65536) == 0))
4084 function setupDeviceMenu(op, obj) {
4085 var meshrights = GetNodeRights(currentNode);
4086 if (op != null) { currentDevicePanel = op; }
4087 QV('p10general', currentDevicePanel == 0);
4088 QV('p10desktop', currentDevicePanel == 1); // Show if we have remote control rights or desktop view only rights
4089 QV('p10files', currentDevicePanel == 2);
4090 QV('p10details', currentDevicePanel == 3);
4091 QV('p10console', currentDevicePanel == 4);
4092 QV('p10terminal', currentDevicePanel == 5);
4093 var menus = [];
4094 if (currentDevicePanel != 0) { menus.push({ n: "General", f: 'setupDeviceMenu(0)' }); }
4095
4096 if ((currentDevicePanel != 1) &&
4097 (currentNode != null) &&
4098 ((meshrights & 8) || (meshrights & 256)) && ((meshrights == 0xFFFFFFFF) || ((meshrights & 65536) == 0)) &&
4099 (((currentNode.agent == null) && (currentNode.intelamt) && ((typeof currentNode.intelamt.sku !== 'number') || ((currentNode.intelamt.sku & 8) != 0))) || (currentNode.agent && (currentNode.agent.caps & 1)))
4100 ) { menus.push({ n: "Desktop", f: 'setupDeviceMenu(1)' }); }
4101
4102 if ((currentDevicePanel != 5) &&
4103 (currentNode != null) &&
4104 ((meshrights & 8) || (meshrights & 256)) && ((meshrights == 0xFFFFFFFF) || ((meshrights & 512) == 0)) &&
4105 (((currentNode.agent == null) && (currentNode.intelamt) && ((typeof currentNode.intelamt.sku !== 'number') || ((currentNode.intelamt.sku & 8) != 0))) || (currentNode.agent && (currentNode.agent.caps & 2)))
4106 ) { menus.push({ n: "Terminal", f: 'setupDeviceMenu(5)' }); }
4107
4108 if ((currentDevicePanel != 2) && (currentNode != null) && (meshrights & 8) && ((meshrights == 0xFFFFFFFF) || ((meshrights & 1024) == 0)) && ((currentNode.mtype != 1) && (currentNode.agent) && (currentNode.agent.caps & 4))) { menus.push({ n: "Files", f: 'setupDeviceMenu(2)' }); }
4109 if ((currentDevicePanel != 3) && (currentNode != null) && (currentNode.mtype < 3) && ((meshrights & 1048576) != 0)) { menus.push({ n: "Details", f: 'setupDeviceMenu(3)' }); }
4110 if ((currentDevicePanel != 4) && (currentNode != null) && (meshrights & 0x00000010) && (currentNode.mtype == 2)) { menus.push({ n: "Console", f: 'setupDeviceMenu(4)' }); }
4111 updateFooterMenu(menus);
4112 updateCurrentUrl();
4113 if (currentDevicePanel == 1) { deskAdjust(); }
4114 }
4115
4116 function deviceActionFunction() {
4117 if (xxdialogMode) return;
4118 var rights = GetNodeRights(currentNode), count = 0;
4119 var x = "Select an operation to perform on this device." + '<br /><br />';
4120 var y = '<select id=d2deviceop style=float:right;width:170px onchange=deviceActionFunctionValidate()>';
4121 var z = '';
4122 if ((currentNode.agent != null) && (currentNode.agent.id == 14)) {
4123 if (((currentNode.conn & 1) != 0) && ((rights & 8) != 0)) {
4124 count++;
4125 y += '<option value=400>' + "Flash" + '</option>';
4126 y += '<option value=401>' + "Vibrate" + '</option>';
4127 z += '<div id=d2devicetimediv>' + addHtmlValue("Time", '<select id=d2devicetime style=float:right;width:170px><option value=1000>' + "1 second" + '</option><option value=5000>' + "5 seconds" + '</option><option value=10000>' + "10 seconds" + '</option></select>') + '</div>';
4128 }
4129 } else {
4130 if ((rights & 64) != 0) { count++; y += '<option value=100>' + "Wake-up" + '</option>'; } // Wake-up permission
4131 //if (((currentNode.conn & 1) != 0) && ((rights & 131072) != 0)) { count++; y += '<option value=106>' + "Run Commands" + '</option>'; } // Remote command permission
4132 if ((currentNode.conn != 0) && ((rights & 262144) != 0)) { count++; y += '<option value=4>' + "Sleep" + '</option><option value=3>' + "Reset" + '</option><option value=2>' + "Power off" + '</option>'; }
4133 //if ((currentNode.conn & 16) != 0) { count++; y += '<option value=103>' + "Send MQTT Message" + '</option>'; }
4134 if ((currentNode.intelamt != null) && (currentNode.intelamt.state == 2) && ((currentNode.conn & 6) != 0) && ((rights & 262144) != 0)) {
4135 count++;
4136 y += '<option value=310>' + "Intel&reg; AMT Reset" + '</option>';
4137 y += '<option value=308>' + "Intel&reg; AMT Power off" + '</option>';
4138 }
4139 if ((currentNode.intelamt != null) && (currentNode.intelamt.state == 2) && ((currentNode.conn & 6) != 0) && ((rights & 64) != 0)) {
4140 count++;
4141 y += '<option value=302>' + "Intel&reg; AMT Power on" + '</option>';
4142 }
4143 //if ((getNodeAmtVersion(currentNode) >= 15) && (currentNode.intelamt.state == 2) && ((currentNode.conn & 6) != 0) && (rights == 0xFFFFFFFF) && ((features & 0x00000400) == 0)) { count++; y += '<option value=107>' + "Intel&reg; AMT One Click Recovery" + '</option>'; } // CIRA (2) or AMT (4) connected
4144 //if (((currentNode.conn & 1) != 0) && ((rights & 32768) != 0)) { count++; y += '<option value=104>' + "Uninstall Agent" + '</option>'; }
4145 }
4146 y += '</select>';
4147 x += addHtmlValue("Operation", y);
4148 if (count == 0) { x = "No actions currently available for this device."; }
4149 setDialogMode(2, "Device Action", (count == 0) ? 2 : 3, deviceActionFunctionEx, x + z);
4150 if (count > 0) { deviceActionFunctionValidate(); }
4151 }
4152
4153 function deviceActionFunctionValidate() {
4154 var op = Q('d2deviceop').value;
4155 try { QV('d2devicetimediv', (op == 400) || (op == 401)); } catch (ex) { }
4156 }
4157
4158 function deviceActionFunctionEx() {
4159 var op = Q('d2deviceop').value;
4160 if (op == 100) {
4161 // Device wake
4162 meshserver.send({ action: 'wakedevices', nodeids: [currentNode._id] });
4163 } else if (op == 103) {
4164 // Send MQTT Message
4165 //p10showSendMqttMsgDialog([currentNode._id]);
4166 } else if (op == 104) {
4167 // Uninstall agent
4168 //p10showSendUninstallAgentDialog([currentNode._id]);
4169 } else if (op == 106) {
4170 // Run commands
4171 /*
4172 var wintype = false, linuxtype = false;
4173 if (currentNode.agent) { if ((currentNode.agent.id > 0) && (currentNode.agent.id < 5)) { wintype = true; } else { linuxtype = true; } }
4174 if ((wintype == true) || (linuxtype == true)) {
4175 var x = "Run commands on selected devices." + '<br />';
4176 if (wintype == true) {
4177 x += '<select id=d2cmdtype style=width:100%;margin-bottom:4px;margin-top:4px>';
4178 x += '<option value=1>' + "Windows Command Prompt" + '</option><option value=2>' + "Windows PowerShell" + '</option>';
4179 if (linuxtype == true) { x += '<option value=3>' + "Linux/BSD/macOS Command Shell" + '</option>'; }
4180 x += '</select>';
4181 }
4182 x += '<select id=d2cmduser style=width:100%;margin-bottom:4px><option value=0>' + "Run as agent" + '</option><option value=1>' + "Run as user, agent if no user" + '</option><option value=2>' + "Must run as user" + '</option></select>';
4183 x += '<textarea id=d2runcmd style=background-color:#fcf3cf;width:100%;height:200px;resize:none;overflow-y:scroll></textarea>';
4184 setDialogMode(2, "Run Commands", 3, deviceRunCmdsFunctionEx, x);
4185 Q('d2runcmd').focus();
4186 //QE('idx_dlgOkButton', true);
4187 }
4188 */
4189 } else if (op == 107) {
4190 // Intel AMT One Click Recovery (OCR)
4191 /*
4192 Q('d3localmodeform').action = 'oneclickrecovery.ashx';
4193 Q('d3auth').value = authCookie;
4194 Q('d3filter').value = '.iso';
4195 Q('d3attrib').value = currentNode._id;
4196 setDialogMode(3, "Intel&reg; AMT One Click Recovery", 3, deviceActionOneClickRecovery);
4197 d3init();
4198 */
4199 } else if (op == 302) { // Intel AMT power on
4200 setDialogMode(2, "Intel&reg; AMT Power Operation", 3, function () { meshserver.send({ action: 'poweraction', nodeids: [currentNode._id], actiontype: parseInt(op) }); }, "Perform Intel&reg; AMT power on?");
4201 } else if (op == 308) { // Intel AMT power off
4202 setDialogMode(2, "Intel&reg; AMT Power Operation", 3, function () { meshserver.send({ action: 'poweraction', nodeids: [currentNode._id], actiontype: parseInt(op) }); }, "Perform Intel&reg; AMT power off?<br><br><b>NOTE: If there is an active AMT session, then power off command will be rejected, so you must disconnect from the AMT session first!</b>");
4203 } else if (op == 310) { // Intel AMT reset
4204 setDialogMode(2, "Intel&reg; AMT Power Operation", 3, function () { meshserver.send({ action: 'poweraction', nodeids: [currentNode._id], actiontype: parseInt(op) }); }, "Perform Intel&reg; AMT reset?");
4205 } else if ((op == 400) || (op == 401)) {
4206 // Flash / vibrate
4207 meshserver.send({ action: 'poweraction', nodeids: [currentNode._id], actiontype: parseInt(op), time: parseInt(Q('d2devicetime').value) });
4208 } else {
4209 // Power operation
4210 meshserver.send({ action: 'poweraction', nodeids: [currentNode._id], actiontype: parseInt(op) });
4211 }
4212 }
4213
4214 function showNotes(readonly, noteid) {
4215 if (xxdialogMode) return;
4216 if (noteid == null) { noteid = encodeURIComponentEx('p' + userinfo._id); }
4217 var x = '<textarea id=d2devNotes ro=' + readonly + ' noteid=' + noteid + ' readonly style=background-color:#fcf3cf;width:100%;height:200px;resize:none;overflow-y:scroll></textarea>';
4218 if (noteid.startsWith('node%2F%2F')) { x += '<span style=font-size:10px>' + "Device group notes can be viewed and changed by other device group administrators." + '<span>'; }
4219 setDialogMode(2, "Notes", 3, showNotesEx, x, noteid);
4220 meshserver.send({ action: 'getNotes', id: decodeURIComponent(noteid) });
4221 }
4222
4223 function showNotesEx(buttons, tag) { meshserver.send({ action: 'setNotes', id: decodeURIComponent(tag), notes: encodeURIComponentEx(Q('d2devNotes').value) }); }
4224
4225 function deviceLockFunction() {
4226 if ((xxdialogMode != null || xxdialogMode == 0) && (desktop != null) && (desktop.contype == 1)) { setDialogMode(2, "Lock Desktop", 3, function() { if ((desktop != null) && (desktop.contype == 1)) { desktop.sendCtrlMsg('{"ctrlChannel":"102938","type":"lock"}'); } }, "Lock user desktop?"); }
4227 }
4228
4229 function deviceChat(e) {
4230 if (xxdialogMode) return;
4231 setDialogMode(2, "Device Action", 3, function () {
4232 var url = '/messenger?id=meshmessenger/' + encodeURIComponentEx(currentNode._id) + '/' + encodeURIComponentEx(userinfo._id) + '&title=' + currentNode.name;
4233 if (serverinfo.domainsuffix != '') { url = '/' + serverinfo.domainsuffix + url; }
4234 if ((authCookie != null) && (authCookie != '')) { url += '&auth=' + authCookie; }
4235 if (e && (e.shiftKey == true)) {
4236 safeNewWindow(url, 'meshmessenger:' + currentNode._id);
4237 } else {
4238 safeNewWindow(url, 'meshmessenger:' + currentNode._id, 'directories=no,titlebar=no,toolbar=no,location=no,status=no,menubar=no,scrollbars=no,resizable=no,width=400,height=560');
4239 }
4240 meshserver.send({ action: 'meshmessenger', nodeid: decodeURIComponent(currentNode._id) });
4241 }, "Start chat session?");
4242 }
4243
4244 function deviceUrlFunction() {
4245 if (xxdialogMode) return;
4246 setDialogMode(2, "Open Page on Device", 3, deviceUrlFunctionEx, '<input id=d2devurl placeholder="http://server.com" style=width:100%;overflow-y:scroll onkeyup=deviceUrlFunctionValidate() onchange=deviceUrlFunctionValidate()></input>');
4247 Q('d2devurl').focus();
4248 deviceUrlFunctionValidate();
4249 }
4250
4251 function deviceUrlFunctionValidate() {
4252 var x = Q('d2devurl').value.toLowerCase();
4253 QE('idx_dlgOkButton', ((x.startsWith('http://') && (x.length > 7)) || (x.startsWith('https://') && (x.length > 8))));
4254 }
4255
4256 function deviceUrlFunctionEx() {
4257 meshserver.send({ action: 'msg', type: 'openUrl', nodeid: currentNode._id, url: Q('d2devurl').value });
4258 }
4259
4260 function runDeviceCmd(nodeid) { if (xxdialogMode) return; d2runCommandDialog({ nodeids: [ nodeid ? decodeURIComponent(nodeid) : currentNode._id ] }); }
4261
4262 function d2runCommandDialog(options) {
4263 var wintype = false, linuxtype = false, agenttype = false;
4264 for (var i in options.nodeids) {
4265 var n = getNodeFromId(options.nodeids[i]);
4266 if (n.agent) { if ((GetNodeRights(n) & 24) == 24) { agenttype = true; }
4267 if (isWindowsNode(n)) { wintype = true; } else { linuxtype = true; } }
4268 }
4269 if ((wintype == true) || (linuxtype == true) || (agenttype == true)) {
4270 // Fetch run options
4271 var runopt = { type:1, runAs:0, source:1, cmd:'' };
4272 try { runopt = JSON.parse(getstore('runopt', runopt)); } catch (ex) {}
4273
4274 if (options.selectedFile) {
4275 var filename = options.selectedFile.name.toLowerCase();
4276 console.log('filename', filename);
4277 if (filename.endsWith('.bat')) { runopt.type = 1; }
4278 if (filename.endsWith('.ps1')) { runopt.type = 2; }
4279 if (filename.endsWith('.sh')) { runopt.type = 3; }
4280 if (filename.endsWith('.agentconsole')) { runopt.type = 4; }
4281 }
4282
4283 var x = '';
4284 if (options.title) { x += options.title + '<br />'; }
4285 x += '<select id=d2cmdtype onclick=d2runCommandValidate() style=width:100%;margin-bottom:4px;margin-top:4px>';
4286 if (wintype == true) { x += '<option value=1' + ((runopt.type == 1)?' selected':'') + '>' + "Windows Command Prompt" + '</option><option value=2' + ((runopt.type == 2)?' selected':'') + '>' + "Windows PowerShell" + '</option>'; }
4287 if (linuxtype == true) { x += '<option value=3' + ((runopt.type == 3)?' selected':'') + '>' + "Linux/BSD/macOS Command Shell" + '</option>'; }
4288 if (agenttype == true) { x += '<option value=4' + ((runopt.type == 4)?' selected':'') + '>' + "Agent Console" + '</option>'; } // MESHRIGHT_REMOTECONTROL & MESHRIGHT_AGENTCONSOLE are needed
4289 x += '</select>';
4290 x += '<select id=d2cmduser style=width:100%;margin-bottom:4px><option value=0' + ((runopt.runAs == 0)?' selected':'') + '>' + "Run as agent" + '</option><option value=1' + ((runopt.runAs == 1)?' selected':'') + '>' + "Run as user, agent if no user" + '</option><option value=2' + ((runopt.runAs == 2)?' selected':'') + '>' + "Must run as user" + '</option></select>';
4291 if (options.selectedFile == null) {
4292 x += '<select id=d2cmdsource onclick=d2runCommandValidate() style=width:100%;margin-bottom:4px><option value=0' + ((runopt.source == 0)?' selected':'') + '>' + "Commands from text box" + '</option><option value=1' + ((runopt.source == 1)?' selected':'') + '>' + "Commands from file" + '</option>';
4293 if (userinfo.siteadmin & 8) { x += '<option value=2' + ((runopt.source == 2)?' selected':'') + '>' + "Commands from file on server" + '</option>'; }
4294 x += '</select><textarea id=d2runcmd onkeyup=d2runCommandValidate() style=background-color:#fcf3cf;width:100%;height:200px;resize:none;overflow-y:scroll>' + (runopt.cmd ? EscapeHtml(decodeURIComponent(runopt.cmd)) : '') + '</textarea>';
4295 x += '<div id=d2runfile style=display:none><input id=d2runfileex type=file onchange=d2runCommandValidate() id=d2localFile name=files onchange=d2runCommandValidate() /></div>';
4296 if (userinfo.siteadmin & 8) { x += '<div id=d2runsfile style=display:none><div id=d2serveraction valign=bottom><input type=button id=p2FolderUp disabled="disabled" onclick=d3folderup() value="Up" />&nbsp;<span id=p2CurrentFolder></span></div><div id=d2serverfiles></div></div>'; }
4297 }
4298 setDialogMode(2, "Run Commands", 3, d2groupActionFunctionRunCommands, x, options);
4299 if (options.selectedFile == null) {
4300 Q('d2runcmd').focus();
4301 if (userinfo.siteadmin & 8) { d3fileoptions = { dialog: 2, files: 'd2serverfiles', folderup: 'p2FolderUp', currentFolder: 'p2CurrentFolder', func: null }; d3updatefiles(); } // Update the server files
4302 }
4303 d2runCommandValidate();
4304 }
4305 }
4306
4307 function d2runCommandValidate() {
4308 QV('d2cmduser', Q('d2cmdtype').value < 4);
4309 if (xxdialogTag.selectedFile == null) {
4310 QV('d2runcmd', Q('d2cmdsource').value == 0);
4311 QV('d2runfile', Q('d2cmdsource').value == 1);
4312 QV('d2runsfile', Q('d2cmdsource').value == 2);
4313 var ok = false;
4314 if (Q('d2cmdsource').value == 0) { if (Q('d2runcmd').value.length > 0) { ok = true; } } // From text box
4315 if (Q('d2cmdsource').value == 1) { if (Q('d2runfileex').files.length == 1) { ok = true; } } // From file
4316 if (Q('d2cmdsource').value == 2) { ok = false; } // From server file
4317 QE('idx_dlgOkButton', ok);
4318 } else {
4319 QE('idx_dlgOkButton', true);
4320 }
4321 }
4322
4323 function d2groupActionFunctionRunCommands(b, options) {
4324 var type = 3;
4325 try { type = parseInt(Q('d2cmdtype').value); } catch (ex) { }
4326 if (options.selectedFile == null) { putstore('runopt', JSON.stringify({ type: type, runAs: parseInt(Q('d2cmduser').value), source: parseInt(Q('d2cmdsource').value), cmd: encodeURIComponent(Q('d2runcmd').value) })); } // Save run options
4327 var cmd = { action: 'runcommands', nodeids: options.nodeids, type: type, runAsUser: parseInt(Q('d2cmduser').value) };
4328 if (options.selectedFile) {
4329 // Drag & drop file
4330 var reader = new FileReader();
4331 reader.onload = function (e) { cmd.cmds = e.target.result; meshserver.send(cmd); if (options.func) { options.func(); } }
4332 reader.readAsText(options.selectedFile);
4333 } else if (Q('d2cmdsource').value == 0) {
4334 // From text box
4335 cmd.cmds = Q('d2runcmd').value;
4336 meshserver.send(cmd);
4337 if (options.func) { options.func(); }
4338 } else if (Q('d2cmdsource').value == 1) {
4339 // From file
4340 var reader = new FileReader();
4341 reader.onload = function (e) { cmd.cmds = e.target.result; meshserver.send(cmd); if (options.func) { options.func(); } }
4342 reader.readAsText(Q('d2runfileex').files[0]);
4343 } else if (Q('d2cmdsource').value == 2) {
4344 // From server file
4345 var files = d3getFileSel();
4346 if (files.length != 1) return;
4347 cmd.cmdpath = d3filetreelocation.join('/') + '/' + files[0];
4348 meshserver.send(cmd);
4349 if (options.func) { options.func(); }
4350 }
4351 }
4352
4353 // Look to see if we need to update the device timeline
4354 function updateDeviceTimeline() {
4355 if ((meshserver.State != 2) || (powerTimelineNode == null) || (powerTimelineUpdate == null) || (currentNode == null) || (currentNode.mtype == 3)) return;
4356 if ((powerTimelineNode == powerTimelineReq) && (currentNode._id == powerTimelineNode) && (powerTimelineUpdate < Date.now())) { powerTimelineUpdate = null; meshserver.send({ action: 'powertimeline', nodeid: currentNode._id }); }
4357 }
4358
4359 // Draw device power bars. The bars are 766px wide.
4360 function drawDeviceTimeline() {
4361 if (currentNode.mtype == 3 || hidePowerTimeline === 'true') { QH('p10html2', '<br />'); return; }
4362 var timeline = null, now = Date.now();
4363 if (currentNode._id == powerTimelineNode) { timeline = powerTimeline; }
4364
4365 // Calculate when the timeline starts
4366 var d = new Date();
4367 d.setHours(0, 0, 0, 0);
4368 d = new Date(d.getTime() - (1000 * 60 * 60 * 24 * 6));
4369 var timelineStart = d.getTime();
4370
4371 // De-compact the timeline
4372 var timeline2 = [];
4373 if (timeline != null && timeline.length > 1) {
4374 timeline2.push([0, timeline[1], timeline[0]]); // Start, End, Power
4375 var ct = timeline[1];
4376 for (var i = 2; i < timeline.length; i += 2) {
4377 var power = timeline[i], dt = now;
4378 if (timeline.length > (i + 1)) { dt = timeline[i + 1]; }
4379 timeline2.push([ct, ct + dt, power]); // Start, End, Power
4380 ct = ct + dt;
4381 }
4382 }
4383
4384 // Draw the timeline
4385 var x = '', count = 1, date = new Date();
4386 var totalWidth = Q('masthead').offsetWidth - (90 + 9 + 9 + 14); // Compute the total width of the power bar
4387 date.setHours(0, 0, 0, 0);
4388 for (var i = 0; i < 7; i++) {
4389 var datavalue = '', start = date.getTime(), end = start + (1000 * 60 * 60 * 24);
4390 for (var j in timeline2) {
4391 var block = timeline2[j];
4392 if (isTimeBlockInside(start, end, block[0], block[1]) == true) {
4393 var ts = Math.max(start, block[0]);
4394 var te = Math.min(Math.min(end, block[1]), now);
4395 var width = Math.round(((te - ts) * totalWidth) / 86400000);
4396 if (width > 0) { datavalue += '<div style=display:table-cell;width:' + width + 'px;background-color:' + powerColor(block[2]) + ';height:16px></div>'; }
4397 }
4398 }
4399 x += '<tr style=' + (((count % 2) == 0) ? 'background-color:#DDD' : '') + '><td><div>&nbsp;' + printDate(date) + '<div></div></div></td><td><div>' + datavalue + '</div></td></tr>';
4400 ++count;
4401 date = new Date(date.getFullYear(), date.getMonth(), date.getDate() - 1);
4402 }
4403 QH('p10html2', '<table style="color:black;background-color:#EEE;border-color:#AAA;border-width:1px;border-style:solid;border-collapse:collapse;width:calc(100% - 18px);margin:9px" border=0 cellpadding=2 cellspacing=0><tbody><tr style=background-color:#AAAAAA;font-weight:bold><th scope=col style=text-align:center;width:90px>' + "Day" + '</th><th scope=col style=text-align:center>' + "Power State" + '</th></tr>' + x + '</tbody></table>');
4404 }
4405
4406 // Return a color for the given power state
4407 function powerColor(x) { if (x < powerColorTable.length) { return powerColorTable[x]; } return 'yellow'; }
4408
4409 // Return true if the time block is visible within the start/end period
4410 function isTimeBlockInside(start, end, blockStart, blockEnd) {
4411 if ((blockStart < start) && (blockEnd > end)) return true; // Block is wider than timespan
4412 if ((blockStart > start) && (blockStart < end)) return true;
4413 if ((blockEnd > start) && (blockEnd < end)) return true;
4414 return false;
4415 }
4416
4417 function addDeviceAttribute(name, value) {
4418 return '<tr><td style=width:100px;color:gray>' + name + '</td><td style=overflow:hidden>' + value + '</td></tr>';
4419 }
4420
4421 function editDeviceAmtSettings(nodeid, func) {
4422 if (xxdialogMode) return;
4423 var x = '', node = getNodeFromId(nodeid), buttons = 3, meshrights = GetNodeRights(node);
4424 if ((meshrights & 4) == 0) return;
4425 x += addHtmlValue("Username", '<input id=dp10username style=width:170px maxlength=32 autocomplete=nope placeholder="admin" onchange=validateDeviceAmtSettings() onkeyup=validateDeviceAmtSettings() />');
4426 x += addHtmlValue("Password", '<input id=dp10password type=password style=width:170px autocomplete=nope maxlength=32 onchange=validateDeviceAmtSettings() onkeyup=validateDeviceAmtSettings() />');
4427 // Only display the TLS setting if the Intel AMT manager is not running on the server. With the manager TLS is auto-detected.
4428 if ((features2 & 1) == 0) { x += addHtmlValue("Security", '<select id=dp10tls style=width:176px><option value=0>' + "No TLS security" + '</option><option value=1>' + "TLS security required" + '</option></select>'); }
4429 if ((node.intelamt.user != null) && (node.intelamt.user != '')) { buttons = 7; }
4430 setDialogMode(2, "Edit Intel&reg; AMT credentials", buttons, editDeviceAmtSettingsEx, x, { node: node, func: func });
4431 if ((node.intelamt.user != null) && (node.intelamt.user != '')) { Q('dp10username').value = node.intelamt.user; } else { Q('dp10username').value = 'admin'; }
4432 if ((features2 & 1) == 0) { Q('dp10tls').value = node.intelamt.tls; }
4433 validateDeviceAmtSettings();
4434 }
4435
4436 function validateDeviceAmtSettings() {
4437 QE('idx_dlgOkButton', passwordcheck(Q('dp10password').value));
4438 }
4439
4440 function editDeviceAmtSettingsEx(button, tag) {
4441 if (button == 2) {
4442 // Delete button pressed, remove credentials
4443 meshserver.send({ action: 'changedevice', nodeid: tag.node._id, intelamt: { user: '', pass: '' } });
4444 } else {
4445 // Change Intel AMT credentials
4446 var amtuser = Q('dp10username').value;
4447 if (amtuser == '') amtuser = 'admin';
4448 var amtpass = Q('dp10password').value;
4449 if (amtpass == '') amtuser = '';
4450 var x = { action: 'changedevice', nodeid: tag.node._id, intelamt: { user: amtuser, pass: amtpass } };
4451 if ((features2 & 1) == 0) { x.intelamt.tls = parseInt(Q('dp10tls').value); }
4452 meshserver.send(x);
4453 if (tag.func) { setTimeout(tag.func, 1000); }
4454 }
4455 }
4456
4457 function p10showDeleteNodeDialog(nodeid) {
4458 if (xxdialogMode) return;
4459 setDialogMode(2, "Delete Node", 3, p10showDeleteNodeDialogEx, format("Delete {0}?", EscapeHtml(currentNode.name)) + '<br /><br /><label><input id=p10check type=checkbox onchange=p10validateDeleteNodeDialog() />' + "Confirm" + '</label>', nodeid);
4460 p10validateDeleteNodeDialog();
4461 }
4462
4463 function p10validateDeleteNodeDialog() {
4464 QE('idx_dlgOkButton', Q('p10check').checked);
4465 }
4466
4467 function p10showDeleteNodeDialogEx(buttons, nodeid) {
4468 meshserver.send({ action: 'removedevices', nodeids: [nodeid] });
4469 }
4470
4471 function p10WebRouter(nodeid, protocol, port, addr) {
4472 var relayid = null;
4473 var node = getNodeFromId(nodeid);
4474 if (node.mtype == 3) { // Setup device relay if needed
4475 var mesh = meshes[node.meshid];
4476 if (mesh && mesh.relayid) { relayid = mesh.relayid; addr = node.host; }
4477 }
4478 var servername = serverinfo.name;
4479 if ((servername.indexOf('.') == -1) || ((features & 2) != 0)) { servername = window.location.hostname; } // If the server name is not set or it's in LAN-only mode, use the URL hostname as server name.
4480 if (webRelayDns != '') { servername = webRelayDns; }
4481 var url = 'https://' + servername + ':' + webRelayPort + '/control-redirect.ashx?n=' + nodeid + '&p=' + port + '&appid=' + protocol + '&c=' + authRelayCookie; // Protocol: 1 = HTTP, 2 = HTTPS
4482 if (addr != null) { url += '&addr=' + addr; }
4483 if (relayid != null) { url += '&relayid=' + relayid; }
4484 safeNewWindow(url, 'WebRelay');
4485 return false;
4486 }
4487
4488 function p10rfb(nodeid, port) {
4489 var node = getNodeFromId(nodeid), addr = null;
4490 var mesh = meshes[node.meshid];
4491 if (port == null) { if (node.rfbport != null) { port = node.rfbport; } else { port = 5900; } }
4492 if (node.mtype == 3) { if (mesh && mesh.relayid) { nodeid = mesh.relayid; addr = node.host; } } // Setup device relay if needed
4493 meshserver.send({ action: 'getcookie', nodeid: nodeid, tcpport: port, tcpaddr: addr, tag: 'novnc', name: mesh ? mesh.name : null });
4494 }
4495
4496 function p10mstsc(nodeid, port) {
4497 var node = getNodeFromId(nodeid);
4498 var mesh = meshes[node.meshid];
4499 if (port == null) { if (node.rdpport != null) { port = node.rdpport; } else { port = 3389; } } // Adjust RDP port if needed
4500 meshserver.send({ action: 'getcookie', nodeid: nodeid, tcpport: port, tag: 'mstsc', name: mesh ? mesh.name : null });
4501 }
4502
4503 function p10ssh(nodeid, port) {
4504 var node = getNodeFromId(nodeid);
4505 var mesh = meshes[node.meshid];
4506 if (port == null) { if (node.sshport != null) { port = node.sshport; } else { port = 22; } }
4507 meshserver.send({ action: 'getcookie', nodeid: nodeid, tcpport: port, tag: 'ssh', name: mesh ? mesh.name : null });
4508 }
4509
4510 function p10showiconselector() {
4511 if (xxdialogMode) return;
4512 var rights = GetNodeRights(currentNode);
4513 if ((rights & 4) == 0) return;
4514
4515 var x = '<table align=center><td style=text-align:center>';
4516 x += '<div style=display:inline-block class=i1 onclick=p10setIcon(1)></div>';
4517 x += '<div style=display:inline-block class=i2 onclick=p10setIcon(2)></div>';
4518 x += '<div style=display:inline-block class=i3 onclick=p10setIcon(3)></div>';
4519 x += '<div style=display:inline-block class=i4 onclick=p10setIcon(4)></div><br />';
4520 x += '<div style=display:inline-block class=i5 onclick=p10setIcon(5)></div>';
4521 x += '<div style=display:inline-block class=i6 onclick=p10setIcon(6)></div>';
4522 x += '<div style=display:inline-block class=i7 onclick=p10setIcon(7)></div>';
4523 x += '<div style=display:inline-block class=i8 onclick=p10setIcon(8)></div></table>';
4524 setDialogMode(2, "Icon Selection", 0, null, x);
4525 QV('id_dialogclose', true);
4526 }
4527
4528 function p10setIcon(icon) {
4529 setDialogMode(0);
4530 meshserver.send({ action: 'changedevice', nodeid: currentNode._id, icon: icon });
4531 }
4532
4533 function showClearSshDialog() { setDialogMode(2, "Edit Device", 3, showClearSshDialogEx, "Clear SSH credentials?"); }
4534 function showClearSshDialogEx(button, mode) { meshserver.send({ action: 'changedevice', nodeid: currentNode._id, ssh: 0 }); }
4535 function showClearRdpDialog() { setDialogMode(2, "Edit Device", 3, showClearRdpDialogEx, "Clear RDP credentials?"); }
4536 function showClearRdpDialogEx(button, mode) { meshserver.send({ action: 'changedevice', nodeid: currentNode._id, rdp: 0 }); }
4537
4538 var showEditNodeValueDialog_modes = ["Device Name", "Hostname", "Description", "Tags"];
4539 var showEditNodeValueDialog_modes2 = ['name', 'host', 'desc', 'tags'];
4540 var showEditNodeValueDialog_modes3 = ['', '', '', "Group1, Group2, Group3"];
4541 function showEditNodeValueDialog(mode) {
4542 if (xxdialogMode) return;
4543 var x = addHtmlValue(showEditNodeValueDialog_modes[mode], '<input id=dp10devicevalue style=width:170px maxlength=64 placeholder="' + showEditNodeValueDialog_modes3[mode] + '" onchange=p10editdevicevalueValidate(' + mode + ',event) onkeyup=p10editdevicevalueValidate(' + mode + ',event) />');
4544 if (mode == 3) {
4545 // Get a list of all possible device tags
4546 var allTags = [], y = '';
4547 for (var i in nodes) { if (nodes[i].tags) { for (var j in nodes[i].tags) { if (allTags.indexOf(nodes[i].tags[j]) == -1) { allTags.push(nodes[i].tags[j]); } } } }
4548 if (allTags.length > 0) {
4549 allTags.sort();
4550 for (var i in allTags) { y += '<span style=padding:4px;background-color:#BBB;border-radius:3px;cursor:pointer onclick=showEditNodeValueDialogAddTag("' + encodeURIComponentEx(allTags[i]) + '")>' + EscapeHtml(allTags[i]) + '</span> '; }
4551 x += '<div style=margin-top:8px;width:280px;line-height:26px;max-height:160px;overflow-y:auto>' + y + '</div>';
4552 }
4553 }
4554 setDialogMode(2, "Edit Device", 3, showEditNodeValueDialogEx, x, mode);
4555 var v = currentNode[showEditNodeValueDialog_modes2[mode]];
4556 if (v == null) v = '';
4557 if (Array.isArray(v)) { v = v.join(', '); }
4558 Q('dp10devicevalue').value = v;
4559 p10editdevicevalueValidate();
4560 Q('dp10devicevalue').focus();
4561 }
4562
4563 function showEditNodeValueDialogAddTag(t) {
4564 var tt = Q('dp10devicevalue').value.split(','), t2 = [];
4565 for (var i in tt) { t2.push(tt[i].trim()); }
4566 if (t2.indexOf(t) >= 0) return;
4567 Q('dp10devicevalue').value += ((Q('dp10devicevalue').value.length == 0) ? '' : ', ') + decodeURIComponent(t);
4568 setTimeout(function () { Q('dp10devicevalue').selectionStart = Q('dp10devicevalue').selectionEnd = 90000; }, 0);
4569 p10editdevicevalueValidate();
4570 }
4571
4572 function showEditNodeValueDialogEx(button, mode) {
4573 var x = { action: 'changedevice', nodeid: currentNode._id };
4574 x[showEditNodeValueDialog_modes2[mode]] = Q('dp10devicevalue').value;
4575 meshserver.send(x);
4576 }
4577
4578 function p10editdevicevalueValidate(mode, e) {
4579 var x = ((mode > 1) || (Q('dp10devicevalue').value.length > 0));
4580 QE('idx_dlgOkButton', x);
4581 if ((e != null) && (x == true) && (e.keyCode == 13)) { dialogclose(1); }
4582 }
4583
4584 //
4585 // DESKTOP
4586 //
4587
4588 var desktop;
4589 var desktopNode;
4590 var desktopsettings = { encoding: 2, showfocus: false, showmouse: true, showcad: true, quality: 40, scaling: 1024, framerate: 50, autolock: false, agentencoding: 4 };
4591 function setupDesktop() {
4592 // Setup the remote desktop
4593 if ((desktopNode != currentNode) && (desktop != null)) { desktop.Stop(); desktopNode = null; desktop = null; }
4594
4595 // If the device desktop is already connected in multi-desktop, use that.
4596 if ((desktopNode != currentNode) || (desktop == null)) {
4597 // Device is not already connected, just setup a blank canvas
4598 //QH('DeskParent', '<canvas id=Desk width=640 height=200 style="width:100%;-ms-touch-action:none;margin-left:0px" oncontextmenu="return false" onmousedown=dmousedown(event) onmouseup=dmouseup(event) onmousemove=dmousemove(event)></canvas>');
4599 desktopNode = currentNode;
4600 // Setup the mouse wheel
4601 Q('Desk').addEventListener('DOMMouseScroll', function (e) { return dmousewheel(e); });
4602 Q('Desk').addEventListener('mousewheel', function (e) { return dmousewheel(e); });
4603 }
4604 desktopNode = currentNode;
4605 updateDesktopButtons();
4606
4607 // On some browsers like IE, we can't save screen shots. Hide the scheenshot/capture buttons.
4608 if (!Q('Desk')['toBlob']) { QV('deskSaveBtn', false); }
4609 }
4610
4611 // Show and enable the right buttons
4612 function updateDesktopButtons() {
4613 var mesh = meshes[currentNode.meshid];
4614 var deskState = 0;
4615 if (desktop != null) { deskState = desktop.State; }
4616 var meshrights = GetNodeRights(currentNode);
4617
4618 // Show the right buttons
4619 QV('disconnectbutton1', (deskState != 0));
4620 QE('deskFullScreen', (deskState != 0));
4621 QV('connectbutton1', (deskState == 0) && ((meshrights & 8) || (meshrights & 256)) && (currentNode.agent != null) && (currentNode.agent.caps & 1));
4622 QV('connectbutton1h',
4623 (deskState == 0) &&
4624 (meshrights & 8) &&
4625 (
4626 ((currentNode.intelamt != null) &&
4627 (currentNode.intelamt.state == 2) &&
4628 (currentNode.intelamt.ver != null) &&
4629 ((currentNode.intelamt.sku == null) ||
4630 ((typeof currentNode.intelamt.sku == 'number') &&
4631 ((currentNode.intelamt.sku & 8) != 0))))
4632 )
4633 );
4634
4635 // Show the right settings
4636 QV('d7amtkvm', (currentNode.intelamt != null && ((typeof currentNode.intelamt.sku != 'number') || ((currentNode.intelamt.sku & 16) == 0)) && ((currentNode.intelamt.ver != null) || (currentNode.agent == null))) && ((deskState == 0) || (desktop.contype == 2)));
4637 QV('d7meshkvm', ((currentNode.agent != null) && (currentNode.agent.caps & 1) && ((deskState == false) || (desktop.contype == 1))));
4638
4639 // Enable buttons
4640 var online = ((currentNode.conn & 1) != 0); // If Agent (1) connected, enable remote desktop
4641 QE('connectbutton1', online);
4642 var hwonline = ((currentNode.conn & 6) != 0); // If CIRA (2) or AMT (4) connected, enable hardware terminal
4643 QE('connectbutton1h', hwonline);
4644 //QE('deskSaveBtn', deskState == 3);
4645 //QV('DeskCAD', meshrights & 8);
4646 //QE('DeskCAD', deskState == 3);
4647 //QV('DeskWD', (currentNode.agent) && (currentNode.agent.id < 5));
4648 //QE('DeskWD', deskState == 3);
4649 //QV('deskkeys', (currentNode.agent) && (currentNode.agent.id < 5));
4650 //QE('deskkeys', deskState == 3);
4651 //QE('DeskToolsButton', online);
4652 QV('DeskToastButton', ((meshrights & 16384) != 0) && (currentNode.agent) && (currentNode.agent.id < 5) && (meshrights & 8));
4653 //QE('DeskToastButton', online);
4654 QV('deskActionsBtn', meshrights & 8);
4655 Q('DeskControl').checked = ((meshrights & 8) != 0);
4656 if (online == false) QV('DeskTools', false);
4657 }
4658
4659 // Used to translate incoming agent console messages
4660 var agentConsoleMessages = ['', "Waiting for user to grant access...", "Denied", "Failed to start remote terminal session, {0} ({1})", "Timeout", "Received invalid network data"];
4661 function formatAgentConsoleMessage(msg, msgid, msgargs) {
4662 var r;
4663 if (msgargs == null) { msgargs = []; }
4664 while (msgargs.length < 3) { msgargs.push(''); } // We need to call the format function in a way that works with older browsers and minifier, can't use apply() or ...
4665 if (msgid && (msgid < agentConsoleMessages.length)) { r = EscapeHtml(format(agentConsoleMessages[msgid], (msgargs[0]), (msgargs[1]), (msgargs[2]))); } else { r = EscapeHtml(msg); }
4666 return r.split('\n').join('<br />') + '<br /><br />';
4667 }
4668
4669 function connectDesktop(e, contype, tsid, consent) {
4670 setSessionActivity();
4671 QV('p11DeskSessionSelector', false);
4672 p11clearConsoleMsg();
4673 if (desktop == null) {
4674 desktopNode = currentNode;
4675 if (contype == 2) {
4676 // Setup the Intel AMT remote desktop
4677 if ((desktopNode.intelamt.user == null) || (desktopNode.intelamt.user == '')) { editDeviceAmtSettings(desktopNode._id, connectDesktop); return; }
4678 desktop = CreateAmtRedirect(CreateAmtRemoteDesktop('Desk'), authCookie);
4679 desktop.debugmode = debugmode;
4680 desktop.onStateChanged = onDesktopStateChange;
4681 desktop.m.bpp = (desktopsettings.encoding == 1 || desktopsettings.encoding == 3) ? 1 : 2;
4682 desktop.m.useZRLE = (desktopsettings.encoding < 3);
4683 desktop.m.showmouse = true;
4684 desktop.m.onScreenSizeChange = function (o, x, y) { if (fullscreen) { QS('deskarea3').width = (x * fullscreenzoom) + 'px'; QS('deskarea3').height = (y * fullscreenzoom) + 'px'; } deskAdjust(); }
4685 // Use TLS if TLS is set
4686 if (desktopNode.conn==4 && desktopNode.intelamt!=null && desktopNode.intelamt.tls==1) {
4687 desktop.Start(desktopNode._id, 16995, '*', '*', 1);
4688 } else {
4689 desktop.Start(desktopNode._id, 16994, '*', '*', 0);
4690 }
4691 desktop.contype = 2;
4692 } else if ((contype == null) || (contype == 1) || ((contype == 3) && (currentNode.agent.id > 4))) {
4693 // Setup the Mesh Agent remote desktop
4694 desktop = CreateAgentRedirect(meshserver, CreateAgentRemoteDesktop('Desk'), serverPublicNamePort, authCookie, authRelayCookie, domainUrl);
4695 desktop.debugmode = debugmode;
4696 desktop.m.debugmode = debugmode;
4697 desktop.attemptWebRTC = attemptWebRTC;
4698 desktop.webrtcconfig = webrtcconfiguration;
4699 desktop.options = {};
4700 if (tsid != null) { desktop.options.tsid = tsid; }
4701 if (consent != null) { desktop.options.consent = consent; }
4702 if (desktopsettings.autolock == true) { desktop.options.autolock = true; }
4703 desktop.onStateChanged = onDesktopStateChange;
4704 if ((features2 & 0x2000) != 0) desktop.m.stopInput = true;
4705 desktop.onConsoleMessageChange = function () {
4706 if (desktop.consoleMessage) {
4707 Q('p11DeskConsoleMsg').innerHTML += formatAgentConsoleMessage(desktop.consoleMessage, desktop.consoleMessageId, desktop.consoleMessageArgs);
4708 QV('p11DeskConsoleMsg', true);
4709 if (p11DeskConsoleMsgTimer != null) { clearTimeout(p11DeskConsoleMsgTimer); }
4710 if (desktop.consoleMessageTimeout) { p11DeskConsoleMsgTimer = setTimeout(p11clearConsoleMsg, desktop.consoleMessageTimeout * 1000); }
4711 } else {
4712 p11clearConsoleMsg();
4713 }
4714 }
4715 desktop.m.ImageType = desktopsettings.agentencoding; // Send 4 if WebP is supported, otherwise send 1 for JPEG.
4716 desktop.m.CompressionLevel = desktopsettings.quality; // Number from 1 to 100. 50 or less is best.
4717 desktop.m.ScalingLevel = desktopsettings.scaling;
4718 desktop.m.FrameRateTimer = desktopsettings.framerate;
4719 desktop.m.onDisplayinfo = deskDisplayInfo;
4720 desktop.m.onScreenSizeChange = function (o, x, y) { if (fullscreen) { QS('deskarea3').width = (x * fullscreenzoom) + 'px'; QS('deskarea3').height = (y * fullscreenzoom) + 'px'; } deskAdjust(); }
4721 desktop.Start(desktopNode._id);
4722 desktop.contype = 1;
4723 } else if (contype == 3) {
4724 // Ask for user sessions
4725 meshserver.send({ action: 'msg', type: 'userSessions', nodeid: currentNode._id, tag: consent });
4726 }
4727 } else {
4728 // Disconnect and clean up the remote desktop
4729 desktop.Stop();
4730 desktopNode = desktop = null;
4731 }
4732 }
4733
4734 function p11clearConsoleMsg() { QH('p11DeskConsoleMsg', ''); QV('p11DeskConsoleMsg', false); if (p11DeskConsoleMsgTimer) { clearTimeout(p11DeskConsoleMsgTimer); p11DeskConsoleMsgTimer = null; } }
4735 function p12clearConsoleMsg() { QH('p12TermConsoleMsg', ''); QV('p12TermConsoleMsg', false); if (p12TermConsoleMsgTimer) { clearTimeout(p12TermConsoleMsgTimer); p12TermConsoleMsgTimer = null; } }
4736 function p13clearConsoleMsg() { QH('p13FilesConsoleMsg', ''); QV('p13FilesConsoleMsg', false); if (p13FilesConsoleMsgTimer) { clearTimeout(p13FilesConsoleMsgTimer); p13FilesConsoleMsgTimer = null; } }
4737
4738 function p12setConsoleMsg(msg, timeout) {
4739 if (msg) {
4740 Q('p12TermConsoleMsg').innerHTML += msg;
4741 QV('p12TermConsoleMsg', true);
4742 if (p12TermConsoleMsgTimer != null) { clearTimeout(p12TermConsoleMsgTimer); }
4743 if (timeout) { p12TermConsoleMsgTimer = setTimeout(p12clearConsoleMsg, timeout); }
4744 } else {
4745 p12clearConsoleMsg();
4746 }
4747 }
4748
4749 function p13setConsoleMsg(msg, timeout) {
4750 if (msg) {
4751 Q('p13FilesConsoleMsg').innerHTML += msg;
4752 QV('p13FilesConsoleMsg', true);
4753 if (p13FilesConsoleMsgTimer != null) { clearTimeout(p13FilesConsoleMsgTimer); }
4754 if (timeout) { p13FilesConsoleMsgTimer = setTimeout(p13clearConsoleMsg, timeout); }
4755 } else {
4756 p13clearConsoleMsg();
4757 }
4758 }
4759
4760 function onDesktopStateChange(xdesktop, state) {
4761 var xstate = state;
4762 if ((xstate == 3) && (xdesktop.contype == 2)) { xstate++; }
4763 var str = StatusStrs[xstate];
4764 if ((desktop != null) && (desktop.webRtcActive == true)) { str += ", WebRTC"; }
4765 //if (desktop.m.stopInput == true) { str += ', Loopback'; }
4766 QH('deskstatus', str);
4767 switch (state) {
4768 case 0:
4769 // Disconnect and clean up the remote desktop
4770 desktop.Stop();
4771 desktopNode = desktop = null;
4772 QV('DeskScreens', false);
4773 if (fullscreen == true) { deskToggleFull(); }
4774 break;
4775 case 2:
4776 break;
4777 default:
4778 //console.log('Unknown onDesktopStateChange state', state);
4779 break;
4780 }
4781 updateDesktopButtons();
4782 deskAdjust();
4783 setTimeout(deskAdjust, 50);
4784 }
4785
4786 function showDesktopSettings() {
4787 if (xxdialogMode) return;
4788 applyDesktopSettings();
4789 updateDesktopButtons();
4790 setDialogMode(7, "Remote Desktop Settings", 3, showDesktopSettingsChanged);
4791 }
4792
4793 function showDesktopSettingsChanged() {
4794 desktopsettings.encoding = d7desktopmode.value;
4795 desktopsettings.quality = d7bitmapquality.value;
4796 desktopsettings.scaling = d7bitmapscaling.value;
4797 desktopsettings.framerate = d7framelimiter.value;
4798 desktopsettings.autolock = d7deskAutoLock.checked;
4799 desktopsettings.agentencoding = d7encoding.value;
4800 localStorage.setItem('desktopsettings', JSON.stringify(desktopsettings));
4801 applyDesktopSettings();
4802 if (desktop) {
4803 if (desktop.contype == 1) {
4804 if (desktop.State != 0) { desktop.m.SendCompressionLevel(desktopsettings.agentencoding, desktopsettings.quality, desktopsettings.scaling, desktopsettings.framerate); }
4805 desktop.sendCtrlMsg('{"ctrlChannel":"102938","type":"autolock","value":' + desktopsettings.autolock + '}');
4806 desktop.m.SendRefresh();
4807 }
4808 if (desktop.contype == 2) {
4809 if (desktop.State != 0) { desktop.Stop(); setTimeout(function () { connectDesktop(null, 2); }, 50); }
4810 }
4811 }
4812 }
4813
4814 function applyDesktopSettings() {
4815 var r = '', ops = (features & 512) ? [100, 90, 70, 50, 40, 30, 20, 10, 5, 1] : [50, 40, 30, 20, 10, 5, 1];
4816 for (var i in ops) { r += '<option value=' + ops[i] + '>' + ops[i] + '%</option>'; }
4817 QH('d7bitmapquality', r);
4818 d7desktopmode.value = desktopsettings.encoding;
4819 d7bitmapquality.value = 40; // Default value
4820 if (desktopsettings.agentencoding) { d7encoding.value = desktopsettings.agentencoding; } else { desktopsettings.agentencoding = 4; }
4821 if (ops.indexOf(parseInt(desktopsettings.quality)) >= 0) { d7bitmapquality.value = desktopsettings.quality; }
4822 d7bitmapscaling.value = desktopsettings.scaling;
4823 if (desktopsettings.framerate) { d7framelimiter.value = desktopsettings.framerate; }
4824 if (desktopsettings.autolock != null) { d7deskAutoLock.checked = desktopsettings.autolock; }
4825 }
4826
4827
4828 var keyboardShown = false;
4829 var keyboardShownTimer = null;
4830 var fullScreenMode = false;
4831 function toggleKeyboard() {
4832 if (xxdialogMode) return;
4833 if (keyboardShownTimer != null) { clearTimeout(keyboardShownTimer); }
4834 if (keyboardShown) { Q('softKeyboard').blur(); keyboardShown = false; } else { Q('softKeyboard').focus(); keyboardShown = true; }
4835 QV('deskkeybutton2a', fullscreen && !keyboardShown);
4836 QV('deskkeybutton2b', fullscreen && keyboardShown);
4837 }
4838
4839 function keyboardFocusChange() {
4840 keyboardShownTimer = setTimeout(function () {
4841 keyboardShownTimer = null;
4842 keyboardShown = (Q('softKeyboard') == document.activeElement);
4843 QV('deskkeybutton2a', fullscreen && !keyboardShown);
4844 QV('deskkeybutton2b', fullscreen && keyboardShown);
4845 }, 10);
4846 }
4847
4848 function exitButton() {
4849 if (xxdialogMode) return;
4850 QV('deskButtonMenu', false);
4851 QV('termButtonMenu', false);
4852 deskToggleFull();
4853 }
4854
4855 function deskMenuButton(x) {
4856 toggleMenu(true);
4857 deskSendKeys(x);
4858 }
4859
4860 //
4861 // Desktop Shortcut Keys
4862 //
4863
4864 function updateDeskShortcutKeys() {
4865 var x = '<div class="menuButton" onclick="deskMenuButton(-1)">' + "Customize" + '</div>';
4866 for (var i in deskKeyboardShortcuts) { x += '<div class="menuButton" onclick="deskMenuButton(' + deskKeyboardShortcuts[i] + ')">' + keyShortcutTotext(deskKeyboardShortcuts[i]) + '</div>'; }
4867 QH('deskButtonMenu', x);
4868 }
4869
4870 var keyStrings = { 8: "BackSpace", 9: "Tab", 13: "Enter", 27: "Escape", 32: "Space", 44: "Print Screen", 45: "Insert", 46: "Del", 36: "Home", 35: "End", 32: "Espace", 33: "Page Up", 34: "Page Down", 37: "Left", 38: "Up", 39: "Right", 40: "Down", 0: "None" }
4871
4872 function keyShortcutTotext(n) {
4873 var x = [];
4874 if (n & 0x010000) { x.push("Shift"); }
4875 if (n & 0x020000) { x.push("Alt"); }
4876 if (n & 0x080000) { x.push("Ctrl"); }
4877 if (n & 0x100000) { x.push("Win"); }
4878 n = (n & 0xFFFF);
4879 if ((n >= 112) && (n <= 123)) { x.push('F' + (n - 111)); } // Fx keys
4880 else if ((n != 0) && (keyStrings[n])) { x.push(keyStrings[n]); }
4881 else { if (n != 0) { x.push(String.fromCharCode(n)); } }
4882 return x.join(' + ');
4883 }
4884
4885 // Customize keyboard shortcuts
4886 function deskCustomizeKeys() {
4887 if (xxdialogMode) return;
4888 var x = '<div id=d2shortcuts style="width:100%;height:180px;padding:4px;overflow-y:auto;border:1px solid gray"></div><div style=width:100%;padding:5px>';
4889 x += '<label><input id=d1kshift type=checkbox /> ' + "Shift" + '</label><label> <input id=d1kalt type=checkbox /> ' + "Alt" + '</label><label> <input id=d1kctrl type=checkbox /> ' + "Ctrl" + '</label> <input id=d1kwin type=checkbox /> ' + "Win" + '</label>';
4890 x += ' <select id=d2keySelect>';
4891 for (var i in keyStrings) { x += '<option value=' + i + '>' + keyStrings[i] + '</option>'; }
4892 for (var i = 1; i <= 12; i++) { x += '<option value=' + (i + 111) + '>F' + i + '</option>'; }
4893 for (var i = 0; i < 10; i++) { x += '<option value=' + (i + 48) + '>' + i + '</option>'; }
4894 for (var i = 0; i < 26; i++) { x += '<option value=' + (i + 65) + '>' + String.fromCharCode(i + 65) + '</option>'; }
4895 x += '</select> <input type=button value=' + "Add" + ' onclick=addDeskCustomizeKey() /></div>';
4896 QH('p10dialog2', x);
4897 xxdialogMode = 2;
4898 QV('p10dialog', true);
4899 deskUpdateShortcutList();
4900 }
4901
4902 function deskCustomizeKeysEx() {
4903 QV('p10dialog', false);
4904 xxdialogMode = 0;
4905 putstore('deskKeyShortcuts', deskKeyboardShortcuts.join(','));
4906 updateDeskShortcutKeys();
4907 }
4908
4909 function restoreDeskCustomizeKey() {
4910 deskKeyboardShortcuts = [];
4911 putstore('deskKeyShortcuts', null);
4912 var deskKeyboardShortcutsStr = getstore('deskKeyShortcuts', '0x0A002E,0x100000,0x100028,0x100026,0x10004C,0x10004D,0x11004D,0x100052,0x020073,0x080057,0x020009,0x100025,0x100027').split(',');
4913 for (var i in deskKeyboardShortcutsStr) { if (deskKeyboardShortcutsStr[i] != "") { deskKeyboardShortcuts.push(parseInt(deskKeyboardShortcutsStr[i])); } }
4914 updateDeskShortcutKeys();
4915 deskUpdateShortcutList();
4916 }
4917
4918 function deskUpdateShortcutList() {
4919 var x = '';
4920 for (var i in deskKeyboardShortcuts) {
4921 var kt = keyShortcutTotext(deskKeyboardShortcuts[i]), orderButtons = '';
4922 if (i != (deskKeyboardShortcuts.length - 1)) { orderButtons += '<img width=8 height=8 style=float:right;cursor:pointer;padding:3px src="images/c2.png" onclick=deskCustomizeKeyDown(' + deskKeyboardShortcuts[i] + ')>'; }
4923 if (i != 0) { orderButtons += '<img width=8 height=8 style=float:right;cursor:pointer;padding:3px src="images/c3.png" onclick=deskCustomizeKeyUp(' + deskKeyboardShortcuts[i] + ')>'; }
4924 x += '<div style="width:100%;background-color:#AAA;border-radius:4px;margin-bottom:4px;padding:4px;text-align:left;box-sizing:border-box" value=' + deskKeyboardShortcuts[i] + '>' + kt + '<img width=10 height=10 style=float:right;cursor:pointer;padding:2px;margin-left:8px src="images/trash.png" onclick=removeDeskCustomizeKey(' + deskKeyboardShortcuts[i] + ')>' + orderButtons + '</div>';
4925 }
4926 if (x == '') { x = '<i>' + "No keyboard shortcuts defined" + '</i>'; }
4927 QH('d2shortcuts', x);
4928 }
4929
4930 function deskCustomizeKeyDown(k) {
4931 var i = deskKeyboardShortcuts.indexOf(k), x = deskKeyboardShortcuts[i + 1];
4932 deskKeyboardShortcuts[i + 1] = deskKeyboardShortcuts[i];
4933 deskKeyboardShortcuts[i] = x;
4934 deskUpdateShortcutList();
4935 }
4936
4937 function deskCustomizeKeyUp(k) {
4938 var i = deskKeyboardShortcuts.indexOf(k), x = deskKeyboardShortcuts[i];
4939 deskKeyboardShortcuts[i] = deskKeyboardShortcuts[i - 1];
4940 deskKeyboardShortcuts[i - 1] = x;
4941 deskUpdateShortcutList();
4942 }
4943
4944 function removeDeskCustomizeKey(k) {
4945 var na = [];
4946 for (var i in deskKeyboardShortcuts) { if (deskKeyboardShortcuts[i] != k) { na.push(deskKeyboardShortcuts[i]); } }
4947 deskKeyboardShortcuts = na;
4948 deskUpdateShortcutList();
4949 }
4950
4951 function addDeskCustomizeKey() {
4952 var k = parseInt(Q('d2keySelect').value);
4953 if (Q('d1kshift').checked) { k |= 0x010000; }
4954 if (Q('d1kalt').checked) { k |= 0x020000; }
4955 if (Q('d1kctrl').checked) { k |= 0x080000; }
4956 if (Q('d1kwin').checked) { k |= 0x100000; }
4957 if ((k > 0) && (deskKeyboardShortcuts.indexOf(k) == -1)) { deskKeyboardShortcuts.push(k); deskUpdateShortcutList(); }
4958 }
4959
4960 // Remote desktop special key combos for Windows
4961 function deskSendKeys(ks) {
4962 if (xxdialogMode || desktop == null || desktop.State != 3) return;
4963
4964 // Construct the key command
4965 if (ks == -1) { deskCustomizeKeys(); return; } // Customize
4966 if (ks == 0x0A002E) { desktop.m.sendcad(); return; } // CTRL-ALT-DEL
4967 //if ((desktop.contype == 1) && (ks == 0x10004C)) { desktop.sendCtrlMsg('{"action":"lock"}'); return; } // Lock desktop, WIN + L
4968
4969 var flags = (ks & 0xFF0000) >> 16, key = (ks & 0xFFFF), keyArray = [], keyArray2 = [];
4970 var amtTranslate = {
4971 8: 0xff08, // BackSpace
4972 9: 0xff09, // Tab
4973 13: 0xff0d, // Return or Enter
4974 27: 0xff1b, // Escape
4975 45: 0xff63, // Insert
4976 46: 0xffff, // Delete
4977 36: 0xff50, // Home
4978 35: 0xff57, // End
4979 33: 0xff55, // Page Up
4980 34: 0xff56, // Page Down
4981 37: 0xff51, // Left arrow
4982 38: 0xff52, // Up arrow
4983 39: 0xff53, // Right arrow
4984 40: 0xff54, // Down arrow
4985 112: 0xffbe, // F1
4986 113: 0xffbf, // F2
4987 114: 0xffc0, // F3
4988 115: 0xffc1, // F4
4989 116: 0xffc2, // F5
4990 117: 0xffc3, // F6
4991 118: 0xffc4, // F7
4992 119: 0xffc5, // F8
4993 120: 0xffc6, // F9
4994 121: 0xffc7, // F10
4995 122: 0xffc8, // F11
4996 123: 0xffc9 // F12
4997 }
4998
4999 // 0x010000 = Shift
5000 // 0x020000 = Left-Alt
Showing first 5,000 of 7,991 lines. View raw