diff --git a/server/www/teleport/app/eom_app/controller/record.py b/server/www/teleport/app/eom_app/controller/record.py index f995fbd..23766ae 100644 --- a/server/www/teleport/app/eom_app/controller/record.py +++ b/server/www/teleport/app/eom_app/controller/record.py @@ -5,6 +5,7 @@ import json import os import platform +from eom_common.eomcore.logger import log from eom_app.app.configs import app_cfg from eom_app.module import record from eom_app.module import user @@ -163,6 +164,29 @@ class RecordGetInfo(TPBaseAdminAuthJsonHandler): return self.write_json(0, data=data) +# class RecordGetInfo(TPBaseAdminAuthHandler): +# def post(self): +# args = self.get_argument('args', None) +# if args is not None: +# args = json.loads(args) +# else: +# return self.write(-1) +# +# record_id = args['id'] +# file_id = args['file_id'] +# +# record_path = os.path.join(app_cfg().core.replay_path, 'ssh', '{:06d}'.format(int(record_id))) +# file_info = os.path.join(record_path, 'tp-ssh.{:03d}'.format(int(file_id))) +# try: +# with open(file_info, 'rb') as f: +# data = f.read() +# log.v('data size:', len(data), '\n') +# self.write(data) +# except: +# log.e('\n') +# self.write(-1) + + class DeleteLog(TPBaseAdminAuthJsonHandler): # TODO: 用户可能会批量删除大量录像文件,因此io操作可能会比较耗时,这里应该改为异步方式。 def post(self): diff --git a/server/www/teleport/app/eom_app/module/record.py b/server/www/teleport/app/eom_app/module/record.py index d6e74cf..3a51561 100644 --- a/server/www/teleport/app/eom_app/module/record.py +++ b/server/www/teleport/app/eom_app/module/record.py @@ -3,6 +3,7 @@ import os import shutil import struct +import base64 from eom_app.app.configs import app_cfg from eom_app.app.db import get_db @@ -113,9 +114,13 @@ def read_record_info(record_id, file_id): temp['w'] = w temp['h'] = h elif action == 2: - _data = _data.decode() - # this is ssh data. - temp['d'] = _data + try: + _d = _data.decode() + temp['d'] = _d + except: + _data = base64.b64encode(_data) + temp['a'] = 3 + temp['d'] = _data.decode() else: return None diff --git a/server/www/teleport/app/eom_common/eomcore/logger.py b/server/www/teleport/app/eom_common/eomcore/logger.py index 999bb61..acd0c02 100644 --- a/server/www/teleport/app/eom_common/eomcore/logger.py +++ b/server/www/teleport/app/eom_common/eomcore/logger.py @@ -115,6 +115,7 @@ class Logger: self._log_datetime = True # 是否记录日志时间 self._file_handle = None # 日志文件的句柄,为None时表示不记录到文件 + self._log_console = self._log_pass self._win_color = None self._set_console(True) diff --git a/server/www/teleport/static/js/common/addons/attach/attach.js b/server/www/teleport/static/js/common/addons/attach/attach.js new file mode 100644 index 0000000..c2a7989 --- /dev/null +++ b/server/www/teleport/static/js/common/addons/attach/attach.js @@ -0,0 +1,126 @@ +/** + * Implements the attach method, that attaches the terminal to a WebSocket stream. + * @module xterm/addons/attach/attach + * @license MIT + */ + +(function (attach) { + if (typeof exports === 'object' && typeof module === 'object') { + /* + * CommonJS environment + */ + module.exports = attach(require('../../xterm')); + } else if (typeof define == 'function') { + /* + * Require.js is available + */ + define(['../../xterm'], attach); + } else { + /* + * Plain browser environment + */ + attach(window.Terminal); + } +})(function (Xterm) { + 'use strict'; + + var exports = {}; + + /** + * Attaches the given terminal to the given socket. + * + * @param {Xterm} term - The terminal to be attached to the given socket. + * @param {WebSocket} socket - The socket to attach the current terminal. + * @param {boolean} bidirectional - Whether the terminal should send data + * to the socket as well. + * @param {boolean} buffered - Whether the rendering of incoming data + * should happen instantly or at a maximum + * frequency of 1 rendering per 10ms. + */ + exports.attach = function (term, socket, bidirectional, buffered) { + bidirectional = (typeof bidirectional == 'undefined') ? true : bidirectional; + term.socket = socket; + + term._flushBuffer = function () { + term.write(term._attachSocketBuffer); + term._attachSocketBuffer = null; + clearTimeout(term._attachSocketBufferTimer); + term._attachSocketBufferTimer = null; + }; + + term._pushToBuffer = function (data) { + if (term._attachSocketBuffer) { + term._attachSocketBuffer += data; + } else { + term._attachSocketBuffer = data; + setTimeout(term._flushBuffer, 10); + } + }; + + term._getMessage = function (ev) { + if (buffered) { + term._pushToBuffer(ev.data); + } else { + term.write(ev.data); + } + }; + + term._sendData = function (data) { + socket.send(data); + }; + + socket.addEventListener('message', term._getMessage); + + if (bidirectional) { + term.on('data', term._sendData); + } + + socket.addEventListener('close', term.detach.bind(term, socket)); + socket.addEventListener('error', term.detach.bind(term, socket)); + }; + + /** + * Detaches the given terminal from the given socket + * + * @param {Xterm} term - The terminal to be detached from the given socket. + * @param {WebSocket} socket - The socket from which to detach the current + * terminal. + */ + exports.detach = function (term, socket) { + term.off('data', term._sendData); + + socket = (typeof socket == 'undefined') ? term.socket : socket; + + if (socket) { + socket.removeEventListener('message', term._getMessage); + } + + delete term.socket; + }; + + /** + * Attaches the current terminal to the given socket + * + * @param {WebSocket} socket - The socket to attach the current terminal. + * @param {boolean} bidirectional - Whether the terminal should send data + * to the socket as well. + * @param {boolean} buffered - Whether the rendering of incoming data + * should happen instantly or at a maximum + * frequency of 1 rendering per 10ms. + */ + Xterm.prototype.attach = function (socket, bidirectional, buffered) { + return exports.attach(this, socket, bidirectional, buffered); + }; + + /** + * Detaches the current terminal from the given socket. + * + * @param {WebSocket} socket - The socket from which to detach the current + * terminal. + */ + Xterm.prototype.detach = function (socket) { + return exports.detach(this, socket); + }; + + return exports; +}); diff --git a/server/www/teleport/static/js/common/addons/fit/fit.js b/server/www/teleport/static/js/common/addons/fit/fit.js new file mode 100644 index 0000000..46b79e9 --- /dev/null +++ b/server/www/teleport/static/js/common/addons/fit/fit.js @@ -0,0 +1,86 @@ +/** + * Fit terminal columns and rows to the dimensions of its DOM element. + * + * ## Approach + * - Rows: Truncate the division of the terminal parent element height by the terminal row height. + * + * - Columns: Truncate the division of the terminal parent element width by the terminal character + * width (apply display: inline at the terminal row and truncate its width with the current + * number of columns). + * @module xterm/addons/fit/fit + * @license MIT + */ + +(function (fit) { + if (typeof exports === 'object' && typeof module === 'object') { + /* + * CommonJS environment + */ + module.exports = fit(require('../../xterm')); + } else if (typeof define == 'function') { + /* + * Require.js is available + */ + define(['../../xterm'], fit); + } else { + /* + * Plain browser environment + */ + fit(window.Terminal); + } +})(function (Xterm) { + var exports = {}; + + exports.proposeGeometry = function (term) { + if (!term.element.parentElement) { + return null; + } + var parentElementStyle = window.getComputedStyle(term.element.parentElement), + parentElementHeight = parseInt(parentElementStyle.getPropertyValue('height')), + parentElementWidth = Math.max(0, parseInt(parentElementStyle.getPropertyValue('width')) - 17), + elementStyle = window.getComputedStyle(term.element), + elementPaddingVer = parseInt(elementStyle.getPropertyValue('padding-top')) + parseInt(elementStyle.getPropertyValue('padding-bottom')), + elementPaddingHor = parseInt(elementStyle.getPropertyValue('padding-right')) + parseInt(elementStyle.getPropertyValue('padding-left')), + availableHeight = parentElementHeight - elementPaddingVer, + availableWidth = parentElementWidth - elementPaddingHor, + container = term.rowContainer, + subjectRow = term.rowContainer.firstElementChild, + contentBuffer = subjectRow.innerHTML, + characterHeight, + rows, + characterWidth, + cols, + geometry; + + subjectRow.style.display = 'inline'; + subjectRow.innerHTML = 'W'; // Common character for measuring width, although on monospace + characterWidth = subjectRow.getBoundingClientRect().width; + subjectRow.style.display = ''; // Revert style before calculating height, since they differ. + characterHeight = subjectRow.getBoundingClientRect().height; + subjectRow.innerHTML = contentBuffer; + + rows = parseInt(availableHeight / characterHeight); + cols = parseInt(availableWidth / characterWidth); + + geometry = {cols: cols, rows: rows}; + return geometry; + }; + + exports.fit = function (term) { + var geometry = exports.proposeGeometry(term); + + if (geometry) { + term.resize(geometry.cols, geometry.rows); + } + }; + + Xterm.prototype.proposeGeometry = function () { + return exports.proposeGeometry(this); + }; + + Xterm.prototype.fit = function () { + return exports.fit(this); + }; + + return exports; +}); diff --git a/server/www/teleport/static/js/common/addons/fullscreen/fullscreen.css b/server/www/teleport/static/js/common/addons/fullscreen/fullscreen.css new file mode 100644 index 0000000..60e8c51 --- /dev/null +++ b/server/www/teleport/static/js/common/addons/fullscreen/fullscreen.css @@ -0,0 +1,10 @@ +.xterm.fullscreen { + position: fixed; + top: 0; + bottom: 0; + left: 0; + right: 0; + width: auto; + height: auto; + z-index: 255; +} diff --git a/server/www/teleport/static/js/common/addons/fullscreen/fullscreen.js b/server/www/teleport/static/js/common/addons/fullscreen/fullscreen.js new file mode 100644 index 0000000..e8e34ea --- /dev/null +++ b/server/www/teleport/static/js/common/addons/fullscreen/fullscreen.js @@ -0,0 +1,50 @@ +/** + * Fullscreen addon for xterm.js + * @module xterm/addons/fullscreen/fullscreen + * @license MIT + */ +(function (fullscreen) { + if (typeof exports === 'object' && typeof module === 'object') { + /* + * CommonJS environment + */ + module.exports = fullscreen(require('../../xterm')); + } else if (typeof define == 'function') { + /* + * Require.js is available + */ + define(['../../xterm'], fullscreen); + } else { + /* + * Plain browser environment + */ + fullscreen(window.Terminal); + } +})(function (Xterm) { + var exports = {}; + + /** + * Toggle the given terminal's fullscreen mode. + * @param {Xterm} term - The terminal to toggle full screen mode + * @param {boolean} fullscreen - Toggle fullscreen on (true) or off (false) + */ + exports.toggleFullScreen = function (term, fullscreen) { + var fn; + + if (typeof fullscreen == 'undefined') { + fn = (term.element.classList.contains('fullscreen')) ? 'remove' : 'add'; + } else if (!fullscreen) { + fn = 'remove'; + } else { + fn = 'add'; + } + + term.element.classList[fn]('fullscreen'); + }; + + Xterm.prototype.toggleFullscreen = function (fullscreen) { + exports.toggleFullScreen(this, fullscreen); + }; + + return exports; +}); diff --git a/server/www/teleport/static/js/common/addons/linkify/linkify.js b/server/www/teleport/static/js/common/addons/linkify/linkify.js new file mode 100644 index 0000000..d239102 --- /dev/null +++ b/server/www/teleport/static/js/common/addons/linkify/linkify.js @@ -0,0 +1,207 @@ +/** + * Methods for turning URL subscrings in the terminal's content into links (`a` DOM elements). + * @module xterm/addons/linkify/linkify + * @license MIT + */ + +(function (linkify) { + if (typeof exports === 'object' && typeof module === 'object') { + /* + * CommonJS environment + */ + module.exports = linkify(require('../../xterm')); + } else if (typeof define == 'function') { + /* + * Require.js is available + */ + define(['../../xterm'], linkify); + } else { + /* + * Plain browser environment + */ + linkify(window.Terminal); + } +})(function (Xterm) { + 'use strict'; + + var exports = {}, + protocolClause = '(https?:\\/\\/)', + domainCharacterSet = '[\\da-z\\.-]+', + negatedDomainCharacterSet = '[^\\da-z\\.-]+', + domainBodyClause = '(' + domainCharacterSet + ')', + tldClause = '([a-z\\.]{2,6})', + ipClause = '((\\d{1,3}\\.){3}\\d{1,3})', + portClause = '(:\\d{1,5})', + hostClause = '((' + domainBodyClause + '\\.' + tldClause + ')|' + ipClause + ')' + portClause + '?', + pathClause = '(\\/[\\/\\w\\.-]*)*', + negatedPathCharacterSet = '[^\\/\\w\\.-]+', + bodyClause = hostClause + pathClause, + start = '(?:^|' + negatedDomainCharacterSet + ')(', + end = ')($|' + negatedPathCharacterSet + ')', + lenientUrlClause = start + protocolClause + '?' + bodyClause + end, + strictUrlClause = start + protocolClause + bodyClause + end, + lenientUrlRegex = new RegExp(lenientUrlClause), + strictUrlRegex = new RegExp(strictUrlClause); + + /** + * Converts all valid URLs found in the given terminal line into + * hyperlinks. The terminal line can be either the HTML element itself + * or the index of the termina line in the children of the terminal + * rows container. + * + * @param {Xterm} terminal - The terminal that owns the given line. + * @param {number|HTMLDivElement} line - The terminal line that should get + * "linkified". + * @param {boolean} lenient - The regex type that will be used to identify links. If lenient is + * false, the regex requires a protocol clause. Defaults to true. + * @param {string} target - Sets target="" attribute with value provided to links. + * Default doesn't set target attribute + * @emits linkify + * @emits linkify:line + */ + exports.linkifyTerminalLine = function (terminal, line, lenient, target) { + if (typeof line == 'number') { + line = terminal.rowContainer.children[line]; + } else if (! (line instanceof HTMLDivElement)) { + var message = 'The "line" argument should be either a number'; + message += ' or an HTMLDivElement'; + + throw new TypeError(message); + } + + if (typeof target === 'undefined') { + target = ''; + } else { + target = 'target="' + target + '"'; + } + + var buffer = document.createElement('span'), + nodes = line.childNodes; + + for (var j=0; j' + url + '', + newHTML = nodeHTML.replace(url, link); + + line.innerHTML = line.innerHTML.replace(nodeHTML, newHTML); + } + + /** + * This event gets emitted when conversion of all URL susbtrings + * to HTML anchor elements (links) has finished, for a specific + * line of the current Xterm instance. + * + * @event linkify:line + */ + terminal.emit('linkify:line', line); + }; + + /** + * Finds a link within a block of text. + * + * @param {string} text - The text to search . + * @param {boolean} lenient - Whether to use the lenient search. + * @return {string} A URL. + */ + exports.findLinkMatch = function (text, lenient) { + var match = text.match(lenient ? lenientUrlRegex : strictUrlRegex); + if (!match || match.length === 0) { + return null; + } + return match[1]; + } + + /** + * Converts all valid URLs found in the terminal view into hyperlinks. + * + * @param {Xterm} terminal - The terminal that should get "linkified". + * @param {boolean} lenient - The regex type that will be used to identify links. If lenient is + * false, the regex requires a protocol clause. Defaults to true. + * @param {string} target - Sets target="" attribute with value provided to links. + * Default doesn't set target attribute + * @emits linkify + * @emits linkify:line + */ + exports.linkify = function (terminal, lenient, target) { + var rows = terminal.rowContainer.children; + + lenient = (typeof lenient == "boolean") ? lenient : true; + for (var i=0; i> 16) & 0xff, - (color >> 8) & 0xff, - color & 0xff - ]); - } - - return out; -})(); - -/** - * Options - */ - -Terminal.defaults = { - colors: Terminal.colors, - convertEol: false, - termName: 'xterm', - geometry: [80, 24], - cursorBlink: true, - visualBell: false, - popOnBell: false, - scrollback: 1000, - screenKeys: false, - debug: false, - useStyle: false - // programFeatures: false, - // focusKeys: false, -}; - -Terminal.options = {}; - -each(keys(Terminal.defaults), function(key) { - Terminal[key] = Terminal.defaults[key]; - Terminal.options[key] = Terminal.defaults[key]; -}); - -/** - * Focused Terminal - */ - -Terminal.focus = null; - -Terminal.prototype.focus = function() { - if (this._textarea) { - this._textarea.focus(); - } - - if (Terminal.focus === this) return; - - if (Terminal.focus) { - Terminal.focus.blur(); - } - - if (this.sendFocus) this.send('\x1b[I'); - this.showCursor(); - - // try { - // this.element.focus(); - // } catch (e) { - // ; - // } - - // this.emit('focus'); - - Terminal.focus = this; -}; - -Terminal.prototype.blur = function() { - if (Terminal.focus !== this) return; - - this.cursorState = 0; - this.refresh(this.y, this.y); - if (this.sendFocus) this.send('\x1b[O'); - - // try { - // this.element.blur(); - // } catch (e) { - // ; - // } - - // this.emit('blur'); - - Terminal.focus = null; -}; - -/** - * Initialize global behavior - */ - -Terminal.prototype.initGlobal = function() { - var document = this.document; - - Terminal._boundDocs = Terminal._boundDocs || []; - if (~indexOf(Terminal._boundDocs, document)) { - return; - } - Terminal._boundDocs.push(document); - - Terminal.bindPaste(document); - - Terminal.bindKeys(document); - - Terminal.bindCopy(document); - - if (this.useStyle) { - Terminal.insertStyle(document, this.colors[256], this.colors[257]); - } -}; - -/** - * Bind to paste event - */ - -Terminal.bindPaste = function(document) { - // This seems to work well for ctrl-V and middle-click, - // even without the contentEditable workaround. - var window = document.defaultView; - on(window, 'paste', function(ev) { - var term = Terminal.focus; - if (!term) return; - if (term._textarea) return; - if (ev.clipboardData) { - term.send(ev.clipboardData.getData('text/plain')); - } else if (term.context.clipboardData) { - term.send(term.context.clipboardData.getData('Text')); - } - // Not necessary. Do it anyway for good measure. - term.element.contentEditable = 'inherit'; - return cancel(ev); - }); -}; - -/** - * Global Events for key handling - */ - -Terminal.bindKeys = function(document) { - // We should only need to check `target === body` below, - // but we can check everything for good measure. - on(document, 'keydown', function(ev) { - if (!Terminal.focus) return; - var target = ev.target || ev.srcElement; - if (!target) return; - if (target === Terminal.focus.element - || target === Terminal.focus.context - || target === Terminal.focus.document - || target === Terminal.focus.body - || target === Terminal.focus._textarea - || target === Terminal.focus.parent) { - return Terminal.focus.keyDown(ev); - } - }, true); - - on(document, 'keypress', function(ev) { - if (!Terminal.focus) return; - var target = ev.target || ev.srcElement; - if (!target) return; - if (target === Terminal.focus.element - || target === Terminal.focus.context - || target === Terminal.focus.document - || target === Terminal.focus.body - || target === Terminal.focus._textarea - || target === Terminal.focus.parent) { - return Terminal.focus.keyPress(ev); - } - }, true); - - // If we click somewhere other than a - // terminal, unfocus the terminal. - on(document, 'mousedown', function(ev) { - if (!Terminal.focus) return; - - var el = ev.target || ev.srcElement; - if (!el) return; - if (!el.parentNode) return; - if (!el.parentNode.parentNode) return; - - do { - if (el === Terminal.focus.element) return; - } while (el = el.parentNode); - - Terminal.focus.blur(); - }); -}; - -/** - * Copy Selection w/ Ctrl-C (Select Mode) - */ - -Terminal.bindCopy = function(document) { - var window = document.defaultView; - - // if (!('onbeforecopy' in document)) { - // // Copies to *only* the clipboard. - // on(window, 'copy', function fn(ev) { - // var term = Terminal.focus; - // if (!term) return; - // if (!term._selected) return; - // var text = term.grabText( - // term._selected.x1, term._selected.x2, - // term._selected.y1, term._selected.y2); - // term.emit('copy', text); - // ev.clipboardData.setData('text/plain', text); - // }); - // return; - // } - - // Copies to primary selection *and* clipboard. - // NOTE: This may work better on capture phase, - // or using the `beforecopy` event. - on(window, 'copy', function(ev) { - var term = Terminal.focus; - if (!term) return; - if (!term._selected) return; - var textarea = term.getCopyTextarea(); - var text = term.grabText( - term._selected.x1, term._selected.x2, - term._selected.y1, term._selected.y2); - term.emit('copy', text); - textarea.focus(); - textarea.textContent = text; - textarea.value = text; - textarea.setSelectionRange(0, text.length); - setTimeout(function() { - term.element.focus(); - term.focus(); - }, 1); - }); -}; - -/** - * Fix Mobile - */ - -Terminal.prototype.getTextarea = function(document) { - var self = this; - - var textarea = document.createElement('textarea'); - textarea.style.position = 'absolute'; - textarea.style.left = '-32000px'; - textarea.style.top = '-32000px'; - textarea.style.width = '100em'; - textarea.style.height = '2em'; - textarea.style.padding = '0'; - textarea.style.opacity = '0'; - textarea.style.color = 'inherit'; - textarea.style.font = 'inherit'; - textarea.style.textIndent = '-1em'; /* Hide text cursor on IE */ - textarea.style.backgroundColor = 'transparent'; - textarea.style.borderStyle = 'none'; - textarea.style.outlineStyle = 'none'; - textarea.autocapitalize = 'none'; - textarea.autocorrect = 'off'; - - var onInputTimestamp; - - var onInput = function(ev){ - if(ev.timeStamp && ev.timeStamp === onInputTimestamp){ - return; - } - onInputTimestamp = ev.timeStamp; - - var value = textarea.textContent || textarea.value; - if (typeof self.select.startPos !== 'undefined'){ - self.select = {}; - self.clearSelectedText(); - self.refresh(0, self.rows - 1); - } - if (!self.compositionStatus) { - textarea.value = ''; - textarea.textContent = ''; - self.send(value); - } - }; - - on(textarea, 'compositionstart', function() { - textarea.style.opacity = "1.0"; - textarea.style.textIndent = "0"; - self.compositionStatus = true; - }); - on(textarea, 'compositionend', function(ev) { - textarea.style.opacity = "0.0"; - textarea.style.textIndent = "-1em"; - self.compositionStatus = false; - setTimeout(function(){ - onInput(ev); // for IE that does not trigger 'input' after the IME composition. - }, 1); - }); - - on(textarea, 'keydown', function(){ - var value = textarea.textContent || textarea.value; - }); - - on(textarea, 'input', onInput); - - if (Terminal.isAndroid) { - on(textarea, 'change', function() { - var value = textarea.textContent || textarea.value; - textarea.value = ''; - textarea.textContent = ''; - self.send(value + '\r'); - }); - } - return textarea; -}; - -/** - * Insert a default style - */ - -Terminal.insertStyle = function(document, bg, fg) { - var style = document.getElementById('term-style'); - if (style) return; - - var head = document.getElementsByTagName('head')[0]; - if (!head) return; - - var style = document.createElement('style'); - style.id = 'term-style'; - - // textContent doesn't work well with IE for + + +<%block name="breadcrumb"> + + + + +
+
+ + + + + +
+## + 跳过无操作时间 +
+ + 状态:正在获取数据 + 总时长:未知 + +
+ +
+
+ + + + +<%block name="extend_content"> + + + + + +<%block name="embed_js"> + \ No newline at end of file