Browse Source

走通从快照导出模板流程

James Iter 8 years ago
parent
commit
51b7124456

+ 1 - 1
README.md

@@ -24,7 +24,7 @@
 
 
 ## 项目描述
 ## 项目描述
 
 
-> 计算机硬件越来越白菜价,性能越来越强劲,企业电子信息化方面的业务越来越多,"互联网+"、大数据的浪潮已经掀起,物联网、AI的趋势正在形成。
+> 计算机硬件越趋便宜,性能更为强劲,企业电子信息化方面的业务加重,"互联网+"、大数据的浪潮已经掀起,物联网、AI的趋势正在形成。
 > 因为上述的一切,虚拟化技术被处于一个软化硬件,揉和硬件与业务系统这么一个核心角色。
 > 因为上述的一切,虚拟化技术被处于一个软化硬件,揉和硬件与业务系统这么一个核心角色。
 > 虚拟化技术虽然已经被普及了很久,但多数企业依然仅仅是把它当做独立的虚拟硬件来使用。在资源的科学分配、高效利用、自动化管理方面,还差些许。
 > 虚拟化技术虽然已经被普及了很久,但多数企业依然仅仅是把它当做独立的虚拟硬件来使用。在资源的科学分配、高效利用、自动化管理方面,还差些许。
 > JimV 是一个,结构清晰简单,易于部署、维护、使用的,低门槛企业私有云管理平台。
 > JimV 是一个,结构清晰简单,易于部署、维护、使用的,低门槛企业私有云管理平台。

+ 10 - 1
api/os_template_image.py

@@ -46,7 +46,8 @@ def r_create():
         Rules.PATH.value,
         Rules.PATH.value,
         Rules.LOGO.value,
         Rules.LOGO.value,
         Rules.OS_TEMPLATE_PROFILE_ID_EXT.value,
         Rules.OS_TEMPLATE_PROFILE_ID_EXT.value,
-        Rules.ACTIVE.value
+        Rules.ACTIVE.value,
+        Rules.OS_TEMPLATE_IMAGE_KIND.value
     ]
     ]
 
 
     os_template_image.label = request.json.get('label')
     os_template_image.label = request.json.get('label')
@@ -55,6 +56,8 @@ def r_create():
     os_template_image.logo = request.json.get('logo')
     os_template_image.logo = request.json.get('logo')
     os_template_image.active = bool(int(request.json.get('active', 1)))
     os_template_image.active = bool(int(request.json.get('active', 1)))
     os_template_image.os_template_profile_id = request.json.get('os_template_profile_id')
     os_template_image.os_template_profile_id = request.json.get('os_template_profile_id')
+    os_template_image.kind = request.json.get('kind')
+    os_template_image.progress = 100
 
 
     try:
     try:
         ji.Check.previewing(args_rules, os_template_image.__dict__)
         ji.Check.previewing(args_rules, os_template_image.__dict__)
@@ -122,6 +125,11 @@ def r_update(_id):
             Rules.OS_TEMPLATE_PROFILE_ID_EXT.value,
             Rules.OS_TEMPLATE_PROFILE_ID_EXT.value,
         )
         )
 
 
+    if 'kind' in request.json:
+        args_rules.append(
+            Rules.OS_TEMPLATE_IMAGE_KIND.value,
+        )
+
     if args_rules.__len__() < 2:
     if args_rules.__len__() < 2:
         ret = dict()
         ret = dict()
         ret['state'] = ji.Common.exchange_state(20000)
         ret['state'] = ji.Common.exchange_state(20000)
@@ -141,6 +149,7 @@ def r_update(_id):
         os_template_image.logo = request.json.get('logo', os_template_image.logo)
         os_template_image.logo = request.json.get('logo', os_template_image.logo)
         os_template_image.os_template_profile_id = \
         os_template_image.os_template_profile_id = \
             request.json.get('os_template_profile_id', os_template_image.os_template_profile_id)
             request.json.get('os_template_profile_id', os_template_image.os_template_profile_id)
+        os_template_image.kind = request.json.get('kind', os_template_image.kind)
 
 
         os_template_image.update()
         os_template_image.update()
         os_template_image.get()
         os_template_image.get()

+ 119 - 1
api/snapshot.py

@@ -6,12 +6,15 @@ from flask import Blueprint
 from flask import request
 from flask import request
 import json
 import json
 import jimit as ji
 import jimit as ji
+import os
 
 
 from api.base import Base
 from api.base import Base
-from models import Guest
+from models import Guest, Config, Disk
 from models import Snapshot, SnapshotDiskMapping
 from models import Snapshot, SnapshotDiskMapping
+from models import OSTemplateImage
 from models import Utils
 from models import Utils
 from models import Rules
 from models import Rules
+from models import OSTemplateImageKind
 
 
 
 
 __author__ = 'James Iter'
 __author__ = 'James Iter'
@@ -239,3 +242,118 @@ def r_revert(snapshot_id):
     except ji.PreviewingError, e:
     except ji.PreviewingError, e:
         return json.loads(e.message)
         return json.loads(e.message)
 
 
+
+@Utils.dumps2response
+def r_get_disks(snapshot_id):
+
+    args_rules = [
+        Rules.SNAPSHOT_ID.value
+    ]
+
+    try:
+        ret = dict()
+        ret['state'] = ji.Common.exchange_state(20000)
+        ret['data'] = list()
+
+        ji.Check.previewing(args_rules, {'snapshot_id': snapshot_id})
+
+        rows, _ = SnapshotDiskMapping.get_by_filter(filter_str=':'.join(['snapshot_id', 'eq', snapshot_id]))
+
+        for row in rows:
+            ret['data'].append(row['disk_uuid'])
+
+        return ret
+
+    except ji.PreviewingError, e:
+        return json.loads(e.message)
+
+
+@Utils.dumps2response
+def r_convert_to_os_template_image(snapshot_id, disk_uuid):
+
+    args_rules = [
+        Rules.SNAPSHOT_ID.value,
+        Rules.DISK_UUID.value,
+        Rules.LABEL.value
+    ]
+
+    try:
+        ret = dict()
+        ret['state'] = ji.Common.exchange_state(20000)
+
+        ji.Check.previewing(args_rules, {'snapshot_id': snapshot_id, 'disk_uuid': disk_uuid,
+                                         'label': request.json.get('label')})
+
+        rows, _ = SnapshotDiskMapping.get_by_filter(filter_str=':'.join(['snapshot_id', 'eq', snapshot_id]))
+
+        disks_uuid = list()
+
+        for row in rows:
+            disks_uuid.append(row['disk_uuid'])
+
+        if disk_uuid not in disks_uuid:
+            ret['state'] = ji.Common.exchange_state(40401)
+            ret['state']['sub']['zh-cn'] = ''.join([ret['state']['sub']['zh-cn'], u': 未在快照: ',
+                                                    snapshot_id, u' 中找到磁盘:', disk_uuid])
+            return ret
+
+        config = Config()
+        config.id = 1
+        config.get()
+
+        snapshot = Snapshot()
+        os_template_image = OSTemplateImage()
+        guest = Guest()
+        disk = Disk()
+
+        snapshot.snapshot_id = snapshot_id
+        snapshot.get_by('snapshot_id')
+
+        guest.uuid = snapshot.guest_uuid
+        guest.get_by('uuid')
+
+        disk.uuid = disk_uuid
+        disk.get_by('uuid')
+
+        os_template_image.id = guest.os_template_image_id
+        os_template_image.get()
+
+        image_name = '_'.join([snapshot.snapshot_id, disk.uuid]) + '.' + disk.format
+
+        os_template_image.id = 0
+        os_template_image.label = request.json.get('label')
+        os_template_image.path = '/'.join([os.path.dirname(os_template_image.path), image_name])
+        os_template_image.kind = OSTemplateImageKind.custom.value
+        os_template_image.progress = 0
+        os_template_image.create_time = ji.Common.tus()
+
+        if os_template_image.exist_by('path'):
+            ret['state'] = ji.Common.exchange_state(40901)
+            ret['state']['sub']['zh-cn'] = ''.join([ret['state']['sub']['zh-cn'], ': ', os_template_image.path])
+            return ret
+
+        os_template_image.create()
+        os_template_image.get_by('path')
+
+        message = {
+            '_object': 'snapshot',
+            'action': 'convert',
+            'uuid': disk.guest_uuid,
+            'snapshot_id': snapshot.snapshot_id,
+            'storage_mode': config.storage_mode,
+            'dfs_volume': config.dfs_volume,
+            'node_id': disk.node_id,
+            'snapshot_path': disk.path,
+            'template_path': os_template_image.path,
+            'os_template_image_id': os_template_image.id,
+            'passback_parameters': {'id': os_template_image.id}
+        }
+
+        Utils.emit_instruction(message=json.dumps(message, ensure_ascii=False))
+
+        return ret
+
+    except ji.PreviewingError, e:
+        return json.loads(e.message)
+
+

+ 3 - 0
api_route_table.py

@@ -150,6 +150,9 @@ add_rule_api(snapshot.blueprints, '/<snapshots_id>', api_func='snapshot.r_get',
 add_rule_api(snapshot.blueprints, '', api_func='snapshot.r_get_by_filter', methods=['GET'])
 add_rule_api(snapshot.blueprints, '', api_func='snapshot.r_get_by_filter', methods=['GET'])
 add_rule_api(snapshot.blueprints, '/_search', api_func='snapshot.r_content_search', methods=['GET'])
 add_rule_api(snapshot.blueprints, '/_search', api_func='snapshot.r_content_search', methods=['GET'])
 add_rule_api(snapshot.blueprint, '/_revert/<snapshot_id>', api_func='snapshot.r_revert', methods=['PUT'])
 add_rule_api(snapshot.blueprint, '/_revert/<snapshot_id>', api_func='snapshot.r_revert', methods=['PUT'])
+add_rule_api(snapshot.blueprint, '/_disks/<snapshot_id>', api_func='snapshot.r_get_disks', methods=['GET'])
+add_rule_api(snapshot.blueprint, '/_convert_to_os_template_image/<snapshot_id>/<disk_uuid>',
+             api_func='snapshot.r_convert_to_os_template_image', methods=['PUT'])
 
 
 # 日志查询
 # 日志查询
 # Guest 性能查询
 # Guest 性能查询

+ 3 - 0
misc/init.sql

@@ -121,6 +121,9 @@ CREATE TABLE IF NOT EXISTS os_template_image(
     active BOOLEAN NOT NULL DEFAULT TRUE,
     active BOOLEAN NOT NULL DEFAULT TRUE,
     logo VARCHAR(255) NOT NULL,
     logo VARCHAR(255) NOT NULL,
     os_template_profile_id BIGINT UNSIGNED NOT NULL,
     os_template_profile_id BIGINT UNSIGNED NOT NULL,
+    kind TINYINT UNSIGNED NOT NULL DEFAULT 0,
+    progress TINYINT UNSIGNED NOT NULL DEFAULT 0,
+    create_time BIGINT UNSIGNED NOT NULL,
     PRIMARY KEY (id))
     PRIMARY KEY (id))
     ENGINE=InnoDB
     ENGINE=InnoDB
     DEFAULT CHARSET=utf8;
     DEFAULT CHARSET=utf8;

+ 4 - 0
misc/v0.3_to_v0.4/update.sql

@@ -30,3 +30,7 @@ CREATE TABLE IF NOT EXISTS snapshot_disk_mapping(
 ALTER TABLE snapshot_disk_mapping ADD UNIQUE INDEX (snapshot_id, disk_uuid);
 ALTER TABLE snapshot_disk_mapping ADD UNIQUE INDEX (snapshot_id, disk_uuid);
 ALTER TABLE snapshot_disk_mapping ADD INDEX (disk_uuid);
 ALTER TABLE snapshot_disk_mapping ADD INDEX (disk_uuid);
 
 
+ALTER TABLE os_template_image ADD COLUMN kind TINYINT UNSIGNED NOT NULL DEFAULT 0;
+ALTER TABLE os_template_image ADD COLUMN progress TINYINT UNSIGNED NOT NULL DEFAULT 0;
+ALTER TABLE os_template_image ADD COLUMN create_time BIGINT UNSIGNED NOT NULL;
+

+ 3 - 2
models/__init__.py

@@ -68,7 +68,8 @@ from status import (
     GuestState,
     GuestState,
     ResponseState,
     ResponseState,
     DiskState,
     DiskState,
-    LogLevel
+    LogLevel,
+    OSTemplateImageKind
 )
 )
 
 
 from guest_xml import (
 from guest_xml import (
@@ -119,6 +120,6 @@ __all__ = [
     'LogLevel', 'ORM', 'User', 'Config', 'Guest', 'Disk', 'GuestXML', 'Log', 'SSHKey', 'SSHKeyGuestMapping',
     'LogLevel', 'ORM', 'User', 'Config', 'Guest', 'Disk', 'GuestXML', 'Log', 'SSHKey', 'SSHKeyGuestMapping',
     'OSTemplateImage', 'OSTemplateProfile', 'OSTemplateInitializeOperateSet', 'OSTemplateInitializeOperate',
     'OSTemplateImage', 'OSTemplateProfile', 'OSTemplateInitializeOperateSet', 'OSTemplateInitializeOperate',
     'EventProcessor', 'ResponseState', 'GuestCPUMemory', 'GuestTraffic', 'GuestDiskIO', 'HostCPUMemory', 'HostTraffic',
     'EventProcessor', 'ResponseState', 'GuestCPUMemory', 'GuestTraffic', 'GuestDiskIO', 'HostCPUMemory', 'HostTraffic',
-    'HostDiskUsageIO', 'Host', 'Snapshot', 'SnapshotDiskMapping'
+    'HostDiskUsageIO', 'Host', 'Snapshot', 'SnapshotDiskMapping', 'OSTemplateImageKind'
 ]
 ]
 
 

+ 26 - 1
models/event_processor.py

@@ -11,7 +11,7 @@ import jimit as ji
 from models import Database as db, Config, GuestCPUMemory, GuestTraffic, GuestDiskIO, SSHKeyGuestMapping
 from models import Database as db, Config, GuestCPUMemory, GuestTraffic, GuestDiskIO, SSHKeyGuestMapping
 from models import Guest
 from models import Guest
 from models import Disk
 from models import Disk
-from models import Snapshot, SnapshotDiskMapping
+from models import Snapshot, SnapshotDiskMapping, OSTemplateImage
 from models import Log
 from models import Log
 from models import Utils
 from models import Utils
 from models import EmitKind
 from models import EmitKind
@@ -36,6 +36,7 @@ class EventProcessor(object):
     disk = Disk()
     disk = Disk()
     snapshot = Snapshot()
     snapshot = Snapshot()
     snapshot_disk_mapping = SnapshotDiskMapping()
     snapshot_disk_mapping = SnapshotDiskMapping()
+    os_template_image = OSTemplateImage()
     config = Config()
     config = Config()
     config.id = 1
     config.id = 1
     guest_cpu_memory = GuestCPUMemory()
     guest_cpu_memory = GuestCPUMemory()
@@ -110,6 +111,18 @@ class EventProcessor(object):
 
 
             cls.guest.progress = cls.message['message']['progress']
             cls.guest.progress = cls.message['message']['progress']
 
 
+        elif cls.guest.status == GuestState.snapshot_converting.value:
+            print cls.message
+            cls.os_template_image.id = cls.message['message']['os_template_image_id']
+            cls.os_template_image.get()
+
+            if cls.message['message']['progress'] <= cls.os_template_image.progress:
+                return
+
+            cls.os_template_image.progress = cls.message['message']['progress']
+            cls.os_template_image.update()
+            return
+
         cls.guest.update()
         cls.guest.update()
 
 
         # 限定特殊情况下更新磁盘所属 Guest,避免迁移、创建时频繁被无意义的更新
         # 限定特殊情况下更新磁盘所属 Guest,避免迁移、创建时频繁被无意义的更新
@@ -295,6 +308,18 @@ class EventProcessor(object):
                 cls.snapshot.progress = 100
                 cls.snapshot.progress = 100
                 cls.snapshot.update()
                 cls.snapshot.update()
 
 
+            if action == 'convert':
+                cls.os_template_image.id = cls.message['message']['passback_parameters']['id']
+                cls.os_template_image.get()
+
+                if state == ResponseState.success.value:
+                    cls.os_template_image.progress = 100
+
+                else:
+                    cls.os_template_image.progress = 255
+
+                cls.os_template_image.update()
+
         else:
         else:
             pass
             pass
 
 

+ 8 - 1
models/os_template_image.py

@@ -2,6 +2,8 @@
 # -*- coding: utf-8 -*-
 # -*- coding: utf-8 -*-
 
 
 
 
+import jimit as ji
+
 from models import FilterFieldType
 from models import FilterFieldType
 from models import ORM
 from models import ORM
 
 
@@ -26,6 +28,9 @@ class OSTemplateImage(ORM):
         self.os_template_profile_id = None
         self.os_template_profile_id = None
         self.path = None
         self.path = None
         self.active = True
         self.active = True
+        self.kind = None
+        self.progress = None
+        self.create_time = ji.Common.tus()
 
 
     @staticmethod
     @staticmethod
     def get_filter_keywords():
     def get_filter_keywords():
@@ -34,7 +39,9 @@ class OSTemplateImage(ORM):
             'label': FilterFieldType.STR.value,
             'label': FilterFieldType.STR.value,
             'path': FilterFieldType.STR.value,
             'path': FilterFieldType.STR.value,
             'os_template_profile_id': FilterFieldType.INT.value,
             'os_template_profile_id': FilterFieldType.INT.value,
-            'active': FilterFieldType.INT.value
+            'active': FilterFieldType.INT.value,
+            'kind': FilterFieldType.INT.value,
+            'progress': FilterFieldType.INT.value
         }
         }
 
 
     @staticmethod
     @staticmethod

+ 1 - 0
models/rules.py

@@ -103,6 +103,7 @@ class Rules(Enum):
     ICON = (basestring, 'icon')
     ICON = (basestring, 'icon')
     LOGO = (basestring, 'logo')
     LOGO = (basestring, 'logo')
 
 
+    OS_TEMPLATE_IMAGE_KIND = (int, 'kind')
     OS_TEMPLATE_PROFILE_ID_EXT = (int, 'os_template_profile_id')
     OS_TEMPLATE_PROFILE_ID_EXT = (int, 'os_template_profile_id')
     OS_TEMPLATE_INITIALIZE_OPERATE_SET_ID_EXT = (int, 'os_template_initialize_operate_set_id')
     OS_TEMPLATE_INITIALIZE_OPERATE_SET_ID_EXT = (int, 'os_template_initialize_operate_set_id')
     OS_TEMPLATE_INITIALIZE_OPERATE_KIND = (int, 'kind')
     OS_TEMPLATE_INITIALIZE_OPERATE_KIND = (int, 'kind')

+ 6 - 0
models/status.py

@@ -47,6 +47,7 @@ class GuestState(IntEnum):
     migrating = 8
     migrating = 8
     update = 9
     update = 9
     creating = 10
     creating = 10
+    snapshot_converting = 11
     dirty = 255
     dirty = 255
 
 
 
 
@@ -91,6 +92,11 @@ class OSTemplateInitializeOperateKind(IntEnum):
     append_file = 2
     append_file = 2
 
 
 
 
+class OSTemplateImageKind(IntEnum):
+    public = 0
+    custom = 1
+
+
 class GuestCollectionPerformanceDataKind(IntEnum):
 class GuestCollectionPerformanceDataKind(IntEnum):
     cpu_memory = 0
     cpu_memory = 0
     traffic = 1
     traffic = 1

+ 1 - 1
templates/guest_create.html

@@ -133,7 +133,7 @@
 
 
     function refresh_os_template_image_id_selectpicker(selectpicker) {
     function refresh_os_template_image_id_selectpicker(selectpicker) {
         $.ajax({
         $.ajax({
-            url : '/api/os_templates_image',
+            url : '/api/os_templates_image?filter=active:eq:1',
             type : 'GET',
             type : 'GET',
             contentType: "application/json; charset=utf-8",
             contentType: "application/json; charset=utf-8",
             dataType: 'json',
             dataType: 'json',

+ 279 - 120
templates/os_templates_image_show.html

@@ -227,6 +227,19 @@
         window.location.href=cur_url;
         window.location.href=cur_url;
     }
     }
 
 
+    function select_this(me) {
+        $(me).parent().parent().children().removeClass('active');
+        $(me).parent().addClass('active');
+
+        $('#public_os_templates_image, #custom_os_templates_image').css('display', 'none');
+
+        if (me.id === 'custom_os_templates_image_label') {
+            $('#custom_os_templates_image').css('display', 'unset');
+        } else {
+            $('#public_os_templates_image').css('display', 'unset');
+        }
+    }
+
     function row_onmouseover(me) {
     function row_onmouseover(me) {
         $(me).find(".edit_label_trigger, .edit_path_trigger").css('display','inline-flex');
         $(me).find(".edit_label_trigger, .edit_path_trigger").css('display','inline-flex');
     }
     }
@@ -490,24 +503,24 @@
 </script>
 </script>
 <div class="panel">
 <div class="panel">
     <div class="panel-body">
     <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>
-                            <a class="btn btn-info" href="javascript:;" data-toggle="modal" data-target="#add_os_template_image_modal" style="border-radius: 0; padding-left: 40px; padding-right: 40px;">添加模板</a>
-                        </div>
+        <ul class="nav nav-tabs mrg25B">
+            <li class="active"><a href="javascript:;" onclick="select_this(this);" id="public_os_templates_image_label">公共镜像</a></li>
+            <li><a href="javascript:;" onclick="select_this(this);" id="custom_os_templates_image_label">自定义镜像</a></li>
+        </ul>
+        <div id="public_os_templates_image" 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>
+                        <a class="btn btn-info" href="javascript:;" data-toggle="modal" data-target="#add_os_template_image_modal" style="border-radius: 0; padding-left: 40px; padding-right: 40px;">添加模板</a>
                     </div>
                     </div>
                 </div>
                 </div>
-                <table id="os_templates_image_list" class="table table-bordered table-hover" cellspacing="0" width="100%" role="grid"
-                       style="width: 100%; margin-bottom: 0; border-bottom-width: 0;">
+            </div>
+            <table id="os_templates_image_list" class="table table-bordered table-hover" cellspacing="0" width="100%" role="grid"
+                   style="width: 100%; margin-bottom: 0; border-bottom-width: 0;">
                 <thead>
                 <thead>
                 <tr role="row">
                 <tr role="row">
                     <th style="display: none;">ID</th>
                     <th style="display: none;">ID</th>
@@ -520,123 +533,269 @@
                 </tr>
                 </tr>
                 </thead>
                 </thead>
                 <tbody>
                 <tbody>
-                {% for item in os_templates_image_ret.data %}
-                <tr role="row" class="odd" onmouseover="row_onmouseover(this);" onmouseout="row_onmouseout(this);">
-                    <td style="display: none;">{{ item.id }}</td>
-                    <td><input title="选中" type="checkbox"></td>
-                    <td>
-                        <div>
-                            <p style="display: inline-block;">{{ item.label }}</p>
-                            <a href="javascript:;" class="edit_label_trigger" data-toggle="modal" data-target="#edit_label_modal" style="display: none; float: right;">
-                                <span class="glyph-icon icon-elusive-pencil" style="width: 20px; height: 20px; margin-left: 10px; border-radius: 0; border: 1px solid rgb(220, 233, 255); background-color: #ffffff;"></span>
-                            </a>
-                        </div>
-                    </td>
-                    <td>{% if item.active == 0 %}
-                        <span style="color: #990000;">未启用</span>
+                {% for item in os_templates_image_ret.data if item.kind == 0 %}
+                    <tr role="row" class="odd" onmouseover="row_onmouseover(this);" onmouseout="row_onmouseout(this);">
+                        <td style="display: none;">{{ item.id }}</td>
+                        <td><input title="选中" type="checkbox"></td>
+                        <td>
+                            <div>
+                                <p style="display: inline-block;">{{ item.label }}</p>
+                                <a href="javascript:;" class="edit_label_trigger" data-toggle="modal" data-target="#edit_label_modal" style="display: none; float: right;">
+                                    <span class="glyph-icon icon-elusive-pencil" style="width: 20px; height: 20px; margin-left: 10px; border-radius: 0; border: 1px solid rgb(220, 233, 255); background-color: #ffffff;"></span>
+                                </a>
+                            </div>
+                        </td>
+                        <td>{% if item.active == 0 %}
+                            <span style="color: #990000;">未启用</span>
                         {% else %}
                         {% else %}
-                        <span style="color: #00BB00;">启用</span>
+                            <span style="color: #00BB00;">启用</span>
                         {% endif %}
                         {% endif %}
-                    </td>
-                    <td>
-                        <div>
-                            <p style="display: inline-block;">{{ item.path }}</p>
-                            <a href="javascript:;" class="edit_path_trigger" data-toggle="modal" data-target="#edit_path_modal" style="display: none; float: right;">
-                                <span class="glyph-icon icon-elusive-pencil" style="width: 20px; height: 20px; margin-left: 10px; border-radius: 0; border: 1px solid rgb(220, 233, 255); background-color: #ffffff;"></span>
-                            </a>
-                        </div>
-                    </td>
-                    <td>
-                        <div>
-                            <a href="javascript:;" data-toggle="modal" data-target="#update_logo_modal">
-                                {% if item.logo == "" %}
-                                    <span class="{{ os_templates_profile_mapping_by_id[item.os_template_profile_id].icon }}"></span>
-                                {% else %}
-                                    <span class="{{ item.logo }}"></span>
-                                {% endif %}
-                            </a>
-                            <p style="display: inline-block;">{{ os_templates_profile_mapping_by_id[item.os_template_profile_id].os_product_name }}</p>
-                        </div>
-                    </td>
-                    <td>
-                        <div class="dropdown inline-block">
-                            <a href="javascript:;" class="dropdown-toggle" data-toggle="dropdown">
-                                更多
-                            </a>
-                            <ul class="dropdown-menu">
-                                <li class="{% if item.active == true %} disabled {% endif %}" style="{% if item.sequence == 0 %} display: none; {% endif %}">
-                                    <a href="javascript:;" onclick="enable_at(this);">
-                                        启用
-                                    </a>
-                                </li>
-                                <li class="{% if item.active != true %} disabled {% endif %}" style="{% if item.sequence == 0 %} display: none; {% endif %}">
-                                    <a href="javascript:;" onclick="disable_at(this);">
-                                        禁用
-                                    </a>
-                                </li>
-                                <li class="divider" style="{% if item.sequence == 0 %} display: none; {% endif %}"></li>
-                                <li>
-                                    <a href="javascript:;" data-toggle="modal" data-target="#update_os_template_image_profile_id_modal"
-                                        onclick="$('#os_template_image_id').val($(this).parent().parent().parent().parent().parent().children()[0].textContent);
+                        </td>
+                        <td>
+                            <div>
+                                <p style="display: inline-block;">{{ item.path }}</p>
+                                <a href="javascript:;" class="edit_path_trigger" data-toggle="modal" data-target="#edit_path_modal" style="display: none; float: right;">
+                                    <span class="glyph-icon icon-elusive-pencil" style="width: 20px; height: 20px; margin-left: 10px; border-radius: 0; border: 1px solid rgb(220, 233, 255); background-color: #ffffff;"></span>
+                                </a>
+                            </div>
+                        </td>
+                        <td>
+                            <div>
+                                <a href="javascript:;" data-toggle="modal" data-target="#update_logo_modal">
+                                    {% if item.logo == "" %}
+                                        <span class="{{ os_templates_profile_mapping_by_id[item.os_template_profile_id].icon }}"></span>
+                                    {% else %}
+                                        <span class="{{ item.logo }}"></span>
+                                    {% endif %}
+                                </a>
+                                <p style="display: inline-block;">{{ os_templates_profile_mapping_by_id[item.os_template_profile_id].os_product_name }}</p>
+                            </div>
+                        </td>
+                        <td>
+                            <div class="dropdown inline-block">
+                                <a href="javascript:;" class="dropdown-toggle" data-toggle="dropdown">
+                                    更多
+                                </a>
+                                <ul class="dropdown-menu">
+                                    <li class="{% if item.active == true %} disabled {% endif %}" style="{% if item.sequence == 0 %} display: none; {% endif %}">
+                                        <a href="javascript:;" onclick="enable_at(this);">
+                                            启用
+                                        </a>
+                                    </li>
+                                    <li class="{% if item.active != true %} disabled {% endif %}" style="{% if item.sequence == 0 %} display: none; {% endif %}">
+                                        <a href="javascript:;" onclick="disable_at(this);">
+                                            禁用
+                                        </a>
+                                    </li>
+                                    <li class="divider" style="{% if item.sequence == 0 %} display: none; {% endif %}"></li>
+                                    <li>
+                                        <a href="javascript:;" data-toggle="modal" data-target="#update_os_template_image_profile_id_modal"
+                                           onclick="$('#os_template_image_id').val($(this).parent().parent().parent().parent().parent().children()[0].textContent);
                                         $('#update_os_template_image_profile_id_instance_desc').text($(this).parent().parent().parent().parent().parent().children()[2].textContent)">
                                         $('#update_os_template_image_profile_id_instance_desc').text($(this).parent().parent().parent().parent().parent().children()[2].textContent)">
-                                        变更发行版本
-                                    </a>
-                                </li>
-                                <li class="divider" style="{% if item.sequence == 0 %} display: none; {% endif %}"></li>
-                                <li>
-                                    <a href="javascript:;" data-toggle="modal" data-target="#delete_modal"
-                                       onclick="$('#os_template_image_id').val($(this).parent().parent().parent().parent().parent().children()[0].textContent);
+                                            变更发行版本
+                                        </a>
+                                    </li>
+                                    <li class="divider" style="{% if item.sequence == 0 %} display: none; {% endif %}"></li>
+                                    <li>
+                                        <a href="javascript:;" data-toggle="modal" data-target="#delete_modal"
+                                           onclick="$('#os_template_image_id').val($(this).parent().parent().parent().parent().parent().children()[0].textContent);
                                        $('#delete_instance_desc').text($(this).parent().parent().parent().parent().parent().children()[2].textContent)">
                                        $('#delete_instance_desc').text($(this).parent().parent().parent().parent().parent().children()[2].textContent)">
-                                        删除
-                                    </a>
-                                </li>
-                            </ul>
+                                            删除
+                                        </a>
+                                    </li>
+                                </ul>
+                            </div>
+                        </td>
+                    </tr>
+                {% 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;">
+                                共有{{ os_templates_image_ret.paging.total }}条,每页显示:
+                                <select id="page_size" name="datatable-row-highlight_length" title="page_size" class="form-control" style="height: 22px; vertical-align: baseline;">
+                                    <option value="10" {% if page_size == 10 %} selected {% endif %}>10</option>
+                                    <option value="20" {% if page_size == 20 %} selected {% endif %}>20</option>
+                                    <option value="50" {% if page_size == 50  %} selected {% endif %}>50</option>
+                                </select>&nbsp;&nbsp;条
+                            </div>
+                            <div class="col-sm-3" style="text-align: left;">
+                                <div class="dataTables_paginate paging_bootstrap" id="datatable-row-highlight_paginate">
+                                    <ul id="pagination" class="pagination">
+                                        <li class="{% if page == 1 %} disabled {% endif %}">
+                                            <a href="{{ resource_path }}?page={{ page - 1 }}&page_size={{ page_size }}{% if keyword %}&keyword={{ keyword }}{% endif %}{% if order_by %}&order_by={{ order_by }}{% endif %}{% if order %}&order={{ order }}{% endif %}">«</a>
+                                        </li>
+                                        {% for item in pages %}
+                                            <li class="{% if item == page %} active {% endif %}">
+                                                <a href="{{ resource_path }}?page={{ item }}&page_size={{ page_size }}{% if keyword %}&keyword={{ keyword }}{% endif %}{% if order_by %}&order_by={{ order_by }}{% endif %}{% if order %}&order={{ order }}{% endif %}">{{ item }}</a>
+                                            </li>
+                                        {% endfor %}
+                                        <li class="{% if page == last_page %} disabled {% endif %}">
+                                            <a href="{{ resource_path }}?page={{ page + 1 }}&page_size={{ page_size }}{% if keyword %}&keyword={{ keyword }}{% endif %}{% if order_by %}&order_by={{ order_by }}{% endif %}{% if order %}&order={{ order }}{% endif %}">»</a>
+                                        </li>
+                                    </ul>
+                                </div>
+                            </div>
                         </div>
                         </div>
-                    </td>
+                    </th>
+                </tr>
+                </tfoot>
+            </table>
+        </div>
+        <div id="custom_os_templates_image" class="dataTables_wrapper form-inline" style="display: none;">
+            <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="os_templates_image_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;">ID</th>
+                    <th><input class="all_selector" title="选取所有" type="checkbox"></th>
+                    <th width="220px;">名称</th>
+                    <th>状态</th>
+                    <th>进度</th>
+                    <th>创建时间</th>
+                    <th>发行版本</th>
+                    <th>操作</th>
                 </tr>
                 </tr>
+                </thead>
+                <tbody>
+                {% for item in os_templates_image_ret.data if item.kind == 1 %}
+                    <tr role="row" class="odd" onmouseover="row_onmouseover(this);" onmouseout="row_onmouseout(this);">
+                        <td style="display: none;">{{ item.id }}</td>
+                        <td><input title="选中" type="checkbox"></td>
+                        <td>
+                            <div>
+                                <p style="display: inline-block;">{{ item.label }}</p>
+                                <a href="javascript:;" class="edit_label_trigger" data-toggle="modal" data-target="#edit_label_modal" style="display: none; float: right;">
+                                    <span class="glyph-icon icon-elusive-pencil" style="width: 20px; height: 20px; margin-left: 10px; border-radius: 0; border: 1px solid rgb(220, 233, 255); background-color: #ffffff;"></span>
+                                </a>
+                            </div>
+                        </td>
+                        <td>{% if item.active == 0 %}
+                            <span style="color: #990000;">未启用</span>
+                        {% else %}
+                            <span style="color: #00BB00;">启用</span>
+                        {% endif %}
+                        </td>
+                        <td>
+                            {% if item.progress == 255 %}
+                                <span style="color: #990000;">创建失败</span>
+                            {% elif item.progress == 254 %}
+                                <span style="color: #ff0000dd;">删除中...</span>
+                            {% elif item.progress == 100 %}
+                                <span style="color: #00BB00;">{{ item.progress }}%</span>
+                            {% else %}
+                                <span style="color: #e5b715;">{{ item.progress }}%</span>
+                            {% endif %}
+                        </td>
+                        <td>{{ format_datetime_by_tus(item.create_time) }}</td>
+                        <td>
+                            <div>
+                                <a href="javascript:;" data-toggle="modal" data-target="#update_logo_modal">
+                                    {% if item.logo == "" %}
+                                        <span class="{{ os_templates_profile_mapping_by_id[item.os_template_profile_id].icon }}"></span>
+                                    {% else %}
+                                        <span class="{{ item.logo }}"></span>
+                                    {% endif %}
+                                </a>
+                                <p style="display: inline-block;">{{ os_templates_profile_mapping_by_id[item.os_template_profile_id].os_product_name }}</p>
+                            </div>
+                        </td>
+                        <td>
+                            <div class="dropdown inline-block">
+                                <a href="javascript:;" class="dropdown-toggle" data-toggle="dropdown">
+                                    更多
+                                </a>
+                                <ul class="dropdown-menu">
+                                    <li class="{% if item.active == true %} disabled {% endif %}" style="{% if item.sequence == 0 %} display: none; {% endif %}">
+                                        <a href="javascript:;" onclick="enable_at(this);">
+                                            启用
+                                        </a>
+                                    </li>
+                                    <li class="{% if item.active != true %} disabled {% endif %}" style="{% if item.sequence == 0 %} display: none; {% endif %}">
+                                        <a href="javascript:;" onclick="disable_at(this);">
+                                            禁用
+                                        </a>
+                                    </li>
+                                    <li class="divider" style="{% if item.sequence == 0 %} display: none; {% endif %}"></li>
+                                    <li>
+                                        <a href="javascript:;" data-toggle="modal" data-target="#update_os_template_image_profile_id_modal"
+                                           onclick="$('#os_template_image_id').val($(this).parent().parent().parent().parent().parent().children()[0].textContent);
+                                        $('#update_os_template_image_profile_id_instance_desc').text($(this).parent().parent().parent().parent().parent().children()[2].textContent)">
+                                            变更发行版本
+                                        </a>
+                                    </li>
+                                    <li class="divider" style="{% if item.sequence == 0 %} display: none; {% endif %}"></li>
+                                    <li>
+                                        <a href="javascript:;" data-toggle="modal" data-target="#delete_modal"
+                                           onclick="$('#os_template_image_id').val($(this).parent().parent().parent().parent().parent().children()[0].textContent);
+                                       $('#delete_instance_desc').text($(this).parent().parent().parent().parent().parent().children()[2].textContent)">
+                                            删除
+                                        </a>
+                                    </li>
+                                </ul>
+                            </div>
+                        </td>
+                    </tr>
                 {% endfor %}
                 {% endfor %}
                 </tbody>
                 </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;">
-                                    共有{{ os_templates_image_ret.paging.total }}条,每页显示:
-                                    <select id="page_size" name="datatable-row-highlight_length" title="page_size" class="form-control" style="height: 22px; vertical-align: baseline;">
-                                        <option value="10" {% if page_size == 10 %} selected {% endif %}>10</option>
-                                        <option value="20" {% if page_size == 20 %} selected {% endif %}>20</option>
-                                        <option value="50" {% if page_size == 50  %} selected {% endif %}>50</option>
-                                    </select>&nbsp;&nbsp;条
-                                </div>
-                                <div class="col-sm-3" style="text-align: left;">
-                                    <div class="dataTables_paginate paging_bootstrap" id="datatable-row-highlight_paginate">
-                                        <ul id="pagination" class="pagination">
-                                            <li class="{% if page == 1 %} disabled {% endif %}">
-                                                <a href="{{ resource_path }}?page={{ page - 1 }}&page_size={{ page_size }}{% if keyword %}&keyword={{ keyword }}{% endif %}{% if order_by %}&order_by={{ order_by }}{% endif %}{% if order %}&order={{ order }}{% endif %}">«</a>
-                                            </li>
-                                            {% for item in pages %}
+            </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;">
+                                共有{{ os_templates_image_ret.paging.total }}条,每页显示:
+                                <select id="page_size" name="datatable-row-highlight_length" title="page_size" class="form-control" style="height: 22px; vertical-align: baseline;">
+                                    <option value="10" {% if page_size == 10 %} selected {% endif %}>10</option>
+                                    <option value="20" {% if page_size == 20 %} selected {% endif %}>20</option>
+                                    <option value="50" {% if page_size == 50  %} selected {% endif %}>50</option>
+                                </select>&nbsp;&nbsp;条
+                            </div>
+                            <div class="col-sm-3" style="text-align: left;">
+                                <div class="dataTables_paginate paging_bootstrap" id="datatable-row-highlight_paginate">
+                                    <ul id="pagination" class="pagination">
+                                        <li class="{% if page == 1 %} disabled {% endif %}">
+                                            <a href="{{ resource_path }}?page={{ page - 1 }}&page_size={{ page_size }}{% if keyword %}&keyword={{ keyword }}{% endif %}{% if order_by %}&order_by={{ order_by }}{% endif %}{% if order %}&order={{ order }}{% endif %}">«</a>
+                                        </li>
+                                        {% for item in pages %}
                                             <li class="{% if item == page %} active {% endif %}">
                                             <li class="{% if item == page %} active {% endif %}">
                                                 <a href="{{ resource_path }}?page={{ item }}&page_size={{ page_size }}{% if keyword %}&keyword={{ keyword }}{% endif %}{% if order_by %}&order_by={{ order_by }}{% endif %}{% if order %}&order={{ order }}{% endif %}">{{ item }}</a>
                                                 <a href="{{ resource_path }}?page={{ item }}&page_size={{ page_size }}{% if keyword %}&keyword={{ keyword }}{% endif %}{% if order_by %}&order_by={{ order_by }}{% endif %}{% if order %}&order={{ order }}{% endif %}">{{ item }}</a>
                                             </li>
                                             </li>
-                                            {% endfor %}
-                                            <li class="{% if page == last_page %} disabled {% endif %}">
-                                                <a href="{{ resource_path }}?page={{ page + 1 }}&page_size={{ page_size }}{% if keyword %}&keyword={{ keyword }}{% endif %}{% if order_by %}&order_by={{ order_by }}{% endif %}{% if order %}&order={{ order }}{% endif %}">»</a>
-                                            </li>
-                                        </ul>
-                                    </div>
+                                        {% endfor %}
+                                        <li class="{% if page == last_page %} disabled {% endif %}">
+                                            <a href="{{ resource_path }}?page={{ page + 1 }}&page_size={{ page_size }}{% if keyword %}&keyword={{ keyword }}{% endif %}{% if order_by %}&order_by={{ order_by }}{% endif %}{% if order %}&order={{ order }}{% endif %}">»</a>
+                                        </li>
+                                    </ul>
                                 </div>
                                 </div>
                             </div>
                             </div>
-                        </th>
-                    </tr>
-                    </tfoot>
-                </table>
-            </div>
+                        </div>
+                    </th>
+                </tr>
+                </tfoot>
+            </table>
         </div>
         </div>
     </div>
     </div>
 </div>
 </div>

+ 138 - 0
templates/snapshots_show.html

@@ -259,6 +259,89 @@
         highlight_selected_element();
         highlight_selected_element();
         shortcut_bar_enable();
         shortcut_bar_enable();
     }
     }
+    
+    function make_list_of_disks(me) {
+        var snapshot_id = $('#snapshot_id').val();
+        var disks_uuid = [];
+
+        $.ajax({
+            url : '/api/snapshot/_disks/' + snapshot_id,
+            type : 'GET',
+            contentType: "application/json; charset=utf-8",
+            dataType: 'json',
+            async: false,
+            error : function() {
+                alter_danger('获取实例磁盘列表失败!');
+            },
+            success : function(data, textStatus, xhr) {
+                disks_uuid = data.data;
+            }
+        });
+
+        $.ajax({
+            url : '/api/disks?order_by=sequence&filter=uuid:in:' + disks_uuid.join(','),
+            type : 'GET',
+            contentType: "application/json; charset=utf-8",
+            dataType: 'json',
+            async: false,
+            error : function() {
+                alter_danger('获取实例磁盘列表失败!');
+            },
+            success : function(data, textStatus, xhr) {
+                var disks_list_tbody = $("#disks_list").find("tbody");
+                var disk_kind = '系统盘';
+                disks_list_tbody.empty();
+
+                $.each(data.data, function(k, v) {
+                    if (v['sequence'] === 0) {
+                        disk_kind = '系统盘';
+                    } else {
+                        disk_kind = '数据盘';
+                    }
+                    disks_list_tbody.append(
+                        '<tr role="row" class="odd" onmouseover="row_onmouseover(this);" onmouseout="row_onmouseout(this);">' +
+                        '<td>' +
+                            '<div>' + v['uuid'] + '</div>' +
+                            '<div>' + '<p style="display: inline-block;">' + v['remark'] + '</p></div>' +
+                        '</td>' +
+                        '<td>' + v['device'] + '</td>' +
+                        '<td>' + v['size'] + ' GB</td>' +
+                        '<td>' + disk_kind + '</td>' +
+                        '<td><a href="javascript:;" style="color: #0066cc" data-toggle="modal" data-target="#create_os_template_image_from_disk_snapshot_modal" ' +
+                        'onclick="$(\'#os_template_image_label\').val($(this).parent().prev().prev().prev().prev().children()[1].textContent);$(\'#disk_uuid\').val(\'' + v['uuid'] + '\');' +
+                        '$(\'#create_os_template_image_from_disk_snapshot_desc\').text($(this).parent().prev().prev().prev().text());$(\'#snapshot_convert_to_os_template_image_modal\').modal(\'hide\');">创建</a></td>' +
+                        '</tr>'
+                        );
+                });
+            }
+        });
+    }
+    
+    function create_os_template_image_from_disk_snapshot() {
+        var snapshot_id = $('#snapshot_id').val();
+        var disk_uuid = $('#disk_uuid').val();
+        var label = $('#os_template_image_label').val();
+
+        $('#create_os_template_image_from_disk_snapshot_modal').modal('hide');
+
+        $.ajax({
+            url : '/api/snapshot/_convert_to_os_template_image/' + snapshot_id + '/' + disk_uuid,
+            type : 'PUT',
+            contentType: "application/json; charset=utf-8",
+            data : JSON.stringify({
+                label: label
+            }),
+            error : function() {
+                alter_danger('从磁盘快照创建自定义镜像失败!');
+            },
+            success : function() {
+                alter_success('从磁盘快照创建自定义镜像成功!');
+                setTimeout(function() {
+                    window.location.href="/os_templates_image";
+                }, 1000);
+            }
+        });
+    }
 </script>
 </script>
 <div class="panel">
 <div class="panel">
     <div class="panel-body">
     <div class="panel-body">
@@ -332,6 +415,10 @@
                             <a href="javascript:;" class="{% if item.progress not in [100] %} a-disabled {% endif %}" style="color: #0066cc" data-toggle="modal" data-target="#snapshot_revert_modal" onclick="$('#snapshot_id').val($(this).parent().parent().parent().children()[0].textContent);
                             <a href="javascript:;" class="{% if item.progress not in [100] %} a-disabled {% endif %}" style="color: #0066cc" data-toggle="modal" data-target="#snapshot_revert_modal" onclick="$('#snapshot_id').val($(this).parent().parent().parent().children()[0].textContent);
                                                                                                                                             $('#revert_snapshot_instance_desc').text($($(this).parent().parent().parent().children()[2]).find('div a')[0].textContent + '/' + $($(this).parent().parent().parent().children()[2]).find('div p')[0].textContent)">恢复</a>
                                                                                                                                             $('#revert_snapshot_instance_desc').text($($(this).parent().parent().parent().children()[2]).find('div a')[0].textContent + '/' + $($(this).parent().parent().parent().children()[2]).find('div p')[0].textContent)">恢复</a>
                             <span> | </span>
                             <span> | </span>
+                            <a href="javascript:;" data-guest_uuid="{{ item.guest_uuid }}" class="{% if item.progress not in [100] %} a-disabled {% endif %}" style="color: #0066cc" data-toggle="modal" data-target="#snapshot_convert_to_os_template_image_modal" onclick="$('#snapshot_id').val($(this).parent().parent().parent().children()[0].textContent);
+                                                                                                                                            make_list_of_disks(this);
+                                                                                                                                            $('#convert_snapshot_instance_desc').text($($(this).parent().parent().parent().children()[2]).find('div a')[0].textContent + '/' + $($(this).parent().parent().parent().children()[2]).find('div p')[0].textContent)">创建镜像</a>
+                            <span> | </span>
                             <a href="javascript:;" class="{% if item.progress not in [100, 255] %} a-disabled {% endif %}" style="color: #0066cc" data-toggle="modal" data-target="#snapshot_delete_modal" onclick="$('#snapshot_id').val($(this).parent().parent().parent().children()[0].textContent);
                             <a href="javascript:;" class="{% if item.progress not in [100, 255] %} a-disabled {% endif %}" style="color: #0066cc" data-toggle="modal" data-target="#snapshot_delete_modal" onclick="$('#snapshot_id').val($(this).parent().parent().parent().children()[0].textContent);
                                                                                                                                             $('#delete_snapshot_instance_desc').text($($(this).parent().parent().parent().children()[2]).find('div a')[0].textContent + '/' + $($(this).parent().parent().parent().children()[2]).find('div p')[0].textContent)">删除</a>
                                                                                                                                             $('#delete_snapshot_instance_desc').text($($(this).parent().parent().parent().children()[2]).find('div a')[0].textContent + '/' + $($(this).parent().parent().parent().children()[2]).find('div p')[0].textContent)">删除</a>
                         </div>
                         </div>
@@ -385,6 +472,7 @@
 </div>
 </div>
 
 
 <input id="snapshot_id" title="快照 ID" class="form-control" name="snapshot_id" hidden>
 <input id="snapshot_id" title="快照 ID" class="form-control" name="snapshot_id" hidden>
+<input id="disk_uuid" title="磁盘 UUID" class="form-control" name="disk_uuid" hidden>
 
 
 <div class="modal" id="edit_label_modal" tabindex="-1" role="dialog" style="margin-top: 100px;">
 <div class="modal" id="edit_label_modal" tabindex="-1" role="dialog" style="margin-top: 100px;">
     <div class="modal-dialog modal-sm">
     <div class="modal-dialog modal-sm">
@@ -421,6 +509,56 @@
     </div>
     </div>
 </div>
 </div>
 
 
+<div class="modal" id="snapshot_convert_to_os_template_image_modal" tabindex="-1" role="dialog" style="margin-top: 100px;">
+    <div class="modal-dialog modal-lg">
+        <div class="modal-content">
+            <div class="modal-header">
+                <h4 class="modal-title">请选择自定义镜像的磁盘:</h4>
+            </div>
+            <div class="modal-body">
+                <h3 style="color: orangered;" id="convert_snapshot_instance_desc"></h3>
+                <p></p>
+                <table id="disks_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 width="280px;">UUID</th>
+                        <th>设备路径</th>
+                        <th>大小</th>
+                        <th>磁盘性别</th>
+                        <th>操作</th>
+                    </tr>
+                    </thead>
+                    <tbody>
+                    </tbody>
+                </table>
+            </div>
+            <div class="modal-footer">
+                <button type="button" class="btn btn-sm btn-default" data-dismiss="modal">取消</button>
+            </div>
+        </div>
+    </div>
+</div>
+
+<div class="modal" id="create_os_template_image_from_disk_snapshot_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">
+                <h3 style="color: orangered;" id="create_os_template_image_from_disk_snapshot_desc"></h3>
+                <p></p>
+                <input id="os_template_image_label" title="自定义镜像名称" class="form-control" name="os_template_image_label">
+            </div>
+            <div class="modal-footer">
+                <button type="button" class="btn btn-sm btn-primary" onclick="create_os_template_image_from_disk_snapshot();">确定</button>
+                <button type="button" class="btn btn-sm btn-default" data-dismiss="modal">取消</button>
+            </div>
+        </div>
+    </div>
+</div>
+
 <div class="modal" id="snapshot_delete_modal" tabindex="-1" role="dialog" style="margin-top: 100px;">
 <div class="modal" id="snapshot_delete_modal" tabindex="-1" role="dialog" style="margin-top: 100px;">
     <div class="modal-dialog">
     <div class="modal-dialog">
         <div class="modal-content">
         <div class="modal-content">

+ 3 - 1
views/os_template_image.py

@@ -107,6 +107,7 @@ def create():
         logo = request.form.get('logo')
         logo = request.form.get('logo')
         active = request.form.get('active', 1)
         active = request.form.get('active', 1)
         os_template_profile_id = request.form.get('os_template_profile_id')
         os_template_profile_id = request.form.get('os_template_profile_id')
+        kind = request.form.get('kind', 0)
 
 
         payload = {
         payload = {
             "label": label,
             "label": label,
@@ -114,7 +115,8 @@ def create():
             "path": path,
             "path": path,
             "logo": logo,
             "logo": logo,
             "active": active,
             "active": active,
-            "os_template_profile_id": int(os_template_profile_id)
+            "os_template_profile_id": int(os_template_profile_id),
+            "kind": int(kind)
         }
         }
 
 
         url = host_url + '/api/os_template_image'
         url = host_url + '/api/os_template_image'