فهرست منبع

初步实现虚拟机启动作业展现页

James Iter 9 سال پیش
والد
کامیت
13ff19b783
4فایلهای تغییر یافته به همراه318 افزوده شده و 1 حذف شده
  1. 245 0
      templates/guests_boot_jobs.html
  2. 1 1
      templates/layout.html
  3. 71 0
      views/guest.py
  4. 1 0
      views_route_table.py

+ 245 - 0
templates/guests_boot_jobs.html

@@ -0,0 +1,245 @@
+{% extends "layout.html" %}
+{% block head %}
+    {{ super() }}
+    <style type="text/css">
+        .table {
+            font-size: 12px;
+            border-width: 1px;
+            line-height: 20px;
+        }
+        .table > thead > tr > th,
+        .table > tfoot > tr > th {
+            color: #999999;
+            font-weight: normal;
+            border-bottom: 0 solid #e1e6eb;
+            background-color: #F5F6FA;
+        }
+        
+        .table > tbody > tr > td {
+            color: #424547;
+        }
+
+        .table-bordered > tbody > tr {
+            padding-top: 10px;
+            padding-bottom: 10px;
+        }
+
+        .table-bordered > thead > tr > th,
+        .table-bordered > tbody > tr > th,
+        .table-bordered > tfoot > tr > th,
+        .table-bordered > thead > tr > td,
+        .table-bordered > tbody > tr > td,
+        .table-bordered > tfoot > tr > td {
+            border-style: solid;
+            border-width: 1px 0 0 0;
+        }
+
+        .btn,
+        .form-control,
+        .modal-content {
+            border-radius: 0 !important;
+        }
+
+        .tr-selected td,
+        .tr-selected {
+            color: #000 !important;
+            background: #fffdf4 !important;
+        }
+    </style>
+
+{% endblock head %}
+{% block content %}
+
+<script type="text/javascript">
+    var page = 1;
+    var page_size = 10;
+    var keyword = '';
+    var resource_path = window.location.pathname;
+    var cur_url = resource_path;
+
+    $(document).ready(function() {
+        page_size = $('#page_size').val();
+        cur_url = resource_path + '?page=' + page + '&page_size=' + page_size;
+
+        var last_ready = null;
+        $('#content_search').keydown(function() {
+            if (last_ready !== null) {
+                clearTimeout(last_ready);
+            }
+            last_ready = setTimeout(function () {
+                keyword = $('#content_search').val();
+                cur_url = resource_path + '?page=' + page + '&page_size=' + page_size;
+                if (keyword.length > 0) {
+                    cur_url = resource_path + '?page=' + page + '&page_size=' + page_size + '&keyword=' + keyword;
+                }
+                window.location.href=cur_url;
+            }, 1000);
+        });
+
+        $('#page_size').change(function () {
+            keyword = $('#content_search').val();
+            page_size = $('#page_size').val();
+            cur_url = resource_path + '?page=' + page + '&page_size=' + page_size;
+            if (keyword.length > 0) {
+                cur_url = resource_path + '?page=' + page + '&page_size=' + page_size + '&keyword=' + keyword;
+            }
+            window.location.href=cur_url;
+        });
+
+        $("thead").on('click', ".all_selector", function() {
+            if ($("thead .all_selector").is(':checked')) {
+                $("tbody tr").find('td input[type="checkbox"]:eq(0)').prop('checked', true);
+                $(".all_selector").prop('checked', true);
+            } else {
+                $("tbody tr").find('td input[type="checkbox"]:eq(0)').prop('checked', false);
+                $(".all_selector").prop('checked', false);
+            }
+
+            select_item_action();
+        });
+
+        $("tfoot").on('click', ".all_selector", function() {
+            if ($("tfoot .all_selector").is(':checked')) {
+                $("tbody tr").find('td input[type="checkbox"]:eq(0)').prop('checked', true);
+                $(".all_selector").prop('checked', true);
+            } else {
+                $("tbody tr").find('td input[type="checkbox"]:eq(0)').prop('checked', false);
+                $(".all_selector").prop('checked', false);
+            }
+
+            select_item_action();
+        });
+
+        $("tbody tr").on('click', "input[type='checkbox']", function() {
+            select_item_action();
+        });
+
+        $('body').addClass('add-transition');
+        $('.add-page-transition').on('click', function(){
+            var transAttr = $(this).attr('data-transition');
+            $('.add-transition').attr('class', 'add-transition');
+            $('.add-transition').addClass(transAttr);
+        });
+    });
+
+    function refresh() {
+        keyword = $('#content_search').val();
+        page = $('#pagination li.active a').text();
+        page_size = $('#page_size').val();
+        cur_url = resource_path + '?page=' + page + '&page_size=' + page_size;
+        if (keyword.length > 0) {
+            cur_url = resource_path + '?page=' + page + '&page_size=' + page_size + '&keyword=' + keyword;
+        }
+        window.location.href=cur_url;
+    }
+</script>
+<div class="panel">
+    <div class="panel-body">
+        <h3 class="title-hero" style="font-size: 24px;">
+            虚拟机启动作业
+        </h3>
+        <div>
+            <div id="datatable-row-highlight_wrapper" class="dataTables_wrapper form-inline">
+                <div class="row" style="padding: 10px 10px 10px 0; width: 100%;">
+                    <div class="col-sm-12" style="padding-right: 0;">
+                        <div id="datatable-row-highlight_filter" class="dataTables_filter" style="display: inline-block;">
+                            <input id="content_search" type="search" class="form-control" placeholder="模糊搜索..." value="{%- if keyword -%} {{ keyword }} {%- endif -%}" style="margin-left: 0; border-radius: 0;">
+                        </div>
+                        <div class="pull-right">
+                            <button class="btn btn-default" onclick="refresh()" style="border-radius: 0;"><span class="glyph-icon icon-elusive-arrows-cw"></span></button>
+                        </div>
+                    </div>
+                </div>
+                <table id="guests_boot_jobs_list" class="table table-bordered table-hover" cellspacing="0" width="100%" role="grid"
+                       style="width: 100%; margin-bottom: 0; border-bottom-width: 0;">
+                <thead>
+                <tr role="row">
+                    <th style="display: none;">UUID</th>
+                    <th style="display: none;">启动作业 ID</th>
+                    <th><input class="all_selector" title="选取所有" type="checkbox"></th>
+                    <th width="180px;">有效时间</th>
+                    <th width="180px;">启动作业</th>
+                    <th width="180px;">名称</th>
+                    <th></th>
+                    <th>状态</th>
+                    <th>IP</th>
+                    <th>操作</th>
+                </tr>
+                </thead>
+                <tbody>
+                {% for uuid, boot_jobs in guests_boot_jobs_ret.data.iteritems() %}
+                    {% for boot_job_id in boot_jobs.boot_jobs %}
+                    <tr role="row" class="odd" onmouseover="row_onmouseover(this);" onmouseout="row_onmouseout(this);">
+                        <td style="display: none;">{{ uuid }}</td>
+                        <td style="display: none;">{{ boot_job_id }}</td>
+                        <td><input title="选中" type="checkbox"></td>
+                        <td>
+                            <span style="color: #b94a48; {% if boot_jobs.ttl // 60 == 0 %}display: none;{% endif %}">{{ boot_jobs.ttl // 60 }}分</span>
+                            <span style="color: #b94a48; {% if boot_jobs.ttl % 60 == 0 %}display: none;{% endif %}">{{ boot_jobs.ttl % 60 }}秒</span>
+                        </td>
+                        <td>{{ boot_jobs_mapping_by_id[boot_job_id | int].name }}</td>
+                        <td>{{ guests_mapping_by_uuid[uuid].label }}</td>
+                        <td><span class="{{ os_templates_mapping_by_id[guests_mapping_by_uuid[uuid].os_template_id].icon }}"></span></td>
+                        <td>{{ format_guest_status(guests_mapping_by_uuid[uuid].status)|safe }}</td>
+                        <td>{{ guests_mapping_by_uuid[uuid].ip }}</td>
+                        <td>
+                            <div class="dropdown inline-block">
+                                <a href="javascript:;" class="dropdown-toggle" data-toggle="dropdown">
+                                    更多
+                                </a>
+                                <ul class="dropdown-menu">
+                                    <li">
+                                        <a href="javascript:;" onclick="delete_at(this);">删除</a>
+                                    </li>
+                                </ul>
+                            </div>
+                        </td>
+                    </tr>
+                    {% endfor %}
+                {% endfor %}
+                </tbody>
+                </table>
+
+                <table class="table table-bordered" style="border-top-width: 0; z-index: 99; position: sticky; bottom: 0;">
+                    <tfoot>
+                    <tr style="height: 70px;">
+                        <th><input type="checkbox" title="选取所有" class="all_selector"></th>
+                        <th>
+                            <div class="row">
+                                <div class="col-sm-6">
+                                </div>
+                                <div class="col-sm-3" style="font-size: 12px; padding-top: 5px; text-align: right;">
+                                </div>
+                                <div class="col-sm-3" style="text-align: left;">
+                                </div>
+                            </div>
+                        </th>
+                    </tr>
+                    </tfoot>
+                </table>
+            </div>
+        </div>
+    </div>
+</div>
+
+<div class="modal" id="reset_password_modal" tabindex="-1" role="dialog" style="margin-top: 100px;">
+    <div class="modal-dialog modal-sm">
+        <div class="modal-content">
+            <div class="modal-header">
+                <h4 class="modal-title">重置密码:</h4>
+            </div>
+            <div class="modal-body">
+                <div class="form-group">
+                    <input id="reset_password_type" title="重置密码类型" class="form-control" name="reset_password_type" value="single" hidden>
+                    <input id="reset_password_instance_uuid" title="实例 UUID" class="form-control" name="uuid" hidden>
+                    <input id="new_password" title="新密码" class="form-control" name="new_password" type="password" placeholder="New password">
+                </div>
+            </div>
+            <div class="modal-footer">
+                <button type="button" class="btn btn-sm btn-primary" onclick="reset_password_go();">确定</button>
+                <button type="button" class="btn btn-sm btn-default" data-dismiss="modal">取消</button>
+            </div>
+        </div>
+    </div>
+</div>
+{% endblock content %}

+ 1 - 1
templates/layout.html

@@ -304,7 +304,7 @@
                             <div class="sidebar-submenu">
                             <div class="sidebar-submenu">
                                 <ul>
                                 <ul>
                                     <li><a href="{{ url_for('v_guests.show') }}" title="虚拟机实例列表"><span>实例</span></a></li>
                                     <li><a href="{{ url_for('v_guests.show') }}" title="虚拟机实例列表"><span>实例</span></a></li>
-                                    <li><a href="#" title="虚拟机启动作业列表"><span>启动作业</span></a></li>
+                                    <li><a href="{{ url_for('v_guests.show_guests_boot_jobs') }}" title="虚拟机启动作业列表"><span>启动作业</span></a></li>
                                 </ul>
                                 </ul>
                             </div><!-- .sidebar-submenu -->
                             </div><!-- .sidebar-submenu -->
                         </li>
                         </li>

+ 71 - 0
views/guest.py

@@ -9,6 +9,7 @@ import requests
 from math import ceil
 from math import ceil
 import re
 import re
 import socket
 import socket
+import jimit as ji
 from models.initialize import q_ws
 from models.initialize import q_ws
 
 
 
 
@@ -221,3 +222,73 @@ def show_boot_job(uuid):
     return render_template('guest_boot_jobs.html', uuid=uuid, guest_ret=guest_ret,
     return render_template('guest_boot_jobs.html', uuid=uuid, guest_ret=guest_ret,
                            guest_boot_jobs_ret=guest_boot_jobs_ret, boot_jobs_mapping_by_id=boot_jobs_mapping_by_id)
                            guest_boot_jobs_ret=guest_boot_jobs_ret, boot_jobs_mapping_by_id=boot_jobs_mapping_by_id)
 
 
+
+def show_guests_boot_jobs():
+    host_url = request.host_url.rstrip('/')
+
+    # 获取所有有启动作业的 Guests uuid
+    uuids_of_all_had_boot_job_url = host_url + url_for('api_guests.r_get_uuids_of_all_had_boot_job')
+
+    uuids_of_all_had_boot_job_ret = requests.get(url=uuids_of_all_had_boot_job_url)
+    uuids_of_all_had_boot_job_ret = json.loads(uuids_of_all_had_boot_job_ret.content)
+    guests_uuid = uuids_of_all_had_boot_job_ret['data']
+
+    guests_boot_jobs_ret = {'data': dict()}
+
+    if guests_uuid.__len__() > 0:
+        # 获取指定 Guest 的启动作业 ID
+        guests_boot_jobs_url = host_url + url_for('api_guests.r_get_boot_jobs',
+                                                  uuids=','.join(guests_uuid))
+        guests_boot_jobs_ret = requests.get(url=guests_boot_jobs_url)
+
+        guests_boot_jobs_ret = json.loads(guests_boot_jobs_ret.content)
+
+        # 统一单个、多个的返回JSON格式
+        if guests_uuid.__len__() == 1:
+            guests_boot_jobs_ret['data'] = {guests_uuid[0]: guests_boot_jobs_ret['data']}
+
+    # 取出 Guests 的启动作业 IDs,并去重
+    guests_boot_jobs_id = list()
+    for uuid, guest_boot_jobs in guests_boot_jobs_ret['data'].items():
+        guests_boot_jobs_id.extend(guest_boot_jobs['boot_jobs'])
+
+    guests_boot_jobs_id = list(set(guests_boot_jobs_id))
+
+    # 获取指定启动作业ID 的启动作业
+    boot_jobs_url = host_url + url_for('api_boot_jobs.r_get_by_filter') + '?filter=id:in:' + \
+        ','.join(guests_boot_jobs_id)
+
+    boot_jobs_ret = requests.get(url=boot_jobs_url)
+    boot_jobs_ret = json.loads(boot_jobs_ret.content)
+
+    # 启动作业 ID 与启动作业的映射
+    boot_jobs_mapping_by_id = dict()
+    for boot_job in boot_jobs_ret['data']:
+        boot_jobs_mapping_by_id[boot_job['id']] = boot_job
+
+    guests_url = host_url + url_for('api_guests.r_get_by_filter')
+
+    guests_url = guests_url + '?uuid=' + ','.join(guests_uuid)
+
+    guests_ret = requests.get(url=guests_url)
+    guests_ret = json.loads(guests_ret.content)
+
+    # Guest uuid 与 Guest 的映射
+    guests_mapping_by_uuid = dict()
+    for guest in guests_ret['data']:
+        guests_mapping_by_uuid[guest['uuid']] = guest
+
+    os_templates_url = host_url + url_for('api_os_templates.r_get_by_filter')
+    os_templates_ret = requests.get(url=os_templates_url)
+    os_templates_ret = json.loads(os_templates_ret.content)
+
+    # 模板与模板 ID 的映射
+    os_templates_mapping_by_id = dict()
+    for os_template in os_templates_ret['data']:
+        os_templates_mapping_by_id[os_template['id']] = os_template
+
+    return render_template('guests_boot_jobs.html', guests_boot_jobs_ret=guests_boot_jobs_ret,
+                           boot_jobs_mapping_by_id=boot_jobs_mapping_by_id,
+                           guests_mapping_by_uuid=guests_mapping_by_uuid,
+                           os_templates_mapping_by_id=os_templates_mapping_by_id)
+

+ 1 - 0
views_route_table.py

@@ -18,6 +18,7 @@ add_rule_views(guest.blueprints, '/success', views_func='guest.success', methods
 add_rule_views(guest.blueprint, '/vnc/<uuid>', views_func='guest.vnc', methods=['GET'])
 add_rule_views(guest.blueprint, '/vnc/<uuid>', views_func='guest.vnc', methods=['GET'])
 add_rule_views(guest.blueprint, '/detail/<uuid>', views_func='guest.detail', methods=['GET'])
 add_rule_views(guest.blueprint, '/detail/<uuid>', views_func='guest.detail', methods=['GET'])
 add_rule_views(guest.blueprint, '/boot_jobs/<uuid>', views_func='guest.show_boot_job', methods=['GET'])
 add_rule_views(guest.blueprint, '/boot_jobs/<uuid>', views_func='guest.show_boot_job', methods=['GET'])
+add_rule_views(guest.blueprints, '/boot_jobs_list', views_func='guest.show_guests_boot_jobs', methods=['GET'])
 
 
 add_rule_views(disk.blueprints, '', views_func='disk.show', methods=['GET'])
 add_rule_views(disk.blueprints, '', views_func='disk.show', methods=['GET'])
 add_rule_views(disk.blueprints, '/create', views_func='disk.create', methods=['GET', 'POST'])
 add_rule_views(disk.blueprints, '/create', views_func='disk.create', methods=['GET', 'POST'])