From efc378be6b81453d79964a77b8c47024df96a45a Mon Sep 17 00:00:00 2001 From: Ylian Saint-Hilaire Date: Tue, 5 May 2020 13:00:51 -0700 Subject: [PATCH] Added recodings manager and recording improvements. --- meshdesktopmultiplex.js | 71 ++++++++--- meshrelay.js | 46 +++++-- meshuser.js | 31 +++++ translate/translate.json | 254 +++++++++++++++++++++++++-------------- views/default.handlebars | 105 ++++++++++++++-- views/player.handlebars | 2 +- webserver.js | 27 +++++ 7 files changed, 410 insertions(+), 126 deletions(-) diff --git a/meshdesktopmultiplex.js b/meshdesktopmultiplex.js index 4d763f4e..d0cee1ee 100644 --- a/meshdesktopmultiplex.js +++ b/meshdesktopmultiplex.js @@ -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. diff --git a/meshrelay.js b/meshrelay.js index 65f9a81a..a23deb95 100644 --- a/meshrelay.js +++ b/meshrelay.js @@ -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. diff --git a/meshuser.js b/meshuser.js index c42bb440..3dda61c2 100644 --- a/meshuser.js +++ b/meshuser.js @@ -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 diff --git a/translate/translate.json b/translate/translate.json index 45f428aa..8cf545f4 100644 --- a/translate/translate.json +++ b/translate/translate.json @@ -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.agent,conn.users,conn.usersessions,conn.relaysession,conn.intelamt,mem.external,mem.heapused,mem.heaptotal,mem.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 @@ ] } ] -} +} \ No newline at end of file diff --git a/views/default.handlebars b/views/default.handlebars index eb10d050..d2bb64d2 100644 --- a/views/default.handlebars +++ b/views/default.handlebars @@ -1047,12 +1047,13 @@
+
-
+

@@ -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 = '', addHeader = true; + x += '
' + "Session" + '' + nobreak("Start Time") + '' + "Duration" + '' + "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 += '
' + day; } + x += addRecordingHtml(i, rec); + } + } + x += '
'; 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 = '' + "Unknown" + ''; + if (rec.name && rec.meshname) { + var recmesh = meshes[rec.meshid]; + if (recmesh != null) { + sessionName = '' + EscapeHtml(rec.meshname) + ' - ' + EscapeHtml(rec.name) + ''; + } 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 += ' - ' + EscapeHtml(ruser.name) + ''; + } else { + sessionName += ' - ' + EscapeHtml(rec.userids[0].split('/')[2]); + } + } + } + var actions = '', icon = 'm0'; + if (rec.present == 1) { icon = 'm1'; actions = '
 
'; } + var x = ''; + x += '
'; + x += '
'; + x += '
'; + x += '
'; + x += '
' + actions + '
' + sessionName + '
' + sessionStartStr + '' + sessionLengthStr + '' + 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 '
' + t + '' + v + '
'; } function addHtmlValue2(t, v) { return '
' + v + '
' + t + '
'; } function addHtmlValue3(t, v) { return '
' + t + '
' + v + '
'; } + function addHtmlValue4(t, v) { return '
' + t + '' + v + '
'; } 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(' '); } + function pad2(num) { var s = '00' + num; return s.substr(s.length - 2); } diff --git a/views/player.handlebars b/views/player.handlebars index bc954ef6..84c5ed14 100644 --- a/views/player.handlebars +++ b/views/player.handlebars @@ -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 diff --git a/webserver.js b/webserver.js index b2fd9a66..75c85e37 100644 --- a/webserver.js +++ b/webserver.js @@ -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);