mirror of https://github.com/tp4a/teleport
web版RDP录像播放,可以播放鼠标移动的录像了,下一步是显示rdp的图像。
parent
9305953218
commit
39c8fd32d5
Binary file not shown.
After Width: | Height: | Size: 1.2 KiB |
Binary file not shown.
After Width: | Height: | Size: 63 KiB |
|
@ -161,10 +161,9 @@ $app.on_table_host_cell_created = function (tbl, row_id, col_key, cell_obj) {
|
||||||
var action = $(this).attr('data-action');
|
var action = $(this).attr('data-action');
|
||||||
|
|
||||||
if (action === 'replay') {
|
if (action === 'replay') {
|
||||||
//$app.dlg_edit_host.show_edit(row_id);
|
|
||||||
if (row_data.protocol_type === TP_PROTOCOL_TYPE_RDP) {
|
if (row_data.protocol_type === TP_PROTOCOL_TYPE_RDP) {
|
||||||
// $tp.notify_error('sorry, not impl.');
|
// $app.do_replay_rdp(row_data.id, row_data.user_username, row_data.acc_username, row_data.host_ip, row_data.time_begin);
|
||||||
$app.do_replay_rdp(row_data.id, row_data.user_username, row_data.acc_username, row_data.host_ip, row_data.time_begin);
|
window.open('/audit/replay/' + row_data.protocol_type + '/' + row_data.id);
|
||||||
} else if (row_data.protocol_type === TP_PROTOCOL_TYPE_SSH) {
|
} else if (row_data.protocol_type === TP_PROTOCOL_TYPE_SSH) {
|
||||||
window.open('/audit/replay/' + row_data.protocol_type + '/' + row_data.id);
|
window.open('/audit/replay/' + row_data.protocol_type + '/' + row_data.id);
|
||||||
}
|
}
|
||||||
|
|
File diff suppressed because one or more lines are too long
|
@ -1,29 +1,5 @@
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
$app.req_record_data = function (record_id, offset) {
|
|
||||||
$tp.ajax_post_json('/audit/get-record-data', {id: record_id, offset: offset},
|
|
||||||
function (ret) {
|
|
||||||
if (ret.code === TPE_OK) {
|
|
||||||
// console.log('data', ret.data);
|
|
||||||
$app.record_data = $app.record_data.concat(ret.data.data_list);
|
|
||||||
$app.record_data_offset += ret.data.data_size;
|
|
||||||
|
|
||||||
if ($app.record_data.length < $app.record_hdr.pkg_count) {
|
|
||||||
$app.req_record_data(record_id, $app.record_data_offset);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
$app.dom.status.text("读取录像数据失败:" + tp_error_msg(ret.code));
|
|
||||||
$tp.notify_error('读取录像数据失败:' + tp_error_msg(ret.code, ret.message));
|
|
||||||
console.log('req_record_info error ', ret.code);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
function () {
|
|
||||||
console.log('req_record_info error');
|
|
||||||
},
|
|
||||||
30 * 1000
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
$app.on_init = function (cb_stack) {
|
$app.on_init = function (cb_stack) {
|
||||||
var record_id = $app.options.record_id;
|
var record_id = $app.options.record_id;
|
||||||
|
|
||||||
|
@ -70,7 +46,7 @@ $app.on_init = function (cb_stack) {
|
||||||
|
|
||||||
Terminal.cursorBlink = false;
|
Terminal.cursorBlink = false;
|
||||||
|
|
||||||
$tp.ajax_post_json('/audit/get-record-header', {id: record_id},
|
$tp.ajax_post_json('/audit/get-record-header', {protocol: TP_PROTOCOL_TYPE_SSH, id: record_id},
|
||||||
function (ret) {
|
function (ret) {
|
||||||
if (ret.code === TPE_OK) {
|
if (ret.code === TPE_OK) {
|
||||||
$app.record_hdr = ret.data;
|
$app.record_hdr = ret.data;
|
||||||
|
@ -184,6 +160,30 @@ $app.on_init = function (cb_stack) {
|
||||||
cb_stack.exec();
|
cb_stack.exec();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
$app.req_record_data = function (record_id, offset) {
|
||||||
|
$tp.ajax_post_json('/audit/get-record-data', {protocol: TP_PROTOCOL_TYPE_SSH, id: record_id, offset: offset},
|
||||||
|
function (ret) {
|
||||||
|
if (ret.code === TPE_OK) {
|
||||||
|
// console.log('data', ret.data);
|
||||||
|
$app.record_data = $app.record_data.concat(ret.data.data_list);
|
||||||
|
$app.record_data_offset += ret.data.data_size;
|
||||||
|
|
||||||
|
if ($app.record_data.length < $app.record_hdr.pkg_count) {
|
||||||
|
$app.req_record_data(record_id, $app.record_data_offset);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$app.dom.status.text("读取录像数据失败:" + tp_error_msg(ret.code));
|
||||||
|
$tp.notify_error('读取录像数据失败:' + tp_error_msg(ret.code, ret.message));
|
||||||
|
console.log('req_record_info error ', ret.code);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
function () {
|
||||||
|
console.log('req_record_info error');
|
||||||
|
},
|
||||||
|
30 * 1000
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
$app.init_and_play = function() {
|
$app.init_and_play = function() {
|
||||||
if (_.isNull($app.player_console_term)) {
|
if (_.isNull($app.player_console_term)) {
|
||||||
$app.player_console_term = new Terminal({
|
$app.player_console_term = new Terminal({
|
||||||
|
|
|
@ -0,0 +1,80 @@
|
||||||
|
<%!
|
||||||
|
page_title_ = '录像回放'
|
||||||
|
%>
|
||||||
|
|
||||||
|
<%inherit file="../page_single_base.mako"/>
|
||||||
|
|
||||||
|
<%block name="extend_js_file">
|
||||||
|
## <script type="text/javascript" src="${ static_url('plugins/xterm/xterm.js') }"></script>
|
||||||
|
|
||||||
|
<script type="text/javascript" src="${ static_url('js/audit/replay-rdp.js') }"></script>
|
||||||
|
</%block>
|
||||||
|
|
||||||
|
<%block name="extend_css_file">
|
||||||
|
## <link href="${ static_url('plugins/xterm/xterm.css') }" rel="stylesheet" type="text/css"/>
|
||||||
|
</%block>
|
||||||
|
|
||||||
|
<%block name="embed_css">
|
||||||
|
<style type="text/css">
|
||||||
|
#screen {
|
||||||
|
margin-top: 5px;
|
||||||
|
##background-color: #547cff;
|
||||||
|
background-color: #000;
|
||||||
|
border: 1px solid #525252;
|
||||||
|
border-right-color: #fff;
|
||||||
|
border-bottom-color: #fff;
|
||||||
|
## padding: 1px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</%block>
|
||||||
|
|
||||||
|
<%block name="page_header">
|
||||||
|
<div class="container-fluid top-navbar">
|
||||||
|
<div class="breadcrumb-container">
|
||||||
|
<ol class="breadcrumb">
|
||||||
|
<li><i class="fa fa-server"></i> ${self.attr.page_title_}</li>
|
||||||
|
<li class="sub-title"><span id="recorder-info"></span></li>
|
||||||
|
</ol>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</%block>
|
||||||
|
|
||||||
|
|
||||||
|
<div class="page-content">
|
||||||
|
<div id="toolbar" style="display: inline-block;">
|
||||||
|
<button id="btn-play" type="button" class="btn btn-primary btn-sm" style="width:80px;"><i class="fa fa-pause fa-fw"> 暂停</i></button>
|
||||||
|
<button id="btn-restart" type="button" class="btn btn-success btn-sm"><i class="fa fa-refresh fa-fw"></i> 重新播放</button>
|
||||||
|
|
||||||
|
<button id="btn-speed" type="button" class="btn btn-info btn-sm" style="width:80px;">正常速度</button>
|
||||||
|
|
||||||
|
<button id="btn-big-font" type="button" class="btn btn-default btn-sm"><i class="fa fa-font fa-fw"></i>+</button>
|
||||||
|
<button id="btn-small-font" type="button" class="btn btn-default btn-sm"><i class="fa fa-font fa-fw"></i>-</button>
|
||||||
|
|
||||||
|
<div style="display:inline-block;position:relative;top:4px;margin-left:10px;margin-right:15px;">
|
||||||
|
<span id="btn-skip" style="cursor:pointer;"><i class="fa fa-check-square-o fa-fw"></i> 跳过无操作时间</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<span id="play-status" class="badge badge-normal" style="margin-left:5px;">正在获取数据</span>
|
||||||
|
<span id="play-time" class="badge badge-success" style="margin-left:5px;">总时长:未知</span>
|
||||||
|
</div>
|
||||||
|
<input id="progress" type="range" value="0" min=0 max=100 style="margin-top: 10px;"/>
|
||||||
|
## <div id="xterm-box"></div>
|
||||||
|
|
||||||
|
<div id="screen"></div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<%block name="extend_content">
|
||||||
|
|
||||||
|
</%block>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<%block name="embed_js">
|
||||||
|
<script type="text/javascript">
|
||||||
|
$app.add_options(${page_param});
|
||||||
|
</script>
|
||||||
|
</%block>
|
|
@ -494,7 +494,8 @@ class ReplayHandler(TPBaseHandler):
|
||||||
def get(self, protocol, record_id):
|
def get(self, protocol, record_id):
|
||||||
protocol = int(protocol)
|
protocol = int(protocol)
|
||||||
if protocol == TP_PROTOCOL_TYPE_RDP:
|
if protocol == TP_PROTOCOL_TYPE_RDP:
|
||||||
return
|
param = {'record_id': record_id}
|
||||||
|
self.render('audit/replay-rdp.mako', page_param=json.dumps(param))
|
||||||
elif protocol == TP_PROTOCOL_TYPE_SSH:
|
elif protocol == TP_PROTOCOL_TYPE_SSH:
|
||||||
param = {'record_id': record_id}
|
param = {'record_id': record_id}
|
||||||
self.render('audit/replay-ssh.mako', page_param=json.dumps(param))
|
self.render('audit/replay-ssh.mako', page_param=json.dumps(param))
|
||||||
|
@ -603,12 +604,13 @@ class DoGetRecordHeaderHandler(TPBaseJsonHandler):
|
||||||
return self.write_json(TPE_JSON_FORMAT)
|
return self.write_json(TPE_JSON_FORMAT)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
protocol_type = int(args['protocol'])
|
||||||
record_id = int(args['id'])
|
record_id = int(args['id'])
|
||||||
except:
|
except:
|
||||||
log.e('\n')
|
log.e('\n')
|
||||||
return self.write_json(TPE_PARAM)
|
return self.write_json(TPE_PARAM)
|
||||||
|
|
||||||
header, err = record.read_record_head(record_id)
|
header, err = record.read_record_head(protocol_type, record_id)
|
||||||
if header is None:
|
if header is None:
|
||||||
return self.write_json(err)
|
return self.write_json(err)
|
||||||
|
|
||||||
|
@ -627,13 +629,19 @@ class DoGetRecordDataHandler(TPBaseJsonHandler):
|
||||||
return self.write_json(TPE_JSON_FORMAT)
|
return self.write_json(TPE_JSON_FORMAT)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
protocol_type = int(args['protocol'])
|
||||||
record_id = int(args['id'])
|
record_id = int(args['id'])
|
||||||
offset = int(args['offset'])
|
offset = int(args['offset'])
|
||||||
except:
|
except:
|
||||||
log.e('\n')
|
log.e('\n')
|
||||||
return self.write_json(TPE_PARAM)
|
return self.write_json(TPE_PARAM)
|
||||||
|
|
||||||
data_list, data_size, err = record.read_record_data(record_id, offset)
|
if protocol_type == TP_PROTOCOL_TYPE_RDP:
|
||||||
|
data_list, data_size, err = record.read_rdp_record_data(record_id, offset)
|
||||||
|
elif protocol_type == TP_PROTOCOL_TYPE_RDP:
|
||||||
|
data_list, data_size, err = record.read_ssh_record_data(record_id, offset)
|
||||||
|
else:
|
||||||
|
self.write_json(TPE_NOT_EXISTS)
|
||||||
self.write_json(err, data={'data_list': data_list, 'data_size': data_size})
|
self.write_json(err, data={'data_list': data_list, 'data_size': data_size})
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -114,12 +114,26 @@ def get_records(handler, sql_filter, sql_order, sql_limit, sql_restrict, sql_exc
|
||||||
return err, s.total_count, s.recorder
|
return err, s.total_count, s.recorder
|
||||||
|
|
||||||
|
|
||||||
def read_record_head(record_id):
|
def _remove_padding_space(s):
|
||||||
|
r = []
|
||||||
|
for i in range(len(s)):
|
||||||
|
if s[i] == 0x00:
|
||||||
|
break
|
||||||
|
r.append(s[i])
|
||||||
|
return bytearray(r)
|
||||||
|
|
||||||
|
|
||||||
|
def read_record_head(protocol_type, record_id):
|
||||||
if not tp_cfg().core.detected:
|
if not tp_cfg().core.detected:
|
||||||
return None, TPE_NO_CORE_SERVER
|
return None, TPE_NO_CORE_SERVER
|
||||||
|
|
||||||
record_path = os.path.join(tp_cfg().core.replay_path, 'ssh', '{:09d}'.format(int(record_id)))
|
if protocol_type == TP_PROTOCOL_TYPE_RDP:
|
||||||
header_file_path = os.path.join(record_path, 'tp-ssh.tpr')
|
path_name = 'rdp'
|
||||||
|
elif protocol_type == TP_PROTOCOL_TYPE_SSH:
|
||||||
|
path_name = 'ssh'
|
||||||
|
|
||||||
|
record_path = os.path.join(tp_cfg().core.replay_path, path_name, '{:09d}'.format(int(record_id)))
|
||||||
|
header_file_path = os.path.join(record_path, 'tp-{}.tpr'.format(path_name))
|
||||||
|
|
||||||
if not os.path.exists(header_file_path):
|
if not os.path.exists(header_file_path):
|
||||||
return None, TPE_NOT_EXISTS
|
return None, TPE_NOT_EXISTS
|
||||||
|
@ -156,22 +170,22 @@ def read_record_head(record_id):
|
||||||
# offset += 4
|
# offset += 4
|
||||||
|
|
||||||
user_name, = struct.unpack_from('64s', data, offset)
|
user_name, = struct.unpack_from('64s', data, offset)
|
||||||
user_name = user_name.decode()
|
user_name = _remove_padding_space(user_name).decode()
|
||||||
offset += 64
|
offset += 64
|
||||||
account, = struct.unpack_from('64s', data, offset)
|
account, = struct.unpack_from('64s', data, offset)
|
||||||
account = account.decode()
|
account = _remove_padding_space(account).decode()
|
||||||
offset += 64
|
offset += 64
|
||||||
|
|
||||||
host_ip, = struct.unpack_from('40s', data, offset)
|
host_ip, = struct.unpack_from('40s', data, offset)
|
||||||
host_ip = host_ip.decode()
|
host_ip = _remove_padding_space(host_ip).decode()
|
||||||
offset += 40
|
offset += 40
|
||||||
conn_ip, = struct.unpack_from('40s', data, offset)
|
conn_ip, = struct.unpack_from('40s', data, offset)
|
||||||
conn_ip = conn_ip.decode()
|
conn_ip = _remove_padding_space(conn_ip).decode()
|
||||||
offset += 40
|
offset += 40
|
||||||
conn_port, = struct.unpack_from('H', data, offset)
|
conn_port, = struct.unpack_from('H', data, offset)
|
||||||
offset += 2
|
offset += 2
|
||||||
client_ip, = struct.unpack_from('40s', data, offset)
|
client_ip, = struct.unpack_from('40s', data, offset)
|
||||||
client_ip = client_ip.decode()
|
client_ip = _remove_padding_space(client_ip).decode()
|
||||||
offset += 40
|
offset += 40
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
@ -197,11 +211,82 @@ def read_record_head(record_id):
|
||||||
return header, TPE_OK
|
return header, TPE_OK
|
||||||
|
|
||||||
|
|
||||||
def read_record_data(record_id, offset):
|
def read_rdp_record_data(record_id, offset):
|
||||||
|
if not tp_cfg().core.detected:
|
||||||
|
return None, TPE_NO_CORE_SERVER
|
||||||
|
|
||||||
|
record_path = os.path.join(tp_cfg().core.replay_path, 'rdp', '{:09d}'.format(int(record_id)))
|
||||||
|
file_data = os.path.join(record_path, 'tp-rdp.dat')
|
||||||
|
|
||||||
|
if not os.path.exists(file_data):
|
||||||
|
return None, 0, TPE_NOT_EXISTS
|
||||||
|
|
||||||
|
data_list = list()
|
||||||
|
data_size = 0
|
||||||
|
file = None
|
||||||
|
try:
|
||||||
|
file_size = os.path.getsize(file_data)
|
||||||
|
if offset >= file_size:
|
||||||
|
return None, 0, TPE_FAILED
|
||||||
|
|
||||||
|
file = open(file_data, 'rb')
|
||||||
|
if offset > 0:
|
||||||
|
file.seek(offset, io.SEEK_SET)
|
||||||
|
|
||||||
|
# read 1000 packages one time from offset.
|
||||||
|
for i in range(1000):
|
||||||
|
"""
|
||||||
|
// 一个数据包的头
|
||||||
|
typedef struct TS_RECORD_PKG
|
||||||
|
{
|
||||||
|
ex_u8 type; // 包的数据类型
|
||||||
|
ex_u32 size; // 这个包的总大小(不含包头)
|
||||||
|
ex_u32 time_ms; // 这个包距起始时间的时间差(毫秒,意味着一个连接不能持续超过49天)
|
||||||
|
ex_u8 _reserve[3]; // 保留
|
||||||
|
}TS_RECORD_PKG;
|
||||||
|
"""
|
||||||
|
_data = file.read(12)
|
||||||
|
data_size += 12
|
||||||
|
_action, _size, _time, = struct.unpack_from('=BII', _data)
|
||||||
|
if offset + data_size + _size > file_size:
|
||||||
|
return None, 0, TPE_FAILED
|
||||||
|
|
||||||
|
_data = file.read(_size)
|
||||||
|
data_size += _size
|
||||||
|
|
||||||
|
temp = dict()
|
||||||
|
temp['a'] = _action
|
||||||
|
temp['t'] = _time
|
||||||
|
if _action == 0x10:
|
||||||
|
# this is mouse movement event.
|
||||||
|
x, y = struct.unpack_from('HH', _data)
|
||||||
|
temp['x'] = x
|
||||||
|
temp['y'] = y
|
||||||
|
elif _action == 0x11:
|
||||||
|
# this is a data package.
|
||||||
|
_data = base64.b64encode(_data)
|
||||||
|
temp['d'] = _data.decode()
|
||||||
|
else:
|
||||||
|
return None, 0, TPE_FAILED
|
||||||
|
|
||||||
|
data_list.append(temp)
|
||||||
|
if offset + data_size == file_size:
|
||||||
|
break
|
||||||
|
|
||||||
|
except Exception:
|
||||||
|
log.e('failed to read record file: {}\n'.format(file_data))
|
||||||
|
return None, 0, TPE_FAILED
|
||||||
|
finally:
|
||||||
|
if file is not None:
|
||||||
|
file.close()
|
||||||
|
|
||||||
|
return data_list, data_size, TPE_OK
|
||||||
|
|
||||||
|
|
||||||
|
def read_ssh_record_data(record_id, offset):
|
||||||
if not tp_cfg().core.detected:
|
if not tp_cfg().core.detected:
|
||||||
return None, TPE_NO_CORE_SERVER
|
return None, TPE_NO_CORE_SERVER
|
||||||
|
|
||||||
# read 1000 packages one time from offset.
|
|
||||||
record_path = os.path.join(tp_cfg().core.replay_path, 'ssh', '{:09d}'.format(int(record_id)))
|
record_path = os.path.join(tp_cfg().core.replay_path, 'ssh', '{:09d}'.format(int(record_id)))
|
||||||
file_data = os.path.join(record_path, 'tp-ssh.dat')
|
file_data = os.path.join(record_path, 'tp-ssh.dat')
|
||||||
|
|
||||||
|
@ -220,6 +305,7 @@ def read_record_data(record_id, offset):
|
||||||
if offset > 0:
|
if offset > 0:
|
||||||
file.seek(offset, io.SEEK_SET)
|
file.seek(offset, io.SEEK_SET)
|
||||||
|
|
||||||
|
# read 1000 packages one time from offset.
|
||||||
for i in range(1000):
|
for i in range(1000):
|
||||||
"""
|
"""
|
||||||
// 一个数据包的头
|
// 一个数据包的头
|
||||||
|
|
Loading…
Reference in New Issue