Xterm是一个基于X Window System的终端仿真器(Terminal Emulator)。Xterm最初由MIT开发,它允许用户在X Window环境下运行文本终端程序。Xterm提供了一个图形界面终端,使用户能够在图形桌面环境中运行命令行程序。而xterm.js是一个用于在浏览器中实现终端仿真的JavaScript库。它允许在Web页面中创建交互式的终端界面,用户可以在浏览器中运行命令行程序,执行命令,并与终端进行交互。
主要特点和功能包括:
终端仿真:  xterm.js通过JavaScript模拟了一个终端环境,支持常见的终端功能,包括光标移动、颜色控制、滚动等。 
多平台支持:  由于是基于JavaScript实现,xterm.js可以在各种现代浏览器上运行,无论是在桌面还是移动设备上。 
自定义外观:  xterm.js提供了丰富的配置选项,用户可以定制终端的外观和行为,包括颜色、字体、光标样式等。 
剪贴板支持:  支持从终端复制文本到剪贴板,并从剪贴板粘贴文本到终端。 
WebSockets和其他集成:  可以与WebSockets等通信协议集成,以便在浏览器中实现实时的终端交互。 
支持Unicode和UTF-8:  能够正确显示和处理Unicode字符,支持UTF-8编码。 
 
xterm.js通常被用于Web应用程序中,尤其是在需要提供命令行界面的场景下,如在线终端、远程服务器管理等。这使得开发者能够在浏览器中实现类似于本地终端的交互体验,而无需使用本地终端模拟器。
AJAX 实现Web交互 AJAX(Asynchronous JavaScript and XML)是一种用于在Web应用程序中实现异步数据交换的技术。它允许在不重新加载整个页面的情况下,通过在后台与服务器进行小规模的数据交换,实现动态更新网页内容的目的。AJAX广泛用于创建交互性强、用户体验良好的Web应用程序,例如在加载新数据、进行表单验证、实现自动完成搜索等方面。
如下前端部分,通过使用ajax向后端提交数据,当success:function接收到数据后直接将数据动态回写到Xterm终端上,代码如下所示;
<!DOCTYPE html > <html  lang ="en" > <head >     <meta  charset ="UTF-8" >      <title > Title</title >      <link  rel ="stylesheet"  href ="https://www.lyshark.com/javascript/xterm/xterm.css"  />      <script  type ="text/javascript"  src ="https://www.lyshark.com/javascript/xterm/xterm.js" > </script >      <script  type ="text/javascript"  src ="https://www.lyshark.com/javascript/jquery/3.5.1/jquery.min.js" > </script >  </head > <body >     <script  type ="text/javascript" >        var  window_width = $(window ).width ()-200 ;       var  window_height = $(window ).height ()-300 ;       var  term = new  Terminal (             {                 cols : Math .floor (window_width/9 ),                 rows : Math .floor (window_height/20 ),                 useStyle :false ,                 convertEol : true ,                 cursorBlink :false ,                 cursorStyle :null ,                 rendererType : "canvas" ,             }     );     term.open (document .getElementById ('terminal' ));       function  show ( ){           var  address = $("#address" ).val ();           var  command = $("#command" ).val ();           console .log (command);           $.ajax ({               url :"/" ,               type :"POST" ,               contentType :"application/json;" ,               data : JSON .stringify ({"address" :address,"command" :command}),               success :function  (res )               {                                      term.writeln ( "\x1B[1;3;33m IP地址: \x1B[0m"  + res.address  );                   term.writeln ( "\x1B[1;3;34m 命令: \x1B[0m"  + res.command  );               }           });       }      </script >          <div  id ="terminal" > </div >          <input  type ="text"  id ="address"  placeholder ="主机地址" />          <input  type ="text"  id ="command"  placeholder ="执行命令" />          <input  type ="button"  value ="执行命令"  onclick ="show()" >      </div >  </body > </html > 
 
