Improved user management

Ylian Saint-Hilaire committed Apr 15, 2018 at 22:22 UTC ff1478c48a95519285cf7c1d83e7659e4fdb40e5
11 files changed +276 -43
agents/MeshCmd-signed.exe
Binary files a/agents/MeshCmd-signed.exe and b/agents/MeshCmd-signed.exe differ
agents/MeshCmd64-signed.exe
Binary files a/agents/MeshCmd64-signed.exe and b/agents/MeshCmd64-signed.exe differ
agents/MeshService-signed.exe
Binary files a/agents/MeshService-signed.exe and b/agents/MeshService-signed.exe differ
agents/MeshService.exe
Binary files a/agents/MeshService.exe and b/agents/MeshService.exe differ
agents/MeshService64-signed.exe
Binary files a/agents/MeshService64-signed.exe and b/agents/MeshService64-signed.exe differ
agents/MeshService64.exe
Binary files a/agents/MeshService64.exe and b/agents/MeshService64.exe differ
meshuser.js
+22 -3
@@ -278,12 +278,18 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain) {
278 }
279 case 'events':
280 {
281 + // Setup the event filter
282 + var filter = user.subscriptions;
283 + if ((command.user != null) && ((user.siteadmin & 2) != 0)) { // SITERIGHT_MANAGEUSERS
284 + // TODO: Add the meshes command.user has access to
285 + filter = ['user/' + domain.id + '/' + command.user.toLowerCase()];
286 + }
287 if ((command.limit == null) || (typeof command.limit != 'number')) {
288 // Send the list of all events for this session
283 - obj.db.GetEvents(user.subscriptions, domain.id, function (err, docs) { if (err != null) return; try { ws.send(JSON.stringify({ action: 'events', events: docs, tag: command.tag })); } catch (ex) { } });
289 + obj.db.GetEvents(filter, domain.id, function (err, docs) { if (err != null) return; try { ws.send(JSON.stringify({ action: 'events', events: docs, user: command.user, tag: command.tag })); } catch (ex) { } });
290 } else {
291 // Send the list of most recent events for this session, up to 'limit' count
286 - obj.db.GetEventsWithLimit(user.subscriptions, domain.id, command.limit, function (err, docs) { if (err != null) return; try { ws.send(JSON.stringify({ action: 'events', events: docs, tag: command.tag })); } catch (ex) { } });
292 + obj.db.GetEventsWithLimit(filter, domain.id, command.limit, function (err, docs) { if (err != null) return; try { ws.send(JSON.stringify({ action: 'events', events: docs, user: command.user, tag: command.tag })); } catch (ex) { } });
293 }
294 break;
295 }
@@ -459,11 +465,24 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain) {
465 }
466 break;
467 }
468 + case 'changeuserpass':
469 + {
470 + // Change a user's password
471 + if (user.siteadmin != 0xFFFFFFFF) break;
472 + if (obj.common.validateString(command.user, 1, 256) == false) break;
473 + if (obj.common.validateString(command.pass, 1, 256) == false) break;
474 + var chguserid = 'user/' + domain.id + '/' + command.user.toLowerCase(), chguser = obj.parent.users[chguserid];
475 + if (chguser && chguser.salt) {
476 + // Compute the password hash & save it
477 + require('./pass').hash(command.pass, chguser.salt, function (err, hash) { if (!err) { chguser.hash = hash; obj.db.SetUser(chguser); } });
478 + }
479 + break;
480 + }
481 case 'notifyuser':
482 {
483 // Send a notification message to a user
484 if ((user.siteadmin & 2) == 0) break;
466 - if (obj.common.validateString(command.userid, 1, 64) == false) break; // Meshname is between 1 and 64 characters
485 + if (obj.common.validateString(command.userid, 1, 2048) == false) break;
486 if (obj.common.validateString(command.msg, 1, 4096) == false) break;
487
488 // Create the notification message
package.json
+1 -1
@@ -1,6 +1,6 @@
1 {
2 "name": "meshcentral",
3 - "version": "0.1.6-j",
3 + "version": "0.1.6-k",
4 "keywords": [
5 "Remote Management",
6 "Intel AMT",
public/images/User-200.png
Binary files /dev/null and b/public/images/User-200.png differ
public/images/User.png
Binary files /dev/null and b/public/images/User.png differ
views/default.handlebars
+253 -39
@@ -75,8 +75,8 @@
75 <td class=style3 style="text-align:right;height:24px"><span title="Toggle full width" style="cursor:pointer;opacity:0.2" onclick="toggleFullScreen(1)">&harr;</span>&nbsp;</td>
76 </tr>
77 </table>
78 - <div id="MainSubMenuSpan" style=display:none>
79 - <table id="MainSubMenu" style="width: 100%; height: 22px;" cellpadding=0 cellspacing=0 class=style1>
78 + <div id=MainSubMenuSpan style=display:none>
79 + <table id=MainSubMenu style=width:100%;height:22px cellpadding=0 cellspacing=0 class=style1>
80 <tr>
81 <td id=MainDev style=width:100px;height:24px;cursor:pointer class=style3 onclick=go(10)>General</td>
82 <td id=MainDevDesktop style=width:100px;height:24px;cursor:pointer class=style3 onclick=go(11)>Desktop</td>
@@ -88,14 +88,23 @@
88 </tr>
89 </table>
90 </div>
91 - <div id="MeshSubMenuSpan" style=display:none>
92 - <table id="MeshSubMenu" style="width: 100%; height: 22px;" cellpadding=0 cellspacing=0 class=style1>
91 + <div id=MeshSubMenuSpan style=display:none>
92 + <table id=MeshSubMenu style=width:100%;height:22px cellpadding=0 cellspacing=0 class=style1>
93 <tr>
94 <td id=MeshGeneral style=width:100px;height:24px;cursor:pointer class=style3 onclick=go(20)>General</td>
95 <td class=style3 style=height:24px>&nbsp;</td>
96 </tr>
97 </table>
98 </div>
99 + <div id=UserSubMenuSpan style=display:none>
100 + <table id=UserSubMenu style=width:100%;height:22px cellpadding=0 cellspacing=0 class=style1>
101 + <tr>
102 + <td id=UserGeneral style=width:100px;height:24px;cursor:pointer class=style3 onclick=go(30)>General</td>
103 + <td id=UserEvents style=width:100px;height:24px;cursor:pointer class=style3 onclick=go(31)>Events</td>
104 + <td class=style3 style=height:24px>&nbsp;</td>
105 + </tr>
106 + </table>
107 + </div>
108 </div>
109 </div>
110 </div>
@@ -542,6 +551,47 @@
551 <h1><span id=p20meshName></span> - General</h1>
552 <p id=p20info></p>
553 </div>
554 + <div id=p30 style=display:none>
555 + <table style="width:100%" cellpadding="0" cellspacing="0">
556 + <tr>
557 + <td style=width:auto valign=top>
558 + <div id="p30title">
559 + <h1><span id=p30userName></span> - General</h1>
560 + </div>
561 + <div id=p30html></div>
562 + </td>
563 + <td style=width:20px></td>
564 + <td style=width:200px>
565 + <img id=MainUserImage src="images/user-200.png" style=border-width:0px;height:200px;width:200px>
566 + <div style="width:100%;text-align:center"><strong><span id=MainUserState></span></strong></div>
567 + </td>
568 + </tr>
569 + </table><br>
570 + <div id=p30html2></div>
571 + <div id=p30html3></div>
572 + </div>
573 + <div id=p31 style=display:none>
574 + <h1><span id=p31userName></span> - Events</h1>
575 + <div style=width:100%;height:24px;background-color:#d3d9d6;margin-bottom:4px>
576 + <div class=style7 style=width:16px;height:100%;float:left>&nbsp;</div>
577 + <div class=h1 style=height:100%;float:left>&nbsp;</div>
578 + <!--<div class=style14 style=height:100%;float:left>&nbsp;&nbsp;<input id=p31deleteall type=button style=display:none value="Delete All..." />&nbsp;</div>-->
579 + <div class=style14 style=height:100%;float:left>&nbsp;&nbsp;<input type=button value=Refresh onclick=refreshUsersEvents() />&nbsp;</div>
580 + <div class="auto-style1" style="height:100%;float:right">
581 + Show
582 + <select id=p31limitdropdown onchange=refreshUsersEvents()>
583 + <option value=60>Last 60</option>
584 + <option value=120>Last 120</option>
585 + <option value=250>Last 250</option>
586 + <option value=500>Last 500</option>
587 + <option value=1000>Last 1000</option>
588 + </select>
589 + <div style="height:100%;width:20px;float:right;background-color:#ffffff"></div>
590 + <div class="h2" style="height:100%;float:right;">&nbsp;</div>
591 + </div>
592 + </div>
593 + <div id=p31events style="max-height:600px;overflow-y:scroll"></div>
594 + </div>
595 <br id="column_l_bottomgap" />
596 </div>
597 <div id=footer class=noselect>
@@ -865,7 +915,7 @@
915 QV('p2ServerActionsRestore', siteRights & 4);
916 QV('p2ServerActionsVersion', siteRights & 16);
917 QV('MainMenuMyFiles', siteRights & 8);
868 - if (((siteRights & 8) == 0) && (xxcurrentView == 5)) { go(1); }
918 + if (((siteRights & 8) == 0) && (xxcurrentView == 5)) { setDialogMode(0); go(1); }
919 if (currentNode != null) { gotoDevice(currentNode._id, xxcurrentView, true); }
920
921 // Update user management state
@@ -879,7 +929,7 @@
929 users = null;
930 wssessions = null;
931 updateUsers();
882 - if (xxcurrentView == 4) go(1);
932 + if (xxcurrentView == 4 || ((xxcurrentView >= 30) && (xxcurrentView < 40))) { setDialogMode(0); go(1); currentUser = null; }
933 }
934 meshserver.send({ action: 'events', limit: parseInt(p3limitdropdown.value) });
935 QV('p2deleteall', userinfo.siteadmin == 0xFFFFFFFF);
@@ -942,7 +992,7 @@
992 onSearchInputChanged();
993 updateDevices();
994 refreshMap(false, true);
945 - if (xxcurrentView == 0) { if ('{{viewmode}}' != '') { go({{viewmode}}); } else { go(1); } }
995 + if (xxcurrentView == 0) { if ('{{viewmode}}' != '') { go({{viewmode}}); } else { setDialogMode(0); go(1); } }
996 if ('{{currentNode}}' != '') { gotoDevice('{{currentNode}}',{{viewmode}});}
997 break;
998 }
@@ -1023,8 +1073,13 @@
1073 break;
1074 }
1075 case 'events': {
1026 - events = message.events;
1027 - events_update();
1076 + if ((message.user != null) && (message.user == currentUser.name)) {
1077 + currentUserEvents = message.events;
1078 + userEvents_update();
1079 + } else {
1080 + events = message.events;
1081 + events_update();
1082 + }
1083 break;
1084 }
1085 case 'getcookie': {
@@ -1114,7 +1169,7 @@
1169 nodes = newnodes;
1170
1171 // If we are looking at a node in the deleted mesh, move back to "My Devices"
1117 - if (xxcurrentView >= 10 && xxcurrentView < 20 && currentNode && currentNode.meshid == message.event.meshid) go(1);
1172 + if (xxcurrentView >= 10 && xxcurrentView < 20 && currentNode && currentNode.meshid == message.event.meshid) { setDialogMode(0); go(1); }
1173 }
1174 }
1175 updateMeshes();
@@ -1140,9 +1195,9 @@
1195 updateDevices();
1196
1197 // If we are looking at a mesh that is now deleted, move back to "My Account"
1143 - if (xxcurrentView >= 20 && xxcurrentView < 30 && currentMesh._id == message.event.meshid) go(2);
1198 + if (xxcurrentView >= 20 && xxcurrentView < 30 && currentMesh._id == message.event.meshid) { setDialogMode(0); go(2); }
1199 // If we are looking at a node in the deleted mesh, move back to "My Devices"
1145 - if (xxcurrentView >= 10 && xxcurrentView < 20 && currentNode && currentNode.meshid == message.event.meshid) go(1);
1200 + if (xxcurrentView >= 10 && xxcurrentView < 20 && currentNode && currentNode.meshid == message.event.meshid) { setDialogMode(0); go(1); }
1201
1202 break;
1203 }
@@ -1168,7 +1223,7 @@
1223 if (index != -1) {
1224 var node = nodes[index];
1225 if (currentNode == node) {
1171 - if (xxcurrentView >= 10 && xxcurrentView < 20) { go(1); }
1226 + if (xxcurrentView >= 10 && xxcurrentView < 20) { setDialogMode(0); go(1); }
1227 delete currentNode; // TODO: Correctly disconnect from this node (Desktop/Terminal/Files...)
1228 }
1229 nodes.splice(index, 1);
@@ -4850,14 +4905,14 @@
4905 for (var i in sortedUserIds) {
4906 var user = users[sortedUserIds[i]], icon = 'm2', msg = '', self = (user.name != userinfo.name);
4907 if (wssessions != null && wssessions[user._id]) {
4853 - if (self) { msg += "<a onclick=showUserAlertDialog(event,\"" + user._id + "\")>"; }
4908 + if (self) { msg += "<a onclick=showUserAlertDialog(event,\"" + encodeURIComponent(user._id) + "\")>"; }
4909 var sessions = wssessions[user._id];
4910 if (sessions == 1) { msg += '1 active session'; } else { msg += sessions + ' active sessions'; }
4911 if (self) { msg += "</a>"; }
4912 }
4913 if (msg != '') msg += ', ';
4914 if (self) { msg += "<a onclick=showUserAdminDialog(event,\"" + encodeURIComponent(user._id) + "\")>"; }
4860 - if ((user.siteadmin != null) && (user.siteadmin == 32)) { msg += "Locked, "; }
4915 + if ((user.siteadmin != null) && ((user.siteadmin & 32) != 0) && (user.siteadmin != 0xFFFFFFFF)) { msg += "Locked, "; }
4916 if ((user.siteadmin == null) || (user.siteadmin == 0) || (user.siteadmin == 32)) {
4917 msg += "User";
4918 } else if (user.siteadmin == 8) {
@@ -4871,7 +4926,7 @@
4926 if (self) { msg += "</a>"; }
4927 var username = EscapeHtml(user.name);
4928 if (user.email != null) { username += ', <a onclick=doemail(event,\"' + user.email + '\")>' + user.email + '</a>' + (((serverinfo.emailcheck == true) && (user.emailVerified != true))?' (unverified)':''); }
4874 - x += '<tr><td style=cursor:pointer onclick=showUserInfoDialog(\"' + user._id + '\")>';
4929 + x += '<tr><td style=cursor:pointer onclick=gotoUser(\"' + encodeURIComponent(user._id) + '\")>';
4930 x += '<div class=bar style=height:24px;width:100%;font-size:medium>';
4931 x += '<div style=float:left;height:24px;width:24px;background-color:white><div class=' + icon + ' style=width:16px;margin-top:4px;margin-left:2px;height:16px></div></div>';
4932 x += '<div class=g1 style=height:24px;float:left></div><div class=g2 style=height:24px;float:right></div>';
@@ -4879,17 +4934,20 @@
4934 }
4935 x += '</table>';
4936 QH('p3users', x);
4937 +
4938 + // Update current user panel if needed
4939 + if ((currentUser != null) && (xxcurrentView == 30)) { gotoUser(encodeURIComponent(currentUser._id),true); }
4940 }
4941
4942 function showUserAlertDialog(e, userid) {
4943 if (xxdialogMode) return;
4944 haltEvent(e);
4887 - setDialogMode(2, "Notify " + EscapeHtml(users[userid].name), 3, showUserAlertDialogEx, 'Send a text notification to this user.<textarea id=d2notifyText maxlength=2048 style="width:100%;height:184px;resize:none"></textarea>', userid);
4945 + setDialogMode(2, "Notify " + EscapeHtml(users[decodeURIComponent(userid)].name), 3, showUserAlertDialogEx, 'Send a text notification to this user.<textarea id=d2notifyText maxlength=2048 style="width:100%;height:184px;resize:none"></textarea>', userid);
4946 Q('d2notifyText').focus();
4947 return false;
4948 }
4949
4892 - function showUserAlertDialogEx(button, userid) { meshserver.send({ action: 'notifyuser', userid: userid, msg: Q('d2notifyText').value }); }
4950 + function showUserAlertDialogEx(button, userid) { meshserver.send({ action: 'notifyuser', userid: decodeURIComponent(userid), msg: Q('d2notifyText').value }); }
4951
4952 function doemail(e, addr) {
4953 if (xxdialogMode) return;
@@ -4898,24 +4956,6 @@
4956 return false;
4957 }
4958
4901 - function showUserInfoDialog(userid) {
4902 - var user = users[userid], x = '';
4903 - if (xxdialogMode) return;
4904 - x += addHtmlValue('Name', EscapeHtml(user.name));
4905 - if (user.email != null) { x += addHtmlValue('Email', EscapeHtml(user.email)); }
4906 - x += addHtmlValue('Creation', new Date(user.creation).toLocaleString());
4907 - if (user.login) x += addHtmlValue('Last Login', new Date(user.login).toLocaleString());
4908 - if (user.quota) x += addHtmlValue('Server Quota', EscapeHtml(parseInt(user.quota) / 1024) + ' k');
4909 - var deletePossible = true;
4910 - if (user._id == userinfo._id) deletePossible = false;
4911 - if (user.siteadmin && user.siteadmin > 0 && userinfo.siteadmin != 0xFFFFFFFF) deletePossible = false;
4912 - setDialogMode(2, "User " + EscapeHtml(user.name), deletePossible?5:1, showUserInfoDialogDelete, x, user);
4913 - }
4914 -
4915 - function showUserInfoDialogDelete(button, user) {
4916 - if (button == 2) { meshserver.send({ action: 'deleteuser', userid: user._id, username: user.name }); }
4917 - }
4918 -
4959 function showCreateNewAccountDialog() {
4960 if (xxdialogMode) return;
4961 var x = '';
@@ -4929,7 +4969,7 @@
4969 }
4970
4971 function showCreateNewAccountDialogValidate() {
4932 - QE('idx_dlgOkButton', Q('p4name').value.length > 0 && Q('p4pass1').value.length > 0 && Q('p4pass1').value == Q('p4pass2').value);
4972 + QE('idx_dlgOkButton', (!Q('p4name') || (Q('p4name').value.length > 0)) && Q('p4pass1').value.length > 0 && Q('p4pass1').value == Q('p4pass2').value);
4973 }
4974
4975 function showCreateNewAccountDialogEx() {
@@ -4997,6 +5037,177 @@
5037 meshserver.send(x);
5038 }
5039
5040 + //
5041 + // MY USERS GENERAL
5042 + //
5043 +
5044 + var currentUser = null;
5045 + function gotoUser(userid, force) {
5046 + if (xxdialogMode && !force) return;
5047 + var user = currentUser = users[decodeURIComponent(userid)];
5048 + if (user == null) { setDialogMode(0); go(4); return; }
5049 + QH('p30userName', user.name);
5050 + QH('p31userName', user.name);
5051 + var self = (user.name == userinfo.name), activeSessions = 0;
5052 +
5053 + if (wssessions != null && wssessions[user._id]) { activeSessions = wssessions[user._id]; }
5054 +
5055 + // Server permissions
5056 + var msg = '';
5057 + if ((user.siteadmin != null) && ((user.siteadmin & 32) != 0) && (user.siteadmin != 0xFFFFFFFF)) { msg += "Locked account, "; }
5058 + if ((user.siteadmin == null) || (user.siteadmin == 0) || (user.siteadmin == 32)) { msg += "No server rights"; } else if (user.siteadmin == 8) { msg += "Access to server files"; } else if (user.siteadmin == 0xFFFFFFFF) { msg += "Full administrator"; } else { msg += "Partial rights"; }
5059 +
5060 + // Show user attributes
5061 + var x = '<div style=min-height:80px><table style=width:100%>';
5062 + if (user.email) x += addDeviceAttribute('Email', '<a style=cursor:pointer onclick=doemail(event,\"' + user.email + '\")>' + EscapeHtml(user.email) + '</a>');
5063 + x += addDeviceAttribute('Creation', new Date(user.creation).toLocaleString());
5064 + if (user.login) x += addDeviceAttribute('Last Login', new Date(user.login).toLocaleString());
5065 + x += addDeviceAttribute('Server Rights', "<a style=cursor:pointer onclick=showUserAdminDialog(event,\"" + userid + "\")>" + msg + "</a>");
5066 + if (user.quota) x += addDeviceAttribute('Server Quota', EscapeHtml(parseInt(user.quota) / 1024) + ' k');
5067 +
5068 + x += '</table></div><br />';
5069 +
5070 + // Add action buttons
5071 + //x += '<input type=button value=Notes title="View notes about this user" onclick=userNotesFunction("' + userid + '") />';
5072 + if (!self && (activeSessions > 0)) { x += '<input type=button value=Notify title="Send user notification" onclick=showUserAlertDialog(event,"' + userid + '") />'; }
5073 +
5074 + // Setup the panel
5075 + QH('p30html', x);
5076 +
5077 + // Draw the user timeline
5078 + drawUserTimeline();
5079 +
5080 + // Check if we can delete this user
5081 + var deletePossible = true;
5082 + if (user._id == userinfo._id) deletePossible = false;
5083 + if (user.siteadmin && user.siteadmin > 0 && userinfo.siteadmin != 0xFFFFFFFF) deletePossible = false;
5084 +
5085 + // Show bottom buttons
5086 + x = '<div style=float:right;font-size:x-small>';
5087 + if (deletePossible) x += '<a style=cursor:pointer onclick=p30showDeleteUserDialog() title="Remove this user">Delete User</a>';
5088 + x += '</div><div style=font-size:x-small>';
5089 + if (userinfo.siteadmin == 0xFFFFFFFF) x += '<a style=cursor:pointer onclick=p30showUserChangePassDialog() title="Change the password for this user">Change Password</a>';
5090 + x += '</div><br>'
5091 + QH('p30html3', x);
5092 +
5093 + // Update user's connection state
5094 + x = '';
5095 + if (activeSessions == 1) { x = '1 active session'; } else if (activeSessions > 1) { x = activeSessions + ' active sessions'; }
5096 + QH('MainUserState', x);
5097 +
5098 + go(30);
5099 +
5100 + // Update user events (TODO: do this only if we change users)
5101 + QH('p31events', '');
5102 + refreshUsersEvents();
5103 + }
5104 +
5105 + function p30showUserChangePassDialog() {
5106 + if (xxdialogMode) return;
5107 + var x = '';
5108 + x += addHtmlValue('Password', '<input id=p4pass1 type=password style=width:230px maxlength=256 onchange=showCreateNewAccountDialogValidate() onkeyup=showCreateNewAccountDialogValidate() />');
5109 + x += addHtmlValue('Password', '<input id=p4pass2 type=password style=width:230px maxlength=256 onchange=showCreateNewAccountDialogValidate() onkeyup=showCreateNewAccountDialogValidate() />');
5110 + setDialogMode(2, "Change Password for " + EscapeHtml(currentUser.name), 3, p30showUserChangePassDialogEx, x);
5111 + showCreateNewAccountDialogValidate();
5112 + Q('p4pass1').focus();
5113 + }
5114 +
5115 + function p30showUserChangePassDialogEx() {
5116 + if (Q('p4pass1').value == Q('p4pass2').value) { meshserver.send({ action: 'changeuserpass', user: currentUser.name, pass: Q('p4pass1').value }); } }
5117 +
5118 + function p30showDeleteUserDialog() {
5119 + if (xxdialogMode) return;
5120 + setDialogMode(2, "Delete User " + EscapeHtml(currentUser.name), 3, p30showDeleteUserDialogEx, 'Confirm deletion of user ' + EscapeHtml(currentUser.name) + '?');
5121 + }
5122 +
5123 + function p30showDeleteUserDialogEx() {
5124 + meshserver.send({ action: 'deleteuser', userid: currentUser._id, username: currentUser.name });
5125 + }
5126 +
5127 + // Draw device power bars. The bars are 766px wide.
5128 + function drawUserTimeline() {
5129 + var timeline = null, now = Date.now();
5130 + //if (currentNode._id == powerTimelineNode) { timeline = powerTimeline; }
5131 + timeline = [];
5132 +
5133 + // Calculate when the timeline starts
5134 + var d = new Date();
5135 + d.setHours(0, 0, 0, 0);
5136 + d = new Date(d.getTime() - (1000 * 60 * 60 * 24 * 6));
5137 + var timelineStart = d.getTime();
5138 +
5139 + // De-compact the timeline
5140 + var timeline2 = [];
5141 + if (timeline != null && timeline.length > 1) {
5142 + timeline2.push([ 0, timeline[1], timeline[0] ]); // Start, End, Power
5143 + var ct = timeline[1];
5144 + for (var i = 2; i < timeline.length; i += 2) {
5145 + var power = timeline[i], dt = now;
5146 + if (timeline.length > (i + 1)) { dt = timeline[i + 1]; }
5147 + timeline2.push([ ct, ct + dt, power ]); // Start, End, Power
5148 + ct = ct + dt;
5149 + }
5150 + }
5151 +
5152 + // Draw the timeline
5153 + var x = '', count = 1, date = new Date();
5154 + date.setHours(0, 0, 0, 0);
5155 + for (var i = 0; i < 7; i++) {
5156 + var datavalue = '', start = date.getTime(), end = start + (1000 * 60 * 60 * 24);
5157 + for (var j in timeline2) {
5158 + var block = timeline2[j];
5159 + if (isTimeBlockInside(start, end, block[0], block[1]) == true) {
5160 + var ts = Math.max(start, block[0]);
5161 + var te = Math.min(Math.min(end, block[1]), now);
5162 + var width = Math.round((te - ts) / 112794);
5163 + if (width > 0) {
5164 + var title = powerStateStrings2[block[2]] + ' from ' + new Date(ts).toLocaleTimeString() + ' to ' + new Date(te).toLocaleTimeString() + '.';
5165 + datavalue += '<div title="' + title + '" style=display:table-cell;width:' + width + 'px;background-color:' + powerColor(block[2]) + ';height:16px></div>';
5166 + }
5167 + }
5168 + }
5169 + x += '<tr style=' + (((count % 2) == 0)?'background-color:#DDD':'') + '><td><div>&nbsp;' + date.toLocaleDateString() + '<div></div></div></td><td><div>' + datavalue + '</div></td></tr>';
5170 + ++count;
5171 + date = new Date(date.getTime() - (1000 * 60 * 60 * 24)); // Substract one day
5172 + }
5173 + QH('p30html2', '<table style="color:black;background-color:#EEE;border-color:#AAA;border-width:1px;border-style:solid;border-collapse:collapse" border=0 cellpadding=2 cellspacing=0 width=100%><tbody><tr style=background-color:#AAAAAA;font-weight:bold><th scope=col style=text-align:center;width:150px>Day</th><th scope=col style=text-align:center>7 Day Login State</th></tr>' + x + '</tbody></table>');
5174 + }
5175 +
5176 + //
5177 + // MY USERS EVENTS
5178 + //
5179 +
5180 + var currentUserEvents = null;
5181 + function userEvents_update() {
5182 + var x = '', dateHeader = null;
5183 + for (var i in currentUserEvents) {
5184 + var event = currentUserEvents[i];
5185 + var time = new Date(event.time);
5186 + if (time.toLocaleDateString() != dateHeader) {
5187 + if (dateHeader != null) x += '</table>';
5188 + x += '<table style=width:100% cellpadding=0 cellspacing=0><tr><td class=DevSt>' + time.toLocaleDateString() + '</td></tr>';
5189 + dateHeader = time.toLocaleDateString();
5190 + }
5191 + var icon = 'si3';
5192 + if (event.etype == 'user') icon = 'm2';
5193 + if (event.etype == 'server') icon = 'si3';
5194 +
5195 + var msg = event.msg.split('(R)').join('&reg;');
5196 + if (event.username && event.username != userinfo.name) { msg += ': ' + event.username; }
5197 + x += '<tr><td><div class=bar18 style=height:18px;width:100%;font-size:medium>';
5198 + x += '<div style=float:left;height:18px;width:18px;background-color:white><div class=' + icon + ' style=width:16px;margin-top:1px;margin-left:2px;height:16px></div></div>';
5199 + x += '<div class=g1 style=height:18px;float:left></div><div class=g2 style=height:18px;float:right></div>';
5200 + x += '<div style=font-size:14px><span style=width:300px>' + time.toLocaleTimeString() + ' - ' + msg + '</span></div></div></td></tr>';
5201 + }
5202 + if (dateHeader != null) x += '</table>';
5203 + if (x == '') x = "<br><i>No Events Found</i><br><br>";
5204 + QH('p31events', x);
5205 + }
5206 +
5207 + function refreshUsersEvents() {
5208 + meshserver.send({ action: 'events', limit: parseInt(p31limitdropdown.value), user: currentUser.name });
5209 + }
5210 +
5211 //
5212 // FILE SELECTOR, DIALOG 3
5213 //
@@ -5237,14 +5448,14 @@
5448 function go(x) {
5449 if (xxdialogMode || xxcurrentView == x) return;
5450 // Edit this line when adding a new screen
5240 - for (var i = 0; i < 22; i++) { QV('p' + i, i == x); }
5451 + for (var i = 0; i < 32; i++) { QV('p' + i, i == x); }
5452 xxcurrentView = x;
5453
5454 QV('topbar', x != 0);
5455 if (x >= 10 && x < 20) { QS('MainMenuMyDevices').backgroundColor = "#606060"; } else { QS('MainMenuMyDevices').backgroundColor = ((x == 1) ? "#003366" : "#808080"); }
5456 if (x >= 20 && x < 30) { QS('MainMenuMyAccount').backgroundColor = "#606060"; } else { QS('MainMenuMyAccount').backgroundColor = ((x == 2) ? "#003366" : "#808080"); }
5457 QS('MainMenuMyEvents').backgroundColor = ((x == 3) ? "#003366" : "#808080");
5247 - QS('MainMenuMyUsers').backgroundColor = ((x == 4) ? "#003366" : "#808080");
5458 + if (x >= 30 && x < 40) { QS('MainMenuMyUsers').backgroundColor = "#606060"; } else { QS('MainMenuMyUsers').backgroundColor = ((x == 4) ? "#003366" : "#808080"); }
5459 QS('MainMenuMyFiles').backgroundColor = ((x == 5) ? "#003366" : "#808080");
5460 QV('MainSubMenuSpan', x >= 10 && x < 20);
5461 QS('MainDev').backgroundColor = ((x == 10) ? "#003366" : "#808080");
@@ -5255,6 +5466,9 @@
5466 QS('MainDevConsole').backgroundColor = ((x == 15) ? "#003366" : "#808080");
5467 QV('MeshSubMenuSpan', x >= 20 && x < 30);
5468 QS('MeshGeneral').backgroundColor = ((x == 20) ? "#003366" : "#808080");
5469 + QV('UserSubMenuSpan', x >= 30 && x < 40);
5470 + QS('UserGeneral').backgroundColor = ((x == 30) ? "#003366" : "#808080");
5471 + QS('UserEvents').backgroundColor = ((x == 31) ? "#003366" : "#808080");
5472 if (x == 1) updateDevices();
5473 }
5474