Improved server UI, new Auto-Remote agent feature.

This commit is contained in:
Ylian Saint-Hilaire 2018-09-26 14:58:55 -07:00
parent 067c4233a8
commit ce236712d2
13 changed files with 19154 additions and 29 deletions

21
db.js
View File

@ -121,6 +121,27 @@ module.exports.CreateDB = function (parent) {
obj.getLocalAmtNodes = function (func) { obj.file.find({ type: 'node', host: { $exists: true, $ne: null }, intelamt: { $exists: true } }, func); };
obj.getAmtUuidNode = function (meshid, uuid, func) { obj.file.find({ type: 'node', meshid: meshid, 'intelamt.uuid': uuid }, func); };
// Get the number of records in the database for various types, this is the slow NeDB way. TODO: MongoDB can use group() to do this faster.
obj.getStats = function (func) {
obj.file.count({ type: 'node' }, function (err, nodeCount) {
obj.file.count({ type: 'mesh' }, function (err, meshCount) {
obj.file.count({ type: 'power' }, function (err, powerCount) {
obj.file.count({ type: 'user' }, function (err, userCount) {
obj.file.count({ type: 'ifinfo' }, function (err, nodeInterfaceCount) {
obj.file.count({ type: 'note' }, function (err, noteCount) {
obj.file.count({ type: 'lastconnect' }, function (err, nodeLastConnectCount) {
obj.file.count({ }, function (err, totalCount) {
func({ nodes: nodeCount, meshes: meshCount, powerEvents: powerCount, users: userCount, nodeInterfaces: nodeInterfaceCount, notes: noteCount, connectEvent: nodeLastConnectCount, total: totalCount });
});
});
});
});
});
});
});
});
}
// This is used to rate limit a number of operation per day. Returns a startValue each new days, but you can substract it and save the value in the db.
obj.getValueOfTheDay = function (id, startValue, func) { obj.Get(id, function (err, docs) { var date = new Date(), t = date.toLocaleDateString(); if (docs.length == 1) { var r = docs[0]; if (r.day == t) { func({ _id: id, value: r.value, day: t }); return; } } func({ _id: id, value: startValue, day: t }); }); };

View File

@ -56,10 +56,14 @@ module.exports.CreateMeshAgent = function (parent, db, ws, req, args, domain) {
delete obj.parent.wsagents[obj.dbNodeKey];
obj.parent.parent.ClearConnectivityState(obj.dbMeshKey, obj.dbNodeKey, 1);
}
// Get the current mesh
var mesh = obj.parent.meshes[obj.dbMeshKey];
// Other clean up may be needed here
if (obj.unauth) { delete obj.unauth; }
if (obj.agentUpdate != null) { obj.fs.close(obj.agentUpdate.fd); obj.agentUpdate = null; }
if ((obj.agentInfo) && (obj.agentInfo.capabilities) && (obj.agentInfo.capabilities & 0x20)) { // This is a temporary agent, remote it
if (((obj.agentInfo) && (obj.agentInfo.capabilities) && (obj.agentInfo.capabilities & 0x20)) || ((mesh) && (mesh.flags) && (mesh.flags & 1))) { // This is a temporary agent, remote it
// Delete this node including network interface information and events
obj.db.Remove(obj.dbNodeKey); // Remove node with that id
obj.db.Remove('if' + obj.dbNodeKey); // Remove interface information
@ -597,7 +601,7 @@ module.exports.CreateMeshAgent = function (parent, db, ws, req, args, domain) {
// Check if anything changes
if (command.name && (command.name != device.name)) { change = 1; device.name = command.name; changes.push('name'); }
if (device.agent.core != command.value) { if ((command.value == null) && (device.agent.core != null)) { delete device.agent.core; } else { device.agent.core = command.value; } change = 1; changes.push('agent core'); }
if ((command.caps != null) && (device.agent.core != command.value)) { if ((command.value == null) && (device.agent.core != null)) { delete device.agent.core; } else { device.agent.core = command.value; } change = 1; changes.push('agent core'); }
if ((command.caps != null) && ((device.agent.caps & 0xFFFFFFE7) != (command.caps & 0xFFFFFFE7))) { device.agent.caps = ((device.agent.caps & 24) + (command.caps & 0xFFFFFFE7)); change = 1; changes.push('agent capabilities'); } // Allow Javascript on the agent to change all capabilities except console and javascript support
if ((command.osdesc != null) && (device.osdesc != command.osdesc)) { device.osdesc = command.osdesc; change = 1; changes.push('os desc'); }
if (command.intelamt) {

View File

@ -57,7 +57,7 @@ function CreateMeshCentralServer(config, args) {
obj.currentVer = null;
obj.serverKey = new Buffer(obj.crypto.randomBytes(32), 'binary');
obj.loginCookieEncryptionKey = null;
obj.serverSelfWriteAllowed = false;
obj.serverSelfWriteAllowed = true;
try { obj.currentVer = JSON.parse(obj.fs.readFileSync(obj.path.join(__dirname, 'package.json'), 'utf8')).version; } catch (e) { } // Fetch server version
// Setup the default configuration and files paths
@ -1096,9 +1096,10 @@ function CreateMeshCentralServer(config, args) {
}
var r = 'time=' + Date.now() + '\r\n';
for (var i in meshServerState) { r += (i + '=' + meshServerState[i] + '\r\n'); }
obj.fs.writeFileSync(obj.getConfigFilePath('serverstate.txt'), r); // Try to write the server state, this may fail if we don't have permission.
obj.serverSelfWriteAllowed = true;
} catch (ex) { obj.serverSelfWriteAllowed = false; } // Do nothing since this is not a critical feature.
try {
obj.fs.writeFileSync(obj.getConfigFilePath('serverstate.txt'), r); // Try to write the server state, this may fail if we don't have permission.
} catch (ex) { obj.serverSelfWriteAllowed = false; }
} catch (ex) { } // Do nothing since this is not a critical feature.
};
// Logging funtions

View File

@ -24,6 +24,7 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain) {
obj.common = parent.common;
obj.fs = require('fs');
obj.path = require('path');
obj.serverStatsTimer = null;
// Send a message to the user
//obj.send = function (data) { try { if (typeof data == 'string') { obj.ws.send(new Buffer(data, 'binary')); } else { obj.ws.send(data); } } catch (e) { } }
@ -122,6 +123,19 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain) {
user.subscriptions = obj.parent.subscribe(user._id, ws); // Subscribe to events
obj.ws._socket.setKeepAlive(true, 240000); // Set TCP keep alive
// Send current server statistics
obj.SendServerStats = function () {
obj.db.getStats(function (data) {
var os = require('os');
var stats = { action: 'serverstats', totalmem: os.totalmem(), freemem: os.freemem() };
if (obj.parent.parent.platform != 'win32') { stats.cpuavg = os.loadavg(); } //else { stats.cpuavg = [ 0.2, 0.5, 0.6 ]; }
var serverStats = { "User Accounts": Object.keys(obj.parent.users).length, "Device Groups": Object.keys(obj.parent.meshes).length, "Connected Agents": Object.keys(obj.parent.wsagents).length, "Connected Users": Object.keys(obj.parent.wssessions2).length };
if (obj.parent.parent.mpsserver != null) { serverStats['Connected Intel® AMT'] = Object.keys(obj.parent.parent.mpsserver.ciraConnections).length; }
stats.values = { "Server State": serverStats, "Database": { "Records": data.total, "Users": data.users, "Device Groups": data.meshes, "Devices": data.nodes, "Device NetInfo": data.nodeInterfaces, "Device Power Event": data.powerEvents, "Notes": data.notes, "Connection Records": data.connectEvents } }
try { ws.send(JSON.stringify(stats)); } catch (ex) { }
});
}
// When data is received from the web socket
ws.on('message', function (msg) {
var command, user = obj.parent.users[req.session.userid], i = 0, mesh = null, meshid = null, nodeid = null, meshlinks = null, change = 0;
@ -130,6 +144,20 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain) {
switch (command.action) {
case 'ping': { try { ws.send(JSON.stringify({ action: 'pong' })); } catch (ex) { } break; }
case 'serverstats':
{
if ((user.siteadmin) != 0) {
if (obj.common.validateInt(command.interval, 1000, 1000000) == false) {
// Clear the timer
if (obj.serverStatsTimer != null) { clearInterval(obj.serverStatsTimer); obj.serverStatsTimer = null; }
} else {
// Set the timer
obj.SendServerStats();
obj.serverStatsTimer = setInterval(obj.SendServerStats, command.interval);
}
}
break;
}
case 'meshes':
{
// Request a list of all meshes this user as rights to
@ -683,9 +711,10 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain) {
if (mesh.links[user._id] == null || ((mesh.links[user._id].rights & 1) == 0)) return;
if ((command.meshid.split('/').length != 3) || (command.meshid.split('/')[1] != domain.id)) return; // Invalid domain, operation only valid for current domain
if ((obj.common.validateString(command.meshname, 1, 64) == true) && (command.meshname != mesh.name)) { change = 'Mesh name changed from "' + mesh.name + '" to "' + command.meshname + '"'; mesh.name = command.meshname; }
if ((obj.common.validateString(command.desc, 0, 1024) == true) && (command.desc != mesh.desc)) { if (change != '') change += ' and description changed'; else change += 'Mesh "' + mesh.name + '" description changed'; mesh.desc = command.desc; }
if (change != '') { obj.db.Set(obj.common.escapeLinksFieldName(mesh)); obj.parent.parent.DispatchEvent(['*', mesh._id, user._id], obj, { etype: 'mesh', username: user.name, meshid: mesh._id, name: mesh.name, mtype: mesh.mtype, desc: mesh.desc, action: 'meshchange', links: mesh.links, msg: change, domain: domain.id }); }
if ((obj.common.validateString(command.meshname, 1, 64) == true) && (command.meshname != mesh.name)) { change = 'Group name changed from "' + mesh.name + '" to "' + command.meshname + '"'; mesh.name = command.meshname; }
if ((obj.common.validateString(command.desc, 0, 1024) == true) && (command.desc != mesh.desc)) { if (change != '') change += ' and description changed'; else change += 'Group "' + mesh.name + '" description changed'; mesh.desc = command.desc; }
if ((obj.common.validateInt(command.flags) == true) && (command.flags != mesh.flags)) { if (change != '') change += ' and flags changed'; else change += 'Group "' + mesh.name + '" flags changed'; mesh.flags = command.flags; }
if (change != '') { obj.db.Set(obj.common.escapeLinksFieldName(mesh)); obj.parent.parent.DispatchEvent(['*', mesh._id, user._id], obj, { etype: 'mesh', username: user.name, meshid: mesh._id, name: mesh.name, mtype: mesh.mtype, desc: mesh.desc, flags: mesh.flags, action: 'meshchange', links: mesh.links, msg: change, domain: domain.id }); }
}
break;
}
@ -1180,7 +1209,7 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain) {
if (obj.common.validateString(command.notes, 1) == false) {
obj.db.Remove('nt' + command.id); // Delete the note for this node
} else {
obj.db.Set({ _id: 'nt' + command.id, value: command.notes }); // Set the note for this node
obj.db.Set({ _id: 'nt' + command.id, type: 'note', value: command.notes }); // Set the note for this node
}
}
}
@ -1196,7 +1225,7 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain) {
if (obj.common.validateString(command.notes, 1) == false) {
obj.db.Remove('nt' + command.id); // Delete the note for this node
} else {
obj.db.Set({ _id: 'nt' + command.id, value: command.notes }); // Set the note for this node
obj.db.Set({ _id: 'nt' + command.id, type: 'note', value: command.notes }); // Set the note for this mesh
}
}
} else if ((idtype == 'user') && ((user.siteadmin & 2) != 0)) {
@ -1204,7 +1233,7 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain) {
if (obj.common.validateString(command.notes, 1) == false) {
obj.db.Remove('nt' + command.id); // Delete the note for this node
} else {
obj.db.Set({ _id: 'nt' + command.id, value: command.notes }); // Set the note for this node
obj.db.Set({ _id: 'nt' + command.id, type: 'note', value: command.notes }); // Set the note for this user
}
}
@ -1276,6 +1305,7 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain) {
// If the web socket is closed
ws.on('close', function (req) {
obj.parent.parent.RemoveAllEventDispatch(ws);
if (obj.serverStatsTimer != null) { clearInterval(obj.serverStatsTimer); obj.serverStatsTimer = null; }
if (req.session && req.session.ws && req.session.ws == ws) { delete req.session.ws; }
if (obj.parent.wssessions2[ws.sessionId]) { delete obj.parent.wssessions2[ws.sessionId]; }
if (obj.parent.wssessions[ws.userid]) {

View File

@ -1,6 +1,6 @@
{
"name": "meshcentral",
"version": "0.2.1-c",
"version": "0.2.1-x",
"keywords": [
"Remote Management",
"Intel AMT",

Binary file not shown.

Before

Width:  |  Height:  |  Size: 26 KiB

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 43 KiB

After

Width:  |  Height:  |  Size: 55 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 50 KiB

18923
public/scripts/charts.js Normal file

File diff suppressed because it is too large Load Diff

View File

@ -236,6 +236,14 @@ a {
border: none;
}
.lb6 {
background: url(../images/leftbar-62.png) -360px 0px;
height: 62px;
width: 62px;
cursor: pointer;
border: none;
}
.m0 { background : url(../images/images16.png) -32px 0px; height : 16px; width : 16px; border:none; float:left }
.m1 { background : url(../images/images16.png) -16px 0px; height : 16px; width : 16px; border:none; float:left }
.m2 { background : url(../images/images16.png) -96px 0px; height : 16px; width : 16px; border:none; float:left }

File diff suppressed because one or more lines are too long

View File

@ -23,6 +23,7 @@
<script type="text/javascript" src="scripts/amt-wsman-ws-0.2.0.js"></script>
<script type="text/javascript" src="scripts/agent-redir-ws-0.1.0.js"></script>
<script type="text/javascript" src="scripts/agent-desktop-0.0.2.js"></script>
<script keeplink=1 type="text/javascript" src="scripts/charts.js"></script>
<script keeplink=1 type="text/javascript" src="scripts/filesaver.1.1.20151003.js"></script>
<script keeplink=1 type="text/javascript" src="scripts/ol.js"></script>
<script keeplink=1 type="text/javascript" src="scripts/ol3-contextmenu.js"></script>
@ -79,6 +80,9 @@
<div id=LeftMenuMyUsers class="lbbutton" title="My Users" onclick=go(4)>
<div class="lb5" style="position:absolute;top:6px;left:6px"></div>
</div>
<div id=LeftMenuMyServer class="lbbutton" title="My Server" onclick=go(6) style="display:none">
<div class="lb6" style="position:absolute;top:6px;left:6px"></div>
</div>
</div>
<div id="page_content" style="max-height:calc(100vh - 130px)">
<div id=topbarmaster>
@ -95,6 +99,7 @@
<td id=MainMenuMyEvents style=width:100px;height:24px;cursor:pointer class=style3 onclick=go(3)>My Events</td>
<td id=MainMenuMyFiles style=width:100px;height:24px;cursor:pointer class=style3 onclick=go(5)>My Files</td>
<td id=MainMenuMyUsers style=width:100px;height:24px;cursor:pointer;display:none class=style3 onclick=go(4)>My Users</td>
<td id=MainMenuMyServer style=width:100px;height:24px;cursor:pointer;display:none class=style3 onclick=go(6)>My Server</td>
<td class=style3 style="text-align:right;height:24px">&nbsp;</td>
</tr>
</table>
@ -237,13 +242,6 @@
<a onclick="account_showDeleteAccount()" style="cursor:pointer">Delete account</a><br />
</p>
</div>
<p id="p2ServerActions"><strong>Server actions</strong></p>
<p style="margin-left:40px">
<a id="p2ServerActionsBackup" href="/backup.zip" target="_blank" style="cursor:pointer">Download server backup</a><br />
<a id="p2ServerActionsRestore" onclick="server_showRestoreDlg()" style="cursor:pointer">Restore server with backup</a><br />
<a id="p2ServerActionsVersion" onclick="server_showVersionDlg()" style="cursor:pointer">Check server version</a><br />
<a id="p2ServerActionsErrors" onclick="server_showErrorsDlg()" style="cursor:pointer">Show server error log</a><br />
</p>
<br style=clear:both />
<strong>Device Groups</strong>
( <a onclick=account_createMesh() style=cursor:pointer><img height=12 src="images/icon-addnew.png" width=12 border=0 /> New</a> )
@ -342,6 +340,29 @@
<tr><td class=style6 style="text-align:left;padding:3px">&nbsp;<span id="p5bottomstatus"></span></td></tr>
</table>
</div>
<div id=p6 style=display:none>
<img id=MainMeshImage src="serverpic.ashx" style=border-width:0px;height:200px;width:200px;float:right>
<h1>My Server</h1>
<p id="p2ServerActions"><strong>Server actions</strong></p>
<p style="margin-left:40px">
<a id="p2ServerActionsBackup" href="/backup.zip" target="_blank" style="cursor:pointer">Download server backup</a><br />
<a id="p2ServerActionsRestore" onclick="server_showRestoreDlg()" style="cursor:pointer">Restore server with backup</a><br />
<a id="p2ServerActionsVersion" onclick="server_showVersionDlg()" style="cursor:pointer">Check server version</a><br />
<a id="p2ServerActionsErrors" onclick="server_showErrorsDlg()" style="cursor:pointer">Show server error log</a><br />
</p>
<br /><strong>Server Statistics</strong><br /><br />
<div id="serverStats" style="margin-left:40px">
<div id="serverCpuChartView" style="display:none">
<div style="width:60px;display:inline-block"><canvas id="serverCpuChart" style="width:60px;height:60px"></canvas></div>
<div style="width:160px;display:inline-block" id="serverCpuChartText"></div>
</div>
<div id="serverMemoryChartView" style="display:none">
<div style="width:60px;display:inline-block"><canvas id="serverMemoryChart" style="width:60px;height:60px"></canvas></div>
<div style="width:160px;display:inline-block" id="serverMemoryChartText"></div>
</div><br /><br />
<div id="serverStatsTable"></div>
</div>
</div>
<div id=p10 style="display:none">
<table style="width:100%" cellpadding="0" cellspacing="0">
<tr>
@ -916,6 +937,9 @@
var x = '';
for (var c = 1; c < 27; c++) x += "<option value='" + c + "'>Ctrl-" + String.fromCharCode(64 + c) + " (" + c + ")</option>";
QH('specialkeylist', x);
// Setup server stats panel
setupServerStats();
}
// Toggle the web page to full screen
@ -1016,7 +1040,9 @@
// Update account actions
QV('p2AccountActions', (features & 4) == 0); // Hide Account Actions if in single user mode
QV('p2ServerActions', siteRights & 5);
QV('p2ServerActions', siteRights & 21);
QV('LeftMenuMyServer', siteRights & 21);
QV('MainMenuMyServer', siteRights & 21);
QV('p2ServerActionsBackup', siteRights & 1);
QV('p2ServerActionsRestore', siteRights & 4);
QV('p2ServerActionsVersion', siteRights & 16);
@ -1039,10 +1065,17 @@
}
meshserver.send({ action: 'events', limit: parseInt(p3limitdropdown.value) });
QV('p2deleteall', userinfo.siteadmin == 0xFFFFFFFF);
// If we are site administrator, register to get server statistics
if ((siteRights & 21) != 0) { meshserver.send({ action: 'serverstats', interval: 10000 }); }
}
function onMessage(server, message) {
switch (message.action) {
case 'serverstats': {
updateServerStats(message);
break;
}
case 'serverinfo': {
serverinfo = message.serverinfo;
break;
@ -1116,7 +1149,7 @@
if (node != null) {
node.lastconnect = message.time;
if ((currentNode._id == node._id) && (Q('MainComputerState').innerHTML == '')) {
QH('MainComputerState', '<span style=font-size:12px>Last connected:<br />' + new Date(node.lastconnect).toLocaleDateString() + ', ' + new Date(node.lastconnect).toLocaleTimeString() + '</span>');
QH('MainComputerState', '<span style=font-size:12px>Last seen:<br />' + new Date(node.lastconnect).toLocaleDateString() + ', ' + new Date(node.lastconnect).toLocaleTimeString() + '</span>');
}
}
break;
@ -1289,6 +1322,7 @@
// This is an existing mesh
meshes[message.event.meshid].name = message.event.name;
meshes[message.event.meshid].desc = message.event.desc;
meshes[message.event.meshid].flags = message.event.flags;
meshes[message.event.meshid].links = message.event.links;
// Check if we lost rights to this mesh in this change.
@ -3139,7 +3173,7 @@
if ((connectivity & 1) != 0) { if (powerstate.length > 0) { powerstate += '<br/>'; } powerstate += '<span style=font-size:12px title="Agent connected">Agent connected</span>'; }
if ((connectivity & 2) != 0) { if (powerstate.length > 0) { powerstate += '<br/>'; } powerstate += '<span style=font-size:12px title="Intel&reg; AMT connected">Intel&reg; AMT connected</span>'; }
if ((connectivity & 4) != 0) { if (powerstate.length > 0) { powerstate += '<br/>'; } powerstate += '<span style=font-size:12px title="Intel&reg; AMT detected">Intel&reg; AMT detected</span>'; }
if ((powerstate == '') && node.lastconnect) { powerstate = '<span style=font-size:12px>Last connected:<br />' + new Date(node.lastconnect).toLocaleDateString() + ', ' + new Date(node.lastconnect).toLocaleTimeString() + '</span>'; }
if ((powerstate == '') && node.lastconnect) { powerstate = '<span style=font-size:12px>Last seen:<br />' + new Date(node.lastconnect).toLocaleDateString() + ', ' + new Date(node.lastconnect).toLocaleTimeString() + '</span>'; }
QH('MainComputerState', powerstate);
// Set the node icon
@ -4900,9 +4934,19 @@
var x = '';
x += addHtmlValue('Name', addLinkConditional(EscapeHtml(currentMesh.name), 'p20editmesh(1)', (meshrights & 1) != 0));
x += addHtmlValue('Description', addLinkConditional(((currentMesh.desc && currentMesh.desc != '')?EscapeHtml(currentMesh.desc):'<i>None</i>'), 'p20editmesh(2)', (meshrights & 1) != 0));
x += addHtmlValue('Type', meshtype);
x += addHtmlValue('Identifier', currentMesh._id.split('/')[2]);
// Display features
var meshFeatures = [];
if (currentMesh.flags) { if (currentMesh.flags & 1) { meshFeatures.push('Auto-Remove'); } }
meshFeatures = meshFeatures.join(', ');
if (meshFeatures == '') { meshFeatures = '<i>None</i>'; }
x += addHtmlValue('Features', addLinkConditional(meshFeatures, 'p20editmeshfeatures()', (meshrights & 1) != 0));
// Display group type
x += addHtmlValue('Type', meshtype);
//x += addHtmlValue('Identifier', currentMesh._id.split('/')[2]);
// Display group note support
x += '<br><input type=button value=Notes title="View notes about this device group" onclick=showNotes(false,"' + encodeURIComponent(currentMesh._id) + '") />';
x += '<br style=clear:both><br>';
@ -4996,6 +5040,19 @@
QE('idx_dlgOkButton', Q('dp20meshname').value.length > 0);
}
function p20editmeshfeatures() {
if (xxdialogMode) return;
var flags = (currentMesh.flags)?currentMesh.flags:0;
var x = "<div><input type=checkbox id=d20flag1 " + ((flags & 1)?'checked':'') + ">Remove device on disconnect<br></div>";
setDialogMode(2, "Edit Device Group Features", 3, p20editmeshfeaturesEx, x);
}
function p20editmeshfeaturesEx() {
var flags = 0;
if (Q('d20flag1').checked) { flags += 1; }
meshserver.send({ action: 'editmesh', meshid: currentMesh._id, flags: flags });
}
function p20showAddMeshUserDialog() {
if (xxdialogMode) return;
var x = "Allow a user to manage this device group and devices in this group<br /><br />";
@ -5171,6 +5228,14 @@
return Math.round(bytes / 1024 / 1024 / 1024) + ' gigabytes remaining';
}
function getNiceSize2(bytes) {
if (bytes <= 0) return 'None';
if (bytes < 2048) return bytes + ' b';
if (bytes < 2097152) return Math.round(bytes / 1024) + ' Kb';
if (bytes < 2147483648) return Math.round(bytes / 1024 / 1024) + ' Mb';
return Math.round(bytes / 1024 / 1024 / 1024) + ' Gb';
}
function p5getQuotabar(f) {
while (f.t > 1 && f.t != 4) { f = f.parent; }
if ((f.t != 1 && f.t != 4) || (f.maxbytes == null)) return '';
@ -5950,6 +6015,60 @@
QV('notifiyBox', false);
}
//
// Server Statistics
//
function setupServerStats() {
window.serverStatCpu = new Chart(document.getElementById('serverCpuChart').getContext('2d'), {
type: 'doughnut',
data: { datasets: [{ data: [0, 0], backgroundColor: ['#AAAAAA', '#00AA00'] }], labels: ['Used', 'Free'] },
options: { responsive: true, legend: { position: 'none', }, animation: { animateScale: true, animateRotate: true }, width: '60px' }
});
window.serverStatMemory = new Chart(document.getElementById('serverMemoryChart').getContext('2d'), {
type: 'doughnut',
data: { datasets: [{ data: [0, 0], backgroundColor: ['#AAAAAA', '#00AA00'] }], labels: ['Used', 'Free'] },
options: { responsive: true, legend: { position: 'none', }, animation: { animateScale: true, animateRotate: true }, width: '60px' }
});
}
var lastServerStats = null;
function updateServerStats(message) {
if (message != null) { lastServerStats = message; } else { message = lastServerStats; }
if (message == null) return;
// Paint the pie graphs
if (typeof message.cpuavg == 'object') {
var m = Math.min(message.cpuavg[0], 1);
window.serverStatCpu.config.data.datasets[0].data = [m, 1 - m];
QH('serverCpuChartText', '<div style=margin-bottom:5px>CPU Load</div><div><b title="CPU load in the last minute">' + message.cpuavg[0] + '</b>%, <b title="CPU load in the last 5 minutes">' + message.cpuavg[1] + '</b>%, <b title="CPU load in the 15 minutes">' + message.cpuavg[2] + '</b>%</div>');
QS('serverCpuChartView')['display'] = 'inline-block';
window.serverStatCpu.update();
}
if ((typeof message.totalmem == 'number') && (typeof message.freemem == 'number')) {
window.serverStatMemory.config.data.datasets[0].data = [message.totalmem - message.freemem, message.freemem];
QH('serverMemoryChartText', '<div style=margin-bottom:5px>Memory</div><div><b>' + getNiceSize2(message.freemem) + '</b> free, <b>' + getNiceSize2(message.totalmem) + '</b> total</div>');
QS('serverMemoryChartView')['display'] = 'inline-block';
window.serverStatMemory.update();
}
// Display all of the server values
var x = '<div style=width:100% cellpadding=0 cellspacing=0>';
if (typeof message.values == 'object') {
for (var i in message.values) {
x += '<div class=userTableHeader style=margin-bottom:4px;width:200px>' + i + '</div>';
for (var j in message.values[i]) {
x += '<div style=width:300px;display:inline-block><div class=bar style=height:24px;width:100%;font-size:medium>';
x += '<div class=g1 style=height:24px;float:left></div><div class=g2 style=height:24px;float:right></div>';
x += '<div><span>' + j + '</span><span style=float:right>' + message.values[i][j] + '</span></div></div></div>';
}
}
}
x += '</div>';
QH('serverStatsTable', x);
}
//
// POPUP DIALOG
//
@ -5998,7 +6117,7 @@
xxcurrentView = x;
// Remove left bar selection
var leftBarItems = ['LeftMenuMyDevices', 'LeftMenuMyAccount', 'LeftMenuMyEvents', 'LeftMenuMyFiles', 'LeftMenuMyUsers'];
var leftBarItems = ['LeftMenuMyDevices', 'LeftMenuMyAccount', 'LeftMenuMyEvents', 'LeftMenuMyFiles', 'LeftMenuMyUsers', 'LeftMenuMyServer'];
for (var i in leftBarItems) { Q(leftBarItems[i]).classList.remove('lbbuttonsel'); Q(leftBarItems[i]).classList.remove('lbbuttonsel2'); }
// My Devices
@ -6025,6 +6144,10 @@
QS('MainMenuMyFiles').backgroundColor = ((x == 5) ? "#003366" : "#808080");
if (x == 5) { Q('LeftMenuMyFiles').classList.add('lbbuttonsel', 'lbbuttonsel2'); }
// My Server
QS('MainMenuMyServer').backgroundColor = ((x == 6) ? "#003366" : "#808080");
if (x == 6) { Q('LeftMenuMyServer').classList.add('lbbuttonsel', 'lbbuttonsel2'); }
// column_l max-height
if (webPageFullScreen) {
QS('column_l')["max-height"] = 'calc(100vh - 135px)';
@ -6059,7 +6182,8 @@
function joinPaths() { var x = []; for (var i in arguments) { var w = arguments[i]; if ((w != null) && (w != '')) { while (w.endsWith('/') || w.endsWith('\\')) { w = w.substring(0, w.length - 1); } while (w.startsWith('/') || w.startsWith('\\')) { w = w.substring(1); } x.push(w); } } return x.join('/'); }
function putstore(name, val) { try { if (typeof (localStorage) === 'undefined') return; localStorage.setItem(name, val); } catch (e) { } }
function getstore(name, val) { try { if (typeof (localStorage) === 'undefined') return val; var v = localStorage.getItem(name); if ((v == null) || (v == null)) return val; return v; } catch (e) { return val; } }
function addLink(x, f) { return "<a style=cursor:pointer;color:darkblue;text-decoration:none onclick='" + f + "'>&diams; " + x + "</a>"; }
//function addLink(x, f) { return "<a style=cursor:pointer;color:darkblue;text-decoration:none onclick='" + f + "'>&diams; " + x + "</a>"; }
function addLink(x, f) { return "<span style=cursor:pointer;text-decoration:none onclick='" + f + "'>" + x + " <img class=hoverButton src=images/link5.png></span>"; }
function addLinkConditional(x, f, c) { if (c) return addLink(x, f); return x; }
function haltEvent(e) { if (e.preventDefault) e.preventDefault(); if (e.stopPropagation) e.stopPropagation(); return false; }
function addOption(q, t, i) { var option = document.createElement("option"); option.text = t; option.value = i; Q(q).add(option); }

View File

@ -717,6 +717,7 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
// Give the web page a list of supported server features
features = 0;
user = obj.users[req.session.userid];
if (obj.args.wanonly == true) { features += 1; } // WAN-only mode
if (obj.args.lanonly == true) { features += 2; } // LAN-only mode
if (obj.args.nousers == true) { features += 4; } // Single user mode
@ -728,7 +729,7 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
if (obj.args.clickonce !== false) { features += 256; } // Enable ClickOnce (Default true)
if (obj.args.allowhighqualitydesktop == true) { features += 512; } // Enable AllowHighQualityDesktop (Default false)
if (obj.args.lanonly == true || obj.args.mpsport == 0) { features += 1024; } // No CIRA
if ((obj.serverSelfWriteAllowed == true) && (user.siteadmin == 0xFFFFFFFF)) { features += 2048; } // Server can self-write (Allows self-update)
if ((obj.parent.serverSelfWriteAllowed == true) && (user != null) && (user.siteadmin == 0xFFFFFFFF)) { features += 2048; } // Server can self-write (Allows self-update)
// Send the master web application
if ((!obj.args.user) && (obj.args.nousers != true) && (nologout == false)) { logoutcontrol += ' <a href=' + domain.url + 'logout?' + Math.random() + ' style=color:white>Logout</a>'; } // If a default user is in use or no user mode, don't display the logout button
@ -1813,6 +1814,19 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
obj.app.ws(url + 'echo.ashx', handleEchoWebSocket);
obj.app.ws(url + 'meshrelay.ashx', function (ws, req) { try { obj.meshRelayHandler.CreateMeshRelay(obj, ws, req, getDomain(req)); } catch (e) { console.log(e); } });
// Server picture
obj.app.get(url + 'serverpic.ashx', function (req, res) {
// Check if we have "server.png" in the data folder, if so, use that.
var p = obj.path.join(obj.parent.datapath, 'server.png');
if (obj.fs.existsSync(p)) {
// Use the data folder server picture
try { res.sendFile(p); } catch (e) { res.sendStatus(404); }
} else {
// Use the default server picture
try { res.sendFile(obj.path.join(__dirname, 'public/images/server-200.png')); } catch (e) { res.sendStatus(404); }
}
});
// Receive mesh agent connections
obj.app.ws(url + 'agent.ashx', function (ws, req) {
//console.log(++obj.agentConnCount);