Lots of progress on security user group UI.
This commit is contained in:
parent
b32f5aaed0
commit
86fe9085a8
|
@ -123,7 +123,7 @@ function CreateMeshCentralServer(config, args) {
|
|||
try { require('./pass').hash('test', function () { }, 0); } catch (e) { console.log('Old version of node, must upgrade.'); return; } // TODO: Not sure if this test works or not.
|
||||
|
||||
// Check for invalid arguments
|
||||
var validArguments = ['_', 'notls', 'user', 'port', 'aliasport', 'mpsport', 'mpsaliasport', 'redirport', 'rediraliasport', 'cert', 'mpscert', 'deletedomain', 'deletedefaultdomain', 'showall', 'showusers', 'shownodes', 'showmeshes', 'showevents', 'showsmbios', 'showpower', 'clearpower', 'showiplocations', 'help', 'exactports', 'xinstall', 'xuninstall', 'install', 'uninstall', 'start', 'stop', 'restart', 'debug', 'filespath', 'datapath', 'noagentupdate', 'launch', 'noserverbackup', 'mongodb', 'mongodbcol', 'wanonly', 'lanonly', 'nousers', 'mpspass', 'ciralocalfqdn', 'dbexport', 'dbexportmin', 'dbimport', 'dbmerge', 'dbencryptkey', 'selfupdate', 'tlsoffload', 'userallowedip', 'userblockedip', 'swarmallowedip', 'agentallowedip', 'agentblockedip', 'fastcert', 'swarmport', 'logintoken', 'logintokenkey', 'logintokengen', 'logintokengen', 'mailtokengen', 'admin', 'unadmin', 'sessionkey', 'sessiontime', 'minify', 'minifycore', 'dblistconfigfiles', 'dbshowconfigfile', 'dbpushconfigfiles', 'dbpullconfigfiles', 'dbdeleteconfigfiles', 'vaultpushconfigfiles', 'vaultpullconfigfiles', 'vaultdeleteconfigfiles', 'configkey', 'loadconfigfromdb', 'npmpath', 'memorytracking', 'serverid', 'recordencryptionrecode', 'vault', 'token', 'unsealkey', 'name', 'log', 'dbstats'];
|
||||
var validArguments = ['_', 'notls', 'user', 'port', 'aliasport', 'mpsport', 'mpsaliasport', 'redirport', 'rediraliasport', 'cert', 'mpscert', 'deletedomain', 'deletedefaultdomain', 'showall', 'showusers', 'showusergroups', 'shownodes', 'showmeshes', 'showevents', 'showsmbios', 'showpower', 'clearpower', 'showiplocations', 'help', 'exactports', 'xinstall', 'xuninstall', 'install', 'uninstall', 'start', 'stop', 'restart', 'debug', 'filespath', 'datapath', 'noagentupdate', 'launch', 'noserverbackup', 'mongodb', 'mongodbcol', 'wanonly', 'lanonly', 'nousers', 'mpspass', 'ciralocalfqdn', 'dbexport', 'dbexportmin', 'dbimport', 'dbmerge', 'dbencryptkey', 'selfupdate', 'tlsoffload', 'userallowedip', 'userblockedip', 'swarmallowedip', 'agentallowedip', 'agentblockedip', 'fastcert', 'swarmport', 'logintoken', 'logintokenkey', 'logintokengen', 'logintokengen', 'mailtokengen', 'admin', 'unadmin', 'sessionkey', 'sessiontime', 'minify', 'minifycore', 'dblistconfigfiles', 'dbshowconfigfile', 'dbpushconfigfiles', 'dbpullconfigfiles', 'dbdeleteconfigfiles', 'vaultpushconfigfiles', 'vaultpullconfigfiles', 'vaultdeleteconfigfiles', 'configkey', 'loadconfigfromdb', 'npmpath', 'memorytracking', 'serverid', 'recordencryptionrecode', 'vault', 'token', 'unsealkey', 'name', 'log', 'dbstats'];
|
||||
for (var arg in obj.args) { obj.args[arg.toLocaleLowerCase()] = obj.args[arg]; if (validArguments.indexOf(arg.toLocaleLowerCase()) == -1) { console.log('Invalid argument "' + arg + '", use --help.'); return; } }
|
||||
if (obj.args.mongodb == true) { console.log('Must specify: --mongodb [connectionstring] \r\nSee https://docs.mongodb.com/manual/reference/connection-string/ for MongoDB connection string.'); return; }
|
||||
for (i in obj.config.settings) { obj.args[i] = obj.config.settings[i]; } // Place all settings into arguments, arguments have already been placed into settings so arguments take precedence.
|
||||
|
@ -437,6 +437,7 @@ function CreateMeshCentralServer(config, args) {
|
|||
if (obj.args.deletedefaultdomain) { obj.db.DeleteDomain('', function () { console.log('Deleted default domain.'); process.exit(); }); return; }
|
||||
if (obj.args.showall) { obj.db.GetAll(function (err, docs) { console.log(docs); process.exit(); }); return; }
|
||||
if (obj.args.showusers) { obj.db.GetAllType('user', function (err, docs) { console.log(docs); process.exit(); }); return; }
|
||||
if (obj.args.showusergroups) { obj.db.GetAllType('ugrp', function (err, docs) { console.log(docs); process.exit(); }); return; }
|
||||
if (obj.args.shownodes) { obj.db.GetAllType('node', function (err, docs) { console.log(docs); process.exit(); }); return; }
|
||||
if (obj.args.showmeshes) { obj.db.GetAllType('mesh', function (err, docs) { console.log(docs); process.exit(); }); return; }
|
||||
if (obj.args.showevents) { obj.db.GetAllEvents(function (err, docs) { console.log(docs); process.exit(); }); return; }
|
||||
|
|
13
meshmail.js
13
meshmail.js
|
@ -139,7 +139,7 @@ module.exports.CreateMeshMail = function (parent) {
|
|||
if ((template == null) || (template.htmlSubject == null) || (template.txtSubject == null) || (parent.certificates == null) || (parent.certificates.CommonName == null) || (parent.certificates.CommonName.indexOf('.') == -1)) return; // If the server name is not set, invitation not possible.
|
||||
|
||||
// Set all the options.
|
||||
var options = { username: username, accountname: accountname, email: email, servername: domain.title, password: password };
|
||||
var options = { username: username, accountname: accountname, email: email, servername: domain.title ? domain.title : 'MeshCentral', password: password };
|
||||
|
||||
// Send the email
|
||||
obj.pendingMails.push({ to: email, from: parent.config.smtp.from, subject: mailReplacements(template.htmlSubject, domain, options), text: mailReplacements(template.txt, domain, options), html: mailReplacements(template.html, domain, options) });
|
||||
|
@ -152,7 +152,7 @@ module.exports.CreateMeshMail = function (parent) {
|
|||
if ((template == null) || (template.htmlSubject == null) || (template.txtSubject == null) || (parent.certificates == null) || (parent.certificates.CommonName == null) || (parent.certificates.CommonName.indexOf('.') == -1)) return; // If the server name is not set, no reset possible.
|
||||
|
||||
// Set all the options.
|
||||
var options = { username: username, email: email, servername: domain.title };
|
||||
var options = { username: username, email: email, servername: domain.title ? domain.title : 'MeshCentral' };
|
||||
options.cookie = obj.parent.encodeCookie({ u: domain.id + '/' + username.toLowerCase(), e: email, a: 1 }, obj.mailCookieEncryptionKey);
|
||||
|
||||
// Send the email
|
||||
|
@ -166,7 +166,7 @@ module.exports.CreateMeshMail = function (parent) {
|
|||
if ((template == null) || (template.htmlSubject == null) || (template.txtSubject == null) || (parent.certificates == null) || (parent.certificates.CommonName == null) || (parent.certificates.CommonName.indexOf('.') == -1)) return; // If the server name is not set, don't validate the email address.
|
||||
|
||||
// Set all the options.
|
||||
var options = { username: username, email: email, servername: domain.title };
|
||||
var options = { username: username, email: email, servername: domain.title ? domain.title : 'MeshCentral' };
|
||||
options.cookie = obj.parent.encodeCookie({ u: domain.id + '/' + username, e: email, a: 2 }, obj.mailCookieEncryptionKey);
|
||||
|
||||
// Send the email
|
||||
|
@ -180,7 +180,7 @@ module.exports.CreateMeshMail = function (parent) {
|
|||
if ((template == null) || (template.htmlSubject == null) || (template.txtSubject == null) || (parent.certificates == null) || (parent.certificates.CommonName == null) || (parent.certificates.CommonName.indexOf('.') == -1)) return; // If the server name is not set, don't validate the email address.
|
||||
|
||||
// Set all the template replacement options and generate the final email text (both in txt and html formats).
|
||||
var options = { username: username, name: name, email: email, installflags: flags, msg: msg, meshid: meshid, meshidhex: meshid.split('/')[2], servername: domain.title };
|
||||
var options = { username: username, name: name, email: email, installflags: flags, msg: msg, meshid: meshid, meshidhex: meshid.split('/')[2], servername: domain.title ? domain.title : 'MeshCentral' };
|
||||
options.windows = ((os == 0) || (os == 1)) ? 1 : 0;
|
||||
options.linux = ((os == 0) || (os == 2)) ? 1 : 0;
|
||||
options.osx = ((os == 0) || (os == 3)) ? 1 : 0;
|
||||
|
@ -220,7 +220,10 @@ module.exports.CreateMeshMail = function (parent) {
|
|||
if (err == null) {
|
||||
console.log('SMTP mail server ' + parent.config.smtp.host + ' working as expected.');
|
||||
} else {
|
||||
console.log('SMTP mail server ' + parent.config.smtp.host + ' failed: ' + JSON.stringify(err));
|
||||
// Remove all non-object types from error to avoid a JSON stringify error.
|
||||
var err2 = {};
|
||||
for (var i in err) { if (typeof (err[i]) != 'object') { err2[i] = err[i]; } }
|
||||
console.log('SMTP mail server ' + parent.config.smtp.host + ' failed: ' + JSON.stringify(err2));
|
||||
}
|
||||
});
|
||||
};
|
||||
|
|
106
meshuser.js
106
meshuser.js
|
@ -1472,16 +1472,114 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use
|
|||
}
|
||||
break;
|
||||
}
|
||||
case 'usergroups':
|
||||
{
|
||||
if ((user.siteadmin & SITERIGHT_USERGROUPS) == 0) { return; }
|
||||
|
||||
// Request a list of all user groups this user as rights to
|
||||
db.GetAllTypeNoTypeField('ugrp', domain.id, function (err, docs) {
|
||||
try { ws.send(JSON.stringify({ action: 'usergroups', ugroups: docs, tag: command.tag })); } catch (ex) { }
|
||||
});
|
||||
break;
|
||||
}
|
||||
case 'createusergroup':
|
||||
{
|
||||
// TODO
|
||||
//console.log(command);
|
||||
var err = null;
|
||||
try {
|
||||
// Check if we have new group restriction
|
||||
if ((user.siteadmin & SITERIGHT_USERGROUPS) == 0) { err = 'Permission denied'; }
|
||||
|
||||
// In some situations, we need a verified email address to create a device group.
|
||||
else if ((parent.parent.mailserver != null) && (domain.auth != 'sspi') && (domain.auth != 'ldap') && (user.emailVerified !== true) && (user.siteadmin != 0xFFFFFFFF)) { err = 'Email verification required'; } // User must verify it's email first.
|
||||
|
||||
// Create user group
|
||||
else if (common.validateString(command.name, 1, 64) == false) { err = 'Invalid group name'; } // User group name is between 1 and 64 characters
|
||||
else if ((command.desc != null) && (common.validateString(command.desc, 0, 1024) == false)) { err = 'Invalid group description'; } // User group description is between 0 and 1024 characters
|
||||
} catch (ex) { err = 'Validation exception: ' + ex; }
|
||||
|
||||
// Handle any errors
|
||||
if (err != null) {
|
||||
if (command.responseid != null) { try { ws.send(JSON.stringify({ action: 'createusergroup', responseid: command.responseid, result: err })); } catch (ex) { } }
|
||||
break;
|
||||
}
|
||||
|
||||
// We only create Agent-less Intel AMT mesh (Type1), or Agent mesh (Type2)
|
||||
parent.crypto.randomBytes(48, function (err, buf) {
|
||||
// Create new device group identifier
|
||||
var ugrpid = 'ugrp/' + domain.id + '/' + buf.toString('base64').replace(/\+/g, '@').replace(/\//g, '$');
|
||||
|
||||
// Create the new device group
|
||||
var ugrp = { type: 'ugrp', _id: ugrpid, name: command.name, desc: command.desc, domain: domain.id, links: {} };
|
||||
db.Set(common.escapeLinksFieldName(ugrp));
|
||||
//parent.meshes[ugrpid] = ugrp;
|
||||
|
||||
// Event the device group creation
|
||||
var event = { etype: 'ugrp', userid: user._id, username: user.name, ugrpid: ugrpid, name: command.name, desc: command.desc, action: 'createusergroup', links: links, msg: 'User group created: ' + command.name, domain: domain.id };
|
||||
parent.parent.DispatchEvent(['*', ugrpid, user._id], obj, event); // Even if DB change stream is active, this event must be acted upon.
|
||||
|
||||
try { ws.send(JSON.stringify({ action: 'createusergroup', responseid: command.responseid, result: 'ok', ugrpid: ugrpid, links: links })); } catch (ex) { }
|
||||
});
|
||||
break;
|
||||
}
|
||||
case 'deleteusergroup':
|
||||
{
|
||||
// TODO
|
||||
//console.log(command);
|
||||
if ((user.siteadmin & SITERIGHT_USERGROUPS) == 0) { return; }
|
||||
|
||||
// Change the name or description of a user group
|
||||
if (common.validateString(command.ugrpid, 1, 1024) == false) break; // Check the user group id
|
||||
var ugroupidsplit = command.ugrpid.split('/');
|
||||
if ((ugroupidsplit.length != 3) || (ugroupidsplit[0] != 'ugrp') || (ugroupidsplit[1] != domain.id)) break;
|
||||
|
||||
db.Get(command.ugrpid, function (err, groups) {
|
||||
if ((err != null) || (groups.length != 1)) return;
|
||||
var group = groups[0];
|
||||
db.Remove(group._id);
|
||||
var event = { etype: 'ugrp', userid: user._id, username: user.name, ugrpid: group._id, action: 'deleteusergroup', msg: change, domain: domain.id };
|
||||
if (db.changeStream) { event.noact = 1; } // If DB change stream is active, don't use this event to change the mesh. Another event will come.
|
||||
parent.parent.DispatchEvent(['*', group._id, user._id], obj, event);
|
||||
});
|
||||
break;
|
||||
}
|
||||
case 'editusergroup':
|
||||
{
|
||||
if ((user.siteadmin & SITERIGHT_USERGROUPS) == 0) { return; }
|
||||
|
||||
// Change the name or description of a user group
|
||||
if (common.validateString(command.ugrpid, 1, 1024) == false) break; // Check the user group id
|
||||
var ugroupidsplit = command.ugrpid.split('/');
|
||||
if ((ugroupidsplit.length != 3) || (ugroupidsplit[0] != 'ugrp') || (ugroupidsplit[1] != domain.id)) break;
|
||||
|
||||
db.Get(command.ugrpid, function (err, groups) {
|
||||
if ((err != null) || (groups.length != 1)) return;
|
||||
var group = groups[0], change = '';
|
||||
|
||||
if ((common.validateString(command.name, 1, 64) == true) && (command.name != group.name)) { change = 'User group name changed from "' + group.name + '" to "' + command.name + '"'; group.name = command.name; }
|
||||
if ((common.validateString(command.desc, 0, 1024) == true) && (command.desc != group.desc)) { if (change != '') change += ' and description changed'; else change += 'User group "' + group.name + '" description changed'; group.desc = command.desc; }
|
||||
if (change != '') {
|
||||
db.Set(common.escapeLinksFieldName(group));
|
||||
var event = { etype: 'ugrp', userid: user._id, username: user.name, ugrpid: group._id, name: group.name, desc: group.desc, action: 'usergroupchange', links: group.links, msg: change, domain: domain.id };
|
||||
if (db.changeStream) { event.noact = 1; } // If DB change stream is active, don't use this event to change the mesh. Another event will come.
|
||||
parent.parent.DispatchEvent(['*', group._id, user._id], obj, event);
|
||||
}
|
||||
});
|
||||
break;
|
||||
}
|
||||
case 'addusertousergroup':
|
||||
{
|
||||
if ((user.siteadmin & SITERIGHT_USERGROUPS) == 0) { return; }
|
||||
|
||||
// Change the name or description of a user group
|
||||
if (common.validateString(command.ugrpid, 1, 1024) == false) break; // Check the user group id
|
||||
var ugroupidsplit = command.ugrpid.split('/');
|
||||
if ((ugroupidsplit.length != 3) || (ugroupidsplit[0] != 'ugrp') || (ugroupidsplit[1] != domain.id)) break;
|
||||
|
||||
db.Get(command.ugrpid, function (err, groups) {
|
||||
if ((err != null) || (groups.length != 1)) return;
|
||||
var group = groups[0];
|
||||
|
||||
// TODO
|
||||
console.log(command);
|
||||
});
|
||||
break;
|
||||
}
|
||||
case 'changemeshnotify':
|
||||
|
|
Binary file not shown.
Before Width: | Height: | Size: 5.7 KiB After Width: | Height: | Size: 6.4 KiB |
Binary file not shown.
After Width: | Height: | Size: 7.8 KiB |
|
@ -16,7 +16,7 @@ function QE(x, y) { try { Q(x).disabled = !y; } catch (x) { } }
|
|||
function QV(x, y) { try { QS(x).display = (y ? '' : 'none'); } catch (x) { } } // "Q" visible
|
||||
function QA(x, y) { Q(x).innerHTML += y; } // "Q" append
|
||||
function QH(x, y) { Q(x).innerHTML = y; } // "Q" html
|
||||
function QC(x) { try { return Q(x).classList; } catch (x) { } } // "Q" class
|
||||
function QC(x) { try { return Q(x).classList; } catch (x) { } } // "Q" class
|
||||
|
||||
// Move cursor to end of input box
|
||||
function inputBoxFocus(x) { Q(x).focus(); var v = Q(x).value; Q(x).value = ''; Q(x).value = v; }
|
||||
|
|
|
@ -1363,6 +1363,14 @@ a {
|
|||
float: left;
|
||||
}
|
||||
|
||||
.m4 {
|
||||
background: url(../images/images16.png) -128px 0px;
|
||||
height: 16px;
|
||||
width: 16px;
|
||||
border: none;
|
||||
float: left;
|
||||
}
|
||||
|
||||
.si1 {
|
||||
background: url(../images/icons16.png) 0px 0px;
|
||||
height: 16px;
|
||||
|
|
|
@ -959,6 +959,29 @@
|
|||
</table>
|
||||
<div id="p50groups"></div>
|
||||
</div>
|
||||
<div id=p51 style="display:none">
|
||||
<table style="width:100%" cellpadding="0" cellspacing="0">
|
||||
<tr>
|
||||
<td style=width:auto valign=top>
|
||||
<div id="p30title">
|
||||
<div style="float:left"><div class="backButton" tabindex=0 onclick=goBack() title="Back" onkeypress="if (event.key == 'Enter') goBack()"><div class="backButtonEx"></div></div></div>
|
||||
<h1>User Group - <span id=p51groupName></span></h1>
|
||||
</div>
|
||||
<div id=p51group></div>
|
||||
</td>
|
||||
<td style=width:20px></td>
|
||||
<td style=width:200px>
|
||||
<picture id=MainUserImage style=border-width:0px;height:200px;width:200px;float:right>
|
||||
<source type="image/webp" width=200 height=200 srcset="images/webp/group-256.webp" />
|
||||
<img alt="" width=200 height=200 src=images/group-256.png />
|
||||
</picture>
|
||||
<div style="width:100%;text-align:center"><strong><span id=MainUserState></span></strong></div>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<div id=p51group2></div>
|
||||
<br />
|
||||
</div>
|
||||
<div id=p19 style="display:none">
|
||||
<h1>Plugins - <span id=p19deviceName></span></h1>
|
||||
<div id="p19headers"></div>
|
||||
|
@ -1101,6 +1124,7 @@
|
|||
var meshes = {};
|
||||
var meshcount = 0;
|
||||
var nodes = null;
|
||||
var usergroups = {};
|
||||
var filetree = {};
|
||||
var userinfo = null;
|
||||
var serverinfo = null;
|
||||
|
@ -1272,7 +1296,7 @@
|
|||
if (pluginHandler != null) pluginHandler.callHook('onWebUIStartupEnd');
|
||||
|
||||
// Deleted non-english style and fix all topbar titles
|
||||
if (Q('MainDevDesktop').innerHTML != 'Desktop') { QC('body').add('nonenglish'); }
|
||||
if (('{{{lang}}}' != 'en') && ('{{{lang}}}' != '')) { QC('body').add('nonenglish'); }
|
||||
var elements = document.getElementsByClassName('topbar_td');
|
||||
for (var i in elements) { if (elements[i].innerHTML) { elements[i].innerHTML = elements[i].innerHTML.split(' ').join(' '); } }
|
||||
}
|
||||
|
@ -1476,6 +1500,7 @@
|
|||
} else if (state == 2) {
|
||||
// Fetch list of meshes, nodes, files
|
||||
meshserver.send({ action: 'meshes' });
|
||||
meshserver.send({ action: 'usergroups' });
|
||||
meshserver.send({ action: 'nodes', id: '{{currentNode}}' });
|
||||
if (pluginHandler != null) { meshserver.send({ action: 'plugins' }); }
|
||||
if ('{{currentNode}}' == '') { meshserver.send({ action: 'files' }); }
|
||||
|
@ -1566,6 +1591,7 @@
|
|||
if (updateNaggleFlags & 1024) { deviceEventsUpdate(); }
|
||||
if (updateNaggleFlags & 2048) { userEventsUpdate(); }
|
||||
if (updateNaggleFlags & 4096) { p20updateMesh(); }
|
||||
if (updateNaggleFlags & 8192) { updateUserGroups(); }
|
||||
updateNaggleTimer = null;
|
||||
updateNaggleFlags = 0;
|
||||
}, 150);
|
||||
|
@ -1694,6 +1720,12 @@
|
|||
masterUpdate(4 + 128);
|
||||
break;
|
||||
}
|
||||
case 'usergroups': {
|
||||
usergroups = {};
|
||||
for (var i in message.ugroups) { usergroups[message.ugroups[i]._id] = message.ugroups[i]; }
|
||||
masterUpdate(8192);
|
||||
break;
|
||||
}
|
||||
case 'files': {
|
||||
filetree = setupBackPointers(message.filetree);
|
||||
updateFiles();
|
||||
|
@ -2182,6 +2214,27 @@
|
|||
updateUsers();
|
||||
break;
|
||||
}
|
||||
case 'createusergroup':
|
||||
case 'usergroupchange': {
|
||||
// User group changed
|
||||
var ugroup = usergroups[message.event.ugrpid];
|
||||
if (ugroup == null) {
|
||||
// This is a new user group for us
|
||||
usergroups[message.event.ugrpid] = { _id: message.event.ugrpid, name: message.event.name, desc: message.event.desc, domain: message.event.domain, links: message.event.links };
|
||||
} else {
|
||||
// This is an existing user group
|
||||
ugroup.name = message.event.name;
|
||||
ugroup.desc = message.event.desc;
|
||||
ugroup.links = message.event.links;
|
||||
}
|
||||
masterUpdate(8192);
|
||||
break;
|
||||
}
|
||||
case 'deleteusergroup': {
|
||||
// User group removed
|
||||
if (usergroups[message.event.ugrpid] != null) { delete usergroups[message.event.ugrpid]; masterUpdate(8192); }
|
||||
break;
|
||||
}
|
||||
case 'createmesh': {
|
||||
// A new mesh was created
|
||||
if ((meshes[message.event.meshid] == null) && (message.event.links[userinfo._id] != null)) { // Check if this is a mesh create for a mesh we own. If site administrator, we get all messages so need to ignore some.
|
||||
|
@ -3720,6 +3773,7 @@
|
|||
if (!skipsave) { putstore('sort', sort); }
|
||||
}
|
||||
|
||||
function nameSort(a, b) { var aa = a.name.toLowerCase(), bb = b.name.toLowerCase(); if (aa > bb) return 1; if (aa < bb) return -1; return 0; }
|
||||
function meshSort(a, b) { if (a.meshnamel > b.meshnamel) return 1; if (a.meshnamel < b.meshnamel) return -1; if (a.meshid == b.meshid) { if (showRealNames == true) { if (a.rnamel > b.rnamel) return 1; if (a.rnamel < b.rnamel) return -1; return 0; } else { if (a.namel > b.namel) return 1; if (a.namel < b.namel) return -1; return 0; } } return 0; }
|
||||
function powerSort(a, b) { var ap = a.pwr?a.pwr:0; var bp = b.pwr?b.pwr:0; if (ap > bp) return -1; if (ap < bp) return 1; if (ap == bp) { if (showRealNames == true) { if (a.rnamel > b.rnamel) return 1; if (a.rnamel < b.rnamel) return -1; return 0; } else { if (a.namel > b.namel) return 1; if (a.namel < b.namel) return -1; return 0; } } return 0; }
|
||||
function deviceSort(a, b) { if (a.namel > b.namel) return 1; if (a.namel < b.namel) return -1; return 0; }
|
||||
|
@ -8509,6 +8563,7 @@
|
|||
x += '<table class=p3eventsTable cellpadding=0 cellspacing=0><tr><td colspan=4 class=DevSt>' + dateHeader + '</td></tr>';
|
||||
}
|
||||
var icon = 'si3';
|
||||
if (event.etype == 'ugrp') icon = 'm4';
|
||||
if (event.etype == 'user') icon = 'm2';
|
||||
if (event.etype == 'server') icon = 'si3';
|
||||
|
||||
|
@ -8978,7 +9033,33 @@
|
|||
//
|
||||
|
||||
function updateUserGroups() {
|
||||
// Sort the list of group names
|
||||
var sortedGroups = [];
|
||||
for (var i in usergroups) { sortedGroups.push(usergroups[i]); }
|
||||
sortedGroups.sort(nameSort);
|
||||
|
||||
// Display the groups using the sorted list
|
||||
var x = '<table class=p3usersTable cellpadding=0 cellspacing=0>', addHeader = true;
|
||||
x += '<th>' + "Name" + '<th style=width:80px>' + "Users";
|
||||
for (var i in sortedGroups) { x += addUserGroupHtml(sortedGroups[i]); }
|
||||
x += '</table>';
|
||||
|
||||
if (sortedGroups.length == 0) { x += '<br />' + "No groups found." + '<br />'; }
|
||||
QH('p50groups', x);
|
||||
|
||||
// Update current user panel if needed
|
||||
if ((currentUserGroup != null) && (xxcurrentView == 51)) { gotoUserGroup(encodeURIComponent(currentUserGroup._id), true); }
|
||||
}
|
||||
|
||||
function addUserGroupHtml(group) {
|
||||
var usercount = 0;
|
||||
if (group.links) { for (var i in group.links) { usercount++; } }
|
||||
var x = '<tr tabindex=0 onmouseover=userMouseHover(this,1) onmouseout=userMouseHover(this,0) onkeypress="if (event.key==\'Enter\') gotoUserGroup(\'' + encodeURIComponent(group._id) + '\')"><td style=cursor:pointer onclick=gotoUserGroup(\"' + encodeURIComponent(group._id) + '\")>';
|
||||
x += '<div class=bar style=width:100%>';
|
||||
x += '<div class=baricon><div class="m4"></div></div>';
|
||||
x += '<div class=g1></div><div class=g2></div>';
|
||||
x += '<div><span style=font-size:16px>' + group.name + '</span></div></div><td style=text-align:center>' + usercount;
|
||||
return x;
|
||||
}
|
||||
|
||||
function showCreateUserGroupDialog() {
|
||||
|
@ -8999,6 +9080,178 @@
|
|||
meshserver.send({ action: 'createusergroup', name: Q('p4name').value, desc: Q('p4desc').value });
|
||||
}
|
||||
|
||||
//
|
||||
// MY USER GROUP
|
||||
//
|
||||
|
||||
var currentUserGroup = null;
|
||||
function gotoUserGroup(groupid, force) {
|
||||
if (xxdialogMode && !force) return;
|
||||
var group = currentUserGroup = usergroups[decodeURIComponent(groupid)];
|
||||
if (group == null) { if (xxcurrentView == 51) { setDialogMode(0); go(50); } return; }
|
||||
QH('p51groupName', group.name);
|
||||
|
||||
var desc = group.desc;
|
||||
if ((desc == null) || (desc == '')) { desc = '<i>' + "None" + '<i>'; } else { desc = EscapeHtml(desc); }
|
||||
|
||||
var x = '<div style=min-height:80px><table style=width:100%>';
|
||||
if ((userinfo.siteadmin & 256) != 0) {
|
||||
x += addDeviceAttribute("Name", '<span onclick=p51editgroup(1) style=cursor:pointer>' + EscapeHtml(group.name) + ' <img class=hoverButton src="images/link5.png" /></span>');
|
||||
x += addDeviceAttribute("Description", '<span onclick=p51editgroup(2) style=cursor:pointer>' + desc + ' <img class=hoverButton src="images/link5.png" /></span>');
|
||||
} else {
|
||||
x += addDeviceAttribute("Name", EscapeHtml(group.name));
|
||||
x += addDeviceAttribute("Description", desc);
|
||||
}
|
||||
x += '</table></div><br />';
|
||||
|
||||
// Setup the panel
|
||||
QH('p51group', x);
|
||||
|
||||
if ((userinfo.siteadmin & 256) != 0) {
|
||||
x = '<a href=# onclick="return p51showAddUserDialog()" style=cursor:pointer;margin-right:10px><img src=images/icon-addnew.png border=0 height=12 width=12> ' + "Add Users" + '</a>';
|
||||
}
|
||||
x += '<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:left;width:430px>' + "Group Members" + '</th><th scope=col style=text-align:left></th></tr>';
|
||||
|
||||
// Sort the users for this mesh
|
||||
var count = 1, sortedusers = [];
|
||||
for (var i in currentUserGroup.links) {
|
||||
var uname = i.split('/')[2];
|
||||
if (currentUserGroup.links[i].name) { uname = currentUserGroup.links[i].name; }
|
||||
if (i == userinfo._id) { uname = userinfo.name; }
|
||||
sortedusers.push({ id: i, name: uname, rights: currentUserGroup.links[i].rights });
|
||||
}
|
||||
sortedusers.sort(function(a, b) { if (a.name > b.name) return 1; if (a.name < b.name) return -1; return 0; });
|
||||
|
||||
// Display all users for this mesh
|
||||
for (var i in sortedusers) {
|
||||
var trash = '<a href=# onclick=\'return p51deleteUser(event,"' + encodeURIComponent(sortedusers[i].id) + '")\' title=\"' + "Remove user rights to this device group" + '\" style=cursor:pointer><img src=images/trash.png border=0 height=10 width=10></a>';
|
||||
x += '<tr tabindex=0 onclick=p20viewuser("' + encodeURIComponent(sortedusers[i].id) + '") onkeypress="if (event.key==\'Enter\') p20viewuser(\'' + encodeURIComponent(sortedusers[i].id) + '\')" style=cursor:pointer' + (((count % 2) == 0) ? ';background-color:#DDD' : '') + '><td><div title=\"' + "User" + '\" class=m2></div><div> ' + EscapeHtml(decodeURIComponent(sortedusers[i].name)) + '<div></div></div></td><td><div style=float:right>' + trash + '</div></td></tr>';
|
||||
++count;
|
||||
}
|
||||
|
||||
if (count == 1) { x += '<tr><td><div style=padding:6px> <i>' + "No Members" + '</i><div></div></div></td><td></td></tr>'; }
|
||||
|
||||
x += '</tbody></table>';
|
||||
if ((userinfo.siteadmin & 256) != 0) {
|
||||
x += '<div style=font-size:x-small;text-align:right><span><a href=# onclick=p51showDeleteUserGroupDialog() style=cursor:pointer>' + "Delete User Group" + '</a></span></div>';
|
||||
}
|
||||
|
||||
QH('p51group2', x);
|
||||
go(51);
|
||||
}
|
||||
|
||||
function p51editgroup(focus) {
|
||||
if (xxdialogMode) return;
|
||||
var x = addHtmlValue("Name", '<input id=dp51name style=width:230px maxlength=32 onchange=p51editgroupValidate() onkeyup=p51editgroupValidate(event) />');
|
||||
x += addHtmlValue("Description", '<div style=width:230px;margin:0;padding:0><textarea id=dp51desc maxlength=1024 style=width:100%;resize:none></textarea></div>');
|
||||
setDialogMode(2, "Edit User Group", 3, p51editgroupEx, x);
|
||||
Q('dp51name').value = currentUserGroup.name;
|
||||
if (currentUserGroup.desc) Q('dp51desc').value = currentUserGroup.desc;
|
||||
p51editgroupValidate();
|
||||
if (focus == 2) { Q('dp51desc').focus(); } else { Q('dp51name').focus(); }
|
||||
}
|
||||
|
||||
function p51editgroupEx() {
|
||||
meshserver.send({ action: 'editusergroup', ugrpid: currentUserGroup._id, name: Q('dp51name').value, desc: Q('dp51desc').value });
|
||||
}
|
||||
|
||||
function p51editgroupValidate(e) {
|
||||
QE('idx_dlgOkButton', (Q('dp51name').value.length > 0) && (Q('dp51name').value.indexOf(' ') == -1));
|
||||
if (e && e.key == 'Enter') { Q('dp51desc').focus(); }
|
||||
}
|
||||
|
||||
function p51showDeleteUserGroupDialog() {
|
||||
if (xxdialogMode) return false;
|
||||
var x = format("Delete user group {0}?", EscapeHtml(currentUserGroup.name)) + '<br /><br />';
|
||||
x += '<label><input id=p51check type=checkbox onchange=p51validateDeleteGroupDialog() />' + "Confirm" + '</label>';
|
||||
setDialogMode(2, "Delete User Group", 3, p51showDeleteUserGroupDialogEx, x);
|
||||
p51validateDeleteGroupDialog();
|
||||
return false;
|
||||
}
|
||||
|
||||
function p51validateDeleteGroupDialog() {
|
||||
QE('idx_dlgOkButton', Q('p51check').checked);
|
||||
}
|
||||
|
||||
function p51showDeleteUserGroupDialogEx(buttons, tag) {
|
||||
meshserver.send({ action: 'deleteusergroup', ugrpid: currentUserGroup._id });
|
||||
}
|
||||
|
||||
function p51deleteUser(e, id) {
|
||||
console.log('p51deleteUser', id);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
function p51showAddUserDialog() {
|
||||
if (xxdialogMode) return false;
|
||||
var x = "Allow users to manage this device group and devices in this group.";
|
||||
if (features & 0x00080000) { x += " Users need to login to this server once before they can be added to a device group." }
|
||||
x += '<br /><br /><div style=\'position:relative\'>';
|
||||
x += addHtmlValue("User Names", '<input id=dp51username style=width:230px maxlength=32 onchange=p51validateAddUserDialog() onkeyup=p51validateAddUserDialog() placeholder="user1, user2, user3" />');
|
||||
x += '<div id=dp51usersuggest class=suggestionBox style=\'top:30px;left:130px;display:none\'></div>';
|
||||
x += '</div><br>';
|
||||
setDialogMode(2, "Add Users to User Group", 3, p51showAddUserDialogEx, x);
|
||||
Q('dp51username').focus();
|
||||
p51validateAddUserDialog();
|
||||
return false;
|
||||
}
|
||||
|
||||
function p51setname(name) {
|
||||
name = decodeURIComponent(name);
|
||||
var xusers = Q('dp51username').value.split(',');
|
||||
for (var i in xusers) { xusers[i] = xusers[i].trim(); }
|
||||
xusers[xusers.length - 1] = name;
|
||||
Q('dp51username').value = xusers.join(', ');
|
||||
p51validateAddUserDialog();
|
||||
return false;
|
||||
}
|
||||
|
||||
function p51validateAddUserDialog() {
|
||||
var meshrights = GetMeshRights(currentMesh);
|
||||
var ok = true;
|
||||
if (Q('dp51username')) {
|
||||
var xusers = Q('dp51username').value.split(',');
|
||||
for (var i in xusers) {
|
||||
var xuser = xusers[i] = xusers[i].trim();
|
||||
if (xuser.length == 0) { ok = false; } else if (xuser.indexOf('"') >= 0) { ok = false; }
|
||||
}
|
||||
|
||||
// Fill the suggestion box
|
||||
var showsuggestbox = false, exactMatch = false;
|
||||
if (users != null) {
|
||||
var lastuser = xusers[xusers.length - 1].trim(), lastuserl = lastuser.toLowerCase(), matchingUsers = [];
|
||||
if (lastuser.length > 0) {
|
||||
for (var i in users) {
|
||||
if (users[i].name === lastuser) { exactMatch = true; break; }
|
||||
if (users[i].name.toLowerCase().indexOf(lastuserl) >= 0) { matchingUsers.push(users[i].name); if (matchingUsers.length >= 8) break; }
|
||||
}
|
||||
if ((exactMatch == false) && (matchingUsers.length > 0)) {
|
||||
var x = '';
|
||||
for (var i in matchingUsers) { x += '<a href=# onclick=\'p51setname("' + encodeURIComponent(matchingUsers[i]) + '")\'>' + matchingUsers[i] + '</a><br />'; }
|
||||
QH('dp51usersuggest', x);
|
||||
showsuggestbox = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
QV('dp51usersuggest', showsuggestbox);
|
||||
}
|
||||
QE('idx_dlgOkButton', ok);
|
||||
}
|
||||
|
||||
function p51showAddUserDialogEx(b, t) {
|
||||
if (t == null) {
|
||||
var users = Q('dp51username').value.split(','), users2 = [];
|
||||
for (var i in users) { users2.push(users[i].trim()); }
|
||||
meshserver.send({ action: 'addusertousergroup', meshid: currentUserGroup._id, usernames: users2 });
|
||||
} else {
|
||||
meshserver.send({ action: 'addusertousergroup', meshid: currentUserGroup._id, usernames: [ t.split('/')[2] ] });
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
//
|
||||
// MY USER GENERAL
|
||||
//
|
||||
|
@ -9050,9 +9303,9 @@
|
|||
|
||||
// Administrative Realms
|
||||
if ((userinfo.siteadmin == 0xFFFFFFFF) || (userinfo.siteadmin & 2)) {
|
||||
var userGroups = '<i>' + "None" + '</i>';
|
||||
if (user.groups) { userGroups = ''; for (var i in user.groups) { userGroups += '<span class="tagSpan">' + user.groups[i] + '</span>'; } }
|
||||
x += addDeviceAttribute("Admin Realms", addLinkConditional(userGroups, 'showUserGroupDialog(event,\"' + userid + '\")', (userinfo.siteadmin == 0xFFFFFFFF) || ((userinfo.groups == null) && (userinfo._id != user._id) && (user.siteadmin != 0xFFFFFFFF))));
|
||||
var xuserGroups = '<i>' + "None" + '</i>';
|
||||
if (user.groups) { xuserGroups = ''; for (var i in user.groups) { xuserGroups += '<span class="tagSpan">' + user.groups[i] + '</span>'; } }
|
||||
x += addDeviceAttribute("Admin Realms", addLinkConditional(xuserGroups, 'showUserGroupDialog(event,\"' + userid + '\")', (userinfo.siteadmin == 0xFFFFFFFF) || ((userinfo.groups == null) && (userinfo._id != user._id) && (user.siteadmin != 0xFFFFFFFF))));
|
||||
}
|
||||
|
||||
var multiFactor = 0;
|
||||
|
@ -9856,11 +10109,11 @@
|
|||
// Set the goback stack, if going to top-level view, clear the stack.
|
||||
if ((xxcurrentView < 0) || (x < 10)) { goBackStack = []; } else {
|
||||
// Do not push into the back stack if we are changing tabs at the same level.
|
||||
if (Math.floor(xxcurrentView / 10) != Math.floor(x / 10)) { goBackStack.push(xxcurrentView); }
|
||||
if ((xxcurrentView == 50) || (Math.floor(xxcurrentView / 10) != Math.floor(x / 10))) { goBackStack.push(xxcurrentView); }
|
||||
}
|
||||
|
||||
// Edit this line when adding a new screen
|
||||
for (var i = 0; i < 51; i++) { QV('p' + i, i == x); }
|
||||
for (var i = 0; i < 52; i++) { QV('p' + i, i == x); }
|
||||
xxcurrentView = x;
|
||||
|
||||
// Remove top bar selection
|
||||
|
@ -9896,7 +10149,7 @@
|
|||
// My Events
|
||||
QC('MainMenuMyEvents').add(mainMenuActiveClass);
|
||||
QC('LeftMenuMyEvents').add(leftMenuActiveClass);
|
||||
} else if (x == 4 || (x >= 30 && x < 40) || x == 50) {
|
||||
} else if (x == 4 || (x >= 30 && x < 40) || (x == 50) || (x == 51)) {
|
||||
// My Users
|
||||
QC('MainMenuMyUsers').add(mainMenuActiveClass);
|
||||
QC('LeftMenuMyUsers').add(leftMenuActiveClass);
|
||||
|
@ -9919,7 +10172,7 @@
|
|||
if ((x == 0) && (webPageFullScreen)) { QC('body').add('arg_hide'); }
|
||||
|
||||
QV('MainSubMenuSpan', (x >= 10) && (x < 20));
|
||||
QV('UserDummyMenuSpan', (x < 10) && (x != 6) && webPageFullScreen);
|
||||
QV('UserDummyMenuSpan', (x == 51) || ((x < 10) && (x != 6) && webPageFullScreen));
|
||||
QV('MeshSubMenuSpan', (x >= 20) && (x < 30));
|
||||
QV('UserSubMenuSpan', (x >= 30) && (x < 40));
|
||||
QV('ServerSubMenuSpan', x == 6 || x == 115 || x == 40 || x == 41 || x == 42 || x == 43);
|
||||
|
|
|
@ -4185,7 +4185,7 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
|
|||
if (fileOptions[acceptLanguages[i]] != null) {
|
||||
// Found a match. If the file no longer exists, default to English.
|
||||
obj.fs.exists(fileOptions[acceptLanguages[i]] + '.handlebars', function (exists) {
|
||||
if (exists) { res.render(fileOptions[acceptLanguages[i]], args); } else { res.render(filename, args); }
|
||||
if (exists) { args.lang = acceptLanguages[i]; res.render(fileOptions[acceptLanguages[i]], args); } else { res.render(filename, args); }
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue