Jelajahi Sumber

实现逐个变更 vlan 功能

James Iter 7 tahun lalu
induk
melakukan
0b47326ac4

+ 31 - 0
jimvc/api/guest.py

@@ -914,6 +914,7 @@ def r_migrate(uuids, node_id):
         return json.loads(e.message)
 
 
+@Utils.dumps2response
 def r_change_vlan(uuids, vlan_id):
     args_rules = [
         Rules.UUIDS.value,
@@ -996,6 +997,14 @@ def r_get(uuids):
     for host in hosts_ret['data']:
         hosts_mapping_by_node_id[int(host['node_id'])] = host
 
+    vlans_url = url_for('api_vlans.r_get_by_filter', _external=True)
+    vlans_ret = requests.get(url=vlans_url, cookies=request.cookies)
+    vlans_ret = json.loads(vlans_ret.content)
+
+    vlans_mapping_by_vlan_id = dict()
+    for vlan in vlans_ret['data']:
+        vlans_mapping_by_vlan_id[int(vlan['vlan_id'])] = vlan
+
     if -1 == uuids.find(','):
         if 'ssh_keys' not in ret['data']:
             ret['data']['ssh_keys'] = list()
@@ -1011,6 +1020,8 @@ def r_get(uuids):
             if not hosts_mapping_by_node_id[ret['data']['node_id']]['alive']:
                 ret['data']['status'] = GuestState.no_state.value
 
+        ret['data']['vlan'] = vlans_mapping_by_vlan_id[ret['data']['vlan_id']]
+
     else:
         for i, guest in enumerate(ret['data']):
             if 'ssh_keys' not in ret['data'][i]:
@@ -1027,6 +1038,8 @@ def r_get(uuids):
             if not hosts_mapping_by_node_id[ret['data'][i]['node_id']]['alive']:
                 ret['data'][i]['status'] = GuestState.no_state.value
 
+            ret['data'][i]['vlan'] = vlans_mapping_by_vlan_id[ret['data'][i]['vlan_id']]
+
     return ret
 
 
@@ -1181,6 +1194,14 @@ def r_get_by_filter():
     for host in hosts_ret['data']:
         hosts_mapping_by_node_id[int(host['node_id'])] = host
 
+    vlans_url = url_for('api_vlans.r_get_by_filter', _external=True)
+    vlans_ret = requests.get(url=vlans_url, cookies=request.cookies)
+    vlans_ret = json.loads(vlans_ret.content)
+
+    vlans_mapping_by_vlan_id = dict()
+    for vlan in vlans_ret['data']:
+        vlans_mapping_by_vlan_id[int(vlan['vlan_id'])] = vlan
+
     os_templates_image, _ = OSTemplateImage.get_by_filter()
     os_templates_image_mapping_by_id = dict()
     for os_template_image in os_templates_image:
@@ -1226,6 +1247,7 @@ def r_get_by_filter():
             ret['data'][i]['status'] = GuestState.no_state.value
 
         ret['data'][i]['hostname'] = hosts_mapping_by_node_id[guest['node_id']]['hostname']
+        ret['data'][i]['vlan'] = vlans_mapping_by_vlan_id[guest['vlan_id']]
 
         ret['data'][i]['html'] = dict()
         ret['data'][i]['html']['logo'], ret['data'][i]['html']['os_template_label'] = exchange_guest_os_templates_logo(
@@ -1288,6 +1310,14 @@ def r_content_search():
     for host in hosts_ret['data']:
         hosts_mapping_by_node_id[int(host['node_id'])] = host
 
+    vlans_url = url_for('api_vlans.r_get_by_filter', _external=True)
+    vlans_ret = requests.get(url=vlans_url, cookies=request.cookies)
+    vlans_ret = json.loads(vlans_ret.content)
+
+    vlans_mapping_by_vlan_id = dict()
+    for vlan in vlans_ret['data']:
+        vlans_mapping_by_vlan_id[int(vlan['vlan_id'])] = vlan
+
     os_templates_image, _ = OSTemplateImage.get_by_filter()
     os_templates_image_mapping_by_id = dict()
     for os_template_image in os_templates_image:
@@ -1333,6 +1363,7 @@ def r_content_search():
             ret['data'][i]['status'] = GuestState.no_state.value
 
         ret['data'][i]['hostname'] = hosts_mapping_by_node_id[guest['node_id']]['hostname']
+        ret['data'][i]['vlan'] = vlans_mapping_by_vlan_id[guest['vlan_id']]
 
         ret['data'][i]['html'] = dict()
         ret['data'][i]['html']['logo'], ret['data'][i]['html']['os_template_label'] = exchange_guest_os_templates_logo(

+ 65 - 9
jimvc/api/vlan.py

@@ -7,6 +7,7 @@ from flask import Blueprint, request, url_for
 import json
 import jimit as ji
 
+from jimvc.models import Config
 from jimvc.models import Utils, Guest
 from jimvc.models import Rules
 from jimvc.models import VLAN
@@ -144,6 +145,19 @@ def r_get(ids):
     if '200' != ret['state']['code']:
         return ret
 
+    config = Config()
+    config.id = 1
+    config.get()
+
+    default_bridge = VLAN()
+    default_bridge.id = 0
+    default_bridge.vlan_id = -1
+    default_bridge.label = u'默认网桥'
+    default_bridge.description = u'系统初始化时的默认网桥。在系统中可见为:' + config.vm_network + u'。'
+    default_bridge.create_time = 0
+
+    ret['data'].insert(0, default_bridge.__dict__)
+
     hosts_url = url_for('api_hosts.r_get_by_filter', _external=True)
     hosts_ret = requests.get(url=hosts_url, cookies=request.cookies)
     hosts_ret = json.loads(hosts_ret.content)
@@ -164,9 +178,14 @@ def r_get(ids):
     posts = hosts.__len__()
 
     for i, vlan in enumerate(ret['data']):
-        vlan_bridge_name = 'vlan' + vlan['vlan_id'].__str__()
-        ret['data'][i]['posts'] = posts
-        ret['data'][i]['arrival'] = hosts_mapping_by_vlan_bridge[vlan_bridge_name].__len__()
+        if ret['data'][i]['vlan_id'] == -1:
+            ret['data'][i]['posts'] = posts
+            ret['data'][i]['arrival'] = posts
+
+        else:
+            vlan_bridge_name = 'vlan' + vlan['vlan_id'].__str__()
+            ret['data'][i]['posts'] = posts
+            ret['data'][i]['arrival'] = hosts_mapping_by_vlan_bridge[vlan_bridge_name].__len__()
 
     return ret
 
@@ -174,9 +193,23 @@ def r_get(ids):
 @Utils.dumps2response
 def r_get_by_filter():
     ret = vlan_base.get_by_filter()
+
     if '200' != ret['state']['code']:
         return ret
 
+    config = Config()
+    config.id = 1
+    config.get()
+
+    default_bridge = VLAN()
+    default_bridge.id = 0
+    default_bridge.vlan_id = -1
+    default_bridge.label = u'默认网桥'
+    default_bridge.description = u'系统初始化时的默认网桥。在系统中可见为:' + config.vm_network + u'。'
+    default_bridge.create_time = 0
+
+    ret['data'].insert(0, default_bridge.__dict__)
+
     hosts_url = url_for('api_hosts.r_get_by_filter', _external=True)
     hosts_ret = requests.get(url=hosts_url, cookies=request.cookies)
     hosts_ret = json.loads(hosts_ret.content)
@@ -197,9 +230,14 @@ def r_get_by_filter():
     posts = hosts.__len__()
 
     for i, vlan in enumerate(ret['data']):
-        vlan_bridge_name = 'vlan' + vlan['vlan_id'].__str__()
-        ret['data'][i]['posts'] = posts
-        ret['data'][i]['arrival'] = hosts_mapping_by_vlan_bridge[vlan_bridge_name].__len__()
+        if ret['data'][i]['vlan_id'] == -1:
+            ret['data'][i]['posts'] = posts
+            ret['data'][i]['arrival'] = posts
+
+        else:
+            vlan_bridge_name = 'vlan' + vlan['vlan_id'].__str__()
+            ret['data'][i]['posts'] = posts
+            ret['data'][i]['arrival'] = hosts_mapping_by_vlan_bridge[vlan_bridge_name].__len__()
 
     return ret
 
@@ -211,6 +249,19 @@ def r_content_search():
     if '200' != ret['state']['code']:
         return ret
 
+    config = Config()
+    config.id = 1
+    config.get()
+
+    default_bridge = VLAN()
+    default_bridge.id = 0
+    default_bridge.vlan_id = -1
+    default_bridge.label = u'默认网桥'
+    default_bridge.description = u'系统初始化时的默认网桥。在系统中可见为:' + config.vm_network + u'。'
+    default_bridge.create_time = 0
+
+    ret['data'].insert(0, default_bridge.__dict__)
+
     hosts_url = url_for('api_hosts.r_get_by_filter', _external=True)
     hosts_ret = requests.get(url=hosts_url, cookies=request.cookies)
     hosts_ret = json.loads(hosts_ret.content)
@@ -231,9 +282,14 @@ def r_content_search():
     posts = hosts.__len__()
 
     for i, vlan in enumerate(ret['data']):
-        vlan_bridge_name = 'vlan' + vlan['vlan_id'].__str__()
-        ret['data'][i]['posts'] = posts
-        ret['data'][i]['arrival'] = hosts_mapping_by_vlan_bridge[vlan_bridge_name].__len__()
+        if ret['data'][i]['vlan_id'] == -1:
+            ret['data'][i]['posts'] = posts
+            ret['data'][i]['arrival'] = posts
+
+        else:
+            vlan_bridge_name = 'vlan' + vlan['vlan_id'].__str__()
+            ret['data'][i]['posts'] = posts
+            ret['data'][i]['arrival'] = hosts_mapping_by_vlan_bridge[vlan_bridge_name].__len__()
 
     return ret
 

+ 2 - 2
jimvc/models/rules.py

@@ -14,8 +14,8 @@ __copyright__ = '(c) 2017 by James Iter.'
 # TODO: 考虑按视角归类,比如自身视角名称(uuid),外部视角名称(guest_uuid)
 class Rules(Enum):
     # 正则表达式方便校验其来自URL的参数
-    REG_NUMBER = 'regex:^\d{1,17}$'
-    REG_NUMBERS = 'regex:^(\d{1,17})(,\d{1,17})*$'
+    REG_NUMBER = 'regex:^-?\d{1,17}$'
+    REG_NUMBERS = 'regex:^-?(\d{1,17})(,-?\d{1,17})*$'
     REG_UUIDS = 'regex:^([\w-]{36})(,[\w-]{36})*$'
     REG_HOSTS_NAME = 'regex:^([\S-]{1,128})(,[\S-]{1,128})*$'
     REG_IP = 'regex:^((?:(?:25[0-5]|2[0-4]\d|((1\d{2})|([1-9]?\d)))\.){3}(?:25[0-5]|2[0-4]\d|((1\d{2})|([1-9]?\d))))$'

+ 9 - 1
jimvc/themes/default/templates/config_show.html

@@ -828,9 +828,17 @@
                 },
                 success : function(data, textStatus, xhr) {
                     var html = NaN;
+                    var disabled = '';
+
                     $('#vlans_list_table tbody').empty();
                     $.each(data.data, function(k, v) {
-                        html = '<tr role="row" data-id="' + v['id'] + '" class="odd" onmouseover="vlan_row_onmouseover(this);" onmouseout="vlan_row_onmouseout(this);">\n' +
+                        if (v['vlan_id'] === -1) {
+                            disabled = ' disabled';
+                        } else {
+                            disabled = '';
+                        }
+
+                        html = '<tr role="row" data-id="' + v['id'] + '" class="odd' + disabled + '" onmouseover="vlan_row_onmouseover(this);" onmouseout="vlan_row_onmouseout(this);">\n' +
                             '    <td>\n' +
                             '       <div>\n' +
                             '           <p style="display: inline-block;">' + v['label'] + '</p>\n' +

+ 78 - 1
jimvc/themes/default/templates/guests_show.html

@@ -263,6 +263,26 @@
             });
         });
 
+        $('#change_vlan_modal').on('show.bs.modal', function (me) {
+            $.ajax({
+                url : '/api/vlans',
+                type : 'GET',
+                contentType: "application/json; charset=utf-8",
+                dataType: 'json',
+                error : function() {
+                },
+                success : function(data, textStatus, xhr) {
+                    $('#change_to_vlan').empty();
+                    $.each(data.data, function(k, v) {
+                        $('#change_to_vlan').append(
+                            $('<option>', {value: v['vlan_id'], text: 'VLAN ID: ' + v['vlan_id'], 'data-subtext': v['label']})
+                        );
+                    });
+                    $('#change_to_vlan').selectpicker('refresh');
+                }
+            });
+        });
+
         $('#reset_password_modal').on('show.bs.modal', function (me) {
             $('#reset_password_instance_uuid').val(
                 $(me.relatedTarget).parent().parent().parent().parent().parent().children()[0].textContent);
@@ -886,6 +906,24 @@
         }, 1000);
     }
 
