Improved mobile app

Ylian Saint-Hilaire committed Jun 5, 2018 at 13:28 UTC 53c7eae4d28764f205b4a7295799fe9fb694a71f
7 files changed +442 -65
meshagent.js
+1 -1
@@ -467,7 +467,7 @@ module.exports.CreateMeshAgent = function (parent, db, ws, req, args, domain) {
467 var cmdstr = JSON.stringify(command);
468 for (var userid in obj.parent.wssessions) { // Find all connected users for this mesh and send the message
469 var user = obj.parent.users[userid];
470 - if (user) {
470 + if ((user != null) && (user.links != null)) {
471 var rights = user.links[obj.dbMeshKey];
472 if (rights != null) { // TODO: Look at what rights are needed for message routing
473 var sessions = obj.parent.wssessions[userid];
mpsserver.js
+11 -7
@@ -135,7 +135,7 @@ module.exports.CreateMpsServer = function (parent, db, args, certificates) {
135 if (nodes.length == 0) {
136 if (mesh.mtype == 1) {
137 // Node is not in the database, add it. Credentials will be empty until added by the user.
138 - var device = { type: 'node', mtype: 1, _id: socket.tag.nodeid, meshid: socket.tag.meshid, name: socket.tag.name, host: null, domain: domainid, intelamt: { user: '', pass: '', tls: 0 } };
138 + var device = { type: 'node', mtype: 1, _id: socket.tag.nodeid, meshid: socket.tag.meshid, name: socket.tag.name, host: null, domain: domainid, intelamt: { user: '', pass: '', tls: 0, state: 2 } };
139 obj.db.Set(device);
140
141 // Event the new node
@@ -152,7 +152,7 @@ module.exports.CreateMpsServer = function (parent, db, args, certificates) {
152 } else {
153 // Node is already present
154 var node = nodes[0];
155 - if (node.intelamt != undefined) { socket.tag.host = node.intelamt.host; }
155 + if ((node.intelamt != undefined) && (node.intelamt.state == 2)) { socket.tag.host = node.intelamt.host; }
156 }
157
158 // Add the connection to the MPS connection list
@@ -249,7 +249,7 @@ module.exports.CreateMpsServer = function (parent, db, args, certificates) {
249 if (nodes.length == 0) {
250 if (mesh.mtype == 1) {
251 // Node is not in the database, add it. Credentials will be empty until added by the user.
252 - var device = { type: 'node', mtype: 1, _id: socket.tag.nodeid, meshid: socket.tag.meshid, name: socket.tag.name, host: null, domain: mesh.domain, intelamt: { user: '', pass: '', tls: 0 } };
252 + var device = { type: 'node', mtype: 1, _id: socket.tag.nodeid, meshid: socket.tag.meshid, name: socket.tag.name, host: null, domain: mesh.domain, intelamt: { user: '', pass: '', tls: 0, state: 2 } };
253 obj.db.Set(device);
254
255 // Event the new node
@@ -266,7 +266,7 @@ module.exports.CreateMpsServer = function (parent, db, args, certificates) {
266 } else {
267 // Node is already present
268 var node = nodes[0];
269 - if (node.intelamt != undefined) { socket.tag.host = node.intelamt.host; }
269 + if ((node.intelamt != undefined) && (node.intelamt.state == 2)) { socket.tag.host = node.intelamt.host; }
270 }
271
272 // Add the connection to the MPS connection list
@@ -286,7 +286,7 @@ module.exports.CreateMpsServer = function (parent, db, args, certificates) {
286
287 // Node is present
288 var node = nodes[0];
289 - if (node.intelamt != undefined) { socket.tag.host = node.intelamt.host; }
289 + if ((node.intelamt != undefined) && (node.intelamt.state == 2)) { socket.tag.host = node.intelamt.host; }
290 socket.tag.nodeid = node._id;
291 socket.tag.meshid = mesh._id;
292 socket.tag.connectTime = Date.now();
@@ -667,7 +667,10 @@ module.exports.CreateMpsServer = function (parent, db, args, certificates) {
667 obj.db.Get(socket.tag.nodeid, function (err, nodes) {
668 if (nodes.length != 1) return;
669 var node = nodes[0];
670 - if ((node.intelamt != undefined) && (node.intelamt.host == host)) return;
670 +
671 + // See if any changes need to be made
672 + console.log(node.intelamt);
673 + if ((node.intelamt != undefined) && (node.intelamt.host == host) && (node.name != '') && (node.intelamt.state == 2)) return;
674
675 // Get the mesh for this device
676 obj.db.Get(node.meshid, function (err, meshes) {
@@ -681,9 +684,10 @@ module.exports.CreateMpsServer = function (parent, db, args, certificates) {
684 // Make the change & save
685 if (node.intelamt == undefined) node.intelamt = {};
686 node.intelamt.host = host;
687 + node.intelamt.state = 2; // Set the state to activated, since this is pretty obvious, we have a CIRA connection.
688 if (node.name == '') { node.name = host.split('.')[0]; }
689 obj.db.Set(node);
686 -
690 +
691 // Event the node change
692 event.msg = 'CIRA changed device ' + node.name + ' from mesh ' + mesh.name + ': ' + changes.join(', ');
693 var node2 = common.Clone(node);
package.json
+1 -1
@@ -1,6 +1,6 @@
1 {
2 "name": "meshcentral",
3 - "version": "0.1.7-x",
3 + "version": "0.1.8-a",
4 "keywords": [
5 "Remote Management",
6 "Intel AMT",
public/images/3bars-30.png
Binary files /dev/null and b/public/images/3bars-30.png differ
public/images/user-50.png
Binary files /dev/null and b/public/images/user-50.png differ
views/default-mobile.handlebars
+409 -44
@@ -48,18 +48,30 @@
48 border-bottom-width: 1px;
49 border-bottom-color: #DDDDDD;
50 }
51 +
52 + .noselect {
53 + -webkit-touch-callout: none;
54 + -webkit-user-select: none;
55 + -khtml-user-select: none;
56 + -moz-user-select: none;
57 + -ms-user-select: none;
58 + user-select: none;
59 + }
60 </style>
61 </head>
62 <body onload="if (typeof(startup) !== 'undefined') startup();" style="overflow-y:hidden;margin:0;padding:0;border:0;color:black;font-size:13px;font-family:\'Trebuchet MS\', Arial, Helvetica, sans-serif">
63 <div id=container>
64 <div id=mastheadx></div>
65 <div id=masthead style="background-color:#036;background-repeat:no-repeat;height:50px;width:100%;overflow:hidden">
57 - <div style="float:left;height:66px;color:#c8c8c8;padding-left:10px;padding-top:6px">
58 - <strong><font style="font-size:36px;font-family:Arial,Helvetica,sans-serif">{{{title}}}</font></strong>
59 - </div>
60 - <div style="float:left;height:66px;color:#c8c8c8;padding-left:5px;padding-top:10px">
61 - <strong><font style="font-size:12px;font-family:Arial,Helvetica,sans-serif">{{{title2}}}</font></strong>
66 + <div style="width:calc(100% - 50px);overflow:hidden">
67 + <div style="float:left;height:66px;color:#c8c8c8;padding-left:10px;padding-top:6px">
68 + <strong><font style="font-size:36px;font-family:Arial,Helvetica,sans-serif">{{{title}}}</font></strong>
69 + </div>
70 + <div style="float:left;height:66px;color:#c8c8c8;padding-left:5px;padding-top:10px">
71 + <strong><font style="font-size:12px;font-family:Arial,Helvetica,sans-serif">{{{title2}}}</font></strong>
72 + </div>
73 </div>
74 + <img class=noselect style="position:absolute;right:0;top:10px;bottom:50px;color:#c8c8c8;font-size:44px;margin-right:8px;cursor:pointer" onclick=topMenu() src="/images/3bars-30.png" width=30 height=30 />
75 </div>
76 <div id=page_content style="overflow-y:scroll;position:absolute;bottom:32px;top:50px;width:100%">
77 <div id=column_l style="width:100%;padding:0;position:absolute;bottom:0px;top:0px">
@@ -76,8 +88,48 @@
88 <div id=p2 style=display:none>
89 <div id=xdevices></div>
90 </div>
79 - <div id=p10 style=display:none;position:absolute;bottom:0;top:0;width:100%>
91 + <div id=p3 style=display:none;position:absolute;bottom:0;top:0;width:100%>
92 <table cellspacing=0 style="margin:0;padding:0;border-spacing:0;border:0;">
93 + <tr style=padding:0>
94 + <td style="padding:0;color:#c8c8c8;text-align:center;cursor:pointer" width=60px valign=top onclick=goBack()>
95 + <div style="padding:0;background-color:#036;width:10px;height:10px;float:right;border:0">
96 + <div style="background-color:white;width:10px;height:10px;border-radius:10px 0 0 0;border-right:1px solid white;border-bottom:1px solid white"></div>
97 + </div>
98 + <div style="padding:0;font-size:25px;background-color:#036;width:50px;border-radius:0 0 10px 0;height:36px">&#9664;</div>
99 + </td>
100 + <td>
101 + <img src="/images/user-50.png" width=50 height=50 />
102 + </td>
103 + <td>
104 + <div style=margin-left:5px>
105 + <strong style="font-size:large"><span id=p3userName></span></strong><br />
106 + </div>
107 + </td>
108 + </tr>
109 + </table>
110 + <div id=p3info style="overflow-y:scroll;position:absolute;top:55px;bottom:0px;width:100%">
111 + <div style="margin-left:8px">
112 + <div id="p3AccountActions">
113 + <p><strong>Account actions</strong></p>
114 + <div style="margin-left:9px;margin-bottom:8px">
115 + <div style="margin-top:5px"><span id="verifyEmailId" style="display:none"><a onclick="account_showVerifyEmail()" style="cursor:pointer">Verify email</a></span></div>
116 + <div style="margin-top:5px"><a onclick="account_showChangeEmail()" style="cursor:pointer">Change email address</a></div>
117 + <div style="margin-top:5px"><a onclick="account_showChangePassword()" style="cursor:pointer">Change password</a></div>
118 + <div style="margin-top:5px"><a onclick="account_showDeleteAccount()" style="cursor:pointer">Delete account</a></div>
119 + </div>
120 + </div>
121 + <br style=clear:both />
122 + <strong>Meshes</strong>
123 + ( <a onclick=account_createMesh() style=cursor:pointer><img height=12 src="images/icon-addnew.png" width=12 border=0 /> New</a> )
124 + <br /><br />
125 + <div id=p3meshes></div>
126 + <div id=p3noMeshFound style=margin-left:9px;display:none>No meshes. <a onclick=account_createMesh() style=cursor:pointer><strong>Get started here!</strong></a></div>
127 + <br style=clear:both />
128 + </div>
129 + </div>
130 + </div>
131 + <div id=p10 style=display:none;position:absolute;bottom:0;top:0;width:100%;overflow:hidden>
132 + <table cellspacing=0 style="margin:0;padding:0;border-spacing:0;border:0;position:absolute;top:0">
133 <tr style=padding:0>
134 <td style="padding:0;color:#c8c8c8;text-align:center;cursor:pointer" width=60px valign=top onclick=goBack()>
135 <div style="padding:0;background-color:#036;width:10px;height:10px;float:right;border:0">
@@ -96,9 +148,11 @@
148 </td>
149 </tr>
150 </table>
99 - <div id=p10html style="margin-left:8px;margin-right:8px"></div>
100 - <div id=p10html2></div>
101 - <div id=p10html3></div>
151 + <div style="overflow-y:scroll;position:absolute;top:55px;bottom:0px;width:100%">
152 + <div id=p10html style="margin-left:8px;margin-right:8px"></div>
153 + <div id=p10html2></div>
154 + <div id=p10html3></div>
155 + </div>
156 </div>
157 <div id=p20 style=display:none;position:absolute;bottom:0;top:0;width:100%>
158 <table cellspacing=0 style="margin:0;padding:0;border-spacing:0;border:0;">
@@ -151,6 +205,10 @@
205 <input id="idx_dlgOkButton" type="button" value="OK" style="float:right;width:80px" onclick="dialogclose(1)">
206 </div>
207 </div>
208 + <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">
209 + <div style="padding:12px;border-top:1px solid gray;color:black;cursor:pointer" onclick=topMenu(1)>My Account</div>
210 + <a href=/logout><div style="padding:12px;border-top:1px solid gray;color:black;cursor:pointer">Logout</div></a>
211 + </div>
212 <script>
213 var debugLevel = {{{debuglevel}}};
214 var features = {{{features}}};
@@ -222,6 +280,7 @@
280 }
281 case 'userinfo': {
282 userinfo = message.userinfo;
283 + QH('p3userName', userinfo.name);
284 //updateSiteAdmin();
285 //QV('verifyEmailId', (userinfo.emailVerified !== true) && (userinfo.email != null) && (serverinfo.emailcheck == true));
286 //QV('verifyEmailId2', (userinfo.emailVerified !== true) && (userinfo.email != null) && (serverinfo.emailcheck == true));
@@ -397,6 +456,7 @@
456
457 // Change the node
458 node.name = message.event.node.name;
459 + node.rname = message.event.node.rname;
460 node.host = message.event.node.host;
461 node.desc = message.event.node.desc;
462 node.publicip = message.event.node.publicip;
@@ -473,20 +533,194 @@
533 }
534 }
535
536 + //
537 + // Menu System
538 + //
539 +
540 + function topMenu(select) {
541 + if ((xxdialogMode != null) && (xxdialogMode != 0) && (xxdialogMode != 999)) return;
542 + if (select === undefined) {
543 + var x = (QS('topMenu').display == 'none');
544 + if (x == true) { if ((xxdialogMode == 0) || (xxdialogMode == null)) { QV('topMenu', true); xxdialogMode = 999; } } else { QV('topMenu', false); xxdialogMode = 0; }
545 + } else {
546 + QV('topMenu', false);
547 + xxdialogMode = 0;
548 + if ((select == 1) && (xxcurrentView != 3)) { goForward('account'); } // My Account
549 + }
550 + }
551 +
552 + var backStack = [];
553 + function goBack() { if (xxdialogMode) return; if (backStack.length > 0) { backStack.pop(); } goStack(); }
554 + function goForward(id) { if (xxdialogMode) return; backStack.push(id); goStack(); }
555 + function goStack() {
556 + if (backStack.length == 0) { go(2); return; }
557 + var id = backStack[backStack.length - 1], idtype = id.split('/')[0];
558 + if (idtype == 'node') { gotoDevice(id); }
559 + if (idtype == 'mesh') { gotoMesh(id); }
560 + if (idtype == 'account') { go(3); }
561 + if (idtype == 'devices') { go(2); }
562 + }
563 +
564 //
565 // MY ACCOUNT
566 //
567
480 - function updateMeshes() { }
568 + function account_showVerifyEmail() {
569 + if (xxdialogMode || (userinfo.emailVerified == true) || (serverinfo.emailcheck != true)) return;
570 + 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.";
571 + setDialogMode(2, "Email Verification", 3, account_showVerifyEmailEx, x);
572 + }
573 +
574 + function account_showVerifyEmailEx() {
575 + meshserver.send({ action: 'verifyemail', email: userinfo.email });
576 + }
577 +
578 + function account_showChangeEmail() {
579 + if (xxdialogMode) return;
580 + var x = addHtmlValue('Email', '<input id=dp3email style=width:170px maxlength=256 onchange=account_validateEmail() onkeyup=account_validateEmail(event) />');
581 + setDialogMode(2, "Email Address Change", 3, account_changeEmail, x);
582 + if (userinfo.email != null) { Q('dp3email').value = userinfo.email; }
583 + account_validateEmail();
584 + Q('dp3email').focus();
585 + }
586 +
587 + function account_validateEmail(e, email) {
588 + QE('idx_dlgOkButton', validateEmail(Q('dp3email').value) && (Q('dp3email').value != userinfo.email));
589 + if ((x == true) && (e != null) && (e.keyCode == 13)) { dialogclose(1); }
590 + }
591 +
592 + function account_changeEmail() {
593 + meshserver.send({ action: 'changeemail', email: Q('dp3email').value });
594 + }
595 +
596 + function account_showDeleteAccount() {
597 + if (xxdialogMode) return;
598 + var x = "<form action='{{{domainurl}}}deleteaccount' method=post><table style=margin-left:10px><tr>";
599 + x += "<td align=right>Password:</td><td><input id=apassword1 type=password name=apassword1 autocomplete=off onchange=account_validateDeleteAccount() onkeyup=account_validateDeleteAccount() /></td>";
600 + 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>";
601 + x += '</tr></table><div style=padding:10px;margin-bottom:4px>';
602 + x += '<input id=account_dlgCancelButton type=button value=Cancel style=float:right;width:80px;margin-left:5px onclick=dialogclose(0)>';
603 + x += '<input id=account_dlgOkButton type=submit value=OK style="float:right;width:80px" onclick=dialogclose(1)>';
604 + x += '</div><br /></form>';
605 + setDialogMode(2, "Delete Account", 0, null, x);
606 + account_validateDeleteAccount();
607 + Q('apassword1').focus();
608 + }
609 +
610 + function account_showChangePassword() {
611 + if (xxdialogMode) return;
612 + var x = "<form action='{{{domainurl}}}changepassword' method=post><table style=margin-left:10px><tr>";
613 + x += "<td align=right>Password:</td><td><input id=apassword1 type=password name=apassword1 autocomplete=off onchange=account_validateNewPassword() onkeyup=account_validateNewPassword() /> <b><span id=dxPassWarn></span></b></td>";
614 + x += "</tr><tr><td align=right>Password:</td><td><input id=apassword2 type=password name=apassword2 autocomplete=off onchange=account_validateNewPassword() onkeyup=account_validateNewPassword() /></td>";
615 + x += "</tr><tr><td align=right>Hint:</td><td><input id=apasswordhint name=apasswordhint maxlength=250 type=text autocomplete=off /></td>";
616 + x += '</tr></table><div style=padding:10px;margin-bottom:4px>';
617 + x += '<input id=account_dlgCancelButton type=button value=Cancel style=float:right;width:80px;margin-left:5px onclick=dialogclose(0)>';
618 + x += '<input id=account_dlgOkButton type=submit value=OK style="float:right;width:80px" onclick=dialogclose(1)>';
619 + x += '</div><br /></form>';
620 + setDialogMode(2, "Change Password", 0, null, x);
621 + account_validateDeleteAccount();
622 + Q('apassword1').focus();
623 + }
624 +
625 + function account_createMesh() {
626 + if (xxdialogMode) return;
627 + var x = addHtmlValue('Name', '<input id=dp3meshname style=width:170px maxlength=64 onchange=account_validateMeshCreate() onkeyup=account_validateMeshCreate() />');
628 + x += addHtmlValue('Type', '<div style=width:170px;margin:0;padding:0><select id=dp3meshtype style=width:100% onchange=account_validateMeshCreate() ><option value=2>Mesh Agent Policy</option><option value=1>Intel&reg; AMT Agent-less Policy</option></select></div>');
629 + x += addHtmlValue('Description', '<div style=width:170px;margin:0;padding:0><textarea id=dp3meshdesc maxlength=1024 style=width:100%;resize:none></textarea></div>');
630 + setDialogMode(2, "Create Mesh", 3, account_createMeshEx, x);
631 + account_validateMeshCreate();
632 + Q('dp3meshname').focus();
633 + }
634 +
635 + function account_validateMeshCreate() {
636 + QE('idx_dlgOkButton', Q('dp3meshname').value.length > 0);
637 + }
638 +
639 + function account_createMeshEx(button, tag) {
640 + meshserver.send({ action: 'createmesh', meshname: Q('dp3meshname').value, meshtype: Q('dp3meshtype').value, desc: Q('dp3meshdesc').value });
641 + }
642 +
643 + function account_validateDeleteAccount() {
644 + QE('account_dlgOkButton', (Q('apassword1').value.length > 0) && (Q('apassword1').value == Q('apassword2').value));
645 + }
646 +
647 + function account_validateNewPassword() {
648 + QE('account_dlgOkButton', (Q('apassword1').value.length > 0) && (Q('apassword1').value == Q('apassword2').value));
649 + var r = '';
650 + if (Q('apassword1').value != '') {
651 + var passStrength = checkPasswordStrength(Q('apassword1').value);
652 + if (passStrength >= 80) { r = '<span style=color:green>Strong<span>'; } else if (passStrength >= 60) { r = '<span style=color:blue>Good<span>'; } else { r = '<span style=color:red>Weak<span>'; }
653 + }
654 + QH('dxPassWarn', r);
655 + }
656 +
657 + // Return a password strength score
658 + function checkPasswordStrength(password) {
659 + 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) }
660 + if (!password) return 0;
661 + for (var i = 0; i< password.length; i++) { letters[password[i]] = (letters[password[i]] || 0) + 1; r += 5.0 / letters[password[i]]; }
662 + for (var c in variations) { varCount += (variations[c] == true) ? 1 : 0; }
663 + return parseInt(r + (varCount - 1) * 10);
664 + }
665 +
666 + function updateMeshes() {
667 + var r = '', count = 0;
668 + for (i in meshes) {
669 + count++;
670 +
671 + // Mesh rights
672 + var meshrights = meshes[i].links['user/{{{domain}}}/' + userinfo.name.toLowerCase()].rights;
673 + var rights = 'Partial Rights';
674 + if (meshrights == 0xFFFFFFFF) rights = 'Full Administrator'; else if (meshrights == 0) rights = 'No Rights';
675 +
676 + // Print the mesh information
677 + r += '<div style=cursor:pointer onclick=goForward(\'' + i + '\')>';
678 + r += '<div style="float:left;margin-left:4px"><img src="/images/meshicon50.png" width=50 height=50 /></div>';
679 + r += '<div style="width:auto;height:40px;background-color:lightgray;margin-top:5px;margin-bottom:5px;margin-left:60px;padding-top:5px;padding-bottom:5px;border-radius:8px 0px 0px 8px">';
680 + r += '<div><div style=padding-left:12px;padding-top:2px><b>' + EscapeHtml(meshes[i].name) + '</b></div><div style=padding-left:12px;padding-top:3px;color:gray>' + rights + '</div></div>';
681 + r += '</div></div>';
682 + }
683 +
684 + meshcount = count;
685 + QH('p3meshes', r);
686 + QV('p3noMeshFound', count == 0);
687 + }
688 +
689 + function gotoMesh(meshid) {
690 + currentMesh = meshes[meshid];
691 + p20updateMesh();
692 + go(20);
693 + }
694 +
695 + function server_showRestoreDlg() {
696 + if (xxdialogMode) return;
697 + var x = 'Restore the server using a backup, <span style=color:red>this will delete the existing server data</span>. Only do this if you know what you are doing.<br /><br />';
698 + x += '<form action="/restoreserver.ashx" enctype="multipart/form-data" method="post"><div>';
699 + x += '<input id=account_dlgFileInput type=file name=datafile style=width:100% accept=".zip,application/octet-stream,application/zip,application/x-zip,application/x-zip-compressed" onchange=account_validateServerRestore()>';
700 + x += '<input id=account_dlgCancelButton type=button value=Cancel style=float:right;width:80px;margin-left:5px onclick=dialogclose(0)>';
701 + x += '<input id=account_dlgOkButton type=submit value=OK style=float:right;width:80px onclick=dialogclose(1)>';
702 + x += '</div><br /><br /></form>';
703 + setDialogMode(2, "Restore Server", 0, null, x);
704 + account_validateServerRestore();
705 + }
706 +
707 + function account_validateServerRestore() {
708 + QE('account_dlgOkButton', Q('account_dlgFileInput').files.length == 1);
709 + }
710 +
711 + function server_showVersionDlg() {
712 + if (xxdialogMode) return;
713 + setDialogMode(2, "MeshCentral Version", 1, null, "Loading...", 'MeshCentralServerUpdate');
714 + meshserver.send({ action: 'serverversion' });
715 + }
716 +
717 + function server_showVersionDlgUpdate() { QE('idx_dlgOkButton', Q('d2updateCheck').checked); }
718 + function server_showVersionDlgEx() { meshserver.send({ action: 'serverupdate' }); }
719
720 //
721 // MY DEVICES
722 //
723
486 - var backStack = [];
487 - function goBack() { if (backStack.length == 0) { goMyDevices(); } else { gotoDevice(backStack.pop()); } }
488 - function goMyDevices() { go(2); }
489 -
724 var sort = 0;
725 var deviceHeaderId = 0;
726 var deviceHeaderCount;
@@ -523,7 +757,7 @@
757 if (current != null) { if (c == 2) { r += '<td><div style=width:301px></div></td>'; } if (r != '') { r += '</tr></table>'; } }
758 r += '<div class=DevSt style=padding-top:4px><span style=float:right>';
759 //r += getMeshActions(mesh2, meshrights);
526 - r += '</span><span id=MxMESH style=cursor:pointer onclick=gotoMesh("' + nodes[i].meshid + '")>' + EscapeHtml(meshes[nodes[i].meshid].name) + '</span>' + extra + '<span id=DevxHeader' + deviceHeaderId + ' style=color:lightgray></span></div>';
760 + 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>';
761 current = nodes[i].meshid;
762 displayedMeshes[current] = 1;
763 c = 0;
@@ -553,7 +787,7 @@
787 // Node
788 var icon = nodes[i].icon, nodestate = NodeStateStr(nodes[i]);
789 if ((!nodes[i].conn) || (nodes[i].conn == 0)) { icon += ' gray'; }
556 - r += '<div style=cursor:pointer onclick=gotoDevice(\'' + nodes[i]._id + '\')>';
790 + r += '<div style=cursor:pointer onclick=goForward(\'' + nodes[i]._id + '\')>';
791 r += '<div class="i' + icon + '" style="float:left;margin-left:4px"></div>';
792 r += '<div style="width:auto;height:40px;background-color:lightgray;margin-top:5px;margin-bottom:5px;margin-left:60px;padding-top:5px;padding-bottom:5px;border-radius:8px 0px 0px 8px">';
793 r += '<div><div style=padding-left:12px;padding-top:2px><b>' + name + '</b></div><div style=padding-left:12px;padding-top:3px;color:gray>' + nodestate + '</div></div>';
@@ -587,7 +821,7 @@
821 if ((current != '') && (r != '')) { r += '</tr></table>'; }
822 r += '<div><div colspan=3 class=DevSt><span style=float:right>';
823 //r += getMeshActions(mesh, meshrights);
590 - r += '</span><span id=MxMESH style=cursor:pointer onclick=gotoMesh("' + mesh._id + '")>' + EscapeHtml(mesh.name) + '</span></div>';
824 + r += '</span><span id=MxMESH style=cursor:pointer onclick=goForward("' + mesh._id + '")>' + EscapeHtml(mesh.name) + '</span></div>';
825 if (mesh.mtype == 1) { r += '<div style=padding:10px><i>No Intel&reg; AMT devices in this mesh'; }
826 if (mesh.mtype == 2) { r += '<div style=padding:10px><i>No devices in this mesh'; }
827 r += '.</i></div></div>';
@@ -604,11 +838,6 @@
838 for (var i in deviceHeadersTitles) { Q(i).title = deviceHeadersTitles[i]; }
839 }
840
607 - function gotoMeshStack(meshid) {
608 - backStack.push(currentNode._id);
609 - gotoMesh(meshid);
610 - }
611 -
841 var powerStatetable = ['', 'Powered', 'Sleep', 'Sleep', 'Sleep', 'Hibernating', 'Power off', 'Present'];
842 var powerStateStrings = ['', '<span title="Device is powered on.">Powered</span>', '<span title="Device is in sleep state (S1).">Sleeping</span>', '<span title="Device is in sleep state (S2).">Sleeping</span>', '<span title="Device is in deep sleep state (S3).">Deep Sleep</span>', '<span title="Device is in hibernating state (S4).">Hibernating</span>', '<span title="Device is in powered off state (S5).">Soft-Off</span>', '<span title="Device is detected but power state could not be obtained.">Present</span>'];
843 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'];
@@ -705,7 +934,7 @@
934 var x = '<table style=width:100%>';
935
936 // Attribute: Mesh
708 - x += addDeviceAttribute('<span title="The name of the administrative group this computer belong to">Mesh</span>', '<a title="The name of the group this computer belong to" onclick=gotoMeshStack("' + node.meshid + '") style=cursor:pointer>' + EscapeHtml(meshes[node.meshid].name) + '</a>');
937 + x += addDeviceAttribute('<span title="The name of the administrative group this computer belong to">Mesh</span>', '<a title="The name of the group this computer belong to" onclick=goForward("' + node.meshid + '") style=cursor:pointer>' + EscapeHtml(meshes[node.meshid].name) + '</a>');
938
939 // Attribute: Name
940 if (node.rname != null) { x += addDeviceAttribute('<span title="The name of this computer as set in the operating system">Name</span>', '<span title="The name of this computer as set in the operating system">' + EscapeHtml(node.rname) + '</span>'); }
@@ -743,23 +972,27 @@
972 // Attribute: Intel AMT
973 if (node.intelamt != null) {
974 var str = '';
746 - var provisioningStates = { 0: 'Not Activated (Pre)', 1: 'Not Activated (In)', 2: 'Activated' };
747 - if (node.intelamt.ver != null && node.intelamt.state == null) { str += '<i>Unknown State</i>, v' + node.intelamt.ver; } else
748 - if (node.intelamt.ver == null || node.intelamt.state == null) { str += '<i>Unknown Version & State</i>'; } else {
975 + var provisioningStates = { 0: 'Not&nbsp;Activated&nbsp;(Pre)', 1: 'Not&nbsp;Activated&nbsp;(In)', 2: 'Activated' };
976 + if (node.intelamt.ver != null && node.intelamt.state == null) { str += '<i>Unknown&nbsp;State</i>, v' + node.intelamt.ver; } else
977 +
978 + if ((node.intelamt.ver == null) && (node.intelamt.state == 2)) { str += '<i>Activated</i>'; }
979 + else if ((node.intelamt.ver == null) || (node.intelamt.state == null)) { str += '<i>Unknown Version & State</i>'; }
980 + else {
981 str += provisioningStates[node.intelamt.state];
982 if (node.intelamt.flags) { if (node.intelamt.flags & 2) { str += ' <span title="Intel AMT is activated in Client Control Mode">CCM</span>'; } else if (node.intelamt.flags & 4) { str += ' <span title="Intel AMT is activated in Admin Control Mode">ACM</span>'; } }
983 str += (', v' + node.intelamt.ver);
984 }
985 +
986 if (node.intelamt.tls == 1) { str += ', <span title="Intel AMT is setup with TLS network security">TLS</span>'; }
987 if (node.intelamt.state == 2) {
988 if (node.intelamt.user == null || node.intelamt.user == '') {
989 if ((meshrights & 4) != 0) {
990 str += ', <i style=color:#FF0000;cursor:pointer title="Edit Intel&reg; AMT credentials" onclick=editDeviceAmtSettings("' + node._id + '")>No&nbsp;Credentials</i>';
991 } else {
759 - str += ', <i style=color:#FF0000>No&nbsp;Credentials</i>';
992 + str += ', <i style=color:#FF0000>No Credentials</i>';
993 }
994 }
762 - str += '&nbsp;';
995 + str += ' ';
996 if ((meshrights & 4) != 0) {
997 str += '<img src=images/link4.png height=10 width=10 title="Edit Intel&reg; AMT credentials" style=cursor:pointer onclick=editDeviceAmtSettings("' + node._id + '")>';
998 }
@@ -795,7 +1028,7 @@
1028
1029 x += '</table><br />';
1030 // Show action button, only show if we have permissions 4, 8, 64
798 - //if ((meshrights & 76) != 0) { x += '<input type=button value=Actions title="Perform power actions on the device" onclick=deviceActionFunction() />'; }
1031 + if ((meshrights & 76) != 0) { x += '<input type=button value=Actions title="Perform power actions on the device" onclick=deviceActionFunction() />'; }
1032 //x += '<input type=button value=Notes title="View notes about this device" onclick=showNotes(' + ((meshrights & 128) == 0) + ',"' + encodeURIComponent(node._id) + '") />';
1033 //if ((connectivity & 1) && (meshrights & 8) && (node.agent.id < 5)) { x += '<input type=button value=Toast title="Display a text message of the remote device" onclick=deviceToastFunction() />'; }
1034 QH('p10html', x);
@@ -804,8 +1037,8 @@
1037 //drawDeviceTimeline();
1038
1039 // Show bottom buttons
807 - x = '<div style=float:right;font-size:x-small>';
808 - //if ((meshrights & 4) != 0) x += '<a style=cursor:pointer onclick=p10showDeleteNodeDialog("' + node._id + '") title="Remove this device">Delete Device</a>';
1040 + x = '<div style=float:right;font-size:x-small;margin-right:10px>';
1041 + if ((meshrights & 4) != 0) x += '<a style=cursor:pointer onclick=p10showDeleteNodeDialog("' + node._id + '") title="Remove this device">Delete Device</a>';
1042 x += '</div><div style=font-size:x-small>';
1043 //if (mesh.mtype == 2) x += '<a style=cursor:pointer onclick=p10showNodeNetInfoDialog("' + node._id + '") title="Show device network interface information">Interfaces</a>&nbsp;';
1044 //if (xxmap != null) x += '<a style=cursor:pointer onclick=p10showNodeLocationDialog("' + node._id + '") title="Show device locations information">Location</a>&nbsp;';
@@ -823,15 +1056,156 @@
1056
1057 // Set the node icon
1058 QH('MainComputerImage', '<div class="i' + node.icon + '"></div>');
1059 +
1060 + // Request the power timeline
1061 + if ((powerTimelineNode != currentNode._id) && (powerTimelineReq != currentNode._id)) { QH('p10html2', ''); powerTimelineReq = currentNode._id; meshserver.send({ action: 'powertimeline', nodeid: currentNode._id }); }
1062 }
1063 if (!panel) panel = 10;
1064 go(panel);
1065 }
1066
1067 + function deviceActionFunction() {
1068 + if (xxdialogMode) return;
1069 + var meshrights = meshes[currentNode.meshid].links['user/{{{domain}}}/' + userinfo.name.toLowerCase()].rights;
1070 + var x = "Select an operation to perform on this device.<br /><br />";
1071 + var y = '<select id=d2deviceop style=float:right;width:170px>';
1072 + if ((meshrights & 64) != 0) { y += '<option value=100>Wake-up</option>'; } // Wake-up permission
1073 + if ((meshrights & 8) != 0) { y += '<option value=4>Sleep</option><option value=3>Reset</option><option value=2>Power off</option>'; } // Remote control permission
1074 + y += '</select>';
1075 + x += addHtmlValue('Operation', y);
1076 + setDialogMode(2, "Device Action", 3, deviceActionFunctionEx, x);
1077 + }
1078 +
1079 + function deviceActionFunctionEx() {
1080 + var op = Q('d2deviceop').value;
1081 + if (op == 100) {
1082 + // Device wake
1083 + meshserver.send({ action: 'wakedevices', nodeids: [ currentNode._id ] });
1084 + } else {
1085 + // Power operation
1086 + meshserver.send({ action: 'poweraction', nodeids: [ currentNode._id ], actiontype: op });
1087 + }
1088 + }
1089 +
1090 + // Look to see if we need to update the device timeline
1091 + function updateDeviceTimeline() {
1092 + if ((meshserver.State != 2) || (powerTimelineNode == null) || (powerTimelineUpdate == null) || (currentNode == null)) return;
1093 + if ((powerTimelineNode == powerTimelineReq) && (currentNode._id == powerTimelineNode) && (powerTimelineUpdate < Date.now())) { powerTimelineUpdate = null; meshserver.send({ action: 'powertimeline', nodeid: currentNode._id }); }
1094 + }
1095 +
1096 + // Draw device power bars. The bars are 766px wide.
1097 + function drawDeviceTimeline() {
1098 + var timeline = null, now = Date.now();
1099 + if (currentNode._id == powerTimelineNode) { timeline = powerTimeline; }
1100 +
1101 + // Calculate when the timeline starts
1102 + var d = new Date();
1103 + d.setHours(0, 0, 0, 0);
1104 + d = new Date(d.getTime() - (1000 * 60 * 60 * 24 * 6));
1105 + var timelineStart = d.getTime();
1106 +
1107 + // De-compact the timeline
1108 + var timeline2 = [];
1109 + if (timeline != null && timeline.length > 1) {
1110 + timeline2.push([ 0, timeline[1], timeline[0] ]); // Start, End, Power
1111 + var ct = timeline[1];
1112 + for (var i = 2; i < timeline.length; i += 2) {
1113 + var power = timeline[i], dt = now;
1114 + if (timeline.length > (i + 1)) { dt = timeline[i + 1]; }
1115 + timeline2.push([ ct, ct + dt, power ]); // Start, End, Power
1116 + ct = ct + dt;
1117 + }
1118 + }
1119 +
1120 + // Draw the timeline
1121 + var x = '', count = 1, date = new Date();
1122 + var totalWidth = Q('masthead').offsetWidth - (90 + 9 + 9 + 14); // Compute the total width of the power bar
1123 + date.setHours(0, 0, 0, 0);
1124 + for (var i = 0; i < 7; i++) {
1125 + var datavalue = '', start = date.getTime(), end = start + (1000 * 60 * 60 * 24);
1126 + for (var j in timeline2) {
1127 + var block = timeline2[j];
1128 + if (isTimeBlockInside(start, end, block[0], block[1]) == true) {
1129 + var ts = Math.max(start, block[0]);
1130 + var te = Math.min(Math.min(end, block[1]), now);
1131 + var width = Math.round(((te - ts) * totalWidth) / 86400000);
1132 + if (width > 0) {
1133 + var title = powerStateStrings2[block[2]] + ' from ' + new Date(ts).toLocaleTimeString() + ' to ' + new Date(te).toLocaleTimeString() + '.';
1134 + datavalue += '<div title="' + title + '" style=display:table-cell;width:' + width + 'px;background-color:' + powerColor(block[2]) + ';height:16px></div>';
1135 + }
1136 + }
1137 + }
1138 + x += '<tr style=' + (((count % 2) == 0)?'background-color:#DDD':'') + '><td><div>&nbsp;' + date.toLocaleDateString() + '<div></div></div></td><td><div>' + datavalue + '</div></td></tr>';
1139 + ++count;
1140 + date = new Date(date.getTime() - (1000 * 60 * 60 * 24)); // Substract one day
1141 + }
1142 + 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>');
1143 + }
1144 +
1145 + // Return a color for the given power state
1146 + function powerColor(x) { if (x < powerColorTable.length) { return powerColorTable[x]; } return 'yellow'; }
1147 +
1148 + // Return true if the time block is visible within the start/end period
1149 + function isTimeBlockInside(start, end, blockStart, blockEnd) {
1150 + if ((blockStart < start) && (blockEnd > end)) return true; // Block is wider than timespan
1151 + if ((blockStart > start) && (blockStart < end)) return true;
1152 + if ((blockEnd > start) && (blockEnd < end)) return true;
1153 + return false;
1154 + }
1155 +
1156 function addDeviceAttribute(name, value) {
1157 return '<tr><td style=width:100px;color:gray>' + name + '</td><td style=overflow:hidden>' + value + '</td></tr>';
1158 }
1159
1160 + function editDeviceAmtSettings(nodeid, func) {
1161 + if (xxdialogMode) return;
1162 + var x = '', node = getNodeFromId(nodeid), buttons = 3, meshrights = getNodeRights(nodeid);
1163 + if ((meshrights & 4) == 0) return;
1164 + x += addHtmlValue('Username', '<input id=dp10username style=width:170px maxlength=32 autocomplete=nope placeholder="admin" onchange=validateDeviceAmtSettings() onkeyup=validateDeviceAmtSettings() />');
1165 + x += addHtmlValue('Password', '<input id=dp10password type=password style=width:170px autocomplete=nope maxlength=32 onchange=validateDeviceAmtSettings() onkeyup=validateDeviceAmtSettings() />');
1166 + x += addHtmlValue('Security', '<select id=dp10tls style=width:176px><option value=0>No TLS security</option><option value=1>TLS security required</option></select>');
1167 + if ((node.intelamt.user != null) && (node.intelamt.user != '')) { buttons = 7; }
1168 + setDialogMode(2, "Edit Intel&reg; AMT credentials", buttons, editDeviceAmtSettingsEx, x, { node: node, func: func });
1169 + if ((node.intelamt.user != null) && (node.intelamt.user != '')) { Q('dp10username').value = node.intelamt.user; } else { Q('dp10username').value = 'admin'; }
1170 + Q('dp10tls').value = node.intelamt.tls;
1171 + validateDeviceAmtSettings();
1172 + }
1173 +
1174 + function validateDeviceAmtSettings() {
1175 + QE('idx_dlgOkButton', passwordcheck(Q('dp10password').value));
1176 + }
1177 +
1178 + function editDeviceAmtSettingsEx(button, tag) {
1179 + if (button == 2) {
1180 + // Delete button pressed, remove credentials
1181 + meshserver.send({ action: 'changedevice', nodeid: tag.node._id, intelamt: { user: '', pass: '' } });
1182 + } else {
1183 + // Change Intel AMT credentials
1184 + var amtuser = Q('dp10username').value;
1185 + if (amtuser == '') amtuser = 'admin';
1186 + var amtpass = Q('dp10password').value;
1187 + if (amtpass == '') amtuser = '';
1188 + meshserver.send({ action: 'changedevice', nodeid: tag.node._id, intelamt: { user: amtuser, pass: amtpass, tls: Q('dp10tls').value } });
1189 + tag.node.intelamt.user = amtuser;
1190 + tag.node.intelamt.tls = Q('dp10tls').value;
1191 + if (tag.func) { setTimeout(tag.func, 300); }
1192 + }
1193 + }
1194 +
1195 + function p10showDeleteNodeDialog(nodeid) {
1196 + if (xxdialogMode) return;
1197 + setDialogMode(2, "Delete Node", 3, p10showDeleteNodeDialogEx, "Delete \"" + EscapeHtml(currentNode.name) + "\"?<br /><br /><input id=p10check type=checkbox onchange=p10validateDeleteNodeDialog() />Confirm", nodeid);
1198 + p10validateDeleteNodeDialog();
1199 + }
1200 +
1201 + function p10validateDeleteNodeDialog() {
1202 + QE('idx_dlgOkButton', Q('p10check').checked);
1203 + }
1204 +
1205 + function p10showDeleteNodeDialogEx(buttons, nodeid) {
1206 + meshserver.send({ action: 'removedevices', nodeids: [ nodeid ] });
1207 + }
1208 +
1209 function p10showiconselector() {
1210 if (xxdialogMode) return;
1211 var mesh = meshes[currentNode.meshid];
@@ -880,17 +1254,6 @@
1254 QE('idx_dlgOkButton', x);
1255 if ((e != null) && (x == true) && (e.keyCode == 13)) { dialogclose(1); }
1256 }
883 -
884 - //
885 - // MY ACCOUNT
886 - //
887 -
888 - function gotoMesh(meshid) {
889 - currentMesh = meshes[meshid];
890 - p20updateMesh();
891 - go(20);
892 - }
893 -
1257
1258 //
1259 // MY MESHS
@@ -898,6 +1261,7 @@
1261
1262 var currentMesh;
1263 function p20updateMesh() {
1264 + if (currentMesh == null) return;
1265 QH('p20meshName', EscapeHtml(currentMesh.name));
1266 var meshtype = 'Unknown #' + currentMesh.mtype;
1267 var meshrights = currentMesh.links['user/{{{domain}}}/' + userinfo.name.toLowerCase()].rights;
@@ -914,7 +1278,7 @@
1278
1279 x += '<br style=clear:both><br>';
1280 var currentMeshLinks = currentMesh.links['user/{{{domain}}}/' + userinfo.name.toLowerCase()];
917 - if (currentMeshLinks && ((currentMeshLinks.rights & 2) != 0)) { x += '<a onclick=p20showAddMeshUserDialog() style=cursor:pointer;margin-right:10px><img src=images/icon-addnew.png border=0 height=12 width=12> Add User</a>'; }
1281 + if (currentMeshLinks && ((currentMeshLinks.rights & 2) != 0)) { x += '<div style=margin-bottom:6px><a onclick=p20showAddMeshUserDialog() style=cursor:pointer><img src=images/icon-addnew.png border=0 height=12 width=12> Add User</a></div>'; }
1282
1283 /*
1284 if ((meshrights & 4) != 0) {
@@ -964,7 +1328,7 @@
1328 x += '</tbody></table>';
1329
1330 // If we are full administrator on this mesh, allow deletion of the mesh
967 - if (meshrights == 0xFFFFFFFF) { x += '<div style=font-size:x-small;text-align:right><span><a onclick=p20showDeleteMeshDialog() style=cursor:pointer>Delete Mesh</a></span></div>'; }
1331 + if (meshrights == 0xFFFFFFFF) { x += '<div style=font-size:small;text-align:right;margin-top:6px><span><a onclick=p20showDeleteMeshDialog() style=cursor:pointer>Delete Mesh</a></span></div>'; }
1332
1333 QH('p20info', x);
1334 }
@@ -1141,6 +1505,7 @@
1505 function addHtmlValue2(t, v) { return '<div><div style=display:inline-block;float:right>' + v + '</div><div style=display:inline-block>' + t + '</div></div>'; }
1506 function addLink(x, f) { return "<a style=cursor:pointer;color:darkblue;text-decoration:none onclick='" + f + "'>&diams; " + x + "</a>"; }
1507 function addLinkConditional(x, f, c) { if (c) return addLink(x, f); return x; }
1508 + function passwordcheck(p) { var re = /(?=.*\d)(?=.*[a-z])(?=.*[A-Z])(?=.*[!@#$%^&*()]).{8,}/; return re.test(p); }
1509
1510 </script>
1511 </body>
views/default.handlebars
+20 -12
@@ -886,13 +886,10 @@
886 //QS('column_l').padding = '0';
887 QS('column_l').width = 'calc(100% - 30px)';
888 }
889 + drawDeviceTimeline();
890 }
891
891 - function getNodeFromId(id) {
892 - for (var i in nodes) { if (nodes[i]._id == id) return nodes[i]; }
893 - return null;
894 - }
895 -
892 + function getNodeFromId(id) { for (var i in nodes) { if (nodes[i]._id == id) return nodes[i]; } return null; }
893 function reload() { window.location.href = window.location.href; }
894
895 function onStateChanged(server, state) {
@@ -1279,6 +1276,7 @@
1276
1277 // Change the node
1278 node.name = message.event.node.name;
1279 + node.rname = message.event.node.rname;
1280 node.host = message.event.node.host;
1281 node.desc = message.event.node.desc;
1282 node.publicip = message.event.node.publicip;
@@ -1600,8 +1598,8 @@
1598 }
1599
1600 // If there is nothing to display, explain the problem
1603 - if ((r == '') && (meshcount > 0)) {
1604 - if ((Q('SearchInput').value == '') && (sort == 3)) {
1601 + if ((r == '') && (meshcount > 0) && (Q('SearchInput').value != '')) {
1602 + if (sort == 3) {
1603 r = '<div style="margin:30px">No devices are included in any groups, click on a device\'s \"Groups\" to add to a group.</div>';
1604 } else {
1605 r = '<div style="margin:30px">No devices matching this search.</div>';
@@ -2874,11 +2872,15 @@
2872 var str = '';
2873 var provisioningStates = { 0: 'Not Activated (Pre)', 1: 'Not Activated (In)', 2: 'Activated' };
2874 if (node.intelamt.ver != null && node.intelamt.state == null) { str += '<i>Unknown State</i>, v' + node.intelamt.ver; } else
2877 - if (node.intelamt.ver == null || node.intelamt.state == null) { str += '<i>Unknown Version & State</i>'; } else {
2875 +
2876 + if ((node.intelamt.ver == null) && (node.intelamt.state == 2)) { str += '<i>Activated</i>'; }
2877 + else if ((node.intelamt.ver == null) || (node.intelamt.state == null)) { str += '<i>Unknown Version & State</i>'; }
2878 + else {
2879 str += provisioningStates[node.intelamt.state];
2880 if (node.intelamt.flags) { if (node.intelamt.flags & 2) { str += ' <span title="Intel AMT is activated in Client Control Mode">CCM</span>'; } else if (node.intelamt.flags & 4) { str += ' <span title="Intel AMT is activated in Admin Control Mode">ACM</span>'; } }
2881 str += (', v' + node.intelamt.ver);
2882 }
2883 +
2884 if (node.intelamt.tls == 1) { str += ', <span title="Intel AMT is setup with TLS network security">TLS</span>'; }
2885 if (node.intelamt.state == 2) {
2886 if (node.intelamt.user == null || node.intelamt.user == '') {
@@ -2977,7 +2979,7 @@
2979 QV('MainDevDesktop', ((mesh.mtype == 1) || (node.agent == null) || (node.agent.caps == null) || ((node.agent.caps & 1) != 0)) && (meshrights & 8));
2980 QV('MainDevTerminal', ((mesh.mtype == 1) || (node.agent == null) || (node.agent.caps == null) || ((node.agent.caps & 2) != 0)) && (meshrights & 8));
2981 QV('MainDevFiles', ((mesh.mtype == 2) && ((node.agent == null) || (node.agent.caps == null) || ((node.agent.caps & 4) != 0))) && (meshrights & 8));
2980 - QV('MainDevAmt', (node.intelamt != null) && (node.intelamt.state == 2) && (meshrights & 8));
2982 + QV('MainDevAmt', (node.intelamt != null) && ((node.intelamt.state == 2) || (node.conn & 2)) && (meshrights & 8));
2983 QV('MainDevConsole', (consoleRights && (mesh.mtype == 2) && ((node.agent == null) || (node.agent.caps == null) || ((node.agent.caps & 8) != 0))) && (meshrights & 8));
2984 QV('p15uploadCore', (node.agent != null) && (node.agent.caps != null) && ((node.agent.caps & 16) != 0) && (userinfo.siteadmin == 0xFFFFFFFF));
2985 QH('p15coreName', ((node.agent != null) && (node.agent.core != null))?node.agent.core:'');
@@ -2996,7 +2998,7 @@
2998 QV('filesActionsBtn', (meshrights & 72) != 0);
2999
3000 // Request the power timeline
2999 - if ((powerTimelineNode != currentNode._id) && (powerTimelineReq != currentNode._id)) { powerTimelineReq = currentNode._id; meshserver.send({ action: 'powertimeline', nodeid: currentNode._id }); }
3001 + if ((powerTimelineNode != currentNode._id) && (powerTimelineReq != currentNode._id)) { QH('p10html2', ''); powerTimelineReq = currentNode._id; meshserver.send({ action: 'powertimeline', nodeid: currentNode._id }); }
3002
3003 // Reset the desktop tools
3004 QV('DeskTools', false);
@@ -3072,6 +3074,7 @@
3074
3075 // Draw device power bars. The bars are 766px wide.
3076 function drawDeviceTimeline() {
3077 + if ((currentNode == null) || (xxcurrentView < 10) || (xxcurrentView > 19)) return;
3078 var timeline = null, now = Date.now();
3079 if (currentNode._id == powerTimelineNode) { timeline = powerTimeline; }
3080
@@ -3096,6 +3099,7 @@
3099
3100 // Draw the timeline
3101 var x = '', count = 1, date = new Date();
3102 + var totalWidth = Q('masthead').offsetWidth - (160 + 9 + 9 + 14); // Compute the total width of the power bar
3103 date.setHours(0, 0, 0, 0);
3104 for (var i = 0; i < 7; i++) {
3105 var datavalue = '', start = date.getTime(), end = start + (1000 * 60 * 60 * 24);
@@ -3104,7 +3108,7 @@
3108 if (isTimeBlockInside(start, end, block[0], block[1]) == true) {
3109 var ts = Math.max(start, block[0]);
3110 var te = Math.min(Math.min(end, block[1]), now);
3107 - var width = Math.round((te - ts) / 112794);
3111 + var width = Math.round(((te - ts) * totalWidth) / 86400000);
3112 if (width > 0) {
3113 var title = powerStateStrings2[block[2]] + ' from ' + new Date(ts).toLocaleTimeString() + ' to ' + new Date(te).toLocaleTimeString() + '.';
3114 datavalue += '<div title="' + title + '" style=display:table-cell;width:' + width + 'px;background-color:' + powerColor(block[2]) + ';height:16px></div>';
@@ -4657,6 +4661,7 @@
4661
4662 var currentMesh;
4663 function p20updateMesh() {
4664 + if (currentMesh == null) return;
4665 QH('p20meshName', EscapeHtml(currentMesh.name));
4666 var meshtype = 'Unknown #' + currentMesh.mtype;
4667 var meshrights = currentMesh.links['user/{{{domain}}}/' + userinfo.name.toLowerCase()].rights;
@@ -5710,7 +5715,10 @@
5715 if (((b & 8) || x) && f) f(x, t);
5716 }
5717
5713 - function center() { QS('dialog').left = ((((getDocWidth() - 400) / 2)) + "px"); deskAdjust(); }
5718 + function center() {
5719 + QS('dialog').left = ((((getDocWidth() - 400) / 2)) + "px"); deskAdjust();
5720 + drawDeviceTimeline();
5721 + }
5722 function messagebox(t, m) { QH('id_dialogMessage', m); setDialogMode(1, t, 1); }
5723 function statusbox(t, m) { QH('id_dialogMessage', m); setDialogMode(1, t); }
5724 function getDocWidth() { if (window.innerWidth) return window.innerWidth; if (document.documentElement && document.documentElement.clientWidth && document.documentElement.clientWidth != 0) return document.documentElement.clientWidth; return document.getElementsByTagName('body')[0].clientWidth; }