后端部分的实现很简单,首先封装一个ssh_shell用于执行命令,用户传入数据后,直接执行并将返回结果放入到ref内即可。
from  flask import  Flask,render_template,requestfrom  flask import  jsonifyimport  paramikoapp = Flask(__name__) ssh = paramiko.SSHClient() ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy()) def  ssh_shell (address,username,password,port,command ):    ssh.connect(address,port=port,username=username,password=password)     stdin, stdout, stderr = ssh.exec_command(command)     result = stdout.read()     if  not  result:         result=stderr.read()     ssh.close()     return  result.decode() @app.route('/' , methods=[ 'GET' , 'POST' ] ) def  index ():    if  request.method == "POST" :                  json_value = request.get_json()         ref = ssh_shell("192.168.150.128" ,"root" ,"123123" ,"22" ,json_value["command" ])                  info = dict ()         info["address" ] = json_value["address" ]         info["command" ] = ref         return  jsonify(info)     else :         return  render_template("index.html" ) if  __name__ == '__main__' :    app.run() 
 
AJAX实现Web终端 继续扩展将编辑框去掉,用户输入数据后直接传入到Xterm内,Xterm里卖弄判断如果出现了回车,则像后端发送ajax数据,否则继续侦听并记下输入数据。
<!DOCTYPE html > <html  lang ="en" > <head >     <meta  charset ="UTF-8" >      <title > Title</title >      <link  rel ="stylesheet"  href ="https://www.lyshark.com/javascript/xterm/xterm.css"  />      <script  type ="text/javascript"  src ="https://www.lyshark.com/javascript/xterm/xterm.js" > </script >      <script  type ="text/javascript"  src ="https://www.lyshark.com/javascript/jquery/3.5.1/jquery.min.js" > </script >  </head > <body >     <div  id ="terminal" > </div >      <script  type ="text/javascript" >          var  window_width = $(window ).width ()-500 ;         var  window_height = $(window ).height ()-300 ;         var  term = new  Terminal          (             {                 cols : Math .floor (window_width/9 ),                 rows : Math .floor (window_height/20 ),                 useStyle :false ,                 convertEol : true ,                 cursorBlink : true ,                         cursorStyle : "underline" ,                  rendererType : "canvas" ,             }         );         term.open (document .getElementById ('terminal' ));         term.writeln ("welcome to lyshark web terminal!" );         term.write ("[shell] # " );         let  input = '' ;         term.on ('key' , (key, ev ) =>  {           let  code = key.charCodeAt (0 );           console .log (code);                      if (code == 13 )           {               term.write ("\r\n" );               $.ajax ({                   url :"/" ,                   type :"POST" ,                   contentType :"application/json;" ,                   data : JSON .stringify ({"command" : input}),                   success :function  (res )                   {                       term.write (res.value );                   }               });               input ='' ;           }                      else  if (code == 127 )           {               term.write ("\b" );           }           else            {               input += key               term.write (key);           }         });      </script > </body > </html > 
 
后端收到数据后解析命令,比对命令是否存在,根据不同的命令执行不同的分支。
from  flask import  Flask,render_template,requestfrom  flask import  jsonifyapp = Flask(__name__) @app.route('/' , methods=[ 'GET' , 'POST' ] ) def  index ():    if  request.method == "POST" :                  json_value = request.get_json()["command" ]         if  len (json_value) != 0 :                          splite_value = json_value.split(" " )             info = dict ()             if  splite_value[0 ] == "help" :                 info["value" ] = "version 1.0"                  info["value" ] = info["value" ] + "\n[shell] # "                  return  jsonify(info)             elif  splite_value[0 ] == "GetCPU" :                 address = splite_value[1 ]                 info["value" ] = "192.168.1 CPU 10%"                  info["value" ] = info["value" ] + "\n[shell] # "                  return  jsonify(info)             else :                 info["value" ] = "命令不存在"                  info["value" ] = info["value" ] + "\n[shell] # "                  return  jsonify(info)         else :             info = dict ()             info["value" ] = "[shell] # "              return  jsonify(info)     else :         return  render_template("index.html" ) if  __name__ == '__main__' :    app.run() 
 
