pull/350/head
midoks 2 years ago
parent 2c3ad7ea99
commit 832f3b45eb
  1. 446
      class/core/ssh_terminal.py
  2. 232
      plugins/webssh/js/webssh.js
  3. 4
      plugins/webssh/menu/index.css
  4. 4
      plugins/webssh/menu/index.html
  5. 49
      route/__init__.py
  6. 143
      route/static/js/term-websocketio.js
  7. 2
      route/static/js/term.js
  8. 2
      route/templates/default/layout.html

@ -0,0 +1,446 @@
# coding: utf-8
# ---------------------------------------------------------------------------------
# MW-Linux面板
# ---------------------------------------------------------------------------------
# copyright (c) 2018-∞(https://github.com/midoks/mdserver-web) All rights reserved.
# ---------------------------------------------------------------------------------
# Author: midoks <midoks@163.com>
# ---------------------------------------------------------------------------------
# ---------------------------------------------------------------------------------
# 公共操作
# ---------------------------------------------------------------------------------
import json
import time
import os
import sys
import socket
import threading
import re
from io import BytesIO, StringIO
import mw
import paramiko
from flask_socketio import SocketIO, emit, send
class ssh_terminal:
__debug_file = 'logs/terminal.log'
__log_type = 'SSH终端'
# websocketio 唯一标识
__sid = ''
__host = None
__port = 22
__user = None
__pass = None
__pkey = None
__key_passwd = None
__rep_ssh_config = False
__rep_ssh_service = False
__sshd_config_backup = None
__connecting = False
__ssh = None
__tp = None
__ps = None
def __init__(self):
pass
def debug(self, msg):
msg = "{} - {}:{} => {} \n".format(mw.formatDate(),
self.__host, self.__port, msg)
# self.history_send(msg)
mw.writeFile(self.__debug_file, msg, 'a+')
def returnMsg(self, status, msg):
return {'status': status, 'msg': msg}
def restartSsh(self, act='reload'):
'''
重启ssh 无参数传递
'''
version = mw.readFile('/etc/redhat-release')
if not os.path.exists('/etc/redhat-release'):
mw.execShell('service ssh ' + act)
elif version.find(' 7.') != -1 or version.find(' 8.') != -1:
mw.execShell("systemctl " + act + " sshd.service")
else:
mw.execShell("/etc/init.d/sshd " + act)
def isRunning(self, rep=False):
try:
if rep and self.__rep_ssh_service:
self.restartSsh('stop')
return True
status = self.getSshStatus()
if not status:
self.restartSsh('start')
self.__rep_ssh_service = True
return True
return False
except:
return False
def setSshdConfig(self, rep=False):
self.isRunning(rep)
if rep and not self.__rep_ssh_config:
return False
try:
sshd_config_file = '/etc/ssh/sshd_config'
if not os.path.exists(sshd_config_file):
return False
sshd_config = mw.readFile(sshd_config_file)
if not sshd_config:
return False
if rep:
if self.__sshd_config_backup:
mw.writeFile(sshd_config_file, self.__sshd_config_backup)
self.restartSsh()
return True
pin = r'^\s*PubkeyAuthentication\s+(yes|no)'
pubkey_status = re.findall(pin, sshd_config, re.I)
if pubkey_status:
if pubkey_status[0] == 'yes':
pubkey_status = True
else:
pubkey_status = False
pin = r'^\s*RSAAuthentication\s+(yes|no)'
rsa_status = re.findall(pin, sshd_config, re.I)
if rsa_status:
if rsa_status[0] == 'yes':
rsa_status = True
else:
rsa_status = False
self._sshd_config_backup = sshd_config
is_write = False
if not pubkey_status:
sshd_config = re.sub(
r'\n#?PubkeyAuthentication\s\w+', '\nPubkeyAuthentication yes', sshd_config)
is_write = True
if not rsa_status:
sshd_config = re.sub(
r'\n#?RSAAuthentication\s\w+', '\nRSAAuthentication yes', sshd_config)
is_write = True
if is_write:
mw.writeFile(sshd_config_file, sshd_config)
self.__rep_ssh_config = True
self.restartSsh()
else:
self.__sshd_config_backup = None
return True
except:
return False
def setSid(self, sid):
self.__sid = sid
def connect(self):
# self.connectBySocket()
if self.__host in ['127.0.0.1', 'localhost']:
return self.connectLocalSsh()
else:
return self.connectBySocket()
def connectLocalSsh(self):
self.createSshInfo()
self.__ps = paramiko.SSHClient()
self.__ps.set_missing_host_key_policy(paramiko.AutoAddPolicy())
self.__port = mw.getSSHPort()
try:
self.__ps.connect(self.__host, self.__port, timeout=60)
except Exception as e:
self.__ps.connect('127.0.0.1', self.__port)
except Exception as e:
self.__ps.connect('localhost', self.__port)
except Exception as e:
self.setSshdConfig(True)
self.__ps.close()
e = str(e)
if e.find('websocket error!') != -1:
return self.returnMsg(True, '连接成功')
if e.find('Authentication timeout') != -1:
self.debug("认证超时{}".format(e))
return self.returnMsg(False, '认证超时,请按回车重试!{}'.format(e))
if e.find('Connection reset by peer') != -1:
self.debug('目标服务器主动拒绝连接')
return self.returnMsg(False, '目标服务器主动拒绝连接')
if e.find('Error reading SSH protocol banner') != -1:
self.debug('协议头响应超时')
return self.returnMsg(False, '协议头响应超时,与目标服务器之间的网络质量太糟糕:' + e)
if not e:
self.debug('SSH协议握手超时')
return self.returnMsg(False, "SSH协议握手超时,与目标服务器之间的网络质量太糟糕")
err = mw.getTracebackInfo()
self.debug(err)
return self.returnMsg(False, "未知错误: {}".format(err))
self.debug('local-ssh:认证成功,正在构建会话通道')
self.__ssh = self.__ps.invoke_shell(term='xterm', width=83, height=21)
self.__ssh.setblocking(0)
self.__connect_time = time.time()
self.__last_send = []
mw.writeLog(self.__log_type, '成功登录到SSH服务器 [{}:{}]'.format(
self.__host, self.__port))
self.debug('local-ssh:通道已构建')
return self.returnMsg(True, '连接成功!')
def connectBySocket(self):
if not self.__host:
return self.returnMsg(False, '错误的连接地址')
if not self.__user:
self.__user = 'root'
if not self.__port:
self.__port = 22
if self.__host in ['127.0.0.1', 'localhost']:
self.__port = mw.getSSHPort()
self.setSshdConfig(True)
num = 0
while num < 5:
num += 1
try:
self.debug('正在尝试第{}次连接'.format(num))
if self.__rep_ssh_config:
time.sleep(0.1)
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.settimeout(2 + num)
sock.setsockopt(socket.SOL_SOCKET, socket.SO_SNDBUF, 8192)
sock.connect((self.__host, self.__port))
break
except Exception as e:
if num == 5:
self.setSshdConfig(True)
self.debug('重试连接失败,{}'.format(e))
if self.__host in ['127.0.0.1', 'localhost']:
return self.returnMsg(False, '连接目标服务器失败: {}'.format("Authentication failed ," + self.__user + "@" + self.__host + ":" + str(self.__port)))
return self.returnMsg(False, '连接目标服务器失败, {}:{}'.format(self.__host, self.__port))
else:
time.sleep(0.2)
# print(sock)
self.__tp = paramiko.Transport(sock)
try:
self.__tp.start_client()
self.__tp.banner_timeout = 60
if not self.__pass or not self.__pkey:
return self.returnMsg(False, '密码或私钥不能都为空: {}:{}'.format(self.__host, self.__port))
if self.__pkey:
self.debug('正在认证私钥')
if sys.version_info[0] == 2:
try:
self.__pkey = self.__pkey.encode('utf-8')
except:
pass
p_file = BytesIO(self.__pkey)
else:
p_file = StringIO(self.__pkey)
try:
pkey = paramiko.RSAKey.from_private_key(p_file)
except:
try:
p_file.seek(0) # 重置游标
pkey = paramiko.Ed25519Key.from_private_key(p_file)
except:
try:
p_file.seek(0)
pkey = paramiko.ECDSAKey.from_private_key(
p_file)
except:
p_file.seek(0)
pkey = paramiko.DSSKey.from_private_key(p_file)
self.__tp.auth_publickey(username=self.__user, key=pkey)
else:
try:
self.__tp.auth_none(self.__user)
except Exception as e:
e = str(e)
if e.find('keyboard-interactive') >= 0:
self._auth_interactive()
else:
self.debug('正在认证密码')
self.__tp.auth_password(
username=self.__user, password=self.__pass)
except Exception as e:
self.setSshdConfig(True)
self.__tp.close()
e = str(e)
if e.find('websocket error!') != -1:
return self.returnMsg(True, '连接成功')
if e.find('Authentication timeout') != -1:
self.debug("认证超时{}".format(e))
return self.returnMsg(False, '认证超时,请按回车重试!{}'.format(e))
if e.find('Authentication failed') != -1:
self.debug('认证失败{}'.format(e))
return self.returnMsg(False, '帐号或密码错误: {}'.format(e + "," + self.__user + "@" + self.__host + ":" + str(self.__port)))
if e.find('Bad authentication type; allowed types') != -1:
self.debug('认证失败{}'.format(e))
if self.__host in ['127.0.0.1', 'localhost'] and self.__pass == 'none':
return self.returnMsg(False, '帐号或密码错误: {}'.format("Authentication failed ," + self.__user + "@" + self.__host + ":" + str(self.__port)))
return self.returnMsg(False, '不支持的身份验证类型: {}'.format(e))
if e.find('Connection reset by peer') != -1:
self.debug('目标服务器主动拒绝连接')
return self.returnMsg(False, '目标服务器主动拒绝连接')
if e.find('Error reading SSH protocol banner') != -1:
self.debug('协议头响应超时')
return self.returnMsg(False, '协议头响应超时,与目标服务器之间的网络质量太糟糕:' + e)
if not e:
self.debug('SSH协议握手超时')
return self.returnMsg(False, "SSH协议握手超时,与目标服务器之间的网络质量太糟糕")
err = mw.getTracebackInfo()
self.debug(err)
return self.returnMsg(False, "未知错误: {}".format(err))
self.debug('认证成功,正在构建会话通道')
self.__ssh = self.__tp.open_session()
self.__ssh.get_pty(term='xterm', width=100, height=34)
self.__ssh.invoke_shell()
self.__connect_time = time.time()
self.__last_send = []
mw.writeLog(self.__log_type, '成功登录到SSH服务器 [{}:{}]'.format(
self.__host, self.__port))
self.debug('通道已构建')
return self.returnMsg(True, '连接成功.')
def setAttr(self, info):
self.__host = info['host'].strip()
if 'port' in info:
self.__port = int(info['port'])
if 'username' in info:
self.__user = info['username']
if 'pkey' in info:
self.__pkey = info['pkey']
if 'password' in info:
self.__pass = info['password']
if 'pkey_passwd' in info:
self.__key_passwd = info['pkey_passwd']
try:
result = self.connect()
except Exception as ex:
if str(ex).find("NoneType") == -1:
raise ex
return result
def send(self):
pass
def close(self):
try:
if self.__ssh:
self.__ssh.close()
if self.__tp: # 关闭宿主服务
self.__tp.close()
if self.__ps:
self.__ps.close()
except:
pass
def heartbeat(self):
while True:
time.sleep(0.1)
if self.__tp and self.__tp.is_active():
self.__tp.send_ignore()
else:
break
if self.__ps and self.__ps.is_active():
self.__ps.send_ignore()
else:
break
def wsSend(self, recv):
try:
t = recv.decode("utf-8")
return emit('server_response', {'data': t})
except Exception as e:
return emit('server_response', {'data': recv})
def run(self, sid, info):
self.__sid = sid
if not self.__sid:
return self.wsSend('WebSocketIO无效')
if self.__connecting and not 'host' in info:
return
if not self.__ssh:
self.__connecting = True
result = self.setAttr(info)
self.__connecting = False
else:
result = self.returnMsg(True, '已连接')
if result['status']:
if type(info) == str:
time.sleep(0.1)
self.__ssh.send(info)
try:
time.sleep(0.005)
recv = self.__ssh.recv(8192)
return self.wsSend(recv)
except Exception as ex:
return self.wsSend('')
else:
return self.wsSend(result['msg'])
def getSshDir(self):
if mw.isAppleSystem():
user = mw.execShell(
"who | sed -n '2, 1p' |awk '{print $1}'")[0].strip()
return '/Users/' + user + '/.ssh'
return '/root/.ssh'
def createRsa(self):
ssh_dir = self.getSshDir()
ssh_ak = ssh_dir + '/authorized_keys'
if not os.path.exists(ssh_ak):
mw.execShell('touch ' + ssh_ak)
if not os.path.exists(ssh_dir + '/id_rsa.pub') and os.path.exists(ssh_dir + '/id_rsa'):
cmd = 'echo y | ssh-keygen -q -t rsa -P "" -f ' + ssh_dir + '/id_rsa'
mw.execShell(cmd)
else:
cmd = 'ssh-keygen -q -t rsa -P "" -f ' + ssh_dir + '/id_rsa'
mw.execShell(cmd)
cmd = 'cat ' + ssh_dir + '/id_rsa.pub >> ' + ssh_dir + '/authorized_keys'
mw.execShell(cmd)
cmd = 'chmod 600 ' + ssh_dir + '/authorized_keys'
mw.execShell(cmd)
def createSshInfo(self):
ssh_dir = self.getSshDir()
if not os.path.exists(ssh_dir + '/id_rsa') or not os.path.exists(ssh_dir + '/id_rsa.pub'):
self.createRsa()
# 检查是否写入authorized_keys
cmd = "cat " + ssh_dir + "/id_rsa.pub | awk '{print $3}'"
data = mw.execShell(cmd)
if data[0] != "":
cmd = "cat " + ssh_dir + "/authorized_keys | grep " + data[0]
ak_data = mw.execShell(cmd)
if ak_data[0] == "":
cmd = 'cat ' + ssh_dir + '/id_rsa.pub >> ' + ssh_dir + '/authorized_keys'
mw.execShell(cmd)
cmd = 'chmod 600 ' + ssh_dir + '/authorized_keys'
mw.execShell(cmd)

