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)">↔</span> </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> </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> </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> </div>
577
+ <div class=h1 style=height:100%;float:left> </div>
578
+ <!--<div class=style14 style=height:100%;float:left> <input id=p31deleteall type=button style=display:none value="Delete All..." /> </div>-->
579
+ <div class=style14 style=height:100%;float:left> <input type=button value=Refresh onclick=refreshUsersEvents() /> </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;"> </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> ' + 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('®');
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