运行后可输出一个交互式WebShell环境,如下图所示;
WebSocket 实现终端 虽然WebSSH可以方便管理主机,但如果需要批量运维则需要开发一个可以多条消息共同推送的命令行。
<!DOCTYPE html > <html  lang ="en" > <head >     <meta  charset ="UTF-8" >      <title > Title</title >      <link  rel ="stylesheet"  href ="https://www.lyshark.com/javascript/xterm/xterm.css"  />      <script  type ="text/javascript"  src ="https://www.lyshark.com/javascript/xterm/xterm.js" > </script >      <script  type ="text/javascript"  src ="https://www.lyshark.com/javascript/jquery/3.5.1/jquery.min.js" > </script >      <script  type ="text/javascript"  src ="https://www.lyshark.com/javascript/socket.io/socket.io.min.js" > </script >  </head > <body >     <div  id ="terminal" > </div >      <script  type ="text/javascript"  charset ="UTF-8" >          $(document ).ready (function ( )         {             namespace = '/Socket' ;             var  socket = io.connect ("http://"  + document .domain  + ":"  + location.port  + namespace);             var  window_width = $(window ).width ()-500 ;             var  window_height = $(window ).height ()-300 ;             var  term = new  Terminal              (                 {                     cols : Math .floor (window_width/9 ),                     rows : Math .floor (window_height/20 ),                     useStyle :false ,                     convertEol : true ,                     cursorBlink : true ,                     rendererType : "canvas" ,                 }             );                          term.open (document .getElementById ('terminal' ));             term.write ("[shell] # " );             let  input_command = '' ;             term.on ('key' , (key, ev ) =>  {               let  code = key.charCodeAt (0 );               console .log (code);                              if (code == 13 )               {                                      term.write ("\r\n" );                   socket.emit ("message" ,{"command" : input_command});                   input_command ='' ;               }                              else  if (code == 127 )               {                   term.write ("\b" );               }               else                {                   input_command += key                   term.write (key);               }             });                          socket.on ('response' , function (recv )             {                 console .log (recv.value );                 term.write (recv.value );             });         });      </script > </body > </html > 
 
后台接收参数,并更具不同的参数执行不同的运维函数,此处只做演示,具体功能需要自行编写。
from  flask import  Flask,render_template,requestfrom  flask_socketio import  SocketIOasync_mode = None  app = Flask(__name__) app.config['SECRET_KEY' ] = "lyshark"  socketio = SocketIO(app) @app.route("/"  ) def  index ():    return  render_template("index.html" ) @socketio.on("message" ,namespace="/Socket"  ) def  socket (message ):    print ("接收到消息:" ,message['command' ])     command = message['command' ]     if  len (command) != 0 :         splite_command = command.split(" " )         if  splite_command[0 ] == "help" :             socketio.emit("response" , {"value" : "version 1.0 \n" }, namespace="/Socket" )         elif  splite_command[0 ] == "Ping" :             if  len (splite_command) == 2 :                 index = splite_command[1 ]                 for  each in  range (int (index)):                     socketio.sleep(0.1 )                     socketio.emit("response" ,{"value" : str (each) + "\n" }, namespace="/Socket" )                 socketio.emit("response" , {"value" : "\n[shell] # " }, namespace="/Socket" )         else :             socketio.emit("response" , {"value" : "lyShell: command not found \n" }, namespace="/Socket" )     else :         socketio.emit("response" , {"value" : "[shell] # " }, namespace="/Socket" ) @socketio.on("connect" ,namespace="/Socket"  ) def  connect ():    print ("链接建立成功.." ) @socketio.on("disconnect" ,namespace="/Socket"  ) def  disconnect ():    print ("链接建立失败.." ) if  __name__ == '__main__' :    socketio.run(app,debug=True ) 
 
Socket版本的将会更流畅,如下图所示;