+    function change_vlan(uuids, vlan_id) {
+        $.ajax({
+            url : '/api/guests/_change_vlan/' + uuids.join(',') + '/' + vlan_id,
+            type : 'PUT',
+            contentType: "application/json; charset=utf-8",
+            error : function() {
+                alter_danger('实例变更 VLAN 指令发送失败!');
+            },
+            success : function() {
+                alter_success('实例变更 VLAN 指令发送成功!');
+            }
+        });
+
+        setTimeout(function() {
+            window.location.reload();
+        }, 3000);
+    }
+
     function boot_at(me) {
         var uuid = $('#instance_uuid').val();
         boot([uuid]);
@@ -1035,6 +1073,14 @@
         change_prepared_by([uuids], service_id);
     }
 
+    function change_vlan_at(me) {
+        $('#change_vlan_modal').modal('hide');
+
+        var uuid = $('#instance_uuid').val();
+        var vlan_id = $('#change_to_vlan').val();
+        change_vlan([uuid], vlan_id);
+    }
+
     function get_selected_uuids() {
         var selected_element = get_selected_element(true);
         var uuids = [];
@@ -1756,7 +1802,10 @@
                                 Gbps
                             {% endif %}
                     </td>
-                    <td>{{ item.ip }}<br><a href="#">vlan: 2010</a></td>
+                    <td>
+                        {{ item.ip }}<br>
+                        <a href="/config#vlan">{{ item.vlan.label }}</a>
+                    </td>
                     <td><span class="unreal_password">*********</span><span class="real_password" style="display: none;">{{ item.password }}</span></td>
                     <td>{{ format_datetime_by_tus(item.create_time) }}</td>
                     <td>
@@ -1857,6 +1906,13 @@
                                        修正 IP
                                     </a>
                                 </li>
+                                <li class="">
+                                    <a href="javascript:;" data-toggle="modal" data-target="#change_vlan_modal"
+                                       onclick="$('#instance_uuid').val($($(this).parent().parent().parent().parent().parent().children()[0]).text());
+                                                $('#change_vlan_instance_desc').text($($(this).parent().parent().parent().parent().parent().children()[2]).find('div a')[0].textContent + '/' + $($(this).parent().parent().parent().parent().parent().children()[2]).find('div p')[0].textContent);">
+                                        变更 VLAN
+                                    </a>
+                                </li>
                                 <li class="divider"></li>
                                 <li class="{% if item.status not in [2] %} disabled {% endif %}">
                                     <a href="javascript:;" data-toggle="modal" data-target="#reset_password_modal"
@@ -2835,4 +2891,25 @@
         </div>
     </div>
 </div>
+
+<div class="modal" id="change_vlan_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">变更 VLAN:</h4>
+            </div>
+            <div class="modal-body">
+                <p>您确定要为该虚拟机变更 VLAN 吗?</p>
+                <h3 style="color: orangered;" id="change_vlan_instance_desc"></h3>
+                <p></p>
+                <select id="change_to_vlan" data-show-subtext="true" name="change_to_vlan" title="可变更的 VLAN" class="selectpicker" data-width="100%">
+                </select>
+            </div>
+            <div class="modal-footer">
+                <button type="button" class="btn btn-sm btn-primary" onclick="change_vlan_at(this);">确定</button>
+                <button type="button" class="btn btn-sm btn-default" data-dismiss="modal">取消</button>
+            </div>
+        </div>
+    </div>
+</div>
 {% endblock content %}