zoukankan      html  css  js  c++  java
  • 简单分析实现运维利器---web远程ssh终端录像回放libl

    背景

    趁着上次实现了基础的运维系统功能—-webssh,那就趁热打铁!继续补充一个web远程ssh终端录像回放功能。

     

    一、思路

    网上找了一下大家实现webssh录像回放的方案,度娘搜的文章统一都是—用asciinema!仔细看了相关技术文档之后确实阿!我一开始以为需要视频文件。没想到,asciinema用特定的格式文件就可以实现视频流。(在写asciinemaweb技术的时候有个大坑- -.琢磨我两个小时,待会说具体问题)

    asciinema实际就是 通过系统输出的信息,将信息写成asciinema特定的格式文件,包含header与data两大部分(也是待会从文章中讲解两部分的结构)。它就能解析成视频。所以我们在昨天的功能逻辑之上:

    1.连接ssh成功后,构造头部内容写入文件中

    2.将开头两条输出信息以特定的格式写入文件中

    3.将线程监听回来的所有输出信息以特定的格式写入文件中

    其中2.3已经包含了我们webssh所有的输出信息了,已经足以构成录像回放

    4.创建html页面展示回访列表

     

    二、实现

    0.先说明一下asciinema文件的格式。

    头部:

    header = {
            "version": 2,  #(asciinema)版本
            "width": 160, #(asciinema)回放时的宽度
            "height": 48, #(asciinema)回放时的高度
            "timestamp": date, #(asciinema)时间戳 用于播放
            "env": {"SHELL": "/bin/bash",  #(asciinema)使用的shell类型
            "TERM": "xterm-256color"}, #(asciinema)终端颜色
            "title": "video"  #(asciinema)标题
        }

    data:

    [0.00699162483215332, "o", "Last login: Thu May  7 18:42:13 2020 from 192.168.254.1
    
    "]
    [0.1905069351196289, "o", "[root@leestudy ~]# "]  #第一个字段为时间戳,第二个字段“o”为输出,“i”为输入,第三个字段为“(o)输出信息/(i)输入信息”

    1.连接ssh成功后写入header

    sshsession = client.get_transport().open_session()
                sshsession.get_pty()
                sshsession.invoke_shell()
    
                asciinemadir = settings.BASE_DIR + '/static/asciinemadir/' #定义一个存放文件的目录(喜欢的也可以保存在数据库)
                if not os.path.isdir(asciinemadir):
                    os.makedirs(asciinemadir)
                starttime=time.strftime("%Y%m%d%H%M%S") #用于记录开始时间
                filena =ip+starttime         #文件名,用ip+开始时间为文件名
                date=time.time()             #开始时间戳
                print(date)
                header = {
                    "version": 2,
                    "width": 160,
                    "height": 48,
                    "timestamp": date,   #开始时间戳
                    "env": {"SHELL": "/bin/bash",
                    "TERM": "xterm-256color"},
                    "title": "video"
                }
    
                writedata= open(asciinemadir + filena, 'w') #打开文件
                writedata.write(json.dumps(header) + '
    ')  #将header写入文件
    

    2.将开头两条输出信息以特定的格式写入文件中

               for i in range(2):
                    messa = sshsession.recv(1024)
                    request.websocket.send(messa)  
                    demessa = messa.decode('utf-8')
                    iodata = [time.time() - date, 'o', f'{demessa}'] #构造格式
                    writedata.write(json.dumps(iodata)+'
    ') #写入文件
    

    3.将线程监听回来的所有输出信息以特定的格式写入文件中         

    def srecv():
                    while True:
                        sshmess = sshsession.recv(2048)
                        if not len(sshmess):
                            print('退出监听发送循环,并关闭写入文件')
                            writedata.close()   #如果不再监听通道,则关闭文件
                            break
                        request.websocket.send(sshmess)
                        print('ssh回复的信息:' + sshmess.decode('utf-8'))
                        print(len(sshmess))
                        desshmess = sshmess.decode('utf-8')
                        iodata2 = [time.time() - date, 'o', f'{desshmess}']#构造格式
                        writedata.write(json.dumps(iodata2) + '
    ')#写进文件
    

     

    4.创建html页面展示回访列表

    在创建html前需要创建一个新的表用于存放录像列表的信息

    models下:

    class video(models.Model):
        hostaddress = models.CharField(max_length=255)
        username = models.CharField(max_length=255)
        filename = models.CharField(max_length=255)
        starttime = models.CharField(max_length=255)
        overtime = models.CharField(max_length=255)
    ####(创建好列表信息后需要进行数据库记录与迁移python manage.py makemigrations与python manage.py migrate)
    

    接着在关闭websocket时创建一条数据,并且在关闭websocket时也关闭文件,判断是否关闭只要用try异常机制即可.    

     except:
                addvideolist = models.video()
                addvideolist.hostaddress = ip
                addvideolist.username = request.session['username']
                addvideolist.filename = filena
                addvideolist.starttime = starttime
                addvideolist.overtime = time.strftime("%Y%m%d%H%M%S")
                addvideolist.save()
                writedata.close()
                print('中断websocket,关闭文件写入')
    

    接着创建html页面展示回放:

    (坑了我两个小时的就是这个asciinemaplayer的js文件,在网上找了很多的asciinemaplayerjs文件,统统都不行!解析不出来!(解坑前根本不知道是JS的问题!)连官方下载的demo都解析不出来,全是在比较大型的网站下的- -.然后我看了下官网(https://asciinema.org/)里面的展示都可以解析的啊!demo都一毛一样!然后索性找了一个demo的首页,点开F12直接找它的js算了!!结果一引用,成了。我靠!!也不知道是不是我的问题)最后给上jshttps://asciinema.org/js/asciinema-player-e90dd959bf7df7e1608c08ac419fca67.js?vsn=d

    现在视图中创建页面函数:

    def Videotape(request):
        videolist = models.video.objects.all()
        return render(request, 'html/Videotape.html', locals())
    

    然后再编写html页面(引用了AdminLte模板)

    {% extends 'base.html' %}
    {% load static %}
    {% block title %}录像{% endblock %}
    {% block css %}
         <link rel="stylesheet" href="{% static 'adminlet-2.4.10/bower_components/datatables.net-bs/css/dataTables.bootstrap.css' %}">
        <link rel="stylesheet" href="{% static 'asciinemaplayer/asciinema-player.css' %}">
    {% endblock %}
    
    <!-- 顶部内容  -->
    {% block breadcrumb %}
        <!-- Content Header (Page header) -->
        <section class="content-header">
          <h1></h1>
        </section>
    {% endblock %}
    
    <!-- 身体内容  -->
    {% block content %}
    
        <!-- playvideo模态框 -->
    <div class="modal fade bs-example-modeal-lg" id="playvideo" tabindex="-1" role="dialog" aria-labelledby="playvideoLabel">
      <div class="modal-dialog modal-lg" role="document">
        <div class="modal-content">
          <div class="modal-header">
            <button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">×</span></button>
            <h4 class="modal-title" id="addhostlLabel">录像播放</h4>
          </div>
          {% csrf_token %}
          <div class="modal-body">
              <div class="modal-body" id="play">
          </div>
    
    
          </div>
    
          <div class="modal-footer">
    
          </div>
        </div>
      </div>
    </div>
    
    
    
    
    
        <!-- datatable -->
          <div class="box box-default">
    
            <div class="box-header with-border">
              <h3 class="box-title">录像列表</h3>
            </div>
    
            <div class="box-body">
        <table id="Videolist" class="display" style="100%">
                        <thead>
                <tr>
                  <th>主机地址</th>
                  <th>操作人</th>
                  <th>录像文件名</th>
                  <th>开始时间</th>
                  <th>结束时间</th>
                  <th>操作</th>
                </tr>
                </thead>
                <tbody>
              {% for videoli in videolist %}
                  <tr>
                  <td>{{ videoli.hostaddress }}</td>
                  <td>{{ videoli.username }}</td>
                  <td>{{ videoli.filename }}</td>
                  <td>{{ videoli.starttime }}</td>
                  <td>{{ videoli.overtime }}</td>
                  <td><input type="button" value="查看录像" style="float: left" class="btn btn-success" onclick="play('{{videoli.filename}}')"/></td>
                  </tr>
                {% endfor %}
                </tbody>
        </table>
            </div>
          </div>
    
    
    
    
    
    
    
    {% endblock %}
    
    <!-- JS内容  -->
    {% block script %}
    <script src="{% static 'adminlet-2.4.10/bower_components/datatables.net/js/jquery.dataTables.min.js' %}"></script>
    <script src="{% static 'adminlet-2.4.10/bower_components/datatables.net-bs/js/dataTables.bootstrap.min.js' %}"></script>
    <script src="{% static 'asciinemaplayer/asciinema-player-e90dd959bf7df7e1608c08ac419fca67.js' %}"></script>
        <script type="text/javascript">//datatable配置
        $(document).ready(function() {
        $('#Videolist').DataTable({
    
           language: {
               "sProcessing": "处理中...",
               "sLengthMenu": "显示 _MENU_ 项结果",
               "sZeroRecords": "没有匹配结果",
               "sInfo": "显示第 _START_ 至 _END_ 项结果,共 _TOTAL_ 项",
               "sInfoEmpty": "显示第 0 至 0 项结果,共 0 项",
               "sInfoFiltered": "(由 _MAX_ 项结果过滤)",
               "sInfoPostFix": "",
               "sSearch": "搜索:",
               "sUrl": "",
               "sEmptyTable": "表中数据为空",
               "sLoadingRecords": "载入中...",
               "sInfoThousands": ",",
               "oPaginate": {
                   "sFirst": "首页",
                   "sPrevious": "上页",
                   "sNext": "下页",
                   "sLast": "末页"
               },
               "oAria": {
                   "sSortAscending": ": 以升序排列此列",
                   "sSortDescending": ": 以降序排列此列"
               }
           },
              "paging": true,       <!-- 允许分页 -->
              "lengthChange": true, <!-- 允许改变每页显示的行数 -->
              "searching": true,    <!-- 允许内容搜索 -->
              "ordering": true,     <!-- 允许排序 -->
              "info": true,         <!-- 显示信息 -->
              "autoWidth": false
    
        });
    } );
    
    
    
    
    // 播放录像
    function play(file) {
      $('#play').html(
        '<asciinema-player id="play" src="/static/asciinemadir/'+file+'"></asciinema-player>'
      )
      $('#playvideo').modal('show');
    }
    
        </script>
    {% endblock %}
    
     

    5.最终效果

     

    结束

    好啦,Django运维系统基础功能之—web远程ssh终端录像回放的功能就先到这里。后面的功能再慢慢补充吧~

  • 相关阅读:
    面试题:找出数组中只出现一次的2个数(异或的巧妙应用)(出现3次)
    线段树 | 第1讲 (给定区间求和)(转)
    C++中的静态多态和动态多态(转)
    ARP与RARP详细解析(转)
    排序算法之归并排序
    byte数组使用Arrays.asList转换List出错
    排序算法之希尔排序
    排序算法之冒泡排序、选择排序、插入排序
    Tomcat配置优化
    内连接、左外连接、右外连接、全外连接、交叉连接
  • 原文地址:https://www.cnblogs.com/eflypro/p/14609540.html
Copyright © 2011-2022 走看看