Improved user management

This commit is contained in:
Ylian Saint-Hilaire 2018-04-15 22:22:44 -07:00
parent ea2a0bb321
commit ff1478c48a
11 changed files with 276 additions and 43 deletions

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -278,12 +278,18 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain) {
}
case 'events':
{
// Setup the event filter
var filter = user.subscriptions;
if ((command.user != null) && ((user.siteadmin & 2) != 0)) { // SITERIGHT_MANAGEUSERS
// TODO: Add the meshes command.user has access to
filter = ['user/' + domain.id + '/' + command.user.toLowerCase()];
}
if ((command.limit == null) || (typeof command.limit != 'number')) {
// Send the list of all events for this session
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) { } });
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) { } });
} else {
// Send the list of most recent events for this session, up to 'limit' count
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) { } });
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) { } });
}
break;
}
@ -459,11 +465,24 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain) {
}
break;
}
case 'changeuserpass':
{
// Change a user's password
if (user.siteadmin != 0xFFFFFFFF) break;
if (obj.common.validateString(command.user, 1, 256) == false) break;
if (obj.common.validateString(command.pass, 1, 256) == false) break;
var chguserid = 'user/' + domain.id + '/' + command.user.toLowerCase(), chguser = obj.parent.users[chguserid];
if (chguser && chguser.salt) {
// Compute the password hash & save it
require('./pass').hash(command.pass, chguser.salt, function (err, hash) { if (!err) { chguser.hash = hash; obj.db.SetUser(chguser); } });
}
break;
}
case 'notifyuser':
{
// Send a notification message to a user
if ((user.siteadmin & 2) == 0) break;
if (obj.common.validateString(command.userid, 1, 64) == false) break; // Meshname is between 1 and 64 characters
if (obj.common.validateString(command.userid, 1, 2048) == false) break;
if (obj.common.validateString(command.msg, 1, 4096) == false) break;
// Create the notification message

View File

@ -1,6 +1,6 @@
{
"name": "meshcentral",
"version": "0.1.6-j",
"version": "0.1.6-k",
"keywords": [
"Remote Management",
"Intel AMT",

BIN
public/images/User-200.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

BIN
public/images/User.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

View File

@ -75,8 +75,8 @@
<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>
</tr>
</table>
<div id="MainSubMenuSpan" style=display:none>
<table id="MainSubMenu" style="width: 100%; height: 22px;" cellpadding=0 cellspacing=0 class=style1>
<div id=MainSubMenuSpan style=display:none>
<table id=MainSubMenu style=width:100%;height:22px cellpadding=0 cellspacing=0 class=style1>
<tr>
<td id=MainDev style=width:100px;height:24px;cursor:pointer class=style3 onclick=go(10)>General</td>
<td id=MainDevDesktop style=width:100px;height:24px;cursor:pointer class=style3 onclick=go(11)>Desktop</td>
@ -88,14 +88,23 @@
</tr>
</table>
</div>
<div id="MeshSubMenuSpan" style=display:none>
<table id="MeshSubMenu" style="width: 100%; height: 22px;" cellpadding=0 cellspacing=0 class=style1>
<div id=MeshSubMenuSpan style=display:none>
<table id=MeshSubMenu style=width:100%;height:22px cellpadding=0 cellspacing=0 class=style1>
<tr>
<td id=MeshGeneral style=width:100px;height:24px;cursor:pointer class=style3 onclick=go(20)>General</td>
<td class=style3 style=height:24px>&nbsp;</td>
</tr>
</table>
</div>
<div id=UserSubMenuSpan style=display:none>
<table id=UserSubMenu style=width:100%;height:22px cellpadding=0 cellspacing=0 class=style1>
<tr>
<td id=UserGeneral style=width:100px;height:24px;cursor:pointer class=style3 onclick=go(30)>General</td>
<td id=UserEvents style=width:100px;height:24px;cursor:pointer class=style3 onclick=go(31)>Events</td>
<td class=style3 style=height:24px>&nbsp;</td>
</tr>
</table>
</div>
</div>
</div>
</div>
@ -542,6 +551,47 @@
<h1><span id=p20meshName></span> - General</h1>
<p id=p20info></p>
</div>
<div id=p30 style=display:none>
<table style="width:100%" cellpadding="0" cellspacing="0">
<tr>
<td style=width:auto valign=top>
<div id="p30title">
<h1><span id=p30userName></span> - General</h1>
</div>
<div id=p30html></div>
</td>
<td style=width:20px></td>
<td style=width:200px>
<img id=MainUserImage src="images/user-200.png" style=border-width:0px;height:200px;width:200px>
<div style="width:100%;text-align:center"><strong><span id=MainUserState></span></strong></div>
</td>
</tr>
</table><br>
<div id=p30html2></div>
<div id=p30html3></div>
</div>
<div id=p31 style=display:none>
<h1><span id=p31userName></span> - Events</h1>
<div style=width:100%;height:24px;background-color:#d3d9d6;margin-bottom:4px>
<div class=style7 style=width:16px;height:100%;float:left>&nbsp;</div>
<div class=h1 style=height:100%;float:left>&nbsp;</div>
<!--<div class=style14 style=height:100%;float:left>&nbsp;&nbsp;<input id=p31deleteall type=button style=display:none value="Delete All..." />&nbsp;</div>-->
<div class=style14 style=height:100%;float:left>&nbsp;&nbsp;<input type=button value=Refresh onclick=refreshUsersEvents() />&nbsp;</div>
<div class="auto-style1" style="height:100%;float:right">
Show
<select id=p31limitdropdown onchange=refreshUsersEvents()>
<option value=60>Last 60</option>
<option value=120>Last 120</option>
<option value=250>Last 250</option>
<option value=500>Last 500</option>
<option value=1000>Last 1000</option>
</select>
<div style="height:100%;width:20px;float:right;background-color:#ffffff"></div>
<div class="h2" style="height:100%;float:right;">&nbsp;</div>
</div>
</div>
<div id=p31events style="max-height:600px;overflow-y:scroll"></div>
</div>
<br id="column_l_bottomgap" />
</div>
<div id=footer class=noselect>
@ -865,7 +915,7 @@
QV('p2ServerActionsRestore', siteRights & 4);
QV('p2ServerActionsVersion', siteRights & 16);
QV('MainMenuMyFiles', siteRights & 8);
if (((siteRights & 8) == 0) && (xxcurrentView == 5)) { go(1); }
if (((siteRights & 8) == 0) && (xxcurrentView == 5)) { setDialogMode(0); go(1); }
if (currentNode != null) { gotoDevice(currentNode._id, xxcurrentView, true); }
// Update user management state
@ -879,7 +929,7 @@
users = null;
wssessions = null;
updateUsers();
if (xxcurrentView == 4) go(1);
if (xxcurrentView == 4 || ((xxcurrentView >= 30) && (xxcurrentView < 40))) { setDialogMode(0); go(1); currentUser = null; }
}
meshserver.send({ action: 'events', limit: parseInt(p3limitdropdown.value) });
QV('p2deleteall', userinfo.siteadmin == 0xFFFFFFFF);
@ -942,7 +992,7 @@
onSearchInputChanged();
updateDevices();
refreshMap(false, true);
if (xxcurrentView == 0) { if ('{{viewmode}}' != '') { go({{viewmode}}); } else { go(1); } }
if (xxcurrentView == 0) { if ('{{viewmode}}' != '') { go({{viewmode}}); } else { setDialogMode(0); go(1); } }
if ('{{currentNode}}' != '') { gotoDevice('{{currentNode}}',{{viewmode}});}
break;
}
@ -1023,8 +1073,13 @@
break;
}
case 'events': {
if ((message.user != null) && (message.user == currentUser.name)) {
currentUserEvents = message.events;
userEvents_update();
} else {
events = message.events;
events_update();
}
break;
}
case 'getcookie': {
@ -1114,7 +1169,7 @@
nodes = newnodes;
// If we are looking at a node in the deleted mesh, move back to "My Devices"
if (xxcurrentView >= 10 && xxcurrentView < 20 && currentNode && currentNode.meshid == message.event.meshid) go(1);
if (xxcurrentView >= 10 && xxcurrentView < 20 && currentNode && currentNode.meshid == message.event.meshid) { setDialogMode(0); go(1); }
}
}
updateMeshes();
@ -1140,9 +1195,9 @@
updateDevices();
// If we are looking at a mesh that is now deleted, move back to "My Account"
if (xxcurrentView >= 20 && xxcurrentView < 30 && currentMesh._id == message.event.meshid) go(2);
if (xxcurrentView >= 20 && xxcurrentView < 30 && currentMesh._id == message.event.meshid) { setDialogMode(0); go(2); }
// If we are looking at a node in the deleted mesh, move back to "My Devices"
if (xxcurrentView >= 10 && xxcurrentView < 20 && currentNode && currentNode.meshid == message.event.meshid) go(1);
if (xxcurrentView >= 10 && xxcurrentView < 20 && currentNode && currentNode.meshid == message.event.meshid) { setDialogMode(0); go(1); }
break;
}
@ -1168,7 +1223,7 @@
if (index != -1) {
var node = nodes[index];
if (currentNode == node) {
if (xxcurrentView >= 10 && xxcurrentView < 20) { go(1); }
if (xxcurrentView >= 10 && xxcurrentView < 20) { setDialogMode(0); go(1); }
delete currentNode; // TODO: Correctly disconnect from this node (Desktop/Terminal/Files...)
}
nodes.splice(index, 1);
@ -4850,14 +4905,14 @@
for (var i in sortedUserIds) {
var user = users[sortedUserIds[i]], icon = 'm2', msg = '', self = (user.name != userinfo.name);
if (wssessions != null && wssessions[user._id]) {
if (self) { msg += "<a onclick=showUserAlertDialog(event,\"" + user._id + "\")>"; }
if (self) { msg += "<a onclick=showUserAlertDialog(event,\"" + encodeURIComponent(user._id) + "\")>"; }
var sessions = wssessions[user._id];
if (sessions == 1) { msg += '1 active session'; } else { msg += sessions + ' active sessions'; }
if (self) { msg += "</a>"; }
}
if (msg != '') msg += ', ';
if (self) { msg += "<a onclick=showUserAdminDialog(event,\"" + encodeURIComponent(user._id) + "\")>"; }
if ((user.siteadmin != null) && (user.siteadmin == 32)) { msg += "Locked, "; }
if ((user.siteadmin != null) && ((user.siteadmin & 32) != 0) && (user.siteadmin != 0xFFFFFFFF)) { msg += "Locked, "; }
if ((user.siteadmin == null) || (user.siteadmin == 0) || (user.siteadmin == 32)) {
msg += "User";
} else if (user.siteadmin == 8) {
@ -4871,7 +4926,7 @@
if (self) { msg += "</a>"; }
var username = EscapeHtml(user.name);
if (user.email != null) { username += ', <a onclick=doemail(event,\"' + user.email + '\")>' + user.email + '</a>' + (((serverinfo.emailcheck == true) && (user.emailVerified != true))?' (unverified)':''); }
x += '<tr><td style=cursor:pointer onclick=showUserInfoDialog(\"' + user._id + '\")>';
x += '<tr><td style=cursor:pointer onclick=gotoUser(\"' + encodeURIComponent(user._id) + '\")>';
x += '<div class=bar style=height:24px;width:100%;font-size:medium>';
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>';
x += '<div class=g1 style=height:24px;float:left></div><div class=g2 style=height:24px;float:right></div>';
@ -4879,17 +4934,20 @@
}
x += '</table>';
QH('p3users', x);
// Update current user panel if needed
if ((currentUser != null) && (xxcurrentView == 30)) { gotoUser(encodeURIComponent(currentUser._id),true); }
}
function showUserAlertDialog(e, userid) {
if (xxdialogMode) return;
haltEvent(e);
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);
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);
Q('d2notifyText').focus();
return false;
}
function showUserAlertDialogEx(button, userid) { meshserver.send({ action: 'notifyuser', userid: userid, msg: Q('d2notifyText').value }); }
function showUserAlertDialogEx(button, userid) { meshserver.send({ action: 'notifyuser', userid: decodeURIComponent(userid), msg: Q('d2notifyText').value }); }
function doemail(e, addr) {
if (xxdialogMode) return;
@ -4898,24 +4956,6 @@
return false;
}
function showUserInfoDialog(userid) {
var user = users[userid], x = '';
if (xxdialogMode) return;
x += addHtmlValue('Name', EscapeHtml(user.name));
if (user.email != null) { x += addHtmlValue('Email', EscapeHtml(user.email)); }
x += addHtmlValue('Creation', new Date(user.creation).toLocaleString());
if (user.login) x += addHtmlValue('Last Login', new Date(user.login).toLocaleString());
if (user.quota) x += addHtmlValue('Server Quota', EscapeHtml(parseInt(user.quota) / 1024) + ' k');
var deletePossible = true;
if (user._id == userinfo._id) deletePossible = false;
if (user.siteadmin && user.siteadmin > 0 && userinfo.siteadmin != 0xFFFFFFFF) deletePossible = false;
setDialogMode(2, "User " + EscapeHtml(user.name), deletePossible?5:1, showUserInfoDialogDelete, x, user);
}
function showUserInfoDialogDelete(button, user) {
if (button == 2) { meshserver.send({ action: 'deleteuser', userid: user._id, username: user.name }); }
}
function showCreateNewAccountDialog() {
if (xxdialogMode) return;
var x = '';
@ -4929,7 +4969,7 @@
}
function showCreateNewAccountDialogValidate() {
QE('idx_dlgOkButton', Q('p4name').value.length > 0 && Q('p4pass1').value.length > 0 && Q('p4pass1').value == Q('p4pass2').value);
QE('idx_dlgOkButton', (!Q('p4name') || (Q('p4name').value.length > 0)) && Q('p4pass1').value.length > 0 && Q('p4pass1').value == Q('p4pass2').value);
}
function showCreateNewAccountDialogEx() {
@ -4997,6 +5037,177 @@
meshserver.send(x);
}
//
// MY USERS GENERAL
//
var currentUser = null;
function gotoUser(userid, force) {
if (xxdialogMode && !force) return;
var user = currentUser = users[decodeURIComponent(userid)];
if (user == null) { setDialogMode(0); go(4); return; }
QH('p30userName', user.name);
QH('p31userName', user.name);
var self = (user.name == userinfo.name), activeSessions = 0;
if (wssessions != null && wssessions[user._id]) { activeSessions = wssessions[user._id]; }
// Server permissions
var msg = '';
if ((user.siteadmin != null) && ((user.siteadmin & 32) != 0) && (user.siteadmin != 0xFFFFFFFF)) { msg += "Locked account, "; }
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"; }
// Show user attributes
var x = '<div style=min-height:80px><table style=width:100%>';
if (user.email) x += addDeviceAttribute('Email', '<a style=cursor:pointer onclick=doemail(event,\"' + user.email + '\")>' + EscapeHtml(user.email) + '</a>');
x += addDeviceAttribute('Creation', new Date(user.creation).toLocaleString());
if (user.login) x += addDeviceAttribute('Last Login', new Date(user.login).toLocaleString());
x += addDeviceAttribute('Server Rights', "<a style=cursor:pointer onclick=showUserAdminDialog(event,\"" + userid + "\")>" + msg + "</a>");
if (user.quota) x += addDeviceAttribute('Server Quota', EscapeHtml(parseInt(user.quota) / 1024) + ' k');
x += '</table></div><br />';
// Add action buttons
//x += '<input type=button value=Notes title="View notes about this user" onclick=userNotesFunction("' + userid + '") />';
if (!self && (activeSessions > 0)) { x += '<input type=button value=Notify title="Send user notification" onclick=showUserAlertDialog(event,"' + userid + '") />'; }
// Setup the panel
QH('p30html', x);
// Draw the user timeline
drawUserTimeline();
// Check if we can delete this user
var deletePossible = true;
if (user._id == userinfo._id) deletePossible = false;
if (user.siteadmin && user.siteadmin > 0 && userinfo.siteadmin != 0xFFFFFFFF) deletePossible = false;
// Show bottom buttons
x = '<div style=float:right;font-size:x-small>';
if (deletePossible) x += '<a style=cursor:pointer onclick=p30showDeleteUserDialog() title="Remove this user">Delete User</a>';
x += '</div><div style=font-size:x-small>';
if (userinfo.siteadmin == 0xFFFFFFFF) x += '<a style=cursor:pointer onclick=p30showUserChangePassDialog() title="Change the password for this user">Change Password</a>';
x += '</div><br>'
QH('p30html3', x);
// Update user's connection state
x = '';
if (activeSessions == 1) { x = '1 active session'; } else if (activeSessions > 1) { x = activeSessions + ' active sessions'; }
QH('MainUserState', x);
go(30);
// Update user events (TODO: do this only if we change users)
QH('p31events', '');
refreshUsersEvents();
}
function p30showUserChangePassDialog() {
if (xxdialogMode) return;
var x = '';
x += addHtmlValue('Password', '<input id=p4pass1 type=password style=width:230px maxlength=256 onchange=showCreateNewAccountDialogValidate() onkeyup=showCreateNewAccountDialogValidate() />');
x += addHtmlValue('Password', '<input id=p4pass2 type=password style=width:230px maxlength=256 onchange=showCreateNewAccountDialogValidate() onkeyup=showCreateNewAccountDialogValidate() />');
setDialogMode(2, "Change Password for " + EscapeHtml(currentUser.name), 3, p30showUserChangePassDialogEx, x);
showCreateNewAccountDialogValidate();
Q('p4pass1').focus();
}
function p30showUserChangePassDialogEx() {
if (Q('p4pass1').value == Q('p4pass2').value) { meshserver.send({ action: 'changeuserpass', user: currentUser.name, pass: Q('p4pass1').value }); } }
function p30showDeleteUserDialog() {
if (xxdialogMode) return;
setDialogMode(2, "Delete User " + EscapeHtml(currentUser.name), 3, p30showDeleteUserDialogEx, 'Confirm deletion of user ' + EscapeHtml(currentUser.name) + '?');
}
function p30showDeleteUserDialogEx() {
meshserver.send({ action: 'deleteuser', userid: currentUser._id, username: currentUser.name });
}
// Draw device power bars. The bars are 766px wide.
function drawUserTimeline() {
var timeline = null, now = Date.now();
//if (currentNode._id == powerTimelineNode) { timeline = powerTimeline; }
timeline = [];
// Calculate when the timeline starts
var d = new Date();
d.setHours(0, 0, 0, 0);
d = new Date(d.getTime() - (1000 * 60 * 60 * 24 * 6));
var timelineStart = d.getTime();
// De-compact the timeline
var timeline2 = [];
if (timeline != null && timeline.length > 1) {
timeline2.push([ 0, timeline[1], timeline[0] ]); // Start, End, Power
var ct = timeline[1];
for (var i = 2; i < timeline.length; i += 2) {
var power = timeline[i], dt = now;
if (timeline.length > (i + 1)) { dt = timeline[i + 1]; }
timeline2.push([ ct, ct + dt, power ]); // Start, End, Power
ct = ct + dt;
}
}
// Draw the timeline
var x = '', count = 1, date = new Date();
date.setHours(0, 0, 0, 0);
for (var i = 0; i < 7; i++) {
var datavalue = '', start = date.getTime(), end = start + (1000 * 60 * 60 * 24);
for (var j in timeline2) {
var block = timeline2[j];
if (isTimeBlockInside(start, end, block[0], block[1]) == true) {
var ts = Math.max(start, block[0]);
var te = Math.min(Math.min(end, block[1]), now);
var width = Math.round((te - ts) / 112794);
if (width > 0) {
var title = powerStateStrings2[block[2]] + ' from ' + new Date(ts).toLocaleTimeString() + ' to ' + new Date(te).toLocaleTimeString() + '.';
datavalue += '<div title="' + title + '" style=display:table-cell;width:' + width + 'px;background-color:' + powerColor(block[2]) + ';height:16px></div>';
}
}
}
x += '<tr style=' + (((count % 2) == 0)?'background-color:#DDD':'') + '><td><div>&nbsp;' + date.toLocaleDateString() + '<div></div></div></td><td><div>' + datavalue + '</div></td></tr>';
++count;
date = new Date(date.getTime() - (1000 * 60 * 60 * 24)); // Substract one day
}
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>');
}
//
// MY USERS EVENTS
//
var currentUserEvents = null;
function userEvents_update() {
var x = '', dateHeader = null;
for (var i in currentUserEvents) {
var event = currentUserEvents[i];
var time = new Date(event.time);
if (time.toLocaleDateString() != dateHeader) {
if (dateHeader != null) x += '</table>';
x += '<table style=width:100% cellpadding=0 cellspacing=0><tr><td class=DevSt>' + time.toLocaleDateString() + '</td></tr>';
dateHeader = time.toLocaleDateString();
}
var icon = 'si3';
if (event.etype == 'user') icon = 'm2';
if (event.etype == 'server') icon = 'si3';
var msg = event.msg.split('(R)').join('&reg;');
if (event.username && event.username != userinfo.name) { msg += ': ' + event.username; }
x += '<tr><td><div class=bar18 style=height:18px;width:100%;font-size:medium>';
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>';
x += '<div class=g1 style=height:18px;float:left></div><div class=g2 style=height:18px;float:right></div>';
x += '<div style=font-size:14px><span style=width:300px>' + time.toLocaleTimeString() + ' - ' + msg + '</span></div></div></td></tr>';
}
if (dateHeader != null) x += '</table>';
if (x == '') x = "<br><i>No Events Found</i><br><br>";
QH('p31events', x);
}
function refreshUsersEvents() {
meshserver.send({ action: 'events', limit: parseInt(p31limitdropdown.value), user: currentUser.name });
}
//
// FILE SELECTOR, DIALOG 3
//
@ -5237,14 +5448,14 @@
function go(x) {
if (xxdialogMode || xxcurrentView == x) return;
// Edit this line when adding a new screen
for (var i = 0; i < 22; i++) { QV('p' + i, i == x); }
for (var i = 0; i < 32; i++) { QV('p' + i, i == x); }
xxcurrentView = x;
QV('topbar', x != 0);
if (x >= 10 && x < 20) { QS('MainMenuMyDevices').backgroundColor = "#606060"; } else { QS('MainMenuMyDevices').backgroundColor = ((x == 1) ? "#003366" : "#808080"); }
if (x >= 20 && x < 30) { QS('MainMenuMyAccount').backgroundColor = "#606060"; } else { QS('MainMenuMyAccount').backgroundColor = ((x == 2) ? "#003366" : "#808080"); }
QS('MainMenuMyEvents').backgroundColor = ((x == 3) ? "#003366" : "#808080");
QS('MainMenuMyUsers').backgroundColor = ((x == 4) ? "#003366" : "#808080");
if (x >= 30 && x < 40) { QS('MainMenuMyUsers').backgroundColor = "#606060"; } else { QS('MainMenuMyUsers').backgroundColor = ((x == 4) ? "#003366" : "#808080"); }
QS('MainMenuMyFiles').backgroundColor = ((x == 5) ? "#003366" : "#808080");
QV('MainSubMenuSpan', x >= 10 && x < 20);
QS('MainDev').backgroundColor = ((x == 10) ? "#003366" : "#808080");
@ -5255,6 +5466,9 @@
QS('MainDevConsole').backgroundColor = ((x == 15) ? "#003366" : "#808080");
QV('MeshSubMenuSpan', x >= 20 && x < 30);
QS('MeshGeneral').backgroundColor = ((x == 20) ? "#003366" : "#808080");
QV('UserSubMenuSpan', x >= 30 && x < 40);
QS('UserGeneral').backgroundColor = ((x == 30) ? "#003366" : "#808080");
QS('UserEvents').backgroundColor = ((x == 31) ? "#003366" : "#808080");
if (x == 1) updateDevices();
}