@ -1,6 +1,6 @@
//全局
var host_ssh_list = [];
function appPost(method,args,callback){
var _args = null;
@ -110,8 +110,30 @@ function webShell_Load(){
webShell_cmd();
});
// 切换服务器终端视图
$('.term_item_tab .list').on('click', 'span.item', function (ev) {
var index = $(this).index(), data = $(this).data();
if ($(this).hasClass('addServer')) {
} else if ($(this).hasClass('tab_tootls')) {
} else {
$(this).addClass('active').siblings().removeClass('active');
$('.term_content_tab .term_item:eq(' + index + ')').addClass('active').siblings().removeClass('active');
var item = host_ssh_list[data.id];
// item.term.focus();
// item.term.FitAddon.fit();
// item.resize({ cols: item.term.cols, rows: item.term.rows });
}
});
$('.term_item_tab').on('click', '.icon-trem-close', function () {
var id = $(this).parent().data('id');
webShell_removeTermView(id);
})
//服务器列表和常用命令
webShell_getHostList();//默认调用
webShell_getHostList();
//服务器列表和命令切换
$('.term_tootls .tab-nav span').click(function(){
var list_type = $(this).attr('data-type');
if (!$(this).hasClass('on')){
@ -243,155 +265,46 @@ function webShell_getCmdList(){
function webShell_Menu2(){
new Terms('#ECFEfRWM8', { ssh_info: { host: "127.0.0.1", ps: "22", id: 'ECFEfRWM8' } });
var random = 'localhost';
host_ssh_list[random] = new Terms_WebSocketIO('#'+random, { ssh_info: { host: "127.0.0.1", ps: "22", id: random } });
}
function webShell_Menu() {
var termCols = 83;
var termRows = 20;
var sendTotal = 0;
if(!socket)socket = io.connect();
var term = new Terminal({ cols: termCols, rows: termRows, screenKeys: true, useStyle: true});
term.open();
term.setOption('cursorBlink', true);
term.setOption('fontSize', 14);
gterm = term;
socket.on('server_response', function (data) {
term.write(data.data);
if (data.data == '\r\n登出\r\n' ||
data.data == '登出\r\n' ||
data.data == '\r\nlogout\r\n' ||
data.data == 'logout\r\n') {
setTimeout(function () {
layer.closeAll();
term.destroy();
clearInterval(interval);
}, 500);
function webShell_openTermView(info) {
console.log(info);
if (typeof info === "undefined") {
info = { host: '127.0.0.1', ps: '本地服务器' }
}
});
$(window).unload(function(){
  term.destroy();
clearInterval(interval);
});
if (socket) {
socket.emit('connect_event', '');
interval = setInterval(function () {
socket.emit('connect_event', '');
}, 1000);
var random = getRandomString(9);
var tab_content = $('.term_content_tab');
var item_list = $('.term_item_tab .list');
tab_content.find('.term_item').removeClass('active').siblings().removeClass('active');
tab_content.append('<div class="term_item active" id="' + random + '" data-host="' + info.host + '"></div>');
item_list.find('.item').removeClass('active');
if (info.ps == ''){
info.ps = info.host;
}
item_list.append('<span class="active item ' + (info.host == '127.0.0.1' ? 'localhost_item' : '') + '" data-host="' + info.host + '" data-id="' + random + '"><i class="icon icon-sucess"></i><div class="content"><span>' + info.ps + '</span></div><span class="icon-trem-close"></span></span>');
host_ssh_list[random] = new Terms_WebSocketIO('#' + random, { ssh_info: { host: info.host, ps: info.ps, id: random } });
}
term.on('data', function (data) {
socket.emit('webssh', data);
});
$(".shell_btn_close").click(function(){
layer.close(term_box);
term.destroy();
clearInterval(interval);
})
setTimeout(function () {
$('.terminal').detach().appendTo('#ECFEfRWM8');
$("#ECFEfRWM8").show();
socket.emit('webssh', "\n");
term.focus();
// 鼠标右键事件
var can = $("#term");
can.contextmenu(function (e) {
var winWidth = can.width();
var winHeight = can.height();
var mouseX = e.pageX;
var mouseY = e.pageY;
var menuWidth = $(".contextmenu").width();
var menuHeight = $(".contextmenu").height();
var minEdgeMargin = 10;
if (mouseX + menuWidth + minEdgeMargin >= winWidth &&
mouseY + menuHeight + minEdgeMargin >= winHeight) {
menuLeft = mouseX - menuWidth - minEdgeMargin + "px";
menuTop = mouseY - menuHeight - minEdgeMargin + "px";
function webShell_removeTermView(id){
var item = $('[data-id="' + id + '"]'), next = item.next(), prev = item.prev();
$('#' + id).remove();
item.remove();
try {
host_ssh_list[id].close();
} catch (error) {
}
else if (mouseX + menuWidth + minEdgeMargin >= winWidth) {
menuLeft = mouseX - menuWidth - minEdgeMargin + "px";
menuTop = mouseY + minEdgeMargin + "px";
}
else if (mouseY + menuHeight + minEdgeMargin >= winHeight) {
menuLeft = mouseX + minEdgeMargin + "px";
menuTop = mouseY - menuHeight - minEdgeMargin + "px";
}
else {
menuLeft = mouseX + minEdgeMargin + "px";
menuTop = mouseY + minEdgeMargin + "px";
};
var selectText = term.getSelection()
var style_str = '';
var paste_str = '';
if (!selectText) {
if (!getCookie('shell_copy_body')) {
paste_str = 'style="color: #bbb;" disable';
}
style_str = 'style="color: #bbb;" disable';
} else {
setCookie('ssh_selection', selectText);
}
var menudiv = '<ul class="contextmenu">\
<li><a class="shell_copy_btn menu_ssh" data-clipboard-text="'+ selectText + '" ' + style_str + '>复制到剪切板</a></li>\
<li><a onclick="shell_paste_text()" '+ paste_str+'>粘贴选中项</a></li>\
<li><a onclick="shell_to_baidu()" ' + style_str + '>百度搜索</a></li>\
</ul>';
$("body").append(menudiv);
$(".contextmenu").css({
"left": menuLeft,
"top": menuTop
});
return false;
});
can.click(function () {
remove_ssh_menu();
});
clipboard = new ClipboardJS('.shell_copy_btn');
clipboard.on('success', function (e) {
layer.msg('复制成功!');
setCookie('shell_copy_body', e.text)
remove_ssh_menu();
term.focus();
});
clipboard.on('error', function (e) {
layer.msg('复制失败,浏览器不兼容!');
setCookie('shell_copy_body', e.text)
remove_ssh_menu();
term.focus();
});
$(".shellbutton").click(function () {
var tobj = $("textarea[name='ssh_copy']");
var ptext = tobj.val();
tobj.val('');
if ($(this).text().indexOf('Alt') != -1) {
ptext +="\n";
delete host_ssh_list[id];
if (item.hasClass('active')) {
if (next.length > 0) {
next.click();
} else {
prev.click();
}
socket.emit('webssh', ptext);
term.focus();
})
$("textarea[name='ssh_copy']").keydown(function (e) {
if (e.ctrlKey && e.keyCode == 13) {
$(".shell_btn_1").click();
} else if (e.altKey && e.keyCode == 13) {
$(".shell_btn_1").click();
}
});
}, 100);
}
function webShell_getHostList(info){
@ -401,7 +314,7 @@ function webShell_getHostList(info){
var tli = '';
for (var i = 0; i < alist.length; i++) {
tli+='<li class="data-host-list" data-index="'+i+'">\
tli+='<li class="data-host-list" data-index="'+i+'" data-host="'+alist[i]['host']+'">\
<i></i>\
<span>'+alist[i]['host']+'</span>\
<span class="tootls">\
@ -430,11 +343,42 @@ function webShell_getHostList(info){
});
});
$('.tootls_host_list').on('click', 'li', function (e) {
var index = $(this).index();
var host = $(this).data('host');
$(this).find('i').addClass('active');
if ($('.item[data-host="' + host + '"]').length > 0) {
layer.msg('已经打开!', { icon: 0, time: 3000 })
// layer.msg('如需多会话窗口,请右键终端标题复制会话!', { icon: 0, time: 3000 });
} else {
webShell_openTermView(alist[index]);
}
});
// tab切换
$('.term_tootls .tab-nav span').click(function () {
if ($(this).hasClass('on')) return;
var index = $(this).index();
$(this).siblings('.on').removeClass('on');
$(this).addClass('on');
$('.term_tootls .tab-con .tab-block').removeClass('on');
$('.term_tootls .tab-con .tab-block').eq(index).addClass('on');
var type = $(this).attr('data-type');
switch (type) {
case 'host':
that.reader_host_list();
break;
case 'shell':
that.reader_command_list();
break;
}
});
});
}
function webShell_addServer(info=[]){
console.log(info);
layer.open({
type: 1,
title: '添加主机信息',

@ -10,6 +10,10 @@
/* menu end */
.xterm{
position: inherit;
}
.tab-nav {
border-bottom: #cacad9 1px solid;
}

@ -7,7 +7,7 @@
<div class="term_box" id="term_box_view">
<div class="term_item_tab">
<div class="list">
<span class="item active" data-host="127.0.0.1" data-id="5erM2eLXJ">
<span class="item active" data-host="127.0.0.1" data-id="localhost">
<i class="icon-sucess icon"></i>
<div class="content"><span>本地服务器</span></div><span class="icon-trem-close"></span></span>
</div>
@ -23,7 +23,7 @@
<!-- 控制台 -->
<div class="term_content_tab">
<div class="term-tool-button tool-hide"><span class="glyphicon glyphicon-menu-right"></span></div>
<div class="term_item" id="ECFEfRWM8" data-host="38.6.224.67"></div>
<div class="term_item" id="localhost" style="display:block;position: inherit;"></div>
</div>
</div>
<div class="term_tootls">

@ -12,8 +12,7 @@ import socket
# reload(sys)
# sys.setdefaultencoding('utf-8')
import paramiko
from datetime import timedelta
from flask import Flask
@ -612,7 +611,7 @@ do
ps -t /dev/$i |grep -v TTY | awk '{print $1}' | xargs kill -9
done
#getHostAddr
# getHostAddr
PLIST=`who | grep "${ip}" | awk '{print $2}'`
for i in $PLIST
do
@ -647,45 +646,32 @@ def connect_ssh():
return True
# methods=["GET", "OPTIONS", "HEAD"]
# pip3 install flask==1.1.2
# pip3 install Werkzeug==1.0.1
@sockets.route('/webssh', methods=["GET", "OPTIONS", "HEAD"])
def webssh(ws):
print("connection start")
while not ws.closed:
msg = ws.receive() # 同步阻塞
print(msg)
now = datetime.datetime.now().isoformat()
ws.send(now) # 发送数据
time.sleep(1)
shell_client = None
# def handle_route_websocket(app_socket):
# @app_socket.route('/webssh')
# def page_websocket_test(ws):
# now = time.strftime('%Y-%m-%d-%H-%M-%S', time.localtime(time.time()))
# while not ws.closed:
# # 回传给clicent
# message = ws.receive() # 接收到消息
# if message is not None:
# print("client says(%s): %s" % (now, message))
# ws.send(str("回执:server已收到消息!-- %s " % now))
# ws.send(str(json.dumps(message))) # 回传给clicent
# else:
# print(now, "no receive")
@socketio.on('webssh_websocketio')
def webssh_websocketio(data):
if not isLogined():
emit('server_response', {'data': '会话丢失,请重新登陆面板!\r\n'})
return
global shell_client
if not shell_client:
import ssh_terminal
shell_client = ssh_terminal.ssh_terminal()
# handle_route_websocket(sockets)
print("request.sid", request.sid)
shell_client.run(request.sid, data)
return
@socketio.on('webssh')
def webssh(msg):
print('webssh cmd:' + msg)
global shell, ssh
if not isLogined():
emit('server_response', {'data': '会话丢失,请重新登陆面板!\r\n'})
return None
global shell, ssh
ssh_success = True
if not shell:
ssh_success = connect_ssh()
@ -729,7 +715,6 @@ def connected_msg(msg):
# print 'connected_msg:' + str(e)
try:
import paramiko
ssh = paramiko.SSHClient()
# 启动尝试时连接

@ -0,0 +1,143 @@
function Terms_WebSocketIO (el, config) {
console.log(config);
if (typeof config == "undefined") {
config = {};
}
this.el = el;
this.id = config.ssh_info.id || '';
this.ws = null; //websocket对象
this.route = 'webssh_websocketio'; // 访问的方法
this.term = null; //term对象
this.info = null; // 请求数据
this.last_body = null;
this.fontSize = 14; //终端字体大小
this.ssh_info = config.ssh_info;
this.term_timer = null;
this.run();
}
var socket, gterm;
Terms_WebSocketIO.prototype = {
// websocket持久化连接
connect: function (callback) {
if(!this.ws){
this.ws = io.connect();
}
},
//连接服务器成功
on_open: function (ws_event) {
var http_token = $("#request_token_head").attr('token');
this.ssh_info['x-http-token'] = http_token;
this.send(JSON.stringify(this.ssh_info || { 'x-http-token': http_token }))
this.term.FitAddon.fit();
this.resize({ cols: this.term.cols, rows: this.term.rows });
},
//服务器消息事件
on_message: function (ws_event) {
// console.log(ws_event.data);
this.term.write(ws_event.data);
if (ws_event.data == '\r\n登出\r\n' || ws_event.data == '登出\r\n' ||
ws_event.data == '\r\nlogout\r\n' || ws_event.data == 'logout\r\n'||
ws_event.data == '\r\nexit\r\n' || ws_event.data == 'exit\r\n') {
this.term.destroy();
clearInterval(this.term_timer);
}
},
//websocket关闭事件
on_close: function (ws_event) {
this.ws = null;
},
//websocket错误事件
on_error: function (ws_event) {
if (ws_event.target.readyState === 3) {
// var msg = '错误: 无法创建WebSocket连接,请在面板设置页面关闭【开发者模式】';
// layer.msg(msg,{time:5000})
// if(Term.state === 3) return
// Term.term.write(msg)
// Term.state = 3;
} else {
console.log(ws_event)
}
},
//发送数据
//@param event 唯一事件名称
//@param data 发送的数据
//@param callback 服务器返回结果时回调的函数,运行完后将被回收
send: function (data, num) {
var that = this;
//如果没有连接,则尝试连接服务器
if (!this.ws || this.bws.readyState == 3 || this.bws.readyState == 2) {
this.connect();
}
//判断当前连接状态,如果!=1,则100ms后尝试重新发送
if (this.ws.readyState === 1) {
this.ws.send(data);
} else {
if (this.state === 3) return;
if (!num) num = 0;
if (num < 5) {
num++;
setTimeout(function () { that.send(data, num++); }, 100)
}
}
},
//关闭连接
close: function () {
this.ws.close();
this.set_term_icon(0);
},
resize: function (size) {
if (this.ws) {
size['resize'] = 1;
this.send(JSON.stringify(size));
}
},
run: function (ssh_info) {
this.connect();
var that = this;
var termCols = 83;
var termRows = 21;
this.term = new Terminal({ cols: termCols, rows: termRows, screenKeys: true, useStyle: true});
this.term.open($('#'+this.id)[0]);
this.term.setOption('cursorBlink', true);
this.term.setOption('fontSize', this.fontSize);
// console.log(this.term.cols,this.term.rows);
// this.term.fit();
// console.log(this.term.cols,this.term.rows);
// this.term.FitAddon = new window.FitAddon();
// this.term.loadAddon(this.term.FitAddon);
// this.term.WebLinksAddon = new WebLinksAddon.WebLinksAddon();
// this.term.loadAddon(this.term.WebLinksAddon);
this.ws.on('server_response', function (ev) { that.on_message(ev)});
if (this.ws) {
that.ws.emit('webssh_websocketio', '');
this.term_timer = setInterval(function () {
that.ws.emit('webssh_websocketio', '');
}, 500);
}
this.term.on('data', function (data) {
that.ws.emit('webssh_websocketio', data);
});
// $('.terminal').detach().appendTo('#'+this.id);
this.ws.emit('webssh_websocketio', this.ssh_info);
this.ws.emit('webssh_websocketio', '\n');
this.term.focus();
}
}

@ -57,6 +57,7 @@ Terms.prototype = {
}
},
//websocket关闭事件
on_close: function (ws_event) {
this.set_term_icon(0);
@ -65,7 +66,6 @@ Terms.prototype = {
/**
* @name 设置终端标题状态
* @author chudong<2020-08-10>
* @param {number} status 终端状态
* @return void
*/

@ -75,7 +75,7 @@
<script src="/static/build/addons/fullscreen/fullscreen.js?v={{config.version}}"></script>
<script src="/static/js/socket.io.min.js?v={{config.version}}"></script>
<script src="/static/build/addons/winptyCompat/winptyCompat.js?v={{config.version}}"></script>
<script src="/static/js/term.js?v={{config.version}}"></script>
<script src="/static/js/term-websocketio.js?v={{config.version}}"></script>
<script src="/static/app/upload.js?v={{config.version}}"></script>
<script src="/static/app/public.js?v={{config.version}}"></script>
<script src="/static/js/echarts.min.js?v={{config.version}}"></script>

Loading…
Cancel
Save