Added recodings manager and recording improvements.

This commit is contained in:
Ylian Saint-Hilaire 2020-05-05 13:00:51 -07:00
parent 69736ca4da
commit efc378be6b
7 changed files with 410 additions and 126 deletions

View File

@ -86,8 +86,10 @@ function CreateDesktopMultiplexor(parent, domain, nodeid, func) {
obj.protocolOptions = null; // Set to the protocol options of the first viewer that connected.
obj.viewerConnected = false; // Set to true if one viewer attempted to connect to the agent.
obj.recordingFile = null; // Present if we are recording to file.
obj.recordingFileSize = 0; // Current size of the recording file.
obj.recordingFileWriting = false; // Set to true is we are in the process if writing to the recording file.
obj.startTime = null; // Starting time of the multiplex session.
obj.userIds = []; // List of userid's that have intertracted with this session.
// Add an agent or viewer
obj.addPeer = function (peer) {
@ -108,6 +110,9 @@ function CreateDesktopMultiplexor(parent, domain, nodeid, func) {
peer.sendQueue = [];
peer.paused = false;
// Add the user to the userids list if needed
if ((peer.user != null) && (obj.userIds.indexOf(peer.user._id) == -1)) { obj.userIds.push(peer.user._id); }
// Setup slow relay is requested. This will show down sending any data to this viewer.
if ((peer.req.query.slowrelay != null)) {
var sr = null;
@ -125,7 +130,7 @@ function CreateDesktopMultiplexor(parent, domain, nodeid, func) {
// Log joining the multiplex session
if (obj.startTime != null) {
var event = { etype: 'relay', action: 'relaylog', domain: domain.id, nodeid: obj.nodeid, userid: peer.user._id, username: peer.user.name, msg: "Joined desktop multiplex session", protocol: 2 };
parent.parent.DispatchEvent(['*', obj.nodeid, peer.user._id], obj, event); // TODO: Add Node MeshID to targets
parent.parent.DispatchEvent(['*', obj.nodeid, peer.user._id, obj.meshid], obj, event);
}
} else {
//console.log('addPeer-agent', obj.nodeid);
@ -149,7 +154,7 @@ function CreateDesktopMultiplexor(parent, domain, nodeid, func) {
// Log multiplex session start
if ((obj.agent != null) && (obj.viewers.length > 0) && (obj.startTime == null)) {
var event = { etype: 'relay', action: 'relaylog', domain: domain.id, nodeid: obj.nodeid, userid: obj.viewers[0].user._id, username: obj.viewers[0].user.name, msg: "Started desktop multiplex session", protocol: 2 };
parent.parent.DispatchEvent(['*', obj.nodeid, obj.viewers[0].user._id], obj, event); // TODO: Add Node MeshID to targets
parent.parent.DispatchEvent(['*', obj.nodeid, obj.viewers[0].user._id, obj.meshid], obj, event);
obj.startTime = Date.now();
}
return true;
@ -195,7 +200,7 @@ function CreateDesktopMultiplexor(parent, domain, nodeid, func) {
// Log leaving the multiplex session
if (obj.startTime != null) {
var event = { etype: 'relay', action: 'relaylog', domain: domain.id, nodeid: obj.nodeid, userid: peer.user._id, username: peer.user.name, msg: "Left the desktop multiplex session", protocol: 2 };
parent.parent.DispatchEvent(['*', obj.nodeid, peer.user._id], obj, event); // TODO: Add Node MeshID to targets
parent.parent.DispatchEvent(['*', obj.nodeid, peer.user._id, obj.meshid], obj, event);
}
// If this is the last viewer, disconnect the agent
@ -214,12 +219,26 @@ function CreateDesktopMultiplexor(parent, domain, nodeid, func) {
// Close the recording file if needed
if (obj.recordingFile != null) {
// Compute session length
if (obj.startTime != null) { obj.sessionStart = obj.startTime; obj.sessionLength = Math.round((Date.now() - obj.startTime) / 1000); }
// Write the last record of the recording file
var rf = obj.recordingFile;
delete obj.recordingFile;
recordingEntry(rf.fd, 3, 0, 'MeshCentralMCREC', function (fd, filename) {
parent.parent.fs.close(fd);
// Now that the recording file is closed, check if we need to index this file.
if (domain.sessionrecording.index !== false) { parent.parent.certificateOperations.acceleratorPerformOperation('indexMcRec', filename); }
// Add a event entry about this recording
var basefile = parent.parent.path.basename(filename);
var event = { etype: 'relay', action: 'recording', domain: domain.id, nodeid: obj.nodeid, msg: "Finished recording session" + (obj.sessionLength ? (', ' + obj.sessionLength + ' second(s)') : ''), filename: basefile, size: obj.recordingFileSize, protocol: 2, icon: obj.icon, name: obj.name, meshid: obj.meshid, userids: obj.userIds, multiplex: true };
var mesh = parent.meshes[obj.meshid];
if (mesh != null) { event.meshname = mesh.name; }
if (obj.sessionStart) { event.startTime = obj.sessionStart; event.lengthTime = obj.sessionLength; }
parent.parent.DispatchEvent(['*', 'recording', obj.nodeid, obj.meshid], obj, event);
cleanUpRecordings();
}, rf.filename);
}
@ -227,7 +246,7 @@ function CreateDesktopMultiplexor(parent, domain, nodeid, func) {
// Log end of multiplex session
if (obj.startTime != null) {
var event = { etype: 'relay', action: 'relaylog', domain: domain.id, nodeid: obj.nodeid, msg: "Closed desktop multiplex session" + ', ' + Math.floor((Date.now() - obj.startTime) / 1000) + ' second(s)', protocol: 2 };
parent.parent.DispatchEvent(['*', obj.nodeid], obj, event); // TODO: Add Node MeshID to targets
parent.parent.DispatchEvent(['*', obj.nodeid, obj.meshid], obj, event);
obj.startTime = null;
}
@ -647,7 +666,7 @@ function CreateDesktopMultiplexor(parent, domain, nodeid, func) {
}
// Write the recording file header
parent.parent.debug('relay', 'Relay: Started recoding to file: ' + recFullFilename);
var metadata = { magic: 'MeshCentralRelaySession', ver: 1, nodeid: obj.nodeid, time: new Date().toLocaleString(), protocol: 2 };
var metadata = { magic: 'MeshCentralRelaySession', ver: 1, nodeid: obj.nodeid, meshid: obj.meshid, time: new Date().toLocaleString(), protocol: 2, devicename: obj.name, devicegroup: obj.meshname };
var firstBlock = JSON.stringify(metadata);
recordingEntry(fd, 1, 0, firstBlock, function () {
obj.recordingFile = { fd: fd, filename: recFullFilename };
@ -684,6 +703,7 @@ function CreateDesktopMultiplexor(parent, domain, nodeid, func) {
header.writeIntBE(new Date(), 10, 6); // Time
var block = Buffer.concat([header, blockData]);
parent.parent.fs.write(fd, block, 0, block.length, function () { func(fd, tag); });
obj.recordingFileSize += block.length;
} else {
// Binary write
var header = Buffer.alloc(16); // Header: Type (2) + Flags (2) + Size(4) + Time(8)
@ -693,6 +713,7 @@ function CreateDesktopMultiplexor(parent, domain, nodeid, func) {
header.writeIntBE(new Date(), 10, 6); // Time
var block = Buffer.concat([header, data]);
parent.parent.fs.write(fd, block, 0, block.length, function () { func(fd, tag); });
obj.recordingFileSize += block.length;
}
} catch (ex) { console.log(ex); func(fd, tag); }
}
@ -725,7 +746,14 @@ function CreateDesktopMultiplexor(parent, domain, nodeid, func) {
}
}
recordingSetup(domain, function () { func(obj); });
// Get node information
parent.db.Get(nodeid, function (err, nodes) {
if ((err != null) || (nodes.length != 1)) { func(null); }
obj.meshid = nodes[0].meshid;
obj.icon = nodes[0].icon;
obj.name = nodes[0].name;
recordingSetup(domain, function () { func(obj); });
});
return obj;
}
@ -784,7 +812,7 @@ module.exports.CreateMeshRelay = function (parent, ws, req, domain, user, cookie
if ((arg == 1) || (arg == null)) { try { ws.close(); parent.parent.debug('relay', 'DesktopRelay: Soft disconnect (' + cleanRemoteAddr(obj.req.ip) + ')'); } catch (e) { console.log(e); } } // Soft close, close the websocket
if (arg == 2) { try { ws._socket._parent.end(); parent.parent.debug('relay', 'DesktopRelay: Hard disconnect (' + cleanRemoteAddr(obj.req.ip) + ')'); } catch (e) { console.log(e); } } // Hard close, close the TCP socket
if (obj.relaySessionCounted) { parent.relaySessionCount--; delete obj.relaySessionCounted; }
if (obj.deskDecoder != null) { if (obj.deskDecoder.removePeer(obj) == true) { delete parent.desktoprelays[obj.nodeid]; } }
if (obj.deskMultiplexor != null) { if (obj.deskMultiplexor.removePeer(obj) == true) { delete parent.desktoprelays[obj.nodeid]; } }
// Aggressive cleanup
delete obj.id;
@ -793,7 +821,7 @@ module.exports.CreateMeshRelay = function (parent, ws, req, domain, user, cookie
delete obj.user;
delete obj.nodeid;
delete obj.ruserid;
delete obj.deskDecoder;
delete obj.deskMultiplexor;
// Clear timers if present
if (obj.pingtimer != null) { clearInterval(obj.pingtimer); delete obj.pingtimer; }
@ -885,23 +913,30 @@ module.exports.CreateMeshRelay = function (parent, ws, req, domain, user, cookie
}
// Create if needed and add this peer to the desktop multiplexor
obj.deskDecoder = parent.desktoprelays[obj.nodeid];
if (obj.deskDecoder == null) {
obj.deskMultiplexor = parent.desktoprelays[obj.nodeid];
if (obj.deskMultiplexor == null) {
parent.desktoprelays[obj.nodeid] = 1; // Indicate that the creating of the desktop multiplexor is pending.
parent.parent.debug('relay', 'DesktopRelay: Creating new desktop multiplexor');
CreateDesktopMultiplexor(parent, domain, obj.nodeid, function (deskDecoder) {
obj.deskDecoder = deskDecoder;
parent.desktoprelays[obj.nodeid] = obj.deskDecoder;
obj.deskDecoder.addPeer(obj);
ws._socket.resume(); // Release the traffic
CreateDesktopMultiplexor(parent, domain, obj.nodeid, function (deskMultiplexor) {
if (deskMultiplexor != null) {
// Desktop multiplexor was created, use it.
obj.deskMultiplexor = deskMultiplexor;
parent.desktoprelays[obj.nodeid] = obj.deskMultiplexor;
obj.deskMultiplexor.addPeer(obj);
ws._socket.resume(); // Release the traffic
} else {
// An error has occured, close this connection
delete parent.desktoprelays[obj.nodeid];
ws.close();
}
});
} else {
if (obj.deskDecoder == 1) {
if (obj.deskMultiplexor == 1) {
// The multiplexor is being created, hold a little and try again. This is to prevent a possible race condition.
setTimeout(function () { performRelay(++retryCount); }, 50);
} else {
// Hook up this peer to the multiplexor and release the traffic
obj.deskDecoder.addPeer(obj);
obj.deskMultiplexor.addPeer(obj);
ws._socket.resume();
}
}
@ -910,7 +945,7 @@ module.exports.CreateMeshRelay = function (parent, ws, req, domain, user, cookie
// When data is received from the mesh relay web socket
ws.on('message', function (data) {
// If this data was received by the agent, decode it.
if (this.me.deskDecoder != null) { this.me.deskDecoder.processData(this.me, data); }
if (this.me.deskMultiplexor != null) { this.me.deskMultiplexor.processData(this.me, data); }
});
// If error, close both sides of the relay.

View File

@ -222,8 +222,8 @@ module.exports.CreateMeshRelay = function (parent, ws, req, domain, user, cookie
if ((sessionUser != null) && (domain.sessionrecording == true || ((typeof domain.sessionrecording == 'object') && ((domain.sessionrecording.protocols == null) || (domain.sessionrecording.protocols.indexOf(parseInt(obj.req.query.p)) >= 0))))) {
// Get the computer name
parent.db.Get(obj.req.query.nodeid, function (err, nodes) {
var xusername = '', xdevicename = '', xdevicename2 = null;
if ((nodes != null) && (nodes.length == 1)) { xdevicename2 = nodes[0].name; xdevicename = '-' + parent.common.makeFilename(nodes[0].name); }
var xusername = '', xdevicename = '', xdevicename2 = null, node = null;
if ((nodes != null) && (nodes.length == 1)) { node = nodes[0]; xdevicename2 = node.name; xdevicename = '-' + parent.common.makeFilename(node.name); }
// Get the username and make it acceptable as a filename
if (sessionUser._id) { xusername = '-' + parent.common.makeFilename(sessionUser._id.split('/')[2]); }
@ -250,8 +250,9 @@ module.exports.CreateMeshRelay = function (parent, ws, req, domain, user, cookie
var metadata = { magic: 'MeshCentralRelaySession', ver: 1, userid: sessionUser._id, username: sessionUser.name, sessionid: obj.id, ipaddr1: cleanRemoteAddr(obj.req.ip), ipaddr2: cleanRemoteAddr(obj.peer.req.ip), time: new Date().toLocaleString(), protocol: (((obj.req == null) || (obj.req.query == null)) ? null : obj.req.query.p), nodeid: (((obj.req == null) || (obj.req.query == null)) ? null : obj.req.query.nodeid ) };
if (xdevicename2 != null) { metadata.devicename = xdevicename2; }
var firstBlock = JSON.stringify(metadata);
recordingEntry(fd, 1, 0, firstBlock, function () {
try { relayinfo.peer1.ws.logfile = ws.logfile = { fd: fd, lock: false, filename: recFullFilename }; } catch (ex) {
var logfile = { fd: fd, lock: false, filename: recFullFilename, startTime: Date.now(), size: 0, nodeid: node._id, meshid: node.meshid, name: node.name, icon: node.icon };
recordingEntry(logfile, 1, 0, firstBlock, function () {
try { relayinfo.peer1.ws.logfile = ws.logfile = logfile; } catch (ex) {
try { ws.send('c'); } catch (ex) { } // Send connect to both peers, 'cr' indicates the session is being recorded.
try { relayinfo.peer1.ws.send('c'); } catch (ex) { }
return;
@ -323,7 +324,7 @@ module.exports.CreateMeshRelay = function (parent, ws, req, domain, user, cookie
if (this.logfile != null) {
// Write data to log file then perform relay
var xthis = this;
recordingEntry(this.logfile.fd, 2, ((obj.req.query.browser) ? 2 : 0), data, function () { xthis.peer.send(data, ws.flushSink); });
recordingEntry(this.logfile, 2, ((obj.req.query.browser) ? 2 : 0), data, function () { xthis.peer.send(data, ws.flushSink); });
} else {
// Perform relay
this.peer.send(data, ws.flushSink);
@ -335,7 +336,7 @@ module.exports.CreateMeshRelay = function (parent, ws, req, domain, user, cookie
if (this.logfile != null) {
// Write data to log file then perform slow relay
var xthis = this;
recordingEntry(this.logfile.fd, 2, ((obj.req.query.browser) ? 2 : 0), data, function () {
recordingEntry(this.logfile, 2, ((obj.req.query.browser) ? 2 : 0), data, function () {
setTimeout(function () { xthis.peer.send(data, ws.flushSink); }, xthis.peer.slowRelay);
});
} else {
@ -406,10 +407,29 @@ module.exports.CreateMeshRelay = function (parent, ws, req, domain, user, cookie
var logfile = ws.logfile;
delete ws.logfile;
if (peer.ws) { delete peer.ws.logfile; }
recordingEntry(logfile.fd, 3, 0, 'MeshCentralMCREC', function (fd, tag) {
parent.parent.fs.close(fd);
recordingEntry(logfile, 3, 0, 'MeshCentralMCREC', function (logfile, tag) {
parent.parent.fs.close(logfile.fd);
// Now that the recording file is closed, check if we need to index this file.
if (domain.sessionrecording.index !== false) { parent.parent.certificateOperations.acceleratorPerformOperation('indexMcRec', tag.logfile.filename); }
// Compute session length
var sessionLength = null;
if (tag.logfile.startTime != null) { sessionLength = Math.round((Date.now() - tag.logfile.startTime) / 1000); }
// Add a event entry about this recording
var basefile = parent.parent.path.basename(tag.logfile.filename);
var event = { etype: 'relay', action: 'recording', domain: domain.id, nodeid: tag.logfile.nodeid, msg: "Finished recording session" + (sessionLength ? (', ' + sessionLength + ' second(s)') : ''), filename: basefile, size: tag.logfile.size };
if (user) { event.userids = [user._id]; } else if (peer.user) { event.userids = [peer.user._id]; }
var xprotocol = (((obj.req == null) || (obj.req.query == null)) ? null : obj.req.query.p);
if (xprotocol != null) { event.protocol = parseInt(xprotocol); }
var mesh = parent.meshes[tag.logfile.meshid];
if (mesh != null) { event.meshname = mesh.name; event.meshid = mesh._id; }
if (tag.logfile.startTime) { event.startTime = tag.logfile.startTime; event.lengthTime = sessionLength; }
if (tag.logfile.name) { event.name = tag.logfile.name; }
if (tag.logfile.icon) { event.icon = tag.logfile.icon; }
parent.parent.DispatchEvent(['*', 'recording', obj.nodeid, obj.meshid], obj, event);
cleanUpRecordings();
}, { ws: ws, pws: peer.ws, logfile: logfile });
}
@ -428,7 +448,7 @@ module.exports.CreateMeshRelay = function (parent, ws, req, domain, user, cookie
}
// Record a new entry in a recording log
function recordingEntry(fd, type, flags, data, func, tag) {
function recordingEntry(logfile, type, flags, data, func, tag) {
try {
if (typeof data == 'string') {
// String write
@ -438,7 +458,8 @@ module.exports.CreateMeshRelay = function (parent, ws, req, domain, user, cookie
header.writeInt32BE(blockData.length, 4); // Size
header.writeIntBE(new Date(), 10, 6); // Time
var block = Buffer.concat([header, blockData]);
parent.parent.fs.write(fd, block, 0, block.length, function () { func(fd, tag); });
parent.parent.fs.write(logfile.fd, block, 0, block.length, function () { func(logfile, tag); });
logfile.size += block.length;
} else {
// Binary write
var header = Buffer.alloc(16); // Header: Type (2) + Flags (2) + Size(4) + Time(8)
@ -447,9 +468,10 @@ module.exports.CreateMeshRelay = function (parent, ws, req, domain, user, cookie
header.writeInt32BE(data.length, 4); // Size
header.writeIntBE(new Date(), 10, 6); // Time
var block = Buffer.concat([header, data]);
parent.parent.fs.write(fd, block, 0, block.length, function () { func(fd, tag); });
parent.parent.fs.write(logfile.fd, block, 0, block.length, function () { func(logfile, tag); });
logfile.size += block.length;
}
} catch (ex) { console.log(ex); func(fd, tag); }
} catch (ex) { console.log(ex); func(logfile, tag); }
}
// Mark this relay session as authenticated if this is the user end.

View File

@ -1211,6 +1211,37 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use
}
break;
}
case 'recordings': {
if (((user.siteadmin & SITERIGHT_RECORDINGS) == 0) || (domain.sessionrecording == null)) return; // Check if recordings is enabled and we have rights to do this.
var recordingsPath = null;
if (domain.sessionrecording.filepath) { recordingsPath = domain.sessionrecording.filepath; } else { recordingsPath = parent.parent.recordpath; }
if (recordingsPath == null) return;
fs.readdir(recordingsPath, function (err, files) {
if (err != null) return;
if ((command.limit == null) || (typeof command.limit != 'number')) {
// Send the list of all recordings
db.GetEvents(['recording'], domain.id, function (err, docs) {
if (err != null) return;
for (var i in docs) {
delete docs[i].action; delete docs[i].etype; delete docs[i].msg; // TODO: We could make a more specific query in the DB and never have these.
if (files.indexOf(docs[i].filename) >= 0) { docs[i].present = 1; }
}
try { ws.send(JSON.stringify({ action: 'recordings', events: docs, tag: command.tag })); } catch (ex) { }
});
} else {
// Send the list of most recent recordings, up to 'limit' count
db.GetEventsWithLimit(['recording'], domain.id, command.limit, function (err, docs) {
if (err != null) return;
for (var i in docs) {
delete docs[i].action; delete docs[i].etype; delete docs[i].msg; // TODO: We could make a more specific query in the DB and never have these.
if (files.indexOf(docs[i].filename) >= 0) { docs[i].present = 1; }
}
try { ws.send(JSON.stringify({ action: 'recordings', events: docs, tag: command.tag })); } catch (ex) { }
});
}
});
break;
}
case 'users':
{
// Request a list of all users

View File

@ -607,7 +607,7 @@
"default-mobile.handlebars->9->248",
"default-mobile.handlebars->9->77",
"default.handlebars->25->1339",
"default.handlebars->25->1649",
"default.handlebars->25->1676",
"default.handlebars->25->727"
]
},
@ -2956,7 +2956,7 @@
"ru": "Счетчик ошибок агента",
"zh-chs": "座席錯誤計數器",
"xloc": [
"default.handlebars->25->1659"
"default.handlebars->25->1686"
]
},
{
@ -3025,7 +3025,7 @@
"ru": "Сессии агентов",
"zh-chs": "座席會議",
"xloc": [
"default.handlebars->25->1675"
"default.handlebars->25->1702"
]
},
{
@ -3148,7 +3148,7 @@
"ru": "Агенты",
"zh-chs": "代理商",
"xloc": [
"default.handlebars->25->1688"
"default.handlebars->25->1715"
]
},
{
@ -3970,7 +3970,7 @@
"ru": "Вы уверенны, что {0} плагин: {1}",
"zh-chs": "您確定要{0}插件嗎:{1}",
"xloc": [
"default.handlebars->25->1728"
"default.handlebars->25->1755"
]
},
{
@ -4399,7 +4399,7 @@
"ru": "Плохой ключ",
"zh-chs": "錯誤的簽名",
"xloc": [
"default.handlebars->25->1666"
"default.handlebars->25->1693"
]
},
{
@ -4416,7 +4416,7 @@
"ru": "Плохой веб-сертификат",
"zh-chs": "錯誤的網絡證書",
"xloc": [
"default.handlebars->25->1665"
"default.handlebars->25->1692"
]
},
{
@ -4679,7 +4679,7 @@
"ru": "CIRA Сервер",
"zh-chs": "CIRA服務器",
"xloc": [
"default.handlebars->25->1716"
"default.handlebars->25->1743"
]
},
{
@ -4696,7 +4696,7 @@
"ru": "CIRA Сервер команды",
"zh-chs": "CIRA服務器命令",
"xloc": [
"default.handlebars->25->1717"
"default.handlebars->25->1744"
]
},
{
@ -4729,7 +4729,7 @@
"ru": "Загрузка CPU",
"zh-chs": "CPU負載",
"xloc": [
"default.handlebars->25->1680"
"default.handlebars->25->1707"
]
},
{
@ -4746,7 +4746,7 @@
"ru": "Загрузка CPU за последние 15 минут",
"zh-chs": "最近15分鐘的CPU負載",
"xloc": [
"default.handlebars->25->1683"
"default.handlebars->25->1710"
]
},
{
@ -4763,7 +4763,7 @@
"ru": "Загрузка CPU за последние 5 минут",
"zh-chs": "最近5分鐘的CPU負載",
"xloc": [
"default.handlebars->25->1682"
"default.handlebars->25->1709"
]
},
{
@ -4780,7 +4780,7 @@
"ru": "Загрузка CPU за последнюю минуту",
"zh-chs": "最後一分鐘的CPU負載",
"xloc": [
"default.handlebars->25->1681"
"default.handlebars->25->1708"
]
},
{
@ -4835,7 +4835,7 @@
"ru": "Ошибка вызова",
"zh-chs": "通話錯誤",
"xloc": [
"default.handlebars->25->1729"
"default.handlebars->25->1756"
]
},
{
@ -5302,7 +5302,7 @@
"ru": "Проверка...",
"zh-chs": "檢查...",
"xloc": [
"default.handlebars->25->1723",
"default.handlebars->25->1750",
"default.handlebars->25->865"
]
},
@ -5518,7 +5518,7 @@
"nl": "Wis alle meldingen",
"zh-chs": "全部清除",
"xloc": [
"default.handlebars->25->1653"
"default.handlebars->25->1680"
]
},
{
@ -5568,7 +5568,7 @@
"ru": "Очистить это уведомление",
"zh-chs": "清除此通知",
"xloc": [
"default.handlebars->25->1652"
"default.handlebars->25->1679"
]
},
{
@ -6380,7 +6380,7 @@
"ru": "Подключено Intel® AMT",
"zh-chs": "連接的英特爾®AMT",
"xloc": [
"default.handlebars->25->1671"
"default.handlebars->25->1698"
]
},
{
@ -6397,7 +6397,7 @@
"ru": "Подключенные пользователи",
"zh-chs": "關聯用戶",
"xloc": [
"default.handlebars->25->1676"
"default.handlebars->25->1703"
]
},
{
@ -6473,7 +6473,7 @@
"ru": "Подключений ",
"zh-chs": "連接數",
"xloc": [
"default.handlebars->25->1687"
"default.handlebars->25->1714"
]
},
{
@ -6490,7 +6490,7 @@
"ru": "Ретранслятор подключения",
"zh-chs": "連接繼電器",
"xloc": [
"default.handlebars->25->1715"
"default.handlebars->25->1742"
]
},
{
@ -6617,7 +6617,7 @@
"ru": "Cookie-кодировщик",
"zh-chs": "Cookie編碼器",
"xloc": [
"default.handlebars->25->1701"
"default.handlebars->25->1728"
]
},
{
@ -6922,7 +6922,7 @@
"ru": "Основной сервер",
"zh-chs": "核心服務器",
"xloc": [
"default.handlebars->25->1700"
"default.handlebars->25->1727"
]
},
{
@ -7872,6 +7872,7 @@
"zh-chs": "桌面",
"xloc": [
"default.handlebars->25->1217",
"default.handlebars->25->1657",
"default.handlebars->25->432",
"default.handlebars->container->topbar->1->1->MainSubMenuSpan->MainSubMenu->1->0->MainDevDesktop",
"default.handlebars->contextMenu->cxdesktop"
@ -8092,7 +8093,8 @@
"default.handlebars->25->1238",
"default.handlebars->25->1512",
"default.handlebars->25->1518",
"default.handlebars->25->1619"
"default.handlebars->25->1619",
"default.handlebars->25->1666"
]
},
{
@ -8132,7 +8134,7 @@
"default.handlebars->25->1477",
"default.handlebars->25->1499",
"default.handlebars->25->1563",
"default.handlebars->25->1674",
"default.handlebars->25->1701",
"default.handlebars->container->column_l->p2->p2info->7"
]
},
@ -8185,6 +8187,7 @@
"zh-chs": "設備名稱",
"xloc": [
"default-mobile.handlebars->9->232",
"default.handlebars->25->1665",
"default.handlebars->25->231",
"default.handlebars->25->669",
"player.handlebars->3->9"
@ -9185,7 +9188,7 @@
"nl": "Dubbele agent",
"zh-chs": "代理重复",
"xloc": [
"default.handlebars->25->1670"
"default.handlebars->25->1697"
]
},
{
@ -9250,6 +9253,8 @@
"ru": "Длительность",
"zh-chs": "持續時間",
"xloc": [
"default.handlebars->25->1651",
"default.handlebars->25->1671",
"player.handlebars->3->2"
]
},
@ -9999,7 +10004,7 @@
"nl": "Email/SMS verkeer",
"zh-chs": "电子邮件/短信流量",
"xloc": [
"default.handlebars->25->1709"
"default.handlebars->25->1736"
]
},
{
@ -10093,6 +10098,12 @@
"default.handlebars->container->column_l->p2->p2info->p2AccountActions->3->accountEnableNotificationsSpan->0"
]
},
{
"en": "Enabled",
"xloc": [
"default.handlebars->25->1673"
]
},
{
"cs": "Kódování",
"de": "Kodierung",
@ -10110,6 +10121,12 @@
"default-mobile.handlebars->dialog->3->dialog7->d7amtkvm->3->3"
]
},
{
"en": "End Time",
"xloc": [
"default.handlebars->25->1670"
]
},
{
"cs": "angličtina",
"de": "Englisch",
@ -10670,7 +10687,7 @@
"ru": "Внешний",
"zh-chs": "外部",
"xloc": [
"default.handlebars->25->1694"
"default.handlebars->25->1721"
]
},
{
@ -10874,6 +10891,7 @@
"zh-chs": "檔案",
"xloc": [
"default.handlebars->25->1224",
"default.handlebars->25->1658",
"default.handlebars->container->topbar->1->1->MainSubMenuSpan->MainSubMenu->1->0->MainDevFiles",
"default.handlebars->contextMenu->cxfiles"
]
@ -11176,8 +11194,8 @@
"ru": "Свободно",
"zh-chs": "自由",
"xloc": [
"default.handlebars->25->1655",
"default.handlebars->25->1657"
"default.handlebars->25->1682",
"default.handlebars->25->1684"
]
},
{
@ -12094,7 +12112,7 @@
"ru": "Всего кучи",
"zh-chs": "堆總數",
"xloc": [
"default.handlebars->25->1696"
"default.handlebars->25->1723"
]
},
{
@ -12111,7 +12129,7 @@
"ru": "Куча используется",
"zh-chs": "堆使用",
"xloc": [
"default.handlebars->25->1695"
"default.handlebars->25->1722"
]
},
{
@ -12897,8 +12915,8 @@
"xloc": [
"default.handlebars->25->1326",
"default.handlebars->25->1334",
"default.handlebars->25->1692",
"default.handlebars->25->1714"
"default.handlebars->25->1719",
"default.handlebars->25->1741"
]
},
{
@ -12935,6 +12953,18 @@
"default.handlebars->25->150"
]
},
{
"en": "Intel AMT Redirection",
"xloc": [
"default.handlebars->25->1660"
]
},
{
"en": "Intel AMT WSMAN",
"xloc": [
"default.handlebars->25->1659"
]
},
{
"cs": "Intel AMT zjištěno",
"de": "Intel AMT erkannt",
@ -13616,7 +13646,7 @@
"ru": "Некорректный тип группы устройств",
"zh-chs": "無效的設備組類型",
"xloc": [
"default.handlebars->25->1669"
"default.handlebars->25->1696"
]
},
{
@ -13633,7 +13663,7 @@
"ru": "Некорректный JSON",
"zh-chs": "無效的JSON",
"xloc": [
"default.handlebars->25->1663"
"default.handlebars->25->1690"
]
},
{
@ -13685,7 +13715,7 @@
"ru": "Некорректная сигнатура PKCS",
"zh-chs": "無效的PKCS簽名",
"xloc": [
"default.handlebars->25->1661"
"default.handlebars->25->1688"
]
},
{
@ -13702,7 +13732,7 @@
"ru": "Некорректная сигнатура RSA",
"zh-chs": "無效的RSA密碼",
"xloc": [
"default.handlebars->25->1662"
"default.handlebars->25->1689"
]
},
{
@ -14785,7 +14815,7 @@
"ru": "Меньше",
"zh-chs": "減",
"xloc": [
"default.handlebars->25->1731"
"default.handlebars->25->1758"
]
},
{
@ -15882,7 +15912,7 @@
"ru": "Сообщения главного сервера",
"zh-chs": "主服務器消息",
"xloc": [
"default.handlebars->25->1703"
"default.handlebars->25->1730"
]
},
{
@ -16308,7 +16338,7 @@
"ru": "Достигнуто максимальное число сессий",
"zh-chs": "達到的會話數上限",
"xloc": [
"default.handlebars->25->1667"
"default.handlebars->25->1694"
]
},
{
@ -16362,7 +16392,7 @@
"ru": "Мегабайт",
"zh-chs": "兆字節",
"xloc": [
"default.handlebars->25->1693"
"default.handlebars->25->1720"
]
},
{
@ -16379,7 +16409,7 @@
"ru": "ОЗУ",
"zh-chs": "記憶",
"xloc": [
"default.handlebars->25->1684",
"default.handlebars->25->1711",
"default.handlebars->25->819",
"default.handlebars->container->column_l->p40->3->1->p40type->3"
]
@ -16515,7 +16545,7 @@
"ru": "Трафик MeshAgent",
"zh-chs": "MeshAgent流量",
"xloc": [
"default.handlebars->25->1705"
"default.handlebars->25->1732"
]
},
{
@ -16532,7 +16562,7 @@
"ru": "Обновление MeshAgent",
"zh-chs": "MeshAgent更新",
"xloc": [
"default.handlebars->25->1706"
"default.handlebars->25->1733"
]
},
{
@ -16635,7 +16665,7 @@
"ru": "Соединения сервера MeshCentral",
"zh-chs": "MeshCentral服務器對等",
"xloc": [
"default.handlebars->25->1704"
"default.handlebars->25->1731"
]
},
{
@ -16898,7 +16928,7 @@
"ru": "Диспетчер сообщения",
"zh-chs": "郵件調度程序",
"xloc": [
"default.handlebars->25->1702"
"default.handlebars->25->1729"
]
},
{
@ -17046,7 +17076,7 @@
"ru": "Еще",
"zh-chs": "更多",
"xloc": [
"default.handlebars->25->1730"
"default.handlebars->25->1757"
]
},
{
@ -17117,6 +17147,12 @@
"default.handlebars->contextMenu->cxmdesktop"
]
},
{
"en": "Multiplexor",
"xloc": [
"default.handlebars->25->1672"
]
},
{
"cs": "Můj účet",
"de": "Mein Konto",
@ -18638,6 +18674,12 @@
"default.handlebars->25->802"
]
},
{
"en": "Not on server",
"xloc": [
"default.handlebars->25->1664"
]
},
{
"cs": "Nenastaveno",
"de": "Nicht gesetzt",
@ -18907,7 +18949,7 @@
"ru": "Произошло в {0}",
"zh-chs": "發生在{0}",
"xloc": [
"default.handlebars->25->1651"
"default.handlebars->25->1678"
]
},
{
@ -20011,7 +20053,7 @@
"zh-chs": "插件動作",
"xloc": [
"default.handlebars->25->169",
"default.handlebars->25->1727"
"default.handlebars->25->1754"
]
},
{
@ -20317,6 +20359,12 @@
"default.handlebars->25->7"
]
},
{
"en": "Present on server",
"xloc": [
"default.handlebars->25->1663"
]
},
{
"cs": "Pro pokračování/pozastavení stiskněte [mezerník].",
"de": "Für Abspielen/Pause [Leertaste] drücken,",
@ -20421,6 +20469,7 @@
"ru": "Протокол",
"zh-chs": "協議",
"xloc": [
"default.handlebars->25->1661",
"player.handlebars->3->15"
]
},
@ -20727,7 +20776,7 @@
"ru": "RSS",
"zh-chs": "的RSS",
"xloc": [
"default.handlebars->25->1697"
"default.handlebars->25->1724"
]
},
{
@ -20818,6 +20867,12 @@
"default.handlebars->container->column_l->p11->deskarea0->deskarea4->1"
]
},
{
"en": "Recording Details",
"xloc": [
"default.handlebars->25->1675"
]
},
{
"en": "Recordings",
"nl": "Opnames",
@ -20885,7 +20940,8 @@
"default.handlebars->25->437",
"default.handlebars->container->column_l->p11->deskarea0->deskarea3x->DeskTools->deskToolsAreaTop->DeskToolsRefreshButton",
"default.handlebars->container->column_l->p13->p13toolbar->1->2->1->3",
"default.handlebars->container->column_l->p40->3->3"
"default.handlebars->container->column_l->p40->3->3",
"default.handlebars->container->column_l->p52->3->1->0->3->3"
]
},
{
@ -20938,7 +20994,7 @@
"ru": "Число ретрансляций",
"zh-chs": "中繼計數",
"xloc": [
"default.handlebars->25->1679"
"default.handlebars->25->1706"
]
},
{
@ -20955,7 +21011,7 @@
"ru": "Ошибки ретранслятора",
"zh-chs": "中繼錯誤",
"xloc": [
"default.handlebars->25->1672"
"default.handlebars->25->1699"
]
},
{
@ -20972,8 +21028,8 @@
"ru": "Сессии ретранслятора",
"zh-chs": "接力會議",
"xloc": [
"default.handlebars->25->1678",
"default.handlebars->25->1691"
"default.handlebars->25->1705",
"default.handlebars->25->1718"
]
},
{
@ -23002,7 +23058,7 @@
"ru": "Сертификат сервера",
"zh-chs": "服務器證書",
"xloc": [
"default.handlebars->25->1707"
"default.handlebars->25->1734"
]
},
{
@ -23019,7 +23075,7 @@
"ru": "База данных сервера",
"zh-chs": "服務器數據庫",
"xloc": [
"default.handlebars->25->1708"
"default.handlebars->25->1735"
]
},
{
@ -23128,7 +23184,7 @@
"ru": "Состояние сервера",
"zh-chs": "服務器狀態",
"xloc": [
"default.handlebars->25->1658"
"default.handlebars->25->1685"
]
},
{
@ -23162,7 +23218,7 @@
"ru": "Трассировка сервера",
"zh-chs": "服務器跟踪",
"xloc": [
"default.handlebars->25->1718"
"default.handlebars->25->1745"
]
},
{
@ -23301,7 +23357,7 @@
"ru": "ServerStats.csv",
"zh-chs": "ServerStats.csv",
"xloc": [
"default.handlebars->25->1699"
"default.handlebars->25->1726"
]
},
{
@ -23338,6 +23394,12 @@
"default.handlebars->container->column_l->p11->deskarea0->deskarea3x->DeskTools->deskToolsAreaTop->deskToolsTopTabService"
]
},
{
"en": "Session",
"xloc": [
"default.handlebars->25->1649"
]
},
{
"cs": "Čas spojení",
"de": "Sitzungszeit",
@ -23795,6 +23857,8 @@
"ru": "Размер",
"zh-chs": "尺寸",
"xloc": [
"default.handlebars->25->1652",
"default.handlebars->25->1667",
"default.handlebars->container->column_l->p1->devListToolbarSpan->1->0->9->devListToolbarSize"
]
},
@ -24602,6 +24666,13 @@
"default.handlebars->25->705"
]
},
{
"en": "Start Time",
"xloc": [
"default.handlebars->25->1650",
"default.handlebars->25->1669"
]
},
{
"cs": "Stav",
"de": "Zustand",
@ -24652,6 +24723,7 @@
"zh-chs": "狀態",
"xloc": [
"default.handlebars->25->1601",
"default.handlebars->25->1662",
"default.handlebars->container->column_l->p42->p42tbl->1->0->7"
]
},
@ -25193,6 +25265,7 @@
"zh-chs": "終奌站",
"xloc": [
"default.handlebars->25->1221",
"default.handlebars->25->1656",
"default.handlebars->25->433",
"default.handlebars->container->topbar->1->1->MainSubMenuSpan->MainSubMenu->1->0->MainDevTerminal",
"default.handlebars->contextMenu->cxterminal"
@ -25393,7 +25466,7 @@
"ru": "На данный момент уведомлений нет",
"zh-chs": "目前沒有任何通知",
"xloc": [
"default.handlebars->25->1650"
"default.handlebars->25->1677"
]
},
{
@ -26623,6 +26696,8 @@
"default-mobile.handlebars->9->182",
"default.handlebars->25->13",
"default.handlebars->25->1641",
"default.handlebars->25->1654",
"default.handlebars->25->1655",
"default.handlebars->25->379",
"default.handlebars->25->41",
"default.handlebars->25->42",
@ -26667,7 +26742,7 @@
"ru": "Неизвестное действие",
"zh-chs": "未知動作",
"xloc": [
"default.handlebars->25->1664"
"default.handlebars->25->1691"
]
},
{
@ -26704,7 +26779,7 @@
"xloc": [
"default.handlebars->25->1510",
"default.handlebars->25->1617",
"default.handlebars->25->1668"
"default.handlebars->25->1695"
]
},
{
@ -26721,7 +26796,7 @@
"ru": "Неизвестная группа",
"zh-chs": "未知群組",
"xloc": [
"default.handlebars->25->1660"
"default.handlebars->25->1687"
]
},
{
@ -26845,7 +26920,7 @@
"ru": "Актуально",
"zh-chs": "最新",
"xloc": [
"default.handlebars->25->1725"
"default.handlebars->25->1752"
]
},
{
@ -27094,8 +27169,8 @@
"ru": "Использовано",
"zh-chs": "用過的",
"xloc": [
"default.handlebars->25->1654",
"default.handlebars->25->1656"
"default.handlebars->25->1681",
"default.handlebars->25->1683"
]
},
{
@ -27116,6 +27191,7 @@
"default.handlebars->25->1179",
"default.handlebars->25->1402",
"default.handlebars->25->1506",
"default.handlebars->25->1674",
"default.handlebars->25->193",
"default.handlebars->25->562"
]
@ -27171,7 +27247,7 @@
"ru": "Учетные записи пользователей",
"zh-chs": "用戶帳號",
"xloc": [
"default.handlebars->25->1673"
"default.handlebars->25->1700"
]
},
{
@ -27383,7 +27459,7 @@
"ru": "Сессии пользователя",
"zh-chs": "用戶會話",
"xloc": [
"default.handlebars->25->1690"
"default.handlebars->25->1717"
]
},
{
@ -27572,7 +27648,7 @@
"xloc": [
"default.handlebars->25->1476",
"default.handlebars->25->1498",
"default.handlebars->25->1689",
"default.handlebars->25->1716",
"default.handlebars->container->topbar->1->1->UsersSubMenuSpan->UsersSubMenu->1->0->UsersGeneral"
]
},
@ -27590,7 +27666,7 @@
"ru": "Сессии пользователей",
"zh-chs": "用戶會話",
"xloc": [
"default.handlebars->25->1677"
"default.handlebars->25->1704"
]
},
{
@ -27773,7 +27849,7 @@
"ru": "Версия несовместима, пожалуйста, сначала обновите установку MeshCentral",
"zh-chs": "版本不兼容请先升级您的MeshCentral安装",
"xloc": [
"default.handlebars->25->1721"
"default.handlebars->25->1748"
]
},
{
@ -27841,8 +27917,8 @@
"ru": "Просмотр журнала изменений",
"zh-chs": "查看变更日志",
"xloc": [
"default.handlebars->25->1724",
"default.handlebars->25->1726"
"default.handlebars->25->1751",
"default.handlebars->25->1753"
]
},
{
@ -28100,8 +28176,8 @@
"ru": "Веб-сервер",
"zh-chs": "網絡服務器",
"xloc": [
"default.handlebars->25->1710",
"default.handlebars->25->1711"
"default.handlebars->25->1737",
"default.handlebars->25->1738"
]
},
{
@ -28118,7 +28194,7 @@
"ru": "Запросы веб-сервера",
"zh-chs": "Web服務器請求",
"xloc": [
"default.handlebars->25->1712"
"default.handlebars->25->1739"
]
},
{
@ -28135,7 +28211,7 @@
"ru": "Ретранслятор Web Socket",
"zh-chs": "Web套接字中繼",
"xloc": [
"default.handlebars->25->1713"
"default.handlebars->25->1740"
]
},
{
@ -29200,7 +29276,7 @@
"ru": "\\\\'",
"zh-chs": "\\\\'",
"xloc": [
"default.handlebars->25->1722"
"default.handlebars->25->1749"
]
},
{
@ -29475,7 +29551,7 @@
"ru": "свободно",
"zh-chs": "自由",
"xloc": [
"default.handlebars->25->1685"
"default.handlebars->25->1712"
]
},
{
@ -29839,7 +29915,7 @@
"ru": "servertrace.csv",
"zh-chs": "servertrace.csv",
"xloc": [
"default.handlebars->25->1720"
"default.handlebars->25->1747"
]
},
{
@ -29892,7 +29968,7 @@
"ru": "time, conn.agent, conn.users, conn.usersessions, conn.relaysession, conn.intelamt, mem.external, mem.heapused, mem.heaptotal, mem.rss",
"zh-chs": "時間conn.agentconn.usersconn.usersessionsconn.relaysessionconn.intelamtmem.externalmem.heapusedmem.heaptotalmem.rss",
"xloc": [
"default.handlebars->25->1698"
"default.handlebars->25->1725"
]
},
{
@ -29909,7 +29985,7 @@
"ru": "time, source, message",
"zh-chs": "時間,來源,訊息",
"xloc": [
"default.handlebars->25->1719"
"default.handlebars->25->1746"
]
},
{
@ -29940,7 +30016,7 @@
"ru": "всего",
"zh-chs": "總",
"xloc": [
"default.handlebars->25->1686"
"default.handlebars->25->1713"
]
},
{
@ -30094,7 +30170,8 @@
"ru": "{0} Kб",
"zh-chs": "{0} Kb",
"xloc": [
"default.handlebars->25->1349"
"default.handlebars->25->1349",
"default.handlebars->25->1653"
]
},
{
@ -30181,7 +30258,8 @@
"zh-chs": "{0}個字節",
"xloc": [
"default-mobile.handlebars->9->88",
"default.handlebars->25->1359"
"default.handlebars->25->1359",
"default.handlebars->25->1668"
]
},
{
@ -30796,4 +30874,4 @@
]
}
]
}
}

View File

@ -1047,12 +1047,13 @@
<input type=button onclick=openRecodringPlayer() value="Open Player..." />
</div>
<div>
<input type=button onclick=refreshRecodings() value="Refresh" />
</div>
</td>
<td class="h2"></td>
</tr>
</table>
<div id=p52recordings style=""></div>
<div id=p52recordings style="overflow-y:auto"></div>
</div>
<br id="column_l_bottomgap" />
</div>
@ -1489,6 +1490,8 @@
QS('p31events')['max-height'] = 'calc(100vh - ' + (50 + xh + xh2) + 'px)';
QS('p41events')['height'] = 'calc(100vh - ' + (48 + xh + xh2) + 'px)';
QS('p41events')['max-height'] = 'calc(100vh - ' + (48 + xh + xh2) + 'px)';
QS('p52recordings')['height'] = 'calc(100vh - ' + (48 + xh + xh2) + 'px)';
QS('p52recordings')['max-height'] = 'calc(100vh - ' + (48 + xh + xh2) + 'px)';
// We are looking at a single device, remove all the back buttons
if ('{{currentNode}}'.toLowerCase() != '') {
@ -2094,6 +2097,11 @@
}
break;
}
case 'recordings': {
p52recordings = message.events;
updateRecordings();
break;
}
case 'getcookie': {
if (message.tag == 'clickonce') {
if (message.trustedCert == true) {
@ -2278,6 +2286,10 @@
}
if (message.event.noact) break; // Take no action on this event
switch (message.event.action) {
case 'recording': {
if (p52recordings != null) { p52recordings.unshift(message.event); message.event.present = 1; updateRecordings(); }
break;
}
case 'userWebState': {
// New user web state, update the web page as needed
try {
@ -6595,7 +6607,7 @@
function deskSaveImage() {
if (xxdialogMode || desktop == null || desktop.State != 3) return;
var d = new Date(), n = 'Desktop-' + currentNode.name + '-' + d.getFullYear() + '-' + ('0' + (d.getMonth() + 1)).slice(-2) + '-' + ('0' + d.getDate()).slice(-2) + '-' + ('0' + d.getHours()).slice(-2) + '-' + ('0' + d.getMinutes()).slice(-2);
Q('Desk')['toBlob'](function (blob) { saveAs(blob, n + '.jpg'); });
Q('Desk')['toBlob'](function (blob) { saveAs(blob, n + '.png'); });
}
function deskDisplayInfo(sender, displays, selDisplay) {
@ -11124,15 +11136,92 @@
var p52recordings = null;
function updateRecordings() {
var x = 'Under construction';
// Display the users using the sorted list
var x = '<table class=p3usersTable cellpadding=0 cellspacing=0>', addHeader = true;
x += '<th>' + "Session" + '<th style=width:110px>' + nobreak("Start Time") + '<th style=width:110px>' + "Duration" + '<th style=width:110px>' + "Size";
if (p52recordings != null) {
var recdate = null;
for (var i in p52recordings) {
var rec = p52recordings[i], rect = new Date(rec.time), day = printDate(rect);
if (day != recdate) { recdate = day; x += '<tr><td class=userTableHeader colspan=4>' + day; }
x += addRecordingHtml(i, rec);
}
}
x += '</table>';
QH('p52recordings', x);
}
function openRecodringPlayer() {
if (xxdialogMode) return;
window.open(window.location.origin + '{{{domainurl}}}player.htm', 'meshcentral-deskplayer');
function addRecordingHtml(i, rec) {
var sessionLengthStr = '';
if (rec.lengthTime) { sessionLengthStr = pad2(Math.floor(rec.lengthTime / 3600)) + ':' + pad2(Math.floor((rec.lengthTime % 3600) / 60)) + ':' + pad2(Math.floor(rec.lengthTime % 60)); }
var sessionStartStr = printTime(new Date(rec.time));
if (rec.sessionStart) { sessionStartStr = printTime(new Date(rec.sessionStart)); }
var sessionSize = '';
if (rec.size) { sessionSize = format("{0} Kb", Math.round(rec.size / 1024)); }
var sessionName = '<i>' + "Unknown" + '</i>';
if (rec.name && rec.meshname) {
var recmesh = meshes[rec.meshid];
if (recmesh != null) {
sessionName = '<a href=# onclick=\'gotoMesh("' + rec.meshid + '")\'>' + EscapeHtml(rec.meshname) + '</a> - <a href=# onclick=\'gotoDevice("' + rec.nodeid + '",10)\'>' + EscapeHtml(rec.name) + '</a>';
} else {
sessionName = EscapeHtml(rec.meshname) + ' - ' + EscapeHtml(rec.name);
}
}
if ((rec.userids != null) && (rec.userids.length > 0)) {
if (rec.userids.length > 1) {
sessionName += ' - ' + format('{0} users', rec.userids.length);
} else {
var ruser = null;
if (users != null) { ruser = users[rec.userids[0]]; }
if (ruser != null) {
sessionName += ' - <a href=# onclick=\'gotoUser("' + rec.userids[0] + '")\'>' + EscapeHtml(ruser.name) + '</a>';
} else {
sessionName += ' - ' + EscapeHtml(rec.userids[0].split('/')[2]);
}
}
}
var actions = '', icon = 'm0';
if (rec.present == 1) { icon = 'm1'; actions = '<div style=cursor:pointer;float:right><a href=recordings.ashx?file=' + encodeURIComponent(rec.filename) + ' download><img src=images/link4.png height=10 width=10 title="Download Recording" download></a>&nbsp;</div>'; }
var x = '<tr tabindex=0 onmouseover=userMouseHover2(this,1) onmouseout=userMouseHover2(this,0) onkeypress="if (event.key==\'Enter\') showRecordingDialog(event,\'' + i + '\')"><td style=cursor:pointer>';
x += '<div class=bar style=width:100%>';
x += '<div class=baricon><input class=RecordingCheckbox value=' + i + ' onclick=p52updateInfo() type=checkbox></div>';
x += '<div class=baricon onclick=showRecordingDialog(event,"' + i + '")><div class=' + icon + '></div></div>';
x += '<div class=g1 onclick=showRecordingDialog(event,"' + i + '")></div><div class=g2 onclick=showRecordingDialog("' + i + '")></div>';
x += '<div onclick=showRecordingDialog(event,"' + i + '")>' + actions + '<div style=font-size:16px>' + sessionName + '</div></div></div><td style=text-align:center>' + sessionStartStr + '<td style=text-align:center>' + sessionLengthStr + '<td style=text-align:center>' + sessionSize;
return x;
}
function showRecordingDialog(event, i) {
if (xxdialogMode) return;
if ((event.target.tagName == 'IMG') || (event.target.tagName == 'A')) return;
var rec = p52recordings[i];
//console.log(rec);
var x = '';
if (rec.protocol) {
var protocolStr = "Unknown";
if (rec.protocol == 1) { protocolStr = "Terminal"; }
if (rec.protocol == 2) { protocolStr = "Desktop"; }
if (rec.protocol == 5) { protocolStr = "Files"; }
if (rec.protocol == 100) { protocolStr = "Intel AMT WSMAN"; }
if (rec.protocol == 101) { protocolStr = "Intel AMT Redirection"; }
x += addHtmlValue4("Protocol", protocolStr);
}
x += addHtmlValue4("Status", (rec.present == 1)?"Present on server":"Not on server");
if (rec.name) { x += addHtmlValue4("Device Name", EscapeHtml(rec.name)); }
if (rec.meshname) { x += addHtmlValue4("Device Group", EscapeHtml(rec.meshname)); }
if (rec.size) { x += addHtmlValue4("Size", format("{0} bytes", rec.size)); }
if (rec.startTime) { x += addHtmlValue4("Start Time", printTime(new Date(rec.startTime))); }
if (rec.time) { x += addHtmlValue4("End Time", printTime(new Date(rec.time))); }
if (rec.lengthTime) { x += addHtmlValue4("Duration", pad2(Math.floor(rec.lengthTime / 3600)) + ':' + pad2(Math.floor((rec.lengthTime % 3600) / 60)) + ':' + pad2(Math.floor(rec.lengthTime % 60))); }
if (rec.multiplex == true) { x += addHtmlValue4("Multiplexor", "Enabled"); }
if (rec.userids) { for (var i in rec.userids) { x += addHtmlValue4("User", rec.userids[i].split('/')[2]); } }
setDialogMode(2, "Recording Details", 9, null, x);
}
function refreshRecodings() { meshserver.send({ action: 'recordings', limit: 1000 }); }
function openRecodringPlayer() { if (!xxdialogMode) window.open(window.location.origin + '{{{domainurl}}}player.htm', 'meshcentral-deskplayer'); }
function p52updateInfo() { }
//
// FILE SELECTOR, DIALOG 3
//
@ -11856,7 +11945,7 @@
if (x == 21) { p21updateMesh(); }
// Update Recordings
if (x == 52) { updateRecordings(); }
if (x == 52) { if (p52recordings == null) { refreshRecodings(); } updateRecordings(); }
// Update the web page title
if ((currentNode) && (x >= 10) && (x < 20)) {
@ -12223,6 +12312,7 @@
function addHtmlValue(t, v) { return '<table><td style=width:120px>' + t + '<td><b>' + v + '</b></table>'; }
function addHtmlValue2(t, v) { return '<div><div style=display:inline-block;float:right>' + v + '</div><div style=display:inline-block>' + t + '</div></div>'; }
function addHtmlValue3(t, v) { return '<div><b>' + t + '</b></div><div style=margin-left:16px>' + v + '</div>'; }
function addHtmlValue4(t, v) { return '<table style=width:100%><td style=width:120px>' + t + '<td style=text-align:right><b>' + v + '</b></table>'; }
function parseUriArgs() { var href = window.document.location.href; if (href.endsWith('#')) { href = href.substring(0, href.length - 1); } var name, r = {}, parsedUri = href.split(/[\?&|\=]/); parsedUri.splice(0, 1); for (x in parsedUri) { switch (x % 2) { case 0: { name = decodeURIComponent(parsedUri[x]); break; } case 1: { r[name] = decodeURIComponent(parsedUri[x]); var x = parseInt(r[name]); if (x == r[name]) { r[name] = x; } break; } default: { break; } } } return r; }
function focusTextBox(x) { setTimeout(function(){ Q(x).selectionStart = Q(x).selectionEnd = 65535; Q(x).focus(); }, 0); }
function validateEmail(v) { var emailReg = /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/; return emailReg.test(v); } // New version
@ -12241,6 +12331,7 @@
function getOrderedList(objList, oname) { var r = []; for (var i in objList) { r.push(objList[i]); } r.sort(function(a, b) { var aa = a[oname].toLowerCase(), bb = b[oname].toLowerCase(); if (aa > bb) return 1; if (aa < bb) return -1; return 0; }); return r; }
function capitalizeFirstLetter(string) { return string.charAt(0).toUpperCase() + string.slice(1); }
function nobreak(x) { return x.split(' ').join('&nbsp;'); }
function pad2(num) { var s = '00' + num; return s.substr(s.length - 2); }
</script>
</body>

View File

@ -94,6 +94,7 @@
var recFileMetadata = null;
var recFileProtocol = 0;
var recFileIndexBasePtr = null;
var recFileExtras = null;
var agentDesktop = null;
var amtDesktop = null;
var playing = false;
@ -427,7 +428,6 @@
cleanup();
recFile = files[0];
recFilePtr = 0;
readNextBlock(processFirstBlock);
readLastBlock(function (type, flags, time, extras) {
if (type == 3) {
// File is ok

View File

@ -2437,6 +2437,32 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
}
}
// Download a desktop recording
function handleGetRecordings(req, res) {
const domain = checkUserIpAddress(req, res);
if (domain == null) { return; }
// Check the query
if (req.query.file == null) { res.sendStatus(401); return; }
// Get the recording path
var recordingsPath = null;
if (domain.sessionrecording.filepath) { recordingsPath = domain.sessionrecording.filepath; } else { recordingsPath = parent.recordpath; }
if (recordingsPath == null) { res.sendStatus(401); return; }
// Get the user and check user rights
var authUserid = null;
if ((req.session != null) && (typeof req.session.userid == 'string')) { authUserid = req.session.userid; }
if (authUserid == null) { res.sendStatus(401); return; }
const user = obj.users[authUserid];
if (user == null) { res.sendStatus(401); return; }
if ((user.siteadmin & 512) == 0) { res.sendStatus(401); return; } // Check if we have right to get recordings
// Send the recorded file
res.set({ 'Cache-Control': 'no-cache, no-store, must-revalidate', 'Pragma': 'no-cache', 'Expires': '0', 'Content-Type': 'application/octet-stream', 'Content-Disposition': 'attachment; filename=\"' + req.query.file + '\"' });
try { res.sendFile(obj.path.join(recordingsPath, req.query.file)); } catch (ex) { res.sendStatus(404); }
}
// Server the player page
function handlePlayerRequest(req, res) {
const domain = checkUserIpAddress(req, res);
@ -3917,6 +3943,7 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
obj.app.get(url + 'logo.png', handleLogoRequest);
obj.app.post(url + 'translations', handleTranslationsRequest);
obj.app.get(url + 'welcome.jpg', handleWelcomeImageRequest);
obj.app.get(url + 'recordings.ashx', handleGetRecordings);
obj.app.get(url + 'player.htm', handlePlayerRequest);
obj.app.get(url + 'player', handlePlayerRequest);
obj.app.ws(url + 'amtactivate', handleAmtActivateWebSocket);